Add foundational array support#441
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
This PR updates the Java→Laurel lowering to support a map-based array model, including array length, allocation, and reads/writes, and adjusts array literal handling to preserve only length.
Changes:
- Lower array initializer lists to allocate an array of the right length (dropping element values) in
ArrayCompiler. - Introduce Laurel prelude declarations and expression rewrites for array operations (
lengthOf,arrayGet,arrayNew_1,arraySet). - Translate Java array types to Laurel map types (
Map<int, elem>).
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.
| File | Description |
|---|---|
| verifier/src/main/java/org/strata/jverify/verifier/compiler/simplifications/ArrayCompiler.java | Lowers array initializer lists to length-only allocation and sets literal type via Symtab. |
| verifier/src/main/java/org/strata/jverify/verifier/compiler/generator/laurel/JavaToLaurelCompiler.java | Adds array map prelude functions and rewrites array operations/typing during Laurel generation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
0f2d63f to
1f29be3
Compare
fabiomadge
left a comment
There was a problem hiding this comment.
Tried this locally (origin/main, same Strata e115b8ec): array element access doesn't verify — both reads and writes exit with a Strata type error rather than a verification result:
static int get0(int[] a) { return a[0]; } // exit 1
// Type checking error. Impossible to unify (arrow (Map int int) (arrow int int)) with (arrow int $__ty69)
Same for a[i] = v. The arrayGet/arraySet calls don't match the Map<int,int> prelude declarations; a non-array control verifies fine through the same path.
Two things: (1) is element get/set meant to verify yet, or is "translates and proceeds" the bar for now? (2) No array test in the PR (only an unrelated ResolutionErrorsStringMethods tweak), so CI doesn't catch it — one read/write test would. Also worth noting the overlap with Strata#1073 (typed Array<T>/Seq<T>), which may supersede the Map<int,int> model.
Encode every Java array type as a Laurel Map<int, int> (the standard Boogie/SMT array model), so array-typed parameters such as int[] xs are accepted and Strata can reason about them via its map-theory axioms. The element type is erased to int — the type the array prelude functions (lengthOf / arrayGet / arraySet / arrayNew_1) are declared over — so every array type-checks uniformly against them. (This is an unbounded int rather than the constrained int32, so element values are not yet provably int32; full element-level verification is deferred to typed-array support, Strata#1073.) Adds AcceptArrayParam, a test with an array-typed parameter method that verifies cleanly. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Translate `arr.length` on an array-typed receiver to a call to a new uninterpreted Laurel prelude function `lengthOf(arr)`. The array-as-map model has no intrinsic length, and Strata's resolver needs a declared symbol; an empty-body (uninterpreted) function also gives same-input/ same-output semantics so repeated `arr.length` references stay consistent. The new unguarded JCFieldAccess switch arm is placed after the existing guarded compile-time-constant arm so the latter is not dominated. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Read a single-dimension array element via a new uninterpreted Laurel prelude function `arrayGet(arr, idx)`. A contract-position `arr[i]` (JCArrayAccess) translates directly; body-level `arr[i]` that ArrayCompiler has lowered to `JArray.get(arr, i)` is routed to the same `arrayGet` symbol. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
Match the JArray owner by stem so the synthetic-static rewrite that appends a `?static` suffix (MoveStaticMethodsToStaticType, which runs after ArrayCompiler) is still recognized, and accept any `.JArray` nested form. Without this the JArray.* routing silently fails once the hoisting pass has run. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
ArrayCompiler lowers an initializer-list array `{v0, ...}` to
`JArray.create(N)` (length-only; element values dropped) instead of
rejecting it.
JavaToLaurelCompiler declares an uninterpreted arrayNew_1(N) prelude
function returning a fresh Map<int,int>, routes JArray.create to it, and
translates JCNewArray: both the sized form `new int[N]` and the
initializer-list form are modelled length-only via arrayNew_1. Only
single-dimension arrays are supported; multi-dimensional allocation is
rejected with a clear JavaViolationException rather than emitting an
undeclared arrayNew_<k> symbol.
Updates ResolutionErrorsStringMethods: now that initializer-list arrays
(here the Object[] from a varargs call) are supported, that case no
longer reports "new array with initializers is not supported"; the
String.length() resolution failure surfaces instead (exit 4).
Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
…set) Declares an uninterpreted arraySet(arr, idx, value) prelude function (a pure-functional map store) and routes the JArray.set call to it. Crucially, ArrayCompiler now rebinds the array variable: `arr[i] = v` is lowered to `arr = arr.set(i, v)` (i.e. `arr := arraySet(arr, i, v)` in Laurel) so the store is observable by subsequent reads — a pure store emitted as a discarded expression would not be. Rebinding is only sound when the array is a simple local variable; element assignment through any other array expression (field, call result, nested index) is rejected with a clear error rather than silently dropping the write. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
JArray.get/set are instance methods, so ArrayCompiler lowers `arr[i]` / `arr[i] = v` to `arr.get(i)` / `arr.set(i, v)` with the array as the receiver. MoveStaticMethodsToStaticType only relocates static methods, so the receiver stayed in the method-select and was dropped when building the call arguments — `arr.get(i)` lowered to `arrayGet(i)`, which Strata rejected with a type error (it expects `arrayGet(Map, int)`). Prepend the receiver (the array) as the first argument of arrayGet / arraySet so the calls match their Map<int,int> prelude declarations and type-check. (create is static, so its array argument is already present.) This makes element get/set translate and type-check; full element-level verification (the int32 element constraint) is still deferred to typed-array support, Strata#1073. Co-authored-by: Kiro <kiro-agent@users.noreply.github.com>
1f29be3 to
2639eee
Compare
|
@fabiomadge Applied the smallish fixes, but indeed the right thing might be to wait for strata-org/Strata#1073. |
fabiomadge
left a comment
There was a problem hiding this comment.
Confirmed prependArrayReceiver fixes the type error — element get/set type-check now, but still don't verify: arrayGet returns unbounded int while a Java int target is constrained int32, leaving an undischarged range-assert. Front-end tightening is Strata-blocked: arrayGet : int32 → "constrained return types on functions not supported"; an in-range ensures is dropped (function postconditions still open, #1173/#668). The procedure route (#1215 merged, #1352 open) is more promising but needs a Strata bump + remodeling.
Re #1073: it adds bounds-checking and value/reference semantics, but still needs one of those postcondition paths to verify element facts, and its Array<T> is T = int only (general element types wait on Laurel polymorphism). So it supersedes #441 for int arrays without fully closing the gap. The type-check fix is a fine incremental step regardless.
What was changed?
Adds foundational array support to the Java → Laurel front end (
JavaToLaurelCompiler+ArrayCompiler). Java arrays are modelled as Laurel maps — every array becomes aMap<int, int>(the standard Boogie/SMT array model) — with element operations translated to a small set of uninterpreted prelude functions (lengthOf,arrayGet,arraySet,arrayNew_1).Commits:
T[]→Map<int, int>; the element type is erased tointso all arrays type-check uniformly against the prelude functions. AddsAcceptArrayParam, a test showing an array-typed parameter method verifies.arr.length→lengthOf(arr).arr[i]/JArray.get→arrayGet(arr, idx).JArrayafter the?statichoisting pass.new int[N]/{…}→arrayNew_1(length-only); multi-dimensional allocation rejected with a clear error.arr[i] = v/JArray.set→ reboundarr = arraySet(arr, i, v)so the store is observable.arrayGet/arraySet—get/setare instance methods, so the array is the call receiver; it must be the first argument. Without this,arr.get(i)lowered toarrayGet(i)and Strata rejected it with a type error.Scope: what verifies vs. what's deferred
This is a deliberately partial foundation:
AcceptArrayParam).int, whereas Javaintis the constrainedint32, soa[i]is not provablyint32(and Strata does not allow constrained function return types, soarrayGetcan't simply returnint32). Bridging that needsint↔int32handling at element sites.So the bar here is "accepts and translates" for element operations, not full element-level verification. That, plus the
Map<int,int>model itself, is expected to be superseded by typed-array support (Strata#1073,Array<T>/Seq<T>) — element-level verification is deferred to that work rather than completed against the map model.Other known limitations
long), reference (Object[]), and nested array elements imprecise.By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0.