GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Fix crashes on unresolved types and on empty for bodies by CatarinaGamboa · Pull Request #432 · liquid-java/liquidjava

Conversation

@CatarinaGamboa

@CatarinaGamboa CatarinaGamboa commented Oct 27, 2026

Copy link
Copy Markdown
Collaborator

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)

import org.example.missing.Cookie;
void createAll(Cookie[] cookies) {
    for (int i = 0; i < cookies.length; i++) cookies[i].create();     // #425
}
request.names().forEach(name -> System.out.println(name));            // #396, Request not on the classpath
NullPointerException: Cannot invoke "CtTypeReference.getSuperclass()" because "type" is null
  at Context.addVarToContext

Spoon leaves the type of the array element, and of a lambda parameter whose target type is unknown, unset. Context looks 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.addVarToContext and addInstanceToContext type such a variable as Object, so it has no supertypes and carries no information. I also removed a supertype lookup in addVarToContext that ran twice (the overload it calls already does it).

2. A write to a field declared outside the analyzed sources (#426)

import org.example.missing.Options;
opts.sampleSize = 1;      // also opts.sampleSize += 1, and java.awt.Point p; p.x = 3
NullPointerException: Cannot invoke "CtElement.getAnnotations()" because "element" is null
  at TypeChecker.getRefinementFromAnnotation
  at TypeChecker.checkVariableRefinements

The field has no declaration, either because its class is not on the classpath or because it is a library field (Point.x crashed 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, and updateGhostField dereferences the declaration. Two supertype lookups that AuxStateHandler repeated after addInstanceToContext are removed: addInstanceVariable already does them, and they would throw on an unresolved type.

3. A for loop whose body is an empty statement (#395)

for (int i = 0; i < counts.length; total += counts[i], i++)
    ;
NullPointerException: Cannot invoke "CtStatement.getElements(Filter)" because the return value of "CtFor.getBody()" is null

Fix: visitCtFor treats a missing body as an empty one. The condition and the update are still checked. while, do and 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 (=, +=, and Point.x) and ErrorUnresolvedFieldWrite.
  • CorrectEmptyLoopBody (for, while, do, for-each with ; as the body, plus a refined counter) and ErrorEmptyLoopBody (a for update 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:

crash error reported passed
main 20 (15 of the 53 originals) 43 11
this PR 1 60 13
  • 14 of the 15 crashes on the 53 originals are fixed, and all 5 on the fixed versions. The remaining crash is GCIS-125 GuessingGameServer: the file itself does not parse (if (socket.) followed by a commented-out body).
  • The 54 files that did not crash give exactly the same output as before.
  • Of the 19 files that no longer crash, 17 now report diagnostics and 2 pass. These diagnostics were hidden by the crash. They are expected bug reports (e.g. the Throwable initCause and BufferedReader typestate bugs at their recorded lines) or limitations that main already shows on other files, in code without unresolved types: Invalid refinement expression on char literals, ~ and .length (False error: a[i].length is rejected as an invalid refinement expression #348), Sorts Int and Bool are incompatible on new InetSocketAddress(...), Not Found Error: Constant 'X.Y' could not be found on a constant of an unresolved class (main gives the same), and an Error: null when the result of an unresolved call is passed to a refined parameter (reproduced on main without this PR).
  • The 2 files that now pass (Delos Gorgoneion, OpenMetadata PersonaContextBuilder) contain an initCause bug on an exception class that is not on the classpath. Its supertypes, including Throwable, are unknown, so the Throwable spec cannot apply.

Known limits:

  • When a lambda's target type cannot be resolved, JDT does not resolve the lambda's body, so violations inside it are not reported (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.
  • A write to a refined field of this file reached through an unresolved expression (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

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
CatarinaGamboa and others added 3 commits October 27, 2026 17:39
@CatarinaGamboa @claude
Type variables of unresolved types as Object
3be1b60
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 <[email protected]>
@CatarinaGamboa @claude
Check writes to fields declared outside the analyzed sources
dd7ca3d
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 <[email protected]>
@CatarinaGamboa @claude
Check for loops whose body is an empty statement
f80754b
`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 <[email protected]>
This was referenced Oct 28, 2026
Verifier crashes on ++ or -- on a field of another object (c.count++) #436
Open
Internal error "Expr cannot be cast to BoolExpr" on a local assigned from an array element (Res r = rs[0]) #437
Open
Soundness: a field write through another object of the same class (o.x = 5) is applied to this.x #438
Open
@CatarinaGamboa

CatarinaGamboa commented Oct 28, 2026

Copy link
Copy Markdown
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 visitCtFor. The new examples exercise the subsequent SMT query and a later violation, which guards against merely hiding the crash. The verifier suite passes locally at this head (./mvnw -q -pl liquidjava-verifier -am test). I found no additional issue within this PR's stated scope.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CatarinaGamboa

CatarinaGamboa commented Oct 28, 2026

Copy link
Copy Markdown
Collaborator Author

[Claude] Thanks for checking. No changes needed.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@CatarinaGamboa
CatarinaGamboa merged commit 6f0239d into main Oct 29, 2026
1 check passed

Uh oh!

There was an error while loading. Please reload this page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Uh oh!

There was an error while loading. Please reload this page.

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Verifier crashes on an array read whose element type cannot be resolved (classes not on the classpath)

Uh oh!

There was an error while loading. Please reload this page.

1 participant

@CatarinaGamboa
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.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.