From b4b41e0e5d32c174b894e80ba6dc14beb4870618 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 13 Nov 2025 20:41:02 +0000 Subject: [PATCH 01/25] Replace `ErrorEmitter` with `LJDiagnostics` (#110) --- .../{SimpleTest.java => CorrectSimple.java} | 2 +- ...ava => WarningExtRefNonExistentClass.java} | 2 +- ...va => WarningExtRefNonExistentMethod.java} | 2 +- ...ava => WarningExtRefWrongConstructor.java} | 2 +- ...a => WarningExtRefWrongParameterType.java} | 2 +- ...pe.java => WarningExtRefWrongRetType.java} | 2 +- .../liquidjava/api/CommandLineLauncher.java | 45 ++-- .../liquidjava/diagnostics/ErrorEmitter.java | 92 ------- .../liquidjava/diagnostics/ErrorHandler.java | 234 ------------------ .../liquidjava/diagnostics/LJDiagnostics.java | 59 ++--- .../diagnostics/TranslationTable.java | 17 ++ .../diagnostics/errors/CustomError.java | 13 +- .../errors/GhostInvocationError.java | 16 +- .../IllegalConstructorTransitionError.java | 3 +- .../errors/InvalidRefinementError.java | 4 +- .../diagnostics/errors/LJError.java | 39 +-- .../diagnostics/errors/NotFoundError.java | 5 +- .../diagnostics/errors/RefinementError.java | 21 +- .../errors/StateConflictError.java | 18 +- .../errors/StateRefinementError.java | 12 +- .../diagnostics/errors/SyntaxError.java | 2 +- .../ExternalClassNotFoundWarning.java | 4 +- .../ExternalMethodNotFoundWarning.java | 4 +- .../diagnostics/warnings/LJWarning.java | 11 +- .../processor/RefinementProcessor.java | 18 +- .../ann_generation/FieldGhostsGeneration.java | 13 +- .../processor/context/AliasWrapper.java | 11 +- .../ExternalRefinementTypeChecker.java | 68 +++-- .../MethodsFirstChecker.java | 19 +- .../RefinementTypeChecker.java | 86 +++---- .../refinement_checker/TypeChecker.java | 54 ++-- .../refinement_checker/VCChecker.java | 128 ++++------ .../general_checkers/OperationsChecker.java | 10 +- .../AuxHierarchyRefinememtsPassage.java | 21 +- .../object_checkers/AuxStateHandler.java | 72 +++--- .../rj_language/BuiltinFunctionPredicate.java | 17 +- .../liquidjava/rj_language/Predicate.java | 35 +-- ...tFoundError.java => NotFoundSMTError.java} | 4 +- .../java/liquidjava/smt/SMTEvaluator.java | 17 +- .../java/liquidjava/smt/TranslatorToZ3.java | 4 +- .../liquidjava/smt/TypeMismatchError.java | 23 -- .../liquidjava/api/tests/TestExamples.java | 38 +-- 42 files changed, 443 insertions(+), 806 deletions(-) rename liquidjava-example/src/main/java/testSuite/{SimpleTest.java => CorrectSimple.java} (87%) rename liquidjava-example/src/main/java/testSuite/{ErrorExtRefNonExistentClass.java => WarningExtRefNonExistentClass.java} (76%) rename liquidjava-example/src/main/java/testSuite/{ErrorExtRefNonExistentMethod.java => WarningExtRefNonExistentMethod.java} (89%) rename liquidjava-example/src/main/java/testSuite/{ErrorExtRefWrongConstructor.java => WarningExtRefWrongConstructor.java} (90%) rename liquidjava-example/src/main/java/testSuite/{ErrorExtRefWrongParameterType.java => WarningExtRefWrongParameterType.java} (89%) rename liquidjava-example/src/main/java/testSuite/{ErrorExtRefWrongRetType.java => WarningExtRefWrongRetType.java} (90%) delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorEmitter.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorHandler.java create mode 100644 liquidjava-verifier/src/main/java/liquidjava/diagnostics/TranslationTable.java rename liquidjava-verifier/src/main/java/liquidjava/smt/{NotFoundError.java => NotFoundSMTError.java} (78%) delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/smt/TypeMismatchError.java diff --git a/liquidjava-example/src/main/java/testSuite/SimpleTest.java b/liquidjava-example/src/main/java/testSuite/CorrectSimple.java similarity index 87% rename from liquidjava-example/src/main/java/testSuite/SimpleTest.java rename to liquidjava-example/src/main/java/testSuite/CorrectSimple.java index 8821c0c9..c9096799 100644 --- a/liquidjava-example/src/main/java/testSuite/SimpleTest.java +++ b/liquidjava-example/src/main/java/testSuite/CorrectSimple.java @@ -3,7 +3,7 @@ import liquidjava.specification.Refinement; @SuppressWarnings("unused") -public class SimpleTest { +public class CorrectSimple { public static void main(String[] args) { // Arithmetic Binary Operations @Refinement("a >= 10") diff --git a/liquidjava-example/src/main/java/testSuite/ErrorExtRefNonExistentClass.java b/liquidjava-example/src/main/java/testSuite/WarningExtRefNonExistentClass.java similarity index 76% rename from liquidjava-example/src/main/java/testSuite/ErrorExtRefNonExistentClass.java rename to liquidjava-example/src/main/java/testSuite/WarningExtRefNonExistentClass.java index 800ee7c0..b79eaa87 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorExtRefNonExistentClass.java +++ b/liquidjava-example/src/main/java/testSuite/WarningExtRefNonExistentClass.java @@ -3,6 +3,6 @@ import liquidjava.specification.ExternalRefinementsFor; @ExternalRefinementsFor("non.existent.Class") -public interface ErrorExtRefNonExistentClass { +public interface WarningExtRefNonExistentClass { public void NonExistentClass(); } diff --git a/liquidjava-example/src/main/java/testSuite/ErrorExtRefNonExistentMethod.java b/liquidjava-example/src/main/java/testSuite/WarningExtRefNonExistentMethod.java similarity index 89% rename from liquidjava-example/src/main/java/testSuite/ErrorExtRefNonExistentMethod.java rename to liquidjava-example/src/main/java/testSuite/WarningExtRefNonExistentMethod.java index 78b1c8c9..b98e48db 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorExtRefNonExistentMethod.java +++ b/liquidjava-example/src/main/java/testSuite/WarningExtRefNonExistentMethod.java @@ -5,7 +5,7 @@ import liquidjava.specification.StateRefinement; @ExternalRefinementsFor("java.util.ArrayList") -public interface ErrorExtRefNonExistentMethod { +public interface WarningExtRefNonExistentMethod { @RefinementPredicate("int size(ArrayList l)") @StateRefinement(to = "size(this) == 0") diff --git a/liquidjava-example/src/main/java/testSuite/ErrorExtRefWrongConstructor.java b/liquidjava-example/src/main/java/testSuite/WarningExtRefWrongConstructor.java similarity index 90% rename from liquidjava-example/src/main/java/testSuite/ErrorExtRefWrongConstructor.java rename to liquidjava-example/src/main/java/testSuite/WarningExtRefWrongConstructor.java index 636d3adb..a81ee0ed 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorExtRefWrongConstructor.java +++ b/liquidjava-example/src/main/java/testSuite/WarningExtRefWrongConstructor.java @@ -5,7 +5,7 @@ import liquidjava.specification.StateRefinement; @ExternalRefinementsFor("java.util.ArrayList") -public interface ErrorExtRefWrongConstructor { +public interface WarningExtRefWrongConstructor { @RefinementPredicate("int size(ArrayList l)") @StateRefinement(to = "size(this) == 0") diff --git a/liquidjava-example/src/main/java/testSuite/ErrorExtRefWrongParameterType.java b/liquidjava-example/src/main/java/testSuite/WarningExtRefWrongParameterType.java similarity index 89% rename from liquidjava-example/src/main/java/testSuite/ErrorExtRefWrongParameterType.java rename to liquidjava-example/src/main/java/testSuite/WarningExtRefWrongParameterType.java index da2135c5..c847d9e7 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorExtRefWrongParameterType.java +++ b/liquidjava-example/src/main/java/testSuite/WarningExtRefWrongParameterType.java @@ -5,7 +5,7 @@ import liquidjava.specification.StateRefinement; @ExternalRefinementsFor("java.util.ArrayList") -public interface ErrorExtRefWrongParameterType { +public interface WarningExtRefWrongParameterType { @RefinementPredicate("int size(ArrayList l)") @StateRefinement(to = "size(this) == 0") diff --git a/liquidjava-example/src/main/java/testSuite/ErrorExtRefWrongRetType.java b/liquidjava-example/src/main/java/testSuite/WarningExtRefWrongRetType.java similarity index 90% rename from liquidjava-example/src/main/java/testSuite/ErrorExtRefWrongRetType.java rename to liquidjava-example/src/main/java/testSuite/WarningExtRefWrongRetType.java index 48509080..bbba0b9a 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorExtRefWrongRetType.java +++ b/liquidjava-example/src/main/java/testSuite/WarningExtRefWrongRetType.java @@ -5,7 +5,7 @@ import liquidjava.specification.StateRefinement; @ExternalRefinementsFor("java.util.ArrayList") -public interface ErrorExtRefWrongRetType { +public interface WarningExtRefWrongRetType { @RefinementPredicate("int size(ArrayList l)") @StateRefinement(to = "size(this) == 0") diff --git a/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java index dc6aa2b4..8b925819 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java +++ b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java @@ -1,10 +1,12 @@ package liquidjava.api; +import static liquidjava.diagnostics.LJDiagnostics.diagnostics; + import java.io.File; import java.util.Arrays; import java.util.List; -import liquidjava.diagnostics.ErrorEmitter; +import liquidjava.diagnostics.errors.CustomError; import liquidjava.processor.RefinementProcessor; import spoon.Launcher; import spoon.processing.ProcessingManager; @@ -14,51 +16,44 @@ public class CommandLineLauncher { public static void main(String[] args) { - // String allPath = "C://Regen/test-projects/src/Main.java"; - // In eclipse only needed this:"../liquidjava-example/src/main/java/" - // In VSCode needs: - // "../liquidjava/liquidjava-umbrella/liquidjava-example/src/main/java/liquidjava/test/project"; - if (args.length == 0) { - System.out.println("No input files or directories provided"); + System.out.println("No input paths provided"); System.out.println("\nUsage: ./liquidjava <...paths>"); - System.out.println(" <...paths>: Paths to files or directories to be verified by LiquidJava"); + System.out.println(" <...paths>: Paths to be verified by LiquidJava"); System.out.println( - "\nExample: ./liquidjava liquidjava-example/src/main/java/test/currentlyTesting liquidjava-example/src/main/java/testingInProgress/Account.java"); + "\nExample: ./liquidjava liquidjava-example/src/main/java/test liquidjava-example/src/main/java/testingInProgress/Account.java"); return; } List paths = Arrays.asList(args); - ErrorEmitter ee = launch(paths.toArray(new String[0])); - System.out.println(ee.foundError() ? (ee.getFullMessage()) : ("Correct! Passed Verification.")); + launch(paths.toArray(new String[0])); + if (diagnostics.foundError()) { + System.out.println(diagnostics.getErrorOutput()); + } else { + System.out.println(diagnostics.getWarningOutput()); + System.out.println("Correct! Passed Verification."); + } } - public static ErrorEmitter launch(String... paths) { + public static void launch(String... paths) { System.out.println("Running LiquidJava on: " + Arrays.toString(paths).replaceAll("[\\[\\]]", "")); - ErrorEmitter ee = new ErrorEmitter(); Launcher launcher = new Launcher(); for (String path : paths) { if (!new File(path).exists()) { - ee.addError("Path not found", "The path " + path + " does not exist", 1); - return ee; + diagnostics.add(new CustomError("The path " + path + " was not found")); + return; } launcher.addInputResource(path); } - launcher.getEnvironment().setNoClasspath(true); - - // Get the current classpath from the system - // String classpath = System.getProperty("java.class.path"); - // launcher.getEnvironment().setSourceClasspath(classpath.split(File.pathSeparator)); - // optional - // launcher.getEnvironment().setSourceClasspath( - // "lib1.jar:lib2.jar".split(":")); + launcher.getEnvironment().setNoClasspath(true); launcher.getEnvironment().setComplianceLevel(8); launcher.run(); + diagnostics.clear(); final Factory factory = launcher.getFactory(); final ProcessingManager processingManager = new QueueProcessingManager(factory); - final RefinementProcessor processor = new RefinementProcessor(factory, ee); + final RefinementProcessor processor = new RefinementProcessor(factory); processingManager.addProcessor(processor); try { @@ -71,6 +66,6 @@ public static ErrorEmitter launch(String... paths) { throw e; } - return ee; + return; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorEmitter.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorEmitter.java deleted file mode 100644 index 02610343..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorEmitter.java +++ /dev/null @@ -1,92 +0,0 @@ -package liquidjava.diagnostics; - -import java.net.URI; -import java.util.HashMap; -import liquidjava.processor.context.PlacementInCode; -import spoon.reflect.cu.SourcePosition; - -public class ErrorEmitter { - - private String titleMessage; - private String fullMessage; - private URI filePath; - private ErrorPosition position; - private int errorStatus; - private HashMap map; - - public ErrorEmitter() { - } - - public void addError(String titleMessage, String msg, SourcePosition p, int errorStatus, - HashMap map) { - this.titleMessage = titleMessage; - fullMessage = msg; - try { - position = new ErrorPosition(p.getLine(), p.getColumn(), p.getEndLine(), p.getEndColumn()); - filePath = p.getFile().toURI(); - } catch (Exception ignored) { - fullMessage = "Seems like this error is created in generated part of source code, so no precise" - + " position is provided. " + fullMessage; - position = null; - filePath = null; - } - this.errorStatus = errorStatus; - this.map = map; - } - - public void addError(String titleMessage, String msg, SourcePosition p, int errorStatus) { - this.titleMessage = titleMessage; - fullMessage = msg; - try { - position = new ErrorPosition(p.getLine(), p.getColumn(), p.getEndLine(), p.getEndColumn()); - filePath = p.getFile().toURI(); - } catch (Exception ignored) { - fullMessage = "Seems like this error is created in generated part of source code, so no precise" - + " position is provided. " + fullMessage; - position = null; - filePath = null; - } - this.errorStatus = errorStatus; - } - - public void addError(String titleMessage, String msg, int errorStatus) { - this.titleMessage = titleMessage; - fullMessage = msg; - this.errorStatus = errorStatus; - } - - public boolean foundError() { - return fullMessage != null; - } - - public String getTitleMessage() { - return titleMessage; - } - - public String getFullMessage() { - return fullMessage; - } - - public URI getFilePath() { - return filePath; - } - - public void reset() { - fullMessage = null; - position = null; - errorStatus = 0; - map = null; - } - - public ErrorPosition getPosition() { - return position; - } - - public int getErrorStatus() { - return errorStatus; - } - - public HashMap getVCMap() { - return map; - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorHandler.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorHandler.java deleted file mode 100644 index 39452f90..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorHandler.java +++ /dev/null @@ -1,234 +0,0 @@ -package liquidjava.diagnostics; - -import java.util.Formatter; -import java.util.HashMap; -import java.util.Locale; - -import liquidjava.processor.VCImplication; -import liquidjava.processor.context.PlacementInCode; -import liquidjava.rj_language.Predicate; -import spoon.reflect.code.CtLiteral; -import spoon.reflect.declaration.CtElement; - -public class ErrorHandler { - - public static void printError(CtElement var, String moreInfo, Predicate expectedType, Predicate cSMT, - HashMap map, ErrorEmitter ee) { - String resumeMessage = "Type expected:" + expectedType.toString(); // + "; " +"Refinement found:" + - // cSMT.toString(); - - StringBuilder sb = new StringBuilder(); - sb.append("______________________________________________________\n"); - // title - StringBuilder sbtitle = new StringBuilder(); - sbtitle.append("Failed to check refinement at: \n\n"); - if (moreInfo != null) - sbtitle.append(moreInfo + "\n"); - sbtitle.append(var.toString()); - // all message - sb.append(sbtitle.toString() + "\n\n"); - sb.append("Type expected:" + expectedType.toString() + "\n"); - sb.append("Refinement found: " + cSMT.simplify().getValue() + "\n"); - sb.append(printMap(map)); - sb.append("Location: " + var.getPosition() + "\n"); - sb.append("______________________________________________________\n"); - - ee.addError(resumeMessage, sb.toString(), var.getPosition(), 1, map); - } - - public static void printStateMismatch(CtElement element, String method, VCImplication constraintForErrorMsg, - String states, HashMap map, ErrorEmitter ee) { - - String resumeMessage = "Failed to check state transitions. " + "Expected possible states:" + states; // + "; - // Found - // state:"+constraintForErrorMsg.toString() - // ; - - StringBuilder sb = new StringBuilder(); - sb.append("______________________________________________________\n"); - - StringBuilder sbtitle = new StringBuilder(); - sbtitle.append("Failed to check state transitions when calling " + method + " in:\n\n"); - sbtitle.append(element + "\n\n"); - - sb.append(sbtitle.toString()); - sb.append("Expected possible states:" + states + "\n"); - sb.append("\nState found:\n"); - sb.append(printLine()); - sb.append("\n" + constraintForErrorMsg /* .toConjunctions() */.toString() + "\n"); - sb.append(printLine()); - sb.append("\n"); - sb.append(printMap(map)); - sb.append("Location: " + element.getPosition() + "\n"); - sb.append("______________________________________________________\n"); - - ee.addError(resumeMessage, sb.toString(), element.getPosition(), 1, map); - } - - public static void printErrorUnknownVariable(CtElement var, String et, String correctRefinement, - HashMap map, ErrorEmitter ee) { - - String resumeMessage = "Encountered unknown variable"; - - StringBuilder sb = new StringBuilder(); - sb.append("______________________________________________________\n"); - StringBuilder sbtitle = new StringBuilder(); - sbtitle.append("Encountered unknown variable\n\n"); - sbtitle.append(var + "\n\n"); - - sb.append(sbtitle.toString()); - sb.append(printMap(map)); - sb.append("Location: " + var.getPosition() + "\n"); - sb.append("______________________________________________________\n"); - - ee.addError(resumeMessage, sb.toString(), var.getPosition(), 2, map); - } - - public static void printNotFound(CtElement var, Predicate constraint, Predicate constraint2, String msg, - HashMap map, ErrorEmitter ee) { - - StringBuilder sb = new StringBuilder(); - sb.append("______________________________________________________\n"); - sb.append(msg); - sb.append(constraint + "\n"); - sb.append(constraint2 + "\n\n"); - sb.append("Error found while checking conditions in:\n"); - sb.append(var + "\n\n"); - sb.append(printMap(map)); - sb.append("Location: " + var.getPosition() + "\n"); - sb.append("______________________________________________________\n"); - - ee.addError(msg, sb.toString(), var.getPosition(), 2, map); - } - - public static void printErrorArgs(CtElement var, Predicate expectedType, String msg, - HashMap map, ErrorEmitter ee) { - StringBuilder sb = new StringBuilder(); - sb.append("______________________________________________________\n"); - String title = "Error in ghost invocation: " + msg + "\n"; - sb.append(title); - sb.append(var + "\nError in refinement:" + expectedType.toString() + "\n"); - sb.append(printMap(map)); - sb.append("Location: " + var.getPosition() + "\n"); - sb.append("______________________________________________________\n"); - - ee.addError(title, sb.toString(), var.getPosition(), 2, map); - } - - public static void printErrorTypeMismatch(CtElement element, Predicate expectedType, String message, - HashMap map, ErrorEmitter ee) { - StringBuilder sb = new StringBuilder(); - sb.append("______________________________________________________\n"); - sb.append(message + "\n\n"); - sb.append(element + "\n"); - sb.append(printMap(map)); - sb.append("Location: " + element.getPosition() + "\n"); - sb.append("______________________________________________________\n"); - - ee.addError(message, sb.toString(), element.getPosition(), 2, map); - } - - public static void printSameStateSetError(CtElement element, Predicate p, String name, - HashMap map, ErrorEmitter ee) { - String resume = "Error found multiple disjoint states from a State Set in a refinement"; - - StringBuilder sb = new StringBuilder(); - sb.append("______________________________________________________\n"); - StringBuilder sbtitle = new StringBuilder(); - sbtitle.append("Error found multiple disjoint states from a State Set in a refinement\n\n"); - sbtitle.append(element + "\n\n"); - sb.append(sbtitle.toString()); - sb.append("In predicate:" + p.toString() + "\n"); - sb.append("In class:" + name + "\n"); - sb.append(printMap(map)); - sb.append("Location: " + element.getPosition() + "\n"); - sb.append("______________________________________________________\n"); - - ee.addError(resume, sb.toString(), element.getPosition(), 1, map); - } - - public static void printErrorConstructorFromState(CtElement element, CtLiteral from, ErrorEmitter ee) { - StringBuilder sb = new StringBuilder(); - sb.append("______________________________________________________\n"); - String s = " Error found constructor with FROM state (Constructor's should only have a TO state)\n\n"; - sb.append(s); - sb.append(element + "\n\n"); - sb.append("State found:" + from + "\n"); - sb.append("Location: " + element.getPosition() + "\n"); - sb.append("______________________________________________________\n"); - - ee.addError(s, sb.toString(), element.getPosition(), 1); - } - - public static void printCustomError(CtElement element, String msg, ErrorEmitter ee) { - StringBuilder sb = new StringBuilder(); - sb.append("______________________________________________________\n"); - String s = "Found Error: " + msg; - sb.append(s + "\n\n"); - sb.append(element + "\n\n"); - sb.append("Location: " + element.getPosition() + "\n"); - sb.append("______________________________________________________\n"); - - ee.addError(s, sb.toString(), element.getPosition(), 1); - } - - public static void printSyntaxError(String msg, String ref, CtElement element, ErrorEmitter ee) { - StringBuilder sb = new StringBuilder(); - sb.append("______________________________________________________\n"); - StringBuilder sbtitle = new StringBuilder(); - sbtitle.append("Syntax error with message\n"); - sbtitle.append(msg + "\n"); - sb.append(sbtitle.toString()); - sb.append("Found in refinement:\n"); - sb.append(ref + "\n"); - sb.append("In:\n"); - sb.append(element + "\n"); - sb.append("Location: " + element.getPosition() + "\n"); - sb.append("______________________________________________________\n"); - - ee.addError(sbtitle.toString(), sb.toString(), element.getPosition(), 2); - } - - public static void printSyntaxError(String msg, String ref, ErrorEmitter ee) { - StringBuilder sb = new StringBuilder(); - sb.append("______________________________________________________\n"); - StringBuilder sbtitle = new StringBuilder(); - sbtitle.append("Syntax error with message\n"); - sbtitle.append(msg + "\n"); - sb.append(sbtitle.toString()); - sb.append("Found in refinement:\n"); - sb.append(ref + "\n"); - sb.append("______________________________________________________\n"); - - ee.addError(sbtitle.toString(), sb.toString(), 2); - } - - private static String printLine() { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < 130; i++) - sb.append("-"); // ----------- - return sb.toString(); - } - - private static String printMap(HashMap map) { - StringBuilder sb = new StringBuilder(); - Formatter formatter = new Formatter(sb, Locale.US); - if (map.isEmpty()) { - formatter.close(); - return ""; - } - formatter.format("\nInstance translation table:\n"); - formatter.format(printLine()); - // title - formatter.format("\n| %-32s | %-60s | %-1s \n", "Variable Name", "Created in", "File"); - formatter.format(printLine() + "\n"); - // data - for (String s : map.keySet()) - formatter.format("| %-32s | %-60s | %-1s \n", s, map.get(s).getText(), map.get(s).getSimplePosition()); - // end - formatter.format(printLine() + "\n\n"); - String s = formatter.toString(); - formatter.close(); - return s; - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostics.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostics.java index 02a6d315..211115be 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostics.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostics.java @@ -1,67 +1,34 @@ package liquidjava.diagnostics; import java.util.ArrayList; -import java.util.HashMap; - import liquidjava.diagnostics.errors.LJError; import liquidjava.diagnostics.warnings.LJWarning; -import liquidjava.processor.context.PlacementInCode; /** - * Singleton class to store diagnostics information (errors, warnings, translation map) during the verification process + * Singleton class to store diagnostics (errors and warnings) during the verification process * * @see LJError * @see LJWarning */ public class LJDiagnostics { - private static LJDiagnostics instance; + public static final LJDiagnostics diagnostics = new LJDiagnostics(); private ArrayList errors; private ArrayList warnings; - private HashMap translationMap; private LJDiagnostics() { this.errors = new ArrayList<>(); this.warnings = new ArrayList<>(); - this.translationMap = new HashMap<>(); - } - - public static LJDiagnostics getInstance() { - if (instance == null) - instance = new LJDiagnostics(); - return instance; - } - - public static LJDiagnostics add(LJError error) { - LJDiagnostics instance = getInstance(); - instance.addError(error); - return instance; } - public static LJDiagnostics add(LJWarning warning) { - LJDiagnostics instance = getInstance(); - instance.addWarning(warning); - return instance; - } - - public static LJDiagnostics add(HashMap map) { - LJDiagnostics instance = getInstance(); - instance.setTranslationMap(map); - return instance; - } - - public void addError(LJError error) { + public void add(LJError error) { this.errors.add(error); } - public void addWarning(LJWarning warning) { + public void add(LJWarning warning) { this.warnings.add(warning); } - public void setTranslationMap(HashMap map) { - this.translationMap = map; - } - public boolean foundError() { return !this.errors.isEmpty(); } @@ -78,10 +45,6 @@ public ArrayList getWarnings() { return this.warnings; } - public HashMap getTranslationMap() { - return this.translationMap; - } - public LJError getError() { return foundError() ? this.errors.get(0) : null; } @@ -93,11 +56,9 @@ public LJWarning getWarning() { public void clear() { this.errors.clear(); this.warnings.clear(); - this.translationMap.clear(); } - @Override - public String toString() { + public String getErrorOutput() { StringBuilder sb = new StringBuilder(); if (foundError()) { for (LJError error : errors) { @@ -114,4 +75,14 @@ public String toString() { } return sb.toString(); } + + public String getWarningOutput() { + StringBuilder sb = new StringBuilder(); + if (foundWarning()) { + for (LJWarning warning : warnings) { + sb.append(warning.toString()).append("\n"); + } + } + return sb.toString(); + } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/TranslationTable.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/TranslationTable.java new file mode 100644 index 00000000..01f96c77 --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/TranslationTable.java @@ -0,0 +1,17 @@ +package liquidjava.diagnostics; + +import java.util.HashMap; + +import liquidjava.processor.context.PlacementInCode; + +/** + * Translation table mapping variable names to their placement in code + * + * @see HashMap + * @see PlacementInCode + */ +public class TranslationTable extends HashMap { + public TranslationTable() { + super(); + } +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/CustomError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/CustomError.java index 7f885e45..e02b8c9f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/CustomError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/CustomError.java @@ -1,5 +1,8 @@ package liquidjava.diagnostics.errors; +import spoon.reflect.cu.SourcePosition; +import spoon.reflect.declaration.CtElement; + /** * Custom error with an arbitrary message * @@ -8,7 +11,15 @@ public class CustomError extends LJError { public CustomError(String message) { - super("Found Error", message, null); + super("Found Error", message, null, null, null); + } + + public CustomError(String message, SourcePosition pos) { + super("Found Error", message, pos, null, null); + } + + public CustomError(CtElement element, String message) { + super("Found Error", message, element.getPosition(), element.toString(), null); } @Override diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java index 7d04e28d..2b539c9b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java @@ -1,7 +1,8 @@ package liquidjava.diagnostics.errors; +import liquidjava.diagnostics.TranslationTable; import liquidjava.rj_language.Predicate; -import spoon.reflect.declaration.CtElement; +import spoon.reflect.cu.SourcePosition; /** * Error indicating that a ghost method invocation is invalid (e.g., has wrong arguments) @@ -10,21 +11,22 @@ */ public class GhostInvocationError extends LJError { - private Predicate expected; + private String expected; - public GhostInvocationError(CtElement element, Predicate expected) { - super("Ghost Invocation Error", "Invalid types or number of arguments in ghost invocation", element); - this.expected = expected; + public GhostInvocationError(String message, SourcePosition pos, Predicate expected, + TranslationTable translationTable) { + super("Ghost Invocation Error", message, pos, null, translationTable); + this.expected = expected.toString(); } - public Predicate getExpected() { + public String getExpected() { return expected; } @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Expected: ").append(expected.toString()).append("\n"); + sb.append("Expected: ").append(expected).append("\n"); return super.toString(sb.toString()); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java index af5f05c6..11908a6b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java @@ -11,7 +11,8 @@ public class IllegalConstructorTransitionError extends LJError { public IllegalConstructorTransitionError(CtElement element) { super("Illegal Constructor Transition Error", - "Found constructor with 'from' state (should only have a 'to' state)", element); + "Found constructor with 'from' state (should only have a 'to' state)", element.getPosition(), + element.toString(), null); } @Override diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java index 02b41265..03c55813 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java @@ -11,8 +11,8 @@ public class InvalidRefinementError extends LJError { private String refinement; - public InvalidRefinementError(String message, CtElement element, String refinement) { - super("Invalid Refinement", message, element); + public InvalidRefinementError(CtElement element, String message, String refinement) { + super("Invalid Refinement", message, element.getPosition(), element.toString(), null); this.refinement = refinement; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java index 6e92057f..12b4c79f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java @@ -1,33 +1,30 @@ package liquidjava.diagnostics.errors; import liquidjava.diagnostics.ErrorPosition; +import liquidjava.diagnostics.TranslationTable; import liquidjava.utils.Utils; import spoon.reflect.cu.SourcePosition; -import spoon.reflect.declaration.CtElement; /** * Base class for all LiquidJava errors */ -public abstract class LJError extends Exception { +public abstract class LJError { private String title; private String message; - private CtElement element; + private String snippet; private ErrorPosition position; private SourcePosition location; + private TranslationTable translationTable; - public LJError(String title, String message, CtElement element) { - super(message); + public LJError(String title, String message, SourcePosition pos, String snippet, + TranslationTable translationTable) { this.title = title; this.message = message; - this.element = element; - try { - this.location = element.getPosition(); - this.position = ErrorPosition.fromSpoonPosition(element.getPosition()); - } catch (Exception e) { - this.location = null; - this.position = null; - } + this.snippet = snippet; + this.translationTable = translationTable != null ? translationTable : new TranslationTable(); + this.location = pos; + this.position = ErrorPosition.fromSpoonPosition(pos); } public String getTitle() { @@ -38,8 +35,8 @@ public String getMessage() { return message; } - public CtElement getElement() { - return element; + public String getSnippet() { + return snippet; } public ErrorPosition getPosition() { @@ -50,13 +47,21 @@ public SourcePosition getLocation() { return location; } + public TranslationTable getTranslationTable() { + return translationTable; + } + @Override public abstract String toString(); public String toString(String extra) { StringBuilder sb = new StringBuilder(); - sb.append(title).append(" at: \n").append(element.toString().replace("@liquidjava.specification.", "@")) - .append("\n\n"); + sb.append(title); + + if (snippet != null) + sb.append(" at: \n").append(snippet.replace("@liquidjava.specification.", "@")); + + sb.append("\n"); sb.append(message).append("\n"); if (extra != null) sb.append(extra).append("\n"); diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java index 77d1ab1f..fda0ddd9 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java @@ -1,5 +1,6 @@ package liquidjava.diagnostics.errors; +import liquidjava.diagnostics.TranslationTable; import spoon.reflect.declaration.CtElement; /** @@ -9,8 +10,8 @@ */ public class NotFoundError extends LJError { - public NotFoundError(String message, CtElement element) { - super("Not Found Error", message, element); + public NotFoundError(CtElement element, String message, TranslationTable translationTable) { + super("Not Found Error", message, element.getPosition(), element.toString(), translationTable); } @Override diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java index fc4b31f1..6451ed3c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java @@ -1,5 +1,6 @@ package liquidjava.diagnostics.errors; +import liquidjava.diagnostics.TranslationTable; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; import liquidjava.utils.Utils; @@ -12,19 +13,29 @@ */ public class RefinementError extends LJError { - private Predicate expected; + private String expected; private ValDerivationNode found; - public RefinementError(CtElement element, Predicate expected, ValDerivationNode found) { - super("Refinement Error", String.format("%s is not a subtype of %s", found.getValue(), expected), element); - this.expected = expected; + public RefinementError(CtElement element, Predicate expected, ValDerivationNode found, + TranslationTable translationTable) { + super("Refinement Error", String.format("%s is not a subtype of %s", found.getValue(), expected), + element.getPosition(), element.toString(), translationTable); + this.expected = expected.toString(); this.found = found; } + public String getExpected() { + return expected; + } + + public ValDerivationNode getFound() { + return found; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Expected: ").append(Utils.stripParens(expected.toString())).append("\n"); + sb.append("Expected: ").append(Utils.stripParens(expected)).append("\n"); sb.append("Found: ").append(found.getValue()); return super.toString(sb.toString()); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java index 1f714cb2..e464349e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java @@ -1,5 +1,6 @@ package liquidjava.diagnostics.errors; +import liquidjava.diagnostics.TranslationTable; import liquidjava.rj_language.Predicate; import spoon.reflect.declaration.CtElement; @@ -10,19 +11,18 @@ */ public class StateConflictError extends LJError { - private Predicate predicate; + private String state; private String className; - public StateConflictError(CtElement element, Predicate predicate, String className) { - super("State Conflict Error", - "Found multiple disjoint states in state transition — State transition can only go to one state of each state set", - element); - this.predicate = predicate; + public StateConflictError(CtElement element, Predicate state, String className, TranslationTable translationTable) { + super("State Conflict Error", "Found multiple disjoint states in state transition — State transition can only go to one state of each state set", element.getPosition(), + element.toString(), translationTable); + this.state = state.toString(); this.className = className; } - public Predicate getPredicate() { - return predicate; + public String getState() { + return state; } public String getClassName() { @@ -33,7 +33,7 @@ public String getClassName() { public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Class: ").append(className).append("\n"); - sb.append("Predicate: ").append(predicate.toString()); + sb.append("State: ").append(state); return super.toString(sb.toString()); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java index 5810946d..e5f95d69 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java @@ -2,6 +2,8 @@ import java.util.Arrays; +import liquidjava.diagnostics.TranslationTable; +import liquidjava.rj_language.Predicate; import spoon.reflect.declaration.CtElement; /** @@ -15,11 +17,13 @@ public class StateRefinementError extends LJError { private final String[] expected; private final String found; - public StateRefinementError(CtElement element, String method, String[] expected, String found) { - super("State Refinement Error", "State refinement transition violation", element); + public StateRefinementError(CtElement element, String method, Predicate[] expected, Predicate found, + TranslationTable translationTable) { + super("State Refinement Error", "State refinement transition violation", element.getPosition(), + element.toString(), translationTable); this.method = method; - this.expected = expected; - this.found = found; + this.expected = Arrays.stream(expected).map(Predicate::toString).toArray(String[]::new); + this.found = found.toString(); } public String getMethod() { diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java index ba679cc6..80a7670c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java @@ -16,7 +16,7 @@ public SyntaxError(String message, String refinement) { } public SyntaxError(String message, CtElement element, String refinement) { - super("Syntax Error", message, element); + super("Syntax Error", message, element.getPosition(), element.toString(), null); this.refinement = refinement; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java index 6a4eef29..95f0d121 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java @@ -11,8 +11,8 @@ public class ExternalClassNotFoundWarning extends LJWarning { private String className; - public ExternalClassNotFoundWarning(CtElement element, String className) { - super("Class in external refinement not found", element); + public ExternalClassNotFoundWarning(CtElement element, String message, String className) { + super(message, element); this.className = className; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java index eee01505..6ea00388 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java @@ -12,8 +12,8 @@ public class ExternalMethodNotFoundWarning extends LJWarning { private String methodName; private String className; - public ExternalMethodNotFoundWarning(CtElement element, String methodName, String className) { - super("Method in external refinement not found", element); + public ExternalMethodNotFoundWarning(CtElement element, String message, String methodName, String className) { + super(message, element); this.methodName = methodName; this.className = className; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java index 7cf59dd9..8cecc0c7 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java @@ -49,10 +49,15 @@ public SourcePosition getLocation() { public String toString(String extra) { StringBuilder sb = new StringBuilder(); - sb.append(message).append(" at: \n").append(element.toString().replace("@liquidjava.specification.", "@")) - .append("\n\n"); + sb.append(message); + + if (element != null) + sb.append(" at: \n").append(element.toString().replace("@liquidjava.specification.", "@")); + if (extra != null) - sb.append(extra).append("\n"); + sb.append("\n").append(extra); + + sb.append("\n"); sb.append("Location: ").append(location != null ? Utils.stripParens(location.toString()) : "") .append("\n"); return sb.toString(); diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java b/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java index 46069f40..75b8edc5 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java @@ -1,9 +1,10 @@ package liquidjava.processor; +import static liquidjava.diagnostics.LJDiagnostics.diagnostics; + import java.util.ArrayList; import java.util.List; -import liquidjava.diagnostics.ErrorEmitter; import liquidjava.processor.ann_generation.FieldGhostsGeneration; import liquidjava.processor.context.Context; import liquidjava.processor.refinement_checker.ExternalRefinementTypeChecker; @@ -18,11 +19,9 @@ public class RefinementProcessor extends AbstractProcessor { List visitedPackages = new ArrayList<>(); Factory factory; - ErrorEmitter errorEmitter; - public RefinementProcessor(Factory factory, ErrorEmitter ee) { + public RefinementProcessor(Factory factory) { this.factory = factory; - errorEmitter = ee; } @Override @@ -32,15 +31,14 @@ public void process(CtPackage pkg) { Context c = Context.getInstance(); c.reinitializeAllContext(); - pkg.accept(new FieldGhostsGeneration(c, factory, errorEmitter)); // generate annotations for field ghosts + pkg.accept(new FieldGhostsGeneration(c, factory)); // generate annotations for field ghosts // void spoon.reflect.visitor.CtVisitable.accept(CtVisitor arg0) - pkg.accept(new ExternalRefinementTypeChecker(c, factory, errorEmitter)); - - pkg.accept(new MethodsFirstChecker(c, factory, errorEmitter)); // double passing idea (instead of headers) + pkg.accept(new ExternalRefinementTypeChecker(c, factory)); + pkg.accept(new MethodsFirstChecker(c, factory)); // double passing idea (instead of headers) - pkg.accept(new RefinementTypeChecker(c, factory, errorEmitter)); - if (errorEmitter.foundError()) + pkg.accept(new RefinementTypeChecker(c, factory)); + if (diagnostics.foundError()) return; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java b/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java index dbe672f5..719e6d40 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java @@ -1,6 +1,7 @@ package liquidjava.processor.ann_generation; -import liquidjava.diagnostics.ErrorEmitter; +import static liquidjava.diagnostics.LJDiagnostics.diagnostics; + import liquidjava.processor.context.Context; import liquidjava.specification.Ghost; import spoon.reflect.declaration.*; @@ -11,12 +12,10 @@ public class FieldGhostsGeneration extends CtScanner { Context context; Factory factory; - ErrorEmitter errorEmitter; - public FieldGhostsGeneration(Context c, Factory fac, ErrorEmitter errorEmitter) { - this.context = c; - this.factory = fac; - this.errorEmitter = errorEmitter; + public FieldGhostsGeneration(Context context, Factory factory) { + this.context = context; + this.factory = factory; } public Context getContext() { @@ -29,7 +28,7 @@ public Factory getFactory() { @Override public void visitCtClass(CtClass ctClass) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/AliasWrapper.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/AliasWrapper.java index cb218d7e..fc8283d5 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/AliasWrapper.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/AliasWrapper.java @@ -5,7 +5,6 @@ import java.util.List; import java.util.Map; -import liquidjava.diagnostics.ErrorEmitter; import liquidjava.processor.facade.AliasDTO; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.Expression; @@ -60,9 +59,8 @@ public Expression getNewExpression(List newNames) { return expr.getExpression().clone(); } - public Predicate getPremises(List list, List newNames, CtElement elem, ErrorEmitter ee) - throws ParsingException { - List invocationPredicates = getPredicatesFromExpression(list, elem, ee); + public Predicate getPremises(List list, List newNames, CtElement elem) throws ParsingException { + List invocationPredicates = getPredicatesFromExpression(list, elem); Predicate prem = new Predicate(); for (int i = 0; i < invocationPredicates.size(); i++) { prem = Predicate.createConjunction(prem, @@ -71,11 +69,10 @@ public Predicate getPremises(List list, List newNames, CtElement return prem.clone(); } - private List getPredicatesFromExpression(List list, CtElement elem, ErrorEmitter ee) - throws ParsingException { + private List getPredicatesFromExpression(List list, CtElement elem) throws ParsingException { List lp = new ArrayList<>(); for (String e : list) - lp.add(new Predicate(e, elem, ee)); + lp.add(new Predicate(e, elem)); return lp; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java index 8580135b..bd825c8f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java @@ -1,12 +1,13 @@ package liquidjava.processor.refinement_checker; +import static liquidjava.diagnostics.LJDiagnostics.diagnostics; + import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; - -import liquidjava.diagnostics.ErrorEmitter; -import liquidjava.diagnostics.ErrorHandler; +import liquidjava.diagnostics.errors.CustomError; +import liquidjava.diagnostics.warnings.ExternalClassNotFoundWarning; +import liquidjava.diagnostics.warnings.ExternalMethodNotFoundWarning; import liquidjava.processor.context.Context; import liquidjava.processor.context.GhostFunction; import liquidjava.processor.facade.GhostDTO; @@ -29,8 +30,8 @@ public class ExternalRefinementTypeChecker extends TypeChecker { String prefix; MethodsFunctionsChecker m; - public ExternalRefinementTypeChecker(Context context, Factory fac, ErrorEmitter errorEmitter) { - super(context, fac, errorEmitter); + public ExternalRefinementTypeChecker(Context context, Factory factory) { + super(context, factory); } @Override @@ -40,20 +41,21 @@ public void visitCtClass(CtClass ctClass) { @Override public void visitCtInterface(CtInterface intrface) { - if (errorEmitter.foundError()) + if (diagnostics.foundError()) return; Optional externalRefinements = getExternalRefinement(intrface); if (externalRefinements.isPresent()) { this.prefix = externalRefinements.get(); if (!classExists(prefix)) { - ErrorHandler.printCustomError(intrface, "Could not find class '" + prefix + "'", errorEmitter); + String message = String.format("Could not find external class '%s'", prefix); + diagnostics.add(new ExternalClassNotFoundWarning(intrface, message, prefix)); return; } try { getRefinementFromAnnotation(intrface); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } handleStateSetsFromAnnotation(intrface); super.visitCtInterface(intrface); @@ -62,14 +64,14 @@ public void visitCtInterface(CtInterface intrface) { @Override public void visitCtField(CtField f) { - if (errorEmitter.foundError()) + if (diagnostics.foundError()) return; Optional oc; try { oc = getRefinementFromAnnotation(f); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } Predicate c = oc.orElse(new Predicate()); context.addGlobalVariableToContext(f.getSimpleName(), prefix, f.getType(), c); @@ -77,7 +79,7 @@ public void visitCtField(CtField f) { } public void visitCtMethod(CtMethod method) { - if (errorEmitter.foundError()) + if (diagnostics.foundError()) return; CtType targetType = factory.Type().createReference(prefix).getTypeDeclaration(); @@ -87,31 +89,24 @@ public void visitCtMethod(CtMethod method) { boolean isConstructor = method.getSimpleName().equals(targetType.getSimpleName()); if (isConstructor) { if (!constructorExists(targetType, method)) { - ErrorHandler.printCustomError(method, - String.format("Could not find constructor '%s' for '%s'", method.getSignature(), prefix), - errorEmitter); - return; + String title = String.format("Could not find constructor '%s' for '%s'", method.getSignature(), prefix); + String[] overloads = getOverloads(targetType, method); + String message = overloads.length == 0 ? title + : title + "\nAvailable constructors:\n " + String.join("\n ", overloads); + + diagnostics.add(new ExternalMethodNotFoundWarning(method, message, method.getSignature(), prefix)); } } else { if (!methodExists(targetType, method)) { - String matchingNames = targetType.getMethods().stream() - .filter(m -> m.getSimpleName().equals(method.getSimpleName())) - .map(m -> String.format("%s %s", m.getType().getSimpleName(), m.getSignature())) - .collect(Collectors.joining("\n ")); - - StringBuilder sb = new StringBuilder(); - sb.append(String.format("Could not find method '%s %s' for '%s'", method.getType().getSimpleName(), - method.getSignature(), prefix)); - - if (!matchingNames.isEmpty()) { - sb.append("\nAvailable overloads:\n "); - sb.append(matchingNames); - } - ErrorHandler.printCustomError(method, sb.toString(), errorEmitter); + String title = String.format("Could not find method '%s %s' for '%s'", method.getType().getSimpleName(), + method.getSignature(), prefix); + String[] overloads = getOverloads(targetType, method); + String message = overloads.length == 0 ? title + : title + "\nAvailable overloads:\n " + String.join("\n ", overloads); + diagnostics.add(new ExternalMethodNotFoundWarning(method, message, method.getSignature(), prefix)); return; } } - MethodsFunctionsChecker mfc = new MethodsFunctionsChecker(this); try { mfc.getMethodRefinements(method, prefix); @@ -119,9 +114,6 @@ public void visitCtMethod(CtMethod method) { return; } super.visitCtMethod(method); - - // - // System.out.println("visited method external"); } protected void getGhostFunction(String value, CtElement element) { @@ -135,8 +127,7 @@ protected void getGhostFunction(String value, CtElement element) { } } catch (ParsingException e) { - ErrorHandler.printCustomError(element, "Could not parse the Ghost Function" + e.getMessage(), errorEmitter); - // e.printStackTrace(); + diagnostics.add(new CustomError(element, "Could not parse the ghost function" + e.getMessage())); } } @@ -206,4 +197,9 @@ private boolean parametersMatch(List targetParams, List refinementParams) } return true; } + + private String[] getOverloads(CtType targetType, CtMethod method) { + return targetType.getMethods().stream().filter(m -> m.getSimpleName().equals(method.getSimpleName())) + .map(m -> String.format("%s %s", m.getType().getSimpleName(), m.getSignature())).toArray(String[]::new); + } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/MethodsFirstChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/MethodsFirstChecker.java index 92cadcf5..6e84985d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/MethodsFirstChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/MethodsFirstChecker.java @@ -1,9 +1,10 @@ package liquidjava.processor.refinement_checker; +import static liquidjava.diagnostics.LJDiagnostics.diagnostics; + import java.util.ArrayList; import java.util.List; -import liquidjava.diagnostics.ErrorEmitter; import liquidjava.processor.context.Context; import liquidjava.processor.refinement_checker.general_checkers.MethodsFunctionsChecker; import liquidjava.rj_language.parsing.ParsingException; @@ -20,15 +21,15 @@ public class MethodsFirstChecker extends TypeChecker { MethodsFunctionsChecker mfc; List visitedClasses; - public MethodsFirstChecker(Context c, Factory fac, ErrorEmitter errorEmitter) { - super(c, fac, errorEmitter); + public MethodsFirstChecker(Context context, Factory factory) { + super(context, factory); mfc = new MethodsFunctionsChecker(this); visitedClasses = new ArrayList<>(); } @Override public void visitCtClass(CtClass ctClass) { - if (errorEmitter.foundError()) + if (diagnostics.foundError()) return; context.reinitializeContext(); @@ -55,7 +56,7 @@ public void visitCtClass(CtClass ctClass) { try { getRefinementFromAnnotation(ctClass); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } handleStateSetsFromAnnotation(ctClass); super.visitCtClass(ctClass); @@ -63,7 +64,7 @@ public void visitCtClass(CtClass ctClass) { @Override public void visitCtInterface(CtInterface intrface) { - if (errorEmitter.foundError()) + if (diagnostics.foundError()) return; if (visitedClasses.contains(intrface.getQualifiedName())) @@ -76,7 +77,7 @@ public void visitCtInterface(CtInterface intrface) { try { getRefinementFromAnnotation(intrface); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } handleStateSetsFromAnnotation(intrface); super.visitCtInterface(intrface); @@ -84,7 +85,7 @@ public void visitCtInterface(CtInterface intrface) { @Override public void visitCtConstructor(CtConstructor c) { - if (errorEmitter.foundError()) + if (diagnostics.foundError()) return; context.enterContext(); @@ -99,7 +100,7 @@ public void visitCtConstructor(CtConstructor c) { } public void visitCtMethod(CtMethod method) { - if (errorEmitter.foundError()) + if (diagnostics.foundError()) return; context.enterContext(); diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java index 53b65116..2365d9b9 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java @@ -1,11 +1,12 @@ package liquidjava.processor.refinement_checker; +import static liquidjava.diagnostics.LJDiagnostics.diagnostics; + import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.List; import java.util.Optional; -import liquidjava.diagnostics.ErrorEmitter; import liquidjava.processor.context.*; import liquidjava.processor.refinement_checker.general_checkers.MethodsFunctionsChecker; import liquidjava.processor.refinement_checker.general_checkers.OperationsChecker; @@ -55,8 +56,8 @@ public class RefinementTypeChecker extends TypeChecker { OperationsChecker otc; MethodsFunctionsChecker mfc; - public RefinementTypeChecker(Context context, Factory factory, ErrorEmitter errorEmitter) { - super(context, factory, errorEmitter); + public RefinementTypeChecker(Context context, Factory factory) { + super(context, factory); otc = new OperationsChecker(this); mfc = new MethodsFunctionsChecker(this); } @@ -65,7 +66,7 @@ public RefinementTypeChecker(Context context, Factory factory, ErrorEmitter erro @Override public void visitCtClass(CtClass ctClass) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -76,7 +77,7 @@ public void visitCtClass(CtClass ctClass) { @Override public void visitCtInterface(CtInterface intrface) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -89,7 +90,7 @@ public void visitCtInterface(CtInterface intrface) { @Override public void visitCtAnnotationType(CtAnnotationType annotationType) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } super.visitCtAnnotationType(annotationType); @@ -97,7 +98,7 @@ public void visitCtAnnotationType(CtAnnotationType ann @Override public void visitCtConstructor(CtConstructor c) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -108,7 +109,7 @@ public void visitCtConstructor(CtConstructor c) { } public void visitCtMethod(CtMethod method) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -122,7 +123,7 @@ public void visitCtMethod(CtMethod method) { @Override public void visitCtLocalVariable(CtLocalVariable localVariable) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -133,7 +134,7 @@ public void visitCtLocalVariable(CtLocalVariable localVariable) { try { a = getRefinementFromAnnotation(localVariable); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } context.addVarToContext(localVariable.getSimpleName(), localVariable.getType(), a.orElse(new Predicate()), localVariable); @@ -151,7 +152,7 @@ public void visitCtLocalVariable(CtLocalVariable localVariable) { checkVariableRefinements(refinementFound, varName, localVariable.getType(), localVariable, localVariable); } catch (ParsingException e1) { - return; // error already in ErrorEmitter + return; // error already reported } AuxStateHandler.addStateRefinements(this, varName, e); @@ -160,7 +161,7 @@ public void visitCtLocalVariable(CtLocalVariable localVariable) { @Override public void visitCtNewArray(CtNewArray newArray) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -172,7 +173,7 @@ public void visitCtNewArray(CtNewArray newArray) { try { c = getExpressionRefinements(exp); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } String name = String.format(Formats.FRESH, context.getCounter()); if (c.getVariableNames().contains(Keys.WILDCARD)) { @@ -183,11 +184,10 @@ public void visitCtNewArray(CtNewArray newArray) { context.addVarToContext(name, factory.Type().INTEGER_PRIMITIVE, c, exp); Predicate ep; try { - ep = Predicate.createEquals( - BuiltinFunctionPredicate.builtin_length(Keys.WILDCARD, newArray, getErrorEmitter()), + ep = Predicate.createEquals(BuiltinFunctionPredicate.length(Keys.WILDCARD, newArray), Predicate.createVar(name)); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } newArray.putMetadata(Keys.REFINEMENT, ep); } @@ -195,7 +195,7 @@ public void visitCtNewArray(CtNewArray newArray) { @Override public void visitCtThisAccess(CtThisAccess thisAccess) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -212,7 +212,7 @@ public void visitCtThisAccess(CtThisAccess thisAccess) { @SuppressWarnings("unchecked") @Override public void visitCtAssignment(CtAssignment assignment) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -246,7 +246,7 @@ public void visitCtAssignment(CtAssignment assignment) { @Override public void visitCtArrayRead(CtArrayRead arrayRead) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -259,7 +259,7 @@ public void visitCtArrayRead(CtArrayRead arrayRead) { @Override public void visitCtLiteral(CtLiteral lit) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -281,7 +281,7 @@ public void visitCtLiteral(CtLiteral lit) { @Override public void visitCtField(CtField f) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -290,7 +290,7 @@ public void visitCtField(CtField f) { try { c = getRefinementFromAnnotation(f); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } // context.addVarToContext(f.getSimpleName(), f.getType(), // c.map( i -> i.substituteVariable(WILD_VAR, f.getSimpleName()).orElse(new @@ -308,7 +308,7 @@ public void visitCtField(CtField f) { @Override public void visitCtFieldRead(CtFieldRead fieldRead) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -332,9 +332,9 @@ public void visitCtFieldRead(CtFieldRead fieldRead) { String targetName = fieldRead.getTarget().toString(); try { fieldRead.putMetadata(Keys.REFINEMENT, Predicate.createEquals(Predicate.createVar(Keys.WILDCARD), - BuiltinFunctionPredicate.builtin_length(targetName, fieldRead, getErrorEmitter()))); + BuiltinFunctionPredicate.length(targetName, fieldRead))); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } } else { fieldRead.putMetadata(Keys.REFINEMENT, new Predicate()); @@ -346,7 +346,7 @@ public void visitCtFieldRead(CtFieldRead fieldRead) { @Override public void visitCtVariableRead(CtVariableRead variableRead) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -360,7 +360,7 @@ public void visitCtVariableRead(CtVariableRead variableRead) { */ @Override public void visitCtBinaryOperator(CtBinaryOperator operator) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -368,13 +368,13 @@ public void visitCtBinaryOperator(CtBinaryOperator operator) { try { otc.getBinaryOpRefinements(operator); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } } @Override public void visitCtUnaryOperator(CtUnaryOperator operator) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -382,12 +382,12 @@ public void visitCtUnaryOperator(CtUnaryOperator operator) { try { otc.getUnaryOpRefinements(operator); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } } public void visitCtInvocation(CtInvocation invocation) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -397,7 +397,7 @@ public void visitCtInvocation(CtInvocation invocation) { @Override public void visitCtReturn(CtReturn ret) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -407,7 +407,7 @@ public void visitCtReturn(CtReturn ret) { @Override public void visitCtIf(CtIf ifElement) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -417,7 +417,7 @@ public void visitCtIf(CtIf ifElement) { try { expRefs = getExpressionRefinements(exp); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } String freshVarName = String.format(Formats.FRESH, context.getCounter()); expRefs = expRefs.substituteVariable(Keys.WILDCARD, freshVarName); @@ -463,7 +463,7 @@ public void visitCtIf(CtIf ifElement) { @Override public void visitCtArrayWrite(CtArrayWrite arrayWrite) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -471,17 +471,17 @@ public void visitCtArrayWrite(CtArrayWrite arrayWrite) { CtExpression index = arrayWrite.getIndexExpression(); BuiltinFunctionPredicate fp; try { - fp = BuiltinFunctionPredicate.builtin_addToIndex(arrayWrite.getTarget().toString(), index.toString(), - Keys.WILDCARD, arrayWrite, getErrorEmitter()); + fp = BuiltinFunctionPredicate.addToIndex(arrayWrite.getTarget().toString(), index.toString(), Keys.WILDCARD, + arrayWrite); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } arrayWrite.putMetadata(Keys.REFINEMENT, fp); } @Override public void visitCtConditional(CtConditional conditional) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -494,7 +494,7 @@ public void visitCtConditional(CtConditional conditional) { @Override public void visitCtConstructorCall(CtConstructorCall ctConstructorCall) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -504,7 +504,7 @@ public void visitCtConstructorCall(CtConstructorCall ctConstructorCall) { @Override public void visitCtNewClass(CtNewClass newClass) { - if (errorEmitter.foundError()) { + if (diagnostics.foundError()) { return; } @@ -534,7 +534,7 @@ private void checkAssignment(String name, CtTypeReference type, CtExpression< try { checkVariableRefinements(refinementFound, name, type, parentElem, varDecl); } catch (ParsingException e) { - return; // error already in ErrorEmitter + return; // error already reported } } @@ -552,7 +552,7 @@ private Predicate getExpressionRefinements(CtExpression element) throws Parsi return getRefinement(op); } else if (element instanceof CtLiteral) { CtLiteral l = (CtLiteral) element; - return new Predicate(l.getValue().toString(), l, errorEmitter); + return new Predicate(l.getValue().toString(), l); } else if (element instanceof CtInvocation) { CtInvocation inv = (CtInvocation) element; visitCtInvocation(inv); diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java index 375bff40..ebce5070 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java @@ -1,12 +1,15 @@ package liquidjava.processor.refinement_checker; +import static liquidjava.diagnostics.LJDiagnostics.diagnostics; + import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.List; import java.util.Optional; -import liquidjava.diagnostics.ErrorEmitter; -import liquidjava.diagnostics.ErrorHandler; +import liquidjava.diagnostics.errors.CustomError; +import liquidjava.diagnostics.errors.InvalidRefinementError; +import liquidjava.diagnostics.errors.SyntaxError; import liquidjava.processor.context.AliasWrapper; import liquidjava.processor.context.Context; import liquidjava.processor.context.GhostFunction; @@ -37,13 +40,11 @@ public abstract class TypeChecker extends CtScanner { Context context; Factory factory; VCChecker vcChecker; - ErrorEmitter errorEmitter; - public TypeChecker(Context c, Factory fac, ErrorEmitter errorEmitter) { - this.context = c; - this.factory = fac; - this.errorEmitter = errorEmitter; - vcChecker = new VCChecker(errorEmitter); + public TypeChecker(Context context, Factory factory) { + this.context = context; + this.factory = factory; + vcChecker = new VCChecker(); } public Context getContext() { @@ -79,14 +80,15 @@ public Optional getRefinementFromAnnotation(CtElement element) throws } } if (ref.isPresent()) { - Predicate p = new Predicate(ref.get(), element, errorEmitter); + Predicate p = new Predicate(ref.get(), element); // check if refinement is valid if (!p.getExpression().isBooleanExpression()) { - ErrorHandler.printCustomError(element, "Refinement predicate must be a boolean expression", - errorEmitter); + diagnostics.add(new InvalidRefinementError(element, "Refinement predicate must be a boolean expression", + ref.get())); + return Optional.empty(); } - if (errorEmitter.foundError()) + if (diagnostics.foundError()) return Optional.empty(); constr = Optional.of(p); @@ -119,8 +121,8 @@ private void createStateSet(CtNewArray e, int set, CtElement element) { CtLiteral s = (CtLiteral) ce; String f = s.getValue(); if (Character.isUpperCase(f.charAt(0))) { - ErrorHandler.printCustomError(s, "State name must start with lowercase in '" + f + "'", - errorEmitter); + diagnostics + .add(new CustomError(s, String.format("State name must start with lowercase in '%s'", f))); } } } @@ -161,12 +163,12 @@ private void createStateGhost(String string, CtAnnotation try { gd = RefinementsParser.getGhostDeclaration(string); } catch (ParsingException e) { - ErrorHandler.printCustomError(ann, "Could not parse the Ghost Function" + e.getMessage(), errorEmitter); + diagnostics.add(new CustomError(ann, "Could not parse the ghost function " + e.getMessage())); return; } if (gd.getParam_types().size() > 0) { - ErrorHandler.printCustomError(ann, "Ghost States have the class as parameter " - + "by default, no other parameters are allowed in '" + string + "'", errorEmitter); + diagnostics.add(new CustomError(ann, "Ghost States have the class as parameter " + + "by default, no other parameters are allowed in '" + string + "'")); return; } // Set class as parameter of Ghost @@ -224,7 +226,7 @@ protected void getGhostFunction(String value, CtElement element) { context.addGhostFunction(gh); } } catch (ParsingException e) { - ErrorHandler.printCustomError(element, "Could not parse the Ghost Function" + e.getMessage(), errorEmitter); + diagnostics.add(new CustomError(element, "Could not parse the ghost function " + e.getMessage())); // e.printStackTrace(); return; } @@ -246,17 +248,15 @@ protected void handleAlias(String value, CtElement element) { a.parse(path); // refinement alias must return a boolean expression if (a.getExpression() != null && !a.getExpression().isBooleanExpression()) { - ErrorHandler.printCustomError(element, "Refinement alias must return a boolean expression", - errorEmitter); + diagnostics.add(new InvalidRefinementError(element, + "Refinement alias must return a boolean expression", value)); return; } AliasWrapper aw = new AliasWrapper(a, factory, Keys.WILDCARD, context, klass, path); context.addAlias(aw); } } catch (ParsingException e) { - ErrorHandler.printSyntaxError(e.getMessage(), value, element, errorEmitter); - return; - // e.printStackTrace(); + diagnostics.add(new SyntaxError(e.getMessage(), element, value)); } } @@ -326,11 +326,7 @@ public void createSameStateError(CtElement element, Predicate expectedType, Stri vcChecker.printSameStateError(element, expectedType, klass); } - public void createStateMismatchError(CtElement element, String method, Predicate c, String states) { - vcChecker.printStateMismatchError(element, method, c, states); - } - - public ErrorEmitter getErrorEmitter() { - return errorEmitter; + public void createStateMismatchError(CtElement element, String method, Predicate found, Predicate[] expected) { + vcChecker.printStateMismatchError(element, method, found, expected); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java index cba055bc..2a4c17ae 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java @@ -1,25 +1,30 @@ package liquidjava.processor.refinement_checker; +import static liquidjava.diagnostics.LJDiagnostics.diagnostics; + import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Set; import java.util.Stack; import java.util.function.Consumer; import java.util.stream.Collectors; -import liquidjava.diagnostics.ErrorEmitter; -import liquidjava.diagnostics.ErrorHandler; +import liquidjava.diagnostics.errors.CustomError; +import liquidjava.diagnostics.errors.GhostInvocationError; +import liquidjava.diagnostics.errors.LJError; +import liquidjava.diagnostics.errors.NotFoundError; +import liquidjava.diagnostics.TranslationTable; +import liquidjava.diagnostics.errors.RefinementError; +import liquidjava.diagnostics.errors.StateConflictError; +import liquidjava.diagnostics.errors.StateRefinementError; import liquidjava.processor.VCImplication; import liquidjava.processor.context.*; import liquidjava.rj_language.Predicate; import liquidjava.smt.GhostFunctionError; -import liquidjava.smt.NotFoundError; +import liquidjava.smt.NotFoundSMTError; import liquidjava.smt.SMTEvaluator; import liquidjava.smt.TypeCheckError; -import liquidjava.smt.TypeMismatchError; import liquidjava.utils.constants.Keys; -import spoon.reflect.code.CtInvocation; import spoon.reflect.cu.SourcePosition; import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; @@ -28,12 +33,10 @@ public class VCChecker { private final Context context; private final List pathVariables; - private final ErrorEmitter errorEmitter; - public VCChecker(ErrorEmitter errorEmitter) { + public VCChecker() { context = Context.getInstance(); pathVariables = new Stack<>(); - this.errorEmitter = errorEmitter; } public void processSubtyping(Predicate expectedType, List list, CtElement element, Factory f) { @@ -42,24 +45,23 @@ public void processSubtyping(Predicate expectedType, List list, CtEl if (expectedType.isBooleanTrue()) return; - HashMap map = new HashMap<>(); + TranslationTable map = new TranslationTable(); String[] s = { Keys.WILDCARD, Keys.THIS }; Predicate premisesBeforeChange = joinPredicates(expectedType, mainVars, lrv, map).toConjunctions(); Predicate premises = new Predicate(); Predicate et = new Predicate(); try { List filtered = filterGhostStatesForVariables(list, mainVars, lrv); - premises = premisesBeforeChange.changeStatesToRefinements(filtered, s, errorEmitter) - .changeAliasToRefinement(context, f); + premises = premisesBeforeChange.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); - et = expectedType.changeStatesToRefinements(filtered, s, errorEmitter).changeAliasToRefinement(context, f); + et = expectedType.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); } catch (Exception e) { - ErrorHandler.printError(element, e.getMessage(), expectedType, premises, map, errorEmitter); + diagnostics.add(new RefinementError(element, expectedType, premises.simplify(), map)); return; } try { - smtChecking(premises, et); + smtChecking(premises, et, element.getPosition()); } catch (Exception e) { // To emit the message we use the constraints before the alias and state change printError(e, premisesBeforeChange, expectedType, element, map); @@ -82,7 +84,7 @@ public boolean canProcessSubtyping(Predicate type, Predicate expectedType, List< return true; // Predicate premises = joinPredicates(type, element, mainVars, lrv); - HashMap map = new HashMap<>(); + TranslationTable map = new TranslationTable(); String[] s = { Keys.WILDCARD, Keys.THIS }; Predicate premises = new Predicate(); @@ -90,16 +92,12 @@ public boolean canProcessSubtyping(Predicate type, Predicate expectedType, List< try { premises = joinPredicates(expectedType, mainVars, lrv, map).toConjunctions(); List filtered = filterGhostStatesForVariables(list, mainVars, lrv); - premises = Predicate.createConjunction(premises, type).changeStatesToRefinements(filtered, s, errorEmitter) + premises = Predicate.createConjunction(premises, type).changeStatesToRefinements(filtered, s) .changeAliasToRefinement(context, f); - et = expectedType.changeStatesToRefinements(filtered, s, errorEmitter).changeAliasToRefinement(context, f); + et = expectedType.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); } catch (Exception e) { return false; - // printError(premises, expectedType, element, map, e.getMessage()); } - - // System.out.println("premise: " + premises.toString() + "\nexpectation: " + - // et.toString()); return smtChecks(premises, et, p); } @@ -145,7 +143,7 @@ private List filterGhostStatesForVariables(List list, Li } private VCImplication joinPredicates(Predicate expectedType, List mainVars, - List vars, HashMap map) { + List vars, TranslationTable map) { VCImplication firstSi = null; VCImplication lastSi = null; @@ -185,19 +183,8 @@ private VCImplication joinPredicates(Predicate expectedType, List map) { + private void addMap(RefinedVariable var, TranslationTable map) { map.put(var.getName(), var.getPlacementInCode()); - // if(var instanceof VariableInstance) { - // VariableInstance vi = (VariableInstance) var; - // if(vi.getParent().isPresent()) - // map.put(vi.getName(), vi.getParent().get().getName()); - // else if(instancePattern.matcher(var.getName()).matches()){ - // String result = var.getName().replaceAll("(_[0-9]+)$", "").replaceAll("^#", - // ""); - // map.put(var.getName(), result); - // } - // }else if(thisPattern.matcher(var.getName()).matches()) - // map.put(var.getName(), "this"); } private void gatherVariables(Predicate expectedType, List lrv, List mainVars) { @@ -249,15 +236,11 @@ private void recAuxGetVars(RefinedVariable var, List newVars) { public boolean smtChecks(Predicate cSMT, Predicate expectedType, SourcePosition p) { try { - new SMTEvaluator().verifySubtype(cSMT, expectedType, context); + new SMTEvaluator().verifySubtype(cSMT, expectedType, context, p); } catch (TypeCheckError e) { return false; } catch (Exception e) { - // System.err.println("Unknown error:"+e.getMessage()); - // e.printStackTrace(); - // System.exit(7); - // fail(); - errorEmitter.addError("Unknown Error", e.getMessage(), p, 7); + diagnostics.add(new CustomError(e.getMessage(), p)); } return true; } @@ -273,9 +256,9 @@ public boolean smtChecks(Predicate cSMT, Predicate expectedType, SourcePosition * @throws GhostFunctionError * @throws TypeCheckError */ - private void smtChecking(Predicate cSMT, Predicate expectedType) + private void smtChecking(Predicate cSMT, Predicate expectedType, SourcePosition p) throws TypeCheckError, GhostFunctionError, Exception { - new SMTEvaluator().verifySubtype(cSMT, expectedType, context); + new SMTEvaluator().verifySubtype(cSMT, expectedType, context, p); } /** @@ -306,10 +289,10 @@ void removePathVariableThatIncludes(String otherVar) { // Errors--------------------------------------------------------------------------------------------------- - private HashMap createMap(CtElement element, Predicate expectedType) { + private TranslationTable createMap(CtElement element, Predicate expectedType) { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(expectedType, lrv, mainVars); - HashMap map = new HashMap<>(); + TranslationTable map = new TranslationTable(); joinPredicates(expectedType, mainVars, lrv, map); return map; } @@ -319,54 +302,41 @@ protected void printSubtypingError(CtElement element, Predicate expectedType, Pr List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(expectedType, lrv, mainVars); gatherVariables(foundType, lrv, mainVars); - HashMap map = new HashMap<>(); + TranslationTable map = new TranslationTable(); Predicate premises = joinPredicates(expectedType, mainVars, lrv, map).toConjunctions(); - - ErrorHandler.printError(element, customeMsg, expectedType, premises, map, errorEmitter); + diagnostics.add(new RefinementError(element, expectedType, premises.simplify(), map)); } public void printSameStateError(CtElement element, Predicate expectedType, String klass) { - HashMap map = createMap(element, expectedType); - ErrorHandler.printSameStateSetError(element, expectedType, klass, map, errorEmitter); + TranslationTable map = createMap(element, expectedType); + diagnostics.add(new StateConflictError(element, expectedType, klass, map)); } private void printError(Exception e, Predicate premisesBeforeChange, Predicate expectedType, CtElement element, - HashMap map) { - String s = null; - if (element instanceof CtInvocation) { - CtInvocation ci = (CtInvocation) element; - String totalS = ci.getExecutable().toString(); - if (ci.getTarget() != null) { - int targetL = ci.getTarget().toString().length(); - totalS = ci.toString().substring(targetL + 1); - } - s = "Method invocation " + totalS + " in:"; - } + TranslationTable map) { + LJError error = mapError(e, premisesBeforeChange, expectedType, element, map); + diagnostics.add(error); + } - Predicate etMessageReady = expectedType; // substituteByMap(expectedType, map); - Predicate cSMTMessageReady = premisesBeforeChange; // substituteByMap(premisesBeforeChange, map); + private LJError mapError(Exception e, Predicate premisesBeforeChange, Predicate expectedType, CtElement element, + TranslationTable map) { if (e instanceof TypeCheckError) { - ErrorHandler.printError(element, s, etMessageReady, cSMTMessageReady, map, errorEmitter); + return new RefinementError(element, expectedType, premisesBeforeChange.simplify(), map); } else if (e instanceof GhostFunctionError) { - ErrorHandler.printErrorArgs(element, etMessageReady, e.getMessage(), map, errorEmitter); - } else if (e instanceof TypeMismatchError) { - ErrorHandler.printErrorTypeMismatch(element, etMessageReady, e.getMessage(), map, errorEmitter); - } else if (e instanceof NotFoundError) { - ErrorHandler.printNotFound(element, cSMTMessageReady, etMessageReady, e.getMessage(), map, errorEmitter); + return new GhostInvocationError("Invalid types or number of arguments in ghost invocation", + element.getPosition(), expectedType, map); + } else if (e instanceof NotFoundSMTError) { + return new NotFoundError(element, e.getMessage(), map); } else { - ErrorHandler.printCustomError(element, e.getMessage(), errorEmitter); - // System.err.println("Unknown error:"+e.getMessage()); - // e.printStackTrace(); - // System.exit(7); + return new CustomError(element, e.getMessage()); } } - public void printStateMismatchError(CtElement element, String method, Predicate c, String states) { + public void printStateMismatchError(CtElement element, String method, Predicate found, Predicate[] states) { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); - gatherVariables(c, lrv, mainVars); - HashMap map = new HashMap<>(); - VCImplication constraintForErrorMsg = joinPredicates(c, mainVars, lrv, map); - // HashMap map = createMap(element, c); - ErrorHandler.printStateMismatch(element, method, constraintForErrorMsg, states, map, errorEmitter); + gatherVariables(found, lrv, mainVars); + TranslationTable map = new TranslationTable(); + VCImplication foundState = joinPredicates(found, mainVars, lrv, map); + diagnostics.add(new StateRefinementError(element, method, states, foundState.toConjunctions(), map)); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java index a4adf7c7..0a93f1ac 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java @@ -237,7 +237,7 @@ private Predicate getOperationRefinements(CtBinaryOperator operator, CtVariab a = a.substituteVariable(Keys.WILDCARD, ""); String s = a.toString().replace("(", "").replace(")", "").replace("==", "").replace(" ", ""); // TODO // IMPROVE - return new Predicate(String.format("(%s)", s), element, rtc.getErrorEmitter()); + return new Predicate(String.format("(%s)", s), element); } else if (element instanceof CtLiteral) { CtLiteral l = (CtLiteral) element; @@ -248,7 +248,7 @@ private Predicate getOperationRefinements(CtBinaryOperator operator, CtVariab if (l.getValue() == null) throw new ParsingException("Null literals are not supported"); - return new Predicate(l.getValue().toString(), element, rtc.getErrorEmitter()); + return new Predicate(l.getValue().toString(), element); } else if (element instanceof CtInvocation) { CtInvocation inv = (CtInvocation) element; @@ -266,7 +266,7 @@ private Predicate getOperationRefinements(CtBinaryOperator operator, CtVariab innerRefs = innerRefs.substituteVariable(Keys.WILDCARD, newName); rtc.getContext().addVarToContext(newName, fi.getType(), innerRefs, inv); - return new Predicate(newName, inv, rtc.getErrorEmitter()); // Return variable that represents the invocation + return new Predicate(newName, inv); // Return variable that represents the invocation } return rtc.getRefinement(element); // TODO Maybe add cases @@ -301,7 +301,7 @@ private Predicate getOperationRefinementFromExternalLib(CtInvocation inv, CtB } rtc.getContext().addVarToContext(newName, fi.getType(), innerRefs, inv); - return new Predicate(newName, inv, rtc.getErrorEmitter()); // Return variable that represents the invocation + return new Predicate(newName, inv); // Return variable that represents the invocation } return new Predicate(); } @@ -375,6 +375,6 @@ private Predicate getOperatorFromKind(UnaryOperatorKind kind, CtElement elem) th case NEG -> "-" + Keys.WILDCARD; default -> throw new ParsingException(kind + "operation not supported"); }; - return new Predicate(ret, elem, rtc.getErrorEmitter()); + return new Predicate(ret, elem); }; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinememtsPassage.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinememtsPassage.java index 04c0c14f..63e5ad18 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinememtsPassage.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinememtsPassage.java @@ -1,5 +1,7 @@ package liquidjava.processor.refinement_checker.object_checkers; +import static liquidjava.diagnostics.LJDiagnostics.diagnostics; + import java.util.HashMap; import java.util.List; import java.util.Optional; @@ -83,8 +85,7 @@ static void transferArgumentsRefinements(RefinedFunction superFunction, RefinedF } else { boolean f = tc.checksStateSMT(superArgRef, argRef, params.get(i).getPosition()); if (!f) { - // ErrorPrinter.printError(method, argRef, superArgRef); - if (!tc.getErrorEmitter().foundError()) + if (!diagnostics.foundError()) tc.createError(method, argRef, superArgRef, ""); } } @@ -100,8 +101,8 @@ static void transferReturnRefinement(RefinedFunction superFunction, RefinedFunct else { String name = String.format(Formats.FRESH, tc.getContext().getCounter()); tc.getContext().addVarToContext(name, superFunction.getType(), new Predicate(), method); - // functionRef might be stronger than superRef -> check (superRef <: - // functionRef) + // functionRef might be stronger than superRef + // check (superRef <: functionRef) functionRef = functionRef.substituteVariable(Keys.WILDCARD, name); superRef = superRef.substituteVariable(Keys.WILDCARD, name); for (String m : super2function.keySet()) @@ -110,10 +111,7 @@ static void transferReturnRefinement(RefinedFunction superFunction, RefinedFunct functionRef = functionRef.substituteVariable(m, super2function.get(m)); tc.checkStateSMT(functionRef, superRef, method, - "Return Refinement of Subclass must be subtype of the Return Refinement of the" + " Superclass"); - // boolean f = tc.checkStateSMT(functionRef, superRef, method); - // if(!f) - // ErrorPrinter.printError(method, superRef, functionRef); + "Return of subclass must be subtype of the return of the superclass"); } } @@ -152,18 +150,13 @@ private static void transferStateRefinements(RefinedFunction superFunction, Refi // fromSup <: fromSub <==> fromSup is sub type and fromSub is expectedType tc.checkStateSMT(superConst, subConst, method, "FROM State from Superclass must be subtype of FROM State from Subclass"); - // boolean correct = tc.checkStateSMT(superConst, subConst, method); - // if(!correct) ErrorPrinter.printError(method, subState.getFrom(), - // superState.getFrom()); superConst = matchVariableNames(Keys.THIS, thisName, superState.getTo()); subConst = matchVariableNames(Keys.THIS, thisName, superFunction, subFunction, subState.getTo()); // toSub <: toSup <==> ToSub is sub type and toSup is expectedType tc.checkStateSMT(subConst, superConst, method, "TO State from Subclass must be subtype of TO State from Superclass"); - // boolean correct = tc.checkStateSMT(subConst, superConst, method); - // if(!correct) ErrorPrinter.printError(method, subState.getTo(), - // superState.getTo()); + } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java index 1d52caa9..4cc11384 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java @@ -1,10 +1,14 @@ package liquidjava.processor.refinement_checker.object_checkers; +import static liquidjava.diagnostics.LJDiagnostics.diagnostics; + import java.lang.annotation.Annotation; import java.util.*; import java.util.stream.Collectors; -import liquidjava.diagnostics.ErrorHandler; +import liquidjava.diagnostics.errors.CustomError; +import liquidjava.diagnostics.errors.IllegalConstructorTransitionError; +import liquidjava.diagnostics.errors.InvalidRefinementError; import liquidjava.processor.context.*; import liquidjava.processor.refinement_checker.TypeChecker; import liquidjava.processor.refinement_checker.TypeCheckingUtils; @@ -40,7 +44,7 @@ public static void handleConstructorState(CtConstructor c, RefinedFunction f, Map m = a.getAllValues(); CtLiteral from = (CtLiteral) m.get("from"); if (from != null) { - ErrorHandler.printErrorConstructorFromState(c, from, tc.getErrorEmitter()); + diagnostics.add(new IllegalConstructorTransitionError(from)); return; } } @@ -69,10 +73,10 @@ private static void setConstructorStates(RefinedFunction f, List ctAnnota String from = TypeCheckingUtils.getStringFromAnnotation(m.get("from")); String to = TypeCheckingUtils.getStringFromAnnotation(m.get("to")); ObjectState state = new ObjectState(); - if (from != null) { // has From + + // has from + if (from != null) state.setFrom(createStatePredicate(from, f.getTargetClass(), tc, e, false, prefix)); - } - if (to != null) { // has To + + // has to + if (to != null) state.setTo(createStatePredicate(to, f.getTargetClass(), tc, e, true, prefix)); - } - if (from != null && to == null) // has From but not To -> the state remains the same - { + // has from but not to, state remains the same + if (from != null && to == null) state.setTo(createStatePredicate(from, f.getTargetClass(), tc, e, true, prefix)); - } - if (from == null && to != null) // has To but not From -> enters with true and exists with a specific state - { + + // has to but not from, state enters with true + if (from == null && to != null) state.setFrom(new Predicate()); - } + return state; } private static Predicate createStatePredicate(String value, /* RefinedFunction f */ String targetClass, TypeChecker tc, CtElement e, boolean isTo, String prefix) throws ParsingException { - Predicate p = new Predicate(value, e, tc.getErrorEmitter(), prefix); + Predicate p = new Predicate(value, e, prefix); if (!p.getExpression().isBooleanExpression()) { - ErrorHandler.printCustomError(e, "State refinement transition must be a boolean expression", - tc.getErrorEmitter()); + diagnostics.add( + new InvalidRefinementError(e, "State refinement transition must be a boolean expression", value)); return new Predicate(); } String t = targetClass; // f.getTargetClass(); @@ -202,9 +208,9 @@ private static Predicate createStatePredicate(String value, /* RefinedFunction f // what is it for? Predicate c1 = isTo ? getMissingStates(t, tc, p) : p; Predicate c = c1.substituteVariable(Keys.THIS, name); - c = c.changeOldMentions(nameOld, name, tc.getErrorEmitter()); + c = c.changeOldMentions(nameOld, name); boolean b = tc.checksStateSMT(new Predicate(), c.negate(), e.getPosition()); - if (b && !tc.getErrorEmitter().foundError()) { + if (b && !diagnostics.foundError()) { tc.createSameStateError(e, p, t); } @@ -384,22 +390,19 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) { stateChange.setFrom(fromPredicate); stateChange.setTo(toPredicate); } catch (ParsingException e) { - ErrorHandler - .printCustomError(fw, - "ParsingException while constructing assignment update for `" + fw + "` in class `" - + fw.getVariable().getDeclaringType() + "` : " + e.getMessage(), - tc.getErrorEmitter()); - + diagnostics.add(new CustomError(field, + String.format("Parsing error while constructing assignment update for `%s` in class `%s` : %s", fw, + field.getDeclaringType().getQualifiedName(), e.getMessage()))); return; } // replace "state(this)" to "state(whatever method is called from) and so on" Predicate expectState = stateChange.getFrom().substituteVariable(Keys.THIS, instanceName) - .changeOldMentions(vi.getName(), instanceName, tc.getErrorEmitter()); + .changeOldMentions(vi.getName(), instanceName); if (!tc.checksStateSMT(prevState, expectState, fw.getPosition())) { // Invalid field transition - if (!tc.getErrorEmitter().foundError()) { // No errors in errorEmitter - String states = stateChange.getFrom().toString(); + if (!diagnostics.foundError()) { // No errors so far + Predicate[] states = { stateChange.getFrom() }; tc.createStateMismatchError(fw, fw.toString(), prevState, states); } return; @@ -470,7 +473,7 @@ private static Predicate changeState(TypeChecker tc, VariableInstance vi, prevCheck = prevCheck.substituteVariable(s, map.get(s)); expectState = expectState.substituteVariable(s, map.get(s)); } - expectState = expectState.changeOldMentions(vi.getName(), instanceName, tc.getErrorEmitter()); + expectState = expectState.changeOldMentions(vi.getName(), instanceName); found = tc.checksStateSMT(prevCheck, expectState, invocation.getPosition()); if (found && stateChange.hasTo()) { @@ -486,10 +489,9 @@ private static Predicate changeState(TypeChecker tc, VariableInstance vi, return transitionedState; } } - if (!found && !tc.getErrorEmitter().foundError()) { // Reaches the end of stateChange no matching states - String states = stateChanges.stream().filter(ObjectState::hasFrom).map(ObjectState::getFrom) - .map(Predicate::toString).collect(Collectors.joining(",")); - + if (!found && !diagnostics.foundError()) { // Reaches the end of stateChange no matching states + Predicate[] states = stateChanges.stream().filter(ObjectState::hasFrom).map(ObjectState::getFrom) + .toArray(Predicate[]::new); String simpleInvocation = invocation.toString(); // .getExecutable().toString(); tc.createStateMismatchError(invocation, simpleInvocation, prevState, states); // ErrorPrinter.printStateMismatch(invocation, simpleInvocation, prevState, @@ -500,7 +502,7 @@ private static Predicate changeState(TypeChecker tc, VariableInstance vi, private static Predicate checkOldMentions(Predicate transitionedState, String instanceName, String newInstanceName, TypeChecker tc) { - return transitionedState.changeOldMentions(instanceName, newInstanceName, tc.getErrorEmitter()); + return transitionedState.changeOldMentions(instanceName, newInstanceName); } /** diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/BuiltinFunctionPredicate.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/BuiltinFunctionPredicate.java index 0b57ac50..61fe26e8 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/BuiltinFunctionPredicate.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/BuiltinFunctionPredicate.java @@ -1,24 +1,21 @@ package liquidjava.rj_language; -import liquidjava.diagnostics.ErrorEmitter; import liquidjava.rj_language.parsing.ParsingException; import spoon.reflect.declaration.CtElement; public class BuiltinFunctionPredicate extends Predicate { - public BuiltinFunctionPredicate(ErrorEmitter ee, CtElement elem, String functionName, String... params) - throws ParsingException { - super(functionName + "(" + getFormattedParams(params) + ")", elem, ee); + public BuiltinFunctionPredicate(CtElement elem, String functionName, String... params) throws ParsingException { + super(functionName + "(" + getFormattedParams(params) + ")", elem); } - public static BuiltinFunctionPredicate builtin_length(String param, CtElement elem, ErrorEmitter ee) - throws ParsingException { - return new BuiltinFunctionPredicate(ee, elem, "length", param); + public static BuiltinFunctionPredicate length(String param, CtElement elem) throws ParsingException { + return new BuiltinFunctionPredicate(elem, "length", param); } - public static BuiltinFunctionPredicate builtin_addToIndex(String array, String index, String value, CtElement elem, - ErrorEmitter ee) throws ParsingException { - return new BuiltinFunctionPredicate(ee, elem, "addToIndex", index, value); + public static BuiltinFunctionPredicate addToIndex(String array, String index, String value, CtElement elem) + throws ParsingException { + return new BuiltinFunctionPredicate(elem, "addToIndex", index, value); } private static String getFormattedParams(String... params) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java index cb17f5ea..41de354b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java @@ -1,13 +1,14 @@ package liquidjava.rj_language; +import static liquidjava.diagnostics.LJDiagnostics.diagnostics; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import liquidjava.diagnostics.ErrorEmitter; -import liquidjava.diagnostics.ErrorHandler; +import liquidjava.diagnostics.errors.SyntaxError; import liquidjava.processor.context.AliasWrapper; import liquidjava.processor.context.Context; import liquidjava.processor.context.GhostState; @@ -58,8 +59,8 @@ public Predicate() { * * @throws ParsingException */ - public Predicate(String ref, CtElement element, ErrorEmitter e) throws ParsingException { - this(ref, element, e, element.getParent(CtType.class).getQualifiedName()); + public Predicate(String ref, CtElement element) throws ParsingException { + this(ref, element, element.getParent(CtType.class).getQualifiedName()); } /** @@ -72,10 +73,10 @@ public Predicate(String ref, CtElement element, ErrorEmitter e) throws ParsingEx * * @throws ParsingException */ - public Predicate(String ref, CtElement element, ErrorEmitter e, String prefix) throws ParsingException { + public Predicate(String ref, CtElement element, String prefix) throws ParsingException { this.prefix = prefix; - exp = parse(ref, element, e); - if (e.foundError()) { + exp = parse(ref, element); + if (diagnostics.foundError()) { return; } if (!(exp instanceof GroupExpression)) { @@ -88,22 +89,22 @@ public Predicate(Expression e) { exp = e; } - protected Expression parse(String ref, CtElement element, ErrorEmitter e) throws ParsingException { + protected Expression parse(String ref, CtElement element) throws ParsingException { try { return RefinementsParser.createAST(ref, prefix); - } catch (ParsingException e1) { - ErrorHandler.printSyntaxError(e1.getMessage(), ref, element, e); - throw e1; + } catch (ParsingException e) { + diagnostics.add(new SyntaxError(e.getMessage(), element, ref)); + throw e; } } - protected Expression innerParse(String ref, ErrorEmitter e, String prefix) { + protected Expression innerParse(String ref, String prefix) { try { return RefinementsParser.createAST(ref, prefix); } catch (ParsingException e1) { - ErrorHandler.printSyntaxError(e1.getMessage(), ref, e); + diagnostics.add(new SyntaxError(e1.getMessage(), ref)); + return null; } - return null; } public Predicate changeAliasToRefinement(Context context, Factory f) throws Exception { @@ -152,7 +153,7 @@ public List getStateInvocations(List lgs) { } /** Change old mentions of previous name to the new name e.g., old(previousName) -> newName */ - public Predicate changeOldMentions(String previousName, String newName, ErrorEmitter ee) { + public Predicate changeOldMentions(String previousName, String newName) { Expression e = exp.clone(); Expression prev = createVar(previousName).getExpression(); List le = new ArrayList<>(); @@ -184,12 +185,12 @@ private void expressionGetOldVariableNames(Expression exp, List ls) { } } - public Predicate changeStatesToRefinements(List ghostState, String[] toChange, ErrorEmitter ee) { + public Predicate changeStatesToRefinements(List ghostState, String[] toChange) { Map nameRefinementMap = new HashMap<>(); for (GhostState gs : ghostState) { if (gs.getRefinement() != null) { // is a state and not a ghost state String name = gs.getQualifiedName(); - Expression exp = innerParse(gs.getRefinement().toString(), ee, gs.getPrefix()); + Expression exp = innerParse(gs.getRefinement().toString(), gs.getPrefix()); nameRefinementMap.put(name, exp); // Also allow simple name lookup to enable hierarchy matching String simple = Utils.getSimpleName(name); diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/NotFoundError.java b/liquidjava-verifier/src/main/java/liquidjava/smt/NotFoundSMTError.java similarity index 78% rename from liquidjava-verifier/src/main/java/liquidjava/smt/NotFoundError.java rename to liquidjava-verifier/src/main/java/liquidjava/smt/NotFoundSMTError.java index 70a80827..0a23d2af 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/NotFoundError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/NotFoundSMTError.java @@ -2,10 +2,10 @@ import spoon.reflect.declaration.CtElement; -public class NotFoundError extends Exception { +public class NotFoundSMTError extends Exception { private CtElement location; - public NotFoundError(String message) { + public NotFoundSMTError(String message) { super(message); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java b/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java index 68dc4862..9eb23e6f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java @@ -1,16 +1,21 @@ package liquidjava.smt; +import static liquidjava.diagnostics.LJDiagnostics.diagnostics; + import com.martiansoftware.jsap.SyntaxException; import com.microsoft.z3.Expr; import com.microsoft.z3.Status; import com.microsoft.z3.Z3Exception; + +import liquidjava.diagnostics.errors.GhostInvocationError; import liquidjava.processor.context.Context; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.Expression; +import spoon.reflect.cu.SourcePosition; public class SMTEvaluator { - public void verifySubtype(Predicate subRef, Predicate supRef, Context c) + public void verifySubtype(Predicate subRef, Predicate supRef, Context c, SourcePosition pos) throws TypeCheckError, GhostFunctionError, Exception { // Creates a parser for our SMT-ready refinement language // Discharges the verification to z3 @@ -37,11 +42,11 @@ public void verifySubtype(Predicate subRef, Predicate supRef, Context c) System.out.println("Could not parse: " + toVerify); e1.printStackTrace(); } catch (Z3Exception e) { - if (e.getLocalizedMessage().substring(0, 24).equals("Wrong number of argument") - || e.getLocalizedMessage().substring(0, 13).equals("Sort mismatch")) - throw new GhostFunctionError(e.getLocalizedMessage()); - else - throw new Z3Exception(e.getLocalizedMessage()); + String msg = e.getLocalizedMessage().toLowerCase(); + if (msg.contains("wrong number of arguments") || msg.contains("sort mismatch")) + diagnostics.add(new GhostInvocationError(msg, pos, supRef, null)); + + throw new Z3Exception(e.getLocalizedMessage()); } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java index ed0db04e..b508592d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java @@ -75,7 +75,7 @@ public Expr makeBooleanLiteral(boolean value) { private Expr getVariableTranslation(String name) throws Exception { if (!varTranslation.containsKey(name)) - throw new NotFoundError("Variable '" + name.toString() + "' not found"); + throw new NotFoundSMTError("Variable '" + name.toString() + "' not found"); Expr e = varTranslation.get(name); if (e == null) e = varTranslation.get(String.format("this#%s", name)); @@ -144,7 +144,7 @@ private FuncDecl resolveFunctionDeclFallback(String name, Expr[] params) t if (candidate != null) { return candidate; } - throw new NotFoundError("Function '" + name + "' not found"); + throw new NotFoundSMTError("Function '" + name + "' not found"); } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/TypeMismatchError.java b/liquidjava-verifier/src/main/java/liquidjava/smt/TypeMismatchError.java deleted file mode 100644 index 4f368874..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/TypeMismatchError.java +++ /dev/null @@ -1,23 +0,0 @@ -package liquidjava.smt; - -import spoon.reflect.declaration.CtElement; - -public class TypeMismatchError extends Exception { - - private CtElement location; - - public TypeMismatchError(String message) { - super(message); - } - - public CtElement getLocation() { - return location; - } - - public void setLocation(CtElement location) { - this.location = location; - } - - /** */ - private static final long serialVersionUID = 1L; -} diff --git a/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java b/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java index 7f216a32..5ffa1dcb 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java +++ b/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java @@ -1,5 +1,6 @@ package liquidjava.api.tests; +import static liquidjava.diagnostics.LJDiagnostics.diagnostics; import static org.junit.Assert.fail; import java.io.IOException; @@ -8,7 +9,6 @@ import java.nio.file.Paths; import java.util.stream.Stream; import liquidjava.api.CommandLineLauncher; -import liquidjava.diagnostics.ErrorEmitter; import org.junit.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -29,17 +29,18 @@ public void testFile(final Path filePath) { String fileName = filePath.getFileName().toString(); // 1. Run the verifier on the file or package - ErrorEmitter errorEmitter = CommandLineLauncher.launch(filePath.toAbsolutePath().toString()); + CommandLineLauncher.launch(filePath.toAbsolutePath().toString()); // 2. Check if the file is correct or contains an error - if ((fileName.startsWith("Correct") && errorEmitter.foundError()) - || (fileName.contains("correct") && errorEmitter.foundError())) { + if (isCorrect(fileName) && diagnostics.foundError()) { + if (fileName.toLowerCase().startsWith("warning")) { + System.out.println("Warning in directory: " + fileName + " --- should be correct with warnings"); + } System.out.println("Error in directory: " + fileName + " --- should be correct but an error was found"); fail(); } // 3. Check if the file has an error but passed verification - if ((fileName.startsWith("Error") && !errorEmitter.foundError()) - || (fileName.contains("error") && !errorEmitter.foundError())) { + if (isError(fileName) && !diagnostics.foundError()) { System.out.println("Error in directory: " + fileName + " --- should be an error but passed verification"); fail(); } @@ -47,8 +48,8 @@ public void testFile(final Path filePath) { /** * Returns a Stream of paths to test files in the testSuite directory. These include files with names starting with - * "Correct" or "Error", and directories containing "correct" or "error". - * + * "Correct" or "Error", and directories containing "correct" or "error". § + * * @return Stream of paths to test files * * @throws IOException @@ -60,11 +61,11 @@ private static Stream fileNameSource() throws IOException { String name = filePath.getFileName().toString(); // 1. Files that start with "Correct" or "Error" boolean isFileStartingWithCorrectOrError = fileAttr.isRegularFile() - && (name.startsWith("Correct") || name.startsWith("Error")); + && (isCorrect(name) || isError(name)); // 2. Folders (directories) that contain "correct" or "error" boolean isDirectoryWithCorrectOrError = fileAttr.isDirectory() - && (name.contains("correct") || name.contains("error")); + && (isCorrect(name) || isError(name)); // Return true if either condition matches return isFileStartingWithCorrectOrError || isDirectoryWithCorrectOrError; @@ -77,14 +78,21 @@ private static Stream fileNameSource() throws IOException { */ @Test public void testMultiplePaths() { - String[] paths = { "../liquidjava-example/src/main/java/testSuite/SimpleTest.java", + String[] paths = { "../liquidjava-example/src/main/java/testSuite/CorrectSimple.java", "../liquidjava-example/src/main/java/testSuite/classes/arraylist_correct", }; - ErrorEmitter errorEmitter = CommandLineLauncher.launch(paths); - + CommandLineLauncher.launch(paths); // Check if any of the paths that should be correct found an error - if (errorEmitter.foundError()) { - System.out.println("Error found in files that should be correct."); + if (diagnostics.foundError()) { + System.out.println("Error found in files that should be correct"); fail(); } } + + private static boolean isCorrect(String path) { + return path.toLowerCase().contains("correct") || path.toLowerCase().contains("warning"); + } + + private static boolean isError(String path) { + return path.toLowerCase().contains("error"); + } } From 01a39ffd347a5b8571d02b6a228d9044f38a36a9 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 13 Nov 2025 20:41:30 +0000 Subject: [PATCH 02/25] Fix Single Equality Expression Simplification (#113) --- .../rj_language/opt/VariableResolver.java | 9 ++ .../opt/ExpressionSimplifierTest.java | 25 ++++ .../rj_language/opt/VariableResolverTest.java | 110 ++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariableResolver.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariableResolver.java index 2ac6d210..1e0239b0 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariableResolver.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariableResolver.java @@ -15,6 +15,15 @@ public class VariableResolver { * Extracts variables with constant values from an expression Returns a map from variable names to their values */ public static Map resolve(Expression exp) { + // if the expression is just a single equality (not a conjunction) don't extract it + // this avoids creating tautologies like "1 == 1" after substitution, which are then simplified to "true" + if (exp instanceof BinaryExpression) { + BinaryExpression be = (BinaryExpression) exp; + if ("==".equals(be.getOperator())) { + return new HashMap<>(); + } + } + Map map = new HashMap<>(); resolveRecursive(exp, map); return resolveTransitive(map); diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/ExpressionSimplifierTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/ExpressionSimplifierTest.java index d1629682..ff034f93 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/ExpressionSimplifierTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/ExpressionSimplifierTest.java @@ -304,6 +304,31 @@ void testComplexArithmeticWithMultipleOperations() { assertDerivationEquals(expected, result, ""); } + @Test + void testSingleEqualityNotSimplifiedToTrue() { + // Given: x == 1 + // Expected: x == 1 (should not be simplified to "true") + + Expression varX = new Var("x"); + Expression one = new LiteralInt(1); + Expression xEquals1 = new BinaryExpression(varX, "==", one); + + // When + ValDerivationNode result = ExpressionSimplifier.simplify(xEquals1); + + // Then + assertNotNull(result, "Result should not be null"); + assertEquals("x == 1", result.getValue().toString(), + "Single equality should not be simplified to a boolean literal"); + + // The result should be the original expression unchanged + assertTrue(result.getValue() instanceof BinaryExpression, "Result should still be a binary expression"); + BinaryExpression resultExpr = (BinaryExpression) result.getValue(); + assertEquals("==", resultExpr.getOperator(), "Operator should still be =="); + assertEquals("x", resultExpr.getFirstOperand().toString(), "Left operand should be x"); + assertEquals("1", resultExpr.getSecondOperand().toString(), "Right operand should be 1"); + } + /** * Helper method to compare two derivation nodes recursively */ diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java new file mode 100644 index 00000000..0d08e9a2 --- /dev/null +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java @@ -0,0 +1,110 @@ +package liquidjava.rj_language.opt; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.Map; + +import org.junit.jupiter.api.Test; + +import liquidjava.rj_language.ast.BinaryExpression; +import liquidjava.rj_language.ast.Expression; +import liquidjava.rj_language.ast.GroupExpression; +import liquidjava.rj_language.ast.LiteralInt; +import liquidjava.rj_language.ast.UnaryExpression; +import liquidjava.rj_language.ast.Var; + +class VariableResolverTest { + + @Test + void testSingleEqualityNotExtracted() { + // x == 1 should not extract because it's a single equality + Expression varX = new Var("x"); + Expression one = new LiteralInt(1); + Expression xEquals1 = new BinaryExpression(varX, "==", one); + Map result = VariableResolver.resolve(xEquals1); + assertTrue(result.isEmpty(), "Single equality should not extract variable mapping"); + } + + @Test + void testConjunctionExtractsVariables() { + // x + y && x == 1 && y == 2 should extract x -> 1, y -> 2 + Expression varX = new Var("x"); + Expression varY = new Var("y"); + Expression one = new LiteralInt(1); + Expression two = new LiteralInt(2); + + Expression xPlusY = new BinaryExpression(varX, "+", varY); + Expression xEquals1 = new BinaryExpression(varX, "==", one); + Expression yEquals2 = new BinaryExpression(varY, "==", two); + + Expression conditions = new BinaryExpression(xEquals1, "&&", yEquals2); + Expression fullExpr = new BinaryExpression(xPlusY, "&&", conditions); + + Map result = VariableResolver.resolve(fullExpr); + assertEquals(2, result.size(), "Should extract both variables"); + assertEquals("1", result.get("x").toString()); + assertEquals("2", result.get("y").toString()); + } + + @Test + void testSingleComparisonNotExtracted() { + // x > 0 should not extract anything + Expression varX = new Var("x"); + Expression zero = new LiteralInt(0); + Expression xGreaterZero = new BinaryExpression(varX, ">", zero); + + Map result = VariableResolver.resolve(xGreaterZero); + assertTrue(result.isEmpty(), "Single comparison should not extract variable mapping"); + } + + @Test + void testSingleArithmeticExpression() { + // x + 1 should not extract anything + Expression varX = new Var("x"); + Expression one = new LiteralInt(1); + Expression xPlusOne = new BinaryExpression(varX, "+", one); + + Map result = VariableResolver.resolve(xPlusOne); + assertTrue(result.isEmpty(), "Single arithmetic expression should not extract variable mapping"); + } + + @Test + void testDisjunctionWithEqualities() { + // x == 1 || y == 2 should not extract anything + Expression varX = new Var("x"); + Expression varY = new Var("y"); + Expression one = new LiteralInt(1); + Expression two = new LiteralInt(2); + + Expression xEquals1 = new BinaryExpression(varX, "==", one); + Expression yEquals2 = new BinaryExpression(varY, "==", two); + Expression disjunction = new BinaryExpression(xEquals1, "||", yEquals2); + + Map result = VariableResolver.resolve(disjunction); + assertTrue(result.isEmpty(), "Disjunction should not extract variable mappings"); + } + + @Test + void testNegatedEquality() { + // !(x == 1) should not extract because it's a single equality + Expression varX = new Var("x"); + Expression one = new LiteralInt(1); + Expression xEquals1 = new BinaryExpression(varX, "==", one); + Expression notXEquals1 = new UnaryExpression("!", xEquals1); + + Map result = VariableResolver.resolve(notXEquals1); + assertTrue(result.isEmpty(), "Negated equality should not extract variable mapping"); + } + + @Test + void testGroupedEquality() { + // (x == 1) should not extract because it's a single equality + Expression varX = new Var("x"); + Expression one = new LiteralInt(1); + Expression xEquals1 = new BinaryExpression(varX, "==", one); + Expression grouped = new GroupExpression(xEquals1); + + Map result = VariableResolver.resolve(grouped); + assertTrue(result.isEmpty(), "Grouped single equality should not extract variable mapping"); + } +} From 87a2153fd73c752f09fce2b480eefca488e3a4cf Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 13 Nov 2025 20:57:47 +0000 Subject: [PATCH 03/25] Prevent Index Out-Of-Bounds in Function Call With Empty Args (#117) --- .../visitors/CreateASTVisitor.java | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java index 102c9e58..bef9525d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java @@ -14,6 +14,7 @@ import liquidjava.rj_language.ast.LiteralString; import liquidjava.rj_language.ast.UnaryExpression; import liquidjava.rj_language.ast.Var; +import liquidjava.rj_language.parsing.ParsingException; import liquidjava.utils.Utils; import org.antlr.v4.runtime.tree.ParseTree; @@ -63,7 +64,7 @@ public CreateASTVisitor(String prefix) { this.prefix = prefix; } - public Expression create(ParseTree rc) { + public Expression create(ParseTree rc) throws ParsingException { if (rc instanceof ProgContext) return progCreate((ProgContext) rc); else if (rc instanceof StartContext) @@ -84,20 +85,20 @@ else if (rc instanceof LiteralContext) return null; } - private Expression progCreate(ProgContext rc) { + private Expression progCreate(ProgContext rc) throws ParsingException { if (rc.start() != null) return create(rc.start()); return null; } - private Expression startCreate(ParseTree rc) { + private Expression startCreate(ParseTree rc) throws ParsingException { if (rc instanceof StartPredContext) return create(((StartPredContext) rc).pred()); // alias and ghost do not have evaluation return null; } - private Expression predCreate(ParseTree rc) { + private Expression predCreate(ParseTree rc) throws ParsingException { if (rc instanceof PredGroupContext) return new GroupExpression(create(((PredGroupContext) rc).pred())); else if (rc instanceof PredNegateContext) @@ -112,7 +113,7 @@ else if (rc instanceof IteContext) return create(((PredExpContext) rc).exp()); } - private Expression expCreate(ParseTree rc) { + private Expression expCreate(ParseTree rc) throws ParsingException { if (rc instanceof ExpGroupContext) return new GroupExpression(create(((ExpGroupContext) rc).exp())); else if (rc instanceof ExpBoolContext) { @@ -124,7 +125,7 @@ else if (rc instanceof ExpBoolContext) { } } - private Expression operandCreate(ParseTree rc) { + private Expression operandCreate(ParseTree rc) throws ParsingException { if (rc instanceof OpLiteralContext) return create(((OpLiteralContext) rc).literalExpression()); else if (rc instanceof OpArithContext) @@ -143,7 +144,7 @@ else if (rc instanceof OpGroupContext) return null; } - private Expression literalExpressionCreate(ParseTree rc) { + private Expression literalExpressionCreate(ParseTree rc) throws ParsingException { if (rc instanceof LitGroupContext) return new GroupExpression(create(((LitGroupContext) rc).literalExpression())); else if (rc instanceof LitContext) @@ -158,20 +159,24 @@ else if (rc instanceof VarContext) { } } - private Expression functionCallCreate(FunctionCallContext rc) { + private Expression functionCallCreate(FunctionCallContext rc) throws ParsingException { if (rc.ghostCall() != null) { GhostCallContext gc = rc.ghostCall(); - List le = getArgs(gc.args()); String name = Utils.qualifyName(prefix, gc.ID().getText()); - return new FunctionInvocation(name, le); + List args = getArgs(gc.args()); + if (args.isEmpty()) + throw new ParsingException("Ghost call cannot have empty arguments"); + return new FunctionInvocation(name, args); } else { AliasCallContext gc = rc.aliasCall(); - List le = getArgs(gc.args()); - return new AliasInvocation(gc.ID_UPPER().getText(), le); + List args = getArgs(gc.args()); + if (args.isEmpty()) + throw new ParsingException("Alias call cannot have empty arguments"); + return new AliasInvocation(gc.ID_UPPER().getText(), args); } } - private List getArgs(ArgsContext args) { + private List getArgs(ArgsContext args) throws ParsingException { List le = new ArrayList<>(); if (args != null) for (PredContext oc : args.pred()) { @@ -180,7 +185,7 @@ private List getArgs(ArgsContext args) { return le; } - private Expression literalCreate(LiteralContext literalContext) { + private Expression literalCreate(LiteralContext literalContext) throws ParsingException { if (literalContext.BOOL() != null) return new LiteralBoolean(literalContext.BOOL().getText()); else if (literalContext.STRING() != null) From 0cbd6fbf8f228e0e5718cf976c5eadf98a1db09d Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 13 Nov 2025 21:58:51 +0000 Subject: [PATCH 04/25] Add `.gitattributes` For LF Line Endings --- .gitattributes | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..94f480de --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf \ No newline at end of file From 9b14c3f332f935a26466f4b0be273466834ec3f4 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Fri, 14 Nov 2025 11:24:35 +0000 Subject: [PATCH 05/25] Refactor Diagnostics (#119) --- .../diagnostics/errors/CustomError.java | 8 + .../errors/GhostInvocationError.java | 11 ++ .../IllegalConstructorTransitionError.java | 11 ++ .../errors/InvalidRefinementError.java | 10 ++ .../diagnostics/errors/NotFoundError.java | 11 ++ .../diagnostics/errors/RefinementError.java | 11 ++ .../errors/StateConflictError.java | 14 ++ .../errors/StateRefinementError.java | 20 +++ .../diagnostics/errors/SyntaxError.java | 11 ++ .../warnings/NonExistentClass.java | 8 + .../warnings/NonExistentConstructor.java | 16 ++ .../warnings/NonExistentMethod.java | 16 ++ .../liquidjava/api/CommandLineLauncher.java | 2 +- .../java/liquidjava/diagnostics/Colors.java | 12 ++ .../{LJDiagnostics.java => Diagnostics.java} | 12 +- .../liquidjava/diagnostics/ErrorPosition.java | 20 +++ .../liquidjava/diagnostics/LJDiagnostic.java | 144 ++++++++++++++++++ .../diagnostics/errors/CustomError.java | 13 +- .../errors/GhostInvocationError.java | 15 +- .../IllegalConstructorTransitionError.java | 10 +- .../errors/InvalidRefinementError.java | 10 +- .../diagnostics/errors/LJError.java | 58 +------ .../diagnostics/errors/NotFoundError.java | 7 +- .../diagnostics/errors/RefinementError.java | 20 +-- .../errors/StateConflictError.java | 19 +-- .../errors/StateRefinementError.java | 27 ++-- .../diagnostics/errors/SyntaxError.java | 13 +- .../ExternalClassNotFoundWarning.java | 9 +- .../ExternalMethodNotFoundWarning.java | 13 +- .../diagnostics/warnings/LJWarning.java | 60 +------- .../processor/RefinementProcessor.java | 7 - .../ann_generation/FieldGhostsGeneration.java | 2 +- .../ExternalRefinementTypeChecker.java | 27 ++-- .../MethodsFirstChecker.java | 2 +- .../RefinementTypeChecker.java | 2 +- .../refinement_checker/TypeChecker.java | 22 +-- .../refinement_checker/VCChecker.java | 52 ++++--- .../MethodsFunctionsChecker.java | 4 +- ...va => AuxHierarchyRefinementsPassage.java} | 11 +- .../object_checkers/AuxStateHandler.java | 32 ++-- .../liquidjava/rj_language/Predicate.java | 31 +++- .../rj_language/ast/AliasInvocation.java | 6 + .../rj_language/ast/BinaryExpression.java | 5 + .../rj_language/ast/Expression.java | 11 ++ .../rj_language/ast/FunctionInvocation.java | 7 + .../rj_language/ast/GroupExpression.java | 5 + .../java/liquidjava/rj_language/ast/Ite.java | 6 + .../rj_language/ast/UnaryExpression.java | 5 + .../rj_language/parsing/ParsingException.java | 3 - .../liquidjava/smt/GhostFunctionError.java | 23 --- .../java/liquidjava/smt/NotFoundSMTError.java | 22 --- .../java/liquidjava/smt/SMTEvaluator.java | 10 +- .../java/liquidjava/smt/TranslatorToZ3.java | 5 +- .../liquidjava/smt/errors/NotFoundError.java | 8 + .../SMTError.java} | 10 +- .../liquidjava/smt/errors/TypeCheckError.java | 8 + .../src/main/java/liquidjava/utils/Utils.java | 10 ++ .../liquidjava/api/tests/TestExamples.java | 2 +- 58 files changed, 569 insertions(+), 380 deletions(-) create mode 100644 liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/CustomError.java create mode 100644 liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/GhostInvocationError.java create mode 100644 liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/IllegalConstructorTransitionError.java create mode 100644 liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/InvalidRefinementError.java create mode 100644 liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/NotFoundError.java create mode 100644 liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/RefinementError.java create mode 100644 liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateConflictError.java create mode 100644 liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateRefinementError.java create mode 100644 liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/SyntaxError.java create mode 100644 liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentClass.java create mode 100644 liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentConstructor.java create mode 100644 liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentMethod.java create mode 100644 liquidjava-verifier/src/main/java/liquidjava/diagnostics/Colors.java rename liquidjava-verifier/src/main/java/liquidjava/diagnostics/{LJDiagnostics.java => Diagnostics.java} (87%) create mode 100644 liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java rename liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/{AuxHierarchyRefinememtsPassage.java => AuxHierarchyRefinementsPassage.java} (96%) delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/smt/GhostFunctionError.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/smt/NotFoundSMTError.java create mode 100644 liquidjava-verifier/src/main/java/liquidjava/smt/errors/NotFoundError.java rename liquidjava-verifier/src/main/java/liquidjava/smt/{TypeCheckError.java => errors/SMTError.java} (59%) create mode 100644 liquidjava-verifier/src/main/java/liquidjava/smt/errors/TypeCheckError.java diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/CustomError.java b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/CustomError.java new file mode 100644 index 00000000..953c56ef --- /dev/null +++ b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/CustomError.java @@ -0,0 +1,8 @@ +package testingInProgress.diagnostics.errors; + +import liquidjava.specification.StateSet; + +@StateSet({"Open", "Closed"}) +public class CustomError { + +} diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/GhostInvocationError.java b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/GhostInvocationError.java new file mode 100644 index 00000000..db4bcd57 --- /dev/null +++ b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/GhostInvocationError.java @@ -0,0 +1,11 @@ +package testingInProgress.diagnostics.errors; + +import liquidjava.specification.Ghost; +import liquidjava.specification.StateRefinement; + +@Ghost("int size") +public class GhostInvocationError { + + @StateRefinement(to="size(this, this) == 0") + public void test() {} +} diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/IllegalConstructorTransitionError.java b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/IllegalConstructorTransitionError.java new file mode 100644 index 00000000..dcfacf93 --- /dev/null +++ b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/IllegalConstructorTransitionError.java @@ -0,0 +1,11 @@ +package testingInProgress.diagnostics.errors; + +import liquidjava.specification.StateRefinement; +import liquidjava.specification.StateSet; + +@StateSet({"open", "closed"}) +public class IllegalConstructorTransitionError { + + @StateRefinement(from="open(this)", to="closed(this)") + public IllegalConstructorTransitionError() {} +} diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/InvalidRefinementError.java b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/InvalidRefinementError.java new file mode 100644 index 00000000..92ed8dbd --- /dev/null +++ b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/InvalidRefinementError.java @@ -0,0 +1,10 @@ +package testingInProgress.diagnostics.errors; + +import liquidjava.specification.Refinement; + +public class InvalidRefinementError { + public static void main(String[] args) { + @Refinement("_ + 1") + int x = 5; + } +} diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/NotFoundError.java b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/NotFoundError.java new file mode 100644 index 00000000..ff22cc9b --- /dev/null +++ b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/NotFoundError.java @@ -0,0 +1,11 @@ +package testingInProgress.diagnostics.errors; + +import liquidjava.specification.Refinement; + +public class NotFoundError { + + public static void main(String[] args) { + @Refinement("x > 0") + int y = 1; + } +} diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/RefinementError.java b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/RefinementError.java new file mode 100644 index 00000000..25dac068 --- /dev/null +++ b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/RefinementError.java @@ -0,0 +1,11 @@ +package testingInProgress.diagnostics.errors; + +import liquidjava.specification.Refinement; + +public class RefinementError { + + public static void main(String[] args) { + @Refinement("x > 0") + int x = -1; + } +} diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateConflictError.java b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateConflictError.java new file mode 100644 index 00000000..9701ff05 --- /dev/null +++ b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateConflictError.java @@ -0,0 +1,14 @@ +package testingInProgress.diagnostics.errors; + +import liquidjava.specification.StateRefinement; +import liquidjava.specification.StateSet; + +@StateSet({"open", "closed"}) +public class StateConflictError { + + @StateRefinement(to="open(this)") + public StateConflictError() {} + + @StateRefinement(from="open(this) && closed(this)") + public void close() {} +} diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateRefinementError.java b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateRefinementError.java new file mode 100644 index 00000000..6e4aa8fb --- /dev/null +++ b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateRefinementError.java @@ -0,0 +1,20 @@ +package testingInProgress.diagnostics.errors; + +import liquidjava.specification.StateRefinement; +import liquidjava.specification.StateSet; + +@StateSet({"open", "closed"}) +public class StateRefinementError { + + @StateRefinement(to="open(this)") + public StateRefinementError() {} + + @StateRefinement(from="!closed(this)", to="closed(this)") + public void close() {} + + public static void main(String[] args) { + StateRefinementError s = new StateRefinementError(); + s.close(); + s.close(); + } +} diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/SyntaxError.java b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/SyntaxError.java new file mode 100644 index 00000000..87953608 --- /dev/null +++ b/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/SyntaxError.java @@ -0,0 +1,11 @@ +package testingInProgress.diagnostics.errors; + +import liquidjava.specification.Refinement; + +public class SyntaxError { + + public static void main(String[] args) { + @Refinement("x $ 0") + int x = -1; + } +} diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentClass.java b/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentClass.java new file mode 100644 index 00000000..f4eb3ebe --- /dev/null +++ b/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentClass.java @@ -0,0 +1,8 @@ +package testingInProgress.diagnostics.warnings; + +import liquidjava.specification.ExternalRefinementsFor; + +@ExternalRefinementsFor("non.existent.Class") +public interface NonExistentClass { + public void NonExistentClass(); +} diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentConstructor.java b/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentConstructor.java new file mode 100644 index 00000000..c4a5f4d5 --- /dev/null +++ b/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentConstructor.java @@ -0,0 +1,16 @@ +package testingInProgress.diagnostics.warnings; + +import liquidjava.specification.ExternalRefinementsFor; +import liquidjava.specification.RefinementPredicate; +import liquidjava.specification.StateRefinement; + +@ExternalRefinementsFor("java.util.ArrayList") +public interface NonExistentConstructor { + + @RefinementPredicate("int size(ArrayList l)") + @StateRefinement(to = "size(this) == 0") + public void ArrayList(String wrongParameter); + + @StateRefinement(to = "size(this) == (size(old(this)) + 1)") + public boolean add(E e); +} diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentMethod.java b/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentMethod.java new file mode 100644 index 00000000..af7237f1 --- /dev/null +++ b/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentMethod.java @@ -0,0 +1,16 @@ +package testingInProgress.diagnostics.warnings; + +import liquidjava.specification.ExternalRefinementsFor; +import liquidjava.specification.RefinementPredicate; +import liquidjava.specification.StateRefinement; + +@ExternalRefinementsFor("java.util.ArrayList") +public interface NonExistentMethod { + + @RefinementPredicate("int size(ArrayList l)") + @StateRefinement(to = "size(this) == 0") + public void ArrayList(); + + @StateRefinement(to = "size(this) == (size(old(this)) + 1)") + public boolean add(String e); +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java index 8b925819..b1187271 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java +++ b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java @@ -1,6 +1,6 @@ package liquidjava.api; -import static liquidjava.diagnostics.LJDiagnostics.diagnostics; +import static liquidjava.diagnostics.Diagnostics.diagnostics; import java.io.File; import java.util.Arrays; diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Colors.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Colors.java new file mode 100644 index 00000000..ff669fec --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Colors.java @@ -0,0 +1,12 @@ +package liquidjava.diagnostics; + +// ANSI color codes +public class Colors { + public static final String RESET = "\u001B[0m"; + public static final String BOLD = "\u001B[1m"; + public static final String GREY = "\u001B[90m"; + public static final String RED = "\u001B[31m"; + public static final String BOLD_RED = "\u001B[1;31m"; + public static final String YELLOW = "\u001B[33m"; + public static final String BOLD_YELLOW = "\u001B[1;33m"; +} \ No newline at end of file diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostics.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java similarity index 87% rename from liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostics.java rename to liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java index 211115be..dd69be2e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostics.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java @@ -10,23 +10,25 @@ * @see LJError * @see LJWarning */ -public class LJDiagnostics { - public static final LJDiagnostics diagnostics = new LJDiagnostics(); +public class Diagnostics { + public static final Diagnostics diagnostics = new Diagnostics(); private ArrayList errors; private ArrayList warnings; - private LJDiagnostics() { + private Diagnostics() { this.errors = new ArrayList<>(); this.warnings = new ArrayList<>(); } public void add(LJError error) { - this.errors.add(error); + if (!this.errors.contains(error)) + this.errors.add(error); } public void add(LJWarning warning) { - this.warnings.add(warning); + if (!this.warnings.contains(warning)) + this.warnings.add(warning); } public boolean foundError() { diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorPosition.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorPosition.java index 1ee72e7d..354e8680 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorPosition.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorPosition.java @@ -37,4 +37,24 @@ public static ErrorPosition fromSpoonPosition(SourcePosition pos) { return null; return new ErrorPosition(pos.getLine(), pos.getColumn(), pos.getEndLine(), pos.getEndColumn()); } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + ErrorPosition other = (ErrorPosition) obj; + return lineStart == other.lineStart && colStart == other.colStart && lineEnd == other.lineEnd + && colEnd == other.colEnd; + } + + @Override + public int hashCode() { + int result = lineStart; + result = 31 * result + colStart; + result = 31 * result + lineEnd; + result = 31 * result + colEnd; + return result; + } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java new file mode 100644 index 00000000..db48c8a9 --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java @@ -0,0 +1,144 @@ +package liquidjava.diagnostics; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import spoon.reflect.cu.SourcePosition; + +public class LJDiagnostic { + + private String title; + private String message; + private String details; + private String file; + private ErrorPosition position; + private String accentColor; + + public LJDiagnostic(String title, String message, String details, SourcePosition pos, String accentColor) { + this.title = title; + this.message = message; + this.details = details; + this.file = pos != null ? pos.getFile().getPath() : null; + this.position = ErrorPosition.fromSpoonPosition(pos); + this.accentColor = accentColor; + } + + public String getTitle() { + return title; + } + + public String getMessage() { + return message; + } + + public String getDetails() { + return details; + } + + public ErrorPosition getPosition() { + return position; + } + + public String getFile() { + return file; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + + // title + sb.append("\n").append(accentColor).append(title).append(": ").append(Colors.RESET).append(message) + .append("\n"); + + // snippet + String snippet = getSnippet(); + if (snippet != null) { + sb.append(snippet); + } + + // details + if (details != null && !details.isEmpty()) { + sb.append(" --> ").append(String.join("\n ", details.split("\n"))).append("\n"); + } + + // location + if (file != null && position != null) { + sb.append("\n").append(file).append(":").append(position.getLineStart()).append(Colors.RESET).append("\n"); + } + + return sb.toString(); + } + + public String getSnippet() { + if (file == null || position == null) + return null; + + Path path = Path.of(file); + try { + List lines = Files.readAllLines(path); + StringBuilder sb = new StringBuilder(); + + // before and after lines for context + int contextBefore = 2; + int contextAfter = 2; + int startLine = Math.max(1, position.getLineStart() - contextBefore); + int endLine = Math.min(lines.size(), position.getLineEnd() + contextAfter); + + // calculate padding for line numbers + int maxLineNum = endLine; + int padding = String.valueOf(maxLineNum).length(); + + for (int i = startLine; i <= endLine; i++) { + String lineNumStr = String.format("%" + padding + "d", i); + String line = lines.get(i - 1); + + // add line + sb.append(Colors.GREY).append(lineNumStr).append(" | ").append(line).append(Colors.RESET).append("\n"); + + // add error markers on the line(s) with the error + if (i >= position.getLineStart() && i <= position.getLineEnd()) { + int colStart = (i == position.getLineStart()) ? position.getColStart() : 1; + int colEnd = (i == position.getLineEnd()) ? position.getColEnd() : line.length(); + + if (colStart > 0 && colEnd > 0) { + // line number padding + " | " + column offset + String indent = " ".repeat(padding) + Colors.GREY + " | " + Colors.RESET + + " ".repeat(colStart - 1); + String markers = accentColor + "^".repeat(Math.max(1, colEnd - colStart + 1)) + Colors.RESET; + sb.append(indent).append(markers).append("\n"); + } + } + } + return sb.toString(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null || getClass() != obj.getClass()) + return false; + LJDiagnostic other = (LJDiagnostic) obj; + return title.equals(other.title) && message.equals(other.message) + && ((details == null && other.details == null) || (details != null && details.equals(other.details))) + && ((file == null && other.file == null) || (file != null && file.equals(other.file))) + && ((position == null && other.position == null) + || (position != null && position.equals(other.position))); + } + + @Override + public int hashCode() { + int result = title.hashCode(); + result = 31 * result + message.hashCode(); + result = 31 * result + (details != null ? details.hashCode() : 0); + result = 31 * result + (file != null ? file.hashCode() : 0); + result = 31 * result + (position != null ? position.hashCode() : 0); + return result; + } +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/CustomError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/CustomError.java index e02b8c9f..b9a5baca 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/CustomError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/CustomError.java @@ -11,19 +11,18 @@ public class CustomError extends LJError { public CustomError(String message) { - super("Found Error", message, null, null, null); + super("Error", message, null, null, null); } public CustomError(String message, SourcePosition pos) { - super("Found Error", message, pos, null, null); + super("Error", message, null, pos, null); } - public CustomError(CtElement element, String message) { - super("Found Error", message, element.getPosition(), element.toString(), null); + public CustomError(String message, String detail, CtElement element) { + super("Error", message, detail, element.getPosition(), null); } - @Override - public String toString() { - return super.toString(null); + public CustomError(String message, CtElement element) { + super("Error", message, null, element.getPosition(), null); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java index 2b539c9b..73a933aa 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java @@ -1,7 +1,7 @@ package liquidjava.diagnostics.errors; import liquidjava.diagnostics.TranslationTable; -import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.ast.Expression; import spoon.reflect.cu.SourcePosition; /** @@ -13,20 +13,13 @@ public class GhostInvocationError extends LJError { private String expected; - public GhostInvocationError(String message, SourcePosition pos, Predicate expected, + public GhostInvocationError(String message, SourcePosition pos, Expression expected, TranslationTable translationTable) { - super("Ghost Invocation Error", message, pos, null, translationTable); - this.expected = expected.toString(); + super("Ghost Invocation Error", message, "", pos, translationTable); + this.expected = expected.toSimplifiedString(); } public String getExpected() { return expected; } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Expected: ").append(expected).append("\n"); - return super.toString(sb.toString()); - } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java index 11908a6b..6ef5f6d7 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java @@ -10,13 +10,7 @@ public class IllegalConstructorTransitionError extends LJError { public IllegalConstructorTransitionError(CtElement element) { - super("Illegal Constructor Transition Error", - "Found constructor with 'from' state (should only have a 'to' state)", element.getPosition(), - element.toString(), null); - } - - @Override - public String toString() { - return super.toString(null); + super("Illegal Constructor Transition Error", "Found constructor with 'from' state", + "Constructor methods should only have a 'to' state", element.getPosition(), null); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java index 03c55813..96cc272d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java @@ -1,5 +1,6 @@ package liquidjava.diagnostics.errors; +import liquidjava.diagnostics.TranslationTable; import spoon.reflect.declaration.CtElement; /** @@ -12,18 +13,11 @@ public class InvalidRefinementError extends LJError { private String refinement; public InvalidRefinementError(CtElement element, String message, String refinement) { - super("Invalid Refinement", message, element.getPosition(), element.toString(), null); + super("Invalid Refinement", message, "", element.getPosition(), null); this.refinement = refinement; } public String getRefinement() { return refinement; } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Refinement: ").append(refinement).append("\n"); - return super.toString(sb.toString()); - } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java index 12b4c79f..446dea22 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java @@ -1,72 +1,24 @@ package liquidjava.diagnostics.errors; -import liquidjava.diagnostics.ErrorPosition; +import liquidjava.diagnostics.LJDiagnostic; import liquidjava.diagnostics.TranslationTable; -import liquidjava.utils.Utils; +import liquidjava.diagnostics.Colors; import spoon.reflect.cu.SourcePosition; /** * Base class for all LiquidJava errors */ -public abstract class LJError { +public abstract class LJError extends LJDiagnostic { - private String title; - private String message; - private String snippet; - private ErrorPosition position; - private SourcePosition location; private TranslationTable translationTable; - public LJError(String title, String message, SourcePosition pos, String snippet, + public LJError(String title, String message, String details, SourcePosition pos, TranslationTable translationTable) { - this.title = title; - this.message = message; - this.snippet = snippet; + super(title, message, details, pos, Colors.BOLD_RED); this.translationTable = translationTable != null ? translationTable : new TranslationTable(); - this.location = pos; - this.position = ErrorPosition.fromSpoonPosition(pos); - } - - public String getTitle() { - return title; - } - - public String getMessage() { - return message; - } - - public String getSnippet() { - return snippet; - } - - public ErrorPosition getPosition() { - return position; - } - - public SourcePosition getLocation() { - return location; } public TranslationTable getTranslationTable() { return translationTable; } - - @Override - public abstract String toString(); - - public String toString(String extra) { - StringBuilder sb = new StringBuilder(); - sb.append(title); - - if (snippet != null) - sb.append(" at: \n").append(snippet.replace("@liquidjava.specification.", "@")); - - sb.append("\n"); - sb.append(message).append("\n"); - if (extra != null) - sb.append(extra).append("\n"); - sb.append("Location: ").append(location != null ? Utils.stripParens(location.toString()) : "") - .append("\n"); - return sb.toString(); - } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java index fda0ddd9..9af41e49 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java @@ -11,11 +11,6 @@ public class NotFoundError extends LJError { public NotFoundError(CtElement element, String message, TranslationTable translationTable) { - super("Not Found Error", message, element.getPosition(), element.toString(), translationTable); - } - - @Override - public String toString() { - return super.toString(null); + super("Not Found Error", message, "", element.getPosition(), translationTable); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java index 6451ed3c..72172934 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java @@ -1,9 +1,8 @@ package liquidjava.diagnostics.errors; import liquidjava.diagnostics.TranslationTable; -import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; -import liquidjava.utils.Utils; import spoon.reflect.declaration.CtElement; /** @@ -16,11 +15,12 @@ public class RefinementError extends LJError { private String expected; private ValDerivationNode found; - public RefinementError(CtElement element, Predicate expected, ValDerivationNode found, + public RefinementError(CtElement element, Expression expected, ValDerivationNode found, TranslationTable translationTable) { - super("Refinement Error", String.format("%s is not a subtype of %s", found.getValue(), expected), - element.getPosition(), element.toString(), translationTable); - this.expected = expected.toString(); + super("Refinement Error", + String.format("%s is not a subtype of %s", found.getValue(), expected.toSimplifiedString()), "", + element.getPosition(), translationTable); + this.expected = expected.toSimplifiedString(); this.found = found; } @@ -31,12 +31,4 @@ public String getExpected() { public ValDerivationNode getFound() { return found; } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Expected: ").append(Utils.stripParens(expected)).append("\n"); - sb.append("Found: ").append(found.getValue()); - return super.toString(sb.toString()); - } } \ No newline at end of file diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java index e464349e..fd250e49 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java @@ -1,7 +1,7 @@ package liquidjava.diagnostics.errors; import liquidjava.diagnostics.TranslationTable; -import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.ast.Expression; import spoon.reflect.declaration.CtElement; /** @@ -14,10 +14,11 @@ public class StateConflictError extends LJError { private String state; private String className; - public StateConflictError(CtElement element, Predicate state, String className, TranslationTable translationTable) { - super("State Conflict Error", "Found multiple disjoint states in state transition — State transition can only go to one state of each state set", element.getPosition(), - element.toString(), translationTable); - this.state = state.toString(); + public StateConflictError(CtElement element, Expression state, String className, + TranslationTable translationTable) { + super("State Conflict Error", "Found multiple disjoint states in state transition", + "State transition can only go to one state of each state set", element.getPosition(), translationTable); + this.state = state.toSimplifiedString(); this.className = className; } @@ -28,12 +29,4 @@ public String getState() { public String getClassName() { return className; } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Class: ").append(className).append("\n"); - sb.append("State: ").append(state); - return super.toString(sb.toString()); - } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java index e5f95d69..013c64d2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java @@ -3,7 +3,7 @@ import java.util.Arrays; import liquidjava.diagnostics.TranslationTable; -import liquidjava.rj_language.Predicate; +import liquidjava.rj_language.ast.Expression; import spoon.reflect.declaration.CtElement; /** @@ -17,13 +17,17 @@ public class StateRefinementError extends LJError { private final String[] expected; private final String found; - public StateRefinementError(CtElement element, String method, Predicate[] expected, Predicate found, + public StateRefinementError(CtElement element, String method, Expression[] expected, Expression found, TranslationTable translationTable) { - super("State Refinement Error", "State refinement transition violation", element.getPosition(), - element.toString(), translationTable); + super("State Refinement Error", "State refinement transition violation", + String.format("Expected: %s\nFound: %s", + String.join(", ", + Arrays.stream(expected).map(Expression::toSimplifiedString).toArray(String[]::new)), + found.toSimplifiedString()), + element.getPosition(), translationTable); this.method = method; - this.expected = Arrays.stream(expected).map(Predicate::toString).toArray(String[]::new); - this.found = found.toString(); + this.expected = Arrays.stream(expected).map(Expression::toSimplifiedString).toArray(String[]::new); + this.found = found.toSimplifiedString(); } public String getMethod() { @@ -37,15 +41,4 @@ public String[] getExpected() { public String getFound() { return found; } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Method: ").append(method).append("\n"); - sb.append("Expected: "); - Arrays.stream(expected).forEach(s -> sb.append(s).append(", ")); - sb.append("\n"); - sb.append("Found: ").append(found); - return super.toString(sb.toString()); - } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java index 80a7670c..1cf7101a 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java @@ -1,6 +1,6 @@ package liquidjava.diagnostics.errors; -import spoon.reflect.declaration.CtElement; +import spoon.reflect.cu.SourcePosition; /** * Error indicating that the syntax of a refinement is invalid @@ -15,19 +15,12 @@ public SyntaxError(String message, String refinement) { this(message, null, refinement); } - public SyntaxError(String message, CtElement element, String refinement) { - super("Syntax Error", message, element.getPosition(), element.toString(), null); + public SyntaxError(String message, SourcePosition pos, String refinement) { + super("Syntax Error", message, "", pos, null); this.refinement = refinement; } public String getRefinement() { return refinement; } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Invalid syntax in refinement: ").append(refinement); - return super.toString(sb.toString()); - } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java index 95f0d121..7fcbc468 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java @@ -12,18 +12,11 @@ public class ExternalClassNotFoundWarning extends LJWarning { private String className; public ExternalClassNotFoundWarning(CtElement element, String message, String className) { - super(message, element); + super(message, "", element.getPosition()); this.className = className; } public String getClassName() { return className; } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Class: ").append(className); - return super.toString(sb.toString()); - } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java index 6ea00388..4f90e994 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java @@ -12,8 +12,9 @@ public class ExternalMethodNotFoundWarning extends LJWarning { private String methodName; private String className; - public ExternalMethodNotFoundWarning(CtElement element, String message, String methodName, String className) { - super(message, element); + public ExternalMethodNotFoundWarning(CtElement element, String message, String details, String methodName, + String className) { + super(message, details, element.getPosition()); this.methodName = methodName; this.className = className; } @@ -25,12 +26,4 @@ public String getMethodName() { public String getClassName() { return className; } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Class: ").append(className).append("\n"); - sb.append("Method: ").append(methodName); - return super.toString(sb.toString()); - } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java index 8cecc0c7..4ad3438a 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java @@ -1,65 +1,15 @@ package liquidjava.diagnostics.warnings; -import liquidjava.diagnostics.ErrorPosition; -import liquidjava.utils.Utils; +import liquidjava.diagnostics.Colors; +import liquidjava.diagnostics.LJDiagnostic; import spoon.reflect.cu.SourcePosition; -import spoon.reflect.declaration.CtElement; /** * Base class for all LiquidJava warnings */ -public abstract class LJWarning { +public abstract class LJWarning extends LJDiagnostic { - private String message; - private CtElement element; - private ErrorPosition position; - private SourcePosition location; - - public LJWarning(String message, CtElement element) { - this.message = message; - this.element = element; - try { - this.location = element.getPosition(); - this.position = ErrorPosition.fromSpoonPosition(element.getPosition()); - } catch (Exception e) { - // This warning is from a generated part of the source code, so no precise position is provided - this.location = null; - this.position = null; - } - } - - public String getMessage() { - return message; - } - - public CtElement getElement() { - return element; - } - - public ErrorPosition getPosition() { - return position; - } - - public SourcePosition getLocation() { - return location; - } - - @Override - public abstract String toString(); - - public String toString(String extra) { - StringBuilder sb = new StringBuilder(); - sb.append(message); - - if (element != null) - sb.append(" at: \n").append(element.toString().replace("@liquidjava.specification.", "@")); - - if (extra != null) - sb.append("\n").append(extra); - - sb.append("\n"); - sb.append("Location: ").append(location != null ? Utils.stripParens(location.toString()) : "") - .append("\n"); - return sb.toString(); + public LJWarning(String message, String details, SourcePosition pos) { + super("Warning", message, details, pos, Colors.BOLD_YELLOW); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java b/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java index 75b8edc5..5254f494 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java @@ -1,7 +1,5 @@ package liquidjava.processor; -import static liquidjava.diagnostics.LJDiagnostics.diagnostics; - import java.util.ArrayList; import java.util.List; @@ -32,14 +30,9 @@ public void process(CtPackage pkg) { c.reinitializeAllContext(); pkg.accept(new FieldGhostsGeneration(c, factory)); // generate annotations for field ghosts - - // void spoon.reflect.visitor.CtVisitable.accept(CtVisitor arg0) pkg.accept(new ExternalRefinementTypeChecker(c, factory)); pkg.accept(new MethodsFirstChecker(c, factory)); // double passing idea (instead of headers) - pkg.accept(new RefinementTypeChecker(c, factory)); - if (diagnostics.foundError()) - return; } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java b/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java index 719e6d40..12b6b49f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java @@ -1,6 +1,6 @@ package liquidjava.processor.ann_generation; -import static liquidjava.diagnostics.LJDiagnostics.diagnostics; +import static liquidjava.diagnostics.Diagnostics.diagnostics; import liquidjava.processor.context.Context; import liquidjava.specification.Ghost; diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java index bd825c8f..ce05d7db 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java @@ -1,6 +1,6 @@ package liquidjava.processor.refinement_checker; -import static liquidjava.diagnostics.LJDiagnostics.diagnostics; +import static liquidjava.diagnostics.Diagnostics.diagnostics; import java.util.Arrays; import java.util.List; @@ -48,7 +48,7 @@ public void visitCtInterface(CtInterface intrface) { if (externalRefinements.isPresent()) { this.prefix = externalRefinements.get(); if (!classExists(prefix)) { - String message = String.format("Could not find external class '%s'", prefix); + String message = String.format("Could not find class '%s'", prefix); diagnostics.add(new ExternalClassNotFoundWarning(intrface, message, prefix)); return; } @@ -89,21 +89,24 @@ public void visitCtMethod(CtMethod method) { boolean isConstructor = method.getSimpleName().equals(targetType.getSimpleName()); if (isConstructor) { if (!constructorExists(targetType, method)) { - String title = String.format("Could not find constructor '%s' for '%s'", method.getSignature(), prefix); + String message = String.format("Could not find constructor '%s' for '%s'", method.getSignature(), + prefix); String[] overloads = getOverloads(targetType, method); - String message = overloads.length == 0 ? title - : title + "\nAvailable constructors:\n " + String.join("\n ", overloads); + String details = overloads.length == 0 ? null + : "Available constructors:\n " + String.join("\n ", overloads); - diagnostics.add(new ExternalMethodNotFoundWarning(method, message, method.getSignature(), prefix)); + diagnostics.add( + new ExternalMethodNotFoundWarning(method, message, details, method.getSignature(), prefix)); } } else { if (!methodExists(targetType, method)) { - String title = String.format("Could not find method '%s %s' for '%s'", method.getType().getSimpleName(), - method.getSignature(), prefix); + String message = String.format("Could not find method '%s %s' for '%s'", + method.getType().getSimpleName(), method.getSignature(), prefix); String[] overloads = getOverloads(targetType, method); - String message = overloads.length == 0 ? title - : title + "\nAvailable overloads:\n " + String.join("\n ", overloads); - diagnostics.add(new ExternalMethodNotFoundWarning(method, message, method.getSignature(), prefix)); + String details = overloads.length == 0 ? null + : "Available overloads:\n " + String.join("\n ", overloads); + diagnostics.add( + new ExternalMethodNotFoundWarning(method, message, details, method.getSignature(), prefix)); return; } } @@ -127,7 +130,7 @@ protected void getGhostFunction(String value, CtElement element) { } } catch (ParsingException e) { - diagnostics.add(new CustomError(element, "Could not parse the ghost function" + e.getMessage())); + diagnostics.add(new CustomError("Could not parse the ghost function", e.getMessage(), element)); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/MethodsFirstChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/MethodsFirstChecker.java index 6e84985d..c4d97695 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/MethodsFirstChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/MethodsFirstChecker.java @@ -1,6 +1,6 @@ package liquidjava.processor.refinement_checker; -import static liquidjava.diagnostics.LJDiagnostics.diagnostics; +import static liquidjava.diagnostics.Diagnostics.diagnostics; import java.util.ArrayList; import java.util.List; diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java index 2365d9b9..6c20c816 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java @@ -1,6 +1,6 @@ package liquidjava.processor.refinement_checker; -import static liquidjava.diagnostics.LJDiagnostics.diagnostics; +import static liquidjava.diagnostics.Diagnostics.diagnostics; import java.lang.annotation.Annotation; import java.util.Arrays; diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java index ebce5070..7b06b3c3 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java @@ -1,6 +1,6 @@ package liquidjava.processor.refinement_checker; -import static liquidjava.diagnostics.LJDiagnostics.diagnostics; +import static liquidjava.diagnostics.Diagnostics.diagnostics; import java.lang.annotation.Annotation; import java.util.Arrays; @@ -20,6 +20,7 @@ import liquidjava.rj_language.Predicate; import liquidjava.rj_language.parsing.ParsingException; import liquidjava.rj_language.parsing.RefinementsParser; +import liquidjava.utils.Utils; import liquidjava.utils.constants.Formats; import liquidjava.utils.constants.Keys; import liquidjava.utils.constants.Types; @@ -86,7 +87,6 @@ public Optional getRefinementFromAnnotation(CtElement element) throws if (!p.getExpression().isBooleanExpression()) { diagnostics.add(new InvalidRefinementError(element, "Refinement predicate must be a boolean expression", ref.get())); - return Optional.empty(); } if (diagnostics.foundError()) return Optional.empty(); @@ -121,8 +121,7 @@ private void createStateSet(CtNewArray e, int set, CtElement element) { CtLiteral s = (CtLiteral) ce; String f = s.getValue(); if (Character.isUpperCase(f.charAt(0))) { - diagnostics - .add(new CustomError(s, String.format("State name must start with lowercase in '%s'", f))); + diagnostics.add(new CustomError("State names must start with lowercase", s)); } } } @@ -163,12 +162,12 @@ private void createStateGhost(String string, CtAnnotation try { gd = RefinementsParser.getGhostDeclaration(string); } catch (ParsingException e) { - diagnostics.add(new CustomError(ann, "Could not parse the ghost function " + e.getMessage())); + diagnostics.add(new CustomError("Could not parse the ghost function", e.getMessage(), ann)); return; } if (gd.getParam_types().size() > 0) { - diagnostics.add(new CustomError(ann, "Ghost States have the class as parameter " - + "by default, no other parameters are allowed in '" + string + "'")); + diagnostics.add(new CustomError( + "Ghost States have the class as parameter " + "by default, no other parameters are allowed", ann)); return; } // Set class as parameter of Ghost @@ -226,7 +225,7 @@ protected void getGhostFunction(String value, CtElement element) { context.addGhostFunction(gh); } } catch (ParsingException e) { - diagnostics.add(new CustomError(element, "Could not parse the ghost function " + e.getMessage())); + diagnostics.add(new CustomError("Could not parse the ghost function", e.getMessage(), element)); // e.printStackTrace(); return; } @@ -256,7 +255,8 @@ protected void handleAlias(String value, CtElement element) { context.addAlias(aw); } } catch (ParsingException e) { - diagnostics.add(new SyntaxError(e.getMessage(), element, value)); + SourcePosition pos = Utils.getRefinementAnnotationPosition(element, value); + diagnostics.add(new SyntaxError(e.getMessage(), pos, value)); } } @@ -318,8 +318,8 @@ public boolean checksStateSMT(Predicate prevState, Predicate expectedState, Sour return vcChecker.canProcessSubtyping(prevState, expectedState, context.getGhostState(), p, factory); } - public void createError(CtElement element, Predicate expectedType, Predicate foundType, String customeMessage) { - vcChecker.printSubtypingError(element, expectedType, foundType, customeMessage); + public void createError(CtElement element, Predicate expectedType, Predicate foundType, String customMessage) { + vcChecker.printSubtypingError(element, expectedType, foundType, customMessage); } public void createSameStateError(CtElement element, Predicate expectedType, String klass) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java index 2a4c17ae..b467a757 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java @@ -1,8 +1,9 @@ package liquidjava.processor.refinement_checker; -import static liquidjava.diagnostics.LJDiagnostics.diagnostics; +import static liquidjava.diagnostics.Diagnostics.diagnostics; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.Stack; @@ -20,10 +21,9 @@ import liquidjava.processor.VCImplication; import liquidjava.processor.context.*; import liquidjava.rj_language.Predicate; -import liquidjava.smt.GhostFunctionError; -import liquidjava.smt.NotFoundSMTError; +import liquidjava.rj_language.ast.Expression; import liquidjava.smt.SMTEvaluator; -import liquidjava.smt.TypeCheckError; +import liquidjava.smt.errors.TypeCheckError; import liquidjava.utils.constants.Keys; import spoon.reflect.cu.SourcePosition; import spoon.reflect.declaration.CtElement; @@ -56,7 +56,7 @@ public void processSubtyping(Predicate expectedType, List list, CtEl et = expectedType.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); } catch (Exception e) { - diagnostics.add(new RefinementError(element, expectedType, premises.simplify(), map)); + diagnostics.add(new RefinementError(element, expectedType.getExpression(), premises.simplify(), map)); return; } @@ -234,13 +234,24 @@ private void recAuxGetVars(RefinedVariable var, List newVars) { getVariablesFromContext(l, newVars, varName); } - public boolean smtChecks(Predicate cSMT, Predicate expectedType, SourcePosition p) { + public boolean smtChecks(Predicate found, Predicate expectedType, SourcePosition p) { try { - new SMTEvaluator().verifySubtype(cSMT, expectedType, context, p); + new SMTEvaluator().verifySubtype(found, expectedType, context, p); } catch (TypeCheckError e) { return false; } catch (Exception e) { - diagnostics.add(new CustomError(e.getMessage(), p)); + String msg = e.getLocalizedMessage().toLowerCase(); + LJError error; + if (msg.contains("wrong number of arguments")) { + error = new GhostInvocationError("Wrong number of arguments in ghost invocation", p, + expectedType.getExpression(), null); + } else if (msg.contains("sort mismatch")) { + error = new GhostInvocationError("Type mismatch in arguments of ghost invocation", p, + expectedType.getExpression(), null); + } else { + error = new CustomError(e.getMessage(), p); + } + diagnostics.add(error); } return true; } @@ -252,12 +263,12 @@ public boolean smtChecks(Predicate cSMT, Predicate expectedType, SourcePosition * @param cSMT * @param expectedType * - * @throws Exception - * @throws GhostFunctionError * @throws TypeCheckError + * @throws Exception + * */ private void smtChecking(Predicate cSMT, Predicate expectedType, SourcePosition p) - throws TypeCheckError, GhostFunctionError, Exception { + throws TypeCheckError, Exception { new SMTEvaluator().verifySubtype(cSMT, expectedType, context, p); } @@ -298,18 +309,18 @@ private TranslationTable createMap(CtElement element, Predicate expectedType) { } protected void printSubtypingError(CtElement element, Predicate expectedType, Predicate foundType, - String customeMsg) { + String customMsg) { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(expectedType, lrv, mainVars); gatherVariables(foundType, lrv, mainVars); TranslationTable map = new TranslationTable(); Predicate premises = joinPredicates(expectedType, mainVars, lrv, map).toConjunctions(); - diagnostics.add(new RefinementError(element, expectedType, premises.simplify(), map)); + diagnostics.add(new RefinementError(element, expectedType.getExpression(), premises.simplify(), map)); } public void printSameStateError(CtElement element, Predicate expectedType, String klass) { TranslationTable map = createMap(element, expectedType); - diagnostics.add(new StateConflictError(element, expectedType, klass, map)); + diagnostics.add(new StateConflictError(element, expectedType.getExpression(), klass, map)); } private void printError(Exception e, Predicate premisesBeforeChange, Predicate expectedType, CtElement element, @@ -321,14 +332,11 @@ private void printError(Exception e, Predicate premisesBeforeChange, Predicate e private LJError mapError(Exception e, Predicate premisesBeforeChange, Predicate expectedType, CtElement element, TranslationTable map) { if (e instanceof TypeCheckError) { - return new RefinementError(element, expectedType, premisesBeforeChange.simplify(), map); - } else if (e instanceof GhostFunctionError) { - return new GhostInvocationError("Invalid types or number of arguments in ghost invocation", - element.getPosition(), expectedType, map); - } else if (e instanceof NotFoundSMTError) { + return new RefinementError(element, expectedType.getExpression(), premisesBeforeChange.simplify(), map); + } else if (e instanceof liquidjava.smt.errors.NotFoundError) { return new NotFoundError(element, e.getMessage(), map); } else { - return new CustomError(element, e.getMessage()); + return new CustomError(e.getMessage(), element); } } @@ -337,6 +345,8 @@ public void printStateMismatchError(CtElement element, String method, Predicate gatherVariables(found, lrv, mainVars); TranslationTable map = new TranslationTable(); VCImplication foundState = joinPredicates(found, mainVars, lrv, map); - diagnostics.add(new StateRefinementError(element, method, states, foundState.toConjunctions(), map)); + diagnostics.add(new StateRefinementError(element, method, + Arrays.stream(states).map(Predicate::getExpression).toArray(Expression[]::new), + foundState.toConjunctions().simplify().getValue(), map)); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java index c729b2ce..52fe40dc 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java @@ -15,7 +15,7 @@ import liquidjava.processor.refinement_checker.TypeChecker; import liquidjava.utils.constants.Formats; import liquidjava.utils.constants.Keys; -import liquidjava.processor.refinement_checker.object_checkers.AuxHierarchyRefinememtsPassage; +import liquidjava.processor.refinement_checker.object_checkers.AuxHierarchyRefinementsPassage; import liquidjava.processor.refinement_checker.object_checkers.AuxStateHandler; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.parsing.ParsingException; @@ -107,7 +107,7 @@ public void getMethodRefinements(CtMethod method) throws ParsingException AuxStateHandler.handleMethodState(method, f, rtc, prefix); if (klass != null) - AuxHierarchyRefinememtsPassage.checkFunctionInSupertypes(klass, method, f, rtc); + AuxHierarchyRefinementsPassage.checkFunctionInSupertypes(klass, method, f, rtc); } public void getMethodRefinements(CtMethod method, String prefix) throws ParsingException { diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinememtsPassage.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java similarity index 96% rename from liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinememtsPassage.java rename to liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java index 63e5ad18..fc92e29a 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinememtsPassage.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java @@ -1,7 +1,5 @@ package liquidjava.processor.refinement_checker.object_checkers; -import static liquidjava.diagnostics.LJDiagnostics.diagnostics; - import java.util.HashMap; import java.util.List; import java.util.Optional; @@ -20,7 +18,7 @@ import spoon.reflect.declaration.CtParameter; import spoon.reflect.reference.CtTypeReference; -public class AuxHierarchyRefinememtsPassage { +public class AuxHierarchyRefinementsPassage { public static void checkFunctionInSupertypes(CtClass klass, CtMethod method, RefinedFunction f, TypeChecker tc) { @@ -83,10 +81,9 @@ static void transferArgumentsRefinements(RefinedFunction superFunction, RefinedF if (argRef.isBooleanTrue()) { arg.setRefinement(superArgRef.substituteVariable(newName, arg.getName())); } else { - boolean f = tc.checksStateSMT(superArgRef, argRef, params.get(i).getPosition()); - if (!f) { - if (!diagnostics.foundError()) - tc.createError(method, argRef, superArgRef, ""); + boolean ok = tc.checksStateSMT(superArgRef, argRef, params.get(i).getPosition()); + if (!ok) { + tc.createError(method, argRef, superArgRef, ""); } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java index 4cc11384..9ec34087 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java @@ -1,6 +1,6 @@ package liquidjava.processor.refinement_checker.object_checkers; -import static liquidjava.diagnostics.LJDiagnostics.diagnostics; +import static liquidjava.diagnostics.Diagnostics.diagnostics; import java.lang.annotation.Annotation; import java.util.*; @@ -197,23 +197,20 @@ private static Predicate createStatePredicate(String value, /* RefinedFunction f new InvalidRefinementError(e, "State refinement transition must be a boolean expression", value)); return new Predicate(); } - String t = targetClass; // f.getTargetClass(); - CtTypeReference r = tc.getFactory().Type().createReference(t); - + CtTypeReference r = tc.getFactory().Type().createReference(targetClass); String nameOld = String.format(Formats.INSTANCE, Keys.THIS, tc.getContext().getCounter()); String name = String.format(Formats.INSTANCE, Keys.THIS, tc.getContext().getCounter()); tc.getContext().addVarToContext(name, r, new Predicate(), e); tc.getContext().addVarToContext(nameOld, r, new Predicate(), e); // TODO REVIEW!! // what is it for? - Predicate c1 = isTo ? getMissingStates(t, tc, p) : p; + Predicate c1 = isTo ? getMissingStates(targetClass, tc, p) : p; Predicate c = c1.substituteVariable(Keys.THIS, name); c = c.changeOldMentions(nameOld, name); - boolean b = tc.checksStateSMT(new Predicate(), c.negate(), e.getPosition()); - if (b && !diagnostics.foundError()) { - tc.createSameStateError(e, p, t); + boolean ok = tc.checksStateSMT(new Predicate(), c.negate(), e.getPosition()); + if (ok) { + tc.createSameStateError(e, p, targetClass); } - return c1; } @@ -390,9 +387,8 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) { stateChange.setFrom(fromPredicate); stateChange.setTo(toPredicate); } catch (ParsingException e) { - diagnostics.add(new CustomError(field, - String.format("Parsing error while constructing assignment update for `%s` in class `%s` : %s", fw, - field.getDeclaringType().getQualifiedName(), e.getMessage()))); + String message = String.format("Parsing error while constructing assignment update for `%s`", fw); + diagnostics.add(new CustomError(message, e.getMessage(), field)); return; } @@ -401,10 +397,8 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) { .changeOldMentions(vi.getName(), instanceName); if (!tc.checksStateSMT(prevState, expectState, fw.getPosition())) { // Invalid field transition - if (!diagnostics.foundError()) { // No errors so far - Predicate[] states = { stateChange.getFrom() }; - tc.createStateMismatchError(fw, fw.toString(), prevState, states); - } + Predicate[] states = { stateChange.getFrom() }; + tc.createStateMismatchError(fw, fw.toString(), prevState, states); return; } @@ -489,13 +483,11 @@ private static Predicate changeState(TypeChecker tc, VariableInstance vi, return transitionedState; } } - if (!found && !diagnostics.foundError()) { // Reaches the end of stateChange no matching states + if (!found) { // Reaches the end of stateChange no matching states Predicate[] states = stateChanges.stream().filter(ObjectState::hasFrom).map(ObjectState::getFrom) .toArray(Predicate[]::new); - String simpleInvocation = invocation.toString(); // .getExecutable().toString(); + String simpleInvocation = invocation.toString(); tc.createStateMismatchError(invocation, simpleInvocation, prevState, states); - // ErrorPrinter.printStateMismatch(invocation, simpleInvocation, prevState, - // states); } return new Predicate(); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java index 41de354b..7410b7bb 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java @@ -1,6 +1,6 @@ package liquidjava.rj_language; -import static liquidjava.diagnostics.LJDiagnostics.diagnostics; +import static liquidjava.diagnostics.Diagnostics.diagnostics; import java.util.ArrayList; import java.util.HashMap; @@ -31,6 +31,7 @@ import liquidjava.utils.constants.Keys; import liquidjava.utils.constants.Ops; import liquidjava.utils.constants.Types; +import spoon.reflect.cu.SourcePosition; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtType; import spoon.reflect.factory.Factory; @@ -76,9 +77,6 @@ public Predicate(String ref, CtElement element) throws ParsingException { public Predicate(String ref, CtElement element, String prefix) throws ParsingException { this.prefix = prefix; exp = parse(ref, element); - if (diagnostics.foundError()) { - return; - } if (!(exp instanceof GroupExpression)) { exp = new GroupExpression(exp); } @@ -93,7 +91,8 @@ protected Expression parse(String ref, CtElement element) throws ParsingExceptio try { return RefinementsParser.createAST(ref, prefix); } catch (ParsingException e) { - diagnostics.add(new SyntaxError(e.getMessage(), element, ref)); + SourcePosition pos = Utils.getRefinementAnnotationPosition(element, ref); + diagnostics.add(new SyntaxError(e.getMessage(), pos, ref)); throw e; } } @@ -223,11 +222,33 @@ public ValDerivationNode simplify() { return ExpressionSimplifier.simplify(exp.clone()); } + private static boolean isBooleanLiteral(Expression expr, boolean value) { + return expr instanceof LiteralBoolean && ((LiteralBoolean) expr).isBooleanTrue() == value; + } + public static Predicate createConjunction(Predicate c1, Predicate c2) { + // simplification: (true && x) = x, (false && x) = false + if (isBooleanLiteral(c1.getExpression(), true)) + return c2; + if (isBooleanLiteral(c2.getExpression(), true)) + return c1; + if (isBooleanLiteral(c1.getExpression(), false)) + return c1; + if (isBooleanLiteral(c2.getExpression(), false)) + return c2; return new Predicate(new BinaryExpression(c1.getExpression(), Ops.AND, c2.getExpression())); } public static Predicate createDisjunction(Predicate c1, Predicate c2) { + // simplification: (false || x) = x, (true || x) = true + if (isBooleanLiteral(c1.getExpression(), false)) + return c2; + if (isBooleanLiteral(c2.getExpression(), false)) + return c1; + if (isBooleanLiteral(c1.getExpression(), true)) + return c1; + if (isBooleanLiteral(c2.getExpression(), true)) + return c2; return new Predicate(new BinaryExpression(c1.getExpression(), Ops.OR, c2.getExpression())); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java index afe7b518..74e54cce 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java @@ -33,6 +33,12 @@ public String toString() { return name + "(" + getArgs().stream().map(p -> p.toString()).collect(Collectors.joining(", ")) + ")"; } + @Override + public String toSimplifiedString() { + return name + "(" + getArgs().stream().map(Expression::toSimplifiedString).collect(Collectors.joining(", ")) + + ")"; + } + @Override public void getVariableNames(List toAdd) { for (Expression e : getArgs()) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java index 5e07cd03..2cc30c6b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java @@ -49,6 +49,11 @@ public String toString() { return getFirstOperand().toString() + " " + op + " " + getSecondOperand().toString(); } + @Override + public String toSimplifiedString() { + return getFirstOperand().toSimplifiedString() + " " + op + " " + getSecondOperand().toSimplifiedString(); + } + @Override public void getVariableNames(List toAdd) { getFirstOperand().getVariableNames(toAdd); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java index 37e3343e..7a7bf0ab 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java @@ -29,6 +29,17 @@ public abstract class Expression { public abstract String toString(); + /** + * Returns a simplified string representation of this expression with unqualified names (e.g., + * com.example.State.open => open Default implementation delegates to toString() Subclasses that contain qualified + * names should override this method + * + * @return simplified string representation + */ + public String toSimplifiedString() { + return toString(); + } + List children = new ArrayList<>(); public void addChild(Expression e) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java index f2c4984f..44d91a75 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java @@ -39,6 +39,13 @@ public String toString() { return name + "(" + getArgs().stream().map(p -> p.toString()).collect(Collectors.joining(",")) + ")"; } + @Override + public String toSimplifiedString() { + String simpleName = Utils.getSimpleName(name); + return simpleName + "(" + + getArgs().stream().map(Expression::toSimplifiedString).collect(Collectors.joining(",")) + ")"; + } + @Override public void getVariableNames(List toAdd) { for (Expression e : getArgs()) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java index a2be4ea2..f2517fb2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java @@ -23,6 +23,11 @@ public String toString() { return "(" + getExpression().toString() + ")"; } + @Override + public String toSimplifiedString() { + return getExpression().toSimplifiedString(); + } + @Override public void getVariableNames(List toAdd) { getExpression().getVariableNames(toAdd); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java index 44f90965..6090c894 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java @@ -34,6 +34,12 @@ public String toString() { return getCondition().toString() + "?" + getThen().toString() + ":" + getElse().toString(); } + @Override + public String toSimplifiedString() { + return getCondition().toSimplifiedString() + "?" + getThen().toSimplifiedString() + ":" + + getElse().toSimplifiedString(); + } + @Override public void getVariableNames(List toAdd) { getCondition().getVariableNames(toAdd); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java index 9bfdfec5..82218ec3 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java @@ -31,6 +31,11 @@ public String toString() { return op + getExpression().toString(); } + @Override + public String toSimplifiedString() { + return op + getExpression().toSimplifiedString(); + } + @Override public void getVariableNames(List toAdd) { getExpression().getVariableNames(toAdd); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/ParsingException.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/ParsingException.java index d4d43439..7eae1e71 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/ParsingException.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/ParsingException.java @@ -2,9 +2,6 @@ public class ParsingException extends Exception { - /** */ - private static final long serialVersionUID = 1L; - public ParsingException(String errorMessage) { super(errorMessage); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/GhostFunctionError.java b/liquidjava-verifier/src/main/java/liquidjava/smt/GhostFunctionError.java deleted file mode 100644 index 9f6259ae..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/GhostFunctionError.java +++ /dev/null @@ -1,23 +0,0 @@ -package liquidjava.smt; - -import spoon.reflect.declaration.CtElement; - -public class GhostFunctionError extends Exception { - - /** */ - private static final long serialVersionUID = 1L; - - private CtElement location; - - public GhostFunctionError(String message) { - super(message); - } - - public CtElement getLocation() { - return location; - } - - public void setLocation(CtElement location) { - this.location = location; - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/NotFoundSMTError.java b/liquidjava-verifier/src/main/java/liquidjava/smt/NotFoundSMTError.java deleted file mode 100644 index 0a23d2af..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/NotFoundSMTError.java +++ /dev/null @@ -1,22 +0,0 @@ -package liquidjava.smt; - -import spoon.reflect.declaration.CtElement; - -public class NotFoundSMTError extends Exception { - private CtElement location; - - public NotFoundSMTError(String message) { - super(message); - } - - public CtElement getLocation() { - return location; - } - - public void setLocation(CtElement location) { - this.location = location; - } - - /** */ - private static final long serialVersionUID = 1L; -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java b/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java index 9eb23e6f..6276a872 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java @@ -1,22 +1,20 @@ package liquidjava.smt; -import static liquidjava.diagnostics.LJDiagnostics.diagnostics; - import com.martiansoftware.jsap.SyntaxException; import com.microsoft.z3.Expr; import com.microsoft.z3.Status; import com.microsoft.z3.Z3Exception; -import liquidjava.diagnostics.errors.GhostInvocationError; import liquidjava.processor.context.Context; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.Expression; +import liquidjava.smt.errors.TypeCheckError; import spoon.reflect.cu.SourcePosition; public class SMTEvaluator { public void verifySubtype(Predicate subRef, Predicate supRef, Context c, SourcePosition pos) - throws TypeCheckError, GhostFunctionError, Exception { + throws TypeCheckError, Exception { // Creates a parser for our SMT-ready refinement language // Discharges the verification to z3 @@ -42,10 +40,6 @@ public void verifySubtype(Predicate subRef, Predicate supRef, Context c, SourceP System.out.println("Could not parse: " + toVerify); e1.printStackTrace(); } catch (Z3Exception e) { - String msg = e.getLocalizedMessage().toLowerCase(); - if (msg.contains("wrong number of arguments") || msg.contains("sort mismatch")) - diagnostics.add(new GhostInvocationError(msg, pos, supRef, null)); - throw new Z3Exception(e.getLocalizedMessage()); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java index b508592d..79eb58cd 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java @@ -17,6 +17,7 @@ import java.util.List; import java.util.Map; import liquidjava.processor.context.AliasWrapper; +import liquidjava.smt.errors.NotFoundError; import liquidjava.utils.Utils; import org.apache.commons.lang3.NotImplementedException; @@ -75,7 +76,7 @@ public Expr makeBooleanLiteral(boolean value) { private Expr getVariableTranslation(String name) throws Exception { if (!varTranslation.containsKey(name)) - throw new NotFoundSMTError("Variable '" + name.toString() + "' not found"); + throw new NotFoundError("Variable '" + name.toString() + "' not found"); Expr e = varTranslation.get(name); if (e == null) e = varTranslation.get(String.format("this#%s", name)); @@ -144,7 +145,7 @@ private FuncDecl resolveFunctionDeclFallback(String name, Expr[] params) t if (candidate != null) { return candidate; } - throw new NotFoundSMTError("Function '" + name + "' not found"); + throw new NotFoundError("Function '" + name + "' not found"); } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/errors/NotFoundError.java b/liquidjava-verifier/src/main/java/liquidjava/smt/errors/NotFoundError.java new file mode 100644 index 00000000..5c163808 --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/errors/NotFoundError.java @@ -0,0 +1,8 @@ +package liquidjava.smt.errors; + +public class NotFoundError extends SMTError { + + public NotFoundError(String message) { + super(message); + } +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/TypeCheckError.java b/liquidjava-verifier/src/main/java/liquidjava/smt/errors/SMTError.java similarity index 59% rename from liquidjava-verifier/src/main/java/liquidjava/smt/TypeCheckError.java rename to liquidjava-verifier/src/main/java/liquidjava/smt/errors/SMTError.java index d2f59fff..04d9890c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/TypeCheckError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/errors/SMTError.java @@ -1,12 +1,11 @@ -package liquidjava.smt; +package liquidjava.smt.errors; import spoon.reflect.declaration.CtElement; -public class TypeCheckError extends Exception { - +public class SMTError extends Exception { private CtElement location; - public TypeCheckError(String message) { + public SMTError(String message) { super(message); } @@ -17,7 +16,4 @@ public CtElement getLocation() { public void setLocation(CtElement location) { this.location = location; } - - /** */ - private static final long serialVersionUID = 1L; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/errors/TypeCheckError.java b/liquidjava-verifier/src/main/java/liquidjava/smt/errors/TypeCheckError.java new file mode 100644 index 00000000..2419b740 --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/errors/TypeCheckError.java @@ -0,0 +1,8 @@ +package liquidjava.smt.errors; + +public class TypeCheckError extends Exception { + + public TypeCheckError(String message) { + super(message); + } +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java b/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java index 089c28d7..b56a8cfd 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java +++ b/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java @@ -3,6 +3,8 @@ import java.util.Set; import liquidjava.utils.constants.Types; +import spoon.reflect.cu.SourcePosition; +import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; import spoon.reflect.reference.CtTypeReference; @@ -36,4 +38,12 @@ public static String qualifyName(String prefix, String name) { public static String stripParens(String s) { return s.startsWith("(") && s.endsWith(")") ? s.substring(1, s.length() - 1) : s; } + + public static SourcePosition getRefinementAnnotationPosition(CtElement element, String refinement) { + return element.getAnnotations().stream().filter(a -> { + String value = a.getValue("value").toString(); + String unquoted = value.substring(1, value.length() - 1); + return unquoted.equals(refinement); + }).findFirst().map(a -> a.getPosition()).orElse(element.getPosition()); + } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java b/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java index 5ffa1dcb..b7f6d218 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java +++ b/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java @@ -1,6 +1,6 @@ package liquidjava.api.tests; -import static liquidjava.diagnostics.LJDiagnostics.diagnostics; +import static liquidjava.diagnostics.Diagnostics.diagnostics; import static org.junit.Assert.fail; import java.io.IOException; From a05507ff810400d841d70bb2690ad07fec518769 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Sun, 16 Nov 2025 18:08:48 +0000 Subject: [PATCH 06/25] Multiple Errors (#121) --- .../test/currentlyTesting/SimpleTest.java | 106 +-------- .../testMultiple/MultipleErrorsExample.java | 19 ++ .../errors/CustomError.java | 2 +- .../errors/GhostInvocationError.java | 2 +- .../IllegalConstructorTransitionError.java | 2 +- .../errors/InvalidRefinementError.java | 2 +- .../errors/NotFoundError.java | 2 +- .../errors/RefinementError.java | 2 +- .../errors/StateConflictError.java | 2 +- .../errors/StateRefinementError.java | 2 +- .../errors/SyntaxError.java | 2 +- .../warnings/NonExistentClass.java | 2 +- .../warnings/NonExistentConstructor.java | 2 +- .../warnings/NonExistentMethod.java | 2 +- liquidjava-verifier/pom.xml | 2 +- .../liquidjava/api/CommandLineLauncher.java | 12 +- .../liquidjava/diagnostics/Diagnostics.java | 59 ++--- .../liquidjava/diagnostics/LJDiagnostic.java | 7 +- .../errors/InvalidRefinementError.java | 1 - .../processor/RefinementProcessor.java | 23 +- .../ann_generation/FieldGhostsGeneration.java | 6 - .../processor/context/AliasWrapper.java | 6 +- .../liquidjava/processor/facade/AliasDTO.java | 5 +- .../ExternalRefinementTypeChecker.java | 53 +---- .../MethodsFirstChecker.java | 64 +++--- .../RefinementTypeChecker.java | 216 ++++-------------- .../refinement_checker/TypeChecker.java | 82 +++---- .../refinement_checker/VCChecker.java | 47 ++-- .../MethodsFunctionsChecker.java | 26 +-- .../general_checkers/OperationsChecker.java | 33 +-- .../AuxHierarchyRefinementsPassage.java | 12 +- .../object_checkers/AuxStateHandler.java | 119 ++++++---- .../rj_language/BuiltinFunctionPredicate.java | 8 +- .../liquidjava/rj_language/Predicate.java | 31 +-- .../rj_language/parsing/ParsingException.java | 8 - .../parsing/RefinementsParser.java | 25 +- .../rj_language/visitors/AliasVisitor.java | 3 - .../visitors/CreateASTVisitor.java | 34 +-- .../liquidjava/api/tests/TestExamples.java | 4 +- .../liquidjava/api/tests/TestMultiple.java | 45 ++++ 40 files changed, 431 insertions(+), 649 deletions(-) create mode 100644 liquidjava-example/src/main/java/testMultiple/MultipleErrorsExample.java rename liquidjava-example/src/main/java/{testingInProgress/diagnostics => testMultiple}/errors/CustomError.java (70%) rename liquidjava-example/src/main/java/{testingInProgress/diagnostics => testMultiple}/errors/GhostInvocationError.java (82%) rename liquidjava-example/src/main/java/{testingInProgress/diagnostics => testMultiple}/errors/IllegalConstructorTransitionError.java (86%) rename liquidjava-example/src/main/java/{testingInProgress/diagnostics => testMultiple}/errors/InvalidRefinementError.java (80%) rename liquidjava-example/src/main/java/{testingInProgress/diagnostics => testMultiple}/errors/NotFoundError.java (79%) rename liquidjava-example/src/main/java/{testingInProgress/diagnostics => testMultiple}/errors/RefinementError.java (80%) rename liquidjava-example/src/main/java/{testingInProgress/diagnostics => testMultiple}/errors/StateConflictError.java (87%) rename liquidjava-example/src/main/java/{testingInProgress/diagnostics => testMultiple}/errors/StateRefinementError.java (91%) rename liquidjava-example/src/main/java/{testingInProgress/diagnostics => testMultiple}/errors/SyntaxError.java (79%) rename liquidjava-example/src/main/java/{testingInProgress/diagnostics => testMultiple}/warnings/NonExistentClass.java (78%) rename liquidjava-example/src/main/java/{testingInProgress/diagnostics => testMultiple}/warnings/NonExistentConstructor.java (91%) rename liquidjava-example/src/main/java/{testingInProgress/diagnostics => testMultiple}/warnings/NonExistentMethod.java (90%) delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/ParsingException.java create mode 100644 liquidjava-verifier/src/test/java/liquidjava/api/tests/TestMultiple.java diff --git a/liquidjava-example/src/main/java/test/currentlyTesting/SimpleTest.java b/liquidjava-example/src/main/java/test/currentlyTesting/SimpleTest.java index 48999074..1d2feab6 100644 --- a/liquidjava-example/src/main/java/test/currentlyTesting/SimpleTest.java +++ b/liquidjava-example/src/main/java/test/currentlyTesting/SimpleTest.java @@ -4,102 +4,16 @@ class SimpleTest { - @Refinement("return > 0") - public int test() { - return 10; + void test1() { + @Refinement("x > 0") + int x = -1; } -} - -// @RefinementAlias("Percentage(int x) { 0 <= x && x <= 100 }") -// @Refinement("Percentage(_)") -// public static int addBonus( -// @Refinement("Percentage(grade)") int grade, -// @Refinement("Percentage(bonus) && (bonus < grade)") -// int bonus) { -// -// if((grade + bonus) > 100) -// return 100; -// -// else -// return grade+bonus; -// } -// - -// @Refinement("_ > 10") -// int i = 10; - -// @Refinement("sum(_) > 30") -// Account a = new Account(50); -// a.deposit(60); - -// Account b = new Account(138); -// b = a.transferTo(b, 10); -// -// @Refinement("sum(_) == 148") -// Account c = b; - -// Order o = new Order(); -// -// Order f = o.addItem("shirt", 60).addGift(); -// .getNewOrderPayThis().addItem("t", 6).addItem("t", 1); -// o.addGift(); -// f.addItem("l", 1).pay(000).addGift().pay(000);//.addTransportCosts().sendToAddress("home"); - -// TrafficLight tl = new TrafficLight(); -// tl.transitionToAmber(); -// - -// TrafficLight tl2 = tl.getTrafficLightStartingRed(); -// tl2.transitionToFlashingAmber(); - -// tl.transitionToAmber(); -// tl.transitionToAmber(); - -// tl.passagersCross(); -// tl.intermitentMalfunction(); -// tl.transitionToFlashingAmber(); + void test2() { + @Refinement("y > 0") + int y = -2; -// @Refinement("size(al) < 4") -// ArrayList al = new ArrayList(); -// al.add(1); -// al.add(1); -// al.get(2); - -// @Refinement("size(t) == 3") -// ArrayList t = al; - -// -// Order o = new Order(); -// o.addItem("shirt", 5) -// .addItem("shirt", 10) -// .addItem("h", 20) -// .addItem("h", 6); - -// InputStreamReader isr = new InputStreamReader(System.in); -// isr.read(); -// isr.read(); -// isr.read(); -// isr.close(); -// -// //... -// isr.read(); - -// @Refinement("_ > 0") -// public int fun (int[] arr) { -// return max(arr[0], 1); -// } -// - -// //@Refinement("_.length(x) >= 0") == -//// @Refinement("length(_, x) >= 0") -//// int[] a1 = new int[5]; -// K(.., ..) - -// } - -// See error NaN -// @Refinement("true") -// double b = 0/0; -// @Refinement("_ > 5") -// double c = b; + @Refinement("z > 0") + int z = 3; + } +} diff --git a/liquidjava-example/src/main/java/testMultiple/MultipleErrorsExample.java b/liquidjava-example/src/main/java/testMultiple/MultipleErrorsExample.java new file mode 100644 index 00000000..a46cde56 --- /dev/null +++ b/liquidjava-example/src/main/java/testMultiple/MultipleErrorsExample.java @@ -0,0 +1,19 @@ +package testMultiple; + +import liquidjava.specification.Refinement; + +class MultipleErrorsExample { + + void test1() { + @Refinement("a > 0") + int a = -1; + } + + void test2() { + @Refinement("b > 0") + int b = -2; + + @Refinement("c > 0") + int c = -3; + } +} diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/CustomError.java b/liquidjava-example/src/main/java/testMultiple/errors/CustomError.java similarity index 70% rename from liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/CustomError.java rename to liquidjava-example/src/main/java/testMultiple/errors/CustomError.java index 953c56ef..7505005d 100644 --- a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/CustomError.java +++ b/liquidjava-example/src/main/java/testMultiple/errors/CustomError.java @@ -1,4 +1,4 @@ -package testingInProgress.diagnostics.errors; +package testMultiple.errors; import liquidjava.specification.StateSet; diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/GhostInvocationError.java b/liquidjava-example/src/main/java/testMultiple/errors/GhostInvocationError.java similarity index 82% rename from liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/GhostInvocationError.java rename to liquidjava-example/src/main/java/testMultiple/errors/GhostInvocationError.java index db4bcd57..831c6ca5 100644 --- a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/GhostInvocationError.java +++ b/liquidjava-example/src/main/java/testMultiple/errors/GhostInvocationError.java @@ -1,4 +1,4 @@ -package testingInProgress.diagnostics.errors; +package testMultiple.errors; import liquidjava.specification.Ghost; import liquidjava.specification.StateRefinement; diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/IllegalConstructorTransitionError.java b/liquidjava-example/src/main/java/testMultiple/errors/IllegalConstructorTransitionError.java similarity index 86% rename from liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/IllegalConstructorTransitionError.java rename to liquidjava-example/src/main/java/testMultiple/errors/IllegalConstructorTransitionError.java index dcfacf93..01134b29 100644 --- a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/IllegalConstructorTransitionError.java +++ b/liquidjava-example/src/main/java/testMultiple/errors/IllegalConstructorTransitionError.java @@ -1,4 +1,4 @@ -package testingInProgress.diagnostics.errors; +package testMultiple.errors; import liquidjava.specification.StateRefinement; import liquidjava.specification.StateSet; diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/InvalidRefinementError.java b/liquidjava-example/src/main/java/testMultiple/errors/InvalidRefinementError.java similarity index 80% rename from liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/InvalidRefinementError.java rename to liquidjava-example/src/main/java/testMultiple/errors/InvalidRefinementError.java index 92ed8dbd..e81cfdff 100644 --- a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/InvalidRefinementError.java +++ b/liquidjava-example/src/main/java/testMultiple/errors/InvalidRefinementError.java @@ -1,4 +1,4 @@ -package testingInProgress.diagnostics.errors; +package testMultiple.errors; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/NotFoundError.java b/liquidjava-example/src/main/java/testMultiple/errors/NotFoundError.java similarity index 79% rename from liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/NotFoundError.java rename to liquidjava-example/src/main/java/testMultiple/errors/NotFoundError.java index ff22cc9b..df3b7d93 100644 --- a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/NotFoundError.java +++ b/liquidjava-example/src/main/java/testMultiple/errors/NotFoundError.java @@ -1,4 +1,4 @@ -package testingInProgress.diagnostics.errors; +package testMultiple.errors; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/RefinementError.java b/liquidjava-example/src/main/java/testMultiple/errors/RefinementError.java similarity index 80% rename from liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/RefinementError.java rename to liquidjava-example/src/main/java/testMultiple/errors/RefinementError.java index 25dac068..b6ab1682 100644 --- a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/RefinementError.java +++ b/liquidjava-example/src/main/java/testMultiple/errors/RefinementError.java @@ -1,4 +1,4 @@ -package testingInProgress.diagnostics.errors; +package testMultiple.errors; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateConflictError.java b/liquidjava-example/src/main/java/testMultiple/errors/StateConflictError.java similarity index 87% rename from liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateConflictError.java rename to liquidjava-example/src/main/java/testMultiple/errors/StateConflictError.java index 9701ff05..66b8b9ae 100644 --- a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateConflictError.java +++ b/liquidjava-example/src/main/java/testMultiple/errors/StateConflictError.java @@ -1,4 +1,4 @@ -package testingInProgress.diagnostics.errors; +package testMultiple.errors; import liquidjava.specification.StateRefinement; import liquidjava.specification.StateSet; diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateRefinementError.java b/liquidjava-example/src/main/java/testMultiple/errors/StateRefinementError.java similarity index 91% rename from liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateRefinementError.java rename to liquidjava-example/src/main/java/testMultiple/errors/StateRefinementError.java index 6e4aa8fb..1187de24 100644 --- a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/StateRefinementError.java +++ b/liquidjava-example/src/main/java/testMultiple/errors/StateRefinementError.java @@ -1,4 +1,4 @@ -package testingInProgress.diagnostics.errors; +package testMultiple.errors; import liquidjava.specification.StateRefinement; import liquidjava.specification.StateSet; diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/SyntaxError.java b/liquidjava-example/src/main/java/testMultiple/errors/SyntaxError.java similarity index 79% rename from liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/SyntaxError.java rename to liquidjava-example/src/main/java/testMultiple/errors/SyntaxError.java index 87953608..082d25b8 100644 --- a/liquidjava-example/src/main/java/testingInProgress/diagnostics/errors/SyntaxError.java +++ b/liquidjava-example/src/main/java/testMultiple/errors/SyntaxError.java @@ -1,4 +1,4 @@ -package testingInProgress.diagnostics.errors; +package testMultiple.errors; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentClass.java b/liquidjava-example/src/main/java/testMultiple/warnings/NonExistentClass.java similarity index 78% rename from liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentClass.java rename to liquidjava-example/src/main/java/testMultiple/warnings/NonExistentClass.java index f4eb3ebe..c1506822 100644 --- a/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentClass.java +++ b/liquidjava-example/src/main/java/testMultiple/warnings/NonExistentClass.java @@ -1,4 +1,4 @@ -package testingInProgress.diagnostics.warnings; +package testMultiple.warnings; import liquidjava.specification.ExternalRefinementsFor; diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentConstructor.java b/liquidjava-example/src/main/java/testMultiple/warnings/NonExistentConstructor.java similarity index 91% rename from liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentConstructor.java rename to liquidjava-example/src/main/java/testMultiple/warnings/NonExistentConstructor.java index c4a5f4d5..eae37e3c 100644 --- a/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentConstructor.java +++ b/liquidjava-example/src/main/java/testMultiple/warnings/NonExistentConstructor.java @@ -1,4 +1,4 @@ -package testingInProgress.diagnostics.warnings; +package testMultiple.warnings; import liquidjava.specification.ExternalRefinementsFor; import liquidjava.specification.RefinementPredicate; diff --git a/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentMethod.java b/liquidjava-example/src/main/java/testMultiple/warnings/NonExistentMethod.java similarity index 90% rename from liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentMethod.java rename to liquidjava-example/src/main/java/testMultiple/warnings/NonExistentMethod.java index af7237f1..9d3d16e6 100644 --- a/liquidjava-example/src/main/java/testingInProgress/diagnostics/warnings/NonExistentMethod.java +++ b/liquidjava-example/src/main/java/testMultiple/warnings/NonExistentMethod.java @@ -1,4 +1,4 @@ -package testingInProgress.diagnostics.warnings; +package testMultiple.warnings; import liquidjava.specification.ExternalRefinementsFor; import liquidjava.specification.RefinementPredicate; diff --git a/liquidjava-verifier/pom.xml b/liquidjava-verifier/pom.xml index e9727bff..5edca456 100644 --- a/liquidjava-verifier/pom.xml +++ b/liquidjava-verifier/pom.xml @@ -11,7 +11,7 @@ io.github.liquid-java liquidjava-verifier - 0.0.1 + 0.0.2 liquidjava-verifier LiquidJava Verifier https://github.com/liquid-java/liquidjava diff --git a/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java index b1187271..08162fe8 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java +++ b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java @@ -1,11 +1,10 @@ package liquidjava.api; -import static liquidjava.diagnostics.Diagnostics.diagnostics; - import java.io.File; import java.util.Arrays; import java.util.List; +import liquidjava.diagnostics.Diagnostics; import liquidjava.diagnostics.errors.CustomError; import liquidjava.processor.RefinementProcessor; import spoon.Launcher; @@ -15,6 +14,9 @@ import spoon.support.QueueProcessingManager; public class CommandLineLauncher { + + private static final Diagnostics diagnostics = Diagnostics.getInstance(); + public static void main(String[] args) { if (args.length == 0) { System.out.println("No input paths provided"); @@ -26,10 +28,12 @@ public static void main(String[] args) { } List paths = Arrays.asList(args); launch(paths.toArray(new String[0])); + + // print diagnostics + System.out.println(diagnostics.getWarningOutput()); if (diagnostics.foundError()) { System.out.println(diagnostics.getErrorOutput()); } else { - System.out.println(diagnostics.getWarningOutput()); System.out.println("Correct! Passed Verification."); } } @@ -37,6 +41,7 @@ public static void main(String[] args) { public static void launch(String... paths) { System.out.println("Running LiquidJava on: " + Arrays.toString(paths).replaceAll("[\\[\\]]", "")); + diagnostics.clear(); Launcher launcher = new Launcher(); for (String path : paths) { if (!new File(path).exists()) { @@ -49,7 +54,6 @@ public static void launch(String... paths) { launcher.getEnvironment().setNoClasspath(true); launcher.getEnvironment().setComplianceLevel(8); launcher.run(); - diagnostics.clear(); final Factory factory = launcher.getFactory(); final ProcessingManager processingManager = new QueueProcessingManager(factory); diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java index dd69be2e..fc7c75f7 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java @@ -1,6 +1,6 @@ package liquidjava.diagnostics; -import java.util.ArrayList; +import java.util.LinkedHashSet; import liquidjava.diagnostics.errors.LJError; import liquidjava.diagnostics.warnings.LJWarning; @@ -11,24 +11,26 @@ * @see LJWarning */ public class Diagnostics { - public static final Diagnostics diagnostics = new Diagnostics(); + private static final Diagnostics instance = new Diagnostics(); - private ArrayList errors; - private ArrayList warnings; + private LinkedHashSet errors; + private LinkedHashSet warnings; private Diagnostics() { - this.errors = new ArrayList<>(); - this.warnings = new ArrayList<>(); + this.errors = new LinkedHashSet<>(); + this.warnings = new LinkedHashSet<>(); + } + + public static Diagnostics getInstance() { + return instance; } public void add(LJError error) { - if (!this.errors.contains(error)) - this.errors.add(error); + this.errors.add(error); } public void add(LJWarning warning) { - if (!this.warnings.contains(warning)) - this.warnings.add(warning); + this.warnings.add(warning); } public boolean foundError() { @@ -39,52 +41,25 @@ public boolean foundWarning() { return !this.warnings.isEmpty(); } - public ArrayList getErrors() { + public LinkedHashSet getErrors() { return this.errors; } - public ArrayList getWarnings() { + public LinkedHashSet getWarnings() { return this.warnings; } - public LJError getError() { - return foundError() ? this.errors.get(0) : null; - } - - public LJWarning getWarning() { - return foundWarning() ? this.warnings.get(0) : null; - } - public void clear() { this.errors.clear(); this.warnings.clear(); } public String getErrorOutput() { - StringBuilder sb = new StringBuilder(); - if (foundError()) { - for (LJError error : errors) { - sb.append(error.toString()).append("\n"); - } - } else { - if (foundWarning()) { - sb.append("Warnings:\n"); - for (LJWarning warning : warnings) { - sb.append(warning.getMessage()).append("\n"); - } - sb.append("Passed Verification!\n"); - } - } - return sb.toString(); + return String.join("\n", errors.stream().map(LJError::toString).toList()) + (errors.isEmpty() ? "" : "\n"); } public String getWarningOutput() { - StringBuilder sb = new StringBuilder(); - if (foundWarning()) { - for (LJWarning warning : warnings) { - sb.append(warning.toString()).append("\n"); - } - } - return sb.toString(); + return String.join("\n", warnings.stream().map(LJWarning::toString).toList()) + + (warnings.isEmpty() ? "" : "\n"); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java index db48c8a9..9beaccfa 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java @@ -6,7 +6,7 @@ import spoon.reflect.cu.SourcePosition; -public class LJDiagnostic { +public class LJDiagnostic extends RuntimeException { private String title; private String message; @@ -49,8 +49,8 @@ public String toString() { StringBuilder sb = new StringBuilder(); // title - sb.append("\n").append(accentColor).append(title).append(": ").append(Colors.RESET).append(message) - .append("\n"); + sb.append("\n").append(accentColor).append(title).append(": ").append(Colors.RESET) + .append(message.toLowerCase()).append("\n"); // snippet String snippet = getSnippet(); @@ -113,7 +113,6 @@ public String getSnippet() { } return sb.toString(); } catch (Exception e) { - e.printStackTrace(); return null; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java index 96cc272d..8012928c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java @@ -1,6 +1,5 @@ package liquidjava.diagnostics.errors; -import liquidjava.diagnostics.TranslationTable; import spoon.reflect.declaration.CtElement; /** diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java b/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java index 5254f494..ba8d268a 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java @@ -3,6 +3,8 @@ import java.util.ArrayList; import java.util.List; +import liquidjava.diagnostics.Diagnostics; +import liquidjava.diagnostics.errors.LJError; import liquidjava.processor.ann_generation.FieldGhostsGeneration; import liquidjava.processor.context.Context; import liquidjava.processor.refinement_checker.ExternalRefinementTypeChecker; @@ -17,6 +19,7 @@ public class RefinementProcessor extends AbstractProcessor { List visitedPackages = new ArrayList<>(); Factory factory; + Diagnostics diagnostics = Diagnostics.getInstance(); public RefinementProcessor(Factory factory) { this.factory = factory; @@ -29,10 +32,22 @@ public void process(CtPackage pkg) { Context c = Context.getInstance(); c.reinitializeAllContext(); - pkg.accept(new FieldGhostsGeneration(c, factory)); // generate annotations for field ghosts - pkg.accept(new ExternalRefinementTypeChecker(c, factory)); - pkg.accept(new MethodsFirstChecker(c, factory)); // double passing idea (instead of headers) - pkg.accept(new RefinementTypeChecker(c, factory)); + try { + // process types in this package only, not sub-packages + // first pass: gather refinements + pkg.getTypes().forEach(type -> { + type.accept(new FieldGhostsGeneration(c, factory)); // generate annotations for field ghosts + type.accept(new ExternalRefinementTypeChecker(c, factory)); // process external refinements + type.accept(new MethodsFirstChecker(c, factory)); // double passing idea (instead of headers) + }); + + // second pass: check refinements + pkg.getTypes().forEach(type -> { + type.accept(new RefinementTypeChecker(c, factory)); + }); + } catch (LJError e) { + diagnostics.add(e); + } } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java b/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java index 12b6b49f..29600b36 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java @@ -1,7 +1,5 @@ package liquidjava.processor.ann_generation; -import static liquidjava.diagnostics.Diagnostics.diagnostics; - import liquidjava.processor.context.Context; import liquidjava.specification.Ghost; import spoon.reflect.declaration.*; @@ -28,10 +26,6 @@ public Factory getFactory() { @Override public void visitCtClass(CtClass ctClass) { - if (diagnostics.foundError()) { - return; - } - ctClass.getDeclaredFields().stream().filter(fld -> fld.getType().getQualifiedName().equals("int")) .forEach(fld -> { CtTypeReference fld_type = fld.getType(); diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/AliasWrapper.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/AliasWrapper.java index fc8283d5..227db985 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/AliasWrapper.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/AliasWrapper.java @@ -5,10 +5,10 @@ import java.util.List; import java.util.Map; +import liquidjava.diagnostics.errors.LJError; import liquidjava.processor.facade.AliasDTO; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.Expression; -import liquidjava.rj_language.parsing.ParsingException; import liquidjava.utils.Utils; import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; @@ -59,7 +59,7 @@ public Expression getNewExpression(List newNames) { return expr.getExpression().clone(); } - public Predicate getPremises(List list, List newNames, CtElement elem) throws ParsingException { + public Predicate getPremises(List list, List newNames, CtElement elem) throws LJError { List invocationPredicates = getPredicatesFromExpression(list, elem); Predicate prem = new Predicate(); for (int i = 0; i < invocationPredicates.size(); i++) { @@ -69,7 +69,7 @@ public Predicate getPremises(List list, List newNames, CtElement return prem.clone(); } - private List getPredicatesFromExpression(List list, CtElement elem) throws ParsingException { + private List getPredicatesFromExpression(List list, CtElement elem) throws LJError { List lp = new ArrayList<>(); for (String e : list) lp.add(new Predicate(e, elem)); diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/facade/AliasDTO.java b/liquidjava-verifier/src/main/java/liquidjava/processor/facade/AliasDTO.java index 8737f572..7b880475 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/facade/AliasDTO.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/facade/AliasDTO.java @@ -2,8 +2,9 @@ import java.util.List; import java.util.stream.Collectors; + +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.ast.Expression; -import liquidjava.rj_language.parsing.ParsingException; import liquidjava.rj_language.parsing.RefinementsParser; import spoon.reflect.reference.CtTypeReference; @@ -32,7 +33,7 @@ public AliasDTO(String name2, List varTypes2, List varNames2, St // Parse the alias expression using the given the prefix to ensure ghost names are qualified consistently with // where the alias is declared or used - public void parse(String prefix) throws ParsingException { + public void parse(String prefix) throws LJError { if (ref != null) { this.expression = RefinementsParser.createAST(ref, prefix); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java index ce05d7db..2321b1f6 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java @@ -1,11 +1,11 @@ package liquidjava.processor.refinement_checker; -import static liquidjava.diagnostics.Diagnostics.diagnostics; - import java.util.Arrays; import java.util.List; import java.util.Optional; -import liquidjava.diagnostics.errors.CustomError; + +import liquidjava.diagnostics.Diagnostics; +import liquidjava.diagnostics.errors.LJError; import liquidjava.diagnostics.warnings.ExternalClassNotFoundWarning; import liquidjava.diagnostics.warnings.ExternalMethodNotFoundWarning; import liquidjava.processor.context.Context; @@ -13,7 +13,6 @@ import liquidjava.processor.facade.GhostDTO; import liquidjava.processor.refinement_checker.general_checkers.MethodsFunctionsChecker; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.parsing.ParsingException; import liquidjava.rj_language.parsing.RefinementsParser; import liquidjava.utils.Utils; import spoon.reflect.declaration.CtClass; @@ -29,6 +28,7 @@ public class ExternalRefinementTypeChecker extends TypeChecker { String prefix; MethodsFunctionsChecker m; + Diagnostics diagnostics = Diagnostics.getInstance(); public ExternalRefinementTypeChecker(Context context, Factory factory) { super(context, factory); @@ -41,9 +41,6 @@ public void visitCtClass(CtClass ctClass) { @Override public void visitCtInterface(CtInterface intrface) { - if (diagnostics.foundError()) - return; - Optional externalRefinements = getExternalRefinement(intrface); if (externalRefinements.isPresent()) { this.prefix = externalRefinements.get(); @@ -52,11 +49,7 @@ public void visitCtInterface(CtInterface intrface) { diagnostics.add(new ExternalClassNotFoundWarning(intrface, message, prefix)); return; } - try { - getRefinementFromAnnotation(intrface); - } catch (ParsingException e) { - return; // error already reported - } + getRefinementFromAnnotation(intrface); handleStateSetsFromAnnotation(intrface); super.visitCtInterface(intrface); } @@ -64,24 +57,13 @@ public void visitCtInterface(CtInterface intrface) { @Override public void visitCtField(CtField f) { - if (diagnostics.foundError()) - return; - - Optional oc; - try { - oc = getRefinementFromAnnotation(f); - } catch (ParsingException e) { - return; // error already reported - } + Optional oc = getRefinementFromAnnotation(f); Predicate c = oc.orElse(new Predicate()); context.addGlobalVariableToContext(f.getSimpleName(), prefix, f.getType(), c); super.visitCtField(f); } public void visitCtMethod(CtMethod method) { - if (diagnostics.foundError()) - return; - CtType targetType = factory.Type().createReference(prefix).getTypeDeclaration(); if (targetType == null || !(targetType instanceof CtClass)) return; @@ -111,26 +93,15 @@ public void visitCtMethod(CtMethod method) { } } MethodsFunctionsChecker mfc = new MethodsFunctionsChecker(this); - try { - mfc.getMethodRefinements(method, prefix); - } catch (ParsingException e) { - return; - } + mfc.getMethodRefinements(method, prefix); super.visitCtMethod(method); } - protected void getGhostFunction(String value, CtElement element) { - try { - // Optional ofd = - // RefinementParser.parseFunctionDecl(value); - GhostDTO f = RefinementsParser.getGhostDeclaration(value); - if (f != null && element.getParent() instanceof CtInterface) { - GhostFunction gh = new GhostFunction(f, factory, prefix); - context.addGhostFunction(gh); - } - - } catch (ParsingException e) { - diagnostics.add(new CustomError("Could not parse the ghost function", e.getMessage(), element)); + protected void getGhostFunction(String value, CtElement element) throws LJError { + GhostDTO f = RefinementsParser.getGhostDeclaration(value); + if (f != null && element.getParent() instanceof CtInterface) { + GhostFunction gh = new GhostFunction(f, factory, prefix); + context.addGhostFunction(gh); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/MethodsFirstChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/MethodsFirstChecker.java index c4d97695..6a148725 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/MethodsFirstChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/MethodsFirstChecker.java @@ -1,13 +1,12 @@ package liquidjava.processor.refinement_checker; -import static liquidjava.diagnostics.Diagnostics.diagnostics; - import java.util.ArrayList; import java.util.List; +import liquidjava.diagnostics.Diagnostics; +import liquidjava.diagnostics.errors.LJError; import liquidjava.processor.context.Context; import liquidjava.processor.refinement_checker.general_checkers.MethodsFunctionsChecker; -import liquidjava.rj_language.parsing.ParsingException; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtConstructor; import spoon.reflect.declaration.CtInterface; @@ -20,6 +19,7 @@ public class MethodsFirstChecker extends TypeChecker { MethodsFunctionsChecker mfc; List visitedClasses; + Diagnostics diagnostics = Diagnostics.getInstance(); public MethodsFirstChecker(Context context, Factory factory) { super(context, factory); @@ -29,9 +29,6 @@ public MethodsFirstChecker(Context context, Factory factory) { @Override public void visitCtClass(CtClass ctClass) { - if (diagnostics.foundError()) - return; - context.reinitializeContext(); if (visitedClasses.contains(ctClass.getQualifiedName())) return; @@ -53,20 +50,25 @@ public void visitCtClass(CtClass ctClass) { if (ct instanceof CtClass) visitCtClass((CtClass) ct); } + // first try-catch: process class-level annotations) + // errors here should not prevent visiting methods, constructors or fields of the class try { getRefinementFromAnnotation(ctClass); - } catch (ParsingException e) { - return; // error already reported + handleStateSetsFromAnnotation(ctClass); + } catch (LJError e) { + diagnostics.add(e); + } + // second try-catch: visit class children (methods, constructors, fields) + // errors from one child should not prevent visiting sibling elements + try { + super.visitCtClass(ctClass); + } catch (LJError e) { + diagnostics.add(e); } - handleStateSetsFromAnnotation(ctClass); - super.visitCtClass(ctClass); } @Override public void visitCtInterface(CtInterface intrface) { - if (diagnostics.foundError()) - return; - if (visitedClasses.contains(intrface.getQualifiedName())) return; else @@ -74,41 +76,35 @@ public void visitCtInterface(CtInterface intrface) { if (getExternalRefinement(intrface).isPresent()) return; + // first try-catch: process interface-level annotations + // errors here should not prevent visiting the interface's methods try { getRefinementFromAnnotation(intrface); - } catch (ParsingException e) { - return; // error already reported + handleStateSetsFromAnnotation(intrface); + } catch (LJError e) { + diagnostics.add(e); + } + // second try-catch: visit interface children (methods) + // errors from one child should not prevent visiting sibling methods + try { + super.visitCtInterface(intrface); + } catch (LJError e) { + diagnostics.add(e); } - handleStateSetsFromAnnotation(intrface); - super.visitCtInterface(intrface); } @Override public void visitCtConstructor(CtConstructor c) { - if (diagnostics.foundError()) - return; - context.enterContext(); - try { - getRefinementFromAnnotation(c); - mfc.getConstructorRefinements(c); - } catch (ParsingException e) { - return; - } + getRefinementFromAnnotation(c); + mfc.getConstructorRefinements(c); super.visitCtConstructor(c); context.exitContext(); } public void visitCtMethod(CtMethod method) { - if (diagnostics.foundError()) - return; - context.enterContext(); - try { - mfc.getMethodRefinements(method); - } catch (ParsingException e) { - return; - } + mfc.getMethodRefinements(method); super.visitCtMethod(method); context.exitContext(); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java index 6c20c816..d318f25e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java @@ -1,19 +1,18 @@ package liquidjava.processor.refinement_checker; -import static liquidjava.diagnostics.Diagnostics.diagnostics; - import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.List; import java.util.Optional; +import liquidjava.diagnostics.Diagnostics; +import liquidjava.diagnostics.errors.LJError; import liquidjava.processor.context.*; import liquidjava.processor.refinement_checker.general_checkers.MethodsFunctionsChecker; import liquidjava.processor.refinement_checker.general_checkers.OperationsChecker; import liquidjava.processor.refinement_checker.object_checkers.AuxStateHandler; import liquidjava.rj_language.BuiltinFunctionPredicate; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.parsing.ParsingException; import liquidjava.utils.constants.Formats; import liquidjava.utils.constants.Keys; import liquidjava.utils.constants.Types; @@ -55,6 +54,7 @@ public class RefinementTypeChecker extends TypeChecker { // Auxiliary TypeCheckers OperationsChecker otc; MethodsFunctionsChecker mfc; + Diagnostics diagnostics = Diagnostics.getInstance(); public RefinementTypeChecker(Context context, Factory factory) { super(context, factory); @@ -66,76 +66,67 @@ public RefinementTypeChecker(Context context, Factory factory) { @Override public void visitCtClass(CtClass ctClass) { - if (diagnostics.foundError()) { - return; - } - // System.out.println("CTCLASS:"+ctClass.getSimpleName()); context.reinitializeContext(); - super.visitCtClass(ctClass); + + try { + super.visitCtClass(ctClass); + } catch (LJError e) { + diagnostics.add(e); + } + } @Override public void visitCtInterface(CtInterface intrface) { - if (diagnostics.foundError()) { - return; - } - // System.out.println("CT INTERFACE: " +intrface.getSimpleName()); if (getExternalRefinement(intrface).isPresent()) { return; } - super.visitCtInterface(intrface); + try { + super.visitCtInterface(intrface); + } catch (LJError e) { + diagnostics.add(e); + } } @Override public void visitCtAnnotationType(CtAnnotationType annotationType) { - if (diagnostics.foundError()) { - return; - } super.visitCtAnnotationType(annotationType); } @Override public void visitCtConstructor(CtConstructor c) { - if (diagnostics.foundError()) { - return; - } - context.enterContext(); mfc.loadFunctionInfo(c); - super.visitCtConstructor(c); + try { + super.visitCtConstructor(c); + } catch (LJError e) { + diagnostics.add(e); + } context.exitContext(); } public void visitCtMethod(CtMethod method) { - if (diagnostics.foundError()) { - return; - } - context.enterContext(); if (!method.getSignature().equals("main(java.lang.String[])")) { mfc.loadFunctionInfo(method); } - super.visitCtMethod(method); + try { + super.visitCtMethod(method); + } catch (LJError e) { + diagnostics.add(e); + } context.exitContext(); } @Override public void visitCtLocalVariable(CtLocalVariable localVariable) { - if (diagnostics.foundError()) { - return; - } - super.visitCtLocalVariable(localVariable); // only declaration, no assignment if (localVariable.getAssignment() == null) { Optional a; - try { - a = getRefinementFromAnnotation(localVariable); - } catch (ParsingException e) { - return; // error already reported - } + a = getRefinementFromAnnotation(localVariable); context.addVarToContext(localVariable.getSimpleName(), localVariable.getType(), a.orElse(new Predicate()), localVariable); } else { @@ -147,34 +138,18 @@ public void visitCtLocalVariable(CtLocalVariable localVariable) { refinementFound = new Predicate(); } context.addVarToContext(varName, localVariable.getType(), new Predicate(), e); - - try { - checkVariableRefinements(refinementFound, varName, localVariable.getType(), localVariable, - localVariable); - } catch (ParsingException e1) { - return; // error already reported - } - + checkVariableRefinements(refinementFound, varName, localVariable.getType(), localVariable, localVariable); AuxStateHandler.addStateRefinements(this, varName, e); } } @Override public void visitCtNewArray(CtNewArray newArray) { - if (diagnostics.foundError()) { - return; - } - super.visitCtNewArray(newArray); List> l = newArray.getDimensionExpressions(); // TODO only working for 1 dimension for (CtExpression exp : l) { - Predicate c; - try { - c = getExpressionRefinements(exp); - } catch (ParsingException e) { - return; // error already reported - } + Predicate c = getExpressionRefinements(exp); String name = String.format(Formats.FRESH, context.getCounter()); if (c.getVariableNames().contains(Keys.WILDCARD)) { c = c.substituteVariable(Keys.WILDCARD, name); @@ -183,22 +158,15 @@ public void visitCtNewArray(CtNewArray newArray) { } context.addVarToContext(name, factory.Type().INTEGER_PRIMITIVE, c, exp); Predicate ep; - try { - ep = Predicate.createEquals(BuiltinFunctionPredicate.length(Keys.WILDCARD, newArray), - Predicate.createVar(name)); - } catch (ParsingException e) { - return; // error already reported - } + ep = Predicate.createEquals(BuiltinFunctionPredicate.length(Keys.WILDCARD, newArray), + Predicate.createVar(name)); + newArray.putMetadata(Keys.REFINEMENT, ep); } } @Override public void visitCtThisAccess(CtThisAccess thisAccess) { - if (diagnostics.foundError()) { - return; - } - super.visitCtThisAccess(thisAccess); CtClass c = thisAccess.getParent(CtClass.class); String s = c.getSimpleName(); @@ -211,11 +179,7 @@ public void visitCtThisAccess(CtThisAccess thisAccess) { @SuppressWarnings("unchecked") @Override - public void visitCtAssignment(CtAssignment assignment) { - if (diagnostics.foundError()) { - return; - } - + public void visitCtAssignment(CtAssignment assignment) throws LJError { super.visitCtAssignment(assignment); CtExpression ex = assignment.getAssigned(); @@ -237,19 +201,10 @@ public void visitCtAssignment(CtAssignment assignment) { AuxStateHandler.updateGhostField(fw, this); } } - // if (ex instanceof CtArrayWrite) { - // Predicate c = getRefinement(ex); - // TODO continue - // c.substituteVariable(WILD_VAR, ); - // } } @Override public void visitCtArrayRead(CtArrayRead arrayRead) { - if (diagnostics.foundError()) { - return; - } - super.visitCtArrayRead(arrayRead); String name = String.format(Formats.INSTANCE, "arrayAccess", context.getCounter()); context.addVarToContext(name, arrayRead.getType(), new Predicate(), arrayRead); @@ -259,10 +214,6 @@ public void visitCtArrayRead(CtArrayRead arrayRead) { @Override public void visitCtLiteral(CtLiteral lit) { - if (diagnostics.foundError()) { - return; - } - List types = Arrays.asList(Types.IMPLEMENTED); String type = lit.getType().getQualifiedName(); if (types.contains(type)) { @@ -281,17 +232,9 @@ public void visitCtLiteral(CtLiteral lit) { @Override public void visitCtField(CtField f) { - if (diagnostics.foundError()) { - return; - } - super.visitCtField(f); - Optional c; - try { - c = getRefinementFromAnnotation(f); - } catch (ParsingException e) { - return; // error already reported - } + Optional c = getRefinementFromAnnotation(f); + // context.addVarToContext(f.getSimpleName(), f.getType(), // c.map( i -> i.substituteVariable(WILD_VAR, f.getSimpleName()).orElse(new // Predicate()) ); @@ -308,10 +251,6 @@ public void visitCtField(CtField f) { @Override public void visitCtFieldRead(CtFieldRead fieldRead) { - if (diagnostics.foundError()) { - return; - } - String fieldName = fieldRead.getVariable().getSimpleName(); if (context.hasVariable(fieldName)) { RefinedVariable rv = context.getVariableByName(fieldName); @@ -330,12 +269,9 @@ public void visitCtFieldRead(CtFieldRead fieldRead) { } else if (fieldRead.getVariable().getSimpleName().equals("length")) { String targetName = fieldRead.getTarget().toString(); - try { - fieldRead.putMetadata(Keys.REFINEMENT, Predicate.createEquals(Predicate.createVar(Keys.WILDCARD), - BuiltinFunctionPredicate.length(targetName, fieldRead))); - } catch (ParsingException e) { - return; // error already reported - } + fieldRead.putMetadata(Keys.REFINEMENT, Predicate.createEquals(Predicate.createVar(Keys.WILDCARD), + BuiltinFunctionPredicate.length(targetName, fieldRead))); + } else { fieldRead.putMetadata(Keys.REFINEMENT, new Predicate()); // TODO DO WE WANT THIS OR TO SHOW ERROR MESSAGE @@ -346,10 +282,6 @@ public void visitCtFieldRead(CtFieldRead fieldRead) { @Override public void visitCtVariableRead(CtVariableRead variableRead) { - if (diagnostics.foundError()) { - return; - } - super.visitCtVariableRead(variableRead); CtVariable varDecl = variableRead.getVariable().getDeclaration(); getPutVariableMetadada(variableRead, varDecl.getSimpleName()); @@ -360,65 +292,32 @@ public void visitCtVariableRead(CtVariableRead variableRead) { */ @Override public void visitCtBinaryOperator(CtBinaryOperator operator) { - if (diagnostics.foundError()) { - return; - } - super.visitCtBinaryOperator(operator); - try { - otc.getBinaryOpRefinements(operator); - } catch (ParsingException e) { - return; // error already reported - } + otc.getBinaryOpRefinements(operator); } @Override public void visitCtUnaryOperator(CtUnaryOperator operator) { - if (diagnostics.foundError()) { - return; - } - super.visitCtUnaryOperator(operator); - try { - otc.getUnaryOpRefinements(operator); - } catch (ParsingException e) { - return; // error already reported - } + otc.getUnaryOpRefinements(operator); } public void visitCtInvocation(CtInvocation invocation) { - if (diagnostics.foundError()) { - return; - } - super.visitCtInvocation(invocation); mfc.getInvocationRefinements(invocation); } @Override public void visitCtReturn(CtReturn ret) { - if (diagnostics.foundError()) { - return; - } - super.visitCtReturn(ret); mfc.getReturnRefinements(ret); } @Override public void visitCtIf(CtIf ifElement) { - if (diagnostics.foundError()) { - return; - } - CtExpression exp = ifElement.getCondition(); + Predicate expRefs = getExpressionRefinements(exp); - Predicate expRefs; - try { - expRefs = getExpressionRefinements(exp); - } catch (ParsingException e) { - return; // error already reported - } String freshVarName = String.format(Formats.FRESH, context.getCounter()); expRefs = expRefs.substituteVariable(Keys.WILDCARD, freshVarName); Predicate lastExpRefs = substituteAllVariablesForLastInstance(expRefs); @@ -463,28 +362,15 @@ public void visitCtIf(CtIf ifElement) { @Override public void visitCtArrayWrite(CtArrayWrite arrayWrite) { - if (diagnostics.foundError()) { - return; - } - super.visitCtArrayWrite(arrayWrite); CtExpression index = arrayWrite.getIndexExpression(); - BuiltinFunctionPredicate fp; - try { - fp = BuiltinFunctionPredicate.addToIndex(arrayWrite.getTarget().toString(), index.toString(), Keys.WILDCARD, - arrayWrite); - } catch (ParsingException e) { - return; // error already reported - } + BuiltinFunctionPredicate fp = BuiltinFunctionPredicate.addToIndex(arrayWrite.getTarget().toString(), + index.toString(), Keys.WILDCARD, arrayWrite); arrayWrite.putMetadata(Keys.REFINEMENT, fp); } @Override public void visitCtConditional(CtConditional conditional) { - if (diagnostics.foundError()) { - return; - } - super.visitCtConditional(conditional); Predicate cond = getRefinement(conditional.getCondition()); Predicate c = Predicate.createITE(cond, getRefinement(conditional.getThenExpression()), @@ -494,27 +380,19 @@ public void visitCtConditional(CtConditional conditional) { @Override public void visitCtConstructorCall(CtConstructorCall ctConstructorCall) { - if (diagnostics.foundError()) { - return; - } - super.visitCtConstructorCall(ctConstructorCall); mfc.getConstructorInvocationRefinements(ctConstructorCall); } @Override public void visitCtNewClass(CtNewClass newClass) { - if (diagnostics.foundError()) { - return; - } - super.visitCtNewClass(newClass); } // ############################### Inner Visitors // ########################################## private void checkAssignment(String name, CtTypeReference type, CtExpression ex, CtExpression assignment, - CtElement parentElem, CtElement varDecl) { + CtElement parentElem, CtElement varDecl) throws LJError { getPutVariableMetadada(ex, name); Predicate refinementFound = getRefinement(assignment); @@ -531,14 +409,10 @@ private void checkAssignment(String name, CtTypeReference type, CtExpression< r.ifPresent(variableInstance -> vcChecker.removePathVariableThatIncludes(variableInstance.getName())); vcChecker.removePathVariableThatIncludes(name); // AQUI!! - try { - checkVariableRefinements(refinementFound, name, type, parentElem, varDecl); - } catch (ParsingException e) { - return; // error already reported - } + checkVariableRefinements(refinementFound, name, type, parentElem, varDecl); } - private Predicate getExpressionRefinements(CtExpression element) throws ParsingException { + private Predicate getExpressionRefinements(CtExpression element) throws LJError { if (element instanceof CtVariableRead) { // CtVariableRead elemVar = (CtVariableRead) element; return getRefinement(element); diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java index 7b06b3c3..3608f23d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java @@ -1,7 +1,5 @@ package liquidjava.processor.refinement_checker; -import static liquidjava.diagnostics.Diagnostics.diagnostics; - import java.lang.annotation.Annotation; import java.util.Arrays; import java.util.List; @@ -9,6 +7,7 @@ import liquidjava.diagnostics.errors.CustomError; import liquidjava.diagnostics.errors.InvalidRefinementError; +import liquidjava.diagnostics.errors.LJError; import liquidjava.diagnostics.errors.SyntaxError; import liquidjava.processor.context.AliasWrapper; import liquidjava.processor.context.Context; @@ -18,7 +17,6 @@ import liquidjava.processor.facade.AliasDTO; import liquidjava.processor.facade.GhostDTO; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.parsing.ParsingException; import liquidjava.rj_language.parsing.RefinementsParser; import liquidjava.utils.Utils; import liquidjava.utils.constants.Formats; @@ -61,7 +59,7 @@ public Predicate getRefinement(CtElement elem) { return c == null ? new Predicate() : c; } - public Optional getRefinementFromAnnotation(CtElement element) throws ParsingException { + public Optional getRefinementFromAnnotation(CtElement element) throws LJError { Optional constr = Optional.empty(); Optional ref = Optional.empty(); for (CtAnnotation ann : element.getAnnotations()) { @@ -85,19 +83,16 @@ public Optional getRefinementFromAnnotation(CtElement element) throws // check if refinement is valid if (!p.getExpression().isBooleanExpression()) { - diagnostics.add(new InvalidRefinementError(element, "Refinement predicate must be a boolean expression", - ref.get())); + throw new InvalidRefinementError(element, "Refinement predicate must be a boolean expression", + ref.get()); } - if (diagnostics.foundError()) - return Optional.empty(); - constr = Optional.of(p); } return constr; } @SuppressWarnings("unchecked") - public void handleStateSetsFromAnnotation(CtElement element) { + public void handleStateSetsFromAnnotation(CtElement element) throws LJError { int set = 0; for (CtAnnotation ann : element.getAnnotations()) { String an = ann.getActualAnnotation().annotationType().getCanonicalName(); @@ -112,7 +107,7 @@ public void handleStateSetsFromAnnotation(CtElement element) { } } - private void createStateSet(CtNewArray e, int set, CtElement element) { + private void createStateSet(CtNewArray e, int set, CtElement element) throws LJError { // if any of the states starts with uppercase, throw error (reserved for alias) for (CtExpression ce : e.getElements()) { @@ -121,7 +116,7 @@ private void createStateSet(CtNewArray e, int set, CtElement element) { CtLiteral s = (CtLiteral) ce; String f = s.getValue(); if (Character.isUpperCase(f.charAt(0))) { - diagnostics.add(new CustomError("State names must start with lowercase", s)); + throw new CustomError("State names must start with lowercase", s); } } } @@ -157,18 +152,12 @@ private void createStateSet(CtNewArray e, int set, CtElement element) { } } - private void createStateGhost(String string, CtAnnotation ann, String an, CtElement element) { - GhostDTO gd = null; - try { - gd = RefinementsParser.getGhostDeclaration(string); - } catch (ParsingException e) { - diagnostics.add(new CustomError("Could not parse the ghost function", e.getMessage(), ann)); - return; - } + private void createStateGhost(String string, CtAnnotation ann, String an, CtElement element) + throws LJError { + GhostDTO gd = RefinementsParser.getGhostDeclaration(string); if (gd.getParam_types().size() > 0) { - diagnostics.add(new CustomError( - "Ghost States have the class as parameter " + "by default, no other parameters are allowed", ann)); - return; + throw new CustomError( + "Ghost States have the class as parameter " + "by default, no other parameters are allowed", ann); } // Set class as parameter of Ghost String qn = getQualifiedClassName(element); @@ -216,22 +205,16 @@ protected Optional createStateGhost(int order, CtElement element) return Optional.empty(); } - protected void getGhostFunction(String value, CtElement element) { - try { - GhostDTO f = RefinementsParser.getGhostDeclaration(value); - if (f != null && element.getParent() instanceof CtClass) { - CtClass klass = (CtClass) element.getParent(); - GhostFunction gh = new GhostFunction(f, factory, klass.getQualifiedName()); - context.addGhostFunction(gh); - } - } catch (ParsingException e) { - diagnostics.add(new CustomError("Could not parse the ghost function", e.getMessage(), element)); - // e.printStackTrace(); - return; + protected void getGhostFunction(String value, CtElement element) throws LJError { + GhostDTO f = RefinementsParser.getGhostDeclaration(value); + if (f != null && element.getParent() instanceof CtClass) { + CtClass klass = (CtClass) element.getParent(); + GhostFunction gh = new GhostFunction(f, factory, klass.getQualifiedName()); + context.addGhostFunction(gh); } } - protected void handleAlias(String value, CtElement element) { + protected void handleAlias(String value, CtElement element) throws LJError { try { AliasDTO a = RefinementsParser.getAliasDeclaration(value); String klass = null; @@ -247,16 +230,16 @@ protected void handleAlias(String value, CtElement element) { a.parse(path); // refinement alias must return a boolean expression if (a.getExpression() != null && !a.getExpression().isBooleanExpression()) { - diagnostics.add(new InvalidRefinementError(element, - "Refinement alias must return a boolean expression", value)); - return; + throw new InvalidRefinementError(element, "Refinement alias must return a boolean expression", + value); } AliasWrapper aw = new AliasWrapper(a, factory, Keys.WILDCARD, context, klass, path); context.addAlias(aw); } - } catch (ParsingException e) { + } catch (SyntaxError e) { + // add location info to error SourcePosition pos = Utils.getRefinementAnnotationPosition(element, value); - diagnostics.add(new SyntaxError(e.getMessage(), pos, value)); + throw new SyntaxError(e.getMessage(), pos, value); } } @@ -273,7 +256,7 @@ Optional getExternalRefinement(CtInterface intrface) { } public void checkVariableRefinements(Predicate refinementFound, String simpleName, CtTypeReference type, - CtElement usage, CtElement variable) throws ParsingException { + CtElement usage, CtElement variable) throws LJError { Optional expectedType = getRefinementFromAnnotation(variable); Predicate cEt; RefinedVariable mainRV = null; @@ -305,28 +288,31 @@ else if (expectedType.isPresent()) context.addRefinementToVariableInContext(simpleName, type, cet, usage); } - public void checkSMT(Predicate expectedType, CtElement element) { + public void checkSMT(Predicate expectedType, CtElement element) throws LJError { vcChecker.processSubtyping(expectedType, context.getGhostState(), element, factory); element.putMetadata(Keys.REFINEMENT, expectedType); } - public void checkStateSMT(Predicate prevState, Predicate expectedState, CtElement target, String moreInfo) { + public void checkStateSMT(Predicate prevState, Predicate expectedState, CtElement target, String moreInfo) + throws LJError { vcChecker.processSubtyping(prevState, expectedState, context.getGhostState(), target, moreInfo, factory); } - public boolean checksStateSMT(Predicate prevState, Predicate expectedState, SourcePosition p) { + public boolean checksStateSMT(Predicate prevState, Predicate expectedState, SourcePosition p) throws LJError { return vcChecker.canProcessSubtyping(prevState, expectedState, context.getGhostState(), p, factory); } - public void createError(CtElement element, Predicate expectedType, Predicate foundType, String customMessage) { + public void createError(CtElement element, Predicate expectedType, Predicate foundType, String customMessage) + throws LJError { vcChecker.printSubtypingError(element, expectedType, foundType, customMessage); } - public void createSameStateError(CtElement element, Predicate expectedType, String klass) { + public void createSameStateError(CtElement element, Predicate expectedType, String klass) throws LJError { vcChecker.printSameStateError(element, expectedType, klass); } - public void createStateMismatchError(CtElement element, String method, Predicate found, Predicate[] expected) { + public void createStateMismatchError(CtElement element, String method, Predicate found, Predicate[] expected) + throws LJError { vcChecker.printStateMismatchError(element, method, found, expected); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java index b467a757..5bae7e90 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java @@ -1,7 +1,5 @@ package liquidjava.processor.refinement_checker; -import static liquidjava.diagnostics.Diagnostics.diagnostics; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -39,7 +37,8 @@ public VCChecker() { pathVariables = new Stack<>(); } - public void processSubtyping(Predicate expectedType, List list, CtElement element, Factory f) { + public void processSubtyping(Predicate expectedType, List list, CtElement element, Factory f) + throws LJError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(expectedType, lrv, mainVars); if (expectedType.isBooleanTrue()) @@ -53,11 +52,9 @@ public void processSubtyping(Predicate expectedType, List list, CtEl try { List filtered = filterGhostStatesForVariables(list, mainVars, lrv); premises = premisesBeforeChange.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); - et = expectedType.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); } catch (Exception e) { - diagnostics.add(new RefinementError(element, expectedType.getExpression(), premises.simplify(), map)); - return; + throw new RefinementError(element, expectedType.getExpression(), premises.simplify(), map); } try { @@ -69,14 +66,14 @@ public void processSubtyping(Predicate expectedType, List list, CtEl } public void processSubtyping(Predicate type, Predicate expectedType, List list, CtElement element, - String string, Factory f) { + String string, Factory f) throws LJError { boolean b = canProcessSubtyping(type, expectedType, list, element.getPosition(), f); if (!b) printSubtypingError(element, expectedType, type, string); } public boolean canProcessSubtyping(Predicate type, Predicate expectedType, List list, SourcePosition p, - Factory f) { + Factory f) throws LJError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(expectedType, lrv, mainVars); gatherVariables(type, lrv, mainVars); @@ -234,24 +231,22 @@ private void recAuxGetVars(RefinedVariable var, List newVars) { getVariablesFromContext(l, newVars, varName); } - public boolean smtChecks(Predicate found, Predicate expectedType, SourcePosition p) { + public boolean smtChecks(Predicate found, Predicate expectedType, SourcePosition p) throws LJError { try { new SMTEvaluator().verifySubtype(found, expectedType, context, p); } catch (TypeCheckError e) { return false; } catch (Exception e) { String msg = e.getLocalizedMessage().toLowerCase(); - LJError error; if (msg.contains("wrong number of arguments")) { - error = new GhostInvocationError("Wrong number of arguments in ghost invocation", p, + throw new GhostInvocationError("Wrong number of arguments in ghost invocation", p, expectedType.getExpression(), null); } else if (msg.contains("sort mismatch")) { - error = new GhostInvocationError("Type mismatch in arguments of ghost invocation", p, + throw new GhostInvocationError("Type mismatch in arguments of ghost invocation", p, expectedType.getExpression(), null); } else { - error = new CustomError(e.getMessage(), p); + throw new CustomError(e.getMessage(), p); } - diagnostics.add(error); } return true; } @@ -262,9 +257,6 @@ public boolean smtChecks(Predicate found, Predicate expectedType, SourcePosition * * @param cSMT * @param expectedType - * - * @throws TypeCheckError - * @throws Exception * */ private void smtChecking(Predicate cSMT, Predicate expectedType, SourcePosition p) @@ -308,25 +300,25 @@ private TranslationTable createMap(CtElement element, Predicate expectedType) { return map; } - protected void printSubtypingError(CtElement element, Predicate expectedType, Predicate foundType, - String customMsg) { + protected void printSubtypingError(CtElement element, Predicate expectedType, Predicate foundType, String customMsg) + throws LJError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(expectedType, lrv, mainVars); gatherVariables(foundType, lrv, mainVars); TranslationTable map = new TranslationTable(); Predicate premises = joinPredicates(expectedType, mainVars, lrv, map).toConjunctions(); - diagnostics.add(new RefinementError(element, expectedType.getExpression(), premises.simplify(), map)); + throw new RefinementError(element, expectedType.getExpression(), premises.simplify(), map); } - public void printSameStateError(CtElement element, Predicate expectedType, String klass) { + public void printSameStateError(CtElement element, Predicate expectedType, String klass) throws LJError { TranslationTable map = createMap(element, expectedType); - diagnostics.add(new StateConflictError(element, expectedType.getExpression(), klass, map)); + throw new StateConflictError(element, expectedType.getExpression(), klass, map); } private void printError(Exception e, Predicate premisesBeforeChange, Predicate expectedType, CtElement element, - TranslationTable map) { + TranslationTable map) throws LJError { LJError error = mapError(e, premisesBeforeChange, expectedType, element, map); - diagnostics.add(error); + throw error; } private LJError mapError(Exception e, Predicate premisesBeforeChange, Predicate expectedType, CtElement element, @@ -340,13 +332,14 @@ private LJError mapError(Exception e, Predicate premisesBeforeChange, Predicate } } - public void printStateMismatchError(CtElement element, String method, Predicate found, Predicate[] states) { + public void printStateMismatchError(CtElement element, String method, Predicate found, Predicate[] states) + throws LJError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(found, lrv, mainVars); TranslationTable map = new TranslationTable(); VCImplication foundState = joinPredicates(found, mainVars, lrv, map); - diagnostics.add(new StateRefinementError(element, method, + throw new StateRefinementError(element, method, Arrays.stream(states).map(Predicate::getExpression).toArray(Expression[]::new), - foundState.toConjunctions().simplify().getValue(), map)); + foundState.toConjunctions().simplify().getValue(), map); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java index 52fe40dc..1b642ee9 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.Optional; +import liquidjava.diagnostics.errors.LJError; import liquidjava.processor.context.Context; import liquidjava.processor.context.RefinedFunction; import liquidjava.processor.context.RefinedVariable; @@ -18,7 +19,6 @@ import liquidjava.processor.refinement_checker.object_checkers.AuxHierarchyRefinementsPassage; import liquidjava.processor.refinement_checker.object_checkers.AuxStateHandler; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.parsing.ParsingException; import liquidjava.utils.Utils; import spoon.reflect.code.CtConstructorCall; import spoon.reflect.code.CtExpression; @@ -45,7 +45,7 @@ public MethodsFunctionsChecker(TypeChecker rtc) { this.rtc = rtc; } - public void getConstructorRefinements(CtConstructor c) throws ParsingException { + public void getConstructorRefinements(CtConstructor c) throws LJError { RefinedFunction f = new RefinedFunction(); f.setName(c.getSimpleName()); f.setType(c.getType()); @@ -65,7 +65,7 @@ public void getConstructorRefinements(CtConstructor c) throws ParsingExceptio AuxStateHandler.handleConstructorState(c, f, rtc); } - public void getConstructorInvocationRefinements(CtConstructorCall ctConstructorCall) { + public void getConstructorInvocationRefinements(CtConstructorCall ctConstructorCall) throws LJError { CtExecutableReference exe = ctConstructorCall.getExecutable(); if (exe != null) { RefinedFunction f = rtc.getContext().getFunction(exe.getSimpleName(), @@ -80,7 +80,7 @@ public void getConstructorInvocationRefinements(CtConstructorCall ctConstruct } // ################### VISIT METHOD ############################## - public void getMethodRefinements(CtMethod method) throws ParsingException { + public void getMethodRefinements(CtMethod method) throws LJError { RefinedFunction f = new RefinedFunction(); f.setName(method.getSimpleName().replaceAll("\\p{C}", "")); // remove any empty chars from string f.setType(method.getType()); @@ -110,7 +110,7 @@ public void getMethodRefinements(CtMethod method) throws ParsingException AuxHierarchyRefinementsPassage.checkFunctionInSupertypes(klass, method, f, rtc); } - public void getMethodRefinements(CtMethod method, String prefix) throws ParsingException { + public void getMethodRefinements(CtMethod method, String prefix) throws LJError { String constructorName = ""; String k = Utils.getSimpleName(prefix); @@ -134,7 +134,7 @@ public void getMethodRefinements(CtMethod method, String prefix) throws P } } - private void auxGetMethodRefinements(CtMethod method, RefinedFunction rf) throws ParsingException { + private void auxGetMethodRefinements(CtMethod method, RefinedFunction rf) throws LJError { // main cannot have refinement - for now if (method.getSignature().equals("main(java.lang.String[])")) return; @@ -151,11 +151,9 @@ private void auxGetMethodRefinements(CtMethod method, RefinedFunction rf) * @param params * * @return Conjunction of all - * - * @throws ParsingException */ private Predicate handleFunctionRefinements(RefinedFunction f, CtElement method, List> params) - throws ParsingException { + throws LJError { Predicate joint = new Predicate(); for (CtParameter param : params) { @@ -190,7 +188,7 @@ public List> getStateAnnotation(CtElement ele return l; } - public void getReturnRefinements(CtReturn ret) { + public void getReturnRefinements(CtReturn ret) throws LJError { CtClass c = ret.getParent(CtClass.class); String className = c.getSimpleName(); if (ret.getReturnedExpression() != null) { @@ -230,7 +228,7 @@ public void getReturnRefinements(CtReturn ret) { // ############################### VISIT INVOCATION // ################################3 - public void getInvocationRefinements(CtInvocation invocation) { + public void getInvocationRefinements(CtInvocation invocation) throws LJError { CtExecutable method = invocation.getExecutable().getDeclaration(); if (method == null) { @@ -267,7 +265,7 @@ public RefinedFunction getRefinementFunction(String methodName, String className return f; } - private void searchMethodInLibrary(CtExecutableReference ctr, CtInvocation invocation) { + private void searchMethodInLibrary(CtExecutableReference ctr, CtInvocation invocation) throws LJError { CtTypeReference ctref = ctr.getDeclaringType(); if (ctref == null) { // Plan B: get us get the definition from the invocation. @@ -306,7 +304,7 @@ private void searchMethodInLibrary(CtExecutableReference ctr, CtInvocation } private Map checkInvocationRefinements(CtElement invocation, List> arguments, - CtExpression target, String methodName, String className) { + CtExpression target, String methodName, String className) throws LJError { // -- Part 1: Check if the invocation is possible int si = arguments.size(); RefinedFunction f = rtc.getContext().getFunction(methodName, className, si); @@ -393,7 +391,7 @@ private String createVariableRepresentingArgument(CtExpression iArg, Variable } private void checkParameters(CtElement invocation, List> arguments, RefinedFunction f, - Map map) { + Map map) throws LJError { List> invocationParams = arguments; List functionParams = f.getArguments(); for (int i = 0; i < invocationParams.size(); i++) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java index 0a93f1ac..69dd190e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java @@ -3,6 +3,9 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; + +import liquidjava.diagnostics.errors.CustomError; +import liquidjava.diagnostics.errors.LJError; import liquidjava.processor.context.RefinedFunction; import liquidjava.processor.context.RefinedVariable; import liquidjava.processor.context.Variable; @@ -13,7 +16,6 @@ import liquidjava.utils.constants.Ops; import liquidjava.utils.constants.Types; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.parsing.ParsingException; import org.apache.commons.lang3.NotImplementedException; import spoon.reflect.code.BinaryOperatorKind; import spoon.reflect.code.CtAssignment; @@ -56,10 +58,8 @@ public OperationsChecker(TypeChecker rtc) { * * @param * @param operator - * - * @throws ParsingException */ - public void getBinaryOpRefinements(CtBinaryOperator operator) throws ParsingException { + public void getBinaryOpRefinements(CtBinaryOperator operator) throws LJError { CtExpression right = operator.getRightHandOperand(); CtExpression left = operator.getLeftHandOperand(); Predicate oper; @@ -103,11 +103,9 @@ public void getBinaryOpRefinements(CtBinaryOperator operator) throws Pars * * @param * @param operator - * - * @throws ParsingException */ @SuppressWarnings({ "unchecked" }) - public void getUnaryOpRefinements(CtUnaryOperator operator) throws ParsingException { + public void getUnaryOpRefinements(CtUnaryOperator operator) throws LJError { CtExpression ex = (CtExpression) operator.getOperand(); String name = Formats.FRESH; Predicate all; @@ -165,11 +163,8 @@ public void getUnaryOpRefinements(CtUnaryOperator operator) throws Parsin * @param element * * @return String with the operation refinements - * - * @throws ParsingException */ - private Predicate getOperationRefinements(CtBinaryOperator operator, CtExpression element) - throws ParsingException { + private Predicate getOperationRefinements(CtBinaryOperator operator, CtExpression element) throws LJError { return getOperationRefinements(operator, null, element); } @@ -184,11 +179,9 @@ private Predicate getOperationRefinements(CtBinaryOperator operator, CtExpres * CtExpression that represent an Binary Operation or one of the operands * * @return Predicate with the operation refinements - * - * @throws ParsingException */ private Predicate getOperationRefinements(CtBinaryOperator operator, CtVariableWrite parentVar, - CtExpression element) throws ParsingException { + CtExpression element) throws LJError { if (element instanceof CtFieldRead) { CtFieldRead field = ((CtFieldRead) element); if (field.getVariable().getSimpleName().equals("length")) { @@ -246,7 +239,7 @@ private Predicate getOperationRefinements(CtBinaryOperator operator, CtVariab return new Predicate(); } if (l.getValue() == null) - throw new ParsingException("Null literals are not supported"); + throw new CustomError("Null literals are not supported"); return new Predicate(l.getValue().toString(), element); @@ -273,7 +266,7 @@ private Predicate getOperationRefinements(CtBinaryOperator operator, CtVariab } private Predicate getOperationRefinementFromExternalLib(CtInvocation inv, CtBinaryOperator operator) - throws ParsingException { + throws LJError { CtExpression t = inv.getTarget(); if (t instanceof CtVariableRead) { @@ -316,11 +309,9 @@ private Predicate getOperationRefinementFromExternalLib(CtInvocation inv, CtB * @param name * * @return String with the refinements - * - * @throws ParsingException */ private Predicate getRefinementUnaryVariableWrite(CtExpression ex, CtUnaryOperator operator, - CtVariableWrite w, String name) throws ParsingException { + CtVariableWrite w, String name) throws LJError { String newName = String.format(Formats.INSTANCE, name, rtc.getContext().getCounter()); CtVariable varDecl = w.getVariable().getDeclaration(); @@ -363,7 +354,7 @@ private String getOperatorFromKind(BinaryOperatorKind kind) { }; } - private Predicate getOperatorFromKind(UnaryOperatorKind kind, CtElement elem) throws ParsingException { + private Predicate getOperatorFromKind(UnaryOperatorKind kind, CtElement elem) throws LJError { String ret = switch (kind) { case POSTINC -> Keys.WILDCARD + " + 1"; case POSTDEC -> Keys.WILDCARD + " - 1"; @@ -373,7 +364,7 @@ private Predicate getOperatorFromKind(UnaryOperatorKind kind, CtElement elem) th case NOT -> "!" + Keys.WILDCARD; case POS -> "0 + " + Keys.WILDCARD; case NEG -> "-" + Keys.WILDCARD; - default -> throw new ParsingException(kind + "operation not supported"); + default -> throw new CustomError(kind + "operation not supported"); }; return new Predicate(ret, elem); }; diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java index fc92e29a..a1b5ae84 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java @@ -4,6 +4,8 @@ import java.util.List; import java.util.Optional; import java.util.stream.Collectors; + +import liquidjava.diagnostics.errors.LJError; import liquidjava.processor.context.ObjectState; import liquidjava.processor.context.RefinedFunction; import liquidjava.processor.context.RefinedVariable; @@ -21,7 +23,7 @@ public class AuxHierarchyRefinementsPassage { public static void checkFunctionInSupertypes(CtClass klass, CtMethod method, RefinedFunction f, - TypeChecker tc) { + TypeChecker tc) throws LJError { String name = method.getSimpleName(); int size = method.getParameters().size(); if (klass.getSuperInterfaces().size() > 0) { // implemented interfaces @@ -40,7 +42,7 @@ public static void checkFunctionInSupertypes(CtClass klass, CtMethod m } static void transferRefinements(RefinedFunction superFunction, RefinedFunction function, CtMethod method, - TypeChecker tc) { + TypeChecker tc) throws LJError { HashMap super2function = getParametersMap(superFunction, function, tc, method); transferReturnRefinement(superFunction, function, method, tc, super2function); transferArgumentsRefinements(superFunction, function, method, tc, super2function); @@ -66,7 +68,7 @@ private static HashMap getParametersMap(RefinedFunction superFun } static void transferArgumentsRefinements(RefinedFunction superFunction, RefinedFunction function, - CtMethod method, TypeChecker tc, HashMap super2function) { + CtMethod method, TypeChecker tc, HashMap super2function) throws LJError { List superArgs = superFunction.getArguments(); List args = function.getArguments(); List> params = method.getParameters(); @@ -90,7 +92,7 @@ static void transferArgumentsRefinements(RefinedFunction superFunction, RefinedF } static void transferReturnRefinement(RefinedFunction superFunction, RefinedFunction function, CtMethod method, - TypeChecker tc, HashMap super2function) { + TypeChecker tc, HashMap super2function) throws LJError { Predicate functionRef = function.getRefinement(); Predicate superRef = superFunction.getRefinement(); if (functionRef.isBooleanTrue()) @@ -125,7 +127,7 @@ static Optional functionInInterface(CtClass klass, String si } private static void transferStateRefinements(RefinedFunction superFunction, RefinedFunction subFunction, - CtMethod method, TypeChecker tc) { + CtMethod method, TypeChecker tc) throws LJError { if (superFunction.hasStateChange()) { if (!subFunction.hasStateChange()) { for (ObjectState o : superFunction.getAllStates()) diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java index 9ec34087..6d6de6d1 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java @@ -1,19 +1,16 @@ package liquidjava.processor.refinement_checker.object_checkers; -import static liquidjava.diagnostics.Diagnostics.diagnostics; - import java.lang.annotation.Annotation; import java.util.*; import java.util.stream.Collectors; -import liquidjava.diagnostics.errors.CustomError; import liquidjava.diagnostics.errors.IllegalConstructorTransitionError; import liquidjava.diagnostics.errors.InvalidRefinementError; +import liquidjava.diagnostics.errors.LJError; import liquidjava.processor.context.*; import liquidjava.processor.refinement_checker.TypeChecker; import liquidjava.processor.refinement_checker.TypeCheckingUtils; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.parsing.ParsingException; import liquidjava.utils.Utils; import liquidjava.utils.constants.Formats; import liquidjava.utils.constants.Keys; @@ -32,20 +29,16 @@ public class AuxStateHandler { * @param c * @param f * @param tc - * - * @throws ParsingException */ @SuppressWarnings({ "unchecked", "rawtypes" }) - public static void handleConstructorState(CtConstructor c, RefinedFunction f, TypeChecker tc) - throws ParsingException { + public static void handleConstructorState(CtConstructor c, RefinedFunction f, TypeChecker tc) throws LJError { List> an = getStateAnnotation(c); if (!an.isEmpty()) { for (CtAnnotation a : an) { Map m = a.getAllValues(); CtLiteral from = (CtLiteral) m.get("from"); if (from != null) { - diagnostics.add(new IllegalConstructorTransitionError(from)); - return; + throw new IllegalConstructorTransitionError(from); } } setConstructorStates(f, an, tc, c); // f.setState(an, context.getGhosts(), c); @@ -61,12 +54,10 @@ public static void handleConstructorState(CtConstructor c, RefinedFunction f, * @param anns * @param tc * @param element - * - * @throws ParsingException */ @SuppressWarnings({ "rawtypes" }) private static void setConstructorStates(RefinedFunction f, List> anns, - TypeChecker tc, CtElement element) throws ParsingException { + TypeChecker tc, CtElement element) throws LJError { List l = new ArrayList<>(); for (CtAnnotation an : anns) { Map m = an.getAllValues(); @@ -75,9 +66,8 @@ private static void setConstructorStates(RefinedFunction f, List getDifferentSets(TypeChecker tc, String klassQualified) { List sets = new ArrayList<>(); List l = getGhostStatesFor(klassQualified, tc); @@ -130,11 +134,9 @@ private static List getDifferentSets(TypeChecker tc, String klass * @param method * @param f * @param tc - * - * @throws ParsingException */ public static void handleMethodState(CtMethod method, RefinedFunction f, TypeChecker tc, String prefix) - throws ParsingException { + throws LJError { List> an = getStateAnnotation(method); if (!an.isEmpty()) { setFunctionStates(f, an, tc, method, prefix); @@ -150,11 +152,9 @@ public static void handleMethodState(CtMethod method, RefinedFunction f, Type * @param anns * @param tc * @param element - * - * @throws ParsingException */ private static void setFunctionStates(RefinedFunction f, List> anns, - TypeChecker tc, CtElement element, String prefix) throws ParsingException { + TypeChecker tc, CtElement element, String prefix) throws LJError { List l = new ArrayList<>(); for (CtAnnotation an : anns) { l.add(getStates(an, f, tc, element, prefix)); @@ -164,7 +164,7 @@ private static void setFunctionStates(RefinedFunction f, List ctAnnotation, RefinedFunction f, - TypeChecker tc, CtElement e, String prefix) throws ParsingException { + TypeChecker tc, CtElement e, String prefix) throws LJError { Map m = ctAnnotation.getAllValues(); String from = TypeCheckingUtils.getStringFromAnnotation(m.get("from")); String to = TypeCheckingUtils.getStringFromAnnotation(m.get("to")); @@ -189,13 +189,23 @@ private static ObjectState getStates(CtAnnotation ctAnnota return state; } + /** + * Creates the predicate for state transition + * + * @param value + * @param targetClass + * @param tc + * @param e + * @param isTo + * @param prefix + * + * @return the created predicate + */ private static Predicate createStatePredicate(String value, /* RefinedFunction f */ String targetClass, - TypeChecker tc, CtElement e, boolean isTo, String prefix) throws ParsingException { + TypeChecker tc, CtElement e, boolean isTo, String prefix) throws LJError { Predicate p = new Predicate(value, e, prefix); if (!p.getExpression().isBooleanExpression()) { - diagnostics.add( - new InvalidRefinementError(e, "State refinement transition must be a boolean expression", value)); - return new Predicate(); + throw new InvalidRefinementError(e, "State refinement transition must be a boolean expression", value); } CtTypeReference r = tc.getFactory().Type().createReference(targetClass); String nameOld = String.format(Formats.INSTANCE, Keys.THIS, tc.getContext().getCounter()); @@ -214,6 +224,15 @@ private static Predicate createStatePredicate(String value, /* RefinedFunction f return c1; } + /** + * Gets the missing states in the predicate and adds equalities to old states + * + * @param t + * @param tc + * @param p + * + * @return the updated predicate + */ private static Predicate getMissingStates(String t, TypeChecker tc, Predicate p) { List gs = p.getStateInvocations(getGhostStatesFor(t, tc)); List sets = getDifferentSets(tc, t); @@ -228,7 +247,12 @@ private static Predicate getMissingStates(String t, TypeChecker tc, Predicate p) } /** - * Collect ghost states for the given qualified class name and its immediate supertypes (superclass and interfaces). + * Collect ghost states for the given qualified class name and its immediate supertypes (superclass and interfaces) + * + * @param qualifiedClass + * @param tc + * + * @return list of ghost states */ private static List getGhostStatesFor(String qualifiedClass, TypeChecker tc) { // Keep order: class, then superclass, then interfaces; avoid duplicates @@ -258,13 +282,13 @@ private static List getGhostStatesFor(String qualifiedClass, TypeChe /** * Create predicate with the equalities with previous versions of the object e.g., ghostfunction1(this) == * ghostfunction1(old(this)) - * + * * @param p * @param th * @param sets * @param tc * - * @return + * @return updated predicate */ private static Predicate addOldStates(Predicate p, Predicate th, List sets, TypeChecker tc) { Predicate c = p; @@ -333,7 +357,7 @@ public static void addStateRefinements(TypeChecker tc, String varName, CtExpress * @param invocation */ public static void checkTargetChanges(TypeChecker tc, RefinedFunction f, CtExpression target2, - Map map, CtElement invocation) { + Map map, CtElement invocation) throws LJError { String parentTargetName = searchFistVariableTarget(tc, target2, invocation); VariableInstance target = getTarget(tc, invocation); if (target != null) { @@ -346,7 +370,13 @@ public static void checkTargetChanges(TypeChecker tc, RefinedFunction f, CtExpre } } - public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) { + /** + * Updates the ghost field after a write + * + * @param fw + * @param tc + */ + public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) throws LJError { CtField field = fw.getVariable().getDeclaration(); String updatedVarName = String.format(Formats.THIS, fw.getVariable().getSimpleName()); String targetClass = field.getDeclaringType().getQualifiedName(); @@ -379,18 +409,11 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) { // ObjectState stateChange = getStates(ann, rf, tc, transitionMethod); ObjectState stateChange = new ObjectState(); - try { - String prefix = field.getDeclaringType().getQualifiedName(); - Predicate fromPredicate = createStatePredicate(stateChangeRefinementFrom, targetClass, tc, fw, false, - prefix); - Predicate toPredicate = createStatePredicate(stateChangeRefinementTo, targetClass, tc, fw, true, prefix); - stateChange.setFrom(fromPredicate); - stateChange.setTo(toPredicate); - } catch (ParsingException e) { - String message = String.format("Parsing error while constructing assignment update for `%s`", fw); - diagnostics.add(new CustomError(message, e.getMessage(), field)); - return; - } + String prefix = field.getDeclaringType().getQualifiedName(); + Predicate fromPredicate = createStatePredicate(stateChangeRefinementFrom, targetClass, tc, fw, false, prefix); + Predicate toPredicate = createStatePredicate(stateChangeRefinementTo, targetClass, tc, fw, true, prefix); + stateChange.setFrom(fromPredicate); + stateChange.setTo(toPredicate); // replace "state(this)" to "state(whatever method is called from) and so on" Predicate expectState = stateChange.getFrom().substituteVariable(Keys.THIS, instanceName) @@ -440,7 +463,7 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) { */ private static Predicate changeState(TypeChecker tc, VariableInstance vi, /* RefinedFunction f */ List stateChanges, String name, Map map, - CtElement invocation) { + CtElement invocation) throws LJError { if (vi.getRefinement() == null) { return new Predicate(); } @@ -508,7 +531,7 @@ private static Predicate checkOldMentions(Predicate transitionedState, String in * @return */ private static Predicate sameState(TypeChecker tc, VariableInstance variableInstance, String name, - CtElement invocation) { + CtElement invocation) throws LJError { if (variableInstance.getRefinement() != null) { String newInstanceName = String.format(Formats.INSTANCE, name, tc.getContext().getCounter()); Predicate c = variableInstance.getRefinement().substituteVariable(Keys.WILDCARD, newInstanceName) @@ -533,7 +556,7 @@ private static Predicate sameState(TypeChecker tc, VariableInstance variableInst * @return */ private static String addInstanceWithState(TypeChecker tc, String superName, String name2, - VariableInstance prevInstance, Predicate transitionedState, CtElement invocation) { + VariableInstance prevInstance, Predicate transitionedState, CtElement invocation) throws LJError { VariableInstance vi2 = (VariableInstance) tc.getContext().addInstanceToContext(name2, prevInstance.getType(), prevInstance.getRefinement(), invocation); // vi2.setState(transitionedState); @@ -564,7 +587,7 @@ private static String addInstanceWithState(TypeChecker tc, String superName, Str * * @param invocation * - * @return + * @return the name of the parent target */ static String searchFistVariableTarget(TypeChecker tc, CtElement target2, CtElement invocation) { if (target2 instanceof CtVariableRead) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/BuiltinFunctionPredicate.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/BuiltinFunctionPredicate.java index 61fe26e8..be9db207 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/BuiltinFunctionPredicate.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/BuiltinFunctionPredicate.java @@ -1,20 +1,20 @@ package liquidjava.rj_language; -import liquidjava.rj_language.parsing.ParsingException; +import liquidjava.diagnostics.errors.LJError; import spoon.reflect.declaration.CtElement; public class BuiltinFunctionPredicate extends Predicate { - public BuiltinFunctionPredicate(CtElement elem, String functionName, String... params) throws ParsingException { + public BuiltinFunctionPredicate(CtElement elem, String functionName, String... params) throws LJError { super(functionName + "(" + getFormattedParams(params) + ")", elem); } - public static BuiltinFunctionPredicate length(String param, CtElement elem) throws ParsingException { + public static BuiltinFunctionPredicate length(String param, CtElement elem) throws LJError { return new BuiltinFunctionPredicate(elem, "length", param); } public static BuiltinFunctionPredicate addToIndex(String array, String index, String value, CtElement elem) - throws ParsingException { + throws LJError { return new BuiltinFunctionPredicate(elem, "addToIndex", index, value); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java index 7410b7bb..6d10d4ec 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java @@ -1,13 +1,12 @@ package liquidjava.rj_language; -import static liquidjava.diagnostics.Diagnostics.diagnostics; - import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; +import liquidjava.diagnostics.errors.LJError; import liquidjava.diagnostics.errors.SyntaxError; import liquidjava.processor.context.AliasWrapper; import liquidjava.processor.context.Context; @@ -25,7 +24,6 @@ import liquidjava.rj_language.ast.Var; import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; import liquidjava.rj_language.opt.ExpressionSimplifier; -import liquidjava.rj_language.parsing.ParsingException; import liquidjava.rj_language.parsing.RefinementsParser; import liquidjava.utils.Utils; import liquidjava.utils.constants.Keys; @@ -57,10 +55,8 @@ public Predicate() { * @param ref * @param element * @param e - * - * @throws ParsingException */ - public Predicate(String ref, CtElement element) throws ParsingException { + public Predicate(String ref, CtElement element) throws LJError { this(ref, element, element.getParent(CtType.class).getQualifiedName()); } @@ -71,10 +67,8 @@ public Predicate(String ref, CtElement element) throws ParsingException { * @param element * @param e * @param prefix - * - * @throws ParsingException */ - public Predicate(String ref, CtElement element, String prefix) throws ParsingException { + public Predicate(String ref, CtElement element, String prefix) throws LJError { this.prefix = prefix; exp = parse(ref, element); if (!(exp instanceof GroupExpression)) { @@ -87,23 +81,18 @@ public Predicate(Expression e) { exp = e; } - protected Expression parse(String ref, CtElement element) throws ParsingException { + protected Expression parse(String ref, CtElement element) throws LJError { try { return RefinementsParser.createAST(ref, prefix); - } catch (ParsingException e) { + } catch (SyntaxError e) { + // add location info to error SourcePosition pos = Utils.getRefinementAnnotationPosition(element, ref); - diagnostics.add(new SyntaxError(e.getMessage(), pos, ref)); - throw e; + throw new SyntaxError(e.getMessage(), pos, ref); } } - protected Expression innerParse(String ref, String prefix) { - try { - return RefinementsParser.createAST(ref, prefix); - } catch (ParsingException e1) { - diagnostics.add(new SyntaxError(e1.getMessage(), ref)); - return null; - } + protected Expression innerParse(String ref, String prefix) throws LJError { + return RefinementsParser.createAST(ref, prefix); } public Predicate changeAliasToRefinement(Context context, Factory f) throws Exception { @@ -184,7 +173,7 @@ private void expressionGetOldVariableNames(Expression exp, List ls) { } } - public Predicate changeStatesToRefinements(List ghostState, String[] toChange) { + public Predicate changeStatesToRefinements(List ghostState, String[] toChange) throws LJError { Map nameRefinementMap = new HashMap<>(); for (GhostState gs : ghostState) { if (gs.getRefinement() != null) { // is a state and not a ghost state diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/ParsingException.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/ParsingException.java deleted file mode 100644 index 7eae1e71..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/ParsingException.java +++ /dev/null @@ -1,8 +0,0 @@ -package liquidjava.rj_language.parsing; - -public class ParsingException extends Exception { - - public ParsingException(String errorMessage) { - super(errorMessage); - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/RefinementsParser.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/RefinementsParser.java index 572d5e71..2fd8e89f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/RefinementsParser.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/RefinementsParser.java @@ -1,6 +1,9 @@ package liquidjava.rj_language.parsing; import java.util.Optional; + +import liquidjava.diagnostics.errors.LJError; +import liquidjava.diagnostics.errors.SyntaxError; import liquidjava.processor.facade.AliasDTO; import liquidjava.processor.facade.GhostDTO; import liquidjava.rj_language.ast.Expression; @@ -17,7 +20,7 @@ public class RefinementsParser { - public static Expression createAST(String toParse, String prefix) throws ParsingException { + public static Expression createAST(String toParse, String prefix) throws LJError { ParseTree pt = compile(toParse); CreateASTVisitor visitor = new CreateASTVisitor(prefix); return visitor.create(pt); @@ -25,25 +28,21 @@ public static Expression createAST(String toParse, String prefix) throws Parsing /** * The triple information of the ghost declaration in the order > - * + * * @param s - * - * @return - * - * @throws ParsingException */ - public static GhostDTO getGhostDeclaration(String s) throws ParsingException { + public static GhostDTO getGhostDeclaration(String s) throws LJError { ParseTree rc = compile(s); GhostDTO g = GhostVisitor.getGhostDecl(rc); if (g == null) - throw new ParsingException("Ghost declarations should be in format ()"); + throw new SyntaxError("Ghost declarations should be in format ()", s); return g; } - public static AliasDTO getAliasDeclaration(String s) throws ParsingException { + public static AliasDTO getAliasDeclaration(String s) throws LJError { Optional os = getErrors(s); if (os.isPresent()) - throw new ParsingException(os.get()); + throw new SyntaxError(os.get(), s); CodePointCharStream input; input = CharStreams.fromString(s); RJErrorListener err = new RJErrorListener(); @@ -61,14 +60,14 @@ public static AliasDTO getAliasDeclaration(String s) throws ParsingException { AliasVisitor av = new AliasVisitor(input); AliasDTO alias = av.getAlias(rc); if (alias == null) - throw new ParsingException("Alias definitions should be in format () { }"); + throw new SyntaxError("Alias definitions should be in format () { }", s); return alias; } - private static ParseTree compile(String toParse) throws ParsingException { + private static ParseTree compile(String toParse) throws LJError { Optional s = getErrors(toParse); if (s.isPresent()) - throw new ParsingException(s.get()); + throw new SyntaxError(s.get(), toParse); CodePointCharStream input = CharStreams.fromString(toParse); RJErrorListener err = new RJErrorListener(); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/AliasVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/AliasVisitor.java index de155282..fe0c7d2d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/AliasVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/AliasVisitor.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.stream.Collectors; import liquidjava.processor.facade.AliasDTO; -import liquidjava.rj_language.parsing.ParsingException; import liquidjava.utils.Pair; import org.antlr.v4.runtime.CodePointCharStream; import org.antlr.v4.runtime.TokenStreamRewriter; @@ -28,8 +27,6 @@ public AliasVisitor(CodePointCharStream input) { * @param rc * * @return - * - * @throws ParsingException */ public AliasDTO getAlias(ParseTree rc) { if (rc instanceof AliasContext) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java index bef9525d..7eca4a33 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java @@ -2,6 +2,9 @@ import java.util.ArrayList; import java.util.List; + +import liquidjava.diagnostics.errors.LJError; +import liquidjava.diagnostics.errors.SyntaxError; import liquidjava.rj_language.ast.AliasInvocation; import liquidjava.rj_language.ast.BinaryExpression; import liquidjava.rj_language.ast.Expression; @@ -14,7 +17,6 @@ import liquidjava.rj_language.ast.LiteralString; import liquidjava.rj_language.ast.UnaryExpression; import liquidjava.rj_language.ast.Var; -import liquidjava.rj_language.parsing.ParsingException; import liquidjava.utils.Utils; import org.antlr.v4.runtime.tree.ParseTree; @@ -64,7 +66,7 @@ public CreateASTVisitor(String prefix) { this.prefix = prefix; } - public Expression create(ParseTree rc) throws ParsingException { + public Expression create(ParseTree rc) throws LJError { if (rc instanceof ProgContext) return progCreate((ProgContext) rc); else if (rc instanceof StartContext) @@ -85,20 +87,20 @@ else if (rc instanceof LiteralContext) return null; } - private Expression progCreate(ProgContext rc) throws ParsingException { + private Expression progCreate(ProgContext rc) throws LJError { if (rc.start() != null) return create(rc.start()); return null; } - private Expression startCreate(ParseTree rc) throws ParsingException { + private Expression startCreate(ParseTree rc) throws LJError { if (rc instanceof StartPredContext) return create(((StartPredContext) rc).pred()); // alias and ghost do not have evaluation return null; } - private Expression predCreate(ParseTree rc) throws ParsingException { + private Expression predCreate(ParseTree rc) throws LJError { if (rc instanceof PredGroupContext) return new GroupExpression(create(((PredGroupContext) rc).pred())); else if (rc instanceof PredNegateContext) @@ -113,7 +115,7 @@ else if (rc instanceof IteContext) return create(((PredExpContext) rc).exp()); } - private Expression expCreate(ParseTree rc) throws ParsingException { + private Expression expCreate(ParseTree rc) throws LJError { if (rc instanceof ExpGroupContext) return new GroupExpression(create(((ExpGroupContext) rc).exp())); else if (rc instanceof ExpBoolContext) { @@ -125,7 +127,7 @@ else if (rc instanceof ExpBoolContext) { } } - private Expression operandCreate(ParseTree rc) throws ParsingException { + private Expression operandCreate(ParseTree rc) throws LJError { if (rc instanceof OpLiteralContext) return create(((OpLiteralContext) rc).literalExpression()); else if (rc instanceof OpArithContext) @@ -144,7 +146,7 @@ else if (rc instanceof OpGroupContext) return null; } - private Expression literalExpressionCreate(ParseTree rc) throws ParsingException { + private Expression literalExpressionCreate(ParseTree rc) throws LJError { if (rc instanceof LitGroupContext) return new GroupExpression(create(((LitGroupContext) rc).literalExpression())); else if (rc instanceof LitContext) @@ -159,24 +161,26 @@ else if (rc instanceof VarContext) { } } - private Expression functionCallCreate(FunctionCallContext rc) throws ParsingException { + private Expression functionCallCreate(FunctionCallContext rc) throws LJError { if (rc.ghostCall() != null) { GhostCallContext gc = rc.ghostCall(); - String name = Utils.qualifyName(prefix, gc.ID().getText()); + String ref = gc.ID().getText(); + String name = Utils.qualifyName(prefix, ref); List args = getArgs(gc.args()); if (args.isEmpty()) - throw new ParsingException("Ghost call cannot have empty arguments"); + throw new SyntaxError("Ghost call cannot have empty arguments", ref); return new FunctionInvocation(name, args); } else { AliasCallContext gc = rc.aliasCall(); + String ref = gc.ID_UPPER().getText(); List args = getArgs(gc.args()); if (args.isEmpty()) - throw new ParsingException("Alias call cannot have empty arguments"); - return new AliasInvocation(gc.ID_UPPER().getText(), args); + throw new SyntaxError("Alias call cannot have empty arguments", ref); + return new AliasInvocation(ref, args); } } - private List getArgs(ArgsContext args) throws ParsingException { + private List getArgs(ArgsContext args) throws LJError { List le = new ArrayList<>(); if (args != null) for (PredContext oc : args.pred()) { @@ -185,7 +189,7 @@ private List getArgs(ArgsContext args) throws ParsingException { return le; } - private Expression literalCreate(LiteralContext literalContext) throws ParsingException { + private Expression literalCreate(LiteralContext literalContext) throws LJError { if (literalContext.BOOL() != null) return new LiteralBoolean(literalContext.BOOL().getText()); else if (literalContext.STRING() != null) diff --git a/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java b/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java index b7f6d218..f0822bef 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java +++ b/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java @@ -1,6 +1,5 @@ package liquidjava.api.tests; -import static liquidjava.diagnostics.Diagnostics.diagnostics; import static org.junit.Assert.fail; import java.io.IOException; @@ -9,6 +8,7 @@ import java.nio.file.Paths; import java.util.stream.Stream; import liquidjava.api.CommandLineLauncher; +import liquidjava.diagnostics.Diagnostics; import org.junit.Test; import org.junit.jupiter.params.ParameterizedTest; @@ -16,6 +16,8 @@ public class TestExamples { + Diagnostics diagnostics = Diagnostics.getInstance(); + /** * Test the file at the given path by launching the verifier and checking for errors. The file/directory is expected * to be either correct or contain an error based on its name. diff --git a/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestMultiple.java b/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestMultiple.java new file mode 100644 index 00000000..6e28cc67 --- /dev/null +++ b/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestMultiple.java @@ -0,0 +1,45 @@ +package liquidjava.api.tests; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.Test; + +import liquidjava.api.CommandLineLauncher; +import liquidjava.diagnostics.Diagnostics; + +public class TestMultiple { + + @Test + public void testMultipleErrorDirectory() { + String path = "../liquidjava-example/src/main/java/testMultiple/errors"; + CommandLineLauncher.launch(path); + Diagnostics diagnostics = Diagnostics.getInstance(); + assertEquals(9, diagnostics.getErrors().size()); + } + + @Test + public void testMultipleWarningDirectory() { + String path = "../liquidjava-example/src/main/java/testMultiple/warnings"; + CommandLineLauncher.launch(path); + Diagnostics diagnostics = Diagnostics.getInstance(); + assertEquals(3, diagnostics.getWarnings().size()); + } + + @Test + public void testMultipleErrorsSameFile() { + String path = "../liquidjava-example/src/main/java/testMultiple/MultipleErrorsExample.java"; + CommandLineLauncher.launch(path); + Diagnostics diagnostics = Diagnostics.getInstance(); + assertEquals(2, diagnostics.getErrors().size()); + } + + @Test + public void testMultipleDirectory() { + String path = "../liquidjava-example/src/main/java/testMultiple"; + CommandLineLauncher.launch(path); + Diagnostics diagnostics = Diagnostics.getInstance(); + + assertEquals(11, diagnostics.getErrors().size()); + assertEquals(3, diagnostics.getWarnings().size()); + } +} \ No newline at end of file From 308ad8108300a00a81af8dd426fe6b2d7b6a5916 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Mon, 17 Nov 2025 11:15:20 +0000 Subject: [PATCH 07/25] Cleanup (#122) --- .../liquidjava/api/CommandLineLauncher.java | 20 ++- .../java/liquidjava/diagnostics/Colors.java | 3 - .../liquidjava/diagnostics/Diagnostics.java | 4 +- .../liquidjava/diagnostics/ErrorPosition.java | 50 +------- .../liquidjava/diagnostics/LJDiagnostic.java | 27 ++-- .../errors/GhostInvocationError.java | 2 +- .../errors/InvalidRefinementError.java | 2 +- .../diagnostics/errors/LJError.java | 2 +- .../diagnostics/errors/RefinementError.java | 4 +- .../errors/StateConflictError.java | 4 +- .../diagnostics/errors/SyntaxError.java | 2 +- .../ExternalClassNotFoundWarning.java | 2 +- .../ExternalMethodNotFoundWarning.java | 4 +- .../processor/TestInsideClasses.java | 27 ---- .../liquidjava/processor/VCImplication.java | 2 +- .../ann_generation/FieldGhostsGeneration.java | 8 +- .../processor/context/AliasWrapper.java | 81 +----------- .../liquidjava/processor/context/Context.java | 66 +--------- .../processor/context/GhostFunction.java | 48 ++++---- .../processor/context/GhostParentState.java | 28 ----- .../processor/context/GhostState.java | 4 +- .../processor/context/PlacementInCode.java | 29 ++--- .../liquidjava/processor/context/Refined.java | 9 +- .../processor/context/RefinedFunction.java | 43 ++----- .../processor/context/RefinedVariable.java | 11 +- .../processor/context/Variable.java | 26 +--- .../processor/context/VariableInstance.java | 10 -- .../liquidjava/processor/facade/AliasDTO.java | 9 +- .../liquidjava/processor/facade/GhostDTO.java | 25 +--- .../ExternalRefinementTypeChecker.java | 9 +- .../RefinementTypeChecker.java | 31 ++--- .../refinement_checker/TypeChecker.java | 52 ++++---- .../refinement_checker/TypeCheckingUtils.java | 6 +- .../refinement_checker/VCChecker.java | 72 +++-------- .../MethodsFunctionsChecker.java | 80 +++--------- .../general_checkers/OperationsChecker.java | 82 +++++-------- .../AuxHierarchyRefinementsPassage.java | 37 +++--- .../object_checkers/AuxStateHandler.java | 116 +++++++----------- .../rj_language/BuiltinFunctionPredicate.java | 3 +- .../liquidjava/rj_language/Predicate.java | 43 +------ .../rj_language/ast/AliasInvocation.java | 11 +- .../rj_language/ast/BinaryExpression.java | 11 +- .../rj_language/ast/Expression.java | 28 ++--- .../rj_language/ast/FunctionInvocation.java | 11 +- .../rj_language/ast/GroupExpression.java | 9 +- .../java/liquidjava/rj_language/ast/Ite.java | 9 +- .../rj_language/ast/LiteralBoolean.java | 5 +- .../rj_language/ast/LiteralInt.java | 8 +- .../rj_language/ast/LiteralReal.java | 12 +- .../rj_language/ast/LiteralString.java | 12 +- .../rj_language/ast/UnaryExpression.java | 11 +- .../java/liquidjava/rj_language/ast/Var.java | 11 +- .../rj_language/ast/typing/TypeInfer.java | 20 ++- .../rj_language/opt/ConstantFolding.java | 15 +-- .../rj_language/opt/ConstantPropagation.java | 9 +- .../rj_language/opt/ExpressionSimplifier.java | 12 +- .../rj_language/opt/VariableResolver.java | 6 +- .../derivation_node/ValDerivationNode.java | 2 +- .../rj_language/parsing/RJErrorListener.java | 10 +- .../rj_language/visitors/AliasVisitor.java | 11 +- .../visitors/CreateASTVisitor.java | 2 +- .../rj_language/visitors/GhostVisitor.java | 10 +- .../liquidjava/smt/ExpressionToZ3Visitor.java | 8 +- .../java/liquidjava/smt/SMTEvaluator.java | 11 +- .../liquidjava/smt/TranslatorContextToZ3.java | 12 +- .../java/liquidjava/smt/TranslatorToZ3.java | 46 +++---- .../src/main/java/liquidjava/utils/Pair.java | 21 +--- .../src/main/java/liquidjava/utils/Utils.java | 6 +- .../liquidjava/utils/constants/Patterns.java | 8 -- 69 files changed, 395 insertions(+), 1035 deletions(-) delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/processor/TestInsideClasses.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/processor/context/GhostParentState.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/utils/constants/Patterns.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java index 08162fe8..6a066f7b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java +++ b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java @@ -30,7 +30,9 @@ public static void main(String[] args) { launch(paths.toArray(new String[0])); // print diagnostics - System.out.println(diagnostics.getWarningOutput()); + if (diagnostics.foundWarning()) { + System.out.println(diagnostics.getWarningOutput()); + } if (diagnostics.foundError()) { System.out.println(diagnostics.getErrorOutput()); } else { @@ -50,7 +52,6 @@ public static void launch(String... paths) { } launcher.addInputResource(path); } - launcher.getEnvironment().setNoClasspath(true); launcher.getEnvironment().setComplianceLevel(8); launcher.run(); @@ -60,16 +61,9 @@ public static void launch(String... paths) { final RefinementProcessor processor = new RefinementProcessor(factory); processingManager.addProcessor(processor); - try { - // analyze all packages - CtPackage root = factory.Package().getRootPackage(); - if (root != null) - processingManager.process(root); - } catch (Exception e) { - e.printStackTrace(); - throw e; - } - - return; + // analyze all packages + CtPackage root = factory.Package().getRootPackage(); + if (root != null) + processingManager.process(root); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Colors.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Colors.java index ff669fec..7b72d190 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Colors.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Colors.java @@ -3,10 +3,7 @@ // ANSI color codes public class Colors { public static final String RESET = "\u001B[0m"; - public static final String BOLD = "\u001B[1m"; public static final String GREY = "\u001B[90m"; - public static final String RED = "\u001B[31m"; public static final String BOLD_RED = "\u001B[1;31m"; - public static final String YELLOW = "\u001B[33m"; public static final String BOLD_YELLOW = "\u001B[1;33m"; } \ No newline at end of file diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java index fc7c75f7..846f3dc6 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java @@ -13,8 +13,8 @@ public class Diagnostics { private static final Diagnostics instance = new Diagnostics(); - private LinkedHashSet errors; - private LinkedHashSet warnings; + private final LinkedHashSet errors; + private final LinkedHashSet warnings; private Diagnostics() { this.errors = new LinkedHashSet<>(); diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorPosition.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorPosition.java index 354e8680..3383f6bf 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorPosition.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorPosition.java @@ -2,59 +2,11 @@ import spoon.reflect.cu.SourcePosition; -public class ErrorPosition { - - private int lineStart; - private int colStart; - private int lineEnd; - private int colEnd; - - public ErrorPosition(int lineStart, int colStart, int lineEnd, int colEnd) { - this.lineStart = lineStart; - this.colStart = colStart; - this.lineEnd = lineEnd; - this.colEnd = colEnd; - } - - public int getLineStart() { - return lineStart; - } - - public int getColStart() { - return colStart; - } - - public int getLineEnd() { - return lineEnd; - } - - public int getColEnd() { - return colEnd; - } +public record ErrorPosition(int lineStart, int colStart, int lineEnd, int colEnd) { public static ErrorPosition fromSpoonPosition(SourcePosition pos) { if (pos == null || !pos.isValidPosition()) return null; return new ErrorPosition(pos.getLine(), pos.getColumn(), pos.getEndLine(), pos.getEndColumn()); } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null || getClass() != obj.getClass()) - return false; - ErrorPosition other = (ErrorPosition) obj; - return lineStart == other.lineStart && colStart == other.colStart && lineEnd == other.lineEnd - && colEnd == other.colEnd; - } - - @Override - public int hashCode() { - int result = lineStart; - result = 31 * result + colStart; - result = 31 * result + lineEnd; - result = 31 * result + colEnd; - return result; - } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java index 9beaccfa..bf7df8b0 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java @@ -8,12 +8,12 @@ public class LJDiagnostic extends RuntimeException { - private String title; - private String message; - private String details; - private String file; - private ErrorPosition position; - private String accentColor; + private final String title; + private final String message; + private final String details; + private final String file; + private final ErrorPosition position; + private final String accentColor; public LJDiagnostic(String title, String message, String details, SourcePosition pos, String accentColor) { this.title = title; @@ -65,7 +65,7 @@ public String toString() { // location if (file != null && position != null) { - sb.append("\n").append(file).append(":").append(position.getLineStart()).append(Colors.RESET).append("\n"); + sb.append("\n").append(file).append(":").append(position.lineStart()).append(Colors.RESET).append("\n"); } return sb.toString(); @@ -83,12 +83,11 @@ public String getSnippet() { // before and after lines for context int contextBefore = 2; int contextAfter = 2; - int startLine = Math.max(1, position.getLineStart() - contextBefore); - int endLine = Math.min(lines.size(), position.getLineEnd() + contextAfter); + int startLine = Math.max(1, position.lineStart() - contextBefore); + int endLine = Math.min(lines.size(), position.lineEnd() + contextAfter); // calculate padding for line numbers - int maxLineNum = endLine; - int padding = String.valueOf(maxLineNum).length(); + int padding = String.valueOf(endLine).length(); for (int i = startLine; i <= endLine; i++) { String lineNumStr = String.format("%" + padding + "d", i); @@ -98,9 +97,9 @@ public String getSnippet() { sb.append(Colors.GREY).append(lineNumStr).append(" | ").append(line).append(Colors.RESET).append("\n"); // add error markers on the line(s) with the error - if (i >= position.getLineStart() && i <= position.getLineEnd()) { - int colStart = (i == position.getLineStart()) ? position.getColStart() : 1; - int colEnd = (i == position.getLineEnd()) ? position.getColEnd() : line.length(); + if (i >= position.lineStart() && i <= position.lineEnd()) { + int colStart = (i == position.lineStart()) ? position.colStart() : 1; + int colEnd = (i == position.lineEnd()) ? position.colEnd() : line.length(); if (colStart > 0 && colEnd > 0) { // line number padding + " | " + column offset diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java index 73a933aa..1c063fa0 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java @@ -11,7 +11,7 @@ */ public class GhostInvocationError extends LJError { - private String expected; + private final String expected; public GhostInvocationError(String message, SourcePosition pos, Expression expected, TranslationTable translationTable) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java index 8012928c..c94ef1ff 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java @@ -9,7 +9,7 @@ */ public class InvalidRefinementError extends LJError { - private String refinement; + private final String refinement; public InvalidRefinementError(CtElement element, String message, String refinement) { super("Invalid Refinement", message, "", element.getPosition(), null); diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java index 446dea22..32a4e17d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java @@ -10,7 +10,7 @@ */ public abstract class LJError extends LJDiagnostic { - private TranslationTable translationTable; + private final TranslationTable translationTable; public LJError(String title, String message, String details, SourcePosition pos, TranslationTable translationTable) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java index 72172934..87fd76e9 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java @@ -12,8 +12,8 @@ */ public class RefinementError extends LJError { - private String expected; - private ValDerivationNode found; + private final String expected; + private final ValDerivationNode found; public RefinementError(CtElement element, Expression expected, ValDerivationNode found, TranslationTable translationTable) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java index fd250e49..1fe85155 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java @@ -11,8 +11,8 @@ */ public class StateConflictError extends LJError { - private String state; - private String className; + private final String state; + private final String className; public StateConflictError(CtElement element, Expression state, String className, TranslationTable translationTable) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java index 1cf7101a..226e6ed5 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java @@ -9,7 +9,7 @@ */ public class SyntaxError extends LJError { - private String refinement; + private final String refinement; public SyntaxError(String message, String refinement) { this(message, null, refinement); diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java index 7fcbc468..e3d1c239 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java @@ -9,7 +9,7 @@ */ public class ExternalClassNotFoundWarning extends LJWarning { - private String className; + private final String className; public ExternalClassNotFoundWarning(CtElement element, String message, String className) { super(message, "", element.getPosition()); diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java index 4f90e994..26ad6f51 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java @@ -9,8 +9,8 @@ */ public class ExternalMethodNotFoundWarning extends LJWarning { - private String methodName; - private String className; + private final String methodName; + private final String className; public ExternalMethodNotFoundWarning(CtElement element, String message, String details, String methodName, String className) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/TestInsideClasses.java b/liquidjava-verifier/src/main/java/liquidjava/processor/TestInsideClasses.java deleted file mode 100644 index 43fa8393..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/TestInsideClasses.java +++ /dev/null @@ -1,27 +0,0 @@ -package liquidjava.processor; - -import spoon.Launcher; - -public class TestInsideClasses { - public static void main(String[] args) { - - Launcher launcher = new Launcher(); - launcher.getEnvironment().setComplianceLevel(8); - launcher.run(); - - // final Factory factory = launcher.getFactory(); - // RefinedVariable vi2 = new Variable("a",factory.Type().INTEGER_PRIMITIVE, new Predicate("a > - // 0")); - // CtTypeReference intt = factory.Type().INTEGER_PRIMITIVE; - // List> l = new ArrayList<>(); - // l.add(intt); - // GhostState s = new GhostState("green", l, intt, "A"); - // GhostState ss = new GhostState("yellow", l, intt, "A"); - // GhostState sss = new GhostState("red", l, intt, "A"); - // List gh = new ArrayList<>(); - // gh.add(s);gh.add(ss);gh.add(sss); - // Predicate p = new Predicate("green(this) && red(this) == iio && u(3)"); - // System.out.println(p.getVariableNames()); - - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java b/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java index 35daad11..152488a8 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/VCImplication.java @@ -32,7 +32,7 @@ public String toString() { String qualType = type.getQualifiedName(); String simpleType = qualType.contains(".") ? Utils.getSimpleName(qualType) : qualType; return String.format("%-20s %s %s", "∀" + name + ":" + simpleType + ",", refinement.toString(), - next != null ? " => \n" + next.toString() : ""); + next != null ? " => \n" + next : ""); } else return String.format("%-20s %s", "", refinement.toString()); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java b/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java index 29600b36..10ad84e6 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/ann_generation/FieldGhostsGeneration.java @@ -28,10 +28,10 @@ public Factory getFactory() { public void visitCtClass(CtClass ctClass) { ctClass.getDeclaredFields().stream().filter(fld -> fld.getType().getQualifiedName().equals("int")) .forEach(fld -> { - CtTypeReference fld_type = fld.getType(); - CtAnnotation gen_ann = factory.createAnnotation(factory.createCtTypeReference(Ghost.class)); - gen_ann.addValue("value", fld_type.getSimpleName() + " " + fld.getSimpleName()); - ctClass.addAnnotation(gen_ann); + CtTypeReference fldType = fld.getType(); + CtAnnotation genAnn = factory.createAnnotation(factory.createCtTypeReference(Ghost.class)); + genAnn.addValue("value", fldType.getSimpleName() + " " + fld.getSimpleName()); + ctClass.addAnnotation(genAnn); }); super.visitCtClass(ctClass); diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/AliasWrapper.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/AliasWrapper.java index 227db985..a9838440 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/AliasWrapper.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/AliasWrapper.java @@ -1,32 +1,22 @@ package liquidjava.processor.context; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; - -import liquidjava.diagnostics.errors.LJError; import liquidjava.processor.facade.AliasDTO; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.ast.Expression; import liquidjava.utils.Utils; -import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; import spoon.reflect.reference.CtTypeReference; public class AliasWrapper { - private String name; - private List> varTypes; - private List varNames; - private Predicate expression; - // private Context context; - - private String newAliasFormat = "#alias_%s_%d"; + private final String name; + private final List> varTypes; + private final List varNames; + private final Predicate expression; - public AliasWrapper(AliasDTO a, Factory factory, String wILD_VAR, Context context2, String klass, String path) { + public AliasWrapper(AliasDTO a, Factory factory, String klass, String path) { name = a.getName(); expression = new Predicate(a.getExpression()); - varTypes = new ArrayList<>(); varNames = a.getVarNames(); for (String s : a.getVarTypes()) { @@ -48,69 +38,10 @@ public List getVarNames() { } public Predicate getClonedPredicate() { - return (Predicate) expression.clone(); - } - - public Expression getNewExpression(List newNames) { - Predicate expr = getClonedPredicate(); - for (int i = 0; i < newNames.size(); i++) { - expr = expr.substituteVariable(varNames.get(i), newNames.get(i)); - } - return expr.getExpression().clone(); - } - - public Predicate getPremises(List list, List newNames, CtElement elem) throws LJError { - List invocationPredicates = getPredicatesFromExpression(list, elem); - Predicate prem = new Predicate(); - for (int i = 0; i < invocationPredicates.size(); i++) { - prem = Predicate.createConjunction(prem, - Predicate.createEquals(Predicate.createVar(newNames.get(i)), invocationPredicates.get(i))); - } - return prem.clone(); - } - - private List getPredicatesFromExpression(List list, CtElement elem) throws LJError { - List lp = new ArrayList<>(); - for (String e : list) - lp.add(new Predicate(e, elem)); - - return lp; - } - - public List getNewVariables(Context context) { - List n = new ArrayList<>(); - for (int i = 0; i < varNames.size(); i++) - n.add(String.format(newAliasFormat, varNames.get(i), context.getCounter())); - return n; - } - - public Map> getTypes(List names) { - Map> m = new HashMap<>(); - for (int i = 0; i < names.size(); i++) { - m.put(names.get(i), varTypes.get(i)); - } - return m; + return expression.clone(); } public AliasDTO createAliasDTO() { return new AliasDTO(name, varTypes, varNames, expression.getExpression()); } - - // public Expression getSubstitutedExpression(List newNames) { - // return null; - // } - // - - // TypeKeyword tk; - // AliasName name; - // - // ParenthesisLeft pl; - // Type type; - // Var var; - // ParenthesisRight rl; - // - // BraceLeft bl; - // Expression e; - // BraceRight br; - } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/Context.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/Context.java index a09fb0e4..ca4d68e8 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/Context.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/Context.java @@ -10,7 +10,7 @@ public class Context { private List ctxFunctions; private List ctxSpecificVars; - private List ctxGlobalVars; + private final List ctxGlobalVars; private List ghosts; private Map> classStates; @@ -24,9 +24,7 @@ private Context() { ctxVars.add(new ArrayList<>()); ctxFunctions = new ArrayList<>(); ctxSpecificVars = new ArrayList<>(); - // globals ctxGlobalVars = new ArrayList<>(); - // ctxGlobalFunctions = new ArrayList<>(); alias = new ArrayList<>(); ghosts = new ArrayList<>(); @@ -34,7 +32,6 @@ private Context() { counter = 0; } - // SINGLETON public static Context getInstance() { if (instance == null) instance = new Context(); @@ -44,11 +41,7 @@ public static Context getInstance() { public void reinitializeContext() { ctxVars = new Stack<>(); ctxVars.add(new ArrayList<>()); // global vars - // ctxFunctions = new ArrayList<>(); ctxSpecificVars = new ArrayList<>(); - // alias = new ArrayList<>(); - // ghosts = new ArrayList<>(); - // counter = 0; } public void reinitializeAllContext() { @@ -95,12 +88,6 @@ public Map> getContext() { } // ---------------------- Global variables ---------------------- - public void addGlobalVariableToContext(String simpleName, CtTypeReference type, Predicate c) { - RefinedVariable vi = new Variable(simpleName, type, c); - ctxGlobalVars.add(vi); - vi.addSuperTypes(type.getSuperclass(), type.getSuperInterfaces()); - } - public void addGlobalVariableToContext(String simpleName, String location, CtTypeReference type, Predicate c) { RefinedVariable vi = new Variable(simpleName, location, type, c); vi.addSuperTypes(type.getSuperclass(), type.getSuperInterfaces()); @@ -109,7 +96,6 @@ public void addGlobalVariableToContext(String simpleName, String location, CtTyp // ---------------------- Add variables and instances ---------------------- public void addVarToContext(RefinedVariable var) { - // if(!hasVariable(var.getName())) ctxVars.peek().add(var); CtTypeReference type = var.getType(); var.addSuperTypes(type.getSuperclass(), type.getSuperInterfaces()); @@ -183,27 +169,15 @@ public boolean hasVariable(String name) { return getVariableByName(name) != null; } - public String allVariablesToString() { - StringBuilder sb = new StringBuilder(); - for (List l : ctxVars) { - for (RefinedVariable var : l) { - sb.append(var.toString() + "; "); - } - } - return sb.toString(); - } - /** * Lists all variables inside the stack * - * @return + * @return list of all variables */ public List getAllVariables() { List lvi = new ArrayList<>(); for (List l : ctxVars) { - for (RefinedVariable var : l) { - lvi.add(var); - } + lvi.addAll(l); } return lvi; } @@ -211,11 +185,11 @@ public List getAllVariables() { public List getAllVariablesWithSupertypes() { List lvi = new ArrayList<>(); for (RefinedVariable rv : getAllVariables()) { - if (rv.getSuperTypes().size() > 0) + if (!rv.getSuperTypes().isEmpty()) lvi.add(rv); } for (RefinedVariable rv : ctxSpecificVars) { - if (rv.getSuperTypes().size() > 0) + if (!rv.getSuperTypes().isEmpty()) lvi.add(rv); } return lvi; @@ -298,18 +272,6 @@ public void addFunctionToContext(RefinedFunction f) { ctxFunctions.add(f); } - public RefinedFunction getFunction(String name, String target) { - for (RefinedFunction fi : ctxFunctions) { - if (fi.getTargetClass() != null && fi.getName().equals(name) && fi.getTargetClass().equals(target)) - return fi; - } - // for(RefinedFunction fi: ctxGlobalFunctions) { - // if(fi.getName().equals(name) && fi.getTargetClass().equals(target)) - // return fi; - // } - return null; - } - public RefinedFunction getFunction(String name, String target, int size) { for (RefinedFunction fi : ctxFunctions) { if (fi.getTargetClass() != null && fi.getName().equals(name) && fi.getTargetClass().equals(target) @@ -333,14 +295,6 @@ public void addGhostFunction(GhostFunction gh) { ghosts.add(gh); } - public boolean hasGhost(String name) { - for (GhostFunction g : ghosts) { - if (g.matches(name)) - return true; - } - return false; - } - public List getGhosts() { return ghosts; } @@ -387,13 +341,10 @@ public String toString() { for (List l : ctxVars) { sb.append("{"); for (RefinedVariable var : l) { - sb.append(var.toString() + "; "); + sb.append(var.toString()).append("; "); } sb.append("}\n"); } - // sb.append("\n############Global Functions:############\n"); - // for(RefinedFunction f : ctxGlobalFunctions) - // sb.append(f.toString()); sb.append("\n############Functions:############\n"); for (RefinedFunction f : ctxFunctions) sb.append(f.toString()); @@ -406,10 +357,5 @@ public String toString() { public Variable getVariableFromInstance(VariableInstance vi) { return vi.getParent().orElse(null); - // for(List lv: ctxVars) - // for(RefinedVariable v: lv) - // if(v instanceof Variable && ((Variable)v).hasInstance(vi)) - // return (Variable)v; - // return null; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/GhostFunction.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/GhostFunction.java index 769f4680..50a4f548 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/GhostFunction.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/GhostFunction.java @@ -9,57 +9,55 @@ public class GhostFunction { - private String name; - private List> param_types; - private CtTypeReference return_type; - private String prefix; + private final String name; + private final List> paramTypes; + private final CtTypeReference returnType; + private final String prefix; public GhostFunction(GhostDTO f, Factory factory, String prefix) { String klass = this.getParentClassName(prefix); - this.name = f.getName(); - this.return_type = Utils.getType(f.getReturn_type().equals(klass) ? prefix : f.getReturn_type(), factory); - this.param_types = new ArrayList<>(); + this.name = f.name(); + this.returnType = Utils.getType(f.returnType().equals(klass) ? prefix : f.returnType(), factory); + this.paramTypes = new ArrayList<>(); this.prefix = prefix; - for (String t : f.getParam_types()) { - this.param_types.add(Utils.getType(t.equals(klass) ? prefix : t, factory)); + for (String t : f.paramTypes()) { + this.paramTypes.add(Utils.getType(t.equals(klass) ? prefix : t, factory)); } } - public GhostFunction(String name, List param_types, CtTypeReference return_type, Factory factory, + public GhostFunction(String name, List paramTypes, CtTypeReference returnType, Factory factory, String prefix) { String klass = this.getParentClassName(prefix); - String type = return_type.toString().equals(klass) ? prefix : return_type.toString(); + String type = returnType.toString().equals(klass) ? prefix : returnType.toString(); this.name = name; - this.return_type = Utils.getType(type, factory); - this.param_types = new ArrayList<>(); + this.returnType = Utils.getType(type, factory); + this.paramTypes = new ArrayList<>(); this.prefix = prefix; - for (int i = 0; i < param_types.size(); i++) { - String mType = param_types.get(i).toString(); - this.param_types.add(Utils.getType(mType.equals(klass) ? prefix : mType, factory)); + for (String mType : paramTypes) { + this.paramTypes.add(Utils.getType(mType.equals(klass) ? prefix : mType, factory)); } } - protected GhostFunction(String name, List> list, CtTypeReference return_type, String prefix) { + protected GhostFunction(String name, List> list, CtTypeReference returnType, String prefix) { this.name = name; - this.return_type = return_type; - this.param_types = new ArrayList<>(); - this.param_types = list; + this.returnType = returnType; + this.paramTypes = list; this.prefix = prefix; } public CtTypeReference getReturnType() { - return return_type; + return returnType; } public List> getParametersTypes() { - return param_types; + return paramTypes; } public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("ghost " + return_type.toString() + " " + name + "("); - for (CtTypeReference t : param_types) { - sb.append(t.toString() + " ,"); + sb.append("ghost ").append(returnType.toString()).append(" ").append(name).append("("); + for (CtTypeReference t : paramTypes) { + sb.append(t.toString()).append(" ,"); } sb.delete(sb.length() - 2, sb.length()); sb.append(")"); diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/GhostParentState.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/GhostParentState.java deleted file mode 100644 index 64974bdf..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/GhostParentState.java +++ /dev/null @@ -1,28 +0,0 @@ -package liquidjava.processor.context; - -import java.util.ArrayList; -import java.util.List; -import spoon.reflect.factory.Factory; -import spoon.reflect.reference.CtTypeReference; - -public class GhostParentState extends GhostFunction { - - private ArrayList states; - - public GhostParentState(String name, List params, CtTypeReference ret, Factory factory, String prefix) { - super(name, params, ret, factory, prefix); - states = new ArrayList<>(); - } - - public void addState(GhostState s) { - states.add(s); - } - - public GhostState getFirstState() { - return states.get(0); - } - - public ArrayList getStates() { - return states; - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/GhostState.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/GhostState.java index 3cdcacb7..121f0339 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/GhostState.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/GhostState.java @@ -9,8 +9,8 @@ public class GhostState extends GhostFunction { private GhostFunction parent; private Predicate refinement; - public GhostState(String name, List> list, CtTypeReference return_type, String prefix) { - super(name, list, return_type, prefix); + public GhostState(String name, List> list, CtTypeReference returnType, String prefix) { + super(name, list, returnType, prefix); } public void setGhostParent(GhostFunction parent) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/PlacementInCode.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/PlacementInCode.java index 2a8f90af..3400f5f7 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/PlacementInCode.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/PlacementInCode.java @@ -7,12 +7,12 @@ import spoon.reflect.declaration.CtElement; public class PlacementInCode { - private String text; - private SourcePosition position; + private final String text; + private final SourcePosition position; - private PlacementInCode(String t, SourcePosition s) { - this.text = t; - this.position = s; + private PlacementInCode(String text, SourcePosition pos) { + this.text = text; + this.position = pos; } public String getText() { @@ -23,24 +23,16 @@ public SourcePosition getPosition() { return position; } - public void setText(String text) { - this.text = text; - } - - public void setPosition(SourcePosition position) { - this.position = position; - } - public static PlacementInCode createPlacement(CtElement elem) { CtElement elemCopy = elem.clone(); // cleanup annotations - if (elem.getAnnotations().size() > 0) { + if (!elem.getAnnotations().isEmpty()) { for (CtAnnotation a : elem.getAnnotations()) { elemCopy.removeAnnotation(a); } } // cleanup comments - if (elem.getComments().size() > 0) { + if (!elem.getComments().isEmpty()) { for (CtComment a : elem.getComments()) { elemCopy.removeComment(a); } @@ -49,13 +41,6 @@ public static PlacementInCode createPlacement(CtElement elem) { return new PlacementInCode(elemText, elem.getPosition()); } - public String getSimplePosition() { - if (position.getFile() == null) { - return "No position provided. Possibly asking for generated code"; - } - return position.getFile().getName() + ":" + position.getLine() + ", " + position.getColumn(); - } - public String toString() { if (position.getFile() == null) { return "No position provided. Possibly asking for generated code"; diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/Refined.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/Refined.java index a597fe24..6028d521 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/Refined.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/Refined.java @@ -78,10 +78,9 @@ public boolean equals(Object obj) { } else if (!name.equals(other.name)) return false; if (type == null) { - if (other.type != null) - return false; - } else if (!type.equals(other.type)) - return false; - return true; + return other.type == null; + } else { + return type.equals(other.type); + } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/RefinedFunction.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/RefinedFunction.java index 807f4682..f1585cee 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/RefinedFunction.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/RefinedFunction.java @@ -5,11 +5,10 @@ import java.util.Optional; import liquidjava.rj_language.Predicate; import spoon.reflect.declaration.CtElement; -import spoon.reflect.reference.CtTypeReference; public class RefinedFunction extends Refined { - private List argRefinements; + private final List argRefinements; private String targetClass; private List stateChange; private String signature; @@ -23,11 +22,6 @@ public List getArguments() { return argRefinements; } - public void addArgRefinements(String varName, CtTypeReference type, Predicate refinement) { - Variable v = new Variable(varName, type, refinement); - this.argRefinements.add(v); - } - public void addArgRefinements(Variable vi) { this.argRefinements.add(vi); } @@ -84,24 +78,9 @@ public Predicate getAllRefinements() { return c; } - /** - * Gives the Predicate for a certain parameter index and regards all the previous parameters' Predicates - * - * @param index - * - * @return - */ - public Predicate getRefinementsForParamIndex(int index) { - Predicate c = new Predicate(); - for (int i = 0; i <= index && i < argRefinements.size(); i++) - c = Predicate.createConjunction(c, argRefinements.get(i).getRefinement()); - return c; - } - public boolean allRefinementsTrue() { - boolean t = true; Predicate p = new Predicate(getRefReturn().getExpression()); - t = t && p.isBooleanTrue(); + boolean t = p.isBooleanTrue(); for (Variable v : argRefinements) { p = new Predicate(v.getRefinement().getExpression()); t = t && p.isBooleanTrue(); @@ -122,7 +101,7 @@ public void addStates(ObjectState e) { } public boolean hasStateChange() { - return stateChange.size() > 0; + return !stateChange.isEmpty(); } public List getFromStates() { @@ -149,7 +128,7 @@ public String toString() { public int hashCode() { final int prime = 31; int result = super.hashCode(); - result = prime * result + ((argRefinements == null) ? 0 : argRefinements.hashCode()); + result = prime * result + argRefinements.hashCode(); result = prime * result + ((targetClass == null) ? 0 : targetClass.hashCode()); result = prime * result + ((signature == null) ? 0 : signature.hashCode()); return result; @@ -164,10 +143,7 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; RefinedFunction other = (RefinedFunction) obj; - if (argRefinements == null) { - if (other.argRefinements != null) - return false; - } else if (!argRefinements.equals(other.argRefinements)) + if (!argRefinements.equals(other.argRefinements)) return false; if (targetClass == null) { if (other.targetClass != null) @@ -175,10 +151,9 @@ public boolean equals(Object obj) { } else if (!targetClass.equals(other.targetClass)) return false; if (signature == null) { - if (other.signature != null) - return false; - } else if (!signature.equals(other.signature)) - return false; - return true; + return other.signature == null; + } else { + return signature.equals(other.signature); + } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/RefinedVariable.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/RefinedVariable.java index fe3abf11..534f8cfa 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/RefinedVariable.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/RefinedVariable.java @@ -7,7 +7,7 @@ import spoon.reflect.reference.CtTypeReference; public abstract class RefinedVariable extends Refined { - private List> supertypes; + private final List> supertypes; private PlacementInCode placementInCode; public RefinedVariable(String name, CtTypeReference type, Predicate c) { @@ -60,10 +60,9 @@ public boolean equals(Object obj) { return false; RefinedVariable other = (RefinedVariable) obj; if (supertypes == null) { - if (other.supertypes != null) - return false; - } else if (!supertypes.equals(other.supertypes)) - return false; - return true; + return other.supertypes == null; + } else { + return supertypes.equals(other.supertypes); + } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/Variable.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/Variable.java index ff616db9..13f74cab 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/Variable.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/Variable.java @@ -32,7 +32,7 @@ public Variable(String name, String location, CtTypeReference type, Predicate private void startVariables() { this.instances = new Stack<>(); - this.instances.push(new ArrayList()); + this.instances.push(new ArrayList<>()); ifCombiner = new Stack<>(); } @@ -73,16 +73,11 @@ public void addInstance(VariableInstance vi) { instances.peek().add(vi); } - public void removeLastInstance() { - if (instances.size() > 0) - instances.peek().remove(instances.size() - 1); - } - public Optional getLastInstance() { Stack> backup = new Stack<>(); - while (instances.size() > 0) { + while (!instances.isEmpty()) { List lvi = instances.peek(); - if (lvi.size() > 0) { // last list in stack has a value + if (!lvi.isEmpty()) { // last list in stack has a value reloadFromBackup(backup); return Optional.of(lvi.get(lvi.size() - 1)); } else { @@ -94,18 +89,10 @@ public Optional getLastInstance() { } private void reloadFromBackup(Stack> backup) { - while (backup.size() > 0) + while (!backup.isEmpty()) instances.add(backup.pop()); } - public boolean hasInstance(VariableInstance vi) { - for (List lv : instances) - for (VariableInstance v : lv) - if (v.equals(vi)) - return true; - return false; - } - // IFS public void newIfCombination() { ifCombiner.push(new Object[ifelseIndex + 1]); @@ -167,8 +154,7 @@ else if (has(ifbeforeIndex)) // value before and in else private boolean has(int index) { Object o = ifCombiner.peek()[index]; - boolean b = o != null && (o instanceof VariableInstance); - return b; + return (o instanceof VariableInstance); } private VariableInstance get(int index) { @@ -180,7 +166,7 @@ private VariableInstance get(int index) { * * @param nName * @param cond - * @param ifThen + * @param then * * @return */ diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/VariableInstance.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/VariableInstance.java index 1a6daf4a..b4bebb0c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/VariableInstance.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/VariableInstance.java @@ -6,7 +6,6 @@ public class VariableInstance extends RefinedVariable { - // private Predicate state; private Variable parent; public VariableInstance(String name, CtTypeReference type, Predicate c) { @@ -37,13 +36,4 @@ public void setParent(Variable p) { public Optional getParent() { return parent == null ? Optional.empty() : Optional.of(parent); } - - // public void setState(Predicate c) { - // state = c; - // } - // public Predicate getState() { - // return state; - // } - // - } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/facade/AliasDTO.java b/liquidjava-verifier/src/main/java/liquidjava/processor/facade/AliasDTO.java index 7b880475..ca120615 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/facade/AliasDTO.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/facade/AliasDTO.java @@ -6,19 +6,20 @@ import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.parsing.RefinementsParser; +import spoon.reflect.declaration.CtTypeInformation; import spoon.reflect.reference.CtTypeReference; public class AliasDTO { - private String name; - private List varTypes; - private List varNames; + private final String name; + private final List varTypes; + private final List varNames; private Expression expression; private String ref; public AliasDTO(String name, List> varTypes, List varNames, Expression expression) { super(); this.name = name; - this.varTypes = varTypes.stream().map(m -> m.getQualifiedName()).collect(Collectors.toList()); + this.varTypes = varTypes.stream().map(CtTypeInformation::getQualifiedName).collect(Collectors.toList()); this.varNames = varNames; this.expression = expression; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/facade/GhostDTO.java b/liquidjava-verifier/src/main/java/liquidjava/processor/facade/GhostDTO.java index 6dc9ea82..87dfb5cc 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/facade/GhostDTO.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/facade/GhostDTO.java @@ -2,28 +2,5 @@ import java.util.List; -public class GhostDTO { - - private String name; - private List param_types; - private String return_type; - - public GhostDTO(String name, List param_types, String return_type) { - super(); - this.name = name; - this.param_types = param_types; - this.return_type = return_type; - } - - public String getName() { - return name; - } - - public List getParam_types() { - return param_types; - } - - public String getReturn_type() { - return return_type; - } +public record GhostDTO(String name, List paramTypes, String returnType) { } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java index 2321b1f6..bfbb8de3 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java @@ -1,6 +1,5 @@ package liquidjava.processor.refinement_checker; -import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -27,7 +26,6 @@ public class ExternalRefinementTypeChecker extends TypeChecker { String prefix; - MethodsFunctionsChecker m; Diagnostics diagnostics = Diagnostics.getInstance(); public ExternalRefinementTypeChecker(Context context, Factory factory) { @@ -36,7 +34,6 @@ public ExternalRefinementTypeChecker(Context context, Factory factory) { @Override public void visitCtClass(CtClass ctClass) { - return; } @Override @@ -65,7 +62,7 @@ public void visitCtField(CtField f) { public void visitCtMethod(CtMethod method) { CtType targetType = factory.Type().createReference(prefix).getTypeDeclaration(); - if (targetType == null || !(targetType instanceof CtClass)) + if (!(targetType instanceof CtClass)) return; boolean isConstructor = method.getSimpleName().equals(targetType.getSimpleName()); @@ -99,7 +96,7 @@ public void visitCtMethod(CtMethod method) { protected void getGhostFunction(String value, CtElement element) throws LJError { GhostDTO f = RefinementsParser.getGhostDeclaration(value); - if (f != null && element.getParent() instanceof CtInterface) { + if (element.getParent() instanceof CtInterface) { GhostFunction gh = new GhostFunction(f, factory, prefix); context.addGhostFunction(gh); } @@ -110,7 +107,7 @@ protected Optional createStateGhost(int order, CtElement element) String klass = Utils.getSimpleName(prefix); if (klass != null) { CtTypeReference ret = factory.Type().INTEGER_PRIMITIVE; - List params = Arrays.asList(klass); + List params = List.of(klass); String name = String.format("state%d", order); GhostFunction gh = new GhostFunction(name, params, ret, factory, prefix); return Optional.of(gh); diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java index d318f25e..43791ccd 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java @@ -47,7 +47,6 @@ public class RefinementTypeChecker extends TypeChecker { // This class should do the following: - // 1. Keep track of the context variable types // 2. Do type checking and inference @@ -189,8 +188,7 @@ public void visitCtAssignment(CtAssignment assignment) th String name = var.getSimpleName(); checkAssignment(name, varDecl.getType(), ex, assignment.getAssignment(), assignment, varDecl); - } else if (ex instanceof CtFieldWrite) { - CtFieldWrite fw = ((CtFieldWrite) ex); + } else if (ex instanceof CtFieldWrite fw) { CtFieldReference cr = fw.getVariable(); CtField f = fw.getVariable().getDeclaration(); String updatedVarName = String.format(Formats.THIS, cr.getSimpleName()); @@ -234,16 +232,12 @@ public void visitCtLiteral(CtLiteral lit) { public void visitCtField(CtField f) { super.visitCtField(f); Optional c = getRefinementFromAnnotation(f); - - // context.addVarToContext(f.getSimpleName(), f.getType(), - // c.map( i -> i.substituteVariable(WILD_VAR, f.getSimpleName()).orElse(new - // Predicate()) ); - String nname = String.format(Formats.THIS, f.getSimpleName()); + String name = String.format(Formats.THIS, f.getSimpleName()); Predicate ret = new Predicate(); if (c.isPresent()) { - ret = c.get().substituteVariable(Keys.WILDCARD, nname).substituteVariable(f.getSimpleName(), nname); + ret = c.get().substituteVariable(Keys.WILDCARD, name).substituteVariable(f.getSimpleName(), name); } - RefinedVariable v = context.addVarToContext(nname, f.getType(), ret, f); + RefinedVariable v = context.addVarToContext(name, f.getType(), ret, f); if (v instanceof Variable) { ((Variable) v).setLocation("this"); } @@ -274,9 +268,8 @@ public void visitCtFieldRead(CtFieldRead fieldRead) { } else { fieldRead.putMetadata(Keys.REFINEMENT, new Predicate()); - // TODO DO WE WANT THIS OR TO SHOW ERROR MESSAGE + // TODO DO WE WANT THIS OR TO SHOW ERROR MESSAGE? } - super.visitCtFieldRead(fieldRead); } @@ -284,7 +277,7 @@ public void visitCtFieldRead(CtFieldRead fieldRead) { public void visitCtVariableRead(CtVariableRead variableRead) { super.visitCtVariableRead(variableRead); CtVariable varDecl = variableRead.getVariable().getDeclaration(); - getPutVariableMetadada(variableRead, varDecl.getSimpleName()); + getPutVariableMetadata(variableRead, varDecl.getSimpleName()); } /** @@ -364,8 +357,7 @@ public void visitCtIf(CtIf ifElement) { public void visitCtArrayWrite(CtArrayWrite arrayWrite) { super.visitCtArrayWrite(arrayWrite); CtExpression index = arrayWrite.getIndexExpression(); - BuiltinFunctionPredicate fp = BuiltinFunctionPredicate.addToIndex(arrayWrite.getTarget().toString(), - index.toString(), Keys.WILDCARD, arrayWrite); + BuiltinFunctionPredicate fp = BuiltinFunctionPredicate.addToIndex(index.toString(), Keys.WILDCARD, arrayWrite); arrayWrite.putMetadata(Keys.REFINEMENT, fp); } @@ -393,7 +385,7 @@ public void visitCtNewClass(CtNewClass newClass) { // ########################################## private void checkAssignment(String name, CtTypeReference type, CtExpression ex, CtExpression assignment, CtElement parentElem, CtElement varDecl) throws LJError { - getPutVariableMetadada(ex, name); + getPutVariableMetadata(ex, name); Predicate refinementFound = getRefinement(assignment); if (refinementFound == null) { @@ -452,18 +444,17 @@ private Predicate substituteAllVariablesForLastInstance(Predicate c) { // ########################################## /** - * @param + * Gets the variable refinement from the context and puts it as metadata in the element + * * @param elem * @param name - * Cannot be null */ - private void getPutVariableMetadada(CtElement elem, String name) { + private void getPutVariableMetadata(CtElement elem, String name) { Predicate cref = Predicate.createEquals(Predicate.createVar(Keys.WILDCARD), Predicate.createVar(name)); Optional ovi = context.getLastVariableInstance(name); if (ovi.isPresent()) { cref = Predicate.createEquals(Predicate.createVar(Keys.WILDCARD), Predicate.createVar(ovi.get().getName())); } - elem.putMetadata(Keys.REFINEMENT, cref); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java index 3608f23d..3b044848 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java @@ -1,7 +1,7 @@ package liquidjava.processor.refinement_checker; import java.lang.annotation.Annotation; -import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -66,7 +66,6 @@ public Optional getRefinementFromAnnotation(CtElement element) throws String an = ann.getActualAnnotation().annotationType().getCanonicalName(); if (an.contentEquals("liquidjava.specification.Refinement")) { String st = TypeCheckingUtils.getStringFromAnnotation(ann.getValue("value")); - // CtLiteral s = (CtLiteral) ann.getAllValues().get("value"); ref = Optional.of(st); } else if (an.contentEquals("liquidjava.specification.RefinementPredicate")) { @@ -102,7 +101,7 @@ public void handleStateSetsFromAnnotation(CtElement element) throws LJError { } if (an.contentEquals("liquidjava.specification.Ghost")) { CtLiteral s = (CtLiteral) ann.getAllValues().get("value"); - createStateGhost(s.getValue(), ann, an, element); + createStateGhost(s.getValue(), ann, element); } } } @@ -122,7 +121,7 @@ private void createStateSet(CtNewArray e, int set, CtElement element) th } Optional og = createStateGhost(set, element); - if (!og.isPresent()) { + if (og.isEmpty()) { throw new RuntimeException("Error in creation of GhostFunction"); } GhostFunction g = og.get(); @@ -140,22 +139,18 @@ private void createStateSet(CtNewArray e, int set, CtElement element) th GhostState gs = new GhostState(f, g.getParametersTypes(), factory.Type().BOOLEAN_PRIMITIVE, g.getPrefix()); gs.setGhostParent(g); - gs.setRefinement( - /* new OperationPredicate(new InvocationPredicate(f, THIS), "<-->", */ - Predicate.createEquals(ip, Predicate.createLit(Integer.toString(order), Types.INT))); // open(THIS) - // -> - // state1(THIS) - // == 1 + gs.setRefinement(Predicate.createEquals(ip, Predicate.createLit(Integer.toString(order), Types.INT))); + // open(THIS) -> state1(THIS) == 1 context.addToGhostClass(g.getParentClassName(), gs); } order++; } } - private void createStateGhost(String string, CtAnnotation ann, String an, CtElement element) + private void createStateGhost(String string, CtAnnotation ann, CtElement element) throws LJError { GhostDTO gd = RefinementsParser.getGhostDeclaration(string); - if (gd.getParam_types().size() > 0) { + if (!gd.paramTypes().isEmpty()) { throw new CustomError( "Ghost States have the class as parameter " + "by default, no other parameters are allowed", ann); } @@ -163,10 +158,10 @@ private void createStateGhost(String string, CtAnnotation String qn = getQualifiedClassName(element); String sn = getSimpleClassName(element); context.addGhostClass(sn); - List> param = Arrays.asList(factory.Type().createReference(qn)); + List> param = Collections.singletonList(factory.Type().createReference(qn)); - CtTypeReference r = factory.Type().createReference(gd.getReturn_type()); - GhostState gs = new GhostState(gd.getName(), param, r, qn); + CtTypeReference r = factory.Type().createReference(gd.returnType()); + GhostState gs = new GhostState(gd.name(), param, r, qn); context.addToGhostClass(sn, gs); } @@ -197,7 +192,7 @@ protected Optional createStateGhost(int order, CtElement element) } if (klass != null) { CtTypeReference ret = factory.Type().INTEGER_PRIMITIVE; - List params = Arrays.asList(klass.getSimpleName()); + List params = Collections.singletonList(klass.getSimpleName()); String name = String.format("state%d", order); GhostFunction gh = new GhostFunction(name, params, ret, factory, klass.getQualifiedName()); return Optional.of(gh); @@ -207,8 +202,7 @@ protected Optional createStateGhost(int order, CtElement element) protected void getGhostFunction(String value, CtElement element) throws LJError { GhostDTO f = RefinementsParser.getGhostDeclaration(value); - if (f != null && element.getParent() instanceof CtClass) { - CtClass klass = (CtClass) element.getParent(); + if (element.getParent()instanceof CtClass klass) { GhostFunction gh = new GhostFunction(f, factory, klass.getQualifiedName()); context.addGhostFunction(gh); } @@ -233,7 +227,7 @@ protected void handleAlias(String value, CtElement element) throws LJError { throw new InvalidRefinementError(element, "Refinement alias must return a boolean expression", value); } - AliasWrapper aw = new AliasWrapper(a, factory, Keys.WILDCARD, context, klass, path); + AliasWrapper aw = new AliasWrapper(a, factory, klass, path); context.addAlias(aw); } } catch (SyntaxError e) { @@ -263,12 +257,11 @@ public void checkVariableRefinements(Predicate refinementFound, String simpleNam if (context.hasVariable(simpleName)) mainRV = context.getVariableByName(simpleName); - if (context.hasVariable(simpleName) && !context.getVariableByName(simpleName).getRefinement().isBooleanTrue()) + if (context.hasVariable(simpleName) && !context.getVariableByName(simpleName).getRefinement().isBooleanTrue()) { cEt = mainRV.getMainRefinement(); - else if (expectedType.isPresent()) - cEt = expectedType.get(); - else - cEt = new Predicate(); + } else { + cEt = expectedType.orElseGet(Predicate::new); + } cEt = cEt.substituteVariable(Keys.WILDCARD, simpleName); Predicate cet = cEt.substituteVariable(Keys.WILDCARD, simpleName); @@ -295,24 +288,23 @@ public void checkSMT(Predicate expectedType, CtElement element) throws LJError { public void checkStateSMT(Predicate prevState, Predicate expectedState, CtElement target, String moreInfo) throws LJError { - vcChecker.processSubtyping(prevState, expectedState, context.getGhostState(), target, moreInfo, factory); + vcChecker.processSubtyping(prevState, expectedState, context.getGhostState(), target, factory); } public boolean checksStateSMT(Predicate prevState, Predicate expectedState, SourcePosition p) throws LJError { return vcChecker.canProcessSubtyping(prevState, expectedState, context.getGhostState(), p, factory); } - public void createError(CtElement element, Predicate expectedType, Predicate foundType, String customMessage) - throws LJError { - vcChecker.printSubtypingError(element, expectedType, foundType, customMessage); + public void createError(CtElement element, Predicate expectedType, Predicate foundType) throws LJError { + vcChecker.raiseSubtypingError(element, expectedType, foundType); } public void createSameStateError(CtElement element, Predicate expectedType, String klass) throws LJError { - vcChecker.printSameStateError(element, expectedType, klass); + vcChecker.raiseSameStateError(element, expectedType, klass); } public void createStateMismatchError(CtElement element, String method, Predicate found, Predicate[] expected) throws LJError { - vcChecker.printStateMismatchError(element, method, found, expected); + vcChecker.raiseStateMismatchError(element, method, found, expected); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeCheckingUtils.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeCheckingUtils.java index ed839368..5c5de261 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeCheckingUtils.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeCheckingUtils.java @@ -8,14 +8,12 @@ public class TypeCheckingUtils { public static String getStringFromAnnotation(CtExpression ce) { - if (ce instanceof CtLiteral) { - CtLiteral cl = (CtLiteral) ce; + if (ce instanceof CtLiteral cl) { CtTypeReference r = ce.getType(); if (r.getSimpleName().equals("String")) return (String) cl.getValue(); - } else if (ce instanceof CtBinaryOperator) { - CtBinaryOperator cbo = (CtBinaryOperator) ce; + } else if (ce instanceof CtBinaryOperator cbo) { String l = getStringFromAnnotation(cbo.getLeftHandOperand()); String r = getStringFromAnnotation(cbo.getRightHandOperand()); return l + r; diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java index 5bae7e90..3636ff93 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java @@ -58,18 +58,18 @@ public void processSubtyping(Predicate expectedType, List list, CtEl } try { - smtChecking(premises, et, element.getPosition()); + smtChecking(premises, et); } catch (Exception e) { // To emit the message we use the constraints before the alias and state change - printError(e, premisesBeforeChange, expectedType, element, map); + raiseError(e, premisesBeforeChange, expectedType, element, map); } } public void processSubtyping(Predicate type, Predicate expectedType, List list, CtElement element, - String string, Factory f) throws LJError { + Factory f) throws LJError { boolean b = canProcessSubtyping(type, expectedType, list, element.getPosition(), f); if (!b) - printSubtypingError(element, expectedType, type, string); + raiseSubtypingError(element, expectedType, type); } public boolean canProcessSubtyping(Predicate type, Predicate expectedType, List list, SourcePosition p, @@ -80,7 +80,6 @@ public boolean canProcessSubtyping(Predicate type, Predicate expectedType, List< if (expectedType.isBooleanTrue() && type.isBooleanTrue()) return true; - // Predicate premises = joinPredicates(type, element, mainVars, lrv); TranslationTable map = new TranslationTable(); String[] s = { Keys.WILDCARD, Keys.THIS }; @@ -108,10 +107,6 @@ private List filterGhostStatesForVariables(List list, Li if (list.isEmpty()) return list; - // Collect all relevant qualified type names from involved variables and their supertypes - if (list == null || list.isEmpty()) - return list; - // Collect all relevant qualified type names (types + supertypes), keeping order and deduping Set allowedPrefixes = new java.util.LinkedHashSet<>(); Consumer collect = rv -> { @@ -171,13 +166,11 @@ private VCImplication joinPredicates(Predicate expectedType, List lrv, private void addAllDifferent(List toExpand, List from, List remove) { from.stream().filter(rv -> !toExpand.contains(rv) && !remove.contains(rv)).forEach(toExpand::add); - // for (RefinedVariable rv : from) { - // if (!toExpand.contains(rv) && !remove.contains(rv)) - // toExpand.add(rv); - // } } private List getVariables(Predicate c, String varName) { @@ -233,7 +222,7 @@ private void recAuxGetVars(RefinedVariable var, List newVars) { public boolean smtChecks(Predicate found, Predicate expectedType, SourcePosition p) throws LJError { try { - new SMTEvaluator().verifySubtype(found, expectedType, context, p); + new SMTEvaluator().verifySubtype(found, expectedType, context); } catch (TypeCheckError e) { return false; } catch (Exception e) { @@ -259,24 +248,10 @@ public boolean smtChecks(Predicate found, Predicate expectedType, SourcePosition * @param expectedType * */ - private void smtChecking(Predicate cSMT, Predicate expectedType, SourcePosition p) - throws TypeCheckError, Exception { - new SMTEvaluator().verifySubtype(cSMT, expectedType, context, p); + private void smtChecking(Predicate cSMT, Predicate expectedType) throws Exception { + new SMTEvaluator().verifySubtype(cSMT, expectedType, context); } - /** - * Change variables in constraint by their value expression in the map - * - * @param c - * @param map - * - * @return - */ - // private Predicate substituteByMap(Predicate c, HashMap map) { - // map.keySet().forEach(s -> c.substituteVariable(s, map.get(s))); - // return c; - // } - public void addPathVariable(RefinedVariable rv) { pathVariables.add(rv); } @@ -286,13 +261,13 @@ public void removePathVariable(RefinedVariable rv) { } void removePathVariableThatIncludes(String otherVar) { - pathVariables.stream().filter(rv -> rv.getRefinement().getVariableNames().contains(otherVar)) - .collect(Collectors.toList()).forEach(pathVariables::remove); + pathVariables.stream().filter(rv -> rv.getRefinement().getVariableNames().contains(otherVar)).toList() + .forEach(pathVariables::remove); } // Errors--------------------------------------------------------------------------------------------------- - private TranslationTable createMap(CtElement element, Predicate expectedType) { + private TranslationTable createMap(Predicate expectedType) { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(expectedType, lrv, mainVars); TranslationTable map = new TranslationTable(); @@ -300,8 +275,7 @@ private TranslationTable createMap(CtElement element, Predicate expectedType) { return map; } - protected void printSubtypingError(CtElement element, Predicate expectedType, Predicate foundType, String customMsg) - throws LJError { + protected void raiseSubtypingError(CtElement element, Predicate expectedType, Predicate foundType) throws LJError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(expectedType, lrv, mainVars); gatherVariables(foundType, lrv, mainVars); @@ -310,29 +284,23 @@ protected void printSubtypingError(CtElement element, Predicate expectedType, Pr throw new RefinementError(element, expectedType.getExpression(), premises.simplify(), map); } - public void printSameStateError(CtElement element, Predicate expectedType, String klass) throws LJError { - TranslationTable map = createMap(element, expectedType); + public void raiseSameStateError(CtElement element, Predicate expectedType, String klass) throws LJError { + TranslationTable map = createMap(expectedType); throw new StateConflictError(element, expectedType.getExpression(), klass, map); } - private void printError(Exception e, Predicate premisesBeforeChange, Predicate expectedType, CtElement element, + private void raiseError(Exception e, Predicate premisesBeforeChange, Predicate expectedType, CtElement element, TranslationTable map) throws LJError { - LJError error = mapError(e, premisesBeforeChange, expectedType, element, map); - throw error; - } - - private LJError mapError(Exception e, Predicate premisesBeforeChange, Predicate expectedType, CtElement element, - TranslationTable map) { if (e instanceof TypeCheckError) { - return new RefinementError(element, expectedType.getExpression(), premisesBeforeChange.simplify(), map); + throw new RefinementError(element, expectedType.getExpression(), premisesBeforeChange.simplify(), map); } else if (e instanceof liquidjava.smt.errors.NotFoundError) { - return new NotFoundError(element, e.getMessage(), map); + throw new NotFoundError(element, e.getMessage(), map); } else { - return new CustomError(e.getMessage(), element); + throw new CustomError(e.getMessage(), element); } } - public void printStateMismatchError(CtElement element, String method, Predicate found, Predicate[] states) + public void raiseStateMismatchError(CtElement element, String method, Predicate found, Predicate[] states) throws LJError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(found, lrv, mainVars); diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java index 1b642ee9..815e8e6c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java @@ -1,18 +1,12 @@ package liquidjava.processor.refinement_checker.general_checkers; -import java.lang.annotation.Annotation; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import liquidjava.diagnostics.errors.LJError; -import liquidjava.processor.context.Context; -import liquidjava.processor.context.RefinedFunction; -import liquidjava.processor.context.RefinedVariable; -import liquidjava.processor.context.Variable; -import liquidjava.processor.context.VariableInstance; +import liquidjava.processor.context.*; import liquidjava.processor.refinement_checker.TypeChecker; import liquidjava.utils.constants.Formats; import liquidjava.utils.constants.Keys; @@ -26,7 +20,6 @@ import spoon.reflect.code.CtInvocation; import spoon.reflect.code.CtReturn; import spoon.reflect.code.CtVariableRead; -import spoon.reflect.declaration.CtAnnotation; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtConstructor; import spoon.reflect.declaration.CtElement; @@ -39,7 +32,7 @@ public class MethodsFunctionsChecker { - private TypeChecker rtc; + private final TypeChecker rtc; public MethodsFunctionsChecker(TypeChecker rtc) { this.rtc = rtc; @@ -57,8 +50,7 @@ public void getConstructorRefinements(CtConstructor c) throws LJError { } else { f.setSignature(c.getSignature()); } - if (c.getParent() instanceof CtClass) { - CtClass klass = (CtClass) c.getParent(); + if (c.getParent()instanceof CtClass klass) { f.setClass(klass.getQualifiedName()); } rtc.getContext().addFunctionToContext(f); @@ -91,8 +83,7 @@ public void getMethodRefinements(CtMethod method) throws LJError { klass = (CtClass) method.getParent(); f.setClass(klass.getQualifiedName()); } - if (method.getParent() instanceof CtInterface) { - CtInterface inter = (CtInterface) method.getParent(); + if (method.getParent()instanceof CtInterface inter) { f.setClass(inter.getQualifiedName()); } String owner = f.getTargetClass(); @@ -147,7 +138,7 @@ private void auxGetMethodRefinements(CtMethod method, RefinedFunction rf) * Joins all the refinements from parameters and return * * @param f - * @param methodRef + * @param method * @param params * * @return Conjunction of all @@ -155,7 +146,6 @@ private void auxGetMethodRefinements(CtMethod method, RefinedFunction rf) private Predicate handleFunctionRefinements(RefinedFunction f, CtElement method, List> params) throws LJError { Predicate joint = new Predicate(); - for (CtParameter param : params) { String paramName = param.getSimpleName(); Optional oc = rtc.getRefinementFromAnnotation(param); @@ -168,26 +158,13 @@ private Predicate handleFunctionRefinements(RefinedFunction f, CtElement method, f.addArgRefinements((Variable) v); joint = Predicate.createConjunction(joint, c); } - Optional oret = rtc.getRefinementFromAnnotation(method); Predicate ret = oret.orElse(new Predicate()); ret = ret.substituteVariable("return", Keys.WILDCARD); f.setRefReturn(ret); - // rtc.context.addFunctionToContext(f); return Predicate.createConjunction(joint, ret); } - public List> getStateAnnotation(CtElement element) { - List> l = new ArrayList<>(); - for (CtAnnotation ann : element.getAnnotations()) { - String an = ann.getActualAnnotation().annotationType().getCanonicalName(); - if (an.contentEquals("liquidjava.specification.StateRefinement")) { - l.add(ann); - } - } - return l; - } - public void getReturnRefinements(CtReturn ret) throws LJError { CtClass c = ret.getParent(CtClass.class); String className = c.getSimpleName(); @@ -244,16 +221,6 @@ public void getInvocationRefinements(CtInvocation invocation) throws LJEr if (f != null) { // inside rtc.context checkInvocationRefinements(invocation, invocation.getArguments(), invocation.getTarget(), method.getSimpleName(), ctype); - - } else { - CtExecutable cet = invocation.getExecutable().getDeclaration(); - if (cet instanceof CtMethod) { - // CtMethod met = (CtMethod) cet; - // rtc.visitCtMethod(met); - // checkInvocationRefinements(invocation, method.getSimpleName()); - } - // rtc.visitCtMethod(method); - } } } @@ -344,7 +311,7 @@ private Map checkInvocationRefinements(CtElement invocation, Lis String viName = String.format(Formats.INSTANCE, f.getName(), rtc.getContext().getCounter()); VariableInstance vi = (VariableInstance) rtc.getContext().addInstanceToContext(viName, f.getType(), - methodRef.substituteVariable(Keys.WILDCARD, viName), invocation); // TODO REVER!! + methodRef.substituteVariable(Keys.WILDCARD, viName), invocation); // TODO REVIEW!! if (varName != null && f.hasStateChange() && equalsThis) rtc.getContext().addRefinementInstanceToVariable(varName, viName); invocation.putMetadata(Keys.TARGET, vi); @@ -353,24 +320,19 @@ private Map checkInvocationRefinements(CtElement invocation, Lis return map; } - private Map mapInvocation(List> arguments, RefinedFunction f) { + private Map mapInvocation(List> arguments, RefinedFunction f) { Map mapInvocation = new HashMap<>(); - List> invocationParams = arguments; List functionParams = f.getArguments(); - for (int i = 0; i < invocationParams.size(); i++) { + for (int i = 0; i < arguments.size(); i++) { Variable fArg = functionParams.get(i); - CtExpression iArg = invocationParams.get(i); + CtExpression iArg = arguments.get(i); String invStr; - // if(iArg instanceof CtLiteral) - // invStr = iArg.toString(); - // else if (iArg instanceof CtFieldRead) { invStr = createVariableRepresentingArgument(iArg, fArg); - } else if (iArg instanceof CtVariableRead) { - CtVariableRead vr = (CtVariableRead) iArg; + } else if (iArg instanceof CtVariableRead vr) { Optional ovi = rtc.getContext() .getLastVariableInstance(vr.getVariable().getSimpleName()); - invStr = ovi.map(o -> o.getName()).orElse(vr.toString()); + invStr = ovi.map(Refined::getName).orElse(vr.toString()); } else // create new variable with the argument refinement invStr = createVariableRepresentingArgument(iArg, fArg); @@ -390,11 +352,10 @@ private String createVariableRepresentingArgument(CtExpression iArg, Variable return nVar; } - private void checkParameters(CtElement invocation, List> arguments, RefinedFunction f, + private void checkParameters(CtElement invocation, List> arguments, RefinedFunction f, Map map) throws LJError { - List> invocationParams = arguments; List functionParams = f.getArguments(); - for (int i = 0; i < invocationParams.size(); i++) { + for (int i = 0; i < arguments.size(); i++) { Variable fArg = functionParams.get(i); Predicate c = fArg.getMainRefinement(); c = c.substituteVariable(fArg.getName(), map.get(fArg.getName())); @@ -415,16 +376,14 @@ private void checkParameters(CtElement invocation, List> arg private void applyRefinementsToArguments(CtElement element, List> arguments, RefinedFunction f, Map map) { Context context = rtc.getContext(); - List> invocationParams = arguments; List functionParams = f.getArguments(); - for (int i = 0; i < invocationParams.size(); i++) { + for (int i = 0; i < arguments.size(); i++) { Variable fArg = functionParams.get(i); Predicate inferredRefinement = fArg.getRefinement(); - CtExpression e = invocationParams.get(i); - if (e instanceof CtVariableRead) { - CtVariableRead v = (CtVariableRead) e; + CtExpression e = arguments.get(i); + if (e instanceof CtVariableRead v) { String varName = v.getVariable().getSimpleName(); // TODO CHANGE RefinedVariable rv = context.getVariableByName(varName); String instanceName = String.format(Formats.INSTANCE, varName, context.getCounter()); @@ -433,13 +392,6 @@ private void applyRefinementsToArguments(CtElement element, List context.addInstanceToContext(instanceName, rv.getType(), inferredRefinement, element); context.addRefinementInstanceToVariable(varName, instanceName); } // TODO else's? - - // c = c.substituteVariable(fArg.getName(), map.get(fArg.getName())); - // List vars = c.getVariableNames(); - // for(String s: vars) - // if(map.containsKey(s)) - // c = c.substituteVariable(s, map.get(s)); - // rtc.checkSMT(c, invocation); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java index 69dd190e..779d68a9 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java @@ -47,7 +47,7 @@ */ public class OperationsChecker { - private TypeChecker rtc; + private final TypeChecker rtc; public OperationsChecker(TypeChecker rtc) { this.rtc = rtc; @@ -69,17 +69,13 @@ public void getBinaryOpRefinements(CtBinaryOperator operator) throws LJEr return; // Operations in annotations are not handled here if (parent instanceof CtAssignment - && ((CtAssignment) parent).getAssigned() instanceof CtVariableWrite) { - CtVariableWrite parentVar = (CtVariableWrite) ((CtAssignment) parent).getAssigned(); + && ((CtAssignment) parent).getAssigned()instanceof CtVariableWrite parentVar) { oper = getOperationRefinements(operator, parentVar, operator); } else { Predicate varLeft = getOperationRefinements(operator, left); Predicate varRight = getOperationRefinements(operator, right); oper = Predicate.createOperation(varLeft, getOperatorFromKind(operator.getKind()), varRight); - // new Predicate(String.format("(%s %s %s)", - // varLeft,,varRight)); - } String type = operator.getType().getQualifiedName(); List types = Arrays.asList(Types.IMPLEMENTED); @@ -109,15 +105,13 @@ public void getUnaryOpRefinements(CtUnaryOperator operator) throws LJErro CtExpression ex = (CtExpression) operator.getOperand(); String name = Formats.FRESH; Predicate all; - if (ex instanceof CtVariableWrite) { - CtVariableWrite w = (CtVariableWrite) ex; + if (ex instanceof CtVariableWrite w) { name = w.getVariable().getSimpleName(); all = getRefinementUnaryVariableWrite(ex, operator, w, name); rtc.checkVariableRefinements(all, name, w.getType(), operator, w.getVariable().getDeclaration()); return; - } else if (ex instanceof CtVariableRead) { - CtVariableRead var = (CtVariableRead) ex; + } else if (ex instanceof CtVariableRead var) { name = var.getVariable().getSimpleName(); // If the variable is the same, the refinements need to be changed try { @@ -137,20 +131,16 @@ public void getUnaryOpRefinements(CtUnaryOperator operator) throws LJErro } Predicate metadata = rtc.getRefinement(ex); - String newName; - if (!name.equals(Formats.FRESH)) - newName = String.format(Formats.INSTANCE, name, rtc.getContext().getCounter()); - else - newName = String.format(name, rtc.getContext().getCounter()); + String newName = !name.equals(Formats.FRESH) + ? String.format(Formats.INSTANCE, name, rtc.getContext().getCounter()) + : String.format(name, rtc.getContext().getCounter()); Predicate newMeta = metadata.substituteVariable(Keys.WILDCARD, newName); Predicate unOp = getOperatorFromKind(operator.getKind(), operator); CtElement p = operator.getParent(); Predicate opS = unOp.substituteVariable(Keys.WILDCARD, newName); - if (p instanceof CtIf) - all = opS; - else - all = Predicate.createEquals(Predicate.createVar(Keys.WILDCARD), opS); // TODO SEE IF () IN OPS IS NEEDED + all = p instanceof CtIf ? opS : Predicate.createEquals(Predicate.createVar(Keys.WILDCARD), opS); + // TODO SEE IF () IN OPS IS NEEDED rtc.getContext().addInstanceToContext(newName, ex.getType(), newMeta, operator); operator.putMetadata(Keys.REFINEMENT, all); } @@ -182,8 +172,7 @@ private Predicate getOperationRefinements(CtBinaryOperator operator, CtExpres */ private Predicate getOperationRefinements(CtBinaryOperator operator, CtVariableWrite parentVar, CtExpression element) throws LJError { - if (element instanceof CtFieldRead) { - CtFieldRead field = ((CtFieldRead) element); + if (element instanceof CtFieldRead field) { if (field.getVariable().getSimpleName().equals("length")) { String name = String.format(Formats.FRESH, rtc.getContext().getCounter()); rtc.getContext().addVarToContext(name, element.getType(), @@ -192,48 +181,41 @@ private Predicate getOperationRefinements(CtBinaryOperator operator, CtVariab } } - if (element instanceof CtVariableRead) { - CtVariableRead elemVar = (CtVariableRead) element; + if (element instanceof CtVariableRead elemVar) { String elemName = elemVar.getVariable().getSimpleName(); if (elemVar instanceof CtFieldRead) elemName = String.format(Formats.THIS, elemName); - Predicate elem_ref = rtc.getContext().getVariableRefinements(elemName); + Predicate elemRef = rtc.getContext().getVariableRefinements(elemName); String returnName = elemName; CtElement parent = operator.getParent(); // No need for specific values if (parent != null && !(parent instanceof CtIfImpl)) { - elem_ref = rtc.getRefinement(elemVar); + elemRef = rtc.getRefinement(elemVar); String newName = String.format(Formats.INSTANCE, elemName, rtc.getContext().getCounter()); - Predicate newElem_ref = elem_ref.substituteVariable(Keys.WILDCARD, newName); - // String newElem_ref = elem_ref.replace(rtc.WILD_VAR, newName); - RefinedVariable newVi = rtc.getContext().addVarToContext(newName, elemVar.getType(), newElem_ref, + Predicate newElemRef = elemRef.substituteVariable(Keys.WILDCARD, newName); + RefinedVariable newVi = rtc.getContext().addVarToContext(newName, elemVar.getType(), newElemRef, elemVar); rtc.getContext().addSpecificVariable(newVi); returnName = newName; } - Predicate e = elem_ref.substituteVariable(Keys.WILDCARD, elemName); + Predicate e = elemRef.substituteVariable(Keys.WILDCARD, elemName); rtc.getContext().addVarToContext(elemName, elemVar.getType(), e, elemVar); return Predicate.createVar(returnName); - } else if (element instanceof CtBinaryOperator) { - CtBinaryOperator binop = (CtBinaryOperator) element; + } else if (element instanceof CtBinaryOperator binop) { Predicate right = getOperationRefinements(operator, parentVar, binop.getRightHandOperand()); Predicate left = getOperationRefinements(operator, parentVar, binop.getLeftHandOperand()); - return Predicate.createOperation(left, getOperatorFromKind(binop.getKind()), right); - // Predicate(left+" "+ getOperatorFromKind(binop.getKind()) +" "+ right); - } else if (element instanceof CtUnaryOperator) { Predicate a = (Predicate) element.getMetadata(Keys.REFINEMENT); a = a.substituteVariable(Keys.WILDCARD, ""); String s = a.toString().replace("(", "").replace(")", "").replace("==", "").replace(" ", ""); // TODO - // IMPROVE + // TODO: IMPROVE return new Predicate(String.format("(%s)", s), element); - } else if (element instanceof CtLiteral) { - CtLiteral l = (CtLiteral) element; + } else if (element instanceof CtLiteral l) { if (l.getType().getQualifiedName().equals("java.lang.String")) { // skip strings return new Predicate(); @@ -243,20 +225,19 @@ private Predicate getOperationRefinements(CtBinaryOperator operator, CtVariab return new Predicate(l.getValue().toString(), element); - } else if (element instanceof CtInvocation) { - CtInvocation inv = (CtInvocation) element; + } else if (element instanceof CtInvocation inv) { CtExecutable method = inv.getExecutable().getDeclaration(); if (method == null) - return getOperationRefinementFromExternalLib(inv, operator); + return getOperationRefinementFromExternalLib(inv); // Get function refinements with non_used variables String met = ((CtClass) method.getParent()).getQualifiedName(); // TODO check RefinedFunction fi = rtc.getContext().getFunction(method.getSimpleName(), met, inv.getArguments().size()); - Predicate innerRefs = fi.getRenamedRefinements(rtc.getContext(), inv); // TODO REVER!! + Predicate innerRefs = fi.getRenamedRefinements(rtc.getContext(), inv); // TODO REVIEW!! + // Substitute _ by the variable that we send String newName = String.format(Formats.FRESH, rtc.getContext().getCounter()); - innerRefs = innerRefs.substituteVariable(Keys.WILDCARD, newName); rtc.getContext().addVarToContext(newName, fi.getType(), innerRefs, inv); return new Predicate(newName, inv); // Return variable that represents the invocation @@ -265,8 +246,7 @@ private Predicate getOperationRefinements(CtBinaryOperator operator, CtVariab // TODO Maybe add cases } - private Predicate getOperationRefinementFromExternalLib(CtInvocation inv, CtBinaryOperator operator) - throws LJError { + private Predicate getOperationRefinementFromExternalLib(CtInvocation inv) throws LJError { CtExpression t = inv.getTarget(); if (t instanceof CtVariableRead) { @@ -280,11 +260,12 @@ private Predicate getOperationRefinementFromExternalLib(CtInvocation inv, CtB String methodInClassName = typeNotParametrized + "." + simpleName; RefinedFunction fi = rtc.getContext().getFunction(methodInClassName, typeNotParametrized, inv.getArguments().size()); - Predicate innerRefs = fi.getRenamedRefinements(rtc.getContext(), inv); // TODO REVER!! + Predicate innerRefs = fi.getRenamedRefinements(rtc.getContext(), inv); // TODO REVIEW!! // Substitute _ by the variable that we send String newName = String.format(Formats.FRESH, rtc.getContext().getCounter()); innerRefs = innerRefs.substituteVariable(Keys.WILDCARD, newName); + // change this for the current instance RefinedVariable r = rtc.getContext().getVariableByName(v.getSimpleName()); if (r instanceof Variable) { @@ -300,7 +281,7 @@ private Predicate getOperationRefinementFromExternalLib(CtInvocation inv, CtB } /** - * Retrieves the refinements for the a variable write inside unary operation + * Retrieves the refinements for the variable write inside unary operation * * @param * @param ex @@ -333,7 +314,7 @@ private Predicate getRefinementUnaryVariableWrite(CtExpression ex, CtUnar * * @param kind * - * @return + * @return operator string */ private String getOperatorFromKind(BinaryOperatorKind kind) { return switch (kind) { @@ -356,15 +337,12 @@ private String getOperatorFromKind(BinaryOperatorKind kind) { private Predicate getOperatorFromKind(UnaryOperatorKind kind, CtElement elem) throws LJError { String ret = switch (kind) { - case POSTINC -> Keys.WILDCARD + " + 1"; - case POSTDEC -> Keys.WILDCARD + " - 1"; - case PREINC -> Keys.WILDCARD + " + 1"; - case PREDEC -> Keys.WILDCARD + " - 1"; + case POSTINC, PREINC -> Keys.WILDCARD + " + 1"; + case POSTDEC, PREDEC -> Keys.WILDCARD + " - 1"; case COMPL -> "(32 & " + Keys.WILDCARD + ")"; case NOT -> "!" + Keys.WILDCARD; case POS -> "0 + " + Keys.WILDCARD; case NEG -> "-" + Keys.WILDCARD; - default -> throw new CustomError(kind + "operation not supported"); }; return new Predicate(ret, elem); }; diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java index a1b5ae84..3fe01862 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java @@ -3,7 +3,6 @@ import java.util.HashMap; import java.util.List; import java.util.Optional; -import java.util.stream.Collectors; import liquidjava.diagnostics.errors.LJError; import liquidjava.processor.context.ObjectState; @@ -18,6 +17,7 @@ import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtMethod; import spoon.reflect.declaration.CtParameter; +import spoon.reflect.declaration.CtTypeInformation; import spoon.reflect.reference.CtTypeReference; public class AuxHierarchyRefinementsPassage { @@ -26,11 +26,9 @@ public static void checkFunctionInSupertypes(CtClass klass, CtMethod m TypeChecker tc) throws LJError { String name = method.getSimpleName(); int size = method.getParameters().size(); - if (klass.getSuperInterfaces().size() > 0) { // implemented interfaces + if (!klass.getSuperInterfaces().isEmpty()) { // implemented interfaces Optional superFunction = functionInInterface(klass, name, size, tc); - if (superFunction.isPresent()) { - transferRefinements(superFunction.get(), f, method, tc); - } + superFunction.ifPresent(refinedFunction -> transferRefinements(refinedFunction, f, method, tc)); } if (klass.getSuperclass() != null) { // extended class CtTypeReference t = klass.getSuperclass(); @@ -85,7 +83,7 @@ static void transferArgumentsRefinements(RefinedFunction superFunction, RefinedF } else { boolean ok = tc.checksStateSMT(superArgRef, argRef, params.get(i).getPosition()); if (!ok) { - tc.createError(method, argRef, superArgRef, ""); + tc.createError(method, argRef, superArgRef); } } } @@ -117,8 +115,7 @@ static void transferReturnRefinement(RefinedFunction superFunction, RefinedFunct static Optional functionInInterface(CtClass klass, String simpleName, int size, TypeChecker tc) { List lrf = tc.getContext().getAllMethodsWithNameSize(simpleName, size); - List st = klass.getSuperInterfaces().stream().map(p -> p.getQualifiedName()) - .collect(Collectors.toList()); + List st = klass.getSuperInterfaces().stream().map(CtTypeInformation::getQualifiedName).toList(); for (RefinedFunction rf : lrf) { if (st.contains(rf.getTargetClass())) return Optional.of(rf); // TODO only works for 1 interface @@ -142,16 +139,15 @@ private static void transferStateRefinements(RefinedFunction superFunction, Refi String thisName = String.format(Formats.FRESH, tc.getContext().getCounter()); createVariableInContext(thisName, tc, subFunction, superFunction, method.getParameters().get(i)); - Predicate superConst = matchVariableNames(Keys.THIS, thisName, superState.getFrom()); - Predicate subConst = matchVariableNames(Keys.THIS, thisName, superFunction, subFunction, - subState.getFrom()); + Predicate superConst = matchVariableNames(thisName, superState.getFrom()); + Predicate subConst = matchVariableNames(thisName, superFunction, subFunction, subState.getFrom()); // fromSup <: fromSub <==> fromSup is sub type and fromSub is expectedType tc.checkStateSMT(superConst, subConst, method, "FROM State from Superclass must be subtype of FROM State from Subclass"); - superConst = matchVariableNames(Keys.THIS, thisName, superState.getTo()); - subConst = matchVariableNames(Keys.THIS, thisName, superFunction, subFunction, subState.getTo()); + superConst = matchVariableNames(thisName, superState.getTo()); + subConst = matchVariableNames(thisName, superFunction, subFunction, subState.getTo()); // toSub <: toSup <==> ToSub is sub type and toSup is expectedType tc.checkStateSMT(subConst, superConst, method, "TO State from Subclass must be subtype of TO State from Superclass"); @@ -166,25 +162,22 @@ private static void createVariableInContext(String thisName, TypeChecker tc, Ref RefinedVariable rv = tc.getContext().addVarToContext(thisName, Utils.getType(subFunction.getTargetClass(), tc.getFactory()), new Predicate(), ctParameter); rv.addSuperType(Utils.getType(superFunction.getTargetClass(), tc.getFactory())); // TODO: change: this only - // works - // for one superclass - + // works for one superclass } /** * Changes all variable names in c to match the names of superFunction * - * @param fromName * @param thisName * @param superFunction * @param subFunction * @param c - * + * * @return */ - private static Predicate matchVariableNames(String fromName, String thisName, RefinedFunction superFunction, + private static Predicate matchVariableNames(String thisName, RefinedFunction superFunction, RefinedFunction subFunction, Predicate c) { - Predicate nc = c.substituteVariable(fromName, thisName); + Predicate nc = c.substituteVariable(Keys.THIS, thisName); List superArgs = superFunction.getArguments(); List subArgs = subFunction.getArguments(); for (int i = 0; i < subArgs.size(); i++) { @@ -193,7 +186,7 @@ private static Predicate matchVariableNames(String fromName, String thisName, Re return nc; } - private static Predicate matchVariableNames(String fromName, String thisName, Predicate c) { - return c.substituteVariable(fromName, thisName); + private static Predicate matchVariableNames(String thisName, Predicate c) { + return c.substituteVariable(Keys.THIS, thisName); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java index 6d6de6d1..44164419 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java @@ -41,7 +41,7 @@ public static void handleConstructorState(CtConstructor c, RefinedFunction f, throw new IllegalConstructorTransitionError(from); } } - setConstructorStates(f, an, tc, c); // f.setState(an, context.getGhosts(), c); + setConstructorStates(f, an, c); } else { setDefaultState(f, tc); } @@ -52,12 +52,11 @@ public static void handleConstructorState(CtConstructor c, RefinedFunction f, * * @param f * @param anns - * @param tc * @param element */ @SuppressWarnings({ "rawtypes" }) private static void setConstructorStates(RefinedFunction f, List> anns, - TypeChecker tc, CtElement element) throws LJError { + CtElement element) throws LJError { List l = new ArrayList<>(); for (CtAnnotation an : anns) { Map m = an.getAllValues(); @@ -116,13 +115,11 @@ public static void setDefaultState(RefinedFunction f, TypeChecker tc) { private static List getDifferentSets(TypeChecker tc, String klassQualified) { List sets = new ArrayList<>(); List l = getGhostStatesFor(klassQualified, tc); - if (l != null) { - for (GhostState g : l) { - if (g.getParent() == null) { - sets.add(g); - } else if (!sets.contains(g.getParent())) { - sets.add(g.getParent()); - } + for (GhostState g : l) { + if (g.getParent() == null) { + sets.add(g); + } else if (!sets.contains(g.getParent())) { + sets.add(g.getParent()); } } return sets; @@ -141,8 +138,6 @@ public static void handleMethodState(CtMethod method, RefinedFunction f, Type if (!an.isEmpty()) { setFunctionStates(f, an, tc, method, prefix); } - // f.setState(an, context.getGhosts(), method); - } /** @@ -201,8 +196,8 @@ private static ObjectState getStates(CtAnnotation ctAnnota * * @return the created predicate */ - private static Predicate createStatePredicate(String value, /* RefinedFunction f */ String targetClass, - TypeChecker tc, CtElement e, boolean isTo, String prefix) throws LJError { + private static Predicate createStatePredicate(String value, String targetClass, TypeChecker tc, CtElement e, + boolean isTo, String prefix) throws LJError { Predicate p = new Predicate(value, e, prefix); if (!p.getExpression().isBooleanExpression()) { throw new InvalidRefinementError(e, "State refinement transition must be a boolean expression", value); @@ -239,11 +234,11 @@ private static Predicate getMissingStates(String t, TypeChecker tc, Predicate p) for (GhostState g : gs) { if (g.getParent() == null && sets.contains(g)) { sets.remove(g); - } else if (g.getParent() != null && sets.contains(g.getParent())) { + } else if (g.getParent() != null) { sets.remove(g.getParent()); } } - return addOldStates(p, Predicate.createVar(Keys.THIS), sets, tc); + return addOldStates(p, Predicate.createVar(Keys.THIS), sets); } /** @@ -286,11 +281,10 @@ private static List getGhostStatesFor(String qualifiedClass, TypeChe * @param p * @param th * @param sets - * @param tc - * + * * @return updated predicate */ - private static Predicate addOldStates(Predicate p, Predicate th, List sets, TypeChecker tc) { + private static Predicate addOldStates(Predicate p, Predicate th, List sets) { Predicate c = p; for (GhostFunction gf : sets) { Predicate eq = Predicate.createEquals( // gf.name == old(gf.name(this)) @@ -314,17 +308,13 @@ private static Predicate addOldStates(Predicate p, Predicate th, List map, CtConstructorCall ctConstructorCall) { List oc = f.getToStates(); - if (oc.size() > 0) { // && !oc.get(0).isBooleanTrue()) - // ctConstructorCall.putMetadata(stateKey, oc.get(0)); + if (!oc.isEmpty()) { Predicate c = oc.get(0); for (String k : map.keySet()) { c = c.substituteVariable(k, map.get(k)); } ctConstructorCall.putMetadata(refKey, c); - // add maping to oc.get(0)-HERE - } else if (oc.size() > 1) { - // TODO: Proper Exception - throw new RuntimeException("Constructor can only have one to state, not multiple."); + // add mapping to oc.get(0)-HERE } } @@ -352,16 +342,15 @@ public static void addStateRefinements(TypeChecker tc, String varName, CtExpress * @param tc * @param f * @param target2 - * @param target2 * @param map * @param invocation */ public static void checkTargetChanges(TypeChecker tc, RefinedFunction f, CtExpression target2, Map map, CtElement invocation) throws LJError { String parentTargetName = searchFistVariableTarget(tc, target2, invocation); - VariableInstance target = getTarget(tc, invocation); + VariableInstance target = getTarget(invocation); if (target != null) { - if (f.hasStateChange() && f.getFromStates().size() > 0) { + if (f.hasStateChange() && !f.getFromStates().isEmpty()) { changeState(tc, target, f.getAllStates(), parentTargetName, map, invocation); } if (!f.hasStateChange()) { @@ -394,20 +383,17 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) throws L } String parentTargetName = ((CtVariableRead) fw.getTarget()).getVariable().getSimpleName(); - Optional invocation_callee = tc.getContext().getLastVariableInstance(parentTargetName); + Optional invocationCallee = tc.getContext().getLastVariableInstance(parentTargetName); - if (!invocation_callee.isPresent()) { + if (invocationCallee.isEmpty()) { return; } - VariableInstance vi = invocation_callee.get(); + VariableInstance vi = invocationCallee.get(); String instanceName = vi.getName(); Predicate prevState = vi.getRefinement().substituteVariable(Keys.WILDCARD, instanceName) .substituteVariable(parentTargetName, instanceName); - // StateRefinement(from="true", to="n(this)=this#n") - // ObjectState stateChange = getStates(ann, rf, tc, transitionMethod); - ObjectState stateChange = new ObjectState(); String prefix = field.getDeclaringType().getQualifiedName(); Predicate fromPredicate = createStatePredicate(stateChangeRefinementFrom, targetClass, tc, fw, false, prefix); @@ -429,7 +415,7 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) throws L Predicate transitionedState = stateChange.getTo().substituteVariable(Keys.WILDCARD, newInstanceName) .substituteVariable(Keys.THIS, newInstanceName); - transitionedState = checkOldMentions(transitionedState, instanceName, newInstanceName, tc); + transitionedState = checkOldMentions(transitionedState, instanceName, newInstanceName); // update of stata of new instance of this#n#(whatever it was + 1) VariableInstance vi2 = (VariableInstance) tc.getContext().addInstanceToContext(newInstanceName, vi.getType(), @@ -458,24 +444,17 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) throws L * @param name * @param map * @param invocation - * - * @return */ - private static Predicate changeState(TypeChecker tc, VariableInstance vi, - /* RefinedFunction f */ List stateChanges, String name, Map map, - CtElement invocation) throws LJError { + private static void changeState(TypeChecker tc, VariableInstance vi, List stateChanges, String name, + Map map, CtElement invocation) throws LJError { if (vi.getRefinement() == null) { - return new Predicate(); + return; } String instanceName = vi.getName(); Predicate prevState = vi.getRefinement().substituteVariable(Keys.WILDCARD, instanceName) .substituteVariable(name, instanceName); - // List stateChanges = f.getAllStates(); - boolean found = false; - // if(los.size() > 1) - // assertFalse("Change state only working for one method with one state",true); for (ObjectState stateChange : stateChanges) { // TODO: only working for 1 state annotation if (found) { break; @@ -500,10 +479,10 @@ private static Predicate changeState(TypeChecker tc, VariableInstance vi, for (String s : map.keySet()) { transitionedState = transitionedState.substituteVariable(s, map.get(s)); } - transitionedState = checkOldMentions(transitionedState, instanceName, newInstanceName, tc); + transitionedState = checkOldMentions(transitionedState, instanceName, newInstanceName); // update of stata of new instance of this#n#(whatever it was + 1) addInstanceWithState(tc, name, newInstanceName, vi, transitionedState, invocation); - return transitionedState; + return; } } if (!found) { // Reaches the end of stateChange no matching states @@ -512,11 +491,10 @@ private static Predicate changeState(TypeChecker tc, VariableInstance vi, String simpleInvocation = invocation.toString(); tc.createStateMismatchError(invocation, simpleInvocation, prevState, states); } - return new Predicate(); } - private static Predicate checkOldMentions(Predicate transitionedState, String instanceName, String newInstanceName, - TypeChecker tc) { + private static Predicate checkOldMentions(Predicate transitionedState, String instanceName, + String newInstanceName) { return transitionedState.changeOldMentions(instanceName, newInstanceName); } @@ -527,20 +505,16 @@ private static Predicate checkOldMentions(Predicate transitionedState, String in * @param variableInstance * @param name * @param invocation - * - * @return */ - private static Predicate sameState(TypeChecker tc, VariableInstance variableInstance, String name, - CtElement invocation) throws LJError { + private static void sameState(TypeChecker tc, VariableInstance variableInstance, String name, CtElement invocation) + throws LJError { if (variableInstance.getRefinement() != null) { String newInstanceName = String.format(Formats.INSTANCE, name, tc.getContext().getCounter()); Predicate c = variableInstance.getRefinement().substituteVariable(Keys.WILDCARD, newInstanceName) .substituteVariable(variableInstance.getName(), newInstanceName); addInstanceWithState(tc, name, newInstanceName, variableInstance, c, invocation); - return c; } - return new Predicate(); } /** @@ -552,14 +526,11 @@ private static Predicate sameState(TypeChecker tc, VariableInstance variableInst * @param prevInstance * @param transitionedState * @param invocation - * - * @return */ - private static String addInstanceWithState(TypeChecker tc, String superName, String name2, + private static void addInstanceWithState(TypeChecker tc, String superName, String name2, VariableInstance prevInstance, Predicate transitionedState, CtElement invocation) throws LJError { VariableInstance vi2 = (VariableInstance) tc.getContext().addInstanceToContext(name2, prevInstance.getType(), prevInstance.getRefinement(), invocation); - // vi2.setState(transitionedState); vi2.setRefinement(transitionedState); Context ctx = tc.getContext(); if (ctx.hasVariable(superName)) { @@ -577,9 +548,7 @@ private static String addInstanceWithState(TypeChecker tc, String superName, Str tc.getContext().addRefinementInstanceToVariable(superName, name2); } } - invocation.putMetadata(Keys.TARGET, vi2); - return name2; } /** @@ -590,14 +559,13 @@ private static String addInstanceWithState(TypeChecker tc, String superName, Str * @return the name of the parent target */ static String searchFistVariableTarget(TypeChecker tc, CtElement target2, CtElement invocation) { - if (target2 instanceof CtVariableRead) { + if (target2 instanceof CtVariableRead v) { // v--------- field read - // means invokation is in a form of `t.method(args)` - CtVariableRead v = (CtVariableRead) target2; + // means invocation is in a form of `t.method(args)` String name = v.getVariable().getSimpleName(); - Optional invocation_callee = tc.getContext().getLastVariableInstance(name); - if (invocation_callee.isPresent()) { - invocation.putMetadata(Keys.TARGET, invocation_callee.get()); + Optional invocationCallee = tc.getContext().getLastVariableInstance(name); + if (invocationCallee.isPresent()) { + invocation.putMetadata(Keys.TARGET, invocationCallee.get()); } else if (target2.getMetadata(Keys.TARGET) == null) { RefinedVariable var = tc.getContext().getVariableByName(name); String nName = String.format(Formats.INSTANCE, name, tc.getContext().getCounter()); @@ -609,17 +577,17 @@ static String searchFistVariableTarget(TypeChecker tc, CtElement target2, CtElem return name; } else if (target2.getMetadata(Keys.TARGET) != null) { - // invokation is in + // invocation is in // who did put the metadata here then? - VariableInstance target2_vi = (VariableInstance) target2.getMetadata(Keys.TARGET); - Optional v = target2_vi.getParent(); - invocation.putMetadata(Keys.TARGET, target2_vi); - return v.map(Refined::getName).orElse(target2_vi.getName()); + VariableInstance target2Vi = (VariableInstance) target2.getMetadata(Keys.TARGET); + Optional v = target2Vi.getParent(); + invocation.putMetadata(Keys.TARGET, target2Vi); + return v.map(Refined::getName).orElse(target2Vi.getName()); } return null; } - static VariableInstance getTarget(TypeChecker tc, CtElement invocation) { + static VariableInstance getTarget(CtElement invocation) { if (invocation.getMetadata(Keys.TARGET) != null) { return (VariableInstance) invocation.getMetadata(Keys.TARGET); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/BuiltinFunctionPredicate.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/BuiltinFunctionPredicate.java index be9db207..36dfc6aa 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/BuiltinFunctionPredicate.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/BuiltinFunctionPredicate.java @@ -13,8 +13,7 @@ public static BuiltinFunctionPredicate length(String param, CtElement elem) thro return new BuiltinFunctionPredicate(elem, "length", param); } - public static BuiltinFunctionPredicate addToIndex(String array, String index, String value, CtElement elem) - throws LJError { + public static BuiltinFunctionPredicate addToIndex(String index, String value, CtElement elem) throws LJError { return new BuiltinFunctionPredicate(elem, "addToIndex", index, value); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java index 6d10d4ec..ed490839 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java @@ -10,6 +10,7 @@ import liquidjava.diagnostics.errors.SyntaxError; import liquidjava.processor.context.AliasWrapper; import liquidjava.processor.context.Context; +import liquidjava.processor.context.GhostFunction; import liquidjava.processor.context.GhostState; import liquidjava.processor.facade.AliasDTO; import liquidjava.rj_language.ast.BinaryExpression; @@ -54,7 +55,6 @@ public Predicate() { * * @param ref * @param element - * @param e */ public Predicate(String ref, CtElement element) throws LJError { this(ref, element, element.getParent(CtType.class).getQualifiedName()); @@ -65,7 +65,6 @@ public Predicate(String ref, CtElement element) throws LJError { * * @param ref * @param element - * @param e * @param prefix */ public Predicate(String ref, CtElement element, String prefix) throws LJError { @@ -126,7 +125,7 @@ public List getVariableNames() { public List getStateInvocations(List lgs) { if (lgs == null) return new ArrayList<>(); - List all = lgs.stream().map(p -> p.getQualifiedName()).collect(Collectors.toList()); + List all = lgs.stream().map(GhostFunction::getQualifiedName).collect(Collectors.toList()); List toAdd = new ArrayList<>(); exp.getStateInvocations(toAdd, all); @@ -150,29 +149,6 @@ public Predicate changeOldMentions(String previousName, String newName) { return new Predicate(e); } - public List getOldVariableNames() { - List ls = new ArrayList<>(); - expressionGetOldVariableNames(this.exp, ls); - return ls; - } - - private void expressionGetOldVariableNames(Expression exp, List ls) { - if (exp instanceof FunctionInvocation) { - FunctionInvocation fi = (FunctionInvocation) exp; - if (fi.getName().equals(Keys.OLD)) { - List le = fi.getArgs(); - for (Expression e : le) { - if (e instanceof Var) - ls.add(((Var) e).getName()); - } - } - } - if (exp.hasChildren()) { - for (var ch : exp.getChildren()) - expressionGetOldVariableNames(ch, ls); - } - } - public Predicate changeStatesToRefinements(List ghostState, String[] toChange) throws LJError { Map nameRefinementMap = new HashMap<>(); for (GhostState gs : ghostState) { @@ -212,7 +188,7 @@ public ValDerivationNode simplify() { } private static boolean isBooleanLiteral(Expression expr, boolean value) { - return expr instanceof LiteralBoolean && ((LiteralBoolean) expr).isBooleanTrue() == value; + return expr instanceof LiteralBoolean && expr.isBooleanTrue() == value; } public static Predicate createConjunction(Predicate c1, Predicate c2) { @@ -228,19 +204,6 @@ public static Predicate createConjunction(Predicate c1, Predicate c2) { return new Predicate(new BinaryExpression(c1.getExpression(), Ops.AND, c2.getExpression())); } - public static Predicate createDisjunction(Predicate c1, Predicate c2) { - // simplification: (false || x) = x, (true || x) = true - if (isBooleanLiteral(c1.getExpression(), false)) - return c2; - if (isBooleanLiteral(c2.getExpression(), false)) - return c1; - if (isBooleanLiteral(c1.getExpression(), true)) - return c1; - if (isBooleanLiteral(c2.getExpression(), true)) - return c2; - return new Predicate(new BinaryExpression(c1.getExpression(), Ops.OR, c2.getExpression())); - } - public static Predicate createEquals(Predicate c1, Predicate c2) { return new Predicate(new BinaryExpression(c1.getExpression(), Ops.EQ, c2.getExpression())); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java index 74e54cce..13b68ba1 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java @@ -30,7 +30,7 @@ public T accept(ExpressionVisitor visitor) throws Exception { @Override public String toString() { - return name + "(" + getArgs().stream().map(p -> p.toString()).collect(Collectors.joining(", ")) + ")"; + return name + "(" + getArgs().stream().map(Expression::toString).collect(Collectors.joining(", ")) + ")"; } @Override @@ -88,10 +88,9 @@ public boolean equals(Object obj) { } else if (!getArgs().equals(other.getArgs())) return false; if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - return true; + return other.name == null; + } else { + return name.equals(other.name); + } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java index 2cc30c6b..9e25e644 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java @@ -6,7 +6,7 @@ public class BinaryExpression extends Expression { - private String op; + private final String op; public BinaryExpression(Expression e1, String op, Expression e2) { this.op = op; @@ -109,10 +109,9 @@ public boolean equals(Object obj) { } else if (!getSecondOperand().equals(other.getSecondOperand())) return false; if (op == null) { - if (other.op != null) - return false; - } else if (!op.equals(other.op)) - return false; - return true; + return other.op == null; + } else { + return op.equals(other.op); + } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java index 7a7bf0ab..48441251 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java @@ -51,7 +51,7 @@ public List getChildren() { } public boolean hasChildren() { - return children.size() > 0; + return !children.isEmpty(); } public void setChild(int index, Expression element) { @@ -114,15 +114,15 @@ private void auxSubstitute(Expression from, Expression to) { /** * Substitutes the function call with the given parameter to the expression e * - * @param s - * @param e + * @param functionName + * @param parameters + * @param sub */ public void substituteFunction(String functionName, List parameters, Expression sub) { if (hasChildren()) for (int i = 0; i < children.size(); i++) { Expression exp = children.get(i); - if (exp instanceof FunctionInvocation) { - FunctionInvocation fi = (FunctionInvocation) exp; + if (exp instanceof FunctionInvocation fi) { if (fi.name.equals(functionName) && fi.argumentsEqual(parameters)) { // substitute by sub in parent setChild(i, sub); @@ -134,14 +134,12 @@ public void substituteFunction(String functionName, List parameters, public Expression substituteState(Map subMap, String[] toChange) { Expression e = clone(); - if (this instanceof FunctionInvocation) { - FunctionInvocation fi = (FunctionInvocation) this; + if (this instanceof FunctionInvocation fi) { String key = fi.name; String simple = Utils.getSimpleName(key); boolean has = subMap.containsKey(key) || subMap.containsKey(simple); - if (has && fi.children.size() == 1 && fi.children.get(0) instanceof Var) { // object + if (has && fi.children.size() == 1 && fi.children.get(0)instanceof Var v) { // object // state - Var v = (Var) fi.children.get(0); Expression sub = (subMap.containsKey(key) ? subMap.get(key) : subMap.get(simple)).clone(); for (String s : toChange) { sub = sub.substitute(new Var(s), v); @@ -158,14 +156,12 @@ private void auxSubstituteState(Map subMap, String[] toChang if (hasChildren()) { for (int i = 0; i < children.size(); i++) { Expression exp = children.get(i); - if (exp instanceof FunctionInvocation) { - FunctionInvocation fi = (FunctionInvocation) exp; + if (exp instanceof FunctionInvocation fi) { String key = fi.name; String simple = Utils.getSimpleName(key); boolean has = subMap.containsKey(key) || subMap.containsKey(simple); - if (has && fi.children.size() == 1 && fi.children.get(0) instanceof Var) { // object + if (has && fi.children.size() == 1 && fi.children.get(0)instanceof Var v) { // object // state - Var v = (Var) fi.children.get(0); Expression sub = (subMap.containsKey(key) ? subMap.get(key) : subMap.get(simple)).clone(); for (String s : toChange) { sub = sub.substitute(new Var(s), v); @@ -181,8 +177,7 @@ private void auxSubstituteState(Map subMap, String[] toChang public Expression changeAlias(Map alias, Context ctx, Factory f) throws Exception { Expression e = clone(); - if (this instanceof AliasInvocation) { - AliasInvocation ai = (AliasInvocation) this; + if (this instanceof AliasInvocation ai) { if (alias.containsKey(ai.name)) { // object state AliasDTO dto = alias.get(ai.name); Expression sub = dto.getExpression().clone(); @@ -209,8 +204,7 @@ public Expression changeAlias(Map alias, Context ctx, Factory private void auxChangeAlias(Map alias, Context ctx, Factory f) throws Exception { if (hasChildren()) for (int i = 0; i < children.size(); i++) { - if (children.get(i) instanceof AliasInvocation) { - AliasInvocation ai = (AliasInvocation) children.get(i); + if (children.get(i)instanceof AliasInvocation ai) { if (!alias.containsKey(ai.name)) throw new Exception("Alias '" + ai.getName() + "' not found"); AliasDTO dto = alias.get(ai.name); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java index 44d91a75..f4dc3b9a 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java @@ -36,7 +36,7 @@ public T accept(ExpressionVisitor visitor) throws Exception { @Override public String toString() { - return name + "(" + getArgs().stream().map(p -> p.toString()).collect(Collectors.joining(",")) + ")"; + return name + "(" + getArgs().stream().map(Expression::toString).collect(Collectors.joining(",")) + ")"; } @Override @@ -117,10 +117,9 @@ public boolean equals(Object obj) { } else if (!getArgs().equals(other.getArgs())) return false; if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - return true; + return other.name == null; + } else { + return name.equals(other.name); + } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java index f2517fb2..24e45fb4 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java @@ -66,10 +66,9 @@ public boolean equals(Object obj) { return false; GroupExpression other = (GroupExpression) obj; if (getExpression() == null) { - if (other.getExpression() != null) - return false; - } else if (!getExpression().equals(other.getExpression())) - return false; - return true; + return other.getExpression() == null; + } else { + return getExpression().equals(other.getExpression()); + } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java index 6090c894..5b72ce6a 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java @@ -94,10 +94,9 @@ public boolean equals(Object obj) { } else if (!getElse().equals(other.getElse())) return false; if (getThen() == null) { - if (other.getThen() != null) - return false; - } else if (!getThen().equals(other.getThen())) - return false; - return true; + return other.getThen() == null; + } else { + return getThen().equals(other.getThen()); + } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralBoolean.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralBoolean.java index 85127fff..8da1091c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralBoolean.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralBoolean.java @@ -33,7 +33,6 @@ public void getVariableNames(List toAdd) { @Override public void getStateInvocations(List toAdd, List all) { // end leaf - } @Override @@ -63,8 +62,6 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; LiteralBoolean other = (LiteralBoolean) obj; - if (value != other.value) - return false; - return true; + return value == other.value; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralInt.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralInt.java index 582ecd92..993c177b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralInt.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralInt.java @@ -6,7 +6,7 @@ public class LiteralInt extends Expression { - private int value; + private final int value; public LiteralInt(int v) { value = v; @@ -32,13 +32,11 @@ public int getValue() { @Override public void getVariableNames(List toAdd) { // end leaf - } @Override public void getStateInvocations(List toAdd, List all) { // end leaf - } @Override @@ -68,8 +66,6 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; LiteralInt other = (LiteralInt) obj; - if (value != other.value) - return false; - return true; + return value == other.value; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralReal.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralReal.java index c80bf750..7913438d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralReal.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralReal.java @@ -6,7 +6,7 @@ public class LiteralReal extends Expression { - private double value; + private final double value; public LiteralReal(double v) { value = v; @@ -32,13 +32,11 @@ public double getValue() { @Override public void getVariableNames(List toAdd) { // end leaf - } @Override public void getStateInvocations(List toAdd, List all) { // end leaf - } @Override @@ -55,9 +53,7 @@ public boolean isBooleanTrue() { public int hashCode() { final int prime = 31; int result = 1; - long temp; - temp = Double.doubleToLongBits(value); - result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + Double.hashCode(value); return result; } @@ -70,8 +66,6 @@ public boolean equals(Object obj) { if (getClass() != obj.getClass()) return false; LiteralReal other = (LiteralReal) obj; - if (Double.doubleToLongBits(value) != Double.doubleToLongBits(other.value)) - return false; - return true; + return Double.doubleToLongBits(value) == Double.doubleToLongBits(other.value); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralString.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralString.java index 38577671..1382535c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralString.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralString.java @@ -5,7 +5,8 @@ import liquidjava.rj_language.visitors.ExpressionVisitor; public class LiteralString extends Expression { - private String value; + + private final String value; public LiteralString(String v) { value = v; @@ -58,10 +59,9 @@ public boolean equals(Object obj) { return false; LiteralString other = (LiteralString) obj; if (value == null) { - if (other.value != null) - return false; - } else if (!value.equals(other.value)) - return false; - return true; + return other.value == null; + } else { + return value.equals(other.value); + } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java index 82218ec3..b805df84 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java @@ -6,7 +6,7 @@ public class UnaryExpression extends Expression { - private String op; + private final String op; public UnaryExpression(String op, Expression e) { this.op = op; @@ -80,10 +80,9 @@ public boolean equals(Object obj) { } else if (!getExpression().equals(other.getExpression())) return false; if (op == null) { - if (other.op != null) - return false; - } else if (!op.equals(other.op)) - return false; - return true; + return other.op == null; + } else { + return op.equals(other.op); + } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Var.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Var.java index 2ab08d4f..e89365c6 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Var.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Var.java @@ -6,7 +6,7 @@ public class Var extends Expression { - private String name; + private final String name; public Var(String name) { this.name = name; @@ -64,10 +64,9 @@ public boolean equals(Object obj) { return false; Var other = (Var) obj; if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - return true; + return other.name == null; + } else { + return name.equals(other.name); + } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java index 4bcf17d4..764793e0 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java @@ -22,12 +22,6 @@ public class TypeInfer { - public static boolean checkCompatibleType(Expression e1, Expression e2, Context ctx, Factory factory) { - Optional> t1 = getType(ctx, factory, e1); - Optional> t2 = getType(ctx, factory, e2); - return t1.isPresent() && t2.isPresent() && t1.get().equals(t2.get()); - } - public static boolean checkCompatibleType(String type, Expression e, Context ctx, Factory factory) { Optional> t1 = getType(ctx, factory, e); CtTypeReference t2 = Utils.getType(type, factory); @@ -44,7 +38,7 @@ else if (e instanceof LiteralReal) else if (e instanceof LiteralBoolean) return boolType(factory); else if (e instanceof Var) - return varType(ctx, factory, (Var) e); + return varType(ctx, (Var) e); else if (e instanceof UnaryExpression) return unaryType(ctx, factory, (UnaryExpression) e); else if (e instanceof Ite) @@ -54,14 +48,14 @@ else if (e instanceof BinaryExpression) else if (e instanceof GroupExpression) return getType(ctx, factory, ((GroupExpression) e).getExpression()); else if (e instanceof FunctionInvocation) - return functionType(ctx, factory, (FunctionInvocation) e); + return functionType(ctx, (FunctionInvocation) e); else if (e instanceof AliasInvocation) return boolType(factory); return Optional.empty(); } - private static Optional> varType(Context ctx, Factory factory, Var v) { + private static Optional> varType(Context ctx, Var v) { String name = v.getName(); if (!ctx.hasVariable(name)) return Optional.empty(); @@ -83,7 +77,7 @@ else if (e.isBooleanOperation()) { // >, >=, <, <=, ==, != } else if (e.isArithmeticOperation()) { Optional> t1 = getType(ctx, factory, e.getFirstOperand()); Optional> t2 = getType(ctx, factory, e.getSecondOperand()); - if (!t1.isPresent() || !t2.isPresent()) + if (t1.isEmpty() || t2.isEmpty()) return Optional.empty(); if (t1.get().equals(t2.get())) return t1; @@ -91,12 +85,12 @@ else if (e.isBooleanOperation()) { // >, >=, <, <=, ==, != throw new NotImplementedException( "To implement in TypeInfer: Binary type, arithmetic with different arg types"); } - return null; + return Optional.empty(); } - private static Optional> functionType(Context ctx, Factory factory, FunctionInvocation e) { + private static Optional> functionType(Context ctx, FunctionInvocation e) { Optional gh = ctx.getGhosts().stream().filter(g -> g.matches(e.getName())).findAny(); - return gh.map(i -> i.getReturnType()); + return gh.map(GhostFunction::getReturnType); } private static Optional> boolType(Factory factory) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ConstantFolding.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ConstantFolding.java index 0d5fe242..b52f8eb7 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ConstantFolding.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ConstantFolding.java @@ -26,8 +26,7 @@ public static ValDerivationNode fold(ValDerivationNode node) { if (exp instanceof UnaryExpression) return foldUnary(node); - if (exp instanceof GroupExpression) { - GroupExpression group = (GroupExpression) exp; + if (exp instanceof GroupExpression group) { if (group.getChildren().size() == 1) { return fold(new ValDerivationNode(group.getChildren().get(0), node.getOrigin())); } @@ -45,9 +44,8 @@ private static ValDerivationNode foldBinary(ValDerivationNode node) { // fold child nodes ValDerivationNode leftNode; ValDerivationNode rightNode; - if (parent instanceof BinaryDerivationNode) { + if (parent instanceof BinaryDerivationNode binaryOrigin) { // has origin (from constant propagation) - BinaryDerivationNode binaryOrigin = (BinaryDerivationNode) parent; leftNode = fold(binaryOrigin.getLeft()); rightNode = fold(binaryOrigin.getRight()); } else { @@ -129,8 +127,8 @@ else if ((left instanceof LiteralInt && right instanceof LiteralReal) } // bool and bool else if (left instanceof LiteralBoolean && right instanceof LiteralBoolean) { - boolean l = ((LiteralBoolean) left).isBooleanTrue(); - boolean r = ((LiteralBoolean) right).isBooleanTrue(); + boolean l = left.isBooleanTrue(); + boolean r = right.isBooleanTrue(); Expression res = switch (op) { case "&&" -> new LiteralBoolean(l && r); case "||" -> new LiteralBoolean(l || r); @@ -158,9 +156,8 @@ private static ValDerivationNode foldUnary(ValDerivationNode node) { // fold child node ValDerivationNode operandNode; - if (parent instanceof UnaryDerivationNode) { + if (parent instanceof UnaryDerivationNode unaryOrigin) { // has origin (from constant propagation) - UnaryDerivationNode unaryOrigin = (UnaryDerivationNode) parent; operandNode = fold(unaryOrigin.getOperand()); } else { // no origin @@ -173,7 +170,7 @@ private static ValDerivationNode foldUnary(ValDerivationNode node) { // unary not if ("!".equals(operator) && operand instanceof LiteralBoolean) { // !true => false, !false => true - boolean value = ((LiteralBoolean) operand).isBooleanTrue(); + boolean value = operand.isBooleanTrue(); Expression res = new LiteralBoolean(!value); return new ValDerivationNode(res, new UnaryDerivationNode(operandNode, operator)); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ConstantPropagation.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ConstantPropagation.java index a72a9b33..5c74897f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ConstantPropagation.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ConstantPropagation.java @@ -30,8 +30,7 @@ public static ValDerivationNode propagate(Expression exp) { private static ValDerivationNode propagateRecursive(Expression exp, Map subs) { // substitute variable - if (exp instanceof Var) { - Var var = (Var) exp; + if (exp instanceof Var var) { String name = var.getName(); Expression value = subs.get(name); // substitution @@ -43,8 +42,7 @@ private static ValDerivationNode propagateRecursive(Expression exp, Map resolve(Expression exp) { // if the expression is just a single equality (not a conjunction) don't extract it // this avoids creating tautologies like "1 == 1" after substitution, which are then simplified to "true" - if (exp instanceof BinaryExpression) { - BinaryExpression be = (BinaryExpression) exp; + if (exp instanceof BinaryExpression be) { if ("==".equals(be.getOperator())) { return new HashMap<>(); } @@ -34,10 +33,9 @@ public static Map resolve(Expression exp) { * Modifies the given map in place */ private static void resolveRecursive(Expression exp, Map map) { - if (!(exp instanceof BinaryExpression)) + if (!(exp instanceof BinaryExpression be)) return; - BinaryExpression be = (BinaryExpression) exp; String op = be.getOperator(); if ("&&".equals(op)) { resolveRecursive(be.getFirstOperand(), map); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java index eeb6f21d..27629839 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java @@ -45,7 +45,7 @@ public JsonElement serialize(Expression exp, Type typeOfSrc, JsonSerializationCo if (exp instanceof LiteralReal) return new JsonPrimitive(((LiteralReal) exp).getValue()); if (exp instanceof LiteralBoolean) - return new JsonPrimitive(((LiteralBoolean) exp).isBooleanTrue()); + return new JsonPrimitive(exp.isBooleanTrue()); if (exp instanceof Var) return new JsonPrimitive(((Var) exp).getName()); return new JsonPrimitive(exp.toString()); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/RJErrorListener.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/RJErrorListener.java index 28c0dc82..8720d439 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/RJErrorListener.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/RJErrorListener.java @@ -19,7 +19,7 @@ public class RJErrorListener implements ANTLRErrorListener { public RJErrorListener() { super(); errors = 0; - msgs = new ArrayList(); + msgs = new ArrayList<>(); } @Override @@ -27,8 +27,7 @@ public void syntaxError(Recognizer recognizer, Object offendingSymbol, int String msg, RecognitionException e) { // Hint for == instead of = String hint = null; - if (e instanceof LexerNoViableAltException) { - LexerNoViableAltException l = (LexerNoViableAltException) e; + if (e instanceof LexerNoViableAltException l) { char c = l.getInputStream().toString().charAt(charPositionInLine); if (c == '=') hint = "Predicates must be compared with == instead of ="; @@ -60,9 +59,10 @@ public int getErrors() { public String getMessages() { StringBuilder sb = new StringBuilder(); String pl = errors == 1 ? "" : "s"; - sb.append("Found ").append(errors).append(" error" + pl).append(", with the message" + pl + ":\n"); + sb.append("Found ").append(errors).append(" error").append(pl).append(", with the message").append(pl) + .append(":\n"); for (String s : msgs) - sb.append("* " + s + "\n"); + sb.append("* ").append(s).append("\n"); return sb.toString(); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/AliasVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/AliasVisitor.java index fe0c7d2d..4237fc4b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/AliasVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/AliasVisitor.java @@ -6,7 +6,6 @@ import liquidjava.processor.facade.AliasDTO; import liquidjava.utils.Pair; import org.antlr.v4.runtime.CodePointCharStream; -import org.antlr.v4.runtime.TokenStreamRewriter; import org.antlr.v4.runtime.misc.Interval; import org.antlr.v4.runtime.tree.ParseTree; import rj.grammar.RJParser.AliasContext; @@ -14,7 +13,6 @@ import rj.grammar.RJParser.PredContext; public class AliasVisitor { - TokenStreamRewriter rewriter; CodePointCharStream input; public AliasVisitor(CodePointCharStream input) { @@ -29,14 +27,13 @@ public AliasVisitor(CodePointCharStream input) { * @return */ public AliasDTO getAlias(ParseTree rc) { - if (rc instanceof AliasContext) { - AliasContext ac = (AliasContext) rc; + if (rc instanceof AliasContext ac) { String name = ac.ID_UPPER().getText(); String ref = getText(ac.pred()); List> args = getArgsDecl(ac.argDeclID()); - List varNames = args.stream().map(p -> p.getSecond()).collect(Collectors.toList()); - List varTypes = args.stream().map(p -> p.getFirst()).collect(Collectors.toList()); + List varNames = args.stream().map(Pair::second).collect(Collectors.toList()); + List varTypes = args.stream().map(Pair::first).collect(Collectors.toList()); return new AliasDTO(name, varTypes, varNames, ref); } else if (rc.getChildCount() > 0) { @@ -63,7 +60,7 @@ private String getText(PredContext pred) { } private List> getArgsDecl(ArgDeclIDContext argDeclID) { - List> l = new ArrayList>(); + List> l = new ArrayList<>(); auxGetArgsDecl(argDeclID, l); return l; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java index 7eca4a33..96530499 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java @@ -198,6 +198,6 @@ else if (literalContext.INT() != null) return new LiteralInt(literalContext.INT().getText()); else if (literalContext.REAL() != null) return new LiteralReal(literalContext.REAL().getText()); - throw new NotImplementedException("Error got to unexistant literal."); + throw new NotImplementedException("Literal type not implemented"); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/GhostVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/GhostVisitor.java index ac4c5495..6409740d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/GhostVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/GhostVisitor.java @@ -12,16 +12,12 @@ public class GhostVisitor { public static GhostDTO getGhostDecl(ParseTree rc) { - if (rc instanceof GhostContext) { - GhostContext gc = (GhostContext) rc; + if (rc instanceof GhostContext gc) { String type = gc.type().getText(); String name = gc.ID().getText(); List> args = getArgsDecl(gc.argDecl()); - List ls = args.stream().map(m -> m.getFirst()).collect(Collectors.toList()); - if (ls == null) - ls = new ArrayList<>(); + List ls = args.stream().map(Pair::first).collect(Collectors.toList()); return new GhostDTO(name, ls, type); - // return new Triple>>(type, name, args); } else if (rc.getChildCount() > 0) { int i = rc.getChildCount(); if (i > 0) @@ -31,7 +27,7 @@ public static GhostDTO getGhostDecl(ParseTree rc) { } private static List> getArgsDecl(ArgDeclContext argDecl) { - List> l = new ArrayList>(); + List> l = new ArrayList<>(); if (argDecl != null) auxGetArgsDecl(argDecl, l); return l; diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java b/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java index 40c025b3..a11f0639 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java @@ -80,22 +80,22 @@ public Expr visitVar(Var var) throws Exception { } @Override - public Expr visitLiteralInt(LiteralInt lit) throws Exception { + public Expr visitLiteralInt(LiteralInt lit) { return ctx.makeIntegerLiteral(lit.getValue()); } @Override - public Expr visitLiteralBoolean(LiteralBoolean lit) throws Exception { + public Expr visitLiteralBoolean(LiteralBoolean lit) { return ctx.makeBooleanLiteral(lit.isBooleanTrue()); } @Override - public Expr visitLiteralReal(LiteralReal lit) throws Exception { + public Expr visitLiteralReal(LiteralReal lit) { return ctx.makeDoubleLiteral(lit.getValue()); } @Override - public Expr visitLiteralString(LiteralString lit) throws Exception { + public Expr visitLiteralString(LiteralString lit) { return ctx.makeString(lit.toString()); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java b/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java index 6276a872..4bec8f9e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java @@ -9,18 +9,13 @@ import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.Expression; import liquidjava.smt.errors.TypeCheckError; -import spoon.reflect.cu.SourcePosition; public class SMTEvaluator { - public void verifySubtype(Predicate subRef, Predicate supRef, Context c, SourcePosition pos) - throws TypeCheckError, Exception { + public void verifySubtype(Predicate subRef, Predicate supRef, Context c) throws Exception { // Creates a parser for our SMT-ready refinement language // Discharges the verification to z3 - Predicate toVerify = Predicate.createConjunction(subRef, supRef.negate()); - // System.out.println("verification query: " + toVerify); // TODO remove - try { Expression exp = toVerify.getExpression(); Status s; @@ -28,14 +23,10 @@ public void verifySubtype(Predicate subRef, Predicate supRef, Context c, SourceP ExpressionToZ3Visitor visitor = new ExpressionToZ3Visitor(tz3); Expr e = exp.accept(visitor); s = tz3.verifyExpression(e); - if (s.equals(Status.SATISFIABLE)) { - // System.out.println("result of SMT: Not Ok!"); throw new TypeCheckError(subRef + " not a subtype of " + supRef); } } - // System.out.println("result of SMT: Ok!"); - } catch (SyntaxException e1) { System.out.println("Could not parse: " + toVerify); e1.printStackTrace(); diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorContextToZ3.java b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorContextToZ3.java index 36b167cc..c3fb32dc 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorContextToZ3.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorContextToZ3.java @@ -51,7 +51,7 @@ private static Expr getExpr(Context z3, String name, CtTypeReference type) }; } - static void addAlias(Context z3, List alias, Map aliasTranslation) { + static void addAlias(List alias, Map aliasTranslation) { for (AliasWrapper a : alias) { aliasTranslation.put(a.getName(), a); } @@ -69,9 +69,8 @@ public static void addGhostFunctions(Context z3, List ghosts, private static void addBuiltinFunctions(Context z3, Map> funcTranslation) { funcTranslation.put("length", z3.mkFuncDecl("length", getSort(z3, "int[]"), getSort(z3, "int"))); // ERRRRRRRRRRRRO!!!!!!!!!!!!! - // System.out.println("\nWorks only for int[] now! Change in future. Ignore this - // message, it is a glorified - // todo"); + // Works only for int[] now! Change in the future + // Ignore this message, it is a glorified TODO // TODO add built-in function Sort[] s = Stream.of(getSort(z3, "int[]"), getSort(z3, "int"), getSort(z3, "int")).toArray(Sort[]::new); funcTranslation.put("addToIndex", z3.mkFuncDecl("addToIndex", s, getSort(z3, "void"))); @@ -90,7 +89,6 @@ static Sort getSort(Context z3, String sort) { case "int[]" -> z3.mkArraySort(z3.mkIntSort(), z3.mkIntSort()); case "String" -> z3.getStringSort(); case "void" -> z3.mkUninterpretedSort("void"); - // case "List" -> z3.mkListSort(name, elemSort); default -> z3.mkUninterpretedSort(sort); }; } @@ -99,15 +97,13 @@ public static void addGhostStates(Context z3, List ghostState, Map> funcTranslation) { for (GhostState g : ghostState) { addGhostFunction(z3, g, funcTranslation); - // if(g.getRefinement() != null) - // premisesToAdd.add(g.getRefinement().getExpression()); } } private static void addGhostFunction(Context z3, GhostFunction gh, Map> funcTranslation) { List> paramTypes = gh.getParametersTypes(); Sort ret = getSort(z3, gh.getReturnType().toString()); - Sort[] domain = paramTypes.stream().map(t -> t.toString()).map(t -> getSort(z3, t)).toArray(Sort[]::new); + Sort[] domain = paramTypes.stream().map(Object::toString).map(t -> getSort(z3, t)).toArray(Sort[]::new); String name = gh.getQualifiedName(); funcTranslation.put(name, z3.mkFuncDecl(name, domain, ret)); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java index 79eb58cd..7c091d2b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java @@ -24,33 +24,25 @@ public class TranslatorToZ3 implements AutoCloseable { - private com.microsoft.z3.Context z3 = new com.microsoft.z3.Context(); - private Map> varTranslation = new HashMap<>(); - private Map>> varSuperTypes = new HashMap<>(); - private Map aliasTranslation = new HashMap<>(); - private Map> funcTranslation = new HashMap<>(); + private final com.microsoft.z3.Context z3 = new com.microsoft.z3.Context(); + private final Map> varTranslation = new HashMap<>(); + private final Map>> varSuperTypes = new HashMap<>(); + private final Map aliasTranslation = new HashMap<>(); // this is not being used + private final Map> funcTranslation = new HashMap<>(); public TranslatorToZ3(liquidjava.processor.context.Context c) { TranslatorContextToZ3.translateVariables(z3, c.getContext(), varTranslation); TranslatorContextToZ3.storeVariablesSubtypes(z3, c.getAllVariablesWithSupertypes(), varSuperTypes); - TranslatorContextToZ3.addAlias(z3, c.getAlias(), aliasTranslation); + TranslatorContextToZ3.addAlias(c.getAlias(), aliasTranslation); TranslatorContextToZ3.addGhostFunctions(z3, c.getGhosts(), funcTranslation); TranslatorContextToZ3.addGhostStates(z3, c.getGhostState(), funcTranslation); } @SuppressWarnings("unchecked") - public Status verifyExpression(Expr e) throws Exception { + public Status verifyExpression(Expr e) { Solver s = z3.mkSolver(); - // s.add((BoolExpr) e.eval(this)); - // for(Expression ex: premisesToAdd) - // s.add((BoolExpr) ex.eval(this)); s.add((BoolExpr) e); - Status st = s.check(); - if (st.equals(Status.SATISFIABLE)) { - // Example of values - // System.out.println(s.getModel()); - } - return st; + return s.check(); } // #####################Literals and Variables##################### @@ -76,7 +68,7 @@ public Expr makeBooleanLiteral(boolean value) { private Expr getVariableTranslation(String name) throws Exception { if (!varTranslation.containsKey(name)) - throw new NotFoundError("Variable '" + name.toString() + "' not found"); + throw new NotFoundError("Variable '" + name + "' not found"); Expr e = varTranslation.get(name); if (e == null) e = varTranslation.get(String.format("this#%s", name)); @@ -91,9 +83,9 @@ public Expr makeVariable(String name) throws Exception { public Expr makeFunctionInvocation(String name, Expr[] params) throws Exception { if (name.equals("addToIndex")) - return makeStore(name, params); + return makeStore(params); if (name.equals("getFromIndex")) - return makeSelect(name, params); + return makeSelect(params); FuncDecl fd = funcTranslation.get(name); if (fd == null) fd = resolveFunctionDeclFallback(name, params); @@ -109,11 +101,7 @@ public Expr makeFunctionInvocation(String name, Expr[] params) throws Exce if (e.getSort().equals(s[i])) params[i] = e; } - // System.out.println("Expected sort"+s[i]+"; Final sort->" - // +params[i].toString() +":"+ - // params[i].getSort()); } - return z3.mkApp(fd, params); } @@ -149,14 +137,14 @@ private FuncDecl resolveFunctionDeclFallback(String name, Expr[] params) t } @SuppressWarnings({ "unchecked", "rawtypes" }) - private Expr makeSelect(String name, Expr[] params) { + private Expr makeSelect(Expr[] params) { if (params.length == 2 && params[0] instanceof ArrayExpr) return z3.mkSelect((ArrayExpr) params[0], params[1]); return null; } @SuppressWarnings({ "unchecked", "rawtypes" }) - private Expr makeStore(String name, Expr[] params) { + private Expr makeStore(Expr[] params) { if (params.length == 3 && params[0] instanceof ArrayExpr) return z3.mkStore((ArrayExpr) params[0], params[1], params[2]); return null; @@ -222,11 +210,6 @@ public Expr makeOr(Expr eval, Expr eval2) { return z3.mkOr((BoolExpr) eval, (BoolExpr) eval2); } - // public Expr makeIf(Expr eval, Expr eval2) { - // z3.mkI - // return z3.mkOr((BoolExpr) eval, (BoolExpr) eval2); - // } - // ##################### Unary Operations ##################### @SuppressWarnings({ "unchecked", "rawtypes" }) public Expr makeMinus(Expr eval) { @@ -280,8 +263,7 @@ private FPExpr toFP(Expr e) { f = (FPExpr) e; } else if (e instanceof IntNum) f = z3.mkFP(((IntNum) e).getInt(), z3.mkFPSort64()); - else if (e instanceof IntExpr) { - IntExpr ee = (IntExpr) e; + else if (e instanceof IntExpr ee) { RealExpr re = z3.mkInt2Real(ee); f = z3.mkFPToFP(z3.mkFPRoundNearestTiesToEven(), re, z3.mkFPSort64()); } else if (e instanceof RealExpr) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/utils/Pair.java b/liquidjava-verifier/src/main/java/liquidjava/utils/Pair.java index 699f80bf..708f4cba 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/utils/Pair.java +++ b/liquidjava-verifier/src/main/java/liquidjava/utils/Pair.java @@ -1,23 +1,4 @@ package liquidjava.utils; -public class Pair { - private K k; - private V v; - - public Pair(K k, V v) { - this.k = k; - this.v = v; - } - - public K getFirst() { - return k; - } - - public V getSecond() { - return v; - } - - public String toString() { - return "Pair [" + k.toString() + ", " + v.toString() + "]"; - } +public record Pair (K first, V second) { } diff --git a/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java b/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java index b56a8cfd..275a5597 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java +++ b/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java @@ -35,15 +35,11 @@ public static String qualifyName(String prefix, String name) { return String.format("%s.%s", prefix, name); } - public static String stripParens(String s) { - return s.startsWith("(") && s.endsWith(")") ? s.substring(1, s.length() - 1) : s; - } - public static SourcePosition getRefinementAnnotationPosition(CtElement element, String refinement) { return element.getAnnotations().stream().filter(a -> { String value = a.getValue("value").toString(); String unquoted = value.substring(1, value.length() - 1); return unquoted.equals(refinement); - }).findFirst().map(a -> a.getPosition()).orElse(element.getPosition()); + }).findFirst().map(CtElement::getPosition).orElse(element.getPosition()); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/utils/constants/Patterns.java b/liquidjava-verifier/src/main/java/liquidjava/utils/constants/Patterns.java deleted file mode 100644 index 2bac52d7..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/utils/constants/Patterns.java +++ /dev/null @@ -1,8 +0,0 @@ -package liquidjava.utils.constants; - -import java.util.regex.Pattern; - -public final class Patterns { - public static final Pattern THIS = Pattern.compile("#this_\\d+"); - public static final Pattern INSTANCE = Pattern.compile("^#(.+)_[0-9]+$"); -} From 08af80425efe26171925f0109f0050f47b878adc Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 20 Nov 2025 16:53:48 +0000 Subject: [PATCH 08/25] Error Refactoring (#123) --- liquidjava-verifier/pom.xml | 2 +- .../liquidjava/diagnostics/LJDiagnostic.java | 21 ++--- .../diagnostics/errors/CustomError.java | 15 +--- .../errors/GhostInvocationError.java | 25 ------ .../IllegalConstructorTransitionError.java | 5 +- .../errors/InvalidRefinementError.java | 6 +- .../diagnostics/errors/LJError.java | 5 +- .../diagnostics/errors/NotFoundError.java | 21 ++++- .../diagnostics/errors/RefinementError.java | 8 +- .../errors/StateConflictError.java | 18 ++--- .../errors/StateRefinementError.java | 26 ++---- .../diagnostics/errors/SyntaxError.java | 2 +- .../ExternalClassNotFoundWarning.java | 2 +- .../ExternalMethodNotFoundWarning.java | 17 +++- .../diagnostics/warnings/LJWarning.java | 4 +- .../ExternalRefinementTypeChecker.java | 9 +-- .../refinement_checker/TypeChecker.java | 25 +++--- .../refinement_checker/VCChecker.java | 81 +++++++++---------- .../AuxHierarchyRefinementsPassage.java | 2 +- .../object_checkers/AuxStateHandler.java | 18 +++-- .../liquidjava/rj_language/Predicate.java | 13 +++ .../java/liquidjava/smt/TranslatorToZ3.java | 5 +- .../liquidjava/smt/errors/NotFoundError.java | 17 +++- .../java/liquidjava/utils/constants/Keys.java | 2 + 24 files changed, 173 insertions(+), 176 deletions(-) delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java diff --git a/liquidjava-verifier/pom.xml b/liquidjava-verifier/pom.xml index 5edca456..2461306e 100644 --- a/liquidjava-verifier/pom.xml +++ b/liquidjava-verifier/pom.xml @@ -11,7 +11,7 @@ io.github.liquid-java liquidjava-verifier - 0.0.2 + 0.0.4 liquidjava-verifier LiquidJava Verifier https://github.com/liquid-java/liquidjava diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java index bf7df8b0..88818dd8 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java @@ -10,16 +10,14 @@ public class LJDiagnostic extends RuntimeException { private final String title; private final String message; - private final String details; private final String file; private final ErrorPosition position; private final String accentColor; - public LJDiagnostic(String title, String message, String details, SourcePosition pos, String accentColor) { + public LJDiagnostic(String title, String message, SourcePosition pos, String accentColor) { this.title = title; this.message = message; - this.details = details; - this.file = pos != null ? pos.getFile().getPath() : null; + this.file = (pos != null && pos.getFile() != null) ? pos.getFile().getPath() : null; this.position = ErrorPosition.fromSpoonPosition(pos); this.accentColor = accentColor; } @@ -33,7 +31,7 @@ public String getMessage() { } public String getDetails() { - return details; + return ""; // to be overridden by subclasses } public ErrorPosition getPosition() { @@ -44,13 +42,17 @@ public String getFile() { return file; } + public String getAccentColor() { + return accentColor; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); // title - sb.append("\n").append(accentColor).append(title).append(": ").append(Colors.RESET) - .append(message.toLowerCase()).append("\n"); + sb.append("\n").append(accentColor).append(title).append(": ").append(Colors.RESET).append(message) + .append("\n"); // snippet String snippet = getSnippet(); @@ -59,7 +61,8 @@ public String toString() { } // details - if (details != null && !details.isEmpty()) { + String details = getDetails(); + if (!details.isEmpty()) { sb.append(" --> ").append(String.join("\n ", details.split("\n"))).append("\n"); } @@ -124,7 +127,6 @@ public boolean equals(Object obj) { return false; LJDiagnostic other = (LJDiagnostic) obj; return title.equals(other.title) && message.equals(other.message) - && ((details == null && other.details == null) || (details != null && details.equals(other.details))) && ((file == null && other.file == null) || (file != null && file.equals(other.file))) && ((position == null && other.position == null) || (position != null && position.equals(other.position))); @@ -134,7 +136,6 @@ public boolean equals(Object obj) { public int hashCode() { int result = title.hashCode(); result = 31 * result + message.hashCode(); - result = 31 * result + (details != null ? details.hashCode() : 0); result = 31 * result + (file != null ? file.hashCode() : 0); result = 31 * result + (position != null ? position.hashCode() : 0); return result; diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/CustomError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/CustomError.java index b9a5baca..94e007f0 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/CustomError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/CustomError.java @@ -1,7 +1,6 @@ package liquidjava.diagnostics.errors; import spoon.reflect.cu.SourcePosition; -import spoon.reflect.declaration.CtElement; /** * Custom error with an arbitrary message @@ -11,18 +10,10 @@ public class CustomError extends LJError { public CustomError(String message) { - super("Error", message, null, null, null); + super("Error", message, null, null); } - public CustomError(String message, SourcePosition pos) { - super("Error", message, null, pos, null); - } - - public CustomError(String message, String detail, CtElement element) { - super("Error", message, detail, element.getPosition(), null); - } - - public CustomError(String message, CtElement element) { - super("Error", message, null, element.getPosition(), null); + public CustomError(String message, SourcePosition position) { + super("Error", message, position, null); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java deleted file mode 100644 index 1c063fa0..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java +++ /dev/null @@ -1,25 +0,0 @@ -package liquidjava.diagnostics.errors; - -import liquidjava.diagnostics.TranslationTable; -import liquidjava.rj_language.ast.Expression; -import spoon.reflect.cu.SourcePosition; - -/** - * Error indicating that a ghost method invocation is invalid (e.g., has wrong arguments) - * - * @see LJError - */ -public class GhostInvocationError extends LJError { - - private final String expected; - - public GhostInvocationError(String message, SourcePosition pos, Expression expected, - TranslationTable translationTable) { - super("Ghost Invocation Error", message, "", pos, translationTable); - this.expected = expected.toSimplifiedString(); - } - - public String getExpected() { - return expected; - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java index 6ef5f6d7..ac04737d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java @@ -10,7 +10,8 @@ public class IllegalConstructorTransitionError extends LJError { public IllegalConstructorTransitionError(CtElement element) { - super("Illegal Constructor Transition Error", "Found constructor with 'from' state", - "Constructor methods should only have a 'to' state", element.getPosition(), null); + super("Illegal Constructor Transition Error", + "Found constructor with 'from' state: constructors should only have a 'to' state", + element.getPosition(), null); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java index c94ef1ff..cc14bacb 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/InvalidRefinementError.java @@ -1,6 +1,6 @@ package liquidjava.diagnostics.errors; -import spoon.reflect.declaration.CtElement; +import spoon.reflect.cu.SourcePosition; /** * Error indicating that a refinement is invalid (e.g., not a boolean expression) @@ -11,8 +11,8 @@ public class InvalidRefinementError extends LJError { private final String refinement; - public InvalidRefinementError(CtElement element, String message, String refinement) { - super("Invalid Refinement", message, "", element.getPosition(), null); + public InvalidRefinementError(SourcePosition position, String message, String refinement) { + super("Invalid Refinement", message, position, null); this.refinement = refinement; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java index 32a4e17d..e3fd5599 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java @@ -12,9 +12,8 @@ public abstract class LJError extends LJDiagnostic { private final TranslationTable translationTable; - public LJError(String title, String message, String details, SourcePosition pos, - TranslationTable translationTable) { - super(title, message, details, pos, Colors.BOLD_RED); + public LJError(String title, String message, SourcePosition pos, TranslationTable translationTable) { + super(title, message, pos, Colors.BOLD_RED); this.translationTable = translationTable != null ? translationTable : new TranslationTable(); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java index 9af41e49..e7973be2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java @@ -1,7 +1,8 @@ package liquidjava.diagnostics.errors; import liquidjava.diagnostics.TranslationTable; -import spoon.reflect.declaration.CtElement; +import liquidjava.utils.Utils; +import spoon.reflect.cu.SourcePosition; /** * Error indicating that an element referenced in a refinement was not found @@ -10,7 +11,21 @@ */ public class NotFoundError extends LJError { - public NotFoundError(CtElement element, String message, TranslationTable translationTable) { - super("Not Found Error", message, "", element.getPosition(), translationTable); + private final String name; + private final String kind; // "Variable" or "Ghost" + + public NotFoundError(SourcePosition position, String message, String name, String kind, + TranslationTable translationTable) { + super("Not Found Error", message, position, translationTable); + this.name = Utils.getSimpleName(name); + this.kind = kind; + } + + public String getName() { + return name; + } + + public String getKind() { + return kind; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java index 87fd76e9..a2c13c91 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java @@ -3,7 +3,7 @@ import liquidjava.diagnostics.TranslationTable; import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; -import spoon.reflect.declaration.CtElement; +import spoon.reflect.cu.SourcePosition; /** * Error indicating that a refinement constraint either was violated or cannot be proven @@ -15,11 +15,11 @@ public class RefinementError extends LJError { private final String expected; private final ValDerivationNode found; - public RefinementError(CtElement element, Expression expected, ValDerivationNode found, + public RefinementError(SourcePosition position, Expression expected, ValDerivationNode found, TranslationTable translationTable) { super("Refinement Error", - String.format("%s is not a subtype of %s", found.getValue(), expected.toSimplifiedString()), "", - element.getPosition(), translationTable); + String.format("%s is not a subtype of %s", found.getValue(), expected.toSimplifiedString()), position, + translationTable); this.expected = expected.toSimplifiedString(); this.found = found; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java index 1fe85155..f1cd7c1c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java @@ -2,7 +2,7 @@ import liquidjava.diagnostics.TranslationTable; import liquidjava.rj_language.ast.Expression; -import spoon.reflect.declaration.CtElement; +import spoon.reflect.cu.SourcePosition; /** * Error indicating that two disjoint states were found in a state refinement @@ -11,22 +11,16 @@ */ public class StateConflictError extends LJError { - private final String state; - private final String className; + private final String state;; - public StateConflictError(CtElement element, Expression state, String className, - TranslationTable translationTable) { - super("State Conflict Error", "Found multiple disjoint states in state transition", - "State transition can only go to one state of each state set", element.getPosition(), translationTable); + public StateConflictError(SourcePosition position, Expression state, TranslationTable translationTable) { + super("State Conflict Error", + "Found multiple disjoint states in state transition: state transition can only go to one state of each state set", + position, translationTable); this.state = state.toSimplifiedString(); - this.className = className; } public String getState() { return state; } - - public String getClassName() { - return className; - } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java index 013c64d2..508e6087 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java @@ -1,10 +1,8 @@ package liquidjava.diagnostics.errors; -import java.util.Arrays; - import liquidjava.diagnostics.TranslationTable; import liquidjava.rj_language.ast.Expression; -import spoon.reflect.declaration.CtElement; +import spoon.reflect.cu.SourcePosition; /** * Error indicating that a state refinement transition was violated @@ -13,28 +11,18 @@ */ public class StateRefinementError extends LJError { - private final String method; - private final String[] expected; + private final String expected; private final String found; - public StateRefinementError(CtElement element, String method, Expression[] expected, Expression found, + public StateRefinementError(SourcePosition position, Expression expected, Expression found, TranslationTable translationTable) { - super("State Refinement Error", "State refinement transition violation", - String.format("Expected: %s\nFound: %s", - String.join(", ", - Arrays.stream(expected).map(Expression::toSimplifiedString).toArray(String[]::new)), - found.toSimplifiedString()), - element.getPosition(), translationTable); - this.method = method; - this.expected = Arrays.stream(expected).map(Expression::toSimplifiedString).toArray(String[]::new); + super("State Refinement Error", String.format("Expected state %s but found %s", expected.toSimplifiedString(), + found.toSimplifiedString()), position, translationTable); + this.expected = expected.toSimplifiedString(); this.found = found.toSimplifiedString(); } - public String getMethod() { - return method; - } - - public String[] getExpected() { + public String getExpected() { return expected; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java index 226e6ed5..aac27123 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/SyntaxError.java @@ -16,7 +16,7 @@ public SyntaxError(String message, String refinement) { } public SyntaxError(String message, SourcePosition pos, String refinement) { - super("Syntax Error", message, "", pos, null); + super("Syntax Error", message, pos, null); this.refinement = refinement; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java index e3d1c239..86438b6b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java @@ -12,7 +12,7 @@ public class ExternalClassNotFoundWarning extends LJWarning { private final String className; public ExternalClassNotFoundWarning(CtElement element, String message, String className) { - super(message, "", element.getPosition()); + super(message, element.getPosition()); this.className = className; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java index 26ad6f51..60f20f17 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java @@ -11,12 +11,14 @@ public class ExternalMethodNotFoundWarning extends LJWarning { private final String methodName; private final String className; + private final String[] overloads; - public ExternalMethodNotFoundWarning(CtElement element, String message, String details, String methodName, - String className) { - super(message, details, element.getPosition()); + public ExternalMethodNotFoundWarning(CtElement element, String message, String methodName, String className, + String[] overloads) { + super(message, element.getPosition()); this.methodName = methodName; this.className = className; + this.overloads = overloads; } public String getMethodName() { @@ -26,4 +28,13 @@ public String getMethodName() { public String getClassName() { return className; } + + public String[] getOverloads() { + return overloads; + } + + @Override + public String getDetails() { + return overloads.length > 0 ? String.format("Available overloads:\n %s", String.join("\n ", overloads)) : ""; + } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java index 4ad3438a..29d65479 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java @@ -9,7 +9,7 @@ */ public abstract class LJWarning extends LJDiagnostic { - public LJWarning(String message, String details, SourcePosition pos) { - super("Warning", message, details, pos, Colors.BOLD_YELLOW); + public LJWarning(String message, SourcePosition pos) { + super("Warning", message, pos, Colors.BOLD_YELLOW); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java index bfbb8de3..a52ac891 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java @@ -71,21 +71,16 @@ public void visitCtMethod(CtMethod method) { String message = String.format("Could not find constructor '%s' for '%s'", method.getSignature(), prefix); String[] overloads = getOverloads(targetType, method); - String details = overloads.length == 0 ? null - : "Available constructors:\n " + String.join("\n ", overloads); - diagnostics.add( - new ExternalMethodNotFoundWarning(method, message, details, method.getSignature(), prefix)); + new ExternalMethodNotFoundWarning(method, message, method.getSignature(), prefix, overloads)); } } else { if (!methodExists(targetType, method)) { String message = String.format("Could not find method '%s %s' for '%s'", method.getType().getSimpleName(), method.getSignature(), prefix); String[] overloads = getOverloads(targetType, method); - String details = overloads.length == 0 ? null - : "Available overloads:\n " + String.join("\n ", overloads); diagnostics.add( - new ExternalMethodNotFoundWarning(method, message, details, method.getSignature(), prefix)); + new ExternalMethodNotFoundWarning(method, message, method.getSignature(), prefix, overloads)); return; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java index 3b044848..da0c6b60 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java @@ -82,8 +82,8 @@ public Optional getRefinementFromAnnotation(CtElement element) throws // check if refinement is valid if (!p.getExpression().isBooleanExpression()) { - throw new InvalidRefinementError(element, "Refinement predicate must be a boolean expression", - ref.get()); + throw new InvalidRefinementError(element.getPosition(), + "Refinement predicate must be a boolean expression", ref.get()); } constr = Optional.of(p); } @@ -115,7 +115,7 @@ private void createStateSet(CtNewArray e, int set, CtElement element) th CtLiteral s = (CtLiteral) ce; String f = s.getValue(); if (Character.isUpperCase(f.charAt(0))) { - throw new CustomError("State names must start with lowercase", s); + throw new CustomError("State names must start with lowercase", s.getPosition()); } } } @@ -152,7 +152,8 @@ private void createStateGhost(String string, CtAnnotation GhostDTO gd = RefinementsParser.getGhostDeclaration(string); if (!gd.paramTypes().isEmpty()) { throw new CustomError( - "Ghost States have the class as parameter " + "by default, no other parameters are allowed", ann); + "Ghost States have the class as parameter " + "by default, no other parameters are allowed", + ann.getPosition()); } // Set class as parameter of Ghost String qn = getQualifiedClassName(element); @@ -224,8 +225,8 @@ protected void handleAlias(String value, CtElement element) throws LJError { a.parse(path); // refinement alias must return a boolean expression if (a.getExpression() != null && !a.getExpression().isBooleanExpression()) { - throw new InvalidRefinementError(element, "Refinement alias must return a boolean expression", - value); + throw new InvalidRefinementError(element.getPosition(), + "Refinement alias must return a boolean expression", value); } AliasWrapper aw = new AliasWrapper(a, factory, klass, path); context.addAlias(aw); @@ -295,16 +296,16 @@ public boolean checksStateSMT(Predicate prevState, Predicate expectedState, Sour return vcChecker.canProcessSubtyping(prevState, expectedState, context.getGhostState(), p, factory); } - public void createError(CtElement element, Predicate expectedType, Predicate foundType) throws LJError { - vcChecker.raiseSubtypingError(element, expectedType, foundType); + public void createError(SourcePosition position, Predicate expectedType, Predicate foundType) throws LJError { + vcChecker.raiseSubtypingError(position, expectedType, foundType); } - public void createSameStateError(CtElement element, Predicate expectedType, String klass) throws LJError { - vcChecker.raiseSameStateError(element, expectedType, klass); + public void createSameStateError(SourcePosition position, Predicate expectedType, String klass) throws LJError { + vcChecker.raiseSameStateError(position, expectedType, klass); } - public void createStateMismatchError(CtElement element, String method, Predicate found, Predicate[] expected) + public void createStateMismatchError(SourcePosition position, String method, Predicate found, Predicate expected) throws LJError { - vcChecker.raiseStateMismatchError(element, method, found, expected); + vcChecker.raiseStateMismatchError(position, method, found, expected); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java index 3636ff93..4cad3a80 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java @@ -1,7 +1,6 @@ package liquidjava.processor.refinement_checker; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Set; import java.util.Stack; @@ -9,7 +8,6 @@ import java.util.stream.Collectors; import liquidjava.diagnostics.errors.CustomError; -import liquidjava.diagnostics.errors.GhostInvocationError; import liquidjava.diagnostics.errors.LJError; import liquidjava.diagnostics.errors.NotFoundError; import liquidjava.diagnostics.TranslationTable; @@ -19,7 +17,6 @@ import liquidjava.processor.VCImplication; import liquidjava.processor.context.*; import liquidjava.rj_language.Predicate; -import liquidjava.rj_language.ast.Expression; import liquidjava.smt.SMTEvaluator; import liquidjava.smt.errors.TypeCheckError; import liquidjava.utils.constants.Keys; @@ -54,14 +51,14 @@ public void processSubtyping(Predicate expectedType, List list, CtEl premises = premisesBeforeChange.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); et = expectedType.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); } catch (Exception e) { - throw new RefinementError(element, expectedType.getExpression(), premises.simplify(), map); + throw new RefinementError(element.getPosition(), expectedType.getExpression(), premises.simplify(), map); } try { smtChecking(premises, et); } catch (Exception e) { // To emit the message we use the constraints before the alias and state change - raiseError(e, premisesBeforeChange, expectedType, element, map); + raiseError(e, element.getPosition(), premisesBeforeChange, expectedType, map); } } @@ -69,11 +66,11 @@ public void processSubtyping(Predicate type, Predicate expectedType, List list, SourcePosition p, - Factory f) throws LJError { + public boolean canProcessSubtyping(Predicate type, Predicate expectedType, List list, + SourcePosition position, Factory f) throws LJError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(expectedType, lrv, mainVars); gatherVariables(type, lrv, mainVars); @@ -94,7 +91,7 @@ public boolean canProcessSubtyping(Predicate type, Predicate expectedType, List< } catch (Exception e) { return false; } - return smtChecks(premises, et, p); + return smtChecks(et, premises, position, map); } /** @@ -220,22 +217,14 @@ private void recAuxGetVars(RefinedVariable var, List newVars) { getVariablesFromContext(l, newVars, varName); } - public boolean smtChecks(Predicate found, Predicate expectedType, SourcePosition p) throws LJError { + public boolean smtChecks(Predicate expected, Predicate found, SourcePosition position, TranslationTable map) + throws LJError { try { - new SMTEvaluator().verifySubtype(found, expectedType, context); + new SMTEvaluator().verifySubtype(found, expected, context); } catch (TypeCheckError e) { return false; } catch (Exception e) { - String msg = e.getLocalizedMessage().toLowerCase(); - if (msg.contains("wrong number of arguments")) { - throw new GhostInvocationError("Wrong number of arguments in ghost invocation", p, - expectedType.getExpression(), null); - } else if (msg.contains("sort mismatch")) { - throw new GhostInvocationError("Type mismatch in arguments of ghost invocation", p, - expectedType.getExpression(), null); - } else { - throw new CustomError(e.getMessage(), p); - } + raiseError(e, position, found, expected, map); } return true; } @@ -275,39 +264,45 @@ private TranslationTable createMap(Predicate expectedType) { return map; } - protected void raiseSubtypingError(CtElement element, Predicate expectedType, Predicate foundType) throws LJError { - List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); - gatherVariables(expectedType, lrv, mainVars); - gatherVariables(foundType, lrv, mainVars); - TranslationTable map = new TranslationTable(); - Predicate premises = joinPredicates(expectedType, mainVars, lrv, map).toConjunctions(); - throw new RefinementError(element, expectedType.getExpression(), premises.simplify(), map); - } - - public void raiseSameStateError(CtElement element, Predicate expectedType, String klass) throws LJError { - TranslationTable map = createMap(expectedType); - throw new StateConflictError(element, expectedType.getExpression(), klass, map); - } - - private void raiseError(Exception e, Predicate premisesBeforeChange, Predicate expectedType, CtElement element, + protected void raiseError(Exception e, SourcePosition position, Predicate found, Predicate expected, TranslationTable map) throws LJError { if (e instanceof TypeCheckError) { - throw new RefinementError(element, expectedType.getExpression(), premisesBeforeChange.simplify(), map); - } else if (e instanceof liquidjava.smt.errors.NotFoundError) { - throw new NotFoundError(element, e.getMessage(), map); + throw new RefinementError(position, expected.getExpression(), found.simplify(), map); + } else if (e instanceof liquidjava.smt.errors.NotFoundError nfe) { + throw new NotFoundError(position, e.getMessage(), nfe.getName(), nfe.getKind(), map); } else { - throw new CustomError(e.getMessage(), element); + String msg = e.getLocalizedMessage().toLowerCase(); + if (msg.contains("wrong number of arguments")) { + throw new CustomError("Wrong number of arguments in ghost invocation", position); + } else if (msg.contains("sort mismatch")) { + throw new CustomError("Type mismatch in arguments of ghost invocation", position); + } else { + throw new CustomError(e.getMessage(), position); + } } } - public void raiseStateMismatchError(CtElement element, String method, Predicate found, Predicate[] states) + protected void raiseSubtypingError(SourcePosition position, Predicate expected, Predicate found) throws LJError { + List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); + gatherVariables(expected, lrv, mainVars); + gatherVariables(found, lrv, mainVars); + TranslationTable map = new TranslationTable(); + Predicate premises = joinPredicates(expected, mainVars, lrv, map).toConjunctions(); + throw new RefinementError(position, expected.getExpression(), premises.simplify(), map); + } + + protected void raiseSameStateError(SourcePosition position, Predicate expected, String klass) throws LJError { + TranslationTable map = createMap(expected); + throw new StateConflictError(position, expected.getExpression(), map); + } + + protected void raiseStateMismatchError(SourcePosition position, String method, Predicate found, Predicate expected) throws LJError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(found, lrv, mainVars); TranslationTable map = new TranslationTable(); VCImplication foundState = joinPredicates(found, mainVars, lrv, map); - throw new StateRefinementError(element, method, - Arrays.stream(states).map(Predicate::getExpression).toArray(Expression[]::new), + throw new StateRefinementError(position, expected.getExpression(), foundState.toConjunctions().simplify().getValue(), map); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java index 3fe01862..c4d27764 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java @@ -83,7 +83,7 @@ static void transferArgumentsRefinements(RefinedFunction superFunction, RefinedF } else { boolean ok = tc.checksStateSMT(superArgRef, argRef, params.get(i).getPosition()); if (!ok) { - tc.createError(method, argRef, superArgRef); + tc.createError(method.getPosition(), argRef, superArgRef); } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java index 44164419..92bfd43d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java @@ -65,7 +65,7 @@ private static void setConstructorStates(RefinedFunction f, List r = tc.getFactory().Type().createReference(targetClass); String nameOld = String.format(Formats.INSTANCE, Keys.THIS, tc.getContext().getCounter()); @@ -214,7 +215,7 @@ private static Predicate createStatePredicate(String value, String targetClass, c = c.changeOldMentions(nameOld, name); boolean ok = tc.checksStateSMT(new Predicate(), c.negate(), e.getPosition()); if (ok) { - tc.createSameStateError(e, p, targetClass); + tc.createSameStateError(e.getPosition(), p, targetClass); } return c1; } @@ -406,8 +407,7 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) throws L .changeOldMentions(vi.getName(), instanceName); if (!tc.checksStateSMT(prevState, expectState, fw.getPosition())) { // Invalid field transition - Predicate[] states = { stateChange.getFrom() }; - tc.createStateMismatchError(fw, fw.toString(), prevState, states); + tc.createStateMismatchError(fw.getPosition(), fw.toString(), prevState, stateChange.getFrom()); return; } @@ -486,10 +486,12 @@ private static void changeState(TypeChecker tc, VariableInstance vi, List makeBooleanLiteral(boolean value) { private Expr getVariableTranslation(String name) throws Exception { if (!varTranslation.containsKey(name)) - throw new NotFoundError("Variable '" + name + "' not found"); + throw new NotFoundError(Keys.VARIABLE, name); Expr e = varTranslation.get(name); if (e == null) e = varTranslation.get(String.format("this#%s", name)); @@ -133,7 +134,7 @@ private FuncDecl resolveFunctionDeclFallback(String name, Expr[] params) t if (candidate != null) { return candidate; } - throw new NotFoundError("Function '" + name + "' not found"); + throw new NotFoundError(Keys.GHOST, name); } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/errors/NotFoundError.java b/liquidjava-verifier/src/main/java/liquidjava/smt/errors/NotFoundError.java index 5c163808..a6fb544a 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/errors/NotFoundError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/errors/NotFoundError.java @@ -2,7 +2,20 @@ public class NotFoundError extends SMTError { - public NotFoundError(String message) { - super(message); + private final String name; + private final String kind; + + public NotFoundError(String kind, String name) { + super(String.format("%s '%s' not found", kind, name)); + this.name = name; + this.kind = kind; + } + + public String getName() { + return name; + } + + public String getKind() { + return kind; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/utils/constants/Keys.java b/liquidjava-verifier/src/main/java/liquidjava/utils/constants/Keys.java index 790845fa..cba8ee40 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/utils/constants/Keys.java +++ b/liquidjava-verifier/src/main/java/liquidjava/utils/constants/Keys.java @@ -6,4 +6,6 @@ public final class Keys { public static final String THIS = "this"; public static final String WILDCARD = "_"; public static final String OLD = "old"; + public static final String VARIABLE = "Variable"; + public static final String GHOST = "Ghost"; } \ No newline at end of file From d4ef2595f69a6567185b109fe0002631d3a9b1d1 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Fri, 21 Nov 2025 15:42:48 +0000 Subject: [PATCH 09/25] Java Compilation Warning (#124) --- .../liquidjava/api/CommandLineLauncher.java | 7 ++++++- .../diagnostics/warnings/CustomWarning.java | 20 +++++++++++++++++++ .../ExternalClassNotFoundWarning.java | 6 +++--- .../ExternalMethodNotFoundWarning.java | 6 +++--- .../ExternalRefinementTypeChecker.java | 6 +++--- 5 files changed, 35 insertions(+), 10 deletions(-) create mode 100644 liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/CustomWarning.java diff --git a/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java index 6a066f7b..e7efeff6 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java +++ b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java @@ -6,6 +6,7 @@ import liquidjava.diagnostics.Diagnostics; import liquidjava.diagnostics.errors.CustomError; +import liquidjava.diagnostics.warnings.CustomWarning; import liquidjava.processor.RefinementProcessor; import spoon.Launcher; import spoon.processing.ProcessingManager; @@ -54,7 +55,11 @@ public static void launch(String... paths) { } launcher.getEnvironment().setNoClasspath(true); launcher.getEnvironment().setComplianceLevel(8); - launcher.run(); + + boolean buildSuccess = launcher.getModelBuilder().build(); + if (!buildSuccess) { + diagnostics.add(new CustomWarning("Java compilation error detected. Verification might be affected.")); + } final Factory factory = launcher.getFactory(); final ProcessingManager processingManager = new QueueProcessingManager(factory); diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/CustomWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/CustomWarning.java new file mode 100644 index 00000000..22e5917d --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/CustomWarning.java @@ -0,0 +1,20 @@ +package liquidjava.diagnostics.warnings; + +import spoon.reflect.cu.SourcePosition; + +/** + * Custom warning with a message + * + * @see LJWarning + */ +public class CustomWarning extends LJWarning { + + public CustomWarning(SourcePosition position, String message) { + super(message, position); + } + + public CustomWarning(String message) { + super(message, null); + } + +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java index 86438b6b..356542f0 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalClassNotFoundWarning.java @@ -1,6 +1,6 @@ package liquidjava.diagnostics.warnings; -import spoon.reflect.declaration.CtElement; +import spoon.reflect.cu.SourcePosition; /** * Warning indicating that a class referenced in an external refinement was not found @@ -11,8 +11,8 @@ public class ExternalClassNotFoundWarning extends LJWarning { private final String className; - public ExternalClassNotFoundWarning(CtElement element, String message, String className) { - super(message, element.getPosition()); + public ExternalClassNotFoundWarning(SourcePosition position, String message, String className) { + super(message, position); this.className = className; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java index 60f20f17..c9f573ae 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/ExternalMethodNotFoundWarning.java @@ -1,6 +1,6 @@ package liquidjava.diagnostics.warnings; -import spoon.reflect.declaration.CtElement; +import spoon.reflect.cu.SourcePosition; /** * Warning indicating that a method referenced in an external refinement was not found @@ -13,9 +13,9 @@ public class ExternalMethodNotFoundWarning extends LJWarning { private final String className; private final String[] overloads; - public ExternalMethodNotFoundWarning(CtElement element, String message, String methodName, String className, + public ExternalMethodNotFoundWarning(SourcePosition position, String message, String methodName, String className, String[] overloads) { - super(message, element.getPosition()); + super(message, position); this.methodName = methodName; this.className = className; this.overloads = overloads; diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java index a52ac891..7bd910b6 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java @@ -43,7 +43,7 @@ public void visitCtInterface(CtInterface intrface) { this.prefix = externalRefinements.get(); if (!classExists(prefix)) { String message = String.format("Could not find class '%s'", prefix); - diagnostics.add(new ExternalClassNotFoundWarning(intrface, message, prefix)); + diagnostics.add(new ExternalClassNotFoundWarning(intrface.getPosition(), message, prefix)); return; } getRefinementFromAnnotation(intrface); @@ -72,7 +72,7 @@ public void visitCtMethod(CtMethod method) { prefix); String[] overloads = getOverloads(targetType, method); diagnostics.add( - new ExternalMethodNotFoundWarning(method, message, method.getSignature(), prefix, overloads)); + new ExternalMethodNotFoundWarning(method.getPosition(), message, method.getSignature(), prefix, overloads)); } } else { if (!methodExists(targetType, method)) { @@ -80,7 +80,7 @@ public void visitCtMethod(CtMethod method) { method.getType().getSimpleName(), method.getSignature(), prefix); String[] overloads = getOverloads(targetType, method); diagnostics.add( - new ExternalMethodNotFoundWarning(method, message, method.getSignature(), prefix, overloads)); + new ExternalMethodNotFoundWarning(method.getPosition(), message, method.getSignature(), prefix, overloads)); return; } } From f59d1c45cd5253b6e0baeb8315c0f6d2a72c3648 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 4 Dec 2025 11:22:00 +0000 Subject: [PATCH 10/25] Check Expected Errors in Tests (#126) --- README.md | 8 +- .../src/main/java/testSuite/ErrorAfterIf.java | 1 + .../main/java/testSuite/ErrorAfterIf2.java | 1 + .../src/main/java/testSuite/ErrorAlias.java | 1 + .../testSuite/ErrorAliasArgumentSize.java | 3 +- .../main/java/testSuite/ErrorAliasSimple.java | 1 + .../testSuite/ErrorAliasTypeMismatch.java | 1 + .../ErrorArithmeticBinaryOperations.java | 1 + .../java/testSuite/ErrorArithmeticFP1.java | 1 + .../java/testSuite/ErrorArithmeticFP2.java | 1 + .../java/testSuite/ErrorArithmeticFP3.java | 1 + .../java/testSuite/ErrorArithmeticFP4.java | 1 + .../ErrorAssignementAfterDeclaration.java | 1 + .../testSuite/ErrorBooleanFunInvocation.java | 1 + .../java/testSuite/ErrorBooleanLiteral.java | 1 + .../testSuite/ErrorDependentRefinement.java | 1 + .../testSuite/ErrorFunctionDeclarations.java | 1 + .../testSuite/ErrorFunctionInvocation.java | 1 + .../testSuite/ErrorFunctionInvocation1.java | 1 + .../ErrorFunctionInvocationParams.java | 1 + .../java/testSuite/ErrorGhostArgsTypes.java | 1 + .../java/testSuite/ErrorGhostNumberArgs.java | 1 + .../java/testSuite/ErrorIfAssignment.java | 1 + .../java/testSuite/ErrorIfAssignment2.java | 1 + ...rrorImplementationSearchValueIntArray.java | 1 + .../java/testSuite/ErrorLenZeroIntArray.java | 1 + .../main/java/testSuite/ErrorLongUsage1.java | 1 + .../main/java/testSuite/ErrorLongUsage2.java | 1 + .../ErrorMissingAliasTypeParameter.java | 1 + .../testSuite/ErrorNoRefinementsInVar.java | 1 + .../main/java/testSuite/ErrorRecursion1.java | 1 + .../java/testSuite/ErrorSearchIntArray.java | 1 + .../testSuite/ErrorSearchValueIntArray1.java | 1 + .../testSuite/ErrorSearchValueIntArray2.java | 1 + .../java/testSuite/ErrorSimpleAssignment.java | 1 + .../testSuite/ErrorSpecificArithmetic.java | 1 + .../java/testSuite/ErrorSpecificValuesIf.java | 1 + .../testSuite/ErrorSpecificValuesIf2.java | 1 + .../ErrorSpecificVarInRefinement.java | 1 + .../ErrorSpecificVarInRefinementIf.java | 1 + .../src/main/java/testSuite/ErrorSyntax1.java | 1 + .../testSuite/ErrorTernaryExpression.java | 1 + .../java/testSuite/ErrorTrafficLightRGB.java | 1 + .../testSuite/ErrorTypeInRefinements.java | 1 + .../java/testSuite/ErrorUnaryOpMinus.java | 1 + .../java/testSuite/ErrorUnaryOperators.java | 1 + .../testSuite/classes/ErrorGhostState.java | 1 + .../classes/boolean_ghost_error/.expected | 1 + .../testSuite/classes/email_error/.expected | 1 + .../index_out_of_bounds_error/.expected | 1 + .../classes/input_reader_error/.expected | 1 + .../classes/input_reader_error2/.expected | 1 + .../classes/iterator_error/.expected | 1 + .../classes/method_overload_error/.expected | 1 + .../classes/order_gift_error/.expected | 1 + .../refs_from_interface_error/.expected | 1 + .../refs_from_superclass_error/.expected | 1 + .../classes/scoreboard_error/.expected | 1 + .../testSuite/classes/socket_error/.expected | 1 + .../classes/state_multiple_error/.expected | 1 + .../field_updates/ErrorFieldUpdate.java | 1 + .../java/testSuite/math/errorAbs/.expected | 1 + .../java/testSuite/math/errorMax/.expected | 1 + .../math/errorMultiplyExact/.expected | 1 + .../ExternalRefinementTypeChecker.java | 8 +- .../liquidjava/api/tests/TestExamples.java | 76 ++++++++++++------- .../test/java/liquidjava/utils/TestUtils.java | 65 ++++++++++++++++ 67 files changed, 185 insertions(+), 37 deletions(-) create mode 100644 liquidjava-example/src/main/java/testSuite/classes/boolean_ghost_error/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/classes/email_error/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/classes/index_out_of_bounds_error/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/classes/input_reader_error/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/classes/input_reader_error2/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/classes/iterator_error/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/classes/method_overload_error/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/classes/order_gift_error/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/classes/refs_from_interface_error/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/classes/refs_from_superclass_error/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/classes/scoreboard_error/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/classes/socket_error/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/classes/state_multiple_error/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/math/errorAbs/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/math/errorMax/.expected create mode 100644 liquidjava-example/src/main/java/testSuite/math/errorMultiplyExact/.expected create mode 100644 liquidjava-verifier/src/test/java/liquidjava/utils/TestUtils.java diff --git a/README.md b/README.md index 1e20ac80..1c51a532 100644 --- a/README.md +++ b/README.md @@ -78,8 +78,6 @@ To run LiquidJava, use the Maven command below, replacing `/path/to/your/project ```bash mvn exec:java -pl liquidjava-verifier -Dexec.mainClass="liquidjava.api.CommandLineLauncher" -Dexec.args="/path/to/your/project" ``` -*Warning: Any change to LiquidJava requires rebuilding the jar.* - If you're on Linux/macOS, you can use the `liquidjava` script (from the repository root) to simplify the process. @@ -105,10 +103,14 @@ The starter test file is `TestExamples.java`, which runs the test suite under th The test suite considers test cases: 1. Files that start with `Correct` or `Error` (e.g., `CorrectRecursion.java`) -2. Packages or folders that contain the word `correct` or `error` (e.g., `arraylist_correct`) +2. Directories that contain the word `correct` or `error` (e.g., `arraylist_correct`) Therefore, the files and folders that do not follow this pattern are ignored. +For failing test cases, the expected error must be specified as follows: +1. In singular test files, the expected error (title) should be written in the first line of the file as a comment +2. In test directories, a `.expected` file should be included in that directory with the expected error (title) + ## Project Structure * **docs**: Contains documents used for the design of the language. This folder includes a [README](./docs/design/README.md) with the link to the full artifact used in the design process. It also contains initial documents used to prepare the design of the refinements language during its evaluation diff --git a/liquidjava-example/src/main/java/testSuite/ErrorAfterIf.java b/liquidjava-example/src/main/java/testSuite/ErrorAfterIf.java index 130434f9..fd375b09 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorAfterIf.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorAfterIf.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorAfterIf2.java b/liquidjava-example/src/main/java/testSuite/ErrorAfterIf2.java index b6a0816f..3ae6ed8e 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorAfterIf2.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorAfterIf2.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorAlias.java b/liquidjava-example/src/main/java/testSuite/ErrorAlias.java index 4cc2b496..7ed25815 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorAlias.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorAlias.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorAliasArgumentSize.java b/liquidjava-example/src/main/java/testSuite/ErrorAliasArgumentSize.java index 6e3682cd..bdf527c5 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorAliasArgumentSize.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorAliasArgumentSize.java @@ -1,3 +1,4 @@ +// Not Found Error package testSuite; import liquidjava.specification.Refinement; @@ -8,7 +9,7 @@ public class ErrorAliasArgumentSize { public static void main(String[] args) { - @Refinement("InRange( _, 10)") + @Refinement("InRange(j, 10)") int j = 15; } } diff --git a/liquidjava-example/src/main/java/testSuite/ErrorAliasSimple.java b/liquidjava-example/src/main/java/testSuite/ErrorAliasSimple.java index ce751d5c..1e2f1787 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorAliasSimple.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorAliasSimple.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorAliasTypeMismatch.java b/liquidjava-example/src/main/java/testSuite/ErrorAliasTypeMismatch.java index 074765a0..1131b73f 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorAliasTypeMismatch.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorAliasTypeMismatch.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorArithmeticBinaryOperations.java b/liquidjava-example/src/main/java/testSuite/ErrorArithmeticBinaryOperations.java index 9556fea0..b2add7fa 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorArithmeticBinaryOperations.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorArithmeticBinaryOperations.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP1.java b/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP1.java index b5798484..45239bc6 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP1.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP1.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP2.java b/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP2.java index ab60cb9b..16e58df7 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP2.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP2.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP3.java b/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP3.java index 0e96412d..ae85bc50 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP3.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP3.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP4.java b/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP4.java index 51706603..88eed94c 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP4.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorArithmeticFP4.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorAssignementAfterDeclaration.java b/liquidjava-example/src/main/java/testSuite/ErrorAssignementAfterDeclaration.java index 83b31147..60acfbd7 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorAssignementAfterDeclaration.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorAssignementAfterDeclaration.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorBooleanFunInvocation.java b/liquidjava-example/src/main/java/testSuite/ErrorBooleanFunInvocation.java index 029fd4dc..99cc67d6 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorBooleanFunInvocation.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorBooleanFunInvocation.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorBooleanLiteral.java b/liquidjava-example/src/main/java/testSuite/ErrorBooleanLiteral.java index 0ad3ded1..09b97f7e 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorBooleanLiteral.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorBooleanLiteral.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorDependentRefinement.java b/liquidjava-example/src/main/java/testSuite/ErrorDependentRefinement.java index d8d5715d..9c83af5e 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorDependentRefinement.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorDependentRefinement.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorFunctionDeclarations.java b/liquidjava-example/src/main/java/testSuite/ErrorFunctionDeclarations.java index 99f6f435..e2184b67 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorFunctionDeclarations.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorFunctionDeclarations.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorFunctionInvocation.java b/liquidjava-example/src/main/java/testSuite/ErrorFunctionInvocation.java index a8439044..f1ca6bf7 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorFunctionInvocation.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorFunctionInvocation.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorFunctionInvocation1.java b/liquidjava-example/src/main/java/testSuite/ErrorFunctionInvocation1.java index 26119616..e4309a4c 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorFunctionInvocation1.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorFunctionInvocation1.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorFunctionInvocationParams.java b/liquidjava-example/src/main/java/testSuite/ErrorFunctionInvocationParams.java index ba398c67..6d1fa473 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorFunctionInvocationParams.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorFunctionInvocationParams.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorGhostArgsTypes.java b/liquidjava-example/src/main/java/testSuite/ErrorGhostArgsTypes.java index 5fce2f7d..207e3edd 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorGhostArgsTypes.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorGhostArgsTypes.java @@ -1,3 +1,4 @@ +// Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorGhostNumberArgs.java b/liquidjava-example/src/main/java/testSuite/ErrorGhostNumberArgs.java index f080428c..b46a1a70 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorGhostNumberArgs.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorGhostNumberArgs.java @@ -1,3 +1,4 @@ +// Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorIfAssignment.java b/liquidjava-example/src/main/java/testSuite/ErrorIfAssignment.java index 90c225c5..06c61126 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorIfAssignment.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorIfAssignment.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorIfAssignment2.java b/liquidjava-example/src/main/java/testSuite/ErrorIfAssignment2.java index c66c4b71..19692a5a 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorIfAssignment2.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorIfAssignment2.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorImplementationSearchValueIntArray.java b/liquidjava-example/src/main/java/testSuite/ErrorImplementationSearchValueIntArray.java index e34f2c4d..41061023 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorImplementationSearchValueIntArray.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorImplementationSearchValueIntArray.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorLenZeroIntArray.java b/liquidjava-example/src/main/java/testSuite/ErrorLenZeroIntArray.java index 04e2f523..2aadc688 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorLenZeroIntArray.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorLenZeroIntArray.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorLongUsage1.java b/liquidjava-example/src/main/java/testSuite/ErrorLongUsage1.java index aef298bb..2bc1d1f6 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorLongUsage1.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorLongUsage1.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorLongUsage2.java b/liquidjava-example/src/main/java/testSuite/ErrorLongUsage2.java index 61a482d3..fe5a231a 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorLongUsage2.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorLongUsage2.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorMissingAliasTypeParameter.java b/liquidjava-example/src/main/java/testSuite/ErrorMissingAliasTypeParameter.java index 52c2c758..f53bf558 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorMissingAliasTypeParameter.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorMissingAliasTypeParameter.java @@ -1,3 +1,4 @@ +// Syntax Error package testSuite; import liquidjava.specification.RefinementAlias; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorNoRefinementsInVar.java b/liquidjava-example/src/main/java/testSuite/ErrorNoRefinementsInVar.java index 2db74f88..c2133b27 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorNoRefinementsInVar.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorNoRefinementsInVar.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorRecursion1.java b/liquidjava-example/src/main/java/testSuite/ErrorRecursion1.java index 7b5b9fe6..518d351e 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorRecursion1.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorRecursion1.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorSearchIntArray.java b/liquidjava-example/src/main/java/testSuite/ErrorSearchIntArray.java index e25c2c13..54ac3507 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorSearchIntArray.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorSearchIntArray.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorSearchValueIntArray1.java b/liquidjava-example/src/main/java/testSuite/ErrorSearchValueIntArray1.java index ad63b279..f7a96890 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorSearchValueIntArray1.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorSearchValueIntArray1.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorSearchValueIntArray2.java b/liquidjava-example/src/main/java/testSuite/ErrorSearchValueIntArray2.java index d9f482ea..944b7e6f 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorSearchValueIntArray2.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorSearchValueIntArray2.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorSimpleAssignment.java b/liquidjava-example/src/main/java/testSuite/ErrorSimpleAssignment.java index a00d4069..d07f1259 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorSimpleAssignment.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorSimpleAssignment.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorSpecificArithmetic.java b/liquidjava-example/src/main/java/testSuite/ErrorSpecificArithmetic.java index 1a63afc4..65bdde9b 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorSpecificArithmetic.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorSpecificArithmetic.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorSpecificValuesIf.java b/liquidjava-example/src/main/java/testSuite/ErrorSpecificValuesIf.java index b15b633e..04704a8e 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorSpecificValuesIf.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorSpecificValuesIf.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorSpecificValuesIf2.java b/liquidjava-example/src/main/java/testSuite/ErrorSpecificValuesIf2.java index c1e373c3..2cae544c 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorSpecificValuesIf2.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorSpecificValuesIf2.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorSpecificVarInRefinement.java b/liquidjava-example/src/main/java/testSuite/ErrorSpecificVarInRefinement.java index 6ec09838..ccb34136 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorSpecificVarInRefinement.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorSpecificVarInRefinement.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorSpecificVarInRefinementIf.java b/liquidjava-example/src/main/java/testSuite/ErrorSpecificVarInRefinementIf.java index 7e1d1c22..c00f5b90 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorSpecificVarInRefinementIf.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorSpecificVarInRefinementIf.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorSyntax1.java b/liquidjava-example/src/main/java/testSuite/ErrorSyntax1.java index bdf498ae..6d28e95c 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorSyntax1.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorSyntax1.java @@ -1,3 +1,4 @@ +// Syntax Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorTernaryExpression.java b/liquidjava-example/src/main/java/testSuite/ErrorTernaryExpression.java index 4543463b..2c89393a 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorTernaryExpression.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorTernaryExpression.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorTrafficLightRGB.java b/liquidjava-example/src/main/java/testSuite/ErrorTrafficLightRGB.java index 018db95a..3b50f615 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorTrafficLightRGB.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorTrafficLightRGB.java @@ -1,3 +1,4 @@ +// State Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorTypeInRefinements.java b/liquidjava-example/src/main/java/testSuite/ErrorTypeInRefinements.java index 6583832f..2981a217 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorTypeInRefinements.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorTypeInRefinements.java @@ -1,3 +1,4 @@ +// Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorUnaryOpMinus.java b/liquidjava-example/src/main/java/testSuite/ErrorUnaryOpMinus.java index 85149333..73eb1529 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorUnaryOpMinus.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorUnaryOpMinus.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorUnaryOperators.java b/liquidjava-example/src/main/java/testSuite/ErrorUnaryOperators.java index dbc59590..b8660ea8 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorUnaryOperators.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorUnaryOperators.java @@ -1,3 +1,4 @@ +// Refinement Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/classes/ErrorGhostState.java b/liquidjava-example/src/main/java/testSuite/classes/ErrorGhostState.java index 6580fab7..80e2741c 100644 --- a/liquidjava-example/src/main/java/testSuite/classes/ErrorGhostState.java +++ b/liquidjava-example/src/main/java/testSuite/classes/ErrorGhostState.java @@ -1,3 +1,4 @@ +// Error package testSuite.classes; import liquidjava.specification.Ghost; diff --git a/liquidjava-example/src/main/java/testSuite/classes/boolean_ghost_error/.expected b/liquidjava-example/src/main/java/testSuite/classes/boolean_ghost_error/.expected new file mode 100644 index 00000000..49d2a05a --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/boolean_ghost_error/.expected @@ -0,0 +1 @@ +State Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/classes/email_error/.expected b/liquidjava-example/src/main/java/testSuite/classes/email_error/.expected new file mode 100644 index 00000000..49d2a05a --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/email_error/.expected @@ -0,0 +1 @@ +State Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/classes/index_out_of_bounds_error/.expected b/liquidjava-example/src/main/java/testSuite/classes/index_out_of_bounds_error/.expected new file mode 100644 index 00000000..6f476981 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/index_out_of_bounds_error/.expected @@ -0,0 +1 @@ +Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/classes/input_reader_error/.expected b/liquidjava-example/src/main/java/testSuite/classes/input_reader_error/.expected new file mode 100644 index 00000000..49d2a05a --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/input_reader_error/.expected @@ -0,0 +1 @@ +State Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/classes/input_reader_error2/.expected b/liquidjava-example/src/main/java/testSuite/classes/input_reader_error2/.expected new file mode 100644 index 00000000..49d2a05a --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/input_reader_error2/.expected @@ -0,0 +1 @@ +State Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/classes/iterator_error/.expected b/liquidjava-example/src/main/java/testSuite/classes/iterator_error/.expected new file mode 100644 index 00000000..49d2a05a --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/iterator_error/.expected @@ -0,0 +1 @@ +State Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/classes/method_overload_error/.expected b/liquidjava-example/src/main/java/testSuite/classes/method_overload_error/.expected new file mode 100644 index 00000000..6f476981 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/method_overload_error/.expected @@ -0,0 +1 @@ +Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/classes/order_gift_error/.expected b/liquidjava-example/src/main/java/testSuite/classes/order_gift_error/.expected new file mode 100644 index 00000000..49d2a05a --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/order_gift_error/.expected @@ -0,0 +1 @@ +State Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/classes/refs_from_interface_error/.expected b/liquidjava-example/src/main/java/testSuite/classes/refs_from_interface_error/.expected new file mode 100644 index 00000000..6f476981 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/refs_from_interface_error/.expected @@ -0,0 +1 @@ +Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/classes/refs_from_superclass_error/.expected b/liquidjava-example/src/main/java/testSuite/classes/refs_from_superclass_error/.expected new file mode 100644 index 00000000..6f476981 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/refs_from_superclass_error/.expected @@ -0,0 +1 @@ +Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/classes/scoreboard_error/.expected b/liquidjava-example/src/main/java/testSuite/classes/scoreboard_error/.expected new file mode 100644 index 00000000..49d2a05a --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/scoreboard_error/.expected @@ -0,0 +1 @@ +State Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/classes/socket_error/.expected b/liquidjava-example/src/main/java/testSuite/classes/socket_error/.expected new file mode 100644 index 00000000..49d2a05a --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/socket_error/.expected @@ -0,0 +1 @@ +State Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/classes/state_multiple_error/.expected b/liquidjava-example/src/main/java/testSuite/classes/state_multiple_error/.expected new file mode 100644 index 00000000..258d98bb --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/classes/state_multiple_error/.expected @@ -0,0 +1 @@ +State Conflict Error diff --git a/liquidjava-example/src/main/java/testSuite/field_updates/ErrorFieldUpdate.java b/liquidjava-example/src/main/java/testSuite/field_updates/ErrorFieldUpdate.java index 4aeaf01e..55a10184 100644 --- a/liquidjava-example/src/main/java/testSuite/field_updates/ErrorFieldUpdate.java +++ b/liquidjava-example/src/main/java/testSuite/field_updates/ErrorFieldUpdate.java @@ -1,3 +1,4 @@ +// State Refinement Error package testSuite.field_updates; import liquidjava.specification.StateRefinement; diff --git a/liquidjava-example/src/main/java/testSuite/math/errorAbs/.expected b/liquidjava-example/src/main/java/testSuite/math/errorAbs/.expected new file mode 100644 index 00000000..6f476981 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/math/errorAbs/.expected @@ -0,0 +1 @@ +Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/math/errorMax/.expected b/liquidjava-example/src/main/java/testSuite/math/errorMax/.expected new file mode 100644 index 00000000..6f476981 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/math/errorMax/.expected @@ -0,0 +1 @@ +Refinement Error diff --git a/liquidjava-example/src/main/java/testSuite/math/errorMultiplyExact/.expected b/liquidjava-example/src/main/java/testSuite/math/errorMultiplyExact/.expected new file mode 100644 index 00000000..6f476981 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/math/errorMultiplyExact/.expected @@ -0,0 +1 @@ +Refinement Error diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java index 7bd910b6..5773a548 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java @@ -71,16 +71,16 @@ public void visitCtMethod(CtMethod method) { String message = String.format("Could not find constructor '%s' for '%s'", method.getSignature(), prefix); String[] overloads = getOverloads(targetType, method); - diagnostics.add( - new ExternalMethodNotFoundWarning(method.getPosition(), message, method.getSignature(), prefix, overloads)); + diagnostics.add(new ExternalMethodNotFoundWarning(method.getPosition(), message, method.getSignature(), + prefix, overloads)); } } else { if (!methodExists(targetType, method)) { String message = String.format("Could not find method '%s %s' for '%s'", method.getType().getSimpleName(), method.getSignature(), prefix); String[] overloads = getOverloads(targetType, method); - diagnostics.add( - new ExternalMethodNotFoundWarning(method.getPosition(), message, method.getSignature(), prefix, overloads)); + diagnostics.add(new ExternalMethodNotFoundWarning(method.getPosition(), message, method.getSignature(), + prefix, overloads)); return; } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java b/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java index f0822bef..be0f1ca0 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java +++ b/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java @@ -1,15 +1,18 @@ package liquidjava.api.tests; +import static liquidjava.utils.TestUtils.*; import static org.junit.Assert.fail; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Optional; import java.util.stream.Stream; import liquidjava.api.CommandLineLauncher; import liquidjava.diagnostics.Diagnostics; +import liquidjava.diagnostics.errors.LJError; import org.junit.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; @@ -22,29 +25,52 @@ public class TestExamples { * Test the file at the given path by launching the verifier and checking for errors. The file/directory is expected * to be either correct or contain an error based on its name. * - * @param filePath + * @param path * path to the file to test */ @ParameterizedTest - @MethodSource("fileNameSource") - public void testFile(final Path filePath) { - String fileName = filePath.getFileName().toString(); + @MethodSource("sourcePaths") + public void testPath(final Path path) { + String pathName = path.getFileName().toString(); + boolean isDirectory = Files.isDirectory(path); - // 1. Run the verifier on the file or package - CommandLineLauncher.launch(filePath.toAbsolutePath().toString()); + // run verification + CommandLineLauncher.launch(path.toAbsolutePath().toString()); - // 2. Check if the file is correct or contains an error - if (isCorrect(fileName) && diagnostics.foundError()) { - if (fileName.toLowerCase().startsWith("warning")) { - System.out.println("Warning in directory: " + fileName + " --- should be correct with warnings"); - } - System.out.println("Error in directory: " + fileName + " --- should be correct but an error was found"); + // verification should pass, check if any errors were found + if (shouldPass(pathName) && diagnostics.foundError()) { + System.out.println("Error in: " + pathName + " --- should pass but an error was found"); fail(); } - // 3. Check if the file has an error but passed verification - if (isError(fileName) && !diagnostics.foundError()) { - System.out.println("Error in directory: " + fileName + " --- should be an error but passed verification"); - fail(); + // verification should fail, check if it failed as expected (we assume each error test has exactly one error) + else if (shouldFail(pathName)) { + if (!diagnostics.foundError()) { + System.out.println("Error in: " + pathName + " --- should fail but no errors were found"); + fail(); + } else { + // check if expected error was found + Optional expected = isDirectory ? getExpectedErrorFromDirectory(path) + : getExpectedErrorFromFile(path); + if (diagnostics.getErrors().size() > 1) { + System.out.println("Multiple errors found in: " + pathName + " --- expected exactly one error"); + fail(); + } + LJError error = diagnostics.getErrors().iterator().next(); + if (expected.isPresent()) { + String expectedError = expected.get(); + String foundError = error.getTitle(); + if (!foundError.equalsIgnoreCase(expectedError)) { + System.out.println("Error in: " + pathName + " --- expected error: " + expectedError + + ", but found: " + foundError); + fail(); + } + } else { + System.out.println("No expected error message found for: " + pathName); + System.out.println("Please provide an expected error in " + (isDirectory + ? "a .expected file in the directory" : "the first line of the test file as a comment")); + fail(); + } + } } } @@ -57,17 +83,17 @@ public void testFile(final Path filePath) { * @throws IOException * if an I/O error occurs or the path does not exist */ - private static Stream fileNameSource() throws IOException { + private static Stream sourcePaths() throws IOException { return Files.find(Paths.get("../liquidjava-example/src/main/java/testSuite/"), Integer.MAX_VALUE, (filePath, fileAttr) -> { String name = filePath.getFileName().toString(); - // 1. Files that start with "Correct" or "Error" + // Files that start with "Correct" or "Error" boolean isFileStartingWithCorrectOrError = fileAttr.isRegularFile() - && (isCorrect(name) || isError(name)); + && (shouldPass(name) || shouldFail(name)); - // 2. Folders (directories) that contain "correct" or "error" + // Directories that contain "correct" or "error" boolean isDirectoryWithCorrectOrError = fileAttr.isDirectory() - && (isCorrect(name) || isError(name)); + && (shouldPass(name) || shouldFail(name)); // Return true if either condition matches return isFileStartingWithCorrectOrError || isDirectoryWithCorrectOrError; @@ -89,12 +115,4 @@ public void testMultiplePaths() { fail(); } } - - private static boolean isCorrect(String path) { - return path.toLowerCase().contains("correct") || path.toLowerCase().contains("warning"); - } - - private static boolean isError(String path) { - return path.toLowerCase().contains("error"); - } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/TestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/TestUtils.java new file mode 100644 index 00000000..9b05725e --- /dev/null +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/TestUtils.java @@ -0,0 +1,65 @@ +package liquidjava.utils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Optional; +import java.util.stream.Stream; + +public class TestUtils { + + /** + * Determines if the given path indicates that the test should pass + * @param path + */ + public static boolean shouldPass(String path) { + return path.toLowerCase().contains("correct") || path.toLowerCase().contains("warning"); + } + + /** + * Determines if the given path indicates that the test should fail + * @param path + */ + public static boolean shouldFail(String path) { + return path.toLowerCase().contains("error"); + } + + /** + * Reads the expected error message from the first line of the given test file + * + * @param filePath + * + * @return optional containing the expected error message if present, otherwise empty + */ + public static Optional getExpectedErrorFromFile(Path filePath) { + try (Stream lines = Files.lines(filePath)) { + Optional first = lines.findFirst(); + if (first.isPresent() && first.get().startsWith("//")) { + return Optional.of(first.get().substring(2).trim()); + } else { + return Optional.empty(); + } + } catch (Exception e) { + return Optional.empty(); + } + } + + /** + * Reads the expected error message from a .expected file in the given directory + * + * @param dirPath + * + * @return optional containing the expected error message if present, otherwise empty + */ + public static Optional getExpectedErrorFromDirectory(Path dirPath) { + Path expectedFilePath = dirPath.resolve(".expected"); + if (Files.exists(expectedFilePath)) { + try (Stream lines = Files.lines(expectedFilePath)) { + return lines.findFirst().map(String::trim); + } catch (IOException e) { + return Optional.empty(); + } + } + return Optional.empty(); + } +} From 8e1ff89394bc01ced54145fea39133b39b437a5c Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 4 Dec 2025 11:29:21 +0000 Subject: [PATCH 11/25] Fix Java Compilation Warning (#128) --- .../main/java/liquidjava/api/CommandLineLauncher.java | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java index e7efeff6..b664ced6 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java +++ b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java @@ -9,6 +9,7 @@ import liquidjava.diagnostics.warnings.CustomWarning; import liquidjava.processor.RefinementProcessor; import spoon.Launcher; +import spoon.compiler.Environment; import spoon.processing.ProcessingManager; import spoon.reflect.declaration.CtPackage; import spoon.reflect.factory.Factory; @@ -53,12 +54,14 @@ public static void launch(String... paths) { } launcher.addInputResource(path); } - launcher.getEnvironment().setNoClasspath(true); - launcher.getEnvironment().setComplianceLevel(8); + + Environment env = launcher.getEnvironment(); + env.setNoClasspath(true); + env.setComplianceLevel(8); boolean buildSuccess = launcher.getModelBuilder().build(); - if (!buildSuccess) { - diagnostics.add(new CustomWarning("Java compilation error detected. Verification might be affected.")); + if (!buildSuccess && (env.getErrorCount() > 0 || env.getWarningCount() > 0)) { + diagnostics.add(new CustomWarning("Java compilation encountered issues. Verification may be affected.")); } final Factory factory = launcher.getFactory(); From 883171c1e2b6c69bb3887e6bc96807b7290015c6 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 4 Dec 2025 17:35:05 +0000 Subject: [PATCH 12/25] Fix Alias & Ghost Argument Mismatch (#127) --- .../testSuite/ErrorAliasArgumentSize.java | 2 +- .../testSuite/ErrorAliasEmptyArguments.java | 15 ++ .../java/testSuite/ErrorAliasNotFound.java | 12 ++ .../testSuite/ErrorAliasTypeMismatch.java | 3 +- .../java/testSuite/ErrorGhostArgsTypes.java | 2 +- .../java/testSuite/ErrorGhostNotFound.java | 12 ++ .../java/testSuite/ErrorGhostNumberArgs.java | 2 +- .../liquidjava/diagnostics/Diagnostics.java | 5 +- .../liquidjava/diagnostics/LJDiagnostic.java | 11 +- .../errors/ArgumentMismatchError.java | 21 +++ .../diagnostics/errors/NotFoundError.java | 11 +- .../refinement_checker/TypeChecker.java | 48 ++--- .../refinement_checker/VCChecker.java | 165 ++++++++---------- .../AuxHierarchyRefinementsPassage.java | 2 +- .../object_checkers/AuxStateHandler.java | 7 +- .../liquidjava/rj_language/Predicate.java | 10 +- .../rj_language/ast/AliasInvocation.java | 3 +- .../rj_language/ast/BinaryExpression.java | 3 +- .../rj_language/ast/Expression.java | 157 +++++++++++++++-- .../rj_language/ast/FunctionInvocation.java | 3 +- .../rj_language/ast/GroupExpression.java | 3 +- .../java/liquidjava/rj_language/ast/Ite.java | 3 +- .../rj_language/ast/LiteralBoolean.java | 3 +- .../rj_language/ast/LiteralInt.java | 3 +- .../rj_language/ast/LiteralReal.java | 3 +- .../rj_language/ast/LiteralString.java | 3 +- .../rj_language/ast/UnaryExpression.java | 3 +- .../java/liquidjava/rj_language/ast/Var.java | 3 +- .../visitors/CreateASTVisitor.java | 6 +- .../visitors/ExpressionVisitor.java | 23 +-- .../liquidjava/smt/ExpressionToZ3Visitor.java | 15 +- .../java/liquidjava/smt/SMTEvaluator.java | 1 - .../java/liquidjava/smt/TranslatorToZ3.java | 18 +- .../smt/{errors => }/TypeCheckError.java | 2 +- .../liquidjava/smt/errors/NotFoundError.java | 21 --- .../java/liquidjava/smt/errors/SMTError.java | 19 -- .../java/liquidjava/utils/constants/Keys.java | 1 + .../liquidjava/api/tests/TestExamples.java | 4 + .../test/java/liquidjava/utils/TestUtils.java | 2 + 39 files changed, 394 insertions(+), 236 deletions(-) create mode 100644 liquidjava-example/src/main/java/testSuite/ErrorAliasEmptyArguments.java create mode 100644 liquidjava-example/src/main/java/testSuite/ErrorAliasNotFound.java create mode 100644 liquidjava-example/src/main/java/testSuite/ErrorGhostNotFound.java create mode 100644 liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/ArgumentMismatchError.java rename liquidjava-verifier/src/main/java/liquidjava/smt/{errors => }/TypeCheckError.java (80%) delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/smt/errors/NotFoundError.java delete mode 100644 liquidjava-verifier/src/main/java/liquidjava/smt/errors/SMTError.java diff --git a/liquidjava-example/src/main/java/testSuite/ErrorAliasArgumentSize.java b/liquidjava-example/src/main/java/testSuite/ErrorAliasArgumentSize.java index bdf527c5..a5dd6c75 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorAliasArgumentSize.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorAliasArgumentSize.java @@ -1,4 +1,4 @@ -// Not Found Error +// Argument Mismatch Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorAliasEmptyArguments.java b/liquidjava-example/src/main/java/testSuite/ErrorAliasEmptyArguments.java new file mode 100644 index 00000000..99231431 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/ErrorAliasEmptyArguments.java @@ -0,0 +1,15 @@ +// Argument Mismatch Error +package testSuite; + +import liquidjava.specification.Refinement; +import liquidjava.specification.RefinementAlias; + +@SuppressWarnings("unused") +@RefinementAlias("InRange(int val, int low, int up) {low < val && val < up}") +public class ErrorAliasEmptyArguments { + + public static void main(String[] args) { + @Refinement("InRange()") + int j = 15; + } +} diff --git a/liquidjava-example/src/main/java/testSuite/ErrorAliasNotFound.java b/liquidjava-example/src/main/java/testSuite/ErrorAliasNotFound.java new file mode 100644 index 00000000..ca21871a --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/ErrorAliasNotFound.java @@ -0,0 +1,12 @@ +// Not Found Error +package testSuite; + +import liquidjava.specification.Refinement; + +public class ErrorAliasNotFound { + + public static void main(String[] args) { + @Refinement("UndefinedAlias(x)") + int x = 5; + } +} diff --git a/liquidjava-example/src/main/java/testSuite/ErrorAliasTypeMismatch.java b/liquidjava-example/src/main/java/testSuite/ErrorAliasTypeMismatch.java index 1131b73f..263a0f9b 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorAliasTypeMismatch.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorAliasTypeMismatch.java @@ -1,4 +1,4 @@ -// Refinement Error +// Argument Mismatch Error package testSuite; import liquidjava.specification.Refinement; @@ -15,6 +15,5 @@ public static void main(String[] args) { @Refinement("Positive(_)") double positive = positiveGrade2; - // Positive(_) fica positive > 0 } } diff --git a/liquidjava-example/src/main/java/testSuite/ErrorGhostArgsTypes.java b/liquidjava-example/src/main/java/testSuite/ErrorGhostArgsTypes.java index 207e3edd..6ff3be00 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorGhostArgsTypes.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorGhostArgsTypes.java @@ -1,4 +1,4 @@ -// Error +// Argument Mismatch Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorGhostNotFound.java b/liquidjava-example/src/main/java/testSuite/ErrorGhostNotFound.java new file mode 100644 index 00000000..be2af75c --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/ErrorGhostNotFound.java @@ -0,0 +1,12 @@ +// Not Found Error +package testSuite; + +import liquidjava.specification.Refinement; + +public class ErrorGhostNotFound { + + public void test() { + @Refinement("notFound(x)") + int x = 5; + } +} diff --git a/liquidjava-example/src/main/java/testSuite/ErrorGhostNumberArgs.java b/liquidjava-example/src/main/java/testSuite/ErrorGhostNumberArgs.java index b46a1a70..f9b8ef35 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorGhostNumberArgs.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorGhostNumberArgs.java @@ -1,4 +1,4 @@ -// Error +// Argument Mismatch Error package testSuite; import liquidjava.specification.Refinement; diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java index 846f3dc6..f125f5fc 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java @@ -55,11 +55,10 @@ public void clear() { } public String getErrorOutput() { - return String.join("\n", errors.stream().map(LJError::toString).toList()) + (errors.isEmpty() ? "" : "\n"); + return String.join("\n", errors.stream().map(LJError::toString).toList()); } public String getWarningOutput() { - return String.join("\n", warnings.stream().map(LJWarning::toString).toList()) - + (warnings.isEmpty() ? "" : "\n"); + return String.join("\n", warnings.stream().map(LJWarning::toString).toList()); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java index 88818dd8..245c53d8 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java @@ -10,9 +10,9 @@ public class LJDiagnostic extends RuntimeException { private final String title; private final String message; - private final String file; - private final ErrorPosition position; private final String accentColor; + private String file; + private ErrorPosition position; public LJDiagnostic(String title, String message, SourcePosition pos, String accentColor) { this.title = title; @@ -38,6 +38,13 @@ public ErrorPosition getPosition() { return position; } + public void setPosition(SourcePosition pos) { + if (pos == null) + return; + this.position = ErrorPosition.fromSpoonPosition(pos); + this.file = pos.getFile().getPath(); + } + public String getFile() { return file; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/ArgumentMismatchError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/ArgumentMismatchError.java new file mode 100644 index 00000000..d8516f91 --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/ArgumentMismatchError.java @@ -0,0 +1,21 @@ +package liquidjava.diagnostics.errors; + +import liquidjava.diagnostics.TranslationTable; +import spoon.reflect.cu.SourcePosition; + +/** + * Error indicating that the arguments provided to a function or method do not match the expected parameters either in + * number or type of arguments + * + * @see LJError + */ +public class ArgumentMismatchError extends LJError { + + public ArgumentMismatchError(String message) { + super("Argument Mismatch Error", message, null, null); + } + + public ArgumentMismatchError(String message, SourcePosition position, TranslationTable translationTable) { + super("Argument Mismatch Error", message, position, translationTable); + } +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java index e7973be2..1a2c260a 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java @@ -12,11 +12,14 @@ public class NotFoundError extends LJError { private final String name; - private final String kind; // "Variable" or "Ghost" + private final String kind; // "Variable" | "Ghost" | "Alias" - public NotFoundError(SourcePosition position, String message, String name, String kind, - TranslationTable translationTable) { - super("Not Found Error", message, position, translationTable); + public NotFoundError(String name, String kind) { + this(null, name, kind, null); + } + + public NotFoundError(SourcePosition position, String name, String kind, TranslationTable translationTable) { + super("Not Found Error", String.format("%s '%s' not found", kind, name), position, translationTable); this.name = Utils.getSimpleName(name); this.kind = kind; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java index da0c6b60..0078f89f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java @@ -5,10 +5,7 @@ import java.util.List; import java.util.Optional; -import liquidjava.diagnostics.errors.CustomError; -import liquidjava.diagnostics.errors.InvalidRefinementError; -import liquidjava.diagnostics.errors.LJError; -import liquidjava.diagnostics.errors.SyntaxError; +import liquidjava.diagnostics.errors.*; import liquidjava.processor.context.AliasWrapper; import liquidjava.processor.context.Context; import liquidjava.processor.context.GhostFunction; @@ -34,16 +31,18 @@ import spoon.reflect.reference.CtTypeReference; import spoon.reflect.visitor.CtScanner; +import static liquidjava.processor.refinement_checker.TypeCheckingUtils.*; + public abstract class TypeChecker extends CtScanner { - Context context; - Factory factory; - VCChecker vcChecker; + protected final Context context; + protected final Factory factory; + protected final VCChecker vcChecker; public TypeChecker(Context context, Factory factory) { this.context = context; this.factory = factory; - vcChecker = new VCChecker(); + this.vcChecker = new VCChecker(); } public Context getContext() { @@ -65,15 +64,15 @@ public Optional getRefinementFromAnnotation(CtElement element) throws for (CtAnnotation ann : element.getAnnotations()) { String an = ann.getActualAnnotation().annotationType().getCanonicalName(); if (an.contentEquals("liquidjava.specification.Refinement")) { - String st = TypeCheckingUtils.getStringFromAnnotation(ann.getValue("value")); + String st = getStringFromAnnotation(ann.getValue("value")); ref = Optional.of(st); } else if (an.contentEquals("liquidjava.specification.RefinementPredicate")) { - String st = TypeCheckingUtils.getStringFromAnnotation(ann.getValue("value")); + String st = getStringFromAnnotation(ann.getValue("value")); getGhostFunction(st, element); } else if (an.contentEquals("liquidjava.specification.RefinementAlias")) { - String st = TypeCheckingUtils.getStringFromAnnotation(ann.getValue("value")); + String st = getStringFromAnnotation(ann.getValue("value")); handleAlias(st, element); } } @@ -209,9 +208,9 @@ protected void getGhostFunction(String value, CtElement element) throws LJError } } - protected void handleAlias(String value, CtElement element) throws LJError { + protected void handleAlias(String ref, CtElement element) throws LJError { try { - AliasDTO a = RefinementsParser.getAliasDeclaration(value); + AliasDTO a = RefinementsParser.getAliasDeclaration(ref); String klass = null; String path = null; if (element instanceof CtClass) { @@ -226,15 +225,16 @@ protected void handleAlias(String value, CtElement element) throws LJError { // refinement alias must return a boolean expression if (a.getExpression() != null && !a.getExpression().isBooleanExpression()) { throw new InvalidRefinementError(element.getPosition(), - "Refinement alias must return a boolean expression", value); + "Refinement alias must return a boolean expression", ref); } AliasWrapper aw = new AliasWrapper(a, factory, klass, path); context.addAlias(aw); } - } catch (SyntaxError e) { + } catch (LJError e) { // add location info to error - SourcePosition pos = Utils.getRefinementAnnotationPosition(element, value); - throw new SyntaxError(e.getMessage(), pos, value); + SourcePosition pos = Utils.getRefinementAnnotationPosition(element, ref); + e.setPosition(pos); + throw e; } } @@ -296,16 +296,16 @@ public boolean checksStateSMT(Predicate prevState, Predicate expectedState, Sour return vcChecker.canProcessSubtyping(prevState, expectedState, context.getGhostState(), p, factory); } - public void createError(SourcePosition position, Predicate expectedType, Predicate foundType) throws LJError { - vcChecker.raiseSubtypingError(position, expectedType, foundType); + public void throwRefinementError(SourcePosition position, Predicate expectedType, Predicate foundType) + throws LJError { + vcChecker.throwRefinementError(position, expectedType, foundType); } - public void createSameStateError(SourcePosition position, Predicate expectedType, String klass) throws LJError { - vcChecker.raiseSameStateError(position, expectedType, klass); + public void throwStateRefinementError(SourcePosition position, Predicate found, Predicate expected) throws LJError { + vcChecker.throwStateRefinementError(position, found, expected); } - public void createStateMismatchError(SourcePosition position, String method, Predicate found, Predicate expected) - throws LJError { - vcChecker.raiseStateMismatchError(position, method, found, expected); + public void throwStateConflictError(SourcePosition position, Predicate expectedType) throws LJError { + vcChecker.throwStateConflictError(position, expectedType); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java index 4cad3a80..3df794ac 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java @@ -7,18 +7,13 @@ import java.util.function.Consumer; import java.util.stream.Collectors; -import liquidjava.diagnostics.errors.CustomError; -import liquidjava.diagnostics.errors.LJError; -import liquidjava.diagnostics.errors.NotFoundError; +import liquidjava.diagnostics.errors.*; import liquidjava.diagnostics.TranslationTable; -import liquidjava.diagnostics.errors.RefinementError; -import liquidjava.diagnostics.errors.StateConflictError; -import liquidjava.diagnostics.errors.StateRefinementError; import liquidjava.processor.VCImplication; import liquidjava.processor.context.*; import liquidjava.rj_language.Predicate; import liquidjava.smt.SMTEvaluator; -import liquidjava.smt.errors.TypeCheckError; +import liquidjava.smt.TypeCheckError; import liquidjava.utils.constants.Keys; import spoon.reflect.cu.SourcePosition; import spoon.reflect.declaration.CtElement; @@ -44,29 +39,64 @@ public void processSubtyping(Predicate expectedType, List list, CtEl TranslationTable map = new TranslationTable(); String[] s = { Keys.WILDCARD, Keys.THIS }; Predicate premisesBeforeChange = joinPredicates(expectedType, mainVars, lrv, map).toConjunctions(); - Predicate premises = new Predicate(); - Predicate et = new Predicate(); + Predicate premises; + Predicate expected; try { List filtered = filterGhostStatesForVariables(list, mainVars, lrv); premises = premisesBeforeChange.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); - et = expectedType.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); - } catch (Exception e) { - throw new RefinementError(element.getPosition(), expectedType.getExpression(), premises.simplify(), map); - } - - try { - smtChecking(premises, et); - } catch (Exception e) { - // To emit the message we use the constraints before the alias and state change - raiseError(e, element.getPosition(), premisesBeforeChange, expectedType, map); + expected = expectedType.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); + } catch (LJError e) { + // add location info to error + e.setPosition(element.getPosition()); + throw e; } + boolean isSubtype = smtChecks(expected, premises, element.getPosition()); + if (!isSubtype) + throw new RefinementError(element.getPosition(), expectedType.getExpression(), + premisesBeforeChange.simplify(), map); } + /** + * Check that type is a subtype of expectedType Throws RefinementError otherwise + * + * @param type + * @param expectedType + * @param list + * @param element + * @param f + * + * @throws LJError + */ public void processSubtyping(Predicate type, Predicate expectedType, List list, CtElement element, Factory f) throws LJError { boolean b = canProcessSubtyping(type, expectedType, list, element.getPosition(), f); if (!b) - raiseSubtypingError(element.getPosition(), expectedType, type); + throwRefinementError(element.getPosition(), expectedType, type); + } + + /** + * Checks the expected against the found constraint + * + * @param expected + * @param found + * @param position + * + * @return true if expected type is subtype of found type, false otherwise + * + * @throws LJError + */ + public boolean smtChecks(Predicate expected, Predicate found, SourcePosition position) throws LJError { + try { + new SMTEvaluator().verifySubtype(found, expected, context); + return true; + } catch (TypeCheckError e) { + return false; + } catch (LJError e) { + e.setPosition(position); + throw e; + } catch (Exception e) { + throw new CustomError(e.getMessage(), position); + } } public boolean canProcessSubtyping(Predicate type, Predicate expectedType, List list, @@ -79,19 +109,14 @@ public boolean canProcessSubtyping(Predicate type, Predicate expectedType, List< TranslationTable map = new TranslationTable(); String[] s = { Keys.WILDCARD, Keys.THIS }; - - Predicate premises = new Predicate(); - Predicate et = new Predicate(); - try { - premises = joinPredicates(expectedType, mainVars, lrv, map).toConjunctions(); - List filtered = filterGhostStatesForVariables(list, mainVars, lrv); - premises = Predicate.createConjunction(premises, type).changeStatesToRefinements(filtered, s) - .changeAliasToRefinement(context, f); - et = expectedType.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); - } catch (Exception e) { - return false; - } - return smtChecks(et, premises, position, map); + Predicate premises = joinPredicates(expectedType, mainVars, lrv, map).toConjunctions(); + List filtered = filterGhostStatesForVariables(list, mainVars, lrv); + premises = Predicate.createConjunction(premises, type).changeStatesToRefinements(filtered, s) + .changeAliasToRefinement(context, f); + Predicate expected = expectedType.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); + + // check subtyping + return smtChecks(expected, premises, position); } /** @@ -196,7 +221,6 @@ private List getVariables(Predicate c, String varName) { getVariablesFromContext(c.getVariableNames(), allVars, varName); List pathNames = pathVariables.stream().map(Refined::getName).collect(Collectors.toList()); getVariablesFromContext(pathNames, allVars, ""); - return allVars; } @@ -217,30 +241,6 @@ private void recAuxGetVars(RefinedVariable var, List newVars) { getVariablesFromContext(l, newVars, varName); } - public boolean smtChecks(Predicate expected, Predicate found, SourcePosition position, TranslationTable map) - throws LJError { - try { - new SMTEvaluator().verifySubtype(found, expected, context); - } catch (TypeCheckError e) { - return false; - } catch (Exception e) { - raiseError(e, position, found, expected, map); - } - return true; - } - - /** - * Checks the expectedType against the cSMT constraint. If the types do not check and error is sent and the program - * ends - * - * @param cSMT - * @param expectedType - * - */ - private void smtChecking(Predicate cSMT, Predicate expectedType) throws Exception { - new SMTEvaluator().verifySubtype(cSMT, expectedType, context); - } - public void addPathVariable(RefinedVariable rv) { pathVariables.add(rv); } @@ -256,53 +256,36 @@ void removePathVariableThatIncludes(String otherVar) { // Errors--------------------------------------------------------------------------------------------------- - private TranslationTable createMap(Predicate expectedType) { + protected void throwRefinementError(SourcePosition position, Predicate expected, Predicate found) + throws RefinementError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); - gatherVariables(expectedType, lrv, mainVars); + gatherVariables(expected, lrv, mainVars); + gatherVariables(found, lrv, mainVars); TranslationTable map = new TranslationTable(); - joinPredicates(expectedType, mainVars, lrv, map); - return map; - } - - protected void raiseError(Exception e, SourcePosition position, Predicate found, Predicate expected, - TranslationTable map) throws LJError { - if (e instanceof TypeCheckError) { - throw new RefinementError(position, expected.getExpression(), found.simplify(), map); - } else if (e instanceof liquidjava.smt.errors.NotFoundError nfe) { - throw new NotFoundError(position, e.getMessage(), nfe.getName(), nfe.getKind(), map); - } else { - String msg = e.getLocalizedMessage().toLowerCase(); - if (msg.contains("wrong number of arguments")) { - throw new CustomError("Wrong number of arguments in ghost invocation", position); - } else if (msg.contains("sort mismatch")) { - throw new CustomError("Type mismatch in arguments of ghost invocation", position); - } else { - throw new CustomError(e.getMessage(), position); - } - } + Predicate premises = joinPredicates(expected, mainVars, lrv, map).toConjunctions(); + throw new RefinementError(position, expected.getExpression(), premises.simplify(), map); } - protected void raiseSubtypingError(SourcePosition position, Predicate expected, Predicate found) throws LJError { + protected void throwStateRefinementError(SourcePosition position, Predicate found, Predicate expected) + throws StateRefinementError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); - gatherVariables(expected, lrv, mainVars); gatherVariables(found, lrv, mainVars); TranslationTable map = new TranslationTable(); - Predicate premises = joinPredicates(expected, mainVars, lrv, map).toConjunctions(); - throw new RefinementError(position, expected.getExpression(), premises.simplify(), map); + VCImplication foundState = joinPredicates(found, mainVars, lrv, map); + throw new StateRefinementError(position, expected.getExpression(), + foundState.toConjunctions().simplify().getValue(), map); } - protected void raiseSameStateError(SourcePosition position, Predicate expected, String klass) throws LJError { + protected void throwStateConflictError(SourcePosition position, Predicate expected) throws StateConflictError { TranslationTable map = createMap(expected); throw new StateConflictError(position, expected.getExpression(), map); } - protected void raiseStateMismatchError(SourcePosition position, String method, Predicate found, Predicate expected) - throws LJError { + private TranslationTable createMap(Predicate expectedType) { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); - gatherVariables(found, lrv, mainVars); + gatherVariables(expectedType, lrv, mainVars); TranslationTable map = new TranslationTable(); - VCImplication foundState = joinPredicates(found, mainVars, lrv, map); - throw new StateRefinementError(position, expected.getExpression(), - foundState.toConjunctions().simplify().getValue(), map); + joinPredicates(expectedType, mainVars, lrv, map); + return map; } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java index c4d27764..88dcf683 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java @@ -83,7 +83,7 @@ static void transferArgumentsRefinements(RefinedFunction superFunction, RefinedF } else { boolean ok = tc.checksStateSMT(superArgRef, argRef, params.get(i).getPosition()); if (!ok) { - tc.createError(method.getPosition(), argRef, superArgRef); + tc.throwRefinementError(method.getPosition(), argRef, superArgRef); } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java index 92bfd43d..bd76950b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java @@ -215,7 +215,7 @@ private static Predicate createStatePredicate(String value, String targetClass, c = c.changeOldMentions(nameOld, name); boolean ok = tc.checksStateSMT(new Predicate(), c.negate(), e.getPosition()); if (ok) { - tc.createSameStateError(e.getPosition(), p, targetClass); + tc.throwStateConflictError(e.getPosition(), p); } return c1; } @@ -407,7 +407,7 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) throws L .changeOldMentions(vi.getName(), instanceName); if (!tc.checksStateSMT(prevState, expectState, fw.getPosition())) { // Invalid field transition - tc.createStateMismatchError(fw.getPosition(), fw.toString(), prevState, stateChange.getFrom()); + tc.throwStateRefinementError(fw.getPosition(), prevState, stateChange.getFrom()); return; } @@ -490,8 +490,7 @@ private static void changeState(TypeChecker tc, VariableInstance vi, List alias = new HashMap<>(); @@ -103,6 +105,8 @@ public Predicate changeAliasToRefinement(Context context, Factory f) throws Exce } ref = ref.changeAlias(alias, context, f); + ref.validateGhostInvocations(context, f); + return new Predicate(ref); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java index 13b68ba1..c9e2f58b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.stream.Collectors; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class AliasInvocation extends Expression { @@ -24,7 +25,7 @@ public List getArgs() { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitAliasInvocation(this); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java index 9e25e644..b60ef2ec 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java @@ -2,6 +2,7 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class BinaryExpression extends Expression { @@ -40,7 +41,7 @@ public boolean isArithmeticOperation() { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitBinaryExpression(this); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java index 48441251..fac053e6 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java @@ -3,17 +3,24 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; +import liquidjava.diagnostics.errors.ArgumentMismatchError; +import liquidjava.diagnostics.errors.LJError; +import liquidjava.diagnostics.errors.NotFoundError; import liquidjava.processor.context.Context; +import liquidjava.processor.context.GhostFunction; import liquidjava.processor.facade.AliasDTO; import liquidjava.rj_language.ast.typing.TypeInfer; import liquidjava.rj_language.visitors.ExpressionVisitor; import liquidjava.utils.Utils; +import liquidjava.utils.constants.Keys; import spoon.reflect.factory.Factory; +import spoon.reflect.reference.CtTypeReference; public abstract class Expression { - public abstract T accept(ExpressionVisitor visitor) throws Exception; + public abstract T accept(ExpressionVisitor visitor) throws LJError; public abstract void getVariableNames(List toAdd); @@ -33,7 +40,7 @@ public abstract class Expression { * Returns a simplified string representation of this expression with unqualified names (e.g., * com.example.State.open => open Default implementation delegates to toString() Subclasses that contain qualified * names should override this method - * + * * @return simplified string representation */ public String toSimplifiedString() { @@ -64,7 +71,7 @@ public boolean isLiteral() { /** * Checks if this expression produces a boolean type based on its structure - * + * * @return true if it is a boolean expression, false otherwise */ public boolean isBooleanExpression() { @@ -175,23 +182,33 @@ private void auxSubstituteState(Map subMap, String[] toChang } } - public Expression changeAlias(Map alias, Context ctx, Factory f) throws Exception { + public Expression changeAlias(Map alias, Context ctx, Factory f) throws LJError { Expression e = clone(); if (this instanceof AliasInvocation ai) { if (alias.containsKey(ai.name)) { // object state AliasDTO dto = alias.get(ai.name); + // check argument count + if (children.size() != dto.getVarNames().size()) { + String msg = String.format( + "Wrong number of arguments in alias invocation '%s': expected %d, got %d", ai.name, + dto.getVarNames().size(), children.size()); + throw new ArgumentMismatchError(msg); + } Expression sub = dto.getExpression().clone(); for (int i = 0; i < children.size(); i++) { Expression varExp = new Var(dto.getVarNames().get(i)); String varType = dto.getVarTypes().get(i); Expression aliasExp = children.get(i); + // check argument types boolean compatible = TypeInfer.checkCompatibleType(varType, aliasExp, ctx, f); - if (!compatible) - throw new Exception("Type Mismatch: Cannot substitute " + aliasExp + " : " - + TypeInfer.getType(ctx, f, aliasExp).get().getQualifiedName() + " by " + varExp + " : " - + TypeInfer.getType(ctx, f, varExp).get().getQualifiedName()); - + if (!compatible) { + String msg = String.format( + "Argument '%s' and parameter '%s' of alias '%s' types are incompatible: expected %s, got %s", + aliasExp, dto.getVarNames().get(i), ai.name, varType, + TypeInfer.getType(ctx, f, aliasExp).get().getQualifiedName()); + throw new ArgumentMismatchError(msg); + } sub = sub.substitute(varExp, aliasExp); } e = new GroupExpression(sub); @@ -201,13 +218,20 @@ public Expression changeAlias(Map alias, Context ctx, Factory return e; } - private void auxChangeAlias(Map alias, Context ctx, Factory f) throws Exception { + private void auxChangeAlias(Map alias, Context ctx, Factory f) throws LJError { if (hasChildren()) for (int i = 0; i < children.size(); i++) { if (children.get(i)instanceof AliasInvocation ai) { if (!alias.containsKey(ai.name)) - throw new Exception("Alias '" + ai.getName() + "' not found"); + throw new NotFoundError(ai.getName(), Keys.ALIAS); AliasDTO dto = alias.get(ai.name); + // check argument count + if (ai.children.size() != dto.getVarNames().size()) { + String msg = String.format( + "Wrong number of arguments in alias invocation '%s': expected %d, got %d", ai.name, + dto.getVarNames().size(), ai.children.size()); + throw new ArgumentMismatchError(msg); + } Expression sub = dto.getExpression().clone(); if (ai.hasChildren()) for (int j = 0; j < ai.children.size(); j++) { @@ -215,13 +239,15 @@ private void auxChangeAlias(Map alias, Context ctx, Factory f) String varType = dto.getVarTypes().get(j); Expression aliasExp = ai.children.get(j); - boolean checks = TypeInfer.checkCompatibleType(varType, aliasExp, ctx, f); - if (!checks) - throw new Exception( - "Type Mismatch: Cannot substitute " + varExp + ":" + varType + " by " + aliasExp - + ":" + TypeInfer.getType(ctx, f, aliasExp).get().getQualifiedName() - + " in alias '" + ai.name + "'"); - + // check argument types + boolean compatible = TypeInfer.checkCompatibleType(varType, aliasExp, ctx, f); + if (!compatible) { + String msg = String.format( + "Argument '%s' and parameter '%s' of alias '%s' types are incompatible: expected %s, got %s", + aliasExp, dto.getVarNames().get(i), ai.name, varType, + TypeInfer.getType(ctx, f, aliasExp).get().getQualifiedName()); + throw new ArgumentMismatchError(msg); + } sub = sub.substitute(varExp, aliasExp); } setChild(i, sub); @@ -229,4 +255,99 @@ private void auxChangeAlias(Map alias, Context ctx, Factory f) children.get(i).auxChangeAlias(alias, ctx, f); } } + + /** + * Validates all ghost function invocations within this expression against the provided context This method supports + * overloading by iterating through all ghost functions with the matching name If a valid signature is found, no + * error is thrown If the invocation name exists but no overload matches the argument types, an + * {@link ArgumentMismatchError} is thrown. + * + * @param ctx + * @param f + * + * @throws LJError + */ + public void validateGhostInvocations(Context ctx, Factory f) throws LJError { + if (this instanceof FunctionInvocation fi) { + // get all ghosts with the matching name + List candidates = ctx.getGhosts().stream().filter(g -> g.matches(fi.name)).toList(); + if (candidates.isEmpty()) + return; // not found error is thrown elsewhere + + // find matching overload + Optional found = candidates.stream().filter(g -> argumentsMatch(fi, g, ctx, f)).findFirst(); + if (found.isEmpty()) { + // no overload found, use the first candidate to throw the error + throwArgumentMismatchError(fi, candidates.get(0), ctx, f); + } + } + // recurse children + if (hasChildren()) { + for (Expression child : children) { + child.validateGhostInvocations(ctx, f); + } + } + } + + /** + * Checks if the arguments of the given function invocation match the parameters of the given ghost function + * + * @param fi + * @param g + * @param ctx + * @param f + */ + private boolean argumentsMatch(FunctionInvocation fi, GhostFunction g, Context ctx, Factory f) { + // check argument count + if (fi.children.size() != g.getParametersTypes().size()) + return false; + + // check argument types + for (int i = 0; i < fi.children.size(); i++) { + Expression arg = fi.children.get(i); + CtTypeReference expected = g.getParametersTypes().get(i); + Optional> actualOpt = TypeInfer.getType(ctx, f, arg); + + if (actualOpt.isPresent()) { + CtTypeReference actual = actualOpt.get(); + if (!actual.equals(expected) && !actual.isSubtypeOf(expected)) { + return false; + } + } + } + return true; + } + + /** + * Throws an ArgumentMismatchError for the given function invocation and ghost function + * + * @param fi + * @param g + * @param ctx + * @param f + * + * @throws ArgumentMismatchError + */ + private void throwArgumentMismatchError(FunctionInvocation fi, GhostFunction g, Context ctx, Factory f) + throws ArgumentMismatchError { + if (fi.children.size() != g.getParametersTypes().size()) { + throw new ArgumentMismatchError( + String.format("Wrong number of arguments in ghost invocation '%s': expected %d, got %d", fi.name, + g.getParametersTypes().size(), fi.children.size())); + } + + for (int i = 0; i < fi.children.size(); i++) { + CtTypeReference expected = g.getParametersTypes().get(i); + Optional> actualOpt = TypeInfer.getType(ctx, f, fi.children.get(i)); + if (actualOpt.isPresent()) { + CtTypeReference actual = actualOpt.get(); + if (!actual.equals(expected) && !actual.isSubtypeOf(expected)) { + Expression arg = fi.children.get(i); + throw new ArgumentMismatchError(String.format( + "Argument '%s' and its respective parameter of ghost '%s' types are incompatible: expected %s, got %s", + arg, fi.name, expected.getSimpleName(), actual.getSimpleName())); + } + } + } + } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java index f4dc3b9a..71f3febd 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.stream.Collectors; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; import liquidjava.utils.Utils; @@ -30,7 +31,7 @@ public void setChild(int index, Expression element) { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitFunctionInvocation(this); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java index 24e45fb4..c68edaa9 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java @@ -2,6 +2,7 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class GroupExpression extends Expression { @@ -15,7 +16,7 @@ public Expression getExpression() { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitGroupExpression(this); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java index 5b72ce6a..a779aad4 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java @@ -2,6 +2,7 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class Ite extends Expression { @@ -25,7 +26,7 @@ public Expression getElse() { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitIte(this); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralBoolean.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralBoolean.java index 8da1091c..81ab62d0 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralBoolean.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralBoolean.java @@ -2,6 +2,7 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class LiteralBoolean extends Expression { @@ -17,7 +18,7 @@ public LiteralBoolean(String value) { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitLiteralBoolean(this); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralInt.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralInt.java index 993c177b..7d68a738 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralInt.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralInt.java @@ -2,6 +2,7 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class LiteralInt extends Expression { @@ -17,7 +18,7 @@ public LiteralInt(String v) { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitLiteralInt(this); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralReal.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralReal.java index 7913438d..b21f1cf2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralReal.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralReal.java @@ -2,6 +2,7 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class LiteralReal extends Expression { @@ -17,7 +18,7 @@ public LiteralReal(String v) { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitLiteralReal(this); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralString.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralString.java index 1382535c..1d8d2842 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralString.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralString.java @@ -2,6 +2,7 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class LiteralString extends Expression { @@ -13,7 +14,7 @@ public LiteralString(String v) { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitLiteralString(this); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java index b805df84..4913e04c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java @@ -2,6 +2,7 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class UnaryExpression extends Expression { @@ -22,7 +23,7 @@ public String getOp() { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitUnaryExpression(this); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Var.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Var.java index e89365c6..495a873b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Var.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Var.java @@ -2,6 +2,7 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class Var extends Expression { @@ -17,7 +18,7 @@ public String getName() { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitVar(this); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java index 96530499..82e6ea52 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java @@ -52,6 +52,8 @@ import rj.grammar.RJParser.StartPredContext; import rj.grammar.RJParser.TargetInvocationContext; import rj.grammar.RJParser.VarContext; +import spoon.reflect.cu.SourcePosition; +import liquidjava.diagnostics.errors.ArgumentMismatchError; /** * Create refinements language AST using antlr @@ -168,14 +170,14 @@ private Expression functionCallCreate(FunctionCallContext rc) throws LJError { String name = Utils.qualifyName(prefix, ref); List args = getArgs(gc.args()); if (args.isEmpty()) - throw new SyntaxError("Ghost call cannot have empty arguments", ref); + throw new ArgumentMismatchError("Ghost call cannot have empty arguments"); return new FunctionInvocation(name, args); } else { AliasCallContext gc = rc.aliasCall(); String ref = gc.ID_UPPER().getText(); List args = getArgs(gc.args()); if (args.isEmpty()) - throw new SyntaxError("Alias call cannot have empty arguments", ref); + throw new ArgumentMismatchError("Alias call cannot have empty arguments"); return new AliasInvocation(ref, args); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java index 51ab5357..dc8d9089 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java @@ -1,5 +1,6 @@ package liquidjava.rj_language.visitors; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.ast.AliasInvocation; import liquidjava.rj_language.ast.BinaryExpression; import liquidjava.rj_language.ast.FunctionInvocation; @@ -13,25 +14,25 @@ import liquidjava.rj_language.ast.Var; public interface ExpressionVisitor { - T visitAliasInvocation(AliasInvocation alias) throws Exception; + T visitAliasInvocation(AliasInvocation alias) throws LJError; - T visitBinaryExpression(BinaryExpression exp) throws Exception; + T visitBinaryExpression(BinaryExpression exp) throws LJError; - T visitFunctionInvocation(FunctionInvocation fun) throws Exception; + T visitFunctionInvocation(FunctionInvocation fun) throws LJError; - T visitGroupExpression(GroupExpression exp) throws Exception; + T visitGroupExpression(GroupExpression exp) throws LJError; - T visitIte(Ite ite) throws Exception; + T visitIte(Ite ite) throws LJError; - T visitLiteralInt(LiteralInt lit) throws Exception; + T visitLiteralInt(LiteralInt lit) throws LJError; - T visitLiteralBoolean(LiteralBoolean lit) throws Exception; + T visitLiteralBoolean(LiteralBoolean lit) throws LJError; - T visitLiteralReal(LiteralReal lit) throws Exception; + T visitLiteralReal(LiteralReal lit) throws LJError; - T visitLiteralString(LiteralString lit) throws Exception; + T visitLiteralString(LiteralString lit) throws LJError; - T visitUnaryExpression(UnaryExpression exp) throws Exception; + T visitUnaryExpression(UnaryExpression exp) throws LJError; - T visitVar(Var var) throws Exception; + T visitVar(Var var) throws LJError; } \ No newline at end of file diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java b/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java index a11f0639..1425b438 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java @@ -2,6 +2,7 @@ import com.microsoft.z3.Expr; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.ast.AliasInvocation; import liquidjava.rj_language.ast.BinaryExpression; import liquidjava.rj_language.ast.FunctionInvocation; @@ -24,7 +25,7 @@ public ExpressionToZ3Visitor(TranslatorToZ3 ctx) { } @Override - public Expr visitAliasInvocation(AliasInvocation alias) throws Exception { + public Expr visitAliasInvocation(AliasInvocation alias) throws LJError { Expr[] argsExpr = new Expr[alias.getArgs().size()]; for (int i = 0; i < argsExpr.length; i++) { argsExpr[i] = alias.getArgs().get(i).accept(this); @@ -33,7 +34,7 @@ public Expr visitAliasInvocation(AliasInvocation alias) throws Exception { } @Override - public Expr visitBinaryExpression(BinaryExpression exp) throws Exception { + public Expr visitBinaryExpression(BinaryExpression exp) throws LJError { Expr e1 = exp.getFirstOperand().accept(this); Expr e2 = exp.getSecondOperand().accept(this); return switch (exp.getOperator()) { @@ -56,7 +57,7 @@ public Expr visitBinaryExpression(BinaryExpression exp) throws Exception { } @Override - public Expr visitFunctionInvocation(FunctionInvocation fun) throws Exception { + public Expr visitFunctionInvocation(FunctionInvocation fun) throws LJError { Expr[] argsExpr = new Expr[fun.getArgs().size()]; for (int i = 0; i < argsExpr.length; i++) { argsExpr[i] = fun.getArgs().get(i).accept(this); @@ -65,17 +66,17 @@ public Expr visitFunctionInvocation(FunctionInvocation fun) throws Exception } @Override - public Expr visitGroupExpression(GroupExpression exp) throws Exception { + public Expr visitGroupExpression(GroupExpression exp) throws LJError { return exp.getExpression().accept(this); } @Override - public Expr visitIte(Ite ite) throws Exception { + public Expr visitIte(Ite ite) throws LJError { return ctx.makeIte(ite.getCondition().accept(this), ite.getThen().accept(this), ite.getElse().accept(this)); } @Override - public Expr visitVar(Var var) throws Exception { + public Expr visitVar(Var var) throws LJError { return ctx.makeVariable(var.getName()); } @@ -100,7 +101,7 @@ public Expr visitLiteralString(LiteralString lit) { } @Override - public Expr visitUnaryExpression(UnaryExpression exp) throws Exception { + public Expr visitUnaryExpression(UnaryExpression exp) throws LJError { return switch (exp.getOp()) { case "-" -> ctx.makeMinus(exp.getExpression().accept(this)); case "!" -> ctx.mkNot(exp.getExpression().accept(this)); diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java b/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java index 4bec8f9e..bf00f999 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java @@ -8,7 +8,6 @@ import liquidjava.processor.context.Context; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.Expression; -import liquidjava.smt.errors.TypeCheckError; public class SMTEvaluator { diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java index 6d08a32d..e844ad12 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java @@ -16,8 +16,10 @@ import java.util.HashMap; import java.util.List; import java.util.Map; + +import liquidjava.diagnostics.errors.LJError; +import liquidjava.diagnostics.errors.NotFoundError; import liquidjava.processor.context.AliasWrapper; -import liquidjava.smt.errors.NotFoundError; import liquidjava.utils.Utils; import liquidjava.utils.constants.Keys; @@ -67,22 +69,22 @@ public Expr makeBooleanLiteral(boolean value) { return z3.mkBool(value); } - private Expr getVariableTranslation(String name) throws Exception { + private Expr getVariableTranslation(String name) throws LJError { if (!varTranslation.containsKey(name)) - throw new NotFoundError(Keys.VARIABLE, name); + throw new NotFoundError(name, Keys.VARIABLE); Expr e = varTranslation.get(name); if (e == null) e = varTranslation.get(String.format("this#%s", name)); if (e == null) - throw new SyntaxException("Unknown variable:" + name); + throw new NotFoundError(name, Keys.VARIABLE); return e; } - public Expr makeVariable(String name) throws Exception { + public Expr makeVariable(String name) throws LJError { return getVariableTranslation(name); // int[] not in varTranslation } - public Expr makeFunctionInvocation(String name, Expr[] params) throws Exception { + public Expr makeFunctionInvocation(String name, Expr[] params) throws LJError { if (name.equals("addToIndex")) return makeStore(params); if (name.equals("getFromIndex")) @@ -111,7 +113,7 @@ public Expr makeFunctionInvocation(String name, Expr[] params) throws Exce * name and number of parameters, preferring an exact qualified-name match if found among candidates; otherwise * returns the first compatible candidate and relies on later coercion via var supertypes. */ - private FuncDecl resolveFunctionDeclFallback(String name, Expr[] params) throws Exception { + private FuncDecl resolveFunctionDeclFallback(String name, Expr[] params) throws LJError { String simple = Utils.getSimpleName(name); FuncDecl candidate = null; for (Map.Entry> entry : funcTranslation.entrySet()) { @@ -134,7 +136,7 @@ private FuncDecl resolveFunctionDeclFallback(String name, Expr[] params) t if (candidate != null) { return candidate; } - throw new NotFoundError(Keys.GHOST, name); + throw new NotFoundError(name, Keys.GHOST); } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/errors/TypeCheckError.java b/liquidjava-verifier/src/main/java/liquidjava/smt/TypeCheckError.java similarity index 80% rename from liquidjava-verifier/src/main/java/liquidjava/smt/errors/TypeCheckError.java rename to liquidjava-verifier/src/main/java/liquidjava/smt/TypeCheckError.java index 2419b740..04c6c07d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/errors/TypeCheckError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/TypeCheckError.java @@ -1,4 +1,4 @@ -package liquidjava.smt.errors; +package liquidjava.smt; public class TypeCheckError extends Exception { diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/errors/NotFoundError.java b/liquidjava-verifier/src/main/java/liquidjava/smt/errors/NotFoundError.java deleted file mode 100644 index a6fb544a..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/errors/NotFoundError.java +++ /dev/null @@ -1,21 +0,0 @@ -package liquidjava.smt.errors; - -public class NotFoundError extends SMTError { - - private final String name; - private final String kind; - - public NotFoundError(String kind, String name) { - super(String.format("%s '%s' not found", kind, name)); - this.name = name; - this.kind = kind; - } - - public String getName() { - return name; - } - - public String getKind() { - return kind; - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/errors/SMTError.java b/liquidjava-verifier/src/main/java/liquidjava/smt/errors/SMTError.java deleted file mode 100644 index 04d9890c..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/errors/SMTError.java +++ /dev/null @@ -1,19 +0,0 @@ -package liquidjava.smt.errors; - -import spoon.reflect.declaration.CtElement; - -public class SMTError extends Exception { - private CtElement location; - - public SMTError(String message) { - super(message); - } - - public CtElement getLocation() { - return location; - } - - public void setLocation(CtElement location) { - this.location = location; - } -} diff --git a/liquidjava-verifier/src/main/java/liquidjava/utils/constants/Keys.java b/liquidjava-verifier/src/main/java/liquidjava/utils/constants/Keys.java index cba8ee40..93311c01 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/utils/constants/Keys.java +++ b/liquidjava-verifier/src/main/java/liquidjava/utils/constants/Keys.java @@ -8,4 +8,5 @@ public final class Keys { public static final String OLD = "old"; public static final String VARIABLE = "Variable"; public static final String GHOST = "Ghost"; + public static final String ALIAS = "Alias"; } \ No newline at end of file diff --git a/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java b/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java index be0f1ca0..0f3552e8 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java +++ b/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java @@ -56,6 +56,10 @@ else if (shouldFail(pathName)) { fail(); } LJError error = diagnostics.getErrors().iterator().next(); + if (error.getPosition() == null) { + System.out.println("Error in: " + pathName + " --- error has no position information"); + fail(); + } if (expected.isPresent()) { String expectedError = expected.get(); String foundError = error.getTitle(); diff --git a/liquidjava-verifier/src/test/java/liquidjava/utils/TestUtils.java b/liquidjava-verifier/src/test/java/liquidjava/utils/TestUtils.java index 9b05725e..cd15f869 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/utils/TestUtils.java +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/TestUtils.java @@ -10,6 +10,7 @@ public class TestUtils { /** * Determines if the given path indicates that the test should pass + * * @param path */ public static boolean shouldPass(String path) { @@ -18,6 +19,7 @@ public static boolean shouldPass(String path) { /** * Determines if the given path indicates that the test should fail + * * @param path */ public static boolean shouldFail(String path) { From 6e9d4fed4842208f1381d735161dbc2a0a7fe5d7 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Thu, 4 Dec 2025 17:51:00 +0000 Subject: [PATCH 13/25] Use Annotation Position in `ExternalClassNotFoundWarning` (#130) --- .../ExternalRefinementTypeChecker.java | 12 ++++++++---- .../processor/refinement_checker/TypeChecker.java | 9 +++------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java index 5773a548..154e94d0 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/ExternalRefinementTypeChecker.java @@ -14,6 +14,8 @@ import liquidjava.rj_language.Predicate; import liquidjava.rj_language.parsing.RefinementsParser; import liquidjava.utils.Utils; +import spoon.reflect.code.CtLiteral; +import spoon.reflect.declaration.CtAnnotation; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtField; @@ -38,12 +40,14 @@ public void visitCtClass(CtClass ctClass) { @Override public void visitCtInterface(CtInterface intrface) { - Optional externalRefinements = getExternalRefinement(intrface); - if (externalRefinements.isPresent()) { - this.prefix = externalRefinements.get(); + Optional> externalRef = getExternalRefinement(intrface); + if (externalRef.isPresent()) { + @SuppressWarnings("unchecked") + CtLiteral literal = (CtLiteral) externalRef.get().getAllValues().get("value"); + this.prefix = literal.getValue(); if (!classExists(prefix)) { String message = String.format("Could not find class '%s'", prefix); - diagnostics.add(new ExternalClassNotFoundWarning(intrface.getPosition(), message, prefix)); + diagnostics.add(new ExternalClassNotFoundWarning(externalRef.get().getPosition(), message, prefix)); return; } getRefinementFromAnnotation(intrface); diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java index 0078f89f..b0d1eda2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java @@ -238,16 +238,13 @@ protected void handleAlias(String ref, CtElement element) throws LJError { } } - Optional getExternalRefinement(CtInterface intrface) { - Optional ref = Optional.empty(); + Optional> getExternalRefinement(CtInterface intrface) { for (CtAnnotation ann : intrface.getAnnotations()) if (ann.getActualAnnotation().annotationType().getCanonicalName() .contentEquals("liquidjava.specification.ExternalRefinementsFor")) { - @SuppressWarnings("unchecked") - CtLiteral s = (CtLiteral) ann.getAllValues().get("value"); - ref = Optional.of(s.getValue()); + return Optional.of(ann); } - return ref; + return Optional.empty(); } public void checkVariableRefinements(Predicate refinementFound, String simpleName, CtTypeReference type, From 86853536e68e8eaa4f090c4d6bbcd147759fcd51 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 10 Dec 2025 17:19:04 +0000 Subject: [PATCH 14/25] Simplification Fixes (#125) --- liquidjava-verifier/pom.xml | 2 +- .../diagnostics/errors/RefinementError.java | 13 +- .../refinement_checker/VCChecker.java | 6 +- .../rj_language/ast/Expression.java | 2 +- .../rj_language/opt/ConstantPropagation.java | 100 +++++-- .../rj_language/opt/ExpressionSimplifier.java | 85 ++++-- .../rj_language/opt/VariableResolver.java | 78 +++++- .../derivation_node/VarDerivationNode.java | 11 + .../opt/ExpressionSimplifierTest.java | 244 +++++++++++++++++- .../rj_language/opt/VariableResolverTest.java | 35 +++ 10 files changed, 499 insertions(+), 77 deletions(-) diff --git a/liquidjava-verifier/pom.xml b/liquidjava-verifier/pom.xml index 2461306e..af57236d 100644 --- a/liquidjava-verifier/pom.xml +++ b/liquidjava-verifier/pom.xml @@ -11,7 +11,7 @@ io.github.liquid-java liquidjava-verifier - 0.0.4 + 0.0.8 liquidjava-verifier LiquidJava Verifier https://github.com/liquid-java/liquidjava diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java index a2c13c91..93a8a447 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java @@ -12,19 +12,18 @@ */ public class RefinementError extends LJError { - private final String expected; + private final ValDerivationNode expected; private final ValDerivationNode found; - public RefinementError(SourcePosition position, Expression expected, ValDerivationNode found, + public RefinementError(SourcePosition position, ValDerivationNode expected, ValDerivationNode found, TranslationTable translationTable) { - super("Refinement Error", - String.format("%s is not a subtype of %s", found.getValue(), expected.toSimplifiedString()), position, - translationTable); - this.expected = expected.toSimplifiedString(); + super("Refinement Error", String.format("%s is not a subtype of %s", found.getValue(), expected.getValue()), + position, translationTable); + this.expected = expected; this.found = found; } - public String getExpected() { + public ValDerivationNode getExpected() { return expected; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java index 3df794ac..0c2fba5e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java @@ -52,8 +52,8 @@ public void processSubtyping(Predicate expectedType, List list, CtEl } boolean isSubtype = smtChecks(expected, premises, element.getPosition()); if (!isSubtype) - throw new RefinementError(element.getPosition(), expectedType.getExpression(), - premisesBeforeChange.simplify(), map); + throw new RefinementError(element.getPosition(), expectedType.simplify(), premisesBeforeChange.simplify(), + map); } /** @@ -263,7 +263,7 @@ protected void throwRefinementError(SourcePosition position, Predicate expected, gatherVariables(found, lrv, mainVars); TranslationTable map = new TranslationTable(); Predicate premises = joinPredicates(expected, mainVars, lrv, map).toConjunctions(); - throw new RefinementError(position, expected.getExpression(), premises.simplify(), map); + throw new RefinementError(position, expected.simplify(), premises.simplify(), map); } protected void throwStateRefinementError(SourcePosition position, Predicate found, Predicate expected) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java index fac053e6..5449949b 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java @@ -271,7 +271,7 @@ public void validateGhostInvocations(Context ctx, Factory f) throws LJError { if (this instanceof FunctionInvocation fi) { // get all ghosts with the matching name List candidates = ctx.getGhosts().stream().filter(g -> g.matches(fi.name)).toList(); - if (candidates.isEmpty()) + if (candidates.isEmpty()) return; // not found error is thrown elsewhere // find matching overload diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ConstantPropagation.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ConstantPropagation.java index 5c74897f..5cc0562e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ConstantPropagation.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ConstantPropagation.java @@ -10,6 +10,7 @@ import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; import liquidjava.rj_language.opt.derivation_node.VarDerivationNode; +import java.util.HashMap; import java.util.Map; public class ConstantPropagation { @@ -19,23 +20,37 @@ public class ConstantPropagation { * VariableResolver to extract variable equalities from the expression first. Returns a derivation node representing * the propagation steps taken. */ - public static ValDerivationNode propagate(Expression exp) { + public static ValDerivationNode propagate(Expression exp, ValDerivationNode previousOrigin) { Map substitutions = VariableResolver.resolve(exp); - return propagateRecursive(exp, substitutions); + + // map of variable origins from the previous derivation tree + Map varOrigins = new HashMap<>(); + if (previousOrigin != null) { + extractVarOrigins(previousOrigin, varOrigins); + } + return propagateRecursive(exp, substitutions, varOrigins); } /** * Recursively performs constant propagation on an expression (e.g. x + y && x == 1 && y == 2 => 1 + 2) */ - private static ValDerivationNode propagateRecursive(Expression exp, Map subs) { + private static ValDerivationNode propagateRecursive(Expression exp, Map subs, + Map varOrigins) { // substitute variable if (exp instanceof Var var) { String name = var.getName(); Expression value = subs.get(name); // substitution - if (value != null) - return new ValDerivationNode(value.clone(), new VarDerivationNode(name)); + if (value != null) { + // check if this variable has an origin from a previous pass + DerivationNode previousOrigin = varOrigins.get(name); + + // preserve origin if value came from previous derivation + DerivationNode origin = previousOrigin != null ? new VarDerivationNode(name, previousOrigin) + : new VarDerivationNode(name); + return new ValDerivationNode(value.clone(), origin); + } // no substitution return new ValDerivationNode(var, null); @@ -43,31 +58,33 @@ private static ValDerivationNode propagateRecursive(Expression exp, Map varOrigins) { + if (node == null) + return; + + Expression value = node.getValue(); + DerivationNode origin = node.getOrigin(); + + // check for equality expressions + if (value instanceof BinaryExpression binExp && "==".equals(binExp.getOperator()) + && origin instanceof BinaryDerivationNode binOrigin) { + Expression left = binExp.getFirstOperand(); + Expression right = binExp.getSecondOperand(); + + // extract variable name and value derivation from either side + String varName = null; + ValDerivationNode valueDerivation = null; + + if (left instanceof Var var && right.isLiteral()) { + varName = var.getName(); + valueDerivation = binOrigin.getRight(); + } else if (right instanceof Var var && left.isLiteral()) { + varName = var.getName(); + valueDerivation = binOrigin.getLeft(); + } + if (varName != null && valueDerivation != null && valueDerivation.getOrigin() != null) { + varOrigins.put(varName, valueDerivation.getOrigin()); + } + } + + // recursively process the origin tree + if (origin instanceof BinaryDerivationNode binOrigin) { + extractVarOrigins(binOrigin.getLeft(), varOrigins); + extractVarOrigins(binOrigin.getRight(), varOrigins); + } else if (origin instanceof UnaryDerivationNode unaryOrigin) { + extractVarOrigins(unaryOrigin.getOperand(), varOrigins); + } else if (origin instanceof ValDerivationNode valOrigin) { + extractVarOrigins(valOrigin, varOrigins); + } + } } \ No newline at end of file diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ExpressionSimplifier.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ExpressionSimplifier.java index 4bb4050a..2e43e326 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ExpressionSimplifier.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/ExpressionSimplifier.java @@ -14,41 +14,88 @@ public class ExpressionSimplifier { * Returns a derivation node representing the tree of simplifications applied */ public static ValDerivationNode simplify(Expression exp) { - ValDerivationNode prop = ConstantPropagation.propagate(exp); + ValDerivationNode fixedPoint = simplifyToFixedPoint(null, exp); + return simplifyValDerivationNode(fixedPoint); + } + + /** + * Recursively applies propagation and folding until the expression stops changing (fixed point) Stops early if the + * expression simplifies to 'true', which means we've simplified too much + */ + private static ValDerivationNode simplifyToFixedPoint(ValDerivationNode current, Expression prevExp) { + // apply propagation and folding + ValDerivationNode prop = ConstantPropagation.propagate(prevExp, current); ValDerivationNode fold = ConstantFolding.fold(prop); - return simplifyDerivationTree(fold); + ValDerivationNode simplified = simplifyValDerivationNode(fold); + Expression currExp = simplified.getValue(); + + // fixed point reached + if (current != null && currExp.equals(current.getValue())) { + return current; + } + + // continue simplifying + return simplifyToFixedPoint(simplified, simplified.getValue()); } /** * Recursively simplifies the derivation tree by removing redundant conjuncts */ - private static ValDerivationNode simplifyDerivationTree(ValDerivationNode node) { + private static ValDerivationNode simplifyValDerivationNode(ValDerivationNode node) { Expression value = node.getValue(); DerivationNode origin = node.getOrigin(); // binary expression with && - if (value instanceof BinaryExpression binExp) { - if ("&&".equals(binExp.getOperator()) && origin instanceof BinaryDerivationNode binOrigin) { - // recursively simplify children - ValDerivationNode leftSimplified = simplifyDerivationTree(binOrigin.getLeft()); - ValDerivationNode rightSimplified = simplifyDerivationTree(binOrigin.getRight()); - - // check if either side is redundant - if (isRedundant(leftSimplified.getValue())) - return rightSimplified; - if (isRedundant(rightSimplified.getValue())) - return leftSimplified; - - // return the conjunction with simplified children - Expression newValue = new BinaryExpression(leftSimplified.getValue(), "&&", rightSimplified.getValue()); - DerivationNode newOrigin = new BinaryDerivationNode(leftSimplified, rightSimplified, "&&"); - return new ValDerivationNode(newValue, newOrigin); + if (value instanceof BinaryExpression binExp && "&&".equals(binExp.getOperator())) { + ValDerivationNode leftSimplified; + ValDerivationNode rightSimplified; + + if (origin instanceof BinaryDerivationNode binOrigin) { + leftSimplified = simplifyValDerivationNode(binOrigin.getLeft()); + rightSimplified = simplifyValDerivationNode(binOrigin.getRight()); + } else { + leftSimplified = simplifyValDerivationNode(new ValDerivationNode(binExp.getFirstOperand(), null)); + rightSimplified = simplifyValDerivationNode(new ValDerivationNode(binExp.getSecondOperand(), null)); } + + // check if either side is redundant + if (isRedundant(leftSimplified.getValue())) + return rightSimplified; + if (isRedundant(rightSimplified.getValue())) + return leftSimplified; + + // collapse identical sides (x && x => x) + if (leftSimplified.getValue().equals(rightSimplified.getValue())) { + return leftSimplified; + } + + // collapse symmetric equalities (e.g. x == y && y == x => x == y) + if (isSymmetricEquality(leftSimplified.getValue(), rightSimplified.getValue())) { + return leftSimplified; + } + + // return the conjunction with simplified children + Expression newValue = new BinaryExpression(leftSimplified.getValue(), "&&", rightSimplified.getValue()); + DerivationNode newOrigin = new BinaryDerivationNode(leftSimplified, rightSimplified, "&&"); + return new ValDerivationNode(newValue, newOrigin); } // no simplification return node; } + private static boolean isSymmetricEquality(Expression left, Expression right) { + if (left instanceof BinaryExpression b1 && "==".equals(b1.getOperator()) && right instanceof BinaryExpression b2 + && "==".equals(b2.getOperator())) { + + Expression l1 = b1.getFirstOperand(); + Expression r1 = b1.getSecondOperand(); + Expression l2 = b2.getFirstOperand(); + Expression r2 = b2.getSecondOperand(); + return l1.equals(r2) && r1.equals(l2); + } + return false; + } + /** * Checks if an expression is redundant (e.g. true or x == x) */ diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariableResolver.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariableResolver.java index b93c78db..9d5850e6 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariableResolver.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/VariableResolver.java @@ -12,25 +12,30 @@ public class VariableResolver { /** - * Extracts variables with constant values from an expression Returns a map from variable names to their values + * Extracts variables with constant values from an expression + * + * @param exp + * + * @returns map from variable names to their values */ public static Map resolve(Expression exp) { - // if the expression is just a single equality (not a conjunction) don't extract it - // this avoids creating tautologies like "1 == 1" after substitution, which are then simplified to "true" - if (exp instanceof BinaryExpression be) { - if ("==".equals(be.getOperator())) { - return new HashMap<>(); - } - } - Map map = new HashMap<>(); + + // extract variable equalities recursively resolveRecursive(exp, map); + + // remove variables that were not used in the expression + map.entrySet().removeIf(entry -> !hasUsage(exp, entry.getKey())); + + // transitively resolve variables return resolveTransitive(map); } /** * Recursively extracts variable equalities from an expression (e.g. ... && x == 1 && y == 2 => map: x -> 1, y -> 2) - * Modifies the given map in place + * + * @param exp + * @param map */ private static void resolveRecursive(Expression exp, Map map) { if (!(exp instanceof BinaryExpression be)) @@ -43,16 +48,20 @@ private static void resolveRecursive(Expression exp, Map map } else if ("==".equals(op)) { Expression left = be.getFirstOperand(); Expression right = be.getSecondOperand(); - if (left instanceof Var && (right.isLiteral() || right instanceof Var)) { - map.put(((Var) left).getName(), right.clone()); - } else if (right instanceof Var && left.isLiteral()) { - map.put(((Var) right).getName(), left.clone()); + if (left instanceof Var var && right.isLiteral()) { + map.put(var.getName(), right.clone()); + } else if (right instanceof Var var && left.isLiteral()) { + map.put(var.getName(), left.clone()); } } } /** * Handles transitive variable equalities in the map (e.g. map: x -> y, y -> 1 => map: x -> 1, y -> 1) + * + * @param map + * + * @return new map with resolved values */ private static Map resolveTransitive(Map map) { Map result = new HashMap<>(); @@ -65,6 +74,12 @@ private static Map resolveTransitive(Map /** * Returns the value of a variable by looking up in the map recursively Uses the seen set to avoid circular * references (e.g. x -> y, y -> x) which would cause infinite recursion + * + * @param exp + * @param map + * @param seen + * + * @return resolved expression */ private static Expression lookup(Expression exp, Map map, Set seen) { if (!(exp instanceof Var)) @@ -81,4 +96,39 @@ private static Expression lookup(Expression exp, Map map, Se seen.add(name); return lookup(value, map, seen); } + + /** + * Checks if a variable is used in the expression (excluding its own definitions) + * + * @param exp + * @param name + * + * @return true if used, false otherwise + */ + private static boolean hasUsage(Expression exp, String name) { + // exclude own definitions + if (exp instanceof BinaryExpression binary && "==".equals(binary.getOperator())) { + Expression left = binary.getFirstOperand(); + Expression right = binary.getSecondOperand(); + if (left instanceof Var v && v.getName().equals(name) && right.isLiteral()) + return false; + if (right instanceof Var v && v.getName().equals(name) && left.isLiteral()) + return false; + } + + // usage found + if (exp instanceof Var var && var.getName().equals(name)) { + return true; + } + + // recurse children + if (exp.hasChildren()) { + for (Expression child : exp.getChildren()) + if (hasUsage(child, name)) + return true; + } + + // usage not found + return false; + } } \ No newline at end of file diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/VarDerivationNode.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/VarDerivationNode.java index 1c044f52..c134a44e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/VarDerivationNode.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/VarDerivationNode.java @@ -3,12 +3,23 @@ public class VarDerivationNode extends DerivationNode { private final String var; + private final DerivationNode origin; public VarDerivationNode(String var) { this.var = var; + this.origin = null; + } + + public VarDerivationNode(String var, DerivationNode origin) { + this.var = var; + this.origin = origin; } public String getVar() { return var; } + + public DerivationNode getOrigin() { + return origin; + } } diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/ExpressionSimplifierTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/ExpressionSimplifierTest.java index ff034f93..b49ce805 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/ExpressionSimplifierTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/ExpressionSimplifierTest.java @@ -110,7 +110,7 @@ void testSimpleComparison() { // Then assertNotNull(result, "Result should not be null"); - assertTrue(result.getValue() instanceof LiteralBoolean, "Result should be a boolean"); + assertInstanceOf(LiteralBoolean.class, result.getValue(), "Result should be a boolean"); assertFalse(((LiteralBoolean) result.getValue()).isBooleanTrue(), "Expected result to befalse"); // (y || true) && y == false => false || true = true @@ -246,8 +246,8 @@ void testComplexArithmeticWithMultipleOperations() { // Then assertNotNull(result, "Result should not be null"); assertNotNull(result.getValue(), "Result value should not be null"); - assertTrue(result.getValue() instanceof LiteralBoolean, "Result should be a boolean literal"); - assertTrue(((LiteralBoolean) result.getValue()).isBooleanTrue(), "Expected result to be true"); + assertInstanceOf(LiteralBoolean.class, result.getValue(), "Result should be a boolean literal"); + assertTrue(result.getValue().isBooleanTrue(), "Expected result to be true"); // 5 * 2 + 7 - 3 ValDerivationNode val5 = new ValDerivationNode(new LiteralInt(5), new VarDerivationNode("a")); @@ -305,7 +305,64 @@ void testComplexArithmeticWithMultipleOperations() { } @Test - void testSingleEqualityNotSimplifiedToTrue() { + void testFixedPointSimplification() { + // Given: x == -y && y == a / b && a == 6 && b == 3 + // Expected: x == -2 + + Expression varX = new Var("x"); + Expression varY = new Var("y"); + Expression varA = new Var("a"); + Expression varB = new Var("b"); + + Expression aDivB = new BinaryExpression(varA, "/", varB); + Expression yEqualsADivB = new BinaryExpression(varY, "==", aDivB); + Expression negY = new UnaryExpression("-", varY); + Expression xEqualsNegY = new BinaryExpression(varX, "==", negY); + Expression six = new LiteralInt(6); + Expression aEquals6 = new BinaryExpression(varA, "==", six); + Expression three = new LiteralInt(3); + Expression bEquals3 = new BinaryExpression(varB, "==", three); + Expression firstAnd = new BinaryExpression(xEqualsNegY, "&&", yEqualsADivB); + Expression secondAnd = new BinaryExpression(aEquals6, "&&", bEquals3); + Expression fullExpression = new BinaryExpression(firstAnd, "&&", secondAnd); + + // When + ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression); + + // Then + assertNotNull(result, "Result should not be null"); + assertEquals("x == -2", result.getValue().toString(), "Expected result to be x == -2"); + + // Compare derivation tree structure + + // Build the derivation chain for the right side: + // 6 came from a, 3 came from b + ValDerivationNode val6FromA = new ValDerivationNode(new LiteralInt(6), new VarDerivationNode("a")); + ValDerivationNode val3FromB = new ValDerivationNode(new LiteralInt(3), new VarDerivationNode("b")); + + // 6 / 3 -> 2 + BinaryDerivationNode divOrigin = new BinaryDerivationNode(val6FromA, val3FromB, "/"); + + // 2 came from y, and y's value came from 6 / 2 + VarDerivationNode yChainedOrigin = new VarDerivationNode("y", divOrigin); + ValDerivationNode val2FromY = new ValDerivationNode(new LiteralInt(2), yChainedOrigin); + + // -2 + UnaryDerivationNode negOrigin = new UnaryDerivationNode(val2FromY, "-"); + ValDerivationNode rightNode = new ValDerivationNode(new LiteralInt(-2), negOrigin); + + // Left node x has no origin + ValDerivationNode leftNode = new ValDerivationNode(new Var("x"), null); + + // Root equality + BinaryDerivationNode rootOrigin = new BinaryDerivationNode(leftNode, rightNode, "=="); + ValDerivationNode expected = new ValDerivationNode(result.getValue(), rootOrigin); + + assertDerivationEquals(expected, result, "Derivation tree structure"); + } + + @Test + void testSingleEqualityShouldNotSimplify() { // Given: x == 1 // Expected: x == 1 (should not be simplified to "true") @@ -322,13 +379,177 @@ void testSingleEqualityNotSimplifiedToTrue() { "Single equality should not be simplified to a boolean literal"); // The result should be the original expression unchanged - assertTrue(result.getValue() instanceof BinaryExpression, "Result should still be a binary expression"); + assertInstanceOf(BinaryExpression.class, result.getValue(), "Result should still be a binary expression"); BinaryExpression resultExpr = (BinaryExpression) result.getValue(); assertEquals("==", resultExpr.getOperator(), "Operator should still be =="); assertEquals("x", resultExpr.getFirstOperand().toString(), "Left operand should be x"); assertEquals("1", resultExpr.getSecondOperand().toString(), "Right operand should be 1"); } + @Test + void testTwoEqualitiesShouldNotSimplify() { + // Given: x == 1 && y == 2 + // Expected: x == 1 && y == 2 (should not be simplified to "true") + + Expression varX = new Var("x"); + Expression one = new LiteralInt(1); + Expression xEquals1 = new BinaryExpression(varX, "==", one); + + Expression varY = new Var("y"); + Expression two = new LiteralInt(2); + Expression yEquals2 = new BinaryExpression(varY, "==", two); + Expression fullExpression = new BinaryExpression(xEquals1, "&&", yEquals2); + + // When + ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression); + + // Then + assertNotNull(result, "Result should not be null"); + assertEquals("x == 1 && y == 2", result.getValue().toString(), + "Two equalities should not be simplified to a boolean literal"); + + // The result should be the original expression unchanged + assertInstanceOf(BinaryExpression.class, result.getValue(), "Result should still be a binary expression"); + BinaryExpression resultExpr = (BinaryExpression) result.getValue(); + assertEquals("&&", resultExpr.getOperator(), "Operator should still be &&"); + assertEquals("x == 1", resultExpr.getFirstOperand().toString(), "Left operand should be x == 1"); + assertEquals("y == 2", resultExpr.getSecondOperand().toString(), "Right operand should be y == 2"); + } + + @Test + void testSameVarTwiceShouldSimplifyToSingle() { + // Given: x && x + // Expected: x + + Expression varX = new Var("x"); + Expression fullExpression = new BinaryExpression(varX, "&&", varX); + // When + + ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression); + // Then + + assertNotNull(result, "Result should not be null"); + assertEquals("x", result.getValue().toString(), + "Same variable twice should be simplified to a single variable"); + } + + @Test + void testSameEqualityTwiceShouldSimplifyToSingle() { + // Given: x == 1 && x == 1 + // Expected: x == 1 + + Expression varX = new Var("x"); + Expression one = new LiteralInt(1); + Expression xEquals1First = new BinaryExpression(varX, "==", one); + Expression xEquals1Second = new BinaryExpression(varX, "==", one); + Expression fullExpression = new BinaryExpression(xEquals1First, "&&", xEquals1Second); + + // When + ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression); + + // Then + assertNotNull(result, "Result should not be null"); + assertEquals("x == 1", result.getValue().toString(), + "Same equality twice should be simplified to a single equality"); + } + + @Test + void testSameExpressionTwiceShouldSimplifyToSingle() { + // Given: a + b == 1 && a + b == 1 + // Expected: a + b == 1 + + Expression varA = new Var("a"); + Expression varB = new Var("b"); + Expression sum = new BinaryExpression(varA, "+", varB); + Expression one = new LiteralInt(1); + Expression sumEquals3First = new BinaryExpression(sum, "==", one); + Expression sumEquals3Second = new BinaryExpression(sum, "==", one); + Expression fullExpression = new BinaryExpression(sumEquals3First, "&&", sumEquals3Second); + + // When + ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression); + + // Then + assertNotNull(result, "Result should not be null"); + assertEquals("a + b == 1", result.getValue().toString(), + "Same expression twice should be simplified to a single equality"); + } + + @Test + void testSymmetricEqualityShouldSimplify() { + // Given: x == y && y == x + // Expected: x == y + + Expression varX = new Var("x"); + Expression varY = new Var("y"); + Expression xEqualsY = new BinaryExpression(varX, "==", varY); + Expression yEqualsX = new BinaryExpression(varY, "==", varX); + Expression fullExpression = new BinaryExpression(xEqualsY, "&&", yEqualsX); + + // When + ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression); + + // Then + assertNotNull(result, "Result should not be null"); + assertEquals("x == y", result.getValue().toString(), + "Symmetric equality should be simplified to a single equality"); + } + + @Test + void testRealExpression() { + // Given: #a_5 == -#fresh_4 && #fresh_4 == #x_2 / #y_3 && #x_2 == #x_0 && #x_0 == 6 && #y_3 == #y_1 && #y_1 == 3 + // Expected: #a_5 == -2 + + Expression varA5 = new Var("#a_5"); + Expression varFresh4 = new Var("#fresh_4"); + Expression varX2 = new Var("#x_2"); + Expression varY3 = new Var("#y_3"); + Expression varX0 = new Var("#x_0"); + Expression varY1 = new Var("#y_1"); + Expression six = new LiteralInt(6); + Expression three = new LiteralInt(3); + Expression fresh4EqualsX2DivY3 = new BinaryExpression(varFresh4, "==", new BinaryExpression(varX2, "/", varY3)); + Expression x2EqualsX0 = new BinaryExpression(varX2, "==", varX0); + Expression x0Equals6 = new BinaryExpression(varX0, "==", six); + Expression y3EqualsY1 = new BinaryExpression(varY3, "==", varY1); + Expression y1Equals3 = new BinaryExpression(varY1, "==", three); + Expression negFresh4 = new UnaryExpression("-", varFresh4); + Expression a5EqualsNegFresh4 = new BinaryExpression(varA5, "==", negFresh4); + Expression firstAnd = new BinaryExpression(a5EqualsNegFresh4, "&&", fresh4EqualsX2DivY3); + Expression secondAnd = new BinaryExpression(x2EqualsX0, "&&", x0Equals6); + Expression thirdAnd = new BinaryExpression(y3EqualsY1, "&&", y1Equals3); + Expression firstBigAnd = new BinaryExpression(firstAnd, "&&", secondAnd); + Expression fullExpression = new BinaryExpression(firstBigAnd, "&&", thirdAnd); + + // When + ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression); + + // Then + assertNotNull(result, "Result should not be null"); + assertEquals("#a_5 == -2", result.getValue().toString(), "Expected result to be #a_5 == -2"); + + } + + @Test + void testTransitive() { + // Given: a == b && b == 1 + // Expected: a == 1 + + Expression varA = new Var("a"); + Expression varB = new Var("b"); + Expression one = new LiteralInt(1); + Expression aEqualsB = new BinaryExpression(varA, "==", varB); + Expression bEquals1 = new BinaryExpression(varB, "==", one); + Expression fullExpression = new BinaryExpression(aEqualsB, "&&", bEquals1); + + // When + ValDerivationNode result = ExpressionSimplifier.simplify(fullExpression); + + // Then + assertNotNull(result, "Result should not be null"); + assertEquals("a == 1", result.getValue().toString(), "Expected result to be a == 1"); + } + /** * Helper method to compare two derivation nodes recursively */ @@ -336,25 +557,22 @@ private void assertDerivationEquals(DerivationNode expected, DerivationNode actu if (expected == null && actual == null) return; + assertNotNull(expected); assertEquals(expected.getClass(), actual.getClass(), message + ": node types should match"); - if (expected instanceof ValDerivationNode) { - ValDerivationNode expectedVal = (ValDerivationNode) expected; + if (expected instanceof ValDerivationNode expectedVal) { ValDerivationNode actualVal = (ValDerivationNode) actual; assertEquals(expectedVal.getValue().toString(), actualVal.getValue().toString(), message + ": values should match"); assertDerivationEquals(expectedVal.getOrigin(), actualVal.getOrigin(), message + " > origin"); - } else if (expected instanceof BinaryDerivationNode) { - BinaryDerivationNode expectedBin = (BinaryDerivationNode) expected; + } else if (expected instanceof BinaryDerivationNode expectedBin) { BinaryDerivationNode actualBin = (BinaryDerivationNode) actual; assertEquals(expectedBin.getOp(), actualBin.getOp(), message + ": operators should match"); assertDerivationEquals(expectedBin.getLeft(), actualBin.getLeft(), message + " > left"); assertDerivationEquals(expectedBin.getRight(), actualBin.getRight(), message + " > right"); - } else if (expected instanceof VarDerivationNode) { - VarDerivationNode expectedVar = (VarDerivationNode) expected; + } else if (expected instanceof VarDerivationNode expectedVar) { VarDerivationNode actualVar = (VarDerivationNode) actual; assertEquals(expectedVar.getVar(), actualVar.getVar(), message + ": variables should match"); - } else if (expected instanceof UnaryDerivationNode) { - UnaryDerivationNode expectedUnary = (UnaryDerivationNode) expected; + } else if (expected instanceof UnaryDerivationNode expectedUnary) { UnaryDerivationNode actualUnary = (UnaryDerivationNode) actual; assertEquals(expectedUnary.getOp(), actualUnary.getOp(), message + ": operators should match"); assertDerivationEquals(expectedUnary.getOperand(), actualUnary.getOperand(), message + " > operand"); diff --git a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java index 0d08e9a2..6f799aa2 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java @@ -107,4 +107,39 @@ void testGroupedEquality() { Map result = VariableResolver.resolve(grouped); assertTrue(result.isEmpty(), "Grouped single equality should not extract variable mapping"); } + + @Test + void testCircularDependency() { + // x == y && y == x should not extract anything due to circular dependency + Expression varX = new Var("x"); + Expression varY = new Var("y"); + + Expression xEqualsY = new BinaryExpression(varX, "==", varY); + Expression yEqualsX = new BinaryExpression(varY, "==", varX); + Expression conjunction = new BinaryExpression(xEqualsY, "&&", yEqualsX); + + Map result = VariableResolver.resolve(conjunction); + assertTrue(result.isEmpty(), "Circular dependency should not extract variable mappings"); + } + + @Test + void testUnusedEqualitiesShouldBeIgnored() { + // z > 0 && x == 1 && y == 2 && z == 3 + Expression varX = new Var("x"); + Expression varY = new Var("y"); + Expression varZ = new Var("z"); + Expression one = new LiteralInt(1); + Expression two = new LiteralInt(2); + Expression three = new LiteralInt(3); + Expression zero = new LiteralInt(0); + Expression zGreaterZero = new BinaryExpression(varZ, ">", zero); + Expression xEquals1 = new BinaryExpression(varX, "==", one); + Expression yEquals2 = new BinaryExpression(varY, "==", two); + Expression zEquals3 = new BinaryExpression(varZ, "==", three); + Expression conditions = new BinaryExpression(xEquals1, "&&", new BinaryExpression(yEquals2, "&&", zEquals3)); + Expression fullExpr = new BinaryExpression(zGreaterZero, "&&", conditions); + Map result = VariableResolver.resolve(fullExpr); + assertEquals(1, result.size(), "Should only extract used variable z"); + assertEquals("3", result.get("z").toString()); + } } From 033fcb4714219a0bb4d94e47cf3f6a68f4fc02e3 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Fri, 23 Jan 2026 15:14:01 +0000 Subject: [PATCH 15/25] Minor Fixes --- liquidjava-verifier/pom.xml | 2 +- .../liquidjava/processor/refinement_checker/TypeChecker.java | 3 +++ liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/liquidjava-verifier/pom.xml b/liquidjava-verifier/pom.xml index af57236d..353b0aef 100644 --- a/liquidjava-verifier/pom.xml +++ b/liquidjava-verifier/pom.xml @@ -11,7 +11,7 @@ io.github.liquid-java liquidjava-verifier - 0.0.8 + 0.0.11 liquidjava-verifier LiquidJava Verifier https://github.com/liquid-java/liquidjava diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java index b0d1eda2..e154a3dc 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java @@ -157,6 +157,9 @@ private void createStateGhost(String string, CtAnnotation // Set class as parameter of Ghost String qn = getQualifiedClassName(element); String sn = getSimpleClassName(element); + if (qn == null || sn == null) + return; // cannot determine class context - skip processing + context.addGhostClass(sn); List> param = Collections.singletonList(factory.Type().createReference(qn)); diff --git a/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java b/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java index 275a5597..7d690d1c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java +++ b/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java @@ -30,7 +30,7 @@ public static String getSimpleName(String name) { } public static String qualifyName(String prefix, String name) { - if (DEFAULT_NAMES.contains(name)) + if (DEFAULT_NAMES.contains(name) || prefix.isEmpty()) return name; // dont qualify return String.format("%s.%s", prefix, name); } From 791d404d261026d32bc8ecc5e0c693168c980369 Mon Sep 17 00:00:00 2001 From: Catarina Gamboa <52540187+CatarinaGamboa@users.noreply.github.com> Date: Fri, 23 Jan 2026 23:53:37 +0000 Subject: [PATCH 16/25] Add badges to README for VS Code and Maven Central Added badges for VS Code extension and Maven Central. --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 1c51a532..6168534d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,7 @@ # LiquidJava - Extending Java with Liquid Types +[![VS Code Extension](https://img.shields.io/visual-studio-marketplace/v/AlcidesFonseca.liquid-java?label=VS%20Code&logo=visual-studio-code)](https://marketplace.visualstudio.com/items?itemName=AlcidesFonseca.liquid-java) +[![Maven Central](https://img.shields.io/maven-central/v/io.github.liquid-java/liquidjava-api)](https://central.sonatype.com/artifact/io.github.liquid-java/liquidjava-api) + ![LiquidJava Banner](docs/design/figs/banner.gif) From 1b290cb4fff1566b6cbc02969154b35dd2dc63e0 Mon Sep 17 00:00:00 2001 From: Catarina Gamboa <52540187+CatarinaGamboa@users.noreply.github.com> Date: Fri, 30 Jan 2026 21:17:24 +0000 Subject: [PATCH 17/25] Add support for Long operations inside predicates (#139) * add support for long operations with proper long values and long operations inside predicates * change catch to if max values --- .../main/java/testSuite/CorrectLongUsage.java | 27 +++++++ .../testSuite/ErrorLongUsagePredicates1.java | 11 +++ .../testSuite/ErrorLongUsagePredicates2.java | 11 +++ .../testSuite/ErrorLongUsagePredicates3.java | 11 +++ .../liquidjava/rj_language/Predicate.java | 4 +- .../rj_language/ast/Expression.java | 3 +- .../rj_language/ast/LiteralLong.java | 72 +++++++++++++++++++ .../rj_language/ast/typing/TypeInfer.java | 3 + .../derivation_node/ValDerivationNode.java | 3 + .../visitors/CreateASTVisitor.java | 13 +++- .../visitors/ExpressionVisitor.java | 3 + .../liquidjava/smt/ExpressionToZ3Visitor.java | 6 ++ .../java/liquidjava/smt/TranslatorToZ3.java | 45 +++++++++--- 13 files changed, 198 insertions(+), 14 deletions(-) create mode 100644 liquidjava-example/src/main/java/testSuite/ErrorLongUsagePredicates1.java create mode 100644 liquidjava-example/src/main/java/testSuite/ErrorLongUsagePredicates2.java create mode 100644 liquidjava-example/src/main/java/testSuite/ErrorLongUsagePredicates3.java create mode 100644 liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralLong.java diff --git a/liquidjava-example/src/main/java/testSuite/CorrectLongUsage.java b/liquidjava-example/src/main/java/testSuite/CorrectLongUsage.java index 30ac3840..b8ceddb7 100644 --- a/liquidjava-example/src/main/java/testSuite/CorrectLongUsage.java +++ b/liquidjava-example/src/main/java/testSuite/CorrectLongUsage.java @@ -36,4 +36,31 @@ public static void main(String[] args) { @Refinement("_ > 10") long f = doubleBiggerThanTwenty(2 * 80); } + + + void testSmallLong() { + @Refinement("v > 0") + long v = 42L; + } + + void testDoublePrecisionBoundary() { + @Refinement("v == 9007199254740993") + long v = 9007199254740993L; + } + + void testLargeSubtraction() { + @Refinement("v - 9007199254740992 == 1") + long v = 9007199254740993L; + } + + void testMaxValueModulo() { + @Refinement("v % 1000 == 807") + long v = 9223372036854775807L; + } + + void testUUID(){ + @Refinement("((v/4096) % 16) == 1") + long v = 0x01000000122341666L; + } + } diff --git a/liquidjava-example/src/main/java/testSuite/ErrorLongUsagePredicates1.java b/liquidjava-example/src/main/java/testSuite/ErrorLongUsagePredicates1.java new file mode 100644 index 00000000..55504b0d --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/ErrorLongUsagePredicates1.java @@ -0,0 +1,11 @@ +// Refinement Error +package testSuite; + +import liquidjava.specification.Refinement; + +public class ErrorLongUsagePredicates1 { + void testUUID(){ + @Refinement("((v/4096) % 16) == 2") + long v = 0x01000000122341666L; + } +} \ No newline at end of file diff --git a/liquidjava-example/src/main/java/testSuite/ErrorLongUsagePredicates2.java b/liquidjava-example/src/main/java/testSuite/ErrorLongUsagePredicates2.java new file mode 100644 index 00000000..fc760314 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/ErrorLongUsagePredicates2.java @@ -0,0 +1,11 @@ +// Refinement Error +package testSuite; + +import liquidjava.specification.Refinement; + +public class ErrorLongUsagePredicates2 { + void testLargeSubtractionWrong() { + @Refinement("v - 9007199254740992 == 2") + long v = 9007199254740993L; + } +} \ No newline at end of file diff --git a/liquidjava-example/src/main/java/testSuite/ErrorLongUsagePredicates3.java b/liquidjava-example/src/main/java/testSuite/ErrorLongUsagePredicates3.java new file mode 100644 index 00000000..e5412994 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/ErrorLongUsagePredicates3.java @@ -0,0 +1,11 @@ +// Refinement Error +package testSuite; + +import liquidjava.specification.Refinement; + +public class ErrorLongUsagePredicates3 { + void testWrongSign() { + @Refinement("v < 0") + long v = 42L; + } +} \ No newline at end of file diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java index 5e4ad5a7..565de7e3 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java @@ -21,6 +21,7 @@ import liquidjava.rj_language.ast.Ite; import liquidjava.rj_language.ast.LiteralBoolean; import liquidjava.rj_language.ast.LiteralInt; +import liquidjava.rj_language.ast.LiteralLong; import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.UnaryExpression; import liquidjava.rj_language.ast.Var; @@ -233,7 +234,8 @@ public static Predicate createLit(String value, String type) { Expression exp = switch (type) { case Types.BOOLEAN -> new LiteralBoolean(value); case Types.INT, Types.SHORT -> new LiteralInt(value); - case Types.DOUBLE, Types.LONG, Types.FLOAT -> new LiteralReal(value); + case Types.LONG -> new LiteralLong(value); + case Types.DOUBLE, Types.FLOAT -> new LiteralReal(value); default -> throw new IllegalArgumentException("Unsupported literal type: " + type); }; return new Predicate(exp); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java index 5449949b..9b4b2e17 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java @@ -66,7 +66,8 @@ public void setChild(int index, Expression element) { } public boolean isLiteral() { - return this instanceof LiteralInt || this instanceof LiteralReal || this instanceof LiteralBoolean; + return this instanceof LiteralInt || this instanceof LiteralLong || this instanceof LiteralReal + || this instanceof LiteralBoolean; } /** diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralLong.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralLong.java new file mode 100644 index 00000000..300f089b --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralLong.java @@ -0,0 +1,72 @@ +package liquidjava.rj_language.ast; + +import java.util.List; + +import liquidjava.diagnostics.errors.LJError; +import liquidjava.rj_language.visitors.ExpressionVisitor; + +public class LiteralLong extends Expression { + + private final long value; + + public LiteralLong(long v) { + value = v; + } + + public LiteralLong(String v) { + value = Long.parseLong(v); + } + + @Override + public T accept(ExpressionVisitor visitor) throws LJError { + return visitor.visitLiteralLong(this); + } + + public String toString() { + return Long.toString(value); + } + + public long getValue() { + return value; + } + + @Override + public void getVariableNames(List toAdd) { + // end leaf + } + + @Override + public void getStateInvocations(List toAdd, List all) { + // end leaf + } + + @Override + public Expression clone() { + return new LiteralLong(value); + } + + @Override + public boolean isBooleanTrue() { + return false; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Long.hashCode(value); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + LiteralLong other = (LiteralLong) obj; + return value == other.value; + } +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java index 764793e0..085f015d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/typing/TypeInfer.java @@ -11,6 +11,7 @@ import liquidjava.rj_language.ast.Ite; import liquidjava.rj_language.ast.LiteralBoolean; import liquidjava.rj_language.ast.LiteralInt; +import liquidjava.rj_language.ast.LiteralLong; import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.LiteralString; import liquidjava.rj_language.ast.UnaryExpression; @@ -33,6 +34,8 @@ public static Optional> getType(Context ctx, Factory factory, return Optional.of(Utils.getType("String", factory)); else if (e instanceof LiteralInt) return Optional.of(Utils.getType("int", factory)); + else if (e instanceof LiteralLong) + return Optional.of(Utils.getType("long", factory)); else if (e instanceof LiteralReal) return Optional.of(Utils.getType("double", factory)); else if (e instanceof LiteralBoolean) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java index 27629839..64eb4fad 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java @@ -12,6 +12,7 @@ import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.ast.LiteralBoolean; import liquidjava.rj_language.ast.LiteralInt; +import liquidjava.rj_language.ast.LiteralLong; import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.Var; @@ -42,6 +43,8 @@ public JsonElement serialize(Expression exp, Type typeOfSrc, JsonSerializationCo return JsonNull.INSTANCE; if (exp instanceof LiteralInt) return new JsonPrimitive(((LiteralInt) exp).getValue()); + if (exp instanceof LiteralLong) + return new JsonPrimitive(((LiteralLong) exp).getValue()); if (exp instanceof LiteralReal) return new JsonPrimitive(((LiteralReal) exp).getValue()); if (exp instanceof LiteralBoolean) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java index 82e6ea52..1bedda99 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java @@ -13,6 +13,7 @@ import liquidjava.rj_language.ast.Ite; import liquidjava.rj_language.ast.LiteralBoolean; import liquidjava.rj_language.ast.LiteralInt; +import liquidjava.rj_language.ast.LiteralLong; import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.LiteralString; import liquidjava.rj_language.ast.UnaryExpression; @@ -196,9 +197,15 @@ private Expression literalCreate(LiteralContext literalContext) throws LJError { return new LiteralBoolean(literalContext.BOOL().getText()); else if (literalContext.STRING() != null) return new LiteralString(literalContext.STRING().getText()); - else if (literalContext.INT() != null) - return new LiteralInt(literalContext.INT().getText()); - else if (literalContext.REAL() != null) + else if (literalContext.INT() != null) { + String text = literalContext.INT().getText(); + long value = Long.parseLong(text); + if (value <= Integer.MAX_VALUE && value >= Integer.MIN_VALUE) { + return new LiteralInt((int) value); + } else { + return new LiteralLong(value); + } + } else if (literalContext.REAL() != null) return new LiteralReal(literalContext.REAL().getText()); throw new NotImplementedException("Literal type not implemented"); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java index dc8d9089..52e37319 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/ExpressionVisitor.java @@ -8,6 +8,7 @@ import liquidjava.rj_language.ast.Ite; import liquidjava.rj_language.ast.LiteralBoolean; import liquidjava.rj_language.ast.LiteralInt; +import liquidjava.rj_language.ast.LiteralLong; import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.LiteralString; import liquidjava.rj_language.ast.UnaryExpression; @@ -26,6 +27,8 @@ public interface ExpressionVisitor { T visitLiteralInt(LiteralInt lit) throws LJError; + T visitLiteralLong(LiteralLong lit) throws LJError; + T visitLiteralBoolean(LiteralBoolean lit) throws LJError; T visitLiteralReal(LiteralReal lit) throws LJError; diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java b/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java index 1425b438..35e74803 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/ExpressionToZ3Visitor.java @@ -10,6 +10,7 @@ import liquidjava.rj_language.ast.Ite; import liquidjava.rj_language.ast.LiteralBoolean; import liquidjava.rj_language.ast.LiteralInt; +import liquidjava.rj_language.ast.LiteralLong; import liquidjava.rj_language.ast.LiteralReal; import liquidjava.rj_language.ast.LiteralString; import liquidjava.rj_language.ast.UnaryExpression; @@ -85,6 +86,11 @@ public Expr visitLiteralInt(LiteralInt lit) { return ctx.makeIntegerLiteral(lit.getValue()); } + @Override + public Expr visitLiteralLong(LiteralLong lit) { + return ctx.makeLongLiteral(lit.getValue()); + } + @Override public Expr visitLiteralBoolean(LiteralBoolean lit) { return ctx.makeBooleanLiteral(lit.isBooleanTrue()); diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java index e844ad12..b207552f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java @@ -157,7 +157,8 @@ private Expr makeStore(Expr[] params) { public Expr makeEquals(Expr e1, Expr e2) { if (e1 instanceof FPExpr || e2 instanceof FPExpr) return z3.mkFPEq(toFP(e1), toFP(e2)); - + if (e1 instanceof RealExpr || e2 instanceof RealExpr) + return z3.mkEq(toReal(e1), toReal(e2)); return z3.mkEq(e1, e2); } @@ -165,7 +166,8 @@ public Expr makeEquals(Expr e1, Expr e2) { public Expr makeLt(Expr e1, Expr e2) { if (e1 instanceof FPExpr || e2 instanceof FPExpr) return z3.mkFPLt(toFP(e1), toFP(e2)); - + if (e1 instanceof RealExpr || e2 instanceof RealExpr) + return z3.mkLt(toReal(e1), toReal(e2)); return z3.mkLt((ArithExpr) e1, (ArithExpr) e2); } @@ -173,7 +175,8 @@ public Expr makeLt(Expr e1, Expr e2) { public Expr makeLtEq(Expr e1, Expr e2) { if (e1 instanceof FPExpr || e2 instanceof FPExpr) return z3.mkFPLEq(toFP(e1), toFP(e2)); - + if (e1 instanceof RealExpr || e2 instanceof RealExpr) + return z3.mkLe(toReal(e1), toReal(e2)); return z3.mkLe((ArithExpr) e1, (ArithExpr) e2); } @@ -181,7 +184,8 @@ public Expr makeLtEq(Expr e1, Expr e2) { public Expr makeGt(Expr e1, Expr e2) { if (e1 instanceof FPExpr || e2 instanceof FPExpr) return z3.mkFPGt(toFP(e1), toFP(e2)); - + if (e1 instanceof RealExpr || e2 instanceof RealExpr) + return z3.mkGt(toReal(e1), toReal(e2)); return z3.mkGt((ArithExpr) e1, (ArithExpr) e2); } @@ -189,7 +193,8 @@ public Expr makeGt(Expr e1, Expr e2) { public Expr makeGtEq(Expr e1, Expr e2) { if (e1 instanceof FPExpr || e2 instanceof FPExpr) return z3.mkFPGEq(toFP(e1), toFP(e2)); - + if (e1 instanceof RealExpr || e2 instanceof RealExpr) + return z3.mkGe(toReal(e1), toReal(e2)); return z3.mkGe((ArithExpr) e1, (ArithExpr) e2); } @@ -226,7 +231,8 @@ public Expr makeMinus(Expr eval) { public Expr makeAdd(Expr eval, Expr eval2) { if (eval instanceof FPExpr || eval2 instanceof FPExpr) return z3.mkFPAdd(z3.mkFPRoundNearestTiesToEven(), toFP(eval), toFP(eval2)); - + if (eval instanceof RealExpr || eval2 instanceof RealExpr) + return z3.mkAdd(toReal(eval), toReal(eval2)); return z3.mkAdd((ArithExpr) eval, (ArithExpr) eval2); } @@ -234,7 +240,8 @@ public Expr makeAdd(Expr eval, Expr eval2) { public Expr makeSub(Expr eval, Expr eval2) { if (eval instanceof FPExpr || eval2 instanceof FPExpr) return z3.mkFPSub(z3.mkFPRoundNearestTiesToEven(), toFP(eval), toFP(eval2)); - + if (eval instanceof RealExpr || eval2 instanceof RealExpr) + return z3.mkSub(toReal(eval), toReal(eval2)); return z3.mkSub((ArithExpr) eval, (ArithExpr) eval2); } @@ -242,7 +249,8 @@ public Expr makeSub(Expr eval, Expr eval2) { public Expr makeMul(Expr eval, Expr eval2) { if (eval instanceof FPExpr || eval2 instanceof FPExpr) return z3.mkFPMul(z3.mkFPRoundNearestTiesToEven(), toFP(eval), toFP(eval2)); - + if (eval instanceof RealExpr || eval2 instanceof RealExpr) + return z3.mkMul(toReal(eval), toReal(eval2)); return z3.mkMul((ArithExpr) eval, (ArithExpr) eval2); } @@ -250,16 +258,35 @@ public Expr makeMul(Expr eval, Expr eval2) { public Expr makeDiv(Expr eval, Expr eval2) { if (eval instanceof FPExpr || eval2 instanceof FPExpr) return z3.mkFPDiv(z3.mkFPRoundNearestTiesToEven(), toFP(eval), toFP(eval2)); - + if (eval instanceof RealExpr || eval2 instanceof RealExpr) + return z3.mkDiv(toReal(eval), toReal(eval2)); return z3.mkDiv((ArithExpr) eval, (ArithExpr) eval2); } public Expr makeMod(Expr eval, Expr eval2) { if (eval instanceof FPExpr || eval2 instanceof FPExpr) return z3.mkFPRem(toFP(eval), toFP(eval2)); + if (eval instanceof RealExpr || eval2 instanceof RealExpr) + return z3.mkMod(toInt(eval), toInt(eval2)); return z3.mkMod((IntExpr) eval, (IntExpr) eval2); } + private RealExpr toReal(Expr e) { + if (e instanceof RealExpr) + return (RealExpr) e; + if (e instanceof IntExpr) + return z3.mkInt2Real((IntExpr) e); + throw new NotImplementedException(); + } + + private IntExpr toInt(Expr e) { + if (e instanceof IntExpr) + return (IntExpr) e; + if (e instanceof RealExpr) + return z3.mkReal2Int((RealExpr) e); + throw new NotImplementedException(); + } + private FPExpr toFP(Expr e) { FPExpr f; if (e instanceof FPExpr) { From 62a28a3232f511bdeca210471cb59c91df36e919 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Mon, 9 Feb 2026 15:36:51 +0000 Subject: [PATCH 18/25] Add Custom Error Messages (#140) --- .../liquidjava/specification/Refinement.java | 2 ++ .../specification/StateRefinement.java | 6 ++-- .../liquidjava/diagnostics/LJDiagnostic.java | 26 ++++++++++---- .../diagnostics/errors/LJError.java | 7 +++- .../diagnostics/errors/RefinementError.java | 4 +-- .../errors/StateRefinementError.java | 4 +-- .../diagnostics/warnings/LJWarning.java | 2 +- .../processor/context/ObjectState.java | 17 +++++++++- .../liquidjava/processor/context/Refined.java | 9 +++++ .../RefinementTypeChecker.java | 9 ++--- .../refinement_checker/TypeChecker.java | 34 ++++++++++++++----- .../refinement_checker/VCChecker.java | 21 +++++++----- .../MethodsFunctionsChecker.java | 6 ++-- .../AuxHierarchyRefinementsPassage.java | 2 +- .../object_checkers/AuxStateHandler.java | 18 +++++++--- 15 files changed, 125 insertions(+), 42 deletions(-) diff --git a/liquidjava-api/src/main/java/liquidjava/specification/Refinement.java b/liquidjava-api/src/main/java/liquidjava/specification/Refinement.java index ce3dc20a..85aff527 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/Refinement.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/Refinement.java @@ -16,4 +16,6 @@ public @interface Refinement { public String value(); + + public String msg() default ""; } diff --git a/liquidjava-api/src/main/java/liquidjava/specification/StateRefinement.java b/liquidjava-api/src/main/java/liquidjava/specification/StateRefinement.java index b652944b..18a57fe3 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/StateRefinement.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/StateRefinement.java @@ -7,9 +7,9 @@ import java.lang.annotation.Target; /** - * Annotation to create state transitions in a method. The annotation has two arguments: from : the + * Annotation to create state transitions in a method. The annotation has three arguments: from : the * state in which the object needs to be for the method to be invoked correctly to : the state in - * which the object will be after the execution of the method + * which the object will be after the execution of the method msg : optional custom error message to display when refinement is violated * e.g. @StateRefinement(from="open(this)", to="closed(this)") * * @author catarina gamboa @@ -21,4 +21,6 @@ public String from() default ""; public String to() default ""; + + public String msg() default ""; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java index 245c53d8..de74d252 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java @@ -11,15 +11,18 @@ public class LJDiagnostic extends RuntimeException { private final String title; private final String message; private final String accentColor; + private final String customMessage; private String file; private ErrorPosition position; + private static final String PIPE = " | "; - public LJDiagnostic(String title, String message, SourcePosition pos, String accentColor) { + public LJDiagnostic(String title, String message, SourcePosition pos, String accentColor, String customMessage) { this.title = title; this.message = message; this.file = (pos != null && pos.getFile() != null) ? pos.getFile().getPath() : null; this.position = ErrorPosition.fromSpoonPosition(pos); this.accentColor = accentColor; + this.customMessage = customMessage; } public String getTitle() { @@ -30,6 +33,10 @@ public String getMessage() { return message; } + public String getCustomMessage() { + return customMessage; + } + public String getDetails() { return ""; // to be overridden by subclasses } @@ -104,7 +111,7 @@ public String getSnippet() { String line = lines.get(i - 1); // add line - sb.append(Colors.GREY).append(lineNumStr).append(" | ").append(line).append(Colors.RESET).append("\n"); + sb.append(Colors.GREY).append(lineNumStr).append(PIPE).append(line).append(Colors.RESET).append("\n"); // add error markers on the line(s) with the error if (i >= position.lineStart() && i <= position.lineEnd()) { @@ -112,11 +119,18 @@ public String getSnippet() { int colEnd = (i == position.lineEnd()) ? position.colEnd() : line.length(); if (colStart > 0 && colEnd > 0) { - // line number padding + " | " + column offset - String indent = " ".repeat(padding) + Colors.GREY + " | " + Colors.RESET + // line number padding + pipe + column offset + String indent = " ".repeat(padding) + Colors.GREY + PIPE + Colors.RESET + " ".repeat(colStart - 1); - String markers = accentColor + "^".repeat(Math.max(1, colEnd - colStart + 1)) + Colors.RESET; - sb.append(indent).append(markers).append("\n"); + String markers = accentColor + "^".repeat(Math.max(1, colEnd - colStart + 1)); + sb.append(indent).append(markers); + + // custom message + if (customMessage != null && !customMessage.isBlank()) { + String offset = " ".repeat(padding + colEnd + PIPE.length() + 1); + sb.append(" " + customMessage.replace("\n", "\n" + offset)); + } + sb.append(Colors.RESET).append("\n"); } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java index e3fd5599..66ff9fd2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java @@ -13,7 +13,12 @@ public abstract class LJError extends LJDiagnostic { private final TranslationTable translationTable; public LJError(String title, String message, SourcePosition pos, TranslationTable translationTable) { - super(title, message, pos, Colors.BOLD_RED); + this(title, message, pos, translationTable, null); + } + + public LJError(String title, String message, SourcePosition pos, TranslationTable translationTable, + String customMessage) { + super(title, message, pos, Colors.BOLD_RED, customMessage); this.translationTable = translationTable != null ? translationTable : new TranslationTable(); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java index 93a8a447..de40bd7f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/RefinementError.java @@ -16,9 +16,9 @@ public class RefinementError extends LJError { private final ValDerivationNode found; public RefinementError(SourcePosition position, ValDerivationNode expected, ValDerivationNode found, - TranslationTable translationTable) { + TranslationTable translationTable, String customMessage) { super("Refinement Error", String.format("%s is not a subtype of %s", found.getValue(), expected.getValue()), - position, translationTable); + position, translationTable, customMessage); this.expected = expected; this.found = found; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java index 508e6087..df7a54c4 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java @@ -15,9 +15,9 @@ public class StateRefinementError extends LJError { private final String found; public StateRefinementError(SourcePosition position, Expression expected, Expression found, - TranslationTable translationTable) { + TranslationTable translationTable, String customMessage) { super("State Refinement Error", String.format("Expected state %s but found %s", expected.toSimplifiedString(), - found.toSimplifiedString()), position, translationTable); + found.toSimplifiedString()), position, translationTable, customMessage); this.expected = expected.toSimplifiedString(); this.found = found.toSimplifiedString(); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java index 29d65479..5f5f0633 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java @@ -10,6 +10,6 @@ public abstract class LJWarning extends LJDiagnostic { public LJWarning(String message, SourcePosition pos) { - super("Warning", message, pos, Colors.BOLD_YELLOW); + super("Warning", message, pos, Colors.BOLD_YELLOW, null); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/ObjectState.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/ObjectState.java index 795e9815..45ac6c68 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/ObjectState.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/ObjectState.java @@ -6,6 +6,7 @@ public class ObjectState { Predicate from; Predicate to; + String message; public ObjectState() { } @@ -15,6 +16,12 @@ public ObjectState(Predicate from, Predicate to) { this.to = to; } + public ObjectState(Predicate from, Predicate to, String message) { + this.from = from; + this.to = to; + this.message = message; + } + public void setFrom(Predicate from) { this.from = from; } @@ -39,8 +46,16 @@ public Predicate getTo() { return to != null ? to : new Predicate(); } + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + public ObjectState clone() { - return new ObjectState(from.clone(), to.clone()); + return new ObjectState(from.clone(), to.clone(), message); } @Override diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/context/Refined.java b/liquidjava-verifier/src/main/java/liquidjava/processor/context/Refined.java index 6028d521..2aa03989 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/context/Refined.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/context/Refined.java @@ -8,6 +8,7 @@ public abstract class Refined { private String name; // y private CtTypeReference type; // int private Predicate refinement; // 9 <= y && y <= 100 + private String message; public Refined() { } @@ -44,6 +45,14 @@ public Predicate getRefinement() { return new Predicate(); } + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + public Predicate getRenamedRefinements(String toReplace) { return refinement.substituteVariable(name, toReplace); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java index 43791ccd..d4d6f89e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java @@ -124,10 +124,10 @@ public void visitCtLocalVariable(CtLocalVariable localVariable) { super.visitCtLocalVariable(localVariable); // only declaration, no assignment if (localVariable.getAssignment() == null) { - Optional a; - a = getRefinementFromAnnotation(localVariable); - context.addVarToContext(localVariable.getSimpleName(), localVariable.getType(), a.orElse(new Predicate()), - localVariable); + Optional a = getRefinementFromAnnotation(localVariable); + RefinedVariable v = context.addVarToContext(localVariable.getSimpleName(), localVariable.getType(), + a.orElse(new Predicate()), localVariable); + getMessageFromAnnotation(localVariable).ifPresent(v::setMessage); } else { String varName = localVariable.getSimpleName(); CtExpression e = localVariable.getAssignment(); @@ -238,6 +238,7 @@ public void visitCtField(CtField f) { ret = c.get().substituteVariable(Keys.WILDCARD, name).substituteVariable(f.getSimpleName(), name); } RefinedVariable v = context.addVarToContext(name, f.getType(), ret, f); + getMessageFromAnnotation(f).ifPresent(v::setMessage); if (v instanceof Variable) { ((Variable) v).setLocation("this"); } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java index e154a3dc..73d68e25 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java @@ -89,6 +89,19 @@ public Optional getRefinementFromAnnotation(CtElement element) throws return constr; } + public Optional getMessageFromAnnotation(CtElement element) { + for (CtAnnotation ann : element.getAnnotations()) { + String an = ann.getActualAnnotation().annotationType().getCanonicalName(); + if (an.contentEquals("liquidjava.specification.Refinement")) { + String msg = getStringFromAnnotation(ann.getValue("msg")); + if (msg != null && !msg.isEmpty()) { + return Optional.of(msg); + } + } + } + return Optional.empty(); + } + @SuppressWarnings("unchecked") public void handleStateSetsFromAnnotation(CtElement element) throws LJError { int set = 0; @@ -277,13 +290,17 @@ public void checkVariableRefinements(Predicate refinementFound, String simpleNam for (CtTypeReference t : mainRV.getSuperTypes()) rv.addSuperType(t); context.addRefinementInstanceToVariable(simpleName, newName); - // smt check - checkSMT(cEt, usage); // TODO CHANGE + String customMessage = getMessageFromAnnotation(variable).orElse(mainRV != null ? mainRV.getMessage() : null); + checkSMT(cEt, usage, customMessage); // TODO CHANGE context.addRefinementToVariableInContext(simpleName, type, cet, usage); } public void checkSMT(Predicate expectedType, CtElement element) throws LJError { - vcChecker.processSubtyping(expectedType, context.getGhostState(), element, factory); + checkSMT(expectedType, element, null); + } + + public void checkSMT(Predicate expectedType, CtElement element, String customMessage) throws LJError { + vcChecker.processSubtyping(expectedType, context.getGhostState(), element, factory, customMessage); element.putMetadata(Keys.REFINEMENT, expectedType); } @@ -296,13 +313,14 @@ public boolean checksStateSMT(Predicate prevState, Predicate expectedState, Sour return vcChecker.canProcessSubtyping(prevState, expectedState, context.getGhostState(), p, factory); } - public void throwRefinementError(SourcePosition position, Predicate expectedType, Predicate foundType) - throws LJError { - vcChecker.throwRefinementError(position, expectedType, foundType); + public void throwRefinementError(SourcePosition position, Predicate expectedType, Predicate foundType, + String customMessage) throws LJError { + vcChecker.throwRefinementError(position, expectedType, foundType, customMessage); } - public void throwStateRefinementError(SourcePosition position, Predicate found, Predicate expected) throws LJError { - vcChecker.throwStateRefinementError(position, found, expected); + public void throwStateRefinementError(SourcePosition position, Predicate found, Predicate expected, + String customMessage) throws LJError { + vcChecker.throwStateRefinementError(position, found, expected, customMessage); } public void throwStateConflictError(SourcePosition position, Predicate expectedType) throws LJError { diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java index 0c2fba5e..6a0fe977 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/VCChecker.java @@ -31,6 +31,11 @@ public VCChecker() { public void processSubtyping(Predicate expectedType, List list, CtElement element, Factory f) throws LJError { + processSubtyping(expectedType, list, element, f, null); + } + + public void processSubtyping(Predicate expectedType, List list, CtElement element, Factory f, + String customMessage) throws LJError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(expectedType, lrv, mainVars); if (expectedType.isBooleanTrue()) @@ -53,7 +58,7 @@ public void processSubtyping(Predicate expectedType, List list, CtEl boolean isSubtype = smtChecks(expected, premises, element.getPosition()); if (!isSubtype) throw new RefinementError(element.getPosition(), expectedType.simplify(), premisesBeforeChange.simplify(), - map); + map, customMessage); } /** @@ -71,7 +76,7 @@ public void processSubtyping(Predicate type, Predicate expectedType, List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(expected, lrv, mainVars); gatherVariables(found, lrv, mainVars); TranslationTable map = new TranslationTable(); Predicate premises = joinPredicates(expected, mainVars, lrv, map).toConjunctions(); - throw new RefinementError(position, expected.simplify(), premises.simplify(), map); + throw new RefinementError(position, expected.simplify(), premises.simplify(), map, customMessage); } - protected void throwStateRefinementError(SourcePosition position, Predicate found, Predicate expected) - throws StateRefinementError { + protected void throwStateRefinementError(SourcePosition position, Predicate found, Predicate expected, + String customMessage) throws StateRefinementError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); gatherVariables(found, lrv, mainVars); TranslationTable map = new TranslationTable(); VCImplication foundState = joinPredicates(found, mainVars, lrv, map); throw new StateRefinementError(position, expected.getExpression(), - foundState.toConjunctions().simplify().getValue(), map); + foundState.toConjunctions().simplify().getValue(), map, customMessage); } protected void throwStateConflictError(SourcePosition position, Predicate expected) throws StateConflictError { diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java index 815e8e6c..ebf908a3 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java @@ -154,6 +154,7 @@ private Predicate handleFunctionRefinements(RefinedFunction f, CtElement method, c = oc.get().substituteVariable(Keys.WILDCARD, paramName); param.putMetadata(Keys.REFINEMENT, c); RefinedVariable v = rtc.getContext().addVarToContext(param.getSimpleName(), param.getType(), c, param); + rtc.getMessageFromAnnotation(param).ifPresent(v::setMessage); if (v instanceof Variable) f.addArgRefinements((Variable) v); joint = Predicate.createConjunction(joint, c); @@ -162,6 +163,7 @@ private Predicate handleFunctionRefinements(RefinedFunction f, CtElement method, Predicate ret = oret.orElse(new Predicate()); ret = ret.substituteVariable("return", Keys.WILDCARD); f.setRefReturn(ret); + rtc.getMessageFromAnnotation(method).ifPresent(f::setMessage); return Predicate.createConjunction(joint, ret); } @@ -196,7 +198,7 @@ public void getReturnRefinements(CtReturn ret) throws LJError { .substituteVariable(Keys.THIS, returnVarName); rtc.getContext().addVarToContext(returnVarName, method.getType(), cretRef, ret); - rtc.checkSMT(cexpectedType, ret); + rtc.checkSMT(cexpectedType, ret, fi.getMessage()); rtc.getContext().newRefinementToVariableInContext(returnVarName, cexpectedType); } } @@ -367,7 +369,7 @@ private void checkParameters(CtElement invocation, List> argumen VariableInstance vi = (VariableInstance) invocation.getMetadata(Keys.TARGET); c = c.substituteVariable(Keys.THIS, vi.getName()); } - rtc.checkSMT(c, invocation); + rtc.checkSMT(c, invocation, fArg.getMessage()); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java index 88dcf683..455e8276 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxHierarchyRefinementsPassage.java @@ -83,7 +83,7 @@ static void transferArgumentsRefinements(RefinedFunction superFunction, RefinedF } else { boolean ok = tc.checksStateSMT(superArgRef, argRef, params.get(i).getPosition()); if (!ok) { - tc.throwRefinementError(method.getPosition(), argRef, superArgRef); + tc.throwRefinementError(method.getPosition(), argRef, superArgRef, function.getMessage()); } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java index bd76950b..facbd2c1 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java @@ -61,7 +61,11 @@ private static void setConstructorStates(RefinedFunction f, List an : anns) { Map m = an.getAllValues(); String to = TypeCheckingUtils.getStringFromAnnotation(m.get("to")); + String msg = TypeCheckingUtils.getStringFromAnnotation(m.get("msg")); ObjectState state = new ObjectState(); + if (msg != null && !msg.isEmpty()) + state.setMessage(msg); + if (to != null) { Predicate p = new Predicate(to, element); if (!p.getExpression().isBooleanExpression()) { @@ -163,7 +167,10 @@ private static ObjectState getStates(CtAnnotation ctAnnota Map m = ctAnnotation.getAllValues(); String from = TypeCheckingUtils.getStringFromAnnotation(m.get("from")); String to = TypeCheckingUtils.getStringFromAnnotation(m.get("to")); + String msg = TypeCheckingUtils.getStringFromAnnotation(m.get("msg")); ObjectState state = new ObjectState(); + if (msg != null && !msg.isEmpty()) + state.setMessage(msg); // has from if (from != null) @@ -407,7 +414,7 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) throws L .changeOldMentions(vi.getName(), instanceName); if (!tc.checksStateSMT(prevState, expectState, fw.getPosition())) { // Invalid field transition - tc.throwStateRefinementError(fw.getPosition(), prevState, stateChange.getFrom()); + tc.throwStateRefinementError(fw.getPosition(), prevState, stateChange.getFrom(), stateChange.getMessage()); return; } @@ -480,7 +487,7 @@ private static void changeState(TypeChecker tc, VariableInstance vi, List msg != null && !msg.isBlank()).distinct().collect(Collectors.joining("\n")); + tc.throwStateRefinementError(invocation.getPosition(), prevState, expectedStatesDisjunction, message); } } From a49b948eb3a9efeb8385b7cb22dcbc245fcde76b Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 11 Feb 2026 15:23:18 +0000 Subject: [PATCH 19/25] Custom Messages Fix (#147) --- .../liquidjava/processor/refinement_checker/TypeChecker.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java index 73d68e25..2e3f0c67 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java @@ -3,6 +3,7 @@ import java.lang.annotation.Annotation; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Optional; import liquidjava.diagnostics.errors.*; @@ -89,11 +90,13 @@ public Optional getRefinementFromAnnotation(CtElement element) throws return constr; } + @SuppressWarnings({ "rawtypes" }) public Optional getMessageFromAnnotation(CtElement element) { for (CtAnnotation ann : element.getAnnotations()) { String an = ann.getActualAnnotation().annotationType().getCanonicalName(); if (an.contentEquals("liquidjava.specification.Refinement")) { - String msg = getStringFromAnnotation(ann.getValue("msg")); + Map values = ann.getAllValues(); + String msg = getStringFromAnnotation((values.get("msg"))); if (msg != null && !msg.isEmpty()) { return Optional.of(msg); } From f3e86b308ea27ccf7e60b053dfa7c83451021145 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 11 Feb 2026 17:26:06 +0000 Subject: [PATCH 20/25] AST Simplified `toString` (#148) --- .../diagnostics/errors/StateConflictError.java | 2 +- .../diagnostics/errors/StateRefinementError.java | 9 +++++---- .../general_checkers/MethodsFunctionsChecker.java | 2 +- .../liquidjava/rj_language/ast/AliasInvocation.java | 6 ------ .../liquidjava/rj_language/ast/BinaryExpression.java | 5 ----- .../java/liquidjava/rj_language/ast/Expression.java | 11 ----------- .../rj_language/ast/FunctionInvocation.java | 10 ++-------- .../liquidjava/rj_language/ast/GroupExpression.java | 7 +------ .../src/main/java/liquidjava/rj_language/ast/Ite.java | 8 +------- .../liquidjava/rj_language/ast/UnaryExpression.java | 5 ----- 10 files changed, 11 insertions(+), 54 deletions(-) diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java index f1cd7c1c..b8e262fa 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateConflictError.java @@ -17,7 +17,7 @@ public StateConflictError(SourcePosition position, Expression state, Translation super("State Conflict Error", "Found multiple disjoint states in state transition: state transition can only go to one state of each state set", position, translationTable); - this.state = state.toSimplifiedString(); + this.state = state.toString(); } public String getState() { diff --git a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java index df7a54c4..976508cf 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java @@ -16,10 +16,11 @@ public class StateRefinementError extends LJError { public StateRefinementError(SourcePosition position, Expression expected, Expression found, TranslationTable translationTable, String customMessage) { - super("State Refinement Error", String.format("Expected state %s but found %s", expected.toSimplifiedString(), - found.toSimplifiedString()), position, translationTable, customMessage); - this.expected = expected.toSimplifiedString(); - this.found = found.toSimplifiedString(); + super("State Refinement Error", + String.format("Expected state %s but found %s", expected.toString(), found.toString()), position, + translationTable, customMessage); + this.expected = expected.toString(); + this.found = found.toString(); } public String getExpected() { diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java index ebf908a3..c2bd580e 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java @@ -296,7 +296,7 @@ private Map checkInvocationRefinements(CtElement invocation, Lis Predicate methodRef = f.getRefReturn(); if (methodRef != null) { - boolean equalsThis = methodRef.toString().equals("(_ == this)"); // TODO change for better + boolean equalsThis = methodRef.toString().equals("_ == this"); // TODO change for better List vars = methodRef.getVariableNames(); for (String s : vars) if (map.containsKey(s)) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java index c9e2f58b..2c02734f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java @@ -34,12 +34,6 @@ public String toString() { return name + "(" + getArgs().stream().map(Expression::toString).collect(Collectors.joining(", ")) + ")"; } - @Override - public String toSimplifiedString() { - return name + "(" + getArgs().stream().map(Expression::toSimplifiedString).collect(Collectors.joining(", ")) - + ")"; - } - @Override public void getVariableNames(List toAdd) { for (Expression e : getArgs()) diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java index b60ef2ec..a5112c8c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/BinaryExpression.java @@ -50,11 +50,6 @@ public String toString() { return getFirstOperand().toString() + " " + op + " " + getSecondOperand().toString(); } - @Override - public String toSimplifiedString() { - return getFirstOperand().toSimplifiedString() + " " + op + " " + getSecondOperand().toSimplifiedString(); - } - @Override public void getVariableNames(List toAdd) { getFirstOperand().getVariableNames(toAdd); diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java index 9b4b2e17..58ea8e93 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Expression.java @@ -36,17 +36,6 @@ public abstract class Expression { public abstract String toString(); - /** - * Returns a simplified string representation of this expression with unqualified names (e.g., - * com.example.State.open => open Default implementation delegates to toString() Subclasses that contain qualified - * names should override this method - * - * @return simplified string representation - */ - public String toSimplifiedString() { - return toString(); - } - List children = new ArrayList<>(); public void addChild(Expression e) { diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java index 71f3febd..d9d53731 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/FunctionInvocation.java @@ -37,14 +37,8 @@ public T accept(ExpressionVisitor visitor) throws LJError { @Override public String toString() { - return name + "(" + getArgs().stream().map(Expression::toString).collect(Collectors.joining(",")) + ")"; - } - - @Override - public String toSimplifiedString() { - String simpleName = Utils.getSimpleName(name); - return simpleName + "(" - + getArgs().stream().map(Expression::toSimplifiedString).collect(Collectors.joining(",")) + ")"; + return Utils.getSimpleName(name) + "(" + + getArgs().stream().map(Expression::toString).collect(Collectors.joining(",")) + ")"; } @Override diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java index c68edaa9..4597a157 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/GroupExpression.java @@ -21,12 +21,7 @@ public T accept(ExpressionVisitor visitor) throws LJError { } public String toString() { - return "(" + getExpression().toString() + ")"; - } - - @Override - public String toSimplifiedString() { - return getExpression().toSimplifiedString(); + return getExpression().toString(); } @Override diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java index a779aad4..cf95b730 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/Ite.java @@ -32,13 +32,7 @@ public T accept(ExpressionVisitor visitor) throws LJError { @Override public String toString() { - return getCondition().toString() + "?" + getThen().toString() + ":" + getElse().toString(); - } - - @Override - public String toSimplifiedString() { - return getCondition().toSimplifiedString() + "?" + getThen().toSimplifiedString() + ":" - + getElse().toSimplifiedString(); + return getCondition().toString() + " ? " + getThen().toString() + " : " + getElse().toString(); } @Override diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java index 4913e04c..4ffbc2d9 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/UnaryExpression.java @@ -32,11 +32,6 @@ public String toString() { return op + getExpression().toString(); } - @Override - public String toSimplifiedString() { - return op + getExpression().toSimplifiedString(); - } - @Override public void getVariableNames(List toAdd) { getExpression().getVariableNames(toAdd); From e7933eaae1339fed764874611ae0232668a6b790 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 11 Feb 2026 17:26:49 +0000 Subject: [PATCH 21/25] Fix Annotation Position Lookup (#141) --- ...yntax1.java => ErrorSyntaxRefinement.java} | 2 +- .../testSuite/ErrorSyntaxStateRefinement.java | 10 ++++++++ .../refinement_checker/TypeChecker.java | 2 +- .../liquidjava/rj_language/Predicate.java | 2 +- .../src/main/java/liquidjava/utils/Utils.java | 23 ++++++++++++++----- 5 files changed, 30 insertions(+), 9 deletions(-) rename liquidjava-example/src/main/java/testSuite/{ErrorSyntax1.java => ErrorSyntaxRefinement.java} (85%) create mode 100644 liquidjava-example/src/main/java/testSuite/ErrorSyntaxStateRefinement.java diff --git a/liquidjava-example/src/main/java/testSuite/ErrorSyntax1.java b/liquidjava-example/src/main/java/testSuite/ErrorSyntaxRefinement.java similarity index 85% rename from liquidjava-example/src/main/java/testSuite/ErrorSyntax1.java rename to liquidjava-example/src/main/java/testSuite/ErrorSyntaxRefinement.java index 6d28e95c..b02c63d0 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorSyntax1.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorSyntaxRefinement.java @@ -4,7 +4,7 @@ import liquidjava.specification.Refinement; @SuppressWarnings("unused") -public class ErrorSyntax1 { +public class ErrorSyntaxRefinement { public static void main(String[] args) { @Refinement("_ < 100 +") int value = 90 + 4; diff --git a/liquidjava-example/src/main/java/testSuite/ErrorSyntaxStateRefinement.java b/liquidjava-example/src/main/java/testSuite/ErrorSyntaxStateRefinement.java new file mode 100644 index 00000000..14fd6a2b --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/ErrorSyntaxStateRefinement.java @@ -0,0 +1,10 @@ +// Syntax Error +package testSuite; + +import liquidjava.specification.StateRefinement; + +public class ErrorSyntaxStateRefinement { + + @StateRefinement(from="$", to="#") + void test() {} +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java index 2e3f0c67..31c019bc 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/TypeChecker.java @@ -251,7 +251,7 @@ protected void handleAlias(String ref, CtElement element) throws LJError { } } catch (LJError e) { // add location info to error - SourcePosition pos = Utils.getRefinementAnnotationPosition(element, ref); + SourcePosition pos = Utils.getAnnotationPosition(element, ref); e.setPosition(pos); throw e; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java index 565de7e3..eecfaa10 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java @@ -87,7 +87,7 @@ protected Expression parse(String ref, CtElement element) throws LJError { return RefinementsParser.createAST(ref, prefix); } catch (LJError e) { // add location info to error - SourcePosition pos = Utils.getRefinementAnnotationPosition(element, ref); + SourcePosition pos = Utils.getAnnotationPosition(element, ref); e.setPosition(pos); throw e; } diff --git a/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java b/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java index 7d690d1c..1cf45bc7 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java +++ b/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java @@ -1,9 +1,12 @@ package liquidjava.utils; +import java.util.Map; import java.util.Set; +import java.util.stream.Stream; import liquidjava.utils.constants.Types; import spoon.reflect.cu.SourcePosition; +import spoon.reflect.declaration.CtAnnotation; import spoon.reflect.declaration.CtElement; import spoon.reflect.factory.Factory; import spoon.reflect.reference.CtTypeReference; @@ -35,11 +38,19 @@ public static String qualifyName(String prefix, String name) { return String.format("%s.%s", prefix, name); } - public static SourcePosition getRefinementAnnotationPosition(CtElement element, String refinement) { - return element.getAnnotations().stream().filter(a -> { - String value = a.getValue("value").toString(); - String unquoted = value.substring(1, value.length() - 1); - return unquoted.equals(refinement); - }).findFirst().map(CtElement::getPosition).orElse(element.getPosition()); + public static SourcePosition getAnnotationPosition(CtElement element, String refinement) { + return element.getAnnotations().stream() + .filter(a -> isLiquidJavaAnnotation(a) && hasRefinementValue(a, "\"" + refinement + "\"")).findFirst() + .map(CtElement::getPosition).orElse(element.getPosition()); + } + + private static boolean isLiquidJavaAnnotation(CtAnnotation annotation) { + return annotation.getAnnotationType().getQualifiedName().startsWith("liquidjava.specification"); + } + + private static boolean hasRefinementValue(CtAnnotation annotation, String refinement) { + Map values = annotation.getValues(); + return Stream.of("value", "to", "from") + .anyMatch(key -> refinement.equals(String.valueOf(values.get(key)))); } } From 3562af1cc755b44091b89e31812ec4980a84c159 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Wed, 11 Feb 2026 17:27:17 +0000 Subject: [PATCH 22/25] Add Ghost Dot Syntax (#142) --- .../CorrectDotNotationIncrementOnce.java | 21 +++++++ .../CorrectDotNotationTrafficLight.java | 27 +++++++++ .../ErrorDotNotationIncrementOnce.java | 21 +++++++ .../testSuite/ErrorDotNotationMultiple.java | 18 ++++++ .../ErrorDotNotationTrafficLight.java | 28 ++++++++++ .../src/main/antlr4/rj/grammar/RJ.g4 | 8 ++- .../visitors/CreateASTVisitor.java | 56 ++++++++++++++++--- 7 files changed, 170 insertions(+), 9 deletions(-) create mode 100644 liquidjava-example/src/main/java/testSuite/CorrectDotNotationIncrementOnce.java create mode 100644 liquidjava-example/src/main/java/testSuite/CorrectDotNotationTrafficLight.java create mode 100644 liquidjava-example/src/main/java/testSuite/ErrorDotNotationIncrementOnce.java create mode 100644 liquidjava-example/src/main/java/testSuite/ErrorDotNotationMultiple.java create mode 100644 liquidjava-example/src/main/java/testSuite/ErrorDotNotationTrafficLight.java diff --git a/liquidjava-example/src/main/java/testSuite/CorrectDotNotationIncrementOnce.java b/liquidjava-example/src/main/java/testSuite/CorrectDotNotationIncrementOnce.java new file mode 100644 index 00000000..8eb1174c --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/CorrectDotNotationIncrementOnce.java @@ -0,0 +1,21 @@ +package testSuite; + +import liquidjava.specification.Ghost; +import liquidjava.specification.StateRefinement; + +@Ghost("int n") +public class CorrectDotNotationIncrementOnce { + + // explicit this + @StateRefinement(to="this.n() == 0") + public CorrectDotNotationIncrementOnce() {} + + // implicit this + @StateRefinement(from="n() == 0", to="n() == old(this).n() + 1") + public void incrementOnce() {} + + public static void main(String[] args) { + CorrectDotNotationIncrementOnce t = new CorrectDotNotationIncrementOnce(); + t.incrementOnce(); + } +} diff --git a/liquidjava-example/src/main/java/testSuite/CorrectDotNotationTrafficLight.java b/liquidjava-example/src/main/java/testSuite/CorrectDotNotationTrafficLight.java new file mode 100644 index 00000000..aaef95fe --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/CorrectDotNotationTrafficLight.java @@ -0,0 +1,27 @@ +package testSuite; + +import liquidjava.specification.StateRefinement; +import liquidjava.specification.StateSet; + +@StateSet({"green", "amber", "red"}) +public class CorrectDotNotationTrafficLight { + + @StateRefinement(to="this.green()") + public CorrectDotNotationTrafficLight() {} + + @StateRefinement(from="this.green()", to="this.amber()") + public void transitionToAmber() {} + + @StateRefinement(from="red()", to="green()") + public void transitionToGreen() {} + + @StateRefinement(from="this.amber()", to="red()") + public void transitionToRed() {} + + public static void main(String[] args) { + CorrectDotNotationTrafficLight tl = new CorrectDotNotationTrafficLight(); + tl.transitionToAmber(); + tl.transitionToRed(); + tl.transitionToGreen(); + } +} diff --git a/liquidjava-example/src/main/java/testSuite/ErrorDotNotationIncrementOnce.java b/liquidjava-example/src/main/java/testSuite/ErrorDotNotationIncrementOnce.java new file mode 100644 index 00000000..8bf06584 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/ErrorDotNotationIncrementOnce.java @@ -0,0 +1,21 @@ +// State Refinement Error +package testSuite; + +import liquidjava.specification.Ghost; +import liquidjava.specification.StateRefinement; + +@Ghost("int n") +public class ErrorDotNotationIncrementOnce { + + @StateRefinement(to="this.n() == 0") + public ErrorDotNotationIncrementOnce() {} + + @StateRefinement(from="n() == 0", to="n() == old(this).n() + 1") + public void incrementOnce() {} + + public static void main(String[] args) { + ErrorDotNotationIncrementOnce t = new ErrorDotNotationIncrementOnce(); + t.incrementOnce(); + t.incrementOnce(); // error + } +} diff --git a/liquidjava-example/src/main/java/testSuite/ErrorDotNotationMultiple.java b/liquidjava-example/src/main/java/testSuite/ErrorDotNotationMultiple.java new file mode 100644 index 00000000..483309b3 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/ErrorDotNotationMultiple.java @@ -0,0 +1,18 @@ +// Syntax Error +package testSuite; + +import liquidjava.specification.Ghost; +import liquidjava.specification.Refinement; +import liquidjava.specification.StateRefinement; + +@Ghost("int size") +public class ErrorDotNotationMultiple { + + @StateRefinement(to="size() == 0") + public ErrorDotNotationMultiple() {} + + void test() { + @Refinement("_ == this.not.size()") + int x = 0; + } +} diff --git a/liquidjava-example/src/main/java/testSuite/ErrorDotNotationTrafficLight.java b/liquidjava-example/src/main/java/testSuite/ErrorDotNotationTrafficLight.java new file mode 100644 index 00000000..e40ddb30 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/ErrorDotNotationTrafficLight.java @@ -0,0 +1,28 @@ +// State Refinement Error +package testSuite; + +import liquidjava.specification.StateRefinement; +import liquidjava.specification.StateSet; + +@StateSet({"green", "amber", "red"}) +public class ErrorDotNotationTrafficLight { + + @StateRefinement(to="this.green()") + public ErrorDotNotationTrafficLight() {} + + @StateRefinement(from="this.green()", to="this.amber()") + public void transitionToAmber() {} + + @StateRefinement(from="red()", to="green()") + public void transitionToGreen() {} + + @StateRefinement(from="this.amber()", to="red()") + public void transitionToRed() {} + + public static void main(String[] args) { + ErrorDotNotationTrafficLight tl = new ErrorDotNotationTrafficLight(); + tl.transitionToAmber(); + tl.transitionToGreen(); // error + tl.transitionToRed(); + } +} diff --git a/liquidjava-verifier/src/main/antlr4/rj/grammar/RJ.g4 b/liquidjava-verifier/src/main/antlr4/rj/grammar/RJ.g4 index e34f40ad..d6f7f45f 100644 --- a/liquidjava-verifier/src/main/antlr4/rj/grammar/RJ.g4 +++ b/liquidjava-verifier/src/main/antlr4/rj/grammar/RJ.g4 @@ -37,13 +37,17 @@ literalExpression: '(' literalExpression ')' #litGroup | literal #lit | ID #var - | ID '.' functionCall #targetInvocation | functionCall #invocation ; functionCall: ghostCall - | aliasCall; + | aliasCall + | dotCall; + +dotCall: + OBJECT_TYPE '(' args? ')' + | ID '(' args? ')' '.' ID '(' args? ')'; ghostCall: ID '(' args? ')'; diff --git a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java index 1bedda99..d906772c 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/visitors/CreateASTVisitor.java @@ -19,11 +19,13 @@ import liquidjava.rj_language.ast.UnaryExpression; import liquidjava.rj_language.ast.Var; import liquidjava.utils.Utils; +import liquidjava.utils.constants.Keys; import org.antlr.v4.runtime.tree.ParseTree; import org.apache.commons.lang3.NotImplementedException; import rj.grammar.RJParser.AliasCallContext; import rj.grammar.RJParser.ArgsContext; +import rj.grammar.RJParser.DotCallContext; import rj.grammar.RJParser.ExpBoolContext; import rj.grammar.RJParser.ExpContext; import rj.grammar.RJParser.ExpGroupContext; @@ -51,9 +53,7 @@ import rj.grammar.RJParser.ProgContext; import rj.grammar.RJParser.StartContext; import rj.grammar.RJParser.StartPredContext; -import rj.grammar.RJParser.TargetInvocationContext; import rj.grammar.RJParser.VarContext; -import spoon.reflect.cu.SourcePosition; import liquidjava.diagnostics.errors.ArgumentMismatchError; /** @@ -82,6 +82,8 @@ else if (rc instanceof OperandContext) return operandCreate(rc); else if (rc instanceof LiteralExpressionContext) return literalExpressionCreate(rc); + else if (rc instanceof DotCallContext) + return dotCallCreate((DotCallContext) rc); else if (rc instanceof FunctionCallContext) return functionCallCreate((FunctionCallContext) rc); else if (rc instanceof LiteralContext) @@ -156,9 +158,7 @@ else if (rc instanceof LitContext) return create(((LitContext) rc).literal()); else if (rc instanceof VarContext) { return new Var(((VarContext) rc).ID().getText()); - } else if (rc instanceof TargetInvocationContext) { - // TODO Finish Invocation with Target (a.len()) - return null; + } else { return create(((InvocationContext) rc).functionCall()); } @@ -171,15 +171,57 @@ private Expression functionCallCreate(FunctionCallContext rc) throws LJError { String name = Utils.qualifyName(prefix, ref); List args = getArgs(gc.args()); if (args.isEmpty()) - throw new ArgumentMismatchError("Ghost call cannot have empty arguments"); + args.add(new Var(Keys.THIS)); // implicit this: size() => this.size() + return new FunctionInvocation(name, args); - } else { + } else if (rc.aliasCall() != null) { AliasCallContext gc = rc.aliasCall(); String ref = gc.ID_UPPER().getText(); List args = getArgs(gc.args()); if (args.isEmpty()) throw new ArgumentMismatchError("Alias call cannot have empty arguments"); + return new AliasInvocation(ref, args); + } else { + return dotCallCreate(rc.dotCall()); + } + } + + /** + * Handles both cases of dot calls: this.func(args) and targetFunc(this).func(args) Converts them to func(this, + * args) and func(targetFunc(this), args) respectively + */ + private Expression dotCallCreate(DotCallContext rc) throws LJError { + if (rc.OBJECT_TYPE() != null) { + String text = rc.OBJECT_TYPE().getText(); + + // check if there are multiple fields (e.g. this.a.b) + if (text.chars().filter(ch -> ch == '.').count() > 1) + throw new SyntaxError("Multiple dot notation is not allowed", text); + + // this.func(args) => func(this, args) + int dot = text.indexOf('.'); + String target = text.substring(0, dot); + String simpleName = text.substring(dot + 1); + String name = Utils.qualifyName(prefix, simpleName); + List args = getArgs(rc.args(0)); + if (!args.isEmpty() && args.get(0)instanceof Var v && v.getName().equals(Keys.THIS) + && target.equals(Keys.THIS)) + throw new SyntaxError("Cannot use both dot notation and explicit 'this' argument. Use either 'this." + + simpleName + "()' or '" + simpleName + "(this)'", text); + + args.add(0, new Var(target)); + return new FunctionInvocation(name, args); + + } else { + // targetFunc(this).func(args) => func(targetFunc(this), args) + String targetFunc = rc.ID(0).getText(); + String func = rc.ID(1).getText(); + String name = Utils.qualifyName(prefix, func); + List targetArgs = getArgs(rc.args(0)); + List funcArgs = getArgs(rc.args(1)); + funcArgs.add(0, new FunctionInvocation(targetFunc, targetArgs)); + return new FunctionInvocation(name, funcArgs); } } From 3fefa2f2907f18a7d2ba95b8a9e8eb3abb9445bf Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Fri, 13 Feb 2026 15:03:08 +0000 Subject: [PATCH 23/25] Add Boxed Types Support (#149) --- .../main/java/testSuite/CorrectBoxedTypes.java | 18 ++++++++++++++++++ .../main/java/testSuite/ErrorBoxedBoolean.java | 12 ++++++++++++ .../main/java/testSuite/ErrorBoxedDouble.java | 12 ++++++++++++ .../main/java/testSuite/ErrorBoxedInteger.java | 12 ++++++++++++ .../liquidjava/smt/TranslatorContextToZ3.java | 18 +++++++++--------- .../src/main/java/liquidjava/utils/Utils.java | 3 +-- 6 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 liquidjava-example/src/main/java/testSuite/CorrectBoxedTypes.java create mode 100644 liquidjava-example/src/main/java/testSuite/ErrorBoxedBoolean.java create mode 100644 liquidjava-example/src/main/java/testSuite/ErrorBoxedDouble.java create mode 100644 liquidjava-example/src/main/java/testSuite/ErrorBoxedInteger.java diff --git a/liquidjava-example/src/main/java/testSuite/CorrectBoxedTypes.java b/liquidjava-example/src/main/java/testSuite/CorrectBoxedTypes.java new file mode 100644 index 00000000..1a404775 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/CorrectBoxedTypes.java @@ -0,0 +1,18 @@ +// Refinement Error +package testSuite; + +import liquidjava.specification.Refinement; + +@SuppressWarnings("unused") +public class CorrectBoxedTypes { + public static void main(String[] args) { + @Refinement("_ == true") + Boolean b = true; + + @Refinement("_ > 0") + Integer i = 1; + + @Refinement("_ > 0") + Double d = 1.0; + } +} diff --git a/liquidjava-example/src/main/java/testSuite/ErrorBoxedBoolean.java b/liquidjava-example/src/main/java/testSuite/ErrorBoxedBoolean.java new file mode 100644 index 00000000..93c6a3c1 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/ErrorBoxedBoolean.java @@ -0,0 +1,12 @@ +// Refinement Error +package testSuite; + +import liquidjava.specification.Refinement; + +@SuppressWarnings("unused") +public class ErrorBoxedBoolean { + public static void main(String[] args) { + @Refinement("_ == true") + Boolean b = false; + } +} diff --git a/liquidjava-example/src/main/java/testSuite/ErrorBoxedDouble.java b/liquidjava-example/src/main/java/testSuite/ErrorBoxedDouble.java new file mode 100644 index 00000000..643b509a --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/ErrorBoxedDouble.java @@ -0,0 +1,12 @@ +// Refinement Error +package testSuite; + +import liquidjava.specification.Refinement; + +@SuppressWarnings("unused") +public class ErrorBoxedDouble { + public static void main(String[] args) { + @Refinement("_ > 0") + Double d = -1.0; + } +} diff --git a/liquidjava-example/src/main/java/testSuite/ErrorBoxedInteger.java b/liquidjava-example/src/main/java/testSuite/ErrorBoxedInteger.java new file mode 100644 index 00000000..2d937e20 --- /dev/null +++ b/liquidjava-example/src/main/java/testSuite/ErrorBoxedInteger.java @@ -0,0 +1,12 @@ +// Refinement Error +package testSuite; + +import liquidjava.specification.Refinement; + +@SuppressWarnings("unused") +public class ErrorBoxedInteger { + public static void main(String[] args) { + @Refinement("_ > 0") + Integer j = -1; + } +} diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorContextToZ3.java b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorContextToZ3.java index c3fb32dc..08749a07 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorContextToZ3.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorContextToZ3.java @@ -42,10 +42,10 @@ public static void storeVariablesSubtypes(Context z3, List vari private static Expr getExpr(Context z3, String name, CtTypeReference type) { String typeName = type.getQualifiedName(); return switch (typeName) { - case "int", "short" -> z3.mkIntConst(name); - case "boolean" -> z3.mkBoolConst(name); - case "long" -> z3.mkRealConst(name); - case "float", "double" -> (FPExpr) z3.mkConst(name, z3.mkFPSort64()); + case "int", "short", "java.lang.Integer", "java.lang.Short" -> z3.mkIntConst(name); + case "boolean", "java.lang.Boolean" -> z3.mkBoolConst(name); + case "long", "java.lang.Long" -> z3.mkRealConst(name); + case "float", "double", "java.lang.Float", "java.lang.Double" -> (FPExpr) z3.mkConst(name, z3.mkFPSort64()); case "int[]" -> z3.mkArrayConst(name, z3.mkIntSort(), z3.mkIntSort()); default -> z3.mkConst(name, z3.mkUninterpretedSort(typeName)); }; @@ -81,11 +81,11 @@ private static void addBuiltinFunctions(Context z3, Map> fun static Sort getSort(Context z3, String sort) { return switch (sort) { - case "int" -> z3.getIntSort(); - case "boolean" -> z3.getBoolSort(); - case "long" -> z3.getRealSort(); - case "float" -> z3.mkFPSort32(); - case "double" -> z3.mkFPSortDouble(); + case "int", "short", "java.lang.Integer", "java.lang.Short" -> z3.getIntSort(); + case "boolean", "java.lang.Boolean" -> z3.getBoolSort(); + case "long", "java.lang.Long" -> z3.getRealSort(); + case "float", "java.lang.Float" -> z3.mkFPSort32(); + case "double", "java.lang.Double" -> z3.mkFPSortDouble(); case "int[]" -> z3.mkArraySort(z3.mkIntSort(), z3.mkIntSort()); case "String" -> z3.getStringSort(); case "void" -> z3.mkUninterpretedSort("void"); diff --git a/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java b/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java index 1cf45bc7..1e99e3f0 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java +++ b/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java @@ -50,7 +50,6 @@ private static boolean isLiquidJavaAnnotation(CtAnnotation annotation) { private static boolean hasRefinementValue(CtAnnotation annotation, String refinement) { Map values = annotation.getValues(); - return Stream.of("value", "to", "from") - .anyMatch(key -> refinement.equals(String.valueOf(values.get(key)))); + return Stream.of("value", "to", "from").anyMatch(key -> refinement.equals(String.valueOf(values.get(key)))); } } From dd5083c33a14ea6516062584261d1af0af7ad8d5 Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Fri, 13 Feb 2026 15:52:25 +0000 Subject: [PATCH 24/25] Update Annotations Javadocs (#144) --- .../specification/ExternalRefinementsFor.java | 33 ++++++++--- .../java/liquidjava/specification/Ghost.java | 32 ++++++++-- .../specification/GhostMultiple.java | 4 +- .../liquidjava/specification/Refinement.java | 43 ++++++++++++-- .../specification/RefinementAlias.java | 33 +++++++++-- .../RefinementAliasMultiple.java | 4 +- .../specification/RefinementPredicate.java | 34 ++++++++++- .../RefinementPredicateMultiple.java | 5 ++ .../specification/StateRefinement.java | 59 ++++++++++++++++--- .../StateRefinementMultiple.java | 5 +- .../liquidjava/specification/StateSet.java | 34 +++++++++-- .../liquidjava/specification/StateSets.java | 5 +- 12 files changed, 246 insertions(+), 45 deletions(-) diff --git a/liquidjava-api/src/main/java/liquidjava/specification/ExternalRefinementsFor.java b/liquidjava-api/src/main/java/liquidjava/specification/ExternalRefinementsFor.java index 1ed03d84..3faef58e 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/ExternalRefinementsFor.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/ExternalRefinementsFor.java @@ -6,18 +6,37 @@ import java.lang.annotation.Target; /** - * Annotation to create refinements for an external library. The annotation receives the path of the - * library e.g. @ExternalRefinementsFor("java.lang.Math") + * Annotation to refine a class or interface of an external library. + *

+ * This annotation allows you to specify refinements and state transitions for classes from external libraries + * that you cannot directly annotate. The refinements apply to all instances of the specified class. + *

+ * Example: + *

+ * {@code
+ * @ExternalRefinementsFor("java.lang.Math")
+ * public interface MathRefinements {
+ *     @Refinement("_ >= 0")
+ *     public double sqrt(double x);
+ * }
+ * }
+ * 
* - * @author catarina gamboa + * @author Catarina Gamboa */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) public @interface ExternalRefinementsFor { + /** - * The prefix of the external method - * - * @return + * The fully qualified name of the class or interface for which the refinements are being defined. + *

+ * Example: + *

+     * {@code
+     * @ExternalRefinementsFor("java.lang.Math")
+     * }
+     * 
*/ - public String value(); + String value(); } diff --git a/liquidjava-api/src/main/java/liquidjava/specification/Ghost.java b/liquidjava-api/src/main/java/liquidjava/specification/Ghost.java index 7fb600e4..195b5693 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/Ghost.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/Ghost.java @@ -7,15 +7,37 @@ import java.lang.annotation.Target; /** - * Annotation to create a ghost variable for a class. The annotation receives - * the type and name of - * the ghost within a string e.g. @Ghost("int size") + * Annotation to create a ghost variable for a class or interface. + *

+ * Ghost variables that only exist during the verification and can be used in refinements and state refinements. + * They are not part of the actual implementation but help specify behavior and invariants. + *

+ * Example: + *

+ * {@code
+ * @Ghost("int size")
+ * public class MyStack {
+ *     // ...
+ * }
+ * }
+ * 
* - * @author catarina gamboa + * @author Catarina Gamboa */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Repeatable(GhostMultiple.class) public @interface Ghost { - public String value(); + + /** + * The type and name of the ghost variable. + *

+ * Example: + *

+     * {@code
+     * @Ghost("int size")
+     * }
+     * 
+ */ + String value(); } diff --git a/liquidjava-api/src/main/java/liquidjava/specification/GhostMultiple.java b/liquidjava-api/src/main/java/liquidjava/specification/GhostMultiple.java index 8b42d74a..4aea5a73 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/GhostMultiple.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/GhostMultiple.java @@ -6,9 +6,9 @@ import java.lang.annotation.Target; /** - * Annotation to allow the creation of multiple @Ghost + * Annotation to allow the creation of multiple ghost variables. * - * @author catarina gamboa + * @author Catarina Gamboa */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) diff --git a/liquidjava-api/src/main/java/liquidjava/specification/Refinement.java b/liquidjava-api/src/main/java/liquidjava/specification/Refinement.java index 85aff527..0048435b 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/Refinement.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/Refinement.java @@ -6,16 +6,49 @@ import java.lang.annotation.Target; /** - * Annotation to add a refinement to variables, class fields, method's parameters and method's - * return value e.g. @Refinement("x > 0") int x; + * Annotation to add a refinement to variables, class fields, method's parameters and method's return values. + *

+ * Refinements are logical predicates that must hold for the annotated element. + *

+ * Example: + *

+ * {@code
+ * @Refinement("x > 0")
+ * int x;
+ * 
+ * @Refinement("_ > 0")
+ * int increment(@Refinement("_ >= 0") int n) {
+ *     return n + 1;
+ * }
+ * 
* - * @author catarina gamboa + * @author Catarina Gamboa */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.LOCAL_VARIABLE, ElementType.PARAMETER, ElementType.TYPE}) public @interface Refinement { - public String value(); + /** + * The refinement string that defines a logical predicate that must hold for the annotated element. + *

+ * Example: + *

+     * {@code
+     * @Refinement("x > 0")
+     * }
+     * 
+ */ + String value(); - public String msg() default ""; + /** + * Custom error message to be shown when the refinement is violated (optional). + *

+ * Example: + *

+     * {@code
+     * @Refinement(value = "x > 0", msg = "x must be positive")
+     * }
+     * 
+ */ + String msg() default ""; } diff --git a/liquidjava-api/src/main/java/liquidjava/specification/RefinementAlias.java b/liquidjava-api/src/main/java/liquidjava/specification/RefinementAlias.java index 5274514a..01253d56 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/RefinementAlias.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/RefinementAlias.java @@ -7,14 +7,39 @@ import java.lang.annotation.Target; /** - * Annotation to create a ghost variable for a class. The annotation receives the type and name of - * the ghost within a string e.g. @RefinementAlias("Nat(int x) {x > 0}") + * Annotation to create a refinement alias to be reused in refinements. + *

+ * Refinement aliases can be used to define reusable refinement predicates with parameters. + * They help reduce duplication and improve readability of complex refinement specifications. + *

+ * Example: + *

+ * {@code
+ * @RefinementAlias("Nat(int x) { x > 0 }")
+ * public class MyClass {
+ *     @Refinement("Nat(_)")
+ *     int value;
+ * }
+ * }
+ * 
* - * @author catarina gamboa + * @see Refinement + * @author Catarina Gamboa */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Repeatable(RefinementAliasMultiple.class) public @interface RefinementAlias { - public String value(); + + /** + * The refinement alias string, which includes the name of the alias, its parameters and the refinement itself. + *

+ * Example: + *

+     * {@code
+     * @RefinementAlias("Nat(int x) { x > 0 }")
+     * }
+     * 
+ */ + String value(); } diff --git a/liquidjava-api/src/main/java/liquidjava/specification/RefinementAliasMultiple.java b/liquidjava-api/src/main/java/liquidjava/specification/RefinementAliasMultiple.java index a4b2256e..2f5e93e0 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/RefinementAliasMultiple.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/RefinementAliasMultiple.java @@ -6,9 +6,9 @@ import java.lang.annotation.Target; /** - * Annotation to create a multiple Alias in a class + * Annotation to create multiple refinement aliases. * - * @author catarina gamboa + * @author Catarina Gamboa */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) diff --git a/liquidjava-api/src/main/java/liquidjava/specification/RefinementPredicate.java b/liquidjava-api/src/main/java/liquidjava/specification/RefinementPredicate.java index aabea663..44d7f4df 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/RefinementPredicate.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/RefinementPredicate.java @@ -6,9 +6,41 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Annotation that allows the creation of ghost variables or refinement aliases within method or constructor scope. + *

+ * This annotation enables the declaration of ghosts and refinement aliases. + *

+ * Example: + *

+ * {@code
+ * @RefinementPredicate("ghost int size")
+ * @RefinementPredicate("type Nat(int x) { x > 0 }")
+ * public void process() {
+ *     // ...
+ * }
+ * }
+ * 
+ * + * @see Ghost + * @see RefinementAlias + * @author Catarina Gamboa + */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @Repeatable(RefinementPredicateMultiple.class) public @interface RefinementPredicate { - public String value(); + + /** + * The refinement predicate string, which can define a ghost variable or a refinement alias. + *

+ * Example: + *

+     * {@code
+     * @RefinementPredicate("ghost int size")
+     * @RefinementPredicate("type Nat(int x) { x > 0 }")
+     * }
+     * 
+ */ + String value(); } diff --git a/liquidjava-api/src/main/java/liquidjava/specification/RefinementPredicateMultiple.java b/liquidjava-api/src/main/java/liquidjava/specification/RefinementPredicateMultiple.java index 3af7267b..ca512fa8 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/RefinementPredicateMultiple.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/RefinementPredicateMultiple.java @@ -5,6 +5,11 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +/** + * Annotation to allow the creation of multiple refinement predicates. + * + * @author Catarina Gamboa + */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) public @interface RefinementPredicateMultiple { diff --git a/liquidjava-api/src/main/java/liquidjava/specification/StateRefinement.java b/liquidjava-api/src/main/java/liquidjava/specification/StateRefinement.java index 18a57fe3..2d235891 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/StateRefinement.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/StateRefinement.java @@ -7,20 +7,63 @@ import java.lang.annotation.Target; /** - * Annotation to create state transitions in a method. The annotation has three arguments: from : the - * state in which the object needs to be for the method to be invoked correctly to : the state in - * which the object will be after the execution of the method msg : optional custom error message to display when refinement is violated - * e.g. @StateRefinement(from="open(this)", to="closed(this)") + * Annotation to create state transitions in a method using states defined in state sets. + *

+ * This annotation specifies the required precondition state and the resulting + * postcondition state when a method or constructor is invoked. + * Constructors can only specify the postcondition state since they create a new object. + *

+ * Example: + *

+ * {@code
+ * @StateRefinement(from="open(this)", to="closed(this)", msg="The object needs to be open before closing")
+ * public void close() {
+ *     // ...
+ * }
+ * }
+ * 
* - * @author catarina gamboa + * @see StateSet + * @author Catarina Gamboa */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) @Repeatable(StateRefinementMultiple.class) public @interface StateRefinement { - public String from() default ""; - public String to() default ""; + /** + * The logical pre-condition that defines the state in which the object needs to be before calling the method (optional) + *

+ * Example: + *

+     * {@code
+     * @StateRefinement(from="open(this)")
+     * }
+     * 
+ */ + String from() default ""; - public String msg() default ""; + /** + * The logical post-condition that defines the state in which the object will be after calling the method (optional) + *

+ * Example: + *

+     * {@code
+     * @StateRefinement(from="open(this)", to="closed(this)")
+     * }
+     * 
+ */ + String to() default ""; + + /** + * Custom error message to be shown when the {@code from} pre-condition is violated (optional) + *

+ * Example: + *

+     * {@code
+     * @StateRefinement(from="open(this)", to="closed(this)", msg="The object needs to be open before closing")
+     * }
+     * 
+ */ + String msg() default ""; } diff --git a/liquidjava-api/src/main/java/liquidjava/specification/StateRefinementMultiple.java b/liquidjava-api/src/main/java/liquidjava/specification/StateRefinementMultiple.java index 0280fcbe..c410a4db 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/StateRefinementMultiple.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/StateRefinementMultiple.java @@ -6,10 +6,9 @@ import java.lang.annotation.Target; /** - * Interface to allow multiple state refinements in a method. A method can have a state refinement - * for each set of different source and destination states + * Annotation to allow the creation of multiple state transitions. * - * @author catarina gamboa + * @author Catarina Gamboa */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.CONSTRUCTOR}) diff --git a/liquidjava-api/src/main/java/liquidjava/specification/StateSet.java b/liquidjava-api/src/main/java/liquidjava/specification/StateSet.java index 71549eca..ccdc4ebb 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/StateSet.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/StateSet.java @@ -7,15 +7,39 @@ import java.lang.annotation.Target; /** - * Annotation to create the disjoint states in which class objects can be. The annotation receives a - * list of strings representing the names of the states. e.g. @StateSet({"open", "reading", - * "closed"}) + * Annotation to create a set of disjoint states in which class objects can be. + *

+ * An object will always be in exactly one state from each state set at any given time, + * and cannot be in more than one state from the same state set (e.g., {@code open} and {@code closed} simultaneously). + * To allow an object to be in multiple states at once, they must be from different state sets. + *

+ * Example: + *

+ * {@code
+ * @StateSet({"open", "reading", "closed"})
+ * @StateSet({"locked", "unlocked"})
+ * public class File {
+ *    // ...
+ * }
+ * 
* - * @author catarina gamboa + * @see StateRefinement + * @author Catarina Gamboa */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) @Repeatable(StateSets.class) public @interface StateSet { - public String[] value(); + + /** + * The array of states to be created. + *

+ * Example: + *

+     * {@code
+     * @StateSet({"open", "reading", "closed"})
+     * }
+     * 
+ */ + String[] value(); } diff --git a/liquidjava-api/src/main/java/liquidjava/specification/StateSets.java b/liquidjava-api/src/main/java/liquidjava/specification/StateSets.java index ecd10fdf..817a0c91 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/StateSets.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/StateSets.java @@ -6,9 +6,8 @@ import java.lang.annotation.Target; /** - * Interface to allow multiple StateSets in a class. - * - * @author catarina gamboa + * Annotation to allow the creation of multiple state sets. + * @author Catarina Gamboa */ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE}) From 375418dee8c9d74482ab1fc9a1a82e12db69d31a Mon Sep 17 00:00:00 2001 From: Ricardo Costa Date: Fri, 13 Feb 2026 17:49:34 +0000 Subject: [PATCH 25/25] New Releases --- README.md | 4 ++-- liquidjava-api/pom.xml | 2 +- liquidjava-example/pom.xml | 2 +- liquidjava-verifier/pom.xml | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 6168534d..39352ea3 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ Additionally, you'll need the following dependency, which includes the LiquidJav io.github.liquid-java liquidjava-api - 0.0.3 + 0.0.4 ``` @@ -63,7 +63,7 @@ repositories { } dependencies { - implementation 'io.github.liquid-java:liquidjava-api:0.0.3' + implementation 'io.github.liquid-java:liquidjava-api:0.0.4' } ``` diff --git a/liquidjava-api/pom.xml b/liquidjava-api/pom.xml index 88bbe565..ca68b54b 100644 --- a/liquidjava-api/pom.xml +++ b/liquidjava-api/pom.xml @@ -11,7 +11,7 @@ io.github.liquid-java liquidjava-api - 0.0.3 + 0.0.4 liquidjava-api LiquidJava API https://github.com/liquid-java/liquidjava diff --git a/liquidjava-example/pom.xml b/liquidjava-example/pom.xml index ef167bc6..2ad19908 100644 --- a/liquidjava-example/pom.xml +++ b/liquidjava-example/pom.xml @@ -50,7 +50,7 @@ io.github.liquid-java liquidjava-api - 0.0.3 + 0.0.4 diff --git a/liquidjava-verifier/pom.xml b/liquidjava-verifier/pom.xml index 353b0aef..65b45140 100644 --- a/liquidjava-verifier/pom.xml +++ b/liquidjava-verifier/pom.xml @@ -11,7 +11,7 @@ io.github.liquid-java liquidjava-verifier - 0.0.11 + 0.0.12 liquidjava-verifier LiquidJava Verifier https://github.com/liquid-java/liquidjava @@ -301,7 +301,7 @@ io.github.liquid-java liquidjava-api - 0.0.3 + 0.0.4