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 diff --git a/README.md b/README.md index 1e20ac80..39352ea3 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) @@ -49,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 ``` @@ -60,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' } ``` @@ -78,8 +81,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 +106,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-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-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 ce3dc20a..0048435b 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/Refinement.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/Refinement.java @@ -6,14 +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(); + + /** + * 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 b652944b..2d235891 100644 --- a/liquidjava-api/src/main/java/liquidjava/specification/StateRefinement.java +++ b/liquidjava-api/src/main/java/liquidjava/specification/StateRefinement.java @@ -7,18 +7,63 @@ import java.lang.annotation.Target; /** - * Annotation to create state transitions in a method. The annotation has two 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 - * 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 ""; + + /** + * 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}) 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-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/testMultiple/errors/CustomError.java b/liquidjava-example/src/main/java/testMultiple/errors/CustomError.java new file mode 100644 index 00000000..7505005d --- /dev/null +++ b/liquidjava-example/src/main/java/testMultiple/errors/CustomError.java @@ -0,0 +1,8 @@ +package testMultiple.errors; + +import liquidjava.specification.StateSet; + +@StateSet({"Open", "Closed"}) +public class CustomError { + +} diff --git a/liquidjava-example/src/main/java/testMultiple/errors/GhostInvocationError.java b/liquidjava-example/src/main/java/testMultiple/errors/GhostInvocationError.java new file mode 100644 index 00000000..831c6ca5 --- /dev/null +++ b/liquidjava-example/src/main/java/testMultiple/errors/GhostInvocationError.java @@ -0,0 +1,11 @@ +package testMultiple.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/testMultiple/errors/IllegalConstructorTransitionError.java b/liquidjava-example/src/main/java/testMultiple/errors/IllegalConstructorTransitionError.java new file mode 100644 index 00000000..01134b29 --- /dev/null +++ b/liquidjava-example/src/main/java/testMultiple/errors/IllegalConstructorTransitionError.java @@ -0,0 +1,11 @@ +package testMultiple.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/testMultiple/errors/InvalidRefinementError.java b/liquidjava-example/src/main/java/testMultiple/errors/InvalidRefinementError.java new file mode 100644 index 00000000..e81cfdff --- /dev/null +++ b/liquidjava-example/src/main/java/testMultiple/errors/InvalidRefinementError.java @@ -0,0 +1,10 @@ +package testMultiple.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/testMultiple/errors/NotFoundError.java b/liquidjava-example/src/main/java/testMultiple/errors/NotFoundError.java new file mode 100644 index 00000000..df3b7d93 --- /dev/null +++ b/liquidjava-example/src/main/java/testMultiple/errors/NotFoundError.java @@ -0,0 +1,11 @@ +package testMultiple.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/testMultiple/errors/RefinementError.java b/liquidjava-example/src/main/java/testMultiple/errors/RefinementError.java new file mode 100644 index 00000000..b6ab1682 --- /dev/null +++ b/liquidjava-example/src/main/java/testMultiple/errors/RefinementError.java @@ -0,0 +1,11 @@ +package testMultiple.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/testMultiple/errors/StateConflictError.java b/liquidjava-example/src/main/java/testMultiple/errors/StateConflictError.java new file mode 100644 index 00000000..66b8b9ae --- /dev/null +++ b/liquidjava-example/src/main/java/testMultiple/errors/StateConflictError.java @@ -0,0 +1,14 @@ +package testMultiple.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/testMultiple/errors/StateRefinementError.java b/liquidjava-example/src/main/java/testMultiple/errors/StateRefinementError.java new file mode 100644 index 00000000..1187de24 --- /dev/null +++ b/liquidjava-example/src/main/java/testMultiple/errors/StateRefinementError.java @@ -0,0 +1,20 @@ +package testMultiple.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/testMultiple/errors/SyntaxError.java b/liquidjava-example/src/main/java/testMultiple/errors/SyntaxError.java new file mode 100644 index 00000000..082d25b8 --- /dev/null +++ b/liquidjava-example/src/main/java/testMultiple/errors/SyntaxError.java @@ -0,0 +1,11 @@ +package testMultiple.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/testMultiple/warnings/NonExistentClass.java b/liquidjava-example/src/main/java/testMultiple/warnings/NonExistentClass.java new file mode 100644 index 00000000..c1506822 --- /dev/null +++ b/liquidjava-example/src/main/java/testMultiple/warnings/NonExistentClass.java @@ -0,0 +1,8 @@ +package testMultiple.warnings; + +import liquidjava.specification.ExternalRefinementsFor; + +@ExternalRefinementsFor("non.existent.Class") +public interface NonExistentClass { + public void NonExistentClass(); +} diff --git a/liquidjava-example/src/main/java/testMultiple/warnings/NonExistentConstructor.java b/liquidjava-example/src/main/java/testMultiple/warnings/NonExistentConstructor.java new file mode 100644 index 00000000..eae37e3c --- /dev/null +++ b/liquidjava-example/src/main/java/testMultiple/warnings/NonExistentConstructor.java @@ -0,0 +1,16 @@ +package testMultiple.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/testMultiple/warnings/NonExistentMethod.java b/liquidjava-example/src/main/java/testMultiple/warnings/NonExistentMethod.java new file mode 100644 index 00000000..9d3d16e6 --- /dev/null +++ b/liquidjava-example/src/main/java/testMultiple/warnings/NonExistentMethod.java @@ -0,0 +1,16 @@ +package testMultiple.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-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/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/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/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/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..a5dd6c75 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorAliasArgumentSize.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorAliasArgumentSize.java @@ -1,3 +1,4 @@ +// Argument Mismatch 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/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/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..263a0f9b 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorAliasTypeMismatch.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorAliasTypeMismatch.java @@ -1,3 +1,4 @@ +// Argument Mismatch Error package testSuite; import liquidjava.specification.Refinement; @@ -14,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/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/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-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/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-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..6ff3be00 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorGhostArgsTypes.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorGhostArgsTypes.java @@ -1,3 +1,4 @@ +// 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 f080428c..f9b8ef35 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorGhostNumberArgs.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorGhostNumberArgs.java @@ -1,3 +1,4 @@ +// Argument Mismatch 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/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-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/ErrorSyntaxRefinement.java similarity index 79% rename from liquidjava-example/src/main/java/testSuite/ErrorSyntax1.java rename to liquidjava-example/src/main/java/testSuite/ErrorSyntaxRefinement.java index bdf498ae..b02c63d0 100644 --- a/liquidjava-example/src/main/java/testSuite/ErrorSyntax1.java +++ b/liquidjava-example/src/main/java/testSuite/ErrorSyntaxRefinement.java @@ -1,9 +1,10 @@ +// Syntax Error package testSuite; 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-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/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-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/pom.xml b/liquidjava-verifier/pom.xml index e9727bff..65b45140 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.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 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/api/CommandLineLauncher.java b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java index dc6aa2b4..b664ced6 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java +++ b/liquidjava-verifier/src/main/java/liquidjava/api/CommandLineLauncher.java @@ -4,73 +4,74 @@ import java.util.Arrays; import java.util.List; -import liquidjava.diagnostics.ErrorEmitter; +import liquidjava.diagnostics.Diagnostics; +import liquidjava.diagnostics.errors.CustomError; +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; import spoon.support.QueueProcessingManager; 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"; + private static final Diagnostics diagnostics = Diagnostics.getInstance(); + + public static void main(String[] args) { 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])); + + // print diagnostics + if (diagnostics.foundWarning()) { + System.out.println(diagnostics.getWarningOutput()); + } + if (diagnostics.foundError()) { + System.out.println(diagnostics.getErrorOutput()); + } else { + 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(); + diagnostics.clear(); 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)); + Environment env = launcher.getEnvironment(); + env.setNoClasspath(true); + env.setComplianceLevel(8); - // optional - // launcher.getEnvironment().setSourceClasspath( - // "lib1.jar:lib2.jar".split(":")); - launcher.getEnvironment().setComplianceLevel(8); - launcher.run(); + boolean buildSuccess = launcher.getModelBuilder().build(); + 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(); final ProcessingManager processingManager = new QueueProcessingManager(factory); - final RefinementProcessor processor = new RefinementProcessor(factory, ee); + 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 ee; + // 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 new file mode 100644 index 00000000..7b72d190 --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Colors.java @@ -0,0 +1,9 @@ +package liquidjava.diagnostics; + +// ANSI color codes +public class Colors { + public static final String RESET = "\u001B[0m"; + public static final String GREY = "\u001B[90m"; + public static final String BOLD_RED = "\u001B[1;31m"; + 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 new file mode 100644 index 00000000..f125f5fc --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/Diagnostics.java @@ -0,0 +1,64 @@ +package liquidjava.diagnostics; + +import java.util.LinkedHashSet; +import liquidjava.diagnostics.errors.LJError; +import liquidjava.diagnostics.warnings.LJWarning; + +/** + * Singleton class to store diagnostics (errors and warnings) during the verification process + * + * @see LJError + * @see LJWarning + */ +public class Diagnostics { + private static final Diagnostics instance = new Diagnostics(); + + private final LinkedHashSet errors; + private final LinkedHashSet warnings; + + private Diagnostics() { + this.errors = new LinkedHashSet<>(); + this.warnings = new LinkedHashSet<>(); + } + + public static Diagnostics getInstance() { + return instance; + } + + public void add(LJError error) { + this.errors.add(error); + } + + public void add(LJWarning warning) { + this.warnings.add(warning); + } + + public boolean foundError() { + return !this.errors.isEmpty(); + } + + public boolean foundWarning() { + return !this.warnings.isEmpty(); + } + + public LinkedHashSet getErrors() { + return this.errors; + } + + public LinkedHashSet getWarnings() { + return this.warnings; + } + + public void clear() { + this.errors.clear(); + this.warnings.clear(); + } + + public String getErrorOutput() { + return String.join("\n", errors.stream().map(LJError::toString).toList()); + } + + public String getWarningOutput() { + return String.join("\n", warnings.stream().map(LJWarning::toString).toList()); + } +} 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/ErrorPosition.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorPosition.java index 1ee72e7d..3383f6bf 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorPosition.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/ErrorPosition.java @@ -2,35 +2,7 @@ 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()) 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..de74d252 --- /dev/null +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostic.java @@ -0,0 +1,164 @@ +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 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, 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() { + return title; + } + + public String getMessage() { + return message; + } + + public String getCustomMessage() { + return customMessage; + } + + public String getDetails() { + return ""; // to be overridden by subclasses + } + + 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; + } + + 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) + .append("\n"); + + // snippet + String snippet = getSnippet(); + if (snippet != null) { + sb.append(snippet); + } + + // details + String details = getDetails(); + if (!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.lineStart()).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.lineStart() - contextBefore); + int endLine = Math.min(lines.size(), position.lineEnd() + contextAfter); + + // calculate padding for line numbers + int padding = String.valueOf(endLine).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(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()) { + 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 + 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)); + 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"); + } + } + } + return sb.toString(); + } catch (Exception e) { + 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) + && ((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 + (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/LJDiagnostics.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostics.java deleted file mode 100644 index 02a6d315..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/LJDiagnostics.java +++ /dev/null @@ -1,117 +0,0 @@ -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 - * - * @see LJError - * @see LJWarning - */ -public class LJDiagnostics { - private static LJDiagnostics instance; - - 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) { - this.errors.add(error); - } - - public void addWarning(LJWarning warning) { - this.warnings.add(warning); - } - - public void setTranslationMap(HashMap map) { - this.translationMap = map; - } - - public boolean foundError() { - return !this.errors.isEmpty(); - } - - public boolean foundWarning() { - return !this.warnings.isEmpty(); - } - - public ArrayList getErrors() { - return this.errors; - } - - public ArrayList getWarnings() { - return this.warnings; - } - - public HashMap getTranslationMap() { - return this.translationMap; - } - - 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(); - this.translationMap.clear(); - } - - @Override - public String toString() { - 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(); - } -} 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/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/CustomError.java b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/CustomError.java index 7f885e45..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,5 +1,7 @@ package liquidjava.diagnostics.errors; +import spoon.reflect.cu.SourcePosition; + /** * Custom error with an arbitrary message * @@ -8,11 +10,10 @@ public class CustomError extends LJError { public CustomError(String message) { - super("Found Error", message, null); + super("Error", message, null, null); } - @Override - public String toString() { - return super.toString(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 7d04e28d..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/GhostInvocationError.java +++ /dev/null @@ -1,30 +0,0 @@ -package liquidjava.diagnostics.errors; - -import liquidjava.rj_language.Predicate; -import spoon.reflect.declaration.CtElement; - -/** - * Error indicating that a ghost method invocation is invalid (e.g., has wrong arguments) - * - * @see LJError - */ -public class GhostInvocationError extends LJError { - - private Predicate 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 Predicate getExpected() { - return expected; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Expected: ").append(expected.toString()).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..ac04737d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/IllegalConstructorTransitionError.java @@ -11,11 +11,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); - } - - @Override - public String toString() { - return super.toString(null); + "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 02b41265..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) @@ -9,21 +9,14 @@ */ public class InvalidRefinementError extends LJError { - private String refinement; + private final String refinement; - public InvalidRefinementError(String message, CtElement element, String refinement) { - super("Invalid Refinement", message, element); + public InvalidRefinementError(SourcePosition position, String message, String refinement) { + super("Invalid Refinement", message, position, 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 6e92057f..66ff9fd2 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/LJError.java @@ -1,67 +1,28 @@ package liquidjava.diagnostics.errors; -import liquidjava.diagnostics.ErrorPosition; -import liquidjava.utils.Utils; +import liquidjava.diagnostics.LJDiagnostic; +import liquidjava.diagnostics.TranslationTable; +import liquidjava.diagnostics.Colors; 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 extends LJDiagnostic { - private String title; - private String message; - private CtElement element; - private ErrorPosition position; - private SourcePosition location; + private final TranslationTable translationTable; - public LJError(String title, String message, CtElement element) { - super(message); - 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; - } + public LJError(String title, String message, SourcePosition pos, TranslationTable translationTable) { + this(title, message, pos, translationTable, null); } - public String getTitle() { - return title; + 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(); } - 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(title).append(" at: \n").append(element.toString().replace("@liquidjava.specification.", "@")) - .append("\n\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(); + public TranslationTable getTranslationTable() { + return 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 77d1ab1f..1a2c260a 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/NotFoundError.java @@ -1,6 +1,8 @@ package liquidjava.diagnostics.errors; -import spoon.reflect.declaration.CtElement; +import liquidjava.diagnostics.TranslationTable; +import liquidjava.utils.Utils; +import spoon.reflect.cu.SourcePosition; /** * Error indicating that an element referenced in a refinement was not found @@ -9,12 +11,24 @@ */ public class NotFoundError extends LJError { - public NotFoundError(String message, CtElement element) { - super("Not Found Error", message, element); + private final String name; + private final String kind; // "Variable" | "Ghost" | "Alias" + + 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; + } + + public String getName() { + return name; } - @Override - public String toString() { - return super.toString(null); + 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 fc4b31f1..de40bd7f 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,9 @@ package liquidjava.diagnostics.errors; -import liquidjava.rj_language.Predicate; +import liquidjava.diagnostics.TranslationTable; +import liquidjava.rj_language.ast.Expression; import liquidjava.rj_language.opt.derivation_node.ValDerivationNode; -import liquidjava.utils.Utils; -import spoon.reflect.declaration.CtElement; +import spoon.reflect.cu.SourcePosition; /** * Error indicating that a refinement constraint either was violated or cannot be proven @@ -12,20 +12,22 @@ */ public class RefinementError extends LJError { - private Predicate expected; - private ValDerivationNode found; + private final ValDerivationNode expected; + private final 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); + public RefinementError(SourcePosition position, ValDerivationNode expected, ValDerivationNode found, + TranslationTable translationTable, String customMessage) { + super("Refinement Error", String.format("%s is not a subtype of %s", found.getValue(), expected.getValue()), + position, translationTable, customMessage); this.expected = expected; this.found = found; } - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Expected: ").append(Utils.stripParens(expected.toString())).append("\n"); - sb.append("Found: ").append(found.getValue()); - return super.toString(sb.toString()); + public ValDerivationNode getExpected() { + return expected; + } + + public ValDerivationNode getFound() { + return found; } } \ 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 1f714cb2..b8e262fa 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,8 @@ package liquidjava.diagnostics.errors; -import liquidjava.rj_language.Predicate; -import spoon.reflect.declaration.CtElement; +import liquidjava.diagnostics.TranslationTable; +import liquidjava.rj_language.ast.Expression; +import spoon.reflect.cu.SourcePosition; /** * Error indicating that two disjoint states were found in a state refinement @@ -10,30 +11,16 @@ */ public class StateConflictError extends LJError { - private Predicate predicate; - private String className; + private final String state;; - public StateConflictError(CtElement element, Predicate predicate, String className) { + 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", - element); - this.predicate = predicate; - this.className = className; + "Found multiple disjoint states in state transition: state transition can only go to one state of each state set", + position, translationTable); + this.state = state.toString(); } - public Predicate getPredicate() { - return predicate; - } - - public String getClassName() { - return className; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append("Class: ").append(className).append("\n"); - sb.append("Predicate: ").append(predicate.toString()); - return super.toString(sb.toString()); + public String getState() { + return state; } } 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..976508cf 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/errors/StateRefinementError.java @@ -1,8 +1,8 @@ package liquidjava.diagnostics.errors; -import java.util.Arrays; - -import spoon.reflect.declaration.CtElement; +import liquidjava.diagnostics.TranslationTable; +import liquidjava.rj_language.ast.Expression; +import spoon.reflect.cu.SourcePosition; /** * Error indicating that a state refinement transition was violated @@ -11,37 +11,23 @@ */ 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, String[] expected, String found) { - super("State Refinement Error", "State refinement transition violation", element); - this.method = method; - this.expected = expected; - this.found = found; - } - - public String getMethod() { - return method; + 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.toString(), found.toString()), position, + translationTable, customMessage); + this.expected = expected.toString(); + this.found = found.toString(); } - public String[] getExpected() { + public String getExpected() { return expected; } 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 ba679cc6..aac27123 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 @@ -9,25 +9,18 @@ */ public class SyntaxError extends LJError { - private String refinement; + private final String refinement; public SyntaxError(String message, String refinement) { this(message, null, refinement); } - public SyntaxError(String message, CtElement element, String refinement) { - super("Syntax Error", message, element); + 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/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 6a4eef29..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 @@ -9,21 +9,14 @@ */ public class ExternalClassNotFoundWarning extends LJWarning { - private String className; + private final String className; - public ExternalClassNotFoundWarning(CtElement element, String className) { - super("Class in external refinement not found", element); + public ExternalClassNotFoundWarning(SourcePosition position, String message, String className) { + super(message, position); 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 eee01505..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 @@ -9,13 +9,16 @@ */ public class ExternalMethodNotFoundWarning extends LJWarning { - private String methodName; - private String className; + private final String methodName; + private final String className; + private final String[] overloads; - public ExternalMethodNotFoundWarning(CtElement element, String methodName, String className) { - super("Method in external refinement not found", element); + public ExternalMethodNotFoundWarning(SourcePosition position, String message, String methodName, String className, + String[] overloads) { + super(message, position); this.methodName = methodName; this.className = className; + this.overloads = overloads; } public String getMethodName() { @@ -26,11 +29,12 @@ public String getClassName() { return className; } + public String[] getOverloads() { + return overloads; + } + @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()); + 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 7cf59dd9..5f5f0633 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java +++ b/liquidjava-verifier/src/main/java/liquidjava/diagnostics/warnings/LJWarning.java @@ -1,60 +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).append(" at: \n").append(element.toString().replace("@liquidjava.specification.", "@")) - .append("\n\n"); - if (extra != null) - sb.append(extra).append("\n"); - sb.append("Location: ").append(location != null ? Utils.stripParens(location.toString()) : "") - .append("\n"); - return sb.toString(); + public LJWarning(String message, SourcePosition pos) { + super("Warning", message, pos, Colors.BOLD_YELLOW, null); } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java b/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java index 46069f40..ba8d268a 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/RefinementProcessor.java @@ -3,7 +3,8 @@ import java.util.ArrayList; import java.util.List; -import liquidjava.diagnostics.ErrorEmitter; +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; @@ -18,11 +19,10 @@ public class RefinementProcessor extends AbstractProcessor { List visitedPackages = new ArrayList<>(); Factory factory; - ErrorEmitter errorEmitter; + Diagnostics diagnostics = Diagnostics.getInstance(); - public RefinementProcessor(Factory factory, ErrorEmitter ee) { + public RefinementProcessor(Factory factory) { this.factory = factory; - errorEmitter = ee; } @Override @@ -32,16 +32,22 @@ public void process(CtPackage pkg) { Context c = Context.getInstance(); c.reinitializeAllContext(); - pkg.accept(new FieldGhostsGeneration(c, factory, errorEmitter)); // 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 RefinementTypeChecker(c, factory, errorEmitter)); - if (errorEmitter.foundError()) - return; + 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/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 dbe672f5..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 @@ -1,6 +1,5 @@ package liquidjava.processor.ann_generation; -import liquidjava.diagnostics.ErrorEmitter; import liquidjava.processor.context.Context; import liquidjava.specification.Ghost; import spoon.reflect.declaration.*; @@ -11,12 +10,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,16 +26,12 @@ public Factory getFactory() { @Override public void visitCtClass(CtClass ctClass) { - if (errorEmitter.foundError()) { - return; - } - 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 cb218d7e..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,33 +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.ErrorEmitter; 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; 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()) { @@ -49,71 +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, ErrorEmitter ee) - throws ParsingException { - List invocationPredicates = getPredicatesFromExpression(list, elem, ee); - 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, ErrorEmitter ee) - throws ParsingException { - List lp = new ArrayList<>(); - for (String e : list) - lp.add(new Predicate(e, elem, ee)); - - 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/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/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..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); } @@ -78,10 +87,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 8737f572..ca120615 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/processor/facade/AliasDTO.java +++ b/liquidjava-verifier/src/main/java/liquidjava/processor/facade/AliasDTO.java @@ -2,22 +2,24 @@ 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.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; } @@ -32,7 +34,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/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 8580135b..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 @@ -1,20 +1,21 @@ package liquidjava.processor.refinement_checker; -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.Diagnostics; +import liquidjava.diagnostics.errors.LJError; +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; 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.code.CtLiteral; +import spoon.reflect.declaration.CtAnnotation; import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.declaration.CtField; @@ -27,34 +28,29 @@ public class ExternalRefinementTypeChecker extends TypeChecker { String prefix; - MethodsFunctionsChecker m; + Diagnostics diagnostics = Diagnostics.getInstance(); - public ExternalRefinementTypeChecker(Context context, Factory fac, ErrorEmitter errorEmitter) { - super(context, fac, errorEmitter); + public ExternalRefinementTypeChecker(Context context, Factory factory) { + super(context, factory); } @Override public void visitCtClass(CtClass ctClass) { - return; } @Override public void visitCtInterface(CtInterface intrface) { - if (errorEmitter.foundError()) - return; - - 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)) { - ErrorHandler.printCustomError(intrface, "Could not find class '" + prefix + "'", errorEmitter); + String message = String.format("Could not find class '%s'", prefix); + diagnostics.add(new ExternalClassNotFoundWarning(externalRef.get().getPosition(), message, prefix)); return; } - try { - getRefinementFromAnnotation(intrface); - } catch (ParsingException e) { - return; // error already in ErrorEmitter - } + getRefinementFromAnnotation(intrface); handleStateSetsFromAnnotation(intrface); super.visitCtInterface(intrface); } @@ -62,81 +58,46 @@ public void visitCtInterface(CtInterface intrface) { @Override public void visitCtField(CtField f) { - if (errorEmitter.foundError()) - return; - - Optional oc; - try { - oc = getRefinementFromAnnotation(f); - } catch (ParsingException e) { - return; // error already in ErrorEmitter - } + 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 (errorEmitter.foundError()) - return; - 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()); if (isConstructor) { if (!constructorExists(targetType, method)) { - ErrorHandler.printCustomError(method, - String.format("Could not find constructor '%s' for '%s'", method.getSignature(), prefix), - errorEmitter); - return; + 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)); } } 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 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)); return; } } - MethodsFunctionsChecker mfc = new MethodsFunctionsChecker(this); - try { - mfc.getMethodRefinements(method, prefix); - } catch (ParsingException e) { - return; - } + mfc.getMethodRefinements(method, prefix); super.visitCtMethod(method); - - // - // System.out.println("visited method external"); } - 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) { - ErrorHandler.printCustomError(element, "Could not parse the Ghost Function" + e.getMessage(), errorEmitter); - // e.printStackTrace(); + protected void getGhostFunction(String value, CtElement element) throws LJError { + GhostDTO f = RefinementsParser.getGhostDeclaration(value); + if (element.getParent() instanceof CtInterface) { + GhostFunction gh = new GhostFunction(f, factory, prefix); + context.addGhostFunction(gh); } } @@ -145,7 +106,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); @@ -206,4 +167,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..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 @@ -3,10 +3,10 @@ import java.util.ArrayList; import java.util.List; -import liquidjava.diagnostics.ErrorEmitter; +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; @@ -19,18 +19,16 @@ public class MethodsFirstChecker extends TypeChecker { MethodsFunctionsChecker mfc; List visitedClasses; + Diagnostics diagnostics = Diagnostics.getInstance(); - 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()) - return; - context.reinitializeContext(); if (visitedClasses.contains(ctClass.getQualifiedName())) return; @@ -52,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 in ErrorEmitter + 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 (errorEmitter.foundError()) - return; - if (visitedClasses.contains(intrface.getQualifiedName())) return; else @@ -73,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 in ErrorEmitter + 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 (errorEmitter.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 (errorEmitter.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 53b65116..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 @@ -5,14 +5,14 @@ import java.util.List; import java.util.Optional; -import liquidjava.diagnostics.ErrorEmitter; +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; @@ -47,16 +47,16 @@ 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 // Auxiliary TypeCheckers OperationsChecker otc; MethodsFunctionsChecker mfc; + Diagnostics diagnostics = Diagnostics.getInstance(); - 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,78 +65,69 @@ public RefinementTypeChecker(Context context, Factory factory, ErrorEmitter erro @Override public void visitCtClass(CtClass ctClass) { - if (errorEmitter.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 (errorEmitter.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 (errorEmitter.foundError()) { - return; - } super.visitCtAnnotationType(annotationType); } @Override public void visitCtConstructor(CtConstructor c) { - if (errorEmitter.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 (errorEmitter.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 (errorEmitter.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 in ErrorEmitter - } - 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(); @@ -146,34 +137,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 in ErrorEmitter - } - + checkVariableRefinements(refinementFound, varName, localVariable.getType(), localVariable, localVariable); AuxStateHandler.addStateRefinements(this, varName, e); } } @Override public void visitCtNewArray(CtNewArray newArray) { - if (errorEmitter.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 in ErrorEmitter - } + Predicate c = getExpressionRefinements(exp); String name = String.format(Formats.FRESH, context.getCounter()); if (c.getVariableNames().contains(Keys.WILDCARD)) { c = c.substituteVariable(Keys.WILDCARD, name); @@ -182,23 +157,15 @@ 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()), - Predicate.createVar(name)); - } catch (ParsingException e) { - return; // error already in ErrorEmitter - } + ep = Predicate.createEquals(BuiltinFunctionPredicate.length(Keys.WILDCARD, newArray), + Predicate.createVar(name)); + newArray.putMetadata(Keys.REFINEMENT, ep); } } @Override public void visitCtThisAccess(CtThisAccess thisAccess) { - if (errorEmitter.foundError()) { - return; - } - super.visitCtThisAccess(thisAccess); CtClass c = thisAccess.getParent(CtClass.class); String s = c.getSimpleName(); @@ -211,11 +178,7 @@ public void visitCtThisAccess(CtThisAccess thisAccess) { @SuppressWarnings("unchecked") @Override - public void visitCtAssignment(CtAssignment assignment) { - if (errorEmitter.foundError()) { - return; - } - + public void visitCtAssignment(CtAssignment assignment) throws LJError { super.visitCtAssignment(assignment); CtExpression ex = assignment.getAssigned(); @@ -225,8 +188,7 @@ public void visitCtAssignment(CtAssignment assignment) { 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()); @@ -237,19 +199,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 (errorEmitter.foundError()) { - return; - } - super.visitCtArrayRead(arrayRead); String name = String.format(Formats.INSTANCE, "arrayAccess", context.getCounter()); context.addVarToContext(name, arrayRead.getType(), new Predicate(), arrayRead); @@ -259,10 +212,6 @@ public void visitCtArrayRead(CtArrayRead arrayRead) { @Override public void visitCtLiteral(CtLiteral lit) { - if (errorEmitter.foundError()) { - return; - } - List types = Arrays.asList(Types.IMPLEMENTED); String type = lit.getType().getQualifiedName(); if (types.contains(type)) { @@ -281,26 +230,15 @@ public void visitCtLiteral(CtLiteral lit) { @Override public void visitCtField(CtField f) { - if (errorEmitter.foundError()) { - return; - } - super.visitCtField(f); - Optional c; - try { - c = getRefinementFromAnnotation(f); - } catch (ParsingException e) { - return; // error already in ErrorEmitter - } - // 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()); + Optional c = getRefinementFromAnnotation(f); + 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); + getMessageFromAnnotation(f).ifPresent(v::setMessage); if (v instanceof Variable) { ((Variable) v).setLocation("this"); } @@ -308,10 +246,6 @@ public void visitCtField(CtField f) { @Override public void visitCtFieldRead(CtFieldRead fieldRead) { - if (errorEmitter.foundError()) { - return; - } - String fieldName = fieldRead.getVariable().getSimpleName(); if (context.hasVariable(fieldName)) { RefinedVariable rv = context.getVariableByName(fieldName); @@ -330,29 +264,21 @@ 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.builtin_length(targetName, fieldRead, getErrorEmitter()))); - } catch (ParsingException e) { - return; // error already in ErrorEmitter - } + 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 + // TODO DO WE WANT THIS OR TO SHOW ERROR MESSAGE? } - super.visitCtFieldRead(fieldRead); } @Override public void visitCtVariableRead(CtVariableRead variableRead) { - if (errorEmitter.foundError()) { - return; - } - super.visitCtVariableRead(variableRead); CtVariable varDecl = variableRead.getVariable().getDeclaration(); - getPutVariableMetadada(variableRead, varDecl.getSimpleName()); + getPutVariableMetadata(variableRead, varDecl.getSimpleName()); } /** @@ -360,65 +286,32 @@ public void visitCtVariableRead(CtVariableRead variableRead) { */ @Override public void visitCtBinaryOperator(CtBinaryOperator operator) { - if (errorEmitter.foundError()) { - return; - } - super.visitCtBinaryOperator(operator); - try { - otc.getBinaryOpRefinements(operator); - } catch (ParsingException e) { - return; // error already in ErrorEmitter - } + otc.getBinaryOpRefinements(operator); } @Override public void visitCtUnaryOperator(CtUnaryOperator operator) { - if (errorEmitter.foundError()) { - return; - } - super.visitCtUnaryOperator(operator); - try { - otc.getUnaryOpRefinements(operator); - } catch (ParsingException e) { - return; // error already in ErrorEmitter - } + otc.getUnaryOpRefinements(operator); } public void visitCtInvocation(CtInvocation invocation) { - if (errorEmitter.foundError()) { - return; - } - super.visitCtInvocation(invocation); mfc.getInvocationRefinements(invocation); } @Override public void visitCtReturn(CtReturn ret) { - if (errorEmitter.foundError()) { - return; - } - super.visitCtReturn(ret); mfc.getReturnRefinements(ret); } @Override public void visitCtIf(CtIf ifElement) { - if (errorEmitter.foundError()) { - return; - } - CtExpression exp = ifElement.getCondition(); + Predicate expRefs = getExpressionRefinements(exp); - Predicate expRefs; - try { - expRefs = getExpressionRefinements(exp); - } catch (ParsingException e) { - return; // error already in ErrorEmitter - } String freshVarName = String.format(Formats.FRESH, context.getCounter()); expRefs = expRefs.substituteVariable(Keys.WILDCARD, freshVarName); Predicate lastExpRefs = substituteAllVariablesForLastInstance(expRefs); @@ -463,28 +356,14 @@ public void visitCtIf(CtIf ifElement) { @Override public void visitCtArrayWrite(CtArrayWrite arrayWrite) { - if (errorEmitter.foundError()) { - return; - } - super.visitCtArrayWrite(arrayWrite); CtExpression index = arrayWrite.getIndexExpression(); - BuiltinFunctionPredicate fp; - try { - fp = BuiltinFunctionPredicate.builtin_addToIndex(arrayWrite.getTarget().toString(), index.toString(), - Keys.WILDCARD, arrayWrite, getErrorEmitter()); - } catch (ParsingException e) { - return; // error already in ErrorEmitter - } + BuiltinFunctionPredicate fp = BuiltinFunctionPredicate.addToIndex(index.toString(), Keys.WILDCARD, arrayWrite); arrayWrite.putMetadata(Keys.REFINEMENT, fp); } @Override public void visitCtConditional(CtConditional conditional) { - if (errorEmitter.foundError()) { - return; - } - super.visitCtConditional(conditional); Predicate cond = getRefinement(conditional.getCondition()); Predicate c = Predicate.createITE(cond, getRefinement(conditional.getThenExpression()), @@ -494,28 +373,20 @@ public void visitCtConditional(CtConditional conditional) { @Override public void visitCtConstructorCall(CtConstructorCall ctConstructorCall) { - if (errorEmitter.foundError()) { - return; - } - super.visitCtConstructorCall(ctConstructorCall); mfc.getConstructorInvocationRefinements(ctConstructorCall); } @Override public void visitCtNewClass(CtNewClass newClass) { - if (errorEmitter.foundError()) { - return; - } - super.visitCtNewClass(newClass); } // ############################### Inner Visitors // ########################################## private void checkAssignment(String name, CtTypeReference type, CtExpression ex, CtExpression assignment, - CtElement parentElem, CtElement varDecl) { - getPutVariableMetadada(ex, name); + CtElement parentElem, CtElement varDecl) throws LJError { + getPutVariableMetadata(ex, name); Predicate refinementFound = getRefinement(assignment); if (refinementFound == null) { @@ -531,14 +402,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 in ErrorEmitter - } + 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); @@ -552,7 +419,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); @@ -578,18 +445,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 375bff40..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 @@ -1,12 +1,12 @@ 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.Map; import java.util.Optional; -import liquidjava.diagnostics.ErrorEmitter; -import liquidjava.diagnostics.ErrorHandler; +import liquidjava.diagnostics.errors.*; import liquidjava.processor.context.AliasWrapper; import liquidjava.processor.context.Context; import liquidjava.processor.context.GhostFunction; @@ -15,8 +15,8 @@ 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; import liquidjava.utils.constants.Keys; import liquidjava.utils.constants.Types; @@ -32,18 +32,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; - ErrorEmitter errorEmitter; + protected final Context context; + protected final Factory factory; + protected final VCChecker vcChecker; - 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; + this.vcChecker = new VCChecker(); } public Context getContext() { @@ -59,43 +59,54 @@ 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()) { 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"); + 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); } } 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); + throw new InvalidRefinementError(element.getPosition(), + "Refinement predicate must be a boolean expression", ref.get()); } - if (errorEmitter.foundError()) - return Optional.empty(); - constr = Optional.of(p); } 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")) { + Map values = ann.getAllValues(); + String msg = getStringFromAnnotation((values.get("msg"))); + if (msg != null && !msg.isEmpty()) { + return Optional.of(msg); + } + } + } + return Optional.empty(); + } + @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(); @@ -105,12 +116,12 @@ public void handleStateSetsFromAnnotation(CtElement element) { } if (an.contentEquals("liquidjava.specification.Ghost")) { CtLiteral s = (CtLiteral) ann.getAllValues().get("value"); - createStateGhost(s.getValue(), ann, an, element); + createStateGhost(s.getValue(), ann, 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()) { @@ -119,14 +130,13 @@ 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); + throw new CustomError("State names must start with lowercase", s.getPosition()); } } } Optional og = createStateGhost(set, element); - if (!og.isPresent()) { + if (og.isEmpty()) { throw new RuntimeException("Error in creation of GhostFunction"); } GhostFunction g = og.get(); @@ -144,39 +154,33 @@ private void createStateSet(CtNewArray e, int set, CtElement element) { 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) { - GhostDTO gd = null; - try { - gd = RefinementsParser.getGhostDeclaration(string); - } catch (ParsingException e) { - ErrorHandler.printCustomError(ann, "Could not parse the Ghost Function" + e.getMessage(), errorEmitter); - 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); - return; + private void createStateGhost(String string, CtAnnotation ann, CtElement element) + throws LJError { + 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.getPosition()); } // 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 = 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); } @@ -207,7 +211,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); @@ -215,24 +219,17 @@ 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) { - ErrorHandler.printCustomError(element, "Could not parse the Ghost Function" + e.getMessage(), errorEmitter); - // e.printStackTrace(); - return; + protected void getGhostFunction(String value, CtElement element) throws LJError { + GhostDTO f = RefinementsParser.getGhostDeclaration(value); + if (element.getParent()instanceof CtClass klass) { + GhostFunction gh = new GhostFunction(f, factory, klass.getQualifiedName()); + context.addGhostFunction(gh); } } - protected void handleAlias(String value, CtElement element) { + 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) { @@ -246,46 +243,42 @@ 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); - return; + throw new InvalidRefinementError(element.getPosition(), + "Refinement alias must return a boolean expression", ref); } - AliasWrapper aw = new AliasWrapper(a, factory, Keys.WILDCARD, context, klass, path); + AliasWrapper aw = new AliasWrapper(a, factory, klass, path); context.addAlias(aw); } - } catch (ParsingException e) { - ErrorHandler.printSyntaxError(e.getMessage(), value, element, errorEmitter); - return; - // e.printStackTrace(); + } catch (LJError e) { + // add location info to error + SourcePosition pos = Utils.getAnnotationPosition(element, ref); + e.setPosition(pos); + throw e; } } - 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, - CtElement usage, CtElement variable) throws ParsingException { + CtElement usage, CtElement variable) throws LJError { Optional expectedType = getRefinementFromAnnotation(variable); Predicate cEt; RefinedVariable mainRV = null; 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); @@ -300,37 +293,40 @@ else if (expectedType.isPresent()) 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) { - vcChecker.processSubtyping(expectedType, context.getGhostState(), element, factory); - element.putMetadata(Keys.REFINEMENT, expectedType); + public void checkSMT(Predicate expectedType, CtElement element) throws LJError { + checkSMT(expectedType, element, null); } - public void checkStateSMT(Predicate prevState, Predicate expectedState, CtElement target, String moreInfo) { - vcChecker.processSubtyping(prevState, expectedState, context.getGhostState(), target, moreInfo, factory); + public void checkSMT(Predicate expectedType, CtElement element, String customMessage) throws LJError { + vcChecker.processSubtyping(expectedType, context.getGhostState(), element, factory, customMessage); + element.putMetadata(Keys.REFINEMENT, expectedType); } - public boolean checksStateSMT(Predicate prevState, Predicate expectedState, SourcePosition p) { - return vcChecker.canProcessSubtyping(prevState, expectedState, context.getGhostState(), p, factory); + public void checkStateSMT(Predicate prevState, Predicate expectedState, CtElement target, String moreInfo) + throws LJError { + vcChecker.processSubtyping(prevState, expectedState, context.getGhostState(), target, factory); } - public void createError(CtElement element, Predicate expectedType, Predicate foundType, String customeMessage) { - vcChecker.printSubtypingError(element, expectedType, foundType, customeMessage); + public boolean checksStateSMT(Predicate prevState, Predicate expectedState, SourcePosition p) throws LJError { + return vcChecker.canProcessSubtyping(prevState, expectedState, context.getGhostState(), p, factory); } - public void createSameStateError(CtElement element, Predicate expectedType, String klass) { - vcChecker.printSameStateError(element, expectedType, klass); + public void throwRefinementError(SourcePosition position, Predicate expectedType, Predicate foundType, + String customMessage) throws LJError { + vcChecker.throwRefinementError(position, expectedType, foundType, customMessage); } - public void createStateMismatchError(CtElement element, String method, Predicate c, String states) { - vcChecker.printStateMismatchError(element, method, c, states); + public void throwStateRefinementError(SourcePosition position, Predicate found, Predicate expected, + String customMessage) throws LJError { + vcChecker.throwStateRefinementError(position, found, expected, customMessage); } - public ErrorEmitter getErrorEmitter() { - return errorEmitter; + 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/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 cba055bc..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 @@ -1,25 +1,20 @@ package liquidjava.processor.refinement_checker; 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.*; +import liquidjava.diagnostics.TranslationTable; import liquidjava.processor.VCImplication; import liquidjava.processor.context.*; import liquidjava.rj_language.Predicate; -import liquidjava.smt.GhostFunctionError; -import liquidjava.smt.NotFoundError; 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,79 +23,105 @@ 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) { + 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()) 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(); + Predicate premises; + Predicate expected; try { List filtered = filterGhostStatesForVariables(list, mainVars, lrv); - premises = premisesBeforeChange.changeStatesToRefinements(filtered, s, errorEmitter) - .changeAliasToRefinement(context, f); - - et = expectedType.changeStatesToRefinements(filtered, s, errorEmitter).changeAliasToRefinement(context, f); - } catch (Exception e) { - ErrorHandler.printError(element, e.getMessage(), expectedType, premises, map, errorEmitter); - return; - } - - try { - 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); + premises = premisesBeforeChange.changeStatesToRefinements(filtered, s).changeAliasToRefinement(context, f); + 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.simplify(), premisesBeforeChange.simplify(), + map, customMessage); } + /** + * 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, - String string, Factory f) { + Factory f) throws LJError { boolean b = canProcessSubtyping(type, expectedType, list, element.getPosition(), f); if (!b) - printSubtypingError(element, expectedType, type, string); + throwRefinementError(element.getPosition(), expectedType, type, null); + } + + /** + * 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, SourcePosition p, - Factory f) { + 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); if (expectedType.isBooleanTrue() && type.isBooleanTrue()) 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 = 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); - 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, errorEmitter) - .changeAliasToRefinement(context, f); - et = expectedType.changeStatesToRefinements(filtered, s, errorEmitter).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); + // check subtyping + return smtChecks(expected, premises, position); } /** @@ -113,10 +134,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 -> { @@ -145,7 +162,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; @@ -176,28 +193,15 @@ 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) { @@ -215,10 +219,6 @@ private void gatherVariables(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) { @@ -226,7 +226,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; } @@ -247,50 +246,6 @@ private void recAuxGetVars(RefinedVariable var, List newVars) { getVariablesFromContext(l, newVars, varName); } - public boolean smtChecks(Predicate cSMT, Predicate expectedType, SourcePosition p) { - try { - new SMTEvaluator().verifySubtype(cSMT, expectedType, context); - } 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); - } - 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 - * - * @throws Exception - * @throws GhostFunctionError - * @throws TypeCheckError - */ - private void smtChecking(Predicate cSMT, Predicate expectedType) - throws TypeCheckError, GhostFunctionError, 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); } @@ -300,73 +255,42 @@ 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 HashMap createMap(CtElement element, Predicate expectedType) { + protected void throwRefinementError(SourcePosition position, Predicate expected, Predicate found, + String customMessage) throws RefinementError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); - gatherVariables(expectedType, lrv, mainVars); - HashMap map = new HashMap<>(); - joinPredicates(expectedType, mainVars, lrv, map); - return map; + 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, customMessage); } - protected void printSubtypingError(CtElement element, Predicate expectedType, Predicate foundType, - String customeMsg) { + protected void throwStateRefinementError(SourcePosition position, Predicate found, Predicate expected, + String customMessage) throws StateRefinementError { List lrv = new ArrayList<>(), mainVars = new ArrayList<>(); - gatherVariables(expectedType, lrv, mainVars); - gatherVariables(foundType, lrv, mainVars); - HashMap map = new HashMap<>(); - Predicate premises = joinPredicates(expectedType, mainVars, lrv, map).toConjunctions(); - - ErrorHandler.printError(element, customeMsg, expectedType, premises, map, errorEmitter); + 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, customMessage); } - public void printSameStateError(CtElement element, Predicate expectedType, String klass) { - HashMap map = createMap(element, expectedType); - ErrorHandler.printSameStateSetError(element, expectedType, klass, map, errorEmitter); + protected void throwStateConflictError(SourcePosition position, Predicate expected) throws StateConflictError { + TranslationTable map = createMap(expected); + throw new StateConflictError(position, expected.getExpression(), 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:"; - } - - Predicate etMessageReady = expectedType; // substituteByMap(expectedType, map); - Predicate cSMTMessageReady = premisesBeforeChange; // substituteByMap(premisesBeforeChange, map); - if (e instanceof TypeCheckError) { - ErrorHandler.printError(element, s, etMessageReady, cSMTMessageReady, map, errorEmitter); - } 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); - } else { - ErrorHandler.printCustomError(element, e.getMessage(), errorEmitter); - // System.err.println("Unknown error:"+e.getMessage()); - // e.printStackTrace(); - // System.exit(7); - } - } - - public void printStateMismatchError(CtElement element, String method, Predicate c, String states) { + private TranslationTable createMap(Predicate expectedType) { 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(expectedType, lrv, mainVars); + TranslationTable map = new TranslationTable(); + joinPredicates(expectedType, mainVars, lrv, map); + return 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..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 @@ -1,24 +1,18 @@ 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.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.diagnostics.errors.LJError; +import liquidjava.processor.context.*; 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; import liquidjava.utils.Utils; import spoon.reflect.code.CtConstructorCall; import spoon.reflect.code.CtExpression; @@ -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,13 +32,13 @@ public class MethodsFunctionsChecker { - private TypeChecker rtc; + private final TypeChecker rtc; 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()); @@ -57,15 +50,14 @@ public void getConstructorRefinements(CtConstructor c) throws ParsingExceptio } 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); 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 +72,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()); @@ -91,8 +83,7 @@ public void getMethodRefinements(CtMethod method) throws ParsingException 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(); @@ -107,10 +98,10 @@ 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 { + public void getMethodRefinements(CtMethod method, String prefix) throws LJError { String constructorName = ""; String k = Utils.getSimpleName(prefix); @@ -134,7 +125,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; @@ -147,17 +138,14 @@ 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 - * - * @throws ParsingException */ private Predicate handleFunctionRefinements(RefinedFunction f, CtElement method, List> params) - throws ParsingException { + throws LJError { Predicate joint = new Predicate(); - for (CtParameter param : params) { String paramName = param.getSimpleName(); Optional oc = rtc.getRefinementFromAnnotation(param); @@ -166,31 +154,20 @@ 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); } - Optional oret = rtc.getRefinementFromAnnotation(method); Predicate ret = oret.orElse(new Predicate()); ret = ret.substituteVariable("return", Keys.WILDCARD); f.setRefReturn(ret); - // rtc.context.addFunctionToContext(f); + rtc.getMessageFromAnnotation(method).ifPresent(f::setMessage); 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) { + public void getReturnRefinements(CtReturn ret) throws LJError { CtClass c = ret.getParent(CtClass.class); String className = c.getSimpleName(); if (ret.getReturnedExpression() != null) { @@ -221,7 +198,7 @@ public void getReturnRefinements(CtReturn ret) { .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); } } @@ -230,7 +207,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) { @@ -246,16 +223,6 @@ public void getInvocationRefinements(CtInvocation invocation) { 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); - } } } @@ -267,7 +234,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 +273,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); @@ -329,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)) @@ -346,7 +313,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); @@ -355,24 +322,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); @@ -392,11 +354,10 @@ private String createVariableRepresentingArgument(CtExpression iArg, Variable return nVar; } - private void checkParameters(CtElement invocation, List> arguments, RefinedFunction f, - Map map) { - List> invocationParams = arguments; + private void checkParameters(CtElement invocation, List> arguments, RefinedFunction f, + Map map) throws LJError { 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())); @@ -408,7 +369,7 @@ private void checkParameters(CtElement invocation, List> arg VariableInstance vi = (VariableInstance) invocation.getMetadata(Keys.TARGET); c = c.substituteVariable(Keys.THIS, vi.getName()); } - rtc.checkSMT(c, invocation); + rtc.checkSMT(c, invocation, fArg.getMessage()); } } @@ -417,16 +378,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()); @@ -435,13 +394,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 a4adf7c7..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 @@ -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; @@ -45,7 +47,7 @@ */ public class OperationsChecker { - private TypeChecker rtc; + private final TypeChecker rtc; public OperationsChecker(TypeChecker rtc) { this.rtc = rtc; @@ -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; @@ -69,17 +69,13 @@ public void getBinaryOpRefinements(CtBinaryOperator operator) throws Pars 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); @@ -103,23 +99,19 @@ 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; - 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 { @@ -139,20 +131,16 @@ public void getUnaryOpRefinements(CtUnaryOperator operator) throws Parsin } 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); } @@ -165,11 +153,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,13 +169,10 @@ 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 { - if (element instanceof CtFieldRead) { - CtFieldRead field = ((CtFieldRead) element); + CtExpression element) throws LJError { + 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(), @@ -199,81 +181,72 @@ 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 - return new Predicate(String.format("(%s)", s), element, rtc.getErrorEmitter()); + // 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(); } 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, rtc.getErrorEmitter()); + 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, 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 } - private Predicate getOperationRefinementFromExternalLib(CtInvocation inv, CtBinaryOperator operator) - throws ParsingException { + private Predicate getOperationRefinementFromExternalLib(CtInvocation inv) throws LJError { CtExpression t = inv.getTarget(); if (t instanceof CtVariableRead) { @@ -287,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) { @@ -301,13 +275,13 @@ 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(); } /** - * Retrieves the refinements for the a variable write inside unary operation + * Retrieves the refinements for the variable write inside unary operation * * @param * @param ex @@ -316,11 +290,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(); @@ -342,7 +314,7 @@ private Predicate getRefinementUnaryVariableWrite(CtExpression ex, CtUnar * * @param kind * - * @return + * @return operator string */ private String getOperatorFromKind(BinaryOperatorKind kind) { return switch (kind) { @@ -363,18 +335,15 @@ 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"; - 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 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/AuxHierarchyRefinementsPassage.java similarity index 75% 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 04c0c14f..455e8276 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 @@ -3,7 +3,8 @@ 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; import liquidjava.processor.context.RefinedFunction; import liquidjava.processor.context.RefinedVariable; @@ -16,19 +17,18 @@ 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 AuxHierarchyRefinememtsPassage { +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 + 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(); @@ -40,7 +40,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 +66,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(); @@ -81,18 +81,16 @@ 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) { - // ErrorPrinter.printError(method, argRef, superArgRef); - if (!tc.getErrorEmitter().foundError()) - tc.createError(method, argRef, superArgRef, ""); + boolean ok = tc.checksStateSMT(superArgRef, argRef, params.get(i).getPosition()); + if (!ok) { + tc.throwRefinementError(method.getPosition(), argRef, superArgRef, function.getMessage()); } } } } 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()) @@ -100,8 +98,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,18 +108,14 @@ 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"); } } 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 @@ -130,7 +124,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()) @@ -145,25 +139,19 @@ 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"); - // 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()); + 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"); - // boolean correct = tc.checkStateSMT(subConst, superConst, method); - // if(!correct) ErrorPrinter.printError(method, subState.getTo(), - // superState.getTo()); + } } } @@ -174,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++) { @@ -201,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 1d52caa9..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 @@ -4,12 +4,13 @@ import java.util.*; import java.util.stream.Collectors; -import liquidjava.diagnostics.ErrorHandler; +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; @@ -28,23 +29,19 @@ 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) { - ErrorHandler.printErrorConstructorFromState(c, from, tc.getErrorEmitter()); - return; + throw new IllegalConstructorTransitionError(from); } } - setConstructorStates(f, an, tc, c); // f.setState(an, context.getGhosts(), c); + setConstructorStates(f, an, c); } else { setDefaultState(f, tc); } @@ -55,25 +52,25 @@ public static void handleConstructorState(CtConstructor c, RefinedFunction f, * * @param 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 { + CtElement element) throws LJError { List l = new ArrayList<>(); for (CtAnnotation 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, tc.getErrorEmitter()); + Predicate p = new Predicate(to, element); if (!p.getExpression().isBooleanExpression()) { - ErrorHandler.printCustomError(element, "State refinement transition must be a boolean expression", - tc.getErrorEmitter()); - return; + throw new InvalidRefinementError(element.getPosition(), + "State refinement transition must be a boolean expression", to); } state.setTo(p); } @@ -82,6 +79,12 @@ private static void setConstructorStates(RefinedFunction f, 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; @@ -126,17 +135,13 @@ 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); } - // f.setState(an, context.getGhosts(), method); - } /** @@ -146,11 +151,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)); @@ -160,72 +163,99 @@ 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")); + String msg = TypeCheckingUtils.getStringFromAnnotation(m.get("msg")); ObjectState state = new ObjectState(); - if (from != null) { // has From + if (msg != null && !msg.isEmpty()) + state.setMessage(msg); + + // 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); + /** + * 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, String targetClass, TypeChecker tc, CtElement e, + boolean isTo, String prefix) throws LJError { + Predicate p = new Predicate(value, e, prefix); if (!p.getExpression().isBooleanExpression()) { - ErrorHandler.printCustomError(e, "State refinement transition must be a boolean expression", - tc.getErrorEmitter()); - return new Predicate(); + throw new InvalidRefinementError(e.getPosition(), + "State refinement transition must be a boolean expression", value); } - 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, tc.getErrorEmitter()); - boolean b = tc.checksStateSMT(new Predicate(), c.negate(), e.getPosition()); - if (b && !tc.getErrorEmitter().foundError()) { - tc.createSameStateError(e, p, t); + c = c.changeOldMentions(nameOld, name); + boolean ok = tc.checksStateSMT(new Predicate(), c.negate(), e.getPosition()); + if (ok) { + tc.throwStateConflictError(e.getPosition(), p); } - 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); 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); } /** - * 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 @@ -255,15 +285,14 @@ 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) { + 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)) @@ -287,17 +316,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 } } @@ -325,16 +350,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) { + 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()) { @@ -343,7 +367,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(); @@ -361,47 +391,30 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) { } 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(); - 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) { - ErrorHandler - .printCustomError(fw, - "ParsingException while constructing assignment update for `" + fw + "` in class `" - + fw.getVariable().getDeclaringType() + "` : " + e.getMessage(), - tc.getErrorEmitter()); - - 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) - .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(); - tc.createStateMismatchError(fw, fw.toString(), prevState, states); - } + tc.throwStateRefinementError(fw.getPosition(), prevState, stateChange.getFrom(), stateChange.getMessage()); return; } @@ -409,7 +422,7 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) { 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(), @@ -438,24 +451,17 @@ public static void updateGhostField(CtFieldWrite fw, TypeChecker tc) { * @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) { + 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; @@ -470,7 +476,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()) { @@ -480,27 +486,27 @@ 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); - // update of stata of new instance of this#n#(whatever it was + 1) + transitionedState = checkOldMentions(transitionedState, instanceName, newInstanceName); + // update of state of new instance of this#n#(whatever it was + 1) addInstanceWithState(tc, name, newInstanceName, vi, transitionedState, invocation); - return transitionedState; + return; } } - 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(",")); - - String simpleInvocation = invocation.toString(); // .getExecutable().toString(); - tc.createStateMismatchError(invocation, simpleInvocation, prevState, states); - // ErrorPrinter.printStateMismatch(invocation, simpleInvocation, prevState, - // states); + if (!found) { // Reaches the end of stateChange no matching states + Predicate expectedStatesDisjunction = stateChanges.stream().filter(ObjectState::hasFrom) + .map(ObjectState::getFrom) + .reduce(Predicate.createLit("false", Types.BOOLEAN), Predicate::createDisjunction); + + // combine messages of all state changes + String message = stateChanges.stream().map(ObjectState::getMessage) + .filter(msg -> msg != null && !msg.isBlank()).distinct().collect(Collectors.joining("\n")); + tc.throwStateRefinementError(invocation.getPosition(), prevState, expectedStatesDisjunction, message); } - return new Predicate(); } - private static Predicate checkOldMentions(Predicate transitionedState, String instanceName, String newInstanceName, - TypeChecker tc) { - return transitionedState.changeOldMentions(instanceName, newInstanceName, tc.getErrorEmitter()); + private static Predicate checkOldMentions(Predicate transitionedState, String instanceName, + String newInstanceName) { + return transitionedState.changeOldMentions(instanceName, newInstanceName); } /** @@ -510,20 +516,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) { + 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(); } /** @@ -535,14 +537,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, - VariableInstance prevInstance, Predicate transitionedState, CtElement invocation) { + 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)) { @@ -560,9 +559,7 @@ private static String addInstanceWithState(TypeChecker tc, String superName, Str tc.getContext().addRefinementInstanceToVariable(superName, name2); } } - invocation.putMetadata(Keys.TARGET, vi2); - return name2; } /** @@ -570,17 +567,16 @@ 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) { + 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()); @@ -592,17 +588,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 0b57ac50..36dfc6aa 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,20 @@ package liquidjava.rj_language; -import liquidjava.diagnostics.ErrorEmitter; -import liquidjava.rj_language.parsing.ParsingException; +import liquidjava.diagnostics.errors.LJError; 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 LJError { + 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 LJError { + 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 index, String value, CtElement elem) throws LJError { + 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..eecfaa10 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java +++ b/liquidjava-verifier/src/main/java/liquidjava/rj_language/Predicate.java @@ -6,10 +6,12 @@ import java.util.Map; import java.util.stream.Collectors; -import liquidjava.diagnostics.ErrorEmitter; -import liquidjava.diagnostics.ErrorHandler; +import liquidjava.diagnostics.errors.ArgumentMismatchError; +import liquidjava.diagnostics.errors.LJError; +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; @@ -19,17 +21,18 @@ 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; 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; 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; @@ -54,12 +57,9 @@ public Predicate() { * * @param ref * @param element - * @param e - * - * @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 LJError { + this(ref, element, element.getParent(CtType.class).getQualifiedName()); } /** @@ -67,17 +67,11 @@ public Predicate(String ref, CtElement element, ErrorEmitter e) throws ParsingEx * * @param ref * @param element - * @param e * @param prefix - * - * @throws ParsingException */ - public Predicate(String ref, CtElement element, ErrorEmitter e, String prefix) throws ParsingException { + public Predicate(String ref, CtElement element, String prefix) throws LJError { this.prefix = prefix; - exp = parse(ref, element, e); - if (e.foundError()) { - return; - } + exp = parse(ref, element); if (!(exp instanceof GroupExpression)) { exp = new GroupExpression(exp); } @@ -88,25 +82,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 LJError { try { return RefinementsParser.createAST(ref, prefix); - } catch (ParsingException e1) { - ErrorHandler.printSyntaxError(e1.getMessage(), ref, element, e); - throw e1; + } catch (LJError e) { + // add location info to error + SourcePosition pos = Utils.getAnnotationPosition(element, ref); + e.setPosition(pos); + throw e; } } - protected Expression innerParse(String ref, ErrorEmitter e, String prefix) { - try { - return RefinementsParser.createAST(ref, prefix); - } catch (ParsingException e1) { - ErrorHandler.printSyntaxError(e1.getMessage(), ref, e); - } - 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 { + public Predicate changeAliasToRefinement(Context context, Factory f) throws LJError { Expression ref = getExpression(); Map alias = new HashMap<>(); @@ -115,6 +106,8 @@ public Predicate changeAliasToRefinement(Context context, Factory f) throws Exce } ref = ref.changeAlias(alias, context, f); + ref.validateGhostInvocations(context, f); + return new Predicate(ref); } @@ -137,7 +130,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); @@ -152,7 +145,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<>(); @@ -161,35 +154,12 @@ public Predicate changeOldMentions(String previousName, String newName, ErrorEmi 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, ErrorEmitter ee) { + 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 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); @@ -222,11 +192,33 @@ public ValDerivationNode simplify() { return ExpressionSimplifier.simplify(exp.clone()); } + private static boolean isBooleanLiteral(Expression expr, boolean value) { + return expr instanceof 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())); } @@ -242,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/AliasInvocation.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/AliasInvocation.java index afe7b518..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 @@ -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,13 +25,13 @@ public List getArgs() { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitAliasInvocation(this); } @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 @@ -82,10 +83,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 5e07cd03..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 @@ -2,11 +2,12 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class BinaryExpression extends Expression { - private String op; + private final String op; public BinaryExpression(Expression e1, String op, Expression e2) { this.op = op; @@ -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); } @@ -104,10 +105,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 37e3343e..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 @@ -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); @@ -40,7 +47,7 @@ public List getChildren() { } public boolean hasChildren() { - return children.size() > 0; + return !children.isEmpty(); } public void setChild(int index, Expression element) { @@ -48,12 +55,13 @@ 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; } /** * 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() { @@ -103,15 +111,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); @@ -123,14 +131,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); @@ -147,14 +153,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); @@ -168,24 +172,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) { - AliasInvocation ai = (AliasInvocation) this; + 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); @@ -195,14 +208,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) { - 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"); + 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++) { @@ -210,13 +229,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); @@ -224,4 +245,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 f2c4984f..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 @@ -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,13 +31,14 @@ 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); } @Override public String toString() { - return name + "(" + getArgs().stream().map(p -> p.toString()).collect(Collectors.joining(",")) + ")"; + return Utils.getSimpleName(name) + "(" + + getArgs().stream().map(Expression::toString).collect(Collectors.joining(",")) + ")"; } @Override @@ -110,10 +112,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 a2be4ea2..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 @@ -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,12 +16,12 @@ public Expression getExpression() { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitGroupExpression(this); } public String toString() { - return "(" + getExpression().toString() + ")"; + return getExpression().toString(); } @Override @@ -61,10 +62,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 44f90965..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 @@ -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,13 +26,13 @@ public Expression getElse() { } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitIte(this); } @Override public String toString() { - return getCondition().toString() + "?" + getThen().toString() + ":" + getElse().toString(); + return getCondition().toString() + " ? " + getThen().toString() + " : " + getElse().toString(); } @Override @@ -88,10 +89,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..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); } @@ -33,7 +34,6 @@ public void getVariableNames(List toAdd) { @Override public void getStateInvocations(List toAdd, List all) { // end leaf - } @Override @@ -63,8 +63,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..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,11 +2,12 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class LiteralInt extends Expression { - private int value; + private final int value; public LiteralInt(int v) { value = v; @@ -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); } @@ -32,13 +33,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 +67,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/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/LiteralReal.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/ast/LiteralReal.java index c80bf750..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,11 +2,12 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class LiteralReal extends Expression { - private double value; + private final double value; public LiteralReal(double v) { value = v; @@ -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); } @@ -32,13 +33,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 +54,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 +67,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..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,17 +2,19 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class LiteralString extends Expression { - private String value; + + private final String value; public LiteralString(String v) { value = v; } @Override - public T accept(ExpressionVisitor visitor) throws Exception { + public T accept(ExpressionVisitor visitor) throws LJError { return visitor.visitLiteralString(this); } @@ -58,10 +60,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 9bfdfec5..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 @@ -2,11 +2,12 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class UnaryExpression extends Expression { - private String op; + private final String op; public UnaryExpression(String op, Expression e) { this.op = op; @@ -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); } @@ -75,10 +76,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..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,11 +2,12 @@ import java.util.List; +import liquidjava.diagnostics.errors.LJError; import liquidjava.rj_language.visitors.ExpressionVisitor; public class Var extends Expression { - private String name; + private final String name; public Var(String name) { this.name = name; @@ -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); } @@ -64,10 +65,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..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; @@ -22,12 +23,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); @@ -39,12 +34,14 @@ 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) 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 +51,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 +80,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 +88,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..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,58 +20,71 @@ 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 var = (Var) exp; + 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); } // lift unary origin - if (exp instanceof UnaryExpression) { - UnaryExpression unary = (UnaryExpression) exp; - ValDerivationNode operand = propagateRecursive(unary.getChildren().get(0), subs); - unary.setChild(0, operand.getValue()); - - DerivationNode origin = operand.getOrigin() != null ? new UnaryDerivationNode(operand, unary.getOp()) - : null; - return new ValDerivationNode(unary, origin); + if (exp instanceof UnaryExpression unary) { + ValDerivationNode operand = propagateRecursive(unary.getChildren().get(0), subs, varOrigins); + UnaryExpression cloned = (UnaryExpression) unary.clone(); + cloned.setChild(0, operand.getValue()); + + return operand.getOrigin() != null + ? new ValDerivationNode(cloned, new UnaryDerivationNode(operand, cloned.getOp())) + : new ValDerivationNode(cloned, null); } // lift binary origin - if (exp instanceof BinaryExpression) { - BinaryExpression binary = (BinaryExpression) exp; - ValDerivationNode left = propagateRecursive(binary.getFirstOperand(), subs); - ValDerivationNode right = propagateRecursive(binary.getSecondOperand(), subs); - binary.setChild(0, left.getValue()); - binary.setChild(1, right.getValue()); - - DerivationNode origin = (left.getOrigin() != null || right.getOrigin() != null) - ? new BinaryDerivationNode(left, right, binary.getOperator()) : null; - return new ValDerivationNode(binary, origin); + if (exp instanceof BinaryExpression binary) { + ValDerivationNode left = propagateRecursive(binary.getFirstOperand(), subs, varOrigins); + ValDerivationNode right = propagateRecursive(binary.getSecondOperand(), subs, varOrigins); + BinaryExpression cloned = (BinaryExpression) binary.clone(); + cloned.setChild(0, left.getValue()); + cloned.setChild(1, right.getValue()); + + return (left.getOrigin() != null || right.getOrigin() != null) + ? new ValDerivationNode(cloned, new BinaryDerivationNode(left, right, cloned.getOperator())) + : new ValDerivationNode(cloned, null); } // recursively propagate children if (exp.hasChildren()) { Expression propagated = exp.clone(); for (int i = 0; i < exp.getChildren().size(); i++) { - ValDerivationNode child = propagateRecursive(exp.getChildren().get(i), subs); + ValDerivationNode child = propagateRecursive(exp.getChildren().get(i), subs, varOrigins); propagated.setChild(i, child.getValue()); } return new ValDerivationNode(propagated, null); @@ -79,4 +93,49 @@ 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 2a022b81..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,55 +14,98 @@ 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) { - BinaryExpression binExp = (BinaryExpression) value; - if ("&&".equals(binExp.getOperator()) && origin instanceof BinaryDerivationNode) { - BinaryDerivationNode binOrigin = (BinaryDerivationNode) origin; - - // 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) */ private static boolean isRedundant(Expression exp) { // true - if (exp instanceof LiteralBoolean && ((LiteralBoolean) exp).isBooleanTrue()) { + if (exp instanceof LiteralBoolean && exp.isBooleanTrue()) { return true; } // x == x - if (exp instanceof BinaryExpression) { - BinaryExpression binExp = (BinaryExpression) exp; + if (exp instanceof BinaryExpression binExp) { if ("==".equals(binExp.getOperator())) { Expression left = binExp.getFirstOperand(); Expression right = binExp.getSecondOperand(); 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..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,23 +12,35 @@ 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) { 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)) + if (!(exp instanceof BinaryExpression be)) return; - BinaryExpression be = (BinaryExpression) exp; String op = be.getOperator(); if ("&&".equals(op)) { resolveRecursive(be.getFirstOperand(), map); @@ -36,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<>(); @@ -58,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)) @@ -74,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/ValDerivationNode.java b/liquidjava-verifier/src/main/java/liquidjava/rj_language/opt/derivation_node/ValDerivationNode.java index eeb6f21d..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,10 +43,12 @@ 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) - 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/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/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 d4d43439..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/rj_language/parsing/ParsingException.java +++ /dev/null @@ -1,11 +0,0 @@ -package liquidjava.rj_language.parsing; - -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/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/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..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 @@ -4,10 +4,8 @@ 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; import org.antlr.v4.runtime.misc.Interval; import org.antlr.v4.runtime.tree.ParseTree; import rj.grammar.RJParser.AliasContext; @@ -15,7 +13,6 @@ import rj.grammar.RJParser.PredContext; public class AliasVisitor { - TokenStreamRewriter rewriter; CodePointCharStream input; public AliasVisitor(CodePointCharStream input) { @@ -28,18 +25,15 @@ public AliasVisitor(CodePointCharStream input) { * @param rc * * @return - * - * @throws ParsingException */ 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) { @@ -66,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 102c9e58..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 @@ -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; @@ -10,16 +13,19 @@ 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; 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; @@ -47,8 +53,8 @@ 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 liquidjava.diagnostics.errors.ArgumentMismatchError; /** * Create refinements language AST using antlr @@ -63,7 +69,7 @@ public CreateASTVisitor(String prefix) { this.prefix = prefix; } - public Expression create(ParseTree rc) { + public Expression create(ParseTree rc) throws LJError { if (rc instanceof ProgContext) return progCreate((ProgContext) rc); else if (rc instanceof StartContext) @@ -76,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) @@ -84,20 +92,20 @@ else if (rc instanceof LiteralContext) return null; } - private Expression progCreate(ProgContext rc) { + private Expression progCreate(ProgContext rc) throws LJError { if (rc.start() != null) return create(rc.start()); return null; } - private Expression startCreate(ParseTree rc) { + 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) { + private Expression predCreate(ParseTree rc) throws LJError { if (rc instanceof PredGroupContext) return new GroupExpression(create(((PredGroupContext) rc).pred())); else if (rc instanceof PredNegateContext) @@ -112,7 +120,7 @@ else if (rc instanceof IteContext) return create(((PredExpContext) rc).exp()); } - private Expression expCreate(ParseTree rc) { + private Expression expCreate(ParseTree rc) throws LJError { if (rc instanceof ExpGroupContext) return new GroupExpression(create(((ExpGroupContext) rc).exp())); else if (rc instanceof ExpBoolContext) { @@ -124,7 +132,7 @@ else if (rc instanceof ExpBoolContext) { } } - private Expression operandCreate(ParseTree rc) { + private Expression operandCreate(ParseTree rc) throws LJError { if (rc instanceof OpLiteralContext) return create(((OpLiteralContext) rc).literalExpression()); else if (rc instanceof OpArithContext) @@ -143,35 +151,81 @@ else if (rc instanceof OpGroupContext) return null; } - private Expression literalExpressionCreate(ParseTree rc) { + private Expression literalExpressionCreate(ParseTree rc) throws LJError { if (rc instanceof LitGroupContext) return new GroupExpression(create(((LitGroupContext) rc).literalExpression())); 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()); } } - private Expression functionCallCreate(FunctionCallContext rc) { + private Expression functionCallCreate(FunctionCallContext rc) throws LJError { 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); - } else { + String ref = gc.ID().getText(); + String name = Utils.qualifyName(prefix, ref); + List args = getArgs(gc.args()); + if (args.isEmpty()) + args.add(new Var(Keys.THIS)); // implicit this: size() => this.size() + + return new FunctionInvocation(name, args); + } else if (rc.aliasCall() != null) { AliasCallContext gc = rc.aliasCall(); - List le = getArgs(gc.args()); - return new AliasInvocation(gc.ID_UPPER().getText(), le); + 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()); } } - private List getArgs(ArgsContext args) { + /** + * 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); + } + } + + private List getArgs(ArgsContext args) throws LJError { List le = new ArrayList<>(); if (args != null) for (PredContext oc : args.pred()) { @@ -180,15 +234,21 @@ private List getArgs(ArgsContext args) { return le; } - private Expression literalCreate(LiteralContext literalContext) { + private Expression literalCreate(LiteralContext literalContext) throws LJError { if (literalContext.BOOL() != null) 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("Error got to unexistant literal."); + 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 51ab5357..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 @@ -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; @@ -7,31 +8,34 @@ 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; 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 visitLiteralLong(LiteralLong lit) throws LJError; - T visitLiteralReal(LiteralReal lit) throws Exception; + T visitLiteralBoolean(LiteralBoolean lit) throws LJError; - T visitLiteralString(LiteralString lit) throws Exception; + T visitLiteralReal(LiteralReal lit) throws LJError; - T visitUnaryExpression(UnaryExpression exp) throws Exception; + T visitLiteralString(LiteralString lit) throws LJError; - T visitVar(Var var) throws Exception; + T visitUnaryExpression(UnaryExpression exp) throws LJError; + + T visitVar(Var var) throws LJError; } \ No newline at end of file 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..35e74803 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; @@ -9,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; @@ -24,7 +26,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 +35,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 +58,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,42 +67,47 @@ 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()); } @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 visitLiteralLong(LiteralLong lit) { + return ctx.makeLongLiteral(lit.getValue()); + } + + @Override + 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()); } @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/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/NotFoundError.java b/liquidjava-verifier/src/main/java/liquidjava/smt/NotFoundError.java deleted file mode 100644 index 70a80827..00000000 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/NotFoundError.java +++ /dev/null @@ -1,22 +0,0 @@ -package liquidjava.smt; - -import spoon.reflect.declaration.CtElement; - -public class NotFoundError extends Exception { - private CtElement location; - - public NotFoundError(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 68dc4862..bf00f999 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/SMTEvaluator.java @@ -4,20 +4,17 @@ import com.microsoft.z3.Expr; import com.microsoft.z3.Status; import com.microsoft.z3.Z3Exception; + import liquidjava.processor.context.Context; import liquidjava.rj_language.Predicate; import liquidjava.rj_language.ast.Expression; public class SMTEvaluator { - public void verifySubtype(Predicate subRef, Predicate supRef, Context c) - throws TypeCheckError, GhostFunctionError, 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; @@ -25,23 +22,15 @@ public void verifySubtype(Predicate subRef, Predicate supRef, Context c) 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(); } 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()); + throw new Z3Exception(e.getLocalizedMessage()); } } } diff --git a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorContextToZ3.java b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorContextToZ3.java index 36b167cc..08749a07 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorContextToZ3.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorContextToZ3.java @@ -42,16 +42,16 @@ 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)); }; } - 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"))); @@ -82,15 +81,14 @@ 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"); - // 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 ed0db04e..b207552f 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/TranslatorToZ3.java @@ -16,40 +16,36 @@ 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.utils.Utils; +import liquidjava.utils.constants.Keys; import org.apache.commons.lang3.NotImplementedException; 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##################### @@ -73,26 +69,26 @@ 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("Variable '" + name.toString() + "' not found"); + 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(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); @@ -108,11 +104,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); } @@ -121,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()) { @@ -144,18 +136,18 @@ private FuncDecl resolveFunctionDeclFallback(String name, Expr[] params) t if (candidate != null) { return candidate; } - throw new NotFoundError("Function '" + name + "' not found"); + throw new NotFoundError(name, Keys.GHOST); } @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; @@ -165,7 +157,8 @@ private Expr makeStore(String name, 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); } @@ -173,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); } @@ -181,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); } @@ -189,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); } @@ -197,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); } @@ -221,11 +218,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) { @@ -239,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); } @@ -247,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); } @@ -255,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); } @@ -263,24 +258,42 @@ 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) { 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/smt/TypeCheckError.java b/liquidjava-verifier/src/main/java/liquidjava/smt/TypeCheckError.java index d2f59fff..04c6c07d 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/smt/TypeCheckError.java +++ b/liquidjava-verifier/src/main/java/liquidjava/smt/TypeCheckError.java @@ -1,23 +1,8 @@ package liquidjava.smt; -import spoon.reflect.declaration.CtElement; - public class TypeCheckError extends Exception { - private CtElement location; - public TypeCheckError(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/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/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 089c28d7..1e99e3f0 100644 --- a/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java +++ b/liquidjava-verifier/src/main/java/liquidjava/utils/Utils.java @@ -1,8 +1,13 @@ 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; @@ -28,12 +33,23 @@ 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); } - public static String stripParens(String s) { - return s.startsWith("(") && s.endsWith(")") ? s.substring(1, s.length() - 1) : s; + 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)))); } } 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..93311c01 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,7 @@ 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"; + public static final String ALIAS = "Alias"; } \ No newline at end of file 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]+$"); -} 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..0f3552e8 100644 --- a/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java +++ b/liquidjava-verifier/src/test/java/liquidjava/api/tests/TestExamples.java @@ -1,70 +1,103 @@ 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.ErrorEmitter; +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; 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. * - * @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 - ErrorEmitter errorEmitter = CommandLineLauncher.launch(filePath.toAbsolutePath().toString()); + // run verification + CommandLineLauncher.launch(path.toAbsolutePath().toString()); - // 2. Check if the file is correct or contains an error - if ((fileName.startsWith("Correct") && errorEmitter.foundError()) - || (fileName.contains("correct") && errorEmitter.foundError())) { - 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 ((fileName.startsWith("Error") && !errorEmitter.foundError()) - || (fileName.contains("error") && !errorEmitter.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 (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(); + 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(); + } + } } } /** * 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 * 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() - && (name.startsWith("Correct") || name.startsWith("Error")); + && (shouldPass(name) || shouldFail(name)); - // 2. Folders (directories) that contain "correct" or "error" + // Directories that contain "correct" or "error" boolean isDirectoryWithCorrectOrError = fileAttr.isDirectory() - && (name.contains("correct") || name.contains("error")); + && (shouldPass(name) || shouldFail(name)); // Return true if either condition matches return isFileStartingWithCorrectOrError || isDirectoryWithCorrectOrError; @@ -77,13 +110,12 @@ 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(); } } 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 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..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")); @@ -304,6 +304,252 @@ void testComplexArithmeticWithMultipleOperations() { assertDerivationEquals(expected, result, ""); } + @Test + 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") + + 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 + 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 */ @@ -311,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 new file mode 100644 index 00000000..6f799aa2 --- /dev/null +++ b/liquidjava-verifier/src/test/java/liquidjava/rj_language/opt/VariableResolverTest.java @@ -0,0 +1,145 @@ +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"); + } + + @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()); + } +} 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..cd15f869 --- /dev/null +++ b/liquidjava-verifier/src/test/java/liquidjava/utils/TestUtils.java @@ -0,0 +1,67 @@ +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(); + } +}