diff --git a/verifier/src/main/java/org/strata/jverify/verifier/compiler/generator/laurel/JavaToLaurelCompiler.java b/verifier/src/main/java/org/strata/jverify/verifier/compiler/generator/laurel/JavaToLaurelCompiler.java index 2d2a7dc66..a16957c99 100644 --- a/verifier/src/main/java/org/strata/jverify/verifier/compiler/generator/laurel/JavaToLaurelCompiler.java +++ b/verifier/src/main/java/org/strata/jverify/verifier/compiler/generator/laurel/JavaToLaurelCompiler.java @@ -33,6 +33,12 @@ public class JavaToLaurelCompiler { private final VerifyAnnotationCompiler annotationCompiler; JCTree.JCCompilationUnit currentCompilationUnit; + /// Names of class/record/sealed types referenced as opaque Laurel + /// CompositeType sorts during translation. Each must be declared with + /// a compositeCommand so Strata's resolver can find the sort; insertion + /// order is preserved for deterministic output. + private final Set referencedCompositeTypes = new LinkedHashSet<>(); + public JavaToLaurelCompiler(Context context) { lowerer = context.get(JavaLowerer.class); contractCompiler = MethodOrLoopContractCompiler.instance(context); @@ -49,6 +55,7 @@ public AnalysisResult analyzeJavaCode(VerifierOptions verifierOptions, List lineMaps = new HashMap<>(); boolean first = true; + Set emittedCompositeTypes = new HashSet<>(); for (var compilationUnit : loweredResult.parsed()) { if (lowerer.isContractSource(compilationUnit)) { continue; @@ -62,6 +69,15 @@ public AnalysisResult analyzeJavaCode(VerifierOptions verifierOptions, List compositeType("int32"); case SHORT -> compositeType("int16"); @@ -131,7 +168,11 @@ private LaurelType translateType(com.sun.tools.javac.code.Type type) { } private static String qualifiedMethodName(Symbol.MethodSymbol sym) { - return sym.outermostClass().name + "_" + sym.name; + // Use the outermost class's fully-qualified (package-included) name, + // sanitised like the CompositeType sort names ('$' -> '.'), so two + // same-named classes in different packages don't produce colliding + // procedure names. + return sym.outermostClass().getQualifiedName().toString().replace('$', '.') + "_" + sym.name; } private class StaticMethodCollector extends TreeScanner { @@ -570,6 +611,54 @@ yield call(toSourceRange(invocation), // expression's type. case JCTree.JCFieldAccess fa when fa.type.constValue() != null -> convertConstantValue(toSourceRange(fa), fa.type.getTag(), fa.type.constValue()); + case JCTree.JCNewClass newClass -> { + // `new T(...)` for class / record types: produce + // a Laurel `new_(T)` value of the matching + // CompositeType. Constructor arguments are NOT + // captured into the resulting value yet — that + // would need a Laurel datatype declaration with + // constructor args matching the source. For + // verification of identity-style properties + // that compare references (e.g. `cover(None, r) + // == r`) the opaque value is sufficient. Body- + // level inspection of record components will + // still error until the datatype encoding + // lands. + SourceRange sr = toSourceRange(newClass); + String name = newClass.type.tsym + .getQualifiedName().toString() + .replace('$', '.'); + // Declare the opaque composite sort even when the type + // appears only here (in `new T(...)`) and never in a + // type position, so the new_(T) value resolves. + referencedCompositeTypes.add(name); + // Translate each argument so unsupported argument + // expressions still surface as errors, but DISCARD + // the result: the opaque new_(T) value models only + // the reference identity, not the constructor's + // arguments or their side effects. Capturing those + // needs a Laurel datatype encoding and expression + // sequencing (let/temporaries), which is future + // work. + for (var arg : newClass.args) { + convertExpression(arg, renames); + } + yield new_(sr, name); + } + case JCTree.JCInstanceOf instanceOf -> { + // `r instanceof X`: the opaque-CompositeType + // encoding carries no runtime tag, so the test + // cannot be modelled precisely yet. Fail with a + // clear, attributable error rather than emitting an + // undeclared `instanceOf_` predicate symbol + // (which would surface only as a confusing + // downstream "Resolution failed" message, and whose + // simple-name form could even collide across + // packages). A precise encoding needs a tagged + // datatype representation; future work. + throw new JavaViolationException( + "instanceof on opaque reference types is not yet supported"); + } default -> throw new JavaViolationException("Unsupported expression: " + expr.getClass().getSimpleName()); }; } diff --git a/verifier/src/test/java/org/strata/jverify/verifier/tests/javasupport/records/AcceptClassParam.java b/verifier/src/test/java/org/strata/jverify/verifier/tests/javasupport/records/AcceptClassParam.java new file mode 100644 index 000000000..9af0c003d --- /dev/null +++ b/verifier/src/test/java/org/strata/jverify/verifier/tests/javasupport/records/AcceptClassParam.java @@ -0,0 +1,16 @@ +package org.strata.jverify.verifier.tests.javasupport.records; + +import org.strata.jverify.testengine.JVerifyTest; + +/** + * A method with a class/record-typed parameter is accepted and verifies: + * the type is modelled as an opaque Laurel composite sort, which is now + * declared (via compositeCommand) so Strata's resolver can find it. + */ +@JVerifyTest(methodsVerified = 3, errorCount = 0) +class AcceptClassParam { + record Point(int x, int y) {} + + static void acceptParam(Point p) { + } +} diff --git a/verifier/src/test/java/org/strata/jverify/verifier/tests/javasupport/records/NewOnlyRecordType.java b/verifier/src/test/java/org/strata/jverify/verifier/tests/javasupport/records/NewOnlyRecordType.java new file mode 100644 index 000000000..7aa6dbd78 --- /dev/null +++ b/verifier/src/test/java/org/strata/jverify/verifier/tests/javasupport/records/NewOnlyRecordType.java @@ -0,0 +1,17 @@ +package org.strata.jverify.verifier.tests.javasupport.records; + +import org.strata.jverify.testengine.JVerifyTest; + +/** + * A record/class type used ONLY in `new T(...)` position (never in a type + * position such as a parameter, local, or return) must still have its opaque + * composite sort declared, so the `new_(T)` value resolves. + */ +@JVerifyTest(methodsVerified = 3, errorCount = 0) +class NewOnlyRecordType { + record Q(int x) {} + + static void make() { + new Q(1); + } +}