Repository navigation
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
Conversation
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>
Contributor
Author
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.
Fixes a set of small executor bugs. Each commit fixes one bug.
Changes
LUtil.checkClassNamenow rejects only real descriptors (;,(,)). The allowlist is gone, soLinkedListEntrypasses too.maincalled frommainre-runsIntrinsics.init(DuplicateThreadContextException)init/terminateof an endpoint only count nesting. The outermost endpoint still ends the run.Verifier.nondet*inMain.<clinit>runs before the thread context exists<clinit>of a class that declaresstatic main(String[])callsIntrinsics.initBeforeEntry. That call starts the execution.main'sinitthen continues it and clears the initializer's locals.ValueFactory.createObjectValueappends to ClassDepot's cached interface list on everyNEWField.getlooks up the field by the method namegetreflectFieldName, and boxes a primitive field.ANEWARRAYString[]row into aMULTIANEWARRAYis droppedStringArrayValueand stored. Writes through other references to the row do not reach the copy, so the store records a context loss.super(...)/this(...)(or any<init>call inside a constructor) breaks the invocation stack<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 uninitializedthis("flags are not assignable").GETVALUE_primitivemismatch halts the executorDoubleValue/FloatValue.asStringValuenot implementedString.valueOf(concrete), and records a context loss if the value is symbolic.EntryAgentSpec(clinit input, nested main, exception from a super constructor),ClassDepotTypeInfoSpec, andasStringValuecases inFloatValueTestFCMP 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 withbaseline-results.jsonl.The measured set has 1501 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.
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:
Tests
gradle test: 8748 passed.agentTest: 7 passed.Known limits
Mainwith anasserthas 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:VerifyErrorin 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