Repository navigation
Fix crashes on unresolved types and on empty for bodies - #432
Merged
Merged
Conversation
When a class is not on the classpath, Spoon leaves the type of an array element of that class, and of a lambda parameter whose target type is that class, unset. Adding such a variable to the context crashed with a null dereference when looking up its supertypes, and would otherwise crash the SMT translation, which needs a sort for every variable. Such a variable is now typed as Object: it has no supertypes and carries no information. Violations around it are still reported. Fixes #425, fixes #396. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Writing a field whose declaration is not available, such as a field of a class that is not on the classpath (`opts.sampleSize = 1`) or of a library class (`point.x = 3`), crashed with a null dereference when reading the declaration's annotations. Compound assignments such as `opts.sampleSize += 1` took the same path. The write now stands in for the missing declaration, so it is checked like one to a field without a declared refinement, and the ghost field update only runs for fields declared in the analyzed sources, which are the only ones that have one. An instance whose type cannot be resolved is typed as Object, as variables already are, which also makes two supertype lookups repeated after adding an instance redundant. Fixes #426. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`for (...; ...; ...) ;` has no body, so `CtFor.getBody()` is null and looking for a `continue` in it crashed with a null dereference. A missing body is now treated as an empty one: the condition and the update are still checked. `while`, `do` and for-each loops with an empty statement body already verified; the regression covers them too. Fixes #395. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 28, 2026
Collaborator
Author
|
[Codex review] I checked the unresolved-type fallback at both variable insertion points, the missing field-declaration path for assignments, and the null body handling in |
Collaborator
Author
|
[Claude] Thanks for checking. No changes needed. |
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.
Four crashes found by verifying single files of real projects, where the project's other classes and its dependencies are not on the classpath. One commit per cause (#425 and #396 share one).
1. A variable whose type cannot be resolved (#425, #396)
Spoon leaves the type of the array element, and of a lambda parameter whose target type is unknown, unset.
Contextlooks up the supertypes of every variable it adds. Skipping that lookup for null types is not enough: the null type then crashes the SMT translation (TranslatorContextToZ3,type.isEnum()) as soon as the variable is part of a query (e.g.any(cookies[0])with a refined parameter). Fix:Context.addVarToContextandaddInstanceToContexttype such a variable asObject, so it has no supertypes and carries no information. I also removed a supertype lookup inaddVarToContextthat ran twice (the overload it calls already does it).2. A write to a field declared outside the analyzed sources (#426)
The field has no declaration, either because its class is not on the classpath or because it is a library field (
Point.xcrashed too). Fix: at the field-write call site, the write itself stands in for a missing declaration. It has no refinement annotations, so the write is checked like a write to an unrefined field. The ghost field update after a field write now only runs for fields declared in the analyzed sources: they are the only ones that have a ghost, andupdateGhostFielddereferences the declaration. Two supertype lookups thatAuxStateHandlerrepeated afteraddInstanceToContextare removed:addInstanceVariablealready does them, and they would throw on an unresolved type.3. A
forloop whose body is an empty statement (#395)Fix:
visitCtFortreats a missing body as an empty one. The condition and the update are still checked.while,doand for-each loops with;as their body already verified.Tests
CorrectUnresolvedArrayElement,CorrectUnresolvedLambdaParameter: the issue reproducers, plus an element passed to a refined parameter, so it reaches the SMT query. All crashed before.ErrorUnresolvedArrayElement,ErrorUnresolvedLambdaParameter: a violation after the unresolved construct, in the same method, is still reported.CorrectUnresolvedFieldWrite(=,+=, andPoint.x) andErrorUnresolvedFieldWrite.CorrectEmptyLoopBody(for,while,do, for-each with;as the body, plus a refined counter) andErrorEmptyLoopBody(aforupdate that breaks a refinement is still reported).mvn test: 504 tests, 0 failures.Results on real files
We ran the 53 real-world files from #388 (74 with the fixed versions, each verified on its own) on main (4a89df6) and on this branch:
GuessingGameServer: the file itself does not parse (if (socket.)followed by a commented-out body).initCauseand BufferedReader typestate bugs at their recorded lines) or limitations that main already shows on other files, in code without unresolved types:Invalid refinement expressionon char literals,~and.length(False error:a[i].lengthis rejected as an invalid refinement expression #348),Sorts Int and Bool are incompatibleonnew InetSocketAddress(...),Not Found Error: Constant 'X.Y' could not be foundon a constant of an unresolved class (main gives the same), and anError: nullwhen the result of an unresolved call is passed to a refined parameter (reproduced on main without this PR).Gorgoneion, OpenMetadataPersonaContextBuilder) contain aninitCausebug on an exception class that is not on the classpath. Its supertypes, includingThrowable, are unknown, so theThrowablespec cannot apply.Known limits:
request.names(() -> positive(0))passes on main too).obj.f++on a field of another object crashes on main as well, with or without unresolved types (a.y++,p.x++,-a.y). It will be handled separately.s.getA().f = -1) is now not checked: its declaring type is unknown. Main crashed there.Closes #425, #426, #396, #395.
🤖 Generated with Claude Code