Skip to content

v0.2.4

Latest

Choose a tag to compare

@github-actions github-actions released this 09 Aug 15:30
· 4 commits to trunk since this release

Added

  • New -Wint-conversion warning (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 constant 0. Does not cover
    file-scope/global initializers, which take a separate constant-evaluation
    path.

Fixed

  • A referenced extern global variable that is never defined anywhere
    resolved silently instead of erroring, and — more seriously — redeclaring
    the same global (across an extern declaration 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 the Obj at codegen time (gen_addr, src/codegen.c),
    but every declaration of a global created its own Obj
    (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-only Objs 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 from f(); int g=42; extern int g; likewise read 0; and
    linking two files where one declared extern int g; and the other
    defined int g=42; int pad=7; silently read pad'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 single Obj (merge_global_decl() in
    src/parse.c, keyed by a new per-TU global_decl_map) and propagating
    each canonical global's offset onto the alias Objs cc_link_progs
    drops (global_aliases array, populated in src/linker.c, applied in
    src/codegen.c's gen() right after the data-segment allocation loop).
    A referenced-but-never-defined global is now a hard undefined global: <name> compile error, mirroring the existing undefined 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 by posix_spawnp in the POSIX test suite) is
    now exposed via the same host-global accessor macro pattern as errno
    and stdin/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.
    See tests/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 a failures
    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_quote diagnostic 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/_BitInt tests that were already correctly
    rejected at compile time but had never been marked EXPECT_COMPILE_ERROR;
    1 extern-symbol test additionally marked CCCC_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 no main(). 1 file (an unreferenced extern global 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 that error_tok() produces, and never incremented
    vm->error_count
    — three Quote() validation diagnostics in
    quote_scan_and_rewrite()/quote_substitute()/quote_core()
    (src/reflection.c) used the location-less error(), so the test
    runner's has_compile_error check (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_ERROR tests as failures. Converted to error_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() in src/reflection.c called cc_parse_stmt() once
    and never inspected the leftover tokens), silently discarding the
    return and 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-safe Quote("{ ... }") 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. A RunCustom
    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 --build process, 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 unrelated expected ','. 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)