Added
- New
-Wint-conversionwarning (part of-Wall) for an implicit
integer↔pointer conversion with no cast —warn_implicit_conversion()
(src/type.c) had branches for pointer↔pointer, integer↔integer and
float conversions, but no branch matched an int/pointer pair, so e.g.
const char *p = 'a';compiled silently and only failed later, at
runtime, if the resulting garbage pointer was dereferenced. Covers
assignment/scalar initialization,return, and prototyped call
arguments; suppressed for the null pointer constant0. Does not cover
file-scope/global initializers, which take a separate constant-evaluation
path.
Fixed
-
A referenced
externglobal variable that is never defined anywhere
resolved silently instead of erroring, and — more seriously — redeclaring
the same global (across anexterndeclaration and its definition,
within one translation unit or across separate ones) could silently read
the wrong global's value or the wrong constant offset entirely (#957).
Global variable references compile to a data-segment offset baked
straight into theObjat codegen time (gen_addr,src/codegen.c),
but every declaration of a global created its ownObj
(new_gvar/global_variable(),src/parse.c) with nothing reconciling
them — codegen's allocation loop gave each one its own slot, and
cc_link_progs(src/linker.c) canonicalized definitions across
translation units by name but never propagated the resulting offset onto
the declaration-onlyObjs it dropped from the merged list. Concretely,
before this fix:extern int g; int f(void){return g;} int g=42;read 0
instead of 42 fromf();int g=42; extern int g;likewise read 0; and
linking two files where one declaredextern int g;and the other
definedint g=42; int pad=7;silently readpad's value (7) instead of
g's (42), with the result depending on file order. Fixed by
canonicalizing every redeclaration of a global variable within a
translation unit onto a singleObj(merge_global_decl()in
src/parse.c, keyed by a new per-TUglobal_decl_map) and propagating
each canonical global's offset onto the aliasObjscc_link_progs
drops (global_aliasesarray, populated insrc/linker.c, applied in
src/codegen.c'sgen()right after the data-segment allocation loop).
A referenced-but-never-defined global is now a hardundefined global: <name>compile error, mirroring the existingundefined function: %s
check (suppressed, not deferred, under-c/--link, since there is no
name-based data relocation mechanism for globals);sizeof()of an
undefined extern global still compiles, since the reference is only
counted where codegen actually materializes an address. Two full
definitions of the same global (int g=1; int g=2;) are now also a
redefinition of '<name>'error, matching the pre-existing cross-TU
check — previously silently accepted, with the second initializer
winning.environ(used byposix_spawnpin the POSIX test suite) is
now exposed via the same host-global accessor macro pattern aserrno
andstdin/stdout/stderr(include/unistd.h,
src/stdlib/posix.c), since it would otherwise have started hitting the
new undefined-global error as an inert, host-disconnected guest global.
Seetests/test_extern_global_undefined.c,
tests/suites/test_suite_global_canonicalization.c,
tests/test_cross_tu_global_offset.c/_reversed.c, and
tests/test_global_redefinition.c. -
tests/failures/was silently excluded from test discovery, so every
test inside it — 41 files — never ran —discover_tests()
(tools/testing/discovery.py) filtered out any path with afailures
component, but nothing generated or consumed that exclusion elsewhere; it
quietly turned the directory into dead weight, including real regression
coverage for tickets #1, #78, #172, #194, #195, #357, #884. The exclusion
is removed and every file audited individually: 33 tests moved into
tests//tests/macros/(12 already correct as-is; 3 that needed the
__builtin_quotediagnostic fix below; 3 memory-tagging tests that needed
CCCC_FLAGS: --memory-tagging+EXPECT_RUNTIME_ERROR, since without the
flag they were passing by accident on reused-but-unvalidated memory
content; 14 error-recovery/_BitInttests that were already correctly
rejected at compile time but had never been markedEXPECT_COMPILE_ERROR;
1extern-symbol test additionally markedCCCC_C4_SKIP, matching
test_bytecode_link_unresolved.c's existing #565 rationale; 1 typedef
test rewritten from a bare non-42 return to the exit-42 assertion
protocol). 7 files deleted as invalid C predating the current test
conventions (a misunderstanding of the declaration-comma vs.
comma-operator distinction, and##at the start of a__VA_OPT__
argument, both rejected by GCC/Clang too) or an incomplete scratch
fragment with nomain(). 1 file (an unreferencedexternglobal that
resolves silently instead of erroring) deleted pending a separate ticket,
since fixing it is a VM-level design question, not a test fix. Also added
tests/test_va_opt_basic.c, since the audit found__VA_OPT__had no
surviving coverage anywhere in the suite. -
error()(no source location) never printed the "N error(s)
generated." summary thaterror_tok()produces, and never incremented
vm->error_count— threeQuote()validation diagnostics in
quote_scan_and_rewrite()/quote_substitute()/quote_core()
(src/reflection.c) used the location-lesserror(), so the test
runner'shas_compile_errorcheck (tools/testing/runner.py, which keys
off that summary text) couldn't distinguish "correctly rejected at compile
time" from "compiled fine and the program itself returned a non-42 exit
code" — the test harness misclassified three correct
EXPECT_COMPILE_ERRORtests as failures. Converted toerror_tok()with
the offending token, which also gives these diagnostics a real source
location instead of none. -
Quote()/QuoteN()templates no longer drop statements after the
first one — an unbraced multi-statement template like
Quote("if (!$1) $1 = f($2); return $1;", a, b)parsed only the leading
if(quote_core()insrc/reflection.ccalledcc_parse_stmt()once
and never inspected the leftover tokens), silently discarding the
returnand leaving the generated function falling through with an
undefined return value — a silent-miscompile-shaped footgun with no
warning or error. An unbraced multi-statement template is now
transparently wrapped in braces and parsed as a block, exactly like the
already-safeQuote("{ ... }")form; any template that still leaves
tokens unparsed after that (e.g. trailing garbage on an expression
template) is now a compile error instead of a silent drop. (#955) -
RunCustom's vendored shell now performs POSIX-correct quote removal,
backslash escaping, and$VAR/${VAR}expansion — the lexer
(src/build_shell.c) previously only recognized a quote as the very
first character of a word, and even then returned its interior
unprocessed: an embedded quote (pre'mid'post), a backslash escape, or a
delimiter inside quotes (a";"b) all passed through with the quote
characters still attached instead of being stripped. ARunCustom
command whose value needed embedded quotes — e.g.cccc -c=generated ... -DSOME_MACRO='"literal"'— therefore handed the child a
multi-character character-constant instead of a string literal, silently
converted to a pointer and dereferenced (SIGSEGV in the child process,
not the outer--buildprocess, which correctly reported the step's
failure and non-zero exit). The word reader now performs real quote
removal and backslash escaping per POSIX, plus$VAR/${VAR}expansion
from the process environment as a single non-re-split, non-globbed
literal chunk. (#954) -
The comptime declaration index no longer mis-names a declaration whose
segment contains a fixed-size array inside an anonymous struct/union body,
or a leading C23 attribute —segment_declarator_name()(src/macros.c)
finds a declaration's declared name by scanning forward for the first
depth-0[array-dimension group, but tracked[/]depth without
tracking brace depth. A member array inside an anonymous struct/union body
declared in the same statement as its own declarator (e.g.typedef struct { char n[32]; } A;) put that member's[at apparent depth 0, so the
declaration was indexed under the member's name (n) instead of its own
(A); a leading attribute ([[deprecated]] int dx;) hit the same[path
with no preceding token, so the declaration was never indexed at all.
Either way, something later needing the real name as a typename (e.g. using
it as another struct's member type) failed to resolve it and misparsed
with an unrelatedexpected ','. The scan now tracks brace depth (only
treating a[as an array dimension at brace depth 0) and skips a leading
[[ ... ]]attribute-specifier-seq as a unit. (#951)