ReleasesPrism changelog and release history
Prism release history. All releases are available on GitHub Releases.
#v1.1.7 current
Prism 1.1.7 hardens transpilation, compiler invocation, output publication, cache correctness, and language-feature parsing.
The procedural regression suite grows by 838 cells to 46,334 platform-inclusive cells, faster test suit.
#fixes
--prism-emitnow publishes output atomically.--prism-emitrejects an input file or hard link as its destination, preserves an old destination on failure, and rejects one explicit destination for multiple sources.- Stdout emission uses the caller's descriptor on POSIX and reports write, flush, and close failures on every output path.
- Generated
#linefilenames now escape quotes, controls, and trigraph-forming question marks. - Raw stripping keeps directives on their own line.
- Stray braces now diagnose safely instead of reaching emission in an invalid parser state.
- Malformed file-scope
orelsenow diagnoses safely. - Illegal bracket
orelsedimensions now diagnose safely. - Declaration conditions now diagnose safely.
- Statement-expression
gotos now diagnose safely. - Parser arena shrinking is safe for library callers.
- First-context allocation failure is safe for library callers.
- Result-error allocation failure is safe for library callers.
- Source-directive recovery is safe for library callers.
prism_reset()is safe for library callers after parser errors.prism_thread_cleanup()is safe for library callers.prism_free(NULL)is safe for library callers.- File APIs reject directories and non-C inputs before invoking the compiler.
- File APIs reject malformed feature-array counts safely.
- Oversized preprocessor argv construction is rejected before unsafe allocation.
- File APIs reject embedded-NUL input before unsafe processing.
- File APIs reject UTF-16-looking
.iinput before unsafe processing. - Compiler processes receive a fresh environment snapshot for every spawn.
- Windows preserves Unicode environment values and rejects malformed UTF-8 conversion instead of silently changing it.
- Stdin preprocessing normalizes
-x noneto C. - Re-emitted API definitions preserve preprocessor order and last-definition-wins behavior.
- Quoted custom compiler commands preserve embedded spaces and platform quote rules without shell parsing.
- Response files now expand lazily, respect
checkand--boundaries, work as option operands, and support nesting and pipes. - Response files support UTF-8 and Windows UTF-16 BOMs and handle malformed reads safely.
- MSVC
/c,/Fe,/Fo,/FI,/D,/E,/EP,/P, stdin, and slash-option operands are modeled correctly. - GNU
-f...flags no longer collide with MSVC dash spellings. - Non-flattened direct system headers retain the original preprocessor stream when re-emission could change macro, pragma, include, or reinclusion ordering.
- The preprocessor cache validates payload checksums, full file identity, working directory, Unicode environment inputs, and volatile header macros.
- The cache bypasses dependency side effects, marker suppression, response files, specs files, volatile flags, and custom compiler wrappers.
- Cache publishing uses unique temporary files, saturated limits, and safe unavailable-admin paths.
- Braceless
defernow captures an entire control statement, includingif/else, loop,switch, anddo/whilebodies. - Braced defer bodies validate embedded
breakandcontinue, preventing an invalid deferred escape from reaching emitted C. - Bounds checking now rejects a conditional base derived from tracked arrays instead of using one branch's extent for another.
- Auto-static no longer mistakes shadowable
trueorfalseidentifiers for constant initializers. - Deferred-capture discovery excludes actual
orelseoperators. - Deferred-capture discovery excludes
rawdeclaration prefixes and raw blocks. defer,orelse, andrawnow recognize literal backslash-LF and backslash-CRLF splices before soft-keyword matching.- C trigraph translation now runs before splice handling, so a trigraph-derived backslash such as
??/followed by LF/CRLF also joins a Prism keyword correctly. - Generated
#linedirectives now retain the physical source line after a splice instead of mapping the next token to the preceding line. - A function that returns a function pointer now synthesizes its defer return-type typedef with the returned type's parameter list.
int (*f(void))(int)producedtypedef int (*T);, so the return temporary was anint *; GCC 16 and Clang 22 reject the resulting initializer andreturnoutright, and older compilers accepted them with incompatible-pointer warnings. Array-pointer returns were already correct;(params)was the one declarator suffix being dropped. - A
returnvalue expression with no terminating;now diagnoses instead of copying the rest of the translation unit into the return temporary's initializer and emitting unbalanced C. - Defer bodies, and the synthetic blocks spliced for
return,break,continue, andgoto, no longer land on a surviving preprocessor directive's line. A conforming preprocessor discards trailing tokens on a directive line, so with-fno-line-directivesa#pragmaimmediately before a scope exit silently deleted the cleanup, and in thereturncase thereturnstatement with it. Line directives happened to force the line break, which is why the failure only appeared with them disabled. - Macro-hygiene wrap parentheses around a declaration initializer no longer hide an empty
orelseaction.int x = (f() orelse);was accepted and lowered tox = x ? x :;, while the unwrappedint x = f() orelse;was already rejected. - An unterminated GNU
__label__declaration no longer duplicates every token from the declaration to end of input. The emitter wrote the tokens before discovering there was no terminator, then reported the statement as unhandled, and the caller emitted them a second time. --prism-emit=<file>publishes with the destination's previous mode when replacing one, and with0666 & ~umaskwhen creating one. Atomic publication is viamkstemp, which forces0600.- Trigraph translation no longer runs twice.
cc -E(and a.iinput) has already completed translation phases 1 to 3 with the backend's trigraph policy, which is off by default for GCC, Clang and MSVC and removed outright in C23. Re-applying phase 1 to that stream rewrote literals the compiler had deliberately kept:const char *s = "what??!";compiled towhat|under Prism andwhat??!under the very compiler Prism was driving. Prism now matchesccexactly under-std=c99,-std=gnu99,-std=c11,-std=c2x,-trigraphsand the default. Raw source reaching the library API is unaffected: Prism is the only front end that text sees, so it still owns phases 1 and 2, and the spliced-keyword forms keep working. - An
orelsefallback value with no terminating;now diagnoses instead of copying the rest of the enclosing block intoLHS = ( ... ). Same defect and same shape as fix 46, on the bare-assignment path. - A
deferdirectly after a GNU__label__declaration is no longer rejected as expression context. Phase 1D handed on the__label__token itself as the previous token, so the following statement looked like a continuation of an expression; valid GNU C plus valid Prism was refused, and only when nothing else stood between the two. - A
__label__declaration as a bracelessdeferbody now diagnoses. It carries no type tag, so the existing braceless-declaration check never saw it, and the body scan emitted a stray}. - A bare
orelsewhose Phase 1 recipe has no recorded assignment=no longer dereferences a null token. Found by UBSan in the fuzz target. - Extension elimination is enforced at emission instead of assumed. A token Phase 1 classified as the keyword, whose feature is enabled and which is not inside a
raw { ... }block, must be consumed by a lowering path; reaching raw emission now diagnoses. Three retired-regression cells were locking in the opposite:defer { int x = 5 }with no;had the rest of the file swallowed into the deferred body and pasted back, so the output declaredtask_btwice and contained a livedefer; and an attribute betweenifand its(leftp = get() orelse 0;in the generated C. All three inputs are rejected by a C compiler on their own merits, and all three previously produced output no compiler could use. The check costs one predictable branch on an already-loaded tag word. - Library calls no longer inherit the line-directive spelling from an earlier call.
use_linemarkersselects GCC's# N "f"linemarker over C99's#line N "f"; only the CLI ever assigned it, and no library entry point reset it, so once anything in a thread set it every laterprism_transpile_sourcesilently emitted the other spelling. Found by sharding the suite: a shard that ran the internal-platform recipe next to the feature matrix lost#linefrom all 64 of its line-directive cells. - Every
posix_spawnsite retries the transient failures. Four of the five had no retry at all -- including the preprocessor spawn -- and the one that did coveredEAGAINandETXTBSYbut notENOMEM, which is what macOS returns when the machine is briefly short of memory. --prism-emitto a destination that is not a regular file (/dev/null, a character device, a fifo) now streams the finished output instead of failing.renamecannot publish onto such a destination, and the fallback also covers a cross-deviceEXDEVwhen the sibling temporary had to fall back to$TMPDIR. The destination is still opened only after translation and verification succeed.
- The Windows build links again. Fix 58's retry helper called
nanosleep, which has no MSVC import. The call had existed for a long time insidespawn_command, whose every caller is POSIX-only, so the linker discarded the function whole and the symbol never had to resolve; giving the retry four more call sites that are live on Windows turned it intounresolved external symbol nanosleep. The sleep is nowSleepon Win32 andnanosleepelsewhere, and the helper's parameters match windows.c'sposix_spawnpshim rather than the POSIX prototype, since a non-const argument converts to POSIX's const one but not the reverse. windows.cincludes<basetsd.h>in lowercase. The MSVC SDK is case-insensitive so both spellings work there, but mingw-w64 ships the lowercase name and a case-sensitive host could not resolve<BaseTsd.h>. This is what had made the_WIN32source path uncheckable anywhere except Windows CI, which is why fix 60 reached a push.
- The suite's two infrastructure counters are declared outside the POSIX guard.
infra_retriesandslow_clock_skipsare read by the summary inmain(), which is compiled on every platform, but sat beside the POSIX-only code that increments them; MSVC reported fourundeclared identifiererrors. Same shape as fix 60 -- code added under one platform's assumptions and never built under the other. - Bounds checking covers
int a[static N]. C11 6.7.6.3p7 promises the argument points at at least N elements, and that promise is the only extent an array parameter has -- it decays to a pointer, so the usualsizeof(a)/sizeof(a[0])would measure the pointer against the element. The bound is the literal N.[static const N]and[const static N]are both recognised; an array parameter without the promise, and a plain pointer parameter, stay unchecked as before. - Bounds checking covers pointer-to-array dereference.
int (*p)[4]carries its extent in the type, and*pandp[0]denote the same object, so(*p)[i],p[0][i]and(*(p))[i]all bound againstsizeof(p[0])/sizeof(p[0][0]). The pointer hop itself stays unchecked -- nothing says how many arraysppoints at -- sop[i]is untouched, and a base built from a pointer-to-array in any other shape is left alone rather than diagnosed. - Header-bearing preprocessor-cache entries are enabled. Recording each header's identity was never the missing piece; knowing whether it is still the file the next preprocess would choose was. An entry now also records the backend's include search path, taken from
-E -vwith the same flags, and each dependency's spelling as the preprocessor reported it. A file appearing ahead of a header in the search path moves that directory's mtime; a symlink retargeted anywhere along the way changes what the spelling resolves to. Either one is a miss. Failing to determine the search path means the entry is not published, so the fallback is exactly the previous behaviour.PP_CACHE_MAGICadvances toPRISMPPC4; older entries are ignored.
- Macro operands of surviving
#pragmadirectives are preserved. No preprocessor expands a pragma's operands, so flattening -- which hands the preprocessed stream through and re-emits no source defines at all -- left#pragma pack(push, PK)in the output withPKdefined nowhere. A define named by a surviving pragma is now kept. Where it goes depends on where it was: a define ahead of the first include is hoisted with the rest, and one that follows a header is emitted in place, immediately before the pragma that needs it, because a define written after a header may be completing or shadowing what that header set up and must not be lifted above it. Defines no pragma names are still dropped. Preserving the define makes the question of whether a given backend expands pragma operands moot: it now does whatever it would have done with the original source. -fno-orelseleaves the wordorelsealone. Phase 1D annotated and then diagnosed the declaration-initializer form without consulting the feature bit, soint j = w || orelse;was rejected with the feature off, where the word is an ordinary identifier and an undeclared one is the backend's complaint to make in its own words. It also meant Prism could not reparse its own output whenever an input used the name as a plain identifier -- the reparse oracle runs with every transformation disabled.- An expression walk that reaches
}without its;diagnoses instead of consuming the closing brace. This was the last of the four expression walks still missing the bound that fixes 46 and 52 added to the others; found by the fuzz corpus. typeof-based zero-init at file scope no longer indexes a negative function slot. Zero-init lowers to a runtime statement -- a byte loop or amemset-- which has no meaning outside a function, and the emitter reached for the enclosing function before checking there was one.- A spawn the machine refused is reported as such instead of as a preprocessing error. The library returned
PRISM_ERR_IOwith "Preprocessing failed" whether the preprocessor had run and disagreed or the machine had declined to start it at all -- two conditions that need different responses, and only one of which is about C. The message now names the refusal and its errno. The retry budget behind it also grows from five attempts over 0.2s to nine over ~2.8s: a Mac with 14.9 GB paged out refused straight through the old window, and waiting longer costs nothing on a machine where the first attempt succeeds.
#v1.1.6
Prism 1.1.6 fixes 41 issues with new procedural test suite with 45,496 test cells, performance is 1.25x faster than 1.1.5 and 1.59x faster than 1.1.4. Warm cached builds are 2.87x faster than 1.1.4.
new test suit measured coverage is 93.35% lines, 75.23% branches, and 99.05% functions.
#fixes
- Separate-argument compiler flags are parsed correctly. This includes
-isysroot,-imultilib,-aux-info,-dumpbase,-dumpbase-ext,-dumpdir,-wrapper,--param, and supported ld64 flags. SDK paths no longer become extra source files on macOS. - A block-comment line ending in
\no longer causes the next live#defineto disappear in non-flatten mode. - Function-like macros now use a valid identifier-only guard.
#define SUM(a,b)is guarded by#ifndef SUM, not#ifndef SUM(a,b). idx[&(a[0])]and equivalent nested forms can no longer bypass commutative-subscript rejection.- Pointer-arithmetic dereferences after braceless
if,while,for, andswitchstatements are rejected instead of passing through unchecked. - Address expressions after braceless control conditions no longer receive a false runtime check on a legal one-past pointer.
},++, and--are recognized as value tokens when distinguishing binary&from address-of, so the following subscript remains checked.- Address-of suppression now spans parentheses.
&(a[n])permits the same legal one-past pointer as&a[n]. - The Windows
mkstempsshim rejects templates without replacementXcharacters instead of opening a predictable fixed path. - The first library call targeting MSVC now emits matching MSVC diagnostic pragmas. It no longer starts with a GCC push and ends with an MSVC pop.
--prism-verifynow counts dialect keywords with the tokenizer. Raw strings, prefixes, UCNs, digraphs, comments, and macro shadowing can no longer hide a leakeddeferororelsetoken.- Nested groups in a
deferexpression now share the 4096-level parser limit. Excessive nesting reports a diagnostic instead of overflowing the stack. setjmptaint now propagates through same-file call edges, preventingdeferin callers that can be bypassed bylongjmp.- Casted calls such as
(void)wrapper(p)no longer hide transitivesetjmporvforktaint. - Calls to a declared function named
deferkeep their arguments.defer(1)is no longer mistaken for the language construct. - Calls through a function-pointer value named
deferare preserved for local, ANSI parameter, typedef parameter, and K&R parameter forms. - Library-mode
deferinside an unresolved preprocessor arm is rejected instead of being moved outside the conditional and made unconditional. - A do-while block containing trailing
deferin a GNU statement expression now receives the same required diagnostic as equivalent block forms. - C23
constexprvalues are treated as integer constant expressions for array sizing instead of as VLAs. - A parameter shadowing a
constexprbinding now restores VLA behavior inside the function. - File-scope symbol discovery now requires a valid function declarator.
defer (void)0;can no longer registerdeferas a function and evade the file-scope diagnostic. - Unevaluated state now survives nested delimiters and comma expressions in
sizeof,typeof, and_Genericcontrol expressions._Genericassociation expressions remain evaluated. - Qualifiers inside balanced
sizeof,alignof, andoffsetofoperands no longer leak into the result type of an enclosingtypeof. - Const
typeofobjects use a declaration initializer when legal instead of delayed byte writes through a const object. - Direct
typeof(variable)lookup preserves atomic qualification. Unsupported const atomic zero initialization now reports a diagnostic. - Direct
typeof(variable)lookup preserves const qualification, whiletypeof_unqualremoves it. - Typedefs derived from
typeofretain that origin through alias chains, preserving whole-object initialization for types such as x86long double. - Top-level
volatileand_Atomicqualifiers on pointer declarators and pointer typedefs are no longer confused with pointee qualifiers. - ANSI parameters retain exact const, volatile, atomic, pointer, and adjusted-array traits for
typeoflookup. - K&R parameter declarations now register const, volatile, atomic, and long-double traits instead of only VLA traits.
- Direct, typedef, complex, and volatile x86
long doublelocals now clear the full object, including x87 padding bytes. - Every link in a chained bare-assignment
orelsenow receives the variably-modified side-effect check. Middle links can no longer evaluate twice. - A declaration fallback ending in a preprocessor directive now places its generated semicolon after the directive line instead of producing
#endif;. - A nonzero
PrismFeaturesarray count with a NULL array pointer is normalized safely instead of dereferencing NULL. - Sparse define, include, flag, and forced-include arrays are checked by every consumer, including non-flatten re-emission.
- Negative feature-array counts are normalized before capacity arithmetic, preventing integer overflow.
prism_transpile_file(NULL, ...)returns a deterministic IO error without invoking the compiler.- Removing
rawat the start of a line now preserves the newline. Declarations after pragmas no longer merge into the pragma line. prism installcreates missing parent directories such as$HOME/.local/bininstead of failing and retrying withsudo.- Reinstalling through a symlink, relative path, hard link, or path containing
/../recognizes the existing binary by file identity and avoids copying it onto itself. - All ten feature flags accept positive and negative forms. Prism consumes only exact feature names, so unrelated flags such as
-fdefer-popand-fno-strict-aliasingstill reach the backend.
#v1.1.5
Prism 1.1.5 fixes 200+ bugs since v1.1.4, Prism gets faster ~2.02x end-to-end warm cache; 1.65x on the stress TU with the cache disabled.
New: preprocessed-output cache, prism check <analyzer>, @file response files, stdin input.
Four fixes address bugs that corrupt a build: a uint16 scope-cap that dropped the scope tree past 32,768 scopes with no diagnostic; orelse reaching the C backend intact inside a case label; a braceless defer declaration that copied every following statement into the cleanup; and orelse on a volatile or _Atomic object reading it up to three times.
#Performance
Preprocessed-output cache. cc -E was 39 to 99% of wall time (8.19 of 8.28 ms on a no-include file; 103.8 of 262.8 ms on test.c). Prism now caches the preprocessor's output and skips the spawn when nothing it read changed.
The preprocess phase drops 10 to 25x on a hit, scaling with how much output has to be read back: 6.5 → 0.4 ms for a 57 KB entry, 29.0 → 1.2 ms for 328 KB, 46.2 → 1.8 ms for 1.2 MB. End-to-end that is 2.02x v1.1.4 on a warm cache (70.9 → 35.0 ms, best-of-3 over four suite files).
Transpiler. Gains are workload-dependent, and the cache accounts for most of the end-to-end figure above. With PRISM_NO_PP_CACHE=1, on a 2,500-function defer/orelse/-fbounds-check stress TU it is 1.65x (70.9 → 43.0 ms); across four ordinary suite files 1.07x, ranging 1.20x on test.torture.c down to 0.94x on test.defer.c. Earlier notes reported ~65% on the phase timings of a stress TU; that holds for that shape, not as a general figure.
On Arch/9950X, GCC -O2 -g -DNDEBUG, CPU 8 pinned, 80-run perf stat batches: the 6,091,701-byte procedural TU moves 77.66 → 70.32 ms task-clock (−9.45%), cycles −10.26%, instructions −10.36%. The 1,109,463-byte self-host TU moves 15.94 → 15.05 ms (−5.58%). Outputs byte-identical.
#Bug Fixes
#defer
- Scope-cap
uint16truncation: a TU with more than 32,768 scopes lost the rest of the scope tree with no error and mis-lowered file-scope declarations (b007d1e) - A declaration as a braceless body scanned past its own semicolon and pasted every statement up to the block close into the deferred code, so they ran a second time at scope exit. The prior test only checked the output contained
int t = 0, so it never saw the duplication (16bba07) - Missing-semicolon detection inspected only the body's first token:
defer break;was rejected,defer f() break;was accepted and emitted asf() break;with the keyword stripped, so the backend error pointed at code the user never wrote (unreleased) - Labeled break/continue unwound the wrong scope, including GNU
__label__(f6bff02) - Mid-expression
deferafter a comma or inreturn(x=1, defer g();,return defer g(), 0;) leaked the keyword in release builds;reject_defer_in_expr_contextwasPRISM_DEBUG-only (78f9c8a) - Expression-context splices through
sizeof,!,~,&, casts and?:(86c20fc) - Scope/block depth confusion over-unwound defers across
gotoboundaries (c0a4935) - Braceless bodies under name shadows mis-parsed;
defer;, for-init and++forms had to stay identifiers (06bb332) - Braceless control flow in defer/orelse bodies broke brace injection (fc098bb)
{ defer }brace-init false reject (86c20fc)typeof(defer)(fc3716d)defer _Pragma(c0a4935)- File-scope
deferwas not rejected (5732201) in_defer_emitthread-local was not reset after apparse_errorlongjmp skipped the restore (a661b6b)- Attribute labels in a defer chain (a661b6b)
- Unevaluated
sizeofdefer was not a no-op (f2cf4aa) - Zero-init-off
orelseinside a defer (6bf4f84) - Braceless
defer die();missed__builtin_unreachable()injection: the synthesized;skipped the noreturn probe (78f9c8a)
#orelse
- Reached the C backend intact inside a
caselabel:case (x orelse 1):was accepted and the literal token passed through. Found by the tag-alphabet totality suite on its first run (16bba07) - On a
volatileor_Atomicobject, read the object up to three times; single-eval hoisting now covers atomic typedefs (ee1fc71) typeof(int[N])/_Atomic(int[N])accepted because arrayness lived on the type rather than the declarator, emitting brokenif (!a){…}(c0a4935)- Dimension holes in
_Alignof,_Generic,_Static_assertand nestedtypeof(78f9c8a) - Return-expression leak:
return (x) orelse 1leaked because the backward walk crossed(x)toreturnand applied thereturn orelse;identifier exemption (78f9c8a) static/extern/_Thread_localdimension orelse lowered to illegal static VLAs (17329c2)_Atomic(int[n orelse …])leaked: type-specifier dims were skipped and the type emitter never walked_Atomic((17329c2).orelse orelsedecl-init (06bb332)- Mid-chain empty
orelse orelse;orelse()now allowed (86c20fc) - Mid-chain
continue/ block-form reject (f2cf4aa) - Struct-value and chain-after-control-flow rejects (b007d1e)
- Keyword leak via
static rawverbatim bypass - Raw string literal
R"(...)"inside#if 0desynchronized block-comment state __attribute__bracket-orelse queue desynchronizationinit-castVLA crash on variably-modified types- Bare comma split escaped braceless control flow; paren comma corruption in chained expressions
- Preprocessor-conditional mangling in bracket orelse
- Designator side effects, compound-literal VLA, and nested
_Static_assertleak (f6bff02) - Non-ICE designator-dimension orelse; false control-flow diagnostic (06bb332)
- Soft type-spelling orelse values (06bb332)
const _Atomic/volatileif-hoist;_Nonnull/_Nullablequalifier scoping (86c20fc)typeof(_Atomic(int) orelse 0)leaked:_Atomic(T)was not treated as a type-specifier constructortypedef int (*F)(int a[0 orelse 1])ternary-lowered parameter dims; the declarator check jumped the(*F)group and typedef walks never called the prototype reject- GNU range designators
[0 ... 2 orelse 3]ternary-destroyed the...into illegal C constexpr int a[0 orelse 1]hoisted a runtime temporary into a constexpr dimension- Empty-orelse emit leaks;
return+ defer subscript orelse (65b0ee7) - Paren-led bare orelse after a control statement (97a9fba, b20455e)
- Multi-label and attribute bare-orelse peel (e03ee60)
- Brace-init designator leak;
_Atomicside-effect register; GNU attribute-between-dims FIFO (f2cf4aa) typeof(enum)did not setis_enum(f2cf4aa)enum E : typeof(T) { orelse }andenum E : _Atomic(int) { orelse }(c0a4935, 86c20fc)asmsymbolic[orelse]operand names (c0a4935)- Array variable orelse that can never trigger is now diagnosed rather than silently lowered
#bounds-check
- Local pointers, including qualified, typedef, pointer-to-array and
rawforms, could shadow a tracked file-scope array without creating a typedef-table shadow, sog[i]inherited the outer array and was wrapped withsizeof(pointer)/sizeof(pointer[0]) - Block-scope incomplete
extern int g[]inherited the complete outer bounds, generating an invalidsizeof(g) - Decayed parameters wrapped against the file-scope array:
int g[10]; f(int g[20]){ return g[i]; }, same for K&R andint *gparams that skipped the shadow optimization - Wraps skipped when the index itself used
orelse(a[i orelse 0]) becausetry_bracket_orelseran before the subscript check in the emit loop; they compose now, with the ternary index inside__prism_bchk(78f9c8a) - Silent bypasses:
(i)[tern],((i))[tern],(2)[tern](last emitted was));*((T*)(a+i))and*(((T*)(a+i)))(a grouping paren hid the cast+additive pair); bareidx[&arr[0]]without an outer paren;(*&(a))[i]and(*a)[j](c0a4935, 78f9c8a) p[a[i]]false-rejected as commutativeidx[arr]despite the subscripted-index exemption (c0a4935)- Cast×subscript used naive
sizeof(arr)/sizeof(arr[0])instead of the cast element size - Cast-prefix unary
&one-past ((void)&a[n],(int*)&a[n]) mistook the cast)for a binary-&operand (78f9c8a) (a+i)[0],0[a+i], cast-deref and_Atomic·typeofdims (f6bff02)- One-past parenthesized bounds (aa1b961); paren-deref (b007d1e)
- for/if/switch-init declarations never registered array bindings (missing wraps) or shadows (pointer init-stmt inheriting outer
sizeof) (c0a4935) - Block-scope
static/constexprinitializers wrappedg[0]into a non-ICE (c0a4935) rawVLAs were still re-registered (c0a4935)- Pointer-arith
*(a+i)bypass after the registry split; the lookup still consulted only the typedef table typeof(fixed_array)bounds registration; VLA-of-pointers false wrapsemit_statementsdid not run bounds checks (e03ee60)- C89
__prism_bchkform (fc3716d)
#zero-init
- Empty and
[0]aggregate memset (06bb332) - Const-subobject memset was undefined behaviour (aa1b961)
typeof(_BitInt(N))sole-FAMand nested-empty{0}, plus register/const × memset rejects- for/if-init
_Atomicandregisterrejects (86c20fc) - Member-side const-union false reject (86c20fc)
_Atomicaggregate and union/VLA const cases now diagnosed rather than silently mis-lowered- Soft-keyword bitfields (fc3716d)
SUE {scope tagging (ee1fc71)
#auto-static / auto-unreachable
- Auto-static promoted mutable
typeof(int[N]); typeof-as-const applies to orelse temps only (c0a4935) - Auto-static missed leading
cleanup/[[attr]], typedef-const, unbraced string initializers, C23true/false,typeof(int[N])(86c20fc) - Statement-final noreturn through casts, grouping, comma tails and call arguments (
(void)(0, die());,die(), 1;,foo(die());) missed__builtin_unreachable(); detection required an immediate;after the call's)(78f9c8a) - Unevaluated operands falsely marked unreachable:
sizeof die();and no-parensizeof +die()(78f9c8a) - Contextual noreturn/taint poisoning via soft-keyword callee, attribute name,
asm __volatile__ goto, declaration shadow (aa1b961) - User TU definitions of
exit/setjmp/… false-tainted and false-unreached _Noreturn void a(), b();tagged only the first declaratorasmsoft-keyword taint (fc3716d)
#raw
raw { … }still lowereddefer/orelseor injected auto-static, bounds and unreachabletypedef int raw; raw raw xkeyword strip (c0a4935)- Statement-form
raw { … }suppress blocks (c0a4935) rawon a VLA is still subject to the goto check, since jumping past a VLA bypasses stack allocation regardless of initialization- An all-
rawdeclaration must emit identically to the same declaration withrawdeleted and zero-init off. That differential failed 32/229 cells on first run and exposed__attribute__((unused)) const raw int v;emittingconst const int v;, invalid C that Phase 1 accepted. The attribute prelude was bounded at therawtoken even whenrawsat inside the specifiers
#tokenizer / preprocessor
- Function-like macros were silently dropped from re-emitted defines while object-like ones survived:
#define MAX(a,b)did not round-trip a transpile (unreleased) .iinput://\line splice, UTF-8 BOM, universal character names in identifiers (06bb332).itranslation units were dropped entirely (fc3716d)- Digraph
%:line markers (86c20fc); digraph/trigraph#define(f6bff02) - UTF-16 BOM now rejected with a diagnostic instead of mis-tokenized (b007d1e)
- Sticky
# N "a.h" 1 3system-skip dropped the rest of a user TU - Hard type keywords used as declarator names (06bb332)
- Parenthesized
_Genericfold (06bb332); glibc_Genericdefault-fold declarations (5732201) - GNU nested-function
is_func_bodyCFG classification (06bb332); nested-function verbatim emission (fc3716d) goto"assigned-first" analysis acceptedx = x + 1(86c20fc)gotointo C23if/switchinit when the init-semicolon search returned NULL- Brace-unsafe walk could scan past
type_endon large TUs (86c20fc) - Enum-walk underflow (65b0ee7)
- Ill-formed
while (decl),do decl,for (raw {…}), scalarfor (raw int x = 1),while(defer 0),if(defer 0)in control parens
#driver / CLI
@fileresponse files treated a backslash as a path separator on Windows, eatingC:\paths (1d18a57)- Force-include re-injection; split
-Iunder-fpreprocessed;bits/re-include (fc3716d) -Mdependency routing (b007d1e)-U_GNU_SOURCEordering; clang#… 3under no-flatten (f6bff02)- No-flatten
#includepath escaping - C++ mix driver preprocess/skip, C++ link,
-x ctemps, cross-compiler prefix (65b0ee7) sos_ensure_doOOM dangling pointer (5732201);sos_*thread-local free (a661b6b)- Calling-convention return capture (6bf4f84)
- Newline emission before declarations after statement boundaries (ee1fc71)
- Control-paren scope tracking in
emit_statements; for-initis_looppreservation (a661b6b);else/doparen-led forms anddoat-statement-start (b20455e); GNU attribute control-paren tracking (e03ee60)
#Windows / MSVC
- memstream, handle inheritance,
PATH,mkstemps, install (aa1b961) build_clean_environwas used bywindows.cbefore its declaration; MSVC infersint()and then rejects the real declaration with C2040. GCC 14+ would reject it too (unreleased)win32_memstream_pathremoved; suite tests rewritten forDELETE_ON_CLOSE(06bb332)mkstempsand noreturn shims (5732201)CLI_PUSHunder-allocation (65b0ee7)__assume(0)now counted as an unreachable marker alongside__builtin_unreachable(17329c2)- Fake
cltest used bash; Alpine has no/bin/bash(1d18a57)
#assertions / harness
PRISM_DEBUGfalse assertions: balanced-group checks treated initializer braces and index brackets as parentheses, and the missing-declaration-recipe assertion forgot that Phase 1 excludes GNU nested-function locals from the outer CFG table (78f9c8a)- Harness accounting: a local
totalintest_win_env_scrubbingshadowed the runner's thread-local CHECK counter, so three checks incrementedpassedbut nottotal. The invarianttotal == passed + failedis now itself a counted meta-test - Four leaked temp/result ownership paths found by LeakSanitizer; undefined
_Boolfeature-mask write; UB-based OOB "trap" oracles replaced with wrapper-presence and zero-survivor checks
#Testing
104 generative tiers Prism test suit moves to more procedural tests to cover much wider set of edge cases. suites test.autostatic.c, test.autounreach.c, test.golf.c, test.cert.c and test.raw.c were absorbed into generative tiers.
#Code organization
C-language logic moved out of prism.c into parse.c, which is moving towards a reusable C-parsing library with Prism dialect support. parse.c 4,595 → 12,928 lines; prism.c 12,336 → 9,696.
#Stats
- 200+ bugs fixed since v1.1.4
- 40 commits; 36 files changed, +37,133 / −17,479
- 104 generative tiers, 3,820 CHECK sites (876 at v1.1.4)
- Suite: 14,543+ platform-inclusive CHECK inventory, up from 6,035 in v1.1.4. Audited: 14,286/14,286 Darwin/Clang, 14,355/14,355 Arch/GCC 16, the latter also clean under ASan + UBSan + LeakSanitizer. 11,327/11,327 verified under GCC across 18 of 19 tiers.
test.parse.crequires Clang, since it usesfor (typedef double x;;), which C11 6.8.5.3 permits only forauto/register - Self-host: stage0 → stage1 → stage2 → stage3 transpile output byte-identical (1,058,598 bytes)
--prism-verifypasses onprism.c,parse.c, and all 21 corpus sources- 2.02x end-to-end warm cache; 1.65x on the stress TU with the cache disabled
#v1.1.4
Prism v1.1.4 is a bug-fix release: ~105 bugs across 8 fix commits, including 8 bounds-check bypass vectors, ~62 C23 init-statement false rejections, soft-keyword identifier corruption in the parser, const+orelse aggregate UB, computed-goto false positives, and a #pragma link parser crash. Also: optimization-attribute support and a Windows thread-attribute fix.
#Security
#pragma linkparser crash on malformed/empty payload:collect_link_pragmasdereferenced past end-of-line on a bare#pragma linkwith no platform tag or names; bounds-checked range-walk now rejects cleanly (0b3f158)- 8 bounds-check (
-fbounds-check) bypass vectors: commutative-subscript paths missed several shapes that let an indeterminate index reach the backend unwrapped:idx[(arr)],(idx)[arr]chained through compound literals,idx[(expr, arr)]where the comma-RHS aliases through a typedef, brute-scan fallback on multi-line array expressions,__builtin_*wrappers, ternary-arm subscripts,_Generic-association subscripts, and the*(arr+idx)pointer-arithmetic form. All 8 now route throughbc_wrapand emit the runtime check (0b3f158) ~9internal-limit silent passes / false rejections: token-pool, scope-tree, P1FuncEntry, and typedef-table growth paths previously could either silently truncate (info hiding) or hard-error at safe sizes; explicit overflow predicates added at every*_count > UINT16_MAX/> UINT32_MAXsite, with tests covering each (0b3f158)- Dynamic-buffer regression from 1.1.3's prep-dir scanner: a length miscalculation could leave one byte uninitialized at the boundary between two consecutive
#include-expanded buffers; backends might read stack noise (0b3f158) const+ bareorelseaggregate fallback emitted indeterminate read:const struct S s = get() orelse {0};produced a ternary whose false-arm readsbefore the assign, exposing whatever was on the stack at the declaration point; bareorelseonconst-qualified aggregates now hard-rejected (must use block formorelse { return ...; }) (48cec3c)- Computed goto false-positive over user-initialized declarations (
void *targets[] = { &&L1, &&L2 }; goto *targets[i];): the CFG verifier'sunverifiable_jumpblocked all zeroinit-eligible decls without checkingdecl.has_init, so the standard label-address-table dispatch idiom was rejected even though the user had taken over initialization (e645922) goto-over-uninit false positive when the label site assigns first: Prism rejectedgoto L; int x; ... L: x = compute(); use(x);despite the first reference toxbeing a plain assignment (no indeterminate read); third-pass scan now applies an assign-first heuristic that walks tokens forward from the label's:, finds the first identifier matching the skipped variable, and accepts when followed by a single-character=(e645922)
#Bug Fixes
- ~62 C23 init-statement over-rejections:
for(int x; ...),if(union U u; ...),switch(_Atomic int a; ...),if(typeof(*p) v; ...)and combinations involving_Atomic(typeof(...)), typedef-of-aggregate,_Alignas, pointer-to-VLA receivers, etc. all relied on a__builtin_memsetinjection that cannot land between the init clause and the controlling expression. The init-statement path now satisfies needs-memset cases with= {0}(or= 0for typeof scalars) directly at the declarator, lifting the rejection. C23if/switch-init VLAs are now accepted uninitialized (matching plain C semantics); onlyfor-init VLAs remain rejected (no compatible escape hatch).const+ unavoidable-memset rejection lifted in init-statement context (no memset emitted, brace-init of aconstis a normal initialization) (0b3f158, e645922) - 4 const-union edge cases:
const union U u = {0};at file scope, in for-init, in stmt-expr last expression, and as a multi-declarator second decl all routed through inconsistent gates that either double-rejected or silently emitted memset over aconst(0b3f158) - Switch unbraced declaration false positives: Phase 1D + Pass 2 rejected every declaration directly in
switch (x) <decl>;even when the user had explicit intent:static,extern,_Thread_local,register,_Atomic,constexpr,_Alignas,typedef,struct/union/enumreference,raw, or any explicit=initializer. Both gates now restrict to the plain auto-no-init trapdoor; the actual case-skips-decl violation is still caught by the CFG verifier (e645922) - GNU nested function false rejection: Prism rejected GCC nested function definitions in any function regardless of whether the outer function used
defer. Plain GNU C nested-function idioms (including statement-expression nested-function definitions:int (*fp)(int) = ({ int g(int x) { return x + 1; } g; });) now pass through verbatim; rejection only fires when the outer function actually contains adeferkeyword (forward-scan from the function body's opening{) (e645922) - File-scope
typeof+orelsesegfault: struct/union/enum declarators at file scope with[1 orelse 2]array dimensions dereferenced a NULLscope_treeslot (cur_sid == 0); guard added at the P1K_DECL allocation site (e645922) - 5 soft-keyword identifier handling issues in the parser: variables named
defer,orelse,raw,noreturn, andunreachablelost their identifier role under specific declarator shapes (multi-decl with attribute, K&R param, function-pointer return, typeof-typed init, sizeof operand). Token classification now consistently checksis_soft_keyword_identifierbefore applying keyword semantics (6fc467d, 48cec3c) - Function declarator handling and MSVC
unreachable()test failures: function-typed declarators with parameter lists containing_Pragmaor__attribute__were misclassified as call expressions; MSVC's__assume(0)mapping forunreachable()was emitted with the wrong macro guard (115d2ba) typeofon local function declarations:void f(void); typeof(f) g;inside a function body fell through to the variable-declaration path because the local extern function decl was not registered in the prescan's typedef table;gthen had VLA-detection run on a non-VLA type (16de944)- Windows thread attribute:
_Thread_local/thread_localmapping for MSVC used__declspec(thread)placement that broke when combined withstatic; the storage-class ordering now matches the MSVC declarator grammar (d385352)
#Features
- Optimization attribute pass-through:
__attribute__((hot)),((cold)),((flatten)),((optimize(...))), and the C23[[gnu:hot]]/[[gnu:cold]]/[[gnu:flatten]]forms are now parsed and emitted verbatim through the function-attribute slot, including in defer-transformed function definitions where the synthesised epilogue must inherit the outer attribute set (d914e2c) - Internal optimizations: token-walk hot paths in Phase 1D and Pass 2, scope-stack iteration, and typedef-lookup hash probing tightened; ~5% wall-time reduction on large translation units (23a91cc, ac53f44)
#Stats
- ~105 bugs fixed (~88 bug fixes, ~17 security vulnerabilities)
- +635 new tests, total: 6,035 tests passing (darwin/clang; alpine x86_64 / linux CI parity maintained)
- +2,226 lines of new test coverage
- 13 files changed, +3,391 insertions, −643 deletions
- Self-host: stage1 == stage2 byte-identical
#v1.1.3
Prism 1.1.3 is a bug-fix release after 1.1.2. Focus: contextual keyword handling, C23/extension keyword identifier cases, orelse disambiguation, and the Windows CI regression around defer union zero-init checks.
#Bug Fixes
- Fixed parenthesized GNU statement-expression declaration
orelsefallbacks escaping Phase 1 validation:int x = get() orelse (({ 0; }));now rejects correctly with the same diagnostic as the direct({ ... })form (9868145) - Fixed C23
u8'…'character literal tokenization so Prism preserves the prefix as part of the character token instead of emitting invalid spacing (6df08b2) - Fixed C23 / extension soft keywords used as ordinary declarator names, typedefs, tags, labels, and goto targets, including
alignas,constexpr,typeof,static_assert,thread_local,bool, and related spellings (6df08b2) - Fixed soft-keyword typedefs and struct/union tags losing aggregate metadata, which could make zero-init emit scalar
0instead of{0}(6df08b2,3d4ca2b) - Fixed
alignastypedef precedence so a real typedef namedalignaswins in type-name position, while truealignas(...)attribute syntax still works (3d4ca2b) - Fixed contextual
orelsehandling in array dimensions: enum constants or variables namedorelseat the start of[...]remain identifiers, while real bracket-orelsefallback syntax is still recognized after an expression LHS (3d4ca2b) - Fixed Prism keywords used as C labels, GNU local labels, computed-goto labels, function identifiers, calls, and parameters (
40866fc) - Fixed Prism keywords used as struct/union/enum declarator names, including
typedef union { ... } defer;and anonymousunion { ... } defer;zero-init cases (40866fc) - Fixed the Windows CI expectation mismatch for the
deferunion zero-init regressions by using name-aware zero-init assertions (67759dd)
#Stats
- 5 bug-fix commits since 1.1.2
- +58 tests
- 5,807 tests passing locally on darwin/arm64
- +718 insertions, −53 deletions across 6 files
- Self-host transpiled source: stage1 == stage2
#v1.1.2
Prism 1.1.2 adds -fbounds-check: runtime subscript trapping for tracked arrays, without wrapping declarator contexts, typedef chains, multi-dimensional arrays, or parenthesized variants incorrectly. It also adds two CLI helpers, plus bounds-, defer-, orelse-, and phase-1 scope-tracking fixes found while shipping the feature.
This release also fixes build-system issues, including Meson.
#Features
-fbounds-check: runtime subscript bounds-checking (a0632b8, 28 follow-up hardening commits).arr[i]is wrapped in a__prism_bchkmacro that traps on out-of-range access. Coverage includes: multi-dimensional subscripts at every level (a[i][j][k],(a[i])[j]), typedef-of-array chains (typedef int T[3][4]; T a;), file-scope arrays with complete dimensions, parenthesized arrays ((a)[i],((a))[i]), array-of-pointers with inner rank guard (int *p[10]; p[0][i]stays unwrapped), side-effect-once semantics for indices, commutativeidx[arr]hard-rejection (ISO C §6.5.2.1 equivalence that every detector misses silently: prism points the user at the equivalentarr[idx]form). Works with auto-static,_Genericcontrolling expressions, and under-fpreprocessed/-x cpp-output/ MSVC / MinGW LLP64. SPEC §6.6 documents the full model.prism run -- <args>(7eee1b5): trailing argv passthrough to the executed binary.prism -O3 run build.c -- hello world 42now works;--is the explicit separator so flag-like prog args (-v) are not mistaken for prism flags. SPEC §8.#pragma link <platform> <libs>...(7eee1b5): declarative per-source link flags.#pragma link macos Cocoa QuartzCoreemits-framework Cocoa -framework QuartzCoreon Apple hosts;#pragma link linux m pthreademits-lm -lpthread; platform tag matches current host triple (e.g.macos_arm64,linux_x86_64, bare OS name) and is filtered at compile time.-fno-link-pragmadisables the scan globally for security-sensitive builds.
#Security
- OOB read in
p1_mark_uneval_brackets: scan iterated fromi=0, reading the uninitializedtoken_pool[0]sentinel; randomTF_SIZEOF/TT_TYPEOFbit on uninitialized flags causedtok_next(garbage_next_idx)to walk a wild pointer. AddressSanitizer-confirmed; manifested as intermittent BUS/SEGV on macOS arm64 (138/139), Linux 139, Windows 1, varying ~50% ofprism run .github/test.cinvocations across CI. Fixed to start ati=1(8d9fbe5); defense-in-depth patch also explicitly zeros slot 0 on first allocation (ad2d5f0) - Const-aggregate memset UB: zero-init emitted
memset(&obj, 0, sizeof obj)for const-qualified unions / VLAs /_Atomicaggregates, laundering the mutation through(void*)past compiler warnings but still violating ISO C11 §6.7.3p6 (modifying a defined const object), poisoning TBAA, and permitting silent reordering; now hard-rejected with a clear diagnostic (f351461) - Commutative-subscript bounds bypass: ISO C defines
arr[idx]andidx[arr]as equivalent, but-fbounds-check's walker only matched the array-on-the-left form, silently emittingidx[arr]raw and bypassing the runtime trap; now a hard error that points the user at the canonical form (f351461) - File-scope arrays never registered for bounds-check: the registration gate skipped globals entirely, so every
-fbounds-checkbuild had a silent info-leak/OOB hole on any module-levelint g[N]; now registered with rank propagation through TU boundaries (502a7ac) - Array-of-pointers never registered:
int *a[10]; a[i]was excluded by!decl.is_pointer, which rejected the array-of-pointers case (is_array && is_pointer) along with true pointer types; replaced with the codebase's standard declarator-top-level array predicate (a0ce82f) - Parenthesized and typedef array subscripts unwrapped:
(a)[i],((a))[i], andtypedef int T[N]; T a; a[i]all escaped the wrap; paren-peel now iterates through all parenthesis layers and registration walks through typedef chains (7c33ec4,9eb60ed) sizeof (a)[i]unevaluated-bracket miss: postfix chain stopped at the)closing(a), so the[i]bracket was not tagged unevaluated and its runtime index could be miscompiled / double-evaluated; now walks the full postfix chain past the closing paren (7c33ec4)offsetof/sizeofsubscript wrap false negatives and spurious one-past-end traps: unary&arr[len](a well-defined one-past-end address computation) was trapping; nestedarr[m[i]]inner index was silently unwrapped; struct-member subscripts shadowed by localsizeofnames produced both class (24d2353)- Auto-static emission without trailing space: under
-fauto-static -fbounds-check,const int a[10] = {0};became uncompilablestaticconst int a[10]...(a0ce82f) - Zero-init skip on
typeof(int[f()])/typeof(int[(n)])aggregates: declarator-array VM scan was folded throughtypeof(...)(a749df0,d0dedf6,d159a98) - Phase-1 scope exhaustion crash on large static tables: files with 131K+ lines (
intel_perf_metrics.c) exhausted theuint16_tscope_id space (>65534) because every{ .reg = x, .val = y }initializer brace allocated a fresh scope; Phase 1A now skips scope-tree entries for init-in-init braces, bounding growth to O(actual compound statements). Phase 1D scope-id consumer also desynced in the process, mis-matching for-loop body braces against unrelated switch scopes and producing spurious zero-init diagnostics: matched-open-tok guard added (a4b5717) realloc-inside-realloc class in bounds-check preamble emission: preamble used__SIZE_TYPE__which is not expanded under-fpreprocessed/-x cpp-output(CI flatten-mode fail); switched tounsigned longthenunsigned long longafter LLP64 truncation was discovered on MinGW/Clang-on-Windows x64 whereunsigned longis 32-bit butsize_tis 64-bit (d59bb16,1a3f08b)
#Bug Fixes
Bounds-check false positives and spurious wraps
- Declarator-bracket class closed at a single guard site: prototype
int f(int g[20]), K&R tail type-declsint f(g) int g[10]; { ... }, nested prototypes, function-pointer parameters with array-typed params, function-pointer typedefs, and struct fields holding function pointers were all wrapping their declarator[as if they were expression subscripts, producing VLA-mismatch warnings or runtime traps.try_bounds_check_subscriptnow has a declarator-context guard (TT_TYPE | TT_QUALIFIER | TT_SUE | TT_TYPEOF or pointer-declarator*);p1_register_param_shadowsregisters non-VLA parameter names as is_param shadows so body uses mask file-scope arrays of the same name (048c31d: seven concrete cases) - Struct-field array dim false-positive:
pthread.h's__cancel_jmp_buf[1]was being wrapped (1abc7f4) - Ternary LHS peel through
bounds_find_array_ident(d0dedf6) &(arr)[i]one-past-end unary-address spurious trap (d159a98)- Member-LHS subscript
s.arr[i]false positive (2814de3) - Bare array inside
sizeofoperand spurious wrap (2814de3) - Static-storage initializers: subscript expressions inside file-scope array initializers now wrapped correctly (
2814de3) _Genericcontrolling expression: was bounds-wrapped (must stay unevaluated) (ab9cd86)- Incomplete
extern int a[];base: bounds skip added (a749df0) static_assert(arr[0] == 0, "...")predicate wrap false positive (cf5ad34)- Comma subscript
arr[(x, i)]pointer base handling (cf5ad34) - Array rank and incomplete outer-dim:
typedef int T[][4]; T a;handled (7becfca) - Rank-16 sentinel:
ARRAY_RANK_WRAP_ALL=255so arrays with >15 dimensions (rare, but legal) don't drop outer wraps (16d1b2c) array_dim_completerecorded on typedef array types for accurate sizeof recovery (16d1b2c)- Side-effect-once verification:
a[bump()][bump()]counters advance exactly once each (58d2116) - Pool-predecessor fallback:
last_emitteddoesn't advance through syntheticOUT_LIT/out_stroutput, walker now consultstoken_pool[idx-1]when seeking root identifier across earlier wraps (58d2116)
Defer, orelse, zero-init
typeof(volatile T): zero-init scan now handles volatile-qualified typeof (d159a98)- Volatile
orelsecompound literal: now rejected (avoids double-read of volatile source) (d159a98) deferbody break at control paren:defer break;insideif (...)correctly handled (342e9cc)defercapture hash restore (d159a98)- Braceless
deferbodyP1_IS_DECLannotation in!at_stmt_startbranch (d159a98) typeofenum ghosts / fixed-underlying balanced skip (C23 attrs) (d159a98,d0dedf6)- K&R param VLA scan (
d159a98) - K&R 2D VLA CFG regression (
565cf86) - Attribute-on-orelse emit desync: C23
[[...]]now emitted verbatim in Pass 2 (565cf86) - Raw
walk_backoff-by-one and subscript strip (342e9cc) - Raw strip skips string literals (
cf5ad34) - Raw stripped from scraped
#defines (a749df0) raw * xsilent miscompile:TF_RAWadded to shadow-registration predicate at 3 Phase 1D sites;P1_HAS_ENTRYearly-return intry_strip_raw(dd1f6b6)sizeof(vla + k)variably-modified type handling (342e9cc)sizeof VLA param+/- pointer arith (cf5ad34)sizeof(vla[i])no-paren subscript wrapped (spurious trap) (0245cf5)- Nested
arr[m[i]]inner index unwrapped (24d2353) - CFG
has_initvs-fno-zeroinitgate (342e9cc,d0dedf6) - Const value
orelserejected whentype_vmis set (16d1b2c) _Atomicsplit fromtype_vmfor array-bounds (a749df0)- Ptr-to-array declarator bracket rank (
cf5ad34) - Bracket-orelse chain first LHS handling + O(n) paren depth (
7becfca,342e9cc) - Commutative
idx[arr[i]]detector (7becfca) - Switch-unbraced-decl diagnostics, case-label-bypasses-init-decl (
2814de3) - Spurious brace wrapping around decl after if-body whose condition contains a nested non-ctrl paren (cast in
_Generic, etc.) (2814de3)
Other
typedefshadow dedup (cf5ad34)- Typedef array pointer
is_arrayregistration (a749df0) - Auto-unreachable for-init in ctrl paren (
cf5ad34) - Auto-static multi-decl (
7becfca) - Struct field vs typedef disambiguation (
7becfca) typeof_unqualaggregate scan (7becfca)skip_one_stmttrail snap beyondSOS_IF_MAXandcache[tix]self-host (cf5ad34,7becfca)structbody tag lookup for volatile/VLA (a749df0)- C23 float literals emitted verbatim (
342e9cc) - Emit
#undefbefore every#definein flatten output: cpp-evaluated#ifndefguards are gone in flattened form, so redefining macros like__attribute_const__across headers became fatal under-Werror(1abc7f4) - Emit system-header flag (3) in linemarkers (
1abc7f4) - Drop
-c/-o/-Sfrom preprocessor argv: clang's-Werror=unused-command-line-argumentfires in meson probes (1abc7f4) - Stop forcing
-D_POSIX_C_SOURCE=200809Lalongside-D_GNU_SOURCE: sources that#undef _GNU_SOURCEwere being left with POSIX alone, suppressing__USE_MISCand droppingsyscall/sbrk/brk/crypt/getentropydecls (1abc7f4) - Capture full
cc --versioninprism --versionoutput (gcc's "Free Software Foundation" identifier is on line 2) for meson/autoconf compat (1abc7f4, Windows twinbad59cf) static/_Thread_local/thread_local/__threadstorage in defer body: now rejected with clear diagnostic (ab9cd86)
Windows / cross-platform
windows fix: unresolved externalcapture_all_outputin MSVC link step; prism.c's--versionhandler called the POSIX-only function unconditionally; added Windows twin in windows.c using CreateProcessW + restricted-handle inheritance, reads to EOF, normalizes CRLF→LF (bad59cf)- Restored
signal_temps_unregister: earlier cleanup removed it as "dead" but Windows memstreamfclosepath still calls it (23a879c) - LLP64 bounds-check truncation on MinGW/Clang-on-Windows x64:
unsigned longis 32-bit whilesize_tis 64-bit, silently truncating indices; switched tounsigned long long(1a3f08b) - Test harness pins 8 MiB pthread stack for CI workers: musl's 128 KiB default overflowed on deep test inputs (Linux-only flake, glibc 8 MiB and macOS 512 KiB normally survive) (
9fbaee6) - Windows test fixes (
cea01b4)
#Stats
- 80+ bugs fixed across 36 commits (spanning bounds-check hardening, defer/orelse correctness, zero-init gates, phase-1 scope tracking, Windows LLP64, and memory-safety UB in token-pool scans)
- +349 new tests, total: 5,749 tests passing (darwin/arm64; CI matrix covers linux x86_64, linux arm64, linux riscv64, macOS x86_64/arm64, windows build)
- Major new test file:
test.bounds.c(+1,958 lines, all runtime-trap probes verified under ASan+UBSan) - +5,929 insertions, −736 deletions across 18 files
- Self-host: stage1 == stage2 byte-identical
- New SPEC coverage: §6.6 bounds checking (full runtime model), §3.2 TypedefEntry
array_rank, §6.4 defer validation storage rejection, §6.10 bounds-check preamble emission, §8 CLI modes (program args, link pragma)
#v1.1.1
Prism 1.1.1 is bug fixes only: 27 bugs across 9 commits, including memory-safety issues in the preprocessor pipeline, multiple zero-init bypass vectors, defer/orelse interaction breakage, and emitter-level codegen drift.
#Security
#definecomment-strip use-after-free:collect_source_definesretained pointers into a buffer that was reallocated mid-scan when a#defineline crossed the SWAR padding boundary; subsequent dereference read freed memory (fe20798)reallocdouble-free in source-defines arena: themodifiedflag was reused across iterations, causing the same buffer to be re-realloc'd then freed twice on certain#definepatterns (fe20798)typeof(int[f()])/typeof(int[(n)])skipped zero-init:is_typeof_func_type's top-level walk did not skip balanced[...]groups, so any(inside an array-dimension expression was misread as a function parameter list, classifying the typeof as a function type and suppressing memset; declared local VLA was left indeterminate,x[0]read uninitialized stack memory (info leak) (98423f6)- Attribute-noise blindspot in VLA predecessor:
is_array_bracket_predecessordid not walk past GNU__attribute__((...))and C23[[...]]between a SUE keyword and a tag name, sotypeof(struct __attribute__((packed)) Tag [n])failed VLA detection and skipped zero-init (17013d6) _Atomic(...)inner-type VLA scan missing:_Atomic(int[n])and_Atomic(VLA_typedef)were not flagged as VLA, falling through to= {0}(compile error) or no init (info leak);parse_type_specifiernow scans_Atomic(...)contents identically totypeof(...)(17013d6)typeof(struct __attribute__((...)) S)zero-init skip: anonymous struct with attribute noise betweenstructkeyword and body was not detected as aggregate, dropping the memset (e11bd95)- VM func-ptr
orelsedouble-evaluation:typeof(RHS)in the bare-orelse temp pattern evaluated the RHS twice when the result type was variably modified (C11 §6.7.2.4p2); a function pointer with VLA-array return type triggered the duplication, double-callingf()and double-reading volatile sources (e11bd95) -fno-orelsedefer-in-paren safety bypass: withF_ORELSEdisabled,defer (return);and similar paren-wrapped defer bodies bypassed Phase 1F validation entirely; theorelseparen-recursion scanner now also runs when onlyF_DEFERis enabled (6c12a53)defer-bodyorelseemitter overshoot:emit_bare_orelse_implandemit_deferred_orelseconsumed the trailing;and advanced past it without respecting the deferred-rangeendboundary, spilling the next user statement (and the function body's closing}) into the emitted defer body: invalid C output (253266e)ret_counterdesync in defer-wrapped return:emit_return_bodyreadctx->ret_countertwice (once for__prism_ret_<N> = (...), once forreturn __prism_ret_<N>;) withemit_all_defers()between them; the defer body's__prism_oe_Kallocations bumped the shared counter, emitting areturn __prism_ret_<M>referring to an undeclared identifier (253266e)
#Bug Fixes
noreturn: K&R-style funcptr param scan misclassifiedvoid (*cb)(int, int);as a forward declaration; precise inner-paren walk discriminates parameter type list from declarator (17013d6)noreturn: forward-declaration scan failed when the noreturn attribute appeared between qualifier and identifier; func-proto map now records both branches (17013d6)- stmt-expr inside control-flow paren: a
({…})insideif(…)/while(…)parens corrupted Phase 1D'sat_stmt_starttracking on the body's first token, dropping declaration annotation and zero-init (17013d6) defer enum bodyfalse shadow: Phase 1D's enum-body scanner falsely flagged inner-scope enum constants as shadowing defer-captured names when no control-flow exit was reachable while the shadow was live (e11bd95)#defineblock-comment leak in source-defines collection: multi-line/* ... */comments containing#definelines silently scraped_FILE_OFFSET_BITSand similar macros into the output, mutating the compiled program's ABI (e11bd95)_Genericdefer body missingSCOPE_GENERIC: defer bodies inside_Genericassociations did not push the generic scope, causing:separators in nested associations to be mis-identified as labels (871c20f)- Stale
last_emittedin 5 orelse scan-context checks:last_emittedfrom the prior emission leaked into orelse's "is keyword" disambiguation, producing both false rejections and false acceptances depending on the prior token (871c20f) - stmt-expr in for-init defer-shadow desync:
for ( ({ defer ...; }) ; ; )produced incorrect shadow-vs-capture decisions because Phase 1D's shadow scanner used the wrong scope-id at the stmt-expr boundary (345e5d2) deferkeyword leak in expression context:(s.defer),arr[defer], and other expression positions wheredeferis a member/subscript-context identifier emitted thedefertoken verbatim to the backend, producing a compile error (345e5d2)__label__ctrl_pendingleak in Phase 1D: GNU__label__ L1, L2;declarations did not clearp1d_ctrl_pending, causing a subsequent declaration to be erroneously bounded to a non-existent braceless control body (387b1c6)- Pass 0 taint variable-name false positive: a local variable named
vfork(or any taint-tagged identifier) inherited the global TT_SPECIAL_FN tag, falsely tainting the function and rejecting defer; member-access predecessor check added (387b1c6) - Wrapper-of-
exitfalsely tainted as vfork:wrapper_taint[]propagated bareTT_NORETURN_FNfrom a callee onto the wrapper's body without mirroring the direct-body scan's vfork-only rule, hard-rejecting any defer in functions calling a user wrapper ofexit/abort/_Exit/thrd_exit/quick_exitwith a misleading "vfork()" error (98423f6) - Braceless defer body declaration with
orelseinitializer emitted invalid__typeof__(int t) __prism_oe_N = ...: Phase 1D walks braceless defer bodies in the!at_stmt_startbranch, missing theP1_IS_DECLannotation; Phase 1F now rejects with a clear message directing the user atdefer { ... }(98423f6) - Pass 1
annfield flag space exhausted at 8 bits: widened touint16_tand addedP1_REJECTEDfor explicit rejection tracking (6c12a53)
#Stats
- 27 bugs fixed (17 bug fixes, 10 security vulnerabilities)
- +166 new tests, total: 5,400 tests passing (alpine x86_64 / linux CI; 5,361 on darwin/clang)
- +1,663 lines of new test coverage
- 14 files changed, +2,131 insertions, −339 deletions
- Self-host: stage1 == stage2 byte-identical
#v1.1.0
Prism 1.1.0 introduces auto-static promotion and bug fixes.
#New Features
- Auto-static:
constarrays with literal initializers are automatically promoted from stack tostaticstorage, eliminating hiddenmemcpyon every function call. Fires conservatively: block-scopeconstarrays only, every initializer token must be a literal or enum constant, novolatile/static/extern/register/constexpr/_Thread_local, no VLA dimensions, noorelse, no attributes on the declarator. Pointer arrays requireconston the array itself (const int * const arr[3]). Opt-out:-fno-auto-static(d0b7f55)
#Security
- Union
= {0}padding infoleak:= {0}only initializes the first named member of a union (C11 §6.7.9p17); §6.7.9p21 implicit zeroing applies only to aggregates (C11 §6.2.5p21: arrays and structs, NOT unions); GCC-15 empirically leaves remaining bytes indeterminate: unions now route to__builtin_memsetunconditionally, withis_uniontracked through the full type system including_Atomic(union)and typedef chains viaTDF_UNION - Compound literal dangling pointer via
top_is_callheuristic: function-call exclusion in compound literal detection allowedidentity_ptr(&(struct Config){.mode=1})to use if/else path, scoping the compound literal to the else block (C11 §6.5.2.5p5): if the function returns the CL address, the pointer dangles; token-level analysis cannot determine escape behavior; removed heuristic entirely, ternary path now unconditional at all parenthesis depths registerunion zeroinit bypass:register union U u;fell through to= {0}(memset blocked by!has_register), only zeroing the first member; now rejected with a hard error in both Phase 1D and Pass 2, sinceregisterforbids address-taking (ISO C11 §6.7.1p6) making memset impossible
#Bug Fixes
noreturn: attribute scanner only searched forward from attribute position: post-declarator forms likevoid die(void) __attribute__((noreturn));invisible because scan hit;before findingident(; added backward scan from attribute origin through token pool (89ef391)zeroinit:_Atomic(union U)and_Atomic(UnionTypedef)missingis_unionflag propagation:_Atomic(type)handler inparse_type_specifiernow setsis_unionfor both direct SUE and typedef inner typeszeroinit: Phase 1D for-init union rejection missing:for(union U u; ...)passed Phase 1D, caught only by Pass 2 defense-in-depth; two-pass invariant violation fixedzeroinit: Phase 1D const-VLAwould_memsetmissing union:const union U u;requiring memset not caught by const rejection check- Windows POSIX shim and test compatibility fixes (d60d16a, bc042f2)
#Tests
- Auto-unreachable test suite: +183 tests validating
__builtin_unreachable()injection after noreturn function calls across all attribute forms, chained calls, and edge cases (89ef391)
#Stats
- 7 issues fixed (4 bugs, 3 security vulnerabilities)
- +112 new test functions, +319 new tests, total: 5,234 tests passing
- +2,337 lines of new test coverage
- 10 files changed, +2,651 insertions, −102 deletions
#v1.0.9
Prism 1.0.9 fixes 17 bugs across defer validation, orelse transformation, zero-initialization, and the typedef table, including chain-aware scanning in defer bodies and orelse side-effect validation.
#Security
- Defer validator scope escape:
skip_to_semicolonin Phase 1F'svalidate_defer_statementhad no scope boundary check; a missing;inside adefer { ... }block caused the scanner to walk past}into unrelated code, either producing spurious errors on valid functions or silently missing real control-flow violations (f7caf27) - Chained orelse defer control-flow leak:
defer_scan_orelse_in_groupandvalidate_defer_statement's inline orelse scanner both had prematurebreakafter the firstorelse; chained(expr orelse expr orelse return)in defer bodies letreturn/goto/break/continuebypass Phase 1F entirely, breaking LIFO unwinding guarantees (792ec8b)
#Bug Fixes
defer: false "shadows a name captured by defer" errors for variables declared at the top level of defer bodies:defer_body_refs_nameused wrong block-depth threshold (1081ea0)defer:walk_balancedexpression-context enum bodies (sizeof(enum { X = 1 })) never registered defer shadow entries: silent rebinding at exit points (864197b)defer:walk_balanced_orelsearray-dimension enum bodies had the same shadow-tracking gap aswalk_balanced(864197b)defer: enum defer shadow check in Phase 1D issued hard error even when enclosing block has no control-flow exits: false positive on valid code (864197b)defer: case/default label scanner invalidate_defer_statementescaped block boundaries on complex case expressions (f7caf27)defer: O(N×M) quadratic blowup indefer_body_refs_namecaused apparent hang on large generated/macro-heavy files: replaced with single-pass O(M) capture set + O(1) per-name lookups (792ec8b)orelse:typeof(... orelse ...)verbatim keyword leak in 9 emit paths:try_typeof_orelsehook missing fromemit_range_ex,emit_expr_to_stop,emit_raw_verbatim_to_semicolon,emit_orelse_fallback_value,emit_expr_to_semicolon, andemit_bare_orelse_implinner loops (792ec8b)orelse: chained typeof orelse side-effect validation gap: Phase 1Dbreakafter firstorelseonly checked first LHS, intermediate LHS ranges intypeof(a orelse b orelse c)unchecked for side effects (792ec8b)zeroinit:__extension__-prefixedfor-init declarations bypassed zero-initialization:p1_scan_init_shadowsdidn't skip__extension__/TT_INLINEprefix tokens (5661e79)zeroinit:P1_IS_DECLannotation placed on__extension__prefix instead of actual type keyword: Pass 2 scan past storage/inline tokens never found annotation (5661e79)zeroinit: declarations after complex case labels (case (int)'A':,case ENUM | FLAG:) missed zeroinit::handler only recognized identifier/number predecessors (1081ea0)zeroinit: bracelesswhile/doloop body zeroinit gated onFEAT(F_DEFER)only: with-fno-defer, brace wrapping never applied (1081ea0)zeroinit:any_would_memsettwo-pass invariant violation: Phase 1D split predicate missingis_typedefin_Atomicaggregate check, disagreeing with Pass 2 (1081ea0)- Inner-scope struct tag redefinitions not registered when body lacked VLA/volatile members:
tag_lookupreturned stale outer-scope flags, causing false CFG errors or incorrect memset (f7caf27) - Struct tag registration blocked by same-name typedefs:
parse_typedef_declarationgated on!is_known_typedef(tag), losing volatile/VLA member tracking for the commontypedef struct Foo { ... } Foopattern (792ec8b)
#Stats
- 17 bugs fixed, 2 security vulnerabilities patched
- +8 new test functions, +56 new tests, total: 4,915 tests passing
- +728 lines of new test coverage
- 8 files changed, +1,031 insertions, −142 deletions
#v1.0.8
Prism 1.0.8 closes 20 bugs across the transpiler, including keyword table coverage, namespace isolation, statement-expression dispatch completeness, and braceless control flow scope tracking.
#Security
- ILP32 keyword shift UB:
KW_MARKERused(uintptr_t)flags << 32which is undefined behavior on 32-bit platforms whereuintptr_tis 32 bits, corrupting all keyword tag/flag classification and silently breaking every keyword-dependent analysis (61b5fb3) - Braceless compound literal scope underflow: compound literal braces
(struct S){...}inside braceless control flow didn't decrementctrl_state.brace_depthproperly, leakingctrl_state.pendingto the next statement and popping defer shadows below the current scope (61b5fb3) - Braceless shadow scope leak:
p1d_probe_declarationregistered typedef shadows using the enclosing block's scope close, so braceless body declarations likeif (c) int MyTypedef;permanently poisoned the name for the entire enclosing scope, blinding the CFG verifier and skipping zero-init (379c242) walk_balancedbracket stmt-expr bypass: the bracket[...]flat-emit loop had nois_stmt_expr_opencheck, so({...})inside array subscripts in balanced groups bypassed zero-init, defer cleanup, and all keyword processing (490a8c5)emit_noise_between_rawsstmt-expr bypass: flatemit_tokloop lacked stmt-expr dispatch, allowing({...})inside attribute noise between raw declarators to bypass the full processing engine (490a8c5)process_declaratorsraw-bail stmt-expr bypass: the raw declaration verbatim-emit loop used flat token emission without stmt-expr detection, allowing zero-init and defer bypass inside statement expressions (490a8c5)_AlignasSUE probe skip in defer:EMIT_DEFER_BODY's SUE body probe didn't skip parenthesized arguments of qualifiers, so_Alignas(16) struct S { int x; };in a defer body caused= 0to be injected into struct field definitions (490a8c5)- Attribute-stealing struct tag scanner:
skip_noisewas missing from the struct tag extraction loop, sostruct __attribute__((aligned(8))) Tag {registeredalignedinstead ofTagas the struct name: corrupting VLA/volatile member tracking (76a1612) - C11 §6.2.3 namespace collapse:
typedef_add_entryduplicate check,typedef_lookup, andparse_type_specifier's typeof handler collapsed C's tag namespace with ordinary identifiers, silently dropping struct tag entries and bypassing zero-init/volatile tracking (76a1612) - Param orelse funcdef bypass:
p1d_reject_proto_param_orelseonly checked for;after)(prototypes), letting function definitions with{escape entirely:void f(int buf[hw_flag orelse 128]) { }produced a ternary with volatile double-read UB (9e71bc1) - Asm specifier evasion: GCC
__asm__("symbol_name")renaming specifiers after)were not skipped byskip_noise, causing the;/{detection to fail and the entire bracket-orelse parameter scan to be silently skipped (9e71bc1) - Block-scope typedef rettype gate:
p1_func_proto_mapatbrace_depth>0missed typedef-named return types, sotypeof(f)inside a function body triggered spurious memset on a function type: writing to .text segment (SIGSEGV) (9e71bc1) - Nested funcptr param scan: bracket-orelse parameter scan was flat (no recursion into nested
()groups), so orelse inside function-pointer parameter brackets was never checked, enabling the same volatile double-eval UB (9e71bc1) typeoffuncptr attribute blindness:is_typeof_func_typeusedtok_next(fs)withoutskip_noise, missing__attribute__/[[...]]between(and*: function pointer type misclassified as function type, skipping memset → write to .text → SIGSEGV (f439990)- Missing C23/GCC type keywords: 14 float types (
__float128,_Float16,_Float32,_Float32x,_Float64,_Float64x,_Float128x,_Decimal32,_Decimal64,_Decimal128, etc.) and 2 typeof_unqual variants (__typeof_unqual__,__typeof_unqual) absent from keyword table: treated as plain identifiers, bypassing zero-init; also fixedis_unquallength check (== 13→>= 13) which broke qualifier stripping for 15/17-char variants (f439990)
#Bug Fixes
orelse:orelseinside GNU__attribute__((...))or C23[[...]]arguments was invisible to Phase 1D, leaking the raw keyword to the backend compiler (320fe44)raw:emit_type_rangehad notry_strip_rawin its finalemit_tokfallthrough, sorawinside struct/union bodies at brace depth > 0 leaked verbatim to C output (320fe44)raw:emit_token_rangeused bareOUT_TOK(t)withouttry_strip_raw, soraw int f() { defer ...; return 42; }producedraw int __prism_ret_0 = (42);(320fe44)orelse:reject_orelse_side_effectsfired unconditionally on bare orelse RHS with LHS indirection, but function return types are never variably-modified (C11 §6.7.6.3p1): false positive on valid code (76a1612)defer:emit_statements':label handler was gated onmode != EMIT_DEFER_BODY, preventing case/default labels from resettingat_stmt_start: bare orelse after case labels in defer bodies leaked verbatim to C output (f439990)
#Stats
- 20 bugs fixed, 15 security vulnerabilities patched
- +13 new test functions, +110 new compliance tests, total: 4,859 tests passing
- +1,131 lines of new test coverage
- 9 files changed, +1,412 insertions, −127 deletions
#v1.0.7
Prism 1.0.7 closes 26 bugs across the transpiler, including defer body emission integrity, two-pass invariant enforcement, volatile/VLA type tracking through struct bodies, and noreturn detection safety.
#Security
- Lexical noreturn shadowing: a local variable or parameter shadowing a noreturn function name (
void (*exit)(void)) still got__builtin_unreachable()injected after the call: silent miscompilation turning reachable code into UB (018a0a0) - Volatile member memset: structs with
volatile-qualified fields were zero-initialized viamemset, which strips the volatile qualifier: compiler may optimize away stores to memory-mapped I/O registers (c81cff2) - C tag namespace collapse:
typedef_lookupcollapsed C's tag namespace (§6.2.3) with ordinary identifiers, soint MMIO = 1;created a shadow that hidstruct MMIO { volatile int x; }: volatile-member and VLA-member lookups on the struct tag silently failed (76c3ba1) typeof/_Atomicparen-skipping in struct body scanners:struct_body_contains_vla()andstruct_body_contains_volatile()skipped all(...)groups, makingtypeof(volatile int)andtypeof(int[n])fields invisible: volatile qualifier stripped or VLA missed (76c3ba1)- Backward goto over VLA: CFG verifier only checked defers between label and goto, not VLA declarations: a backward goto looping over a same-scope VLA re-allocates without freeing, enabling unbounded stack exhaustion (2bf9e0d)
constVLA memset:const typeof(int[len]) buf;emitted__builtin_memseton a const object: undefined behavior (C11 §6.7.3p6) (86be39a)registerVLA:register int buf[n];has no valid zero-init strategy (VLA can't use= {0}, register forbids address-taking for memset): silent failure to initialize (86be39a)- Bare orelse VM-type double evaluation:
typeof(RHS)evaluates operand at runtime for variably-modified types; if the RHS function returns a pointer-to-VLA, the call fires twice (86be39a) - Defer body
ctrl_stateleak:EMIT_DEFER_BODYdidn't consumectrl_stateon{tokens, so afterif(cond)the pending state leaked into the braced body, wrapping declarations in spurious{ int x = 0; }: variable went out of scope immediately (ed9fd38)
#Bug Fixes
defer:typeof-orelse expressions inside defer bodies were gated bymode != EMIT_DEFER_BODY: raworelsekeyword leaked verbatim to backend (ed9fd38)defer: bracket-orelse inside defer bodies had the sameEMIT_DEFER_BODYgate: compound literal and sizeof orelse leaked raw (ed9fd38)defer: paren-wrapped orelse(0 orelse return)inside defer bypassed Phase 1F validation:scan_decl_orelsestrips outer parens, making the return action reachable, corrupting defer stack unwinding (5c3657b)defer:ctrl_statenot reset after braceless body brace-wrap computation: leaked to next declaration, suppressingcheck_defer_var_shadow(c81cff2)defer: body emission loops (emit_block_body,emit_deferred_range) ranwalk_balancedon control-flow conditions withoutat_stmt_starttracking: declarations and labels insidefor()/if()inits bypassed zero-init and CFG checks (2bf9e0d)orelse:constexprcombined with orelse was not rejected by Phase 1D: runtime fallback emitted into a C23 compile-time constant declaration (86be39a)orelse: bracket orelsereject_orelse_side_effectspassedcheck_asm=falseat 4 call sites:asmstatements inside orelse LHS silently duplicated by ternary expansion (86be39a)orelse: Phase 1D multi-declarator split predicate was blind to orelse on the current declarator:int a = f() orelse 5, b;didn't trigger a split, emitting incompatible anonymous struct types (3cc88eb)orelse: Phase 1D split predicate only checkedhas_initfor the next declarator, missing VLA:typeof(int[n]) a = {0}, b;violated the two-pass invariant (3cc88eb)orelse: VM return-type double evaluation: bare orelse exempted bare function calls in RHS from side-effect rejection, but a function can return pointer-to-VLA, causingtypeof(RHS)to evaluate the call at runtime (86be39a)zeroinit:se_brace[8]capacity indefer_body_refs_namewas too small for 9+ nested statement expressions: conservative false-positive blocked valid defer variable references (86be39a)zeroinit: stmt-expr in ctrl condition label blindspot: Phase 1D balanced-group scan abort on({...})inif(...)left the closing)unprocessed, soat_stmt_startwas never set for the braceless body: labels and declarations invisible to CFG verification and zero-init (c2b7f68)- BOFrame fixed-size array overflow:
unsigned oe_ids[16], dim_ids[16]poisoned by inner stmt-expr declarations beyond 16 bracket-orelse entries: now arena-allocated with unbounded capacity (2bf9e0d) - HashMap arena leak:
HashMap.bucketsusedcalloc/free(heap) butFuncMeta.defer_name_setlives on the arena;arena_resetobliterated FuncMeta without freeing buckets: permanent heap leak per function-with-defer inPRISM_LIB_MODE(c81cff2) typeofcontrol-flow keyword rejection unconditionally errored onreturn/goto/break/continue/deferinsidetypeof(): broke glibcINLINE_SYSCALLmacro which expands to__typeof__(({...return...}))(b6f83b2)externdeclaration false positive as nested function: K&R fallback scan walked forward from;through subsequent code to find{(e.g. a switch body), falsely classifying the extern declaration as a nested function definition (ed38b7e)
#Stats
- 26 bugs fixed, 9 security vulnerabilities patched
- +8 new test functions, +37 new compliance tests, total: 4,751 tests passing
- +1,352 lines of new test coverage
- 9 files changed, +1,744 insertions, −244 deletions
#v1.0.6
Prism 1.0.6 drops the _Generic member rewrite engine (spec-only, no codegen) and fixes 18 bugs found in a full-pass audit. Areas covered: C23 attribute handling across orelse/bracket/struct paths, Objective-C compatibility, typeof nesting, __label__ local labels, and orelse keyword shadow disambiguation (including real typedefs named orelse, via positional context).
#Security
- Nested
typeof(typeof(VLA))misidentified as function type: memset skipped, leaving VLA uninitialized (63ab2b4) emit_block_bodymissing zero-init dispatch after labels/case in statement expressions: variables left uninitialized in({...})blocks (194f498)
#Bug Fixes
orelse:typedef int orelse;followed byorelse x = get() orelse 42;produced untransformed output: 12-site fix unifying real typedef + shadow into positional disambiguation across Phase 1D and Pass 2 (63ab2b4)orelse: C23[[attr]]on first declarator stole bracket orelse FIFO queue entry meant for second declarator:int [[attr]] a[f() orelse 1], b[g() orelse 2];corruptedb's transformation (17b40d0)orelse: C23 attribute prefix leakedorelsekeyword into output: attribute noise tokens beforeorelsebypassed keyword detection (17b40d0)orelse: bare orelse comma-prefix range used rawemit_tokloop:rawkeyword leaked through inx = 1, y = f() orelse 0;(63ab2b4)raw:walk_balancedbracket subscript fast-path usedemit_tokinstead ofemit_tok_checked:rawkeyword leaked through[(raw int)x]cast expressions (63ab2b4)typeof:typeof(typeof(int[n]))inner(misidentified as function parameter list: suppressed VLA memset (63ab2b4)typeof: void function with trailing GNU__attribute__((noreturn)):walk_back_skip_attrsstopped at the attribute instead of reaching function name, causing false function-type identification failure (5299296)defer: struct/union/enum bodies inside defer false-positive zero-initialized members:struct { int x; }in defer body gotint x = 0;(194f498)defer: storage class / type specifier prefix on struct declaration inside defer bypassed SUE body detection:static struct S { int x; } s;in defer zero-initialized the struct body (194f498)emit_block_body: missing typeof orelse, bracket orelse, SUE body, and enum defer shadow dispatches in statement expression processing (194f498)zeroinit:_Pragma(...)and C23[[attr]]noise tokens at statement start inside({...})blocked zero-init detection:({ _Pragma("once") int x; x; })leftxuninitialized (5299296)zeroinit: Objective-C@interface/@implementationivar blocks treated as regular struct bodies:{ int _reserved; }after@interfacegot false zero-initialization (4d4e839)zeroinit: Objective-C protocol declarations and generics angle brackets<Type>mismatched as less-than operator: caused parse confusion in subsequent declarations (4d4e839)- C23
[[attr]]before named struct inwalk_back_skip_attrs: attribute tokens between struct keyword and name blocked backward walk, breaking zero-init and function-type detection (86a98d8) auto-unreachable:__builtin_unreachable()injected inside braceless control body in statement expressions:if (cond) noreturn_fn();inside({...})created multi-statement braceless body (86a98d8)- anonymous struct multi-declarator split detection used
skip_prep_dirsinstead ofskip_noise: C23 attributes before anonymous struct body bypassed the split rejection check (86a98d8) __label__: duplicate label names in Phase 1D name mangling produced identical mangled labels:__label__ a; __label__ a;in nested scopes crashed or produced wrong code (17b40d0)- macOS preprocessing missing
_DARWIN_C_SOURCEdefine: system headers using Darwin extensions failed during flatten (4d4e839)
#Other Changes
- Dropped
_Genericmember rewrite engine: removed ~500 lines of codegen and 22 test functions;_Genericnow passes through untransformed (standard C) (1841bdc) - Unified
emit_block_body+emit_deferred_rangeinto 2-modeemit_statementsengine: single code path for all statement-level processing in defer bodies and statement expressions (17d642a) - Shrunk
P1FuncEntryandTypedefEntryfrom 40 → 32 bytes (2 per cache line); reducedskip_one_stmt_implstack from 82KB → 9KB (fe50fa9) - Updated SPEC.md: orelse shadow disambiguation section rewritten,
walk_balancedraw stripping,is_typeof_func_typenested typeof skip, 8 distributed positional check sites documented
#Stats
- 18 bugs fixed, 2 security vulnerabilities patched
- +19 new test functions, total: 4,653 tests passing
- +214 net lines of new test coverage
- 12 files changed, +2,833 insertions, −2,817 deletions
#v1.0.5
Prism 1.0.5 includes internal refactoring for upcoming features, plus correctness and security fixes: 67 bugs across the transpiler, including the _Generic member rewrite engine, two-pass invariant violations, defer cleanup integrity, and orelse type safety.
This release also introduces a formal language specification with 307 compliance tests, expanding validation toward ISO C99 / C11 / C23.
#Security
- Function type erasure:
typeof(func_typedef)through chained typedefs and directtypeof(int(int,int))signatures lost the function-type flag, emittingmemseton function type:.textsegment write / SIGSEGV (c92930a) - Initializer braces pushed
SCOPE_BLOCKinstead ofSCOPE_INIT, inflatingblock_depth: defer cleanup silently dropped on scope exit (6e926ac) - Forward-declared non-noreturn function tagged noreturn by attribute scanner, injecting
__builtin_unreachable(): unconditional crash (edae040) - K&R function name resolution walked past
token_pool[0]: out-of-bounds memory access (6dafccd) emit_defers_extriggered infinite recursion on cyclic scope reference: stack overflow DoS (6dafccd)_Genericmember rewrite used buffer-relative position invalidated by 128KBout_flush(): non-deterministic code corruption at buffer boundary (a694cb6)- Bare orelse
typeof(LHS)with indirection operators (*,->,.,[]) evaluated operand at runtime for variably-modified types: spurious volatile reads / double side effects (8fcca8a)
#Bug Fixes
_Generic: ternary colon confused with association separator, blocking member rewrite on ternary branches (40b6fbc)_Generic: same-name-different-args: function with identical name but different arguments silently lost the argument list (6e926ac)_Generic: extraction discarded trailing expressions after close paren (a694cb6)_Generic: chain walk destroyed factory pattern suffixes:obj.factory().method()truncated toobj.factory()(a694cb6)_Generic: prefix multiplication on chained calls:get_api()->fetch()injected prefix before both identifiers (a694cb6)_Generic: ring buffer overflow on long prefix chains fell back to wrong code silently (e32bdc2)_Generic: paren-wrapped targetint: (handler)bypassed rewrite (e32bdc2)_Generic: multi-layer paren peeling not handled:int: ((handler))bypassed rewrite (452a220)_Generic:prefix_bufoverflow produced silent fallback instead of hard error (452a220)_Generic: ternary branch prefix drop: one arm of conditional target missed injection (3410f7d)_Generic: line directive desync on member rewrite rewind:#linetracking corrupted after output buffer rewind (3410f7d)_Generic: nested rewrite left orphan prefix text in output buffer (3410f7d)_Generic: inner-generic prefix drop: nested_Genericinside outer_Genericassociation lost the member prefix (60fc35e)_Generic: paren-complex target with cast prefix not handled (60fc35e)_Generic: array/member target boundary detection produced wrong rewrite range (60fc35e)_Generic: cast-prefix(type)handlerdropped during association value rewrite (c13effd)_Generic: nested rewrite blind spots in subtree emission loops:walk_balancedandwalk_balanced_orelsemissing hook (e966190)_Generic: member rewrite missing fromemit_deferred_range,emit_range_ex, andemit_token_range_orelse: defer bodies, const orelse init, and bracket orelse emitted_Genericverbatim;emit_token_range_orelseused rawOUT_TOKbypassinglast_emittedand emit save ring entirely (f69d460)defer: ghost enum defer shadow bypass in sizeof/cast/typeof expressions (edae040)defer: ghost enum defer shadow escape into enclosing scopes (452a220)defer: stmt-expr escape in control-flow condition heads inside defer bodies bypassed validation (edae040)defer: backward goto over same-scope defer loop: CFG verifier missed same-depth defer entries (3410f7d)defer: backward goto in nested scope used wrong ancestor-check direction: defer cleanup silently skipped (ae8a43f)defer: C23[[attr]]between function signature and body consumed into return-type typedef synthesis (2ac2200)defer:emit_deferred_range/emit_block_bodyflatemit_tokloop bypassed stmt-expr/orelse/zeroinit processing in control-flow conditions (6e926ac)defer: attributes encapsulating control-flow statements bypassed CFG verification (e966190)orelse:->operator matched as--in side-effect rejection check: false rejection of member access (8fcca8a)orelse: UB shift inorelse_shadow_is_kw: undefined behavior in transpiler itself (8fcca8a)orelse: bracket orelse stmt-expr reentrancy clobbered processing state: nested({...})in array dimensions corrupted context (e51e08b)orelse: orelse inside prototype VLA dimensions not rejected: produced invalid C (e51e08b)orelse:typeof_varmemset queue reentrancy clobber in stmt-expr array dimensions: pending memset entries silently overwritten (44ce49a)orelse:reject_orelse_side_effectsmissing control-flow keyword check:orelse goto Lnot detected as action form (44ce49a)orelse: RHS control-flow duplication: goto/return in chained orelse fallback emitted twice (c13effd)orelse: control-flow keywords inside type specifier positions not rejected (c13effd)orelse: C23constexprdeclarations with orelse not rejected: constexpr requires compile-time constant (c13effd)orelse: last-link bare orelse used ternary with usual arithmetic conversions: silently widened types across arms (e41533c)orelse: deferred orelse action not routed through full transpilation engine: blocks and keywords processed raw (e41533c)orelse: const-pointee VLA false rejection:const int (*p)[n]has const pointee, not const VLA (ae8a43f)orelse: stmt-expr paren-strip leaked keyword:(orelse)inside statement expression bypassed detection (6ca00ce)orelse:typeof_varreentry after fix produced wrong test result (c13effd)orelse: attr-raw VLA safety bypass: attributes betweenrawand VLA dimensions bypassed CFG check (e966190)zeroinit:__extension__prefix bypassed zero-initialization:__extension__ int x;emitted without= 0(edae040)zeroinit: for-init typedef type system desync:TT_TYPEDEFrouting missing inp1_scan_init_shadows(ae8a43f)raw: keyword leak in cast/sizeof/compound-literal:walk_balancedandemit_range_exmissingtry_strip_raw(edae040)raw: per-declaratorrawhidden behind attributes:int x, __attribute__((unused)) raw y;leakedrawinto output (e32bdc2)raw: bitfieldrawleak at colon:raw int x : 4;leaked keyword past colon (60fc35e)emit_type_rangeSUE body strip fooled by__attribute__arguments:packedmatched as struct name, stripping anonymous struct body (2ac2200)- O(N²) in
p1d_scan_balanced_groupon nested parentheses containing statement expressions (40b6fbc) sizeof((arr)[n])VLA false positive:)predecessor falsely signaled type context in expression position (6e926ac)skip_one_stmtelse-transition trail flush poisoning: parent cache entries corrupted by true-branch token caching (6e926ac)skip_one_stmttn=0on do-while entry destroyed parent trail: O(N²) rescans and incorrect cache values (6e926ac)]VLA false positive on multidimensional subscripts:arr[1][n]inner]falsely signaled type context (a694cb6)- MSVC diagnostic push/pop emitted nothing:
#pragma warning(push)/poppair missing (e32bdc2) _Float128suffix silent precision loss:1.0f128suffix not preserved during emission (e32bdc2)- Braceless typedef scope poison: typedef registered in braceless control-flow body persisted past body end (452a220)
- VLA multi-declarator sequence point split: VLA dimension expressions evaluated in wrong order after declaration splitting (452a220)
typeof_unqualoff-by-one const-stripping: qualifier removal consumed one token too few (3410f7d)- C23
enum Color : int { ... }fixed underlying type not parsed: colon consumed as label (60fc35e) - C23
constexprdeclarations silently skipped byTT_SKIP_DECLflag: transpiler never processed constexpr variables (c13effd) out_strlarge token flush tracking:out_total_flushednot updated for tokens exceeding 128KB buffer, corrupting absolute byte positions (e41533c)p1d_scan_balanced_groupflatinner_depthcounter didn't distinguish stmt-expr(from regular(: false error on valid code (e41533c)- Typedef array const-stripping removed
is_constfor array typedefs; added missingis_volatile/TDF_VOLATILEtracking (ae8a43f)
#Stats
- 69 issues fixed (67 bugs, 2 crashes), 7 security vulnerabilities patched
- +53 new test functions, +307 formal compliance tests, total: 4,679 tests passing
- +6,066 lines of new test coverage
- 13 files changed, +8,165 insertions, −1,141 deletions
#v1.0.4
Prism 1.0.4: no new features. Internal cleanup, refactoring, and bug fixes.
#Bug Fixes
orelse: ternary expression only caught by Pass 2 two-pass invariant breach (cf274c1)orelse:scan_decl_orelseparen unlinking miscompile inside larger expressions (5a4243d)orelse: stmt-expr LHS flat-emitted in condition wrap, bypassing defer/zeroinit (5a4243d)orelse: bare cast accepted non-lvalue (5a4243d)orelse: inside ctrl-flow condition parens two-pass violation (7fa5c85)orelse:p1d_decl_has_bracket_orelseparen-skip desync (7fa5c85)orelse: bare orelse without LHS expression leaks to Pass 2 (c4c9311)raw:raw * exprat statement start misclassified as pointer declaration, stripping token (c4c9311)zeroinit: parenthesized function typedef false positive (5a4243d)zeroinit:typeof(param)bypass when param shadows function name (c4c9311)zeroinit: param shadow registration missingp1_func_proto_mapcheck (c4c9311)zeroinit: noreturn attribute argument poisoning:__attribute__((cleanup(noreturn)))injected__builtin_unreachable(8abb51e)zeroinit: VLA deref paren adjacency:sizeof(*(param))hid*from backward look (8abb51e)defer: braceless defer shadow false positive:p1d_ctrl_pendingleaked into shadow checker (8abb51e)defer: O(N²) defer/decl shadow check via bloom filter saturation (c4c9311)
#Refactoring
- Moved ~1,375 lines of parsing infrastructure from prism.c to parse.c
- Replaced
defer_name_bloom(64-bit bloom filter) withdefer_name_set(exact HashMap) - Extracted
defer_body_refs_name,is_stmt_expr_open(13 call sites) shared helpers - Deleted dead code:
defer_name_bloom_bit,INITIAL_ARRAY_CAP,has_zeroinit_decl,tok_name_eq, orelse guard code - Fixed
-Wsign-compareinequal_n
#Stats
- 15 bugs fixed
- +11 new test functions, total: 4,147 tests passing
- prism.c: 10,829 → 8,867 lines (−1,962)
- parse.c: 2,094 → 3,609 lines (+1,515)
- Net: −447 lines of source code
- 9 files changed, +2,525 insertions, −2,627 deletions
#v1.0.3
Prism 1.0.3 adds no new features. Focus is correctness and security: 33 bugs fixed across the two-pass architecture, including two-pass invariant violations, security-critical data corruption paths, and denial-of-service vectors.
#Security
typeof((func))parenthesized function name bypassed function-type detection, emittingmemseton.textsegment: memory corruption (a5d566e)sizeof(n * int[n])commutative VLA rewriting bypassed the VLA detector: silent stack corruption (0abd764)- Deeply nested braceless
dostatements caused unbounded recursion inskip_one_stmt: stack overflow DoS (a945663)
#Bug Fixes
defer: stmt-expr false positive in defer shadow checker (72fcf86)defer: braceless switchfind_switch_scope()leaked to outer switch, wiping its defer count (75ecedf)defer: C23 attribute bypassed defer stmt-expr chain check two-pass violation (a945663)defer: case/defaultp1d_ctrl_pendingleak bypassed CFG goto-over-declaration check (270b1fa)defer: shadow same-block two-pass violation between Phase 1D and Pass 2 (0abd764)orelse:typeofbit-field in bare orelse produced invalid temporary type (8888ae3)orelse: chained assignmenta = b = f() orelse 5split-brain found wrong=(a850967)orelse: VLA param decay false positive:sizeof(arr)on multidim VLA parameter (c2372aa)orelse:__auto_typecast in orelse temporary declaration (c2372aa)orelse: const-VLA orelse duplicated the VLA size expression (acf026b)orelse: GNU stmt-expr({...})in orelse fallback position not recognized (acf026b)orelse: ctrl-paren bracket orelse two-pass violation (acf026b)orelse:typeoforelse side-effect two-pass violation (acf026b)orelse: parenthesized(expr orelse val)two-pass violation (acf026b)orelse: bare orelse preprocessor conditional check two-pass violation (0abd764)orelse: volatile double-write via compound literal orelse fallback (270b1fa)zeroinit: block-scope function prototypetypeofemitted spurious memset on function type (a850967)zeroinit: for-init VLA memset two-pass violation (0abd764)zeroinit: param multidimensional VLAsizeoffalse positive (25cbe72)zeroinit: complex return type attribute consumed into typedef synthesis (25cbe72)raw: raw token leak past GNU/C23 attributes (a945663)- Nested stmt-expr
ctrl_save_stackhijack: inner({...})popped outer's save entry (a5d566e) skip_one_stmtcache poisoning false positive on if-else trail (270b1fa)skip_one_stmtlabel swallowing:L: if(1) {}overran statement boundary (75ecedf)array_size_is_vlaexponential blowup on nested bracket patterns (8888ae3)emit_type_rangestmt-expr bypass in struct bodies (0ea1478)- File-scope
typeofscope_treeNULL dereference (acf026b) - VM-type multi-declarator split two-pass violation (acf026b)
- Dead
const_td_is_arraycode removal (75ecedf)
#Stats
- 33 bugs fixed, 3 security vulnerabilities patched
- +50 new test functions, total: 4,053 tests passing
- +2,293 lines of new test coverage
- 13 files changed, +2,934 insertions, −971 deletions
#v1.0.2
Prism 1.0.2 adds no new features. Focus is quality, performance, and correctness.
#Performance
Prism's transpiler throughput improved by 13.4% (measured by retired instructions on self-transpile). Six hot paths were optimized with bloom filters, O(1) post-annotation typedef rejection, consolidated token metadata access, and a pure-C early exit for files with no orelse usage. (4650fc8)
#Algorithmic Improvements
- Noreturn function tagging reduced from O(N·K) to O(N+K) via single-pass token annotation (46bb2a5)
- Typeof func-proto scanning reduced from O(N²) to O(N) per declarator (12ff66f)
- Nested bracket orelse scanning reduced from O(N²) to O(N) via iterative stack (6bb82d1)
- Noreturn taint propagation reduced from O(D×T) to O(T+D×E) edge-based fixed-point (2b422a8)
#Bug Fixes
defer: orelse fallback block bypassed defer cleanup at scope exit (46bb2a5, fc098bb)defer: stmt-exprgoto/returninside orelse bypassed defer cleanup (6bb82d1)defer: scope/block depth confusion over-unwound defers across goto boundaries (4e79cd5)defer: stmt-expr goto bypassed defer cleanup in bare orelse comma expressions (4e79cd5)defer: braceless control-flow in defer/orelse bodies broke brace injection (fc098bb)orelse: raw string literalR"(...)"inside#if 0desynchronized block-comment state (46bb2a5)orelse:__attribute__bracket orelse queue desynchronization (5942783)orelse: keyword leak viastatic rawverbatim bypass (19032bc)orelse:init-castVLA orelse crash on variably-modified types (12ff66f)orelse: bare comma split escaped braceless control flow (4e79cd5)orelse: preprocessor conditional mangling in bracket orelse rejected in Phase 1D (2b422a8)orelse: paren comma corruption in chained expressions (fc098bb)orelse/defer: shadowed keywords suppressed by variable, enum, or typedef names (3f6e7c3)orelse/defer: braceless control-flow body wrapping conflicts with orelse handler (fc098bb)- MSVC test compatibility fixes (fc098bb)
- Recursion regression in deeply nested braceless
ifchains (2ec6a01)
#Stats
- 23 bugs fixed, 6 performance optimizations
- +21 new test functions, total: 4,020 tests passing
- +1,246 lines of new test coverage
- 9 files changed, +2,502 insertions, −1,040 deletions
#v1.0.1
Prism leaves the 1.0 stabilization phase and moves to a more frequent release cadence.
Prism now also applies progressive enhancements to plain C, not only explicit features like defer and orelse. Passing unmodified source through Prism can produce safer defaults and smaller binaries without source edits.
#Progressive Enhancement: Auto-Unreachable Insertion
Prism now automatically tracks _Noreturn, [[noreturn]], and standard library exit functions across your entire translation unit. It now silently injects __builtin_unreachable() (or __assume(0) on MSVC) after these calls. (b3a6981)
#Performance & Binary Size
Feeding explicit control-flow termination data to the backend C compiler enables aggressive dead-code elimination (DCE), allows the backend to drop unnecessary function epilogues, reduces register pressure, and improves branch prediction. Your binaries get smaller and faster automatically.
#Bug Fixes
defer: cross-branch goto dropped cleanups: LCA exit gated behind flawed depth comparison (c3b824a)defer: braceless for-bodyif/elseprematurely cleared shadow scope (8017af3)defer: noreturn member namespace pollution injected false__builtin_unreachable()(6a0140e)zeroinit: typeof func-type scanner ignored paren depth in_Static_assert(sizeof(...))(c180878)zeroinit: CFG verifier missed braceless-scope declarations in switch (3da543b)orelse: static/extern persistence corruption: runtime re-init destroyed static semantics (05c61e2)orelse:typeof(RHS)double-evaluated VM-type expressions in bare assignment (10cf638)orelse: preprocessor directive in fallback concatenatedifbranches (887766d)
#Stats
- 8 bugs fixed, 1 feature added
- +26 new test functions, total: 3,902 tests passing
- +758 lines of new test coverage
- 10 files changed, +935 insertions, −240 deletions
#v1.0.0
This release rewrites the transpiler from a single-pass prototype into a two-pass design. It adds the orelse keyword, full native Windows (MSVC) support, and a larger test suite.
Prism is now self-hosting.
#Major Highlights
- New Language Feature: The
orelsekeyword for fallback values and control flow. - Native Windows Support: Full compatibility with MSVC (
cl.exe). - Two-Pass Architecture: A rewrite of the internal transpilation pipeline for better maintainability.
- Parser hardening: Closed categories of parsing edge-case bugs.
- Test expansion: Over 2,700 new tests added.
- Formal Specifications: Technical
SPEC.md&DRAFT_SPEC.mddocumentation.
#Two-pass architecture
Before, Prism used a single-pass architecture: it walked the token stream once, building the typedef table on the fly and emitting C as it went. Typedefs were popped from a mutable table on scope exit. Labels were scanned per-function right before emission. Goto safety was checked inline during code generation. It worked, but every edge case was a new special case bolted onto the emitter, and the emitter was already doing too much.
Now there are two distinct passes:
- Pass 1 walks every token at every depth before a single byte is emitted. It builds an immutable symbol table of every typedef, enum constant, parameter shadow, and VLA tag. It constructs a full scope tree: every
{/}pair with parent links and classification (loop, switch, function body, statement expression). It collects every label, goto, defer, case, and declaration into per-function entry arrays. Then a CFG verifier checks every goto→label and switch→case pair against defers and declarations. If your code is unsafe, Prism errors before writing a single byte. - Pass 2 is purely mechanical: it reads the immutable tables from Pass 1 and emits C. No typedef mutations, no scope tracking, no error decisions. Just output.
This separation closed entire categories of bugs. The old architecture had ~20 different places where the emitter had to make semantic decisions mid-output. Now it just reads annotations.
#Architecture Comparison
- Execution: Single-pass emitter → two-pass (7 analysis phases + CFG verification + emission)
- Symbol Table: Mutable typedef table with scope-depth popping → immutable symbol table with token-position-based range lookups
- Label Scanning: Per-function label scan at emit time → all labels/gotos/defers collected in Pass 1, verified in Phase 2A
- Goto Safety: Inline goto safety checks during emission → O(N) snapshot-and-sweep CFG verifier with hash-based label lookup
- Scope Tracking: Ad-hoc scope tracking → full scope tree with parent links, classification, and ancestor queries
#Scale
- The Codebase: 13,132 insertions and 7,287 deletions across 287 commits. The core transpiler grew from 5,839 to 13,494 lines of C (
prism.c+parse.c+windows.c) to accommodate the deep semantic analysis phases. - The Test Suite: Expanded from ~1,000 to 3,795 tests across 9 suites (adding over 37,916 new lines of test code).
- CI/CD Pipeline: Now continuously tested across 6 diverse platforms: Linux x86_64, macOS x86_64, macOS ARM64, Windows build-only, Linux ARM64, Linux RISC-V64.
- Self-Hosting: Prism transpiles itself, the output compiles, that binary transpiles Prism again, and the two outputs match byte-for-byte.
#Documentation & Specs
- SPEC.md: 785-line transpiler specification. Every item corresponds 1:1 to implemented behavior exercised by the test suite. This is not aspirational: it describes what the transpiler does.
- DRAFT_SPEC.md: Ideas under consideration. Nothing here is implemented. Items may be adopted, modified, or rejected. Once implemented, they move to
SPEC.md.
#v0.110.0
Prism v0.110.0 adds native Windows support (MSVC), performance gains, and hardening across the transpiler and parser.
#1.60× faster end-to-end.
| Version | Mean | Range |
|---|---|---|
| v0.105.0 | 317.4 ms ± 7.0 ms | 303.7 ms … 330.7 ms |
| v1.1 | 198.0 ms ± 3.1 ms | 192.0 ms … 207.6 ms |
Full compile pipeline, prism prism.c -o /dev/null, 30 runs (hyperfine)
- Binary size (Linux): 79K → 99K (+25%)
- Tests: 1,032 → 1,276 (+244)
#Performance
- Fixed ~10× performance regression in token walker (750179f)
- Pipe-based preprocessing: transpiler pipes directly to system compiler, skips preprocessor on second pass (f194f56)
- Walker brace optimization + inline hot functions (
emit_tok,new_token, walker) (76872b4) - Tag dispatch for faster token classification (76872b4)
- Big optimization pass with fast paths throughout (c4104bb, cc44676)
#Fixed
- Label zero-init bypass:
:now treated as statement boundary (eac1eb1) - POSIX macro force-override: checks user
#defines before appending defaults (eac1eb1) typeof(volatile)memset: scans insidetypeof()for volatile qualifier (eac1eb1)- Block comment and raw string newline tracking (29c951b)
scan_line_directiveinteger overflow (29c951b)emit_expr_to_semicolonternary:safety via depth tracking (29c951b)scan_pp_numberrejecting'before non-hex letters (e2a27af)- Silently skipped zero-init in edge cases (48a4830)
- Void typedef return bug (b9b6883)
- Hashmap zombie resize corruption (2e29aca)
make_temp_filetemplate truncation (2e29aca)- Typedef de-duplication (949f805)
typeofvars hard limit removed: dynamic allocation (6b72b4f)assert.hemitted twice (f58464f)path_basenameseparator ordering (3c2e355)- Pipe deadlock (949f805)
__builtin_memsetused withoutstring.hdependency (949f805)- Parser depth limit to prevent stack overflow (5ec9f63)
- And many more
#Hardened
ENSURE_ARRAY_CAP:int→size_toverflow prevention, capacity reset on OOM (0823077)new_file: free contents before error return,strdupNULL check (0823077)argv_builder_add:strdupNULL check (f58464f)tokenizer_teardown: NULL guard oninput_files(228561a)system_includes_reset: NULL guard onsystem_include_list(228561a)error(): NULL guard onctxinPRISM_LIB_MODE(29c951b)warn_toksuppressed in lib mode (eac1eb1)- Lib mode OOM leak:
init_keyword_mapmoved beforepp_bufallocation (0823077) - TOCTOU removed in
make_temp_file(0823077) realloc/setjmphandling hardened (6b72b4f)- Hashmap tombstone handling (5ec9f63)
- File view cache memory fix (1e66c01)
- Per-transpilation memory leak in lib mode (1e66c01)
#Testing
- 1,276 tests (up from 1,032)
- Regression tests for K&R defer, label zero-init,
typeof(volatile), raw*ptrdeclarations, VLA zero-init,scan_pp_number, typedef edge cases (e2a27af, 48a4830, 0823077, eac1eb1, 5ec9f63)
#Misc
- K&R-style functions now get
deferand label support (eac1eb1)
#v0.105.0
Prism v0.105.0 is a consolidation release: harden the core and shrink it. It refactors large parts of the parser for speed and maintainability, and closes control-flow safety holes.
- Code (prism.c + parse.c): 7,649 → 5,518 LOC (-27.9%)
- Binary size (Linux): 107K → 79K (-26.2%)
#Fixed
gotoskipping for-init declarations during mixed CLI flags- Scope-leak bugs hardened
deferedge cases bulletproofed
#Changed
- Unified token walker with speedup in classification (1dabdde, 99568a6)
- Tag-based token system with unified declaration parser (a06201b, ee08aea)
- Table-driven CLI parsing, token spacing, and loop handling (ee08aea, d09b376, 23deede)
- Merged control-flow state tracking (1d17dd4, 85f12cf)
- Algebraic token spacing logic (23deede)
- Simplified VLA logic (a1808d0, 6cc5aa0)
- Simplified UTF-8 identifier handling (2d99fff)
- Simplified output buffering with
setvbuf(06b7886) - Simplified feature flags and control state reset (85f12cf)
- Merged type parsing (b5b1525)
- Merged VLA and defer handling (6cc5aa0)
- Unified temporary file creation (d09b376)
- Consolidated switch-scope handling (23deede)
- Merged goto skip logic (d09b376)
- Less verbose error messages (18a9980)
#Removed
- Parsing not needed for Prism core (967aa9d)
- Dead typedef fallback code (23deede)
- Old CLI logic (5e9453b)
- Duplicate array handling code (18a9980)
- Duplicate error handling (1d17dd4)
- Refactored globals towards thread safe lib mode (e67265b)
#Testing
- Added 823 lines of test coverage
- Total: 1,032 tests
#v0.100.0
First single-pass version of Prism. Unsafe early prototype; do not use.