fuzzer crashes from #3959: fixed-array count underflow, unchecked function result size, a piped-call null deref, and the ambiguous AOT cvt_ surface - #3989
Merged
Conversation
getCountOf64 multiplied the running product by uint64_t(fixedDim) for every node of a fixed-array chain. dimAuto is -1 and dimConst is -2, so an unresolved or already-rejected dimension wrapped the product, and the size that fell out read as enormous rather than unknown: pointer arithmetic on class<block[]> reached getSizeOf with 18446744073709551568, which is 2^64-48. Such a dimension now contributes no count at all, which is what a dimension of 0 - the other spelling the parser rejects - already did, and what the getSizeOf64(bool&) overload has always answered for the same input. The two disagreed before this: the flagged one said 0, the flagless one 2^64-48.
The size gate ran on locals, arguments, block arguments, structures, aliases, ascend and new, but never on a function result. A result too big to size therefore reached Program::simulate, where makeFunctionDebugInfo walks it and getSizeOf/getStride assert - and in a build with DAS_NO_ASSERTIONS it walked on with a truncated size instead. preVisit(Function*) now sizes the result the way preVisitArgument sizes an argument, skipping a result that is still auto or an alias, so a generic that has not been instanced is left alone. The fixture carries both shapes: the reported one, whose make-value on the path is diagnosed too, and one where nothing but the result carries the size.
aleksisch
force-pushed
the
aleksisch/fuzzer-fizes
branch
from
September 10, 2026 08:28
73d0e38 to
4b5d543
Compare
…ever resolves A piped call that lands past the first parameter pads the defaults of the parameters it shifts across. isFunctionCompatiblePipedAt admits the candidate on those defaults existing - it type-checks only the arguments the caller actually wrote - and tryPipedCallPadding then splices them into the call. So instancing is handed an argument lookup never matched, the alias loop gives up without progress, and inferGenericType answers null for it. The next statement writes through that null under DAS_ASSERTF, which DAS_NO_ASSERTIONS compiles out, so a stock Release daslang segfaults rather than reporting anything. The padded defaults are now matched once they are resolved, but only where the parameter type is still auto or an alias - a type that never resolves can neither be matched nor instanced, so the candidate is dropped and the call reports a lookup miss. A parameter with a concrete type deliberately stays: its mismatch is a real user error, and the instanced function names it far better (error[30161], both types spelled out) than a lookup miss would. Matching those too was tried and rejected for exactly that reason. With the null's only producer closed off, DAS_ASSERTF(resT, ...) keeps its meaning and stays: if some other path ever yields that null, it is a compiler defect and must be seen as one, not reported to the user as a fault in their code. The other assert on that path goes, because what it claims is false. The no-progress exit is reachable whenever a concrete parameter's default does not match, and falling through is what lets the instanced function produce error[30161]; asserting there fires on a legitimate diagnostic. failed_generic_unknown_arg_type pins the unresolved shape - without the lookup match it aborts on the resT assert - and failed_generic_default_type_mismatch pins the concrete one, which aborts if the deleted assert is put back.
aleksisch
force-pushed
the
aleksisch/fuzzer-fizes
branch
2 times, most recently
from
September 10, 2026 15:36
e7c5ae2 to
9b37275
Compare
Every vec2/vec3/vec4 flavor converts from vec4f, so the six cvt_pass overloads
could not be resolved once their argument was another cvt_'s result - which is
what the emitter writes for a conversion chain:
int3 v = cvt_pass(cvt_uint3(das_alias<Point3>::from(getSamplePoint3())));
A single conversion compiled and a constant chain folded away before codegen,
so only a runtime value through two conversions reached it.
cvt_pass collapses to one function taking and returning vec4f, which is the
identity a pass-through conversion should be. Retyping the cvt_ results to
their concrete vector type would also resolve the call and is the wrong trade:
vec4f is the SIMD register while vec2/vec3/vec4 are structs of scalars, so a
concrete return spills the lanes through v_extract_* and the next conversion
reloads them - one cvttps2dq becomes a dozen instructions of shuffles and
stack traffic. All twelve cvt_ keep their vec4f result.
vec_constructors runs the chains on globals the folder cannot reach, two links
and three; the AOT lane compiles the file, so the ambiguity is a build failure
there rather than a wrong value.
aleksisch
force-pushed
the
aleksisch/fuzzer-fizes
branch
from
September 10, 2026 15:41
9b37275 to
92e56e6
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.
Grammar-fuzzer crashes from #3959. Four defects, one commit each with its own regression fixture.
Closes #3959
What each commit fixes
ast:a non-positive fixed-array dimension is not an element count — issue item 6.getCountOf64multiplied byuint64_t(fixedDim)for every node of a fixed-array chain.dimAutois -1 anddimConstis -2, so an unresolved dimension wrapped the product and thesize read as enormous rather than unknown:
2^64-48for the reported repro. Such a dimensionnow contributes no count, matching what a dimension of 0 already did and what the
getSizeOf64(bool&)overload has always answered for the same input.lint:an oversized function result is diagnosed, not asserted on — issue item 1.The size gate covered locals, arguments, block arguments, structures, aliases,
newandascend, but never a function result, so an oversized result reachedProgram::simulatewhere the debug-info walk sizes it and asserts.
infer:a piped call stops padding in a default whose parameter type never resolves —issue item 5. This one was a SIGSEGV in stock Release, not only an assertion failure.
isFunctionCompatiblePipedAtadmits a piped-call candidate after checking only that theparameters it shifts across have defaults, never that their types match; instancing is then
handed an argument lookup never matched,
inferGenericTypeanswers null, and the nextstatement writes through it under an assert that
DAS_NO_ASSERTIONScompiles out.The fix is at the lookup, not the symptom: padded defaults are matched once resolved, but only
where the parameter type is still auto or an alias. A parameter with a concrete type is left to
flow through on purpose — its mismatch is a real user error and the instanced function reports
it far better (
error[30161], both types named) than a lookup miss would.DAS_ASSERTF(resT, ...)therefore stays: with its only known producer closed off it meanswhat it says again, and a null from any other path is a compiler defect that must surface as
one. The other assert on that path is deleted, because its claim is false — the no-progress
exit is reachable whenever a concrete parameter's default mismatches, and falling through is
what produces
30161.aot:every cvt_ inline answers in its vector type, not vec4f — issue item 2.Every
vec2/vec3/vec4flavor converts to and fromvec4f, so acvt_answering invec4fis fine alone and ambiguous the moment its result becomes the next conversion'sargument:
x |> uint3 |> int3emitscvt_pass(cvt_uint3(..))and all sixcvt_passoverloads accept a
vec4fequally. A single conversion compiled and a constant chain foldedaway before codegen, so only a runtime value through two conversions reached it.
include/daScript/simulate/REVIEW.mdcarries the rule so acvt_added later cannotreintroduce it.
The two items with no code change
return) was already fixed by0e93e8a9c,which landed after the base the report was built from. The repro compiles and runs under
-jittoday.reported
5c734c4d6it does; the non-canonical node is abool[2]carryingconston boththe array node and its element. No live path was found here and no fix is guessed at.