meme-clj v2.0.0 | 2026-04-02
Methodology: 50+ hypotheses tested via live nREPL + 5 deep-dive code analysis agents (tokenizer, parser, printer/formatter, rewrite/lang, CLI/runtime). Non-overlapping with Red Team 2026-04-01 and Magenta Team 2026-04-02.
Test baseline: 918 JVM + 771 CLJS + 11 meme example tests — all passing.
| Severity | Count | Examples |
|---|---|---|
| HIGH | 6 | TRS #() corruption, double syntax-quote, #::{} empty ns, metadata CCE, EDN code exec, TRS :: divergence |
| MEDIUM | 15 | Symbol/keyword splits, BOM, rewrite non-termination, surrogate chars, bare :, register! override, restore-bare-percent |
| LOW | 14 | Comments dropped, null bytes, Unicode control chars, number greedy scan, metadata flat rendering |
| INFO | 5 | clj->meme sugar loss, emit fallback, error message quality |
| Total | 40 |
#() semantic corruption#(+(% 1)) → TRS emits #((+ % 1)) — extra parens make it call the return value as a function. Runtime ClassCastException. Classic/rewrite correctly emit #(+ % 1).
Location: src/meme/trs.cljc, src/meme/lang/meme_trs.cljc
x `` wrong expansionClojure eval of x : `'redteam2/x` (namespace-qualified, one level of quoting). Meme eval of x: 'x (unqualified, wrong structure).
The inner backtick doesn't produce a proper nested expansion. Breaks macro-writing-macros that rely on ~~ ``x patterns.
Location: src/meme/parse/expander.cljc
#::{} produces malformed keywordsTokenizer's read-symbol-str consumes : as a symbol char after #:. Result: ns-name="", output {:/a 1} instead of current-namespace-qualified keywords. Clojure #::{} correctly resolves to current namespace.
Location: src/meme/scan/tokenizer.cljc:338-342, src/meme/parse/reader.cljc:517-520
^:foo 42 — raw ClassCastExceptionMetadata type check passes (keyword is valid metadata), but vary-meta on a number throws unhandled ClassCastException: Long cannot be cast to IObj. Missing metadatable? guard on the target before calling vary-meta.
Location: src/meme/parse/reader.cljc:431
--lang file.edn calls requiring-resolve on symbols in the EDN (loads namespaces from classpath) and slurp+eval on :run string paths. No sandboxing, no path validation, no allowlist.
Location: src/meme/lang.cljc:47-97, src/meme/runtime/cli.clj:77-83
::keyword semantics differ from classicTRS preserves ::foo verbatim in output; classic expands for deferred resolution. When Clojure output is read in a different namespace, ::foo resolves to the wrong namespace. Not covered by lang agreement tests.
Location: src/meme/trs.cljc, test/meme/trs_test.cljc:98-120
foo/bar/baz parsed as two formsMeme splits at first /, producing foo/bar + /baz. Clojure reads as one symbol foo/bar/baz. Silent mis-parse.
:foo/bar/baz keyword splitSame pattern as M1 but for keywords. Silently splits at the second slash.
foo/ trailing slash acceptedMeme accepts and emits foo/. Clojure rejects: "Invalid token: foo/". Produces invalid Clojure output.
foo/1bar digit-starting name acceptedMeme accepts foo/1bar as a symbol. Clojure rejects — name part after / cannot start with a digit.
#'foo(bar) → (var (foo bar)) → invalid ClojureMeme parses #'foo(bar) as var-quote of a call → (var (foo bar)). Clojure's var only accepts symbols, not lists. Output: Syntax error compiling var.
#'^:foo bar silently drops metadataMeme: #'^:foo bar → (var bar) — metadata ^:foo is lost. Clojure preserves metadata on the var form.
Input file: UTF-8 BOM + f(x y). Output file hex: 28 EF BB BF 66 20 78 20 79 29 → (ﻯf x y). The BOM bytes appear inside the opening paren, producing invalid Clojure.
Rule ?x → [?x ?x] causes infinite loop / timeout. The 100-iteration cycle detection is bypassed because each iteration produces a different (larger) form. Needs a size budget or output-size cap.
anon-fn-depth volatile not decremented on error pathsanon-fn-depth incremented at line 541, decremented at line 557. Any error between corrupts the counter. Compare: sq-depth correctly uses try/finally.
Location: src/meme/parse/reader.cljc:537-567
\uD800-\uDFFF acceptedresolve-char lacks the surrogate range check that already exists in parse-unicode-escape for strings. Meme accepts, Clojure throws UnsupportedOperationException.
Location: src/meme/parse/resolve.cljc:88-96
: lone colon accepted as keywordProduces (keyword ""). Clojure rejects: "Invalid token: :".
#:{:a 1} empty namespace acceptedClojure: "Namespaced map must specify a namespace". Meme: silently produces {:/a 1}.
s->m-rules guard drops non-symbol/non-keyword headsLists with nil, true, false, or number heads are not tagged as m-call. clj->meme-text emits (nil 1 2) S-expression syntax instead of valid meme nil(1 2).
Location: src/meme/rewrite/rules.cljc:12-19
register! allows silently overriding built-in langs(register! :meme-classic {...}) hijacks the default lang with no warning. Process-global state mutation. No name-collision check against built-ins.
Location: src/meme/lang.cljc:175-189
restore-bare-percent doesn't recurse into maps/setsnormalize-bare-percent in forms.cljc recurses into maps and sets. restore-bare-percent in printer.cljc does not. Result: #(#{%}) roundtrips as #(#{%1}), violating syntactic transparency.
Location: src/meme/emit/printer.cljc:71-79
| ID | Finding |
|---|---|
| L1 | TRS unexpanded syntax-quote — emits `(f ~x) instead of expanded seq/concat/list |
| L2 | ~x outside syntax-quote: classic errors, rewrite/trs accept — inconsistency |
| L3 | Comment silently dropped by formatter — def(x ;; important\n 42) → def(x 42) |
| L4 | Comment-only file produces empty output in to-clj |
| L5 | Null byte consumed as whitespace — f(x\0y) → (f x y), silent data alteration |
| L6 | Zero-width space (U+200B) absorbed into symbol names — invisible character attack vector |
| L7 | RTL override (U+202E) accepted in symbols — display-level attack |
| L8 | match-pattern returns nil for splice variable ?&args — silently fails to match |
| L9 | read-number greedy: 1N.5 is one token (error), Clojure reads two forms. Same for 1-2, 1+2 |
| L10 | ##foo accepts any symbol after ## — confusing "Invalid number" error vs Clojure's "Invalid token" |
| L11 | \r-only line endings: all tokens report line 1 — sadvance! only increments on \newline |
| L12 | Metadata maps always rendered flat — ^{:doc "...long..."} committed to string before layout engine |
| L13 | flat/format-forms drops trailing file comments — canon handles them, flat does not |
| L14 | match-seq with multiple splice variables is O(n^k) — k splice vars on n elements |
| ID | Finding |
|---|---|
| I1 | clj->meme loses reader sugar (', @, #()) — Clojure reader limitation, not meme bug |
| I2 | Rewrite engine crash on non-seq input — raw IllegalArgumentException instead of structured error |
| I3 | emit fallback to pr-str for AST node records — no error, produces garbage output |
| I4 | format-error drops info for non-ExceptionInfo exceptions — user code eval errors show no source context |
| I5 | REPL default resolve-keyword uses clojure.core/read-string — latent escalation point |
#(inc(%)), ::foo to lang agreement tests, fix TRS #() wrappingmetadatable? check before vary-meta in reader.cljc:431anon-fn-depth in try/finally like sq-depthparse-unicode-escape to resolve-char#:: in tokenizer dispatch (don't let read-symbol-str consume :)map? and set? cases to restore-bare-percent| Defense | Hypotheses Refuted |
|---|---|
| Number validation (1.2.3, 0xGG, 1/0, 1.5N, 1/2/3) | 5 |
| Formatter idempotency at all widths including width=1 | 2 |
| Error messages with accurate source locations | 5 |
| CLI: missing files, unknown commands, wrong extensions | 5 |
| Eval semantics for all tested programs | 5 |
| 200+ level nesting without stack overflow | 2 |
| Duplicate map key detection | 1 |
| Odd-element-count map detection | 1 |
| Reader conditional validation (wrong arity, odd elements) | 2 |
| Stage contract validation | 5 |
| Regex validation for invalid patterns | 1 |
| Character literal roundtrip (named, unicode, octal) | 5 |
| Lang agreement on standard forms | 4 |
| All 918 JVM + 771 CLJS tests pass | — |
50+ hypotheses tested via REPL. 5 deep-dive code analysis agents. 40 findings confirmed across 4 severity levels. 70+ hypotheses refuted.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |