-
Notifications
You must be signed in to change notification settings - Fork 35
Add Counterexamples to Refinement Errors #146
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 7 commits
089dc0a
dd71f59
b3f1112
76a7c99
2270950
ea87100
5e003b7
cb9a1b5
18ccf11
ae2d762
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| package liquidjava.diagnostics; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public record Counterexample(List<String> assignments) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,6 +8,7 @@ | |
| import java.util.stream.Collectors; | ||
|
|
||
| import liquidjava.diagnostics.errors.*; | ||
| import liquidjava.diagnostics.Counterexample; | ||
| import liquidjava.diagnostics.TranslationTable; | ||
| import liquidjava.processor.VCImplication; | ||
| import liquidjava.processor.context.*; | ||
|
|
@@ -55,10 +56,10 @@ public void processSubtyping(Predicate expectedType, List<GhostState> list, CtEl | |
| e.setPosition(element.getPosition()); | ||
| throw e; | ||
| } | ||
| boolean isSubtype = smtChecks(expected, premises, element.getPosition()); | ||
| if (!isSubtype) | ||
| Counterexample counterexample = smtChecks(expected, premises, element.getPosition()); | ||
| if (counterexample != null) | ||
| throw new RefinementError(element.getPosition(), expectedType.simplify(), premisesBeforeChange.simplify(), | ||
| map, customMessage); | ||
| map, counterexample, customMessage); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -74,9 +75,9 @@ public void processSubtyping(Predicate expectedType, List<GhostState> list, CtEl | |
| */ | ||
| public void processSubtyping(Predicate type, Predicate expectedType, List<GhostState> list, CtElement element, | ||
| Factory f) throws LJError { | ||
| boolean b = canProcessSubtyping(type, expectedType, list, element.getPosition(), f); | ||
| if (!b) | ||
| throwRefinementError(element.getPosition(), expectedType, type, null); | ||
| Counterexample counterexample = canProcessSubtyping(type, expectedType, list, element.getPosition(), f); | ||
| if (counterexample != null) | ||
| throwRefinementError(element.getPosition(), expectedType, type, counterexample, null); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -86,16 +87,16 @@ public void processSubtyping(Predicate type, Predicate expectedType, List<GhostS | |
| * @param found | ||
| * @param position | ||
| * | ||
| * @return true if expected type is subtype of found type, false otherwise | ||
| * @return counterexample if expected type is not subtype of found type, otherwise null | ||
| * | ||
| * @throws LJError | ||
| */ | ||
| public boolean smtChecks(Predicate expected, Predicate found, SourcePosition position) throws LJError { | ||
| public Counterexample smtChecks(Predicate expected, Predicate found, SourcePosition position) throws LJError { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't love that we just send the Counterexample and if it fails is null :( Maybe we can create an object for
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually it only fails if the counterexample is not null (no counterexample found).
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But yeah, we should avoid using nulls. I'll try to improve that using your suggestion. |
||
| try { | ||
| new SMTEvaluator().verifySubtype(found, expected, context); | ||
| return true; | ||
| return null; | ||
| } catch (TypeCheckError e) { | ||
| return false; | ||
| return e.getCounterexample(); | ||
| } catch (LJError e) { | ||
| e.setPosition(position); | ||
| throw e; | ||
|
|
@@ -104,13 +105,13 @@ public boolean smtChecks(Predicate expected, Predicate found, SourcePosition pos | |
| } | ||
| } | ||
|
|
||
| public boolean canProcessSubtyping(Predicate type, Predicate expectedType, List<GhostState> list, | ||
| public Counterexample canProcessSubtyping(Predicate type, Predicate expectedType, List<GhostState> list, | ||
| SourcePosition position, Factory f) throws LJError { | ||
| List<RefinedVariable> lrv = new ArrayList<>(), mainVars = new ArrayList<>(); | ||
| gatherVariables(expectedType, lrv, mainVars); | ||
| gatherVariables(type, lrv, mainVars); | ||
| if (expectedType.isBooleanTrue() && type.isBooleanTrue()) | ||
| return true; | ||
| return null; | ||
|
|
||
| TranslationTable map = new TranslationTable(); | ||
| String[] s = { Keys.WILDCARD, Keys.THIS }; | ||
|
|
@@ -262,13 +263,14 @@ void removePathVariableThatIncludes(String otherVar) { | |
| // Errors--------------------------------------------------------------------------------------------------- | ||
|
|
||
| protected void throwRefinementError(SourcePosition position, Predicate expected, Predicate found, | ||
| String customMessage) throws RefinementError { | ||
| Counterexample counterexample, String customMessage) throws RefinementError { | ||
| List<RefinedVariable> lrv = new ArrayList<>(), mainVars = new ArrayList<>(); | ||
| gatherVariables(expected, lrv, mainVars); | ||
| gatherVariables(found, lrv, mainVars); | ||
| TranslationTable map = new TranslationTable(); | ||
| Predicate premises = joinPredicates(expected, mainVars, lrv, map).toConjunctions(); | ||
| throw new RefinementError(position, expected.simplify(), premises.simplify(), map, customMessage); | ||
| throw new RefinementError(position, expected.simplify(), premises.simplify(), map, counterexample, | ||
| customMessage); | ||
| } | ||
|
|
||
| protected void throwStateRefinementError(SourcePosition position, Predicate found, Predicate expected, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,27 +1,34 @@ | ||
| package liquidjava.smt; | ||
|
|
||
| import com.martiansoftware.jsap.SyntaxException; | ||
| import com.microsoft.z3.ArithExpr; | ||
| import com.microsoft.z3.ArrayExpr; | ||
| import com.microsoft.z3.BoolExpr; | ||
| import com.microsoft.z3.Expr; | ||
| import com.microsoft.z3.FPExpr; | ||
| import com.microsoft.z3.FuncDecl; | ||
| import com.microsoft.z3.FuncInterp; | ||
| import com.microsoft.z3.IntExpr; | ||
| import com.microsoft.z3.IntNum; | ||
| import com.microsoft.z3.RealExpr; | ||
| import com.microsoft.z3.Solver; | ||
| import com.microsoft.z3.Sort; | ||
| import com.microsoft.z3.Status; | ||
| import com.microsoft.z3.Symbol; | ||
| import com.microsoft.z3.Model; | ||
|
|
||
| import java.util.ArrayList; | ||
| import java.util.Arrays; | ||
| import java.util.HashMap; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| import liquidjava.diagnostics.Counterexample; | ||
| 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 com.microsoft.z3.enumerations.Z3_sort_kind; | ||
|
|
||
| import org.apache.commons.lang3.NotImplementedException; | ||
|
|
||
|
|
@@ -32,6 +39,8 @@ public class TranslatorToZ3 implements AutoCloseable { | |
| private final Map<String, List<Expr<?>>> varSuperTypes = new HashMap<>(); | ||
| private final Map<String, AliasWrapper> aliasTranslation = new HashMap<>(); // this is not being used | ||
| private final Map<String, FuncDecl<?>> funcTranslation = new HashMap<>(); | ||
| private final Map<String, Expr<?>> funcAppTranslation = new HashMap<>(); | ||
| private final Map<Expr<?>, String> exprToNameTranslation = new HashMap<>(); | ||
|
|
||
| public TranslatorToZ3(liquidjava.processor.context.Context c) { | ||
| TranslatorContextToZ3.translateVariables(z3, c.getContext(), varTranslation); | ||
|
|
@@ -42,10 +51,35 @@ public TranslatorToZ3(liquidjava.processor.context.Context c) { | |
| } | ||
|
|
||
| @SuppressWarnings("unchecked") | ||
| public Status verifyExpression(Expr<?> e) { | ||
| Solver s = z3.mkSolver(); | ||
| s.add((BoolExpr) e); | ||
| return s.check(); | ||
| public Solver makeSolverForExpression(Expr<?> e) { | ||
| Solver solver = z3.mkSolver(); | ||
| solver.add((BoolExpr) e); | ||
| return solver; | ||
| } | ||
|
|
||
| /** | ||
| * Extracts the counterexample from the Z3 model | ||
| */ | ||
| public Counterexample getCounterexample(Model model) { | ||
| List<String> assignments = new ArrayList<>(); | ||
| // Extract constant variable assignments | ||
| for (FuncDecl<?> decl : model.getDecls()) { | ||
| if (decl.getArity() == 0) { | ||
| Symbol name = decl.getName(); | ||
| Expr<?> value = model.getConstInterp(decl); | ||
| // Skip values of uninterpreted sorts | ||
| if (value.getSort().getSortKind() != Z3_sort_kind.Z3_UNINTERPRETED_SORT) | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are uninterpreted sorts do we allow them? can you give an example
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Those are the ones that Z3 returns as e.g.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now, at least |
||
| assignments.add(name + " == " + value); | ||
| } | ||
| } | ||
| // Extract function application values | ||
| for (Map.Entry<String, Expr<?>> entry : funcAppTranslation.entrySet()) { | ||
| String label = entry.getKey(); | ||
| Expr<?> application = entry.getValue(); | ||
| Expr<?> value = model.eval(application, true); | ||
| assignments.add(label + " = " + value); | ||
| } | ||
| return new Counterexample(assignments); | ||
| } | ||
|
|
||
| // #####################Literals and Variables##################### | ||
|
|
@@ -81,7 +115,9 @@ private Expr<?> getVariableTranslation(String name) throws LJError { | |
| } | ||
|
|
||
| public Expr<?> makeVariable(String name) throws LJError { | ||
| return getVariableTranslation(name); // int[] not in varTranslation | ||
| Expr<?> expr = getVariableTranslation(name); // int[] not in varTranslation | ||
| exprToNameTranslation.put(expr, name); // Track for readable labels | ||
| return expr; | ||
| } | ||
|
|
||
| public Expr<?> makeFunctionInvocation(String name, Expr<?>[] params) throws LJError { | ||
|
|
@@ -91,7 +127,7 @@ public Expr<?> makeFunctionInvocation(String name, Expr<?>[] params) throws LJEr | |
| return makeSelect(params); | ||
| FuncDecl<?> fd = funcTranslation.get(name); | ||
| if (fd == null) | ||
| fd = resolveFunctionDeclFallback(name, params); | ||
| fd = resolveFunctionDecl(name, params); | ||
|
|
||
| Sort[] s = fd.getDomain(); | ||
| for (int i = 0; i < s.length; i++) { | ||
|
|
@@ -105,15 +141,18 @@ public Expr<?> makeFunctionInvocation(String name, Expr<?>[] params) throws LJEr | |
| params[i] = e; | ||
| } | ||
| } | ||
| return z3.mkApp(fd, params); | ||
| String label = buildFunctionLabel(name, params); | ||
| Expr<?> app = z3.mkApp(fd, params); | ||
| funcAppTranslation.put(label, app); | ||
| return app; | ||
| } | ||
|
|
||
| /** | ||
| * Fallback resolver for function declarations when an exact qualified name lookup fails. Tries to match by simple | ||
| * 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. | ||
| * Gets function declarations when an exact qualified name lookup fails. Tries to match by simple 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 LJError { | ||
| private FuncDecl<?> resolveFunctionDecl(String name, Expr<?>[] params) throws LJError { | ||
| String simple = Utils.getSimpleName(name); | ||
| FuncDecl<?> candidate = null; | ||
| for (Map.Entry<String, FuncDecl<?>> entry : funcTranslation.entrySet()) { | ||
|
|
@@ -310,6 +349,13 @@ public Expr<?> makeIte(Expr<?> c, Expr<?> t, Expr<?> e) { | |
| throw new RuntimeException("Condition is not a boolean expression"); | ||
| } | ||
|
|
||
| private String buildFunctionLabel(String functionName, Expr<?>[] params) { | ||
| String simpleName = Utils.getSimpleName(functionName); | ||
| String argsString = Arrays.stream(params).map(p -> exprToNameTranslation.getOrDefault(p, p.toString())) | ||
| .collect(Collectors.joining(", ")); | ||
| return simpleName + "(" + argsString + ")"; | ||
| } | ||
|
|
||
| @Override | ||
| public void close() throws Exception { | ||
| z3.close(); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,17 @@ | ||
| package liquidjava.smt; | ||
|
|
||
| import liquidjava.diagnostics.Counterexample; | ||
|
|
||
| public class TypeCheckError extends Exception { | ||
|
|
||
| public TypeCheckError(String message) { | ||
| super(message); | ||
| private final Counterexample counterexample; | ||
|
|
||
| public TypeCheckError(Counterexample counterexample) { | ||
| super("Refinement was violated"); | ||
| this.counterexample = counterexample; | ||
| } | ||
|
|
||
| public Counterexample getCounterexample() { | ||
| return counterexample; | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldnt this be
getCounterExample?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, that's the specific method from
LJDiagnosticto provide additional information in the diagnostic message, so it appears like this:There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can split it and call the
getCounterexampleString()in thegetDetails()because we might want to add more hints besides the counterexample in the future and don't want to mix it up.