Fix the five fuzzer crashes in #3892 - #3906
Merged
Merged
Conversation
aleksisch
force-pushed
the
aleksisch/fix-fuzzer-bugs
branch
6 times, most recently
from
September 4, 2026 15:23
e9845ca to
276bf99
Compare
…ructure
A template's field types are never inferred, so the unresolved call in the
fuzzer's `r<iterator<a>[@{0=(0,C())}]>` reaches deriveAliases with a null `func`,
and its ExprMakeBlock reaches the coverage pass outside of any function. Both get
the !isTemplate gate RunFolding and ast_verify already carry.
Refs #3892
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A sealed or override declaration set parentType from `type->isAuto()`, which is wrong when the field it replaces was declared earlier in the same body: infer looks that name up in the parent, gets nullptr and dereferences it. Gate it on the flag that says the type came from the parent. Refs #3892 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Const folding elides a call to an empty pure function by returning nullptr, which only ExprBlock knows how to drop; as `debug`'s argument the null survives and ExprLooksLikeCall::visit walks into it - the fuzzer's SIGSEGV. AOT emits an undefined `cast<void>::from`, and the JIT gives up. Reject it where a void function argument is already rejected. Refs #3892 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`is_argument` asks the enclosing function whether a name is one of its arguments. Reached through a type declaration - a field's array dimension, say - there is none, so `func` is null and findArgument dereferences it. Having a function is a precondition for that branch, so it joins the condition. Refs #3892 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`goto <expr>` in a captured block emitted a switch over every label in every enclosing scope, so the lambda jumped to a label outside itself and the C++ did not compile. Stop the scan at the closure, as InferTypes::findLabel does; the default arm then throws, which is what the interpreter does. The scan also ran outermost-first: the two reverse() calls around it resolved to linq's pure reverse and copied, so walk `scopes` by index instead. Refs #3892 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`default<int[2000000000]>` parses as an ExprMakeStruct and nothing on the make path sizes it, so it reaches AllocateStack and trips `size <= 0x7fffffff` - or, with the assertion compiled out, hangs. lint already carries that check for a local, argument, field, global, `new` and ascend, and runs before allocateStack; give ExprMakeStruct the same one. Refs #3892 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
visitForSource synthesizes `each(source)` and infers that call, checking only that the source has a type. A source whose dim never reduced to a constant - an [expr] type - then reaches inferArguments, whose assertion says the caller owes that check. Release reports the same diagnostics either way, so it shows only in a build with assertions. Refs #3892 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
das keys a table by the value type's workhorse, and the interpreter and the JIT both use it. AOT used the C++ type: equality through overload resolution, which for BigEntityId is a vector rather than a bool, and hashing over the object's bytes, which disagrees with hashing the workhorse. AOT is per-function, so one table is reached by both tiers - test_cross_tier_table_hash.das is that program. KeyHash mirrors KeyCompare and every table hash site routes through it, the rehash on grow included. BigEntityId declares float4, its das type, instead of the vec4f register, which the JIT has no mapping for. Refs #3892 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aleksisch
force-pushed
the
aleksisch/fix-fuzzer-bugs
branch
from
September 4, 2026 15:32
276bf99 to
ab67153
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Behavior change:
debug()of a void expression no longer compiles (error 30107).This fixes the five crashes reported in #3892. Each one is a null dereference on a path a normal program never reaches, and each is fixed at the layer that owns the invariant. One commit per bug, each with a test that fails without its patch.
Three are guards. The aliasing pass walked into the field types of a
struct template; inference never types those, so it readresultoff an unresolved call's nullfunc. It now skips template structures, which is what const folding, export and annotation binding already do. Asealedoroverridefield that redeclares a field from the same structure body was marked as taking its type from the parent, and inference then dereferenced a parent field that was never there; only an inherited field is marked now.typeinfo is_argumentasked the enclosing function about a name while inferring a type declaration, where there is no enclosing function; it answers false there, like the sibling branch beside it and like the 52 other null-funcguards in that file.debug()of a void expression is now rejected. No tier could carry it: it crashed the const folder, made AOT emitcast<void>::from(...), which has no definition, and made the JIT give up with "failed to get IR". It reuses 30107, the code already reported for a void function argument, so one existing fixture (tests/language/invalid_table_type_mix.das) grew that code in itsexpectline.The last one is codegen only. A computed
goto <expr>inside a captured block must fail at runtime, the way the interpreter does. AOT instead emitted a switch over every label in every enclosing scope, so the generated lambda jumped to a label outside its scope and the C++ did not compile. The label scan now stops at the closure, so the switch carries only labels the lambda can reach and its default arm throws. The scan also ran outermost-first: the tworeverse()calls around it resolve to linq's purereverse, which returns a copy, so the scope stack was never reversed.Where to look:
debug(<void>)is the only source-level behavior change. The AOT emitter change isdaslib/aot_cpp.das,visitExprGoto.Validation, claims, ledger
Validation
tests/language.visitExprGotomakes the emitted C++ fail to compile witherror: label 'label_6' used but not defined;tests/languageis compiled to C++ bytest_aot_subset, which is in ALL, so that is a build failure.debug()was probed with 15 argument shapes (block, function pointer, tuple, struct, array, table, variant, string, pointer,void?, float3, enum, bitfield, iterator, das_string). Each one either runs and emits AOT C++ that compiles clean, or is rejected earlier by the type system with its own diagnostic.voidis the only das type with nocast<>specialization, because it is the only one with no value to cast.untrackedpreflight gate is red on 12 files underweb/examples/glfw/andweb/test/glfw_dynlink/. They are unrelated work already in the worktree before this branch; none of them is in this PR.codexon PATH.src/parser/REVIEW.md, binds diffs that add syntax tods2_parser.yppords2_lexer.lpp; this one changesparser_impl.cpponly.Not done
goto label Nto a label inside the same captured block is still broken. The interpreter aborts the closure and the function that invoked it, and prints nothing. It is not one of the five reported crashes and nothing here touches it.🤖 Generated with Claude Code