Repository navigation
Description
@solid-eureka #92 added a static, whole-program property pre-check (originally #73). It runs inside the executor jar today, but it is static analysis, not symbolic execution, so it belongs in the static pre-analysis step. Can you take a look at whether and how it fits into your analysis?
What it does
- The class:
de.uzl.its.swat.staticcheck.ExceptionFreedomCheck(ASM, about 2k lines insymbolic-executor/.../staticcheck/). It analyses the task classpath, starting fromMain.main, and proves or fails to prove two things:exception_free: no RuntimeException escapesmainor a static initializer that may run. This is theno-runtime-exceptionproperty.assertion_free: no reachable code creates an AssertionError. This isvalid-assert.
- The driver:
svcomp/PropertyPreCheck.pyruns it before round 0, as a subprocess with a 60 s timeout. It reads one JSON line from stdout. - When the property is proven: the verdict is SAFE without exploration. SWAT still runs round 0 as a cross-check; a violation there gives UNKNOWN.
- When it is not proven (including a crash or timeout): the run is unchanged.
- Cost: about 0.2 s CPU and 60 MB per task.
- Effect: +470 points on the 1713-run set, with 0 wrong verdicts. It proves 426 no-runtime-exception tasks and 3 valid-assert tasks. Most of them could never be closed by exploration: MinePump, float_unboundedloop and jayhorn-recursive run into timeouts.
How it proves it (conservative everywhere; unknown → not proven)
- Closed world. Every call goes to application code, to a modelled
Verifiermethod, or to a whitelisted JDK method (JdkModel). Method selection over-approximates the JVM's choice, including default, private and package-private methods. Reflection,invokedynamic(except string concatenation) and method handles fail the check. - Instructions. Every instruction that can throw a RuntimeException (array access, field access, invoke, checkcast, division, athrow, …) must be proven safe or be covered by a handler. The proof uses an abstract interpretation that tracks nullness, int and long constants, array lengths, enum ordinals and exact types.
- Fixpoints. Field, parameter and return facts, and may-throw, are least fixpoints over all application methods.
- Classpath guard. Classes in platform packages fail the check.
- Review. Adversarial reviews found 17 counterexamples. All are fixed and kept as regression programs in
ExceptionFreedomCheckSpec.groovy.
Moving it into the SA: what we found
-
Option (b), move the ASM pass as is into cfg-extraction, as its own entry point. It needs no SootUp view and does not use the CFG graph.
- The unchanged sources compile against the extractor jar, which already bundles ASM, and give identical results on all 998 tasks.
- Effort: about 1–2 days.
- None of the extractor review items have to be fixed first.
- It needs the extractor jar in the SV-COMP archive (it is not shipped today), a pinned version, and a choice of test framework for the regression programs: Spock or JUnit.
-
Option (a), a port onto SootUp. This is possible, but SootUp's building blocks are too loose for a SAFE proof:
- dispatch picks a single target and gets private and package-private selection wrong;
ForwardFlowAnalysisdoes not carry state into exception handlers;- any
TypeHierarchyquery loads the whole JDK, about +4.5 s and +1.25 GB; - Jimple's local types are wrong after javac reuses a slot.
At least 8 of the 17 known counterexamples would come back by default. Estimated effort: 2–3k lines plus new review rounds.
-
The interface should stay as it is: one JSON line on stdout, plus tool and version fields. On the SWAT side only
PropertyPreCheck.command()changes. The result should not go into_metadata.json: the graph SA is now valid-assert only, while most proofs are for no-runtime-exception.
A broader idea
The pre-check is the extreme case of what the SA already does for asserts: it finds the property-relevant program sites, and proves there are none. Generalised:
- The SA computes the relevant sites per property.
- Under valid-assert: the asserts.
- Under no-runtime-exception: the instructions that may throw and that the value analysis cannot discharge.
- SWAT then:
- gives SAFE if there are no such sites;
- otherwise prunes exploration to the branches that can reach them. That would make SA usable under no-runtime-exception too;
- downgrades on precision or context loss only when the loss occurs on a path to a relevant site. This targets the 55 runs that Static property pre-check: SAFE for programs proven free of RuntimeExceptions or AssertionErrors #73 turned from UNKNOWN into SAFE, mostly float-nonlinear. The effect is not measured.
Questions
- Is an ASM pass acceptable in cfg-extraction, as a separate entry point?
- Will the cfg-extraction jar ship in the SV-COMP archive, and how is its version pinned? Could the pre-check ship before the graph SA is ready?
- Who reviews the soundness of changes after the move? A false SAFE costs −32, and this proof skips all of SWAT's downgrades.
- Is the "relevant sites per property" interface something you want to build towards?
🤖 Generated with Claude Code
Activity
solid-eureka commentedon Oct 23, 2026
@nils-loose Thanks for the write-up. Short version: I agree with option (b), with rejecting (a), and with the broader idea. I'd add one concrete link between the two analyses, and one caution about the downgrade idea.
Option (b): move the ASM pass as is into cfg-extraction
Agreed.
BranchKeys, which checks SootUp's ordinals against the bytecode).Option (a): a port onto SootUp
Agreed that it should not be done. Our experience with the graph SA supports it; on top of your list:
getStmts()order is not bytecode order, not even within one source line (e.g. for a switch on a String, or javac'sfinallycopies);exceptionalSuccessors()is unordered.The graph SA can live with that, because it marks what it cannot prove as unknown and checks itself against the trace. A proof that skips every downgrade cannot afford it.
Interface
Agreed: keep the one JSON line, and keep it out of
_metadata.json.The broader idea: relevant sites per property
Agreed. This is where the SA was meant to go. Two additions:
?Exception). That leaves next to nothing to prune.owner.name(desc)|line|kind|ordinal, with ordinals checked against the bytecode). So the check could export its "cannot throw" facts by key, and the extractor could drop those exception edges.Your questions
tests/check_marking_on_graphs.py).🤖 Generated with Claude Code
solid-eureka commentedon Oct 24, 2026
Step 1 of the plan above (move the check as in (b)) is implemented. It is on its own branch in both repositories:
issue-93-precheck-in-cfg-extraction, based onniklas/dev9f71629Run the property pre-check from cfg-extractionb8ff4baShip the property pre-check jar in the SV-COMP archiveissue-93-precheck-in-cfg-extraction, based onfind-assertions825c43eAdd the property pre-check from SWAT as its own entry pointWhat changed
ExceptionFreedomCheckand its four helper classes moved unchanged, except for the package (de.uzl.its.cfg.staticcheck). It is a separate entry point and builds no SootUp view (about 0.13 s per task).toolandversionfields (git describe at build time).precheckJartask buildscfg-extractor-precheck.jar(260 KB) with only the check and ASM. That is what the archive ships, so no SootUp licenses have to be listed. The fat jar contains the check too; its resolved dependencies are unchanged.scripts/precheck_gate.pyis the release gate from point 3: it runs the check over sv-benchmarks and fails on any proof that contradicts an expected verdict.PropertyPreCheckrunscfg-extractor-precheck.jar: from--precheck-jar, else next tosymbolic-explorer/(archive layout), else../cfg-extraction/build/libs/(workspace). Without it the check is skipped asmissing_jar(not proven). The cfg-extraction version is recorded instats.json.cfg-extraction.refpins the cfg-extraction tag. CI checks cfg-extraction out at that tag and builds the jar.package-svcomp.shrequires it and ships it ascfg-extraction/cfg-extractor-precheck.jar; the license file lists its ASM.check-svcomp-package.shalso runs the pre-check in the competition image.Verification
AbstractChart_exceptionlessis proven and answered SAFE, both in the workspace and from a locally built archive. The archive's smoke test passes.check-svcomp-package.shitself (no Docker on the machine I used).Three decisions left before this can be merged
CFG_EXTRACTION_TOKENwith read access to it. The alternative is to make cfg-extraction public. Without either, the build job fails at "Check out cfg-extraction" (run), and the test and package jobs, which depend on it, are skipped. Pull requests from forks do not get secrets, so their builds would fail the same way.cfg-extraction.refnamesv0.1.0, which does not exist yet. It has to be created (on825c43e, or wherever the cfg-extraction branch ends up after review) and pushed. Before every later move of the ref,precheck_gate.pyhas to pass.Steps 2 and 3 of the plan (shared instruction keys between the check and the graph, then pruning under no-runtime-exception) build on this and are not started.
🤖 Generated with Claude Code
solid-eureka commentedon Oct 24, 2026
Steps 2 and 3 are implemented too, plus fixes for two walk desyncs found along the way. Everything is in PR #108 (into
dev, also merged intoniklas/dev) and the cfg-extraction branchissue-93-precheck-in-cfg-extraction.Step 2: shared instruction facts
ExceptionFreedomCheck.siteFacts,--siteson the command line):throw, monitors,arraylength, calls) are grouped by method, line and kind. A group is safe only if every instruction in it is.Main.mainuse the check's facts. Everything else only uses what each method shows itself, which holds in any world.cannotRaise(RaiseSites). A line is only used if its site kinds match the bytecode, which guards against SootUp's occasional wrong line numbers. Over 998 tasks: 99.95% of statement sites matched, 86% are safe, and 564 guards are safe. The graph structure is identical with and without facts.SAGraphgives the exception side of a guard that cannot raise a dead node.Step 3: pruning under no-runtime-exception
runtimeExceptionPointIds: every site that may raise, plusuncaught[T]nodes of RuntimeException types. SWAT chooses the target list by property.<clinit>are masked, and unchecked pairings are never pruned.mainas anExceptionInInitializerError, an Error, so it is not a violation.svcomp test run --sa-no-runtime-exception.Walk desyncs (also under valid-assert)
UtilInstrumented.refEqualschanged in the dev merge, but the extractor still modelled the old two-branch helper. Its branch count depends on runtime types, so the executor now flags the helper's branches like callbacks, and the graph only has the comparison's ownif.Verifier.nondetObject: the executor runs it asfactory.createObject(). The extractor now expands and keys it that way.Valid-assert with SA (978 tasks): desyncs 73 → 0, correct verdicts 812 → 814.
sa-soundness: 114 → 115.Still open
The three decisions from my previous comment: the
CFG_EXTRACTION_TOKENsecret (or making cfg-extraction public), thev0.1.0tag, and a license for cfg-extraction. CI on the PR fails at the cfg-extraction checkout until they are settled.🤖 Generated with Claude Code
Run the property pre-check from cfg-extraction (#93)
Ship the property pre-check jar in the SV-COMP archive (#93)
Cut the exception side of guards that cannot raise (#93)
Prune by the graph under no-runtime-exception too (#93)
CI: check out cfg-extraction without a token (#93)
CI: check out cfg-extraction without a token (#93)
solid-eureka commentedon Oct 27, 2026
The three open decisions are settled:
CFG_EXTRACTION_TOKENline is removed from the workflow (af9fb0f on Move the property pre-check to cfg-extraction; prune under no-runtime-exception (#93) #108's branch, also in Relevance (slicing) and loop summaries; rebased onto svcomp27/overnight-solver-perf #109).v0.1.0exists (cfg-extraction master: the pre-check plus the license).v0.2.0adds the data-flow facts for the relevance analysis in Relevance (slicing) and loop summaries; rebased onto svcomp27/overnight-solver-perf #109, with an unchanged pre-check; Relevance (slicing) and loop summaries; rebased onto svcomp27/overnight-solver-perf #109 pins it.precheck_gate.pypassed at both tags (998 tasks, 0 contradictions).LICENSEfrom GitHub's MIT template). Both jars carry it, so the SV-COMP archive ships it with the pre-check jar.cfg-extraction's
issue-93-precheck-in-cfg-extractionis merged into its master. What's left is merging #108 (or #109, which contains it).🤖 Generated with Claude Code