Skip to content

Fix small executor bugs: entry start in <clinit>, nested mains, constructor exceptions, reflection, multiarray rows, fp toString - #86

Closed
nils-loose wants to merge 10 commits into
svcomp27/integrationfrom
fix/executor-small-bugs
Closed

nils-loose wants to merge 10 commits into
svcomp27/integrationfrom
fix/executor-small-bugs

Conversation

@nils-loose

Copy link
Copy Markdown
Contributor

Fixes a set of small executor bugs. Each commit fixes one bug.

Changes

commit bug change
e9ae9b0 class name check rejects default-package names starting with L Util.checkClassName now rejects only real descriptors (;, (, )). The allowlist is gone, so LinkedListEntry passes too.
eaeb2d9 a main called from main re-runs Intrinsics.init (DuplicateThreadContextException) If the thread already has a context, init/terminate of an endpoint only count nesting. The outermost endpoint still ends the run.
b09b432 Verifier.nondet* in Main.<clinit> runs before the thread context exists In SV_COMP mode, the <clinit> of a class that declares static main(String[]) calls Intrinsics.initBeforeEntry. That call starts the execution. main's init then continues it and clears the initializer's locals.
ccd0536 ValueFactory.createObjectValue appends to ClassDepot's cached interface list on every NEW ClassDepot now returns unmodifiable views, and ValueFactory merges into a copy.
1624541 Field.get looks up the field by the method name get It now uses reflectFieldName, and boxes a primitive field.
81f8042 AASTORE of an ANEWARRAY String[] row into a MULTIANEWARRAY is dropped The row is copied into a StringArrayValue and stored. Writes through other references to the row do not reach the copy, so the store records a context loss.
30be08f an exception leaving super(...)/this(...) (or any <init> call inside a constructor) breaks the invocation stack The processor marks these invokes using the existing probe 420. Like abandoned <clinit>s, it closes them and their frames when an invoke below them ends or throws. A try/catch in the instrumentation is not possible: HotSpot rejects a handler that covers the uninitialized this ("flags are not assignable").
519c375 GETVALUE_primitive mismatch halts the executor It now resyncs to the concrete value and records a context loss. A SAFE result becomes UNKNOWN. Violations found on later paths are still reported.
3ee3cb4 DoubleValue/FloatValue.asStringValue not implemented Returns String.valueOf(concrete), and records a context loss if the value is symbolic.
f6c0a51 tests EntryAgentSpec (clinit input, nested main, exception from a super constructor), ClassDepotTypeInfoSpec, and asStringValue cases in FloatValueTest

FCMP NaN (fcmp-nan-unordered-not-encoded) is not part of this PR: #74 fixes it the same way.

Measurement

The config is the competition one: --sv-comp, 300 s, --mem15g, no SA. Runs are compared with baseline-results.jsonl.

The measured set has 1501 runs:

  • all 1043 baseline-correct runs;
  • 55 runs whose KB record names one of these causes;
  • 362 non-correct runs that took under 150 s;
  • 41 slow expected-VIOLATION runs and algorithms runs.

The float and argv families and every flipped run were re-run on the final head, after the FCMP commit was dropped.

Result: +46 gained, 0 lost, net +46. No new wrong verdict. One wrong verdict is already in the baseline (AlbersProjection_true valid-assert, false VIOLATION) and is unchanged.

task prop base new pts commit
argv-tasks/StdAudio_exceptionless NRE unknown SAFE +2 b09b432
argv-tasks/StdAudio_exceptionprone NRE unknown VIOLATION +1 b09b432
argv-tasks/PieSegment_true VA false VIOLATION timeout +16 b09b432 (see limits)
argv-tasks/PieSegment_false VA timeout VIOLATION +1 b09b432 (see limits)
securibench/Refl2 NRE / VA unknown SAFE / VIOLATION +3 1624541
securibench/Arrays10 NRE / VA unknown VIOLATION / VIOLATION +2 81f8042
securibench/Inter7 NRE / VA unknown SAFE / VIOLATION +3 30be08f
jbmc-regression/StringBuilderCapLen01 both unknown SAFE +4 519c375
jbmc-regression/StringStartEnd01 both unknown SAFE +4 519c375
jbmc-regression/StringIndexMethods01 both unknown SAFE +4 519c375
jbmc-regression/StringBuilderChars04 VA unknown VIOLATION +1 519c375
jbmc-regression/StringValueOf01 both unknown SAFE +4 3ee3cb4
float-nonlinear-calculation/MathSin VA unknown VIOLATION +1 3ee3cb4

Most commits fix a distinct crash signature, so each flip is attributed by the signature it removes. The two PieSegment flips are the exception: they were checked with a build without b09b432, which reproduces the base results.

These bugs are fixed but flip nothing at 300 s:

  • list1, list2 (class name check): they now time out because of path explosion (about 3^10 paths).
  • rtems lock-00-01-10 (nested main): the run now gets past init, but the target deadlocks in some rounds. The task's no-deadlock verdict is false, and the run ends in a stall.
  • StrictLineReader (clinit): UNKNOWN because of uncaught target exceptions, as the KB predicts.
  • SortedListInsert, aastore_aaload1 (ClassDepot): still time out.

Tests

  • gradle test: 8748 passed.
  • agentTest: 7 passed.

Known limits

  • b09b432 affects most tasks. Every Main with an assert has a <clinit> ($assertionsDisabled), so the trace now starts there. Across the 1501 runs this changed only the PieSegment pair. In both, the solver picked different models:
    • PieSegment_true reaches the real assertion failure in base, so the expected verdict looks wrong. On this branch a model with a huge float makes the target loop forever.
    • PieSegment_false: the reverse.
    • Treat these +17 points as luck, not as a fix.
  • 81f8042 always records a context loss on such a store. A SAFE result with this pattern stays UNKNOWN.
  • 519c375 can hide model bugs. It turns model-bug crashes into context losses. The warning is still logged.
  • Unrelated explorer hazard. During development, a VerifyError in the target ("Unable to initialize main class") was reported as SAFE (== OK). A target JVM that fails to start should give UNKNOWN.

🤖 Generated with Claude Code

nils-loose and others added 10 commits October 18, 2026 20:58
Util.checkClassName rejected every name starting with 'L' as a type
descriptor, with an allowlist for two benign names. Class names such as
LinkedListEntry still failed. A reference descriptor always ends in ';',
which the check already rejects, so the leading-'L' clause and the
allowlist are dropped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Every method named main is an endpoint in SV_COMP mode, so a harness
whose Main.main calls another main (e.g. harness.Environment.main) ran
Intrinsics.init twice on the same thread and halted with
DuplicateThreadContextException. An endpoint entered while the thread's
context exists now only increments a nesting count, and its terminate
call decrements it; the outermost endpoint still ends the execution.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The JVM initialises the entry class before it calls main, so a
Verifier.nondet* call in Main.<clinit> (e.g. a static field initialiser)
ran before Intrinsics.init had created the thread context and halted
with NoThreadContextException. In SV_COMP mode the static initializer of
a class declaring static main(String[]) now calls
Intrinsics.initBeforeEntry, which starts the execution if none is
running; main's init then continues it and clears the initializer's
locals from the initial frame.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ValueFactory.createObjectValue appended the parents of a class to the
interface list ClassDepot returned, which is the cached list itself, so
every NEW grew it and the type checks after it scanned an ever longer
list (quadratic on allocation-heavy loops), and an unregistered class
would hit Collections.emptyList().addAll. ClassDepot now hands out
unmodifiable views and ValueFactory merges into a copy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
visitGET_FIELD_REFLECTION resolved the instance field by the invoked
method's name ("get") instead of the reflected field name, so the
class template gained an unknown field and ObjectValue.getField read
past the object's fields array. Use reflectFieldName as the setter path
does, and box the value of a primitive field for Field.get.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AASTORE into a MULTIANEWARRAY's ArrayArrayValue only accepted
AbstractArrayValue rows, but ANEWARRAY creates ObjectArrayValues, so
array[0] = new String[]{...} was dropped and later loads returned the
stale row. For String[][] the row is now copied into a StringArrayValue
and stored; since writes through other references to the row do not
reach the copy, the store records a context loss.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A constructor call inside a constructor (super(...), this(...) or a new
in the body) is instrumented without a try/catch, because the handler
could cover the uninitialized this, which the verifier rejects. An
exception leaving such a call therefore emitted no
INVOKEMETHOD_EXCEPTION, its invoke id stayed open, and the next
exception event halted the executor with "Invocation stack is broken!".
The processor now records these invokes from the probe the
instrumentation places before them and, like abandoned static
initializers, closes them and their frames when an invoke below them
ends or throws.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
visitGETVALUE_primitive asserted when the shadow value did not match
the value the JVM produced, which halts the executor, so one model bug
ended the whole run. It now continues with the concrete value and
records a context loss, since what the trace derived from the diverged
shadow value cannot be trusted: a SAFE becomes UNKNOWN, while
violations found on later paths are still reported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Value.asStringValue threw NotImplementedException for DoubleValue and
FloatValue, so StringBuilder.append(D/F) and String.valueOf(D/F) failed
even for concrete values. They now return String.valueOf of the
concrete value; there is no symbolic model of the conversion, so a
symbolic value records a context loss.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…exceptions

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
nils-loose added a commit that referenced this pull request Oct 19, 2026
DoubleValue/FloatValue.java: asStringValue is already #76's (concrete
String.valueOf, context loss when symbolic), the same as #86's.
FloatValueTest.groovy: keep #74's FCMP cases and #86's asStringValue cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nils-loose

Copy link
Copy Markdown
Contributor Author

Included in dev through #92 (merge commit 534c182), together with the fixes for interactions between PRs. This PR stays as the record of the change and its measured flips.

@nils-loose nils-loose closed this Oct 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant