GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | ||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | ||||||||||||
GeezerVison is showing github.com — Combine try, catch and finally blocks soundly by CatarinaGamboa · Pull Request #366 · liquid-java/liquidjava Conversation
|
| Point | main assumes |
This PR assumes |
|---|---|---|
| (1) start of catch | y == 2 (the whole try ran) |
y == 1 ∨ y == -1 ∨ y == 2 |
| (2) after the try statement | y == 3 (the catch always ran) |
c ? y == 2 : y == 3 |
| (3) start of finally | y == 3 |
any of the above |
Walkthrough
catch: while checking the try, every new value of y is recorded (-1, 2). The catch starts from "the value before the try or any recorded value". So @Refinement("_ > 0") int a = y; placed at (1) is now an error; on main it passed.try statement: the try and the catch are joined like an if/else on an unknown condition c, reusing the if machinery. So @Refinement("_ == 3") int b = y; placed at (2) is now an error.finally: it starts from any value seen in the try or the catch. After it, checking continues from the joined state (2), plus whatever the finally assigns.Path conditions from inside a branch (e.g. if (x <= 0) return; in the try) are also dropped before the next branch, the finally and the code after, since the exception may come before the check.
TryChecker: holds the try/catch/finally logic. The existing try-with-resources code moved there from RefinementTypeChecker, which is now smaller than on main.Context: startRecordingInstances / stopRecordingInstances record the new values variables get inside a block.try_catch_error and try_catch_correct, 13 cases each.finally that updates a variable from itself (y = y + 1) starts from every possible value. This is sound but imprecise.ifs.x = 1; risky(); followed by a catch requiring x == 1. Exception-point-aware snapshots would improve precision.🤖 Generated with Claude Code
Sorry, something went wrong.
There was an error while loading. Please reload this page.
540db82 to
4340fb3
Compare
try/catch blocks were checked one after the other, so after them only the last catch path was considered and errors on the try path were missed. - The try and catch blocks are visited like an if-else-if chain with unknown conditions, and each variable is combined after the join with the existing if machinery. - An exception may leave the try block after any of its statements, so a catch block starts with each variable in any of the states it had before or during the try block (instances are recorded while visiting it). - The finally block starts with any of the states of the try and catch blocks; after it, the normal completion state continues, updated with what the finally block changed. - Path conditions from inside a branch do not leak into later branches, the finally block, or the code after the try statement. - Catch parameters get an instance so refinements referring to them outlive the catch block. Co-Authored-By: Claude Opus 5.5 <[email protected]>
4340fb3 to
6559819
Compare
The Apache Derby shape behind DERBY-2472: exceptions caught in a loop are chained with initCause, but their constructor may already have set the cause, so the call can throw "Can't overwrite cause". The catch parameter's state is unknown, so the call is reported. Co-Authored-By: Claude Opus 5.5 <[email protected]>
There was an error while loading. Please reload this page.
An `if (again) { r.reopen(); }` following `r.close()` previously allowed
`r.use()` even though the skipped branch leaves `r` closed. The join
used the condition's refinement relation as its guard, which made the
then branch appear selected whenever that relation held.
Keep expression value constraints separate from the boolean guard used
for branch assumptions and state joins. Unannotated calls and calls with
constraints independent of their return value retain both possible
outcomes. Preserve the value constraints after the join without
asserting that the condition is true.
Regression examples cover unsafe boolean variables, refined calls,
unannotated calls, and calls with independent constraints; passing
examples cover known true and false values, negation, repeated guards,
and safe joins with and without an explicit else.
Validation: `./mvnw -pl liquidjava-verifier -am test
-Dsurefire.failIfNoSpecifiedTests=false` passes on main including #366.
All five new fixtures also pass when run directly after rebasing.
Fixes #371.
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
Soundness: a catch block is checked as if the whole try block had completed
There was an error while loading. Please reload this page.