Fix the five fuzzer crashes in #3892 - #3906
Open
aleksisch wants to merge 5 commits into
Open
Conversation
deriveAliases visits every structure's field types, a `struct template`'s
included. A template's field type expressions are never inferred, so an
unresolved call inside one -- the dim expression of `r<iterator<a>[@{0=(0,C())}]>`
in the fuzzer's repro -- reaches SourceCollector::preVisit(ExprCall) with a null
`func` and dereferences it.
Give AliasMarker a canVisitStructure that returns !isTemplate, mirroring its own
canVisitFunction isTemplate gate and ConstFolding's canVisitStructure, which
already exists for the same reason.
Refs #3892
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A sealed or override field declaration sets parentType from `type->isAuto()`, which is right when the field it replaces was copied down from the parent and wrong when the field was declared earlier in the same structure body. Infer then reads parentType, looks the name up in the parent, gets nullptr back and dereferences it. parentType means "this field's type comes from the parent's field", so gate it on the flag that says so. Refs #3892 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
No tier can carry it. Const folding elides a call to an empty side-effect-free function by returning nullptr, which only ExprBlock knows how to drop; as the argument of `debug` the null survives and ExprLooksLikeCall::visit walks into it one pass later -- the fuzzer's SIGSEGV. AOT emits `cast<void>::from(...)`, which has no definition, and the JIT gives up with "failed to get IR". `debug(v())` is the only spelling that puts a void call in a subexpression position, so reject it where a void function argument is already rejected, with the same code and the same wording. 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 structure field's array dimension, say -- there is no enclosing function, so `func` is null and findArgument dereferences it. The sibling branch one line down already answers false when the subexpression is not a var; answer false here too. Refs #3892 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A `goto <expr>` inside a captured block emitted a switch over every label in every enclosing scope, the outer function's included, so the generated lambda jumped to a label that is not in its scope and the C++ did not compile: "use of undeclared label 'label_6'". Failing at runtime there is the intended behavior - the interpreter throws "jump to label N failed" - so AOT has to reach the same runtime failure instead of failing to compile. Stop the label scan at the closure, the rule InferTypes::findLabel already follows. The switch then carries only the labels the lambda can reach, and its default arm throws. The scan also ran outermost-first: the two `reverse()` calls around it resolve to linq's pure `reverse`, which returns a copy, so `scopes` was never reversed. Walk it by index instead, so "stop at the closure" means the innermost one. Refs #3892 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
aleksisch
force-pushed
the
aleksisch/fix-fuzzer-bugs
branch
from
August 30, 2026 13:32
897a4d0 to
48f429a
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