Conversation
Parameterized tables for every binary operator in Int and BV arithmetic modes, all literal nodes, and compound expressions; dedicated tests for casts, instanceof, conditional/ite, array/null comparison special case, \result, \old/old (pre-state environment), name/field handler dispatching, JML infix operators, chained comparisons, JML quantifiers (forall/exists, range guards, array and non-primitive binders, bound-variable shadowing), array creation (length assertion vs. initializer), and constructor default handling. Also fixes a latent bug: the JML keyword form \old(x) is lexed with a leading backslash in the method name and was falling through to the call handler, silently becoming an unconstrained constant. Both 'old(x)' and '\old(x)' are now resolved against the pre-state environment.
VC-generation stage suite (VcgSemanticsTest.kt) covering proven, falsifiable and rejected-generation cases in both UNBOUNDED (Int) and BOUNDED (BV32) modes. Fixes uncovered while making the suite green: - execAssign now binds variables phi-style (v' = ite(g, rhs, prev)) so statements on dead paths (after break/continue/return) keep their previous value instead of leaking a fresh unconstrained constant into loop merges and postconditions. - setFlag and execReturn use the same phi form (hard equality) so abrupt completion flags are defined when their guard is false. - array lengths are axiomatised non-negative; without it a BV32 length could take a huge negative value that passes 'len <= 4' under signed comparison, turning a binary-search bound into nonsense (unsound BOUNDED binarySearch). - loop-exit merges fall back to an unconstrained constant of the correct sort ($undef_N) for locations absent from exit snapshots instead of makeFalse(). - array element sorts come from the array's declared sort (BV32 element in BOUNDED, Int in UNBOUNDED); bvsaddo/bvssubo/bvsmulo drop the extra width argument; IntArithmeticTranslator maps boolean/float/double primitives to their real SmtTypes.
Add 45 new corner-case fixtures and tests to the VC-generation suite: - MIN/MAX literals and boundary arithmetic (intMinLiteral, intMaxLiteral, nearMax, boundedIncrement, maxOverflow, minUnderflowWrap) - instanceof as a statement (stmtInstanceof, stmtInstanceofCount) - return/continue/break combined with loops and try-catch-finally (returnInsideLoop, plainReturnInLoop, tryBreakContinue, continueCountOnly, nonNormalLoopExit, nestedTry, tryReturnFinally) - falsifiable-by-design wrong-spec fixtures (returnEarlyNoContinue, tryBreakOnly, continueThenReturn, loopContinueNoTry, loopBreakNoTry, nestedCatchFinally, minUnderflow) - dedicated tests documenting that boxed parameter unboxing and boxed return types are rejected at generation time Engine fixes: - declare the String type sort in the hierarchy and qualify 'String' in instanceof translation (previously emitted an undeclared sort_string) - reject boxed return types (Integer etc.) up front with a clear error instead of emitting an ill-sorted query
Drive VcgResult.kt to 100% line / 100% branch coverage: - parameterized verdict mapping over the shared fixture bank with a stub solver: all-unsat -> PROVEN (95 rows, also asserting memoization and failedConditions reuse), all-sat -> FAILED (17 rows, failedConditions returns every condition), all-unknown -> UNKNOWN (20 rows) - dedicated checks of the verdict loop edge cases: mixed answers mapped in order, answers shorter/trailing/empty/malformed-error tokens, empty condition lists, memoized single solver invocation, copy() giving an independent cache, fresh results re-checking, Status enum, the VerificationCondition data class (components/copy/equality/hash/toString/ range default), and toString on both populated and empty results - checkProgressive: live+final streaming with correct totals, default-arg bridges, cancelled runs that leave unanswered conditions UNKNOWN and are NOT memoized, explicit timeout and failed-verdict reporting - end-to-end runs against the real Z3 solver (guarded by an installation assumption) for a proven, a falsifiable and a constructor fixture
Coverage for tools:vcg now at 97.85% instr / 98.52% line / 88.40% branch across the module, with the ir/ package fully covered (100%). Production: - Remove NfBlock and Statement.asNfOrigin from ir/NfStmt.kt; the IR never constructed a block, and all three walk sites in Vcg.kt (execStmt, modifiedLocations, collectCalleeDeclaredKeys) plus Normalizer.kt were dead. - Drop now-unused imports in Normalizer.kt and NormalizerTest.kt. Fixtures (VcgExamples.java) and rows (VcgSemanticsTest): - makeArray: `return new int[n]` covers ExprTranslator's ArrayCreationExpr visit and the anonymous-array naming (a declarator initializer would be cloned by the normalizer and detached, breaking calculateResolvedType). - returnsUnknown: unresolvable return type -> returnTypeOf fallback. - catchUnknownType: local declared with an unresolvable type -> tryResolve fallback through the assignment-target sort path (an unresolvable *catch* type would break the catch-match guard, which emits an undeclared sort). - callInlineSwitch/helperSwitch + rejection test: switch inside an inlined callee is collected by declared-locals walk before being rejected. - readUnknownField: host-selector fallback for a missing field on a resolved receiver, covering the backwards-sort fallback branch. Tests: - ExprTranslatorTest.testPrimitiveCastIsElided / testReferenceCastEmitsCastTerm cover both cast branches (elide vs. `(cast value sort_C)`). - switch-inline rejection, options lookup/value semantics, and orphan-callable context rejection tests cover the remaining option and context helpers. Suite: 682 tests green (132/202/158/163/27).
The VCG overflow obligations used Z3's bvsaddo/bvssubo/bvsmulo predicates, which do not exist in the Z3 4.8.12 that the CI installs via apt on Ubuntu 24.04, so the solver rejected the query and VcgSemanticsTest's bounded overflow cases (boundedAdd/boundedMul/boundedSub, overflowDetected, boundedOverflow) failed. Encode signed overflow portably instead: sign-extend both operands to a width in which the exact result cannot wrap (w+1 for +/-, 2w for *), compare the exact result against the sign-extended Java wrap-around result, and flag overflow when they differ. Adds SmtTermFactory.signExtend.
Contributor
Code Coverage OverviewLanguages: Java Java / code-coverage/jacoco-ubuntu-latest-21The overall line coverage in commit 34d6f8e in the Show a line coverage summary of the most impacted files.
Updated |
… string index Add four opt-in implicit-exception checks, each gated by a VcgOptions flag and a matching VcgCommand CLI option: - nullcheck: guard -> receiver != null on field-access receivers and instance method-call receivers (arrays exempt: encoded as value maps) - castcheck: guard -> instanceof(v, sort_T) for reference casts; new T(...) binds typeof/instanceof of the fresh object so casts of fresh objects prove - negative-array-size: guard -> dim >= 0 on new T[n] (always provable under the non-negative array-length axiom; test asserts emission + provability) - string-index: engine-internal String/StringBuilder specs (charAt, codePointAt, substring); stringLength function over U, String.length() modelled as a read, declared string-literal constants with length asserts Also folds in earlier uncommitted branch work: VcgFacade/VcgCache with the <fqdn>#method#kind-N@line:col condition-id convention, jmlstub JRE stub regeneration, and the SmtTermFactory idivide soundness fix for Z3 4.8.x.
…non-null The report of `jmltk vcg` is now emitted as soon as a callable's conditions have been solved instead of after all callables finish: VcgFacade gains a callback overload of verifyAndCheckAll that invokes the consumer from the worker thread (completion order) and still blocks until every callable is done; VcgCommand prints each outcome under an output lock so lines of one callable stay contiguous and the totals stay exact. String literals are now declared reference constants (declared once per value, asserted non-null) whenever a receiver-sensitive check is active, so `--check-null` on a literal receiver is provable instead of spuriously FAILing, identical literals no longer emit duplicate declarations, and `--check-string-index` still binds each literal's runtime length.
The solver watchdog used to wake every 100 ms and re-check the cancel flag and deadline. It now waits on a ReentrantLock Condition: the run loop signals it after every parsed answer (so a fresh cancellation is picked up at the latest when the next answer arrives) and shutdown() signals on teardown, while the deadline is handled exactly by awaitNanos. No timer-based polling remains: the watchdog wakes only on progress, shutdown, or deadline.
Model array `==`/`!=` as reference (identity) equality instead of extensional content equality: a store through one alias is exactly a store through the other, so when the identities match the last write wins. Identities are plain `U` values produced by the lazily-declared `$arrayref_<elem>` functions, so the solver can decide aliasing without extensional (content) reasoning. - `==`/`!=` on arrays translates to identity comparison in both the VCG atom and the expression translator (wired through `arrayRefHandler`). - every element store frames the other live array values with `ite(refEqual, store, old)` and preserves their lengths and identities; the stored array keeps its identity too. - copying an array reference (`y = x`) copies the identity, and if-merges emit identity phis so a merged array is whichever branch was taken. - fixtures: aliasArrayParams (the `a==b ==> a[0]==2` case), aliasArrayParamAliased, aliasArrayLocal, aliasArrayTwoLocals, inlineAliasedTwoArgs/bumpBoth.
Scalar/object instance fields of arbitrary receivers (parameters, locals, `this.<field>` objects) are versioned `(Array U T)` heap maps keyed globally by `fld$<tag>.<field>` and SSA-versioned like a local: a write is `map' = ite(g, store(map, receiver, v), map)`, a read is `select(map, receiver)`. Must-alias references (`b2 = b`) share one `U` identity, so `b2.value = 5` is observable as `b.value == 5`; distinct objects observe nothing. - reads of `scope.field` on arbitrary receivers prefer the identity map, then a per-receiver slot, then an unconstrained base (never the enclosing-class type). - `this`-receiver writes keep the flat per-path slot but also update the map, so inlined/contracted callee receivers and `this` aliases observe them. - assignable `this.f` clauses on receiver-bound contract calls refresh the map entry at the receiver; `\everything`/no-assignable havocs version every map. - loop merges and invariant havocs version field-map keys with their printable `(Array U …)` sort (the generic printer would emit the invalid `Object` placeholder), and `modifiedLocations` records map keys for field writes. - fixtures: aliasObjectLocal, aliasObjectTwice (proven in both modes).
Reads of `this`-receiver fields (explicit `this.f`, bare `f`) now go through the identity-keyed field map whenever it is live, so a write through a must-alias of the enclosing instance (`VcgExamples alias = this; alias.val = 9` or a parameter assumed `alias == this`) is observable as `this.val` / bare `val`. Previously such writes updated only the map entry of the alias and this-receiver reads kept returning the stale flat slot — the documented limitation. To keep the flat and map models consistent: - bare writes to fields of the enclosing instance (`counter = 0` normalizes to NfLocal) now update the field map like explicit `this.f = v` writes; - declaring a field map anchors `select(base, this)` to the flat `this.<field>` entry, so requires/entry assumptions on a this-field survive reads switching to the map; - loop merges (unroll final merge, invariant havoc) version this-receiver field-map keys like any modified location, and the invariant step-3 havoc declares map sorts with the printable `(Array U ...)` sort; - `assignable this.f` contract clauses refresh the map entry even without a receiver (plain `assigns f` on the enclosing instance), matching the receiver-bound case. `\old` resolution is fixed along the way: the this-read handler now takes the environment map explicitly so `old(...)` translates against the pre-state instead of the current state. New fixtures `aliasThisViaLocal`, `aliasThisViaParam`, `setValViaAlias` are proven in both UNBOUNDED and BOUNDED modes.
Class-level `invariant` clauses are gathered from the context (the enclosing type and its statically-visible ancestors in the same CU) and folded by a single code transformation into one large conjunction in which every clause is wrapped in a `\lblpos` label that keeps its identity: (\lblpos inv_1 e1 && \lblpos inv_2 e2 && ...) The folded expression is added to the entry assumptions (JML: a non-helper instance method may assume `\invariant_for(this)`) and to the exit obligation (every instance method and constructor must preserve it). `helper` methods and static methods are exempt. Literal `\invariant_for(..)` / `\static_invariant_for(..)` expressions - parsed as `\`-prefixed method calls - expand to the same fold, with a non-`this` receiver bound as the invariants' `this`. `\lbl`/`\lblpos`/`\lblneg` label nodes are transparent to translation. Also fixes the unknown-field/unknown-name fallback to be mode aware (BV32 in bounded mode), which surfaces when invariants read fields of a superclass that the engine does not track as flat slots.
This branch has not been deployed
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.
No description provided.