From 860e66c3577465cf16ec56af0f247eff2f930737 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 1 Feb 2024 13:19:05 +0100 Subject: [PATCH 01/85] Started work on RASI generator --- .../invariants/AbstractProcess.java | 126 ++++++++++++++++++ .../invariants/AbstractState.java | 30 +++++ .../invariants/ExecutableState.java | 3 + .../invariants/ExecutionState.java | 8 ++ .../systemctocol/invariants/Generator.java | 47 +++++++ .../variables/ConcreteVariable.java | 20 +++ .../invariants/variables/FieldVariable.java | 27 ++++ .../variables/SequenceIndexVariable.java | 44 ++++++ 8 files changed, 305 insertions(+) create mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java create mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractState.java create mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutableState.java create mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutionState.java create mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/Generator.java create mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/variables/ConcreteVariable.java create mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/variables/FieldVariable.java create mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/variables/SequenceIndexVariable.java diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java new file mode 100644 index 0000000000..9a9b861ba7 --- /dev/null +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java @@ -0,0 +1,126 @@ +package vct.parsers.transform.systemctocol.invariants; + +import vct.col.ast.*; + +import java.util.ArrayList; +import java.util.List; + +public class AbstractProcess { + + protected RunMethod process_method; + + public AbstractProcess(RunMethod method) { + this.process_method = method; + } + + public List> simulate(int[] program_counter) { + return null; + } + + private List> small_step() { + return null; + } + + /** + * Returns the current statement stack at the execution state given by the program counter. Iteratively resolves the + * context of each statement and returns a stack with the highest-level container at position 0 and the statement + * currently to be evaluated at position n - 1. + * + * @param program_counter A list of indices showing which point in each container points to the right statement + * @return A list of container statements, with the desired statement in the last position + */ + private List> resolve(int[] program_counter) { + List> stack = new ArrayList<>(); + Statement context = process_method.body().get(); + stack.add(context); + for(int index : program_counter) { + context = get_at_index(context, index); + stack.add(context); + } + return stack; + } + + /** + * Takes a statement that (can) contain other statements as well as an index and returns the given index statement + * contained within the container. + * + * @param container Container statement + * @param index Index of desired contained statement + * @return Statement at index index within the body of container + */ + private Statement get_at_index(Statement container, int index) { + // Ignores the following: + // TryCatchFinally + // Synchronized + // ParInvariant + // ParAtomic + // VecBlock + // WandPackage + // ModelDo + if (container instanceof PVLBranch pvl_branch) { + return pvl_branch.branches().apply(index)._2(); + } + else if (container instanceof PVLLoop pvl_loop) { + return switch (index) { + case 0 -> pvl_loop.init(); + case 1 -> pvl_loop.body(); + case 2 -> pvl_loop.update(); + default -> throw new IndexOutOfBoundsException("Loop index must at most be 2."); + }; + } + else if (container instanceof InvokeProcedure invoke_procedure) { + if (index == 0) { + return invoke_procedure.ref().decl().body().get(); + } + else throw new IndexOutOfBoundsException("Invalid index for procedure invocation."); + } + else if (container instanceof InvokeConstructor invoke_constructor) { + if (index == 0) { + return invoke_constructor.ref().decl().body().get(); + } + else throw new IndexOutOfBoundsException("Invalid index for constructor invocation."); + } + else if (container instanceof InvokeMethod invoke_method) { + if (index == 0) { + return invoke_method.ref().decl().body().get(); + } + else throw new IndexOutOfBoundsException("Invalid index for method invocation."); + } + else if (container instanceof Block block) { + return block.statements().apply(index); + } + else if (container instanceof Scope scope) { + if (index == 0) { + return scope.body(); + } + else throw new IndexOutOfBoundsException("Invalid index for scope."); + } + else if (container instanceof Branch branch) { + return branch.branches().apply(index)._2(); + } + else if (container instanceof IndetBranch indet_branch) { + return indet_branch.branches().apply(index); + } + else if (container instanceof Switch switch_stmt) { + if (index == 0) { + return switch_stmt.body(); + } + else throw new IndexOutOfBoundsException("Invalid index for switch statement."); + } + else if (container instanceof Loop loop) { + return switch (index) { + case 0 -> loop.init(); + case 1 -> loop.body(); + case 2 -> loop.update(); + default -> throw new IndexOutOfBoundsException("Loop index must at most be 2."); + }; + } + else if (container instanceof RangedFor ranged_for) { + if (index == 0) { + return ranged_for.body(); + } + else throw new IndexOutOfBoundsException("Invalid index for foreach loop."); + } + else throw new IllegalArgumentException("Statement " + container + " is not supported as a container."); + } +} \ No newline at end of file diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractState.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractState.java new file mode 100644 index 0000000000..863223aae8 --- /dev/null +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractState.java @@ -0,0 +1,30 @@ +package vct.parsers.transform.systemctocol.invariants; + +import vct.parsers.transform.systemctocol.invariants.variables.ConcreteVariable; + +import java.util.HashMap; +import java.util.Map; + +public class AbstractState { + private final Map, Integer> valuations; + + public AbstractState() { + valuations = new HashMap<>(); + } + + public AbstractState(AbstractState previous) { + this.valuations = new HashMap<>(previous.valuations); + } + + public void set_valuation(ConcreteVariable var, int value) { + valuations.put(var, value); + } + + public Integer get_valuation(ConcreteVariable var) { + return valuations.get(var); + } + + public boolean equals(AbstractState other) { + return valuations.equals(other.valuations); + } +} diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutableState.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutableState.java new file mode 100644 index 0000000000..1761426ed9 --- /dev/null +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutableState.java @@ -0,0 +1,3 @@ +package vct.parsers.transform.systemctocol.invariants; + +public record ExecutableState (AbstractState state, int[][] execution_location_by_process, AbstractProcess next_process) {} diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutionState.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutionState.java new file mode 100644 index 0000000000..c14eec3a3e --- /dev/null +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutionState.java @@ -0,0 +1,8 @@ +package vct.parsers.transform.systemctocol.invariants; + +import vct.col.ast.Statement; + +public class ExecutionState { + private AbstractState abstract_state; + private Statement next; +} diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/Generator.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/Generator.java new file mode 100644 index 0000000000..f13e01ff19 --- /dev/null +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/Generator.java @@ -0,0 +1,47 @@ +package vct.parsers.transform.systemctocol.invariants; + +import vct.col.ast.InstanceMethod; +import vct.parsers.ParseResult; +import vct.parsers.transform.systemctocol.invariants.variables.ConcreteVariable; + +import java.util.*; + +public class Generator { + + private final List> processes; + + private final Set> active_branches; + + private final AbstractState considered_state; + + public Generator(ParseResult parse_result, + Map, Integer> considered_variables, + InstanceMethod main_method) { + // Initialize processes + processes = new ArrayList<>(); + initialize_processes(); + + // Initialize active branches + active_branches = new HashSet<>(); + + // Set initial state + considered_state = new AbstractState<>(); + } + + public void execute() { + + initialize_branches(); + } + + private void execute_step() { + + } + + private void initialize_processes() { + + } + + private void initialize_branches() { + + } +} \ No newline at end of file diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/ConcreteVariable.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/ConcreteVariable.java new file mode 100644 index 0000000000..a288348988 --- /dev/null +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/ConcreteVariable.java @@ -0,0 +1,20 @@ +package vct.parsers.transform.systemctocol.invariants.variables; + +import vct.col.ast.Deref; +import vct.col.ast.Expr; +import vct.col.ast.Field; +import vct.col.ast.PVLDeref; + +public abstract class ConcreteVariable { + + public abstract boolean is(Expr expression); + + public abstract boolean equals(ConcreteVariable other); + + protected Field extract_from_expression(Expr expression) { + if (expression instanceof Deref deref) { + return deref.ref().decl(); + } + else throw new IllegalArgumentException("Could not parse expression " + expression.toString()); + } +} diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/FieldVariable.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/FieldVariable.java new file mode 100644 index 0000000000..52f9fbdd94 --- /dev/null +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/FieldVariable.java @@ -0,0 +1,27 @@ +package vct.parsers.transform.systemctocol.invariants.variables; + +import vct.col.ast.Expr; +import vct.col.ast.Field; + +public class FieldVariable extends ConcreteVariable { + + protected Field field; + + public FieldVariable(Field field) { + this.field = field; + } + + @Override + public boolean is(Expr expression) { + return field.equals(extract_from_expression(expression)); + } + + @Override + public boolean equals(ConcreteVariable other) { + if (other == this) return true; + if (other instanceof FieldVariable other_fv) { + return this.field.equals(other_fv.field); + } + else return false; + } +} diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/SequenceIndexVariable.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/SequenceIndexVariable.java new file mode 100644 index 0000000000..1ba5eb3a2b --- /dev/null +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/SequenceIndexVariable.java @@ -0,0 +1,44 @@ +package vct.parsers.transform.systemctocol.invariants.variables; + +import vct.col.ast.*; + +public class SequenceIndexVariable extends ConcreteVariable { + + protected Field sequence; + protected int index; + + public SequenceIndexVariable(Field sequence, int index) { + this.sequence = sequence; + this.index = index; + } + + @Override + public boolean is(Expr expression) { + if (expression instanceof AmbiguousSubscript subscript) { + return extract_from_expression(subscript.collection()).equals(sequence) + && resolve_index_expression(subscript.index()) == index; + } + else if (expression instanceof SeqSubscript subscript) { + return extract_from_expression(subscript.seq()).equals(sequence) + && resolve_index_expression(subscript.index()) == index; + } + else return false; + } + + protected int resolve_index_expression(Expr index_expression) { + if (index_expression instanceof IntegerValue integer_value) { + return integer_value.value().intValue(); + } + // TODO: Support simple arithmetic? + else throw new IllegalArgumentException("Arithmetics in sequence accesses is not yet supported!"); + } + + @Override + public boolean equals(ConcreteVariable other) { + if (other == this) return true; + if (other instanceof SequenceIndexVariable other_siv) { + return this.sequence.equals(other_siv.sequence) && this.index == other_siv.index; + } + else return false; + } +} From 97e4ad38171da617d632a8d673d821eb90858d24 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 1 Feb 2024 14:18:09 +0100 Subject: [PATCH 02/85] Solidified architecture of RASI generator --- .../invariants/AbstractProcess.java | 2 +- .../invariants/ExecutableState.java | 23 ++++++++++++++++- .../invariants/ExecutionState.java | 6 +---- .../systemctocol/invariants/Generator.java | 25 +++++++++++-------- 4 files changed, 39 insertions(+), 17 deletions(-) diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java index 9a9b861ba7..2edf5048bb 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java @@ -13,7 +13,7 @@ public AbstractProcess(RunMethod method) { this.process_method = method; } - public List> simulate(int[] program_counter) { + public List> simulate(int[] program_counter, AbstractState starting_state) { return null; } diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutableState.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutableState.java index 1761426ed9..383253d726 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutableState.java +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutableState.java @@ -1,3 +1,24 @@ package vct.parsers.transform.systemctocol.invariants; -public record ExecutableState (AbstractState state, int[][] execution_location_by_process, AbstractProcess next_process) {} +import java.util.ArrayList; +import java.util.List; + +public record ExecutableState (AbstractState state, + int[][] execution_location_by_process, + int next_process_id) { + + public List> execute_step(List> processes) { + AbstractProcess next_process = processes.get(next_process_id); + List> updates = next_process.simulate(execution_location_by_process[next_process_id], state); + + List> result = new ArrayList<>(); + for (ExecutionState update : updates) { + int[][] updated_counter = execution_location_by_process.clone(); + updated_counter[next_process_id] = update.program_counter(); + for (int i = 0; i < processes.size(); i++) { + result.add(new ExecutableState<>(update.abstract_state(), updated_counter, i)); + } + } + return result; + } +} diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutionState.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutionState.java index c14eec3a3e..a28d2f0bfa 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutionState.java +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutionState.java @@ -1,8 +1,4 @@ package vct.parsers.transform.systemctocol.invariants; -import vct.col.ast.Statement; +public record ExecutionState (AbstractState abstract_state, int[] program_counter) {} -public class ExecutionState { - private AbstractState abstract_state; - private Statement next; -} diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/Generator.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/Generator.java index f13e01ff19..7587c894e3 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/Generator.java +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/Generator.java @@ -10,9 +10,9 @@ public class Generator { private final List> processes; - private final Set> active_branches; + private final List> active_branches; - private final AbstractState considered_state; + private final Set> considered_branches; public Generator(ParseResult parse_result, Map, Integer> considered_variables, @@ -22,19 +22,24 @@ public Generator(ParseResult parse_result, initialize_processes(); // Initialize active branches - active_branches = new HashSet<>(); + active_branches = new ArrayList<>(); - // Set initial state - considered_state = new AbstractState<>(); + // Initialize considered branches to empty list + considered_branches = new HashSet<>(); } public void execute() { - initialize_branches(); - } - - private void execute_step() { + while (!active_branches.isEmpty()) { + ExecutableState exploring = active_branches.remove(0); + List> new_possibilities = exploring.execute_step(processes) + .stream() + .filter((state) -> !considered_branches.contains(state)) + .toList(); + considered_branches.addAll(new_possibilities); + active_branches.addAll(new_possibilities); + } } private void initialize_processes() { @@ -42,6 +47,6 @@ private void initialize_processes() { } private void initialize_branches() { - + AbstractState initial_state = new AbstractState<>(); } } \ No newline at end of file From c441e636c76027ac2c8c83d4dee315e9c38f2be0 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 1 Feb 2024 16:49:52 +0100 Subject: [PATCH 03/85] Refined execution state tracking --- .../invariants/AbstractProcess.java | 8 +++- .../invariants/AbstractState.java | 13 +++++- .../invariants/ResolvedExecutionState.java | 43 +++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/ResolvedExecutionState.java diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java index 2edf5048bb..53942c79a8 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java @@ -14,7 +14,13 @@ public AbstractProcess(RunMethod method) { } public List> simulate(int[] program_counter, AbstractState starting_state) { - return null; + List> final_result = new ArrayList<>(); + + List> current_branches = new ArrayList<>(); + List> current_stack = resolve(program_counter); + current_branches.add(new ResolvedExecutionState<>(starting_state, program_counter, current_stack)); + + return final_result; } private List> small_step() { diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractState.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractState.java index 863223aae8..64a2c73cbc 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractState.java +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractState.java @@ -1,5 +1,6 @@ package vct.parsers.transform.systemctocol.invariants; +import vct.col.ast.Expr; import vct.parsers.transform.systemctocol.invariants.variables.ConcreteVariable; import java.util.HashMap; @@ -16,14 +17,24 @@ public AbstractState(AbstractState previous) { this.valuations = new HashMap<>(previous.valuations); } + public boolean contains_variable(Expr variable_expression) { + return valuations.keySet().stream().anyMatch((var) -> var.is(variable_expression)); + } + public void set_valuation(ConcreteVariable var, int value) { valuations.put(var, value); } - public Integer get_valuation(ConcreteVariable var) { + public int get_valuation(ConcreteVariable var) { return valuations.get(var); } + public int get_valuation(Expr variable_expression) { + return valuations.entrySet().stream() + .filter((entry) -> entry.getKey().is(variable_expression)) + .findFirst().orElseThrow().getValue(); + } + public boolean equals(AbstractState other) { return valuations.equals(other.valuations); } diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/ResolvedExecutionState.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/ResolvedExecutionState.java new file mode 100644 index 0000000000..4a0c8047e2 --- /dev/null +++ b/src/parsers/vct/parsers/transform/systemctocol/invariants/ResolvedExecutionState.java @@ -0,0 +1,43 @@ +package vct.parsers.transform.systemctocol.invariants; + +import vct.col.ast.Statement; + +import java.util.ArrayList; +import java.util.List; + +public class ResolvedExecutionState { + + private AbstractState state; + private int[] program_counter; + private List> program_location; + + public ResolvedExecutionState(AbstractState state, int[] program_counter, List> program_location) { + this.state = new AbstractState<>(state); + this.program_counter = program_counter.clone(); + this.program_location = new ArrayList<>(program_location); + } + + public AbstractState get_state() { + return state; + } + + public int[] get_program_counter() { + return program_counter; + } + + public List> get_program_location() { + return program_location; + } + + public Statement get_current_statement() { + return program_location.get(program_location.size() - 1); + } + + public void move_forward() { + + } + + public ExecutionState export() { + return new ExecutionState<>(state, program_counter); + } +} From df1654b4a136303f06f58c4bba8f20032bd626d5 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 8 Feb 2024 17:59:50 +0100 Subject: [PATCH 04/85] Working on Control Flow Graph for VerCors --- .../vct/rewrite/cfg/CFGGenerator.scala | 187 ++++++++++++++++++ src/rewrite/vct/rewrite/cfg/CFGNode.scala | 5 + 2 files changed, 192 insertions(+) create mode 100644 src/rewrite/vct/rewrite/cfg/CFGGenerator.scala create mode 100644 src/rewrite/vct/rewrite/cfg/CFGNode.scala diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala new file mode 100644 index 0000000000..c78ba0a224 --- /dev/null +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -0,0 +1,187 @@ +package vct.rewrite.cfg + +import vct.col.ast._ + +object CFGGenerator { + + def generate[G](entry: InstanceMethod[G]): CFGNode[G] = { + convert(entry.body.get, GlobalIndex[G]) + } + + def convert[G](node: Statement[G], context: GlobalIndex[G]): CFGNode[G] = + CFGNode(node, find_successors(node, context)) + + def find_successors[G](node: Statement[G], context: GlobalIndex[G]): Set[CFGNode[G]] = node match { + case PVLBranch(branches) => + branches.map(b => convert(b._2, context.enter_scope(node))).toSet + case PVLLoop(init, cond, update, _, body) => { + val cond_node: Eval[G] = Eval(cond)(cond.o) + + } + // NonExecutableStatement + case LocalDecl(local) => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + case SpecIgnoreStart() => + CFGNode(node, successors) + case SpecIgnoreEnd() => + CFGNode(node, successors) + // NormallyCompletingStatement + case Assign(target, value) => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + case Send(decl, delta, res) => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + case Recv(ref) => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + case DefaultCase() => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + case Case(pattern) => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + case Label(decl, stat) => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + case Goto(lbl) => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + case Exhale(res) => // TODO: Can expressions in specifications be ignored? + CFGNode(node, successors) + case Assert(res) => + CFGNode(node, successors) + case Refute(assn) => + CFGNode(node, successors) + case Inhale(res) => + CFGNode(node, successors) + case Assume(assn) => // <-- + CFGNode(node, successors) + case Instantiate(cls, out) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case Wait(obj) => + CFGNode(node, successors) + case Notify(obj) => + CFGNode(node, successors) + case Fork(obj) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case Join(obj) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case Lock(obj) => + CFGNode(node, successors) + case Unlock(obj) => + CFGNode(node, successors) + case Commit(obj) => + CFGNode(node, successors) + case Fold(res) => // TODO: Can expressions in specifications be ignored? + CFGNode(node, successors) + case Unfold(res) => + CFGNode(node, successors) + case WandApply(res) => // <-- + CFGNode(node, successors) + case Havoc(loc) => + CFGNode(node, successors) + case FramedProof(pre, body, post) => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + case Extract(contractedStatement) => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + // ExceptionalStatement + case Eval(expr) => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + // InvocationStatement + case InvokeProcedure(ref, args, outArgs, typeArgs, givenMap, yields) => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + case InvokeConstructor(ref, out, args, outArgs, typeArgs, givenMap, yields) => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + case InvokeMethod(obj, ref, args, outArgs, typeArgs, givenMap, yields) => { + CFGNode[G](node, Set[CFGNode[G]]) + } // TODO + case Return(result) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case Throw(obj) => + CFGNode(node, successors) + case Break(label) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case Continue(label) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + // CompositeStatement + case Block(statements) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case Scope(locals, body) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case Branch(branches) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case IndetBranch(branches) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case Switch(expr, body) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case Loop(init, cond, update, contract, body) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case RangedFor(iter, contract, body) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case TryCatchFinally(body, after, catches) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case Synchronized(obj, body) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case ParInvariant(decl, inv, content) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case ParAtomic(inv, content) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case ParBarrier(block, invs, requires, ensures, content) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case ParStatement(impl) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case VecBlock(iters, requires, ensures, content) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case WandPackage(res, proof) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case ModelDo(model, perm, after, action, impl) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + // CStatement + case CDeclarationStatement(decl) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case CGoto(label) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + // CPPStatement + case CPPDeclarationStatement(decl) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case CPPLifetimeScope(body) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case JavaLocalDeclarationStatement(decl) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + // SilverStatement + case SilverNewRef(v, fields) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case SilverFieldAssign(obj, field, value) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case SilverLocalAssign(v, value) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + // OTHER + case PVLCommunicate(sender, receiver) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case PVLSeqAssign(receiver, field, value) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case Communicate(receiver, sender) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case SeqAssign(receiver, field, value) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case UnresolvedSeqBranch(branches) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case UnresolvedSeqLoop(cond, contract, body) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case SeqBranch(guards, yes, no) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case SeqLoop(guards, contract, body) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case VeyMontAssignExpression(endpoint, assign) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + case CommunicateX(receiver, sender, chanType, assign) => + CFGNode[G](node, Set[CFGNode[G]]) // TODO + } +} diff --git a/src/rewrite/vct/rewrite/cfg/CFGNode.scala b/src/rewrite/vct/rewrite/cfg/CFGNode.scala new file mode 100644 index 0000000000..dd72a81012 --- /dev/null +++ b/src/rewrite/vct/rewrite/cfg/CFGNode.scala @@ -0,0 +1,5 @@ +package vct.rewrite.cfg + +import vct.col.ast.Node + +case class CFGNode[G](ast_node: Node[G], successors: Set[CFGNode[G]]) \ No newline at end of file From 258827c27bdb5b5c9d5782d75862071797fa5666 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 8 Feb 2024 18:00:07 +0100 Subject: [PATCH 05/85] Added indexing system for nested statements --- src/rewrite/vct/rewrite/cfg/Index.scala | 232 ++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 src/rewrite/vct/rewrite/cfg/Index.scala diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala new file mode 100644 index 0000000000..18918ee9de --- /dev/null +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -0,0 +1,232 @@ +package vct.rewrite.cfg + +import vct.col.ast._ + +case class GlobalIndex[G](indices: List[Index[G]]) { + + def enter_scope(statement: Statement[G], index: Int = 0): GlobalIndex[G] = + GlobalIndex(Index[G](statement, index) :: indices) + + + def leave_scope(): GlobalIndex[G] = + GlobalIndex(indices.tail).make_step() + + + def make_step(): GlobalIndex[G] = indices.head.make_step() match { + case Some(index) => GlobalIndex(index :: indices.tail) + case None => GlobalIndex(indices.tail).make_step() + } + + def resolve(): Statement[G] = indices.head.resolve() +} + +sealed trait Index[G] { + def make_step(): Option[Index[G]] + def resolve(): Statement[G] +} + +object Index { + def apply[G](pvl_branch: PVLBranch[G], index: Int): Index[G] = PVLBranchIndex(pvl_branch, index) + def apply[G](pvl_loop: PVLLoop[G], index: Int): Index[G] = PVLLoopIndex(pvl_loop, index) + def apply[G](label: Label[G], index: Int): Index[G] = LabelIndex(label) + def apply[G](framed_proof: FramedProof[G], index: Int): Index[G] = FramedProofIndex(framed_proof) + def apply[G](extract: Extract[G], index: Int): Index[G] = ExtractIndex(extract) + def apply[G](eval: Eval[G], index: Int): Index[G] = EvalIndex(eval, index) + def apply[G](block: Block[G], index: Int): Index[G] = BlockIndex(block, index) + def apply[G](scope: Scope[G], index: Int): Index[G] = ScopeIndex(scope) + def apply[G](branch: Branch[G], index: Int): Index[G] = BranchIndex(branch, index) + def apply[G](indet_branch: IndetBranch[G], index: Int): Index[G] = IndetBranchIndex(indet_branch, index) + def apply[G](switch: Switch[G], index: Int): Index[G] = SwitchIndex(switch) + def apply[G](loop: Loop[G], index: Int): Index[G] = LoopIndex(loop, index) + def apply[G](ranged_for: RangedFor[G], index: Int): Index[G] = RangedForIndex(ranged_for) + def apply[G](try_catch_finally: TryCatchFinally[G], index: Int): Index[G] = TryCatchFinallyIndex(try_catch_finally, index) + def apply[G](synchronized: Synchronized[G], index: Int): Index[G] = SynchronizedIndex(synchronized) + def apply[G](par_invariant: ParInvariant[G], index: Int): Index[G] = ParInvariantIndex(par_invariant) + def apply[G](par_atomic: ParAtomic[G], index: Int): Index[G] = ParAtomicIndex(par_atomic) + def apply[G](par_barrier: ParBarrier[G], index: Int): Index[G] = ParBarrierIndex(par_barrier) + def apply[G](vec_block: VecBlock[G], index: Int): Index[G] = VecBlockIndex(vec_block) + def apply[G](wand_package: WandPackage[G], index: Int): Index[G] = WandPackageIndex(wand_package) + def apply[G](model_do: ModelDo[G], index: Int): Index[G] = ModelDoIndex(model_do) + def apply[G](cpp_lifetime_scope: CPPLifetimeScope[G], index: Int): Index[G] = CPPLifetimeScopeIndex(cpp_lifetime_scope) + def apply[G](unresolved_seq_branch: UnresolvedSeqBranch[G], index: Int): Index[G] = UnresolvedSeqBranchIndex(unresolved_seq_branch, index) + def apply[G](unresolved_seq_loop: UnresolvedSeqLoop[G], index: Int): Index[G] = UnresolvedSeqLoopIndex(unresolved_seq_loop, index) + def apply[G](seq_branch: SeqBranch[G], index: Int): Index[G] = SeqBranchIndex(seq_branch, index) + def apply[G](seq_loop: SeqLoop[G], index: Int): Index[G] = SeqLoopIndex(seq_loop) + def apply[G](veymont_assign_expression: VeyMontAssignExpression[G], index: Int): Index[G] = VeyMontAssignExpressionIndex(veymont_assign_expression) + def apply[G](communicatex: CommunicateX[G], index: Int): Index[G] = CommunicateXIndex(communicatex) +} + +case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = pvl_branch.branches.apply(index)._2 // TODO: Handle expressions in branch conditions +} +case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = { + if (index < 3) Some(PVLLoopIndex(pvl_loop, index + 1)) + else None + } + + override def resolve(): Statement[G] = index match { + case 0 => pvl_loop.init + case 1 => Eval(pvl_loop.cond)(pvl_loop.cond.o) + case 2 => pvl_loop.body + case 3 => pvl_loop.update + } +} +case class LabelIndex[G](label: Label[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = label.stat +} +case class FramedProofIndex[G](framed_proof: FramedProof[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = framed_proof.body +} +case class ExtractIndex[G](extract: Extract[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = extract.contractedStatement +} +case class EvalIndex[G](eval: Eval[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = None // TODO: Implement expressions! + + override def resolve(): Statement[G] = ??? // TODO: Implement expressions! +} +case class BlockIndex[G](block: Block[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = { + if (index < block.statements.size - 1) Some(BlockIndex(block, index + 1)) + else None + } + + override def resolve(): Statement[G] = block.statements.apply(index) +} +case class ScopeIndex[G](scope: Scope[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = scope.body +} +case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = branch.branches.apply(index)._2 +} +case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = indet_branch.branches.apply(index) +} +case class SwitchIndex[G](switch: Switch[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = switch.body +} +case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = { + if (index < 3) Some(LoopIndex(loop, index + 1)) + else None + } + + override def resolve(): Statement[G] = index match { + case 0 => loop.init + case 1 => Eval(loop.cond)(loop.cond.o) + case 2 => loop.body + case 3 => loop.update + } +} +case class RangedForIndex[G](ranged_for: RangedFor[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = ranged_for.body +} +case class TryCatchFinallyIndex[G](try_catch_finally: TryCatchFinally[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = { + if (index == 0) Some(TryCatchFinallyIndex(try_catch_finally, 1)) + else None + } + + override def resolve(): Statement[G] = index match { + case 0 => try_catch_finally.body + case 1 => try_catch_finally.after + } +} +case class SynchronizedIndex[G](synchronized: Synchronized[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = synchronized.body +} +case class ParInvariantIndex[G](par_invariant: ParInvariant[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = par_invariant.content +} +case class ParAtomicIndex[G](par_atomic: ParAtomic[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = par_atomic.content +} +case class ParBarrierIndex[G](par_barrier: ParBarrier[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = par_barrier.content +} +case class VecBlockIndex[G](vec_block: VecBlock[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = vec_block.content +} +case class WandPackageIndex[G](wand_package: WandPackage[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = wand_package.proof +} +case class ModelDoIndex[G](model_do: ModelDo[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = model_do.impl +} +case class CPPLifetimeScopeIndex[G](cpp_lifetime_scope: CPPLifetimeScope[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = cpp_lifetime_scope.body +} +case class UnresolvedSeqBranchIndex[G](unresolved_seq_branch: UnresolvedSeqBranch[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = unresolved_seq_branch.branches.apply(index)._2 +} +case class UnresolvedSeqLoopIndex[G](unresolved_seq_loop: UnresolvedSeqLoop[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = { + if (index == 0) Some(UnresolvedSeqLoopIndex(unresolved_seq_loop, 1)) + else None + } + + override def resolve(): Statement[G] = index match { + case 0 => Eval(unresolved_seq_loop.cond)(unresolved_seq_loop.cond.o) + case 1 => unresolved_seq_loop.body + } +} +case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = index match { + case 0 => seq_branch.yes + case 1 => seq_branch.no.get + } +} +case class SeqLoopIndex[G](seq_loop: SeqLoop[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = seq_loop.body +} +case class VeyMontAssignExpressionIndex[G](veymont_assign_expression: VeyMontAssignExpression[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = veymont_assign_expression.assign +} +case class CommunicateXIndex[G](communicatex: CommunicateX[G]) extends Index[G] { + override def make_step(): Option[Index[G]] = None + + override def resolve(): Statement[G] = communicatex.assign +} \ No newline at end of file From e1cf5334f6307df47fa2a35a72f7e6bf7798cae1 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 9 Feb 2024 12:47:36 +0100 Subject: [PATCH 06/85] Added context indices for subroutine calls --- src/rewrite/vct/rewrite/cfg/Index.scala | 72 +++++++++++++++---------- 1 file changed, 44 insertions(+), 28 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 18918ee9de..1cd425062d 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -32,6 +32,9 @@ object Index { def apply[G](framed_proof: FramedProof[G], index: Int): Index[G] = FramedProofIndex(framed_proof) def apply[G](extract: Extract[G], index: Int): Index[G] = ExtractIndex(extract) def apply[G](eval: Eval[G], index: Int): Index[G] = EvalIndex(eval, index) + def apply[G](invoke_procedure: InvokeProcedure[G], index: Int): Index[G] = InvokeProcedureIndex(invoke_procedure, index) + def apply[G](invoke_constructor: InvokeConstructor[G], index: Int): Index[G] = InvokeConstructorIndex(invoke_constructor, index) + def apply[G](invoke_method: InvokeMethod[G], index: Int): Index[G] = InvokeMethodIndex(invoke_method, index) def apply[G](block: Block[G], index: Int): Index[G] = BlockIndex(block, index) def apply[G](scope: Scope[G], index: Int): Index[G] = ScopeIndex(scope) def apply[G](branch: Branch[G], index: Int): Index[G] = BranchIndex(branch, index) @@ -58,7 +61,6 @@ object Index { case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = pvl_branch.branches.apply(index)._2 // TODO: Handle expressions in branch conditions } case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { @@ -66,7 +68,6 @@ case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { if (index < 3) Some(PVLLoopIndex(pvl_loop, index + 1)) else None } - override def resolve(): Statement[G] = index match { case 0 => pvl_loop.init case 1 => Eval(pvl_loop.cond)(pvl_loop.cond.o) @@ -74,60 +75,75 @@ case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { case 3 => pvl_loop.update } } + case class LabelIndex[G](label: Label[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = label.stat } + case class FramedProofIndex[G](framed_proof: FramedProof[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = framed_proof.body } + case class ExtractIndex[G](extract: Extract[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = extract.contractedStatement } + case class EvalIndex[G](eval: Eval[G], index: Int) extends Index[G] { override def make_step(): Option[Index[G]] = None // TODO: Implement expressions! - override def resolve(): Statement[G] = ??? // TODO: Implement expressions! } + +case class InvokeProcedureIndex[G](invoke_procedure: InvokeProcedure[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = ??? + override def resolve(): Statement[G] = ??? +} + +case class InvokeConstructorIndex[G](invoke_constructor: InvokeConstructor[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = ??? + override def resolve(): Statement[G] = ??? +} + +case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) extends Index[G] { + override def make_step(): Option[Index[G]] = ??? + override def resolve(): Statement[G] = ??? +} + case class BlockIndex[G](block: Block[G], index: Int) extends Index[G] { override def make_step(): Option[Index[G]] = { if (index < block.statements.size - 1) Some(BlockIndex(block, index + 1)) else None } - override def resolve(): Statement[G] = block.statements.apply(index) } + case class ScopeIndex[G](scope: Scope[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = scope.body } + case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = branch.branches.apply(index)._2 } + case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = indet_branch.branches.apply(index) } + case class SwitchIndex[G](switch: Switch[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = switch.body } + case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { override def make_step(): Option[Index[G]] = { if (index < 3) Some(LoopIndex(loop, index + 1)) else None } - override def resolve(): Statement[G] = index match { case 0 => loop.init case 1 => Eval(loop.cond)(loop.cond.o) @@ -135,98 +151,98 @@ case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { case 3 => loop.update } } + case class RangedForIndex[G](ranged_for: RangedFor[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = ranged_for.body } + case class TryCatchFinallyIndex[G](try_catch_finally: TryCatchFinally[G], index: Int) extends Index[G] { override def make_step(): Option[Index[G]] = { if (index == 0) Some(TryCatchFinallyIndex(try_catch_finally, 1)) else None } - override def resolve(): Statement[G] = index match { case 0 => try_catch_finally.body case 1 => try_catch_finally.after } } + case class SynchronizedIndex[G](synchronized: Synchronized[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = synchronized.body } + case class ParInvariantIndex[G](par_invariant: ParInvariant[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = par_invariant.content } + case class ParAtomicIndex[G](par_atomic: ParAtomic[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = par_atomic.content } + case class ParBarrierIndex[G](par_barrier: ParBarrier[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = par_barrier.content } + case class VecBlockIndex[G](vec_block: VecBlock[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = vec_block.content } + case class WandPackageIndex[G](wand_package: WandPackage[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = wand_package.proof } + case class ModelDoIndex[G](model_do: ModelDo[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = model_do.impl } + case class CPPLifetimeScopeIndex[G](cpp_lifetime_scope: CPPLifetimeScope[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = cpp_lifetime_scope.body } + case class UnresolvedSeqBranchIndex[G](unresolved_seq_branch: UnresolvedSeqBranch[G], index: Int) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = unresolved_seq_branch.branches.apply(index)._2 } + case class UnresolvedSeqLoopIndex[G](unresolved_seq_loop: UnresolvedSeqLoop[G], index: Int) extends Index[G] { override def make_step(): Option[Index[G]] = { if (index == 0) Some(UnresolvedSeqLoopIndex(unresolved_seq_loop, 1)) else None } - override def resolve(): Statement[G] = index match { case 0 => Eval(unresolved_seq_loop.cond)(unresolved_seq_loop.cond.o) case 1 => unresolved_seq_loop.body } } + case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = index match { case 0 => seq_branch.yes case 1 => seq_branch.no.get } } + case class SeqLoopIndex[G](seq_loop: SeqLoop[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = seq_loop.body } + case class VeyMontAssignExpressionIndex[G](veymont_assign_expression: VeyMontAssignExpression[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = veymont_assign_expression.assign } + case class CommunicateXIndex[G](communicatex: CommunicateX[G]) extends Index[G] { override def make_step(): Option[Index[G]] = None - override def resolve(): Statement[G] = communicatex.assign } \ No newline at end of file From 9e766d04b683b5a1f7554c56906714fee2cf0bab Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 12 Feb 2024 09:58:50 +0100 Subject: [PATCH 07/85] Continued working on indexing system --- .../vct/rewrite/cfg/CFGGenerator.scala | 319 +++++++++--------- src/rewrite/vct/rewrite/cfg/CFGNode.scala | 2 +- src/rewrite/vct/rewrite/cfg/Index.scala | 35 +- 3 files changed, 193 insertions(+), 163 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index c78ba0a224..556b73bd88 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -2,186 +2,199 @@ package vct.rewrite.cfg import vct.col.ast._ -object CFGGenerator { +import scala.collection.mutable - def generate[G](entry: InstanceMethod[G]): CFGNode[G] = { +case class CFGGenerator[G]() { + private val found_labels: mutable.Map[LabelDecl[G], CFGNode[G]] = mutable.HashMap[LabelDecl[G], CFGNode[G]]() + //private val searched_labels: mutable.Map[LabelDecl[G], mutable.Set[CFGNode[G]]] = mutable.HashMap[LabelDecl[G], mutable.Set[CFGNode[G]]]() + private val searched_labels: mutable.Map[LabelDecl[G], mutable.Set[CFGNode[G]]] = mutable.HashMap[LabelDecl[G], mutable.Set[CFGNode[G]]]() + + def generate(entry: InstanceMethod[G]): CFGNode[G] = { convert(entry.body.get, GlobalIndex[G]) } - def convert[G](node: Statement[G], context: GlobalIndex[G]): CFGNode[G] = - CFGNode(node, find_successors(node, context)) + def convert(node: Statement[G], context: GlobalIndex[G]): CFGNode[G] = { + // Create new node with its successors (if possible) + val cfg_node: CFGNode[G] = CFGNode(node, find_successors(node, context)) + // Handle labels and goto statements + node match { + case label: Label[G] => { + // For labels, add them to the label map and add them to any goto statements going to that label + found_labels.addOne((label.decl, cfg_node)) + searched_labels.getOrElse(label.decl, mutable.Set()).map(g => g.successors.addOne(cfg_node)) + } + case goto: Goto[G] => { + // For goto statements, if the label could not be resolved below, add the statement to the waiting list for the right label + if (cfg_node.successors.isEmpty) { + if (searched_labels.contains(goto.lbl.decl)) searched_labels(goto.lbl.decl).addOne(cfg_node) + else searched_labels.addOne((goto.lbl.decl, mutable.Set(cfg_node))) + } + } + } + cfg_node + } - def find_successors[G](node: Statement[G], context: GlobalIndex[G]): Set[CFGNode[G]] = node match { + def find_successors(node: Statement[G], context: GlobalIndex[G]): mutable.Set[CFGNode[G]] = node match { case PVLBranch(branches) => - branches.map(b => convert(b._2, context.enter_scope(node))).toSet + mutable.Set(branches.zipWithIndex.map(b => convert(b._1._2, context.enter_scope(node, b._2)))) // TODO: Implement support for statements in expressions case PVLLoop(init, cond, update, _, body) => { val cond_node: Eval[G] = Eval(cond)(cond.o) - + // TODO: Implement! + ??? } // NonExecutableStatement - case LocalDecl(local) => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO - case SpecIgnoreStart() => - CFGNode(node, successors) - case SpecIgnoreEnd() => - CFGNode(node, successors) + case LocalDecl(_) => sequential_successor(context) + case SpecIgnoreStart() => sequential_successor(context) // What is this? + case SpecIgnoreEnd() => sequential_successor(context) // NormallyCompletingStatement - case Assign(target, value) => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO - case Send(decl, delta, res) => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO - case Recv(ref) => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO - case DefaultCase() => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO - case Case(pattern) => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO - case Label(decl, stat) => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO + case Assign(target, value) => sequential_successor(context) // TODO: Implement support for statements in expressions + case Send(_, _, res) => sequential_successor(context) // TODO: Implement support for statements in expressions + case Recv(_) => sequential_successor(context) + case DefaultCase() => ??? // TODO: Ask Pieter about switch statements + case Case(pattern) => ??? + case Label(_, stat) => + mutable.Set(convert(stat, context.enter_scope(node))) case Goto(lbl) => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO - case Exhale(res) => // TODO: Can expressions in specifications be ignored? - CFGNode(node, successors) - case Assert(res) => - CFGNode(node, successors) - case Refute(assn) => - CFGNode(node, successors) - case Inhale(res) => - CFGNode(node, successors) - case Assume(assn) => // <-- - CFGNode(node, successors) - case Instantiate(cls, out) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case Wait(obj) => - CFGNode(node, successors) - case Notify(obj) => - CFGNode(node, successors) - case Fork(obj) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case Join(obj) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case Lock(obj) => - CFGNode(node, successors) - case Unlock(obj) => - CFGNode(node, successors) - case Commit(obj) => - CFGNode(node, successors) - case Fold(res) => // TODO: Can expressions in specifications be ignored? - CFGNode(node, successors) - case Unfold(res) => - CFGNode(node, successors) - case WandApply(res) => // <-- - CFGNode(node, successors) - case Havoc(loc) => - CFGNode(node, successors) - case FramedProof(pre, body, post) => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO - case Extract(contractedStatement) => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO + val found_node: Option[CFGNode[G]] = found_labels.get(lbl.decl) + found_node match { + case Some(node) => mutable.Set(node) + case None => mutable.Set() + } + } + case Exhale(res) => sequential_successor(context) // TODO: Can expressions in specifications be ignored? + case Assert(res) => sequential_successor(context) + case Refute(assn) => sequential_successor(context) + case Inhale(res) => sequential_successor(context) + case Assume(assn) => sequential_successor(context) // <-- + case Instantiate(cls, out) => ??? // TODO + case Wait(_) => sequential_successor(context) + case Notify(_) => sequential_successor(context) + case Fork(obj) => ??? // TODO: Find run method from expression + case Join(_) => sequential_successor(context) + case Lock(_) => sequential_successor(context) + case Unlock(_) => sequential_successor(context) + case Commit(_) => sequential_successor(context) + case Fold(res) => sequential_successor(context) // TODO: Can expressions in specifications be ignored? + case Unfold(res) => sequential_successor(context) + case WandApply(res) => sequential_successor(context) // <-- + case Havoc(_) => sequential_successor(context) + case FramedProof(pre, body, post) => + mutable.Set(convert(body, context.enter_scope(node))) // TODO: Can expressions in specifications be ignored? + case Extract(contractedStatement) => + mutable.Set(convert(contractedStatement, context.enter_scope(node))) // ExceptionalStatement - case Eval(expr) => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO + case Eval(expr) => ??? // TODO // InvocationStatement - case InvokeProcedure(ref, args, outArgs, typeArgs, givenMap, yields) => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO + case InvokeProcedure(ref, args, _, _, givenMap, _) => { + if (args.nonEmpty) mutable.Set(convert(Eval(args.head)(args.head.o), context.enter_scope(node))) + else if (ref.decl.body.nonEmpty) mutable.Set(convert(ref.decl.body.get, context.enter_scope(node))) + else sequential_successor(context) + } case InvokeConstructor(ref, out, args, outArgs, typeArgs, givenMap, yields) => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO + if (args.nonEmpty) mutable.Set(convert(Eval(args.head)(args.head.o), context.enter_scope(node))) + else if (ref.decl.body.nonEmpty) mutable.Set(convert(ref.decl.body.get, context.enter_scope(node))) + else sequential_successor(context) + } case InvokeMethod(obj, ref, args, outArgs, typeArgs, givenMap, yields) => { - CFGNode[G](node, Set[CFGNode[G]]) - } // TODO - case Return(result) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case Throw(obj) => - CFGNode(node, successors) - case Break(label) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case Continue(label) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO + if (args.nonEmpty) mutable.Set(convert(Eval(args.head)(args.head.o), context.enter_scope(node))) + else if (ref.decl.body.nonEmpty) mutable.Set(convert(ref.decl.body.get, context.enter_scope(node))) + else sequential_successor(context) + } + case Return(result) => mutable.Set(return_successor(context)) + case Throw(obj) => mutable.Set(exception_successor(obj, context)) + case Break(label) => label match { + case Some(ref) => ??? // TODO: Handle break label! + case None => mutable.Set(break_successor(context)) + } + case Continue(label) => label match { + case Some(ref) => ??? // TODO: Handle continue label! + case None => mutable.Set(continue_successor(context)) + } // CompositeStatement case Block(statements) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case Scope(locals, body) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO + mutable.Set(convert(statements.head, context.enter_scope(node))) + case Scope(_, body) => + mutable.Set(convert(body, context.enter_scope(node))) case Branch(branches) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO + mutable.Set(branches.zipWithIndex.map(b => convert(b._1._2, context.enter_scope(node, b._2)))) // TODO: Handle statements in condition expressions! case IndetBranch(branches) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case Switch(expr, body) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case Loop(init, cond, update, contract, body) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case RangedFor(iter, contract, body) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case TryCatchFinally(body, after, catches) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case Synchronized(obj, body) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case ParInvariant(decl, inv, content) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case ParAtomic(inv, content) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case ParBarrier(block, invs, requires, ensures, content) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case ParStatement(impl) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case VecBlock(iters, requires, ensures, content) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO + mutable.Set(branches.zipWithIndex.map(b => convert(b._1, context.enter_scope(node, b._2)))) + case Switch(expr, body) => ??? // TODO: Ask Pieter about switch statements + case Loop(init, cond, update, _, body) => { + ??? // TODO + } + case RangedFor(_, _, body) => + mutable.Set(convert(body, context.enter_scope(node))) + case TryCatchFinally(body, _, _) => + mutable.Set(convert(body, context.enter_scope(node))) // TODO: Is it safe to ignore the "after" part here? + case Synchronized(_, body) => + mutable.Set(convert(body, context.enter_scope(node))) + case ParInvariant(_, _, content) => + mutable.Set(convert(content, context.enter_scope(node))) + case ParAtomic(_, content) => + mutable.Set(convert(content, context.enter_scope(node))) + case ParBarrier(_, _, _, _, content) => + mutable.Set(convert(content, context.enter_scope(node))) + case ParStatement(_) => sequential_successor(context) + case VecBlock(_, _, _, content) => + mutable.Set(convert(content, context.enter_scope(node))) case WandPackage(res, proof) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO + ??? // TODO case ModelDo(model, perm, after, action, impl) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO + ??? // TODO // CStatement - case CDeclarationStatement(decl) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case CGoto(label) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO + case CDeclarationStatement(_) => sequential_successor(context) + case CGoto(label) => ??? // CPPStatement - case CPPDeclarationStatement(decl) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO + case CPPDeclarationStatement(_) => sequential_successor(context) case CPPLifetimeScope(body) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case JavaLocalDeclarationStatement(decl) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO + mutable.Set(convert(body, context.enter_scope(node))) + case JavaLocalDeclarationStatement(_) => sequential_successor(context) // SilverStatement - case SilverNewRef(v, fields) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case SilverFieldAssign(obj, field, value) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case SilverLocalAssign(v, value) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO + case SilverNewRef(_, _) => sequential_successor(context) + case SilverFieldAssign(_, _, value) => sequential_successor(context) // TODO: Statements in expressions + case SilverLocalAssign(_, value) => sequential_successor(context) // TODO: Statements in expressions // OTHER - case PVLCommunicate(sender, receiver) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case PVLSeqAssign(receiver, field, value) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case Communicate(receiver, sender) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case SeqAssign(receiver, field, value) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO + case PVLCommunicate(_, _) => sequential_successor(context) + case PVLSeqAssign(_, _, value) => sequential_successor(context) // TODO: Statements in expressions + case Communicate(_, _) => sequential_successor(context) + case SeqAssign(_, _, value) => sequential_successor(context) // TODO: Statements in expressions case UnresolvedSeqBranch(branches) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case UnresolvedSeqLoop(cond, contract, body) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case SeqBranch(guards, yes, no) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case SeqLoop(guards, contract, body) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case VeyMontAssignExpression(endpoint, assign) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO - case CommunicateX(receiver, sender, chanType, assign) => - CFGNode[G](node, Set[CFGNode[G]]) // TODO + mutable.Set(branches.zipWithIndex.map(b => convert(b._1._2, context.enter_scope(node, b._2)))) + case UnresolvedSeqLoop(cond, _, body) => { + ??? // TODO: Implement + } + case SeqBranch(_, yes, no) => no match { + case Some(stmt) => mutable.Set(convert(yes, context.enter_scope(node, 0)), convert(stmt, context.enter_scope(node, 1))) + case None => mutable.Set(convert(yes, context.enter_scope(node))) + } + case SeqLoop(_, _, body) => + mutable.Set(convert(body, context.enter_scope(node))) + case VeyMontAssignExpression(_, assign) => + mutable.Set(convert(assign, context.enter_scope(node))) + case CommunicateX(_, _, _, assign) => + mutable.Set(convert(assign, context.enter_scope(node))) + } + + def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGNode[G]] = { + val next_index: GlobalIndex[G] = index.make_step() + val next_statement: Statement[G] = next_index.resolve() + + } + + def return_successor(index: GlobalIndex[G]): CFGNode[G] = { + ??? // TODO: Implement! + } + + def exception_successor(exception: Expr[G], index: GlobalIndex[G]): CFGNode[G] = { + ??? // TODO: Implement! + } + + def break_successor(index: GlobalIndex[G]): CFGNode[G] = { + ??? // TODO: Implement! + } + + def continue_successor(index: GlobalIndex[G]): CFGNode[G] = { + ??? // TODO: Implement! } } diff --git a/src/rewrite/vct/rewrite/cfg/CFGNode.scala b/src/rewrite/vct/rewrite/cfg/CFGNode.scala index dd72a81012..e86db9805a 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGNode.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGNode.scala @@ -2,4 +2,4 @@ package vct.rewrite.cfg import vct.col.ast.Node -case class CFGNode[G](ast_node: Node[G], successors: Set[CFGNode[G]]) \ No newline at end of file +case class CFGNode[G](ast_node: Node[G], successors: scala.collection.mutable.Set[CFGNode[G]]) \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 1cd425062d..1d0b4722ee 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -7,13 +7,11 @@ case class GlobalIndex[G](indices: List[Index[G]]) { def enter_scope(statement: Statement[G], index: Int = 0): GlobalIndex[G] = GlobalIndex(Index[G](statement, index) :: indices) - def leave_scope(): GlobalIndex[G] = GlobalIndex(indices.tail).make_step() - def make_step(): GlobalIndex[G] = indices.head.make_step() match { - case Some(index) => GlobalIndex(index :: indices.tail) + case Some(idx) => GlobalIndex(idx :: indices.tail) case None => GlobalIndex(indices.tail).make_step() } @@ -63,6 +61,7 @@ case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index override def make_step(): Option[Index[G]] = None override def resolve(): Statement[G] = pvl_branch.branches.apply(index)._2 // TODO: Handle expressions in branch conditions } + case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { override def make_step(): Option[Index[G]] = { if (index < 3) Some(PVLLoopIndex(pvl_loop, index + 1)) @@ -97,18 +96,36 @@ case class EvalIndex[G](eval: Eval[G], index: Int) extends Index[G] { } case class InvokeProcedureIndex[G](invoke_procedure: InvokeProcedure[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = ??? - override def resolve(): Statement[G] = ??? + override def make_step(): Option[Index[G]] = { + if (index < invoke_procedure.args.size) Some(InvokeProcedureIndex(invoke_procedure, index + 1)) + else None + } + override def resolve(): Statement[G] = { + if (index < invoke_procedure.args.size) Eval(invoke_procedure.args.apply(index))(invoke_proecedure.args.apply(index).o) + else invoke_procedure.ref.decl.body.get + } } case class InvokeConstructorIndex[G](invoke_constructor: InvokeConstructor[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = ??? - override def resolve(): Statement[G] = ??? + override def make_step(): Option[Index[G]] = { + if (index < invoke_constructor.args.size) Some(InvokeConstructorIndex(invoke_constructor, index + 1)) + else None + } + override def resolve(): Statement[G] = { + if (index < invoke_constructor.args.size) Eval(invoke_constructor.args.apply(index))(invoke_constructor.args.apply(index).o) + else invoke_constructor.ref.decl.body.get + } } case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = ??? - override def resolve(): Statement[G] = ??? + override def make_step(): Option[Index[G]] = { + if (index < invoke_method.args.size) Some(InvokeMethodIndex(invoke_method, index + 1)) + else None + } + override def resolve(): Statement[G] = { + if (index < invoke_method.args.size) Eval(invoke_method.args.apply(index))(invoke_method.args.apply(index).o) + else invoke_method.ref.decl.body.get + } } case class BlockIndex[G](block: Block[G], index: Int) extends Index[G] { From 023ebe3d9b209f998a0ee77a1db4f48a0f2ef4c4 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 12 Feb 2024 09:59:31 +0100 Subject: [PATCH 08/85] Fixed typo --- src/rewrite/vct/rewrite/cfg/Index.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 1d0b4722ee..29d5f1c1d9 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -101,7 +101,7 @@ case class InvokeProcedureIndex[G](invoke_procedure: InvokeProcedure[G], index: else None } override def resolve(): Statement[G] = { - if (index < invoke_procedure.args.size) Eval(invoke_procedure.args.apply(index))(invoke_proecedure.args.apply(index).o) + if (index < invoke_procedure.args.size) Eval(invoke_procedure.args.apply(index))(invoke_procedure.args.apply(index).o) else invoke_procedure.ref.decl.body.get } } From 485a276d34fee544de77cfe023f8e33db689df09 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 12 Feb 2024 12:33:47 +0100 Subject: [PATCH 09/85] Allowed sets of successor indices, added support for fork, added recursion stop for resolve --- .../vct/rewrite/cfg/CFGGenerator.scala | 40 +++--- src/rewrite/vct/rewrite/cfg/Index.scala | 130 ++++++++++-------- 2 files changed, 92 insertions(+), 78 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 556b73bd88..b8115c0daf 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -6,14 +6,16 @@ import scala.collection.mutable case class CFGGenerator[G]() { private val found_labels: mutable.Map[LabelDecl[G], CFGNode[G]] = mutable.HashMap[LabelDecl[G], CFGNode[G]]() - //private val searched_labels: mutable.Map[LabelDecl[G], mutable.Set[CFGNode[G]]] = mutable.HashMap[LabelDecl[G], mutable.Set[CFGNode[G]]]() private val searched_labels: mutable.Map[LabelDecl[G], mutable.Set[CFGNode[G]]] = mutable.HashMap[LabelDecl[G], mutable.Set[CFGNode[G]]]() + private val converted_nodes: mutable.Map[GlobalIndex[G], CFGNode[G]] = mutable.HashMap[GlobalIndex[G], CFGNode[G]]() def generate(entry: InstanceMethod[G]): CFGNode[G] = { convert(entry.body.get, GlobalIndex[G]) } def convert(node: Statement[G], context: GlobalIndex[G]): CFGNode[G] = { + // If a node has already been visited, then it should not be created again + if (converted_nodes.contains(context)) return converted_nodes(context) // Create new node with its successors (if possible) val cfg_node: CFGNode[G] = CFGNode(node, find_successors(node, context)) // Handle labels and goto statements @@ -31,17 +33,15 @@ case class CFGGenerator[G]() { } } } + converted_nodes.addOne((context, cfg_node)) cfg_node } def find_successors(node: Statement[G], context: GlobalIndex[G]): mutable.Set[CFGNode[G]] = node match { case PVLBranch(branches) => mutable.Set(branches.zipWithIndex.map(b => convert(b._1._2, context.enter_scope(node, b._2)))) // TODO: Implement support for statements in expressions - case PVLLoop(init, cond, update, _, body) => { - val cond_node: Eval[G] = Eval(cond)(cond.o) - // TODO: Implement! - ??? - } + case PVLLoop(init, _, _, _, _) => + mutable.Set(convert(init, context.enter_scope(node))) // NonExecutableStatement case LocalDecl(_) => sequential_successor(context) case SpecIgnoreStart() => sequential_successor(context) // What is this? @@ -66,10 +66,13 @@ case class CFGGenerator[G]() { case Refute(assn) => sequential_successor(context) case Inhale(res) => sequential_successor(context) case Assume(assn) => sequential_successor(context) // <-- - case Instantiate(cls, out) => ??? // TODO + case Instantiate(_, out) => sequential_successor(context) case Wait(_) => sequential_successor(context) case Notify(_) => sequential_successor(context) - case Fork(obj) => ??? // TODO: Find run method from expression + case Fork(obj) => { + val run_method: RunMethod[G] = obj.t.asClass.get.cls.decl.declarations.collect { case r: RunMethod[G] => r }.head + val run_inv: MethodInvocation[G] = + } case Join(_) => sequential_successor(context) case Lock(_) => sequential_successor(context) case Unlock(_) => sequential_successor(context) @@ -83,7 +86,7 @@ case class CFGGenerator[G]() { case Extract(contractedStatement) => mutable.Set(convert(contractedStatement, context.enter_scope(node))) // ExceptionalStatement - case Eval(expr) => ??? // TODO + case Eval(expr) => ??? // TODO: Resolve expression statements! // InvocationStatement case InvokeProcedure(ref, args, _, _, givenMap, _) => { if (args.nonEmpty) mutable.Set(convert(Eval(args.head)(args.head.o), context.enter_scope(node))) @@ -120,13 +123,12 @@ case class CFGGenerator[G]() { case IndetBranch(branches) => mutable.Set(branches.zipWithIndex.map(b => convert(b._1, context.enter_scope(node, b._2)))) case Switch(expr, body) => ??? // TODO: Ask Pieter about switch statements - case Loop(init, cond, update, _, body) => { - ??? // TODO - } + case Loop(init, _, _, _, _) => + mutable.Set(convert(init, context.enter_scope(node))) case RangedFor(_, _, body) => mutable.Set(convert(body, context.enter_scope(node))) case TryCatchFinally(body, _, _) => - mutable.Set(convert(body, context.enter_scope(node))) // TODO: Is it safe to ignore the "after" part here? + mutable.Set(convert(body, context.enter_scope(node))) case Synchronized(_, body) => mutable.Set(convert(body, context.enter_scope(node))) case ParInvariant(_, _, content) => @@ -161,9 +163,8 @@ case class CFGGenerator[G]() { case SeqAssign(_, _, value) => sequential_successor(context) // TODO: Statements in expressions case UnresolvedSeqBranch(branches) => mutable.Set(branches.zipWithIndex.map(b => convert(b._1._2, context.enter_scope(node, b._2)))) - case UnresolvedSeqLoop(cond, _, body) => { - ??? // TODO: Implement - } + case UnresolvedSeqLoop(cond, _, _) => + mutable.Set(convert(Eval(cond)(cond.o), context.enter_scope(node))) case SeqBranch(_, yes, no) => no match { case Some(stmt) => mutable.Set(convert(yes, context.enter_scope(node, 0)), convert(stmt, context.enter_scope(node, 1))) case None => mutable.Set(convert(yes, context.enter_scope(node))) @@ -176,11 +177,8 @@ case class CFGGenerator[G]() { mutable.Set(convert(assign, context.enter_scope(node))) } - def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGNode[G]] = { - val next_index: GlobalIndex[G] = index.make_step() - val next_statement: Statement[G] = next_index.resolve() - - } + def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGNode[G]] = + mutable.Set(index.make_step().map(i => convert(i.resolve(), i))) def return_successor(index: GlobalIndex[G]): CFGNode[G] = { ??? // TODO: Implement! diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 29d5f1c1d9..0e62a6fa3d 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -4,26 +4,32 @@ import vct.col.ast._ case class GlobalIndex[G](indices: List[Index[G]]) { - def enter_scope(statement: Statement[G], index: Int = 0): GlobalIndex[G] = - GlobalIndex(Index[G](statement, index) :: indices) - - def leave_scope(): GlobalIndex[G] = - GlobalIndex(indices.tail).make_step() - - def make_step(): GlobalIndex[G] = indices.head.make_step() match { - case Some(idx) => GlobalIndex(idx :: indices.tail) - case None => GlobalIndex(indices.tail).make_step() + def enter_scope(node: Node[G], index: Int = 0): GlobalIndex[G] = + GlobalIndex(Index[G](node, index) :: indices) + + def make_step(): Set[GlobalIndex[G]] = { + if (indices.isEmpty) return Set[GlobalIndex[G]]() + val steps: Set[Option[Index[G]]] = indices.head.make_step() + var res = Set[GlobalIndex[G]]() + for (step <- steps) { + step match { + case Some(index) => res = res ++ GlobalIndex(index +: indices.tail) + case None => res = res ++ GlobalIndex(indices.tail).make_step() + } + } + res } def resolve(): Statement[G] = indices.head.resolve() } sealed trait Index[G] { - def make_step(): Option[Index[G]] + def make_step(): Set[Option[Index[G]]] def resolve(): Statement[G] } object Index { + def apply[G](run_method: RunMethod[G], index: Int): Index[G] = RunMethodIndex(run_method) def apply[G](pvl_branch: PVLBranch[G], index: Int): Index[G] = PVLBranchIndex(pvl_branch, index) def apply[G](pvl_loop: PVLLoop[G], index: Int): Index[G] = PVLLoopIndex(pvl_loop, index) def apply[G](label: Label[G], index: Int): Index[G] = LabelIndex(label) @@ -57,15 +63,22 @@ object Index { def apply[G](communicatex: CommunicateX[G], index: Int): Index[G] = CommunicateXIndex(communicatex) } +case class RunMethodIndex[G](run_method: RunMethod[G]) extends Index[G] { + override def make_step(): Set[Option[Index[G]]] = Set(None) + override def resolve(): Statement[G] = run_method.body.get +} + case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = pvl_branch.branches.apply(index)._2 // TODO: Handle expressions in branch conditions } case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = { - if (index < 3) Some(PVLLoopIndex(pvl_loop, index + 1)) - else None + override def make_step(): Set[Option[Index[G]]] = index match { + case 0 => Set(Some(PVLLoopIndex(pvl_loop, 1))) + case 1 => Set(Some(PVLLoopIndex(pvl_loop, 2)), None) + case 2 => Set(Some(PVLLoopIndex(pvl_loop, 3))) + case 3 => Set(Some(PVLLoopIndex(pvl_loop, 1))) } override def resolve(): Statement[G] = index match { case 0 => pvl_loop.init @@ -76,29 +89,29 @@ case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { } case class LabelIndex[G](label: Label[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = label.stat } case class FramedProofIndex[G](framed_proof: FramedProof[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = framed_proof.body } case class ExtractIndex[G](extract: Extract[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = extract.contractedStatement } case class EvalIndex[G](eval: Eval[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = None // TODO: Implement expressions! + override def make_step(): Set[Option[Index[G]]] = Set(None) // TODO: Implement expressions! override def resolve(): Statement[G] = ??? // TODO: Implement expressions! } case class InvokeProcedureIndex[G](invoke_procedure: InvokeProcedure[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = { - if (index < invoke_procedure.args.size) Some(InvokeProcedureIndex(invoke_procedure, index + 1)) - else None + override def make_step(): Set[Option[Index[G]]] = { + if (index < invoke_procedure.args.size) Set(Some(InvokeProcedureIndex(invoke_procedure, index + 1))) + else Set(None) } override def resolve(): Statement[G] = { if (index < invoke_procedure.args.size) Eval(invoke_procedure.args.apply(index))(invoke_procedure.args.apply(index).o) @@ -107,9 +120,9 @@ case class InvokeProcedureIndex[G](invoke_procedure: InvokeProcedure[G], index: } case class InvokeConstructorIndex[G](invoke_constructor: InvokeConstructor[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = { - if (index < invoke_constructor.args.size) Some(InvokeConstructorIndex(invoke_constructor, index + 1)) - else None + override def make_step(): Set[Option[Index[G]]] = { + if (index < invoke_constructor.args.size) Set(Some(InvokeConstructorIndex(invoke_constructor, index + 1))) + else Set(None) } override def resolve(): Statement[G] = { if (index < invoke_constructor.args.size) Eval(invoke_constructor.args.apply(index))(invoke_constructor.args.apply(index).o) @@ -118,9 +131,9 @@ case class InvokeConstructorIndex[G](invoke_constructor: InvokeConstructor[G], i } case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = { - if (index < invoke_method.args.size) Some(InvokeMethodIndex(invoke_method, index + 1)) - else None + override def make_step(): Set[Option[Index[G]]] = { + if (index < invoke_method.args.size) Set(Some(InvokeMethodIndex(invoke_method, index + 1))) + else Set(None) } override def resolve(): Statement[G] = { if (index < invoke_method.args.size) Eval(invoke_method.args.apply(index))(invoke_method.args.apply(index).o) @@ -129,37 +142,39 @@ case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) exte } case class BlockIndex[G](block: Block[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = { - if (index < block.statements.size - 1) Some(BlockIndex(block, index + 1)) - else None + override def make_step(): Set[Option[Index[G]]] = { + if (index < block.statements.size - 1) Set(Some(BlockIndex(block, index + 1))) + else Set(None) } override def resolve(): Statement[G] = block.statements.apply(index) } case class ScopeIndex[G](scope: Scope[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = scope.body } case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = branch.branches.apply(index)._2 } case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = indet_branch.branches.apply(index) } case class SwitchIndex[G](switch: Switch[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = switch.body } case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = { - if (index < 3) Some(LoopIndex(loop, index + 1)) - else None + override def make_step(): Set[Option[Index[G]]] = index match { + case 0 => Set(Some(LoopIndex(loop, 1))) + case 1 => Set(Some(LoopIndex(loop, 2)), None) + case 2 => Set(Some(LoopIndex(loop, 3))) + case 3 => Set(Some(LoopIndex(loop, 1))) } override def resolve(): Statement[G] = index match { case 0 => loop.init @@ -170,70 +185,71 @@ case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { } case class RangedForIndex[G](ranged_for: RangedFor[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = ranged_for.body } case class TryCatchFinallyIndex[G](try_catch_finally: TryCatchFinally[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = { - if (index == 0) Some(TryCatchFinallyIndex(try_catch_finally, 1)) - else None + override def make_step(): Set[Option[Index[G]]] = { + if (index != 1) Set(Some(TryCatchFinallyIndex(try_catch_finally, 1))) + else Set(None) } override def resolve(): Statement[G] = index match { case 0 => try_catch_finally.body case 1 => try_catch_finally.after + case _ => try_catch_finally.catches.apply(index - 2).body } } case class SynchronizedIndex[G](synchronized: Synchronized[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = synchronized.body } case class ParInvariantIndex[G](par_invariant: ParInvariant[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = par_invariant.content } case class ParAtomicIndex[G](par_atomic: ParAtomic[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = par_atomic.content } case class ParBarrierIndex[G](par_barrier: ParBarrier[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = par_barrier.content } case class VecBlockIndex[G](vec_block: VecBlock[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = vec_block.content } case class WandPackageIndex[G](wand_package: WandPackage[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = wand_package.proof } case class ModelDoIndex[G](model_do: ModelDo[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = model_do.impl } case class CPPLifetimeScopeIndex[G](cpp_lifetime_scope: CPPLifetimeScope[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = cpp_lifetime_scope.body } case class UnresolvedSeqBranchIndex[G](unresolved_seq_branch: UnresolvedSeqBranch[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = unresolved_seq_branch.branches.apply(index)._2 } case class UnresolvedSeqLoopIndex[G](unresolved_seq_loop: UnresolvedSeqLoop[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = { - if (index == 0) Some(UnresolvedSeqLoopIndex(unresolved_seq_loop, 1)) - else None + override def make_step(): Set[Option[Index[G]]] = index match { + case 0 => Set(Some(UnresolvedSeqLoopIndex(unresolved_seq_loop, 1)), None) + case 1 => Set(Some(UnresolvedSeqLoopIndex(unresolved_seq_loop, 0))) } override def resolve(): Statement[G] = index match { case 0 => Eval(unresolved_seq_loop.cond)(unresolved_seq_loop.cond.o) @@ -242,7 +258,7 @@ case class UnresolvedSeqLoopIndex[G](unresolved_seq_loop: UnresolvedSeqLoop[G], } case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = index match { case 0 => seq_branch.yes case 1 => seq_branch.no.get @@ -250,16 +266,16 @@ case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index } case class SeqLoopIndex[G](seq_loop: SeqLoop[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = seq_loop.body } case class VeyMontAssignExpressionIndex[G](veymont_assign_expression: VeyMontAssignExpression[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = veymont_assign_expression.assign } case class CommunicateXIndex[G](communicatex: CommunicateX[G]) extends Index[G] { - override def make_step(): Option[Index[G]] = None + override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = communicatex.assign } \ No newline at end of file From 967a6c40f664af549c50a414641e65e560f2a72d Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 13 Feb 2024 09:48:12 +0100 Subject: [PATCH 10/85] Added support for break, continue, return and throw and started on switch statements --- .../vct/rewrite/cfg/CFGGenerator.scala | 39 +++++++++----- src/rewrite/vct/rewrite/cfg/CFGNode.scala | 2 +- src/rewrite/vct/rewrite/cfg/Index.scala | 52 +++++++++++++++++++ 3 files changed, 78 insertions(+), 15 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index b8115c0daf..c2984b7531 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -50,8 +50,8 @@ case class CFGGenerator[G]() { case Assign(target, value) => sequential_successor(context) // TODO: Implement support for statements in expressions case Send(_, _, res) => sequential_successor(context) // TODO: Implement support for statements in expressions case Recv(_) => sequential_successor(context) - case DefaultCase() => ??? // TODO: Ask Pieter about switch statements - case Case(pattern) => ??? + case DefaultCase() => sequential_successor(context) + case Case(pattern) => sequential_successor(context) case Label(_, stat) => mutable.Set(convert(stat, context.enter_scope(node))) case Goto(lbl) => { @@ -71,7 +71,7 @@ case class CFGGenerator[G]() { case Notify(_) => sequential_successor(context) case Fork(obj) => { val run_method: RunMethod[G] = obj.t.asClass.get.cls.decl.declarations.collect { case r: RunMethod[G] => r }.head - val run_inv: MethodInvocation[G] = + sequential_successor(context).addOne(convert(run_method.body.get, context.enter_scope(run_method))) } case Join(_) => sequential_successor(context) case Lock(_) => sequential_successor(context) @@ -86,7 +86,7 @@ case class CFGGenerator[G]() { case Extract(contractedStatement) => mutable.Set(convert(contractedStatement, context.enter_scope(node))) // ExceptionalStatement - case Eval(expr) => ??? // TODO: Resolve expression statements! + case Eval(expr) => ??? // TODO: Implement side effects in expressions (see ResolveExpressionSideEffects.scala) // InvocationStatement case InvokeProcedure(ref, args, _, _, givenMap, _) => { if (args.nonEmpty) mutable.Set(convert(Eval(args.head)(args.head.o), context.enter_scope(node))) @@ -122,7 +122,10 @@ case class CFGGenerator[G]() { mutable.Set(branches.zipWithIndex.map(b => convert(b._1._2, context.enter_scope(node, b._2)))) // TODO: Handle statements in condition expressions! case IndetBranch(branches) => mutable.Set(branches.zipWithIndex.map(b => convert(b._1, context.enter_scope(node, b._2)))) - case Switch(expr, body) => ??? // TODO: Ask Pieter about switch statements + case Switch(expr, body) => { // TODO: Handle expressions + val cases: mutable.Set[(SwitchCase[G], GlobalIndex[G])] = find_all_cases(body, context) + cases.map(t => convert(t._1, t._2)) + } case Loop(init, _, _, _, _) => mutable.Set(convert(init, context.enter_scope(node))) case RangedFor(_, _, body) => @@ -177,22 +180,30 @@ case class CFGGenerator[G]() { mutable.Set(convert(assign, context.enter_scope(node))) } - def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGNode[G]] = + private def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGNode[G]] = mutable.Set(index.make_step().map(i => convert(i.resolve(), i))) - def return_successor(index: GlobalIndex[G]): CFGNode[G] = { - ??? // TODO: Implement! + private def return_successor(index: GlobalIndex[G]): CFGNode[G] = { + val new_index: GlobalIndex[G] = index.return_from_call() + convert(new_index.resolve(), new_index) + } + + private def exception_successor(exception: Expr[G], index: GlobalIndex[G]): CFGNode[G] = { + val new_index = index.handle_exception(exception) + convert(new_index.resolve(), new_index) } - def exception_successor(exception: Expr[G], index: GlobalIndex[G]): CFGNode[G] = { - ??? // TODO: Implement! + private def break_successor(index: GlobalIndex[G]): CFGNode[G] = { + val new_index = index.handle_break() + convert(new_index.resolve(), new_index) } - def break_successor(index: GlobalIndex[G]): CFGNode[G] = { - ??? // TODO: Implement! + private def continue_successor(index: GlobalIndex[G]): CFGNode[G] = { + val new_index = index.continue_innermost_loop() + convert(new_index.resolve(), new_index) } - def continue_successor(index: GlobalIndex[G]): CFGNode[G] = { - ??? // TODO: Implement! + private def find_all_cases(switch_body: Statement[G], index: GlobalIndex[G]): mutable.Set[(SwitchCase[G], GlobalIndex[G])] = { + ??? } } diff --git a/src/rewrite/vct/rewrite/cfg/CFGNode.scala b/src/rewrite/vct/rewrite/cfg/CFGNode.scala index e86db9805a..6a6761b4eb 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGNode.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGNode.scala @@ -2,4 +2,4 @@ package vct.rewrite.cfg import vct.col.ast.Node -case class CFGNode[G](ast_node: Node[G], successors: scala.collection.mutable.Set[CFGNode[G]]) \ No newline at end of file +case class CFGNode[G](ast_node: Node[G], successors: scala.collection.mutable.Set[CFGNode[G]]) // TODO: Add condition information to edges \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 0e62a6fa3d..66309ab22d 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -21,6 +21,58 @@ case class GlobalIndex[G](indices: List[Index[G]]) { } def resolve(): Statement[G] = indices.head.resolve() + + def return_from_call(): GlobalIndex[G] = { + // Find innermost subroutine call + val stack: List[Index[G]] = indices.dropWhile { + case InvokeProcedureIndex(_, _) | InvokeMethodIndex(_, _) => false + case _ => true + } + // Find the next statement + // TODO: Does this always return exactly one next step? + GlobalIndex(stack.tail).make_step().head + } + + def handle_exception(e: Expr[G]): GlobalIndex[G] = { + // Find innermost try-catch block of appropriate type + val stack: List[Index[G]] = indices.dropWhile { + case TryCatchFinallyIndex(stmt, _) => !stmt.catches.exists(c => c.decl.t.equals(e.t)) + case _ => true + } + // Unhandled exception + if (stack.isEmpty) return GlobalIndex(stack) + // Return to exception handler and go to catch block + stack.head match { + case TryCatchFinallyIndex(stmt, _) => GlobalIndex(TryCatchFinallyIndex(stmt, stmt.catches.indexWhere(c => c.decl.t.equals(e.t)) + 2) +: stack.tail) + } + } + + def handle_break(): GlobalIndex[G] = { + // Find innermost occurrence of either a loop or a switch statement + val stack: List[Index[G]] = indices.dropWhile { + case PVLLoopIndex(_, 0) | LoopIndex(_, 0) => true + case PVLLoopIndex(_, _) | LoopIndex(_, _) | RangedForIndex(_) | UnresolvedSeqLoopIndex(_, _) | SeqLoopIndex(_) | SwitchIndex(_) => false + case _ => true + } + // Find the next statement + // TODO: Does this always return exactly one next step? + GlobalIndex(stack.tail).make_step().head + } + + def continue_innermost_loop(): GlobalIndex[G] = { + // Find innermost loop that could be the target of continue + val stack: List[Index[G]] = indices.dropWhile { + case PVLLoopIndex(_, 0) | LoopIndex(_, 0) => true // If some godless heathen calls break in the init of a loop, don't consider that loop + case PVLLoopIndex(_, _) | LoopIndex(_, _) | RangedForIndex(_) | UnresolvedSeqLoopIndex(_, _) | SeqLoopIndex(_) => false + case _ => true + } + stack.head match { + case PVLLoopIndex(pvl_loop, _) => GlobalIndex(PVLLoopIndex(pvl_loop, 1) +: stack.tail) + case LoopIndex(loop, _) => GlobalIndex(LoopIndex(loop, 1) +: stack.tail) + case UnresolvedSeqLoopIndex(unresolved_seq_loop, _) => GlobalIndex(UnresolvedSeqLoopIndex(unresolved_seq_loop, 0) +: stack.tail) + case RangedForIndex(_) | SeqLoopIndex(_) => GlobalIndex(stack) + } + } } sealed trait Index[G] { From d36e867bd9c543bf3ac56fd79a68f8e0d397bb38 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 13 Feb 2024 14:21:43 +0100 Subject: [PATCH 11/85] Added general support for statements hidden in expressions with side effects --- .../vct/rewrite/cfg/CFGGenerator.scala | 38 ++++++++++++------- src/rewrite/vct/rewrite/cfg/CFGNode.scala | 7 +++- src/rewrite/vct/rewrite/cfg/Index.scala | 25 +++++++++--- src/rewrite/vct/rewrite/cfg/Utils.scala | 31 +++++++++++++++ 4 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 src/rewrite/vct/rewrite/cfg/Utils.scala diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index c2984b7531..7ffe5566b4 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -1,6 +1,8 @@ package vct.rewrite.cfg import vct.col.ast._ +import vct.col.origin.Origin +import vct.col.ref.{Ref, DirectRef} import scala.collection.mutable @@ -87,6 +89,16 @@ case class CFGGenerator[G]() { mutable.Set(convert(contractedStatement, context.enter_scope(node))) // ExceptionalStatement case Eval(expr) => ??? // TODO: Implement side effects in expressions (see ResolveExpressionSideEffects.scala) + case Return(result) => mutable.Set(return_successor(context)) + case Throw(obj) => mutable.Set(exception_successor(obj, context)) + case Break(label) => label match { + case Some(ref) => ??? // TODO: Handle break label! + case None => mutable.Set(break_successor(context)) + } + case Continue(label) => label match { + case Some(ref) => ??? // TODO: Handle continue label! + case None => mutable.Set(continue_successor(context)) + } // InvocationStatement case InvokeProcedure(ref, args, _, _, givenMap, _) => { if (args.nonEmpty) mutable.Set(convert(Eval(args.head)(args.head.o), context.enter_scope(node))) @@ -103,16 +115,6 @@ case class CFGGenerator[G]() { else if (ref.decl.body.nonEmpty) mutable.Set(convert(ref.decl.body.get, context.enter_scope(node))) else sequential_successor(context) } - case Return(result) => mutable.Set(return_successor(context)) - case Throw(obj) => mutable.Set(exception_successor(obj, context)) - case Break(label) => label match { - case Some(ref) => ??? // TODO: Handle break label! - case None => mutable.Set(break_successor(context)) - } - case Continue(label) => label match { - case Some(ref) => ??? // TODO: Handle continue label! - case None => mutable.Set(continue_successor(context)) - } // CompositeStatement case Block(statements) => mutable.Set(convert(statements.head, context.enter_scope(node))) @@ -190,6 +192,8 @@ case class CFGGenerator[G]() { private def exception_successor(exception: Expr[G], index: GlobalIndex[G]): CFGNode[G] = { val new_index = index.handle_exception(exception) + // Terminate on unhandled exception + if (new_index.indices.isEmpty) return CFGNode(exception, mutable.Set()) convert(new_index.resolve(), new_index) } @@ -203,7 +207,15 @@ case class CFGGenerator[G]() { convert(new_index.resolve(), new_index) } - private def find_all_cases(switch_body: Statement[G], index: GlobalIndex[G]): mutable.Set[(SwitchCase[G], GlobalIndex[G])] = { - ??? + private def find_all_cases(body: Statement[G], index: GlobalIndex[G]): mutable.Set[(SwitchCase[G], GlobalIndex[G])] = body match { + // Recursion on statements that can contain case statements + case Label(_, stmt) => find_all_cases(stmt, index.enter_scope(body)) + case Block(stmts) => mutable.Set(stmts.zipWithIndex.flatMap(t => find_all_cases(t._1, index.enter_scope(body, t._2)))) + case Scope(_, stmt) => find_all_cases(stmt, index.enter_scope(body)) + // Recursion end + case c: SwitchCase[G] => mutable.Set((c, index)) + case _ => mutable.Set() // TODO: Assuming that there are no cases in deeper structures (branches, loops etc.) } -} + + private def handle_expression_successor(expr: Expr[G], index: GlobalIndex[G]): Option[CFGNode[G]] = ??? +} \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/cfg/CFGNode.scala b/src/rewrite/vct/rewrite/cfg/CFGNode.scala index 6a6761b4eb..b6b0ce6974 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGNode.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGNode.scala @@ -1,5 +1,8 @@ package vct.rewrite.cfg -import vct.col.ast.Node +import vct.col.ast.{Expr, Node} -case class CFGNode[G](ast_node: Node[G], successors: scala.collection.mutable.Set[CFGNode[G]]) // TODO: Add condition information to edges \ No newline at end of file +import scala.collection.mutable + +case class CFGNode[G](ast_node: Node[G], successors: mutable.Set[CFGNode[G]]) // TODO: Add condition information to edges +case class CFGEdge[G](target: CFGNode[G], condition: Option[Expr[G]]) \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 66309ab22d..0f96c495c1 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -36,7 +36,7 @@ case class GlobalIndex[G](indices: List[Index[G]]) { def handle_exception(e: Expr[G]): GlobalIndex[G] = { // Find innermost try-catch block of appropriate type val stack: List[Index[G]] = indices.dropWhile { - case TryCatchFinallyIndex(stmt, _) => !stmt.catches.exists(c => c.decl.t.equals(e.t)) + case TryCatchFinallyIndex(stmt, 0) => !stmt.catches.exists(c => c.decl.t.equals(e.t)) case _ => true } // Unhandled exception @@ -50,7 +50,7 @@ case class GlobalIndex[G](indices: List[Index[G]]) { def handle_break(): GlobalIndex[G] = { // Find innermost occurrence of either a loop or a switch statement val stack: List[Index[G]] = indices.dropWhile { - case PVLLoopIndex(_, 0) | LoopIndex(_, 0) => true + case PVLLoopIndex(_, 0) | LoopIndex(_, 0) => true // If some godless heathen calls break in the init of a loop, don't consider that loop case PVLLoopIndex(_, _) | LoopIndex(_, _) | RangedForIndex(_) | UnresolvedSeqLoopIndex(_, _) | SeqLoopIndex(_) | SwitchIndex(_) => false case _ => true } @@ -62,7 +62,7 @@ case class GlobalIndex[G](indices: List[Index[G]]) { def continue_innermost_loop(): GlobalIndex[G] = { // Find innermost loop that could be the target of continue val stack: List[Index[G]] = indices.dropWhile { - case PVLLoopIndex(_, 0) | LoopIndex(_, 0) => true // If some godless heathen calls break in the init of a loop, don't consider that loop + case PVLLoopIndex(_, 0) | LoopIndex(_, 0) => true case PVLLoopIndex(_, _) | LoopIndex(_, _) | RangedForIndex(_) | UnresolvedSeqLoopIndex(_, _) | SeqLoopIndex(_) => false case _ => true } @@ -81,6 +81,7 @@ sealed trait Index[G] { } object Index { + def apply[G](instance_method: InstanceMethod[G], index: Int): Index[G] = InitialIndex(instance_method) def apply[G](run_method: RunMethod[G], index: Int): Index[G] = RunMethodIndex(run_method) def apply[G](pvl_branch: PVLBranch[G], index: Int): Index[G] = PVLBranchIndex(pvl_branch, index) def apply[G](pvl_loop: PVLLoop[G], index: Int): Index[G] = PVLLoopIndex(pvl_loop, index) @@ -115,6 +116,11 @@ object Index { def apply[G](communicatex: CommunicateX[G], index: Int): Index[G] = CommunicateXIndex(communicatex) } +case class InitialIndex[G](instance_method: InstanceMethod[G]) extends Index[G] { + override def make_step(): Set[Option[Index[G]]] = Set(None) + override def resolve(): Statement[G] = instance_method.body.get +} + case class RunMethodIndex[G](run_method: RunMethod[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = run_method.body.get @@ -155,9 +161,16 @@ case class ExtractIndex[G](extract: Extract[G]) extends Index[G] { override def resolve(): Statement[G] = extract.contractedStatement } -case class EvalIndex[G](eval: Eval[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) // TODO: Implement expressions! - override def resolve(): Statement[G] = ??? // TODO: Implement expressions! +case class EvalIndex[G](eval: Eval[G], index: Int, subexpressions: Seq[Statement[G]]) extends Index[G] { + def this(eval: Eval[G], index: Int) = this(eval, index, Utils.find_all_subexpressions(eval.expr)) + override def make_step(): Set[Option[Index[G]]] = { + if (index < subexpressions.size - 1) Set(Some(EvalIndex(eval, index + 1))) + else Set(None) + } + override def resolve(): Statement[G] = subexpressions.apply(index) +} +object EvalIndex { + def apply[G](eval: Eval[G], index: Int): EvalIndex[G] = new EvalIndex(eval, index) } case class InvokeProcedureIndex[G](invoke_procedure: InvokeProcedure[G], index: Int) extends Index[G] { diff --git a/src/rewrite/vct/rewrite/cfg/Utils.scala b/src/rewrite/vct/rewrite/cfg/Utils.scala new file mode 100644 index 0000000000..5d2417e8aa --- /dev/null +++ b/src/rewrite/vct/rewrite/cfg/Utils.scala @@ -0,0 +1,31 @@ +package vct.rewrite.cfg + +import vct.col.ast._ +import vct.col.origin.Origin +import vct.col.ref.{DirectRef, Ref} + +object Utils { + + def find_all_subexpressions[G](expr: Expr[G]): Seq[Statement[G]] = expr match { + case pae @ PreAssignExpression(target, value) => Seq(Assign(target, value)(pae.blame)(pae.o)) + case pae @ PostAssignExpression(target, value) => Seq(Assign(target, value)(pae.blame)(pae.o)) + case With(pre, value) => ??? + case Then(value, post) => ??? + case mi @ MethodInvocation(obj, ref, args, outArgs, typeArgs, givenMap, yields) => + Seq(InvokeMethod(obj, ref, args, outArgs, typeArgs, givenMap, yields)(mi.blame)(mi.o)) + case ci @ ConstructorInvocation(ref, args, outArgs, typeArgs, givenMap, yields) => + Seq(InvokeConstructor(ref, get_out_variable(ref.decl.cls, ci.o), args, outArgs, typeArgs, givenMap, yields)(ci.blame)(ci.o)) + case pi @ ProcedureInvocation(ref, args, outArgs, typeArgs, givenMap, yields) => + Seq(InvokeProcedure(ref, args, outArgs, typeArgs, givenMap, yields)(pi.blame)(pi.o)) + case no @ NewObject(cls) => Seq(Instantiate(cls, get_out_variable(cls, no.o))(no.o)) + // TODO: Consider conditions for control flow graph + case Select(condition, whenTrue, whenFalse) => Seq(condition, whenTrue, whenFalse).flatMap(e => find_all_subexpressions(e)) + case Implies(left, right) => Seq(left, right).flatMap(e => find_all_subexpressions(e)) + case And(left, right) => Seq(left, right).flatMap(e => find_all_subexpressions(e)) + case Or(left, right) => Seq(left, right).flatMap(e => find_all_subexpressions(e)) + // + case _ => expr.subnodes.collect{ case ex: Expr[G] => ex }.flatMap(e => find_all_subexpressions(e)) + } + + private def get_out_variable[G](cls: Ref[G, Class[G]], o: Origin): Local[G] = Local(new DirectRef(new Variable(TClass(cls))(o)))(o) +} From 7aedcd456935c071c50de05311ae522731c3f825 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 13 Feb 2024 17:16:47 +0100 Subject: [PATCH 12/85] Improved support for expressions with side effects --- .../vct/rewrite/cfg/CFGGenerator.scala | 84 ++++++++++--------- src/rewrite/vct/rewrite/cfg/Index.scala | 73 ++++++++++++++-- src/rewrite/vct/rewrite/cfg/Utils.scala | 12 +++ 3 files changed, 120 insertions(+), 49 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 7ffe5566b4..786b38d62d 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -40,20 +40,22 @@ case class CFGGenerator[G]() { } def find_successors(node: Statement[G], context: GlobalIndex[G]): mutable.Set[CFGNode[G]] = node match { - case PVLBranch(branches) => - mutable.Set(branches.zipWithIndex.map(b => convert(b._1._2, context.enter_scope(node, b._2)))) // TODO: Implement support for statements in expressions + case PVLBranch(branches) => { + val eval: Eval[G] = Eval(branches.head._1)(branches.head._1.o) + mutable.Set(convert(eval, context.enter_scope(node))) + } case PVLLoop(init, _, _, _, _) => mutable.Set(convert(init, context.enter_scope(node))) // NonExecutableStatement case LocalDecl(_) => sequential_successor(context) - case SpecIgnoreStart() => sequential_successor(context) // What is this? + case SpecIgnoreStart() => sequential_successor(context) // TODO: What is this? case SpecIgnoreEnd() => sequential_successor(context) // NormallyCompletingStatement - case Assign(target, value) => sequential_successor(context) // TODO: Implement support for statements in expressions - case Send(_, _, res) => sequential_successor(context) // TODO: Implement support for statements in expressions + case Assign(target, value) => sequential_successor(context) // TODO: Implement support for multiple expressions in container statement + case Send(_, _, res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) case Recv(_) => sequential_successor(context) case DefaultCase() => sequential_successor(context) - case Case(pattern) => sequential_successor(context) + case Case(pattern) => sequential_successor(context) // TODO: Handle expression side effects in switch case conditions case Label(_, stat) => mutable.Set(convert(stat, context.enter_scope(node))) case Goto(lbl) => { @@ -63,12 +65,12 @@ case class CFGGenerator[G]() { case None => mutable.Set() } } - case Exhale(res) => sequential_successor(context) // TODO: Can expressions in specifications be ignored? - case Assert(res) => sequential_successor(context) - case Refute(assn) => sequential_successor(context) - case Inhale(res) => sequential_successor(context) - case Assume(assn) => sequential_successor(context) // <-- - case Instantiate(_, out) => sequential_successor(context) + case Exhale(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) + case Assert(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) + case Refute(assn) => handle_expression_container(node, Eval(assn)(assn.o), context, sequential_successor(context)) + case Inhale(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) + case Assume(assn) => handle_expression_container(node, Eval(assn)(assn.o), context, sequential_successor(context)) + case Instantiate(_, out) => handle_expression_container(node, Eval(out)(out.o), context, sequential_successor(context)) case Wait(_) => sequential_successor(context) case Notify(_) => sequential_successor(context) case Fork(obj) => { @@ -79,17 +81,17 @@ case class CFGGenerator[G]() { case Lock(_) => sequential_successor(context) case Unlock(_) => sequential_successor(context) case Commit(_) => sequential_successor(context) - case Fold(res) => sequential_successor(context) // TODO: Can expressions in specifications be ignored? - case Unfold(res) => sequential_successor(context) - case WandApply(res) => sequential_successor(context) // <-- + case Fold(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) + case Unfold(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) + case WandApply(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) case Havoc(_) => sequential_successor(context) case FramedProof(pre, body, post) => - mutable.Set(convert(body, context.enter_scope(node))) // TODO: Can expressions in specifications be ignored? + ??? // TODO: Support multiple expressions in a container statement case Extract(contractedStatement) => mutable.Set(convert(contractedStatement, context.enter_scope(node))) // ExceptionalStatement - case Eval(expr) => ??? // TODO: Implement side effects in expressions (see ResolveExpressionSideEffects.scala) - case Return(result) => mutable.Set(return_successor(context)) + case Eval(_) => sequential_successor(context.enter_scope(node)) // TODO: Is this correct? + case Return(result) => handle_expression_container(node, Eval(result)(result.o), context, mutable.Set(return_successor(context))) case Throw(obj) => mutable.Set(exception_successor(obj, context)) case Break(label) => label match { case Some(ref) => ??? // TODO: Handle break label! @@ -100,7 +102,7 @@ case class CFGGenerator[G]() { case None => mutable.Set(continue_successor(context)) } // InvocationStatement - case InvokeProcedure(ref, args, _, _, givenMap, _) => { + case InvokeProcedure(ref, args, _, _, _, _) => { if (args.nonEmpty) mutable.Set(convert(Eval(args.head)(args.head.o), context.enter_scope(node))) else if (ref.decl.body.nonEmpty) mutable.Set(convert(ref.decl.body.get, context.enter_scope(node))) else sequential_successor(context) @@ -120,13 +122,15 @@ case class CFGGenerator[G]() { mutable.Set(convert(statements.head, context.enter_scope(node))) case Scope(_, body) => mutable.Set(convert(body, context.enter_scope(node))) - case Branch(branches) => - mutable.Set(branches.zipWithIndex.map(b => convert(b._1._2, context.enter_scope(node, b._2)))) // TODO: Handle statements in condition expressions! + case Branch(branches) => { + val eval: Eval[G] = Eval(branches.head._1)(branches.head._1.o) + mutable.Set(convert(eval, context.enter_scope(node))) + } case IndetBranch(branches) => mutable.Set(branches.zipWithIndex.map(b => convert(b._1, context.enter_scope(node, b._2)))) - case Switch(expr, body) => { // TODO: Handle expressions - val cases: mutable.Set[(SwitchCase[G], GlobalIndex[G])] = find_all_cases(body, context) - cases.map(t => convert(t._1, t._2)) + case Switch(expr, body) => { + val cases: mutable.Set[(SwitchCase[G], GlobalIndex[G])] = Utils.find_all_cases(body, context.enter_scope(node)) + cases.map(t => convert(t._1, t._2)) // TODO: Handle side effects in switch statement conditions } case Loop(init, _, _, _, _) => mutable.Set(convert(init, context.enter_scope(node))) @@ -151,7 +155,7 @@ case class CFGGenerator[G]() { ??? // TODO // CStatement case CDeclarationStatement(_) => sequential_successor(context) - case CGoto(label) => ??? + case CGoto(label) => ??? // I'm not dealing with string labels // CPPStatement case CPPDeclarationStatement(_) => sequential_successor(context) case CPPLifetimeScope(body) => @@ -159,13 +163,13 @@ case class CFGGenerator[G]() { case JavaLocalDeclarationStatement(_) => sequential_successor(context) // SilverStatement case SilverNewRef(_, _) => sequential_successor(context) - case SilverFieldAssign(_, _, value) => sequential_successor(context) // TODO: Statements in expressions - case SilverLocalAssign(_, value) => sequential_successor(context) // TODO: Statements in expressions + case SilverFieldAssign(_, _, value) => handle_expression_container(node, Eval(value)(value.o), context, sequential_successor(context)) + case SilverLocalAssign(_, value) => handle_expression_container(node, Eval(value)(value.o), context, sequential_successor(context)) // OTHER case PVLCommunicate(_, _) => sequential_successor(context) - case PVLSeqAssign(_, _, value) => sequential_successor(context) // TODO: Statements in expressions + case PVLSeqAssign(_, _, value) => handle_expression_container(node, Eval(value)(value.o), context, sequential_successor(context)) case Communicate(_, _) => sequential_successor(context) - case SeqAssign(_, _, value) => sequential_successor(context) // TODO: Statements in expressions + case SeqAssign(_, _, value) => handle_expression_container(node, Eval(value)(value.o), context, sequential_successor(context)) case UnresolvedSeqBranch(branches) => mutable.Set(branches.zipWithIndex.map(b => convert(b._1._2, context.enter_scope(node, b._2)))) case UnresolvedSeqLoop(cond, _, _) => @@ -182,6 +186,16 @@ case class CFGGenerator[G]() { mutable.Set(convert(assign, context.enter_scope(node))) } + private def handle_expression_container(statement: Statement[G], + expression: Eval[G], + context: GlobalIndex[G], + successors_to_statement: mutable.Set[CFGNode[G]]): mutable.Set[CFGNode[G]] = { + context.indices.head match { + case ExpressionContainerIndex(stmt, 1) if stmt == statement => successors_to_statement + case _ => mutable.Set(convert(expression, context.enter_scope(expression))) // TODO: Handle containers with multiple expressions + } + } + private def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGNode[G]] = mutable.Set(index.make_step().map(i => convert(i.resolve(), i))) @@ -206,16 +220,4 @@ case class CFGGenerator[G]() { val new_index = index.continue_innermost_loop() convert(new_index.resolve(), new_index) } - - private def find_all_cases(body: Statement[G], index: GlobalIndex[G]): mutable.Set[(SwitchCase[G], GlobalIndex[G])] = body match { - // Recursion on statements that can contain case statements - case Label(_, stmt) => find_all_cases(stmt, index.enter_scope(body)) - case Block(stmts) => mutable.Set(stmts.zipWithIndex.flatMap(t => find_all_cases(t._1, index.enter_scope(body, t._2)))) - case Scope(_, stmt) => find_all_cases(stmt, index.enter_scope(body)) - // Recursion end - case c: SwitchCase[G] => mutable.Set((c, index)) - case _ => mutable.Set() // TODO: Assuming that there are no cases in deeper structures (branches, loops etc.) - } - - private def handle_expression_successor(expr: Expr[G], index: GlobalIndex[G]): Option[CFGNode[G]] = ??? } \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 0f96c495c1..cdc03231a9 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -56,6 +56,7 @@ case class GlobalIndex[G](indices: List[Index[G]]) { } // Find the next statement // TODO: Does this always return exactly one next step? + // No, e.g.: another loop in the update statement of a loop GlobalIndex(stack.tail).make_step().head } @@ -76,7 +77,20 @@ case class GlobalIndex[G](indices: List[Index[G]]) { } sealed trait Index[G] { + /** + * Defines the set of possible next steps. An index in the returned set indicates that this index can replace the + * previous index at the top level of the index stack. A None value indicates that a step is possible, but it reaches + * outside the scope of this index to the index below. + * + * @return A set of all steps possible from the current index + */ def make_step(): Set[Option[Index[G]]] + + /** + * Returns the statement that corresponds to the current index. + * + * @return The statement at the current index + */ def resolve(): Statement[G] } @@ -86,7 +100,7 @@ object Index { def apply[G](pvl_branch: PVLBranch[G], index: Int): Index[G] = PVLBranchIndex(pvl_branch, index) def apply[G](pvl_loop: PVLLoop[G], index: Int): Index[G] = PVLLoopIndex(pvl_loop, index) def apply[G](label: Label[G], index: Int): Index[G] = LabelIndex(label) - def apply[G](framed_proof: FramedProof[G], index: Int): Index[G] = FramedProofIndex(framed_proof) + def apply[G](framed_proof: FramedProof[G], index: Int): Index[G] = FramedProofIndex(framed_proof, index) def apply[G](extract: Extract[G], index: Int): Index[G] = ExtractIndex(extract) def apply[G](eval: Eval[G], index: Int): Index[G] = EvalIndex(eval, index) def apply[G](invoke_procedure: InvokeProcedure[G], index: Int): Index[G] = InvokeProcedureIndex(invoke_procedure, index) @@ -114,6 +128,8 @@ object Index { def apply[G](seq_loop: SeqLoop[G], index: Int): Index[G] = SeqLoopIndex(seq_loop) def apply[G](veymont_assign_expression: VeyMontAssignExpression[G], index: Int): Index[G] = VeyMontAssignExpressionIndex(veymont_assign_expression) def apply[G](communicatex: CommunicateX[G], index: Int): Index[G] = CommunicateXIndex(communicatex) + def apply[G](assign: Assign[G], index: Int): Index[G] = AssignmentIndex(assign, index) + def apply[G](statement: Statement[G], index: Int): Index[G] = ExpressionContainerIndex(statement, index) } case class InitialIndex[G](instance_method: InstanceMethod[G]) extends Index[G] { @@ -126,9 +142,33 @@ case class RunMethodIndex[G](run_method: RunMethod[G]) extends Index[G] { override def resolve(): Statement[G] = run_method.body.get } +case class ExpressionContainerIndex[G](statement: Statement[G], index: Int) extends Index[G] { + override def make_step(): Set[Option[Index[G]]] = { + if (index == 0) Set(Some(ExpressionContainerIndex(statement, 1))) + else Set(None) + } + override def resolve(): Statement[G] = statement +} + +// TODO: Is this really a good idea? +case class AssignmentIndex[G](assign: Assign[G], index: Int) extends Index[G] { + override def make_step(): Set[Option[Index[G]]] = ??? + override def resolve(): Statement[G] = ??? +} + case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) - override def resolve(): Statement[G] = pvl_branch.branches.apply(index)._2 // TODO: Handle expressions in branch conditions + override def make_step(): Set[Option[Index[G]]] = { + // Indices 0, 2, 4, ... are the conditions, indices 1, 3, 5, ... are the branch bodies + if (index % 2 == 0 && index < 2 * (pvl_branch.branches.size - 1)) + Set(Some(PVLBranchIndex(pvl_branch, index + 2)), Some(PVLBranchIndex(pvl_branch, index + 1))) + else if (index == 2 * (pvl_branch.branches.size - 1)) + Set(Some(PVLBranchIndex(pvl_branch, index + 1)), None) + else Set(None) + } + override def resolve(): Statement[G] = { + if (index % 2 == 0) Eval(pvl_branch.branches.apply(index / 2)._1)(pvl_branch.branches.apply(index / 2)._1.o) + else pvl_branch.branches.apply((index - 1) / 2)._2 + } } case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { @@ -151,9 +191,16 @@ case class LabelIndex[G](label: Label[G]) extends Index[G] { override def resolve(): Statement[G] = label.stat } -case class FramedProofIndex[G](framed_proof: FramedProof[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) - override def resolve(): Statement[G] = framed_proof.body +case class FramedProofIndex[G](framed_proof: FramedProof[G], index: Int) extends Index[G] { + override def make_step(): Set[Option[Index[G]]] = { + if (index < 2) Set(Some(FramedProofIndex(framed_proof, index + 1))) + else Set(None) + } + override def resolve(): Statement[G] = index match { + case 0 => Eval(framed_proof.pre)(framed_proof.pre.o) + case 1 => Eval(framed_proof.post)(framed_proof.post.o) + case 2 => framed_proof.body + } } case class ExtractIndex[G](extract: Extract[G]) extends Index[G] { @@ -220,8 +267,18 @@ case class ScopeIndex[G](scope: Scope[G]) extends Index[G] { } case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) - override def resolve(): Statement[G] = branch.branches.apply(index)._2 + override def make_step(): Set[Option[Index[G]]] = { + // Indices 0, 2, 4, ... are the conditions, indices 1, 3, 5, ... are the branch bodies + if (index % 2 == 0 && index < 2 * (branch.branches.size - 1)) + Set(Some(BranchIndex(branch, index + 2)), Some(BranchIndex(branch, index + 1))) + else if (index == 2 * (branch.branches.size - 1)) + Set(Some(BranchIndex(branch, index + 1)), None) + else Set(None) + } + override def resolve(): Statement[G] = { + if (index % 2 == 0) Eval(branch.branches.apply(index / 2)._1)(branch.branches.apply(index / 2)._1.o) + else branch.branches.apply((index - 1) / 2)._2 + } } case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends Index[G] { diff --git a/src/rewrite/vct/rewrite/cfg/Utils.scala b/src/rewrite/vct/rewrite/cfg/Utils.scala index 5d2417e8aa..e7c78340e4 100644 --- a/src/rewrite/vct/rewrite/cfg/Utils.scala +++ b/src/rewrite/vct/rewrite/cfg/Utils.scala @@ -4,6 +4,8 @@ import vct.col.ast._ import vct.col.origin.Origin import vct.col.ref.{DirectRef, Ref} +import scala.collection.mutable + object Utils { def find_all_subexpressions[G](expr: Expr[G]): Seq[Statement[G]] = expr match { @@ -28,4 +30,14 @@ object Utils { } private def get_out_variable[G](cls: Ref[G, Class[G]], o: Origin): Local[G] = Local(new DirectRef(new Variable(TClass(cls))(o)))(o) + + def find_all_cases[G](body: Statement[G], index: GlobalIndex[G]): mutable.Set[(SwitchCase[G], GlobalIndex[G])] = body match { + // Recursion on statements that can contain case statements + case Label(_, stmt) => find_all_cases(stmt, index.enter_scope(body)) + case Block(stmts) => mutable.Set(stmts.zipWithIndex.flatMap(t => find_all_cases(t._1, index.enter_scope(body, t._2)))) + case Scope(_, stmt) => find_all_cases(stmt, index.enter_scope(body)) + // Recursion end + case c: SwitchCase[G] => mutable.Set((c, index)) + case _ => mutable.Set() // TODO: Assuming that there are no cases in deeper structures (branches, loops etc.) + } } From 344df78a54206ce01caf356768258e0ff753cc4a Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 14 Feb 2024 11:43:01 +0100 Subject: [PATCH 13/85] Continued support for expression side effects, some syntax fixes, added condition information to edges --- .../vct/rewrite/cfg/CFGGenerator.scala | 142 +++++++++--------- src/rewrite/vct/rewrite/cfg/CFGNode.scala | 2 +- src/rewrite/vct/rewrite/cfg/Index.scala | 24 ++- src/rewrite/vct/rewrite/cfg/Utils.scala | 4 +- 4 files changed, 94 insertions(+), 78 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 786b38d62d..98e359d0a1 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -1,9 +1,8 @@ package vct.rewrite.cfg import vct.col.ast._ -import vct.col.origin.Origin -import vct.col.ref.{Ref, DirectRef} +import scala.collection.IterableOnce.iterableOnceExtensionMethods import scala.collection.mutable case class CFGGenerator[G]() { @@ -12,10 +11,10 @@ case class CFGGenerator[G]() { private val converted_nodes: mutable.Map[GlobalIndex[G], CFGNode[G]] = mutable.HashMap[GlobalIndex[G], CFGNode[G]]() def generate(entry: InstanceMethod[G]): CFGNode[G] = { - convert(entry.body.get, GlobalIndex[G]) + convert(entry.body.get, GlobalIndex[G](List(InitialIndex(entry)))) } - def convert(node: Statement[G], context: GlobalIndex[G]): CFGNode[G] = { + private def convert(node: Statement[G], context: GlobalIndex[G]): CFGNode[G] = { // If a node has already been visited, then it should not be created again if (converted_nodes.contains(context)) return converted_nodes(context) // Create new node with its successors (if possible) @@ -25,45 +24,42 @@ case class CFGGenerator[G]() { case label: Label[G] => { // For labels, add them to the label map and add them to any goto statements going to that label found_labels.addOne((label.decl, cfg_node)) - searched_labels.getOrElse(label.decl, mutable.Set()).map(g => g.successors.addOne(cfg_node)) + searched_labels.getOrElse(label.decl, mutable.Set()).map(g => g.successors.addOne(CFGEdge(cfg_node, None))) } - case goto: Goto[G] => { - // For goto statements, if the label could not be resolved below, add the statement to the waiting list for the right label + case goto: Goto[G] => + // For goto statements, if the label could not be resolved, add the statement to the waiting list for the right label if (cfg_node.successors.isEmpty) { if (searched_labels.contains(goto.lbl.decl)) searched_labels(goto.lbl.decl).addOne(cfg_node) else searched_labels.addOne((goto.lbl.decl, mutable.Set(cfg_node))) } - } } converted_nodes.addOne((context, cfg_node)) cfg_node } - def find_successors(node: Statement[G], context: GlobalIndex[G]): mutable.Set[CFGNode[G]] = node match { - case PVLBranch(branches) => { - val eval: Eval[G] = Eval(branches.head._1)(branches.head._1.o) - mutable.Set(convert(eval, context.enter_scope(node))) - } + private def find_successors(node: Statement[G], context: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = node match { + case PVLBranch(branches) => + mutable.Set(CFGEdge(convert(Eval(branches.head._1)(branches.head._1.o), context.enter_scope(node)), None)) case PVLLoop(init, _, _, _, _) => - mutable.Set(convert(init, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(init, context.enter_scope(node)), None)) // NonExecutableStatement case LocalDecl(_) => sequential_successor(context) case SpecIgnoreStart() => sequential_successor(context) // TODO: What is this? case SpecIgnoreEnd() => sequential_successor(context) // NormallyCompletingStatement - case Assign(target, value) => sequential_successor(context) // TODO: Implement support for multiple expressions in container statement + case Assign(target, _) => context.indices.head match { + case AssignmentIndex(a, _) if a == node => sequential_successor(context) + case _ => mutable.Set(CFGEdge(convert(Eval(target)(target.o), context.enter_scope(node)), None)) + } case Send(_, _, res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) case Recv(_) => sequential_successor(context) case DefaultCase() => sequential_successor(context) case Case(pattern) => sequential_successor(context) // TODO: Handle expression side effects in switch case conditions case Label(_, stat) => - mutable.Set(convert(stat, context.enter_scope(node))) - case Goto(lbl) => { - val found_node: Option[CFGNode[G]] = found_labels.get(lbl.decl) - found_node match { - case Some(node) => mutable.Set(node) - case None => mutable.Set() - } + mutable.Set(CFGEdge(convert(stat, context.enter_scope(node)), None)) + case Goto(lbl) => found_labels.get(lbl.decl) match { + case Some(node) => mutable.Set(CFGEdge(node, None)) + case None => mutable.Set() } case Exhale(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) case Assert(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) @@ -74,8 +70,8 @@ case class CFGGenerator[G]() { case Wait(_) => sequential_successor(context) case Notify(_) => sequential_successor(context) case Fork(obj) => { - val run_method: RunMethod[G] = obj.t.asClass.get.cls.decl.declarations.collect { case r: RunMethod[G] => r }.head - sequential_successor(context).addOne(convert(run_method.body.get, context.enter_scope(run_method))) + val run_method: RunMethod[G] = obj.t.asClass.get.cls.decl.declarations.collect{ case r: RunMethod[G] => r }.head + sequential_successor(context).addOne(CFGEdge(convert(run_method.body.get, context.enter_scope(run_method)), None)) } case Join(_) => sequential_successor(context) case Lock(_) => sequential_successor(context) @@ -85,81 +81,80 @@ case class CFGGenerator[G]() { case Unfold(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) case WandApply(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) case Havoc(_) => sequential_successor(context) - case FramedProof(pre, body, post) => - ??? // TODO: Support multiple expressions in a container statement + case FramedProof(pre, _, _) => + mutable.Set(CFGEdge(convert(Eval(pre)(pre.o), context.enter_scope(node)), None)) case Extract(contractedStatement) => - mutable.Set(convert(contractedStatement, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(contractedStatement, context.enter_scope(node)), None)) // ExceptionalStatement - case Eval(_) => sequential_successor(context.enter_scope(node)) // TODO: Is this correct? - case Return(result) => handle_expression_container(node, Eval(result)(result.o), context, mutable.Set(return_successor(context))) - case Throw(obj) => mutable.Set(exception_successor(obj, context)) + case Eval(_) => evaluate_first(context.enter_scope(node)) + case Return(result) => handle_expression_container(node, Eval(result)(result.o), context, mutable.Set(CFGEdge(return_successor(context), None))) + case Throw(obj) => mutable.Set(CFGEdge(exception_successor(obj, context), None)) case Break(label) => label match { case Some(ref) => ??? // TODO: Handle break label! - case None => mutable.Set(break_successor(context)) + case None => mutable.Set(CFGEdge(break_successor(context), None)) } case Continue(label) => label match { case Some(ref) => ??? // TODO: Handle continue label! - case None => mutable.Set(continue_successor(context)) + case None => mutable.Set(CFGEdge(continue_successor(context), None)) } // InvocationStatement - case InvokeProcedure(ref, args, _, _, _, _) => { - if (args.nonEmpty) mutable.Set(convert(Eval(args.head)(args.head.o), context.enter_scope(node))) - else if (ref.decl.body.nonEmpty) mutable.Set(convert(ref.decl.body.get, context.enter_scope(node))) + // TODO: Handle all the other possible expressions in invocations! + case InvokeProcedure(ref, args, outArgs, typeArgs, givenMap, yields) => { + if (args.nonEmpty) mutable.Set(CFGEdge(convert(Eval(args.head)(args.head.o), context.enter_scope(node)), None)) + else if (ref.decl.body.nonEmpty) mutable.Set(CFGEdge(convert(ref.decl.body.get, context.enter_scope(node)), None)) else sequential_successor(context) } case InvokeConstructor(ref, out, args, outArgs, typeArgs, givenMap, yields) => { - if (args.nonEmpty) mutable.Set(convert(Eval(args.head)(args.head.o), context.enter_scope(node))) - else if (ref.decl.body.nonEmpty) mutable.Set(convert(ref.decl.body.get, context.enter_scope(node))) + if (args.nonEmpty) mutable.Set(CFGEdge(convert(Eval(args.head)(args.head.o), context.enter_scope(node)), None)) + else if (ref.decl.body.nonEmpty) mutable.Set(CFGEdge(convert(ref.decl.body.get, context.enter_scope(node)), None)) else sequential_successor(context) } case InvokeMethod(obj, ref, args, outArgs, typeArgs, givenMap, yields) => { - if (args.nonEmpty) mutable.Set(convert(Eval(args.head)(args.head.o), context.enter_scope(node))) - else if (ref.decl.body.nonEmpty) mutable.Set(convert(ref.decl.body.get, context.enter_scope(node))) + if (args.nonEmpty) mutable.Set(CFGEdge(convert(Eval(args.head)(args.head.o), context.enter_scope(node)), None)) + else if (ref.decl.body.nonEmpty) mutable.Set(CFGEdge(convert(ref.decl.body.get, context.enter_scope(node)), None)) else sequential_successor(context) } // CompositeStatement case Block(statements) => - mutable.Set(convert(statements.head, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(statements.head, context.enter_scope(node)), None)) case Scope(_, body) => - mutable.Set(convert(body, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(body, context.enter_scope(node)), None)) case Branch(branches) => { val eval: Eval[G] = Eval(branches.head._1)(branches.head._1.o) - mutable.Set(convert(eval, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(eval, context.enter_scope(node)), None)) } case IndetBranch(branches) => - mutable.Set(branches.zipWithIndex.map(b => convert(b._1, context.enter_scope(node, b._2)))) - case Switch(expr, body) => { - val cases: mutable.Set[(SwitchCase[G], GlobalIndex[G])] = Utils.find_all_cases(body, context.enter_scope(node)) - cases.map(t => convert(t._1, t._2)) // TODO: Handle side effects in switch statement conditions - } + mutable.LinkedHashSet.from(branches.zipWithIndex.map(b => CFGEdge(convert(b._1, context.enter_scope(node, b._2)), None))) + case Switch(expr, body) => + Utils.find_all_cases(body, context.enter_scope(node)).map(t => CFGEdge(convert(t._1, t._2), None)) // TODO: Handle side effects in switch statement conditions case Loop(init, _, _, _, _) => - mutable.Set(convert(init, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(init, context.enter_scope(node)), None)) case RangedFor(_, _, body) => - mutable.Set(convert(body, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(body, context.enter_scope(node)), None)) case TryCatchFinally(body, _, _) => - mutable.Set(convert(body, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(body, context.enter_scope(node)), None)) case Synchronized(_, body) => - mutable.Set(convert(body, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(body, context.enter_scope(node)), None)) case ParInvariant(_, _, content) => - mutable.Set(convert(content, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(content, context.enter_scope(node)), None)) // TODO: Expression side effects case ParAtomic(_, content) => - mutable.Set(convert(content, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(content, context.enter_scope(node)), None)) // TODO: Expression side effects case ParBarrier(_, _, _, _, content) => - mutable.Set(convert(content, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(content, context.enter_scope(node)), None)) // TODO: Expression side effects case ParStatement(_) => sequential_successor(context) case VecBlock(_, _, _, content) => - mutable.Set(convert(content, context.enter_scope(node))) - case WandPackage(res, proof) => - ??? // TODO - case ModelDo(model, perm, after, action, impl) => - ??? // TODO + mutable.Set(CFGEdge(convert(content, context.enter_scope(node)), None)) // TODO: Expression side effects + case WandPackage(_, proof) => + mutable.Set(CFGEdge(convert(proof, context.enter_scope(node)), None)) // TODO: Expression side effects + case ModelDo(_, _, _, _, impl) => + mutable.Set(CFGEdge(convert(impl, context.enter_scope(node)), None)) // TODO: Expression side effects // CStatement case CDeclarationStatement(_) => sequential_successor(context) case CGoto(label) => ??? // I'm not dealing with string labels // CPPStatement case CPPDeclarationStatement(_) => sequential_successor(context) case CPPLifetimeScope(body) => - mutable.Set(convert(body, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(body, context.enter_scope(node)), None)) case JavaLocalDeclarationStatement(_) => sequential_successor(context) // SilverStatement case SilverNewRef(_, _) => sequential_successor(context) @@ -171,33 +166,38 @@ case class CFGGenerator[G]() { case Communicate(_, _) => sequential_successor(context) case SeqAssign(_, _, value) => handle_expression_container(node, Eval(value)(value.o), context, sequential_successor(context)) case UnresolvedSeqBranch(branches) => - mutable.Set(branches.zipWithIndex.map(b => convert(b._1._2, context.enter_scope(node, b._2)))) + mutable.LinkedHashSet.from(branches.zipWithIndex.map(b => CFGEdge(convert(b._1._2, context.enter_scope(node, b._2)), None))) case UnresolvedSeqLoop(cond, _, _) => - mutable.Set(convert(Eval(cond)(cond.o), context.enter_scope(node))) + mutable.Set(CFGEdge(convert(Eval(cond)(cond.o), context.enter_scope(node)), None)) case SeqBranch(_, yes, no) => no match { - case Some(stmt) => mutable.Set(convert(yes, context.enter_scope(node, 0)), convert(stmt, context.enter_scope(node, 1))) - case None => mutable.Set(convert(yes, context.enter_scope(node))) + case Some(stmt) => mutable.Set(CFGEdge(convert(yes, context.enter_scope(node, 0)), None), CFGEdge(convert(stmt, context.enter_scope(node, 1)), None)) + case None => mutable.Set(CFGEdge(convert(yes, context.enter_scope(node)), None)) } case SeqLoop(_, _, body) => - mutable.Set(convert(body, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(body, context.enter_scope(node)), None)) case VeyMontAssignExpression(_, assign) => - mutable.Set(convert(assign, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(assign, context.enter_scope(node)), None)) case CommunicateX(_, _, _, assign) => - mutable.Set(convert(assign, context.enter_scope(node))) + mutable.Set(CFGEdge(convert(assign, context.enter_scope(node)), None)) } private def handle_expression_container(statement: Statement[G], expression: Eval[G], context: GlobalIndex[G], - successors_to_statement: mutable.Set[CFGNode[G]]): mutable.Set[CFGNode[G]] = { + successors_to_statement: mutable.Set[CFGEdge[G]]): mutable.Set[CFGEdge[G]] = { context.indices.head match { case ExpressionContainerIndex(stmt, 1) if stmt == statement => successors_to_statement - case _ => mutable.Set(convert(expression, context.enter_scope(expression))) // TODO: Handle containers with multiple expressions + case _ => mutable.Set(CFGEdge(convert(expression, context.enter_scope(expression)), None)) } } - private def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGNode[G]] = - mutable.Set(index.make_step().map(i => convert(i.resolve(), i))) + private def evaluate_first(index: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = { + if (index.has_statement()) mutable.Set(CFGEdge(convert(index.resolve(), index), None)) + else sequential_successor(index) + } + + private def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = + mutable.LinkedHashSet.from(index.make_step().map(i => CFGEdge(convert(i.resolve(), i), None))) private def return_successor(index: GlobalIndex[G]): CFGNode[G] = { val new_index: GlobalIndex[G] = index.return_from_call() diff --git a/src/rewrite/vct/rewrite/cfg/CFGNode.scala b/src/rewrite/vct/rewrite/cfg/CFGNode.scala index b6b0ce6974..bb7e3094bf 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGNode.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGNode.scala @@ -4,5 +4,5 @@ import vct.col.ast.{Expr, Node} import scala.collection.mutable -case class CFGNode[G](ast_node: Node[G], successors: mutable.Set[CFGNode[G]]) // TODO: Add condition information to edges +case class CFGNode[G](ast_node: Node[G], successors: mutable.Set[CFGEdge[G]]) case class CFGEdge[G](target: CFGNode[G], condition: Option[Expr[G]]) \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index cdc03231a9..5fae5ef9cb 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -22,6 +22,8 @@ case class GlobalIndex[G](indices: List[Index[G]]) { def resolve(): Statement[G] = indices.head.resolve() + def has_statement(): Boolean = indices.head.has_statement() + def return_from_call(): GlobalIndex[G] = { // Find innermost subroutine call val stack: List[Index[G]] = indices.dropWhile { @@ -56,7 +58,6 @@ case class GlobalIndex[G](indices: List[Index[G]]) { } // Find the next statement // TODO: Does this always return exactly one next step? - // No, e.g.: another loop in the update statement of a loop GlobalIndex(stack.tail).make_step().head } @@ -92,6 +93,13 @@ sealed trait Index[G] { * @return The statement at the current index */ def resolve(): Statement[G] + + /** + * Determines whether the index contains a statement. + * + * @return true if the index contains at least one statement, false otherwise + */ + def has_statement(): Boolean = true } object Index { @@ -150,10 +158,16 @@ case class ExpressionContainerIndex[G](statement: Statement[G], index: Int) exte override def resolve(): Statement[G] = statement } -// TODO: Is this really a good idea? case class AssignmentIndex[G](assign: Assign[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = ??? - override def resolve(): Statement[G] = ??? + override def make_step(): Set[Option[Index[G]]] = { + if (index < 2) Set(Some(AssignmentIndex(assign, index + 1))) + else Set(None) + } + override def resolve(): Statement[G] = index match { + case 0 => Eval(assign.target)(assign.target.o) + case 1 => Eval(assign.value)(assign.value.o) + case 2 => assign + } } case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index[G] { @@ -215,6 +229,7 @@ case class EvalIndex[G](eval: Eval[G], index: Int, subexpressions: Seq[Statement else Set(None) } override def resolve(): Statement[G] = subexpressions.apply(index) + override def has_statement(): Boolean = subexpressions.nonEmpty } object EvalIndex { def apply[G](eval: Eval[G], index: Int): EvalIndex[G] = new EvalIndex(eval, index) @@ -286,6 +301,7 @@ case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends override def resolve(): Statement[G] = indet_branch.branches.apply(index) } +// TODO: Switch cases could be multiple context indices deep; this does not work with the single index for make_step() case class SwitchIndex[G](switch: Switch[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = switch.body diff --git a/src/rewrite/vct/rewrite/cfg/Utils.scala b/src/rewrite/vct/rewrite/cfg/Utils.scala index e7c78340e4..c2041028fc 100644 --- a/src/rewrite/vct/rewrite/cfg/Utils.scala +++ b/src/rewrite/vct/rewrite/cfg/Utils.scala @@ -29,12 +29,12 @@ object Utils { case _ => expr.subnodes.collect{ case ex: Expr[G] => ex }.flatMap(e => find_all_subexpressions(e)) } - private def get_out_variable[G](cls: Ref[G, Class[G]], o: Origin): Local[G] = Local(new DirectRef(new Variable(TClass(cls))(o)))(o) + private def get_out_variable[G](cls: Ref[G, Class[G]], o: Origin): Local[G] = Local(new DirectRef[G, Variable[G]](new Variable(TClass(cls))(o)))(o) def find_all_cases[G](body: Statement[G], index: GlobalIndex[G]): mutable.Set[(SwitchCase[G], GlobalIndex[G])] = body match { // Recursion on statements that can contain case statements case Label(_, stmt) => find_all_cases(stmt, index.enter_scope(body)) - case Block(stmts) => mutable.Set(stmts.zipWithIndex.flatMap(t => find_all_cases(t._1, index.enter_scope(body, t._2)))) + case Block(stmts) => mutable.LinkedHashSet.from(stmts.zipWithIndex.flatMap(t => find_all_cases(t._1, index.enter_scope(body, t._2)))) case Scope(_, stmt) => find_all_cases(stmt, index.enter_scope(body)) // Recursion end case c: SwitchCase[G] => mutable.Set((c, index)) From c4a7755e3178e6076f4d5172d5e225a79327929e Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 14 Feb 2024 13:05:25 +0100 Subject: [PATCH 14/85] Syntax fixes, project compiles --- .../vct/rewrite/cfg/CFGGenerator.scala | 2 +- src/rewrite/vct/rewrite/cfg/Index.scala | 87 ++++++++++--------- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 98e359d0a1..3fe85b5c68 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -197,7 +197,7 @@ case class CFGGenerator[G]() { } private def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = - mutable.LinkedHashSet.from(index.make_step().map(i => CFGEdge(convert(i.resolve(), i), None))) + index.make_step().map(i => CFGEdge(convert(i.resolve(), i), None)) private def return_successor(index: GlobalIndex[G]): CFGNode[G] = { val new_index: GlobalIndex[G] = index.return_from_call() diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 5fae5ef9cb..a7ae3ef03c 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -2,19 +2,21 @@ package vct.rewrite.cfg import vct.col.ast._ +import scala.collection.mutable + case class GlobalIndex[G](indices: List[Index[G]]) { def enter_scope(node: Node[G], index: Int = 0): GlobalIndex[G] = GlobalIndex(Index[G](node, index) :: indices) - def make_step(): Set[GlobalIndex[G]] = { - if (indices.isEmpty) return Set[GlobalIndex[G]]() + def make_step(): mutable.Set[GlobalIndex[G]] = { + if (indices.isEmpty) return mutable.Set[GlobalIndex[G]]() val steps: Set[Option[Index[G]]] = indices.head.make_step() - var res = Set[GlobalIndex[G]]() + val res = mutable.Set[GlobalIndex[G]]() for (step <- steps) { step match { - case Some(index) => res = res ++ GlobalIndex(index +: indices.tail) - case None => res = res ++ GlobalIndex(indices.tail).make_step() + case Some(index) => res.addOne(GlobalIndex(index +: indices.tail)) + case None => res.addAll(GlobalIndex(indices.tail).make_step()) } } res @@ -103,41 +105,46 @@ sealed trait Index[G] { } object Index { - def apply[G](instance_method: InstanceMethod[G], index: Int): Index[G] = InitialIndex(instance_method) - def apply[G](run_method: RunMethod[G], index: Int): Index[G] = RunMethodIndex(run_method) - def apply[G](pvl_branch: PVLBranch[G], index: Int): Index[G] = PVLBranchIndex(pvl_branch, index) - def apply[G](pvl_loop: PVLLoop[G], index: Int): Index[G] = PVLLoopIndex(pvl_loop, index) - def apply[G](label: Label[G], index: Int): Index[G] = LabelIndex(label) - def apply[G](framed_proof: FramedProof[G], index: Int): Index[G] = FramedProofIndex(framed_proof, index) - def apply[G](extract: Extract[G], index: Int): Index[G] = ExtractIndex(extract) - def apply[G](eval: Eval[G], index: Int): Index[G] = EvalIndex(eval, index) - def apply[G](invoke_procedure: InvokeProcedure[G], index: Int): Index[G] = InvokeProcedureIndex(invoke_procedure, index) - def apply[G](invoke_constructor: InvokeConstructor[G], index: Int): Index[G] = InvokeConstructorIndex(invoke_constructor, index) - def apply[G](invoke_method: InvokeMethod[G], index: Int): Index[G] = InvokeMethodIndex(invoke_method, index) - def apply[G](block: Block[G], index: Int): Index[G] = BlockIndex(block, index) - def apply[G](scope: Scope[G], index: Int): Index[G] = ScopeIndex(scope) - def apply[G](branch: Branch[G], index: Int): Index[G] = BranchIndex(branch, index) - def apply[G](indet_branch: IndetBranch[G], index: Int): Index[G] = IndetBranchIndex(indet_branch, index) - def apply[G](switch: Switch[G], index: Int): Index[G] = SwitchIndex(switch) - def apply[G](loop: Loop[G], index: Int): Index[G] = LoopIndex(loop, index) - def apply[G](ranged_for: RangedFor[G], index: Int): Index[G] = RangedForIndex(ranged_for) - def apply[G](try_catch_finally: TryCatchFinally[G], index: Int): Index[G] = TryCatchFinallyIndex(try_catch_finally, index) - def apply[G](synchronized: Synchronized[G], index: Int): Index[G] = SynchronizedIndex(synchronized) - def apply[G](par_invariant: ParInvariant[G], index: Int): Index[G] = ParInvariantIndex(par_invariant) - def apply[G](par_atomic: ParAtomic[G], index: Int): Index[G] = ParAtomicIndex(par_atomic) - def apply[G](par_barrier: ParBarrier[G], index: Int): Index[G] = ParBarrierIndex(par_barrier) - def apply[G](vec_block: VecBlock[G], index: Int): Index[G] = VecBlockIndex(vec_block) - def apply[G](wand_package: WandPackage[G], index: Int): Index[G] = WandPackageIndex(wand_package) - def apply[G](model_do: ModelDo[G], index: Int): Index[G] = ModelDoIndex(model_do) - def apply[G](cpp_lifetime_scope: CPPLifetimeScope[G], index: Int): Index[G] = CPPLifetimeScopeIndex(cpp_lifetime_scope) - def apply[G](unresolved_seq_branch: UnresolvedSeqBranch[G], index: Int): Index[G] = UnresolvedSeqBranchIndex(unresolved_seq_branch, index) - def apply[G](unresolved_seq_loop: UnresolvedSeqLoop[G], index: Int): Index[G] = UnresolvedSeqLoopIndex(unresolved_seq_loop, index) - def apply[G](seq_branch: SeqBranch[G], index: Int): Index[G] = SeqBranchIndex(seq_branch, index) - def apply[G](seq_loop: SeqLoop[G], index: Int): Index[G] = SeqLoopIndex(seq_loop) - def apply[G](veymont_assign_expression: VeyMontAssignExpression[G], index: Int): Index[G] = VeyMontAssignExpressionIndex(veymont_assign_expression) - def apply[G](communicatex: CommunicateX[G], index: Int): Index[G] = CommunicateXIndex(communicatex) - def apply[G](assign: Assign[G], index: Int): Index[G] = AssignmentIndex(assign, index) - def apply[G](statement: Statement[G], index: Int): Index[G] = ExpressionContainerIndex(statement, index) + def from[G](node: Node[G], index: Int): Index[G] = node match { + case instance_method: InstanceMethod[G] => InitialIndex(instance_method) + case run_method: RunMethod[G] => RunMethodIndex(run_method) + case assign: Assign[G] => AssignmentIndex(assign, index) + case pvl_branch: PVLBranch[G] => PVLBranchIndex(pvl_branch, index) + case pvl_loop: PVLLoop[G] => PVLLoopIndex(pvl_loop, index) + case label: Label[G] => LabelIndex(label) + case framed_proof: FramedProof[G] => FramedProofIndex(framed_proof, index) + case extract: Extract[G] => ExtractIndex(extract) + case eval: Eval[G] => EvalIndex(eval, index) + case invoke_procedure: InvokeProcedure[G] => InvokeProcedureIndex(invoke_procedure, index) + case invoke_constructor: InvokeConstructor[G] => InvokeConstructorIndex(invoke_constructor, index) + case invoke_method: InvokeMethod[G] => InvokeMethodIndex(invoke_method, index) + case block: Block[G] => BlockIndex(block, index) + case scope: Scope[G] => ScopeIndex(scope) + case branch: Branch[G] => BranchIndex(branch, index) + case indet_branch: IndetBranch[G] => IndetBranchIndex(indet_branch, index) + case switch: Switch[G] => SwitchIndex(switch) + case loop: Loop[G] => LoopIndex(loop, index) + case ranged_for: RangedFor[G] => RangedForIndex(ranged_for) + case try_catch_finally: TryCatchFinally[G] => TryCatchFinallyIndex(try_catch_finally, index) + case synchronized: Synchronized[G] => SynchronizedIndex(synchronized) + case par_invariant: ParInvariant[G] => ParInvariantIndex(par_invariant) + case par_atomic: ParAtomic[G] => ParAtomicIndex(par_atomic) + case par_barrier: ParBarrier[G] => ParBarrierIndex(par_barrier) + case vec_block: VecBlock[G] => VecBlockIndex(vec_block) + case wand_package: WandPackage[G] => WandPackageIndex(wand_package) + case model_do: ModelDo[G] => ModelDoIndex(model_do) + case cpp_lifetime_scope: CPPLifetimeScope[G] => CPPLifetimeScopeIndex(cpp_lifetime_scope) + case unresolved_seq_branch: UnresolvedSeqBranch[G] => UnresolvedSeqBranchIndex(unresolved_seq_branch, index) + case unresolved_seq_loop: UnresolvedSeqLoop[G] => UnresolvedSeqLoopIndex(unresolved_seq_loop, index) + case seq_branch: SeqBranch[G] => SeqBranchIndex(seq_branch, index) + case seq_loop: SeqLoop[G] => SeqLoopIndex(seq_loop) + case veymont_assign_expression: VeyMontAssignExpression[G] => VeyMontAssignExpressionIndex(veymont_assign_expression) + case communicatex: CommunicateX[G] => CommunicateXIndex(communicatex) + case statement: Statement[G] => ExpressionContainerIndex(statement, index) + case _ => ??? + } + + def apply[G](node: Node[G], index: Int): Index[G] = from(node, index) } case class InitialIndex[G](instance_method: InstanceMethod[G]) extends Index[G] { From 4bbb2ab84dd000959304098e8cbeba1011bf0fd3 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 14 Feb 2024 14:33:58 +0100 Subject: [PATCH 15/85] Implemented GraphViz/DOT printer for control flow graph --- src/rewrite/vct/rewrite/cfg/Printer.scala | 50 +++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 src/rewrite/vct/rewrite/cfg/Printer.scala diff --git a/src/rewrite/vct/rewrite/cfg/Printer.scala b/src/rewrite/vct/rewrite/cfg/Printer.scala new file mode 100644 index 0000000000..4c431125f5 --- /dev/null +++ b/src/rewrite/vct/rewrite/cfg/Printer.scala @@ -0,0 +1,50 @@ +package vct.rewrite.cfg + +import hre.io.RWFile +import vct.col.ast.{BooleanValue, Expr, InstanceMethod} +import vct.col.origin.Origin + +import java.io.Writer +import java.nio.file.Path +import scala.collection.mutable + +case class Printer[G]() { + private val node_names: mutable.Map[CFGNode[G], String] = mutable.HashMap[CFGNode[G], String]() + private val no_condition: Expr[G] = BooleanValue(value = true)(Origin(Seq())) + private var node_index: Int = 0 + + private def print_cfg(writer: Writer): Unit = { + writer.append("digraph {") + // Write all nodes + node_names.foreach(naming => writer.append(naming._2) + .append(s" [label=${"\""}") + .append(naming._1.ast_node.toInlineString) + .append(s"${"\""}];\n")) + // Write all edges + node_names.foreach(naming => naming._1.successors.foreach(edge => writer.append(naming._2) + .append(" -> ") + .append(node_names(edge.target)) + .append(s" [label=${"\""}") + .append(edge.condition.getOrElse(no_condition).toInlineString) + .append(s"${"\""}];\n"))) + writer.append("}") + } + + def print_ast_as_cfg(entry_point: InstanceMethod[G], path: Path): Unit = { + val cfg_root: CFGNode[G] = CFGGenerator().generate(entry_point) + find_all_nodes(cfg_root) + RWFile(path.toFile).write(w => print_cfg(w)) + } + + private def find_all_nodes(node: CFGNode[G]): Unit = { + if (!node_names.contains(node)) { + // Give new name to node + val node_name: String = s"n$node_index" + node_index += 1 + node_names.addOne((node, node_name)) + + // Recursively call for successors + node.successors.foreach(e => find_all_nodes(e.target)) + } + } +} From c89a7286331967036c78e6b4f2422b48beb775de Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 14 Feb 2024 15:42:47 +0100 Subject: [PATCH 16/85] Started work on frontend support for CFG conversion --- src/main/vct/main/Main.scala | 4 +++- src/main/vct/main/stages/Stages.scala | 5 +++++ src/main/vct/options/Options.scala | 13 +++++++++++++ src/main/vct/options/types/Mode.scala | 1 + src/rewrite/vct/rewrite/cfg/Printer.scala | 2 +- 5 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/main/vct/main/Main.scala b/src/main/vct/main/Main.scala index 484800b44d..2f48e7f4e9 100644 --- a/src/main/vct/main/Main.scala +++ b/src/main/vct/main/Main.scala @@ -94,9 +94,11 @@ case object Main extends LazyLogging { } EXIT_CODE_SUCCESS case Mode.VeyMont => VeyMont.runOptions(options) - case Mode.VeSUV => + case Mode.VeSUV => { logger.info("Starting transformation") Transform.runOptions(options) + } + case Mode.CFG => case Mode.BatchTest => ??? } } finally { diff --git a/src/main/vct/main/stages/Stages.scala b/src/main/vct/main/stages/Stages.scala index 2be2b1151a..4f8e58fe39 100644 --- a/src/main/vct/main/stages/Stages.scala +++ b/src/main/vct/main/stages/Stages.scala @@ -70,4 +70,9 @@ case object Stages { Parsing.ofOptions(options, blameProvider) .thenRun(Output.vesuvOfOptions(options)) } + + def cfgTransformationOfOptions(options: Options, blameProvider: BlameProvider): Stages[Seq[Readable], Unit] = { + Parsing.ofOptions(options, blameProvider) + .thenRun() + } } \ No newline at end of file diff --git a/src/main/vct/options/Options.scala b/src/main/vct/options/Options.scala index 6d55980cc6..287369c192 100644 --- a/src/main/vct/options/Options.scala +++ b/src/main/vct/options/Options.scala @@ -268,6 +268,16 @@ case object Options { .action((path, c) => c.copy(vesuvOutput = path)) ), + note(""), + note("Control flow graph"), + opt[Unit]("build-cfg") + .action((_, c) => c.copy(mode = Mode.CFG)) + .text("Instead of verifying a program, build its control flow graph for further analysis") + .children( + opt[Path]("cfg-output").required().valueName("") + .action((path, c) => c.copy(cfgOutput = path)) + ), + note(""), note("Batch Testing Mode"), opt[Unit]("test") @@ -403,6 +413,9 @@ case class Options // VeSUV options vesuvOutput: Path = null, + // Control flow graph options + cfgOutput: Path = null, + // Batch test options testDir: Path = null, // required testFilterBackend: Option[Seq[Backend]] = None, diff --git a/src/main/vct/options/types/Mode.scala b/src/main/vct/options/types/Mode.scala index f7c07eb730..addf6b3c88 100644 --- a/src/main/vct/options/types/Mode.scala +++ b/src/main/vct/options/types/Mode.scala @@ -8,5 +8,6 @@ case object Mode { case object Verify extends Mode case object VeyMont extends Mode case object VeSUV extends Mode + case object CFG extends Mode case object BatchTest extends Mode } diff --git a/src/rewrite/vct/rewrite/cfg/Printer.scala b/src/rewrite/vct/rewrite/cfg/Printer.scala index 4c431125f5..2053b8cbfb 100644 --- a/src/rewrite/vct/rewrite/cfg/Printer.scala +++ b/src/rewrite/vct/rewrite/cfg/Printer.scala @@ -14,7 +14,7 @@ case class Printer[G]() { private var node_index: Int = 0 private def print_cfg(writer: Writer): Unit = { - writer.append("digraph {") + writer.append("digraph {\n") // Write all nodes node_names.foreach(naming => writer.append(naming._2) .append(s" [label=${"\""}") From 1f994f66a1db97af2ef3d0c9690076aedc118b61 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 14 Feb 2024 16:29:05 +0100 Subject: [PATCH 17/85] Fixed implementation of subroutine invocations --- src/main/vct/main/Main.scala | 2 +- src/main/vct/main/stages/Stages.scala | 4 +- .../vct/rewrite/cfg/CFGGenerator.scala | 22 +-- src/rewrite/vct/rewrite/cfg/Index.scala | 128 +++++++++++++++--- 4 files changed, 118 insertions(+), 38 deletions(-) diff --git a/src/main/vct/main/Main.scala b/src/main/vct/main/Main.scala index 2f48e7f4e9..7bdb2f7d79 100644 --- a/src/main/vct/main/Main.scala +++ b/src/main/vct/main/Main.scala @@ -98,7 +98,7 @@ case object Main extends LazyLogging { logger.info("Starting transformation") Transform.runOptions(options) } - case Mode.CFG => + case Mode.CFG => ??? case Mode.BatchTest => ??? } } finally { diff --git a/src/main/vct/main/stages/Stages.scala b/src/main/vct/main/stages/Stages.scala index 4f8e58fe39..4f5714d4a8 100644 --- a/src/main/vct/main/stages/Stages.scala +++ b/src/main/vct/main/stages/Stages.scala @@ -71,8 +71,8 @@ case object Stages { .thenRun(Output.vesuvOfOptions(options)) } - def cfgTransformationOfOptions(options: Options, blameProvider: BlameProvider): Stages[Seq[Readable], Unit] = { + /*def cfgTransformationOfOptions(options: Options, blameProvider: BlameProvider): Stages[Seq[Readable], Unit] = { Parsing.ofOptions(options, blameProvider) .thenRun() - } + }*/ } \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 3fe85b5c68..c727ee1deb 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -2,7 +2,6 @@ package vct.rewrite.cfg import vct.col.ast._ -import scala.collection.IterableOnce.iterableOnceExtensionMethods import scala.collection.mutable case class CFGGenerator[G]() { @@ -11,7 +10,7 @@ case class CFGGenerator[G]() { private val converted_nodes: mutable.Map[GlobalIndex[G], CFGNode[G]] = mutable.HashMap[GlobalIndex[G], CFGNode[G]]() def generate(entry: InstanceMethod[G]): CFGNode[G] = { - convert(entry.body.get, GlobalIndex[G](List(InitialIndex(entry)))) + convert(entry.body.get, GlobalIndex[G](mutable.Seq(InitialIndex(entry)))) } private def convert(node: Statement[G], context: GlobalIndex[G]): CFGNode[G] = { @@ -98,22 +97,9 @@ case class CFGGenerator[G]() { case None => mutable.Set(CFGEdge(continue_successor(context), None)) } // InvocationStatement - // TODO: Handle all the other possible expressions in invocations! - case InvokeProcedure(ref, args, outArgs, typeArgs, givenMap, yields) => { - if (args.nonEmpty) mutable.Set(CFGEdge(convert(Eval(args.head)(args.head.o), context.enter_scope(node)), None)) - else if (ref.decl.body.nonEmpty) mutable.Set(CFGEdge(convert(ref.decl.body.get, context.enter_scope(node)), None)) - else sequential_successor(context) - } - case InvokeConstructor(ref, out, args, outArgs, typeArgs, givenMap, yields) => { - if (args.nonEmpty) mutable.Set(CFGEdge(convert(Eval(args.head)(args.head.o), context.enter_scope(node)), None)) - else if (ref.decl.body.nonEmpty) mutable.Set(CFGEdge(convert(ref.decl.body.get, context.enter_scope(node)), None)) - else sequential_successor(context) - } - case InvokeMethod(obj, ref, args, outArgs, typeArgs, givenMap, yields) => { - if (args.nonEmpty) mutable.Set(CFGEdge(convert(Eval(args.head)(args.head.o), context.enter_scope(node)), None)) - else if (ref.decl.body.nonEmpty) mutable.Set(CFGEdge(convert(ref.decl.body.get, context.enter_scope(node)), None)) - else sequential_successor(context) - } + case InvokeProcedure(_, _, _, _, _, _) => evaluate_first(context.enter_scope(node)) + case InvokeConstructor(_, _, _, _, _, _, _) => evaluate_first(context.enter_scope(node)) + case InvokeMethod(_, _, _, _, _, _, _) => evaluate_first(context.enter_scope(node)) // CompositeStatement case Block(statements) => mutable.Set(CFGEdge(convert(statements.head, context.enter_scope(node)), None)) diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index a7ae3ef03c..62c1b2c837 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -1,13 +1,14 @@ package vct.rewrite.cfg import vct.col.ast._ +import vct.col.ref.Ref import scala.collection.mutable -case class GlobalIndex[G](indices: List[Index[G]]) { +case class GlobalIndex[G](indices: mutable.Seq[Index[G]]) { def enter_scope(node: Node[G], index: Int = 0): GlobalIndex[G] = - GlobalIndex(Index[G](node, index) :: indices) + GlobalIndex(indices.prepended(Index[G](node, index))) def make_step(): mutable.Set[GlobalIndex[G]] = { if (indices.isEmpty) return mutable.Set[GlobalIndex[G]]() @@ -22,13 +23,13 @@ case class GlobalIndex[G](indices: List[Index[G]]) { res } - def resolve(): Statement[G] = indices.head.resolve() + def resolve(): Statement[G] = indices.dropWhile(i => !i.has_statement()).head.resolve() def has_statement(): Boolean = indices.head.has_statement() def return_from_call(): GlobalIndex[G] = { // Find innermost subroutine call - val stack: List[Index[G]] = indices.dropWhile { + val stack: mutable.Seq[Index[G]] = indices.dropWhile { case InvokeProcedureIndex(_, _) | InvokeMethodIndex(_, _) => false case _ => true } @@ -39,7 +40,7 @@ case class GlobalIndex[G](indices: List[Index[G]]) { def handle_exception(e: Expr[G]): GlobalIndex[G] = { // Find innermost try-catch block of appropriate type - val stack: List[Index[G]] = indices.dropWhile { + val stack: mutable.Seq[Index[G]] = indices.dropWhile { case TryCatchFinallyIndex(stmt, 0) => !stmt.catches.exists(c => c.decl.t.equals(e.t)) case _ => true } @@ -53,7 +54,7 @@ case class GlobalIndex[G](indices: List[Index[G]]) { def handle_break(): GlobalIndex[G] = { // Find innermost occurrence of either a loop or a switch statement - val stack: List[Index[G]] = indices.dropWhile { + val stack: mutable.Seq[Index[G]] = indices.dropWhile { case PVLLoopIndex(_, 0) | LoopIndex(_, 0) => true // If some godless heathen calls break in the init of a loop, don't consider that loop case PVLLoopIndex(_, _) | LoopIndex(_, _) | RangedForIndex(_) | UnresolvedSeqLoopIndex(_, _) | SeqLoopIndex(_) | SwitchIndex(_) => false case _ => true @@ -65,7 +66,7 @@ case class GlobalIndex[G](indices: List[Index[G]]) { def continue_innermost_loop(): GlobalIndex[G] = { // Find innermost loop that could be the target of continue - val stack: List[Index[G]] = indices.dropWhile { + val stack: mutable.Seq[Index[G]] = indices.dropWhile { case PVLLoopIndex(_, 0) | LoopIndex(_, 0) => true case PVLLoopIndex(_, _) | LoopIndex(_, _) | RangedForIndex(_) | UnresolvedSeqLoopIndex(_, _) | SeqLoopIndex(_) => false case _ => true @@ -243,35 +244,128 @@ object EvalIndex { } case class InvokeProcedureIndex[G](invoke_procedure: InvokeProcedure[G], index: Int) extends Index[G] { + // Order of operations: + // 1. args + // 2. given + // 3. outArgs + // 4. yields + // 5. procedure body override def make_step(): Set[Option[Index[G]]] = { - if (index < invoke_procedure.args.size) Set(Some(InvokeProcedureIndex(invoke_procedure, index + 1))) + val args: Seq[Expr[G]] = invoke_procedure.args + val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_procedure.givenMap + val outArgs: Seq[Expr[G]] = invoke_procedure.outArgs + val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_procedure.yields + if (index < args.size + givenMap.size + outArgs.size + yields.size - 1 || + index == args.size + givenMap.size + outArgs.size + yields.size - 1 && invoke_procedure.ref.decl.body.nonEmpty) + Set(Some(InvokeProcedureIndex(invoke_procedure, index + 1))) else Set(None) } override def resolve(): Statement[G] = { - if (index < invoke_procedure.args.size) Eval(invoke_procedure.args.apply(index))(invoke_procedure.args.apply(index).o) - else invoke_procedure.ref.decl.body.get - } + val args: Seq[Expr[G]] = invoke_procedure.args + val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_procedure.givenMap + val outArgs: Seq[Expr[G]] = invoke_procedure.outArgs + val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_procedure.yields + if (index < args.size) { + val expr: Expr[G] = args.apply(index) + Eval(expr)(expr.o) + } else if (index < args.size + givenMap.size) { + val expr: Expr[G] = givenMap.apply(index - args.size)._2 + Eval(expr)(expr.o) + } else if (index < args.size + givenMap.size + outArgs.size) { + val expr: Expr[G] = outArgs.apply(index - args.size - givenMap.size) + Eval(expr)(expr.o) + } else if (index < args.size + givenMap.size + outArgs.size + yields.size) { + val expr: Expr[G] = yields.apply(index - args.size - givenMap.size - outArgs.size)._1 + Eval(expr)(expr.o) + } else invoke_procedure.ref.decl.body.get + } + override def has_statement(): Boolean = + invoke_procedure.args.nonEmpty || + invoke_procedure.givenMap.nonEmpty || + invoke_procedure.outArgs.nonEmpty || + invoke_procedure.yields.nonEmpty || + invoke_procedure.ref.decl.body.nonEmpty } case class InvokeConstructorIndex[G](invoke_constructor: InvokeConstructor[G], index: Int) extends Index[G] { + // Order of operations: + // 1. args + // 2. given + // 3. outArgs + // 4. yields + // 5. out + // 6. constructor body override def make_step(): Set[Option[Index[G]]] = { - if (index < invoke_constructor.args.size) Set(Some(InvokeConstructorIndex(invoke_constructor, index + 1))) + val args: Seq[Expr[G]] = invoke_constructor.args + val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_constructor.givenMap + val outArgs: Seq[Expr[G]] = invoke_constructor.outArgs + val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_constructor.yields + if (index < args.size + givenMap.size + outArgs.size + yields.size || + index == args.size + givenMap.size + outArgs.size + yields.size && invoke_constructor.ref.decl.body.nonEmpty) + Set(Some(InvokeConstructorIndex(invoke_constructor, index + 1))) else Set(None) } override def resolve(): Statement[G] = { - if (index < invoke_constructor.args.size) Eval(invoke_constructor.args.apply(index))(invoke_constructor.args.apply(index).o) - else invoke_constructor.ref.decl.body.get + val args: Seq[Expr[G]] = invoke_constructor.args + val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_constructor.givenMap + val outArgs: Seq[Expr[G]] = invoke_constructor.outArgs + val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_constructor.yields + if (index < args.size) { + val expr: Expr[G] = args.apply(index) + Eval(expr)(expr.o) + } else if (index < args.size + givenMap.size) { + val expr: Expr[G] = givenMap.apply(index - args.size)._2 + Eval(expr)(expr.o) + } else if (index < args.size + givenMap.size + outArgs.size) { + val expr: Expr[G] = outArgs.apply(index - args.size - givenMap.size) + Eval(expr)(expr.o) + } else if (index < args.size + givenMap.size + outArgs.size + yields.size) { + val expr: Expr[G] = yields.apply(index - args.size - givenMap.size - outArgs.size)._1 + Eval(expr)(expr.o) + } else if (index == args.size + givenMap.size + outArgs.size + yields.size) { + Eval(invoke_constructor.out)(invoke_constructor.out.o) + } else invoke_constructor.ref.decl.body.get } } case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) extends Index[G] { + // Order of operations: + // 1. obj + // 2. args + // 3. given + // 4. outArgs + // 5. yields + // 6. method body override def make_step(): Set[Option[Index[G]]] = { - if (index < invoke_method.args.size) Set(Some(InvokeMethodIndex(invoke_method, index + 1))) + val args: Seq[Expr[G]] = invoke_method.args + val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_method.givenMap + val outArgs: Seq[Expr[G]] = invoke_method.outArgs + val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_method.yields + if (index < args.size + givenMap.size + outArgs.size + yields.size || + index == args.size + givenMap.size + outArgs.size + yields.size && invoke_method.ref.decl.body.nonEmpty) + Set(Some(InvokeMethodIndex(invoke_method, index + 1))) else Set(None) } override def resolve(): Statement[G] = { - if (index < invoke_method.args.size) Eval(invoke_method.args.apply(index))(invoke_method.args.apply(index).o) - else invoke_method.ref.decl.body.get + val args: Seq[Expr[G]] = invoke_method.args + val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_method.givenMap + val outArgs: Seq[Expr[G]] = invoke_method.outArgs + val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_method.yields + if (index == 0) { + Eval(invoke_method.obj)(invoke_method.obj.o) + } else if (index < args.size + 1) { + val expr: Expr[G] = args.apply(index - 1) + Eval(expr)(expr.o) + } else if (index < args.size + givenMap.size + 1) { + val expr: Expr[G] = givenMap.apply(index - args.size - 1)._2 + Eval(expr)(expr.o) + } else if (index < args.size + givenMap.size + outArgs.size + 1) { + val expr: Expr[G] = outArgs.apply(index - args.size - givenMap.size - 1) + Eval(expr)(expr.o) + } else if (index < args.size + givenMap.size + outArgs.size + yields.size + 1) { + val expr: Expr[G] = yields.apply(index - args.size - givenMap.size - outArgs.size - 1)._1 + Eval(expr)(expr.o) + } else invoke_method.ref.decl.body.get } } From f9672e0a2c1ef53e6c46517cdddf7303fe3f43e3 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 15 Feb 2024 09:33:19 +0100 Subject: [PATCH 18/85] Renamed VeSUV pass to avoid confusion --- src/main/vct/main/Main.scala | 6 ++---- src/main/vct/main/modes/{Transform.scala => VeSUV.scala} | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) rename src/main/vct/main/modes/{Transform.scala => VeSUV.scala} (93%) diff --git a/src/main/vct/main/Main.scala b/src/main/vct/main/Main.scala index 7bdb2f7d79..a296fc497c 100644 --- a/src/main/vct/main/Main.scala +++ b/src/main/vct/main/Main.scala @@ -7,9 +7,7 @@ import hre.progress.Progress import org.slf4j.LoggerFactory import scopt.OParser import vct.col.ast.Node -import vct.main.modes.Verify -import vct.main.modes.VeyMont -import vct.main.modes.Transform +import vct.main.modes.{VeSUV, Verify, VeyMont} import vct.main.stages.Transformation import vct.options.types.{Mode, Verbosity} import vct.options.Options @@ -96,7 +94,7 @@ case object Main extends LazyLogging { case Mode.VeyMont => VeyMont.runOptions(options) case Mode.VeSUV => { logger.info("Starting transformation") - Transform.runOptions(options) + VeSUV.runOptions(options) } case Mode.CFG => ??? case Mode.BatchTest => ??? diff --git a/src/main/vct/main/modes/Transform.scala b/src/main/vct/main/modes/VeSUV.scala similarity index 93% rename from src/main/vct/main/modes/Transform.scala rename to src/main/vct/main/modes/VeSUV.scala index 4630934795..ece778b0df 100644 --- a/src/main/vct/main/modes/Transform.scala +++ b/src/main/vct/main/modes/VeSUV.scala @@ -7,7 +7,7 @@ import vct.main.stages.Stages import vct.options.Options import vct.parsers.transform.ConstantBlameProvider -case object Transform extends LazyLogging { +case object VeSUV extends LazyLogging { def runOptions(options: Options) : Int = { val collector = BlameCollector() val stages = Stages.vesuvOfOptions(options, ConstantBlameProvider(collector)) From 77e242a7aaaf8c1be592dd45c36a19fd62c569af Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 15 Feb 2024 10:27:26 +0100 Subject: [PATCH 19/85] Connected frontend to backend for control flow graph printer --- src/main/vct/main/Main.scala | 7 +++-- src/main/vct/main/modes/CFG.scala | 21 ++++++++++++++ src/main/vct/main/stages/PrintCFG.scala | 29 +++++++++++++++++++ src/main/vct/main/stages/Stages.scala | 7 +++-- .../cfg/{Printer.scala => CFGPrinter.scala} | 2 +- 5 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 src/main/vct/main/modes/CFG.scala create mode 100644 src/main/vct/main/stages/PrintCFG.scala rename src/rewrite/vct/rewrite/cfg/{Printer.scala => CFGPrinter.scala} (98%) diff --git a/src/main/vct/main/Main.scala b/src/main/vct/main/Main.scala index a296fc497c..9ea260cafb 100644 --- a/src/main/vct/main/Main.scala +++ b/src/main/vct/main/Main.scala @@ -7,7 +7,7 @@ import hre.progress.Progress import org.slf4j.LoggerFactory import scopt.OParser import vct.col.ast.Node -import vct.main.modes.{VeSUV, Verify, VeyMont} +import vct.main.modes.{CFG, VeSUV, Verify, VeyMont} import vct.main.stages.Transformation import vct.options.types.{Mode, Verbosity} import vct.options.Options @@ -96,7 +96,10 @@ case object Main extends LazyLogging { logger.info("Starting transformation") VeSUV.runOptions(options) } - case Mode.CFG => ??? + case Mode.CFG => { + logger.info("Starting control flow graph transformation") + CFG.runOptions(options) + } case Mode.BatchTest => ??? } } finally { diff --git a/src/main/vct/main/modes/CFG.scala b/src/main/vct/main/modes/CFG.scala new file mode 100644 index 0000000000..9fc7cf7eee --- /dev/null +++ b/src/main/vct/main/modes/CFG.scala @@ -0,0 +1,21 @@ +package vct.main.modes + +import com.typesafe.scalalogging.LazyLogging +import vct.col.origin.BlameCollector +import vct.main.Main.{EXIT_CODE_ERROR, EXIT_CODE_SUCCESS} +import vct.main.stages.Stages +import vct.options.Options +import vct.parsers.transform.ConstantBlameProvider + +case object CFG extends LazyLogging { + def runOptions(options: Options) : Int = { + val collector = BlameCollector() + val stages = Stages.cfgTransformationOfOptions(options, ConstantBlameProvider(collector)) + stages.run(options.inputs) match { + case Left(_) => EXIT_CODE_ERROR + case Right(()) => + logger.info("Transformation complete") + EXIT_CODE_SUCCESS + } + } +} diff --git a/src/main/vct/main/stages/PrintCFG.scala b/src/main/vct/main/stages/PrintCFG.scala new file mode 100644 index 0000000000..2688fb1fc5 --- /dev/null +++ b/src/main/vct/main/stages/PrintCFG.scala @@ -0,0 +1,29 @@ +package vct.main.stages + +import hre.stages.Stage +import sourcecode.Name +import vct.col.ast.{InstanceMethod, Node} +import vct.col.rewrite.Generation +import vct.options.Options +import vct.rewrite.cfg.CFGPrinter + +import java.nio.file.Path + +case object PrintCFG { + def ofOptions(options: Options): Stage[Node[_ <: Generation], Unit] = { + PrintCFG(options.cfgOutput) + } +} + +case class PrintCFG(out: Path) extends Stage[Node[_ <: Generation], Unit] { + + override def friendlyName: String = "Printing control flow graph" + + override def progressWeight: Int = 0 + + override def run(in1: Node[_ <: Generation]): Unit = { + // TODO: Burn this and do it properly + val main_method = in1.transSubnodes.collectFirst{ case m: InstanceMethod[_] if m.o.getPreferredName.getOrElse(Name()).toString.equals("main") => m }.get + CFGPrinter().print_ast_as_cfg(main_method, out) + } +} diff --git a/src/main/vct/main/stages/Stages.scala b/src/main/vct/main/stages/Stages.scala index 4f5714d4a8..78cab0cb4a 100644 --- a/src/main/vct/main/stages/Stages.scala +++ b/src/main/vct/main/stages/Stages.scala @@ -71,8 +71,9 @@ case object Stages { .thenRun(Output.vesuvOfOptions(options)) } - /*def cfgTransformationOfOptions(options: Options, blameProvider: BlameProvider): Stages[Seq[Readable], Unit] = { + def cfgTransformationOfOptions(options: Options, blameProvider: BlameProvider): Stages[Seq[Readable], Unit] = { Parsing.ofOptions(options, blameProvider) - .thenRun() - }*/ + .thenRun(Resolution.ofOptions(options, blameProvider)) + .thenRun(PrintCFG.ofOptions(options)) + } } \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/cfg/Printer.scala b/src/rewrite/vct/rewrite/cfg/CFGPrinter.scala similarity index 98% rename from src/rewrite/vct/rewrite/cfg/Printer.scala rename to src/rewrite/vct/rewrite/cfg/CFGPrinter.scala index 2053b8cbfb..3a11ea2817 100644 --- a/src/rewrite/vct/rewrite/cfg/Printer.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGPrinter.scala @@ -8,7 +8,7 @@ import java.io.Writer import java.nio.file.Path import scala.collection.mutable -case class Printer[G]() { +case class CFGPrinter[G]() { private val node_names: mutable.Map[CFGNode[G], String] = mutable.HashMap[CFGNode[G], String]() private val no_condition: Expr[G] = BooleanValue(value = true)(Origin(Seq())) private var node_index: Int = 0 From 817d06760dc45595491e29f6ef2616505434df00 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 15 Feb 2024 11:32:39 +0100 Subject: [PATCH 20/85] Fixed small errors preventing execution --- src/main/vct/main/stages/PrintCFG.scala | 3 ++- src/rewrite/vct/rewrite/cfg/CFGGenerator.scala | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/vct/main/stages/PrintCFG.scala b/src/main/vct/main/stages/PrintCFG.scala index 2688fb1fc5..3ed85f5d50 100644 --- a/src/main/vct/main/stages/PrintCFG.scala +++ b/src/main/vct/main/stages/PrintCFG.scala @@ -23,7 +23,8 @@ case class PrintCFG(out: Path) extends Stage[Node[_ <: Generation], Unit] { override def run(in1: Node[_ <: Generation]): Unit = { // TODO: Burn this and do it properly - val main_method = in1.transSubnodes.collectFirst{ case m: InstanceMethod[_] if m.o.getPreferredName.getOrElse(Name()).toString.equals("main") => m }.get + // if m.o.getPreferredName.getOrElse(Name()).toString.equals("main") + val main_method = in1.transSubnodes.collectFirst{ case m: InstanceMethod[_] => m }.get CFGPrinter().print_ast_as_cfg(main_method, out) } } diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index c727ee1deb..1dd2af74d8 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -31,6 +31,7 @@ case class CFGGenerator[G]() { if (searched_labels.contains(goto.lbl.decl)) searched_labels(goto.lbl.decl).addOne(cfg_node) else searched_labels.addOne((goto.lbl.decl, mutable.Set(cfg_node))) } + case _ => } converted_nodes.addOne((context, cfg_node)) cfg_node From dc5d57ba71c3d4a3b2679221595842d6d2f1e809 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 15 Feb 2024 13:48:52 +0100 Subject: [PATCH 21/85] Fixed an infinite recursion when referring back to incompletely transformed nodes --- .../vct/rewrite/cfg/CFGGenerator.scala | 5 +- src/rewrite/vct/rewrite/cfg/Index.scala | 146 +++++++++++++++++- 2 files changed, 148 insertions(+), 3 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 1dd2af74d8..12f8da0efc 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -17,7 +17,9 @@ case class CFGGenerator[G]() { // If a node has already been visited, then it should not be created again if (converted_nodes.contains(context)) return converted_nodes(context) // Create new node with its successors (if possible) - val cfg_node: CFGNode[G] = CFGNode(node, find_successors(node, context)) + val cfg_node: CFGNode[G] = CFGNode(node, mutable.Set()) + converted_nodes.addOne((context, cfg_node)) + cfg_node.successors.addAll(find_successors(node, context)) // Handle labels and goto statements node match { case label: Label[G] => { @@ -33,7 +35,6 @@ case class CFGGenerator[G]() { } case _ => } - converted_nodes.addOne((context, cfg_node)) cfg_node } diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 62c1b2c837..56bce55266 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -7,6 +7,11 @@ import scala.collection.mutable case class GlobalIndex[G](indices: mutable.Seq[Index[G]]) { + override def equals(obj: scala.Any): Boolean = obj match { + case GlobalIndex(other_indices) => indices.size == other_indices.size && indices.zip(other_indices).forall(t => t._1.equals(t._2)) + case _ => false + } + def enter_scope(node: Node[G], index: Int = 0): GlobalIndex[G] = GlobalIndex(indices.prepended(Index[G](node, index))) @@ -142,7 +147,6 @@ object Index { case veymont_assign_expression: VeyMontAssignExpression[G] => VeyMontAssignExpressionIndex(veymont_assign_expression) case communicatex: CommunicateX[G] => CommunicateXIndex(communicatex) case statement: Statement[G] => ExpressionContainerIndex(statement, index) - case _ => ??? } def apply[G](node: Node[G], index: Int): Index[G] = from(node, index) @@ -151,11 +155,19 @@ object Index { case class InitialIndex[G](instance_method: InstanceMethod[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = instance_method.body.get + override def equals(obj: scala.Any): Boolean = obj match { + case InitialIndex(m) => m.equals(instance_method) + case _ => false + } } case class RunMethodIndex[G](run_method: RunMethod[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = run_method.body.get + override def equals(obj: scala.Any): Boolean = obj match { + case RunMethodIndex(m) => m.equals(run_method) + case _ => false + } } case class ExpressionContainerIndex[G](statement: Statement[G], index: Int) extends Index[G] { @@ -164,6 +176,10 @@ case class ExpressionContainerIndex[G](statement: Statement[G], index: Int) exte else Set(None) } override def resolve(): Statement[G] = statement + override def equals(obj: scala.Any): Boolean = obj match { + case ExpressionContainerIndex(s, i) => i == index && s.equals(statement) + case _ => false + } } case class AssignmentIndex[G](assign: Assign[G], index: Int) extends Index[G] { @@ -176,6 +192,10 @@ case class AssignmentIndex[G](assign: Assign[G], index: Int) extends Index[G] { case 1 => Eval(assign.value)(assign.value.o) case 2 => assign } + override def equals(obj: scala.Any): Boolean = obj match { + case AssignmentIndex(a, i) => i == index && a.equals(assign) + case _ => false + } } case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index[G] { @@ -191,6 +211,10 @@ case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index if (index % 2 == 0) Eval(pvl_branch.branches.apply(index / 2)._1)(pvl_branch.branches.apply(index / 2)._1.o) else pvl_branch.branches.apply((index - 1) / 2)._2 } + override def equals(obj: scala.Any): Boolean = obj match { + case PVLBranchIndex(b, i) => i == index && b.equals(pvl_branch) + case _ => false + } } case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { @@ -206,11 +230,19 @@ case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { case 2 => pvl_loop.body case 3 => pvl_loop.update } + override def equals(obj: scala.Any): Boolean = obj match { + case PVLLoopIndex(l, i) => i == index && l.equals(pvl_loop) + case _ => false + } } case class LabelIndex[G](label: Label[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = label.stat + override def equals(obj: scala.Any): Boolean = obj match { + case LabelIndex(l) => l.equals(label) + case _ => false + } } case class FramedProofIndex[G](framed_proof: FramedProof[G], index: Int) extends Index[G] { @@ -223,11 +255,19 @@ case class FramedProofIndex[G](framed_proof: FramedProof[G], index: Int) extends case 1 => Eval(framed_proof.post)(framed_proof.post.o) case 2 => framed_proof.body } + override def equals(obj: scala.Any): Boolean = obj match { + case FramedProofIndex(p, i) => i == index && p.equals(framed_proof) + case _ => false + } } case class ExtractIndex[G](extract: Extract[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = extract.contractedStatement + override def equals(obj: scala.Any): Boolean = obj match { + case ExtractIndex(e) => e.equals(extract) + case _ => false + } } case class EvalIndex[G](eval: Eval[G], index: Int, subexpressions: Seq[Statement[G]]) extends Index[G] { @@ -238,6 +278,10 @@ case class EvalIndex[G](eval: Eval[G], index: Int, subexpressions: Seq[Statement } override def resolve(): Statement[G] = subexpressions.apply(index) override def has_statement(): Boolean = subexpressions.nonEmpty + override def equals(obj: scala.Any): Boolean = obj match { + case EvalIndex(e, i, _) => i == index && e.equals(eval) + case _ => false + } } object EvalIndex { def apply[G](eval: Eval[G], index: Int): EvalIndex[G] = new EvalIndex(eval, index) @@ -285,6 +329,10 @@ case class InvokeProcedureIndex[G](invoke_procedure: InvokeProcedure[G], index: invoke_procedure.outArgs.nonEmpty || invoke_procedure.yields.nonEmpty || invoke_procedure.ref.decl.body.nonEmpty + override def equals(obj: scala.Any): Boolean = obj match { + case InvokeProcedureIndex(p, i) => i == index && p.equals(invoke_procedure) + case _ => false + } } case class InvokeConstructorIndex[G](invoke_constructor: InvokeConstructor[G], index: Int) extends Index[G] { @@ -326,6 +374,10 @@ case class InvokeConstructorIndex[G](invoke_constructor: InvokeConstructor[G], i Eval(invoke_constructor.out)(invoke_constructor.out.o) } else invoke_constructor.ref.decl.body.get } + override def equals(obj: scala.Any): Boolean = obj match { + case InvokeConstructorIndex(c, i) => i == index && c.equals(invoke_constructor) + case _ => false + } } case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) extends Index[G] { @@ -367,6 +419,10 @@ case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) exte Eval(expr)(expr.o) } else invoke_method.ref.decl.body.get } + override def equals(obj: scala.Any): Boolean = obj match { + case InvokeMethodIndex(m, i) => i == index && m.equals(invoke_method) + case _ => false + } } case class BlockIndex[G](block: Block[G], index: Int) extends Index[G] { @@ -375,11 +431,19 @@ case class BlockIndex[G](block: Block[G], index: Int) extends Index[G] { else Set(None) } override def resolve(): Statement[G] = block.statements.apply(index) + override def equals(obj: scala.Any): Boolean = obj match { + case BlockIndex(b, i) => i == index && b.equals(block) + case _ => false + } } case class ScopeIndex[G](scope: Scope[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = scope.body + override def equals(obj: scala.Any): Boolean = obj match { + case ScopeIndex(s) => s.equals(scope) + case _ => false + } } case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { @@ -395,17 +459,29 @@ case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { if (index % 2 == 0) Eval(branch.branches.apply(index / 2)._1)(branch.branches.apply(index / 2)._1.o) else branch.branches.apply((index - 1) / 2)._2 } + override def equals(obj: scala.Any): Boolean = obj match { + case BranchIndex(b, i) => i == index && b.equals(branch) + case _ => false + } } case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = indet_branch.branches.apply(index) + override def equals(obj: scala.Any): Boolean = obj match { + case IndetBranchIndex(b, i) => i == index && b.equals(indet_branch) + case _ => false + } } // TODO: Switch cases could be multiple context indices deep; this does not work with the single index for make_step() case class SwitchIndex[G](switch: Switch[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = switch.body + override def equals(obj: scala.Any): Boolean = obj match { + case SwitchIndex(s) => s.equals(switch) + case _ => false + } } case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { @@ -421,11 +497,19 @@ case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { case 2 => loop.body case 3 => loop.update } + override def equals(obj: scala.Any): Boolean = obj match { + case LoopIndex(l, i) => i == index && l.equals(loop) + case _ => false + } } case class RangedForIndex[G](ranged_for: RangedFor[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = ranged_for.body + override def equals(obj: scala.Any): Boolean = obj match { + case RangedForIndex(r) => r.equals(ranged_for) + case _ => false + } } case class TryCatchFinallyIndex[G](try_catch_finally: TryCatchFinally[G], index: Int) extends Index[G] { @@ -438,51 +522,91 @@ case class TryCatchFinallyIndex[G](try_catch_finally: TryCatchFinally[G], index: case 1 => try_catch_finally.after case _ => try_catch_finally.catches.apply(index - 2).body } + override def equals(obj: scala.Any): Boolean = obj match { + case TryCatchFinallyIndex(t, i) => i == index && t.equals(try_catch_finally) + case _ => false + } } case class SynchronizedIndex[G](synchronized: Synchronized[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = synchronized.body + override def equals(obj: scala.Any): Boolean = obj match { + case SynchronizedIndex(s) => s.equals(synchronized) + case _ => false + } } case class ParInvariantIndex[G](par_invariant: ParInvariant[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = par_invariant.content + override def equals(obj: scala.Any): Boolean = obj match { + case ParInvariantIndex(p) => p.equals(par_invariant) + case _ => false + } } case class ParAtomicIndex[G](par_atomic: ParAtomic[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = par_atomic.content + override def equals(obj: scala.Any): Boolean = obj match { + case ParAtomicIndex(p) => p.equals(par_atomic) + case _ => false + } } case class ParBarrierIndex[G](par_barrier: ParBarrier[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = par_barrier.content + override def equals(obj: scala.Any): Boolean = obj match { + case ParBarrierIndex(p) => p.equals(par_barrier) + case _ => false + } } case class VecBlockIndex[G](vec_block: VecBlock[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = vec_block.content + override def equals(obj: scala.Any): Boolean = obj match { + case VecBlockIndex(v) => v.equals(vec_block) + case _ => false + } } case class WandPackageIndex[G](wand_package: WandPackage[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = wand_package.proof + override def equals(obj: scala.Any): Boolean = obj match { + case WandPackageIndex(w) => w.equals(wand_package) + case _ => false + } } case class ModelDoIndex[G](model_do: ModelDo[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = model_do.impl + override def equals(obj: scala.Any): Boolean = obj match { + case ModelDoIndex(m) => m.equals(model_do) + case _ => false + } } case class CPPLifetimeScopeIndex[G](cpp_lifetime_scope: CPPLifetimeScope[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = cpp_lifetime_scope.body + override def equals(obj: scala.Any): Boolean = obj match { + case CPPLifetimeScopeIndex(c) => c.equals(cpp_lifetime_scope) + case _ => false + } } case class UnresolvedSeqBranchIndex[G](unresolved_seq_branch: UnresolvedSeqBranch[G], index: Int) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = unresolved_seq_branch.branches.apply(index)._2 + override def equals(obj: scala.Any): Boolean = obj match { + case UnresolvedSeqBranchIndex(u, i) => i == index && u.equals(unresolved_seq_branch) + case _ => false + } } case class UnresolvedSeqLoopIndex[G](unresolved_seq_loop: UnresolvedSeqLoop[G], index: Int) extends Index[G] { @@ -494,6 +618,10 @@ case class UnresolvedSeqLoopIndex[G](unresolved_seq_loop: UnresolvedSeqLoop[G], case 0 => Eval(unresolved_seq_loop.cond)(unresolved_seq_loop.cond.o) case 1 => unresolved_seq_loop.body } + override def equals(obj: scala.Any): Boolean = obj match { + case UnresolvedSeqLoopIndex(u, i) => i == index && u.equals(unresolved_seq_loop) + case _ => false + } } case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index[G] { @@ -502,19 +630,35 @@ case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index case 0 => seq_branch.yes case 1 => seq_branch.no.get } + override def equals(obj: scala.Any): Boolean = obj match { + case SeqBranchIndex(s, i) => i == index && s.equals(seq_branch) + case _ => false + } } case class SeqLoopIndex[G](seq_loop: SeqLoop[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = seq_loop.body + override def equals(obj: scala.Any): Boolean = obj match { + case SeqLoopIndex(s) => s.equals(seq_loop) + case _ => false + } } case class VeyMontAssignExpressionIndex[G](veymont_assign_expression: VeyMontAssignExpression[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = veymont_assign_expression.assign + override def equals(obj: scala.Any): Boolean = obj match { + case VeyMontAssignExpressionIndex(v) => v.equals(veymont_assign_expression) + case _ => false + } } case class CommunicateXIndex[G](communicatex: CommunicateX[G]) extends Index[G] { override def make_step(): Set[Option[Index[G]]] = Set(None) override def resolve(): Statement[G] = communicatex.assign + override def equals(obj: scala.Any): Boolean = obj match { + case CommunicateXIndex(c) => c.equals(communicatex) + case _ => false + } } \ No newline at end of file From b7c114eeebd873d61cb357650aceb3f07d61542f Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 15 Feb 2024 15:15:56 +0100 Subject: [PATCH 22/85] Added path conditions to CFG edges --- .../vct/rewrite/cfg/CFGGenerator.scala | 11 +- src/rewrite/vct/rewrite/cfg/Index.scala | 168 +++++++++--------- src/rewrite/vct/rewrite/cfg/Utils.scala | 17 +- 3 files changed, 110 insertions(+), 86 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 12f8da0efc..e88421cf34 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -39,6 +39,7 @@ case class CFGGenerator[G]() { } private def find_successors(node: Statement[G], context: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = node match { + // TODO: Can these be simplified using evaluate_first()? case PVLBranch(branches) => mutable.Set(CFGEdge(convert(Eval(branches.head._1)(branches.head._1.o), context.enter_scope(node)), None)) case PVLLoop(init, _, _, _, _) => @@ -113,7 +114,7 @@ case class CFGGenerator[G]() { } case IndetBranch(branches) => mutable.LinkedHashSet.from(branches.zipWithIndex.map(b => CFGEdge(convert(b._1, context.enter_scope(node, b._2)), None))) - case Switch(expr, body) => + case Switch(expr, body) => // TODO: Add conditions to switch statement transitions Utils.find_all_cases(body, context.enter_scope(node)).map(t => CFGEdge(convert(t._1, t._2), None)) // TODO: Handle side effects in switch statement conditions case Loop(init, _, _, _, _) => mutable.Set(CFGEdge(convert(init, context.enter_scope(node)), None)) @@ -153,15 +154,15 @@ case class CFGGenerator[G]() { case PVLSeqAssign(_, _, value) => handle_expression_container(node, Eval(value)(value.o), context, sequential_successor(context)) case Communicate(_, _) => sequential_successor(context) case SeqAssign(_, _, value) => handle_expression_container(node, Eval(value)(value.o), context, sequential_successor(context)) - case UnresolvedSeqBranch(branches) => + case UnresolvedSeqBranch(branches) => // TODO: Consider branch conditions mutable.LinkedHashSet.from(branches.zipWithIndex.map(b => CFGEdge(convert(b._1._2, context.enter_scope(node, b._2)), None))) case UnresolvedSeqLoop(cond, _, _) => mutable.Set(CFGEdge(convert(Eval(cond)(cond.o), context.enter_scope(node)), None)) - case SeqBranch(_, yes, no) => no match { + case SeqBranch(_, yes, no) => no match { // TODO: What are the conditions here? case Some(stmt) => mutable.Set(CFGEdge(convert(yes, context.enter_scope(node, 0)), None), CFGEdge(convert(stmt, context.enter_scope(node, 1)), None)) case None => mutable.Set(CFGEdge(convert(yes, context.enter_scope(node)), None)) } - case SeqLoop(_, _, body) => + case SeqLoop(_, _, body) => // TODO: What are the conditions here? mutable.Set(CFGEdge(convert(body, context.enter_scope(node)), None)) case VeyMontAssignExpression(_, assign) => mutable.Set(CFGEdge(convert(assign, context.enter_scope(node)), None)) @@ -185,7 +186,7 @@ case class CFGGenerator[G]() { } private def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = - index.make_step().map(i => CFGEdge(convert(i.resolve(), i), None)) + index.make_step().map(i => CFGEdge(convert(i._1.resolve(), i._1), i._2)) private def return_successor(index: GlobalIndex[G]): CFGNode[G] = { val new_index: GlobalIndex[G] = index.return_from_call() diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 56bce55266..f348251187 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -15,14 +15,14 @@ case class GlobalIndex[G](indices: mutable.Seq[Index[G]]) { def enter_scope(node: Node[G], index: Int = 0): GlobalIndex[G] = GlobalIndex(indices.prepended(Index[G](node, index))) - def make_step(): mutable.Set[GlobalIndex[G]] = { - if (indices.isEmpty) return mutable.Set[GlobalIndex[G]]() - val steps: Set[Option[Index[G]]] = indices.head.make_step() - val res = mutable.Set[GlobalIndex[G]]() + def make_step(): mutable.Set[(GlobalIndex[G], Option[Expr[G]])] = { + if (indices.isEmpty) return mutable.Set() + val steps: Set[(Option[Index[G]], Option[Expr[G]])] = indices.head.make_step() + val res = mutable.Set[(GlobalIndex[G], Option[Expr[G]])]() for (step <- steps) { step match { - case Some(index) => res.addOne(GlobalIndex(index +: indices.tail)) - case None => res.addAll(GlobalIndex(indices.tail).make_step()) + case (Some(index), cond) => res.addOne((GlobalIndex(index +: indices.tail), cond)) + case (None, cond) => res.addAll(GlobalIndex(indices.tail).make_step().map(tup => (tup._1, Utils.and(tup._2, cond)))) } } res @@ -40,7 +40,7 @@ case class GlobalIndex[G](indices: mutable.Seq[Index[G]]) { } // Find the next statement // TODO: Does this always return exactly one next step? - GlobalIndex(stack.tail).make_step().head + GlobalIndex(stack.tail).make_step().head._1 } def handle_exception(e: Expr[G]): GlobalIndex[G] = { @@ -66,7 +66,7 @@ case class GlobalIndex[G](indices: mutable.Seq[Index[G]]) { } // Find the next statement // TODO: Does this always return exactly one next step? - GlobalIndex(stack.tail).make_step().head + GlobalIndex(stack.tail).make_step().head._1 } def continue_innermost_loop(): GlobalIndex[G] = { @@ -89,11 +89,12 @@ sealed trait Index[G] { /** * Defines the set of possible next steps. An index in the returned set indicates that this index can replace the * previous index at the top level of the index stack. A None value indicates that a step is possible, but it reaches - * outside the scope of this index to the index below. + * outside the scope of this index to the index below. Along with these steps, this method returns the conditions for + * such a step in the form of a boolean expression, or None if a transition is certain to occur. * - * @return A set of all steps possible from the current index + * @return A set of all steps possible from the current index, along with conditions for taking them */ - def make_step(): Set[Option[Index[G]]] + def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] /** * Returns the statement that corresponds to the current index. @@ -153,7 +154,7 @@ object Index { } case class InitialIndex[G](instance_method: InstanceMethod[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = instance_method.body.get override def equals(obj: scala.Any): Boolean = obj match { case InitialIndex(m) => m.equals(instance_method) @@ -162,8 +163,9 @@ case class InitialIndex[G](instance_method: InstanceMethod[G]) extends Index[G] } case class RunMethodIndex[G](run_method: RunMethod[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = run_method.body.get + override def has_statement(): Boolean = run_method.body.nonEmpty override def equals(obj: scala.Any): Boolean = obj match { case RunMethodIndex(m) => m.equals(run_method) case _ => false @@ -171,9 +173,9 @@ case class RunMethodIndex[G](run_method: RunMethod[G]) extends Index[G] { } case class ExpressionContainerIndex[G](statement: Statement[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = { - if (index == 0) Set(Some(ExpressionContainerIndex(statement, 1))) - else Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + if (index == 0) Set((Some(ExpressionContainerIndex(statement, 1)), None)) + else Set((None, None)) } override def resolve(): Statement[G] = statement override def equals(obj: scala.Any): Boolean = obj match { @@ -183,9 +185,9 @@ case class ExpressionContainerIndex[G](statement: Statement[G], index: Int) exte } case class AssignmentIndex[G](assign: Assign[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = { - if (index < 2) Set(Some(AssignmentIndex(assign, index + 1))) - else Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + if (index < 2) Set((Some(AssignmentIndex(assign, index + 1)), None)) + else Set((None, None)) } override def resolve(): Statement[G] = index match { case 0 => Eval(assign.target)(assign.target.o) @@ -199,13 +201,15 @@ case class AssignmentIndex[G](assign: Assign[G], index: Int) extends Index[G] { } case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = { + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { // Indices 0, 2, 4, ... are the conditions, indices 1, 3, 5, ... are the branch bodies if (index % 2 == 0 && index < 2 * (pvl_branch.branches.size - 1)) - Set(Some(PVLBranchIndex(pvl_branch, index + 2)), Some(PVLBranchIndex(pvl_branch, index + 1))) + Set((Some(PVLBranchIndex(pvl_branch, index + 2)), Some(Utils.negate(pvl_branch.branches.apply(index / 2)._1))), + (Some(PVLBranchIndex(pvl_branch, index + 1)), Some(pvl_branch.branches.apply(index / 2)._1))) else if (index == 2 * (pvl_branch.branches.size - 1)) - Set(Some(PVLBranchIndex(pvl_branch, index + 1)), None) - else Set(None) + Set((Some(PVLBranchIndex(pvl_branch, index + 1)), Some(pvl_branch.branches.apply(index / 2)._1)), + (None, Some(Utils.negate(pvl_branch.branches.apply(index / 2)._1)))) + else Set((None, None)) } override def resolve(): Statement[G] = { if (index % 2 == 0) Eval(pvl_branch.branches.apply(index / 2)._1)(pvl_branch.branches.apply(index / 2)._1.o) @@ -218,11 +222,11 @@ case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index } case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = index match { - case 0 => Set(Some(PVLLoopIndex(pvl_loop, 1))) - case 1 => Set(Some(PVLLoopIndex(pvl_loop, 2)), None) - case 2 => Set(Some(PVLLoopIndex(pvl_loop, 3))) - case 3 => Set(Some(PVLLoopIndex(pvl_loop, 1))) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = index match { + case 0 => Set((Some(PVLLoopIndex(pvl_loop, 1)), None)) + case 1 => Set((Some(PVLLoopIndex(pvl_loop, 2)), Some(pvl_loop.cond)), (None, Some(Utils.negate(pvl_loop.cond)))) + case 2 => Set((Some(PVLLoopIndex(pvl_loop, 3)), None)) + case 3 => Set((Some(PVLLoopIndex(pvl_loop, 1)), None)) } override def resolve(): Statement[G] = index match { case 0 => pvl_loop.init @@ -237,7 +241,7 @@ case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { } case class LabelIndex[G](label: Label[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = label.stat override def equals(obj: scala.Any): Boolean = obj match { case LabelIndex(l) => l.equals(label) @@ -246,9 +250,9 @@ case class LabelIndex[G](label: Label[G]) extends Index[G] { } case class FramedProofIndex[G](framed_proof: FramedProof[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = { - if (index < 2) Set(Some(FramedProofIndex(framed_proof, index + 1))) - else Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + if (index < 2) Set((Some(FramedProofIndex(framed_proof, index + 1)), None)) + else Set((None, None)) } override def resolve(): Statement[G] = index match { case 0 => Eval(framed_proof.pre)(framed_proof.pre.o) @@ -262,7 +266,7 @@ case class FramedProofIndex[G](framed_proof: FramedProof[G], index: Int) extends } case class ExtractIndex[G](extract: Extract[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = extract.contractedStatement override def equals(obj: scala.Any): Boolean = obj match { case ExtractIndex(e) => e.equals(extract) @@ -272,9 +276,10 @@ case class ExtractIndex[G](extract: Extract[G]) extends Index[G] { case class EvalIndex[G](eval: Eval[G], index: Int, subexpressions: Seq[Statement[G]]) extends Index[G] { def this(eval: Eval[G], index: Int) = this(eval, index, Utils.find_all_subexpressions(eval.expr)) - override def make_step(): Set[Option[Index[G]]] = { - if (index < subexpressions.size - 1) Set(Some(EvalIndex(eval, index + 1))) - else Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + // TODO: Consider conditions in expressions, too + if (index < subexpressions.size - 1) Set((Some(EvalIndex(eval, index + 1)), None)) + else Set((None, None)) } override def resolve(): Statement[G] = subexpressions.apply(index) override def has_statement(): Boolean = subexpressions.nonEmpty @@ -294,15 +299,15 @@ case class InvokeProcedureIndex[G](invoke_procedure: InvokeProcedure[G], index: // 3. outArgs // 4. yields // 5. procedure body - override def make_step(): Set[Option[Index[G]]] = { + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { val args: Seq[Expr[G]] = invoke_procedure.args val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_procedure.givenMap val outArgs: Seq[Expr[G]] = invoke_procedure.outArgs val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_procedure.yields if (index < args.size + givenMap.size + outArgs.size + yields.size - 1 || index == args.size + givenMap.size + outArgs.size + yields.size - 1 && invoke_procedure.ref.decl.body.nonEmpty) - Set(Some(InvokeProcedureIndex(invoke_procedure, index + 1))) - else Set(None) + Set((Some(InvokeProcedureIndex(invoke_procedure, index + 1)), None)) + else Set((None, None)) } override def resolve(): Statement[G] = { val args: Seq[Expr[G]] = invoke_procedure.args @@ -343,15 +348,15 @@ case class InvokeConstructorIndex[G](invoke_constructor: InvokeConstructor[G], i // 4. yields // 5. out // 6. constructor body - override def make_step(): Set[Option[Index[G]]] = { + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { val args: Seq[Expr[G]] = invoke_constructor.args val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_constructor.givenMap val outArgs: Seq[Expr[G]] = invoke_constructor.outArgs val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_constructor.yields if (index < args.size + givenMap.size + outArgs.size + yields.size || index == args.size + givenMap.size + outArgs.size + yields.size && invoke_constructor.ref.decl.body.nonEmpty) - Set(Some(InvokeConstructorIndex(invoke_constructor, index + 1))) - else Set(None) + Set((Some(InvokeConstructorIndex(invoke_constructor, index + 1)), None)) + else Set((None, None)) } override def resolve(): Statement[G] = { val args: Seq[Expr[G]] = invoke_constructor.args @@ -388,15 +393,15 @@ case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) exte // 4. outArgs // 5. yields // 6. method body - override def make_step(): Set[Option[Index[G]]] = { + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { val args: Seq[Expr[G]] = invoke_method.args val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_method.givenMap val outArgs: Seq[Expr[G]] = invoke_method.outArgs val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_method.yields if (index < args.size + givenMap.size + outArgs.size + yields.size || index == args.size + givenMap.size + outArgs.size + yields.size && invoke_method.ref.decl.body.nonEmpty) - Set(Some(InvokeMethodIndex(invoke_method, index + 1))) - else Set(None) + Set((Some(InvokeMethodIndex(invoke_method, index + 1)), None)) + else Set((None, None)) } override def resolve(): Statement[G] = { val args: Seq[Expr[G]] = invoke_method.args @@ -426,9 +431,9 @@ case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) exte } case class BlockIndex[G](block: Block[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = { - if (index < block.statements.size - 1) Set(Some(BlockIndex(block, index + 1))) - else Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + if (index < block.statements.size - 1) Set((Some(BlockIndex(block, index + 1)), None)) + else Set((None, None)) } override def resolve(): Statement[G] = block.statements.apply(index) override def equals(obj: scala.Any): Boolean = obj match { @@ -438,7 +443,7 @@ case class BlockIndex[G](block: Block[G], index: Int) extends Index[G] { } case class ScopeIndex[G](scope: Scope[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = scope.body override def equals(obj: scala.Any): Boolean = obj match { case ScopeIndex(s) => s.equals(scope) @@ -447,13 +452,15 @@ case class ScopeIndex[G](scope: Scope[G]) extends Index[G] { } case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = { + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { // Indices 0, 2, 4, ... are the conditions, indices 1, 3, 5, ... are the branch bodies if (index % 2 == 0 && index < 2 * (branch.branches.size - 1)) - Set(Some(BranchIndex(branch, index + 2)), Some(BranchIndex(branch, index + 1))) + Set((Some(BranchIndex(branch, index + 2)), Some(Utils.negate(branch.branches.apply(index / 2)._1))), + (Some(BranchIndex(branch, index + 1)), Some(branch.branches.apply(index / 2)._1))) else if (index == 2 * (branch.branches.size - 1)) - Set(Some(BranchIndex(branch, index + 1)), None) - else Set(None) + Set((Some(BranchIndex(branch, index + 1)), Some(branch.branches.apply(index / 2)._1)), + (None, Some(Utils.negate(branch.branches.apply(index / 2)._1)))) + else Set((None, None)) } override def resolve(): Statement[G] = { if (index % 2 == 0) Eval(branch.branches.apply(index / 2)._1)(branch.branches.apply(index / 2)._1.o) @@ -466,7 +473,7 @@ case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { } case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = indet_branch.branches.apply(index) override def equals(obj: scala.Any): Boolean = obj match { case IndetBranchIndex(b, i) => i == index && b.equals(indet_branch) @@ -476,7 +483,7 @@ case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends // TODO: Switch cases could be multiple context indices deep; this does not work with the single index for make_step() case class SwitchIndex[G](switch: Switch[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = switch.body override def equals(obj: scala.Any): Boolean = obj match { case SwitchIndex(s) => s.equals(switch) @@ -485,11 +492,11 @@ case class SwitchIndex[G](switch: Switch[G]) extends Index[G] { } case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = index match { - case 0 => Set(Some(LoopIndex(loop, 1))) - case 1 => Set(Some(LoopIndex(loop, 2)), None) - case 2 => Set(Some(LoopIndex(loop, 3))) - case 3 => Set(Some(LoopIndex(loop, 1))) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = index match { + case 0 => Set((Some(LoopIndex(loop, 1)), None)) + case 1 => Set((Some(LoopIndex(loop, 2)), Some(loop.cond)), (None, Some(Utils.negate(loop.cond)))) + case 2 => Set((Some(LoopIndex(loop, 3)), None)) + case 3 => Set((Some(LoopIndex(loop, 1)), None)) } override def resolve(): Statement[G] = index match { case 0 => loop.init @@ -504,7 +511,7 @@ case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { } case class RangedForIndex[G](ranged_for: RangedFor[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((Some(this), None), (None, None)) override def resolve(): Statement[G] = ranged_for.body override def equals(obj: scala.Any): Boolean = obj match { case RangedForIndex(r) => r.equals(ranged_for) @@ -513,9 +520,9 @@ case class RangedForIndex[G](ranged_for: RangedFor[G]) extends Index[G] { } case class TryCatchFinallyIndex[G](try_catch_finally: TryCatchFinally[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = { - if (index != 1) Set(Some(TryCatchFinallyIndex(try_catch_finally, 1))) - else Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + if (index != 1) Set((Some(TryCatchFinallyIndex(try_catch_finally, 1)), None)) + else Set((None, None)) } override def resolve(): Statement[G] = index match { case 0 => try_catch_finally.body @@ -529,7 +536,7 @@ case class TryCatchFinallyIndex[G](try_catch_finally: TryCatchFinally[G], index: } case class SynchronizedIndex[G](synchronized: Synchronized[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = synchronized.body override def equals(obj: scala.Any): Boolean = obj match { case SynchronizedIndex(s) => s.equals(synchronized) @@ -538,7 +545,7 @@ case class SynchronizedIndex[G](synchronized: Synchronized[G]) extends Index[G] } case class ParInvariantIndex[G](par_invariant: ParInvariant[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = par_invariant.content override def equals(obj: scala.Any): Boolean = obj match { case ParInvariantIndex(p) => p.equals(par_invariant) @@ -547,7 +554,7 @@ case class ParInvariantIndex[G](par_invariant: ParInvariant[G]) extends Index[G] } case class ParAtomicIndex[G](par_atomic: ParAtomic[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = par_atomic.content override def equals(obj: scala.Any): Boolean = obj match { case ParAtomicIndex(p) => p.equals(par_atomic) @@ -556,7 +563,7 @@ case class ParAtomicIndex[G](par_atomic: ParAtomic[G]) extends Index[G] { } case class ParBarrierIndex[G](par_barrier: ParBarrier[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = par_barrier.content override def equals(obj: scala.Any): Boolean = obj match { case ParBarrierIndex(p) => p.equals(par_barrier) @@ -565,7 +572,7 @@ case class ParBarrierIndex[G](par_barrier: ParBarrier[G]) extends Index[G] { } case class VecBlockIndex[G](vec_block: VecBlock[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = vec_block.content override def equals(obj: scala.Any): Boolean = obj match { case VecBlockIndex(v) => v.equals(vec_block) @@ -574,7 +581,7 @@ case class VecBlockIndex[G](vec_block: VecBlock[G]) extends Index[G] { } case class WandPackageIndex[G](wand_package: WandPackage[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = wand_package.proof override def equals(obj: scala.Any): Boolean = obj match { case WandPackageIndex(w) => w.equals(wand_package) @@ -583,7 +590,7 @@ case class WandPackageIndex[G](wand_package: WandPackage[G]) extends Index[G] { } case class ModelDoIndex[G](model_do: ModelDo[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = model_do.impl override def equals(obj: scala.Any): Boolean = obj match { case ModelDoIndex(m) => m.equals(model_do) @@ -592,7 +599,7 @@ case class ModelDoIndex[G](model_do: ModelDo[G]) extends Index[G] { } case class CPPLifetimeScopeIndex[G](cpp_lifetime_scope: CPPLifetimeScope[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = cpp_lifetime_scope.body override def equals(obj: scala.Any): Boolean = obj match { case CPPLifetimeScopeIndex(c) => c.equals(cpp_lifetime_scope) @@ -601,7 +608,7 @@ case class CPPLifetimeScopeIndex[G](cpp_lifetime_scope: CPPLifetimeScope[G]) ext } case class UnresolvedSeqBranchIndex[G](unresolved_seq_branch: UnresolvedSeqBranch[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = unresolved_seq_branch.branches.apply(index)._2 override def equals(obj: scala.Any): Boolean = obj match { case UnresolvedSeqBranchIndex(u, i) => i == index && u.equals(unresolved_seq_branch) @@ -610,9 +617,10 @@ case class UnresolvedSeqBranchIndex[G](unresolved_seq_branch: UnresolvedSeqBranc } case class UnresolvedSeqLoopIndex[G](unresolved_seq_loop: UnresolvedSeqLoop[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = index match { - case 0 => Set(Some(UnresolvedSeqLoopIndex(unresolved_seq_loop, 1)), None) - case 1 => Set(Some(UnresolvedSeqLoopIndex(unresolved_seq_loop, 0))) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = index match { + case 0 => Set((Some(UnresolvedSeqLoopIndex(unresolved_seq_loop, 1)), Some(unresolved_seq_loop.cond)), + (None, Some(Utils.negate(unresolved_seq_loop.cond)))) + case 1 => Set((Some(UnresolvedSeqLoopIndex(unresolved_seq_loop, 0)), None)) } override def resolve(): Statement[G] = index match { case 0 => Eval(unresolved_seq_loop.cond)(unresolved_seq_loop.cond.o) @@ -625,7 +633,7 @@ case class UnresolvedSeqLoopIndex[G](unresolved_seq_loop: UnresolvedSeqLoop[G], } case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = index match { case 0 => seq_branch.yes case 1 => seq_branch.no.get @@ -637,7 +645,7 @@ case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index } case class SeqLoopIndex[G](seq_loop: SeqLoop[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = seq_loop.body override def equals(obj: scala.Any): Boolean = obj match { case SeqLoopIndex(s) => s.equals(seq_loop) @@ -646,7 +654,7 @@ case class SeqLoopIndex[G](seq_loop: SeqLoop[G]) extends Index[G] { } case class VeyMontAssignExpressionIndex[G](veymont_assign_expression: VeyMontAssignExpression[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = veymont_assign_expression.assign override def equals(obj: scala.Any): Boolean = obj match { case VeyMontAssignExpressionIndex(v) => v.equals(veymont_assign_expression) @@ -655,7 +663,7 @@ case class VeyMontAssignExpressionIndex[G](veymont_assign_expression: VeyMontAss } case class CommunicateXIndex[G](communicatex: CommunicateX[G]) extends Index[G] { - override def make_step(): Set[Option[Index[G]]] = Set(None) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) override def resolve(): Statement[G] = communicatex.assign override def equals(obj: scala.Any): Boolean = obj match { case CommunicateXIndex(c) => c.equals(communicatex) diff --git a/src/rewrite/vct/rewrite/cfg/Utils.scala b/src/rewrite/vct/rewrite/cfg/Utils.scala index c2041028fc..0b8617334c 100644 --- a/src/rewrite/vct/rewrite/cfg/Utils.scala +++ b/src/rewrite/vct/rewrite/cfg/Utils.scala @@ -9,6 +9,7 @@ import scala.collection.mutable object Utils { def find_all_subexpressions[G](expr: Expr[G]): Seq[Statement[G]] = expr match { + // Expressions causing side effects case pae @ PreAssignExpression(target, value) => Seq(Assign(target, value)(pae.blame)(pae.o)) case pae @ PostAssignExpression(target, value) => Seq(Assign(target, value)(pae.blame)(pae.o)) case With(pre, value) => ??? @@ -20,12 +21,13 @@ object Utils { case pi @ ProcedureInvocation(ref, args, outArgs, typeArgs, givenMap, yields) => Seq(InvokeProcedure(ref, args, outArgs, typeArgs, givenMap, yields)(pi.blame)(pi.o)) case no @ NewObject(cls) => Seq(Instantiate(cls, get_out_variable(cls, no.o))(no.o)) + // Expressions that affect the edge condition // TODO: Consider conditions for control flow graph case Select(condition, whenTrue, whenFalse) => Seq(condition, whenTrue, whenFalse).flatMap(e => find_all_subexpressions(e)) case Implies(left, right) => Seq(left, right).flatMap(e => find_all_subexpressions(e)) case And(left, right) => Seq(left, right).flatMap(e => find_all_subexpressions(e)) case Or(left, right) => Seq(left, right).flatMap(e => find_all_subexpressions(e)) - // + // General recursion case case _ => expr.subnodes.collect{ case ex: Expr[G] => ex }.flatMap(e => find_all_subexpressions(e)) } @@ -40,4 +42,17 @@ object Utils { case c: SwitchCase[G] => mutable.Set((c, index)) case _ => mutable.Set() // TODO: Assuming that there are no cases in deeper structures (branches, loops etc.) } + + def negate[G](expr: Expr[G]): Expr[G] = expr match { + case Not(inner) => inner + case _ => Not(expr)(expr.o) + } + + def and[G](expr1: Option[Expr[G]], expr2: Option[Expr[G]]): Option[Expr[G]] = expr1 match { + case None => expr2 + case Some(e1) => expr2 match { + case None => expr1 + case Some(e2) => Some(And(e1, e2)(e1.o)) // TODO: Ignore origin of e2? + } + } } From 7daac9ef88c55081d1e4573d144224e71f00d649 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 15 Feb 2024 17:02:40 +0100 Subject: [PATCH 23/85] Finding main method automatically, added support for unresolved-containers, some code cleanup --- src/main/vct/main/stages/PrintCFG.scala | 5 +- .../vct/rewrite/cfg/CFGGenerator.scala | 79 +++++++------------ src/rewrite/vct/rewrite/cfg/Index.scala | 18 ++++- 3 files changed, 44 insertions(+), 58 deletions(-) diff --git a/src/main/vct/main/stages/PrintCFG.scala b/src/main/vct/main/stages/PrintCFG.scala index 3ed85f5d50..e4c63e2e48 100644 --- a/src/main/vct/main/stages/PrintCFG.scala +++ b/src/main/vct/main/stages/PrintCFG.scala @@ -22,9 +22,8 @@ case class PrintCFG(out: Path) extends Stage[Node[_ <: Generation], Unit] { override def progressWeight: Int = 0 override def run(in1: Node[_ <: Generation]): Unit = { - // TODO: Burn this and do it properly - // if m.o.getPreferredName.getOrElse(Name()).toString.equals("main") - val main_method = in1.transSubnodes.collectFirst{ case m: InstanceMethod[_] => m }.get + // TODO: Is there a better way to find a "main" method? + val main_method = in1.transSubnodes.collectFirst{ case m: InstanceMethod[_] if m.o.getPreferredName.get.snake.equals("main") => m }.get CFGPrinter().print_ast_as_cfg(main_method, out) } } diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index e88421cf34..03d6336a59 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -40,25 +40,22 @@ case class CFGGenerator[G]() { private def find_successors(node: Statement[G], context: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = node match { // TODO: Can these be simplified using evaluate_first()? - case PVLBranch(branches) => - mutable.Set(CFGEdge(convert(Eval(branches.head._1)(branches.head._1.o), context.enter_scope(node)), None)) - case PVLLoop(init, _, _, _, _) => - mutable.Set(CFGEdge(convert(init, context.enter_scope(node)), None)) + case PVLBranch(_) => evaluate_first(context.enter_scope(node)) + case PVLLoop(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) // NonExecutableStatement case LocalDecl(_) => sequential_successor(context) case SpecIgnoreStart() => sequential_successor(context) // TODO: What is this? case SpecIgnoreEnd() => sequential_successor(context) // NormallyCompletingStatement - case Assign(target, _) => context.indices.head match { + case Assign(_, _) => context.indices.head match { case AssignmentIndex(a, _) if a == node => sequential_successor(context) - case _ => mutable.Set(CFGEdge(convert(Eval(target)(target.o), context.enter_scope(node)), None)) + case _ => evaluate_first(context.enter_scope(node)) } case Send(_, _, res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) case Recv(_) => sequential_successor(context) case DefaultCase() => sequential_successor(context) case Case(pattern) => sequential_successor(context) // TODO: Handle expression side effects in switch case conditions - case Label(_, stat) => - mutable.Set(CFGEdge(convert(stat, context.enter_scope(node)), None)) + case Label(_, _) => evaluate_first(context.enter_scope(node)) case Goto(lbl) => found_labels.get(lbl.decl) match { case Some(node) => mutable.Set(CFGEdge(node, None)) case None => mutable.Set() @@ -83,10 +80,8 @@ case class CFGGenerator[G]() { case Unfold(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) case WandApply(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) case Havoc(_) => sequential_successor(context) - case FramedProof(pre, _, _) => - mutable.Set(CFGEdge(convert(Eval(pre)(pre.o), context.enter_scope(node)), None)) - case Extract(contractedStatement) => - mutable.Set(CFGEdge(convert(contractedStatement, context.enter_scope(node)), None)) + case FramedProof(_, _, _) => evaluate_first(context.enter_scope(node)) + case Extract(_) => evaluate_first(context.enter_scope(node)) // ExceptionalStatement case Eval(_) => evaluate_first(context.enter_scope(node)) case Return(result) => handle_expression_container(node, Eval(result)(result.o), context, mutable.Set(CFGEdge(return_successor(context), None))) @@ -104,46 +99,30 @@ case class CFGGenerator[G]() { case InvokeConstructor(_, _, _, _, _, _, _) => evaluate_first(context.enter_scope(node)) case InvokeMethod(_, _, _, _, _, _, _) => evaluate_first(context.enter_scope(node)) // CompositeStatement - case Block(statements) => - mutable.Set(CFGEdge(convert(statements.head, context.enter_scope(node)), None)) - case Scope(_, body) => - mutable.Set(CFGEdge(convert(body, context.enter_scope(node)), None)) - case Branch(branches) => { - val eval: Eval[G] = Eval(branches.head._1)(branches.head._1.o) - mutable.Set(CFGEdge(convert(eval, context.enter_scope(node)), None)) - } + case Block(_) => evaluate_first(context.enter_scope(node)) + case Scope(_, _) => evaluate_first(context.enter_scope(node)) + case Branch(_) => evaluate_first(context.enter_scope(node)) case IndetBranch(branches) => mutable.LinkedHashSet.from(branches.zipWithIndex.map(b => CFGEdge(convert(b._1, context.enter_scope(node, b._2)), None))) case Switch(expr, body) => // TODO: Add conditions to switch statement transitions Utils.find_all_cases(body, context.enter_scope(node)).map(t => CFGEdge(convert(t._1, t._2), None)) // TODO: Handle side effects in switch statement conditions - case Loop(init, _, _, _, _) => - mutable.Set(CFGEdge(convert(init, context.enter_scope(node)), None)) - case RangedFor(_, _, body) => - mutable.Set(CFGEdge(convert(body, context.enter_scope(node)), None)) - case TryCatchFinally(body, _, _) => - mutable.Set(CFGEdge(convert(body, context.enter_scope(node)), None)) - case Synchronized(_, body) => - mutable.Set(CFGEdge(convert(body, context.enter_scope(node)), None)) - case ParInvariant(_, _, content) => - mutable.Set(CFGEdge(convert(content, context.enter_scope(node)), None)) // TODO: Expression side effects - case ParAtomic(_, content) => - mutable.Set(CFGEdge(convert(content, context.enter_scope(node)), None)) // TODO: Expression side effects - case ParBarrier(_, _, _, _, content) => - mutable.Set(CFGEdge(convert(content, context.enter_scope(node)), None)) // TODO: Expression side effects + case Loop(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) + case RangedFor(_, _, _) => evaluate_first(context.enter_scope(node)) + case TryCatchFinally(_, _, _) => evaluate_first(context.enter_scope(node)) + case Synchronized(_, _) => evaluate_first(context.enter_scope(node)) + case ParInvariant(_, _, _) => evaluate_first(context.enter_scope(node)) // TODO: Expression side effects + case ParAtomic(_, _) => evaluate_first(context.enter_scope(node)) // TODO: Expression side effects + case ParBarrier(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) // TODO: Expression side effects case ParStatement(_) => sequential_successor(context) - case VecBlock(_, _, _, content) => - mutable.Set(CFGEdge(convert(content, context.enter_scope(node)), None)) // TODO: Expression side effects - case WandPackage(_, proof) => - mutable.Set(CFGEdge(convert(proof, context.enter_scope(node)), None)) // TODO: Expression side effects - case ModelDo(_, _, _, _, impl) => - mutable.Set(CFGEdge(convert(impl, context.enter_scope(node)), None)) // TODO: Expression side effects + case VecBlock(_, _, _, _) => evaluate_first(context.enter_scope(node)) // TODO: Expression side effects + case WandPackage(_, _) => evaluate_first(context.enter_scope(node)) // TODO: Expression side effects + case ModelDo(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) // TODO: Expression side effects // CStatement case CDeclarationStatement(_) => sequential_successor(context) case CGoto(label) => ??? // I'm not dealing with string labels // CPPStatement case CPPDeclarationStatement(_) => sequential_successor(context) - case CPPLifetimeScope(body) => - mutable.Set(CFGEdge(convert(body, context.enter_scope(node)), None)) + case CPPLifetimeScope(_) => evaluate_first(context.enter_scope(node)) case JavaLocalDeclarationStatement(_) => sequential_successor(context) // SilverStatement case SilverNewRef(_, _) => sequential_successor(context) @@ -154,20 +133,16 @@ case class CFGGenerator[G]() { case PVLSeqAssign(_, _, value) => handle_expression_container(node, Eval(value)(value.o), context, sequential_successor(context)) case Communicate(_, _) => sequential_successor(context) case SeqAssign(_, _, value) => handle_expression_container(node, Eval(value)(value.o), context, sequential_successor(context)) - case UnresolvedSeqBranch(branches) => // TODO: Consider branch conditions + case UnresolvedSeqBranch(branches) => mutable.LinkedHashSet.from(branches.zipWithIndex.map(b => CFGEdge(convert(b._1._2, context.enter_scope(node, b._2)), None))) - case UnresolvedSeqLoop(cond, _, _) => - mutable.Set(CFGEdge(convert(Eval(cond)(cond.o), context.enter_scope(node)), None)) - case SeqBranch(_, yes, no) => no match { // TODO: What are the conditions here? + case UnresolvedSeqLoop(_, _, _) => evaluate_first(context.enter_scope(node)) + case SeqBranch(_, yes, no) => no match { // TODO: What are the conditions here? case Some(stmt) => mutable.Set(CFGEdge(convert(yes, context.enter_scope(node, 0)), None), CFGEdge(convert(stmt, context.enter_scope(node, 1)), None)) case None => mutable.Set(CFGEdge(convert(yes, context.enter_scope(node)), None)) } - case SeqLoop(_, _, body) => // TODO: What are the conditions here? - mutable.Set(CFGEdge(convert(body, context.enter_scope(node)), None)) - case VeyMontAssignExpression(_, assign) => - mutable.Set(CFGEdge(convert(assign, context.enter_scope(node)), None)) - case CommunicateX(_, _, _, assign) => - mutable.Set(CFGEdge(convert(assign, context.enter_scope(node)), None)) + case SeqLoop(_, _, _) => evaluate_first(context.enter_scope(node)) // TODO: What are the conditions here? + case VeyMontAssignExpression(_, _) => evaluate_first(context.enter_scope(node)) + case CommunicateX(_, _, _, _) => evaluate_first(context.enter_scope(node)) } private def handle_expression_container(statement: Statement[G], diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index f348251187..8c8d217f4f 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -608,8 +608,20 @@ case class CPPLifetimeScopeIndex[G](cpp_lifetime_scope: CPPLifetimeScope[G]) ext } case class UnresolvedSeqBranchIndex[G](unresolved_seq_branch: UnresolvedSeqBranch[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) - override def resolve(): Statement[G] = unresolved_seq_branch.branches.apply(index)._2 + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + // Indices 0, 2, 4, ... are the conditions, indices 1, 3, 5, ... are the branch bodies + if (index % 2 == 0 && index < 2 * (unresolved_seq_branch.branches.size - 1)) + Set((Some(UnresolvedSeqBranchIndex(unresolved_seq_branch, index + 2)), Some(Utils.negate(unresolved_seq_branch.branches.apply(index / 2)._1))), + (Some(UnresolvedSeqBranchIndex(unresolved_seq_branch, index + 1)), Some(unresolved_seq_branch.branches.apply(index / 2)._1))) + else if (index == 2 * (unresolved_seq_branch.branches.size - 1)) + Set((Some(UnresolvedSeqBranchIndex(unresolved_seq_branch, index + 1)), Some(unresolved_seq_branch.branches.apply(index / 2)._1)), + (None, Some(Utils.negate(unresolved_seq_branch.branches.apply(index / 2)._1)))) + else Set((None, None)) + } + override def resolve(): Statement[G] = { + if (index % 2 == 0) Eval(unresolved_seq_branch.branches.apply(index / 2)._1)(unresolved_seq_branch.branches.apply(index / 2)._1.o) + else unresolved_seq_branch.branches.apply((index - 1) / 2)._2 + } override def equals(obj: scala.Any): Boolean = obj match { case UnresolvedSeqBranchIndex(u, i) => i == index && u.equals(unresolved_seq_branch) case _ => false @@ -645,7 +657,7 @@ case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index } case class SeqLoopIndex[G](seq_loop: SeqLoop[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((Some(this), None), (None, None)) override def resolve(): Statement[G] = seq_loop.body override def equals(obj: scala.Any): Boolean = obj match { case SeqLoopIndex(s) => s.equals(seq_loop) From 2bc3a50df4f81c0719f9e698ebe5c78e80d22281 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 16 Feb 2024 09:34:52 +0100 Subject: [PATCH 24/85] Changed fork to create new stack; enabled terminal CFG nodes --- .../vct/rewrite/cfg/CFGGenerator.scala | 19 ++++++++++-------- src/rewrite/vct/rewrite/cfg/CFGNode.scala | 15 ++++++++++++-- src/rewrite/vct/rewrite/cfg/CFGPrinter.scala | 20 +++++++++---------- 3 files changed, 34 insertions(+), 20 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 03d6336a59..af32f3e681 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -70,7 +70,8 @@ case class CFGGenerator[G]() { case Notify(_) => sequential_successor(context) case Fork(obj) => { val run_method: RunMethod[G] = obj.t.asClass.get.cls.decl.declarations.collect{ case r: RunMethod[G] => r }.head - sequential_successor(context).addOne(CFGEdge(convert(run_method.body.get, context.enter_scope(run_method)), None)) + // Get the successor(s) of the fork statement as well as the new thread, starting with the run method + sequential_successor(context).addOne(CFGEdge(convert(run_method.body.get, GlobalIndex[G](mutable.Seq()).enter_scope(run_method)), None)) } case Join(_) => sequential_successor(context) case Lock(_) => sequential_successor(context) @@ -160,27 +161,29 @@ case class CFGGenerator[G]() { else sequential_successor(index) } - private def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = - index.make_step().map(i => CFGEdge(convert(i._1.resolve(), i._1), i._2)) + private def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = { + if (index.indices.nonEmpty) index.make_step().map(i => CFGEdge(convert(i._1.resolve(), i._1), i._2)) + else mutable.Set(CFGEdge(CFGTerminal(), None)) + } - private def return_successor(index: GlobalIndex[G]): CFGNode[G] = { + private def return_successor(index: GlobalIndex[G]): CFGEntry[G] = { val new_index: GlobalIndex[G] = index.return_from_call() convert(new_index.resolve(), new_index) } - private def exception_successor(exception: Expr[G], index: GlobalIndex[G]): CFGNode[G] = { + private def exception_successor(exception: Expr[G], index: GlobalIndex[G]): CFGEntry[G] = { val new_index = index.handle_exception(exception) // Terminate on unhandled exception - if (new_index.indices.isEmpty) return CFGNode(exception, mutable.Set()) + if (new_index.indices.isEmpty) return CFGTerminal() convert(new_index.resolve(), new_index) } - private def break_successor(index: GlobalIndex[G]): CFGNode[G] = { + private def break_successor(index: GlobalIndex[G]): CFGEntry[G] = { val new_index = index.handle_break() convert(new_index.resolve(), new_index) } - private def continue_successor(index: GlobalIndex[G]): CFGNode[G] = { + private def continue_successor(index: GlobalIndex[G]): CFGEntry[G] = { val new_index = index.continue_innermost_loop() convert(new_index.resolve(), new_index) } diff --git a/src/rewrite/vct/rewrite/cfg/CFGNode.scala b/src/rewrite/vct/rewrite/cfg/CFGNode.scala index bb7e3094bf..ca2587f035 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGNode.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGNode.scala @@ -4,5 +4,16 @@ import vct.col.ast.{Expr, Node} import scala.collection.mutable -case class CFGNode[G](ast_node: Node[G], successors: mutable.Set[CFGEdge[G]]) -case class CFGEdge[G](target: CFGNode[G], condition: Option[Expr[G]]) \ No newline at end of file +sealed trait CFGEntry[G] { + def get_successors: mutable.Set[CFGEdge[G]] +} +case class CFGNode[G](ast_node: Node[G], successors: mutable.Set[CFGEdge[G]]) extends CFGEntry[G] { + override def get_successors: mutable.Set[CFGEdge[G]] = successors + override def toString: String = ast_node.toInlineString +} +case class CFGTerminal[G]() extends CFGEntry[G] { + override def get_successors: mutable.Set[CFGEdge[G]] = mutable.Set() + override def toString: String = "" +} + +case class CFGEdge[G](target: CFGEntry[G], condition: Option[Expr[G]]) \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/cfg/CFGPrinter.scala b/src/rewrite/vct/rewrite/cfg/CFGPrinter.scala index 3a11ea2817..465b81792d 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGPrinter.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGPrinter.scala @@ -9,7 +9,7 @@ import java.nio.file.Path import scala.collection.mutable case class CFGPrinter[G]() { - private val node_names: mutable.Map[CFGNode[G], String] = mutable.HashMap[CFGNode[G], String]() + private val node_names: mutable.Map[CFGEntry[G], String] = mutable.HashMap[CFGEntry[G], String]() private val no_condition: Expr[G] = BooleanValue(value = true)(Origin(Seq())) private var node_index: Int = 0 @@ -18,15 +18,15 @@ case class CFGPrinter[G]() { // Write all nodes node_names.foreach(naming => writer.append(naming._2) .append(s" [label=${"\""}") - .append(naming._1.ast_node.toInlineString) + .append(naming._1.toString) .append(s"${"\""}];\n")) // Write all edges - node_names.foreach(naming => naming._1.successors.foreach(edge => writer.append(naming._2) - .append(" -> ") - .append(node_names(edge.target)) - .append(s" [label=${"\""}") - .append(edge.condition.getOrElse(no_condition).toInlineString) - .append(s"${"\""}];\n"))) + node_names.foreach(naming => naming._1.get_successors.foreach(edge => writer.append(naming._2) + .append(" -> ") + .append(node_names(edge.target)) + .append(s" [label=${"\""}") + .append(edge.condition.getOrElse(no_condition).toInlineString) + .append(s"${"\""}];\n"))) writer.append("}") } @@ -36,7 +36,7 @@ case class CFGPrinter[G]() { RWFile(path.toFile).write(w => print_cfg(w)) } - private def find_all_nodes(node: CFGNode[G]): Unit = { + private def find_all_nodes(node: CFGEntry[G]): Unit = { if (!node_names.contains(node)) { // Give new name to node val node_name: String = s"n$node_index" @@ -44,7 +44,7 @@ case class CFGPrinter[G]() { node_names.addOne((node, node_name)) // Recursively call for successors - node.successors.foreach(e => find_all_nodes(e.target)) + node.get_successors.foreach(e => find_all_nodes(e.target)) } } } From 585c3c737bd94c06e77a9effdd48362dfc874672 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 16 Feb 2024 10:39:03 +0100 Subject: [PATCH 25/85] Enable terminal node --- .../vct/rewrite/cfg/CFGGenerator.scala | 33 ++++++++----------- src/rewrite/vct/rewrite/cfg/Index.scala | 9 +++-- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index af32f3e681..2e73b2b0a5 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -157,34 +157,29 @@ case class CFGGenerator[G]() { } private def evaluate_first(index: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = { - if (index.has_statement()) mutable.Set(CFGEdge(convert(index.resolve(), index), None)) + if (index.has_statement()) mutable.Set(CFGEdge(resolve_index(index), None)) else sequential_successor(index) } private def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = { - if (index.indices.nonEmpty) index.make_step().map(i => CFGEdge(convert(i._1.resolve(), i._1), i._2)) + if (index.indices.nonEmpty) index.make_step().map(i => CFGEdge(resolve_index(i._1), i._2)) else mutable.Set(CFGEdge(CFGTerminal(), None)) } - private def return_successor(index: GlobalIndex[G]): CFGEntry[G] = { - val new_index: GlobalIndex[G] = index.return_from_call() - convert(new_index.resolve(), new_index) - } + private def return_successor(index: GlobalIndex[G]): CFGEntry[G] = + resolve_index(index.return_from_call()) - private def exception_successor(exception: Expr[G], index: GlobalIndex[G]): CFGEntry[G] = { - val new_index = index.handle_exception(exception) - // Terminate on unhandled exception - if (new_index.indices.isEmpty) return CFGTerminal() - convert(new_index.resolve(), new_index) - } + private def exception_successor(exception: Expr[G], index: GlobalIndex[G]): CFGEntry[G] = + resolve_index(index.handle_exception(exception)) - private def break_successor(index: GlobalIndex[G]): CFGEntry[G] = { - val new_index = index.handle_break() - convert(new_index.resolve(), new_index) - } + private def break_successor(index: GlobalIndex[G]): CFGEntry[G] = + resolve_index(index.handle_break()) + + private def continue_successor(index: GlobalIndex[G]): CFGEntry[G] = + resolve_index(index.continue_innermost_loop()) - private def continue_successor(index: GlobalIndex[G]): CFGEntry[G] = { - val new_index = index.continue_innermost_loop() - convert(new_index.resolve(), new_index) + private def resolve_index(index: GlobalIndex[G]): CFGEntry[G] = index.resolve() match { + case Some(stmt) => convert(stmt, index) + case None => CFGTerminal() } } \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 8c8d217f4f..7011b47325 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -16,7 +16,7 @@ case class GlobalIndex[G](indices: mutable.Seq[Index[G]]) { GlobalIndex(indices.prepended(Index[G](node, index))) def make_step(): mutable.Set[(GlobalIndex[G], Option[Expr[G]])] = { - if (indices.isEmpty) return mutable.Set() + if (indices.isEmpty) return mutable.Set((this, None)) val steps: Set[(Option[Index[G]], Option[Expr[G]])] = indices.head.make_step() val res = mutable.Set[(GlobalIndex[G], Option[Expr[G]])]() for (step <- steps) { @@ -28,9 +28,12 @@ case class GlobalIndex[G](indices: mutable.Seq[Index[G]]) { res } - def resolve(): Statement[G] = indices.dropWhile(i => !i.has_statement()).head.resolve() + def resolve(): Option[Statement[G]] = indices.dropWhile(i => !i.has_statement()).headOption match{ + case Some(idx) => Some(idx.resolve()) + case None => None + } - def has_statement(): Boolean = indices.head.has_statement() + def has_statement(): Boolean = indices.nonEmpty && indices.head.has_statement() def return_from_call(): GlobalIndex[G] = { // Find innermost subroutine call From 8da6793c97fbd4cf3533eb18e03f2b104eb160e2 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 16 Feb 2024 11:49:31 +0100 Subject: [PATCH 26/85] Added support for edge conditions in expression side effects --- src/rewrite/vct/rewrite/cfg/Utils.scala | 35 ++++++++++++++++++++----- 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/Utils.scala b/src/rewrite/vct/rewrite/cfg/Utils.scala index 0b8617334c..8842d52ede 100644 --- a/src/rewrite/vct/rewrite/cfg/Utils.scala +++ b/src/rewrite/vct/rewrite/cfg/Utils.scala @@ -22,11 +22,34 @@ object Utils { Seq(InvokeProcedure(ref, args, outArgs, typeArgs, givenMap, yields)(pi.blame)(pi.o)) case no @ NewObject(cls) => Seq(Instantiate(cls, get_out_variable(cls, no.o))(no.o)) // Expressions that affect the edge condition - // TODO: Consider conditions for control flow graph - case Select(condition, whenTrue, whenFalse) => Seq(condition, whenTrue, whenFalse).flatMap(e => find_all_subexpressions(e)) - case Implies(left, right) => Seq(left, right).flatMap(e => find_all_subexpressions(e)) - case And(left, right) => Seq(left, right).flatMap(e => find_all_subexpressions(e)) - case Or(left, right) => Seq(left, right).flatMap(e => find_all_subexpressions(e)) + case Select(condition, whenTrue, whenFalse) => { + val statements: Seq[Statement[G]] = find_all_subexpressions(condition) + val true_stmts: Seq[Statement[G]] = find_all_subexpressions(whenTrue) + val false_stmts: Seq[Statement[G]] = find_all_subexpressions(whenFalse) + var branches: Seq[(Expr[G], Statement[G])] = Seq() + if (true_stmts.nonEmpty) branches = branches.appended((condition, Block(true_stmts)(whenTrue.o))) + if (false_stmts.nonEmpty) branches = branches.appended((negate(condition), Block(false_stmts)(whenFalse.o))) + if (branches.nonEmpty) statements ++ Seq(Branch(branches)(expr.o)) + else statements + } + case Implies(left, right) => { + val left_stmts: Seq[Statement[G]] = find_all_subexpressions(left) + val right_stmts: Seq[Statement[G]] = find_all_subexpressions(right) + if (right_stmts.nonEmpty) left_stmts ++ Seq(Branch(Seq((left, Block(right_stmts)(right.o))))(expr.o)) + else left_stmts + } + case And(left, right) => { + val left_stmts: Seq[Statement[G]] = find_all_subexpressions(left) + val right_stmts: Seq[Statement[G]] = find_all_subexpressions(right) + if (right_stmts.nonEmpty) left_stmts ++ Seq(Branch(Seq((left, Block(right_stmts)(right.o))))(expr.o)) + else left_stmts + } + case Or(left, right) => { + val left_stmts: Seq[Statement[G]] = find_all_subexpressions(left) + val right_stmts: Seq[Statement[G]] = find_all_subexpressions(right) + if (right_stmts.nonEmpty) left_stmts ++ Seq(Branch(Seq((negate(left), Block(right_stmts)(right.o))))(expr.o)) + else left_stmts + } // General recursion case case _ => expr.subnodes.collect{ case ex: Expr[G] => ex }.flatMap(e => find_all_subexpressions(e)) } @@ -52,7 +75,7 @@ object Utils { case None => expr2 case Some(e1) => expr2 match { case None => expr1 - case Some(e2) => Some(And(e1, e2)(e1.o)) // TODO: Ignore origin of e2? + case Some(e2) => Some(And(e1, e2)(e1.o)) // TODO: Is the origin important? } } } From bd091dc42609be8e7ea3f99d181c823c6e077000 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 16 Feb 2024 14:21:37 +0100 Subject: [PATCH 27/85] Started work on CFG traversal for RASI generation --- .../vct/rewrite/rasi/AbstractState.scala | 27 +++++++++++++++++++ .../vct/rewrite/rasi/ConcreteVariable.scala | 24 +++++++++++++++++ .../vct/rewrite/rasi/RASIGenerator.scala | 14 ++++++++++ src/rewrite/vct/rewrite/rasi/Utils.scala | 11 ++++++++ 4 files changed, 76 insertions(+) create mode 100644 src/rewrite/vct/rewrite/rasi/AbstractState.scala create mode 100644 src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala create mode 100644 src/rewrite/vct/rewrite/rasi/RASIGenerator.scala create mode 100644 src/rewrite/vct/rewrite/rasi/Utils.scala diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala new file mode 100644 index 0000000000..d92f73d9d1 --- /dev/null +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -0,0 +1,27 @@ +package vct.rewrite.rasi + +import vct.col.ast._ + +case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int]) { + def resolve_integer_expression(expr: Expr[G]): Int = expr match { + case CIntegerValue(value) => value.intValue + case IntegerValue(value) => value.intValue + case Local(ref) => ??? + case DerefHeapVariable(ref) => ??? + case Deref(obj, ref) => ??? + case DerefPointer(pointer) => ??? + case SizeOf(tname) => ??? + case UMinus(arg) => -resolve_integer_expression(arg) + case AmbiguousMult(left, right) => resolve_integer_expression(left) * resolve_integer_expression(right) + case AmbiguousPlus(left, right) => resolve_integer_expression(left) + resolve_integer_expression(right) + case AmbiguousMinus(left, right) => resolve_integer_expression(left) - resolve_integer_expression(right) + case AmbiguousComputationalOr(left, right) => resolve_integer_expression(left) | resolve_integer_expression(right) + case AmbiguousComputationalXor(left, right) => resolve_integer_expression(left) ^ resolve_integer_expression(right) + case AmbiguousComputationalAnd(left, right) => resolve_integer_expression(left) & resolve_integer_expression(right) + case + } + + def to_expression(): Expr[G] = { + ??? + } +} diff --git a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala new file mode 100644 index 0000000000..24594169d2 --- /dev/null +++ b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala @@ -0,0 +1,24 @@ +package vct.rewrite.rasi + +import vct.col.ast._ + +trait ConcreteVariable[G] { + def is(expr: Expr[G]): Boolean + def extract_from_expression(expr: Expr[G]): Field[G] = expr match { + // TODO: Need to also consider the object! + case Deref(obj, ref) => ref.decl + } +} + +case class FieldVariable[G](field: Field[G]) extends ConcreteVariable[G] { + override def is(expr: Expr[G]): Boolean = field.equals(extract_from_expression(expr)) +} + +case class IndexedVariable[G](field: Field[G], i: Int) extends ConcreteVariable[G] { + override def is(expr: Expr[G]): Boolean = expr match { + case AmbiguousSubscript(collection, index) => field.equals(extract_from_expression(collection)) && i == Utils.resolve_integer_expression(index) + case SeqSubscript(seq, index) => field.equals(extract_from_expression(seq)) && i == Utils.resolve_integer_expression(index) + case ArraySubscript(arr, index) => field.equals(extract_from_expression(arr)) && i == Utils.resolve_integer_expression(index) + case PointerSubscript(pointer, index) => field.equals(extract_from_expression(pointer)) && i == Utils.resolve_integer_expression(index) + } +} diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala new file mode 100644 index 0000000000..82c65caf90 --- /dev/null +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -0,0 +1,14 @@ +package vct.rewrite.rasi + +import vct.col.ast.{BooleanValue, Expr, Or} +import vct.rewrite.cfg.CFGNode + +case class RASIGenerator[G]() { + private def neutral_element: Expr[G] = ??? + + private def explore(node: CFGNode[G]): Set[AbstractState[G]] = ??? + + def generate_rasi(node: CFGNode[G]): Expr[G] = { + explore(node).map(a => a.to_expression()).fold(neutral_element)((e1, e2) => Or(e1, e2)(e1.o)) + } +} diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala new file mode 100644 index 0000000000..7726531fdf --- /dev/null +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -0,0 +1,11 @@ +package vct.rewrite.rasi + +import vct.col.ast._ + +case object Utils { + def resolve_integer_expression[G](expr: Expr[G]): Int = expr match { + case IntegerValue(value) => value.intValue + case Plus(left, right) => resolve_integer_expression(left) + resolve_integer_expression(right) + + } +} From d43a762d1b38d37463061bf29aad1441a651fb0b Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 16 Feb 2024 16:19:27 +0100 Subject: [PATCH 28/85] Working on RASI generator --- .../vct/rewrite/rasi/AbstractProcess.scala | 17 +++++ .../vct/rewrite/rasi/AbstractState.scala | 70 ++++++++++++++++++- .../vct/rewrite/rasi/ConcreteVariable.scala | 3 + .../vct/rewrite/rasi/RASIGenerator.scala | 27 +++++-- 4 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 src/rewrite/vct/rewrite/rasi/AbstractProcess.scala diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala new file mode 100644 index 0000000000..8dc4e4bf30 --- /dev/null +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -0,0 +1,17 @@ +package vct.rewrite.rasi + +import vct.col.ast._ +import vct.rewrite.cfg.{CFGEdge, CFGEntry, CFGNode, CFGTerminal} + +case class AbstractProcess[G](name: String) { + def get_next(node: CFGEntry[G], state: AbstractState[G]): Set[AbstractState[G]] = node match { + case CFGTerminal() => Set() + case CFGNode(n, succ) => succ.filter(e => e.condition.isEmpty || state.resolve_boolean_expression (e.condition.get)).flatMap (e => process_cfg_edge(e, n)).toSet + } + + private def process_cfg_edge(edge: CFGEdge[G], ast_node: Node[G]): Set[AbstractState[G]] = ast_node match { + // TODO: Implement! + case Assign(target, value) => ??? + case _ => ??? + } +} diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index d92f73d9d1..f614c6c4ab 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -1,8 +1,13 @@ package vct.rewrite.rasi import vct.col.ast._ +import vct.col.origin.{LabelContext, Origin} +import vct.rewrite.cfg.CFGEntry + +case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int], processes: Map[AbstractProcess[G], CFGEntry[G]]) { + def successors(): Set[AbstractState[G]] = + processes.flatMap(p => p._1.get_next(p._2, this)).toSet -case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int]) { def resolve_integer_expression(expr: Expr[G]): Int = expr match { case CIntegerValue(value) => value.intValue case IntegerValue(value) => value.intValue @@ -12,16 +17,75 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int]) { case DerefPointer(pointer) => ??? case SizeOf(tname) => ??? case UMinus(arg) => -resolve_integer_expression(arg) + case BitNot(arg) => ~resolve_integer_expression(arg) case AmbiguousMult(left, right) => resolve_integer_expression(left) * resolve_integer_expression(right) case AmbiguousPlus(left, right) => resolve_integer_expression(left) + resolve_integer_expression(right) case AmbiguousMinus(left, right) => resolve_integer_expression(left) - resolve_integer_expression(right) case AmbiguousComputationalOr(left, right) => resolve_integer_expression(left) | resolve_integer_expression(right) case AmbiguousComputationalXor(left, right) => resolve_integer_expression(left) ^ resolve_integer_expression(right) case AmbiguousComputationalAnd(left, right) => resolve_integer_expression(left) & resolve_integer_expression(right) - case + case ComputationalOr(left, right) => resolve_integer_expression(left) | resolve_integer_expression(right) + case ComputationalXor(left, right) => resolve_integer_expression(left) ^ resolve_integer_expression(right) + case ComputationalAnd(left, right) => resolve_integer_expression(left) & resolve_integer_expression(right) + case Exp(left, right) => scala.math.pow(resolve_integer_expression(left), resolve_integer_expression(right)).intValue() + case Plus(left, right) => resolve_integer_expression(left) + resolve_integer_expression(right) + case Minus(left, right) => resolve_integer_expression(left) - resolve_integer_expression(right) + case Mult(left, right) => resolve_integer_expression(left) * resolve_integer_expression(right) + case FloorDiv(left, right) => resolve_integer_expression(left) / resolve_integer_expression(right) + case Mod(left, right) => resolve_integer_expression(left) % resolve_integer_expression(right) + case BitAnd(left, right) => resolve_integer_expression(left) & resolve_integer_expression(right) + case BitOr(left, right) => resolve_integer_expression(left) | resolve_integer_expression(right) + case BitXor(left, right) => resolve_integer_expression(left) ^ resolve_integer_expression(right) + case BitShl(left, right) => resolve_integer_expression(left) << resolve_integer_expression(right) + case BitShr(left, right) => resolve_integer_expression(left) >> resolve_integer_expression(right) + case BitUShr(left, right) => resolve_integer_expression(left) >>> resolve_integer_expression(right) + case Select(cond, ift, iff) => { + if (resolve_boolean_expression(cond)) resolve_integer_expression(ift) + else resolve_integer_expression(iff) + } + case AmbiguousSubscript(collection, index) => ??? + case SeqSubscript(seq, index) => ??? + case ArraySubscript(arr, index) => ??? + case PointerSubscript(pointer, index) => ??? + case Length(arr) => ??? + case Size(obj) => ??? + } + + def resolve_boolean_expression(expr: Expr[G]): Boolean = expr match { + case BooleanValue(value) => value + case Local(ref) => ??? + case DerefHeapVariable(ref) => ??? + case Deref(obj, ref) => ??? + case DerefPointer(pointer) => ??? + case Not(arg) => !resolve_boolean_expression(arg) + case AmbiguousOr(left, right) => resolve_boolean_expression(left) || resolve_boolean_expression(right) + case And(left, right) => resolve_boolean_expression(left) && resolve_boolean_expression(right) + case Or(left, right) => resolve_boolean_expression(left) || resolve_boolean_expression(right) + case Implies(left, right) => (!resolve_boolean_expression(left)) || resolve_boolean_expression(right) + case Eq(left, right) => resolve_integer_expression(left) == resolve_integer_expression(right) + case Neq(left, right) => resolve_integer_expression(left) != resolve_integer_expression(right) + case AmbiguousGreater(left, right) => resolve_integer_expression(left) > resolve_integer_expression(right) + case AmbiguousLess(left, right) => resolve_integer_expression(left) < resolve_integer_expression(right) + case AmbiguousGreaterEq(left, right) => resolve_integer_expression(left) >= resolve_integer_expression(right) + case AmbiguousLessEq(left, right) => resolve_integer_expression(left) <= resolve_integer_expression(right) + case Greater(left, right) => resolve_integer_expression(left) > resolve_integer_expression(right) + case Less(left, right) => resolve_integer_expression(left) < resolve_integer_expression(right) + case GreaterEq(left, right) => resolve_integer_expression(left) >= resolve_integer_expression(right) + case LessEq(left, right) => resolve_integer_expression(left) <= resolve_integer_expression(right) + case c: SetComparison[G] => ??? + case Select(cond, ift, iff) => { + if (resolve_boolean_expression(cond)) resolve_boolean_expression(ift) + else resolve_boolean_expression(iff) + } + case AmbiguousSubscript(collection, index) => ??? + case SeqSubscript(seq, index) => ??? + case ArraySubscript(arr, index) => ??? + case PointerSubscript(pointer, index) => ??? } + private def neutral_element: Expr[G] = BooleanValue(value = true)(Origin(Seq(LabelContext("neutral element for and")))) def to_expression(): Expr[G] = { - ??? + valuations.map(v => Eq(v._1.to_expression(), IntegerValue(v._2)(Origin(Seq(LabelContext("int value")))))(Origin(Seq(LabelContext("valuation"))))) + .fold(neutral_element)((e1, e2) => And(e1, e2)(e1.o)) } } diff --git a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala index 24594169d2..0186656907 100644 --- a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala +++ b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala @@ -8,10 +8,12 @@ trait ConcreteVariable[G] { // TODO: Need to also consider the object! case Deref(obj, ref) => ref.decl } + def to_expression(): Expr[G] } case class FieldVariable[G](field: Field[G]) extends ConcreteVariable[G] { override def is(expr: Expr[G]): Boolean = field.equals(extract_from_expression(expr)) + override def to_expression(): Expr[G] = ??? } case class IndexedVariable[G](field: Field[G], i: Int) extends ConcreteVariable[G] { @@ -21,4 +23,5 @@ case class IndexedVariable[G](field: Field[G], i: Int) extends ConcreteVariable[ case ArraySubscript(arr, index) => field.equals(extract_from_expression(arr)) && i == Utils.resolve_integer_expression(index) case PointerSubscript(pointer, index) => field.equals(extract_from_expression(pointer)) && i == Utils.resolve_integer_expression(index) } + override def to_expression(): Expr[G] = ??? } diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index 82c65caf90..33ac489584 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -1,14 +1,33 @@ package vct.rewrite.rasi import vct.col.ast.{BooleanValue, Expr, Or} +import vct.col.origin.{LabelContext, Origin} import vct.rewrite.cfg.CFGNode +import scala.collection.mutable + case class RASIGenerator[G]() { - private def neutral_element: Expr[G] = ??? + private val found_states: mutable.Set[AbstractState[G]] = mutable.LinkedHashSet() + private val current_branches: mutable.Set[AbstractState[G]] = mutable.LinkedHashSet() + + private def neutral_element: Expr[G] = BooleanValue(value = false)(Origin(Seq(LabelContext("neutral element of or")))) + def generate_rasi(node: CFGNode[G], vars: Set[ConcreteVariable[G]]): Expr[G] = { + explore(node, vars) + found_states.map(a => a.to_expression()).fold(neutral_element)((e1, e2) => Or(e1, e2)(e1.o)) + } - private def explore(node: CFGNode[G]): Set[AbstractState[G]] = ??? + private def explore(node: CFGNode[G], vars: Set[ConcreteVariable[G]]): Unit = { + val initial_state = AbstractState(get_initial_values(vars), Map((AbstractProcess("main"), node))) + found_states.addOne(initial_state) + current_branches.addOne(initial_state) - def generate_rasi(node: CFGNode[G]): Expr[G] = { - explore(node).map(a => a.to_expression()).fold(neutral_element)((e1, e2) => Or(e1, e2)(e1.o)) + while (current_branches.nonEmpty) { + val curr = current_branches.head + current_branches.remove(curr) + + curr.successors().foreach(s => if (!found_states.contains(s)) {found_states.addOne(curr); current_branches.addOne(curr)}) + } } + + private def get_initial_values(vars: Set[ConcreteVariable[G]]): Map[ConcreteVariable[G], Int] = ??? } From 0d22af47d9bb64ce8d0379efeb3c285607cc6bde Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 19 Feb 2024 11:14:12 +0100 Subject: [PATCH 29/85] Added uncertain values to RASI generator --- .../vct/rewrite/rasi/AbstractProcess.scala | 3 +- .../vct/rewrite/rasi/AbstractState.scala | 4 +-- .../vct/rewrite/rasi/UncertainValue.scala | 36 +++++++++++++++++++ 3 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 src/rewrite/vct/rewrite/rasi/UncertainValue.scala diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index 8dc4e4bf30..f8f8110c65 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -6,7 +6,8 @@ import vct.rewrite.cfg.{CFGEdge, CFGEntry, CFGNode, CFGTerminal} case class AbstractProcess[G](name: String) { def get_next(node: CFGEntry[G], state: AbstractState[G]): Set[AbstractState[G]] = node match { case CFGTerminal() => Set() - case CFGNode(n, succ) => succ.filter(e => e.condition.isEmpty || state.resolve_boolean_expression (e.condition.get)).flatMap (e => process_cfg_edge(e, n)).toSet + case CFGNode(n, succ) => succ.filter(e => e.condition.isEmpty || state.resolve_boolean_expression(e.condition.get)) + .flatMap (e => process_cfg_edge(e, n)).toSet } private def process_cfg_edge(edge: CFGEdge[G], ast_node: Node[G]): Set[AbstractState[G]] = ast_node match { diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index f614c6c4ab..97fe5ad157 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -51,8 +51,8 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int], processes case Size(obj) => ??? } - def resolve_boolean_expression(expr: Expr[G]): Boolean = expr match { - case BooleanValue(value) => value + def resolve_boolean_expression(expr: Expr[G]): UncertainBooleanValue = expr match { + case BooleanValue(value) => UncertainBooleanValue(value, !value) case Local(ref) => ??? case DerefHeapVariable(ref) => ??? case Deref(obj, ref) => ??? diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala new file mode 100644 index 0000000000..5b19f535be --- /dev/null +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -0,0 +1,36 @@ +package vct.rewrite.rasi + +trait UncertainValue { + def can_be_equal(other: UncertainValue): Boolean + def can_be_unequal(other: UncertainValue): Boolean +} + +case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) extends UncertainValue { + override def can_be_equal(other: UncertainValue): Boolean = other match { + case UncertainBooleanValue(t, f) => can_be_true && t || can_be_false && f + case _ => false + } + + override def can_be_unequal(other: UncertainValue): Boolean = other match { + case UncertainBooleanValue(t, f) => can_be_true && f || can_be_false && t + case _ => true + } + + def &&(other: UncertainBooleanValue): UncertainBooleanValue = + UncertainBooleanValue(can_be_true && other.can_be_true, can_be_false || other.can_be_false) + + def ||(other: UncertainBooleanValue): UncertainBooleanValue = + UncertainBooleanValue(can_be_true || other.can_be_true, can_be_false && other.can_be_false) + + def !(): UncertainBooleanValue = + UncertainBooleanValue(can_be_false, can_be_true) + + def ^(other: UncertainBooleanValue): UncertainBooleanValue = + UncertainBooleanValue(can_be_true && other.can_be_false || can_be_false && other.can_be_true, can_be_true && other.can_be_true || can_be_false && other.can_be_false) +} + +case class UncertainIntegerValue() extends UncertainValue { + override def can_be_equal(other: UncertainValue): Boolean = ??? + + override def can_be_unequal(other: UncertainValue): Boolean = ??? +} From 8f212d148ad917b51784126dbd91081ee340de57 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 19 Feb 2024 11:25:38 +0100 Subject: [PATCH 30/85] Finished expression side effects for With and Then --- src/rewrite/vct/rewrite/cfg/CFGGenerator.scala | 1 - src/rewrite/vct/rewrite/cfg/Utils.scala | 11 ++++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 2e73b2b0a5..805e411ed3 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -39,7 +39,6 @@ case class CFGGenerator[G]() { } private def find_successors(node: Statement[G], context: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = node match { - // TODO: Can these be simplified using evaluate_first()? case PVLBranch(_) => evaluate_first(context.enter_scope(node)) case PVLLoop(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) // NonExecutableStatement diff --git a/src/rewrite/vct/rewrite/cfg/Utils.scala b/src/rewrite/vct/rewrite/cfg/Utils.scala index 8842d52ede..197849e05a 100644 --- a/src/rewrite/vct/rewrite/cfg/Utils.scala +++ b/src/rewrite/vct/rewrite/cfg/Utils.scala @@ -12,8 +12,8 @@ object Utils { // Expressions causing side effects case pae @ PreAssignExpression(target, value) => Seq(Assign(target, value)(pae.blame)(pae.o)) case pae @ PostAssignExpression(target, value) => Seq(Assign(target, value)(pae.blame)(pae.o)) - case With(pre, value) => ??? - case Then(value, post) => ??? + case With(pre, value) => pre +: find_all_subexpressions(value) + case Then(value, post) => find_all_subexpressions(value) :+ post case mi @ MethodInvocation(obj, ref, args, outArgs, typeArgs, givenMap, yields) => Seq(InvokeMethod(obj, ref, args, outArgs, typeArgs, givenMap, yields)(mi.blame)(mi.o)) case ci @ ConstructorInvocation(ref, args, outArgs, typeArgs, givenMap, yields) => @@ -35,19 +35,19 @@ object Utils { case Implies(left, right) => { val left_stmts: Seq[Statement[G]] = find_all_subexpressions(left) val right_stmts: Seq[Statement[G]] = find_all_subexpressions(right) - if (right_stmts.nonEmpty) left_stmts ++ Seq(Branch(Seq((left, Block(right_stmts)(right.o))))(expr.o)) + if (right_stmts.nonEmpty) left_stmts :+ Branch(Seq((left, Block(right_stmts)(right.o))))(expr.o) else left_stmts } case And(left, right) => { val left_stmts: Seq[Statement[G]] = find_all_subexpressions(left) val right_stmts: Seq[Statement[G]] = find_all_subexpressions(right) - if (right_stmts.nonEmpty) left_stmts ++ Seq(Branch(Seq((left, Block(right_stmts)(right.o))))(expr.o)) + if (right_stmts.nonEmpty) left_stmts :+ Branch(Seq((left, Block(right_stmts)(right.o))))(expr.o) else left_stmts } case Or(left, right) => { val left_stmts: Seq[Statement[G]] = find_all_subexpressions(left) val right_stmts: Seq[Statement[G]] = find_all_subexpressions(right) - if (right_stmts.nonEmpty) left_stmts ++ Seq(Branch(Seq((negate(left), Block(right_stmts)(right.o))))(expr.o)) + if (right_stmts.nonEmpty) left_stmts :+ Branch(Seq((negate(left), Block(right_stmts)(right.o))))(expr.o) else left_stmts } // General recursion case @@ -57,6 +57,7 @@ object Utils { private def get_out_variable[G](cls: Ref[G, Class[G]], o: Origin): Local[G] = Local(new DirectRef[G, Variable[G]](new Variable(TClass(cls))(o)))(o) def find_all_cases[G](body: Statement[G], index: GlobalIndex[G]): mutable.Set[(SwitchCase[G], GlobalIndex[G])] = body match { + case Switch(_, _) => mutable.Set() // Recursion on statements that can contain case statements case Label(_, stmt) => find_all_cases(stmt, index.enter_scope(body)) case Block(stmts) => mutable.LinkedHashSet.from(stmts.zipWithIndex.flatMap(t => find_all_cases(t._1, index.enter_scope(body, t._2)))) From e2e5cf0cdc2aa471af71ea1cd68af5981692aee2 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 19 Feb 2024 14:35:20 +0100 Subject: [PATCH 31/85] Started with uncertainty arithmetic; made compilable --- .../vct/rewrite/rasi/AbstractProcess.scala | 2 +- .../vct/rewrite/rasi/AbstractState.scala | 51 ++++---- .../vct/rewrite/rasi/ConcreteVariable.scala | 4 +- src/rewrite/vct/rewrite/rasi/Interval.scala | 119 ++++++++++++++++++ .../vct/rewrite/rasi/RASIGenerator.scala | 3 +- .../vct/rewrite/rasi/UncertainValue.scala | 59 ++++++++- src/rewrite/vct/rewrite/rasi/Utils.scala | 8 +- 7 files changed, 209 insertions(+), 37 deletions(-) create mode 100644 src/rewrite/vct/rewrite/rasi/Interval.scala diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index f8f8110c65..313d563db0 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -6,7 +6,7 @@ import vct.rewrite.cfg.{CFGEdge, CFGEntry, CFGNode, CFGTerminal} case class AbstractProcess[G](name: String) { def get_next(node: CFGEntry[G], state: AbstractState[G]): Set[AbstractState[G]] = node match { case CFGTerminal() => Set() - case CFGNode(n, succ) => succ.filter(e => e.condition.isEmpty || state.resolve_boolean_expression(e.condition.get)) + case CFGNode(n, succ) => succ.filter(e => e.condition.isEmpty || state.resolve_boolean_expression(e.condition.get).can_be_true) .flatMap (e => process_cfg_edge(e, n)).toSet } diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 97fe5ad157..f2a592bda0 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -4,44 +4,49 @@ import vct.col.ast._ import vct.col.origin.{LabelContext, Origin} import vct.rewrite.cfg.CFGEntry -case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int], processes: Map[AbstractProcess[G], CFGEntry[G]]) { +import scala.collection.immutable.HashMap + +case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int], processes: HashMap[AbstractProcess[G], CFGEntry[G]]) { def successors(): Set[AbstractState[G]] = processes.flatMap(p => p._1.get_next(p._2, this)).toSet - def resolve_integer_expression(expr: Expr[G]): Int = expr match { - case CIntegerValue(value) => value.intValue - case IntegerValue(value) => value.intValue + def resolve_integer_expression(expr: Expr[G]): UncertainIntegerValue = expr match { + case CIntegerValue(value) => UncertainIntegerValue.single(value.intValue) + case IntegerValue(value) => UncertainIntegerValue.single(value.intValue) case Local(ref) => ??? case DerefHeapVariable(ref) => ??? case Deref(obj, ref) => ??? case DerefPointer(pointer) => ??? case SizeOf(tname) => ??? case UMinus(arg) => -resolve_integer_expression(arg) - case BitNot(arg) => ~resolve_integer_expression(arg) case AmbiguousMult(left, right) => resolve_integer_expression(left) * resolve_integer_expression(right) case AmbiguousPlus(left, right) => resolve_integer_expression(left) + resolve_integer_expression(right) case AmbiguousMinus(left, right) => resolve_integer_expression(left) - resolve_integer_expression(right) - case AmbiguousComputationalOr(left, right) => resolve_integer_expression(left) | resolve_integer_expression(right) - case AmbiguousComputationalXor(left, right) => resolve_integer_expression(left) ^ resolve_integer_expression(right) - case AmbiguousComputationalAnd(left, right) => resolve_integer_expression(left) & resolve_integer_expression(right) - case ComputationalOr(left, right) => resolve_integer_expression(left) | resolve_integer_expression(right) - case ComputationalXor(left, right) => resolve_integer_expression(left) ^ resolve_integer_expression(right) - case ComputationalAnd(left, right) => resolve_integer_expression(left) & resolve_integer_expression(right) - case Exp(left, right) => scala.math.pow(resolve_integer_expression(left), resolve_integer_expression(right)).intValue() + case Exp(left, right) => resolve_integer_expression(left).pow(resolve_integer_expression(right)) case Plus(left, right) => resolve_integer_expression(left) + resolve_integer_expression(right) case Minus(left, right) => resolve_integer_expression(left) - resolve_integer_expression(right) case Mult(left, right) => resolve_integer_expression(left) * resolve_integer_expression(right) case FloorDiv(left, right) => resolve_integer_expression(left) / resolve_integer_expression(right) case Mod(left, right) => resolve_integer_expression(left) % resolve_integer_expression(right) - case BitAnd(left, right) => resolve_integer_expression(left) & resolve_integer_expression(right) - case BitOr(left, right) => resolve_integer_expression(left) | resolve_integer_expression(right) - case BitXor(left, right) => resolve_integer_expression(left) ^ resolve_integer_expression(right) - case BitShl(left, right) => resolve_integer_expression(left) << resolve_integer_expression(right) - case BitShr(left, right) => resolve_integer_expression(left) >> resolve_integer_expression(right) - case BitUShr(left, right) => resolve_integer_expression(left) >>> resolve_integer_expression(right) + // TODO: Support bit operations + case BitNot(arg) => ??? // ~resolve_integer_expression(arg) + case AmbiguousComputationalOr(left, right) => ??? // resolve_integer_expression(left) | resolve_integer_expression(right) + case AmbiguousComputationalXor(left, right) => ??? // resolve_integer_expression(left) ^ resolve_integer_expression(right) + case AmbiguousComputationalAnd(left, right) => ??? // resolve_integer_expression(left) & resolve_integer_expression(right) + case ComputationalOr(left, right) => ??? // resolve_integer_expression(left) | resolve_integer_expression(right) + case ComputationalXor(left, right) => ??? // resolve_integer_expression(left) ^ resolve_integer_expression(right) + case ComputationalAnd(left, right) => ??? // resolve_integer_expression(left) & resolve_integer_expression(right) + case BitAnd(left, right) => ??? // resolve_integer_expression(left) & resolve_integer_expression(right) + case BitOr(left, right) => ??? // resolve_integer_expression(left) | resolve_integer_expression(right) + case BitXor(left, right) => ??? // resolve_integer_expression(left) ^ resolve_integer_expression(right) + case BitShl(left, right) => ??? // resolve_integer_expression(left) << resolve_integer_expression(right) + case BitShr(left, right) => ??? // resolve_integer_expression(left) >> resolve_integer_expression(right) + case BitUShr(left, right) => ??? // resolve_integer_expression(left) >>> resolve_integer_expression(right) case Select(cond, ift, iff) => { - if (resolve_boolean_expression(cond)) resolve_integer_expression(ift) - else resolve_integer_expression(iff) + var value: UncertainIntegerValue = UncertainIntegerValue.empty() + if (resolve_boolean_expression(cond).can_be_true) value = value.union(resolve_integer_expression(ift)) + if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_integer_expression(iff)) + value } case AmbiguousSubscript(collection, index) => ??? case SeqSubscript(seq, index) => ??? @@ -74,8 +79,10 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int], processes case LessEq(left, right) => resolve_integer_expression(left) <= resolve_integer_expression(right) case c: SetComparison[G] => ??? case Select(cond, ift, iff) => { - if (resolve_boolean_expression(cond)) resolve_boolean_expression(ift) - else resolve_boolean_expression(iff) + var value: UncertainBooleanValue = UncertainBooleanValue(can_be_true = false, can_be_false = false) + if (resolve_boolean_expression(cond).can_be_true) value = value.union(resolve_boolean_expression(ift)) + if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_boolean_expression(iff)) + value } case AmbiguousSubscript(collection, index) => ??? case SeqSubscript(seq, index) => ??? diff --git a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala index 0186656907..5b9a49f598 100644 --- a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala +++ b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala @@ -17,11 +17,11 @@ case class FieldVariable[G](field: Field[G]) extends ConcreteVariable[G] { } case class IndexedVariable[G](field: Field[G], i: Int) extends ConcreteVariable[G] { - override def is(expr: Expr[G]): Boolean = expr match { + override def is(expr: Expr[G]): Boolean = ??? /*expr match { case AmbiguousSubscript(collection, index) => field.equals(extract_from_expression(collection)) && i == Utils.resolve_integer_expression(index) case SeqSubscript(seq, index) => field.equals(extract_from_expression(seq)) && i == Utils.resolve_integer_expression(index) case ArraySubscript(arr, index) => field.equals(extract_from_expression(arr)) && i == Utils.resolve_integer_expression(index) case PointerSubscript(pointer, index) => field.equals(extract_from_expression(pointer)) && i == Utils.resolve_integer_expression(index) - } + }*/ override def to_expression(): Expr[G] = ??? } diff --git a/src/rewrite/vct/rewrite/rasi/Interval.scala b/src/rewrite/vct/rewrite/rasi/Interval.scala new file mode 100644 index 0000000000..c13e292db1 --- /dev/null +++ b/src/rewrite/vct/rewrite/rasi/Interval.scala @@ -0,0 +1,119 @@ +package vct.rewrite.rasi + +trait Interval { + def empty(): Boolean + def non_empty(): Boolean = !empty() + def intersection(other: Interval): Interval + def union(other: Interval): Interval + def complement(): Interval + def +(other: Interval): Interval + def -(other: Interval): Interval + def *(other: Interval): Interval + def /(other: Interval): Interval + def %(other: Interval): Interval + def unary_- : Interval + def pow(other:Interval): Interval + def sub_intervals(): Seq[Interval] = Seq(this) +} + +case object EmptyInterval extends Interval { + override def empty(): Boolean = true + override def intersection(other: Interval): Interval = this + override def union(other: Interval): Interval = other + override def complement(): Interval = UnboundedInterval + override def +(other: Interval): Interval = this + override def -(other: Interval): Interval = this + override def *(other: Interval): Interval = this + override def /(other: Interval): Interval = this + override def %(other: Interval): Interval = this + override def unary_- : Interval = this + override def pow(other: Interval): Interval = this +} + +case class MultiInterval(intervals: Seq[Interval]) extends Interval { + override def empty(): Boolean = intervals.isEmpty || intervals.forall(i => i.empty()) + override def intersection(other: Interval): Interval = MultiInterval(intervals.map(i => i.intersection(other))) + override def union(other: Interval): Interval = ??? + override def complement(): Interval = ??? + override def +(other: Interval): Interval = ??? + override def -(other: Interval): Interval = ??? + override def *(other: Interval): Interval = ??? + override def /(other: Interval): Interval = ??? + override def %(other: Interval): Interval = ??? + override def unary_- : Interval = MultiInterval(intervals.map(i => -i)) + override def pow(other: Interval): Interval = ??? + override def sub_intervals(): Seq[Interval] = intervals.flatMap(i => i.sub_intervals()) +} + +case class BoundedInterval(lower: Int, upper: Int) extends Interval { + override def empty(): Boolean = lower > upper + override def intersection(other: Interval): Interval = ??? + override def union(other: Interval): Interval = ??? + override def complement(): Interval = + MultiInterval(Seq(UpperBoundedInterval(lower - 1), LowerBoundedInterval(upper + 1))) + override def +(other: Interval): Interval = ??? + override def -(other: Interval): Interval = ??? + override def *(other: Interval): Interval = ??? + override def /(other: Interval): Interval = ??? + override def %(other: Interval): Interval = ??? + override def unary_- : Interval = BoundedInterval(-upper, -lower) + override def pow(other: Interval): Interval = ??? +} + +case class LowerBoundedInterval(lower: Int) extends Interval { + override def empty(): Boolean = false + override def intersection(other: Interval): Interval = ??? + override def union(other: Interval): Interval = ??? + override def complement(): Interval = UpperBoundedInterval(lower - 1) + override def +(other: Interval): Interval = ??? + override def -(other: Interval): Interval = ??? + override def *(other: Interval): Interval = ??? + override def /(other: Interval): Interval = ??? + override def %(other: Interval): Interval = ??? + override def unary_- : Interval = UpperBoundedInterval(-lower) + override def pow(other: Interval): Interval = ??? +} + +case class UpperBoundedInterval(upper: Int) extends Interval { + override def empty(): Boolean = false + override def intersection(other: Interval): Interval = ??? + override def union(other: Interval): Interval = ??? + override def complement(): Interval = LowerBoundedInterval(upper + 1) + override def +(other: Interval): Interval = ??? + override def -(other: Interval): Interval = ??? + override def *(other: Interval): Interval = ??? + override def /(other: Interval): Interval = ??? + override def %(other: Interval): Interval = ??? + override def unary_- : Interval = LowerBoundedInterval(-upper) + override def pow(other: Interval): Interval = ??? +} + +case object UnboundedInterval extends Interval { + override def empty(): Boolean = false + override def intersection(other: Interval): Interval = other + override def union(other: Interval): Interval = this + override def complement(): Interval = EmptyInterval + override def +(other: Interval): Interval = this + override def -(other: Interval): Interval = this + override def *(other: Interval): Interval = this + override def /(other: Interval): Interval = this + override def %(other: Interval): Interval = other match { + case UnboundedInterval | EmptyInterval => other + case mi: MultiInterval => { + val intvs = mi.sub_intervals() + if (intvs.collect{case LowerBoundedInterval(_) | UpperBoundedInterval(_) | UnboundedInterval => 0}.nonEmpty) + return this + val max = intvs.map{case EmptyInterval => 0; case BoundedInterval(lower, upper) => Utils.absmax(lower, upper)}.max - 1 + if (max <= 0) EmptyInterval + else BoundedInterval(-max, max) + } + case BoundedInterval(lower, upper) => { + val max = Utils.absmax(lower, upper) - 1 + BoundedInterval(-max, max) + } + case LowerBoundedInterval(_) => this + case UpperBoundedInterval(_) => this + } + override def unary_- : Interval = this + override def pow(other: Interval): Interval = this +} \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index 33ac489584..c00f457c31 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -4,6 +4,7 @@ import vct.col.ast.{BooleanValue, Expr, Or} import vct.col.origin.{LabelContext, Origin} import vct.rewrite.cfg.CFGNode +import scala.collection.immutable.HashMap import scala.collection.mutable case class RASIGenerator[G]() { @@ -17,7 +18,7 @@ case class RASIGenerator[G]() { } private def explore(node: CFGNode[G], vars: Set[ConcreteVariable[G]]): Unit = { - val initial_state = AbstractState(get_initial_values(vars), Map((AbstractProcess("main"), node))) + val initial_state = AbstractState(get_initial_values(vars), HashMap((AbstractProcess[G]("main"), node))) found_states.addOne(initial_state) current_branches.addOne(initial_state) diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index 5b19f535be..58de293393 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -16,21 +16,72 @@ case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) ex case _ => true } + def union(other: UncertainBooleanValue): UncertainBooleanValue = + UncertainBooleanValue(can_be_true || other.can_be_true, can_be_false || other.can_be_false) + + def &(other: UncertainBooleanValue): UncertainBooleanValue = this && other + def &&(other: UncertainBooleanValue): UncertainBooleanValue = UncertainBooleanValue(can_be_true && other.can_be_true, can_be_false || other.can_be_false) + def |(other: UncertainBooleanValue): UncertainBooleanValue = this || other + def ||(other: UncertainBooleanValue): UncertainBooleanValue = UncertainBooleanValue(can_be_true || other.can_be_true, can_be_false && other.can_be_false) - def !(): UncertainBooleanValue = + def unary_! : UncertainBooleanValue = UncertainBooleanValue(can_be_false, can_be_true) def ^(other: UncertainBooleanValue): UncertainBooleanValue = UncertainBooleanValue(can_be_true && other.can_be_false || can_be_false && other.can_be_true, can_be_true && other.can_be_true || can_be_false && other.can_be_false) + + def ==(other: UncertainBooleanValue): UncertainBooleanValue = + UncertainBooleanValue(can_be_true && other.can_be_true || can_be_false && other.can_be_false, can_be_true && other.can_be_false || can_be_false && other.can_be_true) + + def !=(other: UncertainBooleanValue): UncertainBooleanValue = this ^ other } -case class UncertainIntegerValue() extends UncertainValue { - override def can_be_equal(other: UncertainValue): Boolean = ??? +case class UncertainIntegerValue(value: Interval) extends UncertainValue { + override def can_be_equal(other: UncertainValue): Boolean = other match { + case UncertainIntegerValue(v) => value.intersection(v).non_empty() + case _ => false + } + + override def can_be_unequal(other: UncertainValue): Boolean = other match { + case UncertainIntegerValue(v) => value.intersection(v).complement().non_empty() + case _ => true + } + + def union(other: UncertainIntegerValue): UncertainIntegerValue = + UncertainIntegerValue(value.union(other.value)) + + def ==(other: UncertainIntegerValue): UncertainBooleanValue = + UncertainBooleanValue(can_be_equal(other), can_be_unequal(other)) + def !=(other: UncertainIntegerValue): UncertainBooleanValue = + UncertainBooleanValue(can_be_unequal(other), can_be_equal(other)) + def >=(other: UncertainIntegerValue): UncertainBooleanValue = ??? + def <=(other: UncertainIntegerValue): UncertainBooleanValue = ??? + def >(other: UncertainIntegerValue): UncertainBooleanValue = ??? + def <(other: UncertainIntegerValue): UncertainBooleanValue = ??? - override def can_be_unequal(other: UncertainValue): Boolean = ??? + def unary_- : UncertainIntegerValue = + UncertainIntegerValue(-value) + def +(other: UncertainIntegerValue): UncertainIntegerValue = + UncertainIntegerValue(value + other.value) + def -(other: UncertainIntegerValue): UncertainIntegerValue = + UncertainIntegerValue(value - other.value) + def *(other: UncertainIntegerValue): UncertainIntegerValue = + UncertainIntegerValue(value * other.value) + def /(other: UncertainIntegerValue): UncertainIntegerValue = + UncertainIntegerValue(value / other.value) + def %(other: UncertainIntegerValue): UncertainIntegerValue = + UncertainIntegerValue(value % other.value) + def pow(other: UncertainIntegerValue): UncertainIntegerValue = + UncertainIntegerValue(value.pow(other.value)) } +case object UncertainIntegerValue { + def empty(): UncertainIntegerValue = UncertainIntegerValue(EmptyInterval) + def uncertain(): UncertainIntegerValue = UncertainIntegerValue(UnboundedInterval) + def above(int: Int): UncertainIntegerValue = UncertainIntegerValue(LowerBoundedInterval(int)) + def single(int: Int): UncertainIntegerValue = UncertainIntegerValue(BoundedInterval(int, int)) +} \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala index 7726531fdf..73b203c984 100644 --- a/src/rewrite/vct/rewrite/rasi/Utils.scala +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -1,11 +1,5 @@ package vct.rewrite.rasi -import vct.col.ast._ - case object Utils { - def resolve_integer_expression[G](expr: Expr[G]): Int = expr match { - case IntegerValue(value) => value.intValue - case Plus(left, right) => resolve_integer_expression(left) + resolve_integer_expression(right) - - } + def absmax(a: Int, b: Int): Int = Seq(-a, a, -b, b).max } From e789ad9ee3e43e5755c3f96405931a2130e735a5 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 19 Feb 2024 16:44:33 +0100 Subject: [PATCH 32/85] Made options for index switches more explicit --- src/rewrite/vct/rewrite/cfg/Index.scala | 176 +++++++++++++----------- 1 file changed, 93 insertions(+), 83 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 7011b47325..03a74434c6 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -5,6 +5,16 @@ import vct.col.ref.Ref import scala.collection.mutable +sealed abstract class NextIndex[G] { + def get: Index[G] +} +case class Step[G](value: Index[G]) extends NextIndex[G] { + override def get: Index[G] = value +} +case class Outgoing[G]() extends NextIndex[G] { + override def get: Index[G] = throw new NoSuchElementException("Trying to access outgoing edge") +} + case class GlobalIndex[G](indices: mutable.Seq[Index[G]]) { override def equals(obj: scala.Any): Boolean = obj match { @@ -17,12 +27,12 @@ case class GlobalIndex[G](indices: mutable.Seq[Index[G]]) { def make_step(): mutable.Set[(GlobalIndex[G], Option[Expr[G]])] = { if (indices.isEmpty) return mutable.Set((this, None)) - val steps: Set[(Option[Index[G]], Option[Expr[G]])] = indices.head.make_step() + val steps: Set[(NextIndex[G], Option[Expr[G]])] = indices.head.make_step() val res = mutable.Set[(GlobalIndex[G], Option[Expr[G]])]() for (step <- steps) { step match { - case (Some(index), cond) => res.addOne((GlobalIndex(index +: indices.tail), cond)) - case (None, cond) => res.addAll(GlobalIndex(indices.tail).make_step().map(tup => (tup._1, Utils.and(tup._2, cond)))) + case (Step(index), cond) => res.addOne((GlobalIndex(index +: indices.tail), cond)) + case (Outgoing, cond) => res.addAll(GlobalIndex(indices.tail).make_step().map(tup => (tup._1, Utils.and(tup._2, cond)))) } } res @@ -97,7 +107,7 @@ sealed trait Index[G] { * * @return A set of all steps possible from the current index, along with conditions for taking them */ - def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] + def make_step(): Set[(NextIndex[G], Option[Expr[G]])] /** * Returns the statement that corresponds to the current index. @@ -157,7 +167,7 @@ object Index { } case class InitialIndex[G](instance_method: InstanceMethod[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = instance_method.body.get override def equals(obj: scala.Any): Boolean = obj match { case InitialIndex(m) => m.equals(instance_method) @@ -166,7 +176,7 @@ case class InitialIndex[G](instance_method: InstanceMethod[G]) extends Index[G] } case class RunMethodIndex[G](run_method: RunMethod[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = run_method.body.get override def has_statement(): Boolean = run_method.body.nonEmpty override def equals(obj: scala.Any): Boolean = obj match { @@ -176,9 +186,9 @@ case class RunMethodIndex[G](run_method: RunMethod[G]) extends Index[G] { } case class ExpressionContainerIndex[G](statement: Statement[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { - if (index == 0) Set((Some(ExpressionContainerIndex(statement, 1)), None)) - else Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { + if (index == 0) Set((Step(ExpressionContainerIndex(statement, 1)), None)) + else Set((Outgoing, None)) } override def resolve(): Statement[G] = statement override def equals(obj: scala.Any): Boolean = obj match { @@ -188,9 +198,9 @@ case class ExpressionContainerIndex[G](statement: Statement[G], index: Int) exte } case class AssignmentIndex[G](assign: Assign[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { - if (index < 2) Set((Some(AssignmentIndex(assign, index + 1)), None)) - else Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { + if (index < 2) Set((Step(AssignmentIndex(assign, index + 1)), None)) + else Set((Outgoing, None)) } override def resolve(): Statement[G] = index match { case 0 => Eval(assign.target)(assign.target.o) @@ -204,15 +214,15 @@ case class AssignmentIndex[G](assign: Assign[G], index: Int) extends Index[G] { } case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { // Indices 0, 2, 4, ... are the conditions, indices 1, 3, 5, ... are the branch bodies if (index % 2 == 0 && index < 2 * (pvl_branch.branches.size - 1)) - Set((Some(PVLBranchIndex(pvl_branch, index + 2)), Some(Utils.negate(pvl_branch.branches.apply(index / 2)._1))), - (Some(PVLBranchIndex(pvl_branch, index + 1)), Some(pvl_branch.branches.apply(index / 2)._1))) + Set((Step(PVLBranchIndex(pvl_branch, index + 2)), Some(Utils.negate(pvl_branch.branches.apply(index / 2)._1))), + (Step(PVLBranchIndex(pvl_branch, index + 1)), Some(pvl_branch.branches.apply(index / 2)._1))) else if (index == 2 * (pvl_branch.branches.size - 1)) - Set((Some(PVLBranchIndex(pvl_branch, index + 1)), Some(pvl_branch.branches.apply(index / 2)._1)), - (None, Some(Utils.negate(pvl_branch.branches.apply(index / 2)._1)))) - else Set((None, None)) + Set((Step(PVLBranchIndex(pvl_branch, index + 1)), Some(pvl_branch.branches.apply(index / 2)._1)), + (Outgoing, Some(Utils.negate(pvl_branch.branches.apply(index / 2)._1)))) + else Set((Outgoing, None)) } override def resolve(): Statement[G] = { if (index % 2 == 0) Eval(pvl_branch.branches.apply(index / 2)._1)(pvl_branch.branches.apply(index / 2)._1.o) @@ -225,11 +235,11 @@ case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index } case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = index match { - case 0 => Set((Some(PVLLoopIndex(pvl_loop, 1)), None)) - case 1 => Set((Some(PVLLoopIndex(pvl_loop, 2)), Some(pvl_loop.cond)), (None, Some(Utils.negate(pvl_loop.cond)))) - case 2 => Set((Some(PVLLoopIndex(pvl_loop, 3)), None)) - case 3 => Set((Some(PVLLoopIndex(pvl_loop, 1)), None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = index match { + case 0 => Set((Step(PVLLoopIndex(pvl_loop, 1)), None)) + case 1 => Set((Step(PVLLoopIndex(pvl_loop, 2)), Some(pvl_loop.cond)), (Outgoing, Some(Utils.negate(pvl_loop.cond)))) + case 2 => Set((Step(PVLLoopIndex(pvl_loop, 3)), None)) + case 3 => Set((Step(PVLLoopIndex(pvl_loop, 1)), None)) } override def resolve(): Statement[G] = index match { case 0 => pvl_loop.init @@ -244,7 +254,7 @@ case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { } case class LabelIndex[G](label: Label[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = label.stat override def equals(obj: scala.Any): Boolean = obj match { case LabelIndex(l) => l.equals(label) @@ -253,9 +263,9 @@ case class LabelIndex[G](label: Label[G]) extends Index[G] { } case class FramedProofIndex[G](framed_proof: FramedProof[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { - if (index < 2) Set((Some(FramedProofIndex(framed_proof, index + 1)), None)) - else Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { + if (index < 2) Set((Step(FramedProofIndex(framed_proof, index + 1)), None)) + else Set((Outgoing, None)) } override def resolve(): Statement[G] = index match { case 0 => Eval(framed_proof.pre)(framed_proof.pre.o) @@ -269,7 +279,7 @@ case class FramedProofIndex[G](framed_proof: FramedProof[G], index: Int) extends } case class ExtractIndex[G](extract: Extract[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = extract.contractedStatement override def equals(obj: scala.Any): Boolean = obj match { case ExtractIndex(e) => e.equals(extract) @@ -279,10 +289,10 @@ case class ExtractIndex[G](extract: Extract[G]) extends Index[G] { case class EvalIndex[G](eval: Eval[G], index: Int, subexpressions: Seq[Statement[G]]) extends Index[G] { def this(eval: Eval[G], index: Int) = this(eval, index, Utils.find_all_subexpressions(eval.expr)) - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { // TODO: Consider conditions in expressions, too - if (index < subexpressions.size - 1) Set((Some(EvalIndex(eval, index + 1)), None)) - else Set((None, None)) + if (index < subexpressions.size - 1) Set((Step(EvalIndex(eval, index + 1)), None)) + else Set((Outgoing, None)) } override def resolve(): Statement[G] = subexpressions.apply(index) override def has_statement(): Boolean = subexpressions.nonEmpty @@ -302,15 +312,15 @@ case class InvokeProcedureIndex[G](invoke_procedure: InvokeProcedure[G], index: // 3. outArgs // 4. yields // 5. procedure body - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { val args: Seq[Expr[G]] = invoke_procedure.args val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_procedure.givenMap val outArgs: Seq[Expr[G]] = invoke_procedure.outArgs val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_procedure.yields if (index < args.size + givenMap.size + outArgs.size + yields.size - 1 || index == args.size + givenMap.size + outArgs.size + yields.size - 1 && invoke_procedure.ref.decl.body.nonEmpty) - Set((Some(InvokeProcedureIndex(invoke_procedure, index + 1)), None)) - else Set((None, None)) + Set((Step(InvokeProcedureIndex(invoke_procedure, index + 1)), None)) + else Set((Outgoing, None)) } override def resolve(): Statement[G] = { val args: Seq[Expr[G]] = invoke_procedure.args @@ -351,15 +361,15 @@ case class InvokeConstructorIndex[G](invoke_constructor: InvokeConstructor[G], i // 4. yields // 5. out // 6. constructor body - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { val args: Seq[Expr[G]] = invoke_constructor.args val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_constructor.givenMap val outArgs: Seq[Expr[G]] = invoke_constructor.outArgs val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_constructor.yields if (index < args.size + givenMap.size + outArgs.size + yields.size || index == args.size + givenMap.size + outArgs.size + yields.size && invoke_constructor.ref.decl.body.nonEmpty) - Set((Some(InvokeConstructorIndex(invoke_constructor, index + 1)), None)) - else Set((None, None)) + Set((Step(InvokeConstructorIndex(invoke_constructor, index + 1)), None)) + else Set((Outgoing, None)) } override def resolve(): Statement[G] = { val args: Seq[Expr[G]] = invoke_constructor.args @@ -396,15 +406,15 @@ case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) exte // 4. outArgs // 5. yields // 6. method body - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { val args: Seq[Expr[G]] = invoke_method.args val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_method.givenMap val outArgs: Seq[Expr[G]] = invoke_method.outArgs val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_method.yields if (index < args.size + givenMap.size + outArgs.size + yields.size || index == args.size + givenMap.size + outArgs.size + yields.size && invoke_method.ref.decl.body.nonEmpty) - Set((Some(InvokeMethodIndex(invoke_method, index + 1)), None)) - else Set((None, None)) + Set((Step(InvokeMethodIndex(invoke_method, index + 1)), None)) + else Set((Outgoing, None)) } override def resolve(): Statement[G] = { val args: Seq[Expr[G]] = invoke_method.args @@ -434,9 +444,9 @@ case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) exte } case class BlockIndex[G](block: Block[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { - if (index < block.statements.size - 1) Set((Some(BlockIndex(block, index + 1)), None)) - else Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { + if (index < block.statements.size - 1) Set((Step(BlockIndex(block, index + 1)), None)) + else Set((Outgoing, None)) } override def resolve(): Statement[G] = block.statements.apply(index) override def equals(obj: scala.Any): Boolean = obj match { @@ -446,7 +456,7 @@ case class BlockIndex[G](block: Block[G], index: Int) extends Index[G] { } case class ScopeIndex[G](scope: Scope[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = scope.body override def equals(obj: scala.Any): Boolean = obj match { case ScopeIndex(s) => s.equals(scope) @@ -455,15 +465,15 @@ case class ScopeIndex[G](scope: Scope[G]) extends Index[G] { } case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { // Indices 0, 2, 4, ... are the conditions, indices 1, 3, 5, ... are the branch bodies if (index % 2 == 0 && index < 2 * (branch.branches.size - 1)) - Set((Some(BranchIndex(branch, index + 2)), Some(Utils.negate(branch.branches.apply(index / 2)._1))), - (Some(BranchIndex(branch, index + 1)), Some(branch.branches.apply(index / 2)._1))) + Set((Step(BranchIndex(branch, index + 2)), Some(Utils.negate(branch.branches.apply(index / 2)._1))), + (Step(BranchIndex(branch, index + 1)), Some(branch.branches.apply(index / 2)._1))) else if (index == 2 * (branch.branches.size - 1)) - Set((Some(BranchIndex(branch, index + 1)), Some(branch.branches.apply(index / 2)._1)), - (None, Some(Utils.negate(branch.branches.apply(index / 2)._1)))) - else Set((None, None)) + Set((Step(BranchIndex(branch, index + 1)), Some(branch.branches.apply(index / 2)._1)), + (Outgoing, Some(Utils.negate(branch.branches.apply(index / 2)._1)))) + else Set((Outgoing, None)) } override def resolve(): Statement[G] = { if (index % 2 == 0) Eval(branch.branches.apply(index / 2)._1)(branch.branches.apply(index / 2)._1.o) @@ -476,7 +486,7 @@ case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { } case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = indet_branch.branches.apply(index) override def equals(obj: scala.Any): Boolean = obj match { case IndetBranchIndex(b, i) => i == index && b.equals(indet_branch) @@ -486,7 +496,7 @@ case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends // TODO: Switch cases could be multiple context indices deep; this does not work with the single index for make_step() case class SwitchIndex[G](switch: Switch[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = switch.body override def equals(obj: scala.Any): Boolean = obj match { case SwitchIndex(s) => s.equals(switch) @@ -495,11 +505,11 @@ case class SwitchIndex[G](switch: Switch[G]) extends Index[G] { } case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = index match { - case 0 => Set((Some(LoopIndex(loop, 1)), None)) - case 1 => Set((Some(LoopIndex(loop, 2)), Some(loop.cond)), (None, Some(Utils.negate(loop.cond)))) - case 2 => Set((Some(LoopIndex(loop, 3)), None)) - case 3 => Set((Some(LoopIndex(loop, 1)), None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = index match { + case 0 => Set((Step(LoopIndex(loop, 1)), None)) + case 1 => Set((Step(LoopIndex(loop, 2)), Some(loop.cond)), (Outgoing, Some(Utils.negate(loop.cond)))) + case 2 => Set((Step(LoopIndex(loop, 3)), None)) + case 3 => Set((Step(LoopIndex(loop, 1)), None)) } override def resolve(): Statement[G] = index match { case 0 => loop.init @@ -514,7 +524,7 @@ case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { } case class RangedForIndex[G](ranged_for: RangedFor[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((Some(this), None), (None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Step(this), None), (Outgoing, None)) override def resolve(): Statement[G] = ranged_for.body override def equals(obj: scala.Any): Boolean = obj match { case RangedForIndex(r) => r.equals(ranged_for) @@ -523,9 +533,9 @@ case class RangedForIndex[G](ranged_for: RangedFor[G]) extends Index[G] { } case class TryCatchFinallyIndex[G](try_catch_finally: TryCatchFinally[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { - if (index != 1) Set((Some(TryCatchFinallyIndex(try_catch_finally, 1)), None)) - else Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { + if (index != 1) Set((Step(TryCatchFinallyIndex(try_catch_finally, 1)), None)) + else Set((Outgoing, None)) } override def resolve(): Statement[G] = index match { case 0 => try_catch_finally.body @@ -539,7 +549,7 @@ case class TryCatchFinallyIndex[G](try_catch_finally: TryCatchFinally[G], index: } case class SynchronizedIndex[G](synchronized: Synchronized[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = synchronized.body override def equals(obj: scala.Any): Boolean = obj match { case SynchronizedIndex(s) => s.equals(synchronized) @@ -548,7 +558,7 @@ case class SynchronizedIndex[G](synchronized: Synchronized[G]) extends Index[G] } case class ParInvariantIndex[G](par_invariant: ParInvariant[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = par_invariant.content override def equals(obj: scala.Any): Boolean = obj match { case ParInvariantIndex(p) => p.equals(par_invariant) @@ -557,7 +567,7 @@ case class ParInvariantIndex[G](par_invariant: ParInvariant[G]) extends Index[G] } case class ParAtomicIndex[G](par_atomic: ParAtomic[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = par_atomic.content override def equals(obj: scala.Any): Boolean = obj match { case ParAtomicIndex(p) => p.equals(par_atomic) @@ -566,7 +576,7 @@ case class ParAtomicIndex[G](par_atomic: ParAtomic[G]) extends Index[G] { } case class ParBarrierIndex[G](par_barrier: ParBarrier[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = par_barrier.content override def equals(obj: scala.Any): Boolean = obj match { case ParBarrierIndex(p) => p.equals(par_barrier) @@ -575,7 +585,7 @@ case class ParBarrierIndex[G](par_barrier: ParBarrier[G]) extends Index[G] { } case class VecBlockIndex[G](vec_block: VecBlock[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = vec_block.content override def equals(obj: scala.Any): Boolean = obj match { case VecBlockIndex(v) => v.equals(vec_block) @@ -584,7 +594,7 @@ case class VecBlockIndex[G](vec_block: VecBlock[G]) extends Index[G] { } case class WandPackageIndex[G](wand_package: WandPackage[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = wand_package.proof override def equals(obj: scala.Any): Boolean = obj match { case WandPackageIndex(w) => w.equals(wand_package) @@ -593,7 +603,7 @@ case class WandPackageIndex[G](wand_package: WandPackage[G]) extends Index[G] { } case class ModelDoIndex[G](model_do: ModelDo[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = model_do.impl override def equals(obj: scala.Any): Boolean = obj match { case ModelDoIndex(m) => m.equals(model_do) @@ -602,7 +612,7 @@ case class ModelDoIndex[G](model_do: ModelDo[G]) extends Index[G] { } case class CPPLifetimeScopeIndex[G](cpp_lifetime_scope: CPPLifetimeScope[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = cpp_lifetime_scope.body override def equals(obj: scala.Any): Boolean = obj match { case CPPLifetimeScopeIndex(c) => c.equals(cpp_lifetime_scope) @@ -611,15 +621,15 @@ case class CPPLifetimeScopeIndex[G](cpp_lifetime_scope: CPPLifetimeScope[G]) ext } case class UnresolvedSeqBranchIndex[G](unresolved_seq_branch: UnresolvedSeqBranch[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = { + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { // Indices 0, 2, 4, ... are the conditions, indices 1, 3, 5, ... are the branch bodies if (index % 2 == 0 && index < 2 * (unresolved_seq_branch.branches.size - 1)) - Set((Some(UnresolvedSeqBranchIndex(unresolved_seq_branch, index + 2)), Some(Utils.negate(unresolved_seq_branch.branches.apply(index / 2)._1))), - (Some(UnresolvedSeqBranchIndex(unresolved_seq_branch, index + 1)), Some(unresolved_seq_branch.branches.apply(index / 2)._1))) + Set((Step(UnresolvedSeqBranchIndex(unresolved_seq_branch, index + 2)), Some(Utils.negate(unresolved_seq_branch.branches.apply(index / 2)._1))), + (Step(UnresolvedSeqBranchIndex(unresolved_seq_branch, index + 1)), Some(unresolved_seq_branch.branches.apply(index / 2)._1))) else if (index == 2 * (unresolved_seq_branch.branches.size - 1)) - Set((Some(UnresolvedSeqBranchIndex(unresolved_seq_branch, index + 1)), Some(unresolved_seq_branch.branches.apply(index / 2)._1)), - (None, Some(Utils.negate(unresolved_seq_branch.branches.apply(index / 2)._1)))) - else Set((None, None)) + Set((Step(UnresolvedSeqBranchIndex(unresolved_seq_branch, index + 1)), Some(unresolved_seq_branch.branches.apply(index / 2)._1)), + (Outgoing, Some(Utils.negate(unresolved_seq_branch.branches.apply(index / 2)._1)))) + else Set((Outgoing, None)) } override def resolve(): Statement[G] = { if (index % 2 == 0) Eval(unresolved_seq_branch.branches.apply(index / 2)._1)(unresolved_seq_branch.branches.apply(index / 2)._1.o) @@ -632,10 +642,10 @@ case class UnresolvedSeqBranchIndex[G](unresolved_seq_branch: UnresolvedSeqBranc } case class UnresolvedSeqLoopIndex[G](unresolved_seq_loop: UnresolvedSeqLoop[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = index match { - case 0 => Set((Some(UnresolvedSeqLoopIndex(unresolved_seq_loop, 1)), Some(unresolved_seq_loop.cond)), - (None, Some(Utils.negate(unresolved_seq_loop.cond)))) - case 1 => Set((Some(UnresolvedSeqLoopIndex(unresolved_seq_loop, 0)), None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = index match { + case 0 => Set((Step(UnresolvedSeqLoopIndex(unresolved_seq_loop, 1)), Some(unresolved_seq_loop.cond)), + (Outgoing, Some(Utils.negate(unresolved_seq_loop.cond)))) + case 1 => Set((Step(UnresolvedSeqLoopIndex(unresolved_seq_loop, 0)), None)) } override def resolve(): Statement[G] = index match { case 0 => Eval(unresolved_seq_loop.cond)(unresolved_seq_loop.cond.o) @@ -648,7 +658,7 @@ case class UnresolvedSeqLoopIndex[G](unresolved_seq_loop: UnresolvedSeqLoop[G], } case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = index match { case 0 => seq_branch.yes case 1 => seq_branch.no.get @@ -660,7 +670,7 @@ case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index } case class SeqLoopIndex[G](seq_loop: SeqLoop[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((Some(this), None), (None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Step(this), None), (Outgoing, None)) override def resolve(): Statement[G] = seq_loop.body override def equals(obj: scala.Any): Boolean = obj match { case SeqLoopIndex(s) => s.equals(seq_loop) @@ -669,7 +679,7 @@ case class SeqLoopIndex[G](seq_loop: SeqLoop[G]) extends Index[G] { } case class VeyMontAssignExpressionIndex[G](veymont_assign_expression: VeyMontAssignExpression[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = veymont_assign_expression.assign override def equals(obj: scala.Any): Boolean = obj match { case VeyMontAssignExpressionIndex(v) => v.equals(veymont_assign_expression) @@ -678,7 +688,7 @@ case class VeyMontAssignExpressionIndex[G](veymont_assign_expression: VeyMontAss } case class CommunicateXIndex[G](communicatex: CommunicateX[G]) extends Index[G] { - override def make_step(): Set[(Option[Index[G]], Option[Expr[G]])] = Set((None, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) override def resolve(): Statement[G] = communicatex.assign override def equals(obj: scala.Any): Boolean = obj match { case CommunicateXIndex(c) => c.equals(communicatex) From 465798cfcab0d9aeba4c9bc3ba6e20549b9b884e Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 19 Feb 2024 16:52:08 +0100 Subject: [PATCH 33/85] Syntax fixes --- src/rewrite/vct/rewrite/cfg/Index.scala | 78 ++++++++++++------------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 03a74434c6..f4a0108be4 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -32,7 +32,7 @@ case class GlobalIndex[G](indices: mutable.Seq[Index[G]]) { for (step <- steps) { step match { case (Step(index), cond) => res.addOne((GlobalIndex(index +: indices.tail), cond)) - case (Outgoing, cond) => res.addAll(GlobalIndex(indices.tail).make_step().map(tup => (tup._1, Utils.and(tup._2, cond)))) + case (Outgoing(), cond) => res.addAll(GlobalIndex(indices.tail).make_step().map(tup => (tup._1, Utils.and(tup._2, cond)))) } } res @@ -167,7 +167,7 @@ object Index { } case class InitialIndex[G](instance_method: InstanceMethod[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = instance_method.body.get override def equals(obj: scala.Any): Boolean = obj match { case InitialIndex(m) => m.equals(instance_method) @@ -176,7 +176,7 @@ case class InitialIndex[G](instance_method: InstanceMethod[G]) extends Index[G] } case class RunMethodIndex[G](run_method: RunMethod[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = run_method.body.get override def has_statement(): Boolean = run_method.body.nonEmpty override def equals(obj: scala.Any): Boolean = obj match { @@ -188,7 +188,7 @@ case class RunMethodIndex[G](run_method: RunMethod[G]) extends Index[G] { case class ExpressionContainerIndex[G](statement: Statement[G], index: Int) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { if (index == 0) Set((Step(ExpressionContainerIndex(statement, 1)), None)) - else Set((Outgoing, None)) + else Set((Outgoing(), None)) } override def resolve(): Statement[G] = statement override def equals(obj: scala.Any): Boolean = obj match { @@ -200,7 +200,7 @@ case class ExpressionContainerIndex[G](statement: Statement[G], index: Int) exte case class AssignmentIndex[G](assign: Assign[G], index: Int) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { if (index < 2) Set((Step(AssignmentIndex(assign, index + 1)), None)) - else Set((Outgoing, None)) + else Set((Outgoing(), None)) } override def resolve(): Statement[G] = index match { case 0 => Eval(assign.target)(assign.target.o) @@ -221,8 +221,8 @@ case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index (Step(PVLBranchIndex(pvl_branch, index + 1)), Some(pvl_branch.branches.apply(index / 2)._1))) else if (index == 2 * (pvl_branch.branches.size - 1)) Set((Step(PVLBranchIndex(pvl_branch, index + 1)), Some(pvl_branch.branches.apply(index / 2)._1)), - (Outgoing, Some(Utils.negate(pvl_branch.branches.apply(index / 2)._1)))) - else Set((Outgoing, None)) + (Outgoing(), Some(Utils.negate(pvl_branch.branches.apply(index / 2)._1)))) + else Set((Outgoing(), None)) } override def resolve(): Statement[G] = { if (index % 2 == 0) Eval(pvl_branch.branches.apply(index / 2)._1)(pvl_branch.branches.apply(index / 2)._1.o) @@ -237,7 +237,7 @@ case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = index match { case 0 => Set((Step(PVLLoopIndex(pvl_loop, 1)), None)) - case 1 => Set((Step(PVLLoopIndex(pvl_loop, 2)), Some(pvl_loop.cond)), (Outgoing, Some(Utils.negate(pvl_loop.cond)))) + case 1 => Set((Step(PVLLoopIndex(pvl_loop, 2)), Some(pvl_loop.cond)), (Outgoing(), Some(Utils.negate(pvl_loop.cond)))) case 2 => Set((Step(PVLLoopIndex(pvl_loop, 3)), None)) case 3 => Set((Step(PVLLoopIndex(pvl_loop, 1)), None)) } @@ -254,7 +254,7 @@ case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { } case class LabelIndex[G](label: Label[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = label.stat override def equals(obj: scala.Any): Boolean = obj match { case LabelIndex(l) => l.equals(label) @@ -265,7 +265,7 @@ case class LabelIndex[G](label: Label[G]) extends Index[G] { case class FramedProofIndex[G](framed_proof: FramedProof[G], index: Int) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { if (index < 2) Set((Step(FramedProofIndex(framed_proof, index + 1)), None)) - else Set((Outgoing, None)) + else Set((Outgoing(), None)) } override def resolve(): Statement[G] = index match { case 0 => Eval(framed_proof.pre)(framed_proof.pre.o) @@ -279,7 +279,7 @@ case class FramedProofIndex[G](framed_proof: FramedProof[G], index: Int) extends } case class ExtractIndex[G](extract: Extract[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = extract.contractedStatement override def equals(obj: scala.Any): Boolean = obj match { case ExtractIndex(e) => e.equals(extract) @@ -292,7 +292,7 @@ case class EvalIndex[G](eval: Eval[G], index: Int, subexpressions: Seq[Statement override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { // TODO: Consider conditions in expressions, too if (index < subexpressions.size - 1) Set((Step(EvalIndex(eval, index + 1)), None)) - else Set((Outgoing, None)) + else Set((Outgoing(), None)) } override def resolve(): Statement[G] = subexpressions.apply(index) override def has_statement(): Boolean = subexpressions.nonEmpty @@ -320,7 +320,7 @@ case class InvokeProcedureIndex[G](invoke_procedure: InvokeProcedure[G], index: if (index < args.size + givenMap.size + outArgs.size + yields.size - 1 || index == args.size + givenMap.size + outArgs.size + yields.size - 1 && invoke_procedure.ref.decl.body.nonEmpty) Set((Step(InvokeProcedureIndex(invoke_procedure, index + 1)), None)) - else Set((Outgoing, None)) + else Set((Outgoing(), None)) } override def resolve(): Statement[G] = { val args: Seq[Expr[G]] = invoke_procedure.args @@ -369,7 +369,7 @@ case class InvokeConstructorIndex[G](invoke_constructor: InvokeConstructor[G], i if (index < args.size + givenMap.size + outArgs.size + yields.size || index == args.size + givenMap.size + outArgs.size + yields.size && invoke_constructor.ref.decl.body.nonEmpty) Set((Step(InvokeConstructorIndex(invoke_constructor, index + 1)), None)) - else Set((Outgoing, None)) + else Set((Outgoing(), None)) } override def resolve(): Statement[G] = { val args: Seq[Expr[G]] = invoke_constructor.args @@ -414,7 +414,7 @@ case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) exte if (index < args.size + givenMap.size + outArgs.size + yields.size || index == args.size + givenMap.size + outArgs.size + yields.size && invoke_method.ref.decl.body.nonEmpty) Set((Step(InvokeMethodIndex(invoke_method, index + 1)), None)) - else Set((Outgoing, None)) + else Set((Outgoing(), None)) } override def resolve(): Statement[G] = { val args: Seq[Expr[G]] = invoke_method.args @@ -446,7 +446,7 @@ case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) exte case class BlockIndex[G](block: Block[G], index: Int) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { if (index < block.statements.size - 1) Set((Step(BlockIndex(block, index + 1)), None)) - else Set((Outgoing, None)) + else Set((Outgoing(), None)) } override def resolve(): Statement[G] = block.statements.apply(index) override def equals(obj: scala.Any): Boolean = obj match { @@ -456,7 +456,7 @@ case class BlockIndex[G](block: Block[G], index: Int) extends Index[G] { } case class ScopeIndex[G](scope: Scope[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = scope.body override def equals(obj: scala.Any): Boolean = obj match { case ScopeIndex(s) => s.equals(scope) @@ -472,8 +472,8 @@ case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { (Step(BranchIndex(branch, index + 1)), Some(branch.branches.apply(index / 2)._1))) else if (index == 2 * (branch.branches.size - 1)) Set((Step(BranchIndex(branch, index + 1)), Some(branch.branches.apply(index / 2)._1)), - (Outgoing, Some(Utils.negate(branch.branches.apply(index / 2)._1)))) - else Set((Outgoing, None)) + (Outgoing(), Some(Utils.negate(branch.branches.apply(index / 2)._1)))) + else Set((Outgoing(), None)) } override def resolve(): Statement[G] = { if (index % 2 == 0) Eval(branch.branches.apply(index / 2)._1)(branch.branches.apply(index / 2)._1.o) @@ -486,7 +486,7 @@ case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { } case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = indet_branch.branches.apply(index) override def equals(obj: scala.Any): Boolean = obj match { case IndetBranchIndex(b, i) => i == index && b.equals(indet_branch) @@ -496,7 +496,7 @@ case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends // TODO: Switch cases could be multiple context indices deep; this does not work with the single index for make_step() case class SwitchIndex[G](switch: Switch[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = switch.body override def equals(obj: scala.Any): Boolean = obj match { case SwitchIndex(s) => s.equals(switch) @@ -507,7 +507,7 @@ case class SwitchIndex[G](switch: Switch[G]) extends Index[G] { case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = index match { case 0 => Set((Step(LoopIndex(loop, 1)), None)) - case 1 => Set((Step(LoopIndex(loop, 2)), Some(loop.cond)), (Outgoing, Some(Utils.negate(loop.cond)))) + case 1 => Set((Step(LoopIndex(loop, 2)), Some(loop.cond)), (Outgoing(), Some(Utils.negate(loop.cond)))) case 2 => Set((Step(LoopIndex(loop, 3)), None)) case 3 => Set((Step(LoopIndex(loop, 1)), None)) } @@ -524,7 +524,7 @@ case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { } case class RangedForIndex[G](ranged_for: RangedFor[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Step(this), None), (Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Step(this), None), (Outgoing(), None)) override def resolve(): Statement[G] = ranged_for.body override def equals(obj: scala.Any): Boolean = obj match { case RangedForIndex(r) => r.equals(ranged_for) @@ -535,7 +535,7 @@ case class RangedForIndex[G](ranged_for: RangedFor[G]) extends Index[G] { case class TryCatchFinallyIndex[G](try_catch_finally: TryCatchFinally[G], index: Int) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { if (index != 1) Set((Step(TryCatchFinallyIndex(try_catch_finally, 1)), None)) - else Set((Outgoing, None)) + else Set((Outgoing(), None)) } override def resolve(): Statement[G] = index match { case 0 => try_catch_finally.body @@ -549,7 +549,7 @@ case class TryCatchFinallyIndex[G](try_catch_finally: TryCatchFinally[G], index: } case class SynchronizedIndex[G](synchronized: Synchronized[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = synchronized.body override def equals(obj: scala.Any): Boolean = obj match { case SynchronizedIndex(s) => s.equals(synchronized) @@ -558,7 +558,7 @@ case class SynchronizedIndex[G](synchronized: Synchronized[G]) extends Index[G] } case class ParInvariantIndex[G](par_invariant: ParInvariant[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = par_invariant.content override def equals(obj: scala.Any): Boolean = obj match { case ParInvariantIndex(p) => p.equals(par_invariant) @@ -567,7 +567,7 @@ case class ParInvariantIndex[G](par_invariant: ParInvariant[G]) extends Index[G] } case class ParAtomicIndex[G](par_atomic: ParAtomic[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = par_atomic.content override def equals(obj: scala.Any): Boolean = obj match { case ParAtomicIndex(p) => p.equals(par_atomic) @@ -576,7 +576,7 @@ case class ParAtomicIndex[G](par_atomic: ParAtomic[G]) extends Index[G] { } case class ParBarrierIndex[G](par_barrier: ParBarrier[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = par_barrier.content override def equals(obj: scala.Any): Boolean = obj match { case ParBarrierIndex(p) => p.equals(par_barrier) @@ -585,7 +585,7 @@ case class ParBarrierIndex[G](par_barrier: ParBarrier[G]) extends Index[G] { } case class VecBlockIndex[G](vec_block: VecBlock[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = vec_block.content override def equals(obj: scala.Any): Boolean = obj match { case VecBlockIndex(v) => v.equals(vec_block) @@ -594,7 +594,7 @@ case class VecBlockIndex[G](vec_block: VecBlock[G]) extends Index[G] { } case class WandPackageIndex[G](wand_package: WandPackage[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = wand_package.proof override def equals(obj: scala.Any): Boolean = obj match { case WandPackageIndex(w) => w.equals(wand_package) @@ -603,7 +603,7 @@ case class WandPackageIndex[G](wand_package: WandPackage[G]) extends Index[G] { } case class ModelDoIndex[G](model_do: ModelDo[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = model_do.impl override def equals(obj: scala.Any): Boolean = obj match { case ModelDoIndex(m) => m.equals(model_do) @@ -612,7 +612,7 @@ case class ModelDoIndex[G](model_do: ModelDo[G]) extends Index[G] { } case class CPPLifetimeScopeIndex[G](cpp_lifetime_scope: CPPLifetimeScope[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = cpp_lifetime_scope.body override def equals(obj: scala.Any): Boolean = obj match { case CPPLifetimeScopeIndex(c) => c.equals(cpp_lifetime_scope) @@ -628,8 +628,8 @@ case class UnresolvedSeqBranchIndex[G](unresolved_seq_branch: UnresolvedSeqBranc (Step(UnresolvedSeqBranchIndex(unresolved_seq_branch, index + 1)), Some(unresolved_seq_branch.branches.apply(index / 2)._1))) else if (index == 2 * (unresolved_seq_branch.branches.size - 1)) Set((Step(UnresolvedSeqBranchIndex(unresolved_seq_branch, index + 1)), Some(unresolved_seq_branch.branches.apply(index / 2)._1)), - (Outgoing, Some(Utils.negate(unresolved_seq_branch.branches.apply(index / 2)._1)))) - else Set((Outgoing, None)) + (Outgoing(), Some(Utils.negate(unresolved_seq_branch.branches.apply(index / 2)._1)))) + else Set((Outgoing(), None)) } override def resolve(): Statement[G] = { if (index % 2 == 0) Eval(unresolved_seq_branch.branches.apply(index / 2)._1)(unresolved_seq_branch.branches.apply(index / 2)._1.o) @@ -644,7 +644,7 @@ case class UnresolvedSeqBranchIndex[G](unresolved_seq_branch: UnresolvedSeqBranc case class UnresolvedSeqLoopIndex[G](unresolved_seq_loop: UnresolvedSeqLoop[G], index: Int) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = index match { case 0 => Set((Step(UnresolvedSeqLoopIndex(unresolved_seq_loop, 1)), Some(unresolved_seq_loop.cond)), - (Outgoing, Some(Utils.negate(unresolved_seq_loop.cond)))) + (Outgoing(), Some(Utils.negate(unresolved_seq_loop.cond)))) case 1 => Set((Step(UnresolvedSeqLoopIndex(unresolved_seq_loop, 0)), None)) } override def resolve(): Statement[G] = index match { @@ -658,7 +658,7 @@ case class UnresolvedSeqLoopIndex[G](unresolved_seq_loop: UnresolvedSeqLoop[G], } case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = index match { case 0 => seq_branch.yes case 1 => seq_branch.no.get @@ -670,7 +670,7 @@ case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index } case class SeqLoopIndex[G](seq_loop: SeqLoop[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Step(this), None), (Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Step(this), None), (Outgoing(), None)) override def resolve(): Statement[G] = seq_loop.body override def equals(obj: scala.Any): Boolean = obj match { case SeqLoopIndex(s) => s.equals(seq_loop) @@ -679,7 +679,7 @@ case class SeqLoopIndex[G](seq_loop: SeqLoop[G]) extends Index[G] { } case class VeyMontAssignExpressionIndex[G](veymont_assign_expression: VeyMontAssignExpression[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = veymont_assign_expression.assign override def equals(obj: scala.Any): Boolean = obj match { case VeyMontAssignExpressionIndex(v) => v.equals(veymont_assign_expression) @@ -688,7 +688,7 @@ case class VeyMontAssignExpressionIndex[G](veymont_assign_expression: VeyMontAss } case class CommunicateXIndex[G](communicatex: CommunicateX[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing, None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = communicatex.assign override def equals(obj: scala.Any): Boolean = obj match { case CommunicateXIndex(c) => c.equals(communicatex) From 8212df849a9c11c07744ba31bcfe4e173b2647f9 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 20 Feb 2024 11:57:15 +0100 Subject: [PATCH 34/85] Added expression evaluations to lock and process related statements --- src/rewrite/vct/rewrite/cfg/CFGGenerator.scala | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 805e411ed3..54facc86e7 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -65,21 +65,21 @@ case class CFGGenerator[G]() { case Inhale(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) case Assume(assn) => handle_expression_container(node, Eval(assn)(assn.o), context, sequential_successor(context)) case Instantiate(_, out) => handle_expression_container(node, Eval(out)(out.o), context, sequential_successor(context)) - case Wait(_) => sequential_successor(context) - case Notify(_) => sequential_successor(context) + case Wait(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, sequential_successor(context)) + case Notify(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, sequential_successor(context)) case Fork(obj) => { val run_method: RunMethod[G] = obj.t.asClass.get.cls.decl.declarations.collect{ case r: RunMethod[G] => r }.head // Get the successor(s) of the fork statement as well as the new thread, starting with the run method sequential_successor(context).addOne(CFGEdge(convert(run_method.body.get, GlobalIndex[G](mutable.Seq()).enter_scope(run_method)), None)) } - case Join(_) => sequential_successor(context) - case Lock(_) => sequential_successor(context) - case Unlock(_) => sequential_successor(context) - case Commit(_) => sequential_successor(context) + case Join(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, sequential_successor(context)) + case Lock(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, sequential_successor(context)) + case Unlock(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, sequential_successor(context)) + case Commit(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, sequential_successor(context)) case Fold(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) case Unfold(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) case WandApply(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) - case Havoc(_) => sequential_successor(context) + case Havoc(loc) => handle_expression_container(node, Eval(loc)(loc.o), context, sequential_successor(context)) case FramedProof(_, _, _) => evaluate_first(context.enter_scope(node)) case Extract(_) => evaluate_first(context.enter_scope(node)) // ExceptionalStatement From 39cb088c09c08ff35386aec0911ff0ab7189cbbb Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 20 Feb 2024 11:57:51 +0100 Subject: [PATCH 35/85] Implemented some logic for interval arithmetic, started on implementation of abstract process logic --- .../vct/rewrite/rasi/AbstractProcess.scala | 65 +++- .../vct/rewrite/rasi/AbstractState.scala | 16 +- src/rewrite/vct/rewrite/rasi/Interval.scala | 313 +++++++++++++++--- .../vct/rewrite/rasi/UncertainValue.scala | 33 +- src/rewrite/vct/rewrite/rasi/Utils.scala | 6 +- 5 files changed, 375 insertions(+), 58 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index 313d563db0..c4daa5cf9d 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -7,12 +7,69 @@ case class AbstractProcess[G](name: String) { def get_next(node: CFGEntry[G], state: AbstractState[G]): Set[AbstractState[G]] = node match { case CFGTerminal() => Set() case CFGNode(n, succ) => succ.filter(e => e.condition.isEmpty || state.resolve_boolean_expression(e.condition.get).can_be_true) - .flatMap (e => process_cfg_edge(e, n)).toSet + .map(e => process_cfg_edge(e, n, state)).toSet } - private def process_cfg_edge(edge: CFGEdge[G], ast_node: Node[G]): Set[AbstractState[G]] = ast_node match { + private def process_cfg_edge(edge: CFGEdge[G], ast_node: Node[G], state: AbstractState[G]): AbstractState[G] = ast_node match { // TODO: Implement! - case Assign(target, value) => ??? - case _ => ??? + case Assign(target, value) => state.with_valuation(target, state.resolve_integer_expression(value).try_to_resolve().get) + case Exhale(res) => ??? + case Assert(res) => ??? + case Refute(assn) => ??? + case Inhale(res) => ??? + case Assume(assn) => ??? + case Instantiate(_, out) => ??? + case Wait(_) => ??? + case Notify(_) => ??? + case Fork(obj) => ??? // TODO: This needs to be decided in the outer method... + case Join(_) => ??? + case Lock(_) => ??? + case Unlock(_) => ??? + case Commit(_) => ??? + case Fold(res) => ??? + case Unfold(res) => ??? + case WandApply(res) => ??? + case Havoc(_) => ??? + case FramedProof(_, _, _) => ??? + case Extract(_) => ??? + case Eval(_) => ??? + case Return(result) => ??? + case Throw(obj) => ??? + case Break(label) => ??? + case Continue(label) => ??? + case InvokeProcedure(_, _, _, _, _, _) => ??? + case InvokeConstructor(_, _, _, _, _, _, _) => ??? + case InvokeMethod(_, _, _, _, _, _, _) => ??? + case Block(_) => ??? + case Scope(_, _) => ??? + case Branch(_) => ??? + case IndetBranch(branches) => ??? + case Switch(expr, body) => ??? + case Loop(_, _, _, _, _) => ??? + case RangedFor(_, _, _) => ??? + case TryCatchFinally(_, _, _) => ??? + case Synchronized(_, _) => ??? + case ParInvariant(_, _, _) => ??? + case ParAtomic(_, _) => ??? + case ParBarrier(_, _, _, _, _) => ??? + case ParStatement(_) => ??? + case VecBlock(_, _, _, _) => ??? + case WandPackage(_, _) => ??? + case ModelDo(_, _, _, _, _) => ??? + case CDeclarationStatement(_) => ??? + case CGoto(label) => ??? + case CPPDeclarationStatement(_) => ??? + case CPPLifetimeScope(_) => ??? + case JavaLocalDeclarationStatement(_) => ??? + case SilverNewRef(_, _) => ??? + case SilverFieldAssign(_, _, value) => ??? + case SilverLocalAssign(_, value) => ??? + case PVLCommunicate(_, _) => ??? + case PVLSeqAssign(_, _, value) => ??? + case Communicate(_, _) => ??? + case SeqAssign(_, _, value) => ??? + case UnresolvedSeqBranch(branches) => ??? + case UnresolvedSeqLoop(_, _, _) => ??? + case _ => state.with_process_at(this, edge.target) } } diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index f2a592bda0..9bce02bf29 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -10,6 +10,14 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int], processes def successors(): Set[AbstractState[G]] = processes.flatMap(p => p._1.get_next(p._2, this)).toSet + def with_process_at(process: AbstractProcess[G], position: CFGEntry[G]): AbstractState[G] = + AbstractState(valuations, processes + (process -> position)) + + def with_valuation(variable: Expr[G], value: Int): AbstractState[G] = variable_from_expr(variable) match { + case Some(concrete_variable) => AbstractState(valuations + (concrete_variable -> value), processes) + case None => this + } + def resolve_integer_expression(expr: Expr[G]): UncertainIntegerValue = expr match { case CIntegerValue(value) => UncertainIntegerValue.single(value.intValue) case IntegerValue(value) => UncertainIntegerValue.single(value.intValue) @@ -17,7 +25,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int], processes case DerefHeapVariable(ref) => ??? case Deref(obj, ref) => ??? case DerefPointer(pointer) => ??? - case SizeOf(tname) => ??? + case SizeOf(tname) => UncertainIntegerValue.above(0) // TODO: Can we use more information about sizeof? case UMinus(arg) => -resolve_integer_expression(arg) case AmbiguousMult(left, right) => resolve_integer_expression(left) * resolve_integer_expression(right) case AmbiguousPlus(left, right) => resolve_integer_expression(left) + resolve_integer_expression(right) @@ -52,8 +60,8 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int], processes case SeqSubscript(seq, index) => ??? case ArraySubscript(arr, index) => ??? case PointerSubscript(pointer, index) => ??? - case Length(arr) => ??? - case Size(obj) => ??? + case Length(arr) => UncertainIntegerValue.above(0) // TODO: Use contextual information from the global invariant + case Size(obj) => UncertainIntegerValue.above(0) // here as well } def resolve_boolean_expression(expr: Expr[G]): UncertainBooleanValue = expr match { @@ -90,6 +98,8 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int], processes case PointerSubscript(pointer, index) => ??? } + private def variable_from_expr(variable: Expr[G]): Option[ConcreteVariable[G]] = ??? + private def neutral_element: Expr[G] = BooleanValue(value = true)(Origin(Seq(LabelContext("neutral element for and")))) def to_expression(): Expr[G] = { valuations.map(v => Eq(v._1.to_expression(), IntegerValue(v._2)(Origin(Seq(LabelContext("int value")))))(Origin(Seq(LabelContext("valuation"))))) diff --git a/src/rewrite/vct/rewrite/rasi/Interval.scala b/src/rewrite/vct/rewrite/rasi/Interval.scala index c13e292db1..b75315d690 100644 --- a/src/rewrite/vct/rewrite/rasi/Interval.scala +++ b/src/rewrite/vct/rewrite/rasi/Interval.scala @@ -1,100 +1,337 @@ package vct.rewrite.rasi -trait Interval { +import vct.col.ast._ + +sealed abstract class IntervalSize { + def get: Int + def +(other: IntervalSize): IntervalSize = this match { + case Infinite() => Infinite() + case Finite(v1) => other match { + case Infinite() => Infinite() + case Finite(v2) => Finite(v1 + v2) + } + } + def >=(other: IntervalSize): Boolean = this match { + case Infinite() => true + case Finite(v1) => other match { + case Infinite() => false + case Finite(v2) => v1 >= v2 + } + } +} +case class Finite(value: Int) extends IntervalSize { + override def get: Int = value +} +case class Infinite() extends IntervalSize { + override def get: Int = throw new NoSuchElementException("Accessing infinite interval size element") +} + +sealed abstract class Interval { def empty(): Boolean def non_empty(): Boolean = !empty() + def size(): IntervalSize def intersection(other: Interval): Interval def union(other: Interval): Interval def complement(): Interval + def below_max(): Interval + def above_min(): Interval def +(other: Interval): Interval - def -(other: Interval): Interval - def *(other: Interval): Interval + def -(other: Interval): Interval = this.+(-other) + def *(other: Interval): Interval // TODO: Modeling the multiple of an interval as an interval is imprecise def /(other: Interval): Interval def %(other: Interval): Interval def unary_- : Interval def pow(other:Interval): Interval - def sub_intervals(): Seq[Interval] = Seq(this) + def sub_intervals(): Set[Interval] = Set(this) + def try_to_resolve(): Option[Int] + def to_expression[G](variable: Expr[G]): Expr[G] // TODO: Use proper origin } case object EmptyInterval extends Interval { override def empty(): Boolean = true + override def size(): IntervalSize = Finite(0) override def intersection(other: Interval): Interval = this override def union(other: Interval): Interval = other override def complement(): Interval = UnboundedInterval + override def below_max(): Interval = this + override def above_min(): Interval = this override def +(other: Interval): Interval = this - override def -(other: Interval): Interval = this override def *(other: Interval): Interval = this override def /(other: Interval): Interval = this override def %(other: Interval): Interval = this override def unary_- : Interval = this override def pow(other: Interval): Interval = this + override def try_to_resolve(): Option[Int] = None + override def to_expression[G](variable: Expr[G]): Expr[G] = BooleanValue(value = false)(variable.o) } -case class MultiInterval(intervals: Seq[Interval]) extends Interval { +case class MultiInterval(intervals: Set[Interval]) extends Interval { override def empty(): Boolean = intervals.isEmpty || intervals.forall(i => i.empty()) - override def intersection(other: Interval): Interval = MultiInterval(intervals.map(i => i.intersection(other))) - override def union(other: Interval): Interval = ??? - override def complement(): Interval = ??? - override def +(other: Interval): Interval = ??? - override def -(other: Interval): Interval = ??? - override def *(other: Interval): Interval = ??? - override def /(other: Interval): Interval = ??? - override def %(other: Interval): Interval = ??? + override def size(): IntervalSize = intervals.map(i => i.size()).fold(Finite(0))((s1, s2) => s1 + s2) + override def intersection(other: Interval): Interval = MultiInterval(MultiInterval(intervals.map(i => i.intersection(other))).sub_intervals()) + override def union(other: Interval): Interval = { + val (intersecting, non_intersecting) = intervals.partition(i => i.intersection(other).non_empty()) + // Merge together intervals that are connected by the new interval + val new_intervals = non_intersecting + intersecting.fold(other)((i1, i2) => i1.union(i2)) + // It could be that all intervals are now connected into one + if (new_intervals.size > 1) MultiInterval(new_intervals) + else new_intervals.head + } + override def complement(): Interval = intervals.fold(UnboundedInterval)((i1, i2) => i1.intersection(i2.complement())) + override def below_max(): Interval = intervals.fold(EmptyInterval)((i1, i2) => i1.union(i2.below_max())) + override def above_min(): Interval = intervals.fold(EmptyInterval)((i1, i2) => i1.union(i2.above_min())) + override def +(other: Interval): Interval = { + val new_intervals = intervals.map(i => i + other) + // It could be that all intervals are now connected into one + if (new_intervals.size > 1) MultiInterval(new_intervals) + else new_intervals.head + } + override def *(other: Interval): Interval = { + val new_intervals = intervals.map(i => i * other) + // It could be that all intervals are now connected into one + if (new_intervals.size > 1) MultiInterval(new_intervals) + else new_intervals.head + } + override def /(other: Interval): Interval = { + var new_intervals = intervals.map(i => i / other) + new_intervals = merge_intersecting(new_intervals) + // It could be that all intervals are now connected into one + if (new_intervals.size > 1) MultiInterval(new_intervals) + else new_intervals.head + } + override def %(other: Interval): Interval = { + var new_intervals = intervals.map(i => i % other) + new_intervals = merge_intersecting(new_intervals) + // It could be that all intervals are now connected into one + if (new_intervals.size > 1) MultiInterval(new_intervals) + else new_intervals.head + } override def unary_- : Interval = MultiInterval(intervals.map(i => -i)) - override def pow(other: Interval): Interval = ??? - override def sub_intervals(): Seq[Interval] = intervals.flatMap(i => i.sub_intervals()) + override def pow(other: Interval): Interval = { + val new_intervals = intervals.map(i => i.pow(other)) + // It could be that all intervals are now connected into one + if (new_intervals.size > 1) MultiInterval(new_intervals) + else new_intervals.head + } + override def sub_intervals(): Set[Interval] = intervals.flatMap(i => i.sub_intervals()) + override def try_to_resolve(): Option[Int] = { + if (intervals.count(i => i != EmptyInterval) == 1) intervals.filter(i => i != EmptyInterval).head.try_to_resolve() + else None + } + override def to_expression[G](variable: Expr[G]): Expr[G] = + intervals.map(i => i.to_expression(variable)).fold(BooleanValue[G](value = false)(variable.o))((e1, e2) => Or(e1, e2)(variable.o)) + + private def merge_intersecting(is: Set[Interval]): Set[Interval] = { + ??? + } } case class BoundedInterval(lower: Int, upper: Int) extends Interval { override def empty(): Boolean = lower > upper - override def intersection(other: Interval): Interval = ??? - override def union(other: Interval): Interval = ??? + override def size(): IntervalSize = Finite(scala.math.max(upper - lower + 1, 0)) + override def intersection(other: Interval): Interval = other match { + case EmptyInterval => other + case mi: MultiInterval => mi.intersection(this) + case BoundedInterval(low, up) => + if (up <= upper && up >= lower || low <= upper && low >= lower) BoundedInterval(scala.math.max(low, lower), scala.math.min(up, upper)) + else EmptyInterval + case LowerBoundedInterval(low) => + if (upper >= low) BoundedInterval(scala.math.max(low, lower), upper) + else EmptyInterval + case UpperBoundedInterval(up) => + if (lower <= up) BoundedInterval(lower, scala.math.min(up, upper)) + else EmptyInterval + case UnboundedInterval => this + } + override def union(other: Interval): Interval = other match { + case EmptyInterval => this + case mi: MultiInterval => mi.union(this) + case BoundedInterval(low, up) => + if (up <= upper && up >= lower || low <= upper && low >= lower) BoundedInterval(scala.math.min(low, lower), scala.math.max(up, upper)) + else MultiInterval(Set(this, other)) + case LowerBoundedInterval(low) => + if (upper >= low) LowerBoundedInterval(scala.math.min(low, lower)) + else MultiInterval(Set(this, other)) + case UpperBoundedInterval(up) => + if (lower <= up) UpperBoundedInterval(scala.math.max(up, upper)) + else MultiInterval(Set(this, other)) + case UnboundedInterval => other + } override def complement(): Interval = - MultiInterval(Seq(UpperBoundedInterval(lower - 1), LowerBoundedInterval(upper + 1))) - override def +(other: Interval): Interval = ??? - override def -(other: Interval): Interval = ??? - override def *(other: Interval): Interval = ??? - override def /(other: Interval): Interval = ??? + MultiInterval(Set(UpperBoundedInterval(lower - 1), LowerBoundedInterval(upper + 1))) + override def below_max(): Interval = UpperBoundedInterval(upper) + override def above_min(): Interval = LowerBoundedInterval(lower) + override def +(other: Interval): Interval = other match { + case EmptyInterval | UnboundedInterval => other + case mi: MultiInterval => mi.+(this) + case BoundedInterval(low, up) => BoundedInterval(lower + low, upper + up) + case LowerBoundedInterval(low) => LowerBoundedInterval(lower + low) + case UpperBoundedInterval(up) => UpperBoundedInterval(upper + up) + } + override def *(other: Interval): Interval = other match { + case EmptyInterval | UnboundedInterval => other + case mi: MultiInterval => mi.*(this) + case BoundedInterval(low, up) => BoundedInterval(Utils.prod_min(low, up, lower, upper), Utils.prod_max(low, up, lower, upper)) + case LowerBoundedInterval(low) => + if (lower < 0 && upper > 0) UnboundedInterval + else if (lower >= 0) LowerBoundedInterval(scala.math.min(low * upper, low * lower)) + else UpperBoundedInterval(scala.math.max(low * upper, low * lower)) + case UpperBoundedInterval(up) => + if (lower < 0 && upper > 0) UnboundedInterval + else if (lower >= 0) UpperBoundedInterval(scala.math.max(up * lower, up * upper)) + else LowerBoundedInterval(scala.math.min(up * lower, up * upper)) + } + override def /(other: Interval): Interval = other match { + case EmptyInterval => other + case MultiInterval(intervals) => ??? + case BoundedInterval(low, up) => ??? + case LowerBoundedInterval(low) => ??? + case UpperBoundedInterval(up) => ??? + case UnboundedInterval => BoundedInterval(-Utils.abs_max(lower, upper), Utils.abs_max(lower, upper)) + } override def %(other: Interval): Interval = ??? override def unary_- : Interval = BoundedInterval(-upper, -lower) - override def pow(other: Interval): Interval = ??? + override def pow(other: Interval): Interval = other match { + case EmptyInterval => other + case MultiInterval(intervals) => ??? + case BoundedInterval(low, up) => ??? + case LowerBoundedInterval(low) => ??? + case UpperBoundedInterval(up) => ??? + case UnboundedInterval => + if (lower < 0) other + else LowerBoundedInterval(0) + } + override def try_to_resolve(): Option[Int] = { + if (lower == upper) Some(upper) + else None + } + override def to_expression[G](variable: Expr[G]): Expr[G] = { + if (lower == upper) Eq(variable, IntegerValue(upper)(variable.o))(variable.o) + else And(LessEq(variable, IntegerValue(upper)(variable.o))(variable.o), GreaterEq(variable, IntegerValue(lower)(variable.o))(variable.o))(variable.o) + } } case class LowerBoundedInterval(lower: Int) extends Interval { override def empty(): Boolean = false - override def intersection(other: Interval): Interval = ??? - override def union(other: Interval): Interval = ??? + override def size(): IntervalSize = Infinite() + override def intersection(other: Interval): Interval = other match { + case EmptyInterval => other + case mi: MultiInterval => mi.intersection(this) + case BoundedInterval(low, up) => + if (up >= lower) BoundedInterval(scala.math.max(lower, low), up) + else EmptyInterval + case LowerBoundedInterval(low) => LowerBoundedInterval(scala.math.max(low, lower)) + case UpperBoundedInterval(up) => + if (up >= lower) BoundedInterval(lower, up) + else EmptyInterval + case UnboundedInterval => this + } + override def union(other: Interval): Interval = other match { + case EmptyInterval => this + case mi: MultiInterval => mi.union(this) + case BoundedInterval(low, up) => + if (up >= lower) LowerBoundedInterval(scala.math.min(low, lower)) + else MultiInterval(Set(other, this)) + case LowerBoundedInterval(low) => LowerBoundedInterval(scala.math.min(low, lower)) + case UpperBoundedInterval(up) => + if (up >= lower) UnboundedInterval + else MultiInterval(Set(other, this)) + case UnboundedInterval => other + } override def complement(): Interval = UpperBoundedInterval(lower - 1) - override def +(other: Interval): Interval = ??? - override def -(other: Interval): Interval = ??? - override def *(other: Interval): Interval = ??? + override def below_max(): Interval = UnboundedInterval + override def above_min(): Interval = this + override def +(other: Interval): Interval = other match { + case EmptyInterval | UnboundedInterval => other + case mi: MultiInterval => mi.+(this) + case BoundedInterval(low, _) => LowerBoundedInterval(lower + low) + case LowerBoundedInterval(low) => LowerBoundedInterval(lower + low) + case UpperBoundedInterval(_) => UnboundedInterval + } + override def *(other: Interval): Interval = other match { + case EmptyInterval | UnboundedInterval => other + case mi: MultiInterval => mi.*(this) + case bi: BoundedInterval => bi.*(this) + case LowerBoundedInterval(low) => + if (low < 0 || lower < 0) UnboundedInterval + else LowerBoundedInterval(low * lower) + case UpperBoundedInterval(up) => + if (lower < 0 || up > 0) UnboundedInterval + else UpperBoundedInterval(up * lower) + } override def /(other: Interval): Interval = ??? override def %(other: Interval): Interval = ??? override def unary_- : Interval = UpperBoundedInterval(-lower) override def pow(other: Interval): Interval = ??? + override def try_to_resolve(): Option[Int] = None + override def to_expression[G](variable: Expr[G]): Expr[G] = GreaterEq(variable, IntegerValue(lower)(variable.o))(variable.o) } case class UpperBoundedInterval(upper: Int) extends Interval { override def empty(): Boolean = false - override def intersection(other: Interval): Interval = ??? - override def union(other: Interval): Interval = ??? + override def size(): IntervalSize = Infinite() + override def intersection(other: Interval): Interval = other match { + case EmptyInterval => other + case mi: MultiInterval => mi.intersection(this) + case BoundedInterval(low, up) => + if (low <= upper) BoundedInterval(low, scala.math.min(up, upper)) + else EmptyInterval + case LowerBoundedInterval(low) => + if (low <= upper) BoundedInterval(low, upper) + else EmptyInterval + case UpperBoundedInterval(up) => UpperBoundedInterval(scala.math.min(up, upper)) + case UnboundedInterval => this + } + override def union(other: Interval): Interval = other match { + case EmptyInterval => this + case mi: MultiInterval => mi.union(this) + case BoundedInterval(low, up) => + if (low <= upper) UpperBoundedInterval(scala.math.max(upper, up)) + else MultiInterval(Set(this, other)) + case LowerBoundedInterval(low) => + if (low <= upper) UnboundedInterval + else MultiInterval(Set(this, other)) + case UpperBoundedInterval(up) => UpperBoundedInterval(scala.math.max(upper, up)) + case UnboundedInterval => other + } override def complement(): Interval = LowerBoundedInterval(upper + 1) - override def +(other: Interval): Interval = ??? - override def -(other: Interval): Interval = ??? - override def *(other: Interval): Interval = ??? + override def below_max(): Interval = this + override def above_min(): Interval = UnboundedInterval + override def +(other: Interval): Interval = other match { + case EmptyInterval | UnboundedInterval => other + case mi: MultiInterval => mi.+(this) + case BoundedInterval(_, up) => UpperBoundedInterval(upper + up) + case LowerBoundedInterval(_) => UnboundedInterval + case UpperBoundedInterval(up) => UpperBoundedInterval(upper + up) + } + override def *(other: Interval): Interval = other match { + case EmptyInterval | UnboundedInterval => other + case mi: MultiInterval => mi.*(this) + case bi: BoundedInterval => bi.*(this) + case LowerBoundedInterval(low) => + if (low < 0 || upper > 0) UnboundedInterval + else UpperBoundedInterval(low * upper) + case UpperBoundedInterval(up) => + if (up > 0 || upper > 0) UnboundedInterval + else LowerBoundedInterval(up * upper) + } override def /(other: Interval): Interval = ??? override def %(other: Interval): Interval = ??? override def unary_- : Interval = LowerBoundedInterval(-upper) override def pow(other: Interval): Interval = ??? + override def try_to_resolve(): Option[Int] = None + override def to_expression[G](variable: Expr[G]): Expr[G] = LessEq(variable, IntegerValue(upper)(variable.o))(variable.o) } case object UnboundedInterval extends Interval { override def empty(): Boolean = false + override def size(): IntervalSize = Infinite() override def intersection(other: Interval): Interval = other override def union(other: Interval): Interval = this override def complement(): Interval = EmptyInterval + override def below_max(): Interval = this + override def above_min(): Interval = this override def +(other: Interval): Interval = this - override def -(other: Interval): Interval = this override def *(other: Interval): Interval = this override def /(other: Interval): Interval = this override def %(other: Interval): Interval = other match { @@ -103,12 +340,12 @@ case object UnboundedInterval extends Interval { val intvs = mi.sub_intervals() if (intvs.collect{case LowerBoundedInterval(_) | UpperBoundedInterval(_) | UnboundedInterval => 0}.nonEmpty) return this - val max = intvs.map{case EmptyInterval => 0; case BoundedInterval(lower, upper) => Utils.absmax(lower, upper)}.max - 1 + val max = intvs.map{case EmptyInterval => 0; case BoundedInterval(lower, upper) => Utils.abs_max(lower, upper)}.max - 1 if (max <= 0) EmptyInterval else BoundedInterval(-max, max) } case BoundedInterval(lower, upper) => { - val max = Utils.absmax(lower, upper) - 1 + val max = Utils.abs_max(lower, upper) - 1 BoundedInterval(-max, max) } case LowerBoundedInterval(_) => this @@ -116,4 +353,6 @@ case object UnboundedInterval extends Interval { } override def unary_- : Interval = this override def pow(other: Interval): Interval = this + override def try_to_resolve(): Option[Int] = None + override def to_expression[G](variable: Expr[G]): Expr[G] = BooleanValue(value = true)(variable.o) } \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index 58de293393..a433e2f1ea 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -16,28 +16,29 @@ case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) ex case _ => true } + def try_to_resolve(): Option[Boolean] = { + if (can_be_true && !can_be_false) Some(true) + else if (can_be_false && !can_be_true) Some(false) + else None + } + def union(other: UncertainBooleanValue): UncertainBooleanValue = UncertainBooleanValue(can_be_true || other.can_be_true, can_be_false || other.can_be_false) def &(other: UncertainBooleanValue): UncertainBooleanValue = this && other - def &&(other: UncertainBooleanValue): UncertainBooleanValue = UncertainBooleanValue(can_be_true && other.can_be_true, can_be_false || other.can_be_false) - def |(other: UncertainBooleanValue): UncertainBooleanValue = this || other - def ||(other: UncertainBooleanValue): UncertainBooleanValue = UncertainBooleanValue(can_be_true || other.can_be_true, can_be_false && other.can_be_false) - def unary_! : UncertainBooleanValue = UncertainBooleanValue(can_be_false, can_be_true) - def ^(other: UncertainBooleanValue): UncertainBooleanValue = - UncertainBooleanValue(can_be_true && other.can_be_false || can_be_false && other.can_be_true, can_be_true && other.can_be_true || can_be_false && other.can_be_false) - + UncertainBooleanValue(can_be_true && other.can_be_false || can_be_false && other.can_be_true, + can_be_true && other.can_be_true || can_be_false && other.can_be_false) def ==(other: UncertainBooleanValue): UncertainBooleanValue = - UncertainBooleanValue(can_be_true && other.can_be_true || can_be_false && other.can_be_false, can_be_true && other.can_be_false || can_be_false && other.can_be_true) - + UncertainBooleanValue(can_be_true && other.can_be_true || can_be_false && other.can_be_false, + can_be_true && other.can_be_false || can_be_false && other.can_be_true) def !=(other: UncertainBooleanValue): UncertainBooleanValue = this ^ other } @@ -52,6 +53,8 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { case _ => true } + def try_to_resolve(): Option[Int] = value.try_to_resolve() + def union(other: UncertainIntegerValue): UncertainIntegerValue = UncertainIntegerValue(value.union(other.value)) @@ -59,10 +62,14 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { UncertainBooleanValue(can_be_equal(other), can_be_unequal(other)) def !=(other: UncertainIntegerValue): UncertainBooleanValue = UncertainBooleanValue(can_be_unequal(other), can_be_equal(other)) - def >=(other: UncertainIntegerValue): UncertainBooleanValue = ??? - def <=(other: UncertainIntegerValue): UncertainBooleanValue = ??? - def >(other: UncertainIntegerValue): UncertainBooleanValue = ??? - def <(other: UncertainIntegerValue): UncertainBooleanValue = ??? + def >=(other: UncertainIntegerValue): UncertainBooleanValue = + UncertainBooleanValue(value.below_max().intersection(other.value).non_empty(), + value.above_min().intersection(other.value).size() >= Finite(1)) + def <=(other: UncertainIntegerValue): UncertainBooleanValue = + UncertainBooleanValue(value.above_min().intersection(other.value).non_empty(), + value.below_max().intersection(other.value).size() >= Finite(1)) + def >(other: UncertainIntegerValue): UncertainBooleanValue = !(this <= other) + def <(other: UncertainIntegerValue): UncertainBooleanValue = !(this >= other) def unary_- : UncertainIntegerValue = UncertainIntegerValue(-value) diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala index 73b203c984..c421bc35ff 100644 --- a/src/rewrite/vct/rewrite/rasi/Utils.scala +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -1,5 +1,9 @@ package vct.rewrite.rasi case object Utils { - def absmax(a: Int, b: Int): Int = Seq(-a, a, -b, b).max + def abs_max(a: Int, b: Int): Int = Seq(-a, a, -b, b).max + + def prod_max(a1: Int, a2: Int, b1: Int, b2: Int): Int = Seq(a1 * b1, a1 * b2, a2 * b1, a2 * b2).max + + def prod_min(a1: Int, a2: Int, b1: Int, b2: Int): Int = Seq(a1 * b1, a1 * b2, a2 * b1, a2 * b2).min } From 88dcc4033bf21a7e75bd3e9882236248306e4c8f Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 20 Feb 2024 16:37:17 +0100 Subject: [PATCH 36/85] Started implementing switch statements for CFG --- .../vct/rewrite/cfg/CFGGenerator.scala | 35 ++++++++++++---- src/rewrite/vct/rewrite/cfg/Utils.scala | 41 +++++++++++++++---- 2 files changed, 60 insertions(+), 16 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 54facc86e7..0883a48b28 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -43,7 +43,7 @@ case class CFGGenerator[G]() { case PVLLoop(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) // NonExecutableStatement case LocalDecl(_) => sequential_successor(context) - case SpecIgnoreStart() => sequential_successor(context) // TODO: What is this? + case SpecIgnoreStart() => sequential_successor(context) case SpecIgnoreEnd() => sequential_successor(context) // NormallyCompletingStatement case Assign(_, _) => context.indices.head match { @@ -104,19 +104,18 @@ case class CFGGenerator[G]() { case Branch(_) => evaluate_first(context.enter_scope(node)) case IndetBranch(branches) => mutable.LinkedHashSet.from(branches.zipWithIndex.map(b => CFGEdge(convert(b._1, context.enter_scope(node, b._2)), None))) - case Switch(expr, body) => // TODO: Add conditions to switch statement transitions - Utils.find_all_cases(body, context.enter_scope(node)).map(t => CFGEdge(convert(t._1, t._2), None)) // TODO: Handle side effects in switch statement conditions + case s: Switch[G] => handle_switch_statement(s, context) case Loop(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) case RangedFor(_, _, _) => evaluate_first(context.enter_scope(node)) case TryCatchFinally(_, _, _) => evaluate_first(context.enter_scope(node)) case Synchronized(_, _) => evaluate_first(context.enter_scope(node)) - case ParInvariant(_, _, _) => evaluate_first(context.enter_scope(node)) // TODO: Expression side effects - case ParAtomic(_, _) => evaluate_first(context.enter_scope(node)) // TODO: Expression side effects - case ParBarrier(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) // TODO: Expression side effects + case ParInvariant(_, _, _) => evaluate_first(context.enter_scope(node)) + case ParAtomic(_, _) => evaluate_first(context.enter_scope(node)) + case ParBarrier(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) case ParStatement(_) => sequential_successor(context) - case VecBlock(_, _, _, _) => evaluate_first(context.enter_scope(node)) // TODO: Expression side effects - case WandPackage(_, _) => evaluate_first(context.enter_scope(node)) // TODO: Expression side effects - case ModelDo(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) // TODO: Expression side effects + case VecBlock(_, _, _, _) => evaluate_first(context.enter_scope(node)) + case WandPackage(_, _) => evaluate_first(context.enter_scope(node)) + case ModelDo(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) // CStatement case CDeclarationStatement(_) => sequential_successor(context) case CGoto(label) => ??? // I'm not dealing with string labels @@ -165,6 +164,24 @@ case class CFGGenerator[G]() { else mutable.Set(CFGEdge(CFGTerminal(), None)) } + private def handle_switch_statement(switch: Switch[G], context: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = { + val switches: Seq[(SwitchCase[G], GlobalIndex[G])] = Utils.find_all_cases(switch.body, context.enter_scope(switch)) + var conds: Seq[(Eval[G], GlobalIndex[G])] = Seq() + var default: Option[(DefaultCase[G], GlobalIndex[G])] = None + for (s <- switches) { + s._1 match { + case c @ Case(pattern) => { + val cond = Eq(switch.expr, pattern)(c.o) + // Collect only up to default case + if (default.isEmpty) conds = conds :+ (Eval(cond)(cond.o), s._2) + } + case c @ DefaultCase() => default = Some((c, s._2)) + } + } + // TODO: Finish implementation + ??? + } + private def return_successor(index: GlobalIndex[G]): CFGEntry[G] = resolve_index(index.return_from_call()) diff --git a/src/rewrite/vct/rewrite/cfg/Utils.scala b/src/rewrite/vct/rewrite/cfg/Utils.scala index 197849e05a..9b543653b1 100644 --- a/src/rewrite/vct/rewrite/cfg/Utils.scala +++ b/src/rewrite/vct/rewrite/cfg/Utils.scala @@ -56,15 +56,42 @@ object Utils { private def get_out_variable[G](cls: Ref[G, Class[G]], o: Origin): Local[G] = Local(new DirectRef[G, Variable[G]](new Variable(TClass(cls))(o)))(o) - def find_all_cases[G](body: Statement[G], index: GlobalIndex[G]): mutable.Set[(SwitchCase[G], GlobalIndex[G])] = body match { - case Switch(_, _) => mutable.Set() + def find_all_cases[G](body: Statement[G], index: GlobalIndex[G]): Seq[(SwitchCase[G], GlobalIndex[G])] = body match { + case Switch(_, _) => Seq() // Recursion on statements that can contain case statements - case Label(_, stmt) => find_all_cases(stmt, index.enter_scope(body)) - case Block(stmts) => mutable.LinkedHashSet.from(stmts.zipWithIndex.flatMap(t => find_all_cases(t._1, index.enter_scope(body, t._2)))) - case Scope(_, stmt) => find_all_cases(stmt, index.enter_scope(body)) + case Label(_, stat) => find_all_cases(stat, index.enter_scope(body)) + case Block(statements) => statements.zipWithIndex.flatMap(t => find_all_cases(t._1, index.enter_scope(body, t._2))) + case Scope(_, bod) => find_all_cases(bod, index.enter_scope(body)) + case PVLBranch(branches) => branches.zipWithIndex.flatMap(t => find_all_cases(t._1._2, index.enter_scope(body, t._2 * 2 + 1))) + case PVLLoop(init, _, update, _, bod) => Seq((init, 0), (bod, 2), (update, 3)).flatMap(t => find_all_cases(t._1, index.enter_scope(body, t._2))) + case FramedProof(_, bod, _) => find_all_cases(bod, index.enter_scope(body)) + case Extract(contractedStatement) => find_all_cases(contractedStatement, index.enter_scope(body)) + case Branch(branches) => branches.zipWithIndex.flatMap(t => find_all_cases(t._1._2, index.enter_scope(body, t._2 * 2 + 1))) + case IndetBranch(branches) => branches.zipWithIndex.flatMap(t => find_all_cases(t._1, index.enter_scope(body, t._2))) + case Loop(init, _, update, _, bod) => Seq((init, 0), (bod, 2), (update, 3)).flatMap(t => find_all_cases(t._1, index.enter_scope(body, t._2))) + case RangedFor(_, _, bod) => find_all_cases(bod, index.enter_scope(body)) + case TryCatchFinally(bod, after, catches) => Seq((bod, 0), (after, 1)).concat(catches.zipWithIndex.map(t => (t._1.body, t._2 + 2))) + .flatMap(t => find_all_cases(t._1, index.enter_scope(body, t._2))) + case Synchronized(_, bod) => find_all_cases(bod, index.enter_scope(body)) + case ParInvariant(_, _, content) => find_all_cases(content, index.enter_scope(body)) + case ParAtomic(_, content) => find_all_cases(content, index.enter_scope(body)) + case ParBarrier(_, _, _, _, content) => find_all_cases(content, index.enter_scope(body)) + case VecBlock(_, _, _, content) => find_all_cases(content, index.enter_scope(body)) + case WandPackage(_, proof) => find_all_cases(proof, index.enter_scope(body)) + case ModelDo(_, _, _, _, impl) => find_all_cases(impl, index.enter_scope(body)) + case CPPLifetimeScope(bod) => find_all_cases(bod, index.enter_scope(body)) + case UnresolvedSeqBranch(branches) => branches.zipWithIndex.flatMap(t => find_all_cases(t._1._2, index.enter_scope(body, t._2 * 2 + 1))) + case UnresolvedSeqLoop(_, _, bod) => find_all_cases(bod, index.enter_scope(body)) + case SeqBranch(_, yes, no) => no match { + case Some(stmt) => Seq((yes, 0), (stmt, 1)).flatMap(t => find_all_cases(t._1, index.enter_scope(body))) + case None => find_all_cases(yes, index.enter_scope(body)) + } + case SeqLoop(_, _, bod) => find_all_cases(bod, index.enter_scope(body)) + case VeyMontAssignExpression(_, assign) => find_all_cases(assign, index.enter_scope(body)) + case CommunicateX(_, _, _, assign) => find_all_cases(assign, index.enter_scope(body)) // Recursion end - case c: SwitchCase[G] => mutable.Set((c, index)) - case _ => mutable.Set() // TODO: Assuming that there are no cases in deeper structures (branches, loops etc.) + case c: SwitchCase[G] => Seq((c, index)) + case _ => Seq() } def negate[G](expr: Expr[G]): Expr[G] = expr match { From 33ac27959703996ec647c94a6dd417d43e1a4d95 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 20 Feb 2024 16:40:29 +0100 Subject: [PATCH 37/85] Made variable valuations abstract in RASI generator --- .../vct/rewrite/rasi/AbstractProcess.scala | 2 +- .../vct/rewrite/rasi/AbstractState.scala | 41 ++++++++----------- .../vct/rewrite/rasi/RASIGenerator.scala | 4 +- .../vct/rewrite/rasi/UncertainValue.scala | 35 ++++++++-------- 4 files changed, 40 insertions(+), 42 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index c4daa5cf9d..f272f3fb7d 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -12,7 +12,7 @@ case class AbstractProcess[G](name: String) { private def process_cfg_edge(edge: CFGEdge[G], ast_node: Node[G], state: AbstractState[G]): AbstractState[G] = ast_node match { // TODO: Implement! - case Assign(target, value) => state.with_valuation(target, state.resolve_integer_expression(value).try_to_resolve().get) + case Assign(target, value) => state.with_valuation(target, state.resolve_expression(value)) case Exhale(res) => ??? case Assert(res) => ??? case Refute(assn) => ??? diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 9bce02bf29..fc65f4a00e 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -6,25 +6,27 @@ import vct.rewrite.cfg.CFGEntry import scala.collection.immutable.HashMap -case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int], processes: HashMap[AbstractProcess[G], CFGEntry[G]]) { +case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue], processes: HashMap[AbstractProcess[G], CFGEntry[G]]) { def successors(): Set[AbstractState[G]] = processes.flatMap(p => p._1.get_next(p._2, this)).toSet def with_process_at(process: AbstractProcess[G], position: CFGEntry[G]): AbstractState[G] = AbstractState(valuations, processes + (process -> position)) - def with_valuation(variable: Expr[G], value: Int): AbstractState[G] = variable_from_expr(variable) match { + def with_valuation(variable: Expr[G], value: UncertainValue): AbstractState[G] = variable_from_expr(variable) match { case Some(concrete_variable) => AbstractState(valuations + (concrete_variable -> value), processes) case None => this } - def resolve_integer_expression(expr: Expr[G]): UncertainIntegerValue = expr match { + def resolve_expression(expr: Expr[G]): UncertainValue = expr.t match { + case _: IntType[_] => resolve_integer_expression(expr) + case _: TBool[_] => resolve_boolean_expression(expr) + case _ => throw new IllegalArgumentException(s"Type ${expr.t.toInlineString} is not supported") + } + + private def resolve_integer_expression(expr: Expr[G]): UncertainIntegerValue = expr match { case CIntegerValue(value) => UncertainIntegerValue.single(value.intValue) case IntegerValue(value) => UncertainIntegerValue.single(value.intValue) - case Local(ref) => ??? - case DerefHeapVariable(ref) => ??? - case Deref(obj, ref) => ??? - case DerefPointer(pointer) => ??? case SizeOf(tname) => UncertainIntegerValue.above(0) // TODO: Can we use more information about sizeof? case UMinus(arg) => -resolve_integer_expression(arg) case AmbiguousMult(left, right) => resolve_integer_expression(left) * resolve_integer_expression(right) @@ -56,20 +58,16 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int], processes if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_integer_expression(iff)) value } - case AmbiguousSubscript(collection, index) => ??? - case SeqSubscript(seq, index) => ??? - case ArraySubscript(arr, index) => ??? - case PointerSubscript(pointer, index) => ??? + case Local(_) | DerefHeapVariable(_) | Deref(_, _) | DerefPointer(_) | AmbiguousSubscript(_, _) | SeqSubscript(_, _) | ArraySubscript(_, _) | PointerSubscript(_, _) => variable_from_expr(expr) match { + case Some(v) => valuations(v).asInstanceOf[UncertainIntegerValue] + case None => UncertainIntegerValue.uncertain() + } case Length(arr) => UncertainIntegerValue.above(0) // TODO: Use contextual information from the global invariant case Size(obj) => UncertainIntegerValue.above(0) // here as well } def resolve_boolean_expression(expr: Expr[G]): UncertainBooleanValue = expr match { case BooleanValue(value) => UncertainBooleanValue(value, !value) - case Local(ref) => ??? - case DerefHeapVariable(ref) => ??? - case Deref(obj, ref) => ??? - case DerefPointer(pointer) => ??? case Not(arg) => !resolve_boolean_expression(arg) case AmbiguousOr(left, right) => resolve_boolean_expression(left) || resolve_boolean_expression(right) case And(left, right) => resolve_boolean_expression(left) && resolve_boolean_expression(right) @@ -92,17 +90,14 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], Int], processes if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_boolean_expression(iff)) value } - case AmbiguousSubscript(collection, index) => ??? - case SeqSubscript(seq, index) => ??? - case ArraySubscript(arr, index) => ??? - case PointerSubscript(pointer, index) => ??? + case Local(_) | DerefHeapVariable(_) | Deref(_, _) | DerefPointer(_) | AmbiguousSubscript(_, _) | SeqSubscript(_, _) | ArraySubscript(_, _) | PointerSubscript(_, _) => variable_from_expr(expr) match { + case Some(v) => valuations(v).asInstanceOf[UncertainBooleanValue] + case None => UncertainBooleanValue(can_be_true = true, can_be_false = true) + } } private def variable_from_expr(variable: Expr[G]): Option[ConcreteVariable[G]] = ??? private def neutral_element: Expr[G] = BooleanValue(value = true)(Origin(Seq(LabelContext("neutral element for and")))) - def to_expression(): Expr[G] = { - valuations.map(v => Eq(v._1.to_expression(), IntegerValue(v._2)(Origin(Seq(LabelContext("int value")))))(Origin(Seq(LabelContext("valuation"))))) - .fold(neutral_element)((e1, e2) => And(e1, e2)(e1.o)) - } + def to_expression(): Expr[G] = valuations.map(v => v._2.to_expression(v._1.to_expression())).fold(neutral_element)((e1, e2) => And(e1, e2)(e1.o)) } diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index c00f457c31..3c2d8195cf 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -23,12 +23,12 @@ case class RASIGenerator[G]() { current_branches.addOne(initial_state) while (current_branches.nonEmpty) { - val curr = current_branches.head + val curr: AbstractState[G] = current_branches.head current_branches.remove(curr) curr.successors().foreach(s => if (!found_states.contains(s)) {found_states.addOne(curr); current_branches.addOne(curr)}) } } - private def get_initial_values(vars: Set[ConcreteVariable[G]]): Map[ConcreteVariable[G], Int] = ??? + private def get_initial_values(vars: Set[ConcreteVariable[G]]): Map[ConcreteVariable[G], UncertainValue] = ??? } diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index a433e2f1ea..86f64bf7b8 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -1,8 +1,11 @@ package vct.rewrite.rasi +import vct.col.ast.{BooleanValue, Expr, Not} + trait UncertainValue { def can_be_equal(other: UncertainValue): Boolean def can_be_unequal(other: UncertainValue): Boolean + def to_expression[G](variable: Expr[G]): Expr[G] } case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) extends UncertainValue { @@ -16,6 +19,13 @@ case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) ex case _ => true } + override def to_expression[G](variable: Expr[G]): Expr[G] = { + if (can_be_true && can_be_false) BooleanValue(value = true)(variable.o) + else if (can_be_true) variable + else if (can_be_false) Not(variable)(variable.o) + else BooleanValue(value = false)(variable.o) + } + def try_to_resolve(): Option[Boolean] = { if (can_be_true && !can_be_false) Some(true) else if (can_be_false && !can_be_true) Some(false) @@ -25,10 +35,8 @@ case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) ex def union(other: UncertainBooleanValue): UncertainBooleanValue = UncertainBooleanValue(can_be_true || other.can_be_true, can_be_false || other.can_be_false) - def &(other: UncertainBooleanValue): UncertainBooleanValue = this && other def &&(other: UncertainBooleanValue): UncertainBooleanValue = UncertainBooleanValue(can_be_true && other.can_be_true, can_be_false || other.can_be_false) - def |(other: UncertainBooleanValue): UncertainBooleanValue = this || other def ||(other: UncertainBooleanValue): UncertainBooleanValue = UncertainBooleanValue(can_be_true || other.can_be_true, can_be_false && other.can_be_false) def unary_! : UncertainBooleanValue = @@ -53,6 +61,8 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { case _ => true } + override def to_expression[G](variable: Expr[G]): Expr[G] = value.to_expression(variable) + def try_to_resolve(): Option[Int] = value.try_to_resolve() def union(other: UncertainIntegerValue): UncertainIntegerValue = @@ -71,20 +81,13 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { def >(other: UncertainIntegerValue): UncertainBooleanValue = !(this <= other) def <(other: UncertainIntegerValue): UncertainBooleanValue = !(this >= other) - def unary_- : UncertainIntegerValue = - UncertainIntegerValue(-value) - def +(other: UncertainIntegerValue): UncertainIntegerValue = - UncertainIntegerValue(value + other.value) - def -(other: UncertainIntegerValue): UncertainIntegerValue = - UncertainIntegerValue(value - other.value) - def *(other: UncertainIntegerValue): UncertainIntegerValue = - UncertainIntegerValue(value * other.value) - def /(other: UncertainIntegerValue): UncertainIntegerValue = - UncertainIntegerValue(value / other.value) - def %(other: UncertainIntegerValue): UncertainIntegerValue = - UncertainIntegerValue(value % other.value) - def pow(other: UncertainIntegerValue): UncertainIntegerValue = - UncertainIntegerValue(value.pow(other.value)) + def unary_- : UncertainIntegerValue = UncertainIntegerValue(-value) + def +(other: UncertainIntegerValue): UncertainIntegerValue = UncertainIntegerValue(value + other.value) + def -(other: UncertainIntegerValue): UncertainIntegerValue = UncertainIntegerValue(value - other.value) + def *(other: UncertainIntegerValue): UncertainIntegerValue = UncertainIntegerValue(value * other.value) + def /(other: UncertainIntegerValue): UncertainIntegerValue = UncertainIntegerValue.uncertain() // TODO: UncertainIntegerValue(value / other.value) + def %(other: UncertainIntegerValue): UncertainIntegerValue = UncertainIntegerValue.uncertain() // TODO: UncertainIntegerValue(value % other.value) + def pow(other: UncertainIntegerValue): UncertainIntegerValue = UncertainIntegerValue.uncertain() // TODO: UncertainIntegerValue(value.pow(other.value)) } case object UncertainIntegerValue { def empty(): UncertainIntegerValue = UncertainIntegerValue(EmptyInterval) From a23673be31f00bce1e7a15246dfa6e6bed8313bb Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 21 Feb 2024 14:27:39 +0100 Subject: [PATCH 38/85] Implemented some abstract process logic; added edges to RASI; improved ConcreteVariable recognition --- .../vct/rewrite/rasi/AbstractProcess.scala | 111 ++++++++---------- .../vct/rewrite/rasi/AbstractState.scala | 61 ++++++---- .../vct/rewrite/rasi/ConcreteVariable.scala | 45 ++++--- .../vct/rewrite/rasi/RASIGenerator.scala | 29 +++-- .../vct/rewrite/rasi/UncertainValue.scala | 4 + 5 files changed, 132 insertions(+), 118 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index f272f3fb7d..843cd58015 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -3,73 +3,54 @@ package vct.rewrite.rasi import vct.col.ast._ import vct.rewrite.cfg.{CFGEdge, CFGEntry, CFGNode, CFGTerminal} +import scala.collection.mutable + case class AbstractProcess[G](name: String) { def get_next(node: CFGEntry[G], state: AbstractState[G]): Set[AbstractState[G]] = node match { - case CFGTerminal() => Set() - case CFGNode(n, succ) => succ.filter(e => e.condition.isEmpty || state.resolve_boolean_expression(e.condition.get).can_be_true) - .map(e => process_cfg_edge(e, n, state)).toSet + case CFGTerminal() => Set(state.without_process(this)) + case CFGNode(n, succ) => n match { + // Assign statements change the state of variables directly (if they appear in the valuation) + case Assign(target, value) => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(target, state.resolve_expression(value)))) + case Havoc(loc) => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(loc, loc.t match { + case _: IntType[_] => UncertainIntegerValue.uncertain() + case _: TBool[_] => UncertainBooleanValue.uncertain() + }))) + // TODO: Consider state changes by specifications + case Assume(assn) => viable_edges(succ, state).map(e => take_edge(e, state)) + case Inhale(res) => viable_edges(succ, state).map(e => take_edge(e, state)) + case InvokeProcedure(ref, _, _, _, _, _) => viable_edges(succ, state).map(e => take_edge(e, state)) + case InvokeConstructor(ref, _, _, _, _, _, _) => viable_edges(succ, state).map(e => take_edge(e, state)) + case InvokeMethod(_, ref, _, _, _, _, _) => viable_edges(succ, state).map(e => take_edge(e, state)) + // TODO: What do wait and notify do? + case Wait(obj) => viable_edges(succ, state).map(e => take_edge(e, state)) + case Notify(obj) => viable_edges(succ, state).map(e => take_edge(e, state)) + // Lock and Unlock manipulate the global lock and are potentially blocking TODO: Differentiate between locks! + case Lock(_) => state.lock match { + case Some(proc) => if (!proc.equals(this)) Set(state) + else throw new IllegalStateException("Trying to lock already acquired lock") + case None => viable_edges(succ, state).map(e => take_edge(e, state).locked_by(this)) + } + case Unlock(_) => state.lock match { + case Some(proc) => if (proc.equals(this)) viable_edges(succ, state).map(e => take_edge(e, state).unlocked()) + else throw new IllegalStateException("Trying to unlock lock owned by other process") + case None => throw new IllegalStateException("Trying to unlock unlocked lock") + } + // When forking a new process, make the step of creating it simultaneously to the normal steps TODO: consider join + case Fork(obj) => + val edges: (Set[CFGEdge[G]], Set[CFGEdge[G]]) = viable_edges(succ, state).partition(e => e.target match { + case CFGTerminal() => false + case CFGNode(t, _) => t.equals(obj.t.asClass.get.cls.decl.declarations.collect{ case r: RunMethod[G] => r }.head.body.get) + }) + edges._2.map(e => take_edge(e, state.with_process_at(AbstractProcess(s"${name}_${obj.toInlineString}"), edges._1.head.target))) + case Join(obj) => viable_edges(succ, state).map(e => take_edge(e, state)) + // Everything else does not affect the state, so simply go to the next step + case _ => viable_edges(succ, state).map(e => take_edge(e, state)) + } } - private def process_cfg_edge(edge: CFGEdge[G], ast_node: Node[G], state: AbstractState[G]): AbstractState[G] = ast_node match { - // TODO: Implement! - case Assign(target, value) => state.with_valuation(target, state.resolve_expression(value)) - case Exhale(res) => ??? - case Assert(res) => ??? - case Refute(assn) => ??? - case Inhale(res) => ??? - case Assume(assn) => ??? - case Instantiate(_, out) => ??? - case Wait(_) => ??? - case Notify(_) => ??? - case Fork(obj) => ??? // TODO: This needs to be decided in the outer method... - case Join(_) => ??? - case Lock(_) => ??? - case Unlock(_) => ??? - case Commit(_) => ??? - case Fold(res) => ??? - case Unfold(res) => ??? - case WandApply(res) => ??? - case Havoc(_) => ??? - case FramedProof(_, _, _) => ??? - case Extract(_) => ??? - case Eval(_) => ??? - case Return(result) => ??? - case Throw(obj) => ??? - case Break(label) => ??? - case Continue(label) => ??? - case InvokeProcedure(_, _, _, _, _, _) => ??? - case InvokeConstructor(_, _, _, _, _, _, _) => ??? - case InvokeMethod(_, _, _, _, _, _, _) => ??? - case Block(_) => ??? - case Scope(_, _) => ??? - case Branch(_) => ??? - case IndetBranch(branches) => ??? - case Switch(expr, body) => ??? - case Loop(_, _, _, _, _) => ??? - case RangedFor(_, _, _) => ??? - case TryCatchFinally(_, _, _) => ??? - case Synchronized(_, _) => ??? - case ParInvariant(_, _, _) => ??? - case ParAtomic(_, _) => ??? - case ParBarrier(_, _, _, _, _) => ??? - case ParStatement(_) => ??? - case VecBlock(_, _, _, _) => ??? - case WandPackage(_, _) => ??? - case ModelDo(_, _, _, _, _) => ??? - case CDeclarationStatement(_) => ??? - case CGoto(label) => ??? - case CPPDeclarationStatement(_) => ??? - case CPPLifetimeScope(_) => ??? - case JavaLocalDeclarationStatement(_) => ??? - case SilverNewRef(_, _) => ??? - case SilverFieldAssign(_, _, value) => ??? - case SilverLocalAssign(_, value) => ??? - case PVLCommunicate(_, _) => ??? - case PVLSeqAssign(_, _, value) => ??? - case Communicate(_, _) => ??? - case SeqAssign(_, _, value) => ??? - case UnresolvedSeqBranch(branches) => ??? - case UnresolvedSeqLoop(_, _, _) => ??? - case _ => state.with_process_at(this, edge.target) - } + private def viable_edges(edges: mutable.Set[CFGEdge[G]], state: AbstractState[G]): Set[CFGEdge[G]] = + edges.filter(e => e.condition.isEmpty || state.resolve_boolean_expression(e.condition.get).can_be_true).toSet + + private def take_edge(edge: CFGEdge[G], state: AbstractState[G]): AbstractState[G] = + state.with_process_at(this, edge.target) } diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index fc65f4a00e..2e2dc18ec2 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -1,30 +1,36 @@ package vct.rewrite.rasi import vct.col.ast._ -import vct.col.origin.{LabelContext, Origin} import vct.rewrite.cfg.CFGEntry import scala.collection.immutable.HashMap -case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue], processes: HashMap[AbstractProcess[G], CFGEntry[G]]) { +case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue], processes: HashMap[AbstractProcess[G], CFGEntry[G]], lock: Option[AbstractProcess[G]]) { def successors(): Set[AbstractState[G]] = processes.flatMap(p => p._1.get_next(p._2, this)).toSet def with_process_at(process: AbstractProcess[G], position: CFGEntry[G]): AbstractState[G] = - AbstractState(valuations, processes + (process -> position)) + AbstractState(valuations, processes + (process -> position), lock) + + def without_process(process: AbstractProcess[G]): AbstractState[G] = + AbstractState(valuations, processes.removed(process), lock) def with_valuation(variable: Expr[G], value: UncertainValue): AbstractState[G] = variable_from_expr(variable) match { - case Some(concrete_variable) => AbstractState(valuations + (concrete_variable -> value), processes) + case Some(concrete_variable) => AbstractState(valuations + (concrete_variable -> value), processes, lock) case None => this } + def locked_by(process: AbstractProcess[G]): AbstractState[G] = AbstractState(valuations, processes, Some(process)) + + def unlocked(): AbstractState[G] = AbstractState(valuations, processes, None) + def resolve_expression(expr: Expr[G]): UncertainValue = expr.t match { case _: IntType[_] => resolve_integer_expression(expr) case _: TBool[_] => resolve_boolean_expression(expr) case _ => throw new IllegalArgumentException(s"Type ${expr.t.toInlineString} is not supported") } - private def resolve_integer_expression(expr: Expr[G]): UncertainIntegerValue = expr match { + def resolve_integer_expression(expr: Expr[G]): UncertainIntegerValue = expr match { case CIntegerValue(value) => UncertainIntegerValue.single(value.intValue) case IntegerValue(value) => UncertainIntegerValue.single(value.intValue) case SizeOf(tname) => UncertainIntegerValue.above(0) // TODO: Can we use more information about sizeof? @@ -38,20 +44,20 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case Mult(left, right) => resolve_integer_expression(left) * resolve_integer_expression(right) case FloorDiv(left, right) => resolve_integer_expression(left) / resolve_integer_expression(right) case Mod(left, right) => resolve_integer_expression(left) % resolve_integer_expression(right) - // TODO: Support bit operations - case BitNot(arg) => ??? // ~resolve_integer_expression(arg) - case AmbiguousComputationalOr(left, right) => ??? // resolve_integer_expression(left) | resolve_integer_expression(right) - case AmbiguousComputationalXor(left, right) => ??? // resolve_integer_expression(left) ^ resolve_integer_expression(right) - case AmbiguousComputationalAnd(left, right) => ??? // resolve_integer_expression(left) & resolve_integer_expression(right) - case ComputationalOr(left, right) => ??? // resolve_integer_expression(left) | resolve_integer_expression(right) - case ComputationalXor(left, right) => ??? // resolve_integer_expression(left) ^ resolve_integer_expression(right) - case ComputationalAnd(left, right) => ??? // resolve_integer_expression(left) & resolve_integer_expression(right) - case BitAnd(left, right) => ??? // resolve_integer_expression(left) & resolve_integer_expression(right) - case BitOr(left, right) => ??? // resolve_integer_expression(left) | resolve_integer_expression(right) - case BitXor(left, right) => ??? // resolve_integer_expression(left) ^ resolve_integer_expression(right) - case BitShl(left, right) => ??? // resolve_integer_expression(left) << resolve_integer_expression(right) - case BitShr(left, right) => ??? // resolve_integer_expression(left) >> resolve_integer_expression(right) - case BitUShr(left, right) => ??? // resolve_integer_expression(left) >>> resolve_integer_expression(right) + // Bit operations destroy any knowledge of integer state TODO: Support bit operations + case BitNot(_) => UncertainIntegerValue.uncertain() + case AmbiguousComputationalOr(_, _) => UncertainIntegerValue.uncertain() + case AmbiguousComputationalXor(_, _) => UncertainIntegerValue.uncertain() + case AmbiguousComputationalAnd(_, _) => UncertainIntegerValue.uncertain() + case ComputationalOr(_, _) => UncertainIntegerValue.uncertain() + case ComputationalXor(_, _) => UncertainIntegerValue.uncertain() + case ComputationalAnd(_, _) => UncertainIntegerValue.uncertain() + case BitAnd(_, _) => UncertainIntegerValue.uncertain() + case BitOr(_, _) => UncertainIntegerValue.uncertain() + case BitXor(_, _) => UncertainIntegerValue.uncertain() + case BitShl(_, _) => UncertainIntegerValue.uncertain() + case BitShr(_, _) => UncertainIntegerValue.uncertain() + case BitUShr(_, _) => UncertainIntegerValue.uncertain() case Select(cond, ift, iff) => { var value: UncertainIntegerValue = UncertainIntegerValue.empty() if (resolve_boolean_expression(cond).can_be_true) value = value.union(resolve_integer_expression(ift)) @@ -64,10 +70,12 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] } case Length(arr) => UncertainIntegerValue.above(0) // TODO: Use contextual information from the global invariant case Size(obj) => UncertainIntegerValue.above(0) // here as well + case InvokeProcedure(_, _, _, _, _, _) => UncertainIntegerValue.uncertain() // TODO: return value from procedure/method? + case InvokeMethod(_, _, _, _, _, _, _) => UncertainIntegerValue.uncertain() } def resolve_boolean_expression(expr: Expr[G]): UncertainBooleanValue = expr match { - case BooleanValue(value) => UncertainBooleanValue(value, !value) + case BooleanValue(value) => UncertainBooleanValue.from(value) case Not(arg) => !resolve_boolean_expression(arg) case AmbiguousOr(left, right) => resolve_boolean_expression(left) || resolve_boolean_expression(right) case And(left, right) => resolve_boolean_expression(left) && resolve_boolean_expression(right) @@ -83,7 +91,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case Less(left, right) => resolve_integer_expression(left) < resolve_integer_expression(right) case GreaterEq(left, right) => resolve_integer_expression(left) >= resolve_integer_expression(right) case LessEq(left, right) => resolve_integer_expression(left) <= resolve_integer_expression(right) - case c: SetComparison[G] => ??? + case c: SetComparison[G] => UncertainBooleanValue.uncertain() // TODO: Implement? case Select(cond, ift, iff) => { var value: UncertainBooleanValue = UncertainBooleanValue(can_be_true = false, can_be_false = false) if (resolve_boolean_expression(cond).can_be_true) value = value.union(resolve_boolean_expression(ift)) @@ -92,12 +100,15 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] } case Local(_) | DerefHeapVariable(_) | Deref(_, _) | DerefPointer(_) | AmbiguousSubscript(_, _) | SeqSubscript(_, _) | ArraySubscript(_, _) | PointerSubscript(_, _) => variable_from_expr(expr) match { case Some(v) => valuations(v).asInstanceOf[UncertainBooleanValue] - case None => UncertainBooleanValue(can_be_true = true, can_be_false = true) + case None => UncertainBooleanValue.uncertain() } + case InvokeProcedure(_, _, _, _, _, _) => UncertainBooleanValue.uncertain() // TODO: return value from procedure/method? + case InvokeMethod(_, _, _, _, _, _, _) => UncertainBooleanValue.uncertain() } - private def variable_from_expr(variable: Expr[G]): Option[ConcreteVariable[G]] = ??? + private def variable_from_expr(variable: Expr[G]): Option[ConcreteVariable[G]] = { + valuations.keys.collectFirst{ case c: ConcreteVariable[G] if c.is(variable, this) => c } + } - private def neutral_element: Expr[G] = BooleanValue(value = true)(Origin(Seq(LabelContext("neutral element for and")))) - def to_expression(): Expr[G] = valuations.map(v => v._2.to_expression(v._1.to_expression())).fold(neutral_element)((e1, e2) => And(e1, e2)(e1.o)) + def to_expression: Expr[G] = valuations.map(v => v._2.to_expression(v._1.to_expression)).reduce((e1, e2) => And(e1, e2)(e1.o)) } diff --git a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala index 5b9a49f598..ec644885d8 100644 --- a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala +++ b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala @@ -3,25 +3,38 @@ package vct.rewrite.rasi import vct.col.ast._ trait ConcreteVariable[G] { - def is(expr: Expr[G]): Boolean - def extract_from_expression(expr: Expr[G]): Field[G] = expr match { - // TODO: Need to also consider the object! - case Deref(obj, ref) => ref.decl + def is(expr: Expr[G], state: AbstractState[G]): Boolean + def to_expression: Expr[G] + def t: Type[G] + def field_equals(expr: Expr[G], field: InstanceField[G]): Boolean = expr match { + // TODO: Support other types of expressions? Take object into account? + case Deref(_, f) => f.decl.equals(field) + case _ => false } - def to_expression(): Expr[G] } -case class FieldVariable[G](field: Field[G]) extends ConcreteVariable[G] { - override def is(expr: Expr[G]): Boolean = field.equals(extract_from_expression(expr)) - override def to_expression(): Expr[G] = ??? +case class FieldVariable[G](field: InstanceField[G]) extends ConcreteVariable[G] { + override def is(expr: Expr[G], state: AbstractState[G]): Boolean = field_equals(expr, field) + override def to_expression: Expr[G] = Deref[G](AmbiguousThis()(field.o), field.ref)(field.o)(field.o) + override def t: Type[G] = field.t } -case class IndexedVariable[G](field: Field[G], i: Int) extends ConcreteVariable[G] { - override def is(expr: Expr[G]): Boolean = ??? /*expr match { - case AmbiguousSubscript(collection, index) => field.equals(extract_from_expression(collection)) && i == Utils.resolve_integer_expression(index) - case SeqSubscript(seq, index) => field.equals(extract_from_expression(seq)) && i == Utils.resolve_integer_expression(index) - case ArraySubscript(arr, index) => field.equals(extract_from_expression(arr)) && i == Utils.resolve_integer_expression(index) - case PointerSubscript(pointer, index) => field.equals(extract_from_expression(pointer)) && i == Utils.resolve_integer_expression(index) - }*/ - override def to_expression(): Expr[G] = ??? +case class IndexedVariable[G](field: InstanceField[G], i: Int) extends ConcreteVariable[G] { + override def is(expr: Expr[G], state: AbstractState[G]): Boolean = expr match { + case AmbiguousSubscript(collection, index) => field_equals(collection, field) && i == state.resolve_integer_expression(index).try_to_resolve().getOrElse(-1) + case SeqSubscript(seq, index) => field_equals(seq, field) && i == state.resolve_integer_expression(index).try_to_resolve().getOrElse(-1) + case ArraySubscript(arr, index) => field_equals(arr, field) && i == state.resolve_integer_expression(index).try_to_resolve().getOrElse(-1) + case PointerSubscript(pointer, index) => field_equals(pointer, field) && i == state.resolve_integer_expression(index).try_to_resolve().getOrElse(-1) + case _ => false + } + override def to_expression: Expr[G] = field.t match { + case TSeq(_) => SeqSubscript(Deref[G](AmbiguousThis()(field.o), field.ref)(field.o)(field.o), IntegerValue(i)(field.o))(field.o)(field.o) + case TArray(_) => ArraySubscript(Deref[G](AmbiguousThis()(field.o), field.ref)(field.o)(field.o), IntegerValue(i)(field.o))(field.o)(field.o) + case TPointer(_) => PointerSubscript(Deref[G](AmbiguousThis()(field.o), field.ref)(field.o)(field.o), IntegerValue(i)(field.o))(field.o)(field.o) + } + override def t: Type[G] = field.t match { + case TSeq(element) => element + case TArray(element) => element + case TPointer(element) => element + } } diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index 3c2d8195cf..390c2ed436 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -1,34 +1,39 @@ package vct.rewrite.rasi -import vct.col.ast.{BooleanValue, Expr, Or} -import vct.col.origin.{LabelContext, Origin} +import vct.col.ast.{Expr, IntType, Or, TBool} import vct.rewrite.cfg.CFGNode import scala.collection.immutable.HashMap import scala.collection.mutable case class RASIGenerator[G]() { - private val found_states: mutable.Set[AbstractState[G]] = mutable.LinkedHashSet() - private val current_branches: mutable.Set[AbstractState[G]] = mutable.LinkedHashSet() + private val found_states: mutable.ArrayBuffer[AbstractState[G]] = mutable.ArrayBuffer() + private val found_edges: mutable.ArrayBuffer[(AbstractState[G], AbstractState[G])] = mutable.ArrayBuffer() + private val current_branches: mutable.ArrayBuffer[AbstractState[G]] = mutable.ArrayBuffer() - private def neutral_element: Expr[G] = BooleanValue(value = false)(Origin(Seq(LabelContext("neutral element of or")))) def generate_rasi(node: CFGNode[G], vars: Set[ConcreteVariable[G]]): Expr[G] = { explore(node, vars) - found_states.map(a => a.to_expression()).fold(neutral_element)((e1, e2) => Or(e1, e2)(e1.o)) + found_states.distinctBy(s => s.valuations).map(s => s.to_expression).reduce((e1, e2) => Or(e1, e2)(e1.o)) } private def explore(node: CFGNode[G], vars: Set[ConcreteVariable[G]]): Unit = { - val initial_state = AbstractState(get_initial_values(vars), HashMap((AbstractProcess[G]("main"), node))) - found_states.addOne(initial_state) - current_branches.addOne(initial_state) + val initial_state = AbstractState(get_initial_values(vars), HashMap((AbstractProcess[G]("main"), node)), None) + found_states += initial_state + current_branches += initial_state while (current_branches.nonEmpty) { val curr: AbstractState[G] = current_branches.head - current_branches.remove(curr) + current_branches -= curr - curr.successors().foreach(s => if (!found_states.contains(s)) {found_states.addOne(curr); current_branches.addOne(curr)}) + val successors: Set[AbstractState[G]] = curr.successors() + found_edges.addAll(successors.map(s => (curr, s))) + successors.foreach(s => if (!found_states.contains(s)) {found_states += curr; current_branches += curr}) } } - private def get_initial_values(vars: Set[ConcreteVariable[G]]): Map[ConcreteVariable[G], UncertainValue] = ??? + private def get_initial_values(vars: Set[ConcreteVariable[G]]): Map[ConcreteVariable[G], UncertainValue] = + Map.from(vars.map(v => (v, v.t match { + case _: IntType[_] => UncertainIntegerValue.uncertain() + case _: TBool[_] => UncertainIntegerValue.uncertain() + }))) } diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index 86f64bf7b8..3cc9c0e74b 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -49,6 +49,10 @@ case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) ex can_be_true && other.can_be_false || can_be_false && other.can_be_true) def !=(other: UncertainBooleanValue): UncertainBooleanValue = this ^ other } +case object UncertainBooleanValue { + def from(bool: Boolean): UncertainBooleanValue = UncertainBooleanValue(bool, !bool) + def uncertain(): UncertainBooleanValue = UncertainBooleanValue(can_be_true = true, can_be_false = true) +} case class UncertainIntegerValue(value: Interval) extends UncertainValue { override def can_be_equal(other: UncertainValue): Boolean = other match { From c215e4ec90f8ac85179c4db8a719c0570c160597 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 21 Feb 2024 14:38:18 +0100 Subject: [PATCH 39/85] Added option to compare booleans in expressions --- .../vct/rewrite/rasi/AbstractState.scala | 12 +++++----- .../vct/rewrite/rasi/UncertainValue.scala | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 2e2dc18ec2..79252fd053 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -70,8 +70,8 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] } case Length(arr) => UncertainIntegerValue.above(0) // TODO: Use contextual information from the global invariant case Size(obj) => UncertainIntegerValue.above(0) // here as well - case InvokeProcedure(_, _, _, _, _, _) => UncertainIntegerValue.uncertain() // TODO: return value from procedure/method? - case InvokeMethod(_, _, _, _, _, _, _) => UncertainIntegerValue.uncertain() + case ProcedureInvocation(_, _, _, _, _, _) => UncertainIntegerValue.uncertain() // TODO: return value from procedure/method? + case MethodInvocation(_, _, _, _, _, _, _) => UncertainIntegerValue.uncertain() } def resolve_boolean_expression(expr: Expr[G]): UncertainBooleanValue = expr match { @@ -81,8 +81,8 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case And(left, right) => resolve_boolean_expression(left) && resolve_boolean_expression(right) case Or(left, right) => resolve_boolean_expression(left) || resolve_boolean_expression(right) case Implies(left, right) => (!resolve_boolean_expression(left)) || resolve_boolean_expression(right) - case Eq(left, right) => resolve_integer_expression(left) == resolve_integer_expression(right) - case Neq(left, right) => resolve_integer_expression(left) != resolve_integer_expression(right) + case Eq(left, right) => resolve_expression(left) == resolve_expression(right) + case Neq(left, right) => resolve_expression(left) != resolve_expression(right) case AmbiguousGreater(left, right) => resolve_integer_expression(left) > resolve_integer_expression(right) case AmbiguousLess(left, right) => resolve_integer_expression(left) < resolve_integer_expression(right) case AmbiguousGreaterEq(left, right) => resolve_integer_expression(left) >= resolve_integer_expression(right) @@ -102,8 +102,8 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case Some(v) => valuations(v).asInstanceOf[UncertainBooleanValue] case None => UncertainBooleanValue.uncertain() } - case InvokeProcedure(_, _, _, _, _, _) => UncertainBooleanValue.uncertain() // TODO: return value from procedure/method? - case InvokeMethod(_, _, _, _, _, _, _) => UncertainBooleanValue.uncertain() + case ProcedureInvocation(_, _, _, _, _, _) => UncertainBooleanValue.uncertain() // TODO: return value from procedure/method? + case MethodInvocation(_, _, _, _, _, _, _) => UncertainBooleanValue.uncertain() } private def variable_from_expr(variable: Expr[G]): Option[ConcreteVariable[G]] = { diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index 3cc9c0e74b..1dd1ce36c8 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -6,6 +6,8 @@ trait UncertainValue { def can_be_equal(other: UncertainValue): Boolean def can_be_unequal(other: UncertainValue): Boolean def to_expression[G](variable: Expr[G]): Expr[G] + def ==(other: UncertainValue): UncertainBooleanValue + def !=(other: UncertainValue): UncertainBooleanValue } case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) extends UncertainValue { @@ -26,6 +28,16 @@ case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) ex else BooleanValue(value = false)(variable.o) } + override def ==(other: UncertainValue): UncertainBooleanValue = other match { + case b: UncertainBooleanValue => this == b + case _ => UncertainBooleanValue.from(false) + } + + override def !=(other: UncertainValue): UncertainBooleanValue = other match { + case b: UncertainBooleanValue => this != b + case _ => UncertainBooleanValue.from(true) + } + def try_to_resolve(): Option[Boolean] = { if (can_be_true && !can_be_false) Some(true) else if (can_be_false && !can_be_true) Some(false) @@ -67,6 +79,16 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { override def to_expression[G](variable: Expr[G]): Expr[G] = value.to_expression(variable) + override def ==(other: UncertainValue): UncertainBooleanValue = other match { + case i: UncertainIntegerValue => this == i + case _ => UncertainBooleanValue.from(false) + } + + override def !=(other: UncertainValue): UncertainBooleanValue = other match { + case i: UncertainIntegerValue => this != i + case _ => UncertainBooleanValue.from(true) + } + def try_to_resolve(): Option[Int] = value.try_to_resolve() def union(other: UncertainIntegerValue): UncertainIntegerValue = From c02cb2474aea1519ca5f741b578cc15b8bd28acf Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 21 Feb 2024 15:26:32 +0100 Subject: [PATCH 40/85] Added a printer for the RASI state space, but not connected to the frontend yet --- .../vct/rewrite/rasi/RASIGenerator.scala | 17 ++++++++++++++- src/rewrite/vct/rewrite/rasi/Utils.scala | 21 +++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index 390c2ed436..d0f7299a20 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -3,6 +3,7 @@ package vct.rewrite.rasi import vct.col.ast.{Expr, IntType, Or, TBool} import vct.rewrite.cfg.CFGNode +import java.nio.file.Path import scala.collection.immutable.HashMap import scala.collection.mutable @@ -16,6 +17,12 @@ case class RASIGenerator[G]() { found_states.distinctBy(s => s.valuations).map(s => s.to_expression).reduce((e1, e2) => Or(e1, e2)(e1.o)) } + def print_state_space(node: CFGNode[G], vars: Set[ConcreteVariable[G]], out_path: Path): Unit = { + explore(node, vars) + val (ns, es) = reduce_redundant_states() + Utils.print(ns, es, out_path) + } + private def explore(node: CFGNode[G], vars: Set[ConcreteVariable[G]]): Unit = { val initial_state = AbstractState(get_initial_values(vars), HashMap((AbstractProcess[G]("main"), node)), None) found_states += initial_state @@ -31,9 +38,17 @@ case class RASIGenerator[G]() { } } - private def get_initial_values(vars: Set[ConcreteVariable[G]]): Map[ConcreteVariable[G], UncertainValue] = + private def get_initial_values(vars: Set[ConcreteVariable[G]]): Map[ConcreteVariable[G], UncertainValue] = { + // TODO: Should this be uncertain or should it be defined (e.g. 0/false)? Map.from(vars.map(v => (v, v.t match { case _: IntType[_] => UncertainIntegerValue.uncertain() case _: TBool[_] => UncertainIntegerValue.uncertain() }))) + } + + private def reduce_redundant_states(): (Seq[AbstractState[G]], Seq[(AbstractState[G], AbstractState[G])]) = { + val state_groups: Map[AbstractState[G], AbstractState[G]] = found_states.groupBy(s => s.valuations).flatMap(t => t._2.map(s => (s, t._2.head))) + val edge_groups: Seq[(AbstractState[G], AbstractState[G])] = Seq.from(found_edges.map(t => (state_groups(t._1), state_groups(t._2))).distinct) + (state_groups.keySet.toSeq, edge_groups) + } } diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala index c421bc35ff..bc4287dab1 100644 --- a/src/rewrite/vct/rewrite/rasi/Utils.scala +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -1,9 +1,30 @@ package vct.rewrite.rasi +import hre.io.RWFile + +import java.io.Writer +import java.nio.file.Path + case object Utils { def abs_max(a: Int, b: Int): Int = Seq(-a, a, -b, b).max def prod_max(a1: Int, a2: Int, b1: Int, b2: Int): Int = Seq(a1 * b1, a1 * b2, a2 * b1, a2 * b2).max def prod_min(a1: Int, a2: Int, b1: Int, b2: Int): Int = Seq(a1 * b1, a1 * b2, a2 * b1, a2 * b2).min + + def print[G](states: Seq[AbstractState[G]], edges: Seq[(AbstractState[G], AbstractState[G])], out: Path): Unit = { + val node_names: Map[AbstractState[G], String] = Map.from(states.zipWithIndex.map(t => (t._1, s"n${t._2}"))) + RWFile(out.toFile).write(w => print_state_space(node_names, edges, w)) + } + + private def print_state_space[G](names: Map[AbstractState[G], String], edges: Seq[(AbstractState[G], AbstractState[G])], writer: Writer): Unit = { + names.foreach(t => writer.append(t._2) + .append(s"[label=${"\""}") + .append(t._1.to_expression.toInlineString) + .append(s"${"\""}];")) + edges.foreach(t => writer.append(names(t._1)) + .append(" -> ") + .append(names(t._2)) + .append(";")) + } } From cff50d298ebbf40387e8c8b6f9bd85c10853c9ff Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 21 Feb 2024 16:59:08 +0100 Subject: [PATCH 41/85] Improved support for uncertain integer values --- src/rewrite/vct/rewrite/rasi/Interval.scala | 24 ++++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/Interval.scala b/src/rewrite/vct/rewrite/rasi/Interval.scala index b75315d690..ea1d6e1695 100644 --- a/src/rewrite/vct/rewrite/rasi/Interval.scala +++ b/src/rewrite/vct/rewrite/rasi/Interval.scala @@ -77,9 +77,9 @@ case class MultiInterval(intervals: Set[Interval]) extends Interval { if (new_intervals.size > 1) MultiInterval(new_intervals) else new_intervals.head } - override def complement(): Interval = intervals.fold(UnboundedInterval)((i1, i2) => i1.intersection(i2.complement())) - override def below_max(): Interval = intervals.fold(EmptyInterval)((i1, i2) => i1.union(i2.below_max())) - override def above_min(): Interval = intervals.fold(EmptyInterval)((i1, i2) => i1.union(i2.above_min())) + override def complement(): Interval = intervals.foldLeft[Interval](UnboundedInterval)((i1, i2) => i1.intersection(i2.complement())) + override def below_max(): Interval = intervals.foldLeft[Interval](EmptyInterval)((i1, i2) => i1.union(i2.below_max())) + override def above_min(): Interval = intervals.foldLeft[Interval](EmptyInterval)((i1, i2) => i1.union(i2.above_min())) override def +(other: Interval): Interval = { val new_intervals = intervals.map(i => i + other) // It could be that all intervals are now connected into one @@ -113,16 +113,15 @@ case class MultiInterval(intervals: Set[Interval]) extends Interval { if (new_intervals.size > 1) MultiInterval(new_intervals) else new_intervals.head } + private def merge_intersecting(is: Set[Interval]): Set[Interval] = MultiInterval(is).flatten().complement().complement().sub_intervals() override def sub_intervals(): Set[Interval] = intervals.flatMap(i => i.sub_intervals()) + private def flatten(): MultiInterval = MultiInterval(this.sub_intervals()) override def try_to_resolve(): Option[Int] = { if (intervals.count(i => i != EmptyInterval) == 1) intervals.filter(i => i != EmptyInterval).head.try_to_resolve() else None } - override def to_expression[G](variable: Expr[G]): Expr[G] = + override def to_expression[G](variable: Expr[G]): Expr[G] = { intervals.map(i => i.to_expression(variable)).fold(BooleanValue[G](value = false)(variable.o))((e1, e2) => Or(e1, e2)(variable.o)) - - private def merge_intersecting(is: Set[Interval]): Set[Interval] = { - ??? } } @@ -189,7 +188,16 @@ case class BoundedInterval(lower: Int, upper: Int) extends Interval { case UpperBoundedInterval(up) => ??? case UnboundedInterval => BoundedInterval(-Utils.abs_max(lower, upper), Utils.abs_max(lower, upper)) } - override def %(other: Interval): Interval = ??? + override def %(other: Interval): Interval = other match { + case EmptyInterval => other + case MultiInterval(intervals) => ??? + case BoundedInterval(low, up) => ??? + case LowerBoundedInterval(low) => ??? + case UpperBoundedInterval(up) => ??? + case UnboundedInterval => + if (lower < 0) BoundedInterval(lower, scala.math.max(0, upper)) + else BoundedInterval(0, upper) + } override def unary_- : Interval = BoundedInterval(-upper, -lower) override def pow(other: Interval): Interval = other match { case EmptyInterval => other From deb912f3497b46afd0f603b413e9c62b1046d475 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 21 Feb 2024 16:59:31 +0100 Subject: [PATCH 42/85] Added infrastructure for decoding assumption-induced state changes --- .../vct/rewrite/rasi/AbstractProcess.scala | 27 ++++++++++++------- .../vct/rewrite/rasi/AbstractState.scala | 12 +++++++-- .../vct/rewrite/rasi/RASIGenerator.scala | 5 +--- .../vct/rewrite/rasi/UncertainValue.scala | 8 +++++- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index 843cd58015..fc378555a2 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -11,16 +11,23 @@ case class AbstractProcess[G](name: String) { case CFGNode(n, succ) => n match { // Assign statements change the state of variables directly (if they appear in the valuation) case Assign(target, value) => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(target, state.resolve_expression(value)))) - case Havoc(loc) => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(loc, loc.t match { - case _: IntType[_] => UncertainIntegerValue.uncertain() - case _: TBool[_] => UncertainBooleanValue.uncertain() - }))) - // TODO: Consider state changes by specifications - case Assume(assn) => viable_edges(succ, state).map(e => take_edge(e, state)) - case Inhale(res) => viable_edges(succ, state).map(e => take_edge(e, state)) - case InvokeProcedure(ref, _, _, _, _, _) => viable_edges(succ, state).map(e => take_edge(e, state)) - case InvokeConstructor(ref, _, _, _, _, _, _) => viable_edges(succ, state).map(e => take_edge(e, state)) - case InvokeMethod(_, ref, _, _, _, _, _) => viable_edges(succ, state).map(e => take_edge(e, state)) + case Havoc(loc) => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(loc, UncertainValue.uncertain_of(loc.t)))) + // Statements that induce assumptions about the state, such as assume, inhale, or a method's postcondition, might change the state implicitly + case Assume(assn) => viable_edges(succ, state).map(e => take_edge(e, state.with_assumption(assn))) + case Inhale(res) => viable_edges(succ, state).map(e => take_edge(e, state.with_assumption(res))) + // Abstract procedures, constructors and methods are defined by their postconditions + case InvokeProcedure(ref, args, _, _, _, _) => ref.decl.body match { + case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) + case None => viable_edges(succ, state).map(e => take_edge(e, state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))))) + } + case InvokeConstructor(ref, _, args, _, _, _, _) => ref.decl.body match { + case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) + case None => viable_edges(succ, state).map(e => take_edge(e, state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))))) + } + case InvokeMethod(_, ref, args, _, _, _, _) => ref.decl.body match { + case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) + case None => viable_edges(succ, state).map(e => take_edge(e, state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))))) + } // TODO: What do wait and notify do? case Wait(obj) => viable_edges(succ, state).map(e => take_edge(e, state)) case Notify(obj) => viable_edges(succ, state).map(e => take_edge(e, state)) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 79252fd053..17cfa7131e 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -1,6 +1,7 @@ package vct.rewrite.rasi import vct.col.ast._ +import vct.col.util.AstBuildHelpers import vct.rewrite.cfg.CFGEntry import scala.collection.immutable.HashMap @@ -15,14 +16,21 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] def without_process(process: AbstractProcess[G]): AbstractState[G] = AbstractState(valuations, processes.removed(process), lock) + def locked_by(process: AbstractProcess[G]): AbstractState[G] = AbstractState(valuations, processes, Some(process)) + + def unlocked(): AbstractState[G] = AbstractState(valuations, processes, None) + def with_valuation(variable: Expr[G], value: UncertainValue): AbstractState[G] = variable_from_expr(variable) match { case Some(concrete_variable) => AbstractState(valuations + (concrete_variable -> value), processes, lock) case None => this } - def locked_by(process: AbstractProcess[G]): AbstractState[G] = AbstractState(valuations, processes, Some(process)) + def with_assumption(assumption: Expr[G]): AbstractState[G] = ??? // TODO: Implement! - def unlocked(): AbstractState[G] = AbstractState(valuations, processes, None) + def with_postcondition(post: AccountedPredicate[G], args: Map[Variable[G], Expr[G]]): AbstractState[G] = + with_assumption(unify_expression(AstBuildHelpers.unfoldPredicate(post).reduce((e1, e2) => Star(e1, e2)(e1.o)), args)) + + private def unify_expression(cond: Expr[G], args: Map[Variable[G], Expr[G]]): Expr[G] = cond // TODO: Consider arguments! def resolve_expression(expr: Expr[G]): UncertainValue = expr.t match { case _: IntType[_] => resolve_integer_expression(expr) diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index d0f7299a20..64d120359a 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -40,10 +40,7 @@ case class RASIGenerator[G]() { private def get_initial_values(vars: Set[ConcreteVariable[G]]): Map[ConcreteVariable[G], UncertainValue] = { // TODO: Should this be uncertain or should it be defined (e.g. 0/false)? - Map.from(vars.map(v => (v, v.t match { - case _: IntType[_] => UncertainIntegerValue.uncertain() - case _: TBool[_] => UncertainIntegerValue.uncertain() - }))) + Map.from(vars.map(v => (v, UncertainValue.uncertain_of(v.t)))) } private def reduce_redundant_states(): (Seq[AbstractState[G]], Seq[(AbstractState[G], AbstractState[G])]) = { diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index 1dd1ce36c8..b4a695b881 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -1,6 +1,6 @@ package vct.rewrite.rasi -import vct.col.ast.{BooleanValue, Expr, Not} +import vct.col.ast.{BooleanValue, Expr, IntType, Not, TBool, Type} trait UncertainValue { def can_be_equal(other: UncertainValue): Boolean @@ -9,6 +9,12 @@ trait UncertainValue { def ==(other: UncertainValue): UncertainBooleanValue def !=(other: UncertainValue): UncertainBooleanValue } +case object UncertainValue { + def uncertain_of(t: Type[_]): UncertainValue = t match { + case _: IntType[_] => UncertainIntegerValue.uncertain() + case _: TBool[_] => UncertainBooleanValue.uncertain() + } +} case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) extends UncertainValue { override def can_be_equal(other: UncertainValue): Boolean = other match { From a97665bc7adcd25892d43c0df9848531100b544e Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 22 Feb 2024 15:47:14 +0100 Subject: [PATCH 43/85] Implemented extraction of state changes out of assumptions and postconditions --- .../vct/rewrite/rasi/AbstractProcess.scala | 10 +- .../vct/rewrite/rasi/AbstractState.scala | 96 ++++++++++++++++++- .../vct/rewrite/rasi/UncertainValue.scala | 13 +++ 3 files changed, 109 insertions(+), 10 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index fc378555a2..9c7d04d0d1 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -13,20 +13,20 @@ case class AbstractProcess[G](name: String) { case Assign(target, value) => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(target, state.resolve_expression(value)))) case Havoc(loc) => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(loc, UncertainValue.uncertain_of(loc.t)))) // Statements that induce assumptions about the state, such as assume, inhale, or a method's postcondition, might change the state implicitly - case Assume(assn) => viable_edges(succ, state).map(e => take_edge(e, state.with_assumption(assn))) - case Inhale(res) => viable_edges(succ, state).map(e => take_edge(e, state.with_assumption(res))) + case Assume(assn) => viable_edges(succ, state).flatMap(e => state.with_assumption(assn).map(s => take_edge(e, s))) + case Inhale(res) => viable_edges(succ, state).flatMap(e => state.with_assumption(res).map(s => take_edge(e, s))) // Abstract procedures, constructors and methods are defined by their postconditions case InvokeProcedure(ref, args, _, _, _, _) => ref.decl.body match { case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) - case None => viable_edges(succ, state).map(e => take_edge(e, state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))))) + case None => viable_edges(succ, state).flatMap(e => state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))).map(s => take_edge(e, s))) } case InvokeConstructor(ref, _, args, _, _, _, _) => ref.decl.body match { case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) - case None => viable_edges(succ, state).map(e => take_edge(e, state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))))) + case None => viable_edges(succ, state).flatMap(e => state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))).map(s => take_edge(e, s))) } case InvokeMethod(_, ref, args, _, _, _, _) => ref.decl.body match { case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) - case None => viable_edges(succ, state).map(e => take_edge(e, state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))))) + case None => viable_edges(succ, state).flatMap(e => state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))).map(s => take_edge(e, s))) } // TODO: What do wait and notify do? case Wait(obj) => viable_edges(succ, state).map(e => take_edge(e, state)) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 17cfa7131e..fb8bcb870f 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -1,7 +1,7 @@ package vct.rewrite.rasi import vct.col.ast._ -import vct.col.util.AstBuildHelpers +import vct.col.util.{AstBuildHelpers, Substitute} import vct.rewrite.cfg.CFGEntry import scala.collection.immutable.HashMap @@ -21,16 +21,94 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] def unlocked(): AbstractState[G] = AbstractState(valuations, processes, None) def with_valuation(variable: Expr[G], value: UncertainValue): AbstractState[G] = variable_from_expr(variable) match { - case Some(concrete_variable) => AbstractState(valuations + (concrete_variable -> value), processes, lock) + case Some(concrete_variable) => val_updated(concrete_variable, value) case None => this } + private def val_updated(variable: ConcreteVariable[G], value: UncertainValue): AbstractState[G] = + AbstractState(valuations + (variable -> value), processes, lock) + + def with_assumption(assumption: Expr[G]): Set[AbstractState[G]] = resolve_effect(assumption, negate = false) + + private def resolve_effect(assumption: Expr[G], negate: Boolean): Set[AbstractState[G]] = assumption match { + // Consider boolean/separation logic operators + case Not(arg) => this.resolve_effect(arg, !negate) + case Star(left, right) => if (!negate) handle_and(left, right) else handle_or(left, right, neg_left = true, neg_right = true) + case And(left, right) => if (!negate) handle_and(left, right) else handle_or(left, right, neg_left = true, neg_right = true) + case Or(left, right) => if (!negate) handle_or(left, right) else handle_and(left, right, neg_left = true, neg_right = true) + case Implies(left, right) => if (!negate) handle_or(left, right, neg_left = true) else handle_and(left, right, neg_right = true) + // Atomic comparisons, if they contain a concrete variable, can actually affect the state + case c: Comparison[G] => handle_update(c, negate) + // Boolean variables could appear in the assumption without any comparison + case e: Expr[G] if valuations.keys.exists(v => v.is(e, this)) => Set(this.val_updated(variable_from_expr(e).get, UncertainBooleanValue.from(!negate))) + case _ => throw new IllegalArgumentException(s"Effect of contract expression ${assumption.toInlineString} is not implemented") + } + + private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[AbstractState[G]] = + this.resolve_effect(left, neg_left).flatMap(s => s.resolve_effect(right, neg_right)) + + private def handle_or(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[AbstractState[G]] = { + val pure_left = is_pure(left) + val pure_right = is_pure(right) + // If one side is pure and the other has an effect on the state, treat this "or" as an implication. If both sides + // update the state, treat it as a split in the state space instead + if (pure_left && pure_right) Set(this) + else if (pure_left) handle_implies(left, right, !neg_left, neg_right) + else if (pure_right) handle_implies(right, left, !neg_right, neg_left) + else this.resolve_effect(left, neg_left) ++ this.resolve_effect(right, neg_right) + } + + private def handle_implies(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[AbstractState[G]] = { + val resolve_left: UncertainBooleanValue = resolve_boolean_expression(left) + var res: Set[AbstractState[G]] = Set() + if (neg_left && resolve_left.can_be_false || (!neg_left) && resolve_left.can_be_true) res = res ++ resolve_effect(right, neg_right) + if (neg_left && resolve_left.can_be_true || (!neg_left) && resolve_left.can_be_false) res = res ++ Set(this) + res + } - def with_assumption(assumption: Expr[G]): AbstractState[G] = ??? // TODO: Implement! + private def handle_update(comp: Comparison[G], negate: Boolean): Set[AbstractState[G]] = { + val pure_left = is_pure(comp.left) + val pure_right = is_pure(comp.right) + if (pure_left == pure_right) return Set(this) + // Now, exactly one side is pure, and the other contains a concrete variable + // Resolve the variable and the other side of the equation TODO: Reduce the side with the variable if it contains anything else as well + val variable: ConcreteVariable[G] = if (pure_left) variable_from_expr(comp.right).get else variable_from_expr(comp.left).get + val value: UncertainValue = if (pure_left) resolve_expression(comp.left) else resolve_expression(comp.right) + + comp match { + case _: Eq[_] => Set(if (!negate) this.val_updated(variable, value) else this.val_updated(variable, value.complement())) + case _: Neq[_] => Set(if (!negate) this.val_updated(variable, value.complement()) else this.val_updated(variable, value)) + case AmbiguousGreater(_, _) | Greater(_, _) => bound_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, negate) + case AmbiguousGreaterEq(_, _) | GreaterEq(_, _) => bound_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, !negate) + case AmbiguousLessEq(_, _) | LessEq(_, _) => bound_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left != negate, !negate) + case AmbiguousLess(_, _) | Less(_, _) => bound_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left != negate, negate) + } + } - def with_postcondition(post: AccountedPredicate[G], args: Map[Variable[G], Expr[G]]): AbstractState[G] = + private def bound_variable(v: ConcreteVariable[G], b: UncertainIntegerValue, var_greater: Boolean, can_be_equal: Boolean): Set[AbstractState[G]] = { + if (var_greater) { + if (can_be_equal) Set(this.val_updated(v, b.above_eq())) + else Set(this.val_updated(v, b.above())) + } + else { + if (can_be_equal) Set(this.val_updated(v, b.below_eq())) + else Set(this.val_updated(v, b.below())) + } + } + + private def is_pure(node: Node[G]): Boolean = node match { + // The old value of a variable is always pure, since it cannot be updated + case _: Old[_] => true + case e: UnExpr[_] => e.subnodes.forall(n => is_pure(n)) + case e: BinExpr[_] => e.subnodes.forall(n => is_pure(n)) + case e: Expr[_] => if (valuations.keys.exists(v => v.is(e, this))) false else e.subnodes.forall(n => is_pure(n)) + case _ => true + } + + def with_postcondition(post: AccountedPredicate[G], args: Map[Variable[G], Expr[G]]): Set[AbstractState[G]] = with_assumption(unify_expression(AstBuildHelpers.unfoldPredicate(post).reduce((e1, e2) => Star(e1, e2)(e1.o)), args)) - private def unify_expression(cond: Expr[G], args: Map[Variable[G], Expr[G]]): Expr[G] = cond // TODO: Consider arguments! + private def unify_expression(cond: Expr[G], args: Map[Variable[G], Expr[G]]): Expr[G] = + Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> e }).dispatch(cond) def resolve_expression(expr: Expr[G]): UncertainValue = expr.t match { case _: IntType[_] => resolve_integer_expression(expr) @@ -72,6 +150,10 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_integer_expression(iff)) value } + case Old(exp, at) => at match { + case Some(_) => throw new IllegalArgumentException(s"Cannot resolve labelled old expression ${expr.toInlineString}") + case None => resolve_integer_expression(exp) + } case Local(_) | DerefHeapVariable(_) | Deref(_, _) | DerefPointer(_) | AmbiguousSubscript(_, _) | SeqSubscript(_, _) | ArraySubscript(_, _) | PointerSubscript(_, _) => variable_from_expr(expr) match { case Some(v) => valuations(v).asInstanceOf[UncertainIntegerValue] case None => UncertainIntegerValue.uncertain() @@ -106,6 +188,10 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_boolean_expression(iff)) value } + case Old(exp, at) => at match { + case Some(_) => throw new IllegalArgumentException(s"Cannot resolve labelled old expression ${expr.toInlineString}") + case None => resolve_boolean_expression(exp) + } case Local(_) | DerefHeapVariable(_) | Deref(_, _) | DerefPointer(_) | AmbiguousSubscript(_, _) | SeqSubscript(_, _) | ArraySubscript(_, _) | PointerSubscript(_, _) => variable_from_expr(expr) match { case Some(v) => valuations(v).asInstanceOf[UncertainBooleanValue] case None => UncertainBooleanValue.uncertain() diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index b4a695b881..540f9d16ee 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -5,6 +5,7 @@ import vct.col.ast.{BooleanValue, Expr, IntType, Not, TBool, Type} trait UncertainValue { def can_be_equal(other: UncertainValue): Boolean def can_be_unequal(other: UncertainValue): Boolean + def complement(): UncertainValue def to_expression[G](variable: Expr[G]): Expr[G] def ==(other: UncertainValue): UncertainBooleanValue def !=(other: UncertainValue): UncertainBooleanValue @@ -27,6 +28,8 @@ case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) ex case _ => true } + override def complement(): UncertainValue = !this + override def to_expression[G](variable: Expr[G]): Expr[G] = { if (can_be_true && can_be_false) BooleanValue(value = true)(variable.o) else if (can_be_true) variable @@ -83,6 +86,11 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { case _ => true } + override def complement(): UncertainValue = value match { + case BoundedInterval(lower, upper) if lower == upper => UncertainIntegerValue(value.complement()) + case _ => UncertainIntegerValue.uncertain() + } + override def to_expression[G](variable: Expr[G]): Expr[G] = value.to_expression(variable) override def ==(other: UncertainValue): UncertainBooleanValue = other match { @@ -100,6 +108,11 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { def union(other: UncertainIntegerValue): UncertainIntegerValue = UncertainIntegerValue(value.union(other.value)) + def below_eq(): UncertainIntegerValue = UncertainIntegerValue(value.below_max()) + def below(): UncertainIntegerValue = below_eq() + UncertainIntegerValue.single(-1) + def above_eq(): UncertainIntegerValue = UncertainIntegerValue(value.above_min()) + def above(): UncertainIntegerValue = above_eq() + UncertainIntegerValue.single(1) + def ==(other: UncertainIntegerValue): UncertainBooleanValue = UncertainBooleanValue(can_be_equal(other), can_be_unequal(other)) def !=(other: UncertainIntegerValue): UncertainBooleanValue = From d9a5b856d61b23a3f5a2deb0afe32b43515b0e6e Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 22 Feb 2024 17:16:17 +0100 Subject: [PATCH 44/85] Started work on connecting the RASI generator to the frontend (doesn't compile) --- src/main/vct/main/stages/GenerateRASI.scala | 39 +++++++++++++++++++ src/main/vct/main/stages/PrintCFG.scala | 1 - src/main/vct/main/stages/Stages.scala | 11 +++++- src/main/vct/options/Options.scala | 10 ++++- .../vct/rewrite/rasi/RASIGenerator.scala | 15 ++++--- 5 files changed, 66 insertions(+), 10 deletions(-) create mode 100644 src/main/vct/main/stages/GenerateRASI.scala diff --git a/src/main/vct/main/stages/GenerateRASI.scala b/src/main/vct/main/stages/GenerateRASI.scala new file mode 100644 index 0000000000..79f2a96a15 --- /dev/null +++ b/src/main/vct/main/stages/GenerateRASI.scala @@ -0,0 +1,39 @@ +package vct.main.stages + +import hre.stages.Stage +import vct.col.ast.{InstanceField, InstanceMethod, Node} +import vct.col.rewrite.Generation +import vct.options.Options +import vct.rewrite.rasi.{ConcreteVariable, FieldVariable, IndexedVariable, RASIGenerator} + +import java.nio.file.Path + +case object GenerateRASI { + def ofOptions(options: Options): Stage[Node[_ <: Generation], Unit] = { + GenerateRASI(options.vesuvRasiVariables, options.vesuvOutput) + } +} + +case class GenerateRASI(vars: Option[Seq[String]], out: Path) extends Stage[Node[_ <: Generation], Unit] { + + override def friendlyName: String = "Printing control flow graph" + + override def progressWeight: Int = 0 + + override def run(in1: Node[_ <: Generation]): Unit = { + val main_method = in1.transSubnodes.collectFirst{ case m: InstanceMethod[_] if m.o.getPreferredName.get.snake.equals("main") => m }.get + val variables: Set[ConcreteVariable[_ <: Generation]] = vars.getOrElse(Seq()).map(s => resolve_variable(in1, s)).toSet + RASIGenerator().test(main_method, variables, out) + } + + private def resolve_variable(in1: Node[_ <: Generation], name: String): ConcreteVariable[_ <: Generation] = { + val name_len = name.indexOf("[") + val var_name = if (name_len == -1) name else name.substring(0, name_len) + val index: Option[Integer] = if (name_len == -1) None else Some(Integer.valueOf(name.substring(name_len + 2, name.length - 1))) + val instance_field = in1.transSubnodes.collectFirst{ case f: InstanceField[_] if f.o.getPreferredName.get.snake.equals(var_name) => f }.get + index match { + case Some(i) => IndexedVariable(instance_field, i) + case None => FieldVariable(instance_field) + } + } +} diff --git a/src/main/vct/main/stages/PrintCFG.scala b/src/main/vct/main/stages/PrintCFG.scala index e4c63e2e48..bc4adbd6b9 100644 --- a/src/main/vct/main/stages/PrintCFG.scala +++ b/src/main/vct/main/stages/PrintCFG.scala @@ -1,7 +1,6 @@ package vct.main.stages import hre.stages.Stage -import sourcecode.Name import vct.col.ast.{InstanceMethod, Node} import vct.col.rewrite.Generation import vct.options.Options diff --git a/src/main/vct/main/stages/Stages.scala b/src/main/vct/main/stages/Stages.scala index 78cab0cb4a..cfe0e7760a 100644 --- a/src/main/vct/main/stages/Stages.scala +++ b/src/main/vct/main/stages/Stages.scala @@ -67,8 +67,15 @@ case object Stages { } def vesuvOfOptions(options: Options, blameProvider: BlameProvider) : Stages[Seq[Readable], Unit] = { - Parsing.ofOptions(options, blameProvider) - .thenRun(Output.vesuvOfOptions(options)) + if (options.vesuvGenerateRasi) { + Parsing.ofOptions(options, blameProvider) + .thenRun(Resolution.ofOptions(options, blameProvider)) + .thenRun(GenerateRASI.ofOptions(options)) + } + else { + Parsing.ofOptions(options, blameProvider) + .thenRun(Output.vesuvOfOptions(options)) + } } def cfgTransformationOfOptions(options: Options, blameProvider: BlameProvider): Stages[Seq[Readable], Unit] = { diff --git a/src/main/vct/options/Options.scala b/src/main/vct/options/Options.scala index 287369c192..8f83f6a969 100644 --- a/src/main/vct/options/Options.scala +++ b/src/main/vct/options/Options.scala @@ -264,8 +264,12 @@ case object Options { .action((_, c) => c.copy(mode = Mode.VeSUV)) .text("Enable VeSUV mode: transform SystemC designs to PVL to be deductively verified") .children( - opt[Path]("vesuv-output").required().valueName("") // TODO: Give option for default location? - .action((path, c) => c.copy(vesuvOutput = path)) + opt[Path]("vesuv-output").required().valueName("") + .action((path, c) => c.copy(vesuvOutput = path)), + opt[Unit]("generate-rasi").action((_, c) => c.copy(vesuvGenerateRasi = true)).children( + opt[Seq[String]]("rasi-vars").required().valueName(",...") + .action((vars, c) => c.copy(vesuvRasiVariables = Some(vars))) + ) ), note(""), @@ -412,6 +416,8 @@ case class Options // VeSUV options vesuvOutput: Path = null, + vesuvGenerateRasi: Boolean = false, + vesuvRasiVariables: Option[Seq[String]] = None, // Control flow graph options cfgOutput: Path = null, diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index 64d120359a..c50636a26d 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -1,7 +1,7 @@ package vct.rewrite.rasi -import vct.col.ast.{Expr, IntType, Or, TBool} -import vct.rewrite.cfg.CFGNode +import vct.col.ast.{Expr, InstanceMethod, Or} +import vct.rewrite.cfg.{CFGEntry, CFGGenerator} import java.nio.file.Path import scala.collection.immutable.HashMap @@ -12,18 +12,23 @@ case class RASIGenerator[G]() { private val found_edges: mutable.ArrayBuffer[(AbstractState[G], AbstractState[G])] = mutable.ArrayBuffer() private val current_branches: mutable.ArrayBuffer[AbstractState[G]] = mutable.ArrayBuffer() - def generate_rasi(node: CFGNode[G], vars: Set[ConcreteVariable[G]]): Expr[G] = { + def execute(entry_point: InstanceMethod[G], vars: Set[ConcreteVariable[G]]): Expr[G] = + generate_rasi(CFGGenerator().generate(entry_point), vars) + def test(entry_point: InstanceMethod[G], vars: Set[ConcreteVariable[G]], out_path: Path): Unit = + print_state_space(CFGGenerator().generate(entry_point), vars, out_path) + + private def generate_rasi(node: CFGEntry[G], vars: Set[ConcreteVariable[G]]): Expr[G] = { explore(node, vars) found_states.distinctBy(s => s.valuations).map(s => s.to_expression).reduce((e1, e2) => Or(e1, e2)(e1.o)) } - def print_state_space(node: CFGNode[G], vars: Set[ConcreteVariable[G]], out_path: Path): Unit = { + private def print_state_space(node: CFGEntry[G], vars: Set[ConcreteVariable[G]], out_path: Path): Unit = { explore(node, vars) val (ns, es) = reduce_redundant_states() Utils.print(ns, es, out_path) } - private def explore(node: CFGNode[G], vars: Set[ConcreteVariable[G]]): Unit = { + private def explore(node: CFGEntry[G], vars: Set[ConcreteVariable[G]]): Unit = { val initial_state = AbstractState(get_initial_values(vars), HashMap((AbstractProcess[G]("main"), node)), None) found_states += initial_state current_branches += initial_state From 8235eff21e870e08c71b62f772f487c00954583b Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 23 Feb 2024 09:29:04 +0100 Subject: [PATCH 45/85] Added comments to VeSUV command line options --- src/main/vct/options/Options.scala | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/vct/options/Options.scala b/src/main/vct/options/Options.scala index 8f83f6a969..133c5a583f 100644 --- a/src/main/vct/options/Options.scala +++ b/src/main/vct/options/Options.scala @@ -265,10 +265,14 @@ case object Options { .text("Enable VeSUV mode: transform SystemC designs to PVL to be deductively verified") .children( opt[Path]("vesuv-output").required().valueName("") - .action((path, c) => c.copy(vesuvOutput = path)), - opt[Unit]("generate-rasi").action((_, c) => c.copy(vesuvGenerateRasi = true)).children( + .action((path, c) => c.copy(vesuvOutput = path)) + .text("Output file for the result of the transformation"), + opt[Unit]("generate-rasi").action((_, c) => c.copy(vesuvGenerateRasi = true)) + .text("Instead of transforming a SystemC design to PVL, generate a global invariant for a PVL program") + .children( opt[Seq[String]]("rasi-vars").required().valueName(",...") .action((vars, c) => c.copy(vesuvRasiVariables = Some(vars))) + .text("[WIP] Preliminary selection mechanism for RASI variables; might be replaced later") ) ), From b64bf510c0b207c00ad1cf9c69f8106f6286586e Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 23 Feb 2024 12:57:09 +0100 Subject: [PATCH 46/85] Fixed type error, project compiles --- src/main/vct/main/stages/GenerateRASI.scala | 9 +++++---- src/main/vct/options/Options.scala | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/vct/main/stages/GenerateRASI.scala b/src/main/vct/main/stages/GenerateRASI.scala index 79f2a96a15..5111c4939c 100644 --- a/src/main/vct/main/stages/GenerateRASI.scala +++ b/src/main/vct/main/stages/GenerateRASI.scala @@ -21,16 +21,17 @@ case class GenerateRASI(vars: Option[Seq[String]], out: Path) extends Stage[Node override def progressWeight: Int = 0 override def run(in1: Node[_ <: Generation]): Unit = { - val main_method = in1.transSubnodes.collectFirst{ case m: InstanceMethod[_] if m.o.getPreferredName.get.snake.equals("main") => m }.get - val variables: Set[ConcreteVariable[_ <: Generation]] = vars.getOrElse(Seq()).map(s => resolve_variable(in1, s)).toSet + val in = in1.asInstanceOf[Node[Generation]] + val main_method = in.transSubnodes.collectFirst{ case m: InstanceMethod[_] if m.o.getPreferredName.get.snake.equals("main") => m }.get + val variables: Set[ConcreteVariable[Generation]] = vars.getOrElse(Seq()).map(s => resolve_variable(in, s)).toSet RASIGenerator().test(main_method, variables, out) } - private def resolve_variable(in1: Node[_ <: Generation], name: String): ConcreteVariable[_ <: Generation] = { + private def resolve_variable(in: Node[Generation], name: String): ConcreteVariable[Generation] = { val name_len = name.indexOf("[") val var_name = if (name_len == -1) name else name.substring(0, name_len) val index: Option[Integer] = if (name_len == -1) None else Some(Integer.valueOf(name.substring(name_len + 2, name.length - 1))) - val instance_field = in1.transSubnodes.collectFirst{ case f: InstanceField[_] if f.o.getPreferredName.get.snake.equals(var_name) => f }.get + val instance_field = in.transSubnodes.collectFirst{ case f: InstanceField[_] if f.o.getPreferredName.get.snake.equals(var_name) => f }.get index match { case Some(i) => IndexedVariable(instance_field, i) case None => FieldVariable(instance_field) diff --git a/src/main/vct/options/Options.scala b/src/main/vct/options/Options.scala index 133c5a583f..01d23e1062 100644 --- a/src/main/vct/options/Options.scala +++ b/src/main/vct/options/Options.scala @@ -284,6 +284,7 @@ case object Options { .children( opt[Path]("cfg-output").required().valueName("") .action((path, c) => c.copy(cfgOutput = path)) + .text("Output file for the control flow graph in .dot format") ), note(""), From 75446586656e200770a8b60334ced949a73e2cef Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 23 Feb 2024 13:05:03 +0100 Subject: [PATCH 47/85] Progress on switch/case implementation --- .../vct/rewrite/cfg/CFGGenerator.scala | 39 ++++++++++++------- src/rewrite/vct/rewrite/cfg/Index.scala | 2 - 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 0883a48b28..6688578347 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -22,12 +22,11 @@ case class CFGGenerator[G]() { cfg_node.successors.addAll(find_successors(node, context)) // Handle labels and goto statements node match { - case label: Label[G] => { + case label: Label[_] => // For labels, add them to the label map and add them to any goto statements going to that label found_labels.addOne((label.decl, cfg_node)) searched_labels.getOrElse(label.decl, mutable.Set()).map(g => g.successors.addOne(CFGEdge(cfg_node, None))) - } - case goto: Goto[G] => + case goto: Goto[_] => // For goto statements, if the label could not be resolved, add the statement to the waiting list for the right label if (cfg_node.successors.isEmpty) { if (searched_labels.contains(goto.lbl.decl)) searched_labels(goto.lbl.decl).addOne(cfg_node) @@ -53,7 +52,7 @@ case class CFGGenerator[G]() { case Send(_, _, res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) case Recv(_) => sequential_successor(context) case DefaultCase() => sequential_successor(context) - case Case(pattern) => sequential_successor(context) // TODO: Handle expression side effects in switch case conditions + case Case(_) => sequential_successor(context) case Label(_, _) => evaluate_first(context.enter_scope(node)) case Goto(lbl) => found_labels.get(lbl.decl) match { case Some(node) => mutable.Set(CFGEdge(node, None)) @@ -104,7 +103,7 @@ case class CFGGenerator[G]() { case Branch(_) => evaluate_first(context.enter_scope(node)) case IndetBranch(branches) => mutable.LinkedHashSet.from(branches.zipWithIndex.map(b => CFGEdge(convert(b._1, context.enter_scope(node, b._2)), None))) - case s: Switch[G] => handle_switch_statement(s, context) + case s: Switch[_] => handle_switch_statement(s, context.enter_scope(node)) case Loop(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) case RangedFor(_, _, _) => evaluate_first(context.enter_scope(node)) case TryCatchFinally(_, _, _) => evaluate_first(context.enter_scope(node)) @@ -159,27 +158,39 @@ case class CFGGenerator[G]() { else sequential_successor(index) } - private def sequential_successor(index: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = { - if (index.indices.nonEmpty) index.make_step().map(i => CFGEdge(resolve_index(i._1), i._2)) - else mutable.Set(CFGEdge(CFGTerminal(), None)) + private def sequential_successor(index: GlobalIndex[G], cond: Option[Expr[G]] = None): mutable.Set[CFGEdge[G]] = { + if (index.indices.nonEmpty) index.make_step().map(i => CFGEdge(resolve_index(i._1), Utils.and(i._2, cond))) + else mutable.Set(CFGEdge(CFGTerminal(), cond)) } private def handle_switch_statement(switch: Switch[G], context: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = { val switches: Seq[(SwitchCase[G], GlobalIndex[G])] = Utils.find_all_cases(switch.body, context.enter_scope(switch)) - var conds: Seq[(Eval[G], GlobalIndex[G])] = Seq() + var conds: Seq[(Case[G], Eval[G], GlobalIndex[G])] = Seq() var default: Option[(DefaultCase[G], GlobalIndex[G])] = None for (s <- switches) { s._1 match { - case c @ Case(pattern) => { + case c @ Case(pattern) => val cond = Eq(switch.expr, pattern)(c.o) // Collect only up to default case - if (default.isEmpty) conds = conds :+ (Eval(cond)(cond.o), s._2) - } + if (default.isEmpty) conds = conds :+ (c, Eval(cond)(cond.o), s._2) case c @ DefaultCase() => default = Some((c, s._2)) } } - // TODO: Finish implementation - ??? + conds = conds.reverse + + var previous_cond: Option[Expr[G]] = Some(Utils.negate(conds.head._2.expr)) + var successor_to_previous: mutable.Set[CFGEdge[G]] = default match { + case Some(t) => mutable.Set(CFGEdge(convert(t._1, t._2), previous_cond)) + case None => sequential_successor(context, previous_cond) + } + + for (c <- conds) { + successor_to_previous.addOne(CFGEdge(convert(c._1, c._3), Some(c._2.expr))) + val node = CFGNode(c._2, successor_to_previous) + // TODO: Enter node at appropriate index in converted_nodes! + successor_to_previous = mutable.Set(CFGEdge(node, None)) // TODO: Add edge condition here! + } + successor_to_previous } private def return_successor(index: GlobalIndex[G]): CFGEntry[G] = diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index f4a0108be4..656ea5dce4 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -290,7 +290,6 @@ case class ExtractIndex[G](extract: Extract[G]) extends Index[G] { case class EvalIndex[G](eval: Eval[G], index: Int, subexpressions: Seq[Statement[G]]) extends Index[G] { def this(eval: Eval[G], index: Int) = this(eval, index, Utils.find_all_subexpressions(eval.expr)) override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { - // TODO: Consider conditions in expressions, too if (index < subexpressions.size - 1) Set((Step(EvalIndex(eval, index + 1)), None)) else Set((Outgoing(), None)) } @@ -494,7 +493,6 @@ case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends } } -// TODO: Switch cases could be multiple context indices deep; this does not work with the single index for make_step() case class SwitchIndex[G](switch: Switch[G]) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = switch.body From 40d70590c7b942fb500365847e3fb7dc580ea08b Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 23 Feb 2024 16:23:44 +0100 Subject: [PATCH 48/85] Fixing the most severe bugs, program compiles and runs --- src/main/vct/main/stages/GenerateRASI.scala | 2 +- .../vct/rewrite/rasi/AbstractProcess.scala | 15 +++-- .../vct/rewrite/rasi/AbstractState.scala | 2 +- src/rewrite/vct/rewrite/rasi/Interval.scala | 62 ++++++++++++++++--- .../vct/rewrite/rasi/RASIGenerator.scala | 9 +-- src/rewrite/vct/rewrite/rasi/Utils.scala | 6 +- 6 files changed, 74 insertions(+), 22 deletions(-) diff --git a/src/main/vct/main/stages/GenerateRASI.scala b/src/main/vct/main/stages/GenerateRASI.scala index 5111c4939c..b061092fe7 100644 --- a/src/main/vct/main/stages/GenerateRASI.scala +++ b/src/main/vct/main/stages/GenerateRASI.scala @@ -16,7 +16,7 @@ case object GenerateRASI { case class GenerateRASI(vars: Option[Seq[String]], out: Path) extends Stage[Node[_ <: Generation], Unit] { - override def friendlyName: String = "Printing control flow graph" + override def friendlyName: String = "Generate reachable abstract states invariant" override def progressWeight: Int = 0 diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index 9c7d04d0d1..7dc6683c26 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -5,12 +5,15 @@ import vct.rewrite.cfg.{CFGEdge, CFGEntry, CFGNode, CFGTerminal} import scala.collection.mutable -case class AbstractProcess[G](name: String) { +case class AbstractProcess[G](obj: Expr[G]) { def get_next(node: CFGEntry[G], state: AbstractState[G]): Set[AbstractState[G]] = node match { case CFGTerminal() => Set(state.without_process(this)) case CFGNode(n, succ) => n match { // Assign statements change the state of variables directly (if they appear in the valuation) - case Assign(target, value) => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(target, state.resolve_expression(value)))) + case Assign(target, value) => target.t match { + case _: IntType[_] | TBool() => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(target, state.resolve_expression(value)))) + case _ => viable_edges(succ, state).map(e => take_edge(e, state)) + } case Havoc(loc) => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(loc, UncertainValue.uncertain_of(loc.t)))) // Statements that induce assumptions about the state, such as assume, inhale, or a method's postcondition, might change the state implicitly case Assume(assn) => viable_edges(succ, state).flatMap(e => state.with_assumption(assn).map(s => take_edge(e, s))) @@ -42,14 +45,16 @@ case class AbstractProcess[G](name: String) { else throw new IllegalStateException("Trying to unlock lock owned by other process") case None => throw new IllegalStateException("Trying to unlock unlocked lock") } - // When forking a new process, make the step of creating it simultaneously to the normal steps TODO: consider join + // When forking a new process, make the step of creating it simultaneously to the normal steps case Fork(obj) => val edges: (Set[CFGEdge[G]], Set[CFGEdge[G]]) = viable_edges(succ, state).partition(e => e.target match { case CFGTerminal() => false case CFGNode(t, _) => t.equals(obj.t.asClass.get.cls.decl.declarations.collect{ case r: RunMethod[G] => r }.head.body.get) }) - edges._2.map(e => take_edge(e, state.with_process_at(AbstractProcess(s"${name}_${obj.toInlineString}"), edges._1.head.target))) - case Join(obj) => viable_edges(succ, state).map(e => take_edge(e, state)) + edges._2.map(e => take_edge(e, state.with_process_at(AbstractProcess(obj), edges._1.head.target))) // TODO: Can only one thread per class instance be launched? + case Join(obj) => + if (state.processes.keys.forall(p => p.obj != obj)) viable_edges(succ, state).map(e => take_edge(e, state)) + else Set(state) // Everything else does not affect the state, so simply go to the next step case _ => viable_edges(succ, state).map(e => take_edge(e, state)) } diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index fb8bcb870f..a35ac1e76a 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -11,7 +11,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] processes.flatMap(p => p._1.get_next(p._2, this)).toSet def with_process_at(process: AbstractProcess[G], position: CFGEntry[G]): AbstractState[G] = - AbstractState(valuations, processes + (process -> position), lock) + AbstractState(valuations, processes.removed(process) + (process -> position), lock) def without_process(process: AbstractProcess[G]): AbstractState[G] = AbstractState(valuations, processes.removed(process), lock) diff --git a/src/rewrite/vct/rewrite/rasi/Interval.scala b/src/rewrite/vct/rewrite/rasi/Interval.scala index ea1d6e1695..f2c05dfb27 100644 --- a/src/rewrite/vct/rewrite/rasi/Interval.scala +++ b/src/rewrite/vct/rewrite/rasi/Interval.scala @@ -26,6 +26,49 @@ case class Infinite() extends IntervalSize { override def get: Int = throw new NoSuchElementException("Accessing infinite interval size element") } +/** + * An interval represents a set of integers. It can be empty (type EmptyInterval), unbounded (type + * UnboundedInterval), bounded by two integers (type BoundedInterval), below an upper bound + * (type UpperBoundedInterval), above a lower bound (type LowerBoundedInterval) or a union of + * a finite set of other intervals (type MultiInterval). If it is bounded, then the bounds are contained in + * the interval. + * + * An interval supports the following operations: + * + * SIZE OPERATIONS + * - empty() returns true if the interval is empty and false otherwise + * - non_empty() returns false if the interval is empty and true otherwise + * - size() returns the size of the interval in the form of Finite(n) if the interval is + * finite and of size n, or Infinite() otherwise + * + * SET OPERATIONS + * - intersection(Interval) returns the intersection between this interval and the argument + * - union(Interval) returns the union of this interval and the argument + * - complement() returns the interval representing all integers that are not contained in this interval + * + * ARITHMETIC OPERATIONS + * - below_max() returns an interval whose upper bound is the maximum entry in this interval, or an + * unbounded interval if there is no maximum + * - above_min() returns an interval whose lower bound is the minimum entry in this interval, or an + * unbounded interval if there is no minimum + * - + returns the interval representing the set of sums of entries in both intervals + * - - returns the interval representing the set of subtractions of entries of the second interval from + * entries in the first + * - * returns the interval overapproximating the set of products from both intervals. Since this is not + * generally a contiguous interval, this overapproximates to the interval between the minimum and maximum product or, + * if multiple such intervals can be defined, to the union of these intervals + * - unary_- returns the element-wise negation of this interval + * - / NOT IMPLEMENTED + * - % NOT IMPLEMENTED + * - pow NOT IMPLEMENTED + * + * ADMINISTRATIVE OPERATIONS + * - sub_intervals() returns the minimum set of contiguous intervals this interval is a union of + * - try_to_resolve() if this interval contains exactly one integer, returns this integer, otherwise + * returns None + * - to_expression(Variable) returns an expression indicating that the given variable is within this + * interval + */ sealed abstract class Interval { def empty(): Boolean def non_empty(): Boolean = !empty() @@ -113,7 +156,7 @@ case class MultiInterval(intervals: Set[Interval]) extends Interval { if (new_intervals.size > 1) MultiInterval(new_intervals) else new_intervals.head } - private def merge_intersecting(is: Set[Interval]): Set[Interval] = MultiInterval(is).flatten().complement().complement().sub_intervals() + private def merge_intersecting(is: Set[Interval]): Set[Interval] = MultiInterval(is).sub_intervals().reduce((i1, i2) => i1.union(i2)).sub_intervals() override def sub_intervals(): Set[Interval] = intervals.flatMap(i => i.sub_intervals()) private def flatten(): MultiInterval = MultiInterval(this.sub_intervals()) override def try_to_resolve(): Option[Int] = { @@ -146,13 +189,13 @@ case class BoundedInterval(lower: Int, upper: Int) extends Interval { case EmptyInterval => this case mi: MultiInterval => mi.union(this) case BoundedInterval(low, up) => - if (up <= upper && up >= lower || low <= upper && low >= lower) BoundedInterval(scala.math.min(low, lower), scala.math.max(up, upper)) + if (up <= upper + 1 && up >= lower - 1 || low <= upper + 1 && low >= lower - 1) BoundedInterval(scala.math.min(low, lower), scala.math.max(up, upper)) else MultiInterval(Set(this, other)) case LowerBoundedInterval(low) => - if (upper >= low) LowerBoundedInterval(scala.math.min(low, lower)) + if (upper + 1 >= low) LowerBoundedInterval(scala.math.min(low, lower)) else MultiInterval(Set(this, other)) case UpperBoundedInterval(up) => - if (lower <= up) UpperBoundedInterval(scala.math.max(up, upper)) + if (lower - 1 <= up) UpperBoundedInterval(scala.math.max(up, upper)) else MultiInterval(Set(this, other)) case UnboundedInterval => other } @@ -206,7 +249,8 @@ case class BoundedInterval(lower: Int, upper: Int) extends Interval { case LowerBoundedInterval(low) => ??? case UpperBoundedInterval(up) => ??? case UnboundedInterval => - if (lower < 0) other + if (lower < -1) other + else if (lower == -1) LowerBoundedInterval(-1) else LowerBoundedInterval(0) } override def try_to_resolve(): Option[Int] = { @@ -238,11 +282,11 @@ case class LowerBoundedInterval(lower: Int) extends Interval { case EmptyInterval => this case mi: MultiInterval => mi.union(this) case BoundedInterval(low, up) => - if (up >= lower) LowerBoundedInterval(scala.math.min(low, lower)) + if (up + 1 >= lower) LowerBoundedInterval(scala.math.min(low, lower)) else MultiInterval(Set(other, this)) case LowerBoundedInterval(low) => LowerBoundedInterval(scala.math.min(low, lower)) case UpperBoundedInterval(up) => - if (up >= lower) UnboundedInterval + if (up + 1 >= lower) UnboundedInterval else MultiInterval(Set(other, this)) case UnboundedInterval => other } @@ -294,10 +338,10 @@ case class UpperBoundedInterval(upper: Int) extends Interval { case EmptyInterval => this case mi: MultiInterval => mi.union(this) case BoundedInterval(low, up) => - if (low <= upper) UpperBoundedInterval(scala.math.max(upper, up)) + if (low - 1 <= upper) UpperBoundedInterval(scala.math.max(upper, up)) else MultiInterval(Set(this, other)) case LowerBoundedInterval(low) => - if (low <= upper) UnboundedInterval + if (low - 1 <= upper) UnboundedInterval else MultiInterval(Set(this, other)) case UpperBoundedInterval(up) => UpperBoundedInterval(scala.math.max(upper, up)) case UnboundedInterval => other diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index c50636a26d..95433bb7b2 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -1,6 +1,7 @@ package vct.rewrite.rasi -import vct.col.ast.{Expr, InstanceMethod, Or} +import vct.col.ast.{Expr, InstanceMethod, Null, Or} +import vct.col.origin.Origin import vct.rewrite.cfg.{CFGEntry, CFGGenerator} import java.nio.file.Path @@ -29,7 +30,7 @@ case class RASIGenerator[G]() { } private def explore(node: CFGEntry[G], vars: Set[ConcreteVariable[G]]): Unit = { - val initial_state = AbstractState(get_initial_values(vars), HashMap((AbstractProcess[G]("main"), node)), None) + val initial_state = AbstractState(get_initial_values(vars), HashMap((AbstractProcess[G](Null()(Origin(Seq()))), node)), None) found_states += initial_state current_branches += initial_state @@ -39,7 +40,7 @@ case class RASIGenerator[G]() { val successors: Set[AbstractState[G]] = curr.successors() found_edges.addAll(successors.map(s => (curr, s))) - successors.foreach(s => if (!found_states.contains(s)) {found_states += curr; current_branches += curr}) + successors.foreach(s => if (!found_states.contains(s)) {found_states += s; current_branches += s}) } } @@ -51,6 +52,6 @@ case class RASIGenerator[G]() { private def reduce_redundant_states(): (Seq[AbstractState[G]], Seq[(AbstractState[G], AbstractState[G])]) = { val state_groups: Map[AbstractState[G], AbstractState[G]] = found_states.groupBy(s => s.valuations).flatMap(t => t._2.map(s => (s, t._2.head))) val edge_groups: Seq[(AbstractState[G], AbstractState[G])] = Seq.from(found_edges.map(t => (state_groups(t._1), state_groups(t._2))).distinct) - (state_groups.keySet.toSeq, edge_groups) + (state_groups.values.toSeq.distinct, edge_groups) } } diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala index bc4287dab1..3274c780dc 100644 --- a/src/rewrite/vct/rewrite/rasi/Utils.scala +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -18,13 +18,15 @@ case object Utils { } private def print_state_space[G](names: Map[AbstractState[G], String], edges: Seq[(AbstractState[G], AbstractState[G])], writer: Writer): Unit = { + writer.append("digraph {\n") names.foreach(t => writer.append(t._2) .append(s"[label=${"\""}") .append(t._1.to_expression.toInlineString) - .append(s"${"\""}];")) + .append(s"${"\""}];\n")) edges.foreach(t => writer.append(names(t._1)) .append(" -> ") .append(names(t._2)) - .append(";")) + .append(";\n")) + writer.append("}") } } From e445aa22665b23f19d392e4588380afa4a6d2900 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 26 Feb 2024 14:02:39 +0100 Subject: [PATCH 49/85] Refactored CFG generation to use statement supertypes instead of pattern matching all statements --- src/col/vct/col/ast/Node.scala | 136 +++++++++--------- .../lang/silver/SilverFieldAssignImpl.scala | 4 +- .../lang/silver/SilverLocalAssignImpl.scala | 4 +- .../ExpressionContainerStatementImpl.scala | 8 ++ .../ast/statement/terminal/AssertImpl.scala | 4 +- .../ast/statement/terminal/AssumeImpl.scala | 4 +- .../ast/statement/terminal/CommitImpl.scala | 4 +- .../ast/statement/terminal/ExhaleImpl.scala | 4 +- .../col/ast/statement/terminal/FoldImpl.scala | 4 +- .../col/ast/statement/terminal/ForkImpl.scala | 4 +- .../ast/statement/terminal/HavocImpl.scala | 4 +- .../ast/statement/terminal/InhaleImpl.scala | 4 +- .../col/ast/statement/terminal/JoinImpl.scala | 4 +- .../col/ast/statement/terminal/LockImpl.scala | 4 +- .../ast/statement/terminal/NotifyImpl.scala | 4 +- .../ast/statement/terminal/RefuteImpl.scala | 4 +- .../col/ast/statement/terminal/SendImpl.scala | 6 +- .../ast/statement/terminal/UnfoldImpl.scala | 4 +- .../ast/statement/terminal/UnlockImpl.scala | 4 +- .../col/ast/statement/terminal/WaitImpl.scala | 4 +- .../statement/terminal/WandApplyImpl.scala | 4 +- .../statement/veymont/PVLSeqAssignImpl.scala | 4 +- .../ast/statement/veymont/SeqAssignImpl.scala | 4 +- .../col/ast/unsorted/InstantiateImpl.scala | 3 +- .../vct/rewrite/cfg/CFGGenerator.scala | 93 ++---------- .../vct/rewrite/rasi/AbstractState.scala | 2 +- 26 files changed, 158 insertions(+), 170 deletions(-) create mode 100644 src/col/vct/col/ast/statement/behavior/ExpressionContainerStatementImpl.scala diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index df74796728..9301b54e4a 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -72,6 +72,7 @@ import vct.col.ast.lang.smt._ import vct.col.ast.lang.sycl._ import vct.col.ast.node._ import vct.col.ast.statement._ +import vct.col.ast.statement.behavior.ExpressionContainerStatementImpl import vct.col.ast.statement.composite._ import vct.col.ast.statement.exceptional._ import vct.col.ast.statement.nonexecutable._ @@ -178,72 +179,75 @@ final case class IterationContract[G](requires: Expr[G], ensures: Expr[G], conte @family final case class IterVariable[G](variable: Variable[G], from: Expr[G], to: Expr[G])(implicit val o: Origin) extends NodeFamily[G] with IterVariableImpl[G] @family sealed trait Statement[G] extends NodeFamily[G] with StatementImpl[G] +sealed trait PurelySequentialStatement[G] extends Statement[G] +sealed trait ControlContainerStatement[G] extends Statement[G] +sealed trait ExpressionContainerStatement[G] extends Statement[G] with ExpressionContainerStatementImpl[G] -final case class PVLBranch[G](branches: Seq[(Expr[G], Statement[G])])(val blame: Blame[FrontendIfFailure])(implicit val o: Origin) extends Statement[G] with PVLBranchImpl[G] -final case class PVLLoop[G](init: Statement[G], cond: Expr[G], update: Statement[G], contract: LoopContract[G], body: Statement[G])(val blame: Blame[FrontEndLoopFailure])(implicit val o: Origin) extends Statement[G] with PVLLoopImpl[G] +final case class PVLBranch[G](branches: Seq[(Expr[G], Statement[G])])(val blame: Blame[FrontendIfFailure])(implicit val o: Origin) extends Statement[G] with ControlContainerStatement[G] with PVLBranchImpl[G] +final case class PVLLoop[G](init: Statement[G], cond: Expr[G], update: Statement[G], contract: LoopContract[G], body: Statement[G])(val blame: Blame[FrontEndLoopFailure])(implicit val o: Origin) extends Statement[G] with ControlContainerStatement[G] with PVLLoopImpl[G] sealed trait NonExecutableStatement[G] extends Statement[G] with NonExecutableStatementImpl[G] -final case class LocalDecl[G](local: Variable[G])(implicit val o: Origin) extends NonExecutableStatement[G] with LocalDeclImpl[G] -final case class SpecIgnoreStart[G]()(implicit val o: Origin) extends NonExecutableStatement[G] with SpecIgnoreStartImpl[G] -final case class SpecIgnoreEnd[G]()(implicit val o: Origin) extends NonExecutableStatement[G] with SpecIgnoreEndImpl[G] +final case class LocalDecl[G](local: Variable[G])(implicit val o: Origin) extends NonExecutableStatement[G] with PurelySequentialStatement[G] with LocalDeclImpl[G] +final case class SpecIgnoreStart[G]()(implicit val o: Origin) extends NonExecutableStatement[G] with PurelySequentialStatement[G] with SpecIgnoreStartImpl[G] +final case class SpecIgnoreEnd[G]()(implicit val o: Origin) extends NonExecutableStatement[G] with PurelySequentialStatement[G] with SpecIgnoreEndImpl[G] sealed trait NormallyCompletingStatement[G] extends Statement[G] with NormallyCompletingStatementImpl[G] final case class Assign[G](target: Expr[G], value: Expr[G])(val blame: Blame[AssignFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with AssignImpl[G] -final case class Send[G](decl: SendDecl[G], delta: BigInt, res: Expr[G])(val blame: Blame[SendFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with SendImpl[G] -final case class Recv[G](ref: Ref[G, SendDecl[G]])(implicit val o: Origin) extends NormallyCompletingStatement[G] with RecvImpl[G] +final case class Send[G](decl: SendDecl[G], delta: BigInt, res: Expr[G])(val blame: Blame[SendFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with SendImpl[G] +final case class Recv[G](ref: Ref[G, SendDecl[G]])(implicit val o: Origin) extends NormallyCompletingStatement[G] with PurelySequentialStatement[G] with RecvImpl[G] sealed trait SwitchCase[G] extends NormallyCompletingStatement[G] with SwitchCaseImpl[G] -final case class DefaultCase[G]()(implicit val o: Origin) extends SwitchCase[G] with DefaultCaseImpl[G] -final case class Case[G](pattern: Expr[G])(implicit val o: Origin) extends SwitchCase[G] with CaseImpl[G] -final case class Label[G](decl: LabelDecl[G], stat: Statement[G])(implicit val o: Origin) extends NormallyCompletingStatement[G] with LabelImpl[G] +final case class DefaultCase[G]()(implicit val o: Origin) extends SwitchCase[G] with PurelySequentialStatement[G] with DefaultCaseImpl[G] +final case class Case[G](pattern: Expr[G])(implicit val o: Origin) extends SwitchCase[G] with PurelySequentialStatement[G] with CaseImpl[G] +final case class Label[G](decl: LabelDecl[G], stat: Statement[G])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ControlContainerStatement[G] with LabelImpl[G] final case class Goto[G](lbl: Ref[G, LabelDecl[G]])(implicit val o: Origin) extends NormallyCompletingStatement[G] with GotoImpl[G] -final case class Exhale[G](res: Expr[G])(val blame: Blame[ExhaleFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExhaleImpl[G] -final case class Assert[G](res: Expr[G])(val blame: Blame[AssertFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with AssertImpl[G] -final case class Refute[G](assn: Expr[G])(val blame: Blame[RefuteFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with RefuteImpl[G] -final case class Inhale[G](res: Expr[G])(implicit val o: Origin) extends NormallyCompletingStatement[G] with InhaleImpl[G] -final case class Assume[G](assn: Expr[G])(implicit val o: Origin) extends NormallyCompletingStatement[G] with AssumeImpl[G] -final case class Instantiate[G](cls: Ref[G, Class[G]], out: Expr[G])(implicit val o: Origin) extends NormallyCompletingStatement[G] with InstantiateImpl[G] -final case class Wait[G](obj: Expr[G])(val blame: Blame[UnlockFailure])(implicit val o: Origin) extends NormallyCompletingStatement[G] with WaitImpl[G] -final case class Notify[G](obj: Expr[G])(val blame: Blame[NotifyFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with NotifyImpl[G] -final case class Fork[G](obj: Expr[G])(val blame: Blame[ForkFailure])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ForkImpl[G] -final case class Join[G](obj: Expr[G])(val blame: Blame[JoinFailure])(implicit val o: Origin) extends NormallyCompletingStatement[G] with JoinImpl[G] -final case class Lock[G](obj: Expr[G])(val blame: Blame[LockFailure])(implicit val o: Origin) extends NormallyCompletingStatement[G] with LockImpl[G] -final case class Unlock[G](obj: Expr[G])(val blame: Blame[UnlockFailure])(implicit val o: Origin) extends NormallyCompletingStatement[G] with UnlockImpl[G] -final case class Commit[G](obj: Expr[G])(val blame: Blame[CommitFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with CommitImpl[G] -final case class Fold[G](res: Expr[G])(val blame: Blame[FoldFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with FoldImpl[G] -final case class Unfold[G](res: Expr[G])(val blame: Blame[UnfoldFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with UnfoldImpl[G] -final case class WandApply[G](res: Expr[G])(val blame: Blame[WandApplyFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with WandApplyImpl[G] -final case class Havoc[G](loc: Expr[G])(implicit val o: Origin) extends NormallyCompletingStatement[G] with HavocImpl[G] -final case class FramedProof[G](pre: Expr[G], body: Statement[G], post: Expr[G])(val blame: Blame[FramedProofFailure])(implicit val o: Origin) extends NormallyCompletingStatement[G] with FramedProofImpl[G] -final case class Extract[G](contractedStatement: Statement[G])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExtractImpl[G] +final case class Exhale[G](res: Expr[G])(val blame: Blame[ExhaleFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with ExhaleImpl[G] +final case class Assert[G](res: Expr[G])(val blame: Blame[AssertFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with AssertImpl[G] +final case class Refute[G](assn: Expr[G])(val blame: Blame[RefuteFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with RefuteImpl[G] +final case class Inhale[G](res: Expr[G])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with InhaleImpl[G] +final case class Assume[G](assn: Expr[G])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with AssumeImpl[G] +final case class Instantiate[G](cls: Ref[G, Class[G]], out: Expr[G])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with InstantiateImpl[G] +final case class Wait[G](obj: Expr[G])(val blame: Blame[UnlockFailure])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with WaitImpl[G] +final case class Notify[G](obj: Expr[G])(val blame: Blame[NotifyFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with NotifyImpl[G] +final case class Fork[G](obj: Expr[G])(val blame: Blame[ForkFailure])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with ForkImpl[G] +final case class Join[G](obj: Expr[G])(val blame: Blame[JoinFailure])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with JoinImpl[G] +final case class Lock[G](obj: Expr[G])(val blame: Blame[LockFailure])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with LockImpl[G] +final case class Unlock[G](obj: Expr[G])(val blame: Blame[UnlockFailure])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with UnlockImpl[G] +final case class Commit[G](obj: Expr[G])(val blame: Blame[CommitFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with CommitImpl[G] +final case class Fold[G](res: Expr[G])(val blame: Blame[FoldFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with FoldImpl[G] +final case class Unfold[G](res: Expr[G])(val blame: Blame[UnfoldFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with UnfoldImpl[G] +final case class WandApply[G](res: Expr[G])(val blame: Blame[WandApplyFailed])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with WandApplyImpl[G] +final case class Havoc[G](loc: Expr[G])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ExpressionContainerStatement[G] with HavocImpl[G] +final case class FramedProof[G](pre: Expr[G], body: Statement[G], post: Expr[G])(val blame: Blame[FramedProofFailure])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ControlContainerStatement[G] with FramedProofImpl[G] +final case class Extract[G](contractedStatement: Statement[G])(implicit val o: Origin) extends NormallyCompletingStatement[G] with ControlContainerStatement[G] with ExtractImpl[G] sealed trait ExceptionalStatement[G] extends Statement[G] with ExceptionalStatementImpl[G] -final case class Eval[G](expr: Expr[G])(implicit val o: Origin) extends ExceptionalStatement[G] with EvalImpl[G] +final case class Eval[G](expr: Expr[G])(implicit val o: Origin) extends ExceptionalStatement[G] with ControlContainerStatement[G] with EvalImpl[G] sealed trait InvocationStatement[G] extends ExceptionalStatement[G] with InvokingNode[G] with InvocationStatementImpl[G] -final case class InvokeProcedure[G](ref: Ref[G, Procedure[G]], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends InvocationStatement[G] with InvokeProcedureImpl[G] -final case class InvokeConstructor[G](ref: Ref[G, Constructor[G]], out: Expr[G], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends InvocationStatement[G] with InvokeConstructorImpl[G] -final case class InvokeMethod[G](obj: Expr[G], ref: Ref[G, InstanceMethod[G]], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InstanceInvocationFailure])(implicit val o: Origin) extends InvocationStatement[G] with InstanceApply[G] with InvokeMethodImpl[G] +final case class InvokeProcedure[G](ref: Ref[G, Procedure[G]], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends InvocationStatement[G] with ControlContainerStatement[G] with InvokeProcedureImpl[G] +final case class InvokeConstructor[G](ref: Ref[G, Constructor[G]], out: Expr[G], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends InvocationStatement[G] with ControlContainerStatement[G] with InvokeConstructorImpl[G] +final case class InvokeMethod[G](obj: Expr[G], ref: Ref[G, InstanceMethod[G]], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InstanceInvocationFailure])(implicit val o: Origin) extends InvocationStatement[G] with InstanceApply[G] with ControlContainerStatement[G] with InvokeMethodImpl[G] final case class Return[G](result: Expr[G])(implicit val o: Origin) extends ExceptionalStatement[G] with ReturnImpl[G] final case class Throw[G](obj: Expr[G])(val blame: Blame[ThrowNull])(implicit val o: Origin) extends ExceptionalStatement[G] with ThrowImpl[G] final case class Break[G](label: Option[Ref[G, LabelDecl[G]]])(implicit val o: Origin) extends ExceptionalStatement[G] with BreakImpl[G] final case class Continue[G](label: Option[Ref[G, LabelDecl[G]]])(implicit val o: Origin) extends ExceptionalStatement[G] with ContinueImpl[G] sealed trait CompositeStatement[G] extends Statement[G] with CompositeStatementImpl[G] -final case class Block[G](statements: Seq[Statement[G]])(implicit val o: Origin) extends CompositeStatement[G] with BlockImpl[G] -@scopes[Variable] @scopes[CLocalDeclaration] @scopes[CPPLocalDeclaration] @scopes[JavaLocalDeclaration] final case class Scope[G](locals: Seq[Variable[G]], body: Statement[G])(implicit val o: Origin) extends CompositeStatement[G] with ScopeImpl[G] -final case class Branch[G](branches: Seq[(Expr[G], Statement[G])])(implicit val o: Origin) extends CompositeStatement[G] with BranchImpl[G] -final case class IndetBranch[G](branches: Seq[Statement[G]])(implicit val o: Origin) extends CompositeStatement[G] with IndetBranchImpl[G] -final case class Switch[G](expr: Expr[G], body: Statement[G])(implicit val o: Origin) extends CompositeStatement[G] with SwitchImpl[G] -@scopes[SendDecl] final case class Loop[G](init: Statement[G], cond: Expr[G], update: Statement[G], contract: LoopContract[G], body: Statement[G])(implicit val o: Origin) extends CompositeStatement[G] with LoopImpl[G] -@scopes[Variable] final case class RangedFor[G](iter: IterVariable[G], contract: LoopContract[G], body: Statement[G])(implicit val o: Origin) extends CompositeStatement[G] with Declarator[G] with RangedForImpl[G] -final case class TryCatchFinally[G](body: Statement[G], after: Statement[G], catches: Seq[CatchClause[G]])(implicit val o: Origin) extends CompositeStatement[G] with TryCatchFinallyImpl[G] -final case class Synchronized[G](obj: Expr[G], body: Statement[G])(val blame: Blame[LockRegionFailure])(implicit val o: Origin) extends CompositeStatement[G] with SynchronizedImpl[G] -@scopes[ParInvariantDecl] final case class ParInvariant[G](decl: ParInvariantDecl[G], inv: Expr[G], content: Statement[G])(val blame: Blame[ParInvariantNotEstablished])(implicit val o: Origin) extends CompositeStatement[G] with ParInvariantImpl[G] -final case class ParAtomic[G](inv: Seq[Ref[G, ParInvariantDecl[G]]], content: Statement[G])(val blame: Blame[ParInvariantNotMaintained])(implicit val o: Origin) extends CompositeStatement[G] with ParAtomicImpl[G] -final case class ParBarrier[G](block: Ref[G, ParBlockDecl[G]], invs: Seq[Ref[G, ParInvariantDecl[G]]], requires: Expr[G], ensures: Expr[G], content: Statement[G])(val blame: Blame[ParBarrierFailure])(implicit val o: Origin) extends CompositeStatement[G] with ParBarrierImpl[G] -final case class ParStatement[G](impl: ParRegion[G])(implicit val o: Origin) extends CompositeStatement[G] with ParStatementImpl[G] -@scopes[Variable] final case class VecBlock[G](iters: Seq[IterVariable[G]], requires: Expr[G], ensures: Expr[G], content: Statement[G])(implicit val o: Origin) extends CompositeStatement[G] with VecBlockImpl[G] -final case class WandPackage[G](res: Expr[G], proof: Statement[G])(val blame: Blame[PackageFailure])(implicit val o: Origin) extends CompositeStatement[G] with WandPackageImpl[G] -final case class ModelDo[G](model: Expr[G], perm: Expr[G], after: Expr[G], action: Expr[G], impl: Statement[G])(implicit val o: Origin) extends CompositeStatement[G] with ModelDoImpl[G] +final case class Block[G](statements: Seq[Statement[G]])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with BlockImpl[G] +@scopes[Variable] @scopes[CLocalDeclaration] @scopes[CPPLocalDeclaration] @scopes[JavaLocalDeclaration] final case class Scope[G](locals: Seq[Variable[G]], body: Statement[G])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with ScopeImpl[G] +final case class Branch[G](branches: Seq[(Expr[G], Statement[G])])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with BranchImpl[G] +final case class IndetBranch[G](branches: Seq[Statement[G]])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with IndetBranchImpl[G] +final case class Switch[G](expr: Expr[G], body: Statement[G])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with SwitchImpl[G] +@scopes[SendDecl] final case class Loop[G](init: Statement[G], cond: Expr[G], update: Statement[G], contract: LoopContract[G], body: Statement[G])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with LoopImpl[G] +@scopes[Variable] final case class RangedFor[G](iter: IterVariable[G], contract: LoopContract[G], body: Statement[G])(implicit val o: Origin) extends CompositeStatement[G] with Declarator[G] with ControlContainerStatement[G] with RangedForImpl[G] +final case class TryCatchFinally[G](body: Statement[G], after: Statement[G], catches: Seq[CatchClause[G]])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with TryCatchFinallyImpl[G] +final case class Synchronized[G](obj: Expr[G], body: Statement[G])(val blame: Blame[LockRegionFailure])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with SynchronizedImpl[G] +@scopes[ParInvariantDecl] final case class ParInvariant[G](decl: ParInvariantDecl[G], inv: Expr[G], content: Statement[G])(val blame: Blame[ParInvariantNotEstablished])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with ParInvariantImpl[G] +final case class ParAtomic[G](inv: Seq[Ref[G, ParInvariantDecl[G]]], content: Statement[G])(val blame: Blame[ParInvariantNotMaintained])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with ParAtomicImpl[G] +final case class ParBarrier[G](block: Ref[G, ParBlockDecl[G]], invs: Seq[Ref[G, ParInvariantDecl[G]]], requires: Expr[G], ensures: Expr[G], content: Statement[G])(val blame: Blame[ParBarrierFailure])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with ParBarrierImpl[G] +final case class ParStatement[G](impl: ParRegion[G])(implicit val o: Origin) extends CompositeStatement[G] with PurelySequentialStatement[G] with ParStatementImpl[G] +@scopes[Variable] final case class VecBlock[G](iters: Seq[IterVariable[G]], requires: Expr[G], ensures: Expr[G], content: Statement[G])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with VecBlockImpl[G] +final case class WandPackage[G](res: Expr[G], proof: Statement[G])(val blame: Blame[PackageFailure])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with WandPackageImpl[G] +final case class ModelDo[G](model: Expr[G], perm: Expr[G], after: Expr[G], action: Expr[G], impl: Statement[G])(implicit val o: Origin) extends CompositeStatement[G] with ControlContainerStatement[G] with ModelDoImpl[G] @family sealed trait GlobalDeclaration[G] extends Declaration[G] with GlobalDeclarationImpl[G] final class HeapVariable[G](val t: Type[G])(implicit val o: Origin) extends GlobalDeclaration[G] with HeapVariableImpl[G] @@ -978,7 +982,7 @@ sealed trait CAbstractDeclaration[G] extends GlobalDeclaration[G] with CAbstract } sealed trait CStatement[G] extends Statement[G] with CStatementImpl[G] -final case class CDeclarationStatement[G](decl: CLocalDeclaration[G])(implicit val o: Origin) extends CStatement[G] with CDeclarationStatementImpl[G] +final case class CDeclarationStatement[G](decl: CLocalDeclaration[G])(implicit val o: Origin) extends CStatement[G] with PurelySequentialStatement[G] with CDeclarationStatementImpl[G] final case class CGoto[G](label: String)(implicit val o: Origin) extends CStatement[G] with CGotoImpl[G] { var ref: Option[LabelDecl[G]] = None } @@ -1072,8 +1076,8 @@ sealed trait CPPAbstractDeclaration[G] extends GlobalDeclaration[G] with CPPAbst } sealed trait CPPStatement[G] extends Statement[G] with CPPStatementImpl[G] -final case class CPPDeclarationStatement[G](decl: CPPLocalDeclaration[G])(implicit val o: Origin) extends CPPStatement[G] with CPPDeclarationStatementImpl[G] -final case class CPPLifetimeScope[G](body: Statement[G])(implicit val o: Origin) extends CPPStatement[G] with CPPLifetimeScopeImpl[G] +final case class CPPDeclarationStatement[G](decl: CPPLocalDeclaration[G])(implicit val o: Origin) extends CPPStatement[G] with PurelySequentialStatement[G] with CPPDeclarationStatementImpl[G] +final case class CPPLifetimeScope[G](body: Statement[G])(implicit val o: Origin) extends CPPStatement[G] with ControlContainerStatement[G] with CPPLifetimeScopeImpl[G] sealed trait CPPExpr[G] extends Expr[G] with CPPExprImpl[G] final case class CPPLocal[G](name: String, genericArgs: Seq[CPPExprOrTypeSpecifier[G]])(val blame: Blame[DerefInsufficientPermission])(implicit val o: Origin) extends CPPExpr[G] with CPPLocalImpl[G] { @@ -1170,7 +1174,7 @@ final class JavaFields[G](val modifiers: Seq[JavaModifier[G]], val t: Type[G], v @family final class JavaLocalDeclaration[G](val modifiers: Seq[JavaModifier[G]], val t: Type[G], val decls: Seq[JavaVariableDeclaration[G]])(implicit val o: Origin) extends Declaration[G] with JavaLocalDeclarationImpl[G] sealed trait JavaStatement[G] extends Statement[G] with JavaStatementImpl[G] -final case class JavaLocalDeclarationStatement[G](decl: JavaLocalDeclaration[G])(implicit val o: Origin) extends JavaStatement[G] with JavaLocalDeclarationStatementImpl[G] +final case class JavaLocalDeclarationStatement[G](decl: JavaLocalDeclaration[G])(implicit val o: Origin) extends JavaStatement[G] with PurelySequentialStatement[G] with JavaLocalDeclarationStatementImpl[G] sealed trait JavaType[G] extends Type[G] with JavaTypeImpl[G] final case class JavaNamedType[G](names: Seq[(String, Option[Seq[Type[G]]])])(implicit val o: Origin) extends JavaType[G] with JavaNamedTypeImpl[G] { @@ -1334,8 +1338,8 @@ case class PVLFamilyRange[G](family: String, binder: String, start: Expr[G], end @family case class PVLAccess[G](subject: PVLSubject[G], field: String)(val blame: Blame[PVLAccessFailure])(implicit val o: Origin) extends NodeFamily[G] with PVLAccessImpl[G] { var ref: Option[RefField[G]] = None } -case class PVLCommunicate[G](sender: PVLAccess[G], receiver: PVLAccess[G])(val blame: Blame[PVLCommunicateFailure])(implicit val o: Origin) extends Statement[G] with PVLCommunicateImpl[G] -final case class PVLSeqAssign[G](receiver: Ref[G, PVLEndpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(val blame: Blame[PVLSeqAssignFailure])(implicit val o: Origin) extends Statement[G] with PVLSeqAssignImpl[G] +case class PVLCommunicate[G](sender: PVLAccess[G], receiver: PVLAccess[G])(val blame: Blame[PVLCommunicateFailure])(implicit val o: Origin) extends Statement[G] with PurelySequentialStatement[G] with PVLCommunicateImpl[G] +final case class PVLSeqAssign[G](receiver: Ref[G, PVLEndpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(val blame: Blame[PVLSeqAssignFailure])(implicit val o: Origin) extends Statement[G] with ExpressionContainerStatement[G] with PVLSeqAssignImpl[G] @family final class Endpoint[G](val cls: Ref[G, Class[G]], val constructor: Ref[G, Constructor[G]], val args: Seq[Expr[G]])(val blame: Blame[EndpointFailure])(implicit val o: Origin) extends Declaration[G] with EndpointImpl[G] @scopes[Endpoint] final class SeqProg[G](val contract: ApplicableContract[G], val args : Seq[Variable[G]], val endpoints: Seq[Endpoint[G]], val run: SeqRun[G], val decls: Seq[ClassDeclaration[G]])(val blame: Blame[SeqCallableFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with SeqProgImpl[G] @@ -1343,23 +1347,23 @@ final case class PVLSeqAssign[G](receiver: Ref[G, PVLEndpoint[G]], field: Ref[G, @family sealed trait Subject[G] extends NodeFamily[G] with SubjectImpl[G] final case class EndpointName[G](ref: Ref[G, Endpoint[G]])(implicit val o: Origin) extends Subject[G] with EndpointNameImpl[G] @family case class Access[G](subject: Subject[G], field: Ref[G, InstanceField[G]])(val blame: Blame[AccessFailure])(implicit val o: Origin) extends NodeFamily[G] with AccessImpl[G] -final case class Communicate[G](receiver: Access[G], sender: Access[G])(val blame: Blame[CommunicateFailure])(implicit val o: Origin) extends Statement[G] with CommunicateImpl[G] +final case class Communicate[G](receiver: Access[G], sender: Access[G])(val blame: Blame[CommunicateFailure])(implicit val o: Origin) extends Statement[G] with PurelySequentialStatement[G] with CommunicateImpl[G] -final case class SeqAssign[G](receiver: Ref[G, Endpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(val blame: Blame[SeqAssignFailure])(implicit val o: Origin) extends Statement[G] with SeqAssignImpl[G] +final case class SeqAssign[G](receiver: Ref[G, Endpoint[G]], field: Ref[G, InstanceField[G]], value: Expr[G])(val blame: Blame[SeqAssignFailure])(implicit val o: Origin) extends Statement[G] with ExpressionContainerStatement[G] with SeqAssignImpl[G] final case class EndpointUse[G](ref: Ref[G, Endpoint[G]])(implicit val o: Origin) extends Expr[G] with EndpointUseImpl[G] -final case class UnresolvedSeqBranch[G](branches: Seq[(Expr[G], Statement[G])])(val blame: Blame[SeqBranchFailure])(implicit val o: Origin) extends Statement[G] with UnresolvedSeqBranchImpl[G] -final case class UnresolvedSeqLoop[G](cond: Expr[G], contract: LoopContract[G], body: Statement[G])(val blame: Blame[SeqLoopFailure])(implicit val o: Origin) extends Statement[G] with UnresolvedSeqLoopImpl[G] +final case class UnresolvedSeqBranch[G](branches: Seq[(Expr[G], Statement[G])])(val blame: Blame[SeqBranchFailure])(implicit val o: Origin) extends Statement[G] with ControlContainerStatement[G] with UnresolvedSeqBranchImpl[G] +final case class UnresolvedSeqLoop[G](cond: Expr[G], contract: LoopContract[G], body: Statement[G])(val blame: Blame[SeqLoopFailure])(implicit val o: Origin) extends Statement[G] with ControlContainerStatement[G] with UnresolvedSeqLoopImpl[G] @family sealed trait SeqGuard[G] extends NodeFamily[G] with SeqGuardImpl[G] final case class EndpointGuard[G](endpoint: Ref[G, Endpoint[G]], condition: Expr[G])(implicit val o: Origin) extends SeqGuard[G] with EndpointGuardImpl[G] final case class UnpointedGuard[G](condition: Expr[G])(implicit val o: Origin) extends SeqGuard[G] with UnpointedGuardImpl[G] -final case class SeqBranch[G](guards: Seq[SeqGuard[G]], yes: Statement[G], no: Option[Statement[G]])(val blame: Blame[SeqBranchFailure])(implicit val o: Origin) extends Statement[G] with SeqBranchImpl[G] -final case class SeqLoop[G](guards: Seq[SeqGuard[G]], contract: LoopContract[G], body: Statement[G])(val blame: Blame[SeqLoopFailure])(implicit val o: Origin) extends Statement[G] with SeqLoopImpl[G] +final case class SeqBranch[G](guards: Seq[SeqGuard[G]], yes: Statement[G], no: Option[Statement[G]])(val blame: Blame[SeqBranchFailure])(implicit val o: Origin) extends Statement[G] with ControlContainerStatement[G] with SeqBranchImpl[G] +final case class SeqLoop[G](guards: Seq[SeqGuard[G]], contract: LoopContract[G], body: Statement[G])(val blame: Blame[SeqLoopFailure])(implicit val o: Origin) extends Statement[G] with ControlContainerStatement[G] with SeqLoopImpl[G] -final case class VeyMontAssignExpression[G](endpoint: Ref[G, Endpoint[G]], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with VeyMontAssignExpressionImpl[G] -final case class CommunicateX[G](receiver: Ref[G, Endpoint[G]], sender: Ref[G, Endpoint[G]], chanType: Type[G], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with CommunicateXImpl[G] +final case class VeyMontAssignExpression[G](endpoint: Ref[G, Endpoint[G]], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with ControlContainerStatement[G] with VeyMontAssignExpressionImpl[G] +final case class CommunicateX[G](receiver: Ref[G, Endpoint[G]], sender: Ref[G, Endpoint[G]], chanType: Type[G], assign: Statement[G])(implicit val o: Origin) extends Statement[G] with ControlContainerStatement[G] with CommunicateXImpl[G] sealed trait SilverExpr[G] extends Expr[G] with SilverExprImpl[G] final case class SilverDeref[G](obj: Expr[G], field: Ref[G, SilverField[G]])(val blame: Blame[InsufficientPermission])(implicit val o: Origin) extends SilverExpr[G] with HeapDeref[G] with SilverDerefImpl[G] @@ -1379,11 +1383,11 @@ final case class SilverPartialADTFunctionInvocation[G](name: String, args: Seq[E final case class SilverUntypedNonemptyLiteralMap[G](values: Seq[(Expr[G], Expr[G])])(implicit val o: Origin) extends SilverExpr[G] with SilverUntypedNonemptyLiteralMapImpl[G] sealed trait SilverStatement[G] extends Statement[G] with SilverStatementImpl[G] -final case class SilverNewRef[G](v: Ref[G, Variable[G]], fields: Seq[Ref[G, SilverField[G]]])(implicit val o: Origin) extends SilverStatement[G] with SilverNewRefImpl[G] +final case class SilverNewRef[G](v: Ref[G, Variable[G]], fields: Seq[Ref[G, SilverField[G]]])(implicit val o: Origin) extends SilverStatement[G] with PurelySequentialStatement[G] with SilverNewRefImpl[G] sealed trait SilverAssign[G] extends SilverStatement[G] with SilverAssignImpl[G] -final case class SilverFieldAssign[G](obj: Expr[G], field: Ref[G, SilverField[G]], value: Expr[G])(val blame: Blame[AssignFailed])(implicit val o: Origin) extends SilverAssign[G] with SilverFieldAssignImpl[G] -final case class SilverLocalAssign[G](v: Ref[G, Variable[G]], value: Expr[G])(implicit val o: Origin) extends SilverAssign[G] with SilverLocalAssignImpl[G] +final case class SilverFieldAssign[G](obj: Expr[G], field: Ref[G, SilverField[G]], value: Expr[G])(val blame: Blame[AssignFailed])(implicit val o: Origin) extends SilverAssign[G] with ExpressionContainerStatement[G] with SilverFieldAssignImpl[G] +final case class SilverLocalAssign[G](v: Ref[G, Variable[G]], value: Expr[G])(implicit val o: Origin) extends SilverAssign[G] with ExpressionContainerStatement[G] with SilverLocalAssignImpl[G] sealed trait SilverDeclaration[G] extends GlobalDeclaration[G] with SilverDeclarationImpl[G] final class SilverField[G](val t: Type[G])(implicit val o: Origin) extends SilverDeclaration[G] with SilverFieldImpl[G] diff --git a/src/col/vct/col/ast/lang/silver/SilverFieldAssignImpl.scala b/src/col/vct/col/ast/lang/silver/SilverFieldAssignImpl.scala index d99657c937..5483a0f410 100644 --- a/src/col/vct/col/ast/lang/silver/SilverFieldAssignImpl.scala +++ b/src/col/vct/col/ast/lang/silver/SilverFieldAssignImpl.scala @@ -1,10 +1,12 @@ package vct.col.ast.lang.silver -import vct.col.ast.SilverFieldAssign +import vct.col.ast.{Expr, SilverFieldAssign} import vct.col.print.{Ctx, Doc, Precedence, Text} import vct.col.ast.ops.SilverFieldAssignOps trait SilverFieldAssignImpl[G] extends SilverFieldAssignOps[G] { this: SilverFieldAssign[G] => override def layout(implicit ctx: Ctx): Doc = obj.bind(Precedence.POSTFIX) <> "." <> ctx.name(field) <+> ":=" <+> value + + override def expr: Expr[G] = this.value } \ No newline at end of file diff --git a/src/col/vct/col/ast/lang/silver/SilverLocalAssignImpl.scala b/src/col/vct/col/ast/lang/silver/SilverLocalAssignImpl.scala index 95f26cb5fd..432549a54a 100644 --- a/src/col/vct/col/ast/lang/silver/SilverLocalAssignImpl.scala +++ b/src/col/vct/col/ast/lang/silver/SilverLocalAssignImpl.scala @@ -1,10 +1,12 @@ package vct.col.ast.lang.silver -import vct.col.ast.SilverLocalAssign +import vct.col.ast.{Expr, SilverLocalAssign} import vct.col.print.{Ctx, Doc, Text} import vct.col.ast.ops.SilverLocalAssignOps trait SilverLocalAssignImpl[G] extends SilverLocalAssignOps[G] { this: SilverLocalAssign[G] => override def layout(implicit ctx: Ctx): Doc = Text(ctx.name(v)) <+> ":=" <+> value + + override def expr: Expr[G] = this.value } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/behavior/ExpressionContainerStatementImpl.scala b/src/col/vct/col/ast/statement/behavior/ExpressionContainerStatementImpl.scala new file mode 100644 index 0000000000..b9e503bccb --- /dev/null +++ b/src/col/vct/col/ast/statement/behavior/ExpressionContainerStatementImpl.scala @@ -0,0 +1,8 @@ +package vct.col.ast.statement.behavior + +import vct.col.ast.{Expr, ExpressionContainerStatement} +import vct.col.ast.statement.StatementImpl + +trait ExpressionContainerStatementImpl[G] extends StatementImpl[G] { this: ExpressionContainerStatement[G] => + def expr: Expr[G] +} diff --git a/src/col/vct/col/ast/statement/terminal/AssertImpl.scala b/src/col/vct/col/ast/statement/terminal/AssertImpl.scala index fa2d2991ca..c34211c664 100644 --- a/src/col/vct/col/ast/statement/terminal/AssertImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/AssertImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Assert +import vct.col.ast.{Assert, Expr} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.AssertOps @@ -15,4 +15,6 @@ trait AssertImpl[G] extends AssertOps[G] { this: Assert[G] => case Ctx.Silver => layoutSilver case _ => Doc.inlineSpec(Show.lazily(layoutSpec(_))) } + + override def expr: Expr[G] = this.res } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/AssumeImpl.scala b/src/col/vct/col/ast/statement/terminal/AssumeImpl.scala index 998a03b3b1..0c6ebb0227 100644 --- a/src/col/vct/col/ast/statement/terminal/AssumeImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/AssumeImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Assume +import vct.col.ast.{Assume, Expr} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.AssumeOps @@ -15,4 +15,6 @@ trait AssumeImpl[G] extends AssumeOps[G] { this: Assume[G] => case Ctx.Silver => layoutSilver case _ => Doc.inlineSpec(Show.lazily(layoutSpec(_))) } + + override def expr: Expr[G] = this.assn } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/CommitImpl.scala b/src/col/vct/col/ast/statement/terminal/CommitImpl.scala index 3d5b56a99e..58260e1576 100644 --- a/src/col/vct/col/ast/statement/terminal/CommitImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/CommitImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Commit +import vct.col.ast.{Commit, Expr} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.CommitOps @@ -9,4 +9,6 @@ trait CommitImpl[G] extends CommitOps[G] { this: Commit[G] => Text("commit") <+> obj <> ";" override def layout(implicit ctx: Ctx): Doc = Doc.inlineSpec(Show.lazily(layoutSpec(_))) + + override def expr: Expr[G] = this.obj } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/ExhaleImpl.scala b/src/col/vct/col/ast/statement/terminal/ExhaleImpl.scala index cc4b9f02ce..a4f547867f 100644 --- a/src/col/vct/col/ast/statement/terminal/ExhaleImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/ExhaleImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Exhale +import vct.col.ast.{Exhale, Expr} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.ExhaleOps @@ -15,4 +15,6 @@ trait ExhaleImpl[G] extends ExhaleOps[G] { this: Exhale[G] => case Ctx.Silver => layoutSilver case _ => Doc.inlineSpec(Show.lazily(layoutSpec(_))) } + + override def expr: Expr[G] = this.res } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/FoldImpl.scala b/src/col/vct/col/ast/statement/terminal/FoldImpl.scala index ca666aae69..ace25ef36f 100644 --- a/src/col/vct/col/ast/statement/terminal/FoldImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/FoldImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Fold +import vct.col.ast.{Expr, Fold} import vct.col.ast.node.NodeFamilyImpl import vct.col.ast.util.CheckFoldUnfoldTarget import vct.col.print.{Ctx, Doc, Show, Text} @@ -17,4 +17,6 @@ trait FoldImpl[G] extends NodeFamilyImpl[G] with CheckFoldUnfoldTarget[G] with F case Ctx.Silver => layoutSilver case _ => Doc.inlineSpec(Show.lazily(layoutSpec(_))) } + + override def expr: Expr[G] = this.res } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/ForkImpl.scala b/src/col/vct/col/ast/statement/terminal/ForkImpl.scala index e5852ce79b..6ae45b80c8 100644 --- a/src/col/vct/col/ast/statement/terminal/ForkImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/ForkImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Fork +import vct.col.ast.{Expr, Fork} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.ForkOps @@ -9,4 +9,6 @@ trait ForkImpl[G] extends ForkOps[G] { this: Fork[G] => Text("fork") <+> obj <> ";" override def layout(implicit ctx: Ctx): Doc = Doc.inlineSpec(Show.lazily(layoutSpec(_))) + + override def expr: Expr[G] = this.obj } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/HavocImpl.scala b/src/col/vct/col/ast/statement/terminal/HavocImpl.scala index b3a546b71c..eb346ef388 100644 --- a/src/col/vct/col/ast/statement/terminal/HavocImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/HavocImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Havoc +import vct.col.ast.{Expr, Havoc} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.HavocOps @@ -9,4 +9,6 @@ trait HavocImpl[G] extends HavocOps[G] { this: Havoc[G] => Text("havoc") <+> loc <> ";" override def layout(implicit ctx: Ctx): Doc = Doc.inlineSpec(Show.lazily(layoutSpec(_))) + + override def expr: Expr[G] = this.loc } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/InhaleImpl.scala b/src/col/vct/col/ast/statement/terminal/InhaleImpl.scala index 47693b025e..0a4dfcf00a 100644 --- a/src/col/vct/col/ast/statement/terminal/InhaleImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/InhaleImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Inhale +import vct.col.ast.{Expr, Inhale} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.InhaleOps @@ -15,4 +15,6 @@ trait InhaleImpl[G] extends InhaleOps[G] { this: Inhale[G] => case Ctx.Silver => layoutSilver case _ => Doc.inlineSpec(Show.lazily(layoutSpec(_))) } + + override def expr: Expr[G] = this.res } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/JoinImpl.scala b/src/col/vct/col/ast/statement/terminal/JoinImpl.scala index 1904f6702d..3d7f3f9bde 100644 --- a/src/col/vct/col/ast/statement/terminal/JoinImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/JoinImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Join +import vct.col.ast.{Expr, Join} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.JoinOps @@ -9,4 +9,6 @@ trait JoinImpl[G] extends JoinOps[G] { this: Join[G] => Text("join") <+> obj <> ";" override def layout(implicit ctx: Ctx): Doc = Doc.inlineSpec(Show.lazily(layoutSpec(_))) + + override def expr: Expr[G] = this.obj } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/LockImpl.scala b/src/col/vct/col/ast/statement/terminal/LockImpl.scala index 6962fcae94..6a864ee3f7 100644 --- a/src/col/vct/col/ast/statement/terminal/LockImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/LockImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Lock +import vct.col.ast.{Expr, Lock} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.LockOps @@ -9,4 +9,6 @@ trait LockImpl[G] extends LockOps[G] { this: Lock[G] => Text("lock") <+> obj <> ";" override def layout(implicit ctx: Ctx): Doc = Doc.inlineSpec(Show.lazily(layoutSpec(_))) + + override def expr: Expr[G] = this.obj } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/NotifyImpl.scala b/src/col/vct/col/ast/statement/terminal/NotifyImpl.scala index 8803df8c7e..28a79fd47a 100644 --- a/src/col/vct/col/ast/statement/terminal/NotifyImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/NotifyImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Notify +import vct.col.ast.{Expr, Notify} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.NotifyOps @@ -9,4 +9,6 @@ trait NotifyImpl[G] extends NotifyOps[G] { this: Notify[G] => Text("notify") <+> obj <> ";" override def layout(implicit ctx: Ctx): Doc = Doc.inlineSpec(Show.lazily(layoutSpec(_))) + + override def expr: Expr[G] = this.obj } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/RefuteImpl.scala b/src/col/vct/col/ast/statement/terminal/RefuteImpl.scala index b88f33c6a6..957e4ed4e4 100644 --- a/src/col/vct/col/ast/statement/terminal/RefuteImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/RefuteImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Refute +import vct.col.ast.{Expr, Refute} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.RefuteOps @@ -15,4 +15,6 @@ trait RefuteImpl[G] extends RefuteOps[G] { this: Refute[G] => case Ctx.Silver => layoutSilver case _ => Doc.inlineSpec(Show.lazily(layoutSpec(_))) } + + override def expr: Expr[G] = this.assn } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/SendImpl.scala b/src/col/vct/col/ast/statement/terminal/SendImpl.scala index e596a220f3..fd007d8340 100644 --- a/src/col/vct/col/ast/statement/terminal/SendImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/SendImpl.scala @@ -1,7 +1,7 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Send -import vct.col.print.{Ctx, Doc, Show, Text, Group} +import vct.col.ast.{Expr, Send} +import vct.col.print.{Ctx, Doc, Group, Show, Text} import vct.col.ast.ops.SendOps trait SendImpl[G] extends SendOps[G] { this: Send[G] => @@ -10,4 +10,6 @@ trait SendImpl[G] extends SendOps[G] { this: Send[G] => override def layout(implicit ctx: Ctx): Doc = Doc.inlineSpec(Show.lazily(layoutSpec(_))) + + override def expr: Expr[G] = this.res } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/UnfoldImpl.scala b/src/col/vct/col/ast/statement/terminal/UnfoldImpl.scala index f2b3e9602d..759e81c9c5 100644 --- a/src/col/vct/col/ast/statement/terminal/UnfoldImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/UnfoldImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Unfold +import vct.col.ast.{Expr, Unfold} import vct.col.ast.node.NodeFamilyImpl import vct.col.ast.util.CheckFoldUnfoldTarget import vct.col.print.{Ctx, Doc, Show, Text} @@ -17,4 +17,6 @@ trait UnfoldImpl[G] extends NodeFamilyImpl[G] with CheckFoldUnfoldTarget[G] with case Ctx.Silver => layoutSilver case _ => Doc.inlineSpec(Show.lazily(layoutSpec(_))) } + + override def expr: Expr[G] = this.res } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/UnlockImpl.scala b/src/col/vct/col/ast/statement/terminal/UnlockImpl.scala index 0f80ea4c44..3f4f2acc23 100644 --- a/src/col/vct/col/ast/statement/terminal/UnlockImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/UnlockImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Unlock +import vct.col.ast.{Expr, Unlock} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.UnlockOps @@ -9,4 +9,6 @@ trait UnlockImpl[G] extends UnlockOps[G] { this: Unlock[G] => Text("unlock") <+> obj <> ";" override def layout(implicit ctx: Ctx): Doc = Doc.inlineSpec(Show.lazily(layoutSpec(_))) + + override def expr: Expr[G] = this.obj } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/WaitImpl.scala b/src/col/vct/col/ast/statement/terminal/WaitImpl.scala index 340b36d14c..317a91b89c 100644 --- a/src/col/vct/col/ast/statement/terminal/WaitImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/WaitImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.Wait +import vct.col.ast.{Expr, Wait} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.WaitOps @@ -9,4 +9,6 @@ trait WaitImpl[G] extends WaitOps[G] { this: Wait[G] => Text("wait") <+> obj <> ";" override def layout(implicit ctx: Ctx): Doc = Doc.inlineSpec(Show.lazily(layoutSpec(_))) + + override def expr: Expr[G] = this.obj } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/terminal/WandApplyImpl.scala b/src/col/vct/col/ast/statement/terminal/WandApplyImpl.scala index 0e1e225e60..332ca1d490 100644 --- a/src/col/vct/col/ast/statement/terminal/WandApplyImpl.scala +++ b/src/col/vct/col/ast/statement/terminal/WandApplyImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.terminal -import vct.col.ast.WandApply +import vct.col.ast.{Expr, WandApply} import vct.col.print.{Ctx, Doc, Show, Text} import vct.col.ast.ops.WandApplyOps @@ -12,4 +12,6 @@ trait WandApplyImpl[G] extends WandApplyOps[G] { this: WandApply[G] => case Ctx.Silver => Text("apply") <+> res case _ => Doc.inlineSpec(Show.lazily(layoutSpec(_))) } + + override def expr: Expr[G] = this.res } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/veymont/PVLSeqAssignImpl.scala b/src/col/vct/col/ast/statement/veymont/PVLSeqAssignImpl.scala index 13ceba06dc..9dcbfaa67a 100644 --- a/src/col/vct/col/ast/statement/veymont/PVLSeqAssignImpl.scala +++ b/src/col/vct/col/ast/statement/veymont/PVLSeqAssignImpl.scala @@ -1,10 +1,12 @@ package vct.col.ast.statement.veymont -import vct.col.ast.PVLSeqAssign +import vct.col.ast.{Expr, PVLSeqAssign} import vct.col.print.{Ctx, Doc, Group, Text} import vct.col.ast.ops.PVLSeqAssignOps trait PVLSeqAssignImpl[G] extends PVLSeqAssignOps[G] { this: PVLSeqAssign[G] => override def layout(implicit ctx: Ctx): Doc = Group(Text(receiver.decl.name) <> "." <> ctx.name(field) <+> ":=" <+> value.show) + + override def expr: Expr[G] = this.value } diff --git a/src/col/vct/col/ast/statement/veymont/SeqAssignImpl.scala b/src/col/vct/col/ast/statement/veymont/SeqAssignImpl.scala index 4ea2a6f88d..6f19873baf 100644 --- a/src/col/vct/col/ast/statement/veymont/SeqAssignImpl.scala +++ b/src/col/vct/col/ast/statement/veymont/SeqAssignImpl.scala @@ -1,6 +1,6 @@ package vct.col.ast.statement.veymont -import vct.col.ast.{Endpoint, SeqAssign} +import vct.col.ast.{Endpoint, Expr, SeqAssign} import vct.col.ast.statement.StatementImpl import vct.col.check.{CheckContext, CheckError, SeqProgParticipant} import vct.col.print.{Ctx, Doc, Group, Text} @@ -18,4 +18,6 @@ trait SeqAssignImpl[G] extends StatementImpl[G] with SeqAssignOps[G] { this: Seq if (!context.currentParticipatingEndpoints.get.contains(this.receiver.decl)) Seq(SeqProgParticipant(this)) else Nil + + override def expr: Expr[G] = this.value } diff --git a/src/col/vct/col/ast/unsorted/InstantiateImpl.scala b/src/col/vct/col/ast/unsorted/InstantiateImpl.scala index 824bcb5862..85fadd5742 100644 --- a/src/col/vct/col/ast/unsorted/InstantiateImpl.scala +++ b/src/col/vct/col/ast/unsorted/InstantiateImpl.scala @@ -1,9 +1,10 @@ package vct.col.ast.unsorted -import vct.col.ast.Instantiate +import vct.col.ast.{Expr, Instantiate} import vct.col.ast.ops.InstantiateOps import vct.col.print._ trait InstantiateImpl[G] extends InstantiateOps[G] { this: Instantiate[G] => // override def layout(implicit ctx: Ctx): Doc = ??? + override def expr: Expr[G] = this.out } diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 6688578347..214ce28d26 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -38,53 +38,21 @@ case class CFGGenerator[G]() { } private def find_successors(node: Statement[G], context: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = node match { - case PVLBranch(_) => evaluate_first(context.enter_scope(node)) - case PVLLoop(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) - // NonExecutableStatement - case LocalDecl(_) => sequential_successor(context) - case SpecIgnoreStart() => sequential_successor(context) - case SpecIgnoreEnd() => sequential_successor(context) - // NormallyCompletingStatement + // Exceptional statements case Assign(_, _) => context.indices.head match { case AssignmentIndex(a, _) if a == node => sequential_successor(context) case _ => evaluate_first(context.enter_scope(node)) } - case Send(_, _, res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) - case Recv(_) => sequential_successor(context) - case DefaultCase() => sequential_successor(context) - case Case(_) => sequential_successor(context) - case Label(_, _) => evaluate_first(context.enter_scope(node)) case Goto(lbl) => found_labels.get(lbl.decl) match { case Some(node) => mutable.Set(CFGEdge(node, None)) case None => mutable.Set() } - case Exhale(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) - case Assert(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) - case Refute(assn) => handle_expression_container(node, Eval(assn)(assn.o), context, sequential_successor(context)) - case Inhale(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) - case Assume(assn) => handle_expression_container(node, Eval(assn)(assn.o), context, sequential_successor(context)) - case Instantiate(_, out) => handle_expression_container(node, Eval(out)(out.o), context, sequential_successor(context)) - case Wait(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, sequential_successor(context)) - case Notify(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, sequential_successor(context)) - case Fork(obj) => { + case Fork(obj) => // TODO: Evaluate the obj expression before executing fork val run_method: RunMethod[G] = obj.t.asClass.get.cls.decl.declarations.collect{ case r: RunMethod[G] => r }.head // Get the successor(s) of the fork statement as well as the new thread, starting with the run method sequential_successor(context).addOne(CFGEdge(convert(run_method.body.get, GlobalIndex[G](mutable.Seq()).enter_scope(run_method)), None)) - } - case Join(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, sequential_successor(context)) - case Lock(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, sequential_successor(context)) - case Unlock(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, sequential_successor(context)) - case Commit(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, sequential_successor(context)) - case Fold(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) - case Unfold(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) - case WandApply(res) => handle_expression_container(node, Eval(res)(res.o), context, sequential_successor(context)) - case Havoc(loc) => handle_expression_container(node, Eval(loc)(loc.o), context, sequential_successor(context)) - case FramedProof(_, _, _) => evaluate_first(context.enter_scope(node)) - case Extract(_) => evaluate_first(context.enter_scope(node)) - // ExceptionalStatement - case Eval(_) => evaluate_first(context.enter_scope(node)) case Return(result) => handle_expression_container(node, Eval(result)(result.o), context, mutable.Set(CFGEdge(return_successor(context), None))) - case Throw(obj) => mutable.Set(CFGEdge(exception_successor(obj, context), None)) + case Throw(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, mutable.Set(CFGEdge(exception_successor(obj, context), None))) case Break(label) => label match { case Some(ref) => ??? // TODO: Handle break label! case None => mutable.Set(CFGEdge(break_successor(context), None)) @@ -93,54 +61,19 @@ case class CFGGenerator[G]() { case Some(ref) => ??? // TODO: Handle continue label! case None => mutable.Set(CFGEdge(continue_successor(context), None)) } - // InvocationStatement - case InvokeProcedure(_, _, _, _, _, _) => evaluate_first(context.enter_scope(node)) - case InvokeConstructor(_, _, _, _, _, _, _) => evaluate_first(context.enter_scope(node)) - case InvokeMethod(_, _, _, _, _, _, _) => evaluate_first(context.enter_scope(node)) - // CompositeStatement - case Block(_) => evaluate_first(context.enter_scope(node)) - case Scope(_, _) => evaluate_first(context.enter_scope(node)) - case Branch(_) => evaluate_first(context.enter_scope(node)) case IndetBranch(branches) => mutable.LinkedHashSet.from(branches.zipWithIndex.map(b => CFGEdge(convert(b._1, context.enter_scope(node, b._2)), None))) case s: Switch[_] => handle_switch_statement(s, context.enter_scope(node)) - case Loop(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) - case RangedFor(_, _, _) => evaluate_first(context.enter_scope(node)) - case TryCatchFinally(_, _, _) => evaluate_first(context.enter_scope(node)) - case Synchronized(_, _) => evaluate_first(context.enter_scope(node)) - case ParInvariant(_, _, _) => evaluate_first(context.enter_scope(node)) - case ParAtomic(_, _) => evaluate_first(context.enter_scope(node)) - case ParBarrier(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) - case ParStatement(_) => sequential_successor(context) - case VecBlock(_, _, _, _) => evaluate_first(context.enter_scope(node)) - case WandPackage(_, _) => evaluate_first(context.enter_scope(node)) - case ModelDo(_, _, _, _, _) => evaluate_first(context.enter_scope(node)) - // CStatement - case CDeclarationStatement(_) => sequential_successor(context) - case CGoto(label) => ??? // I'm not dealing with string labels - // CPPStatement - case CPPDeclarationStatement(_) => sequential_successor(context) - case CPPLifetimeScope(_) => evaluate_first(context.enter_scope(node)) - case JavaLocalDeclarationStatement(_) => sequential_successor(context) - // SilverStatement - case SilverNewRef(_, _) => sequential_successor(context) - case SilverFieldAssign(_, _, value) => handle_expression_container(node, Eval(value)(value.o), context, sequential_successor(context)) - case SilverLocalAssign(_, value) => handle_expression_container(node, Eval(value)(value.o), context, sequential_successor(context)) - // OTHER - case PVLCommunicate(_, _) => sequential_successor(context) - case PVLSeqAssign(_, _, value) => handle_expression_container(node, Eval(value)(value.o), context, sequential_successor(context)) - case Communicate(_, _) => sequential_successor(context) - case SeqAssign(_, _, value) => handle_expression_container(node, Eval(value)(value.o), context, sequential_successor(context)) - case UnresolvedSeqBranch(branches) => - mutable.LinkedHashSet.from(branches.zipWithIndex.map(b => CFGEdge(convert(b._1._2, context.enter_scope(node, b._2)), None))) - case UnresolvedSeqLoop(_, _, _) => evaluate_first(context.enter_scope(node)) + case CGoto(label) => ??? case SeqBranch(_, yes, no) => no match { // TODO: What are the conditions here? case Some(stmt) => mutable.Set(CFGEdge(convert(yes, context.enter_scope(node, 0)), None), CFGEdge(convert(stmt, context.enter_scope(node, 1)), None)) case None => mutable.Set(CFGEdge(convert(yes, context.enter_scope(node)), None)) } case SeqLoop(_, _, _) => evaluate_first(context.enter_scope(node)) // TODO: What are the conditions here? - case VeyMontAssignExpression(_, _) => evaluate_first(context.enter_scope(node)) - case CommunicateX(_, _, _, _) => evaluate_first(context.enter_scope(node)) + // Statements that can be categorized into a broader role for control flow analysis + case ecs: ExpressionContainerStatement[_] => handle_expression_container(node, Eval(ecs.expr)(ecs.o), context, sequential_successor(context)) + case _: ControlContainerStatement[_] => evaluate_first(context.enter_scope(node)) + case _: PurelySequentialStatement[_] => sequential_successor(context) } private def handle_expression_container(statement: Statement[G], @@ -178,17 +111,17 @@ case class CFGGenerator[G]() { } conds = conds.reverse - var previous_cond: Option[Expr[G]] = Some(Utils.negate(conds.head._2.expr)) var successor_to_previous: mutable.Set[CFGEdge[G]] = default match { - case Some(t) => mutable.Set(CFGEdge(convert(t._1, t._2), previous_cond)) - case None => sequential_successor(context, previous_cond) + case Some(t) => mutable.Set(CFGEdge(convert(t._1, t._2), Some(Utils.negate(conds.head._2.expr)))) + case None => sequential_successor(context, Some(Utils.negate(conds.head._2.expr))) } - for (c <- conds) { + for ((c, i) <- conds.zipWithIndex) { successor_to_previous.addOne(CFGEdge(convert(c._1, c._3), Some(c._2.expr))) val node = CFGNode(c._2, successor_to_previous) // TODO: Enter node at appropriate index in converted_nodes! - successor_to_previous = mutable.Set(CFGEdge(node, None)) // TODO: Add edge condition here! + if (i == conds.size - 1) successor_to_previous = mutable.Set(CFGEdge(node, None)) + else successor_to_previous = mutable.Set(CFGEdge(node, Some(Utils.negate(conds(i + 1)._2.expr)))) } successor_to_previous } diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index a35ac1e76a..78558f31f2 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -119,7 +119,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] def resolve_integer_expression(expr: Expr[G]): UncertainIntegerValue = expr match { case CIntegerValue(value) => UncertainIntegerValue.single(value.intValue) case IntegerValue(value) => UncertainIntegerValue.single(value.intValue) - case SizeOf(tname) => UncertainIntegerValue.above(0) // TODO: Can we use more information about sizeof? + case SizeOf(tname) => UncertainIntegerValue.above(1) // TODO: Can we use more information about sizeof? case UMinus(arg) => -resolve_integer_expression(arg) case AmbiguousMult(left, right) => resolve_integer_expression(left) * resolve_integer_expression(right) case AmbiguousPlus(left, right) => resolve_integer_expression(left) + resolve_integer_expression(right) From fd3739b4ff1a54e58ab06dca12f0ff16c40e99ca Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 26 Feb 2024 15:35:12 +0100 Subject: [PATCH 50/85] Removed expression container node before resolution of expression --- src/col/vct/col/ast/Node.scala | 4 +- .../statement/exceptional/ReturnImpl.scala | 2 + .../ast/statement/exceptional/ThrowImpl.scala | 4 +- .../vct/rewrite/cfg/CFGGenerator.scala | 65 ++++++++++--------- src/rewrite/vct/rewrite/cfg/Index.scala | 13 ++-- src/rewrite/vct/rewrite/cfg/Utils.scala | 3 +- 6 files changed, 52 insertions(+), 39 deletions(-) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 9301b54e4a..3829a4949c 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -226,8 +226,8 @@ sealed trait InvocationStatement[G] extends ExceptionalStatement[G] with Invokin final case class InvokeProcedure[G](ref: Ref[G, Procedure[G]], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends InvocationStatement[G] with ControlContainerStatement[G] with InvokeProcedureImpl[G] final case class InvokeConstructor[G](ref: Ref[G, Constructor[G]], out: Expr[G], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InvocationFailure])(implicit val o: Origin) extends InvocationStatement[G] with ControlContainerStatement[G] with InvokeConstructorImpl[G] final case class InvokeMethod[G](obj: Expr[G], ref: Ref[G, InstanceMethod[G]], args: Seq[Expr[G]], outArgs: Seq[Expr[G]], typeArgs: Seq[Type[G]], givenMap: Seq[(Ref[G, Variable[G]], Expr[G])], yields: Seq[(Expr[G], Ref[G, Variable[G]])])(val blame: Blame[InstanceInvocationFailure])(implicit val o: Origin) extends InvocationStatement[G] with InstanceApply[G] with ControlContainerStatement[G] with InvokeMethodImpl[G] -final case class Return[G](result: Expr[G])(implicit val o: Origin) extends ExceptionalStatement[G] with ReturnImpl[G] -final case class Throw[G](obj: Expr[G])(val blame: Blame[ThrowNull])(implicit val o: Origin) extends ExceptionalStatement[G] with ThrowImpl[G] +final case class Return[G](result: Expr[G])(implicit val o: Origin) extends ExceptionalStatement[G] with ExpressionContainerStatement[G] with ReturnImpl[G] +final case class Throw[G](obj: Expr[G])(val blame: Blame[ThrowNull])(implicit val o: Origin) extends ExceptionalStatement[G] with ExpressionContainerStatement[G] with ThrowImpl[G] final case class Break[G](label: Option[Ref[G, LabelDecl[G]]])(implicit val o: Origin) extends ExceptionalStatement[G] with BreakImpl[G] final case class Continue[G](label: Option[Ref[G, LabelDecl[G]]])(implicit val o: Origin) extends ExceptionalStatement[G] with ContinueImpl[G] diff --git a/src/col/vct/col/ast/statement/exceptional/ReturnImpl.scala b/src/col/vct/col/ast/statement/exceptional/ReturnImpl.scala index 2be3cf2267..0dbccb5968 100644 --- a/src/col/vct/col/ast/statement/exceptional/ReturnImpl.scala +++ b/src/col/vct/col/ast/statement/exceptional/ReturnImpl.scala @@ -18,4 +18,6 @@ trait ReturnImpl[G] extends ExceptionalStatementImpl[G] with ReturnOps[G] { this override def layout(implicit ctx: Ctx): Doc = Text("return") <> (if(result == Void[G]()) Text(";") else Empty <+> result <> ";") + + override def expr: Expr[G] = this.result } \ No newline at end of file diff --git a/src/col/vct/col/ast/statement/exceptional/ThrowImpl.scala b/src/col/vct/col/ast/statement/exceptional/ThrowImpl.scala index c6925487c9..c49866e6e9 100644 --- a/src/col/vct/col/ast/statement/exceptional/ThrowImpl.scala +++ b/src/col/vct/col/ast/statement/exceptional/ThrowImpl.scala @@ -1,10 +1,12 @@ package vct.col.ast.statement.exceptional -import vct.col.ast.Throw +import vct.col.ast.{Expr, Throw} import vct.col.print.{Ctx, Doc, Text} import vct.col.ast.ops.ThrowOps trait ThrowImpl[G] extends ThrowOps[G] { this: Throw[G] => override def layout(implicit ctx: Ctx): Doc = Text("throw") <+> obj <> ";" + + override def expr: Expr[G] = this.obj } \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 214ce28d26..31e4637336 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -16,43 +16,61 @@ case class CFGGenerator[G]() { private def convert(node: Statement[G], context: GlobalIndex[G]): CFGNode[G] = { // If a node has already been visited, then it should not be created again if (converted_nodes.contains(context)) return converted_nodes(context) - // Create new node with its successors (if possible) - val cfg_node: CFGNode[G] = CFGNode(node, mutable.Set()) - converted_nodes.addOne((context, cfg_node)) - cfg_node.successors.addAll(find_successors(node, context)) - // Handle labels and goto statements + // Convert to CFG node depending on type of statement node match { + case assign: Assign[_] => assign_to_cfg(assign, context) + case stmt: ExpressionContainerStatement[_] => expression_container_to_cfg(stmt, context) case label: Label[_] => + val cfg_node: CFGNode[G] = statement_to_cfg(node, context) // For labels, add them to the label map and add them to any goto statements going to that label found_labels.addOne((label.decl, cfg_node)) - searched_labels.getOrElse(label.decl, mutable.Set()).map(g => g.successors.addOne(CFGEdge(cfg_node, None))) + searched_labels.getOrElse(label.decl, mutable.Set()).foreach(g => g.successors.addOne(CFGEdge(cfg_node, None))) + cfg_node case goto: Goto[_] => + val cfg_node: CFGNode[G] = statement_to_cfg(node, context) // For goto statements, if the label could not be resolved, add the statement to the waiting list for the right label if (cfg_node.successors.isEmpty) { if (searched_labels.contains(goto.lbl.decl)) searched_labels(goto.lbl.decl).addOne(cfg_node) else searched_labels.addOne((goto.lbl.decl, mutable.Set(cfg_node))) } - case _ => + cfg_node + case _ => statement_to_cfg(node, context) } + } + + private def statement_to_cfg(stmt: Statement[G], context: GlobalIndex[G]): CFGNode[G] = { + val cfg_node: CFGNode[G] = CFGNode(stmt, mutable.Set()) + converted_nodes.addOne((context, cfg_node)) + cfg_node.successors.addAll(find_successors(stmt, context)) cfg_node } + private def assign_to_cfg(assign: Assign[G], context: GlobalIndex[G]): CFGNode[G] = context.indices.head match { + case AssignmentIndex(a, _) if a == assign => statement_to_cfg(assign, context) + case _ => convert(Eval(assign.target)(assign.target.o), context.enter_scope(assign)) + } + + private def expression_container_to_cfg(stmt: ExpressionContainerStatement[G], context: GlobalIndex[G]): CFGNode[G] = context.indices.head match { + case ExpressionContainerIndex(s, _) if s == stmt => statement_to_cfg(stmt, context) + case _ => convert(Eval(stmt.expr)(stmt.expr.o), context.enter_scope(stmt)) + } + private def find_successors(node: Statement[G], context: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = node match { // Exceptional statements - case Assign(_, _) => context.indices.head match { - case AssignmentIndex(a, _) if a == node => sequential_successor(context) - case _ => evaluate_first(context.enter_scope(node)) - } + // Statements that can jump to another part of the program case Goto(lbl) => found_labels.get(lbl.decl) match { case Some(node) => mutable.Set(CFGEdge(node, None)) case None => mutable.Set() } - case Fork(obj) => // TODO: Evaluate the obj expression before executing fork + case CGoto(label) => ??? + // Statements that simultaneously affect other threads + case Fork(obj) => val run_method: RunMethod[G] = obj.t.asClass.get.cls.decl.declarations.collect{ case r: RunMethod[G] => r }.head // Get the successor(s) of the fork statement as well as the new thread, starting with the run method sequential_successor(context).addOne(CFGEdge(convert(run_method.body.get, GlobalIndex[G](mutable.Seq()).enter_scope(run_method)), None)) - case Return(result) => handle_expression_container(node, Eval(result)(result.o), context, mutable.Set(CFGEdge(return_successor(context), None))) - case Throw(obj) => handle_expression_container(node, Eval(obj)(obj.o), context, mutable.Set(CFGEdge(exception_successor(obj, context), None))) + // Statements that jump out of the current control flow context + case Return(_) => mutable.Set(CFGEdge(return_successor(context), None)) + case Throw(obj) => mutable.Set(CFGEdge(exception_successor(obj, context), None)) case Break(label) => label match { case Some(ref) => ??? // TODO: Handle break label! case None => mutable.Set(CFGEdge(break_successor(context), None)) @@ -61,31 +79,20 @@ case class CFGGenerator[G]() { case Some(ref) => ??? // TODO: Handle continue label! case None => mutable.Set(CFGEdge(continue_successor(context), None)) } + // Irregular control container statements case IndetBranch(branches) => mutable.LinkedHashSet.from(branches.zipWithIndex.map(b => CFGEdge(convert(b._1, context.enter_scope(node, b._2)), None))) case s: Switch[_] => handle_switch_statement(s, context.enter_scope(node)) - case CGoto(label) => ??? - case SeqBranch(_, yes, no) => no match { // TODO: What are the conditions here? + case SeqBranch(_, yes, no) => no match { case Some(stmt) => mutable.Set(CFGEdge(convert(yes, context.enter_scope(node, 0)), None), CFGEdge(convert(stmt, context.enter_scope(node, 1)), None)) case None => mutable.Set(CFGEdge(convert(yes, context.enter_scope(node)), None)) } - case SeqLoop(_, _, _) => evaluate_first(context.enter_scope(node)) // TODO: What are the conditions here? - // Statements that can be categorized into a broader role for control flow analysis - case ecs: ExpressionContainerStatement[_] => handle_expression_container(node, Eval(ecs.expr)(ecs.o), context, sequential_successor(context)) + // Other statements that can be categorized into a broader role for control flow analysis + case _: ExpressionContainerStatement[_] => sequential_successor(context) case _: ControlContainerStatement[_] => evaluate_first(context.enter_scope(node)) case _: PurelySequentialStatement[_] => sequential_successor(context) } - private def handle_expression_container(statement: Statement[G], - expression: Eval[G], - context: GlobalIndex[G], - successors_to_statement: mutable.Set[CFGEdge[G]]): mutable.Set[CFGEdge[G]] = { - context.indices.head match { - case ExpressionContainerIndex(stmt, 1) if stmt == statement => successors_to_statement - case _ => mutable.Set(CFGEdge(convert(expression, context.enter_scope(expression)), None)) - } - } - private def evaluate_first(index: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = { if (index.has_statement()) mutable.Set(CFGEdge(resolve_index(index), None)) else sequential_successor(index) diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 656ea5dce4..4f11e26fca 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -160,7 +160,7 @@ object Index { case seq_loop: SeqLoop[G] => SeqLoopIndex(seq_loop) case veymont_assign_expression: VeyMontAssignExpression[G] => VeyMontAssignExpressionIndex(veymont_assign_expression) case communicatex: CommunicateX[G] => CommunicateXIndex(communicatex) - case statement: Statement[G] => ExpressionContainerIndex(statement, index) + case statement: ExpressionContainerStatement[G] => ExpressionContainerIndex(statement, index) } def apply[G](node: Node[G], index: Int): Index[G] = from(node, index) @@ -185,12 +185,15 @@ case class RunMethodIndex[G](run_method: RunMethod[G]) extends Index[G] { } } -case class ExpressionContainerIndex[G](statement: Statement[G], index: Int) extends Index[G] { +case class ExpressionContainerIndex[G](statement: ExpressionContainerStatement[G], index: Int) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { if (index == 0) Set((Step(ExpressionContainerIndex(statement, 1)), None)) else Set((Outgoing(), None)) } - override def resolve(): Statement[G] = statement + override def resolve(): Statement[G] = index match { + case 0 => Eval(statement.expr)(statement.expr.o) + case 1 => statement + } override def equals(obj: scala.Any): Boolean = obj match { case ExpressionContainerIndex(s, i) => i == index && s.equals(statement) case _ => false @@ -656,7 +659,7 @@ case class UnresolvedSeqLoopIndex[G](unresolved_seq_loop: UnresolvedSeqLoop[G], } case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) // TODO: What are the conditions? override def resolve(): Statement[G] = index match { case 0 => seq_branch.yes case 1 => seq_branch.no.get @@ -668,7 +671,7 @@ case class SeqBranchIndex[G](seq_branch: SeqBranch[G], index: Int) extends Index } case class SeqLoopIndex[G](seq_loop: SeqLoop[G]) extends Index[G] { - override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Step(this), None), (Outgoing(), None)) + override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Step(this), None), (Outgoing(), None)) // TODO: What are the conditions? override def resolve(): Statement[G] = seq_loop.body override def equals(obj: scala.Any): Boolean = obj match { case SeqLoopIndex(s) => s.equals(seq_loop) diff --git a/src/rewrite/vct/rewrite/cfg/Utils.scala b/src/rewrite/vct/rewrite/cfg/Utils.scala index 9b543653b1..4fd3f5f92a 100644 --- a/src/rewrite/vct/rewrite/cfg/Utils.scala +++ b/src/rewrite/vct/rewrite/cfg/Utils.scala @@ -4,8 +4,6 @@ import vct.col.ast._ import vct.col.origin.Origin import vct.col.ref.{DirectRef, Ref} -import scala.collection.mutable - object Utils { def find_all_subexpressions[G](expr: Expr[G]): Seq[Statement[G]] = expr match { @@ -96,6 +94,7 @@ object Utils { def negate[G](expr: Expr[G]): Expr[G] = expr match { case Not(inner) => inner + case BooleanValue(value) => BooleanValue(!value)(expr.o) case _ => Not(expr)(expr.o) } From 53709b39cf2da1fb9a35a88d31bef6fbd79daad2 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 26 Feb 2024 17:11:39 +0100 Subject: [PATCH 51/85] Fixed return followed by branches; removed scope nodes from CFG --- src/rewrite/vct/rewrite/cfg/CFGGenerator.scala | 17 ++++++++++++++--- src/rewrite/vct/rewrite/cfg/Index.scala | 7 +++---- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 31e4637336..7d2d962233 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -18,8 +18,10 @@ case class CFGGenerator[G]() { if (converted_nodes.contains(context)) return converted_nodes(context) // Convert to CFG node depending on type of statement node match { + // If a statement contains an expression, the expression(s) must be evaluated before the statement case assign: Assign[_] => assign_to_cfg(assign, context) case stmt: ExpressionContainerStatement[_] => expression_container_to_cfg(stmt, context) + // Handle labels and gotos, since those can occur in arbitrary order case label: Label[_] => val cfg_node: CFGNode[G] = statement_to_cfg(node, context) // For labels, add them to the label map and add them to any goto statements going to that label @@ -34,6 +36,13 @@ case class CFGGenerator[G]() { else searched_labels.addOne((goto.lbl.decl, mutable.Set(cfg_node))) } cfg_node + // Leave all container statements except for invocations out of the CFG + case _: InvocationStatement[_] => statement_to_cfg(node, context) + case c: ControlContainerStatement[_] => + val new_context = context.enter_scope(c) + if (!new_context.has_statement()) statement_to_cfg(c, context) + else convert(new_context.resolve().get, new_context) + // Any non-special statement is simply converted to a CFG node case _ => statement_to_cfg(node, context) } } @@ -69,7 +78,7 @@ case class CFGGenerator[G]() { // Get the successor(s) of the fork statement as well as the new thread, starting with the run method sequential_successor(context).addOne(CFGEdge(convert(run_method.body.get, GlobalIndex[G](mutable.Seq()).enter_scope(run_method)), None)) // Statements that jump out of the current control flow context - case Return(_) => mutable.Set(CFGEdge(return_successor(context), None)) + case Return(_) => return_successors(context) case Throw(obj) => mutable.Set(CFGEdge(exception_successor(obj, context), None)) case Break(label) => label match { case Some(ref) => ??? // TODO: Handle break label! @@ -87,6 +96,8 @@ case class CFGGenerator[G]() { case Some(stmt) => mutable.Set(CFGEdge(convert(yes, context.enter_scope(node, 0)), None), CFGEdge(convert(stmt, context.enter_scope(node, 1)), None)) case None => mutable.Set(CFGEdge(convert(yes, context.enter_scope(node)), None)) } + // Assign statements cannot be easily categorized because they contain two expressions + case Assign(_, _) => sequential_successor(context) // Other statements that can be categorized into a broader role for control flow analysis case _: ExpressionContainerStatement[_] => sequential_successor(context) case _: ControlContainerStatement[_] => evaluate_first(context.enter_scope(node)) @@ -133,8 +144,8 @@ case class CFGGenerator[G]() { successor_to_previous } - private def return_successor(index: GlobalIndex[G]): CFGEntry[G] = - resolve_index(index.return_from_call()) + private def return_successors(index: GlobalIndex[G]): mutable.Set[CFGEdge[G]] = + index.return_from_call().map(t => CFGEdge(resolve_index(t._1), t._2)) private def exception_successor(exception: Expr[G], index: GlobalIndex[G]): CFGEntry[G] = resolve_index(index.handle_exception(exception)) diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index 4f11e26fca..a5b634c149 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -45,15 +45,14 @@ case class GlobalIndex[G](indices: mutable.Seq[Index[G]]) { def has_statement(): Boolean = indices.nonEmpty && indices.head.has_statement() - def return_from_call(): GlobalIndex[G] = { + def return_from_call(): mutable.Set[(GlobalIndex[G], Option[Expr[G]])] = { // Find innermost subroutine call val stack: mutable.Seq[Index[G]] = indices.dropWhile { case InvokeProcedureIndex(_, _) | InvokeMethodIndex(_, _) => false case _ => true } - // Find the next statement - // TODO: Does this always return exactly one next step? - GlobalIndex(stack.tail).make_step().head._1 + // Find the possible next statements + GlobalIndex(stack.tail).make_step() } def handle_exception(e: Expr[G]): GlobalIndex[G] = { From 7c3857c27adcbc2dbac1acc2485886fb33ddedbc Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 27 Feb 2024 09:33:01 +0100 Subject: [PATCH 52/85] Added node back to CFG if it is the first context of a run method; removed old Java attempt at RASI generator --- .../invariants/AbstractProcess.java | 132 ------------------ .../invariants/AbstractState.java | 41 ------ .../invariants/ExecutableState.java | 24 ---- .../invariants/ExecutionState.java | 4 - .../systemctocol/invariants/Generator.java | 52 ------- .../invariants/ResolvedExecutionState.java | 43 ------ .../variables/ConcreteVariable.java | 20 --- .../invariants/variables/FieldVariable.java | 27 ---- .../variables/SequenceIndexVariable.java | 44 ------ .../vct/rewrite/cfg/CFGGenerator.scala | 7 +- .../vct/rewrite/rasi/AbstractProcess.scala | 4 +- 11 files changed, 8 insertions(+), 390 deletions(-) delete mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java delete mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractState.java delete mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutableState.java delete mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutionState.java delete mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/Generator.java delete mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/ResolvedExecutionState.java delete mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/variables/ConcreteVariable.java delete mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/variables/FieldVariable.java delete mode 100644 src/parsers/vct/parsers/transform/systemctocol/invariants/variables/SequenceIndexVariable.java diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java deleted file mode 100644 index 53942c79a8..0000000000 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractProcess.java +++ /dev/null @@ -1,132 +0,0 @@ -package vct.parsers.transform.systemctocol.invariants; - -import vct.col.ast.*; - -import java.util.ArrayList; -import java.util.List; - -public class AbstractProcess { - - protected RunMethod process_method; - - public AbstractProcess(RunMethod method) { - this.process_method = method; - } - - public List> simulate(int[] program_counter, AbstractState starting_state) { - List> final_result = new ArrayList<>(); - - List> current_branches = new ArrayList<>(); - List> current_stack = resolve(program_counter); - current_branches.add(new ResolvedExecutionState<>(starting_state, program_counter, current_stack)); - - return final_result; - } - - private List> small_step() { - return null; - } - - /** - * Returns the current statement stack at the execution state given by the program counter. Iteratively resolves the - * context of each statement and returns a stack with the highest-level container at position 0 and the statement - * currently to be evaluated at position n - 1. - * - * @param program_counter A list of indices showing which point in each container points to the right statement - * @return A list of container statements, with the desired statement in the last position - */ - private List> resolve(int[] program_counter) { - List> stack = new ArrayList<>(); - Statement context = process_method.body().get(); - stack.add(context); - for(int index : program_counter) { - context = get_at_index(context, index); - stack.add(context); - } - return stack; - } - - /** - * Takes a statement that (can) contain other statements as well as an index and returns the given index statement - * contained within the container. - * - * @param container Container statement - * @param index Index of desired contained statement - * @return Statement at index index within the body of container - */ - private Statement get_at_index(Statement container, int index) { - // Ignores the following: - // TryCatchFinally - // Synchronized - // ParInvariant - // ParAtomic - // VecBlock - // WandPackage - // ModelDo - if (container instanceof PVLBranch pvl_branch) { - return pvl_branch.branches().apply(index)._2(); - } - else if (container instanceof PVLLoop pvl_loop) { - return switch (index) { - case 0 -> pvl_loop.init(); - case 1 -> pvl_loop.body(); - case 2 -> pvl_loop.update(); - default -> throw new IndexOutOfBoundsException("Loop index must at most be 2."); - }; - } - else if (container instanceof InvokeProcedure invoke_procedure) { - if (index == 0) { - return invoke_procedure.ref().decl().body().get(); - } - else throw new IndexOutOfBoundsException("Invalid index for procedure invocation."); - } - else if (container instanceof InvokeConstructor invoke_constructor) { - if (index == 0) { - return invoke_constructor.ref().decl().body().get(); - } - else throw new IndexOutOfBoundsException("Invalid index for constructor invocation."); - } - else if (container instanceof InvokeMethod invoke_method) { - if (index == 0) { - return invoke_method.ref().decl().body().get(); - } - else throw new IndexOutOfBoundsException("Invalid index for method invocation."); - } - else if (container instanceof Block block) { - return block.statements().apply(index); - } - else if (container instanceof Scope scope) { - if (index == 0) { - return scope.body(); - } - else throw new IndexOutOfBoundsException("Invalid index for scope."); - } - else if (container instanceof Branch branch) { - return branch.branches().apply(index)._2(); - } - else if (container instanceof IndetBranch indet_branch) { - return indet_branch.branches().apply(index); - } - else if (container instanceof Switch switch_stmt) { - if (index == 0) { - return switch_stmt.body(); - } - else throw new IndexOutOfBoundsException("Invalid index for switch statement."); - } - else if (container instanceof Loop loop) { - return switch (index) { - case 0 -> loop.init(); - case 1 -> loop.body(); - case 2 -> loop.update(); - default -> throw new IndexOutOfBoundsException("Loop index must at most be 2."); - }; - } - else if (container instanceof RangedFor ranged_for) { - if (index == 0) { - return ranged_for.body(); - } - else throw new IndexOutOfBoundsException("Invalid index for foreach loop."); - } - else throw new IllegalArgumentException("Statement " + container + " is not supported as a container."); - } -} \ No newline at end of file diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractState.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractState.java deleted file mode 100644 index 64a2c73cbc..0000000000 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/AbstractState.java +++ /dev/null @@ -1,41 +0,0 @@ -package vct.parsers.transform.systemctocol.invariants; - -import vct.col.ast.Expr; -import vct.parsers.transform.systemctocol.invariants.variables.ConcreteVariable; - -import java.util.HashMap; -import java.util.Map; - -public class AbstractState { - private final Map, Integer> valuations; - - public AbstractState() { - valuations = new HashMap<>(); - } - - public AbstractState(AbstractState previous) { - this.valuations = new HashMap<>(previous.valuations); - } - - public boolean contains_variable(Expr variable_expression) { - return valuations.keySet().stream().anyMatch((var) -> var.is(variable_expression)); - } - - public void set_valuation(ConcreteVariable var, int value) { - valuations.put(var, value); - } - - public int get_valuation(ConcreteVariable var) { - return valuations.get(var); - } - - public int get_valuation(Expr variable_expression) { - return valuations.entrySet().stream() - .filter((entry) -> entry.getKey().is(variable_expression)) - .findFirst().orElseThrow().getValue(); - } - - public boolean equals(AbstractState other) { - return valuations.equals(other.valuations); - } -} diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutableState.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutableState.java deleted file mode 100644 index 383253d726..0000000000 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutableState.java +++ /dev/null @@ -1,24 +0,0 @@ -package vct.parsers.transform.systemctocol.invariants; - -import java.util.ArrayList; -import java.util.List; - -public record ExecutableState (AbstractState state, - int[][] execution_location_by_process, - int next_process_id) { - - public List> execute_step(List> processes) { - AbstractProcess next_process = processes.get(next_process_id); - List> updates = next_process.simulate(execution_location_by_process[next_process_id], state); - - List> result = new ArrayList<>(); - for (ExecutionState update : updates) { - int[][] updated_counter = execution_location_by_process.clone(); - updated_counter[next_process_id] = update.program_counter(); - for (int i = 0; i < processes.size(); i++) { - result.add(new ExecutableState<>(update.abstract_state(), updated_counter, i)); - } - } - return result; - } -} diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutionState.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutionState.java deleted file mode 100644 index a28d2f0bfa..0000000000 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/ExecutionState.java +++ /dev/null @@ -1,4 +0,0 @@ -package vct.parsers.transform.systemctocol.invariants; - -public record ExecutionState (AbstractState abstract_state, int[] program_counter) {} - diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/Generator.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/Generator.java deleted file mode 100644 index 7587c894e3..0000000000 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/Generator.java +++ /dev/null @@ -1,52 +0,0 @@ -package vct.parsers.transform.systemctocol.invariants; - -import vct.col.ast.InstanceMethod; -import vct.parsers.ParseResult; -import vct.parsers.transform.systemctocol.invariants.variables.ConcreteVariable; - -import java.util.*; - -public class Generator { - - private final List> processes; - - private final List> active_branches; - - private final Set> considered_branches; - - public Generator(ParseResult parse_result, - Map, Integer> considered_variables, - InstanceMethod main_method) { - // Initialize processes - processes = new ArrayList<>(); - initialize_processes(); - - // Initialize active branches - active_branches = new ArrayList<>(); - - // Initialize considered branches to empty list - considered_branches = new HashSet<>(); - } - - public void execute() { - initialize_branches(); - - while (!active_branches.isEmpty()) { - ExecutableState exploring = active_branches.remove(0); - List> new_possibilities = exploring.execute_step(processes) - .stream() - .filter((state) -> !considered_branches.contains(state)) - .toList(); - considered_branches.addAll(new_possibilities); - active_branches.addAll(new_possibilities); - } - } - - private void initialize_processes() { - - } - - private void initialize_branches() { - AbstractState initial_state = new AbstractState<>(); - } -} \ No newline at end of file diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/ResolvedExecutionState.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/ResolvedExecutionState.java deleted file mode 100644 index 4a0c8047e2..0000000000 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/ResolvedExecutionState.java +++ /dev/null @@ -1,43 +0,0 @@ -package vct.parsers.transform.systemctocol.invariants; - -import vct.col.ast.Statement; - -import java.util.ArrayList; -import java.util.List; - -public class ResolvedExecutionState { - - private AbstractState state; - private int[] program_counter; - private List> program_location; - - public ResolvedExecutionState(AbstractState state, int[] program_counter, List> program_location) { - this.state = new AbstractState<>(state); - this.program_counter = program_counter.clone(); - this.program_location = new ArrayList<>(program_location); - } - - public AbstractState get_state() { - return state; - } - - public int[] get_program_counter() { - return program_counter; - } - - public List> get_program_location() { - return program_location; - } - - public Statement get_current_statement() { - return program_location.get(program_location.size() - 1); - } - - public void move_forward() { - - } - - public ExecutionState export() { - return new ExecutionState<>(state, program_counter); - } -} diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/ConcreteVariable.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/ConcreteVariable.java deleted file mode 100644 index a288348988..0000000000 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/ConcreteVariable.java +++ /dev/null @@ -1,20 +0,0 @@ -package vct.parsers.transform.systemctocol.invariants.variables; - -import vct.col.ast.Deref; -import vct.col.ast.Expr; -import vct.col.ast.Field; -import vct.col.ast.PVLDeref; - -public abstract class ConcreteVariable { - - public abstract boolean is(Expr expression); - - public abstract boolean equals(ConcreteVariable other); - - protected Field extract_from_expression(Expr expression) { - if (expression instanceof Deref deref) { - return deref.ref().decl(); - } - else throw new IllegalArgumentException("Could not parse expression " + expression.toString()); - } -} diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/FieldVariable.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/FieldVariable.java deleted file mode 100644 index 52f9fbdd94..0000000000 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/FieldVariable.java +++ /dev/null @@ -1,27 +0,0 @@ -package vct.parsers.transform.systemctocol.invariants.variables; - -import vct.col.ast.Expr; -import vct.col.ast.Field; - -public class FieldVariable extends ConcreteVariable { - - protected Field field; - - public FieldVariable(Field field) { - this.field = field; - } - - @Override - public boolean is(Expr expression) { - return field.equals(extract_from_expression(expression)); - } - - @Override - public boolean equals(ConcreteVariable other) { - if (other == this) return true; - if (other instanceof FieldVariable other_fv) { - return this.field.equals(other_fv.field); - } - else return false; - } -} diff --git a/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/SequenceIndexVariable.java b/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/SequenceIndexVariable.java deleted file mode 100644 index 1ba5eb3a2b..0000000000 --- a/src/parsers/vct/parsers/transform/systemctocol/invariants/variables/SequenceIndexVariable.java +++ /dev/null @@ -1,44 +0,0 @@ -package vct.parsers.transform.systemctocol.invariants.variables; - -import vct.col.ast.*; - -public class SequenceIndexVariable extends ConcreteVariable { - - protected Field sequence; - protected int index; - - public SequenceIndexVariable(Field sequence, int index) { - this.sequence = sequence; - this.index = index; - } - - @Override - public boolean is(Expr expression) { - if (expression instanceof AmbiguousSubscript subscript) { - return extract_from_expression(subscript.collection()).equals(sequence) - && resolve_index_expression(subscript.index()) == index; - } - else if (expression instanceof SeqSubscript subscript) { - return extract_from_expression(subscript.seq()).equals(sequence) - && resolve_index_expression(subscript.index()) == index; - } - else return false; - } - - protected int resolve_index_expression(Expr index_expression) { - if (index_expression instanceof IntegerValue integer_value) { - return integer_value.value().intValue(); - } - // TODO: Support simple arithmetic? - else throw new IllegalArgumentException("Arithmetics in sequence accesses is not yet supported!"); - } - - @Override - public boolean equals(ConcreteVariable other) { - if (other == this) return true; - if (other instanceof SequenceIndexVariable other_siv) { - return this.sequence.equals(other_siv.sequence) && this.index == other_siv.index; - } - else return false; - } -} diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 7d2d962233..9a5cc61439 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -36,10 +36,13 @@ case class CFGGenerator[G]() { else searched_labels.addOne((goto.lbl.decl, mutable.Set(cfg_node))) } cfg_node - // Leave all container statements except for invocations out of the CFG + // Leave all container statements except for invocations out of the CFG with two exceptions: Expressions and the + // first scope of a run method must remain so that the run method can later still be identified case _: InvocationStatement[_] => statement_to_cfg(node, context) - case c: ControlContainerStatement[_] => + case c: ControlContainerStatement[_] if !context.indices.head.isInstanceOf[RunMethodIndex[_]] => val new_context = context.enter_scope(c) + // If the new scope is empty (e.g. expression evaluation with no contained statements), transform it normally, + // since this is the only node that can represent this part of the CFG if (!new_context.has_statement()) statement_to_cfg(c, context) else convert(new_context.resolve().get, new_context) // Any non-special statement is simply converted to a CFG node diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index 7dc6683c26..c957247ca1 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -18,7 +18,7 @@ case class AbstractProcess[G](obj: Expr[G]) { // Statements that induce assumptions about the state, such as assume, inhale, or a method's postcondition, might change the state implicitly case Assume(assn) => viable_edges(succ, state).flatMap(e => state.with_assumption(assn).map(s => take_edge(e, s))) case Inhale(res) => viable_edges(succ, state).flatMap(e => state.with_assumption(res).map(s => take_edge(e, s))) - // Abstract procedures, constructors and methods are defined by their postconditions + // Abstract procedures, constructors and methods are defined by their postconditions TODO: Temporarily add parameter to state if it is assigned to a tracked variable case InvokeProcedure(ref, args, _, _, _, _) => ref.decl.body match { case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) case None => viable_edges(succ, state).flatMap(e => state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))).map(s => take_edge(e, s))) @@ -31,6 +31,8 @@ case class AbstractProcess[G](obj: Expr[G]) { case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) case None => viable_edges(succ, state).flatMap(e => state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))).map(s => take_edge(e, s))) } + // TODO: Remove temporary parameter from state after method return + case Return(result) => viable_edges(succ, state).map(e => take_edge(e, state)) // TODO: What do wait and notify do? case Wait(obj) => viable_edges(succ, state).map(e => take_edge(e, state)) case Notify(obj) => viable_edges(succ, state).map(e => take_edge(e, state)) From 9b1c7bc9bb40b7732a279798791fee80f0117c1b Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 5 Mar 2024 11:41:40 +0100 Subject: [PATCH 53/85] Added new keyword 'vesuv_entry' for main method --- src/col/vct/col/ast/Node.scala | 1 + .../vct/col/ast/declaration/cls/MainMethodImpl.scala | 12 ++++++++++++ src/parsers/antlr4/LangPVLLexer.g4 | 1 + src/parsers/antlr4/LangPVLParser.g4 | 4 +++- src/parsers/vct/parsers/transform/PVLToCol.scala | 5 +++++ 5 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 src/col/vct/col/ast/declaration/cls/MainMethodImpl.scala diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 3829a4949c..5f5913730f 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -297,6 +297,7 @@ case class Boogie[G]()(implicit val o: Origin) extends ProverLanguage[G] with Bo extends ClassDeclaration[G] with AbstractPredicate[G] with InstancePredicateImpl[G] final class InstanceField[G](val t: Type[G], val flags: Seq[FieldFlag[G]])(implicit val o: Origin) extends ClassDeclaration[G] with Field[G] with InstanceFieldImpl[G] final class RunMethod[G](val body: Option[Statement[G]], val contract: ApplicableContract[G])(val blame: Blame[CallableFailure])(implicit val o: Origin) extends ClassDeclaration[G] with RunMethodImpl[G] +final class MainMethod[G](val body: Option[Statement[G]])(val blame: Blame[CallableFailure])(implicit val o: Origin) extends ClassDeclaration[G] with MainMethodImpl[G] final class InstanceOperatorFunction[G](val returnType: Type[G], val operator: Operator[G], val args: Seq[Variable[G]], val body: Option[Expr[G]], val contract: ApplicableContract[G], val inline: Boolean, val threadLocal: Boolean = false) diff --git a/src/col/vct/col/ast/declaration/cls/MainMethodImpl.scala b/src/col/vct/col/ast/declaration/cls/MainMethodImpl.scala new file mode 100644 index 0000000000..d59143891e --- /dev/null +++ b/src/col/vct/col/ast/declaration/cls/MainMethodImpl.scala @@ -0,0 +1,12 @@ +package vct.col.ast.declaration.cls + +import vct.col.ast.MainMethod +import vct.col.print.{Ctx, Doc, Empty, Text} +import vct.col.ast.ops.MainMethodOps + +trait MainMethodImpl[G] extends MainMethodOps[G] { this: MainMethod[G] => + override def layout(implicit ctx: Ctx): Doc = + Doc.stack(Seq( + Text("vesuv_entry") <> body.map(Empty <+> _.layoutAsBlock).getOrElse(Text(";")), + )) +} diff --git a/src/parsers/antlr4/LangPVLLexer.g4 b/src/parsers/antlr4/LangPVLLexer.g4 index 772764968a..ba6cc8930b 100644 --- a/src/parsers/antlr4/LangPVLLexer.g4 +++ b/src/parsers/antlr4/LangPVLLexer.g4 @@ -55,6 +55,7 @@ BARRIER: 'barrier'; INVARIANT: 'invariant'; CONSTRUCTOR: 'constructor'; RUN: 'run'; +ENTRY: 'vesuv_entry'; THREAD: 'thread'; ENDPOINT: 'endpoint'; diff --git a/src/parsers/antlr4/LangPVLParser.g4 b/src/parsers/antlr4/LangPVLParser.g4 index 0165d36b9a..173b8ca8e3 100644 --- a/src/parsers/antlr4/LangPVLParser.g4 +++ b/src/parsers/antlr4/LangPVLParser.g4 @@ -6,7 +6,7 @@ parser grammar LangPVLParser; program : programDecl* EOF EOF ; -programDecl : valGlobalDeclaration | declClass | enumDecl | method | declVeyMontSeqProg; +programDecl : valGlobalDeclaration | declClass | enumDecl | method | declVeyMontSeqProg | vesuvEntry; enumDecl : 'enum' identifier '{' identifierList? ','? '}' ; @@ -38,6 +38,8 @@ constructor : contract 'constructor' '(' args? ')' methodBody ; runMethod : contract 'run' methodBody ; +vesuvEntry : 'vesuv_entry' methodBody ; + contract : valContractClause* ; args diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index 0a8e3eac70..dd5a5e77d3 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -151,6 +151,11 @@ case class PVLToCol[G](override val baseOrigin: Origin, ) } + def convert(implicit method: VesuvEntryContext): Seq[MainMethod[G]] = method match { + case VesuvEntry0(_, maybeBody) => + Seq(new MainMethod(convert(maybeBody))(blame(method))) + } + def convert(implicit args: ArgsContext): Seq[Variable[G]] = args match { case Args0(t, name) => Seq(new Variable(convert(t))(origin(name).sourceName(convert(name)))) case Args1(t, name, _, args) => From 760711296c2456775687d9b8c26828936448a50c Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 5 Mar 2024 14:27:31 +0100 Subject: [PATCH 54/85] Made MainMethod global rather than class declaration --- src/col/vct/col/ast/Node.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index 5f5913730f..a32ffd1f9b 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -266,6 +266,7 @@ final class Model[G](val declarations: Seq[ModelDeclaration[G]])(implicit val o: val inline: Boolean = false, val pure: Boolean = false) (val blame: Blame[CallableFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with AbstractMethod[G] with ProcedureImpl[G] +final class MainMethod[G](val body: Option[Statement[G]])(val blame: Blame[CallableFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with MainMethodImpl[G] @scopes[Variable] final class Predicate[G](val args: Seq[Variable[G]], val body: Option[Expr[G]], val threadLocal: Boolean = false, val inline: Boolean = false)(implicit val o: Origin) extends GlobalDeclaration[G] with AbstractPredicate[G] with PredicateImpl[G] @@ -297,7 +298,6 @@ case class Boogie[G]()(implicit val o: Origin) extends ProverLanguage[G] with Bo extends ClassDeclaration[G] with AbstractPredicate[G] with InstancePredicateImpl[G] final class InstanceField[G](val t: Type[G], val flags: Seq[FieldFlag[G]])(implicit val o: Origin) extends ClassDeclaration[G] with Field[G] with InstanceFieldImpl[G] final class RunMethod[G](val body: Option[Statement[G]], val contract: ApplicableContract[G])(val blame: Blame[CallableFailure])(implicit val o: Origin) extends ClassDeclaration[G] with RunMethodImpl[G] -final class MainMethod[G](val body: Option[Statement[G]])(val blame: Blame[CallableFailure])(implicit val o: Origin) extends ClassDeclaration[G] with MainMethodImpl[G] final class InstanceOperatorFunction[G](val returnType: Type[G], val operator: Operator[G], val args: Seq[Variable[G]], val body: Option[Expr[G]], val contract: ApplicableContract[G], val inline: Boolean, val threadLocal: Boolean = false) From 838b12c1375d8a3b01cb029cc3c93b7cba087286 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 5 Mar 2024 15:29:00 +0100 Subject: [PATCH 55/85] Working on effect of collection updates on collection entries --- .../vct/rewrite/cfg/CFGGenerator.scala | 2 +- .../vct/rewrite/rasi/AbstractProcess.scala | 1 + .../vct/rewrite/rasi/AbstractState.scala | 47 +++++++++++++++++++ .../vct/rewrite/rasi/ConcreteVariable.scala | 6 +++ 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 9a5cc61439..ec8a7bd3ab 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -140,7 +140,7 @@ case class CFGGenerator[G]() { for ((c, i) <- conds.zipWithIndex) { successor_to_previous.addOne(CFGEdge(convert(c._1, c._3), Some(c._2.expr))) val node = CFGNode(c._2, successor_to_previous) - // TODO: Enter node at appropriate index in converted_nodes! + // TODO: Should this node be added to the explored nodes? Probably not, as it is a generated node? if (i == conds.size - 1) successor_to_previous = mutable.Set(CFGEdge(node, None)) else successor_to_previous = mutable.Set(CFGEdge(node, Some(Utils.negate(conds(i + 1)._2.expr)))) } diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index c957247ca1..940d1b75fd 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -12,6 +12,7 @@ case class AbstractProcess[G](obj: Expr[G]) { // Assign statements change the state of variables directly (if they appear in the valuation) case Assign(target, value) => target.t match { case _: IntType[_] | TBool() => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(target, state.resolve_expression(value)))) + case _: TArray[_] | TSeq(_) => viable_edges(succ, state).map(e => take_edge(e, state.with_updated_collection(target, value))) case _ => viable_edges(succ, state).map(e => take_edge(e, state)) } case Havoc(loc) => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(loc, UncertainValue.uncertain_of(loc.t)))) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 78558f31f2..8eb2fe6780 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -24,9 +24,56 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case Some(concrete_variable) => val_updated(concrete_variable, value) case None => this } + private def val_updated(variable: ConcreteVariable[G], value: UncertainValue): AbstractState[G] = AbstractState(valuations + (variable -> value), processes, lock) + def with_updated_collection(variable: Expr[G], assigned: Expr[G]): AbstractState[G] = { + val affected: Set[IndexedVariable[G]] = valuations.keySet.filter(v => v.is_contained_by(variable, this)).collect{ case v: IndexedVariable[_] => v } + if (affected.isEmpty) return this + val by_index: Map[Int, IndexedVariable[G]] = Map.from(affected.map(v => (v.i, v))) + val new_values = get_collection_value(assigned) + var vals = valuations + by_index.foreach(t => vals = vals + (t._2 -> new_values(t._1))) + AbstractState(vals, processes, lock) + } + + private def get_collection_value(lit: Expr[G]): Seq[UncertainValue] = lit match { + case LiteralSeq(_, values) => values.map(e => resolve_expression(e)) + case UntypedLiteralSeq(values) => values.map(e => resolve_expression(e)) + case Cons(x, xs) => resolve_expression(x) +: get_collection_value(xs) + case Concat(xs, ys) => get_collection_value(xs) ++ get_collection_value(ys) + // TODO: Store size of variables + case d: Deref[_] => ??? + // TODO: Ask about array semantics + case Values(arr, from, to) => ??? + case NewArray(element, dims, moreDims, initialize) => ??? + // TODO: Handle cases in which index is not perfectly apparent + case Drop(xs, count) => resolve_integer_expression(count).try_to_resolve() match { + case None => ??? + case Some(i) => get_collection_value(xs).drop(i) + } + case Take(xs, count) => resolve_integer_expression(count).try_to_resolve() match { + case None => ??? + case Some(i) => get_collection_value(xs).take(i) + } + case SeqUpdate(xs, i, x) => resolve_integer_expression(i).try_to_resolve() match { + case None => ??? + case Some(index) => get_collection_value(xs).updated(index, resolve_expression(x)) + } + case RemoveAt(xs, i) => resolve_integer_expression(i).try_to_resolve() match { + case None => ??? + case Some(index) => get_collection_value(xs).zipWithIndex.filter(_._2 != index).map(_._1) + } + case Slice(xs, from, to) => resolve_integer_expression(from).try_to_resolve() match { + case None => ??? + case Some(f) => resolve_integer_expression(to).try_to_resolve() match { + case None => ??? + case Some(t) => get_collection_value(xs).slice(f, t) + } + } + } + def with_assumption(assumption: Expr[G]): Set[AbstractState[G]] = resolve_effect(assumption, negate = false) private def resolve_effect(assumption: Expr[G], negate: Boolean): Set[AbstractState[G]] = assumption match { diff --git a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala index ec644885d8..1d2eaa5187 100644 --- a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala +++ b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala @@ -4,6 +4,7 @@ import vct.col.ast._ trait ConcreteVariable[G] { def is(expr: Expr[G], state: AbstractState[G]): Boolean + def is_contained_by(expr: Expr[G], state: AbstractState[G]): Boolean def to_expression: Expr[G] def t: Type[G] def field_equals(expr: Expr[G], field: InstanceField[G]): Boolean = expr match { @@ -15,6 +16,7 @@ trait ConcreteVariable[G] { case class FieldVariable[G](field: InstanceField[G]) extends ConcreteVariable[G] { override def is(expr: Expr[G], state: AbstractState[G]): Boolean = field_equals(expr, field) + override def is_contained_by(expr: Expr[G], state: AbstractState[G]): Boolean = field_equals(expr, field) override def to_expression: Expr[G] = Deref[G](AmbiguousThis()(field.o), field.ref)(field.o)(field.o) override def t: Type[G] = field.t } @@ -27,6 +29,10 @@ case class IndexedVariable[G](field: InstanceField[G], i: Int) extends ConcreteV case PointerSubscript(pointer, index) => field_equals(pointer, field) && i == state.resolve_integer_expression(index).try_to_resolve().getOrElse(-1) case _ => false } + override def is_contained_by(expr: Expr[G], state: AbstractState[G]): Boolean = expr match { + // TODO: What about slices? + case _ => field_equals(expr, field) + } override def to_expression: Expr[G] = field.t match { case TSeq(_) => SeqSubscript(Deref[G](AmbiguousThis()(field.o), field.ref)(field.o)(field.o), IntegerValue(i)(field.o))(field.o)(field.o) case TArray(_) => ArraySubscript(Deref[G](AmbiguousThis()(field.o), field.ref)(field.o)(field.o), IntegerValue(i)(field.o))(field.o)(field.o) From c4728bde8436fc7694578b3fc58b40aa4efa04a5 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 11 Mar 2024 15:40:03 +0100 Subject: [PATCH 56/85] [FAILED] Tried to switch from sequences to individual variables --- .../systemctocol/colmodel/COLSystem.java | 108 ++++--- .../engine/ExpressionTransformer.java | 79 +++-- .../engine/KnownTypeTransformer.java | 300 +++++++++--------- .../systemctocol/engine/MainTransformer.java | 171 +++------- .../systemctocol/engine/Transformer.java | 63 ++-- 5 files changed, 350 insertions(+), 371 deletions(-) diff --git a/src/parsers/vct/parsers/transform/systemctocol/colmodel/COLSystem.java b/src/parsers/vct/parsers/transform/systemctocol/colmodel/COLSystem.java index e4a4394b66..7a91f0f3c9 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/colmodel/COLSystem.java +++ b/src/parsers/vct/parsers/transform/systemctocol/colmodel/COLSystem.java @@ -144,7 +144,7 @@ public Expr fold_star(java.util.List> expressions) { * @return A single expression with all given expressions connected by stars */ private Expr _fold_star(java.util.ArrayList> expressions) { - if (expressions == null || expressions.size() == 0) return TRUE; + if (expressions == null || expressions.isEmpty()) return TRUE; if (expressions.size() == 1) return expressions.get(0); Expr first = expressions.remove(0); return new Star<>(first, _fold_star(expressions), OriGen.create()); @@ -168,7 +168,7 @@ public Expr fold_and(java.util.List> expressions) { * @return A conjunction of all given expressions */ private Expr _fold_and(java.util.ArrayList> expressions) { - if (expressions == null || expressions.size() == 0) return TRUE; + if (expressions == null || expressions.isEmpty()) return TRUE; if (expressions.size() == 1) return expressions.get(0); Expr first = expressions.remove(0); return new And<>(first, _fold_and(expressions), OriGen.create()); @@ -192,7 +192,7 @@ public Expr fold_or(java.util.List> expressions) { * @return A disjunction of all given expressions */ private Expr _fold_or(java.util.ArrayList> expressions) { - if (expressions == null || expressions.size() == 0) return FALSE; + if (expressions == null || expressions.isEmpty()) return FALSE; if (expressions.size() == 1) return expressions.get(0); Expr first = expressions.remove(0); return new Or<>(first, _fold_or(expressions), OriGen.create()); @@ -325,19 +325,24 @@ else if (size_expr instanceof SCVariableExpression var_expr && is_parameter(var_ private final java.util.Map> prim_channel_perms; /** - * Field for the process_state sequence. + * Fields for the process_state encoding. */ - private InstanceField process_state; + private final java.util.Map> process_state; /** - * Field for the event_state sequence. + * Fields for the event_state encoding. */ - private InstanceField event_state; + private final java.util.Map> event_state; /** - * Field for the primitive_channel_update sequence. + * Fields for the primitive_channel_update encoding. */ - private InstanceField primitive_channel_update; + private final java.util.Map> primitive_channel_update; + + /** + * A mapping from known types to their respective ID in primitive_channel_update. + */ + private final java.util.Map primitive_channel_ids; /** * A list of all global declarations (e.g. classes) in the system; represents the top-level AST node during @@ -481,6 +486,10 @@ else if (size_expr instanceof SCVariableExpression var_expr && is_parameter(var_ public COLSystem() { this.enums = new java.util.ArrayList<>(); this.prim_channel_perms = new java.util.HashMap<>(); + this.process_state = new java.util.HashMap<>(); + this.event_state = new java.util.HashMap<>(); + this.primitive_channel_update = new java.util.HashMap<>(); + this.primitive_channel_ids = new java.util.HashMap<>(); this.primitive_channels = new java.util.HashMap<>(); this.global_declarations = new java.util.ArrayList<>(); this.process_mapping = new java.util.HashMap<>(); @@ -674,57 +683,80 @@ public java.util.Collection> get_all_prim_channel_invariant } /** - * Registers the field for the process state sequence. + * Returns the process_state field for the given process ID. Creates a new field if it doesn't exist. * - * @param new_proc_state Process state sequence field + * @param id Process ID the field should represent + * @return An instance field of the Main class containing the process state */ - public void set_process_state(InstanceField new_proc_state) { - this.process_state = new_proc_state; + public InstanceField get_process_state(int id) { + if (process_state.containsKey(id)) return process_state.get(id); + InstanceField new_process_state = new InstanceField<>(T_INT, NO_FLAGS, OriGen.create("process_state_" + id)); + process_state.put(id, new_process_state); + return new_process_state; } /** - * Returns the process_state sequence field. + * Returns the value set of all process states. * - * @return An instance field of the Main class containing the process state + * @return A collection containing all process state variables */ - public InstanceField get_process_state() { - return process_state; + public java.util.Collection> get_all_process_states() { + return process_state.values(); } /** - * Registers the field for the event state sequence. + * Returns the event_state field for the given event ID. Creates a new field if it doesn't exist. * - * @param new_event_state Event state sequence field + * @param id Event ID the field should represent + * @return An instance field of the Main class containing the event state */ - public void set_event_state(InstanceField new_event_state) { - this.event_state = new_event_state; + public InstanceField get_event_state(int id) { + if (event_state.containsKey(id)) return event_state.get(id); + InstanceField new_event_state = new InstanceField<>(T_INT, NO_FLAGS, OriGen.create("event_state_" + id)); + event_state.put(id, new_event_state); + return new_event_state; } /** - * Returns the event_state sequence field. + * Returns the value set of all event states. * - * @return An instance field of the Main class containing the event state + * @return A collection containing all event state variables */ - public InstanceField get_event_state() { - return event_state; + public java.util.Collection> get_all_event_states() { + return event_state.values(); } /** - * Registers the field for the primitive channel update sequence. + * Returns the primitive_channel_update field for the given primitive channel ID. Creates a new field + * if it doesn't exist. * - * @param new_prim_update Primitive channel update sequence field + * @param id Primitive channel ID the field should represent + * @return An instance field of the Main class containing the primitive channel updates */ - public void set_primitive_channel_update(InstanceField new_prim_update) { - this.primitive_channel_update = new_prim_update; + public InstanceField get_primitive_channel_update(int id) { + if (primitive_channel_update.containsKey(id)) return primitive_channel_update.get(id); + InstanceField new_prim_channel = new InstanceField<>(T_BOOL, NO_FLAGS, OriGen.create("primitive_channel_update_" + id)); + primitive_channel_update.put(id, new_prim_channel); + return new_prim_channel; } /** - * Returns the primitive_channel_update sequence field. + * Returns the value set of all primitive channel updates. * - * @return An instance field of the Main class containing the primitive channel updates + * @return A collection containing all primitive channel update variables */ - public InstanceField get_primitive_channel_update() { - return primitive_channel_update; + public java.util.Collection> get_all_primitive_channel_updates() { + return primitive_channel_update.values(); + } + + public void register_primitive_channel(SCKnownType sc_inst) { + primitive_channel_ids.put(sc_inst, total_nr_primitive_channels); + InstanceField update_field = new InstanceField<>(T_BOOL, NO_FLAGS, OriGen.create("primitive_channel_update_" + total_nr_primitive_channels++)) + primitive_channel_update.put(total_nr_primitive_channels, update_field); + } + + public int get_primitive_channel_id(SCKnownType sc_inst) { + return primitive_channel_ids.get(sc_inst); } /** @@ -1123,7 +1155,6 @@ public SCClassInstance get_hierarchical_port_connection(SCClassInstance module, */ public void add_primitive_channel(SCKnownType sc_inst, InstanceField main_field) { this.primitive_channels.put(sc_inst, main_field); - total_nr_primitive_channels += 1; } /** @@ -1259,13 +1290,4 @@ public void add_wait_event() { public int get_total_nr_events() { return total_nr_events; } - - /** - * Returns the number of primitive channels in the COL system. - * - * @return Number of primitive channel instances in the COL system - */ - public int get_nr_primitive_channels() { - return total_nr_primitive_channels; - } } diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/ExpressionTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/ExpressionTransformer.java index 5bec2e09fb..689bfc61c8 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/ExpressionTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/ExpressionTransformer.java @@ -413,7 +413,7 @@ private Statement transform_event_notification_expression(EventNotificationEx if (expr.getEvent() instanceof SCVariableExpression var_expr) { if (var_expr.getVar() instanceof SCEvent event) { // Find corresponding event ID - Expr event_id = new IntegerValue<>(BigInt.apply(col_system.get_shared_event(sc_inst, event)), OriGen.create()); + int event_id = col_system.get_shared_event(sc_inst, event); // Find wait time Expr wait_time; @@ -432,12 +432,11 @@ private Statement transform_event_notification_expression(EventNotificationEx // Create reference to event sequence Ref> m_ref = new DirectRef<>(m, ClassTag$.MODULE$.apply(InstanceField.class)); Deref m_deref = new Deref<>(col_system.THIS, m_ref, new GeneratedBlame<>(), OriGen.create()); - Ref> event_ref = new DirectRef<>(col_system.get_event_state(), ClassTag$.MODULE$.apply(InstanceField.class)); + Ref> event_ref = new DirectRef<>(col_system.get_event_state(event_id), ClassTag$.MODULE$.apply(InstanceField.class)); Deref events_deref = new Deref<>(m_deref, event_ref, new GeneratedBlame<>(), OriGen.create()); // Create sequence update - SeqUpdate update_event = new SeqUpdate<>(events_deref, event_id, wait_time, OriGen.create()); - Statement result = new Assign<>(events_deref, update_event, new GeneratedBlame<>(), OriGen.create()); + Statement result = new Assign<>(events_deref, wait_time, new GeneratedBlame<>(), OriGen.create()); // Handle label return append_label(result, expr); @@ -807,15 +806,10 @@ private Statement transform_while_loop_expression(WhileLoopExpression expr, S private Statement transform_wait_expression(FunctionCallExpression expr, SCClassInstance sc_inst, Expr obj) { java.util.List> statements = new java.util.ArrayList<>(); - // Process and event field refs + // Find main field Ref> m_ref = new DirectRef<>(m, ClassTag$.MODULE$.apply(InstanceField.class)); Deref m_deref = new Deref<>(col_system.THIS, m_ref, new GeneratedBlame<>(), OriGen.create()); - Ref> proc_ref = new DirectRef<>(col_system.get_process_state(), ClassTag$.MODULE$.apply(InstanceField.class)); - Ref> event_ref = new DirectRef<>(col_system.get_event_state(), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref procs_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); - Deref events_deref = new Deref<>(m_deref, event_ref, new GeneratedBlame<>(), OriGen.create()); - // Get parameters of wait call java.util.List params = expr.getParameters(); @@ -838,9 +832,14 @@ private Statement transform_wait_expression(FunctionCallExpression expr, SCCl } IntegerValue ev_id = new IntegerValue<>(BigInt.apply(event_id), OriGen.create()); + // Find references to relevant process and event state fields + Ref> proc_ref = new DirectRef<>(col_system.get_process_state(process_id), ClassTag$.MODULE$.apply(InstanceField.class)); + Ref> event_ref = new DirectRef<>(col_system.get_event_state(event_id), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref proc_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); + Deref event_deref = new Deref<>(m_deref, event_ref, new GeneratedBlame<>(), OriGen.create()); + // Create process state update - SeqUpdate update_proc = new SeqUpdate<>(procs_deref, proc_id, ev_id, OriGen.create()); - statements.add(new Assign<>(procs_deref, update_proc, new GeneratedBlame<>(), OriGen.create())); + statements.add(new Assign<>(proc_deref, ev_id, new GeneratedBlame<>(), OriGen.create())); // If the wait is waiting for time, also notify the event if (params.size() == 2) { @@ -871,12 +870,11 @@ private Statement transform_wait_expression(FunctionCallExpression expr, SCCl } // Create event state update - SeqUpdate update_event = new SeqUpdate<>(events_deref, ev_id, wait_time, OriGen.create()); - statements.add(new Assign<>(events_deref, update_event, new GeneratedBlame<>(), OriGen.create())); + statements.add(new Assign<>(event_deref, wait_time, new GeneratedBlame<>(), OriGen.create())); } // Add wait loop and finish block - statements.add(create_wait_loop(proc_id, ev_id)); + statements.add(create_wait_loop(process_id, event_id)); Statement result = new Block<>(List.from(CollectionConverters.asScala(statements)), OriGen.create()); // Handle label @@ -905,23 +903,21 @@ private Statement transform_fifo_call_expression(SCPortSCSocketExpression fif // Process field reference Ref> m_ref = new DirectRef<>(m, ClassTag$.MODULE$.apply(InstanceField.class)); Deref m_deref = new Deref<>(col_system.THIS, m_ref, new GeneratedBlame<>(), OriGen.create()); - Ref> proc_ref = new DirectRef<>(col_system.get_process_state(), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref procs_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); // Decode the fifo queue SCPort sc_port = fifo.getSCPortSCSocket(); SCKnownType channel = col_system.get_primitive_port_connection(sc_inst, sc_port); Expr fifo_queue = transform_sc_port_sc_socket_expression(fifo, sc_inst); - InstanceField fifo_buffer = col_system.get_primitive_instance_field(channel, Constants.FIFO_BUFFER); - Ref> buf_ref = new DirectRef<>(fifo_buffer, ClassTag$.MODULE$.apply(InstanceField.class)); + Ref> buf_ref = new LazyRef<>(() -> col_system.get_primitive_instance_field(channel, Constants.FIFO_BUFFER), + Option.empty(), ClassTag$.MODULE$.apply(InstanceField.class)); Deref buf_deref = new Deref<>(fifo_queue, buf_ref, new GeneratedBlame<>(), OriGen.create()); Size buf_size = new Size<>(buf_deref, OriGen.create()); - InstanceField fifo_written = col_system.get_primitive_instance_field(channel, Constants.FIFO_WRITTEN); - Ref> written_ref = new DirectRef<>(fifo_written, ClassTag$.MODULE$.apply(InstanceField.class)); + Ref> written_ref = new LazyRef<>(() -> col_system.get_primitive_instance_field(channel, Constants.FIFO_WRITTEN), + Option.empty(), ClassTag$.MODULE$.apply(InstanceField.class)); Deref written_deref = new Deref<>(fifo_queue, written_ref, new GeneratedBlame<>(), OriGen.create()); Size written_size = new Size<>(written_deref, OriGen.create()); - InstanceField fifo_num_read = col_system.get_primitive_instance_field(channel, Constants.FIFO_NUM_READ); - Ref> read_ref = new DirectRef<>(fifo_num_read, ClassTag$.MODULE$.apply(InstanceField.class)); + Ref> read_ref = new LazyRef<>(() -> col_system.get_primitive_instance_field(channel, Constants.FIFO_NUM_READ), + Option.empty(), ClassTag$.MODULE$.apply(InstanceField.class)); Deref read_deref = new Deref<>(fifo_queue, read_ref, new GeneratedBlame<>(), OriGen.create()); // Get a reference to the FIFO size parameter @@ -946,8 +942,8 @@ private Statement transform_fifo_call_expression(SCPortSCSocketExpression fif } default -> throw new UnsupportedException("FIFO method " + fun.getFunction().getName() + " is not supported."); } - InstanceMethod fifo_method = col_system.get_primitive_instance_method(channel, method_index); - Ref> method_ref = new DirectRef<>(fifo_method, ClassTag$.MODULE$.apply(InstanceMethod.class)); + Ref> method_ref = new LazyRef<>(() -> col_system.get_primitive_instance_method(channel, method_index), + Option.empty(), ClassTag$.MODULE$.apply(InstanceMethod.class)); // Decode function call parameters java.util.List> args = new java.util.ArrayList<>(); @@ -958,16 +954,19 @@ private Statement transform_fifo_call_expression(SCPortSCSocketExpression fif } // Create process and event id - IntegerValue proc_id = new IntegerValue<>(BigInt.apply(corr_proc.get_process_id()), OriGen.create()); + int proc_id = corr_proc.get_process_id(); int event_id = col_system.get_channel_events(channel).get(wait_event_index); IntegerValue ev_id = new IntegerValue<>(BigInt.apply(event_id), OriGen.create()); + // Find process state variable + Ref> proc_ref = new DirectRef<>(col_system.get_process_state(proc_id), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref procs_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); + // Create wait statement - SeqUpdate new_process_state = new SeqUpdate<>(procs_deref, proc_id, ev_id, OriGen.create()); - Assign update_process_state = new Assign<>(procs_deref, new_process_state, new GeneratedBlame<>(), OriGen.create()); + Assign update_process_state = new Assign<>(procs_deref, ev_id, new GeneratedBlame<>(), OriGen.create()); // Create wait loop - Loop wait_loop = create_wait_loop(proc_id, ev_id); + Loop wait_loop = create_wait_loop(proc_id, event_id); // Create loop invariant for the big loop SpecificationTransformer specification_transformer = new SpecificationTransformer<>(col_class, col_system, m); @@ -1021,8 +1020,8 @@ private Statement transform_signal_call_expression_to_statement(SCPortSCSocke case "read" -> Constants.SIGNAL_READ_METHOD; default -> throw new UnsupportedException("Signal method " + sc_fun.getName() + " is not supported!"); }; - InstanceMethod signal_method = col_system.get_primitive_instance_method(channel, method_index); - Ref> method_ref = new DirectRef<>(signal_method, ClassTag$.MODULE$.apply(InstanceMethod.class)); + Ref> method_ref = new LazyRef<>(() -> col_system.get_primitive_instance_method(channel, method_index), + Option.empty(), ClassTag$.MODULE$.apply(InstanceMethod.class)); // Decode function call parameters java.util.List> args = new java.util.ArrayList<>(); @@ -1079,15 +1078,15 @@ private Assign decode_assignment(Expr left, String op, Expr right) { * @param event_id ID of the event it should wait on * @return A loop that unlocks the global lock until the event occurred and the process is woken up */ - private Loop create_wait_loop(IntegerValue process_id, IntegerValue event_id) { + private Loop create_wait_loop(int process_id, int event_id) { // Process and event field refs Ref> m_ref = new DirectRef<>(m, ClassTag$.MODULE$.apply(InstanceField.class)); Deref m_deref = new Deref<>(col_system.THIS, m_ref, new GeneratedBlame<>(), OriGen.create()); - Ref> proc_ref = new DirectRef<>(col_system.get_process_state(), ClassTag$.MODULE$.apply(InstanceField.class)); - Ref> event_ref = new DirectRef<>(col_system.get_event_state(), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref procs_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); - Deref events_deref = new Deref<>(m_deref, event_ref, new GeneratedBlame<>(), OriGen.create()); + Ref> proc_ref = new DirectRef<>(col_system.get_process_state(process_id), ClassTag$.MODULE$.apply(InstanceField.class)); + Ref> event_ref = new DirectRef<>(col_system.get_event_state(event_id), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref proc_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); + Deref event_deref = new Deref<>(m_deref, event_ref, new GeneratedBlame<>(), OriGen.create()); // Create waiting loop body Unlock unlock_m = new Unlock<>(m_deref, new GeneratedBlame<>(), OriGen.create()); @@ -1096,10 +1095,8 @@ private Loop create_wait_loop(IntegerValue process_id, IntegerValue eve Block loop_body = new Block<>(List.from(CollectionConverters.asScala(loop_body_statements)), OriGen.create()); // Create loop condition - SeqSubscript proc_index = new SeqSubscript<>(procs_deref, process_id, new GeneratedBlame<>(), OriGen.create()); - Neq proc_ready = new Neq<>(proc_index, col_system.MINUS_ONE, OriGen.create()); - SeqSubscript ev_index = new SeqSubscript<>(events_deref, event_id, new GeneratedBlame<>(), OriGen.create()); - Neq ev_notified = new Neq<>(ev_index, col_system.MINUS_TWO, OriGen.create()); + Neq proc_ready = new Neq<>(proc_deref, col_system.MINUS_ONE, OriGen.create()); + Neq ev_notified = new Neq<>(event_deref, col_system.MINUS_TWO, OriGen.create()); Or loop_cond = new Or<>(proc_ready, ev_notified, OriGen.create()); // Create loop contract @@ -1556,7 +1553,7 @@ private Expr transform_sc_port_sc_socket_expression(SCPortSCSocketExpression // Find the corresponding field in the Main class Ref> channel_ref; if (prim_channel != null) { - channel_ref = new DirectRef<>(col_system.get_primitive_channel(prim_channel), ClassTag$.MODULE$.apply(InstanceField.class)); + channel_ref = new LazyRef<>(() -> col_system.get_primitive_channel(prim_channel), Option.empty(), ClassTag$.MODULE$.apply(InstanceField.class)); } else if (hier_channel != null) { channel_ref = new LazyRef<>(() -> col_system.get_instance_by_class(col_system.get_state_class(hier_channel)), Option.empty(), diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java index b62b7a53d6..d616ee39b5 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java @@ -42,16 +42,6 @@ public class KnownTypeTransformer { */ private final COLSystem col_system; - /** - * Event IDs used by the generated channel. - */ - private final java.util.List event_ids; - - /** - * Index of the primitive channel. - */ - private int prim_channel_index; - /** * Constructor. * @@ -61,7 +51,6 @@ public class KnownTypeTransformer { public KnownTypeTransformer(SCKnownType sc_inst, COLSystem col_system) { this.sc_inst = sc_inst; this.col_system = col_system; - this.event_ids = new java.util.ArrayList<>(); } /** @@ -72,9 +61,6 @@ public void transform() { SCClass sc_class = sc_inst.getSCClass(); String name = generate_class_name(); - // Find index of this channel - prim_channel_index = col_system.get_nr_primitive_channels(); - // Transform the primitive channel Class cls = switch (sc_class.getName()) { case Constants.CLASS_FIFO_INT -> transform_fifo(OriGen.create(name), col_system.T_INT); @@ -84,9 +70,7 @@ public void transform() { default -> throw new UnsupportedException("The known type " + sc_class.getName() + " is not supported."); }; - // Add channel class and events to COL system - java.util.Collections.sort(event_ids); - col_system.add_channel_events(sc_inst, event_ids); + // Add channel class to COL system col_system.add_global_declaration(cls); // Add channel field to COL system @@ -299,37 +283,43 @@ private InstanceMethod create_fifo_read_method(Type t, InstanceField m, Deref written_deref = new Deref<>(col_system.THIS, written_ref, new GeneratedBlame<>(), written.o()); // Get scheduling variable references - Ref> update_ref = new DirectRef<>(col_system.get_primitive_channel_update(), ClassTag$.MODULE$.apply(InstanceField.class)); + InstanceField update = col_system.get_primitive_channel_update(col_system.get_primitive_channel_id(sc_inst)); + Ref> update_ref = new DirectRef<>(update, ClassTag$.MODULE$.apply(InstanceField.class)); Deref update_deref = new Deref<>(m_deref, update_ref, new GeneratedBlame<>(), OriGen.create()); // Create precondition Less not_all_read = new Less<>(read_deref, buf_size, OriGen.create()); AccountedPredicate precondition = new UnitAccountedPredicate<>(new Star<>(perms, not_all_read, OriGen.create()), OriGen.create()); + // Collect conditions + java.util.List> conditions = new java.util.ArrayList<>(); + conditions.add(perms); + // Unchanged variables - Eq written_is_old = new Eq<>(written_deref, new Old<>(written_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); - Eq buffer_is_old = new Eq<>(buf_deref, new Old<>(buf_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + conditions.add(new Eq<>(written_deref, new Old<>(written_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); + conditions.add(new Eq<>(buf_deref, new Old<>(buf_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); // Return value - Ref> ref = new LazyRef>(() -> col_system.get_primitive_instance_method(sc_inst, Constants.FIFO_READ_METHOD), + Ref> ref = new LazyRef<>(() -> col_system.get_primitive_instance_method(sc_inst, Constants.FIFO_READ_METHOD), Option.empty(), ClassTag$.MODULE$.apply(ContractApplicable.class)); Result ret = new Result<>(ref, OriGen.create()); SeqSubscript access = new SeqSubscript<>(buf_deref, read_deref, new GeneratedBlame<>(), OriGen.create()); - Eq result = new Eq<>(ret, new Old<>(access, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + conditions.add(new Eq<>(ret, new Old<>(access, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); // Update to num_read Old old_read = new Old<>(read_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); Plus incr_old = new Plus<>(old_read, col_system.ONE, OriGen.create()); - Eq incr_read = new Eq<>(read_deref, incr_old, OriGen.create()); + conditions.add(new Eq<>(read_deref, incr_old, OriGen.create())); // Update to primitive_channel_update - IntegerValue update_index = new IntegerValue<>(BigInt.apply(prim_channel_index), OriGen.create()); - Old old_update = new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - SeqUpdate update_update = new SeqUpdate<>(old_update, update_index, col_system.TRUE, OriGen.create()); - Eq new_update = new Eq<>(update_deref, update_update, OriGen.create()); + conditions.add(new Eq<>(update_deref, col_system.TRUE, OriGen.create())); + for (InstanceField field : col_system.get_all_primitive_channel_updates()) { + if (!field.equals(update)) { + conditions.add(unchanged_field(m_deref, field)); + } + } // Create postcondition - java.util.List> conditions = java.util.List.of(perms, written_is_old, buffer_is_old, result, incr_read, new_update); AccountedPredicate postcondition = new UnitAccountedPredicate<>(col_system.fold_star(conditions), OriGen.create()); // Finishing the method @@ -370,7 +360,8 @@ private InstanceMethod create_fifo_write_method(Type t, InstanceField m Size written_size = new Size<>(written_deref, OriGen.create()); // Get scheduling variable references - Ref> update_ref = new DirectRef<>(col_system.get_primitive_channel_update(), ClassTag$.MODULE$.apply(InstanceField.class)); + InstanceField update = col_system.get_primitive_channel_update(col_system.get_primitive_channel_id(sc_inst)); + Ref> update_ref = new DirectRef<>(update, ClassTag$.MODULE$.apply(InstanceField.class)); Deref update_deref = new Deref<>(m_deref, update_ref, new GeneratedBlame<>(), OriGen.create()); // Get parameter references @@ -382,25 +373,30 @@ private InstanceMethod create_fifo_write_method(Type t, InstanceField m Less within_bound = new Less<>(total_size, fifo_size_deref, OriGen.create()); AccountedPredicate precondition = new UnitAccountedPredicate<>(new Star<>(perms, within_bound, OriGen.create()), OriGen.create()); + // Collect conditions + java.util.List> conditions = new java.util.ArrayList<>(); + conditions.add(perms); + // Unchanged variables - Eq read_is_old = new Eq<>(read_deref, new Old<>(read_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); - Eq buffer_is_old = new Eq<>(buf_deref, new Old<>(buf_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + conditions.add(new Eq<>(read_deref, new Old<>(read_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); + conditions.add(new Eq<>(buf_deref, new Old<>(buf_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); // Update to written Ref> ref_to_new_val = new DirectRef<>(new_val, ClassTag$.MODULE$.apply(Variable.class)); Seq> literal_vals = List.from(CollectionConverters.asScala(java.util.List.of(new Local<>(ref_to_new_val, new_val.o())))); LiteralSeq new_vals = new LiteralSeq<>(t, literal_vals, OriGen.create()); Concat concat = new Concat<>(written_deref, new_vals, OriGen.create()); - Eq new_written = new Eq<>(written_deref, new Old<>(concat, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + conditions.add(new Eq<>(written_deref, new Old<>(concat, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); // Update to primitive_channel_update - IntegerValue update_index = new IntegerValue<>(BigInt.apply(prim_channel_index), OriGen.create()); - Old old_update = new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - SeqUpdate update_update = new SeqUpdate<>(old_update, update_index, col_system.TRUE, OriGen.create()); - Eq new_update = new Eq<>(update_deref, update_update, OriGen.create()); + conditions.add(new Eq<>(update_deref, col_system.TRUE, OriGen.create())); + for (InstanceField field : col_system.get_all_primitive_channel_updates()) { + if (!field.equals(update)) { + conditions.add(unchanged_field(m_deref, field)); + } + } // Create postcondition - java.util.List> conditions = java.util.List.of(perms, read_is_old, buffer_is_old, new_written, new_update); AccountedPredicate postcondition = new UnitAccountedPredicate<>(col_system.fold_star(conditions), OriGen.create()); // Finishing the method @@ -436,74 +432,69 @@ private InstanceMethod create_fifo_update_method(InstanceField m, Instance Size written_size = new Size<>(written_deref, OriGen.create()); Old old_wr_size = new Old<>(written_size, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - // Get scheduling variable references - Ref> update_ref = new DirectRef<>(col_system.get_primitive_channel_update(), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref update_deref = new Deref<>(m_deref, update_ref, new GeneratedBlame<>(), OriGen.create()); - Ref> proc_ref = new DirectRef<>(col_system.get_process_state(), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref proc_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); - Ref> ev_ref = new DirectRef<>(col_system.get_event_state(), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref ev_deref = new Deref<>(m_deref, ev_ref, new GeneratedBlame<>(), OriGen.create()); - Size ev_size = new Size<>(ev_deref, OriGen.create()); - // Create precondition AccountedPredicate precondition = new UnitAccountedPredicate<>(perms, OriGen.create()); + java.util.List> conditions = new java.util.ArrayList<>(); + conditions.add(perms); // Unchanged variables - Eq proc_is_old = new Eq<>(proc_deref, new Old<>(proc_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); - Eq update_is_old = new Eq<>(update_deref, new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + for (InstanceField field : col_system.get_all_process_states()) { + conditions.add(unchanged_field(m_deref, field)); + } + for (InstanceField field : col_system.get_all_primitive_channel_updates()) { + conditions.add(unchanged_field(m_deref, field)); + } // Access update sequence - IntegerValue update_index = new IntegerValue<>(BigInt.apply(prim_channel_index), OriGen.create()); - SeqSubscript u_i = new SeqSubscript<>(update_deref, update_index, new GeneratedBlame<>(), OriGen.create()); + InstanceField this_update = col_system.get_primitive_channel_update(col_system.get_primitive_channel_id(sc_inst)); + Ref> this_update_ref = new DirectRef<>(this_update, ClassTag$.MODULE$.apply(InstanceField.class)); + Deref u_i = new Deref<>(m_deref, this_update_ref, new GeneratedBlame<>(), OriGen.create()); Not n_u_i = new Not<>(u_i, OriGen.create()); // CASE 1: Everything is unchanged - Eq read_is_old = new Eq<>(read_deref, new Old<>(read_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); - Eq written_is_old = new Eq<>(written_deref, new Old<>(written_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); - Eq buf_is_old = new Eq<>(buf_deref, new Old<>(buf_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); - Eq ev_is_old = new Eq<>(ev_deref, new Old<>(ev_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); - Expr everything_is_old = col_system.fold_and(java.util.List.of(read_is_old, written_is_old, buf_is_old, ev_is_old)); - Implies not_updated = new Implies<>(n_u_i, everything_is_old, OriGen.create()); - - // Get read and write events - int curr_ev_id = col_system.get_total_nr_events(); - int read_event = curr_ev_id + Constants.FIFO_READ_EVENT; - event_ids.add(read_event); - int write_event = curr_ev_id + Constants.FIFO_WRITE_EVENT; - event_ids.add(write_event); - IntegerValue min_ev = new IntegerValue<>(BigInt.apply(curr_ev_id), OriGen.create()); - IntegerValue r_ev = new IntegerValue<>(BigInt.apply(read_event), OriGen.create()); - IntegerValue w_ev = new IntegerValue<>(BigInt.apply(write_event), OriGen.create()); - IntegerValue next_ev = new IntegerValue<>(BigInt.apply(curr_ev_id + event_ids.size()), OriGen.create()); + java.util.List> case_1_effects = new java.util.ArrayList<>(); + case_1_effects.add(new Eq<>(read_deref, new Old<>(read_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); + case_1_effects.add(new Eq<>(written_deref, new Old<>(written_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); + case_1_effects.add(new Eq<>(buf_deref, new Old<>(buf_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); + for (InstanceField field : col_system.get_all_event_states()) { + case_1_effects.add(unchanged_field(m_deref, field)); + } + conditions.add(new Implies<>(n_u_i, col_system.fold_and(case_1_effects), OriGen.create())); // Preparations for CASE 2 - Slice buf_slice = new Slice<>(buf_deref, read_deref, buf_size, OriGen.create()); - Concat buf_written = new Concat<>(buf_slice, written_deref, OriGen.create()); - Take prev_evs = new Take<>(ev_deref, min_ev, OriGen.create()); - Drop next_evs = new Drop<>(ev_deref, next_ev, OriGen.create()); + // Read event + InstanceField read_event_state = col_system.get_event_state(col_system.get_channel_events(sc_inst).get(Constants.FIFO_READ_EVENT)); + Ref> read_event_state_ref = new DirectRef<>(read_event_state, ClassTag$.MODULE$.apply(InstanceField.class)); + Deref read_event_state_deref = new Deref<>(m_deref, read_event_state_ref, new GeneratedBlame<>(), OriGen.create()); + Old old_read_event_state = new Old<>(read_event_state_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); Greater has_been_read = new Greater<>(old_read, col_system.ZERO, OriGen.create()); - SeqSubscript read_ev_status = new SeqSubscript<>(ev_deref, r_ev, new GeneratedBlame<>(), OriGen.create()); - Old old_r_status = new Old<>(read_ev_status, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - Select new_r_status = new Select<>(has_been_read, col_system.MINUS_ONE, old_r_status, OriGen.create()); + Select new_read_status = new Select<>(has_been_read, col_system.MINUS_ONE, old_read_event_state, OriGen.create()); + // Write event + InstanceField write_event_state = col_system.get_event_state(col_system.get_channel_events(sc_inst).get(Constants.FIFO_WRITE_EVENT)); + Ref> write_event_state_ref = new DirectRef<>(write_event_state, ClassTag$.MODULE$.apply(InstanceField.class)); + Deref write_event_state_deref = new Deref<>(m_deref, write_event_state_ref, new GeneratedBlame<>(), OriGen.create()); + Old old_write_event_state = new Old<>(write_event_state_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); Greater has_been_written = new Greater<>(old_wr_size, col_system.ZERO, OriGen.create()); - SeqSubscript write_ev_status = new SeqSubscript<>(ev_deref, w_ev, new GeneratedBlame<>(), OriGen.create()); - Old old_w_status = new Old<>(write_ev_status, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - Select new_w_status = new Select<>(has_been_written, col_system.MINUS_ONE, old_w_status, OriGen.create()); + Select new_write_status = new Select<>(has_been_written, col_system.MINUS_ONE, old_write_event_state, OriGen.create()); + // Buffer + Slice buf_slice = new Slice<>(buf_deref, read_deref, buf_size, OriGen.create()); + Concat buf_written = new Concat<>(buf_slice, written_deref, OriGen.create()); // CASE 2: Update is executed - Eq read_is_zero = new Eq<>(read_deref, col_system.ZERO, OriGen.create()); - Eq written_is_empty = new Eq<>(written_size, col_system.ZERO, OriGen.create()); - Eq buf_update = new Eq<>(buf_deref, new Old<>(buf_written, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); - Eq prev_evs_unchanged = new Eq<>(prev_evs, new Old<>(prev_evs, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); - Eq read_ev_changed = new Eq<>(read_ev_status, new_r_status, OriGen.create()); - Eq write_ev_changed = new Eq<>(write_ev_status, new_w_status, OriGen.create()); - Eq next_evs_unchanged = new Eq<>(next_evs, new Old<>(next_evs, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); - java.util.List> changes = java.util.List.of(read_is_zero, written_is_empty, buf_update, prev_evs_unchanged, - read_ev_changed, write_ev_changed, next_evs_unchanged); - Implies updated = new Implies<>(u_i, col_system.fold_and(changes), OriGen.create()); + java.util.List> case_2_effects = new java.util.ArrayList<>(); + case_2_effects.add(new Eq<>(read_deref, col_system.ZERO, OriGen.create())); + case_2_effects.add(new Eq<>(written_size, col_system.ZERO, OriGen.create())); + case_2_effects.add(new Eq<>(buf_deref, new Old<>(buf_written, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); + for (InstanceField field : col_system.get_all_event_states()) { + if (!field.equals(read_event_state) && !field.equals(write_event_state)) { + case_2_effects.add(unchanged_field(m_deref, field)); + } + } + case_2_effects.add(new Eq<>(read_event_state_deref, new_read_status, OriGen.create())); + case_2_effects.add(new Eq<>(write_event_state_deref, new_write_status, OriGen.create())); + conditions.add(new Implies<>(u_i, col_system.fold_and(case_2_effects), OriGen.create())); // Create postcondition - java.util.List> conditions = java.util.List.of(perms, proc_is_old, update_is_old, not_updated, updated); AccountedPredicate postcondition = new UnitAccountedPredicate<>(col_system.fold_star(conditions), OriGen.create()); // Finish the method @@ -646,22 +637,25 @@ private InstanceMethod create_signal_read_method(Type t, InstanceField Deref m_deref = new Deref<>(col_system.THIS, new DirectRef<>(m, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), m.o()); Deref val_deref = new Deref<>(col_system.THIS, new DirectRef<>(val, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), val.o()); Deref _val_deref = new Deref<>(col_system.THIS, new DirectRef<>(_val, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), _val.o()); - Ref> ref_to_update = new DirectRef<>(col_system.get_primitive_channel_update(), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref update_deref = new Deref<>(m_deref, ref_to_update, new GeneratedBlame<>(), col_system.get_process_state().o()); + + // Collect conditions + java.util.List> conditions = new java.util.ArrayList<>(); + conditions.add(perms); // Unchanged variables - Eq update_is_old = new Eq<>(update_deref, new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); - Eq val_is_old = new Eq<>(val_deref, new Old<>(val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); - Eq _val_is_old = new Eq<>(_val_deref, new Old<>(_val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + for (InstanceField field : col_system.get_all_primitive_channel_updates()) { + conditions.add(unchanged_field(m_deref, field)); + } + conditions.add(new Eq<>(val_deref, new Old<>(val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); + conditions.add(new Eq<>(_val_deref, new Old<>(_val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); // Method return value Ref> ref = new LazyRef<>(() -> col_system.get_primitive_instance_method(sc_inst, Constants.SIGNAL_READ_METHOD), Option.empty(), ClassTag$.MODULE$.apply(ContractApplicable.class)); Result ret = new Result<>(ref, OriGen.create()); - Eq result = new Eq<>(ret, new Old<>(val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + conditions.add(new Eq<>(ret, new Old<>(val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); // Postcondition - java.util.List> conditions = java.util.List.of(perms, update_is_old, val_is_old, _val_is_old, result); AccountedPredicate postcondition = new UnitAccountedPredicate<>(col_system.fold_star(conditions), OriGen.create()); // Finishing the method @@ -684,7 +678,7 @@ private InstanceMethod create_signal_write_method(Type t, InstanceField Expr perms = create_general_contract(m, false); // Parameters - Variable new_val = new Variable<>(t, OriGen.create("newVal")); + Variable new_val = new Variable<>(t, OriGen.create("new_val")); List> params = List.from(CollectionConverters.asScala(java.util.List.of(new_val))); // Precondition @@ -695,30 +689,37 @@ private InstanceMethod create_signal_write_method(Type t, InstanceField Deref val_deref = new Deref<>(col_system.THIS, new DirectRef<>(val, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), val.o()); Old old_val = new Old<>(val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); Deref _val_deref = new Deref<>(col_system.THIS, new DirectRef<>(_val, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), _val.o()); - Ref> ref_to_update = new DirectRef<>(col_system.get_primitive_channel_update(), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref update_deref = new Deref<>(m_deref, ref_to_update, new GeneratedBlame<>(), col_system.get_process_state().o()); - Old old_update = new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); Local new_val_local = new Local<>(new DirectRef<>(new_val, ClassTag$.MODULE$.apply(Variable.class)), new_val.o()); + // Collect conditions + java.util.List> conditions = new java.util.ArrayList<>(); + conditions.add(perms); + // Unchanged variables - Eq val_is_old = new Eq<>(val_deref, old_val, OriGen.create()); + conditions.add(new Eq<>(val_deref, old_val, OriGen.create())); // Changed _val - Eq changed_val = new Eq<>(_val_deref, new_val_local, OriGen.create()); + conditions.add(new Eq<>(_val_deref, new_val_local, OriGen.create())); - // primitive_channel_update is set if the stored value changed + // Prepare for update + InstanceField update = col_system.get_primitive_channel_update(col_system.get_primitive_channel_id(sc_inst)); + Ref> update_ref = new DirectRef<>(update, ClassTag$.MODULE$.apply(InstanceField.class)); + Deref update_deref = new Deref<>(m_deref, update_ref, new GeneratedBlame<>(), OriGen.create()); + Eq updated = new Eq<>(update_deref, col_system.TRUE, OriGen.create()); + Eq not_updated = new Eq<>(update_deref, new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); Neq val_changed = new Neq<>(new_val_local, old_val, OriGen.create()); Eq val_not_changed = new Eq<>(new_val_local, old_val, OriGen.create()); - IntegerValue prim_index = new IntegerValue<>(BigInt.apply(prim_channel_index), OriGen.create()); - SeqUpdate update_update = new SeqUpdate<>(update_deref, prim_index, col_system.TRUE, OriGen.create()); - Old old_update_update = new Old<>(update_update, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - Eq update_changed = new Eq<>(update_deref, old_update_update, OriGen.create()); - Eq update_unchanged = new Eq<>(update_deref, old_update, OriGen.create()); - Implies updated = new Implies<>(val_changed, update_changed, OriGen.create()); - Implies not_updated = new Implies<>(val_not_changed, update_unchanged, OriGen.create()); + + // primitive_channel_update is set if the stored value changed + for (InstanceField field : col_system.get_all_primitive_channel_updates()) { + if (!field.equals(update)) { + conditions.add(unchanged_field(m_deref, field)); + } + } + conditions.add(new Implies<>(val_changed, updated, OriGen.create())); + conditions.add(new Implies<>(val_not_changed, not_updated, OriGen.create())); // Postcondition - java.util.List> conditions = java.util.List.of(perms, val_is_old, changed_val, updated, not_updated); AccountedPredicate postcondition = new UnitAccountedPredicate<>(col_system.fold_star(conditions), OriGen.create()); // Finishing the method @@ -746,52 +747,52 @@ private InstanceMethod create_signal_update_method(InstanceField m, Instan Deref _val_deref = new Deref<>(col_system.THIS, new DirectRef<>(_val, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), _val.o()); Old old__val = new Old<>(_val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - // Get scheduling variable references - Ref> update_ref = new DirectRef<>(col_system.get_primitive_channel_update(), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref update_deref = new Deref<>(m_deref, update_ref, new GeneratedBlame<>(), OriGen.create()); - Old old_update = new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - Ref> proc_ref = new DirectRef<>(col_system.get_process_state(), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref proc_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); - Old old_proc = new Old<>(proc_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - Ref> ev_ref = new DirectRef<>(col_system.get_event_state(), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref ev_deref = new Deref<>(m_deref, ev_ref, new GeneratedBlame<>(), OriGen.create()); - Old old_ev = new Old<>(ev_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - // Precondition AccountedPredicate precondition = new UnitAccountedPredicate<>(perms, OriGen.create()); + java.util.List> conditions = new java.util.ArrayList<>(); + conditions.add(perms); + // Unchanged fields - Eq process_is_old = new Eq<>(proc_deref, old_proc, OriGen.create()); - Eq update_is_old = new Eq<>(update_deref, old_update, OriGen.create()); - Eq _val_is_old = new Eq<>(_val_deref, old__val, OriGen.create()); + for (InstanceField field : col_system.get_all_process_states()) { + conditions.add(unchanged_field(m_deref, field)); + } + for (InstanceField field : col_system.get_all_primitive_channel_updates()) { + conditions.add(unchanged_field(m_deref, field)); + } + conditions.add(new Eq<>(_val_deref, old__val, OriGen.create())); - // Access update sequence - IntegerValue prim_index = new IntegerValue<>(BigInt.apply(prim_channel_index), OriGen.create()); - SeqSubscript u_i = new SeqSubscript<>(update_deref, prim_index, new GeneratedBlame<>(), OriGen.create()); + // Access update field + InstanceField this_update = col_system.get_primitive_channel_update(col_system.get_primitive_channel_id(sc_inst)); + Ref> this_update_ref = new DirectRef<>(this_update, ClassTag$.MODULE$.apply(InstanceField.class)); + Deref u_i = new Deref<>(m_deref, this_update_ref, new GeneratedBlame<>(), OriGen.create()); Not n_u_i = new Not<>(u_i, OriGen.create()); // CASE 1: Everything is unchanged - Eq ev_is_old = new Eq<>(ev_deref, old_ev, OriGen.create()); - Eq val_is_old = new Eq<>(val_deref, old_val, OriGen.create()); - And everything_unchanged = new And<>(ev_is_old, val_is_old, OriGen.create()); - Implies not_updated = new Implies<>(n_u_i, everything_unchanged, OriGen.create()); + java.util.List> case_1_changes = new java.util.ArrayList<>(); + for (InstanceField field : col_system.get_all_event_states()) { + case_1_changes.add(unchanged_field(m_deref, field)); + } + case_1_changes.add(new Eq<>(val_deref, old_val, OriGen.create())); + conditions.add(new Implies<>(n_u_i, col_system.fold_and(case_1_changes), OriGen.create())); // Get write event - int curr_ev_id = col_system.get_total_nr_events(); - int write_event = curr_ev_id + Constants.SIGNAL_WRITE_EVENT; - event_ids.add(write_event); - IntegerValue w_ev = new IntegerValue<>(BigInt.apply(write_event), OriGen.create()); + InstanceField write_event_state = col_system.get_event_state(col_system.get_channel_events(sc_inst).get(Constants.SIGNAL_WRITE_EVENT)); + Ref> write_event_state_ref = new DirectRef<>(write_event_state, ClassTag$.MODULE$.apply(InstanceField.class)); + Deref write_event_state_deref = new Deref<>(m_deref, write_event_state_ref, new GeneratedBlame<>(), OriGen.create()); // CASE 2: Value changed and write event notified - SeqUpdate ev_update = new SeqUpdate<>(ev_deref, w_ev, col_system.MINUS_ONE, OriGen.create()); - Old old_ev_update = new Old<>(ev_update, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - Eq write_event_notified = new Eq<>(ev_deref, old_ev_update, OriGen.create()); - Eq val_is_hidden_val = new Eq<>(val_deref, old__val, OriGen.create()); - And both_changed = new And<>(write_event_notified, val_is_hidden_val, OriGen.create()); - Implies updated = new Implies<>(u_i, both_changed, OriGen.create()); + java.util.List> case_2_changes = new java.util.ArrayList<>(); + for (InstanceField field : col_system.get_all_event_states()) { + if (!field.equals(write_event_state)) { + case_2_changes.add(unchanged_field(m_deref, field)); + } + } + case_2_changes.add(new Eq<>(write_event_state_deref, col_system.MINUS_ONE, OriGen.create())); + case_2_changes.add(new Eq<>(val_deref, old__val, OriGen.create())); + conditions.add(new Implies<>(u_i, col_system.fold_and(case_2_changes), OriGen.create())); // Postcondition - java.util.List> conditions = java.util.List.of(perms, process_is_old, update_is_old, _val_is_old, not_updated, updated); AccountedPredicate postcondition = new UnitAccountedPredicate<>(col_system.fold_star(conditions), OriGen.create()); // Finish the method @@ -847,4 +848,19 @@ private Expr create_general_contract(InstanceField m, boolean include_sche // Connect the individual conditions with stars and return return col_system.fold_star(java.util.List.of(perm_m, m_not_null, held_m, scheduler_perms, parameter_perms, channel_perms, this_is_self)); } + + /** + * Helper method that returns the equation to indicate that a field is unchanged by a method, i.e. is equal to its + * \old value. + * + * @param m_deref Expression that references the main instance + * @param field Field that should be unchanged + * @return Equation of the form m.field == \old(m.field) + */ + private Eq unchanged_field(Deref m_deref, InstanceField field) { + Ref> f_ref = new DirectRef<>(field, ClassTag$.MODULE$.apply(InstanceField.class)); + Deref f_deref = new Deref<>(m_deref, f_ref, new GeneratedBlame<>(), OriGen.create()); + Old f_old = new Old<>(f_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); + return new Eq<>(f_deref, f_old, OriGen.create()); + } } diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java index 530a416422..574d99977b 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java @@ -232,24 +232,8 @@ private String create_instance_name(COLClass col_class) { * variable primitive_channel_update. */ private void create_update_invariant() { - // Create reference to primitive_channel_update - InstanceField prim_channel_update = col_system.get_primitive_channel_update(); - Ref> update_ref = new DirectRef<>(prim_channel_update, ClassTag$.MODULE$.apply(InstanceField.class)); - Deref update_deref = new Deref<>(col_system.THIS, update_ref, new GeneratedBlame<>(), OriGen.create()); - FieldLocation update_loc = new FieldLocation<>(col_system.THIS, update_ref, OriGen.create()); - - // Create some auxiliary values - Size update_size = new Size<>(update_deref, OriGen.create()); - IntegerValue nr_prim_channels = new IntegerValue<>(BigInt.apply(col_system.get_nr_primitive_channels()), OriGen.create()); - - // Create predicate conditions - Perm update_perm = new Perm<>(update_loc, new WritePerm<>(OriGen.create()), OriGen.create()); - Eq update_length = new Eq<>(update_size, nr_prim_channels, OriGen.create()); - - // Put it all together and register the predicate in the COL system - java.util.List> conditions = java.util.List.of(update_perm, update_length); - update_permission_invariant = new InstancePredicate<>(col_system.NO_VARS, Option.apply(col_system.fold_star(conditions)), - false, true, OriGen.create("update_permission_invariant")); + Option> conds = Option.apply(col_system.fold_star(col_system.get_all_primitive_channel_updates().stream().map(this::write_perm_to_field).toList())); + update_permission_invariant = new InstancePredicate<>(col_system.NO_VARS, conds, false, true, OriGen.create("update_permission_invariant")); col_system.set_update_perms(update_permission_invariant); } @@ -259,54 +243,28 @@ private void create_update_invariant() { * for event_state. */ private void create_scheduler_invariant() { - // Create references to the scheduling variables - Ref> proc_state_ref = new DirectRef<>(col_system.get_process_state(), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref proc_state_deref = new Deref<>(col_system.THIS, proc_state_ref, new GeneratedBlame<>(), OriGen.create()); - FieldLocation proc_state_loc = new FieldLocation<>(col_system.THIS, proc_state_ref, OriGen.create()); - Ref> ev_state_ref = new DirectRef<>(col_system.get_event_state(), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref ev_state_deref = new Deref<>(col_system.THIS, ev_state_ref, new GeneratedBlame<>(), OriGen.create()); - FieldLocation ev_state_loc = new FieldLocation<>(col_system.THIS, ev_state_ref, OriGen.create()); - - // Create some auxiliary values - Size proc_size = new Size<>(proc_state_deref, OriGen.create()); - IntegerValue nr_procs = new IntegerValue<>(BigInt.apply(ProcessClass.get_nr_processes()), OriGen.create()); - Size ev_size = new Size<>(ev_state_deref, OriGen.create()); - IntegerValue nr_events = new IntegerValue<>(BigInt.apply(col_system.get_total_nr_events()), OriGen.create()); + java.util.List> conditions = new java.util.ArrayList<>(); // Apply update permission invariant Ref> ref_update_invariant = new DirectRef<>(update_permission_invariant, ClassTag$.MODULE$.apply(InstancePredicate.class)); - InstancePredicateApply apply_update_perms = new InstancePredicateApply<>(col_system.THIS, ref_update_invariant, - col_system.NO_EXPRS, new WritePerm<>(OriGen.create()), OriGen.create()); - - // Create conditions - Perm perm_to_proc = new Perm<>(proc_state_loc, new WritePerm<>(OriGen.create()), OriGen.create()); - Eq proc_length = new Eq<>(proc_size, nr_procs, OriGen.create()); - Perm perm_to_ev = new Perm<>(ev_state_loc, new WritePerm<>(OriGen.create()), OriGen.create()); - Eq ev_length = new Eq<>(ev_size, nr_events, OriGen.create()); - - // Create forall statement variable - Variable i = new Variable<>(col_system.T_INT, OriGen.create("i")); - Local i_loc = new Local<>(new DirectRef<>(i, ClassTag$.MODULE$.apply(Variable.class)), OriGen.create()); - GreaterEq i_lower = new GreaterEq<>(i_loc, col_system.ZERO, OriGen.create()); - Less i_upper = new Less<>(i_loc, proc_size, OriGen.create()); - And i_bounds = new And<>(i_lower, i_upper, OriGen.create()); - - // Create forall body - SeqSubscript proc_i = new SeqSubscript<>(proc_state_deref, i_loc, new GeneratedBlame<>(), OriGen.create()); - InlinePattern trigger = new InlinePattern<>(proc_i, 0, 0, OriGen.create()); - Eq proc_ready = new Eq<>(trigger, col_system.MINUS_ONE, OriGen.create()); - LessEq proc_lower = new LessEq<>(col_system.ZERO, proc_i, OriGen.create()); - Less proc_upper = new Less<>(proc_i, ev_size, OriGen.create()); - And proc_bounds = new And<>(proc_lower, proc_upper, OriGen.create()); - Or body = new Or<>(proc_ready, proc_bounds, OriGen.create()); - - // Create forall statement - Implies forall_body = new Implies<>(i_bounds, body, OriGen.create()); - List> bindings = List.from(CollectionConverters.asScala(java.util.List.of(i))); - Forall forall = new Forall<>(bindings, col_system.NO_TRIGGERS, forall_body, OriGen.create()); + conditions.add(new InstancePredicateApply<>(col_system.THIS, ref_update_invariant, col_system.NO_EXPRS, new WritePerm<>(OriGen.create()), OriGen.create())); + + IntegerValue ev_size = new IntegerValue<>(BigInt.apply(col_system.get_total_nr_events()), OriGen.create()); + + // Create permissions + for (InstanceField field : col_system.get_all_process_states()) { + conditions.add(write_perm_to_field(field)); + Deref f_deref = new Deref<>(col_system.THIS, new DirectRef<>(field, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), OriGen.create()); + Eq ready = new Eq<>(f_deref, col_system.MINUS_ONE, OriGen.create()); + Less below_ev = new Less<>(f_deref, ev_size, OriGen.create()); + LessEq non_negative = new LessEq<>(col_system.ZERO, f_deref, OriGen.create()); + conditions.add(new Or<>(ready, new And<>(below_ev, non_negative, OriGen.create()), OriGen.create())); + } + for (InstanceField field : col_system.get_all_event_states()) { + conditions.add(write_perm_to_field(field)); + } // Put it all together and register the invariant in the COL system context - java.util.List> conditions = java.util.List.of(apply_update_perms, perm_to_proc, proc_length, perm_to_ev, ev_length, forall); scheduler_invariant = new InstancePredicate<>(col_system.NO_VARS, Option.apply(col_system.fold_star(conditions)), false, true, OriGen.create("scheduler_invariant")); col_system.set_scheduler_perms(scheduler_invariant); @@ -449,9 +407,9 @@ private void create_main_constructor() { java.util.List> initializations = new java.util.ArrayList<>(); // Create initializations for the scheduling variables - initializations.add(create_process_state_initialization()); - initializations.add(create_event_state_initialization()); - initializations.add(create_primitive_channel_update_initialization()); + initializations.addAll(create_process_state_initialization()); + initializations.addAll(create_event_state_initialization()); + initializations.addAll(create_primitive_channel_update_initialization()); // Create initializations for all instance fields for (InstanceField channel : channels) { @@ -477,70 +435,31 @@ private void create_main_constructor() { } /** - * Generates the initialization of the process_state scheduling variable. Every entry is set to -1. + * Generates the initialization of the process_state scheduling variables. Every entry is set to -1. * - * @return An assignment for the process state initialization + * @return Assignments for the process state initialization */ - private Statement create_process_state_initialization() { - // Get reference to process state field - InstanceField process_state = col_system.get_process_state(); - Ref> state_ref = new DirectRef<>(process_state, ClassTag$.MODULE$.apply(InstanceField.class)); - Deref state_deref = new Deref<>(col_system.THIS, state_ref, new GeneratedBlame<>(), OriGen.create()); - - // Construct the literal sequence it should be initialized as ([-1] * #processes) - java.util.List> literal_values = new java.util.ArrayList<>(); - for (int i = 0; i < ProcessClass.get_nr_processes(); i++) { - literal_values.add(col_system.MINUS_ONE); - } - LiteralSeq literal = new LiteralSeq<>(col_system.T_INT, List.from(CollectionConverters.asScala(literal_values)), OriGen.create()); - - // Assign the literal to the field - return new Assign<>(state_deref, literal, new GeneratedBlame<>(), OriGen.create()); + private java.util.List> create_process_state_initialization() { + return col_system.get_all_process_states().stream().map((f) -> assign_to_field(f, col_system.MINUS_ONE)).toList(); } /** - * Generates the initialization of the event_state scheduling variable. Every entry is set to -3. + * Generates the initialization of the event_state scheduling variables. Every entry is set to -3. * - * @return An assignment for the event state initialization + * @return Assignments for the event state initialization */ - private Statement create_event_state_initialization() { - // Get reference to the event state field - InstanceField event_state = col_system.get_event_state(); - Ref> state_ref = new DirectRef<>(event_state, ClassTag$.MODULE$.apply(InstanceField.class)); - Deref state_deref = new Deref<>(col_system.THIS, state_ref, new GeneratedBlame<>(), OriGen.create()); - - // Construct the literal sequence it should be initialized as ([-3] * #events) - java.util.List> literal_values = new java.util.ArrayList<>(); - for (int i = 0; i < col_system.get_total_nr_events(); i++) { - literal_values.add(col_system.MINUS_THREE); - } - LiteralSeq literal = new LiteralSeq<>(col_system.T_INT, List.from(CollectionConverters.asScala(literal_values)), OriGen.create()); - - // Assign the literal to the field - return new Assign<>(state_deref, literal, new GeneratedBlame<>(), OriGen.create()); + private java.util.List> create_event_state_initialization() { + return col_system.get_all_event_states().stream().map((f) -> assign_to_field(f, col_system.MINUS_THREE)).toList(); } /** - * Generates the initialization of the primitive_channel_update scheduling variable. Every entry is set - * to false. + * Generates the initialization of the primitive_channel_update scheduling variables. Every entry is + * set to false. * - * @return An assignment for the primitive channel update initialization + * @return Assignments for the primitive channel update initialization */ - private Statement create_primitive_channel_update_initialization() { - // Get reference to the primitive channel update field - InstanceField prim_channel_update = col_system.get_primitive_channel_update(); - Ref> update_ref = new DirectRef<>(prim_channel_update, ClassTag$.MODULE$.apply(InstanceField.class)); - Deref update_deref = new Deref<>(col_system.THIS, update_ref, new GeneratedBlame<>(), OriGen.create()); - - // Construct the literal sequence it should be initialized as ([false] * #primitive channels) - java.util.List> literal_values = new java.util.ArrayList<>(); - for (int i = 0; i < col_system.get_nr_primitive_channels(); i++) { - literal_values.add(col_system.FALSE); - } - LiteralSeq literal = new LiteralSeq<>(col_system.T_BOOL, List.from(CollectionConverters.asScala(literal_values)), OriGen.create()); - - // Assign the literal to the field - return new Assign<>(update_deref, literal, new GeneratedBlame<>(), OriGen.create()); + private java.util.List> create_primitive_channel_update_initialization() { + return col_system.get_all_primitive_channel_updates().stream().map((f) -> assign_to_field(f, col_system.FALSE)).toList(); } /** @@ -574,7 +493,7 @@ private Statement create_field_initialization(InstanceField field) { SCVariableDeclarationExpression declaration = sc_inst.getDeclaration(); if (declaration != null) { java.util.List params = declaration.getInitialValues(); - if (params != null && params.size() > 0) { + if (params != null && !params.isEmpty()) { // The first parameter is the name - ignore it params.remove(0); @@ -1201,9 +1120,9 @@ private void assemble_main() { java.util.List> declarations = new java.util.ArrayList<>(); // Add all fields to the class - declarations.add(col_system.get_process_state()); - declarations.add(col_system.get_event_state()); - declarations.add(col_system.get_primitive_channel_update()); + declarations.addAll(col_system.get_all_process_states()); + declarations.addAll(col_system.get_all_event_states()); + declarations.addAll(col_system.get_all_primitive_channel_updates()); declarations.addAll(processes); declarations.addAll(state_classes); declarations.addAll(channels); @@ -1241,4 +1160,16 @@ private void assemble_main() { col_system.add_global_declaration(main_class); col_system.set_main(main_class); } + + private Expr write_perm_to_field(InstanceField field) { + Ref> field_ref = new DirectRef<>(field, ClassTag$.MODULE$.apply(InstanceField.class)); + FieldLocation field_loc = new FieldLocation<>(col_system.THIS, field_ref, OriGen.create()); + return new Perm<>(field_loc, new WritePerm<>(OriGen.create()), OriGen.create()); + } + + private Assign assign_to_field(InstanceField field, Expr value) { + Ref> field_ref = new DirectRef<>(field, ClassTag$.MODULE$.apply(InstanceField.class)); + Deref field_deref = new Deref<>(col_system.THIS, field_ref, new GeneratedBlame<>(), OriGen.create()); + return new Assign<>(field_deref, value, new GeneratedBlame<>(), OriGen.create()); + } } diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/Transformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/Transformer.java index 1c4d9a4c75..7a18e3dfa8 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/Transformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/Transformer.java @@ -61,16 +61,16 @@ public void create_col_model() { scan_port_connections(); // Register enums to be translated to integer values scan_enums(); - // Create references to necessary Main class attributes - create_minimal_main(); + // Register events and channel IDs for primitive channels + scan_primitive_channels(); // Translate system parameters translate_system_parameters(); - // Transform known types - transform_known_types(); // Transform shared event variables to event IDs handle_shared_events(); // Transform all regular classes transform_classes(); + // Transform known types + transform_known_types(); // Finish the Main class create_main_class(); } @@ -160,7 +160,7 @@ private void scan_processes() { // Multiple threads are declared -> one thread class for each thread and possibly a state class for shared functionality default -> { // Create state class if at least one member function or attribute is declared - if (member_functions.size() > 0 || members.size() > 0) { + if (!member_functions.isEmpty() || !members.isEmpty()) { StateClass state_class = new StateClass(inst); state_class.set_constructor(constructor); state_class.add_attributes(members); @@ -226,11 +226,11 @@ private void scan_port_connections() { java.util.List hierarchical = sc_port_inst.getModuleInstances(); // If there is a SystemC-internal channel, register it in the COL system context - if (primitive.size() > 0) { + if (!primitive.isEmpty()) { col_system.add_primitive_port_connection(sc_inst, sc_port, primitive.get(0)); } // If there is a user-defined channel, register it in the COL system context - else if (hierarchical.size() > 0) { + else if (!hierarchical.isEmpty()) { col_system.add_hierarchical_port_connection(sc_inst, sc_port, hierarchical.get(0)); } else throw new SystemCFormatException("Port " + sc_port + " has no bound primitive or hierarchical channels!"); @@ -249,13 +249,26 @@ private void scan_enums() { } /** - * Creates the Main class's instance fields for the process and event state sequences. Does not yet instantiate the - * Main class itself. + * Scans the SystemC system for primitive channels and creates the necessary event and primitive update fields. */ - private void create_minimal_main() { - col_system.set_process_state(new InstanceField<>(col_system.T_SEQ_INT, col_system.NO_FLAGS, OriGen.create("process_state"))); - col_system.set_event_state(new InstanceField<>(col_system.T_SEQ_INT, col_system.NO_FLAGS, OriGen.create("event_state"))); - col_system.set_primitive_channel_update(new InstanceField<>(col_system.T_SEQ_BOOL, col_system.NO_FLAGS, OriGen.create("primitive_channel_update"))); + private void scan_primitive_channels() { + for (SCClassInstance instance : sc_system.getInstances()) { + if (instance instanceof SCKnownType sc_inst) { + java.util.ArrayList events = switch (sc_inst.getSCClass().getName()) { + case Constants.CLASS_FIFO_INT, Constants.CLASS_FIFO_BOOL -> + new java.util.ArrayList<>(java.util.List.of(col_system.get_total_nr_events() + Constants.FIFO_READ_EVENT, + col_system.get_total_nr_events() + Constants.FIFO_WRITE_EVENT)); + case Constants.CLASS_SIGNAL_INT, Constants.CLASS_SIGNAL_BOOL -> + new java.util.ArrayList<>(java.util.List.of(col_system.get_total_nr_events() + Constants.SIGNAL_WRITE_EVENT)); + default -> throw new UnsupportedException("Known type " + sc_inst.getSCClass().getName() + " is not supported."); + }; + // Register events + java.util.Collections.sort(events); + col_system.add_channel_events(sc_inst, events); + // Register primitive channel + col_system.register_primitive_channel(sc_inst); + } + } } /** @@ -283,18 +296,6 @@ private void translate_system_parameters() { } } - /** - * Transforms known types, i.e. SystemC-internal primitive channels, to their respective abstract encoding. - */ - private void transform_known_types() { - for (SCClassInstance instance : sc_system.getInstances()) { - if (instance instanceof SCKnownType inst) { - KnownTypeTransformer transformer = new KnownTypeTransformer<>(inst, col_system); - transformer.transform(); - } - } - } - /** * Transforms explicit sc_event variables in the SystemC system to event IDs in the COL system. */ @@ -337,6 +338,18 @@ private void transform_classes() { } } + /** + * Transforms known types, i.e. SystemC-internal primitive channels, to their respective abstract encoding. + */ + private void transform_known_types() { + for (SCClassInstance instance : sc_system.getInstances()) { + if (instance instanceof SCKnownType inst) { + KnownTypeTransformer transformer = new KnownTypeTransformer<>(inst, col_system); + transformer.transform(); + } + } + } + /** * Generates the Main class. The main class contains an attribute for each instance in the encoded system, the * global permission invariant, the permission invariant for the scheduler, the permission invariant for each From fd8f40e5c176524760b06043193ec4e8637aea47 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 11 Mar 2024 15:40:17 +0100 Subject: [PATCH 57/85] Revert "[FAILED] Tried to switch from sequences to individual variables" This reverts commit c4728bde8436fc7694578b3fc58b40aa4efa04a5. --- .../systemctocol/colmodel/COLSystem.java | 108 +++---- .../engine/ExpressionTransformer.java | 79 ++--- .../engine/KnownTypeTransformer.java | 300 +++++++++--------- .../systemctocol/engine/MainTransformer.java | 171 +++++++--- .../systemctocol/engine/Transformer.java | 63 ++-- 5 files changed, 371 insertions(+), 350 deletions(-) diff --git a/src/parsers/vct/parsers/transform/systemctocol/colmodel/COLSystem.java b/src/parsers/vct/parsers/transform/systemctocol/colmodel/COLSystem.java index 7a91f0f3c9..e4a4394b66 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/colmodel/COLSystem.java +++ b/src/parsers/vct/parsers/transform/systemctocol/colmodel/COLSystem.java @@ -144,7 +144,7 @@ public Expr fold_star(java.util.List> expressions) { * @return A single expression with all given expressions connected by stars */ private Expr _fold_star(java.util.ArrayList> expressions) { - if (expressions == null || expressions.isEmpty()) return TRUE; + if (expressions == null || expressions.size() == 0) return TRUE; if (expressions.size() == 1) return expressions.get(0); Expr first = expressions.remove(0); return new Star<>(first, _fold_star(expressions), OriGen.create()); @@ -168,7 +168,7 @@ public Expr fold_and(java.util.List> expressions) { * @return A conjunction of all given expressions */ private Expr _fold_and(java.util.ArrayList> expressions) { - if (expressions == null || expressions.isEmpty()) return TRUE; + if (expressions == null || expressions.size() == 0) return TRUE; if (expressions.size() == 1) return expressions.get(0); Expr first = expressions.remove(0); return new And<>(first, _fold_and(expressions), OriGen.create()); @@ -192,7 +192,7 @@ public Expr fold_or(java.util.List> expressions) { * @return A disjunction of all given expressions */ private Expr _fold_or(java.util.ArrayList> expressions) { - if (expressions == null || expressions.isEmpty()) return FALSE; + if (expressions == null || expressions.size() == 0) return FALSE; if (expressions.size() == 1) return expressions.get(0); Expr first = expressions.remove(0); return new Or<>(first, _fold_or(expressions), OriGen.create()); @@ -325,24 +325,19 @@ else if (size_expr instanceof SCVariableExpression var_expr && is_parameter(var_ private final java.util.Map> prim_channel_perms; /** - * Fields for the process_state encoding. + * Field for the process_state sequence. */ - private final java.util.Map> process_state; + private InstanceField process_state; /** - * Fields for the event_state encoding. + * Field for the event_state sequence. */ - private final java.util.Map> event_state; + private InstanceField event_state; /** - * Fields for the primitive_channel_update encoding. + * Field for the primitive_channel_update sequence. */ - private final java.util.Map> primitive_channel_update; - - /** - * A mapping from known types to their respective ID in primitive_channel_update. - */ - private final java.util.Map primitive_channel_ids; + private InstanceField primitive_channel_update; /** * A list of all global declarations (e.g. classes) in the system; represents the top-level AST node during @@ -486,10 +481,6 @@ else if (size_expr instanceof SCVariableExpression var_expr && is_parameter(var_ public COLSystem() { this.enums = new java.util.ArrayList<>(); this.prim_channel_perms = new java.util.HashMap<>(); - this.process_state = new java.util.HashMap<>(); - this.event_state = new java.util.HashMap<>(); - this.primitive_channel_update = new java.util.HashMap<>(); - this.primitive_channel_ids = new java.util.HashMap<>(); this.primitive_channels = new java.util.HashMap<>(); this.global_declarations = new java.util.ArrayList<>(); this.process_mapping = new java.util.HashMap<>(); @@ -683,80 +674,57 @@ public java.util.Collection> get_all_prim_channel_invariant } /** - * Returns the process_state field for the given process ID. Creates a new field if it doesn't exist. + * Registers the field for the process state sequence. * - * @param id Process ID the field should represent - * @return An instance field of the Main class containing the process state + * @param new_proc_state Process state sequence field */ - public InstanceField get_process_state(int id) { - if (process_state.containsKey(id)) return process_state.get(id); - InstanceField new_process_state = new InstanceField<>(T_INT, NO_FLAGS, OriGen.create("process_state_" + id)); - process_state.put(id, new_process_state); - return new_process_state; + public void set_process_state(InstanceField new_proc_state) { + this.process_state = new_proc_state; } /** - * Returns the value set of all process states. + * Returns the process_state sequence field. * - * @return A collection containing all process state variables + * @return An instance field of the Main class containing the process state */ - public java.util.Collection> get_all_process_states() { - return process_state.values(); + public InstanceField get_process_state() { + return process_state; } /** - * Returns the event_state field for the given event ID. Creates a new field if it doesn't exist. + * Registers the field for the event state sequence. * - * @param id Event ID the field should represent - * @return An instance field of the Main class containing the event state + * @param new_event_state Event state sequence field */ - public InstanceField get_event_state(int id) { - if (event_state.containsKey(id)) return event_state.get(id); - InstanceField new_event_state = new InstanceField<>(T_INT, NO_FLAGS, OriGen.create("event_state_" + id)); - event_state.put(id, new_event_state); - return new_event_state; + public void set_event_state(InstanceField new_event_state) { + this.event_state = new_event_state; } /** - * Returns the value set of all event states. + * Returns the event_state sequence field. * - * @return A collection containing all event state variables + * @return An instance field of the Main class containing the event state */ - public java.util.Collection> get_all_event_states() { - return event_state.values(); + public InstanceField get_event_state() { + return event_state; } /** - * Returns the primitive_channel_update field for the given primitive channel ID. Creates a new field - * if it doesn't exist. + * Registers the field for the primitive channel update sequence. * - * @param id Primitive channel ID the field should represent - * @return An instance field of the Main class containing the primitive channel updates + * @param new_prim_update Primitive channel update sequence field */ - public InstanceField get_primitive_channel_update(int id) { - if (primitive_channel_update.containsKey(id)) return primitive_channel_update.get(id); - InstanceField new_prim_channel = new InstanceField<>(T_BOOL, NO_FLAGS, OriGen.create("primitive_channel_update_" + id)); - primitive_channel_update.put(id, new_prim_channel); - return new_prim_channel; + public void set_primitive_channel_update(InstanceField new_prim_update) { + this.primitive_channel_update = new_prim_update; } /** - * Returns the value set of all primitive channel updates. + * Returns the primitive_channel_update sequence field. * - * @return A collection containing all primitive channel update variables + * @return An instance field of the Main class containing the primitive channel updates */ - public java.util.Collection> get_all_primitive_channel_updates() { - return primitive_channel_update.values(); - } - - public void register_primitive_channel(SCKnownType sc_inst) { - primitive_channel_ids.put(sc_inst, total_nr_primitive_channels); - InstanceField update_field = new InstanceField<>(T_BOOL, NO_FLAGS, OriGen.create("primitive_channel_update_" + total_nr_primitive_channels++)) - primitive_channel_update.put(total_nr_primitive_channels, update_field); - } - - public int get_primitive_channel_id(SCKnownType sc_inst) { - return primitive_channel_ids.get(sc_inst); + public InstanceField get_primitive_channel_update() { + return primitive_channel_update; } /** @@ -1155,6 +1123,7 @@ public SCClassInstance get_hierarchical_port_connection(SCClassInstance module, */ public void add_primitive_channel(SCKnownType sc_inst, InstanceField main_field) { this.primitive_channels.put(sc_inst, main_field); + total_nr_primitive_channels += 1; } /** @@ -1290,4 +1259,13 @@ public void add_wait_event() { public int get_total_nr_events() { return total_nr_events; } + + /** + * Returns the number of primitive channels in the COL system. + * + * @return Number of primitive channel instances in the COL system + */ + public int get_nr_primitive_channels() { + return total_nr_primitive_channels; + } } diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/ExpressionTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/ExpressionTransformer.java index 689bfc61c8..5bec2e09fb 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/ExpressionTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/ExpressionTransformer.java @@ -413,7 +413,7 @@ private Statement transform_event_notification_expression(EventNotificationEx if (expr.getEvent() instanceof SCVariableExpression var_expr) { if (var_expr.getVar() instanceof SCEvent event) { // Find corresponding event ID - int event_id = col_system.get_shared_event(sc_inst, event); + Expr event_id = new IntegerValue<>(BigInt.apply(col_system.get_shared_event(sc_inst, event)), OriGen.create()); // Find wait time Expr wait_time; @@ -432,11 +432,12 @@ private Statement transform_event_notification_expression(EventNotificationEx // Create reference to event sequence Ref> m_ref = new DirectRef<>(m, ClassTag$.MODULE$.apply(InstanceField.class)); Deref m_deref = new Deref<>(col_system.THIS, m_ref, new GeneratedBlame<>(), OriGen.create()); - Ref> event_ref = new DirectRef<>(col_system.get_event_state(event_id), ClassTag$.MODULE$.apply(InstanceField.class)); + Ref> event_ref = new DirectRef<>(col_system.get_event_state(), ClassTag$.MODULE$.apply(InstanceField.class)); Deref events_deref = new Deref<>(m_deref, event_ref, new GeneratedBlame<>(), OriGen.create()); // Create sequence update - Statement result = new Assign<>(events_deref, wait_time, new GeneratedBlame<>(), OriGen.create()); + SeqUpdate update_event = new SeqUpdate<>(events_deref, event_id, wait_time, OriGen.create()); + Statement result = new Assign<>(events_deref, update_event, new GeneratedBlame<>(), OriGen.create()); // Handle label return append_label(result, expr); @@ -806,10 +807,15 @@ private Statement transform_while_loop_expression(WhileLoopExpression expr, S private Statement transform_wait_expression(FunctionCallExpression expr, SCClassInstance sc_inst, Expr obj) { java.util.List> statements = new java.util.ArrayList<>(); - // Find main field + // Process and event field refs Ref> m_ref = new DirectRef<>(m, ClassTag$.MODULE$.apply(InstanceField.class)); Deref m_deref = new Deref<>(col_system.THIS, m_ref, new GeneratedBlame<>(), OriGen.create()); + Ref> proc_ref = new DirectRef<>(col_system.get_process_state(), ClassTag$.MODULE$.apply(InstanceField.class)); + Ref> event_ref = new DirectRef<>(col_system.get_event_state(), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref procs_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); + Deref events_deref = new Deref<>(m_deref, event_ref, new GeneratedBlame<>(), OriGen.create()); + // Get parameters of wait call java.util.List params = expr.getParameters(); @@ -832,14 +838,9 @@ private Statement transform_wait_expression(FunctionCallExpression expr, SCCl } IntegerValue ev_id = new IntegerValue<>(BigInt.apply(event_id), OriGen.create()); - // Find references to relevant process and event state fields - Ref> proc_ref = new DirectRef<>(col_system.get_process_state(process_id), ClassTag$.MODULE$.apply(InstanceField.class)); - Ref> event_ref = new DirectRef<>(col_system.get_event_state(event_id), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref proc_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); - Deref event_deref = new Deref<>(m_deref, event_ref, new GeneratedBlame<>(), OriGen.create()); - // Create process state update - statements.add(new Assign<>(proc_deref, ev_id, new GeneratedBlame<>(), OriGen.create())); + SeqUpdate update_proc = new SeqUpdate<>(procs_deref, proc_id, ev_id, OriGen.create()); + statements.add(new Assign<>(procs_deref, update_proc, new GeneratedBlame<>(), OriGen.create())); // If the wait is waiting for time, also notify the event if (params.size() == 2) { @@ -870,11 +871,12 @@ private Statement transform_wait_expression(FunctionCallExpression expr, SCCl } // Create event state update - statements.add(new Assign<>(event_deref, wait_time, new GeneratedBlame<>(), OriGen.create())); + SeqUpdate update_event = new SeqUpdate<>(events_deref, ev_id, wait_time, OriGen.create()); + statements.add(new Assign<>(events_deref, update_event, new GeneratedBlame<>(), OriGen.create())); } // Add wait loop and finish block - statements.add(create_wait_loop(process_id, event_id)); + statements.add(create_wait_loop(proc_id, ev_id)); Statement result = new Block<>(List.from(CollectionConverters.asScala(statements)), OriGen.create()); // Handle label @@ -903,21 +905,23 @@ private Statement transform_fifo_call_expression(SCPortSCSocketExpression fif // Process field reference Ref> m_ref = new DirectRef<>(m, ClassTag$.MODULE$.apply(InstanceField.class)); Deref m_deref = new Deref<>(col_system.THIS, m_ref, new GeneratedBlame<>(), OriGen.create()); + Ref> proc_ref = new DirectRef<>(col_system.get_process_state(), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref procs_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); // Decode the fifo queue SCPort sc_port = fifo.getSCPortSCSocket(); SCKnownType channel = col_system.get_primitive_port_connection(sc_inst, sc_port); Expr fifo_queue = transform_sc_port_sc_socket_expression(fifo, sc_inst); - Ref> buf_ref = new LazyRef<>(() -> col_system.get_primitive_instance_field(channel, Constants.FIFO_BUFFER), - Option.empty(), ClassTag$.MODULE$.apply(InstanceField.class)); + InstanceField fifo_buffer = col_system.get_primitive_instance_field(channel, Constants.FIFO_BUFFER); + Ref> buf_ref = new DirectRef<>(fifo_buffer, ClassTag$.MODULE$.apply(InstanceField.class)); Deref buf_deref = new Deref<>(fifo_queue, buf_ref, new GeneratedBlame<>(), OriGen.create()); Size buf_size = new Size<>(buf_deref, OriGen.create()); - Ref> written_ref = new LazyRef<>(() -> col_system.get_primitive_instance_field(channel, Constants.FIFO_WRITTEN), - Option.empty(), ClassTag$.MODULE$.apply(InstanceField.class)); + InstanceField fifo_written = col_system.get_primitive_instance_field(channel, Constants.FIFO_WRITTEN); + Ref> written_ref = new DirectRef<>(fifo_written, ClassTag$.MODULE$.apply(InstanceField.class)); Deref written_deref = new Deref<>(fifo_queue, written_ref, new GeneratedBlame<>(), OriGen.create()); Size written_size = new Size<>(written_deref, OriGen.create()); - Ref> read_ref = new LazyRef<>(() -> col_system.get_primitive_instance_field(channel, Constants.FIFO_NUM_READ), - Option.empty(), ClassTag$.MODULE$.apply(InstanceField.class)); + InstanceField fifo_num_read = col_system.get_primitive_instance_field(channel, Constants.FIFO_NUM_READ); + Ref> read_ref = new DirectRef<>(fifo_num_read, ClassTag$.MODULE$.apply(InstanceField.class)); Deref read_deref = new Deref<>(fifo_queue, read_ref, new GeneratedBlame<>(), OriGen.create()); // Get a reference to the FIFO size parameter @@ -942,8 +946,8 @@ private Statement transform_fifo_call_expression(SCPortSCSocketExpression fif } default -> throw new UnsupportedException("FIFO method " + fun.getFunction().getName() + " is not supported."); } - Ref> method_ref = new LazyRef<>(() -> col_system.get_primitive_instance_method(channel, method_index), - Option.empty(), ClassTag$.MODULE$.apply(InstanceMethod.class)); + InstanceMethod fifo_method = col_system.get_primitive_instance_method(channel, method_index); + Ref> method_ref = new DirectRef<>(fifo_method, ClassTag$.MODULE$.apply(InstanceMethod.class)); // Decode function call parameters java.util.List> args = new java.util.ArrayList<>(); @@ -954,19 +958,16 @@ private Statement transform_fifo_call_expression(SCPortSCSocketExpression fif } // Create process and event id - int proc_id = corr_proc.get_process_id(); + IntegerValue proc_id = new IntegerValue<>(BigInt.apply(corr_proc.get_process_id()), OriGen.create()); int event_id = col_system.get_channel_events(channel).get(wait_event_index); IntegerValue ev_id = new IntegerValue<>(BigInt.apply(event_id), OriGen.create()); - // Find process state variable - Ref> proc_ref = new DirectRef<>(col_system.get_process_state(proc_id), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref procs_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); - // Create wait statement - Assign update_process_state = new Assign<>(procs_deref, ev_id, new GeneratedBlame<>(), OriGen.create()); + SeqUpdate new_process_state = new SeqUpdate<>(procs_deref, proc_id, ev_id, OriGen.create()); + Assign update_process_state = new Assign<>(procs_deref, new_process_state, new GeneratedBlame<>(), OriGen.create()); // Create wait loop - Loop wait_loop = create_wait_loop(proc_id, event_id); + Loop wait_loop = create_wait_loop(proc_id, ev_id); // Create loop invariant for the big loop SpecificationTransformer specification_transformer = new SpecificationTransformer<>(col_class, col_system, m); @@ -1020,8 +1021,8 @@ private Statement transform_signal_call_expression_to_statement(SCPortSCSocke case "read" -> Constants.SIGNAL_READ_METHOD; default -> throw new UnsupportedException("Signal method " + sc_fun.getName() + " is not supported!"); }; - Ref> method_ref = new LazyRef<>(() -> col_system.get_primitive_instance_method(channel, method_index), - Option.empty(), ClassTag$.MODULE$.apply(InstanceMethod.class)); + InstanceMethod signal_method = col_system.get_primitive_instance_method(channel, method_index); + Ref> method_ref = new DirectRef<>(signal_method, ClassTag$.MODULE$.apply(InstanceMethod.class)); // Decode function call parameters java.util.List> args = new java.util.ArrayList<>(); @@ -1078,15 +1079,15 @@ private Assign decode_assignment(Expr left, String op, Expr right) { * @param event_id ID of the event it should wait on * @return A loop that unlocks the global lock until the event occurred and the process is woken up */ - private Loop create_wait_loop(int process_id, int event_id) { + private Loop create_wait_loop(IntegerValue process_id, IntegerValue event_id) { // Process and event field refs Ref> m_ref = new DirectRef<>(m, ClassTag$.MODULE$.apply(InstanceField.class)); Deref m_deref = new Deref<>(col_system.THIS, m_ref, new GeneratedBlame<>(), OriGen.create()); - Ref> proc_ref = new DirectRef<>(col_system.get_process_state(process_id), ClassTag$.MODULE$.apply(InstanceField.class)); - Ref> event_ref = new DirectRef<>(col_system.get_event_state(event_id), ClassTag$.MODULE$.apply(InstanceField.class)); - Deref proc_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); - Deref event_deref = new Deref<>(m_deref, event_ref, new GeneratedBlame<>(), OriGen.create()); + Ref> proc_ref = new DirectRef<>(col_system.get_process_state(), ClassTag$.MODULE$.apply(InstanceField.class)); + Ref> event_ref = new DirectRef<>(col_system.get_event_state(), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref procs_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); + Deref events_deref = new Deref<>(m_deref, event_ref, new GeneratedBlame<>(), OriGen.create()); // Create waiting loop body Unlock unlock_m = new Unlock<>(m_deref, new GeneratedBlame<>(), OriGen.create()); @@ -1095,8 +1096,10 @@ private Loop create_wait_loop(int process_id, int event_id) { Block loop_body = new Block<>(List.from(CollectionConverters.asScala(loop_body_statements)), OriGen.create()); // Create loop condition - Neq proc_ready = new Neq<>(proc_deref, col_system.MINUS_ONE, OriGen.create()); - Neq ev_notified = new Neq<>(event_deref, col_system.MINUS_TWO, OriGen.create()); + SeqSubscript proc_index = new SeqSubscript<>(procs_deref, process_id, new GeneratedBlame<>(), OriGen.create()); + Neq proc_ready = new Neq<>(proc_index, col_system.MINUS_ONE, OriGen.create()); + SeqSubscript ev_index = new SeqSubscript<>(events_deref, event_id, new GeneratedBlame<>(), OriGen.create()); + Neq ev_notified = new Neq<>(ev_index, col_system.MINUS_TWO, OriGen.create()); Or loop_cond = new Or<>(proc_ready, ev_notified, OriGen.create()); // Create loop contract @@ -1553,7 +1556,7 @@ private Expr transform_sc_port_sc_socket_expression(SCPortSCSocketExpression // Find the corresponding field in the Main class Ref> channel_ref; if (prim_channel != null) { - channel_ref = new LazyRef<>(() -> col_system.get_primitive_channel(prim_channel), Option.empty(), ClassTag$.MODULE$.apply(InstanceField.class)); + channel_ref = new DirectRef<>(col_system.get_primitive_channel(prim_channel), ClassTag$.MODULE$.apply(InstanceField.class)); } else if (hier_channel != null) { channel_ref = new LazyRef<>(() -> col_system.get_instance_by_class(col_system.get_state_class(hier_channel)), Option.empty(), diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java index d616ee39b5..b62b7a53d6 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/KnownTypeTransformer.java @@ -42,6 +42,16 @@ public class KnownTypeTransformer { */ private final COLSystem col_system; + /** + * Event IDs used by the generated channel. + */ + private final java.util.List event_ids; + + /** + * Index of the primitive channel. + */ + private int prim_channel_index; + /** * Constructor. * @@ -51,6 +61,7 @@ public class KnownTypeTransformer { public KnownTypeTransformer(SCKnownType sc_inst, COLSystem col_system) { this.sc_inst = sc_inst; this.col_system = col_system; + this.event_ids = new java.util.ArrayList<>(); } /** @@ -61,6 +72,9 @@ public void transform() { SCClass sc_class = sc_inst.getSCClass(); String name = generate_class_name(); + // Find index of this channel + prim_channel_index = col_system.get_nr_primitive_channels(); + // Transform the primitive channel Class cls = switch (sc_class.getName()) { case Constants.CLASS_FIFO_INT -> transform_fifo(OriGen.create(name), col_system.T_INT); @@ -70,7 +84,9 @@ public void transform() { default -> throw new UnsupportedException("The known type " + sc_class.getName() + " is not supported."); }; - // Add channel class to COL system + // Add channel class and events to COL system + java.util.Collections.sort(event_ids); + col_system.add_channel_events(sc_inst, event_ids); col_system.add_global_declaration(cls); // Add channel field to COL system @@ -283,43 +299,37 @@ private InstanceMethod create_fifo_read_method(Type t, InstanceField m, Deref written_deref = new Deref<>(col_system.THIS, written_ref, new GeneratedBlame<>(), written.o()); // Get scheduling variable references - InstanceField update = col_system.get_primitive_channel_update(col_system.get_primitive_channel_id(sc_inst)); - Ref> update_ref = new DirectRef<>(update, ClassTag$.MODULE$.apply(InstanceField.class)); + Ref> update_ref = new DirectRef<>(col_system.get_primitive_channel_update(), ClassTag$.MODULE$.apply(InstanceField.class)); Deref update_deref = new Deref<>(m_deref, update_ref, new GeneratedBlame<>(), OriGen.create()); // Create precondition Less not_all_read = new Less<>(read_deref, buf_size, OriGen.create()); AccountedPredicate precondition = new UnitAccountedPredicate<>(new Star<>(perms, not_all_read, OriGen.create()), OriGen.create()); - // Collect conditions - java.util.List> conditions = new java.util.ArrayList<>(); - conditions.add(perms); - // Unchanged variables - conditions.add(new Eq<>(written_deref, new Old<>(written_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); - conditions.add(new Eq<>(buf_deref, new Old<>(buf_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); + Eq written_is_old = new Eq<>(written_deref, new Old<>(written_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + Eq buffer_is_old = new Eq<>(buf_deref, new Old<>(buf_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); // Return value - Ref> ref = new LazyRef<>(() -> col_system.get_primitive_instance_method(sc_inst, Constants.FIFO_READ_METHOD), + Ref> ref = new LazyRef>(() -> col_system.get_primitive_instance_method(sc_inst, Constants.FIFO_READ_METHOD), Option.empty(), ClassTag$.MODULE$.apply(ContractApplicable.class)); Result ret = new Result<>(ref, OriGen.create()); SeqSubscript access = new SeqSubscript<>(buf_deref, read_deref, new GeneratedBlame<>(), OriGen.create()); - conditions.add(new Eq<>(ret, new Old<>(access, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); + Eq result = new Eq<>(ret, new Old<>(access, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); // Update to num_read Old old_read = new Old<>(read_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); Plus incr_old = new Plus<>(old_read, col_system.ONE, OriGen.create()); - conditions.add(new Eq<>(read_deref, incr_old, OriGen.create())); + Eq incr_read = new Eq<>(read_deref, incr_old, OriGen.create()); // Update to primitive_channel_update - conditions.add(new Eq<>(update_deref, col_system.TRUE, OriGen.create())); - for (InstanceField field : col_system.get_all_primitive_channel_updates()) { - if (!field.equals(update)) { - conditions.add(unchanged_field(m_deref, field)); - } - } + IntegerValue update_index = new IntegerValue<>(BigInt.apply(prim_channel_index), OriGen.create()); + Old old_update = new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); + SeqUpdate update_update = new SeqUpdate<>(old_update, update_index, col_system.TRUE, OriGen.create()); + Eq new_update = new Eq<>(update_deref, update_update, OriGen.create()); // Create postcondition + java.util.List> conditions = java.util.List.of(perms, written_is_old, buffer_is_old, result, incr_read, new_update); AccountedPredicate postcondition = new UnitAccountedPredicate<>(col_system.fold_star(conditions), OriGen.create()); // Finishing the method @@ -360,8 +370,7 @@ private InstanceMethod create_fifo_write_method(Type t, InstanceField m Size written_size = new Size<>(written_deref, OriGen.create()); // Get scheduling variable references - InstanceField update = col_system.get_primitive_channel_update(col_system.get_primitive_channel_id(sc_inst)); - Ref> update_ref = new DirectRef<>(update, ClassTag$.MODULE$.apply(InstanceField.class)); + Ref> update_ref = new DirectRef<>(col_system.get_primitive_channel_update(), ClassTag$.MODULE$.apply(InstanceField.class)); Deref update_deref = new Deref<>(m_deref, update_ref, new GeneratedBlame<>(), OriGen.create()); // Get parameter references @@ -373,30 +382,25 @@ private InstanceMethod create_fifo_write_method(Type t, InstanceField m Less within_bound = new Less<>(total_size, fifo_size_deref, OriGen.create()); AccountedPredicate precondition = new UnitAccountedPredicate<>(new Star<>(perms, within_bound, OriGen.create()), OriGen.create()); - // Collect conditions - java.util.List> conditions = new java.util.ArrayList<>(); - conditions.add(perms); - // Unchanged variables - conditions.add(new Eq<>(read_deref, new Old<>(read_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); - conditions.add(new Eq<>(buf_deref, new Old<>(buf_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); + Eq read_is_old = new Eq<>(read_deref, new Old<>(read_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + Eq buffer_is_old = new Eq<>(buf_deref, new Old<>(buf_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); // Update to written Ref> ref_to_new_val = new DirectRef<>(new_val, ClassTag$.MODULE$.apply(Variable.class)); Seq> literal_vals = List.from(CollectionConverters.asScala(java.util.List.of(new Local<>(ref_to_new_val, new_val.o())))); LiteralSeq new_vals = new LiteralSeq<>(t, literal_vals, OriGen.create()); Concat concat = new Concat<>(written_deref, new_vals, OriGen.create()); - conditions.add(new Eq<>(written_deref, new Old<>(concat, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); + Eq new_written = new Eq<>(written_deref, new Old<>(concat, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); // Update to primitive_channel_update - conditions.add(new Eq<>(update_deref, col_system.TRUE, OriGen.create())); - for (InstanceField field : col_system.get_all_primitive_channel_updates()) { - if (!field.equals(update)) { - conditions.add(unchanged_field(m_deref, field)); - } - } + IntegerValue update_index = new IntegerValue<>(BigInt.apply(prim_channel_index), OriGen.create()); + Old old_update = new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); + SeqUpdate update_update = new SeqUpdate<>(old_update, update_index, col_system.TRUE, OriGen.create()); + Eq new_update = new Eq<>(update_deref, update_update, OriGen.create()); // Create postcondition + java.util.List> conditions = java.util.List.of(perms, read_is_old, buffer_is_old, new_written, new_update); AccountedPredicate postcondition = new UnitAccountedPredicate<>(col_system.fold_star(conditions), OriGen.create()); // Finishing the method @@ -432,69 +436,74 @@ private InstanceMethod create_fifo_update_method(InstanceField m, Instance Size written_size = new Size<>(written_deref, OriGen.create()); Old old_wr_size = new Old<>(written_size, Option.empty(), new GeneratedBlame<>(), OriGen.create()); + // Get scheduling variable references + Ref> update_ref = new DirectRef<>(col_system.get_primitive_channel_update(), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref update_deref = new Deref<>(m_deref, update_ref, new GeneratedBlame<>(), OriGen.create()); + Ref> proc_ref = new DirectRef<>(col_system.get_process_state(), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref proc_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); + Ref> ev_ref = new DirectRef<>(col_system.get_event_state(), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref ev_deref = new Deref<>(m_deref, ev_ref, new GeneratedBlame<>(), OriGen.create()); + Size ev_size = new Size<>(ev_deref, OriGen.create()); + // Create precondition AccountedPredicate precondition = new UnitAccountedPredicate<>(perms, OriGen.create()); - java.util.List> conditions = new java.util.ArrayList<>(); - conditions.add(perms); // Unchanged variables - for (InstanceField field : col_system.get_all_process_states()) { - conditions.add(unchanged_field(m_deref, field)); - } - for (InstanceField field : col_system.get_all_primitive_channel_updates()) { - conditions.add(unchanged_field(m_deref, field)); - } + Eq proc_is_old = new Eq<>(proc_deref, new Old<>(proc_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + Eq update_is_old = new Eq<>(update_deref, new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); // Access update sequence - InstanceField this_update = col_system.get_primitive_channel_update(col_system.get_primitive_channel_id(sc_inst)); - Ref> this_update_ref = new DirectRef<>(this_update, ClassTag$.MODULE$.apply(InstanceField.class)); - Deref u_i = new Deref<>(m_deref, this_update_ref, new GeneratedBlame<>(), OriGen.create()); + IntegerValue update_index = new IntegerValue<>(BigInt.apply(prim_channel_index), OriGen.create()); + SeqSubscript u_i = new SeqSubscript<>(update_deref, update_index, new GeneratedBlame<>(), OriGen.create()); Not n_u_i = new Not<>(u_i, OriGen.create()); // CASE 1: Everything is unchanged - java.util.List> case_1_effects = new java.util.ArrayList<>(); - case_1_effects.add(new Eq<>(read_deref, new Old<>(read_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); - case_1_effects.add(new Eq<>(written_deref, new Old<>(written_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); - case_1_effects.add(new Eq<>(buf_deref, new Old<>(buf_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); - for (InstanceField field : col_system.get_all_event_states()) { - case_1_effects.add(unchanged_field(m_deref, field)); - } - conditions.add(new Implies<>(n_u_i, col_system.fold_and(case_1_effects), OriGen.create())); + Eq read_is_old = new Eq<>(read_deref, new Old<>(read_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + Eq written_is_old = new Eq<>(written_deref, new Old<>(written_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + Eq buf_is_old = new Eq<>(buf_deref, new Old<>(buf_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + Eq ev_is_old = new Eq<>(ev_deref, new Old<>(ev_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + Expr everything_is_old = col_system.fold_and(java.util.List.of(read_is_old, written_is_old, buf_is_old, ev_is_old)); + Implies not_updated = new Implies<>(n_u_i, everything_is_old, OriGen.create()); + + // Get read and write events + int curr_ev_id = col_system.get_total_nr_events(); + int read_event = curr_ev_id + Constants.FIFO_READ_EVENT; + event_ids.add(read_event); + int write_event = curr_ev_id + Constants.FIFO_WRITE_EVENT; + event_ids.add(write_event); + IntegerValue min_ev = new IntegerValue<>(BigInt.apply(curr_ev_id), OriGen.create()); + IntegerValue r_ev = new IntegerValue<>(BigInt.apply(read_event), OriGen.create()); + IntegerValue w_ev = new IntegerValue<>(BigInt.apply(write_event), OriGen.create()); + IntegerValue next_ev = new IntegerValue<>(BigInt.apply(curr_ev_id + event_ids.size()), OriGen.create()); // Preparations for CASE 2 - // Read event - InstanceField read_event_state = col_system.get_event_state(col_system.get_channel_events(sc_inst).get(Constants.FIFO_READ_EVENT)); - Ref> read_event_state_ref = new DirectRef<>(read_event_state, ClassTag$.MODULE$.apply(InstanceField.class)); - Deref read_event_state_deref = new Deref<>(m_deref, read_event_state_ref, new GeneratedBlame<>(), OriGen.create()); - Old old_read_event_state = new Old<>(read_event_state_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - Greater has_been_read = new Greater<>(old_read, col_system.ZERO, OriGen.create()); - Select new_read_status = new Select<>(has_been_read, col_system.MINUS_ONE, old_read_event_state, OriGen.create()); - // Write event - InstanceField write_event_state = col_system.get_event_state(col_system.get_channel_events(sc_inst).get(Constants.FIFO_WRITE_EVENT)); - Ref> write_event_state_ref = new DirectRef<>(write_event_state, ClassTag$.MODULE$.apply(InstanceField.class)); - Deref write_event_state_deref = new Deref<>(m_deref, write_event_state_ref, new GeneratedBlame<>(), OriGen.create()); - Old old_write_event_state = new Old<>(write_event_state_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - Greater has_been_written = new Greater<>(old_wr_size, col_system.ZERO, OriGen.create()); - Select new_write_status = new Select<>(has_been_written, col_system.MINUS_ONE, old_write_event_state, OriGen.create()); - // Buffer Slice buf_slice = new Slice<>(buf_deref, read_deref, buf_size, OriGen.create()); Concat buf_written = new Concat<>(buf_slice, written_deref, OriGen.create()); + Take prev_evs = new Take<>(ev_deref, min_ev, OriGen.create()); + Drop next_evs = new Drop<>(ev_deref, next_ev, OriGen.create()); + Greater has_been_read = new Greater<>(old_read, col_system.ZERO, OriGen.create()); + SeqSubscript read_ev_status = new SeqSubscript<>(ev_deref, r_ev, new GeneratedBlame<>(), OriGen.create()); + Old old_r_status = new Old<>(read_ev_status, Option.empty(), new GeneratedBlame<>(), OriGen.create()); + Select new_r_status = new Select<>(has_been_read, col_system.MINUS_ONE, old_r_status, OriGen.create()); + Greater has_been_written = new Greater<>(old_wr_size, col_system.ZERO, OriGen.create()); + SeqSubscript write_ev_status = new SeqSubscript<>(ev_deref, w_ev, new GeneratedBlame<>(), OriGen.create()); + Old old_w_status = new Old<>(write_ev_status, Option.empty(), new GeneratedBlame<>(), OriGen.create()); + Select new_w_status = new Select<>(has_been_written, col_system.MINUS_ONE, old_w_status, OriGen.create()); // CASE 2: Update is executed - java.util.List> case_2_effects = new java.util.ArrayList<>(); - case_2_effects.add(new Eq<>(read_deref, col_system.ZERO, OriGen.create())); - case_2_effects.add(new Eq<>(written_size, col_system.ZERO, OriGen.create())); - case_2_effects.add(new Eq<>(buf_deref, new Old<>(buf_written, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); - for (InstanceField field : col_system.get_all_event_states()) { - if (!field.equals(read_event_state) && !field.equals(write_event_state)) { - case_2_effects.add(unchanged_field(m_deref, field)); - } - } - case_2_effects.add(new Eq<>(read_event_state_deref, new_read_status, OriGen.create())); - case_2_effects.add(new Eq<>(write_event_state_deref, new_write_status, OriGen.create())); - conditions.add(new Implies<>(u_i, col_system.fold_and(case_2_effects), OriGen.create())); + Eq read_is_zero = new Eq<>(read_deref, col_system.ZERO, OriGen.create()); + Eq written_is_empty = new Eq<>(written_size, col_system.ZERO, OriGen.create()); + Eq buf_update = new Eq<>(buf_deref, new Old<>(buf_written, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + Eq prev_evs_unchanged = new Eq<>(prev_evs, new Old<>(prev_evs, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + Eq read_ev_changed = new Eq<>(read_ev_status, new_r_status, OriGen.create()); + Eq write_ev_changed = new Eq<>(write_ev_status, new_w_status, OriGen.create()); + Eq next_evs_unchanged = new Eq<>(next_evs, new Old<>(next_evs, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + java.util.List> changes = java.util.List.of(read_is_zero, written_is_empty, buf_update, prev_evs_unchanged, + read_ev_changed, write_ev_changed, next_evs_unchanged); + Implies updated = new Implies<>(u_i, col_system.fold_and(changes), OriGen.create()); // Create postcondition + java.util.List> conditions = java.util.List.of(perms, proc_is_old, update_is_old, not_updated, updated); AccountedPredicate postcondition = new UnitAccountedPredicate<>(col_system.fold_star(conditions), OriGen.create()); // Finish the method @@ -637,25 +646,22 @@ private InstanceMethod create_signal_read_method(Type t, InstanceField Deref m_deref = new Deref<>(col_system.THIS, new DirectRef<>(m, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), m.o()); Deref val_deref = new Deref<>(col_system.THIS, new DirectRef<>(val, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), val.o()); Deref _val_deref = new Deref<>(col_system.THIS, new DirectRef<>(_val, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), _val.o()); - - // Collect conditions - java.util.List> conditions = new java.util.ArrayList<>(); - conditions.add(perms); + Ref> ref_to_update = new DirectRef<>(col_system.get_primitive_channel_update(), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref update_deref = new Deref<>(m_deref, ref_to_update, new GeneratedBlame<>(), col_system.get_process_state().o()); // Unchanged variables - for (InstanceField field : col_system.get_all_primitive_channel_updates()) { - conditions.add(unchanged_field(m_deref, field)); - } - conditions.add(new Eq<>(val_deref, new Old<>(val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); - conditions.add(new Eq<>(_val_deref, new Old<>(_val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); + Eq update_is_old = new Eq<>(update_deref, new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + Eq val_is_old = new Eq<>(val_deref, new Old<>(val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + Eq _val_is_old = new Eq<>(_val_deref, new Old<>(_val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); // Method return value Ref> ref = new LazyRef<>(() -> col_system.get_primitive_instance_method(sc_inst, Constants.SIGNAL_READ_METHOD), Option.empty(), ClassTag$.MODULE$.apply(ContractApplicable.class)); Result ret = new Result<>(ref, OriGen.create()); - conditions.add(new Eq<>(ret, new Old<>(val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create())); + Eq result = new Eq<>(ret, new Old<>(val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); // Postcondition + java.util.List> conditions = java.util.List.of(perms, update_is_old, val_is_old, _val_is_old, result); AccountedPredicate postcondition = new UnitAccountedPredicate<>(col_system.fold_star(conditions), OriGen.create()); // Finishing the method @@ -678,7 +684,7 @@ private InstanceMethod create_signal_write_method(Type t, InstanceField Expr perms = create_general_contract(m, false); // Parameters - Variable new_val = new Variable<>(t, OriGen.create("new_val")); + Variable new_val = new Variable<>(t, OriGen.create("newVal")); List> params = List.from(CollectionConverters.asScala(java.util.List.of(new_val))); // Precondition @@ -689,37 +695,30 @@ private InstanceMethod create_signal_write_method(Type t, InstanceField Deref val_deref = new Deref<>(col_system.THIS, new DirectRef<>(val, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), val.o()); Old old_val = new Old<>(val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); Deref _val_deref = new Deref<>(col_system.THIS, new DirectRef<>(_val, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), _val.o()); + Ref> ref_to_update = new DirectRef<>(col_system.get_primitive_channel_update(), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref update_deref = new Deref<>(m_deref, ref_to_update, new GeneratedBlame<>(), col_system.get_process_state().o()); + Old old_update = new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); Local new_val_local = new Local<>(new DirectRef<>(new_val, ClassTag$.MODULE$.apply(Variable.class)), new_val.o()); - // Collect conditions - java.util.List> conditions = new java.util.ArrayList<>(); - conditions.add(perms); - // Unchanged variables - conditions.add(new Eq<>(val_deref, old_val, OriGen.create())); + Eq val_is_old = new Eq<>(val_deref, old_val, OriGen.create()); // Changed _val - conditions.add(new Eq<>(_val_deref, new_val_local, OriGen.create())); + Eq changed_val = new Eq<>(_val_deref, new_val_local, OriGen.create()); - // Prepare for update - InstanceField update = col_system.get_primitive_channel_update(col_system.get_primitive_channel_id(sc_inst)); - Ref> update_ref = new DirectRef<>(update, ClassTag$.MODULE$.apply(InstanceField.class)); - Deref update_deref = new Deref<>(m_deref, update_ref, new GeneratedBlame<>(), OriGen.create()); - Eq updated = new Eq<>(update_deref, col_system.TRUE, OriGen.create()); - Eq not_updated = new Eq<>(update_deref, new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()), OriGen.create()); + // primitive_channel_update is set if the stored value changed Neq val_changed = new Neq<>(new_val_local, old_val, OriGen.create()); Eq val_not_changed = new Eq<>(new_val_local, old_val, OriGen.create()); - - // primitive_channel_update is set if the stored value changed - for (InstanceField field : col_system.get_all_primitive_channel_updates()) { - if (!field.equals(update)) { - conditions.add(unchanged_field(m_deref, field)); - } - } - conditions.add(new Implies<>(val_changed, updated, OriGen.create())); - conditions.add(new Implies<>(val_not_changed, not_updated, OriGen.create())); + IntegerValue prim_index = new IntegerValue<>(BigInt.apply(prim_channel_index), OriGen.create()); + SeqUpdate update_update = new SeqUpdate<>(update_deref, prim_index, col_system.TRUE, OriGen.create()); + Old old_update_update = new Old<>(update_update, Option.empty(), new GeneratedBlame<>(), OriGen.create()); + Eq update_changed = new Eq<>(update_deref, old_update_update, OriGen.create()); + Eq update_unchanged = new Eq<>(update_deref, old_update, OriGen.create()); + Implies updated = new Implies<>(val_changed, update_changed, OriGen.create()); + Implies not_updated = new Implies<>(val_not_changed, update_unchanged, OriGen.create()); // Postcondition + java.util.List> conditions = java.util.List.of(perms, val_is_old, changed_val, updated, not_updated); AccountedPredicate postcondition = new UnitAccountedPredicate<>(col_system.fold_star(conditions), OriGen.create()); // Finishing the method @@ -747,52 +746,52 @@ private InstanceMethod create_signal_update_method(InstanceField m, Instan Deref _val_deref = new Deref<>(col_system.THIS, new DirectRef<>(_val, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), _val.o()); Old old__val = new Old<>(_val_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); + // Get scheduling variable references + Ref> update_ref = new DirectRef<>(col_system.get_primitive_channel_update(), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref update_deref = new Deref<>(m_deref, update_ref, new GeneratedBlame<>(), OriGen.create()); + Old old_update = new Old<>(update_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); + Ref> proc_ref = new DirectRef<>(col_system.get_process_state(), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref proc_deref = new Deref<>(m_deref, proc_ref, new GeneratedBlame<>(), OriGen.create()); + Old old_proc = new Old<>(proc_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); + Ref> ev_ref = new DirectRef<>(col_system.get_event_state(), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref ev_deref = new Deref<>(m_deref, ev_ref, new GeneratedBlame<>(), OriGen.create()); + Old old_ev = new Old<>(ev_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); + // Precondition AccountedPredicate precondition = new UnitAccountedPredicate<>(perms, OriGen.create()); - java.util.List> conditions = new java.util.ArrayList<>(); - conditions.add(perms); - // Unchanged fields - for (InstanceField field : col_system.get_all_process_states()) { - conditions.add(unchanged_field(m_deref, field)); - } - for (InstanceField field : col_system.get_all_primitive_channel_updates()) { - conditions.add(unchanged_field(m_deref, field)); - } - conditions.add(new Eq<>(_val_deref, old__val, OriGen.create())); + Eq process_is_old = new Eq<>(proc_deref, old_proc, OriGen.create()); + Eq update_is_old = new Eq<>(update_deref, old_update, OriGen.create()); + Eq _val_is_old = new Eq<>(_val_deref, old__val, OriGen.create()); - // Access update field - InstanceField this_update = col_system.get_primitive_channel_update(col_system.get_primitive_channel_id(sc_inst)); - Ref> this_update_ref = new DirectRef<>(this_update, ClassTag$.MODULE$.apply(InstanceField.class)); - Deref u_i = new Deref<>(m_deref, this_update_ref, new GeneratedBlame<>(), OriGen.create()); + // Access update sequence + IntegerValue prim_index = new IntegerValue<>(BigInt.apply(prim_channel_index), OriGen.create()); + SeqSubscript u_i = new SeqSubscript<>(update_deref, prim_index, new GeneratedBlame<>(), OriGen.create()); Not n_u_i = new Not<>(u_i, OriGen.create()); // CASE 1: Everything is unchanged - java.util.List> case_1_changes = new java.util.ArrayList<>(); - for (InstanceField field : col_system.get_all_event_states()) { - case_1_changes.add(unchanged_field(m_deref, field)); - } - case_1_changes.add(new Eq<>(val_deref, old_val, OriGen.create())); - conditions.add(new Implies<>(n_u_i, col_system.fold_and(case_1_changes), OriGen.create())); + Eq ev_is_old = new Eq<>(ev_deref, old_ev, OriGen.create()); + Eq val_is_old = new Eq<>(val_deref, old_val, OriGen.create()); + And everything_unchanged = new And<>(ev_is_old, val_is_old, OriGen.create()); + Implies not_updated = new Implies<>(n_u_i, everything_unchanged, OriGen.create()); // Get write event - InstanceField write_event_state = col_system.get_event_state(col_system.get_channel_events(sc_inst).get(Constants.SIGNAL_WRITE_EVENT)); - Ref> write_event_state_ref = new DirectRef<>(write_event_state, ClassTag$.MODULE$.apply(InstanceField.class)); - Deref write_event_state_deref = new Deref<>(m_deref, write_event_state_ref, new GeneratedBlame<>(), OriGen.create()); + int curr_ev_id = col_system.get_total_nr_events(); + int write_event = curr_ev_id + Constants.SIGNAL_WRITE_EVENT; + event_ids.add(write_event); + IntegerValue w_ev = new IntegerValue<>(BigInt.apply(write_event), OriGen.create()); // CASE 2: Value changed and write event notified - java.util.List> case_2_changes = new java.util.ArrayList<>(); - for (InstanceField field : col_system.get_all_event_states()) { - if (!field.equals(write_event_state)) { - case_2_changes.add(unchanged_field(m_deref, field)); - } - } - case_2_changes.add(new Eq<>(write_event_state_deref, col_system.MINUS_ONE, OriGen.create())); - case_2_changes.add(new Eq<>(val_deref, old__val, OriGen.create())); - conditions.add(new Implies<>(u_i, col_system.fold_and(case_2_changes), OriGen.create())); + SeqUpdate ev_update = new SeqUpdate<>(ev_deref, w_ev, col_system.MINUS_ONE, OriGen.create()); + Old old_ev_update = new Old<>(ev_update, Option.empty(), new GeneratedBlame<>(), OriGen.create()); + Eq write_event_notified = new Eq<>(ev_deref, old_ev_update, OriGen.create()); + Eq val_is_hidden_val = new Eq<>(val_deref, old__val, OriGen.create()); + And both_changed = new And<>(write_event_notified, val_is_hidden_val, OriGen.create()); + Implies updated = new Implies<>(u_i, both_changed, OriGen.create()); // Postcondition + java.util.List> conditions = java.util.List.of(perms, process_is_old, update_is_old, _val_is_old, not_updated, updated); AccountedPredicate postcondition = new UnitAccountedPredicate<>(col_system.fold_star(conditions), OriGen.create()); // Finish the method @@ -848,19 +847,4 @@ private Expr create_general_contract(InstanceField m, boolean include_sche // Connect the individual conditions with stars and return return col_system.fold_star(java.util.List.of(perm_m, m_not_null, held_m, scheduler_perms, parameter_perms, channel_perms, this_is_self)); } - - /** - * Helper method that returns the equation to indicate that a field is unchanged by a method, i.e. is equal to its - * \old value. - * - * @param m_deref Expression that references the main instance - * @param field Field that should be unchanged - * @return Equation of the form m.field == \old(m.field) - */ - private Eq unchanged_field(Deref m_deref, InstanceField field) { - Ref> f_ref = new DirectRef<>(field, ClassTag$.MODULE$.apply(InstanceField.class)); - Deref f_deref = new Deref<>(m_deref, f_ref, new GeneratedBlame<>(), OriGen.create()); - Old f_old = new Old<>(f_deref, Option.empty(), new GeneratedBlame<>(), OriGen.create()); - return new Eq<>(f_deref, f_old, OriGen.create()); - } } diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java index 574d99977b..530a416422 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java @@ -232,8 +232,24 @@ private String create_instance_name(COLClass col_class) { * variable primitive_channel_update. */ private void create_update_invariant() { - Option> conds = Option.apply(col_system.fold_star(col_system.get_all_primitive_channel_updates().stream().map(this::write_perm_to_field).toList())); - update_permission_invariant = new InstancePredicate<>(col_system.NO_VARS, conds, false, true, OriGen.create("update_permission_invariant")); + // Create reference to primitive_channel_update + InstanceField prim_channel_update = col_system.get_primitive_channel_update(); + Ref> update_ref = new DirectRef<>(prim_channel_update, ClassTag$.MODULE$.apply(InstanceField.class)); + Deref update_deref = new Deref<>(col_system.THIS, update_ref, new GeneratedBlame<>(), OriGen.create()); + FieldLocation update_loc = new FieldLocation<>(col_system.THIS, update_ref, OriGen.create()); + + // Create some auxiliary values + Size update_size = new Size<>(update_deref, OriGen.create()); + IntegerValue nr_prim_channels = new IntegerValue<>(BigInt.apply(col_system.get_nr_primitive_channels()), OriGen.create()); + + // Create predicate conditions + Perm update_perm = new Perm<>(update_loc, new WritePerm<>(OriGen.create()), OriGen.create()); + Eq update_length = new Eq<>(update_size, nr_prim_channels, OriGen.create()); + + // Put it all together and register the predicate in the COL system + java.util.List> conditions = java.util.List.of(update_perm, update_length); + update_permission_invariant = new InstancePredicate<>(col_system.NO_VARS, Option.apply(col_system.fold_star(conditions)), + false, true, OriGen.create("update_permission_invariant")); col_system.set_update_perms(update_permission_invariant); } @@ -243,28 +259,54 @@ private void create_update_invariant() { * for event_state. */ private void create_scheduler_invariant() { - java.util.List> conditions = new java.util.ArrayList<>(); + // Create references to the scheduling variables + Ref> proc_state_ref = new DirectRef<>(col_system.get_process_state(), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref proc_state_deref = new Deref<>(col_system.THIS, proc_state_ref, new GeneratedBlame<>(), OriGen.create()); + FieldLocation proc_state_loc = new FieldLocation<>(col_system.THIS, proc_state_ref, OriGen.create()); + Ref> ev_state_ref = new DirectRef<>(col_system.get_event_state(), ClassTag$.MODULE$.apply(InstanceField.class)); + Deref ev_state_deref = new Deref<>(col_system.THIS, ev_state_ref, new GeneratedBlame<>(), OriGen.create()); + FieldLocation ev_state_loc = new FieldLocation<>(col_system.THIS, ev_state_ref, OriGen.create()); + + // Create some auxiliary values + Size proc_size = new Size<>(proc_state_deref, OriGen.create()); + IntegerValue nr_procs = new IntegerValue<>(BigInt.apply(ProcessClass.get_nr_processes()), OriGen.create()); + Size ev_size = new Size<>(ev_state_deref, OriGen.create()); + IntegerValue nr_events = new IntegerValue<>(BigInt.apply(col_system.get_total_nr_events()), OriGen.create()); // Apply update permission invariant Ref> ref_update_invariant = new DirectRef<>(update_permission_invariant, ClassTag$.MODULE$.apply(InstancePredicate.class)); - conditions.add(new InstancePredicateApply<>(col_system.THIS, ref_update_invariant, col_system.NO_EXPRS, new WritePerm<>(OriGen.create()), OriGen.create())); - - IntegerValue ev_size = new IntegerValue<>(BigInt.apply(col_system.get_total_nr_events()), OriGen.create()); - - // Create permissions - for (InstanceField field : col_system.get_all_process_states()) { - conditions.add(write_perm_to_field(field)); - Deref f_deref = new Deref<>(col_system.THIS, new DirectRef<>(field, ClassTag$.MODULE$.apply(InstanceField.class)), new GeneratedBlame<>(), OriGen.create()); - Eq ready = new Eq<>(f_deref, col_system.MINUS_ONE, OriGen.create()); - Less below_ev = new Less<>(f_deref, ev_size, OriGen.create()); - LessEq non_negative = new LessEq<>(col_system.ZERO, f_deref, OriGen.create()); - conditions.add(new Or<>(ready, new And<>(below_ev, non_negative, OriGen.create()), OriGen.create())); - } - for (InstanceField field : col_system.get_all_event_states()) { - conditions.add(write_perm_to_field(field)); - } + InstancePredicateApply apply_update_perms = new InstancePredicateApply<>(col_system.THIS, ref_update_invariant, + col_system.NO_EXPRS, new WritePerm<>(OriGen.create()), OriGen.create()); + + // Create conditions + Perm perm_to_proc = new Perm<>(proc_state_loc, new WritePerm<>(OriGen.create()), OriGen.create()); + Eq proc_length = new Eq<>(proc_size, nr_procs, OriGen.create()); + Perm perm_to_ev = new Perm<>(ev_state_loc, new WritePerm<>(OriGen.create()), OriGen.create()); + Eq ev_length = new Eq<>(ev_size, nr_events, OriGen.create()); + + // Create forall statement variable + Variable i = new Variable<>(col_system.T_INT, OriGen.create("i")); + Local i_loc = new Local<>(new DirectRef<>(i, ClassTag$.MODULE$.apply(Variable.class)), OriGen.create()); + GreaterEq i_lower = new GreaterEq<>(i_loc, col_system.ZERO, OriGen.create()); + Less i_upper = new Less<>(i_loc, proc_size, OriGen.create()); + And i_bounds = new And<>(i_lower, i_upper, OriGen.create()); + + // Create forall body + SeqSubscript proc_i = new SeqSubscript<>(proc_state_deref, i_loc, new GeneratedBlame<>(), OriGen.create()); + InlinePattern trigger = new InlinePattern<>(proc_i, 0, 0, OriGen.create()); + Eq proc_ready = new Eq<>(trigger, col_system.MINUS_ONE, OriGen.create()); + LessEq proc_lower = new LessEq<>(col_system.ZERO, proc_i, OriGen.create()); + Less proc_upper = new Less<>(proc_i, ev_size, OriGen.create()); + And proc_bounds = new And<>(proc_lower, proc_upper, OriGen.create()); + Or body = new Or<>(proc_ready, proc_bounds, OriGen.create()); + + // Create forall statement + Implies forall_body = new Implies<>(i_bounds, body, OriGen.create()); + List> bindings = List.from(CollectionConverters.asScala(java.util.List.of(i))); + Forall forall = new Forall<>(bindings, col_system.NO_TRIGGERS, forall_body, OriGen.create()); // Put it all together and register the invariant in the COL system context + java.util.List> conditions = java.util.List.of(apply_update_perms, perm_to_proc, proc_length, perm_to_ev, ev_length, forall); scheduler_invariant = new InstancePredicate<>(col_system.NO_VARS, Option.apply(col_system.fold_star(conditions)), false, true, OriGen.create("scheduler_invariant")); col_system.set_scheduler_perms(scheduler_invariant); @@ -407,9 +449,9 @@ private void create_main_constructor() { java.util.List> initializations = new java.util.ArrayList<>(); // Create initializations for the scheduling variables - initializations.addAll(create_process_state_initialization()); - initializations.addAll(create_event_state_initialization()); - initializations.addAll(create_primitive_channel_update_initialization()); + initializations.add(create_process_state_initialization()); + initializations.add(create_event_state_initialization()); + initializations.add(create_primitive_channel_update_initialization()); // Create initializations for all instance fields for (InstanceField channel : channels) { @@ -435,31 +477,70 @@ private void create_main_constructor() { } /** - * Generates the initialization of the process_state scheduling variables. Every entry is set to -1. + * Generates the initialization of the process_state scheduling variable. Every entry is set to -1. * - * @return Assignments for the process state initialization + * @return An assignment for the process state initialization */ - private java.util.List> create_process_state_initialization() { - return col_system.get_all_process_states().stream().map((f) -> assign_to_field(f, col_system.MINUS_ONE)).toList(); + private Statement create_process_state_initialization() { + // Get reference to process state field + InstanceField process_state = col_system.get_process_state(); + Ref> state_ref = new DirectRef<>(process_state, ClassTag$.MODULE$.apply(InstanceField.class)); + Deref state_deref = new Deref<>(col_system.THIS, state_ref, new GeneratedBlame<>(), OriGen.create()); + + // Construct the literal sequence it should be initialized as ([-1] * #processes) + java.util.List> literal_values = new java.util.ArrayList<>(); + for (int i = 0; i < ProcessClass.get_nr_processes(); i++) { + literal_values.add(col_system.MINUS_ONE); + } + LiteralSeq literal = new LiteralSeq<>(col_system.T_INT, List.from(CollectionConverters.asScala(literal_values)), OriGen.create()); + + // Assign the literal to the field + return new Assign<>(state_deref, literal, new GeneratedBlame<>(), OriGen.create()); } /** - * Generates the initialization of the event_state scheduling variables. Every entry is set to -3. + * Generates the initialization of the event_state scheduling variable. Every entry is set to -3. * - * @return Assignments for the event state initialization + * @return An assignment for the event state initialization */ - private java.util.List> create_event_state_initialization() { - return col_system.get_all_event_states().stream().map((f) -> assign_to_field(f, col_system.MINUS_THREE)).toList(); + private Statement create_event_state_initialization() { + // Get reference to the event state field + InstanceField event_state = col_system.get_event_state(); + Ref> state_ref = new DirectRef<>(event_state, ClassTag$.MODULE$.apply(InstanceField.class)); + Deref state_deref = new Deref<>(col_system.THIS, state_ref, new GeneratedBlame<>(), OriGen.create()); + + // Construct the literal sequence it should be initialized as ([-3] * #events) + java.util.List> literal_values = new java.util.ArrayList<>(); + for (int i = 0; i < col_system.get_total_nr_events(); i++) { + literal_values.add(col_system.MINUS_THREE); + } + LiteralSeq literal = new LiteralSeq<>(col_system.T_INT, List.from(CollectionConverters.asScala(literal_values)), OriGen.create()); + + // Assign the literal to the field + return new Assign<>(state_deref, literal, new GeneratedBlame<>(), OriGen.create()); } /** - * Generates the initialization of the primitive_channel_update scheduling variables. Every entry is - * set to false. + * Generates the initialization of the primitive_channel_update scheduling variable. Every entry is set + * to false. * - * @return Assignments for the primitive channel update initialization + * @return An assignment for the primitive channel update initialization */ - private java.util.List> create_primitive_channel_update_initialization() { - return col_system.get_all_primitive_channel_updates().stream().map((f) -> assign_to_field(f, col_system.FALSE)).toList(); + private Statement create_primitive_channel_update_initialization() { + // Get reference to the primitive channel update field + InstanceField prim_channel_update = col_system.get_primitive_channel_update(); + Ref> update_ref = new DirectRef<>(prim_channel_update, ClassTag$.MODULE$.apply(InstanceField.class)); + Deref update_deref = new Deref<>(col_system.THIS, update_ref, new GeneratedBlame<>(), OriGen.create()); + + // Construct the literal sequence it should be initialized as ([false] * #primitive channels) + java.util.List> literal_values = new java.util.ArrayList<>(); + for (int i = 0; i < col_system.get_nr_primitive_channels(); i++) { + literal_values.add(col_system.FALSE); + } + LiteralSeq literal = new LiteralSeq<>(col_system.T_BOOL, List.from(CollectionConverters.asScala(literal_values)), OriGen.create()); + + // Assign the literal to the field + return new Assign<>(update_deref, literal, new GeneratedBlame<>(), OriGen.create()); } /** @@ -493,7 +574,7 @@ private Statement create_field_initialization(InstanceField field) { SCVariableDeclarationExpression declaration = sc_inst.getDeclaration(); if (declaration != null) { java.util.List params = declaration.getInitialValues(); - if (params != null && !params.isEmpty()) { + if (params != null && params.size() > 0) { // The first parameter is the name - ignore it params.remove(0); @@ -1120,9 +1201,9 @@ private void assemble_main() { java.util.List> declarations = new java.util.ArrayList<>(); // Add all fields to the class - declarations.addAll(col_system.get_all_process_states()); - declarations.addAll(col_system.get_all_event_states()); - declarations.addAll(col_system.get_all_primitive_channel_updates()); + declarations.add(col_system.get_process_state()); + declarations.add(col_system.get_event_state()); + declarations.add(col_system.get_primitive_channel_update()); declarations.addAll(processes); declarations.addAll(state_classes); declarations.addAll(channels); @@ -1160,16 +1241,4 @@ private void assemble_main() { col_system.add_global_declaration(main_class); col_system.set_main(main_class); } - - private Expr write_perm_to_field(InstanceField field) { - Ref> field_ref = new DirectRef<>(field, ClassTag$.MODULE$.apply(InstanceField.class)); - FieldLocation field_loc = new FieldLocation<>(col_system.THIS, field_ref, OriGen.create()); - return new Perm<>(field_loc, new WritePerm<>(OriGen.create()), OriGen.create()); - } - - private Assign assign_to_field(InstanceField field, Expr value) { - Ref> field_ref = new DirectRef<>(field, ClassTag$.MODULE$.apply(InstanceField.class)); - Deref field_deref = new Deref<>(col_system.THIS, field_ref, new GeneratedBlame<>(), OriGen.create()); - return new Assign<>(field_deref, value, new GeneratedBlame<>(), OriGen.create()); - } } diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/Transformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/Transformer.java index 7a18e3dfa8..1c4d9a4c75 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/Transformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/Transformer.java @@ -61,16 +61,16 @@ public void create_col_model() { scan_port_connections(); // Register enums to be translated to integer values scan_enums(); - // Register events and channel IDs for primitive channels - scan_primitive_channels(); + // Create references to necessary Main class attributes + create_minimal_main(); // Translate system parameters translate_system_parameters(); + // Transform known types + transform_known_types(); // Transform shared event variables to event IDs handle_shared_events(); // Transform all regular classes transform_classes(); - // Transform known types - transform_known_types(); // Finish the Main class create_main_class(); } @@ -160,7 +160,7 @@ private void scan_processes() { // Multiple threads are declared -> one thread class for each thread and possibly a state class for shared functionality default -> { // Create state class if at least one member function or attribute is declared - if (!member_functions.isEmpty() || !members.isEmpty()) { + if (member_functions.size() > 0 || members.size() > 0) { StateClass state_class = new StateClass(inst); state_class.set_constructor(constructor); state_class.add_attributes(members); @@ -226,11 +226,11 @@ private void scan_port_connections() { java.util.List hierarchical = sc_port_inst.getModuleInstances(); // If there is a SystemC-internal channel, register it in the COL system context - if (!primitive.isEmpty()) { + if (primitive.size() > 0) { col_system.add_primitive_port_connection(sc_inst, sc_port, primitive.get(0)); } // If there is a user-defined channel, register it in the COL system context - else if (!hierarchical.isEmpty()) { + else if (hierarchical.size() > 0) { col_system.add_hierarchical_port_connection(sc_inst, sc_port, hierarchical.get(0)); } else throw new SystemCFormatException("Port " + sc_port + " has no bound primitive or hierarchical channels!"); @@ -249,26 +249,13 @@ private void scan_enums() { } /** - * Scans the SystemC system for primitive channels and creates the necessary event and primitive update fields. + * Creates the Main class's instance fields for the process and event state sequences. Does not yet instantiate the + * Main class itself. */ - private void scan_primitive_channels() { - for (SCClassInstance instance : sc_system.getInstances()) { - if (instance instanceof SCKnownType sc_inst) { - java.util.ArrayList events = switch (sc_inst.getSCClass().getName()) { - case Constants.CLASS_FIFO_INT, Constants.CLASS_FIFO_BOOL -> - new java.util.ArrayList<>(java.util.List.of(col_system.get_total_nr_events() + Constants.FIFO_READ_EVENT, - col_system.get_total_nr_events() + Constants.FIFO_WRITE_EVENT)); - case Constants.CLASS_SIGNAL_INT, Constants.CLASS_SIGNAL_BOOL -> - new java.util.ArrayList<>(java.util.List.of(col_system.get_total_nr_events() + Constants.SIGNAL_WRITE_EVENT)); - default -> throw new UnsupportedException("Known type " + sc_inst.getSCClass().getName() + " is not supported."); - }; - // Register events - java.util.Collections.sort(events); - col_system.add_channel_events(sc_inst, events); - // Register primitive channel - col_system.register_primitive_channel(sc_inst); - } - } + private void create_minimal_main() { + col_system.set_process_state(new InstanceField<>(col_system.T_SEQ_INT, col_system.NO_FLAGS, OriGen.create("process_state"))); + col_system.set_event_state(new InstanceField<>(col_system.T_SEQ_INT, col_system.NO_FLAGS, OriGen.create("event_state"))); + col_system.set_primitive_channel_update(new InstanceField<>(col_system.T_SEQ_BOOL, col_system.NO_FLAGS, OriGen.create("primitive_channel_update"))); } /** @@ -296,6 +283,18 @@ private void translate_system_parameters() { } } + /** + * Transforms known types, i.e. SystemC-internal primitive channels, to their respective abstract encoding. + */ + private void transform_known_types() { + for (SCClassInstance instance : sc_system.getInstances()) { + if (instance instanceof SCKnownType inst) { + KnownTypeTransformer transformer = new KnownTypeTransformer<>(inst, col_system); + transformer.transform(); + } + } + } + /** * Transforms explicit sc_event variables in the SystemC system to event IDs in the COL system. */ @@ -338,18 +337,6 @@ private void transform_classes() { } } - /** - * Transforms known types, i.e. SystemC-internal primitive channels, to their respective abstract encoding. - */ - private void transform_known_types() { - for (SCClassInstance instance : sc_system.getInstances()) { - if (instance instanceof SCKnownType inst) { - KnownTypeTransformer transformer = new KnownTypeTransformer<>(inst, col_system); - transformer.transform(); - } - } - } - /** * Generates the Main class. The main class contains an attribute for each instance in the encoded system, the * global permission invariant, the permission invariant for the scheduler, the permission invariant for each From 7bda0ad409bc9e3403dda733471b54f7730b8eab Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 12 Mar 2024 11:44:25 +0100 Subject: [PATCH 58/85] Implemented sequence semantics under uncertainty --- .../vct/rewrite/rasi/AbstractProcess.scala | 2 +- .../vct/rewrite/rasi/AbstractState.scala | 79 +++++++++++-------- .../vct/rewrite/rasi/ConcreteVariable.scala | 2 +- src/rewrite/vct/rewrite/rasi/Interval.scala | 5 +- .../vct/rewrite/rasi/UncertainValue.scala | 43 ++++++++++ 5 files changed, 92 insertions(+), 39 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index 940d1b75fd..78a04feede 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -12,7 +12,7 @@ case class AbstractProcess[G](obj: Expr[G]) { // Assign statements change the state of variables directly (if they appear in the valuation) case Assign(target, value) => target.t match { case _: IntType[_] | TBool() => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(target, state.resolve_expression(value)))) - case _: TArray[_] | TSeq(_) => viable_edges(succ, state).map(e => take_edge(e, state.with_updated_collection(target, value))) + case _: TSeq[_] => viable_edges(succ, state).map(e => take_edge(e, state.with_updated_collection(target, value))) // TODO: Consider arrays case _ => viable_edges(succ, state).map(e => take_edge(e, state)) } case Havoc(loc) => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(loc, UncertainValue.uncertain_of(loc.t)))) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 8eb2fe6780..fc7b3d7acb 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -6,7 +6,9 @@ import vct.rewrite.cfg.CFGEntry import scala.collection.immutable.HashMap -case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue], processes: HashMap[AbstractProcess[G], CFGEntry[G]], lock: Option[AbstractProcess[G]]) { +case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue], + processes: HashMap[AbstractProcess[G], CFGEntry[G]], + lock: Option[AbstractProcess[G]]) { def successors(): Set[AbstractState[G]] = processes.flatMap(p => p._1.get_next(p._2, this)).toSet @@ -28,50 +30,57 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] private def val_updated(variable: ConcreteVariable[G], value: UncertainValue): AbstractState[G] = AbstractState(valuations + (variable -> value), processes, lock) + private def refined_valuation(variable: ConcreteVariable[G], value: UncertainValue): AbstractState[G] = { + val_updated(variable, valuations(variable).intersection(value)) + } + def with_updated_collection(variable: Expr[G], assigned: Expr[G]): AbstractState[G] = { val affected: Set[IndexedVariable[G]] = valuations.keySet.filter(v => v.is_contained_by(variable, this)).collect{ case v: IndexedVariable[_] => v } if (affected.isEmpty) return this val by_index: Map[Int, IndexedVariable[G]] = Map.from(affected.map(v => (v.i, v))) - val new_values = get_collection_value(assigned) - var vals = valuations - by_index.foreach(t => vals = vals + (t._2 -> new_values(t._1))) + val new_values: UncertainSequence = get_collection_value(assigned) + var vals: Map[ConcreteVariable[G], UncertainValue] = valuations + by_index.foreach(t => vals = vals + (t._2 -> new_values.get(t._1))) AbstractState(vals, processes, lock) } - private def get_collection_value(lit: Expr[G]): Seq[UncertainValue] = lit match { - case LiteralSeq(_, values) => values.map(e => resolve_expression(e)) - case UntypedLiteralSeq(values) => values.map(e => resolve_expression(e)) - case Cons(x, xs) => resolve_expression(x) +: get_collection_value(xs) - case Concat(xs, ys) => get_collection_value(xs) ++ get_collection_value(ys) - // TODO: Store size of variables - case d: Deref[_] => ??? + private def get_collection_value(lit: Expr[G]): UncertainSequence = lit match { + // Literals + case LiteralSeq(element, values) => + UncertainSequence(UncertainIntegerValue.single(values.size), + values.zipWithIndex.map(t => UncertainIntegerValue.single(t._2) -> resolve_expression(t._1)), + element) + case UntypedLiteralSeq(values) => + UncertainSequence(UncertainIntegerValue.single(values.size), + values.zipWithIndex.map(t => UncertainIntegerValue.single(t._2) -> resolve_expression(t._1)), + values.head.t) + // Variables + case d: Deref[_] => collection_from_variable(d) // TODO: Ask about array semantics case Values(arr, from, to) => ??? case NewArray(element, dims, moreDims, initialize) => ??? - // TODO: Handle cases in which index is not perfectly apparent - case Drop(xs, count) => resolve_integer_expression(count).try_to_resolve() match { - case None => ??? - case Some(i) => get_collection_value(xs).drop(i) - } - case Take(xs, count) => resolve_integer_expression(count).try_to_resolve() match { - case None => ??? - case Some(i) => get_collection_value(xs).take(i) - } - case SeqUpdate(xs, i, x) => resolve_integer_expression(i).try_to_resolve() match { - case None => ??? - case Some(index) => get_collection_value(xs).updated(index, resolve_expression(x)) - } - case RemoveAt(xs, i) => resolve_integer_expression(i).try_to_resolve() match { - case None => ??? - case Some(index) => get_collection_value(xs).zipWithIndex.filter(_._2 != index).map(_._1) - } - case Slice(xs, from, to) => resolve_integer_expression(from).try_to_resolve() match { - case None => ??? - case Some(f) => resolve_integer_expression(to).try_to_resolve() match { - case None => ??? - case Some(t) => get_collection_value(xs).slice(f, t) - } + // Sequence operations + case Cons(x, xs) => get_collection_value(xs).prepend(resolve_expression(x)) + case Concat(xs, ys) => get_collection_value(xs).concat(get_collection_value(ys)) + case Drop(xs, count) => get_collection_value(xs).drop(resolve_integer_expression(count)) + case Take(xs, count) => get_collection_value(xs).take(resolve_integer_expression(count)) + case SeqUpdate(xs, i, x) => get_collection_value(xs).updated(resolve_integer_expression(i), resolve_expression(x)) + case RemoveAt(xs, i) => get_collection_value(xs).remove(resolve_integer_expression(i)) + case Slice(xs, from, to) => get_collection_value(xs).slice(resolve_integer_expression(from), resolve_integer_expression(to)) + // Other expressions that can evaluate to a collection + case Old(expr, _) => get_collection_value(expr) + } + + private def collection_from_variable(deref: Deref[G]): UncertainSequence = { + val affected: Set[IndexedVariable[G]] = valuations.keySet.filter(v => v.is_contained_by(deref, this)).collect { case v: IndexedVariable[_] => v } + val t: Type[G] = deref.ref.decl.t match { + case TArray(element) => element + case TSeq(element) => element + case _ => throw new IllegalArgumentException(s"Unsupported collection type ${deref.ref.decl.t.toInlineString}") } + UncertainSequence(UncertainIntegerValue.above(affected.map(v => v.i).max), + affected.map(v => UncertainIntegerValue.single(v.i) -> valuations(v)).toSeq, + t) } def with_assumption(assumption: Expr[G]): Set[AbstractState[G]] = resolve_effect(assumption, negate = false) @@ -206,7 +215,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case None => UncertainIntegerValue.uncertain() } case Length(arr) => UncertainIntegerValue.above(0) // TODO: Use contextual information from the global invariant - case Size(obj) => UncertainIntegerValue.above(0) // here as well + case Size(obj) => get_collection_value(obj).len case ProcedureInvocation(_, _, _, _, _, _) => UncertainIntegerValue.uncertain() // TODO: return value from procedure/method? case MethodInvocation(_, _, _, _, _, _, _) => UncertainIntegerValue.uncertain() } diff --git a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala index 1d2eaa5187..f9b42fa5a0 100644 --- a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala +++ b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala @@ -16,7 +16,7 @@ trait ConcreteVariable[G] { case class FieldVariable[G](field: InstanceField[G]) extends ConcreteVariable[G] { override def is(expr: Expr[G], state: AbstractState[G]): Boolean = field_equals(expr, field) - override def is_contained_by(expr: Expr[G], state: AbstractState[G]): Boolean = field_equals(expr, field) + override def is_contained_by(expr: Expr[G], state: AbstractState[G]): Boolean = is(expr, state) override def to_expression: Expr[G] = Deref[G](AmbiguousThis()(field.o), field.ref)(field.o)(field.o) override def t: Type[G] = field.t } diff --git a/src/rewrite/vct/rewrite/rasi/Interval.scala b/src/rewrite/vct/rewrite/rasi/Interval.scala index f2c05dfb27..c3d23d6ab0 100644 --- a/src/rewrite/vct/rewrite/rasi/Interval.scala +++ b/src/rewrite/vct/rewrite/rasi/Interval.scala @@ -158,7 +158,6 @@ case class MultiInterval(intervals: Set[Interval]) extends Interval { } private def merge_intersecting(is: Set[Interval]): Set[Interval] = MultiInterval(is).sub_intervals().reduce((i1, i2) => i1.union(i2)).sub_intervals() override def sub_intervals(): Set[Interval] = intervals.flatMap(i => i.sub_intervals()) - private def flatten(): MultiInterval = MultiInterval(this.sub_intervals()) override def try_to_resolve(): Option[Int] = { if (intervals.count(i => i != EmptyInterval) == 1) intervals.filter(i => i != EmptyInterval).head.try_to_resolve() else None @@ -227,7 +226,9 @@ case class BoundedInterval(lower: Int, upper: Int) extends Interval { case EmptyInterval => other case MultiInterval(intervals) => ??? case BoundedInterval(low, up) => ??? - case LowerBoundedInterval(low) => ??? + case LowerBoundedInterval(low) => + if (low < 0) BoundedInterval(-Utils.abs_max(lower, upper), Utils.abs_max(lower, upper)) + else BoundedInterval(scala.math.min(lower / low, 0), scala.math.max(upper / low, 0)) case UpperBoundedInterval(up) => ??? case UnboundedInterval => BoundedInterval(-Utils.abs_max(lower, upper), Utils.abs_max(lower, upper)) } diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index 540f9d16ee..7b8ea57ede 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -6,6 +6,7 @@ trait UncertainValue { def can_be_equal(other: UncertainValue): Boolean def can_be_unequal(other: UncertainValue): Boolean def complement(): UncertainValue + def intersection(other: UncertainValue): UncertainValue def to_expression[G](variable: Expr[G]): Expr[G] def ==(other: UncertainValue): UncertainBooleanValue def !=(other: UncertainValue): UncertainBooleanValue @@ -30,6 +31,11 @@ case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) ex override def complement(): UncertainValue = !this + override def intersection(other: UncertainValue): UncertainValue = other match { + case UncertainBooleanValue(t, f) => UncertainBooleanValue(can_be_true && t, can_be_false && f) + case _ => throw new IllegalArgumentException("Trying to intersect boolean with a different type") + } + override def to_expression[G](variable: Expr[G]): Expr[G] = { if (can_be_true && can_be_false) BooleanValue(value = true)(variable.o) else if (can_be_true) variable @@ -91,6 +97,11 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { case _ => UncertainIntegerValue.uncertain() } + override def intersection(other: UncertainValue): UncertainValue = other match { + case UncertainIntegerValue(v) => UncertainIntegerValue(value.intersection(v)) + case _ => throw new IllegalArgumentException("Trying to intersect integer with different type") + } + override def to_expression[G](variable: Expr[G]): Expr[G] = value.to_expression(variable) override def ==(other: UncertainValue): UncertainBooleanValue = other match { @@ -139,4 +150,36 @@ case object UncertainIntegerValue { def uncertain(): UncertainIntegerValue = UncertainIntegerValue(UnboundedInterval) def above(int: Int): UncertainIntegerValue = UncertainIntegerValue(LowerBoundedInterval(int)) def single(int: Int): UncertainIntegerValue = UncertainIntegerValue(BoundedInterval(int, int)) +} + +case class UncertainSequence(len: UncertainIntegerValue, values: Seq[(UncertainIntegerValue, UncertainValue)], t: Type[_]) { + def concat(other: UncertainSequence): UncertainSequence = + UncertainSequence(len + other.len, values ++ other.values.map(t => (t._1 + len) -> t._2), t) + + def prepend(value: UncertainValue): UncertainSequence = + UncertainSequence(len + UncertainIntegerValue.single(1), (UncertainIntegerValue.single(0) -> value) +: values, t) + + def updated(index: UncertainIntegerValue, value: UncertainValue): UncertainSequence = + UncertainSequence(len, values.filter(t => !t._1.can_be_equal(index)) :+ index -> value, t) + + def remove(index: UncertainIntegerValue): UncertainSequence = + UncertainSequence(len - UncertainIntegerValue.single(1), values.filter(t => !t._1.can_be_equal(index)), t) + + def take(num: UncertainIntegerValue): UncertainSequence = + UncertainSequence(num, values.filter(t => t._1.<(num).can_be_true).map(t => t._1.intersection(num.below()).asInstanceOf[UncertainIntegerValue] -> t._2), t) + + def drop(num: UncertainIntegerValue): UncertainSequence = { + val red: UncertainIntegerValue = num.intersection(len.below()).asInstanceOf[UncertainIntegerValue] + UncertainSequence(len - red, values.filter(t => t._1.>=(red).can_be_true).map(t => t._1.intersection(red.above_eq()).asInstanceOf[UncertainIntegerValue] -> t._2), t) + } + + def slice(lower: UncertainIntegerValue, upper: UncertainIntegerValue): UncertainSequence = + take(upper).drop(lower) + + def get(index: Int): UncertainValue = { + if (index < 0) throw new IllegalArgumentException(s"Trying to access negative index $index") + val i = values.indexWhere(t => t._1.try_to_resolve().getOrElse(-1) == index) + if (i >= 0) values(i)._2 + else UncertainValue.uncertain_of(t) + } } \ No newline at end of file From 693064bc9e37babeb830f1fca9c8f6d6e63d0af6 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 12 Mar 2024 12:54:32 +0100 Subject: [PATCH 59/85] Some documentation for the abstract state --- .../vct/rewrite/rasi/AbstractState.scala | 101 ++++++++++++++++-- 1 file changed, 93 insertions(+), 8 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index fc7b3d7acb..b365a41e2e 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -9,19 +9,55 @@ import scala.collection.immutable.HashMap case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue], processes: HashMap[AbstractProcess[G], CFGEntry[G]], lock: Option[AbstractProcess[G]]) { + /** + * Main function of the abstract state. For all processes that could potentially run, execute all possible next steps. + * + * @return The set of possible successor states + */ def successors(): Set[AbstractState[G]] = processes.flatMap(p => p._1.get_next(p._2, this)).toSet + /** + * Updates the state by changing the program counter for a process. + * + * @param process Process to update + * @param position New position of the process + * @return An abstract state that is a copy of this one with the updated process location + */ def with_process_at(process: AbstractProcess[G], position: CFGEntry[G]): AbstractState[G] = AbstractState(valuations, processes.removed(process) + (process -> position), lock) + /** + * Updates the state by removing a process from the active list. + * + * @param process Process to remove + * @return An abstract state that is a copy of this one without the given process + */ def without_process(process: AbstractProcess[G]): AbstractState[G] = AbstractState(valuations, processes.removed(process), lock) + /** + * Updates the state by locking the global lock. + * + * @param process Process that should hold the global lock + * @return An abstract state that is a copy of this one with the lock held by the given process + */ def locked_by(process: AbstractProcess[G]): AbstractState[G] = AbstractState(valuations, processes, Some(process)) + /** + * Updates the state by unlocking the global lock. + * + * @return An abstract state that is a copy of this one with the global lock unlocked + */ def unlocked(): AbstractState[G] = AbstractState(valuations, processes, None) + /** + * Updates the state by updating the value of a certain variable. + * + * @param variable Variable to update + * @param value New value for the variable + * @return An abstract state that is a copy of this one with the valuation for the given variable changed + */ def with_valuation(variable: Expr[G], value: UncertainValue): AbstractState[G] = variable_from_expr(variable) match { case Some(concrete_variable) => val_updated(concrete_variable, value) case None => this @@ -34,6 +70,14 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] val_updated(variable, valuations(variable).intersection(value)) } + /** + * Updates the state by updating all variables that are affected by an update to a collection. + * + * @param variable The collection that should be updated + * @param assigned New value for the collection + * @return An abstract state that is a copy of this one with the values of all variables that are affected by the + * collection updated accordingly + */ def with_updated_collection(variable: Expr[G], assigned: Expr[G]): AbstractState[G] = { val affected: Set[IndexedVariable[G]] = valuations.keySet.filter(v => v.is_contained_by(variable, this)).collect{ case v: IndexedVariable[_] => v } if (affected.isEmpty) return this @@ -56,7 +100,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] values.head.t) // Variables case d: Deref[_] => collection_from_variable(d) - // TODO: Ask about array semantics + // TODO: Implement array semantics case Values(arr, from, to) => ??? case NewArray(element, dims, moreDims, initialize) => ??? // Sequence operations @@ -83,6 +127,12 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] t) } + /** + * Updates the state by taking a specification in the form of an assumption into account. + * + * @param assumption Boolean expression expressing a state update + * @return A set of abstract states that are a copy of this one, updated according to the given assumption + */ def with_assumption(assumption: Expr[G]): Set[AbstractState[G]] = resolve_effect(assumption, negate = false) private def resolve_effect(assumption: Expr[G], negate: Boolean): Set[AbstractState[G]] = assumption match { @@ -99,8 +149,10 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case _ => throw new IllegalArgumentException(s"Effect of contract expression ${assumption.toInlineString} is not implemented") } - private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[AbstractState[G]] = + private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[AbstractState[G]] = { + // TODO: This is wrong for contracts that mention the same variable multiple times this.resolve_effect(left, neg_left).flatMap(s => s.resolve_effect(right, neg_right)) + } private def handle_or(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[AbstractState[G]] = { val pure_left = is_pure(left) @@ -127,20 +179,21 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] if (pure_left == pure_right) return Set(this) // Now, exactly one side is pure, and the other contains a concrete variable // Resolve the variable and the other side of the equation TODO: Reduce the side with the variable if it contains anything else as well + // TODO: Handle sequences that are assigned in contracts val variable: ConcreteVariable[G] = if (pure_left) variable_from_expr(comp.right).get else variable_from_expr(comp.left).get val value: UncertainValue = if (pure_left) resolve_expression(comp.left) else resolve_expression(comp.right) comp match { case _: Eq[_] => Set(if (!negate) this.val_updated(variable, value) else this.val_updated(variable, value.complement())) case _: Neq[_] => Set(if (!negate) this.val_updated(variable, value.complement()) else this.val_updated(variable, value)) - case AmbiguousGreater(_, _) | Greater(_, _) => bound_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, negate) - case AmbiguousGreaterEq(_, _) | GreaterEq(_, _) => bound_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, !negate) - case AmbiguousLessEq(_, _) | LessEq(_, _) => bound_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left != negate, !negate) - case AmbiguousLess(_, _) | Less(_, _) => bound_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left != negate, negate) + case AmbiguousGreater(_, _) | Greater(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, negate) + case AmbiguousGreaterEq(_, _) | GreaterEq(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, !negate) + case AmbiguousLessEq(_, _) | LessEq(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left != negate, !negate) + case AmbiguousLess(_, _) | Less(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left != negate, negate) } } - private def bound_variable(v: ConcreteVariable[G], b: UncertainIntegerValue, var_greater: Boolean, can_be_equal: Boolean): Set[AbstractState[G]] = { + private def limit_variable(v: ConcreteVariable[G], b: UncertainIntegerValue, var_greater: Boolean, can_be_equal: Boolean): Set[AbstractState[G]] = { if (var_greater) { if (can_be_equal) Set(this.val_updated(v, b.above_eq())) else Set(this.val_updated(v, b.above())) @@ -156,22 +209,43 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case _: Old[_] => true case e: UnExpr[_] => e.subnodes.forall(n => is_pure(n)) case e: BinExpr[_] => e.subnodes.forall(n => is_pure(n)) - case e: Expr[_] => if (valuations.keys.exists(v => v.is(e, this))) false else e.subnodes.forall(n => is_pure(n)) + case e: Expr[_] => if (valuations.keys.exists(v => v.is_contained_by(e, this))) false else e.subnodes.forall(n => is_pure(n)) case _ => true } + /** + * Updates the state by assuming a postcondition. + * + * @param post Postcondition that alters the state + * @param args A map from the method parameters to the given arguments, to be textually replaced + * @return A set of abstract states that are a copy of this one after assuming the given postcondition with the given + * arguments + */ def with_postcondition(post: AccountedPredicate[G], args: Map[Variable[G], Expr[G]]): Set[AbstractState[G]] = with_assumption(unify_expression(AstBuildHelpers.unfoldPredicate(post).reduce((e1, e2) => Star(e1, e2)(e1.o)), args)) private def unify_expression(cond: Expr[G], args: Map[Variable[G], Expr[G]]): Expr[G] = Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> e }).dispatch(cond) + /** + * Evaluates an expression and returns an uncertain value, depending on the type of expression and the values it can + * take with the given level of abstraction. + * + * @param expr COL expression to resolve + * @return An uncertain value of the correct type + */ def resolve_expression(expr: Expr[G]): UncertainValue = expr.t match { case _: IntType[_] => resolve_integer_expression(expr) case _: TBool[_] => resolve_boolean_expression(expr) case _ => throw new IllegalArgumentException(s"Type ${expr.t.toInlineString} is not supported") } + /** + * Evaluates an integer expression and returns an uncertain integer value. + * + * @param expr integer-type COL expression + * @return An uncertain value that represents all possible valuations of the given expression + */ def resolve_integer_expression(expr: Expr[G]): UncertainIntegerValue = expr match { case CIntegerValue(value) => UncertainIntegerValue.single(value.intValue) case IntegerValue(value) => UncertainIntegerValue.single(value.intValue) @@ -220,6 +294,12 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case MethodInvocation(_, _, _, _, _, _, _) => UncertainIntegerValue.uncertain() } + /** + * Evaluates a boolean expression and returns an uncertain boolean value. + * + * @param expr boolean-type COL expression + * @return An uncertain boolean value that represents all possible values that the given expression can take on + */ def resolve_boolean_expression(expr: Expr[G]): UncertainBooleanValue = expr match { case BooleanValue(value) => UncertainBooleanValue.from(value) case Not(arg) => !resolve_boolean_expression(arg) @@ -260,5 +340,10 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] valuations.keys.collectFirst{ case c: ConcreteVariable[G] if c.is(variable, this) => c } } + /** + * Returns an expression to represent this state of the form variable1 == value1 && variable2 == value2 && ... + * + * @return An expression that encodes this state + */ def to_expression: Expr[G] = valuations.map(v => v._2.to_expression(v._1.to_expression)).reduce((e1, e2) => And(e1, e2)(e1.o)) } From 1aefbaa6513c4a30e3ebdc272f26c2aad29a9574 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 13 Mar 2024 14:51:11 +0100 Subject: [PATCH 60/85] Improved support for extracting information out of assumptions --- src/main/vct/options/Options.scala | 2 +- .../vct/rewrite/rasi/AbstractState.scala | 117 +++++++++++------- .../vct/rewrite/rasi/ConstraintMap.scala | 17 +++ .../vct/rewrite/rasi/RASIGenerator.scala | 7 +- 4 files changed, 95 insertions(+), 48 deletions(-) create mode 100644 src/rewrite/vct/rewrite/rasi/ConstraintMap.scala diff --git a/src/main/vct/options/Options.scala b/src/main/vct/options/Options.scala index 01d23e1062..7579cd8d9d 100644 --- a/src/main/vct/options/Options.scala +++ b/src/main/vct/options/Options.scala @@ -270,7 +270,7 @@ case object Options { opt[Unit]("generate-rasi").action((_, c) => c.copy(vesuvGenerateRasi = true)) .text("Instead of transforming a SystemC design to PVL, generate a global invariant for a PVL program") .children( - opt[Seq[String]]("rasi-vars").required().valueName(",...") + opt[Seq[String]]("rasi-vars").valueName(",...") .action((vars, c) => c.copy(vesuvRasiVariables = Some(vars))) .text("[WIP] Preliminary selection mechanism for RASI variables; might be replaced later") ) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index b365a41e2e..5e6bec9c35 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -8,7 +8,8 @@ import scala.collection.immutable.HashMap case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue], processes: HashMap[AbstractProcess[G], CFGEntry[G]], - lock: Option[AbstractProcess[G]]) { + lock: Option[AbstractProcess[G]], + seq_lengths: Map[InstanceField[G], UncertainIntegerValue]) { /** * Main function of the abstract state. For all processes that could potentially run, execute all possible next steps. * @@ -25,7 +26,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @return An abstract state that is a copy of this one with the updated process location */ def with_process_at(process: AbstractProcess[G], position: CFGEntry[G]): AbstractState[G] = - AbstractState(valuations, processes.removed(process) + (process -> position), lock) + AbstractState(valuations, processes.removed(process) + (process -> position), lock, seq_lengths) /** * Updates the state by removing a process from the active list. @@ -34,7 +35,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @return An abstract state that is a copy of this one without the given process */ def without_process(process: AbstractProcess[G]): AbstractState[G] = - AbstractState(valuations, processes.removed(process), lock) + AbstractState(valuations, processes.removed(process), lock, seq_lengths) /** * Updates the state by locking the global lock. @@ -42,14 +43,14 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @param process Process that should hold the global lock * @return An abstract state that is a copy of this one with the lock held by the given process */ - def locked_by(process: AbstractProcess[G]): AbstractState[G] = AbstractState(valuations, processes, Some(process)) + def locked_by(process: AbstractProcess[G]): AbstractState[G] = AbstractState(valuations, processes, Some(process), seq_lengths) /** * Updates the state by unlocking the global lock. * * @return An abstract state that is a copy of this one with the global lock unlocked */ - def unlocked(): AbstractState[G] = AbstractState(valuations, processes, None) + def unlocked(): AbstractState[G] = AbstractState(valuations, processes, None, seq_lengths) /** * Updates the state by updating the value of a certain variable. @@ -64,11 +65,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] } private def val_updated(variable: ConcreteVariable[G], value: UncertainValue): AbstractState[G] = - AbstractState(valuations + (variable -> value), processes, lock) - - private def refined_valuation(variable: ConcreteVariable[G], value: UncertainValue): AbstractState[G] = { - val_updated(variable, valuations(variable).intersection(value)) - } + AbstractState(valuations + (variable -> value), processes, lock, seq_lengths) /** * Updates the state by updating all variables that are affected by an update to a collection. @@ -85,7 +82,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] val new_values: UncertainSequence = get_collection_value(assigned) var vals: Map[ConcreteVariable[G], UncertainValue] = valuations by_index.foreach(t => vals = vals + (t._2 -> new_values.get(t._1))) - AbstractState(vals, processes, lock) + AbstractState(vals, processes, lock, seq_lengths + (affected.head.field -> new_values.len)) } private def get_collection_value(lit: Expr[G]): UncertainSequence = lit match { @@ -117,12 +114,13 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] private def collection_from_variable(deref: Deref[G]): UncertainSequence = { val affected: Set[IndexedVariable[G]] = valuations.keySet.filter(v => v.is_contained_by(deref, this)).collect { case v: IndexedVariable[_] => v } + val len: Option[UncertainIntegerValue] = seq_lengths.get(deref.ref.decl) val t: Type[G] = deref.ref.decl.t match { case TArray(element) => element case TSeq(element) => element case _ => throw new IllegalArgumentException(s"Unsupported collection type ${deref.ref.decl.t.toInlineString}") } - UncertainSequence(UncertainIntegerValue.above(affected.map(v => v.i).max), + UncertainSequence(len.getOrElse(UncertainIntegerValue.above(affected.map(v => v.i).max)), affected.map(v => UncertainIntegerValue.single(v.i) -> valuations(v)).toSeq, t) } @@ -133,59 +131,85 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @param assumption Boolean expression expressing a state update * @return A set of abstract states that are a copy of this one, updated according to the given assumption */ - def with_assumption(assumption: Expr[G]): Set[AbstractState[G]] = resolve_effect(assumption, negate = false) + def with_assumption(assumption: Expr[G]): Set[AbstractState[G]] = + resolve_effect(assumption, negate = false).map(m => AbstractState(valuations ++ m.resolve, processes, lock, seq_lengths)) - private def resolve_effect(assumption: Expr[G], negate: Boolean): Set[AbstractState[G]] = assumption match { + private def resolve_effect(assumption: Expr[G], negate: Boolean): Set[ConstraintMap[G]] = assumption match { // Consider boolean/separation logic operators case Not(arg) => this.resolve_effect(arg, !negate) - case Star(left, right) => if (!negate) handle_and(left, right) else handle_or(left, right, neg_left = true, neg_right = true) - case And(left, right) => if (!negate) handle_and(left, right) else handle_or(left, right, neg_left = true, neg_right = true) - case Or(left, right) => if (!negate) handle_or(left, right) else handle_and(left, right, neg_left = true, neg_right = true) - case Implies(left, right) => if (!negate) handle_or(left, right, neg_left = true) else handle_and(left, right, neg_right = true) + case AmbiguousOr(left, right) => + if (!negate) handle_or(left, right) + else handle_and(left, right, neg_left = true, neg_right = true) + case Star(left, right) => + if (!negate) handle_and(left, right) + else handle_or(left, right, neg_left = true, neg_right = true) + case And(left, right) => + if (!negate) handle_and(left, right) + else handle_or(left, right, neg_left = true, neg_right = true) + case Or(left, right) => + if (!negate) handle_or(left, right) + else handle_and(left, right, neg_left = true, neg_right = true) + case Implies(left, right) => + if (!negate) handle_or(left, right, neg_left = true) + else handle_and(left, right, neg_right = true) // Atomic comparisons, if they contain a concrete variable, can actually affect the state case c: Comparison[G] => handle_update(c, negate) // Boolean variables could appear in the assumption without any comparison - case e: Expr[G] if valuations.keys.exists(v => v.is(e, this)) => Set(this.val_updated(variable_from_expr(e).get, UncertainBooleanValue.from(!negate))) + case e: Expr[G] if valuations.keys.exists(v => v.is(e, this)) => Set(ConstraintMap.from(variable_from_expr(e).get, UncertainBooleanValue.from(!negate))) case _ => throw new IllegalArgumentException(s"Effect of contract expression ${assumption.toInlineString} is not implemented") } - private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[AbstractState[G]] = { - // TODO: This is wrong for contracts that mention the same variable multiple times - this.resolve_effect(left, neg_left).flatMap(s => s.resolve_effect(right, neg_right)) + private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { + val left_constraints = resolve_effect(left, neg_left) + val right_constraints = resolve_effect(right, neg_right) + left_constraints.flatMap(m1 => right_constraints.map(m2 => m1 ++ m2)) } - private def handle_or(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[AbstractState[G]] = { + private def handle_or(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { val pure_left = is_pure(left) val pure_right = is_pure(right) // If one side is pure and the other has an effect on the state, treat this "or" as an implication. If both sides // update the state, treat it as a split in the state space instead - if (pure_left && pure_right) Set(this) + if (pure_left && pure_right) Set(ConstraintMap.empty[G]) else if (pure_left) handle_implies(left, right, !neg_left, neg_right) else if (pure_right) handle_implies(right, left, !neg_right, neg_left) else this.resolve_effect(left, neg_left) ++ this.resolve_effect(right, neg_right) } - private def handle_implies(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[AbstractState[G]] = { + private def handle_implies(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { val resolve_left: UncertainBooleanValue = resolve_boolean_expression(left) - var res: Set[AbstractState[G]] = Set() + var res: Set[ConstraintMap[G]] = Set() if (neg_left && resolve_left.can_be_false || (!neg_left) && resolve_left.can_be_true) res = res ++ resolve_effect(right, neg_right) - if (neg_left && resolve_left.can_be_true || (!neg_left) && resolve_left.can_be_false) res = res ++ Set(this) + if (neg_left && resolve_left.can_be_true || (!neg_left) && resolve_left.can_be_false) res = res ++ Set(ConstraintMap.empty[G]) res } - private def handle_update(comp: Comparison[G], negate: Boolean): Set[AbstractState[G]] = { + private def handle_update(comp: Comparison[G], negate: Boolean): Set[ConstraintMap[G]] = { val pure_left = is_pure(comp.left) val pure_right = is_pure(comp.right) - if (pure_left == pure_right) return Set(this) + if (pure_left == pure_right) return Set(ConstraintMap.empty[G]) // Now, exactly one side is pure, and the other contains a concrete variable // Resolve the variable and the other side of the equation TODO: Reduce the side with the variable if it contains anything else as well - // TODO: Handle sequences that are assigned in contracts + if (valuations.exists(v => v._1.is(if (pure_left) comp.right else comp.left, this))) handle_single_update(comp, pure_left, negate) + else handle_collection_update(comp, pure_left, negate) + } + + private def is_pure(node: Node[G]): Boolean = node match { + // The old value of a variable is always pure, since it cannot be updated + case _: Old[_] => true + case e: UnExpr[_] => e.subnodes.forall(n => is_pure(n)) + case e: BinExpr[_] => e.subnodes.forall(n => is_pure(n)) + case e: Expr[_] => if (valuations.keys.exists(v => v.is_contained_by(e, this))) false else e.subnodes.forall(n => is_pure(n)) + case _ => true + } + + private def handle_single_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): Set[ConstraintMap[G]] = { val variable: ConcreteVariable[G] = if (pure_left) variable_from_expr(comp.right).get else variable_from_expr(comp.left).get val value: UncertainValue = if (pure_left) resolve_expression(comp.left) else resolve_expression(comp.right) comp match { - case _: Eq[_] => Set(if (!negate) this.val_updated(variable, value) else this.val_updated(variable, value.complement())) - case _: Neq[_] => Set(if (!negate) this.val_updated(variable, value.complement()) else this.val_updated(variable, value)) + case _: Eq[_] => Set(if (!negate) ConstraintMap.from(variable, value) else ConstraintMap.from(variable, value.complement())) + case _: Neq[_] => Set(if (!negate) ConstraintMap.from(variable, value.complement()) else ConstraintMap.from(variable, value)) case AmbiguousGreater(_, _) | Greater(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, negate) case AmbiguousGreaterEq(_, _) | GreaterEq(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, !negate) case AmbiguousLessEq(_, _) | LessEq(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left != negate, !negate) @@ -193,24 +217,27 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] } } - private def limit_variable(v: ConcreteVariable[G], b: UncertainIntegerValue, var_greater: Boolean, can_be_equal: Boolean): Set[AbstractState[G]] = { + private def limit_variable(v: ConcreteVariable[G], b: UncertainIntegerValue, var_greater: Boolean, can_be_equal: Boolean): Set[ConstraintMap[G]] = { if (var_greater) { - if (can_be_equal) Set(this.val_updated(v, b.above_eq())) - else Set(this.val_updated(v, b.above())) + if (can_be_equal) Set(ConstraintMap.from(v, b.above_eq())) + else Set(ConstraintMap.from(v, b.above())) } else { - if (can_be_equal) Set(this.val_updated(v, b.below_eq())) - else Set(this.val_updated(v, b.below())) + if (can_be_equal) Set(ConstraintMap.from(v, b.below_eq())) + else Set(ConstraintMap.from(v, b.below())) } } - private def is_pure(node: Node[G]): Boolean = node match { - // The old value of a variable is always pure, since it cannot be updated - case _: Old[_] => true - case e: UnExpr[_] => e.subnodes.forall(n => is_pure(n)) - case e: BinExpr[_] => e.subnodes.forall(n => is_pure(n)) - case e: Expr[_] => if (valuations.keys.exists(v => v.is_contained_by(e, this))) false else e.subnodes.forall(n => is_pure(n)) - case _ => true + private def handle_collection_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): Set[ConstraintMap[G]] = { + val variable: Expr[G] = if (pure_left) comp.right else comp.left + val value: UncertainSequence = get_collection_value(if (pure_left) comp.left else comp.right) + val affected: Set[IndexedVariable[G]] = valuations.keySet.filter(v => v.is_contained_by(variable, this)).collect{ case v: IndexedVariable[_] => v } + + comp match { + case _: Eq[_] if !negate => Set(ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i)))) + case _: Neq[_] if negate => Set(ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i)))) + case _ => throw new IllegalArgumentException(s"The operator ${comp.toInlineString} is not supported for collections") + } } /** @@ -225,7 +252,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] with_assumption(unify_expression(AstBuildHelpers.unfoldPredicate(post).reduce((e1, e2) => Star(e1, e2)(e1.o)), args)) private def unify_expression(cond: Expr[G], args: Map[Variable[G], Expr[G]]): Expr[G] = - Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> e }).dispatch(cond) + Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> Old(e, None)(e.o)(e.o) }).dispatch(cond) /** * Evaluates an expression and returns an uncertain value, depending on the type of expression and the values it can diff --git a/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala b/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala new file mode 100644 index 0000000000..cb3266e586 --- /dev/null +++ b/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala @@ -0,0 +1,17 @@ +package vct.rewrite.rasi + +case class ConstraintMap[G](constraints: Map[ConcreteVariable[G], Set[UncertainValue]]) { + def ++(other: ConstraintMap[G]): ConstraintMap[G] = { + var map: Map[ConcreteVariable[G], Set[UncertainValue]] = constraints + for (e <- other.constraints) { + map = map + (e._1 -> (map.getOrElse(e._1, Set()) ++ e._2)) + } + ConstraintMap(map) + } + def resolve: Map[ConcreteVariable[G], UncertainValue] = constraints.map(e => e._1 -> e._2.reduce((v1, v2) => v1.intersection(v2))) +} +case object ConstraintMap { + def from[G](variable: ConcreteVariable[G], value: UncertainValue): ConstraintMap[G] = ConstraintMap(Map.from(Seq(variable -> Set(value)))) + def from_cons[G](cons: Set[(ConcreteVariable[G], UncertainValue)]): ConstraintMap[G] = ConstraintMap(Map.from(cons.map(t => t._1 -> Set(t._2)))) + def empty[G]: ConstraintMap[G] = ConstraintMap(Map.empty) +} diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index 95433bb7b2..def2bd385a 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -1,6 +1,6 @@ package vct.rewrite.rasi -import vct.col.ast.{Expr, InstanceMethod, Null, Or} +import vct.col.ast.{Expr, InstanceField, InstanceMethod, Null, Or} import vct.col.origin.Origin import vct.rewrite.cfg.{CFGEntry, CFGGenerator} @@ -30,7 +30,10 @@ case class RASIGenerator[G]() { } private def explore(node: CFGEntry[G], vars: Set[ConcreteVariable[G]]): Unit = { - val initial_state = AbstractState(get_initial_values(vars), HashMap((AbstractProcess[G](Null()(Origin(Seq()))), node)), None) + val initial_state = AbstractState(get_initial_values(vars), + HashMap((AbstractProcess[G](Null()(Origin(Seq()))), node)), + None, + Map.empty[InstanceField[G], UncertainIntegerValue]) found_states += initial_state current_branches += initial_state From 978f0782e130e24b545f0e1689cad37af8fc6a07 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 13 Mar 2024 15:10:14 +0100 Subject: [PATCH 61/85] Removed impossible states from assumption resolution --- src/rewrite/vct/rewrite/rasi/AbstractState.scala | 2 +- src/rewrite/vct/rewrite/rasi/ConstraintMap.scala | 1 + src/rewrite/vct/rewrite/rasi/UncertainValue.scala | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 5e6bec9c35..33d456262d 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -132,7 +132,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @return A set of abstract states that are a copy of this one, updated according to the given assumption */ def with_assumption(assumption: Expr[G]): Set[AbstractState[G]] = - resolve_effect(assumption, negate = false).map(m => AbstractState(valuations ++ m.resolve, processes, lock, seq_lengths)) + resolve_effect(assumption, negate = false).filter(m => !m.is_impossible).map(m => AbstractState(valuations ++ m.resolve, processes, lock, seq_lengths)) private def resolve_effect(assumption: Expr[G], negate: Boolean): Set[ConstraintMap[G]] = assumption match { // Consider boolean/separation logic operators diff --git a/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala b/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala index cb3266e586..2317577558 100644 --- a/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala +++ b/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala @@ -9,6 +9,7 @@ case class ConstraintMap[G](constraints: Map[ConcreteVariable[G], Set[UncertainV ConstraintMap(map) } def resolve: Map[ConcreteVariable[G], UncertainValue] = constraints.map(e => e._1 -> e._2.reduce((v1, v2) => v1.intersection(v2))) + def is_impossible: Boolean = resolve.exists(t => t._2.is_impossible) } case object ConstraintMap { def from[G](variable: ConcreteVariable[G], value: UncertainValue): ConstraintMap[G] = ConstraintMap(Map.from(Seq(variable -> Set(value)))) diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index 7b8ea57ede..592d9c374b 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -5,6 +5,7 @@ import vct.col.ast.{BooleanValue, Expr, IntType, Not, TBool, Type} trait UncertainValue { def can_be_equal(other: UncertainValue): Boolean def can_be_unequal(other: UncertainValue): Boolean + def is_impossible: Boolean def complement(): UncertainValue def intersection(other: UncertainValue): UncertainValue def to_expression[G](variable: Expr[G]): Expr[G] @@ -29,6 +30,8 @@ case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) ex case _ => true } + override def is_impossible: Boolean = !can_be_true && !can_be_false + override def complement(): UncertainValue = !this override def intersection(other: UncertainValue): UncertainValue = other match { @@ -92,6 +95,8 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { case _ => true } + override def is_impossible: Boolean = value.empty() + override def complement(): UncertainValue = value match { case BoundedInterval(lower, upper) if lower == upper => UncertainIntegerValue(value.complement()) case _ => UncertainIntegerValue.uncertain() From c7136dde268571c5e497c30d9f91bb8949f52571 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 14 Mar 2024 10:23:55 +0100 Subject: [PATCH 62/85] Fixed errors in the VeSUV implementation --- build.sc | 3 ++- .../vct/col/ast/declaration/cls/InstancePredicateImpl.scala | 2 +- src/col/vct/col/ast/declaration/global/ClassImpl.scala | 2 +- .../parsers/transform/systemctocol/engine/MainTransformer.java | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/build.sc b/build.sc index 372bd2e646..0b36024c8b 100644 --- a/build.sc +++ b/build.sc @@ -832,7 +832,8 @@ object vercors extends Module { ) } def deps = Agg( - ivy"org.antlr:antlr4-runtime:4.8" + ivy"org.antlr:antlr4-runtime:4.8", + ivy"org.apache.logging.log4j:log4j-to-slf4j:2.23.1", ) override def moduleDeps = Seq(hre, col, serialize) diff --git a/src/col/vct/col/ast/declaration/cls/InstancePredicateImpl.scala b/src/col/vct/col/ast/declaration/cls/InstancePredicateImpl.scala index 9ff53f2f32..8452b3c796 100644 --- a/src/col/vct/col/ast/declaration/cls/InstancePredicateImpl.scala +++ b/src/col/vct/col/ast/declaration/cls/InstancePredicateImpl.scala @@ -15,6 +15,6 @@ trait InstancePredicateImpl[G] extends ClassDeclarationImpl[G] with AbstractPred override def layout(implicit ctx: Ctx): Doc = Group( Doc.rspread(layoutModifiers) <> "resource" <+> ctx.name(this) <> "(" <> Doc.args(args) <> ")" <> - body.map(Text(" =") <>> _).getOrElse(Text(";")) + body.map(Text(" =") <>> _ <> ";").getOrElse(Text(";")) ) } \ No newline at end of file diff --git a/src/col/vct/col/ast/declaration/global/ClassImpl.scala b/src/col/vct/col/ast/declaration/global/ClassImpl.scala index 5050c85052..0d722da16d 100644 --- a/src/col/vct/col/ast/declaration/global/ClassImpl.scala +++ b/src/col/vct/col/ast/declaration/global/ClassImpl.scala @@ -16,7 +16,7 @@ trait ClassImpl[G] extends Declarator[G] with ClassOps[G] { this: Class[G] => def transSupportArrows: Seq[(Class[G], Class[G])] = transSupportArrows(Set.empty) def layoutLockInvariant(implicit ctx: Ctx): Doc = - Text("lock_invariant") <+> intrinsicLockInvariant <+/> Empty + Text("lock_invariant") <+> intrinsicLockInvariant <> ";" <+/> Empty override def layout(implicit ctx: Ctx): Doc = (if(intrinsicLockInvariant == tt[G]) Empty else Doc.spec(Show.lazily(layoutLockInvariant(_)))) <> diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java index 530a416422..eb516ecf8c 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java @@ -224,7 +224,7 @@ private void create_instances() { * associated with */ private String create_instance_name(COLClass col_class) { - return col_class.get_generating_instance().getName() + "_" + col_system.get_col_class_translation(col_class).o().getPreferredNameOrElse(Seqs.singleton("unknown")); + return col_class.get_generating_instance().getName() + "_" + col_system.get_col_class_translation(col_class).o().getPreferredNameOrElse(Seqs.singleton("unknown")).snake(); } /** From a9f08abe61719e427cd38809872ae24f77cb9e4b Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 14 Mar 2024 10:59:51 +0100 Subject: [PATCH 63/85] Fixed some errors in the CFG and variable resolution for the RASI --- src/main/vct/main/stages/GenerateRASI.scala | 2 +- src/rewrite/vct/rewrite/cfg/Index.scala | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/vct/main/stages/GenerateRASI.scala b/src/main/vct/main/stages/GenerateRASI.scala index b061092fe7..fce4625a9f 100644 --- a/src/main/vct/main/stages/GenerateRASI.scala +++ b/src/main/vct/main/stages/GenerateRASI.scala @@ -30,7 +30,7 @@ case class GenerateRASI(vars: Option[Seq[String]], out: Path) extends Stage[Node private def resolve_variable(in: Node[Generation], name: String): ConcreteVariable[Generation] = { val name_len = name.indexOf("[") val var_name = if (name_len == -1) name else name.substring(0, name_len) - val index: Option[Integer] = if (name_len == -1) None else Some(Integer.valueOf(name.substring(name_len + 2, name.length - 1))) + val index: Option[Integer] = if (name_len == -1) None else Some(Integer.valueOf(name.substring(name_len + 1, name.length - 1))) val instance_field = in.transSubnodes.collectFirst{ case f: InstanceField[_] if f.o.getPreferredName.get.snake.equals(var_name) => f }.get index match { case Some(i) => IndexedVariable(instance_field, i) diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index a5b634c149..dd26848a63 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -168,6 +168,7 @@ object Index { case class InitialIndex[G](instance_method: InstanceMethod[G]) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = instance_method.body.get + override def has_statement(): Boolean = instance_method.body.nonEmpty override def equals(obj: scala.Any): Boolean = obj match { case InitialIndex(m) => m.equals(instance_method) case _ => false @@ -230,6 +231,7 @@ case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index if (index % 2 == 0) Eval(pvl_branch.branches.apply(index / 2)._1)(pvl_branch.branches.apply(index / 2)._1.o) else pvl_branch.branches.apply((index - 1) / 2)._2 } + override def has_statement(): Boolean = pvl_branch.branches.nonEmpty override def equals(obj: scala.Any): Boolean = obj match { case PVLBranchIndex(b, i) => i == index && b.equals(pvl_branch) case _ => false @@ -450,6 +452,7 @@ case class BlockIndex[G](block: Block[G], index: Int) extends Index[G] { else Set((Outgoing(), None)) } override def resolve(): Statement[G] = block.statements.apply(index) + override def has_statement(): Boolean = block.statements.nonEmpty override def equals(obj: scala.Any): Boolean = obj match { case BlockIndex(b, i) => i == index && b.equals(block) case _ => false @@ -480,6 +483,7 @@ case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { if (index % 2 == 0) Eval(branch.branches.apply(index / 2)._1)(branch.branches.apply(index / 2)._1.o) else branch.branches.apply((index - 1) / 2)._2 } + override def has_statement(): Boolean = branch.branches.nonEmpty override def equals(obj: scala.Any): Boolean = obj match { case BranchIndex(b, i) => i == index && b.equals(branch) case _ => false @@ -489,6 +493,7 @@ case class BranchIndex[G](branch: Branch[G], index: Int) extends Index[G] { case class IndetBranchIndex[G](indet_branch: IndetBranch[G], index: Int) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = indet_branch.branches.apply(index) + override def has_statement(): Boolean = indet_branch.branches.nonEmpty override def equals(obj: scala.Any): Boolean = obj match { case IndetBranchIndex(b, i) => i == index && b.equals(indet_branch) case _ => false @@ -635,6 +640,7 @@ case class UnresolvedSeqBranchIndex[G](unresolved_seq_branch: UnresolvedSeqBranc if (index % 2 == 0) Eval(unresolved_seq_branch.branches.apply(index / 2)._1)(unresolved_seq_branch.branches.apply(index / 2)._1.o) else unresolved_seq_branch.branches.apply((index - 1) / 2)._2 } + override def has_statement(): Boolean = unresolved_seq_branch.branches.nonEmpty override def equals(obj: scala.Any): Boolean = obj match { case UnresolvedSeqBranchIndex(u, i) => i == index && u.equals(unresolved_seq_branch) case _ => false From 8854b9805a3960a44ab2b12596e7022ef29f2973 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 14 Mar 2024 12:59:28 +0100 Subject: [PATCH 64/85] Added support for semantics of more expressions --- .../vct/rewrite/rasi/AbstractState.scala | 15 +++- src/rewrite/vct/rewrite/rasi/Interval.scala | 86 +++++++++++++++++++ .../vct/rewrite/rasi/UncertainValue.scala | 19 ++++ src/rewrite/vct/rewrite/rasi/Utils.scala | 11 +++ 4 files changed, 129 insertions(+), 2 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 33d456262d..4584dd1e0d 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -109,6 +109,14 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case RemoveAt(xs, i) => get_collection_value(xs).remove(resolve_integer_expression(i)) case Slice(xs, from, to) => get_collection_value(xs).slice(resolve_integer_expression(from), resolve_integer_expression(to)) // Other expressions that can evaluate to a collection + case Select(cond, ift, iff) => + val condition: UncertainBooleanValue = resolve_boolean_expression(cond) + val ift_seq: UncertainSequence = get_collection_value(ift) + val iff_seq: UncertainSequence = get_collection_value(iff) + if (condition.can_be_true && condition.can_be_false) ift_seq.union(iff_seq) + else if (condition.can_be_true) ift_seq + else if (condition.can_be_false) iff_seq + else UncertainSequence.empty(ift_seq.t) case Old(expr, _) => get_collection_value(expr) } @@ -156,7 +164,8 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case c: Comparison[G] => handle_update(c, negate) // Boolean variables could appear in the assumption without any comparison case e: Expr[G] if valuations.keys.exists(v => v.is(e, this)) => Set(ConstraintMap.from(variable_from_expr(e).get, UncertainBooleanValue.from(!negate))) - case _ => throw new IllegalArgumentException(s"Effect of contract expression ${assumption.toInlineString} is not implemented") + // TODO: What other expressions could affect the state? + case _ => Set(ConstraintMap.empty[G]) } private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { @@ -315,10 +324,12 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case Some(v) => valuations(v).asInstanceOf[UncertainIntegerValue] case None => UncertainIntegerValue.uncertain() } - case Length(arr) => UncertainIntegerValue.above(0) // TODO: Use contextual information from the global invariant + case Length(arr) => UncertainIntegerValue.above(0) // TODO: Implement array semantics case Size(obj) => get_collection_value(obj).len case ProcedureInvocation(_, _, _, _, _, _) => UncertainIntegerValue.uncertain() // TODO: return value from procedure/method? case MethodInvocation(_, _, _, _, _, _, _) => UncertainIntegerValue.uncertain() + case FunctionInvocation(_, _, _, _, _) => UncertainIntegerValue.uncertain() + case InstanceFunctionInvocation(_, _, _, _, _, _) => UncertainIntegerValue.uncertain() } /** diff --git a/src/rewrite/vct/rewrite/rasi/Interval.scala b/src/rewrite/vct/rewrite/rasi/Interval.scala index c3d23d6ab0..c09acfc9a0 100644 --- a/src/rewrite/vct/rewrite/rasi/Interval.scala +++ b/src/rewrite/vct/rewrite/rasi/Interval.scala @@ -76,6 +76,7 @@ sealed abstract class Interval { def intersection(other: Interval): Interval def union(other: Interval): Interval def complement(): Interval + def is_subset_of(other: Interval): Boolean def below_max(): Interval def above_min(): Interval def +(other: Interval): Interval @@ -96,6 +97,7 @@ case object EmptyInterval extends Interval { override def intersection(other: Interval): Interval = this override def union(other: Interval): Interval = other override def complement(): Interval = UnboundedInterval + override def is_subset_of(other: Interval): Boolean = true override def below_max(): Interval = this override def above_min(): Interval = this override def +(other: Interval): Interval = this @@ -110,8 +112,11 @@ case object EmptyInterval extends Interval { case class MultiInterval(intervals: Set[Interval]) extends Interval { override def empty(): Boolean = intervals.isEmpty || intervals.forall(i => i.empty()) + override def size(): IntervalSize = intervals.map(i => i.size()).fold(Finite(0))((s1, s2) => s1 + s2) + override def intersection(other: Interval): Interval = MultiInterval(MultiInterval(intervals.map(i => i.intersection(other))).sub_intervals()) + override def union(other: Interval): Interval = { val (intersecting, non_intersecting) = intervals.partition(i => i.intersection(other).non_empty()) // Merge together intervals that are connected by the new interval @@ -120,21 +125,29 @@ case class MultiInterval(intervals: Set[Interval]) extends Interval { if (new_intervals.size > 1) MultiInterval(new_intervals) else new_intervals.head } + override def complement(): Interval = intervals.foldLeft[Interval](UnboundedInterval)((i1, i2) => i1.intersection(i2.complement())) + + override def is_subset_of(other: Interval): Boolean = intervals.forall(p => p.is_subset_of(other)) + override def below_max(): Interval = intervals.foldLeft[Interval](EmptyInterval)((i1, i2) => i1.union(i2.below_max())) + override def above_min(): Interval = intervals.foldLeft[Interval](EmptyInterval)((i1, i2) => i1.union(i2.above_min())) + override def +(other: Interval): Interval = { val new_intervals = intervals.map(i => i + other) // It could be that all intervals are now connected into one if (new_intervals.size > 1) MultiInterval(new_intervals) else new_intervals.head } + override def *(other: Interval): Interval = { val new_intervals = intervals.map(i => i * other) // It could be that all intervals are now connected into one if (new_intervals.size > 1) MultiInterval(new_intervals) else new_intervals.head } + override def /(other: Interval): Interval = { var new_intervals = intervals.map(i => i / other) new_intervals = merge_intersecting(new_intervals) @@ -142,6 +155,7 @@ case class MultiInterval(intervals: Set[Interval]) extends Interval { if (new_intervals.size > 1) MultiInterval(new_intervals) else new_intervals.head } + override def %(other: Interval): Interval = { var new_intervals = intervals.map(i => i % other) new_intervals = merge_intersecting(new_intervals) @@ -149,19 +163,25 @@ case class MultiInterval(intervals: Set[Interval]) extends Interval { if (new_intervals.size > 1) MultiInterval(new_intervals) else new_intervals.head } + override def unary_- : Interval = MultiInterval(intervals.map(i => -i)) + override def pow(other: Interval): Interval = { val new_intervals = intervals.map(i => i.pow(other)) // It could be that all intervals are now connected into one if (new_intervals.size > 1) MultiInterval(new_intervals) else new_intervals.head } + private def merge_intersecting(is: Set[Interval]): Set[Interval] = MultiInterval(is).sub_intervals().reduce((i1, i2) => i1.union(i2)).sub_intervals() + override def sub_intervals(): Set[Interval] = intervals.flatMap(i => i.sub_intervals()) + override def try_to_resolve(): Option[Int] = { if (intervals.count(i => i != EmptyInterval) == 1) intervals.filter(i => i != EmptyInterval).head.try_to_resolve() else None } + override def to_expression[G](variable: Expr[G]): Expr[G] = { intervals.map(i => i.to_expression(variable)).fold(BooleanValue[G](value = false)(variable.o))((e1, e2) => Or(e1, e2)(variable.o)) } @@ -169,7 +189,9 @@ case class MultiInterval(intervals: Set[Interval]) extends Interval { case class BoundedInterval(lower: Int, upper: Int) extends Interval { override def empty(): Boolean = lower > upper + override def size(): IntervalSize = Finite(scala.math.max(upper - lower + 1, 0)) + override def intersection(other: Interval): Interval = other match { case EmptyInterval => other case mi: MultiInterval => mi.intersection(this) @@ -184,6 +206,7 @@ case class BoundedInterval(lower: Int, upper: Int) extends Interval { else EmptyInterval case UnboundedInterval => this } + override def union(other: Interval): Interval = other match { case EmptyInterval => this case mi: MultiInterval => mi.union(this) @@ -198,10 +221,23 @@ case class BoundedInterval(lower: Int, upper: Int) extends Interval { else MultiInterval(Set(this, other)) case UnboundedInterval => other } + override def complement(): Interval = MultiInterval(Set(UpperBoundedInterval(lower - 1), LowerBoundedInterval(upper + 1))) + + override def is_subset_of(other: Interval): Boolean = empty || (other match { + case EmptyInterval => false + case MultiInterval(intervals) => intervals.exists(p => is_subset_of(p)) + case BoundedInterval(low, up) => low <= lower && up >= upper + case LowerBoundedInterval(low) => low <= lower + case UpperBoundedInterval(up) => up >= upper + case UnboundedInterval => true + }) + override def below_max(): Interval = UpperBoundedInterval(upper) + override def above_min(): Interval = LowerBoundedInterval(lower) + override def +(other: Interval): Interval = other match { case EmptyInterval | UnboundedInterval => other case mi: MultiInterval => mi.+(this) @@ -209,6 +245,7 @@ case class BoundedInterval(lower: Int, upper: Int) extends Interval { case LowerBoundedInterval(low) => LowerBoundedInterval(lower + low) case UpperBoundedInterval(up) => UpperBoundedInterval(upper + up) } + override def *(other: Interval): Interval = other match { case EmptyInterval | UnboundedInterval => other case mi: MultiInterval => mi.*(this) @@ -222,6 +259,7 @@ case class BoundedInterval(lower: Int, upper: Int) extends Interval { else if (lower >= 0) UpperBoundedInterval(scala.math.max(up * lower, up * upper)) else LowerBoundedInterval(scala.math.min(up * lower, up * upper)) } + override def /(other: Interval): Interval = other match { case EmptyInterval => other case MultiInterval(intervals) => ??? @@ -232,6 +270,7 @@ case class BoundedInterval(lower: Int, upper: Int) extends Interval { case UpperBoundedInterval(up) => ??? case UnboundedInterval => BoundedInterval(-Utils.abs_max(lower, upper), Utils.abs_max(lower, upper)) } + override def %(other: Interval): Interval = other match { case EmptyInterval => other case MultiInterval(intervals) => ??? @@ -242,7 +281,9 @@ case class BoundedInterval(lower: Int, upper: Int) extends Interval { if (lower < 0) BoundedInterval(lower, scala.math.max(0, upper)) else BoundedInterval(0, upper) } + override def unary_- : Interval = BoundedInterval(-upper, -lower) + override def pow(other: Interval): Interval = other match { case EmptyInterval => other case MultiInterval(intervals) => ??? @@ -254,10 +295,12 @@ case class BoundedInterval(lower: Int, upper: Int) extends Interval { else if (lower == -1) LowerBoundedInterval(-1) else LowerBoundedInterval(0) } + override def try_to_resolve(): Option[Int] = { if (lower == upper) Some(upper) else None } + override def to_expression[G](variable: Expr[G]): Expr[G] = { if (lower == upper) Eq(variable, IntegerValue(upper)(variable.o))(variable.o) else And(LessEq(variable, IntegerValue(upper)(variable.o))(variable.o), GreaterEq(variable, IntegerValue(lower)(variable.o))(variable.o))(variable.o) @@ -266,7 +309,9 @@ case class BoundedInterval(lower: Int, upper: Int) extends Interval { case class LowerBoundedInterval(lower: Int) extends Interval { override def empty(): Boolean = false + override def size(): IntervalSize = Infinite() + override def intersection(other: Interval): Interval = other match { case EmptyInterval => other case mi: MultiInterval => mi.intersection(this) @@ -279,6 +324,7 @@ case class LowerBoundedInterval(lower: Int) extends Interval { else EmptyInterval case UnboundedInterval => this } + override def union(other: Interval): Interval = other match { case EmptyInterval => this case mi: MultiInterval => mi.union(this) @@ -291,9 +337,20 @@ case class LowerBoundedInterval(lower: Int) extends Interval { else MultiInterval(Set(other, this)) case UnboundedInterval => other } + override def complement(): Interval = UpperBoundedInterval(lower - 1) + + override def is_subset_of(other: Interval): Boolean = other match { + case EmptyInterval | BoundedInterval(_, _) | UpperBoundedInterval(_) => false + case MultiInterval(intervals) => intervals.exists(p => is_subset_of(p)) + case LowerBoundedInterval(low) => low <= lower + case UnboundedInterval => true + } + override def below_max(): Interval = UnboundedInterval + override def above_min(): Interval = this + override def +(other: Interval): Interval = other match { case EmptyInterval | UnboundedInterval => other case mi: MultiInterval => mi.+(this) @@ -301,6 +358,7 @@ case class LowerBoundedInterval(lower: Int) extends Interval { case LowerBoundedInterval(low) => LowerBoundedInterval(lower + low) case UpperBoundedInterval(_) => UnboundedInterval } + override def *(other: Interval): Interval = other match { case EmptyInterval | UnboundedInterval => other case mi: MultiInterval => mi.*(this) @@ -312,17 +370,25 @@ case class LowerBoundedInterval(lower: Int) extends Interval { if (lower < 0 || up > 0) UnboundedInterval else UpperBoundedInterval(up * lower) } + override def /(other: Interval): Interval = ??? + override def %(other: Interval): Interval = ??? + override def unary_- : Interval = UpperBoundedInterval(-lower) + override def pow(other: Interval): Interval = ??? + override def try_to_resolve(): Option[Int] = None + override def to_expression[G](variable: Expr[G]): Expr[G] = GreaterEq(variable, IntegerValue(lower)(variable.o))(variable.o) } case class UpperBoundedInterval(upper: Int) extends Interval { override def empty(): Boolean = false + override def size(): IntervalSize = Infinite() + override def intersection(other: Interval): Interval = other match { case EmptyInterval => other case mi: MultiInterval => mi.intersection(this) @@ -335,6 +401,7 @@ case class UpperBoundedInterval(upper: Int) extends Interval { case UpperBoundedInterval(up) => UpperBoundedInterval(scala.math.min(up, upper)) case UnboundedInterval => this } + override def union(other: Interval): Interval = other match { case EmptyInterval => this case mi: MultiInterval => mi.union(this) @@ -347,9 +414,20 @@ case class UpperBoundedInterval(upper: Int) extends Interval { case UpperBoundedInterval(up) => UpperBoundedInterval(scala.math.max(upper, up)) case UnboundedInterval => other } + override def complement(): Interval = LowerBoundedInterval(upper + 1) + + override def is_subset_of(other: Interval): Boolean = other match { + case EmptyInterval | BoundedInterval(_, _) | LowerBoundedInterval(_) => false + case MultiInterval(intervals) => intervals.exists(p => is_subset_of(p)) + case UpperBoundedInterval(up) => up >= upper + case UnboundedInterval => true + } + override def below_max(): Interval = this + override def above_min(): Interval = UnboundedInterval + override def +(other: Interval): Interval = other match { case EmptyInterval | UnboundedInterval => other case mi: MultiInterval => mi.+(this) @@ -357,6 +435,7 @@ case class UpperBoundedInterval(upper: Int) extends Interval { case LowerBoundedInterval(_) => UnboundedInterval case UpperBoundedInterval(up) => UpperBoundedInterval(upper + up) } + override def *(other: Interval): Interval = other match { case EmptyInterval | UnboundedInterval => other case mi: MultiInterval => mi.*(this) @@ -368,11 +447,17 @@ case class UpperBoundedInterval(upper: Int) extends Interval { if (up > 0 || upper > 0) UnboundedInterval else LowerBoundedInterval(up * upper) } + override def /(other: Interval): Interval = ??? + override def %(other: Interval): Interval = ??? + override def unary_- : Interval = LowerBoundedInterval(-upper) + override def pow(other: Interval): Interval = ??? + override def try_to_resolve(): Option[Int] = None + override def to_expression[G](variable: Expr[G]): Expr[G] = LessEq(variable, IntegerValue(upper)(variable.o))(variable.o) } @@ -382,6 +467,7 @@ case object UnboundedInterval extends Interval { override def intersection(other: Interval): Interval = other override def union(other: Interval): Interval = this override def complement(): Interval = EmptyInterval + override def is_subset_of(other: Interval): Boolean = other == this override def below_max(): Interval = this override def above_min(): Interval = this override def +(other: Interval): Interval = this diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index 592d9c374b..ec875e5c1b 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -7,6 +7,7 @@ trait UncertainValue { def can_be_unequal(other: UncertainValue): Boolean def is_impossible: Boolean def complement(): UncertainValue + def is_subset_of(other: UncertainValue): Boolean def intersection(other: UncertainValue): UncertainValue def to_expression[G](variable: Expr[G]): Expr[G] def ==(other: UncertainValue): UncertainBooleanValue @@ -34,6 +35,11 @@ case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) ex override def complement(): UncertainValue = !this + override def is_subset_of(other: UncertainValue): Boolean = other match { + case UncertainBooleanValue(t, f) => (t || !can_be_true) && (f || !can_be_false) + case _ => false + } + override def intersection(other: UncertainValue): UncertainValue = other match { case UncertainBooleanValue(t, f) => UncertainBooleanValue(can_be_true && t, can_be_false && f) case _ => throw new IllegalArgumentException("Trying to intersect boolean with a different type") @@ -102,6 +108,11 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { case _ => UncertainIntegerValue.uncertain() } + override def is_subset_of(other: UncertainValue): Boolean = other match { + case UncertainIntegerValue(v) => value.is_subset_of(v) + case _ => false + } + override def intersection(other: UncertainValue): UncertainValue = other match { case UncertainIntegerValue(v) => UncertainIntegerValue(value.intersection(v)) case _ => throw new IllegalArgumentException("Trying to intersect integer with different type") @@ -158,6 +169,11 @@ case object UncertainIntegerValue { } case class UncertainSequence(len: UncertainIntegerValue, values: Seq[(UncertainIntegerValue, UncertainValue)], t: Type[_]) { + def union(other: UncertainSequence): UncertainSequence = { + if (t != other.t) throw new IllegalArgumentException("Unioning sequences of different types") + UncertainSequence(len.union(other.len), Utils.combine_values(values, other.values), t) + } + def concat(other: UncertainSequence): UncertainSequence = UncertainSequence(len + other.len, values ++ other.values.map(t => (t._1 + len) -> t._2), t) @@ -187,4 +203,7 @@ case class UncertainSequence(len: UncertainIntegerValue, values: Seq[(UncertainI if (i >= 0) values(i)._2 else UncertainValue.uncertain_of(t) } +} +case object UncertainSequence { + def empty(t: Type[_]): UncertainSequence = UncertainSequence(UncertainIntegerValue.empty(), Seq(), t) } \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala index 3274c780dc..02e74bc865 100644 --- a/src/rewrite/vct/rewrite/rasi/Utils.scala +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -12,6 +12,17 @@ case object Utils { def prod_min(a1: Int, a2: Int, b1: Int, b2: Int): Int = Seq(a1 * b1, a1 * b2, a2 * b1, a2 * b2).min + def combine_values(v1: Seq[(UncertainIntegerValue, UncertainValue)], v2: Seq[(UncertainIntegerValue, UncertainValue)]): Seq[(UncertainIntegerValue, UncertainValue)] = { + var res: Seq[(UncertainIntegerValue, UncertainValue)] = Seq() + for (v <- v1) { + for (comp <- v2) { + if (v._1.is_subset_of(comp._1) && v._2.is_subset_of(comp._2)) res :+= comp + else if (comp._1.is_subset_of(v._1) && comp._2.is_subset_of(v._2)) res :+= v + } + } + res.distinct + } + def print[G](states: Seq[AbstractState[G]], edges: Seq[(AbstractState[G], AbstractState[G])], out: Path): Unit = { val node_names: Map[AbstractState[G], String] = Map.from(states.zipWithIndex.map(t => (t._1, s"n${t._2}"))) RWFile(out.toFile).write(w => print_state_space(node_names, edges, w)) From f2335520fc689180502f6debcbe2f954d460cf0b Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 14 Mar 2024 14:08:11 +0100 Subject: [PATCH 65/85] Fixed some errors in VeSUV from porting to VerCors 2.0.0 --- .../systemctocol/engine/SpecificationTransformer.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/SpecificationTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/SpecificationTransformer.java index 768f0a35ec..a3e9e936ac 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/SpecificationTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/SpecificationTransformer.java @@ -92,7 +92,7 @@ public ApplicableContract create_constructor_contract(java.util.Map> conds = new java.util.ArrayList<>(); - conds.add(new Perm<>(m_loc, new ReadPerm<>(OriGen.create()), OriGen.create())); + conds.add(new Perm<>(m_loc, new WritePerm<>(OriGen.create()), OriGen.create())); conds.add(new Eq<>(m_deref, m_param_local, OriGen.create())); // Add permissions and functional properties about all other fields as well @@ -118,6 +118,8 @@ public ApplicableContract create_constructor_contract(java.util.Map(col_system.THIS, OriGen.create())); + // TODO: Add specifications for variables that are set by parameters or by constants // Convert expression list to applicable contract and return From 9c83f6765f99a0c5143a9e969e02fbf3ec378f7d Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 15 Mar 2024 10:54:48 +0100 Subject: [PATCH 66/85] Fixed an error where state class constructors would ensure idle tokens --- .../transform/systemctocol/engine/SpecificationTransformer.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/SpecificationTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/SpecificationTransformer.java index a3e9e936ac..1262aa5fce 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/SpecificationTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/SpecificationTransformer.java @@ -118,7 +118,7 @@ public ApplicableContract create_constructor_contract(java.util.Map(col_system.THIS, OriGen.create())); + if (col_class instanceof ProcessClass) conds.add(new IdleToken<>(col_system.THIS, OriGen.create())); // TODO: Add specifications for variables that are set by parameters or by constants From ce9bd80065566b4b6425bc9cafe504ef9ae94ae1 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 15 Mar 2024 15:24:34 +0100 Subject: [PATCH 67/85] Implemented return value resolution from contracts; cleaned up constraint resolution --- .../vct/rewrite/rasi/AbstractState.scala | 273 ++++++------------ .../vct/rewrite/rasi/ConcreteVariable.scala | 13 +- .../vct/rewrite/rasi/ConstraintMap.scala | 10 +- .../vct/rewrite/rasi/ConstraintSolver.scala | 116 ++++++++ .../vct/rewrite/rasi/UncertainValue.scala | 35 ++- 5 files changed, 249 insertions(+), 198 deletions(-) create mode 100644 src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 4584dd1e0d..849c78dce0 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -60,13 +60,10 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @return An abstract state that is a copy of this one with the valuation for the given variable changed */ def with_valuation(variable: Expr[G], value: UncertainValue): AbstractState[G] = variable_from_expr(variable) match { - case Some(concrete_variable) => val_updated(concrete_variable, value) + case Some(concrete_variable) => AbstractState(valuations + (variable -> value), processes, lock, seq_lengths) case None => this } - private def val_updated(variable: ConcreteVariable[G], value: UncertainValue): AbstractState[G] = - AbstractState(valuations + (variable -> value), processes, lock, seq_lengths) - /** * Updates the state by updating all variables that are affected by an update to a collection. * @@ -79,59 +76,12 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] val affected: Set[IndexedVariable[G]] = valuations.keySet.filter(v => v.is_contained_by(variable, this)).collect{ case v: IndexedVariable[_] => v } if (affected.isEmpty) return this val by_index: Map[Int, IndexedVariable[G]] = Map.from(affected.map(v => (v.i, v))) - val new_values: UncertainSequence = get_collection_value(assigned) + val new_values: UncertainSequence = resolve_collection_expression(assigned) var vals: Map[ConcreteVariable[G], UncertainValue] = valuations by_index.foreach(t => vals = vals + (t._2 -> new_values.get(t._1))) AbstractState(vals, processes, lock, seq_lengths + (affected.head.field -> new_values.len)) } - private def get_collection_value(lit: Expr[G]): UncertainSequence = lit match { - // Literals - case LiteralSeq(element, values) => - UncertainSequence(UncertainIntegerValue.single(values.size), - values.zipWithIndex.map(t => UncertainIntegerValue.single(t._2) -> resolve_expression(t._1)), - element) - case UntypedLiteralSeq(values) => - UncertainSequence(UncertainIntegerValue.single(values.size), - values.zipWithIndex.map(t => UncertainIntegerValue.single(t._2) -> resolve_expression(t._1)), - values.head.t) - // Variables - case d: Deref[_] => collection_from_variable(d) - // TODO: Implement array semantics - case Values(arr, from, to) => ??? - case NewArray(element, dims, moreDims, initialize) => ??? - // Sequence operations - case Cons(x, xs) => get_collection_value(xs).prepend(resolve_expression(x)) - case Concat(xs, ys) => get_collection_value(xs).concat(get_collection_value(ys)) - case Drop(xs, count) => get_collection_value(xs).drop(resolve_integer_expression(count)) - case Take(xs, count) => get_collection_value(xs).take(resolve_integer_expression(count)) - case SeqUpdate(xs, i, x) => get_collection_value(xs).updated(resolve_integer_expression(i), resolve_expression(x)) - case RemoveAt(xs, i) => get_collection_value(xs).remove(resolve_integer_expression(i)) - case Slice(xs, from, to) => get_collection_value(xs).slice(resolve_integer_expression(from), resolve_integer_expression(to)) - // Other expressions that can evaluate to a collection - case Select(cond, ift, iff) => - val condition: UncertainBooleanValue = resolve_boolean_expression(cond) - val ift_seq: UncertainSequence = get_collection_value(ift) - val iff_seq: UncertainSequence = get_collection_value(iff) - if (condition.can_be_true && condition.can_be_false) ift_seq.union(iff_seq) - else if (condition.can_be_true) ift_seq - else if (condition.can_be_false) iff_seq - else UncertainSequence.empty(ift_seq.t) - case Old(expr, _) => get_collection_value(expr) - } - - private def collection_from_variable(deref: Deref[G]): UncertainSequence = { - val affected: Set[IndexedVariable[G]] = valuations.keySet.filter(v => v.is_contained_by(deref, this)).collect { case v: IndexedVariable[_] => v } - val len: Option[UncertainIntegerValue] = seq_lengths.get(deref.ref.decl) - val t: Type[G] = deref.ref.decl.t match { - case TArray(element) => element - case TSeq(element) => element - case _ => throw new IllegalArgumentException(s"Unsupported collection type ${deref.ref.decl.t.toInlineString}") - } - UncertainSequence(len.getOrElse(UncertainIntegerValue.above(affected.map(v => v.i).max)), - affected.map(v => UncertainIntegerValue.single(v.i) -> valuations(v)).toSeq, - t) - } /** * Updates the state by taking a specification in the form of an assumption into account. @@ -139,114 +89,9 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @param assumption Boolean expression expressing a state update * @return A set of abstract states that are a copy of this one, updated according to the given assumption */ - def with_assumption(assumption: Expr[G]): Set[AbstractState[G]] = - resolve_effect(assumption, negate = false).filter(m => !m.is_impossible).map(m => AbstractState(valuations ++ m.resolve, processes, lock, seq_lengths)) - - private def resolve_effect(assumption: Expr[G], negate: Boolean): Set[ConstraintMap[G]] = assumption match { - // Consider boolean/separation logic operators - case Not(arg) => this.resolve_effect(arg, !negate) - case AmbiguousOr(left, right) => - if (!negate) handle_or(left, right) - else handle_and(left, right, neg_left = true, neg_right = true) - case Star(left, right) => - if (!negate) handle_and(left, right) - else handle_or(left, right, neg_left = true, neg_right = true) - case And(left, right) => - if (!negate) handle_and(left, right) - else handle_or(left, right, neg_left = true, neg_right = true) - case Or(left, right) => - if (!negate) handle_or(left, right) - else handle_and(left, right, neg_left = true, neg_right = true) - case Implies(left, right) => - if (!negate) handle_or(left, right, neg_left = true) - else handle_and(left, right, neg_right = true) - // Atomic comparisons, if they contain a concrete variable, can actually affect the state - case c: Comparison[G] => handle_update(c, negate) - // Boolean variables could appear in the assumption without any comparison - case e: Expr[G] if valuations.keys.exists(v => v.is(e, this)) => Set(ConstraintMap.from(variable_from_expr(e).get, UncertainBooleanValue.from(!negate))) - // TODO: What other expressions could affect the state? - case _ => Set(ConstraintMap.empty[G]) - } - - private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { - val left_constraints = resolve_effect(left, neg_left) - val right_constraints = resolve_effect(right, neg_right) - left_constraints.flatMap(m1 => right_constraints.map(m2 => m1 ++ m2)) - } - - private def handle_or(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { - val pure_left = is_pure(left) - val pure_right = is_pure(right) - // If one side is pure and the other has an effect on the state, treat this "or" as an implication. If both sides - // update the state, treat it as a split in the state space instead - if (pure_left && pure_right) Set(ConstraintMap.empty[G]) - else if (pure_left) handle_implies(left, right, !neg_left, neg_right) - else if (pure_right) handle_implies(right, left, !neg_right, neg_left) - else this.resolve_effect(left, neg_left) ++ this.resolve_effect(right, neg_right) - } - - private def handle_implies(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { - val resolve_left: UncertainBooleanValue = resolve_boolean_expression(left) - var res: Set[ConstraintMap[G]] = Set() - if (neg_left && resolve_left.can_be_false || (!neg_left) && resolve_left.can_be_true) res = res ++ resolve_effect(right, neg_right) - if (neg_left && resolve_left.can_be_true || (!neg_left) && resolve_left.can_be_false) res = res ++ Set(ConstraintMap.empty[G]) - res - } - - private def handle_update(comp: Comparison[G], negate: Boolean): Set[ConstraintMap[G]] = { - val pure_left = is_pure(comp.left) - val pure_right = is_pure(comp.right) - if (pure_left == pure_right) return Set(ConstraintMap.empty[G]) - // Now, exactly one side is pure, and the other contains a concrete variable - // Resolve the variable and the other side of the equation TODO: Reduce the side with the variable if it contains anything else as well - if (valuations.exists(v => v._1.is(if (pure_left) comp.right else comp.left, this))) handle_single_update(comp, pure_left, negate) - else handle_collection_update(comp, pure_left, negate) - } - - private def is_pure(node: Node[G]): Boolean = node match { - // The old value of a variable is always pure, since it cannot be updated - case _: Old[_] => true - case e: UnExpr[_] => e.subnodes.forall(n => is_pure(n)) - case e: BinExpr[_] => e.subnodes.forall(n => is_pure(n)) - case e: Expr[_] => if (valuations.keys.exists(v => v.is_contained_by(e, this))) false else e.subnodes.forall(n => is_pure(n)) - case _ => true - } - - private def handle_single_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): Set[ConstraintMap[G]] = { - val variable: ConcreteVariable[G] = if (pure_left) variable_from_expr(comp.right).get else variable_from_expr(comp.left).get - val value: UncertainValue = if (pure_left) resolve_expression(comp.left) else resolve_expression(comp.right) - - comp match { - case _: Eq[_] => Set(if (!negate) ConstraintMap.from(variable, value) else ConstraintMap.from(variable, value.complement())) - case _: Neq[_] => Set(if (!negate) ConstraintMap.from(variable, value.complement()) else ConstraintMap.from(variable, value)) - case AmbiguousGreater(_, _) | Greater(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, negate) - case AmbiguousGreaterEq(_, _) | GreaterEq(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, !negate) - case AmbiguousLessEq(_, _) | LessEq(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left != negate, !negate) - case AmbiguousLess(_, _) | Less(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left != negate, negate) - } - } - - private def limit_variable(v: ConcreteVariable[G], b: UncertainIntegerValue, var_greater: Boolean, can_be_equal: Boolean): Set[ConstraintMap[G]] = { - if (var_greater) { - if (can_be_equal) Set(ConstraintMap.from(v, b.above_eq())) - else Set(ConstraintMap.from(v, b.above())) - } - else { - if (can_be_equal) Set(ConstraintMap.from(v, b.below_eq())) - else Set(ConstraintMap.from(v, b.below())) - } - } - - private def handle_collection_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): Set[ConstraintMap[G]] = { - val variable: Expr[G] = if (pure_left) comp.right else comp.left - val value: UncertainSequence = get_collection_value(if (pure_left) comp.left else comp.right) - val affected: Set[IndexedVariable[G]] = valuations.keySet.filter(v => v.is_contained_by(variable, this)).collect{ case v: IndexedVariable[_] => v } - - comp match { - case _: Eq[_] if !negate => Set(ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i)))) - case _: Neq[_] if negate => Set(ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i)))) - case _ => throw new IllegalArgumentException(s"The operator ${comp.toInlineString} is not supported for collections") - } + def with_assumption(assumption: Expr[G]): Set[AbstractState[G]] = { + val constraints: Set[ConstraintMap[G]] = new ConstraintSolver(this, valuations.keySet).resolve_assumption(assumption).filter(m => !m.is_impossible) + constraints.map(m => m.resolve).map(m => AbstractState(valuations.map(e => e._1 -> m.getOrElse(e._1, e._2)), processes, lock, seq_lengths)) } /** @@ -260,12 +105,9 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] def with_postcondition(post: AccountedPredicate[G], args: Map[Variable[G], Expr[G]]): Set[AbstractState[G]] = with_assumption(unify_expression(AstBuildHelpers.unfoldPredicate(post).reduce((e1, e2) => Star(e1, e2)(e1.o)), args)) - private def unify_expression(cond: Expr[G], args: Map[Variable[G], Expr[G]]): Expr[G] = - Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> Old(e, None)(e.o)(e.o) }).dispatch(cond) - /** * Evaluates an expression and returns an uncertain value, depending on the type of expression and the values it can - * take with the given level of abstraction. + * take with the given level of abstraction. This method can only handle single-value types, not collections. * * @param expr COL expression to resolve * @return An uncertain value of the correct type @@ -312,8 +154,8 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case BitUShr(_, _) => UncertainIntegerValue.uncertain() case Select(cond, ift, iff) => { var value: UncertainIntegerValue = UncertainIntegerValue.empty() - if (resolve_boolean_expression(cond).can_be_true) value = value.union(resolve_integer_expression(ift)) - if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_integer_expression(iff)) + if (resolve_boolean_expression(cond).can_be_true) value = value.union(resolve_integer_expression(ift)).asInstanceOf[UncertainIntegerValue] + if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_integer_expression(iff)).asInstanceOf[UncertainIntegerValue] value } case Old(exp, at) => at match { @@ -325,11 +167,15 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case None => UncertainIntegerValue.uncertain() } case Length(arr) => UncertainIntegerValue.above(0) // TODO: Implement array semantics - case Size(obj) => get_collection_value(obj).len - case ProcedureInvocation(_, _, _, _, _, _) => UncertainIntegerValue.uncertain() // TODO: return value from procedure/method? - case MethodInvocation(_, _, _, _, _, _, _) => UncertainIntegerValue.uncertain() - case FunctionInvocation(_, _, _, _, _) => UncertainIntegerValue.uncertain() - case InstanceFunctionInvocation(_, _, _, _, _, _) => UncertainIntegerValue.uncertain() + case Size(obj) => resolve_collection_expression(obj).len + case ProcedureInvocation(ref, args, _, _, _, _) => + get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainIntegerValue] + case MethodInvocation(_, ref, args, _, _, _, _) => + get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainIntegerValue] + case FunctionInvocation(ref, args, _, _, _) => + get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainIntegerValue] + case InstanceFunctionInvocation(_, ref, args, _, _, _) => + get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainIntegerValue] } /** @@ -358,8 +204,8 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case c: SetComparison[G] => UncertainBooleanValue.uncertain() // TODO: Implement? case Select(cond, ift, iff) => { var value: UncertainBooleanValue = UncertainBooleanValue(can_be_true = false, can_be_false = false) - if (resolve_boolean_expression(cond).can_be_true) value = value.union(resolve_boolean_expression(ift)) - if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_boolean_expression(iff)) + if (resolve_boolean_expression(cond).can_be_true) value = value.union(resolve_boolean_expression(ift)).asInstanceOf[UncertainBooleanValue] + if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_boolean_expression(iff)).asInstanceOf[UncertainBooleanValue] value } case Old(exp, at) => at match { @@ -370,14 +216,87 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case Some(v) => valuations(v).asInstanceOf[UncertainBooleanValue] case None => UncertainBooleanValue.uncertain() } - case ProcedureInvocation(_, _, _, _, _, _) => UncertainBooleanValue.uncertain() // TODO: return value from procedure/method? - case MethodInvocation(_, _, _, _, _, _, _) => UncertainBooleanValue.uncertain() + case ProcedureInvocation(ref, args, _, _, _, _) => + get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainBooleanValue] + case MethodInvocation(_, ref, args, _, _, _, _) => + get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainBooleanValue] + case FunctionInvocation(ref, args, _, _, _) => + get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainBooleanValue] + case InstanceFunctionInvocation(_, ref, args, _, _, _) => + get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainBooleanValue] } - private def variable_from_expr(variable: Expr[G]): Option[ConcreteVariable[G]] = { - valuations.keys.collectFirst{ case c: ConcreteVariable[G] if c.is(variable, this) => c } + /** + * Evaluates a collection expression and returns an uncertain collection value. + * + * @param expr collection-type COL expression + * @return An uncertain collection value that represents all possible values that the given expression can take on, + * possibly of uncertain length and with uncertain values at uncertain indices + */ + def resolve_collection_expression(expr: Expr[G]): UncertainSequence = expr match { + // Literals + case LiteralSeq(element, values) => + UncertainSequence(UncertainIntegerValue.single(values.size), + values.zipWithIndex.map(t => UncertainIntegerValue.single(t._2) -> resolve_expression(t._1)), + element) + case UntypedLiteralSeq(values) => + UncertainSequence(UncertainIntegerValue.single(values.size), + values.zipWithIndex.map(t => UncertainIntegerValue.single(t._2) -> resolve_expression(t._1)), + values.head.t) + // Variables + case d: Deref[_] => collection_from_variable(d) + // TODO: Implement array semantics + case Values(arr, from, to) => ??? + case NewArray(element, dims, moreDims, initialize) => ??? + // Sequence operations + case Cons(x, xs) => resolve_collection_expression(xs).prepend(resolve_expression(x)) + case Concat(xs, ys) => resolve_collection_expression(xs).concat(resolve_collection_expression(ys)) + case Drop(xs, count) => resolve_collection_expression(xs).drop(resolve_integer_expression(count)) + case Take(xs, count) => resolve_collection_expression(xs).take(resolve_integer_expression(count)) + case SeqUpdate(xs, i, x) => resolve_collection_expression(xs).updated(resolve_integer_expression(i), resolve_expression(x)) + case RemoveAt(xs, i) => resolve_collection_expression(xs).remove(resolve_integer_expression(i)) + case Slice(xs, from, to) => resolve_collection_expression(xs).slice(resolve_integer_expression(from), resolve_integer_expression(to)) + // Other expressions that can evaluate to a collection + case Select(cond, ift, iff) => + val condition: UncertainBooleanValue = resolve_boolean_expression(cond) + val ift_seq: UncertainSequence = resolve_collection_expression(ift) + val iff_seq: UncertainSequence = resolve_collection_expression(iff) + if (condition.can_be_true && condition.can_be_false) ift_seq.union(iff_seq) + else if (condition.can_be_true) ift_seq + else if (condition.can_be_false) iff_seq + else UncertainSequence.empty(ift_seq.t) + case Old(expr, _) => resolve_collection_expression(expr) + } + + private def collection_from_variable(deref: Deref[G]): UncertainSequence = { + val affected: Set[IndexedVariable[G]] = valuations.keySet.filter(v => v.is_contained_by(deref, this)).collect { case v: IndexedVariable[_] => v } + val len: Option[UncertainIntegerValue] = seq_lengths.get(deref.ref.decl) + val t: Type[G] = deref.ref.decl.t match { + case TArray(element) => element + case TSeq(element) => element + case _ => throw new IllegalArgumentException(s"Unsupported collection type ${deref.ref.decl.t.toInlineString}") + } + UncertainSequence(len.getOrElse(UncertainIntegerValue.above(affected.map(v => v.i).max)), + affected.map(v => UncertainIntegerValue.single(v.i) -> valuations(v)).toSeq, + t) + } + + private def get_subroutine_return(post: AccountedPredicate[G], args: Map[Variable[G], Expr[G]], return_type: Type[G]): UncertainValue = + get_return(unify_expression(AstBuildHelpers.unfoldPredicate(post).reduce((e1, e2) => Star(e1, e2)(e1.o)), args), return_type) + + private def get_return(contract: Expr[G], return_type: Type[G]): UncertainValue = { + val result_var: ResultVariable[G] = ResultVariable() + val constraints: Set[ConstraintMap[G]] = new ConstraintSolver(this, Set(result_var)).resolve_assumption(contract).filter(m => !m.is_impossible) + val possible_vals: Set[UncertainValue] = constraints.map(m => m.resolve.getOrElse(result_var, UncertainValue.uncertain_of(return_type))) + possible_vals.reduce((v1, v2) => v1.union(v2)) } + private def unify_expression(cond: Expr[G], args: Map[Variable[G], Expr[G]]): Expr[G] = + Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> Old(e, None)(e.o)(e.o) }).dispatch(cond) + + private def variable_from_expr(variable: Expr[G]): Option[ConcreteVariable[G]] = + valuations.keys.collectFirst{ case c: ConcreteVariable[G] if c.is(variable, this) => c } + /** * Returns an expression to represent this state of the form variable1 == value1 && variable2 == value2 && ... * diff --git a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala index f9b42fa5a0..f5e2a9ce3f 100644 --- a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala +++ b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala @@ -2,9 +2,20 @@ package vct.rewrite.rasi import vct.col.ast._ -trait ConcreteVariable[G] { +sealed trait ResolvableVariable[G] { def is(expr: Expr[G], state: AbstractState[G]): Boolean def is_contained_by(expr: Expr[G], state: AbstractState[G]): Boolean +} + +case class ResultVariable[G]() extends ResolvableVariable[G] { + override def is(expr: Expr[G], state: AbstractState[G]): Boolean = expr match { + case AmbiguousResult() | Result(_) => true + case _ => false + } + override def is_contained_by(expr: Expr[G], state: AbstractState[G]): Boolean = is(expr, state) +} + +sealed trait ConcreteVariable[G] extends ResolvableVariable[G] { def to_expression: Expr[G] def t: Type[G] def field_equals(expr: Expr[G], field: InstanceField[G]): Boolean = expr match { diff --git a/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala b/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala index 2317577558..18717656f4 100644 --- a/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala +++ b/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala @@ -1,18 +1,18 @@ package vct.rewrite.rasi -case class ConstraintMap[G](constraints: Map[ConcreteVariable[G], Set[UncertainValue]]) { +case class ConstraintMap[G](constraints: Map[ResolvableVariable[G], Set[UncertainValue]]) { def ++(other: ConstraintMap[G]): ConstraintMap[G] = { - var map: Map[ConcreteVariable[G], Set[UncertainValue]] = constraints + var map: Map[ResolvableVariable[G], Set[UncertainValue]] = constraints for (e <- other.constraints) { map = map + (e._1 -> (map.getOrElse(e._1, Set()) ++ e._2)) } ConstraintMap(map) } - def resolve: Map[ConcreteVariable[G], UncertainValue] = constraints.map(e => e._1 -> e._2.reduce((v1, v2) => v1.intersection(v2))) + def resolve: Map[ResolvableVariable[G], UncertainValue] = constraints.map(e => e._1 -> e._2.reduce((v1, v2) => v1.intersection(v2))) def is_impossible: Boolean = resolve.exists(t => t._2.is_impossible) } case object ConstraintMap { - def from[G](variable: ConcreteVariable[G], value: UncertainValue): ConstraintMap[G] = ConstraintMap(Map.from(Seq(variable -> Set(value)))) - def from_cons[G](cons: Set[(ConcreteVariable[G], UncertainValue)]): ConstraintMap[G] = ConstraintMap(Map.from(cons.map(t => t._1 -> Set(t._2)))) + def from[G](variable: ResolvableVariable[G], value: UncertainValue): ConstraintMap[G] = ConstraintMap(Map.from(Seq(variable -> Set(value)))) + def from_cons[G](cons: Set[(ResolvableVariable[G], UncertainValue)]): ConstraintMap[G] = ConstraintMap(Map.from(cons.map(t => t._1 -> Set(t._2)))) def empty[G]: ConstraintMap[G] = ConstraintMap(Map.empty) } diff --git a/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala new file mode 100644 index 0000000000..bb23aa8b23 --- /dev/null +++ b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala @@ -0,0 +1,116 @@ +package vct.rewrite.rasi + +import vct.col.ast._ + +class ConstraintSolver[G](state: AbstractState[G], vars: Set[ResolvableVariable[G]]) { + def resolve_assumption(expr: Expr[G]): Set[ConstraintMap[G]] = resolve(expr) + + private def resolve(expr: Expr[G], negate: Boolean = false): Set[ConstraintMap[G]] = expr match { + // Consider boolean/separation logic operators + case Not(arg) => this.resolve(arg, !negate) + case AmbiguousOr(left, right) => + if (!negate) handle_or(left, right) + else handle_and(left, right, neg_left = true, neg_right = true) + case Star(left, right) => + if (!negate) handle_and(left, right) + else handle_or(left, right, neg_left = true, neg_right = true) + case And(left, right) => + if (!negate) handle_and(left, right) + else handle_or(left, right, neg_left = true, neg_right = true) + case Or(left, right) => + if (!negate) handle_or(left, right) + else handle_and(left, right, neg_left = true, neg_right = true) + case Implies(left, right) => + if (!negate) handle_or(left, right, neg_left = true) + else handle_and(left, right, neg_right = true) + // Atomic comparisons, if they contain a concrete variable, can actually affect the state + case c: Comparison[G] => handle_update(c, negate) + // Boolean variables could appear in the assumption without any comparison + case e: Expr[G] if vars.exists(v => v.is(e, state)) => Set(ConstraintMap.from(get_var(e).get, UncertainBooleanValue.from(!negate))) + // TODO: What other expressions could affect the state? + case _ => Set(ConstraintMap.empty[G]) + } + + private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { + val left_constraints = resolve(left, neg_left) + val right_constraints = resolve(right, neg_right) + left_constraints.flatMap(m1 => right_constraints.map(m2 => m1 ++ m2)) + } + + private def handle_or(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { + val pure_left = is_pure(left) + val pure_right = is_pure(right) + // If one side is pure and the other has an effect on the state, treat this "or" as an implication. If both sides + // update the state, treat it as a split in the state space instead + if (pure_left && pure_right) Set(ConstraintMap.empty[G]) + else if (pure_left) handle_implies(left, right, !neg_left, neg_right) + else if (pure_right) handle_implies(right, left, !neg_right, neg_left) + else this.resolve(left, neg_left) ++ this.resolve(right, neg_right) + } + + private def handle_implies(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { + val resolve_left: UncertainBooleanValue = state.resolve_boolean_expression(left) + var res: Set[ConstraintMap[G]] = Set() + if (neg_left && resolve_left.can_be_false || (!neg_left) && resolve_left.can_be_true) res = res ++ resolve(right, neg_right) + if (neg_left && resolve_left.can_be_true || (!neg_left) && resolve_left.can_be_false) res = res ++ Set(ConstraintMap.empty[G]) + res + } + + private def handle_update(comp: Comparison[G], negate: Boolean): Set[ConstraintMap[G]] = { + val pure_left = is_pure(comp.left) + val pure_right = is_pure(comp.right) + if (pure_left == pure_right) return Set(ConstraintMap.empty[G]) + // Now, exactly one side is pure, and the other contains a concrete variable + // Resolve the variable and the other side of the equation TODO: Reduce the side with the variable if it contains anything else as well + if (vars.exists(v => v.is(if (pure_left) comp.right else comp.left, state))) handle_single_update(comp, pure_left, negate) + else handle_collection_update(comp, pure_left, negate) + } + + private def is_pure(node: Node[G]): Boolean = node match { + // The old value of a variable is always pure, since it cannot be updated + case _: Old[_] => true + case e: UnExpr[_] => e.subnodes.forall(n => is_pure(n)) + case e: BinExpr[_] => e.subnodes.forall(n => is_pure(n)) + case e: Expr[_] => if (vars.exists(v => v.is_contained_by(e, state))) false else e.subnodes.forall(n => is_pure(n)) + case _ => true + } + + private def handle_single_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): Set[ConstraintMap[G]] = { + val variable: ResolvableVariable[G] = if (pure_left) get_var(comp.right).get else get_var(comp.left).get + val value: UncertainValue = if (pure_left) state.resolve_expression(comp.left) else state.resolve_expression(comp.right) + + comp match { + case _: Eq[_] => Set(if (!negate) ConstraintMap.from(variable, value) else ConstraintMap.from(variable, value.complement())) + case _: Neq[_] => Set(if (!negate) ConstraintMap.from(variable, value.complement()) else ConstraintMap.from(variable, value)) + case AmbiguousGreater(_, _) | Greater(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, negate) + case AmbiguousGreaterEq(_, _) | GreaterEq(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, !negate) + case AmbiguousLessEq(_, _) | LessEq(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left != negate, !negate) + case AmbiguousLess(_, _) | Less(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left != negate, negate) + } + } + + private def limit_variable(v: ResolvableVariable[G], b: UncertainIntegerValue, var_greater: Boolean, can_be_equal: Boolean): Set[ConstraintMap[G]] = { + if (var_greater) { + if (can_be_equal) Set(ConstraintMap.from(v, b.above_eq())) + else Set(ConstraintMap.from(v, b.above())) + } + else { + if (can_be_equal) Set(ConstraintMap.from(v, b.below_eq())) + else Set(ConstraintMap.from(v, b.below())) + } + } + + private def handle_collection_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): Set[ConstraintMap[G]] = { + val variable: Expr[G] = if (pure_left) comp.right else comp.left + val value: UncertainSequence = state.resolve_collection_expression(if (pure_left) comp.left else comp.right) + val affected: Set[IndexedVariable[G]] = vars.filter(v => v.is_contained_by(variable, state)).collect{ case v: IndexedVariable[_] => v } + + comp match { + case _: Eq[_] if !negate => Set(ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i)))) + case _: Neq[_] if negate => Set(ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i)))) + case _ => throw new IllegalArgumentException(s"The operator ${comp.toInlineString} is not supported for collections") + } + } + + private def get_var(expr: Expr[G]): Option[ResolvableVariable[G]] = vars.collectFirst{ case v: ResolvableVariable[G] if v.is(expr, state) => v } +} diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index ec875e5c1b..029600ebcc 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -6,9 +6,10 @@ trait UncertainValue { def can_be_equal(other: UncertainValue): Boolean def can_be_unequal(other: UncertainValue): Boolean def is_impossible: Boolean - def complement(): UncertainValue def is_subset_of(other: UncertainValue): Boolean + def complement(): UncertainValue def intersection(other: UncertainValue): UncertainValue + def union(other: UncertainValue): UncertainValue def to_expression[G](variable: Expr[G]): Expr[G] def ==(other: UncertainValue): UncertainBooleanValue def !=(other: UncertainValue): UncertainBooleanValue @@ -33,18 +34,23 @@ case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) ex override def is_impossible: Boolean = !can_be_true && !can_be_false - override def complement(): UncertainValue = !this - override def is_subset_of(other: UncertainValue): Boolean = other match { case UncertainBooleanValue(t, f) => (t || !can_be_true) && (f || !can_be_false) case _ => false } + override def complement(): UncertainValue = !this + override def intersection(other: UncertainValue): UncertainValue = other match { case UncertainBooleanValue(t, f) => UncertainBooleanValue(can_be_true && t, can_be_false && f) case _ => throw new IllegalArgumentException("Trying to intersect boolean with a different type") } + override def union(other: UncertainValue): UncertainValue = other match { + case UncertainBooleanValue(t, f) => UncertainBooleanValue(can_be_true || t, can_be_false || f) + case _ => throw new IllegalArgumentException("Trying to union boolean with a different type") + } + override def to_expression[G](variable: Expr[G]): Expr[G] = { if (can_be_true && can_be_false) BooleanValue(value = true)(variable.o) else if (can_be_true) variable @@ -68,9 +74,6 @@ case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) ex else None } - def union(other: UncertainBooleanValue): UncertainBooleanValue = - UncertainBooleanValue(can_be_true || other.can_be_true, can_be_false || other.can_be_false) - def &&(other: UncertainBooleanValue): UncertainBooleanValue = UncertainBooleanValue(can_be_true && other.can_be_true, can_be_false || other.can_be_false) def ||(other: UncertainBooleanValue): UncertainBooleanValue = @@ -103,21 +106,26 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { override def is_impossible: Boolean = value.empty() - override def complement(): UncertainValue = value match { - case BoundedInterval(lower, upper) if lower == upper => UncertainIntegerValue(value.complement()) - case _ => UncertainIntegerValue.uncertain() - } - override def is_subset_of(other: UncertainValue): Boolean = other match { case UncertainIntegerValue(v) => value.is_subset_of(v) case _ => false } + override def complement(): UncertainValue = value match { + case BoundedInterval(lower, upper) if lower == upper => UncertainIntegerValue(value.complement()) + case _ => UncertainIntegerValue.uncertain() + } + override def intersection(other: UncertainValue): UncertainValue = other match { case UncertainIntegerValue(v) => UncertainIntegerValue(value.intersection(v)) case _ => throw new IllegalArgumentException("Trying to intersect integer with different type") } + override def union(other: UncertainValue): UncertainValue = other match { + case UncertainIntegerValue(v) => UncertainIntegerValue(value.union(v)) + case _ => throw new IllegalArgumentException("Trying to union integer with different type") + } + override def to_expression[G](variable: Expr[G]): Expr[G] = value.to_expression(variable) override def ==(other: UncertainValue): UncertainBooleanValue = other match { @@ -132,9 +140,6 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { def try_to_resolve(): Option[Int] = value.try_to_resolve() - def union(other: UncertainIntegerValue): UncertainIntegerValue = - UncertainIntegerValue(value.union(other.value)) - def below_eq(): UncertainIntegerValue = UncertainIntegerValue(value.below_max()) def below(): UncertainIntegerValue = below_eq() + UncertainIntegerValue.single(-1) def above_eq(): UncertainIntegerValue = UncertainIntegerValue(value.above_min()) @@ -171,7 +176,7 @@ case object UncertainIntegerValue { case class UncertainSequence(len: UncertainIntegerValue, values: Seq[(UncertainIntegerValue, UncertainValue)], t: Type[_]) { def union(other: UncertainSequence): UncertainSequence = { if (t != other.t) throw new IllegalArgumentException("Unioning sequences of different types") - UncertainSequence(len.union(other.len), Utils.combine_values(values, other.values), t) + UncertainSequence(len.union(other.len).asInstanceOf[UncertainIntegerValue], Utils.combine_values(values, other.values), t) } def concat(other: UncertainSequence): UncertainSequence = From 2280f94bf271faa7ba1ed91451915aef21a5bf6d Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 15 Mar 2024 16:12:44 +0100 Subject: [PATCH 68/85] Fixed some type errors --- src/rewrite/vct/rewrite/rasi/AbstractState.scala | 5 +++-- src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 849c78dce0..766cd1749c 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -60,7 +60,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @return An abstract state that is a copy of this one with the valuation for the given variable changed */ def with_valuation(variable: Expr[G], value: UncertainValue): AbstractState[G] = variable_from_expr(variable) match { - case Some(concrete_variable) => AbstractState(valuations + (variable -> value), processes, lock, seq_lengths) + case Some(concrete_variable) => AbstractState(valuations + (concrete_variable -> value), processes, lock, seq_lengths) case None => this } @@ -286,7 +286,8 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] private def get_return(contract: Expr[G], return_type: Type[G]): UncertainValue = { val result_var: ResultVariable[G] = ResultVariable() - val constraints: Set[ConstraintMap[G]] = new ConstraintSolver(this, Set(result_var)).resolve_assumption(contract).filter(m => !m.is_impossible) + val result_set: Set[ResolvableVariable[G]] = Set(result_var) + val constraints: Set[ConstraintMap[G]] = new ConstraintSolver(this, result_set).resolve_assumption(contract).filter(m => !m.is_impossible) val possible_vals: Set[UncertainValue] = constraints.map(m => m.resolve.getOrElse(result_var, UncertainValue.uncertain_of(return_type))) possible_vals.reduce((v1, v2) => v1.union(v2)) } diff --git a/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala index bb23aa8b23..6cc4e646e8 100644 --- a/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala +++ b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala @@ -2,7 +2,7 @@ package vct.rewrite.rasi import vct.col.ast._ -class ConstraintSolver[G](state: AbstractState[G], vars: Set[ResolvableVariable[G]]) { +class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVariable[G]]) { def resolve_assumption(expr: Expr[G]): Set[ConstraintMap[G]] = resolve(expr) private def resolve(expr: Expr[G], negate: Boolean = false): Set[ConstraintMap[G]] = expr match { @@ -103,7 +103,7 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[ResolvableVariable[ private def handle_collection_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): Set[ConstraintMap[G]] = { val variable: Expr[G] = if (pure_left) comp.right else comp.left val value: UncertainSequence = state.resolve_collection_expression(if (pure_left) comp.left else comp.right) - val affected: Set[IndexedVariable[G]] = vars.filter(v => v.is_contained_by(variable, state)).collect{ case v: IndexedVariable[_] => v } + val affected: Set[IndexedVariable[G]] = vars.filter(v => v.is_contained_by(variable, state)).collect{ case v: IndexedVariable[G] => v } comp match { case _: Eq[_] if !negate => Set(ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i)))) From 8b566872026e2fb6171a490244abfdc5723a61d9 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 18 Mar 2024 11:11:45 +0100 Subject: [PATCH 69/85] Fixed an error in the redundant state filter of the RASI generator --- src/rewrite/vct/rewrite/rasi/RASIGenerator.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index def2bd385a..b9bf44acee 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -7,6 +7,7 @@ import vct.rewrite.cfg.{CFGEntry, CFGGenerator} import java.nio.file.Path import scala.collection.immutable.HashMap import scala.collection.mutable +import scala.collection.mutable.ArrayBuffer case class RASIGenerator[G]() { private val found_states: mutable.ArrayBuffer[AbstractState[G]] = mutable.ArrayBuffer() @@ -53,8 +54,8 @@ case class RASIGenerator[G]() { } private def reduce_redundant_states(): (Seq[AbstractState[G]], Seq[(AbstractState[G], AbstractState[G])]) = { - val state_groups: Map[AbstractState[G], AbstractState[G]] = found_states.groupBy(s => s.valuations).flatMap(t => t._2.map(s => (s, t._2.head))) - val edge_groups: Seq[(AbstractState[G], AbstractState[G])] = Seq.from(found_edges.map(t => (state_groups(t._1), state_groups(t._2))).distinct) - (state_groups.values.toSeq.distinct, edge_groups) + val state_groups: Map[Map[ConcreteVariable[G], UncertainValue], ArrayBuffer[AbstractState[G]]] = Map.from(found_states.groupBy(s => s.valuations)) + val edge_groups: Seq[(AbstractState[G], AbstractState[G])] = Seq.from(found_edges.map(t => (state_groups(t._1.valuations).head, state_groups(t._2.valuations).head)).distinct) + (state_groups.values.toSeq.map(v => v.head), edge_groups) } } From 1e109fff555ff742efb685d52f08e0cf6dbc2d97 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 18 Mar 2024 11:12:15 +0100 Subject: [PATCH 70/85] Implemented verification support for vesuv main method --- src/col/vct/col/ast/Node.scala | 4 ++-- ...inMethodImpl.scala => VeSUVMainMethodImpl.scala} | 6 +++--- src/parsers/vct/parsers/transform/PVLToCol.scala | 4 ++-- src/rewrite/vct/rewrite/lang/LangPVLToCol.scala | 13 +++++++++++++ .../vct/rewrite/lang/LangSpecificToCol.scala | 1 + 5 files changed, 21 insertions(+), 7 deletions(-) rename src/col/vct/col/ast/declaration/cls/{MainMethodImpl.scala => VeSUVMainMethodImpl.scala} (59%) diff --git a/src/col/vct/col/ast/Node.scala b/src/col/vct/col/ast/Node.scala index a32ffd1f9b..0b23512949 100644 --- a/src/col/vct/col/ast/Node.scala +++ b/src/col/vct/col/ast/Node.scala @@ -263,10 +263,10 @@ final class Model[G](val declarations: Seq[ModelDeclaration[G]])(implicit val o: val args: Seq[Variable[G]], val outArgs: Seq[Variable[G]], val typeArgs: Seq[Variable[G]], val body: Option[Statement[G]], val contract: ApplicableContract[G], - val inline: Boolean = false, val pure: Boolean = false) + val inline: Boolean = false, val pure: Boolean = false, val vesuv_entry: Boolean = false) (val blame: Blame[CallableFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with AbstractMethod[G] with ProcedureImpl[G] -final class MainMethod[G](val body: Option[Statement[G]])(val blame: Blame[CallableFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with MainMethodImpl[G] +final class VeSUVMainMethod[G](val body: Option[Statement[G]])(val blame: Blame[CallableFailure])(implicit val o: Origin) extends GlobalDeclaration[G] with VeSUVMainMethodImpl[G] @scopes[Variable] final class Predicate[G](val args: Seq[Variable[G]], val body: Option[Expr[G]], val threadLocal: Boolean = false, val inline: Boolean = false)(implicit val o: Origin) extends GlobalDeclaration[G] with AbstractPredicate[G] with PredicateImpl[G] diff --git a/src/col/vct/col/ast/declaration/cls/MainMethodImpl.scala b/src/col/vct/col/ast/declaration/cls/VeSUVMainMethodImpl.scala similarity index 59% rename from src/col/vct/col/ast/declaration/cls/MainMethodImpl.scala rename to src/col/vct/col/ast/declaration/cls/VeSUVMainMethodImpl.scala index d59143891e..e918f0b10f 100644 --- a/src/col/vct/col/ast/declaration/cls/MainMethodImpl.scala +++ b/src/col/vct/col/ast/declaration/cls/VeSUVMainMethodImpl.scala @@ -1,10 +1,10 @@ package vct.col.ast.declaration.cls -import vct.col.ast.MainMethod +import vct.col.ast.VeSUVMainMethod import vct.col.print.{Ctx, Doc, Empty, Text} -import vct.col.ast.ops.MainMethodOps +import vct.col.ast.ops.VeSUVMainMethodOps -trait MainMethodImpl[G] extends MainMethodOps[G] { this: MainMethod[G] => +trait VeSUVMainMethodImpl[G] extends VeSUVMainMethodOps[G] { this: VeSUVMainMethod[G] => override def layout(implicit ctx: Ctx): Doc = Doc.stack(Seq( Text("vesuv_entry") <> body.map(Empty <+> _.layoutAsBlock).getOrElse(Text(";")), diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index dd5a5e77d3..5ab9ae02f9 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -151,9 +151,9 @@ case class PVLToCol[G](override val baseOrigin: Origin, ) } - def convert(implicit method: VesuvEntryContext): Seq[MainMethod[G]] = method match { + def convert(implicit method: VesuvEntryContext): Seq[VeSUVMainMethod[G]] = method match { case VesuvEntry0(_, maybeBody) => - Seq(new MainMethod(convert(maybeBody))(blame(method))) + Seq(new VeSUVMainMethod(convert(maybeBody))(blame(method))) } def convert(implicit args: ArgsContext): Seq[Variable[G]] = args match { diff --git a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala index 65a68db399..be907a053c 100644 --- a/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangPVLToCol.scala @@ -142,4 +142,17 @@ case class LangPVLToCol[Pre <: Generation](rw: LangSpecificToCol[Pre], veymontGe else assign.rewriteDefault() + def rewriteMainMethod(main: VeSUVMainMethod[Pre]): Unit = { + implicit val o: Origin = main.o + main.drop() + val body: Option[Statement[Post]] = main.body match { + case None => None + case Some(s) => Some(s.rewriteDefault()) + } + val empty_pred: AccountedPredicate[Post] = UnitAccountedPredicate(BooleanValue(value = true)) + // TODO: Where does the blame come from? + val contract: ApplicableContract[Post] = ApplicableContract(empty_pred, empty_pred, BooleanValue(value = true), Seq(), Seq(), Seq(), None)(o) + val new_main: Procedure[Post] = new Procedure(TVoid(), Seq(), Seq(), Seq(), body, contract, false, false, true)(main.blame) + new_main.declare() + } } diff --git a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala index e59d7a5a26..d8b7ae0f9c 100644 --- a/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala +++ b/src/rewrite/vct/rewrite/lang/LangSpecificToCol.scala @@ -101,6 +101,7 @@ case class LangSpecificToCol[Pre <: Generation](veymontGeneratePermissions: Bool case p: JavaParam[Pre] => java.rewriteParameter(p) case cons: PVLConstructor[Pre] => pvl.rewriteConstructor(cons) + case main: VeSUVMainMethod[Pre] => pvl.rewriteMainMethod(main) case method: JavaMethod[Pre] => java.rewriteMethod(method) From 1299e5be5e58b7b97d1f2a77b35de2654cfad390 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 18 Mar 2024 13:07:05 +0100 Subject: [PATCH 71/85] Enabled parsing and resolution of VeSUV main method --- src/col/vct/col/resolve/ctx/Referrable.scala | 3 +++ src/col/vct/col/typerules/CoercingRewriter.scala | 2 ++ src/parsers/antlr4/LangPVLLexer.g4 | 2 +- src/parsers/antlr4/LangPVLParser.g4 | 2 +- src/parsers/vct/parsers/transform/PVLToCol.scala | 1 + 5 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/col/vct/col/resolve/ctx/Referrable.scala b/src/col/vct/col/resolve/ctx/Referrable.scala index 9aea44929d..9eecd1733d 100644 --- a/src/col/vct/col/resolve/ctx/Referrable.scala +++ b/src/col/vct/col/resolve/ctx/Referrable.scala @@ -42,6 +42,7 @@ sealed trait Referrable[G] { case RefAxiomaticDataType(decl) => Referrable.originName(decl) case RefFunction(decl) => Referrable.originName(decl) case RefProcedure(decl) => Referrable.originName(decl) + case RefVeSUVMainMethod(_) => "" case RefPredicate(decl) => Referrable.originName(decl) case RefClass(decl) => Referrable.originName(decl) case RefModel(decl) => Referrable.originName(decl) @@ -137,6 +138,7 @@ case object Referrable { case decl: AxiomaticDataType[G] => RefAxiomaticDataType(decl) case decl: Function[G] => RefFunction(decl) case decl: Procedure[G] => RefProcedure(decl) + case decl: VeSUVMainMethod[G] => RefVeSUVMainMethod(decl) case decl: Predicate[G] => RefPredicate(decl) case decl: Class[G] => RefClass(decl) case decl: Model[G] => RefModel(decl) @@ -276,6 +278,7 @@ case class RefSimplificationRule[G](decl: SimplificationRule[G]) extends Referra case class RefAxiomaticDataType[G](decl: AxiomaticDataType[G]) extends Referrable[G] with SpecTypeNameTarget[G] with SpecNameTarget[G] with JavaDerefTarget[G] case class RefFunction[G](decl: Function[G]) extends Referrable[G] with SpecInvocationTarget[G] with ResultTarget[G] case class RefProcedure[G](decl: Procedure[G]) extends Referrable[G] with SpecInvocationTarget[G] with ResultTarget[G] +case class RefVeSUVMainMethod[G](decl: VeSUVMainMethod[G]) extends Referrable[G] with SpecInvocationTarget[G] with ResultTarget[G] case class RefPredicate[G](decl: Predicate[G]) extends Referrable[G] with SpecInvocationTarget[G] case class RefClass[G](decl: Class[G]) extends Referrable[G] with PVLTypeNameTarget[G] with SpecNameTarget[G] with ThisTarget[G] case class RefModel[G](decl: Model[G]) extends Referrable[G] with SpecTypeNameTarget[G] with ThisTarget[G] with PVLConstructorTarget[G] with JavaConstructorTarget[G] diff --git a/src/col/vct/col/typerules/CoercingRewriter.scala b/src/col/vct/col/typerules/CoercingRewriter.scala index e3e15c9150..30ebba915e 100644 --- a/src/col/vct/col/typerules/CoercingRewriter.scala +++ b/src/col/vct/col/typerules/CoercingRewriter.scala @@ -1583,6 +1583,8 @@ abstract class CoercingRewriter[Pre <: Generation]() extends BaseCoercingRewrite new Function[Pre](function.returnType, function.args, function.typeArgs, function.body.map(coerce(_, function.returnType)), function.contract, function.inline, function.threadLocal)(function.blame) case procedure: Procedure[Pre] => procedure + case main_method: VeSUVMainMethod[Pre] => + main_method case predicate: Predicate[Pre] => new Predicate[Pre](predicate.args, predicate.body.map(res), predicate.threadLocal, predicate.inline) case definition: CFunctionDefinition[Pre] => diff --git a/src/parsers/antlr4/LangPVLLexer.g4 b/src/parsers/antlr4/LangPVLLexer.g4 index ba6cc8930b..e3331e00e4 100644 --- a/src/parsers/antlr4/LangPVLLexer.g4 +++ b/src/parsers/antlr4/LangPVLLexer.g4 @@ -55,7 +55,7 @@ BARRIER: 'barrier'; INVARIANT: 'invariant'; CONSTRUCTOR: 'constructor'; RUN: 'run'; -ENTRY: 'vesuv_entry'; +VESUV_ENTRY: 'vesuv_entry'; THREAD: 'thread'; ENDPOINT: 'endpoint'; diff --git a/src/parsers/antlr4/LangPVLParser.g4 b/src/parsers/antlr4/LangPVLParser.g4 index 173b8ca8e3..fb0562aff2 100644 --- a/src/parsers/antlr4/LangPVLParser.g4 +++ b/src/parsers/antlr4/LangPVLParser.g4 @@ -6,7 +6,7 @@ parser grammar LangPVLParser; program : programDecl* EOF EOF ; -programDecl : valGlobalDeclaration | declClass | enumDecl | method | declVeyMontSeqProg | vesuvEntry; +programDecl : valGlobalDeclaration | declClass | enumDecl | method | declVeyMontSeqProg | vesuvEntry ; enumDecl : 'enum' identifier '{' identifierList? ','? '}' ; diff --git a/src/parsers/vct/parsers/transform/PVLToCol.scala b/src/parsers/vct/parsers/transform/PVLToCol.scala index 5ab9ae02f9..cbdadb5a07 100644 --- a/src/parsers/vct/parsers/transform/PVLToCol.scala +++ b/src/parsers/vct/parsers/transform/PVLToCol.scala @@ -31,6 +31,7 @@ case class PVLToCol[G](override val baseOrigin: Origin, case ProgramDecl2(enum) => Seq(convert(enum)) case ProgramDecl3(method) => Seq(convertProcedure(method)) case ProgramDecl4(seqProg) => Seq(convertSeqProg(seqProg)) + case ProgramDecl5(vesuv_entry) => convert(vesuv_entry) } def convert(implicit enum: EnumDeclContext): Enum[G] = enum match { From 6123a98fe5c4a19371401cc0c173d4f957261c98 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 18 Mar 2024 13:59:38 +0100 Subject: [PATCH 72/85] Started implementing support for considering atomic blocks --- .../vct/rewrite/cfg/CFGGenerator.scala | 4 +-- .../vct/rewrite/rasi/AbstractProcess.scala | 35 +++++++++++++++++-- .../vct/rewrite/rasi/AbstractState.scala | 10 ++---- .../vct/rewrite/rasi/RASIGenerator.scala | 5 +-- src/rewrite/vct/rewrite/rasi/Utils.scala | 18 ++++++++++ 5 files changed, 58 insertions(+), 14 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index ec8a7bd3ab..2ff2bfd238 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -4,7 +4,7 @@ import vct.col.ast._ import scala.collection.mutable -case class CFGGenerator[G]() { +case class CFGGenerator[G](hide_composite_statements: Boolean = true) { private val found_labels: mutable.Map[LabelDecl[G], CFGNode[G]] = mutable.HashMap[LabelDecl[G], CFGNode[G]]() private val searched_labels: mutable.Map[LabelDecl[G], mutable.Set[CFGNode[G]]] = mutable.HashMap[LabelDecl[G], mutable.Set[CFGNode[G]]]() private val converted_nodes: mutable.Map[GlobalIndex[G], CFGNode[G]] = mutable.HashMap[GlobalIndex[G], CFGNode[G]]() @@ -39,7 +39,7 @@ case class CFGGenerator[G]() { // Leave all container statements except for invocations out of the CFG with two exceptions: Expressions and the // first scope of a run method must remain so that the run method can later still be identified case _: InvocationStatement[_] => statement_to_cfg(node, context) - case c: ControlContainerStatement[_] if !context.indices.head.isInstanceOf[RunMethodIndex[_]] => + case c: ControlContainerStatement[_] if hide_composite_statements && !context.indices.head.isInstanceOf[RunMethodIndex[_]] => val new_context = context.enter_scope(c) // If the new scope is empty (e.g. expression evaluation with no contained statements), transform it normally, // since this is the only node that can represent this part of the CFG diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index 78a04feede..9b63647708 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -6,7 +6,23 @@ import vct.rewrite.cfg.{CFGEdge, CFGEntry, CFGNode, CFGTerminal} import scala.collection.mutable case class AbstractProcess[G](obj: Expr[G]) { - def get_next(node: CFGEntry[G], state: AbstractState[G]): Set[AbstractState[G]] = node match { + def atomic_step(starting_state: AbstractState[G]): Set[AbstractState[G]] = { + var states: Set[AbstractState[G]] = small_step(starting_state.processes(this), starting_state) + var atomic: Boolean = true + while (atomic) { + val next: Set[(Boolean, Set[AbstractState[G]])] = states.map(s => small_step_if_atomic(s)) + states = next.flatMap(t => t._2) + atomic = next.exists(t => t._1) + } + states + } + + private def small_step_if_atomic(state: AbstractState[G]): (Boolean, Set[AbstractState[G]]) = { + if (!state.processes.contains(this) || !is_atomic(state.processes(this))) (false, Set(state)) + else (true, small_step(state.processes(this), state)) + } + + private def small_step(node: CFGEntry[G], state: AbstractState[G]): Set[AbstractState[G]] = node match { case CFGTerminal() => Set(state.without_process(this)) case CFGNode(n, succ) => n match { // Assign statements change the state of variables directly (if they appear in the valuation) @@ -19,7 +35,7 @@ case class AbstractProcess[G](obj: Expr[G]) { // Statements that induce assumptions about the state, such as assume, inhale, or a method's postcondition, might change the state implicitly case Assume(assn) => viable_edges(succ, state).flatMap(e => state.with_assumption(assn).map(s => take_edge(e, s))) case Inhale(res) => viable_edges(succ, state).flatMap(e => state.with_assumption(res).map(s => take_edge(e, s))) - // Abstract procedures, constructors and methods are defined by their postconditions TODO: Temporarily add parameter to state if it is assigned to a tracked variable + // Abstract procedures, constructors and methods are defined by their postconditions TODO: Handle local variables case InvokeProcedure(ref, args, _, _, _, _) => ref.decl.body match { case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) case None => viable_edges(succ, state).flatMap(e => state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))).map(s => take_edge(e, s))) @@ -32,7 +48,7 @@ case class AbstractProcess[G](obj: Expr[G]) { case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) case None => viable_edges(succ, state).flatMap(e => state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))).map(s => take_edge(e, s))) } - // TODO: Remove temporary parameter from state after method return + // TODO: Handle local variables case Return(result) => viable_edges(succ, state).map(e => take_edge(e, state)) // TODO: What do wait and notify do? case Wait(obj) => viable_edges(succ, state).map(e => take_edge(e, state)) @@ -68,4 +84,17 @@ case class AbstractProcess[G](obj: Expr[G]) { private def take_edge(edge: CFGEdge[G], state: AbstractState[G]): AbstractState[G] = state.with_process_at(this, edge.target) + + private def is_atomic(node: CFGEntry[G]): Boolean = node match { + case CFGTerminal() => true + case CFGNode(n, _) => n match { + case Unlock(_) => false + case InvokeMethod(_, ref, _, _, _, _, _) => Utils.contains_global_invariant(Utils.contract_to_expression(ref.decl.contract.requires)) + case InvokeProcedure(ref, _, _, _, _, _) => Utils.contains_global_invariant(Utils.contract_to_expression(ref.decl.contract.requires)) + case Loop(_, _, _, contract, _) => Utils.contains_global_invariant(Utils.loop_contract_to_expression(contract)) + case PVLLoop(_, _, _, contract, _) => Utils.contains_global_invariant(Utils.loop_contract_to_expression(contract)) + case Assert(res) => Utils.contains_global_invariant(res) + case _ => true // TODO: What about end of a method/loop? + } + } } diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 766cd1749c..c80e0d5103 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -1,7 +1,6 @@ package vct.rewrite.rasi import vct.col.ast._ -import vct.col.util.{AstBuildHelpers, Substitute} import vct.rewrite.cfg.CFGEntry import scala.collection.immutable.HashMap @@ -16,7 +15,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @return The set of possible successor states */ def successors(): Set[AbstractState[G]] = - processes.flatMap(p => p._1.get_next(p._2, this)).toSet + processes.keySet.flatMap(p => p.atomic_step(this)) /** * Updates the state by changing the program counter for a process. @@ -103,7 +102,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * arguments */ def with_postcondition(post: AccountedPredicate[G], args: Map[Variable[G], Expr[G]]): Set[AbstractState[G]] = - with_assumption(unify_expression(AstBuildHelpers.unfoldPredicate(post).reduce((e1, e2) => Star(e1, e2)(e1.o)), args)) + with_assumption(Utils.unify_expression(Utils.contract_to_expression(post), args)) /** * Evaluates an expression and returns an uncertain value, depending on the type of expression and the values it can @@ -282,7 +281,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] } private def get_subroutine_return(post: AccountedPredicate[G], args: Map[Variable[G], Expr[G]], return_type: Type[G]): UncertainValue = - get_return(unify_expression(AstBuildHelpers.unfoldPredicate(post).reduce((e1, e2) => Star(e1, e2)(e1.o)), args), return_type) + get_return(Utils.unify_expression(Utils.contract_to_expression(post), args), return_type) private def get_return(contract: Expr[G], return_type: Type[G]): UncertainValue = { val result_var: ResultVariable[G] = ResultVariable() @@ -292,9 +291,6 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] possible_vals.reduce((v1, v2) => v1.union(v2)) } - private def unify_expression(cond: Expr[G], args: Map[Variable[G], Expr[G]]): Expr[G] = - Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> Old(e, None)(e.o)(e.o) }).dispatch(cond) - private def variable_from_expr(variable: Expr[G]): Option[ConcreteVariable[G]] = valuations.keys.collectFirst{ case c: ConcreteVariable[G] if c.is(variable, this) => c } diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index b9bf44acee..dbb4474c51 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -15,9 +15,10 @@ case class RASIGenerator[G]() { private val current_branches: mutable.ArrayBuffer[AbstractState[G]] = mutable.ArrayBuffer() def execute(entry_point: InstanceMethod[G], vars: Set[ConcreteVariable[G]]): Expr[G] = - generate_rasi(CFGGenerator().generate(entry_point), vars) + generate_rasi(CFGGenerator(false).generate(entry_point), vars) + def test(entry_point: InstanceMethod[G], vars: Set[ConcreteVariable[G]], out_path: Path): Unit = - print_state_space(CFGGenerator().generate(entry_point), vars, out_path) + print_state_space(CFGGenerator(false).generate(entry_point), vars, out_path) private def generate_rasi(node: CFGEntry[G], vars: Set[ConcreteVariable[G]]): Expr[G] = { explore(node, vars) diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala index 02e74bc865..012ef730ae 100644 --- a/src/rewrite/vct/rewrite/rasi/Utils.scala +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -1,6 +1,8 @@ package vct.rewrite.rasi import hre.io.RWFile +import vct.col.ast._ +import vct.col.util.{AstBuildHelpers, Substitute} import java.io.Writer import java.nio.file.Path @@ -23,6 +25,22 @@ case object Utils { res.distinct } + def loop_contract_to_expression[G](contract: LoopContract[G]): Expr[G] = contract match { + case LoopInvariant(inv, _) => inv + } + + def contract_to_expression[G](contract: AccountedPredicate[G]): Expr[G] = + AstBuildHelpers.unfoldPredicate(contract).reduce((e1, e2) => Star(e1, e2)(e1.o)) + + def unify_expression[G](cond: Expr[G], args: Map[Variable[G], Expr[G]]): Expr[G] = + Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> Old(e, None)(e.o)(e.o) }).dispatch(cond) + + def contains_global_invariant[G](node: Node[G]): Boolean = node match { + case PredicateApply(ref, _, _) => ref.decl.o.getPreferredName.get.snake.equals("global_invariant") // TODO: This must be possible to do better + case e: Expr[G] => e.subnodes.exists(n => contains_global_invariant(n)) + case _ => false + } + def print[G](states: Seq[AbstractState[G]], edges: Seq[(AbstractState[G], AbstractState[G])], out: Path): Unit = { val node_names: Map[AbstractState[G], String] = Map.from(states.zipWithIndex.map(t => (t._1, s"n${t._2}"))) RWFile(out.toFile).write(w => print_state_space(node_names, edges, w)) From 4671b54ba32fb3cd485fdb49770c4780e6c9a9ad Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Mon, 18 Mar 2024 15:08:00 +0100 Subject: [PATCH 73/85] Added predicate unfolding in search of global invariant --- src/rewrite/vct/rewrite/rasi/Utils.scala | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala index 012ef730ae..b84932a6b7 100644 --- a/src/rewrite/vct/rewrite/rasi/Utils.scala +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -36,7 +36,8 @@ case object Utils { Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> Old(e, None)(e.o)(e.o) }).dispatch(cond) def contains_global_invariant[G](node: Node[G]): Boolean = node match { - case PredicateApply(ref, _, _) => ref.decl.o.getPreferredName.get.snake.equals("global_invariant") // TODO: This must be possible to do better + case PredicateApply(ref, _, _) => ref.decl.o.getPreferredName.get.snake.equals("global_invariant") || // TODO: This must be possible to do better + contains_global_invariant(ref.decl.body.getOrElse(BooleanValue(value = true)(node.o))) case e: Expr[G] => e.subnodes.exists(n => contains_global_invariant(n)) case _ => false } From b9bca0b8c01415d9b41abe50d7310f5baca67047 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 19 Mar 2024 09:53:44 +0100 Subject: [PATCH 74/85] Added explicit checks for loop and method contracts into the AST --- .../vct/rewrite/cfg/CFGGenerator.scala | 4 +- src/rewrite/vct/rewrite/cfg/Index.scala | 84 ++++++++++++------- src/rewrite/vct/rewrite/cfg/Utils.scala | 8 ++ .../vct/rewrite/rasi/RASIGenerator.scala | 4 +- 4 files changed, 67 insertions(+), 33 deletions(-) diff --git a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala index 2ff2bfd238..ec8a7bd3ab 100644 --- a/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala +++ b/src/rewrite/vct/rewrite/cfg/CFGGenerator.scala @@ -4,7 +4,7 @@ import vct.col.ast._ import scala.collection.mutable -case class CFGGenerator[G](hide_composite_statements: Boolean = true) { +case class CFGGenerator[G]() { private val found_labels: mutable.Map[LabelDecl[G], CFGNode[G]] = mutable.HashMap[LabelDecl[G], CFGNode[G]]() private val searched_labels: mutable.Map[LabelDecl[G], mutable.Set[CFGNode[G]]] = mutable.HashMap[LabelDecl[G], mutable.Set[CFGNode[G]]]() private val converted_nodes: mutable.Map[GlobalIndex[G], CFGNode[G]] = mutable.HashMap[GlobalIndex[G], CFGNode[G]]() @@ -39,7 +39,7 @@ case class CFGGenerator[G](hide_composite_statements: Boolean = true) { // Leave all container statements except for invocations out of the CFG with two exceptions: Expressions and the // first scope of a run method must remain so that the run method can later still be identified case _: InvocationStatement[_] => statement_to_cfg(node, context) - case c: ControlContainerStatement[_] if hide_composite_statements && !context.indices.head.isInstanceOf[RunMethodIndex[_]] => + case c: ControlContainerStatement[_] if !context.indices.head.isInstanceOf[RunMethodIndex[_]] => val new_context = context.enter_scope(c) // If the new scope is empty (e.g. expression evaluation with no contained statements), transform it normally, // since this is the only node that can represent this part of the CFG diff --git a/src/rewrite/vct/rewrite/cfg/Index.scala b/src/rewrite/vct/rewrite/cfg/Index.scala index dd26848a63..d3871828c5 100644 --- a/src/rewrite/vct/rewrite/cfg/Index.scala +++ b/src/rewrite/vct/rewrite/cfg/Index.scala @@ -52,7 +52,7 @@ case class GlobalIndex[G](indices: mutable.Seq[Index[G]]) { case _ => true } // Find the possible next statements - GlobalIndex(stack.tail).make_step() + GlobalIndex(stack).make_step() } def handle_exception(e: Expr[G]): GlobalIndex[G] = { @@ -165,6 +165,8 @@ object Index { def apply[G](node: Node[G], index: Int): Index[G] = from(node, index) } +// TODO: Handle contract assertions for InitialIndex, RunMethodIndex, FramedProof, RangedForIndex, ??? + case class InitialIndex[G](instance_method: InstanceMethod[G]) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = Set((Outgoing(), None)) override def resolve(): Statement[G] = instance_method.body.get @@ -241,15 +243,17 @@ case class PVLBranchIndex[G](pvl_branch: PVLBranch[G], index: Int) extends Index case class PVLLoopIndex[G](pvl_loop: PVLLoop[G], index: Int) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = index match { case 0 => Set((Step(PVLLoopIndex(pvl_loop, 1)), None)) - case 1 => Set((Step(PVLLoopIndex(pvl_loop, 2)), Some(pvl_loop.cond)), (Outgoing(), Some(Utils.negate(pvl_loop.cond)))) - case 2 => Set((Step(PVLLoopIndex(pvl_loop, 3)), None)) - case 3 => Set((Step(PVLLoopIndex(pvl_loop, 1)), None)) + case 1 => Set((Step(PVLLoopIndex(pvl_loop, 2)), None)) + case 2 => Set((Step(PVLLoopIndex(pvl_loop, 3)), Some(pvl_loop.cond)), (Outgoing(), Some(Utils.negate(pvl_loop.cond)))) + case 3 => Set((Step(PVLLoopIndex(pvl_loop, 4)), None)) + case 4 => Set((Step(PVLLoopIndex(pvl_loop, 1)), None)) } override def resolve(): Statement[G] = index match { case 0 => pvl_loop.init - case 1 => Eval(pvl_loop.cond)(pvl_loop.cond.o) - case 2 => pvl_loop.body - case 3 => pvl_loop.update + case 1 => Assert(Utils.loop_contract_to_expression(pvl_loop.contract))(pvl_loop.o)(pvl_loop.o) + case 2 => Eval(pvl_loop.cond)(pvl_loop.cond.o) + case 3 => pvl_loop.body + case 4 => pvl_loop.update } override def equals(obj: scala.Any): Boolean = obj match { case PVLLoopIndex(l, i) => i == index && l.equals(pvl_loop) @@ -314,14 +318,16 @@ case class InvokeProcedureIndex[G](invoke_procedure: InvokeProcedure[G], index: // 2. given // 3. outArgs // 4. yields - // 5. procedure body + // 5. precondition + // 6. procedure body + // 7. postcondition override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = { val args: Seq[Expr[G]] = invoke_procedure.args val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_procedure.givenMap val outArgs: Seq[Expr[G]] = invoke_procedure.outArgs val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_procedure.yields - if (index < args.size + givenMap.size + outArgs.size + yields.size - 1 || - index == args.size + givenMap.size + outArgs.size + yields.size - 1 && invoke_procedure.ref.decl.body.nonEmpty) + if (index < args.size + givenMap.size + outArgs.size + yields.size + 1 || + index == args.size + givenMap.size + outArgs.size + yields.size + 1 && invoke_procedure.ref.decl.body.nonEmpty) Set((Step(InvokeProcedureIndex(invoke_procedure, index + 1)), None)) else Set((Outgoing(), None)) } @@ -342,14 +348,16 @@ case class InvokeProcedureIndex[G](invoke_procedure: InvokeProcedure[G], index: } else if (index < args.size + givenMap.size + outArgs.size + yields.size) { val expr: Expr[G] = yields.apply(index - args.size - givenMap.size - outArgs.size)._1 Eval(expr)(expr.o) - } else invoke_procedure.ref.decl.body.get + } else if (index == args.size + givenMap.size + outArgs.size + yields.size) { + val expr: Expr[G] = Star(Utils.contract_to_expression(invoke_procedure.ref.decl.contract.requires), invoke_procedure.ref.decl.contract.contextEverywhere)(invoke_procedure.o) + Assert(expr)(expr.o)(expr.o) + } else if (index == args.size + givenMap.size + outArgs.size + yields.size + 1 && invoke_procedure.ref.decl.body.nonEmpty) { + invoke_procedure.ref.decl.body.get + } else { + val expr: Expr[G] = Star(Utils.contract_to_expression(invoke_procedure.ref.decl.contract.ensures), invoke_procedure.ref.decl.contract.contextEverywhere)(invoke_procedure.o) + Assert(expr)(expr.o)(expr.o) + } } - override def has_statement(): Boolean = - invoke_procedure.args.nonEmpty || - invoke_procedure.givenMap.nonEmpty || - invoke_procedure.outArgs.nonEmpty || - invoke_procedure.yields.nonEmpty || - invoke_procedure.ref.decl.body.nonEmpty override def equals(obj: scala.Any): Boolean = obj match { case InvokeProcedureIndex(p, i) => i == index && p.equals(invoke_procedure) case _ => false @@ -369,8 +377,8 @@ case class InvokeConstructorIndex[G](invoke_constructor: InvokeConstructor[G], i val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_constructor.givenMap val outArgs: Seq[Expr[G]] = invoke_constructor.outArgs val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_constructor.yields - if (index < args.size + givenMap.size + outArgs.size + yields.size || - index == args.size + givenMap.size + outArgs.size + yields.size && invoke_constructor.ref.decl.body.nonEmpty) + if (index < args.size + givenMap.size + outArgs.size + yields.size + 2 || + index == args.size + givenMap.size + outArgs.size + yields.size + 2 && invoke_constructor.ref.decl.body.nonEmpty) Set((Step(InvokeConstructorIndex(invoke_constructor, index + 1)), None)) else Set((Outgoing(), None)) } @@ -393,7 +401,15 @@ case class InvokeConstructorIndex[G](invoke_constructor: InvokeConstructor[G], i Eval(expr)(expr.o) } else if (index == args.size + givenMap.size + outArgs.size + yields.size) { Eval(invoke_constructor.out)(invoke_constructor.out.o) - } else invoke_constructor.ref.decl.body.get + } else if (index == args.size + givenMap.size + outArgs.size + yields.size + 1) { + val expr: Expr[G] = Star(Utils.contract_to_expression(invoke_constructor.ref.decl.contract.requires), invoke_constructor.ref.decl.contract.contextEverywhere)(invoke_constructor.o) + Assert(expr)(expr.o)(expr.o) + } else if (index == args.size + givenMap.size + outArgs.size + yields.size + 2 && invoke_constructor.ref.decl.body.nonEmpty) { + invoke_constructor.ref.decl.body.get + } else { + val expr: Expr[G] = Star(Utils.contract_to_expression(invoke_constructor.ref.decl.contract.ensures), invoke_constructor.ref.decl.contract.contextEverywhere)(invoke_constructor.o) + Assert(expr)(expr.o)(expr.o) + } } override def equals(obj: scala.Any): Boolean = obj match { case InvokeConstructorIndex(c, i) => i == index && c.equals(invoke_constructor) @@ -414,8 +430,8 @@ case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) exte val givenMap: Seq[(Ref[G, Variable[G]], Expr[G])] = invoke_method.givenMap val outArgs: Seq[Expr[G]] = invoke_method.outArgs val yields: Seq[(Expr[G], Ref[G, Variable[G]])] = invoke_method.yields - if (index < args.size + givenMap.size + outArgs.size + yields.size || - index == args.size + givenMap.size + outArgs.size + yields.size && invoke_method.ref.decl.body.nonEmpty) + if (index < args.size + givenMap.size + outArgs.size + yields.size + 2 || + index == args.size + givenMap.size + outArgs.size + yields.size + 2 && invoke_method.ref.decl.body.nonEmpty) Set((Step(InvokeMethodIndex(invoke_method, index + 1)), None)) else Set((Outgoing(), None)) } @@ -438,7 +454,15 @@ case class InvokeMethodIndex[G](invoke_method: InvokeMethod[G], index: Int) exte } else if (index < args.size + givenMap.size + outArgs.size + yields.size + 1) { val expr: Expr[G] = yields.apply(index - args.size - givenMap.size - outArgs.size - 1)._1 Eval(expr)(expr.o) - } else invoke_method.ref.decl.body.get + } else if (index == args.size + givenMap.size + outArgs.size + yields.size + 1) { + val expr: Expr[G] = Star(Utils.contract_to_expression(invoke_method.ref.decl.contract.requires), invoke_method.ref.decl.contract.contextEverywhere)(invoke_method.o) + Assert(expr)(expr.o)(expr.o) + } else if (index == args.size + givenMap.size + outArgs.size + yields.size + 2 && invoke_method.ref.decl.body.nonEmpty) { + invoke_method.ref.decl.body.get + } else { + val expr: Expr[G] = Star(Utils.contract_to_expression(invoke_method.ref.decl.contract.ensures), invoke_method.ref.decl.contract.contextEverywhere)(invoke_method.o) + Assert(expr)(expr.o)(expr.o) + } } override def equals(obj: scala.Any): Boolean = obj match { case InvokeMethodIndex(m, i) => i == index && m.equals(invoke_method) @@ -512,15 +536,17 @@ case class SwitchIndex[G](switch: Switch[G]) extends Index[G] { case class LoopIndex[G](loop: Loop[G], index: Int) extends Index[G] { override def make_step(): Set[(NextIndex[G], Option[Expr[G]])] = index match { case 0 => Set((Step(LoopIndex(loop, 1)), None)) - case 1 => Set((Step(LoopIndex(loop, 2)), Some(loop.cond)), (Outgoing(), Some(Utils.negate(loop.cond)))) - case 2 => Set((Step(LoopIndex(loop, 3)), None)) - case 3 => Set((Step(LoopIndex(loop, 1)), None)) + case 1 => Set((Step(LoopIndex(loop, 2)), None)) + case 2 => Set((Step(LoopIndex(loop, 3)), Some(loop.cond)), (Outgoing(), Some(Utils.negate(loop.cond)))) + case 3 => Set((Step(LoopIndex(loop, 4)), None)) + case 4 => Set((Step(LoopIndex(loop, 1)), None)) } override def resolve(): Statement[G] = index match { case 0 => loop.init - case 1 => Eval(loop.cond)(loop.cond.o) - case 2 => loop.body - case 3 => loop.update + case 1 => Assert(Utils.loop_contract_to_expression(loop.contract))(loop.o)(loop.o) + case 2 => Eval(loop.cond)(loop.cond.o) + case 3 => loop.body + case 4 => loop.update } override def equals(obj: scala.Any): Boolean = obj match { case LoopIndex(l, i) => i == index && l.equals(loop) diff --git a/src/rewrite/vct/rewrite/cfg/Utils.scala b/src/rewrite/vct/rewrite/cfg/Utils.scala index 4fd3f5f92a..98720e81cc 100644 --- a/src/rewrite/vct/rewrite/cfg/Utils.scala +++ b/src/rewrite/vct/rewrite/cfg/Utils.scala @@ -3,6 +3,7 @@ package vct.rewrite.cfg import vct.col.ast._ import vct.col.origin.Origin import vct.col.ref.{DirectRef, Ref} +import vct.col.util.AstBuildHelpers object Utils { @@ -105,4 +106,11 @@ object Utils { case Some(e2) => Some(And(e1, e2)(e1.o)) // TODO: Is the origin important? } } + + def loop_contract_to_expression[G](contract: LoopContract[G]): Expr[G] = contract match { + case LoopInvariant(inv, _) => inv + } + + def contract_to_expression[G](contract: AccountedPredicate[G]): Expr[G] = + AstBuildHelpers.unfoldPredicate(contract).reduce((e1, e2) => Star(e1, e2)(e1.o)) } diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index dbb4474c51..7e10b58542 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -15,10 +15,10 @@ case class RASIGenerator[G]() { private val current_branches: mutable.ArrayBuffer[AbstractState[G]] = mutable.ArrayBuffer() def execute(entry_point: InstanceMethod[G], vars: Set[ConcreteVariable[G]]): Expr[G] = - generate_rasi(CFGGenerator(false).generate(entry_point), vars) + generate_rasi(CFGGenerator().generate(entry_point), vars) def test(entry_point: InstanceMethod[G], vars: Set[ConcreteVariable[G]], out_path: Path): Unit = - print_state_space(CFGGenerator(false).generate(entry_point), vars, out_path) + print_state_space(CFGGenerator().generate(entry_point), vars, out_path) private def generate_rasi(node: CFGEntry[G], vars: Set[ConcreteVariable[G]]): Expr[G] = { explore(node, vars) From 8640b79d3afa82b117ee28930041cfccfbdf338f Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Tue, 19 Mar 2024 10:22:50 +0100 Subject: [PATCH 75/85] Fixed an error where atomic blocks would never terminate if they could not make progress --- .../vct/rewrite/rasi/AbstractProcess.scala | 57 +++++++++---------- 1 file changed, 26 insertions(+), 31 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index 9b63647708..fb40b76a5c 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -7,8 +7,7 @@ import scala.collection.mutable case class AbstractProcess[G](obj: Expr[G]) { def atomic_step(starting_state: AbstractState[G]): Set[AbstractState[G]] = { - var states: Set[AbstractState[G]] = small_step(starting_state.processes(this), starting_state) - var atomic: Boolean = true + var (atomic, states): (Boolean, Set[AbstractState[G]]) = small_step(starting_state.processes(this), starting_state) while (atomic) { val next: Set[(Boolean, Set[AbstractState[G]])] = states.map(s => small_step_if_atomic(s)) states = next.flatMap(t => t._2) @@ -19,48 +18,48 @@ case class AbstractProcess[G](obj: Expr[G]) { private def small_step_if_atomic(state: AbstractState[G]): (Boolean, Set[AbstractState[G]]) = { if (!state.processes.contains(this) || !is_atomic(state.processes(this))) (false, Set(state)) - else (true, small_step(state.processes(this), state)) + else small_step(state.processes(this), state) } - private def small_step(node: CFGEntry[G], state: AbstractState[G]): Set[AbstractState[G]] = node match { - case CFGTerminal() => Set(state.without_process(this)) + private def small_step(node: CFGEntry[G], state: AbstractState[G]): (Boolean, Set[AbstractState[G]]) = node match { + case CFGTerminal() => (false, Set(state.without_process(this))) case CFGNode(n, succ) => n match { // Assign statements change the state of variables directly (if they appear in the valuation) - case Assign(target, value) => target.t match { + case Assign(target, value) => (true, target.t match { case _: IntType[_] | TBool() => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(target, state.resolve_expression(value)))) case _: TSeq[_] => viable_edges(succ, state).map(e => take_edge(e, state.with_updated_collection(target, value))) // TODO: Consider arrays case _ => viable_edges(succ, state).map(e => take_edge(e, state)) - } - case Havoc(loc) => viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(loc, UncertainValue.uncertain_of(loc.t)))) + }) + case Havoc(loc) => (true, viable_edges(succ, state).map(e => take_edge(e, state.with_valuation(loc, UncertainValue.uncertain_of(loc.t))))) // Statements that induce assumptions about the state, such as assume, inhale, or a method's postcondition, might change the state implicitly - case Assume(assn) => viable_edges(succ, state).flatMap(e => state.with_assumption(assn).map(s => take_edge(e, s))) - case Inhale(res) => viable_edges(succ, state).flatMap(e => state.with_assumption(res).map(s => take_edge(e, s))) + case Assume(assn) => (true, viable_edges(succ, state).flatMap(e => state.with_assumption(assn).map(s => take_edge(e, s)))) + case Inhale(res) => (true, viable_edges(succ, state).flatMap(e => state.with_assumption(res).map(s => take_edge(e, s)))) // Abstract procedures, constructors and methods are defined by their postconditions TODO: Handle local variables - case InvokeProcedure(ref, args, _, _, _, _) => ref.decl.body match { + case InvokeProcedure(ref, args, _, _, _, _) => (true, ref.decl.body match { case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) case None => viable_edges(succ, state).flatMap(e => state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))).map(s => take_edge(e, s))) - } - case InvokeConstructor(ref, _, args, _, _, _, _) => ref.decl.body match { + }) + case InvokeConstructor(ref, _, args, _, _, _, _) => (true, ref.decl.body match { case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) case None => viable_edges(succ, state).flatMap(e => state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))).map(s => take_edge(e, s))) - } - case InvokeMethod(_, ref, args, _, _, _, _) => ref.decl.body match { + }) + case InvokeMethod(_, ref, args, _, _, _, _) => (true, ref.decl.body match { case Some(_) => viable_edges(succ, state).map(e => take_edge(e, state)) case None => viable_edges(succ, state).flatMap(e => state.with_postcondition(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args))).map(s => take_edge(e, s))) - } + }) // TODO: Handle local variables - case Return(result) => viable_edges(succ, state).map(e => take_edge(e, state)) + case Return(result) => (true, viable_edges(succ, state).map(e => take_edge(e, state))) // TODO: What do wait and notify do? - case Wait(obj) => viable_edges(succ, state).map(e => take_edge(e, state)) - case Notify(obj) => viable_edges(succ, state).map(e => take_edge(e, state)) + case Wait(obj) => (true, viable_edges(succ, state).map(e => take_edge(e, state))) + case Notify(obj) => (true, viable_edges(succ, state).map(e => take_edge(e, state))) // Lock and Unlock manipulate the global lock and are potentially blocking TODO: Differentiate between locks! case Lock(_) => state.lock match { - case Some(proc) => if (!proc.equals(this)) Set(state) + case Some(proc) => if (!proc.equals(this)) (false, Set(state)) else throw new IllegalStateException("Trying to lock already acquired lock") - case None => viable_edges(succ, state).map(e => take_edge(e, state).locked_by(this)) + case None => (true, viable_edges(succ, state).map(e => take_edge(e, state).locked_by(this))) } case Unlock(_) => state.lock match { - case Some(proc) => if (proc.equals(this)) viable_edges(succ, state).map(e => take_edge(e, state).unlocked()) + case Some(proc) => if (proc.equals(this)) (true, viable_edges(succ, state).map(e => take_edge(e, state).unlocked())) else throw new IllegalStateException("Trying to unlock lock owned by other process") case None => throw new IllegalStateException("Trying to unlock unlocked lock") } @@ -70,12 +69,12 @@ case class AbstractProcess[G](obj: Expr[G]) { case CFGTerminal() => false case CFGNode(t, _) => t.equals(obj.t.asClass.get.cls.decl.declarations.collect{ case r: RunMethod[G] => r }.head.body.get) }) - edges._2.map(e => take_edge(e, state.with_process_at(AbstractProcess(obj), edges._1.head.target))) // TODO: Can only one thread per class instance be launched? + (true, edges._2.map(e => take_edge(e, state.with_process_at(AbstractProcess(obj), edges._1.head.target)))) // TODO: Can only one thread per class instance be launched? case Join(obj) => - if (state.processes.keys.forall(p => p.obj != obj)) viable_edges(succ, state).map(e => take_edge(e, state)) - else Set(state) + if (state.processes.keys.forall(p => p.obj != obj)) (true, viable_edges(succ, state).map(e => take_edge(e, state))) + else (false, Set(state)) // Everything else does not affect the state, so simply go to the next step - case _ => viable_edges(succ, state).map(e => take_edge(e, state)) + case _ => (true, viable_edges(succ, state).map(e => take_edge(e, state))) } } @@ -89,12 +88,8 @@ case class AbstractProcess[G](obj: Expr[G]) { case CFGTerminal() => true case CFGNode(n, _) => n match { case Unlock(_) => false - case InvokeMethod(_, ref, _, _, _, _, _) => Utils.contains_global_invariant(Utils.contract_to_expression(ref.decl.contract.requires)) - case InvokeProcedure(ref, _, _, _, _, _) => Utils.contains_global_invariant(Utils.contract_to_expression(ref.decl.contract.requires)) - case Loop(_, _, _, contract, _) => Utils.contains_global_invariant(Utils.loop_contract_to_expression(contract)) - case PVLLoop(_, _, _, contract, _) => Utils.contains_global_invariant(Utils.loop_contract_to_expression(contract)) case Assert(res) => Utils.contains_global_invariant(res) - case _ => true // TODO: What about end of a method/loop? + case _ => true } } } From 35035f2609f12ce19b1c629fb70377649e89efd8 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 20 Mar 2024 11:27:06 +0100 Subject: [PATCH 76/85] Documentation; Fixed an error where atomic steps would always step over unlock statements without letting other processes execute --- .../vct/rewrite/rasi/AbstractProcess.scala | 47 ++++++++++++++++++- .../vct/rewrite/rasi/AbstractState.scala | 2 +- .../vct/rewrite/rasi/ConcreteVariable.scala | 5 +- .../vct/rewrite/rasi/UncertainValue.scala | 5 ++ src/rewrite/vct/rewrite/rasi/Utils.scala | 2 +- 5 files changed, 55 insertions(+), 6 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index fb40b76a5c..92419f3f8b 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -6,6 +6,13 @@ import vct.rewrite.cfg.{CFGEdge, CFGEntry, CFGNode, CFGTerminal} import scala.collection.mutable case class AbstractProcess[G](obj: Expr[G]) { + + /** + * Simulates an atomic execution step for this process on the given abstract state. + * + * @param starting_state Abstract starting state + * @return A set of all successor states + */ def atomic_step(starting_state: AbstractState[G]): Set[AbstractState[G]] = { var (atomic, states): (Boolean, Set[AbstractState[G]]) = small_step(starting_state.processes(this), starting_state) while (atomic) { @@ -16,11 +23,26 @@ case class AbstractProcess[G](obj: Expr[G]) { states } + /** + * Tests if another small step can be executed without breaking the current atomic block, and if so, executes the next + * small step. + * + * @param state Current abstract state + * @return A tuple of a boolean indicating whether atomic progress could be made and a set of all successor states + */ private def small_step_if_atomic(state: AbstractState[G]): (Boolean, Set[AbstractState[G]]) = { if (!state.processes.contains(this) || !is_atomic(state.processes(this))) (false, Set(state)) else small_step(state.processes(this), state) } + /** + * Makes a small step in the symbolic execution. Executes the effect of the given CFG node on the given abstract state + * and returns a tuple of a boolean indicating whether progress was made and a set of all possible successor states. + * + * @param node CFG node to simulate + * @param state Current abstract state + * @return A tuple of a boolean indicating whether progress was made and a set of all successor states. + */ private def small_step(node: CFGEntry[G], state: AbstractState[G]): (Boolean, Set[AbstractState[G]]) = node match { case CFGTerminal() => (false, Set(state.without_process(this))) case CFGNode(n, succ) => n match { @@ -58,8 +80,8 @@ case class AbstractProcess[G](obj: Expr[G]) { else throw new IllegalStateException("Trying to lock already acquired lock") case None => (true, viable_edges(succ, state).map(e => take_edge(e, state).locked_by(this))) } - case Unlock(_) => state.lock match { - case Some(proc) => if (proc.equals(this)) (true, viable_edges(succ, state).map(e => take_edge(e, state).unlocked())) + case Unlock(_) => state.lock match { // TODO: Progress was made, but this is still false to allow other processes to execute + case Some(proc) => if (proc.equals(this)) (false, viable_edges(succ, state).map(e => take_edge(e, state).unlocked())) else throw new IllegalStateException("Trying to unlock lock owned by other process") case None => throw new IllegalStateException("Trying to unlock unlocked lock") } @@ -78,12 +100,33 @@ case class AbstractProcess[G](obj: Expr[G]) { } } + /** + * Filters out CFG edges from a given set based on whether they would be viable in the given abstract state. + * + * @param edges A set of CFG edges + * @param state Current abstract state + * @return A set of those entries of edges whose conditions could evaluate to true in the + * given abstract state + */ private def viable_edges(edges: mutable.Set[CFGEdge[G]], state: AbstractState[G]): Set[CFGEdge[G]] = edges.filter(e => e.condition.isEmpty || state.resolve_boolean_expression(e.condition.get).can_be_true).toSet + /** + * Returns the abstract state that results in this process taking a transition in the CFG. + * + * @param edge CFG transition edge to take + * @param state Current abstract state + * @return Copy of state with this process at the target node of the CFG transition + */ private def take_edge(edge: CFGEdge[G], state: AbstractState[G]): AbstractState[G] = state.with_process_at(this, edge.target) + /** + * Checks whether the considered block is still atomic when adding the next node. + * + * @param node CFG node that might be considered next + * @return false if the node checks the global invariant, true otherwise + */ private def is_atomic(node: CFGEntry[G]): Boolean = node match { case CFGTerminal() => true case CFGNode(n, _) => n match { diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index c80e0d5103..a29866f739 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -284,7 +284,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] get_return(Utils.unify_expression(Utils.contract_to_expression(post), args), return_type) private def get_return(contract: Expr[G], return_type: Type[G]): UncertainValue = { - val result_var: ResultVariable[G] = ResultVariable() + val result_var: ResultVariable[G] = ResultVariable(return_type) val result_set: Set[ResolvableVariable[G]] = Set(result_var) val constraints: Set[ConstraintMap[G]] = new ConstraintSolver(this, result_set).resolve_assumption(contract).filter(m => !m.is_impossible) val possible_vals: Set[UncertainValue] = constraints.map(m => m.resolve.getOrElse(result_var, UncertainValue.uncertain_of(return_type))) diff --git a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala index f5e2a9ce3f..902d8bad53 100644 --- a/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala +++ b/src/rewrite/vct/rewrite/rasi/ConcreteVariable.scala @@ -5,19 +5,20 @@ import vct.col.ast._ sealed trait ResolvableVariable[G] { def is(expr: Expr[G], state: AbstractState[G]): Boolean def is_contained_by(expr: Expr[G], state: AbstractState[G]): Boolean + def t: Type[G] } -case class ResultVariable[G]() extends ResolvableVariable[G] { +case class ResultVariable[G](return_type: Type[G]) extends ResolvableVariable[G] { override def is(expr: Expr[G], state: AbstractState[G]): Boolean = expr match { case AmbiguousResult() | Result(_) => true case _ => false } override def is_contained_by(expr: Expr[G], state: AbstractState[G]): Boolean = is(expr, state) + override def t: Type[G] = return_type } sealed trait ConcreteVariable[G] extends ResolvableVariable[G] { def to_expression: Expr[G] - def t: Type[G] def field_equals(expr: Expr[G], field: InstanceField[G]): Boolean = expr match { // TODO: Support other types of expressions? Take object into account? case Deref(_, f) => f.decl.equals(field) diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index 029600ebcc..5034c698d5 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -19,6 +19,10 @@ case object UncertainValue { case _: IntType[_] => UncertainIntegerValue.uncertain() case _: TBool[_] => UncertainBooleanValue.uncertain() } + def empty_of(t: Type[_]): UncertainValue = t match { + case _: IntType[_] => UncertainIntegerValue.empty() + case _: TBool[_] => UncertainBooleanValue.empty() + } } case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) extends UncertainValue { @@ -89,6 +93,7 @@ case class UncertainBooleanValue(can_be_true: Boolean, can_be_false: Boolean) ex def !=(other: UncertainBooleanValue): UncertainBooleanValue = this ^ other } case object UncertainBooleanValue { + def empty(): UncertainBooleanValue = UncertainBooleanValue(can_be_true = false, can_be_false = false) def from(bool: Boolean): UncertainBooleanValue = UncertainBooleanValue(bool, !bool) def uncertain(): UncertainBooleanValue = UncertainBooleanValue(can_be_true = true, can_be_false = true) } diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala index b84932a6b7..7f852ce807 100644 --- a/src/rewrite/vct/rewrite/rasi/Utils.scala +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -33,7 +33,7 @@ case object Utils { AstBuildHelpers.unfoldPredicate(contract).reduce((e1, e2) => Star(e1, e2)(e1.o)) def unify_expression[G](cond: Expr[G], args: Map[Variable[G], Expr[G]]): Expr[G] = - Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> Old(e, None)(e.o)(e.o) }).dispatch(cond) + Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> e }).dispatch(cond) def contains_global_invariant[G](node: Node[G]): Boolean = node match { case PredicateApply(ref, _, _) => ref.decl.o.getPreferredName.get.snake.equals("global_invariant") || // TODO: This must be possible to do better From 99747d1697b45788c2378805e9488fa7af98087c Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 20 Mar 2024 11:27:32 +0100 Subject: [PATCH 77/85] Cleanup --- src/rewrite/vct/rewrite/rasi/RASIGenerator.scala | 1 - 1 file changed, 1 deletion(-) diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index 7e10b58542..a2205fb444 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -50,7 +50,6 @@ case class RASIGenerator[G]() { } private def get_initial_values(vars: Set[ConcreteVariable[G]]): Map[ConcreteVariable[G], UncertainValue] = { - // TODO: Should this be uncertain or should it be defined (e.g. 0/false)? Map.from(vars.map(v => (v, UncertainValue.uncertain_of(v.t)))) } From dbeca4eceb1ddd8c40bc2384fc66c2e3723a048f Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 20 Mar 2024 11:27:59 +0100 Subject: [PATCH 78/85] Tried to change constraint solving implementation --- .../vct/rewrite/rasi/ConstraintMap.scala | 58 ++++++++++++++++--- .../vct/rewrite/rasi/ConstraintSolver.scala | 44 +++++++------- 2 files changed, 72 insertions(+), 30 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala b/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala index 18717656f4..ddc6743aae 100644 --- a/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala +++ b/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala @@ -1,18 +1,60 @@ package vct.rewrite.rasi -case class ConstraintMap[G](constraints: Map[ResolvableVariable[G], Set[UncertainValue]]) { - def ++(other: ConstraintMap[G]): ConstraintMap[G] = { - var map: Map[ResolvableVariable[G], Set[UncertainValue]] = constraints +case class ConstraintMap[G](constraints: Map[ResolvableVariable[G], Constraint]) { + def ||(other: ConstraintMap[G]): ConstraintMap[G] = { + var map: Map[ResolvableVariable[G], Constraint] = Map.empty[ResolvableVariable[G], Constraint] + // If a variable is in both constraint maps, use the disjunction. If it is only in one of them, use an uncertain value instead. + for (e <- constraints) { + map = map + (e._1 -> (other.constraints.getOrElse(e._1, Constraint.from(UncertainValue.uncertain_of(e._1.t))) || e._2)) + } + for (e <- other.constraints) { + if (!map.contains(e._1)) map = map + (e._1 -> Constraint.from(UncertainValue.uncertain_of(e._1.t))) + } + ConstraintMap(map) + } + + def &&(other: ConstraintMap[G]): ConstraintMap[G] = { + var map: Map[ResolvableVariable[G], Constraint] = Map.empty[ResolvableVariable[G], Constraint] + // If a variable is in both constraint maps, use the conjunction. Otherwise, use the constraint of the map it is in. + for (e <- constraints) { + if (other.constraints.contains(e._1)) map = map + (e._1 -> (other.constraints(e._1) && e._2)) + else map = map + e + } for (e <- other.constraints) { - map = map + (e._1 -> (map.getOrElse(e._1, Set()) ++ e._2)) + if (!map.contains(e._1)) map = map + e } ConstraintMap(map) } - def resolve: Map[ResolvableVariable[G], UncertainValue] = constraints.map(e => e._1 -> e._2.reduce((v1, v2) => v1.intersection(v2))) + + def resolve: Map[ResolvableVariable[G], UncertainValue] = constraints.map(e => e._1 -> e._2.resolve) + def is_impossible: Boolean = resolve.exists(t => t._2.is_impossible) } case object ConstraintMap { - def from[G](variable: ResolvableVariable[G], value: UncertainValue): ConstraintMap[G] = ConstraintMap(Map.from(Seq(variable -> Set(value)))) - def from_cons[G](cons: Set[(ResolvableVariable[G], UncertainValue)]): ConstraintMap[G] = ConstraintMap(Map.from(cons.map(t => t._1 -> Set(t._2)))) - def empty[G]: ConstraintMap[G] = ConstraintMap(Map.empty) + def from[G](variable: ResolvableVariable[G], value: UncertainValue): ConstraintMap[G] = + ConstraintMap(Map.from(Seq(variable -> Constraint.from(value)))) + + def from_cons[G](cons: Set[(ResolvableVariable[G], UncertainValue)]): ConstraintMap[G] = + ConstraintMap(Map.from(cons.map(t => t._1 -> Constraint.from(t._2)))) + + def empty[G]: ConstraintMap[G] = + ConstraintMap(Map.empty) + + def impossible[G](vars: Set[_ <: ResolvableVariable[G]]): ConstraintMap[G] = + ConstraintMap(Map.from(vars.map(v => v -> Constraint.from(UncertainValue.empty_of(v.t))))) +} + +case class Constraint(constraints: Set[Set[UncertainValue]]) { + def ||(other: Constraint): Constraint = + Constraint(constraints ++ other.constraints) + + def &&(other: Constraint): Constraint = + Constraint(constraints.flatMap(s1 => other.constraints.map(s2 => s1 ++ s2))) + + def resolve: UncertainValue = + constraints.map(s => s.reduce((v1, v2) => v1.intersection(v2))).reduce((v1, v2) => v1.union(v2)) } +case object Constraint { + def from(value: UncertainValue): Constraint = + Constraint(Set(Set(value))) +} \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala index 6cc4e646e8..310bbe240a 100644 --- a/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala +++ b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala @@ -3,9 +3,9 @@ package vct.rewrite.rasi import vct.col.ast._ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVariable[G]]) { - def resolve_assumption(expr: Expr[G]): Set[ConstraintMap[G]] = resolve(expr) + def resolve_assumption(expr: Expr[G]): ConstraintMap[G] = resolve(expr) - private def resolve(expr: Expr[G], negate: Boolean = false): Set[ConstraintMap[G]] = expr match { + private def resolve(expr: Expr[G], negate: Boolean = false): ConstraintMap[G] = expr match { // Consider boolean/separation logic operators case Not(arg) => this.resolve(arg, !negate) case AmbiguousOr(left, right) => @@ -31,13 +31,13 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari case _ => Set(ConstraintMap.empty[G]) } - private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { + private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): ConstraintMap[G] = { val left_constraints = resolve(left, neg_left) val right_constraints = resolve(right, neg_right) - left_constraints.flatMap(m1 => right_constraints.map(m2 => m1 ++ m2)) + left_constraints.flatMap(m1 => right_constraints.map(m2 => m1 && m2)) } - private def handle_or(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { + private def handle_or(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): ConstraintMap[G] = { val pure_left = is_pure(left) val pure_right = is_pure(right) // If one side is pure and the other has an effect on the state, treat this "or" as an implication. If both sides @@ -48,18 +48,18 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari else this.resolve(left, neg_left) ++ this.resolve(right, neg_right) } - private def handle_implies(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { + private def handle_implies(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): ConstraintMap[G] = { val resolve_left: UncertainBooleanValue = state.resolve_boolean_expression(left) - var res: Set[ConstraintMap[G]] = Set() - if (neg_left && resolve_left.can_be_false || (!neg_left) && resolve_left.can_be_true) res = res ++ resolve(right, neg_right) - if (neg_left && resolve_left.can_be_true || (!neg_left) && resolve_left.can_be_false) res = res ++ Set(ConstraintMap.empty[G]) + var res: ConstraintMap[G] = ConstraintMap.impossible(vars) + if (neg_left && resolve_left.can_be_false || (!neg_left) && resolve_left.can_be_true) res = res || resolve(right, neg_right) + if (neg_left && resolve_left.can_be_true || (!neg_left) && resolve_left.can_be_false) res = res || ConstraintMap.empty[G] res } - private def handle_update(comp: Comparison[G], negate: Boolean): Set[ConstraintMap[G]] = { + private def handle_update(comp: Comparison[G], negate: Boolean): ConstraintMap[G] = { val pure_left = is_pure(comp.left) val pure_right = is_pure(comp.right) - if (pure_left == pure_right) return Set(ConstraintMap.empty[G]) + if (pure_left == pure_right) return ConstraintMap.empty[G] // Now, exactly one side is pure, and the other contains a concrete variable // Resolve the variable and the other side of the equation TODO: Reduce the side with the variable if it contains anything else as well if (vars.exists(v => v.is(if (pure_left) comp.right else comp.left, state))) handle_single_update(comp, pure_left, negate) @@ -75,13 +75,13 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari case _ => true } - private def handle_single_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): Set[ConstraintMap[G]] = { + private def handle_single_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): ConstraintMap[G] = { val variable: ResolvableVariable[G] = if (pure_left) get_var(comp.right).get else get_var(comp.left).get val value: UncertainValue = if (pure_left) state.resolve_expression(comp.left) else state.resolve_expression(comp.right) comp match { - case _: Eq[_] => Set(if (!negate) ConstraintMap.from(variable, value) else ConstraintMap.from(variable, value.complement())) - case _: Neq[_] => Set(if (!negate) ConstraintMap.from(variable, value.complement()) else ConstraintMap.from(variable, value)) + case _: Eq[_] => if (!negate) ConstraintMap.from(variable, value) else ConstraintMap.from(variable, value.complement()) + case _: Neq[_] => if (!negate) ConstraintMap.from(variable, value.complement()) else ConstraintMap.from(variable, value) case AmbiguousGreater(_, _) | Greater(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, negate) case AmbiguousGreaterEq(_, _) | GreaterEq(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, !negate) case AmbiguousLessEq(_, _) | LessEq(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left != negate, !negate) @@ -89,25 +89,25 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari } } - private def limit_variable(v: ResolvableVariable[G], b: UncertainIntegerValue, var_greater: Boolean, can_be_equal: Boolean): Set[ConstraintMap[G]] = { + private def limit_variable(v: ResolvableVariable[G], b: UncertainIntegerValue, var_greater: Boolean, can_be_equal: Boolean): ConstraintMap[G] = { if (var_greater) { - if (can_be_equal) Set(ConstraintMap.from(v, b.above_eq())) - else Set(ConstraintMap.from(v, b.above())) + if (can_be_equal) ConstraintMap.from(v, b.above_eq()) + else ConstraintMap.from(v, b.above()) } else { - if (can_be_equal) Set(ConstraintMap.from(v, b.below_eq())) - else Set(ConstraintMap.from(v, b.below())) + if (can_be_equal) ConstraintMap.from(v, b.below_eq()) + else ConstraintMap.from(v, b.below()) } } - private def handle_collection_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): Set[ConstraintMap[G]] = { + private def handle_collection_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): ConstraintMap[G] = { val variable: Expr[G] = if (pure_left) comp.right else comp.left val value: UncertainSequence = state.resolve_collection_expression(if (pure_left) comp.left else comp.right) val affected: Set[IndexedVariable[G]] = vars.filter(v => v.is_contained_by(variable, state)).collect{ case v: IndexedVariable[G] => v } comp match { - case _: Eq[_] if !negate => Set(ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i)))) - case _: Neq[_] if negate => Set(ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i)))) + case _: Eq[_] if !negate => ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i))) + case _: Neq[_] if negate => ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i))) case _ => throw new IllegalArgumentException(s"The operator ${comp.toInlineString} is not supported for collections") } } From 82854f72f24a2c864584e14e22a773dd554277b9 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 20 Mar 2024 11:30:17 +0100 Subject: [PATCH 79/85] Revert "Tried to change constraint solving implementation" This reverts commit dbeca4eceb1ddd8c40bc2384fc66c2e3723a048f. --- .../vct/rewrite/rasi/ConstraintMap.scala | 58 +++---------------- .../vct/rewrite/rasi/ConstraintSolver.scala | 44 +++++++------- 2 files changed, 30 insertions(+), 72 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala b/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala index ddc6743aae..18717656f4 100644 --- a/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala +++ b/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala @@ -1,60 +1,18 @@ package vct.rewrite.rasi -case class ConstraintMap[G](constraints: Map[ResolvableVariable[G], Constraint]) { - def ||(other: ConstraintMap[G]): ConstraintMap[G] = { - var map: Map[ResolvableVariable[G], Constraint] = Map.empty[ResolvableVariable[G], Constraint] - // If a variable is in both constraint maps, use the disjunction. If it is only in one of them, use an uncertain value instead. - for (e <- constraints) { - map = map + (e._1 -> (other.constraints.getOrElse(e._1, Constraint.from(UncertainValue.uncertain_of(e._1.t))) || e._2)) - } - for (e <- other.constraints) { - if (!map.contains(e._1)) map = map + (e._1 -> Constraint.from(UncertainValue.uncertain_of(e._1.t))) - } - ConstraintMap(map) - } - - def &&(other: ConstraintMap[G]): ConstraintMap[G] = { - var map: Map[ResolvableVariable[G], Constraint] = Map.empty[ResolvableVariable[G], Constraint] - // If a variable is in both constraint maps, use the conjunction. Otherwise, use the constraint of the map it is in. - for (e <- constraints) { - if (other.constraints.contains(e._1)) map = map + (e._1 -> (other.constraints(e._1) && e._2)) - else map = map + e - } +case class ConstraintMap[G](constraints: Map[ResolvableVariable[G], Set[UncertainValue]]) { + def ++(other: ConstraintMap[G]): ConstraintMap[G] = { + var map: Map[ResolvableVariable[G], Set[UncertainValue]] = constraints for (e <- other.constraints) { - if (!map.contains(e._1)) map = map + e + map = map + (e._1 -> (map.getOrElse(e._1, Set()) ++ e._2)) } ConstraintMap(map) } - - def resolve: Map[ResolvableVariable[G], UncertainValue] = constraints.map(e => e._1 -> e._2.resolve) - + def resolve: Map[ResolvableVariable[G], UncertainValue] = constraints.map(e => e._1 -> e._2.reduce((v1, v2) => v1.intersection(v2))) def is_impossible: Boolean = resolve.exists(t => t._2.is_impossible) } case object ConstraintMap { - def from[G](variable: ResolvableVariable[G], value: UncertainValue): ConstraintMap[G] = - ConstraintMap(Map.from(Seq(variable -> Constraint.from(value)))) - - def from_cons[G](cons: Set[(ResolvableVariable[G], UncertainValue)]): ConstraintMap[G] = - ConstraintMap(Map.from(cons.map(t => t._1 -> Constraint.from(t._2)))) - - def empty[G]: ConstraintMap[G] = - ConstraintMap(Map.empty) - - def impossible[G](vars: Set[_ <: ResolvableVariable[G]]): ConstraintMap[G] = - ConstraintMap(Map.from(vars.map(v => v -> Constraint.from(UncertainValue.empty_of(v.t))))) -} - -case class Constraint(constraints: Set[Set[UncertainValue]]) { - def ||(other: Constraint): Constraint = - Constraint(constraints ++ other.constraints) - - def &&(other: Constraint): Constraint = - Constraint(constraints.flatMap(s1 => other.constraints.map(s2 => s1 ++ s2))) - - def resolve: UncertainValue = - constraints.map(s => s.reduce((v1, v2) => v1.intersection(v2))).reduce((v1, v2) => v1.union(v2)) + def from[G](variable: ResolvableVariable[G], value: UncertainValue): ConstraintMap[G] = ConstraintMap(Map.from(Seq(variable -> Set(value)))) + def from_cons[G](cons: Set[(ResolvableVariable[G], UncertainValue)]): ConstraintMap[G] = ConstraintMap(Map.from(cons.map(t => t._1 -> Set(t._2)))) + def empty[G]: ConstraintMap[G] = ConstraintMap(Map.empty) } -case object Constraint { - def from(value: UncertainValue): Constraint = - Constraint(Set(Set(value))) -} \ No newline at end of file diff --git a/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala index 310bbe240a..6cc4e646e8 100644 --- a/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala +++ b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala @@ -3,9 +3,9 @@ package vct.rewrite.rasi import vct.col.ast._ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVariable[G]]) { - def resolve_assumption(expr: Expr[G]): ConstraintMap[G] = resolve(expr) + def resolve_assumption(expr: Expr[G]): Set[ConstraintMap[G]] = resolve(expr) - private def resolve(expr: Expr[G], negate: Boolean = false): ConstraintMap[G] = expr match { + private def resolve(expr: Expr[G], negate: Boolean = false): Set[ConstraintMap[G]] = expr match { // Consider boolean/separation logic operators case Not(arg) => this.resolve(arg, !negate) case AmbiguousOr(left, right) => @@ -31,13 +31,13 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari case _ => Set(ConstraintMap.empty[G]) } - private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): ConstraintMap[G] = { + private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { val left_constraints = resolve(left, neg_left) val right_constraints = resolve(right, neg_right) - left_constraints.flatMap(m1 => right_constraints.map(m2 => m1 && m2)) + left_constraints.flatMap(m1 => right_constraints.map(m2 => m1 ++ m2)) } - private def handle_or(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): ConstraintMap[G] = { + private def handle_or(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { val pure_left = is_pure(left) val pure_right = is_pure(right) // If one side is pure and the other has an effect on the state, treat this "or" as an implication. If both sides @@ -48,18 +48,18 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari else this.resolve(left, neg_left) ++ this.resolve(right, neg_right) } - private def handle_implies(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): ConstraintMap[G] = { + private def handle_implies(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { val resolve_left: UncertainBooleanValue = state.resolve_boolean_expression(left) - var res: ConstraintMap[G] = ConstraintMap.impossible(vars) - if (neg_left && resolve_left.can_be_false || (!neg_left) && resolve_left.can_be_true) res = res || resolve(right, neg_right) - if (neg_left && resolve_left.can_be_true || (!neg_left) && resolve_left.can_be_false) res = res || ConstraintMap.empty[G] + var res: Set[ConstraintMap[G]] = Set() + if (neg_left && resolve_left.can_be_false || (!neg_left) && resolve_left.can_be_true) res = res ++ resolve(right, neg_right) + if (neg_left && resolve_left.can_be_true || (!neg_left) && resolve_left.can_be_false) res = res ++ Set(ConstraintMap.empty[G]) res } - private def handle_update(comp: Comparison[G], negate: Boolean): ConstraintMap[G] = { + private def handle_update(comp: Comparison[G], negate: Boolean): Set[ConstraintMap[G]] = { val pure_left = is_pure(comp.left) val pure_right = is_pure(comp.right) - if (pure_left == pure_right) return ConstraintMap.empty[G] + if (pure_left == pure_right) return Set(ConstraintMap.empty[G]) // Now, exactly one side is pure, and the other contains a concrete variable // Resolve the variable and the other side of the equation TODO: Reduce the side with the variable if it contains anything else as well if (vars.exists(v => v.is(if (pure_left) comp.right else comp.left, state))) handle_single_update(comp, pure_left, negate) @@ -75,13 +75,13 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari case _ => true } - private def handle_single_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): ConstraintMap[G] = { + private def handle_single_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): Set[ConstraintMap[G]] = { val variable: ResolvableVariable[G] = if (pure_left) get_var(comp.right).get else get_var(comp.left).get val value: UncertainValue = if (pure_left) state.resolve_expression(comp.left) else state.resolve_expression(comp.right) comp match { - case _: Eq[_] => if (!negate) ConstraintMap.from(variable, value) else ConstraintMap.from(variable, value.complement()) - case _: Neq[_] => if (!negate) ConstraintMap.from(variable, value.complement()) else ConstraintMap.from(variable, value) + case _: Eq[_] => Set(if (!negate) ConstraintMap.from(variable, value) else ConstraintMap.from(variable, value.complement())) + case _: Neq[_] => Set(if (!negate) ConstraintMap.from(variable, value.complement()) else ConstraintMap.from(variable, value)) case AmbiguousGreater(_, _) | Greater(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, negate) case AmbiguousGreaterEq(_, _) | GreaterEq(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left == negate, !negate) case AmbiguousLessEq(_, _) | LessEq(_, _) => limit_variable(variable, value.asInstanceOf[UncertainIntegerValue], pure_left != negate, !negate) @@ -89,25 +89,25 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari } } - private def limit_variable(v: ResolvableVariable[G], b: UncertainIntegerValue, var_greater: Boolean, can_be_equal: Boolean): ConstraintMap[G] = { + private def limit_variable(v: ResolvableVariable[G], b: UncertainIntegerValue, var_greater: Boolean, can_be_equal: Boolean): Set[ConstraintMap[G]] = { if (var_greater) { - if (can_be_equal) ConstraintMap.from(v, b.above_eq()) - else ConstraintMap.from(v, b.above()) + if (can_be_equal) Set(ConstraintMap.from(v, b.above_eq())) + else Set(ConstraintMap.from(v, b.above())) } else { - if (can_be_equal) ConstraintMap.from(v, b.below_eq()) - else ConstraintMap.from(v, b.below()) + if (can_be_equal) Set(ConstraintMap.from(v, b.below_eq())) + else Set(ConstraintMap.from(v, b.below())) } } - private def handle_collection_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): ConstraintMap[G] = { + private def handle_collection_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): Set[ConstraintMap[G]] = { val variable: Expr[G] = if (pure_left) comp.right else comp.left val value: UncertainSequence = state.resolve_collection_expression(if (pure_left) comp.left else comp.right) val affected: Set[IndexedVariable[G]] = vars.filter(v => v.is_contained_by(variable, state)).collect{ case v: IndexedVariable[G] => v } comp match { - case _: Eq[_] if !negate => ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i))) - case _: Neq[_] if negate => ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i))) + case _: Eq[_] if !negate => Set(ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i)))) + case _: Neq[_] if negate => Set(ConstraintMap.from_cons(affected.map(v => v -> value.get(v.i)))) case _ => throw new IllegalArgumentException(s"The operator ${comp.toInlineString} is not supported for collections") } } From f10d417760dbf9ab18a57c6de2299f409953d874 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 20 Mar 2024 14:57:11 +0100 Subject: [PATCH 80/85] Taking into account expression viability for constraint resolution; added more expressions to resolve --- .../vct/rewrite/rasi/AbstractState.scala | 33 +++++++++++---- .../vct/rewrite/rasi/ConstraintMap.scala | 16 ++++++-- .../vct/rewrite/rasi/ConstraintSolver.scala | 41 ++++++++++++++----- .../vct/rewrite/rasi/UncertainValue.scala | 41 ++++++++++++++++++- src/rewrite/vct/rewrite/rasi/Utils.scala | 11 ----- 5 files changed, 107 insertions(+), 35 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index a29866f739..bee66912e5 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -151,12 +151,11 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case BitShl(_, _) => UncertainIntegerValue.uncertain() case BitShr(_, _) => UncertainIntegerValue.uncertain() case BitUShr(_, _) => UncertainIntegerValue.uncertain() - case Select(cond, ift, iff) => { + case Select(cond, ift, iff) => var value: UncertainIntegerValue = UncertainIntegerValue.empty() if (resolve_boolean_expression(cond).can_be_true) value = value.union(resolve_integer_expression(ift)).asInstanceOf[UncertainIntegerValue] if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_integer_expression(iff)).asInstanceOf[UncertainIntegerValue] value - } case Old(exp, at) => at match { case Some(_) => throw new IllegalArgumentException(s"Cannot resolve labelled old expression ${expr.toInlineString}") case None => resolve_integer_expression(exp) @@ -175,6 +174,8 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainIntegerValue] case InstanceFunctionInvocation(_, ref, args, _, _, _) => get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainIntegerValue] + case Result(_) => UncertainIntegerValue.uncertain() + case AmbiguousResult() => UncertainIntegerValue.uncertain() } /** @@ -187,11 +188,12 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case BooleanValue(value) => UncertainBooleanValue.from(value) case Not(arg) => !resolve_boolean_expression(arg) case AmbiguousOr(left, right) => resolve_boolean_expression(left) || resolve_boolean_expression(right) + case Star(left, right) => resolve_boolean_expression(left) && resolve_boolean_expression(right) case And(left, right) => resolve_boolean_expression(left) && resolve_boolean_expression(right) case Or(left, right) => resolve_boolean_expression(left) || resolve_boolean_expression(right) case Implies(left, right) => (!resolve_boolean_expression(left)) || resolve_boolean_expression(right) - case Eq(left, right) => resolve_expression(left) == resolve_expression(right) - case Neq(left, right) => resolve_expression(left) != resolve_expression(right) + case Eq(left, right) => handle_equality(left, right) + case Neq(left, right) => !handle_equality(left, right) case AmbiguousGreater(left, right) => resolve_integer_expression(left) > resolve_integer_expression(right) case AmbiguousLess(left, right) => resolve_integer_expression(left) < resolve_integer_expression(right) case AmbiguousGreaterEq(left, right) => resolve_integer_expression(left) >= resolve_integer_expression(right) @@ -200,13 +202,11 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case Less(left, right) => resolve_integer_expression(left) < resolve_integer_expression(right) case GreaterEq(left, right) => resolve_integer_expression(left) >= resolve_integer_expression(right) case LessEq(left, right) => resolve_integer_expression(left) <= resolve_integer_expression(right) - case c: SetComparison[G] => UncertainBooleanValue.uncertain() // TODO: Implement? - case Select(cond, ift, iff) => { + case Select(cond, ift, iff) => var value: UncertainBooleanValue = UncertainBooleanValue(can_be_true = false, can_be_false = false) if (resolve_boolean_expression(cond).can_be_true) value = value.union(resolve_boolean_expression(ift)).asInstanceOf[UncertainBooleanValue] if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_boolean_expression(iff)).asInstanceOf[UncertainBooleanValue] value - } case Old(exp, at) => at match { case Some(_) => throw new IllegalArgumentException(s"Cannot resolve labelled old expression ${expr.toInlineString}") case None => resolve_boolean_expression(exp) @@ -223,6 +223,18 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainBooleanValue] case InstanceFunctionInvocation(_, ref, args, _, _, _) => get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainBooleanValue] + case Held(_) => UncertainBooleanValue.from(lock.nonEmpty) // TODO: This means that ANY process holds the lock! + case Scale(_, res) => resolve_boolean_expression(res) // TODO: Do anything with permission fraction? + case PredicateApply(ref, args, _) => // TODO: Do anything with permission fraction? + if (ref.decl.body.nonEmpty) UncertainBooleanValue.from(true) //resolve_boolean_expression(Utils.unify_expression(ref.decl.body.get, Map.from(ref.decl.args.zip(args)))) + else ??? // TODO: Track resource ownership? + case InstancePredicateApply(_, ref, args, _) => // TODO: Do anything with permission fraction? + if (ref.decl.body.nonEmpty) UncertainBooleanValue.from(true) //resolve_boolean_expression(Utils.unify_expression(ref.decl.body.get, Map.from(ref.decl.args.zip(args)))) + else ??? // TODO: Track resource ownership? + case Perm(_, _) => UncertainBooleanValue.from(true) // TODO: Do anything with permissions/resources? + case _: Binder[_] => UncertainBooleanValue.from(true) // TODO: How to handle quantifiers?! + case Result(_) => UncertainBooleanValue.uncertain() + case AmbiguousResult() => UncertainBooleanValue.uncertain() } /** @@ -275,7 +287,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case TSeq(element) => element case _ => throw new IllegalArgumentException(s"Unsupported collection type ${deref.ref.decl.t.toInlineString}") } - UncertainSequence(len.getOrElse(UncertainIntegerValue.above(affected.map(v => v.i).max)), + UncertainSequence(len.getOrElse(UncertainIntegerValue.above(if (affected.isEmpty) 0 else affected.map(v => v.i).max)), affected.map(v => UncertainIntegerValue.single(v.i) -> valuations(v)).toSeq, t) } @@ -291,6 +303,11 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] possible_vals.reduce((v1, v2) => v1.union(v2)) } + private def handle_equality(left: Expr[G], right: Expr[G]): UncertainBooleanValue = left.t match { + case _: IntType[_] | TBool() => resolve_expression(left) == resolve_expression(right) + case _ => resolve_collection_expression(left) == resolve_collection_expression(right) + } + private def variable_from_expr(variable: Expr[G]): Option[ConcreteVariable[G]] = valuations.keys.collectFirst{ case c: ConcreteVariable[G] if c.is(variable, this) => c } diff --git a/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala b/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala index 18717656f4..6506840a84 100644 --- a/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala +++ b/src/rewrite/vct/rewrite/rasi/ConstraintMap.scala @@ -8,11 +8,21 @@ case class ConstraintMap[G](constraints: Map[ResolvableVariable[G], Set[Uncertai } ConstraintMap(map) } - def resolve: Map[ResolvableVariable[G], UncertainValue] = constraints.map(e => e._1 -> e._2.reduce((v1, v2) => v1.intersection(v2))) + + def resolve: Map[ResolvableVariable[G], UncertainValue] = + constraints.map(e => e._1 -> e._2.reduce((v1, v2) => v1.intersection(v2))) + def is_impossible: Boolean = resolve.exists(t => t._2.is_impossible) } case object ConstraintMap { - def from[G](variable: ResolvableVariable[G], value: UncertainValue): ConstraintMap[G] = ConstraintMap(Map.from(Seq(variable -> Set(value)))) - def from_cons[G](cons: Set[(ResolvableVariable[G], UncertainValue)]): ConstraintMap[G] = ConstraintMap(Map.from(cons.map(t => t._1 -> Set(t._2)))) + def from[G](variable: ResolvableVariable[G], value: UncertainValue): ConstraintMap[G] = + ConstraintMap(Map.from(Seq(variable -> Set(value)))) + + def from_cons[G](cons: Set[(ResolvableVariable[G], UncertainValue)]): ConstraintMap[G] = + ConstraintMap(Map.from(cons.map(t => t._1 -> Set(t._2)))) + def empty[G]: ConstraintMap[G] = ConstraintMap(Map.empty) + + def impossible[G](vars: Set[_ <: ResolvableVariable[G]]): ConstraintMap[G] = + ConstraintMap(Map.from(vars.map(v => v -> Set(UncertainValue.empty_of(v.t))))) } diff --git a/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala index 6cc4e646e8..e77c8cf487 100644 --- a/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala +++ b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala @@ -32,20 +32,39 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari } private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { - val left_constraints = resolve(left, neg_left) - val right_constraints = resolve(right, neg_right) - left_constraints.flatMap(m1 => right_constraints.map(m2 => m1 ++ m2)) + val left_is_possible: UncertainBooleanValue = state.resolve_boolean_expression(left) + val right_is_possible: UncertainBooleanValue = state.resolve_boolean_expression(right) + val possible_left: Boolean = (neg_left && left_is_possible.can_be_false) || (!neg_left && left_is_possible.can_be_true) + val possible_right: Boolean = (neg_right && right_is_possible.can_be_false) || (!neg_right && right_is_possible.can_be_true) + + if (!possible_left || !possible_right) Set(ConstraintMap.impossible(vars)) + else { + val left_constraints: Set[ConstraintMap[G]] = resolve(left, neg_left) + val right_constraints: Set[ConstraintMap[G]] = resolve(right, neg_right) + left_constraints.flatMap(m1 => right_constraints.map(m2 => m1 ++ m2)) + } } private def handle_or(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { - val pure_left = is_pure(left) - val pure_right = is_pure(right) - // If one side is pure and the other has an effect on the state, treat this "or" as an implication. If both sides - // update the state, treat it as a split in the state space instead - if (pure_left && pure_right) Set(ConstraintMap.empty[G]) - else if (pure_left) handle_implies(left, right, !neg_left, neg_right) - else if (pure_right) handle_implies(right, left, !neg_right, neg_left) - else this.resolve(left, neg_left) ++ this.resolve(right, neg_right) + val left_is_possible: UncertainBooleanValue = state.resolve_boolean_expression(left) + val right_is_possible: UncertainBooleanValue = state.resolve_boolean_expression(right) + val possible_left: Boolean = (neg_left && left_is_possible.can_be_false) || (!neg_left && left_is_possible.can_be_true) + val possible_right: Boolean = (neg_right && right_is_possible.can_be_false) || (!neg_right && right_is_possible.can_be_true) + + if (!possible_left && !possible_right) Set(ConstraintMap.impossible(vars)) + else if (!possible_left) resolve(right, neg_right) + else if (!possible_right) resolve(left, neg_left) + else { + // Both branches are viable + val pure_left: Boolean = is_pure(left) + val pure_right: Boolean = is_pure(right) + // If one side is pure and the other has an effect on the state, treat this "or" as an implication. If both sides + // update the state, treat it as a split in the state space instead + if (pure_left && pure_right) Set(ConstraintMap.empty[G]) + else if (pure_left) handle_implies(left, right, !neg_left, neg_right) + else if (pure_right) handle_implies(right, left, !neg_right, neg_left) + else resolve(left, neg_left) ++ resolve(right, neg_right) + } } private def handle_implies(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index 5034c698d5..345e58aaee 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -2,6 +2,7 @@ package vct.rewrite.rasi import vct.col.ast.{BooleanValue, Expr, IntType, Not, TBool, Type} +// TODO: Factor out into uncertain single value and uncertain collection value trait UncertainValue { def can_be_equal(other: UncertainValue): Boolean def can_be_unequal(other: UncertainValue): Boolean @@ -105,7 +106,7 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { } override def can_be_unequal(other: UncertainValue): Boolean = other match { - case UncertainIntegerValue(v) => value.intersection(v).complement().non_empty() + case UncertainIntegerValue(v) => value.intersection(v.complement()).non_empty() case _ => true } @@ -179,9 +180,34 @@ case object UncertainIntegerValue { } case class UncertainSequence(len: UncertainIntegerValue, values: Seq[(UncertainIntegerValue, UncertainValue)], t: Type[_]) { + def ==(other: UncertainSequence): UncertainBooleanValue = { + if (len.intersection(other.len).is_impossible) return UncertainBooleanValue.from(false) + if (values.exists(t1 => t1._1.try_to_resolve().nonEmpty && other.values.exists(t2 => !t2._1.intersection(t1._1).is_impossible && t2._2.can_be_unequal(t1._2)))) + return UncertainBooleanValue.from(false) + val length: Option[Int] = len.try_to_resolve() + val other_length: Option[Int] = other.len.try_to_resolve() + if (length.nonEmpty && other_length.nonEmpty && length.get == other_length.get) { + for (i <- Range(0, length.get)) { + val values_index: Int = values.indexWhere(t => t._1.try_to_resolve().getOrElse(-1) == i) + val other_values_index: Int = other.values.indexWhere(t => t._1.try_to_resolve().getOrElse(-1) == i) + if (values_index >= 0 && other_values_index >= 0) { + val value = values(values_index)._2 + val other_value = other.values(other_values_index)._2 + if (!value.can_be_unequal(other_value)) return UncertainBooleanValue.uncertain() + } + else return UncertainBooleanValue.uncertain() + } + } + else return UncertainBooleanValue.uncertain() + // Only if the lengths are exactly the same and perfectly certain, + // and every value in between is defined in both sequences and perfectly certain and equal, + // only then are two uncertain sequences definitely equal + UncertainBooleanValue.from(true) + } + def union(other: UncertainSequence): UncertainSequence = { if (t != other.t) throw new IllegalArgumentException("Unioning sequences of different types") - UncertainSequence(len.union(other.len).asInstanceOf[UncertainIntegerValue], Utils.combine_values(values, other.values), t) + UncertainSequence(len.union(other.len).asInstanceOf[UncertainIntegerValue], combine_values(values, other.values), t) } def concat(other: UncertainSequence): UncertainSequence = @@ -213,6 +239,17 @@ case class UncertainSequence(len: UncertainIntegerValue, values: Seq[(UncertainI if (i >= 0) values(i)._2 else UncertainValue.uncertain_of(t) } + + private def combine_values(v1: Seq[(UncertainIntegerValue, UncertainValue)], v2: Seq[(UncertainIntegerValue, UncertainValue)]): Seq[(UncertainIntegerValue, UncertainValue)] = { + var res: Seq[(UncertainIntegerValue, UncertainValue)] = Seq() + for (v <- v1) { + for (comp <- v2) { + if (v._1.is_subset_of(comp._1) && v._2.is_subset_of(comp._2)) res :+= comp + else if (comp._1.is_subset_of(v._1) && comp._2.is_subset_of(v._2)) res :+= v + } + } + res.distinct + } } case object UncertainSequence { def empty(t: Type[_]): UncertainSequence = UncertainSequence(UncertainIntegerValue.empty(), Seq(), t) diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala index 7f852ce807..3f1e33c9b3 100644 --- a/src/rewrite/vct/rewrite/rasi/Utils.scala +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -14,17 +14,6 @@ case object Utils { def prod_min(a1: Int, a2: Int, b1: Int, b2: Int): Int = Seq(a1 * b1, a1 * b2, a2 * b1, a2 * b2).min - def combine_values(v1: Seq[(UncertainIntegerValue, UncertainValue)], v2: Seq[(UncertainIntegerValue, UncertainValue)]): Seq[(UncertainIntegerValue, UncertainValue)] = { - var res: Seq[(UncertainIntegerValue, UncertainValue)] = Seq() - for (v <- v1) { - for (comp <- v2) { - if (v._1.is_subset_of(comp._1) && v._2.is_subset_of(comp._2)) res :+= comp - else if (comp._1.is_subset_of(v._1) && comp._2.is_subset_of(v._2)) res :+= v - } - } - res.distinct - } - def loop_contract_to_expression[G](contract: LoopContract[G]): Expr[G] = contract match { case LoopInvariant(inv, _) => inv } From 960ed510989e41f6d2eb6f5aaae9985355f52f9a Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Wed, 20 Mar 2024 16:27:10 +0100 Subject: [PATCH 81/85] Fixed an error where global invariant assertions were not properly detected --- src/rewrite/vct/rewrite/rasi/AbstractProcess.scala | 3 +-- src/rewrite/vct/rewrite/rasi/AbstractState.scala | 1 + src/rewrite/vct/rewrite/rasi/Utils.scala | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala index 92419f3f8b..0f9de6acc0 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractProcess.scala @@ -130,8 +130,7 @@ case class AbstractProcess[G](obj: Expr[G]) { private def is_atomic(node: CFGEntry[G]): Boolean = node match { case CFGTerminal() => true case CFGNode(n, _) => n match { - case Unlock(_) => false - case Assert(res) => Utils.contains_global_invariant(res) + case Assert(res) => !Utils.contains_global_invariant(res) case _ => true } } diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index bee66912e5..493bc0b467 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -15,6 +15,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @return The set of possible successor states */ def successors(): Set[AbstractState[G]] = + // TODO: Only consider the state that holds the global lock if it is locked? processes.keySet.flatMap(p => p.atomic_step(this)) /** diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala index 3f1e33c9b3..430c0a5874 100644 --- a/src/rewrite/vct/rewrite/rasi/Utils.scala +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -25,8 +25,9 @@ case object Utils { Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> e }).dispatch(cond) def contains_global_invariant[G](node: Node[G]): Boolean = node match { - case PredicateApply(ref, _, _) => ref.decl.o.getPreferredName.get.snake.equals("global_invariant") || // TODO: This must be possible to do better - contains_global_invariant(ref.decl.body.getOrElse(BooleanValue(value = true)(node.o))) + case InstancePredicateApply(_, ref, _, _) => + if (ref.decl.o.getPreferredName.get.snake.equals("global_invariant")) true + else contains_global_invariant(ref.decl.body.getOrElse(BooleanValue(value = true)(node.o))) case e: Expr[G] => e.subnodes.exists(n => contains_global_invariant(n)) case _ => false } From eba3309fe3c6f8bae04d6df7e99a68fe3178113e Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 21 Mar 2024 11:17:34 +0100 Subject: [PATCH 82/85] Changed encoding of find_minimum_advance in VeSUV to avoid sequence parameter --- .../systemctocol/engine/MainTransformer.java | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java index eb516ecf8c..4935824398 100644 --- a/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java +++ b/src/parsers/vct/parsers/transform/systemctocol/engine/MainTransformer.java @@ -749,21 +749,21 @@ private InstanceMethod create_reset_events_no_delta() { */ private InstanceMethod create_find_minimum_advance() { // Create parameters - Variable vals = new Variable<>(col_system.T_SEQ_INT, OriGen.create("vals")); - List> params = List.from(CollectionConverters.asScala(java.util.List.of(vals))); + java.util.List> parameters = new java.util.ArrayList<>(); + java.util.List> locals = new java.util.ArrayList<>(); + for (int i = 0; i < col_system.get_total_nr_events(); i++) { + Variable param_i = new Variable<>(col_system.T_INT, OriGen.create("val" + i)); + Ref> param_i_ref = new DirectRef<>(param_i, ClassTag$.MODULE$.apply(Variable.class)); + parameters.add(param_i); + locals.add(new Local<>(param_i_ref, OriGen.create())); + } + List> params = List.from(CollectionConverters.asScala(parameters)); - // Create appropriate references to the parameter and the method result - Ref> vals_ref = new DirectRef<>(vals, ClassTag$.MODULE$.apply(Variable.class)); - Local vals_local = new Local<>(vals_ref, OriGen.create()); + // Create appropriate references to the method and its result Ref> this_method = new LazyRef<>(() -> find_minimum_advance, Option.empty(), ClassTag$.MODULE$.apply(ContractApplicable.class)); Result result = new Result<>(this_method, OriGen.create()); - // Create precondition - Size vals_size = new Size<>(vals_local, OriGen.create()); - IntegerValue size_value = new IntegerValue<>(BigInt.apply(col_system.get_total_nr_events()), OriGen.create()); - Eq requires = new Eq<>(vals_size, size_value, OriGen.create()); - // Start collecting postconditions java.util.List> ensures = new java.util.ArrayList<>(); @@ -772,11 +772,8 @@ private InstanceMethod create_find_minimum_advance() { java.util.List> none_exists = new java.util.ArrayList<>(); java.util.List> result_equals_timeout_left = new java.util.ArrayList<>(); java.util.List> result_equals_timeout_right = new java.util.ArrayList<>(); - for (int i = 0; i < col_system.get_total_nr_events(); i++) { - // Prepare sequence access - SeqSubscript vals_i = new SeqSubscript<>(vals_local, new IntegerValue<>(BigInt.apply(i), OriGen.create()), - new GeneratedBlame<>(), OriGen.create()); + for (Local vals_i : locals) { // Create condition that result is a lower bound LessEq bound = new LessEq<>(result, vals_i, OriGen.create()); Less except = new Less<>(vals_i, col_system.MINUS_ONE, OriGen.create()); @@ -807,7 +804,7 @@ private InstanceMethod create_find_minimum_advance() { ensures.add(new And<>(first_implies, second_implies, OriGen.create())); // Generate contract and method and return - ApplicableContract contract = col_system.to_applicable_contract(requires, col_system.fold_star(ensures)); + ApplicableContract contract = col_system.to_applicable_contract(col_system.TRUE, col_system.fold_star(ensures)); return new InstanceMethod<>(col_system.T_INT, params, col_system.NO_VARS, col_system.NO_VARS, Option.empty(), contract, false, true, new GeneratedBlame<>(), OriGen.create("find_minimum_advance")); } @@ -1090,8 +1087,12 @@ private Statement create_scheduler_loop_if_body() { // Assign to min_advance Ref> fma_ref = new DirectRef<>(find_minimum_advance, ClassTag$.MODULE$.apply(InstanceMethod.class)); - java.util.List> params = java.util.List.of(event_state_deref); - MethodInvocation fma_invoke = new MethodInvocation<>(col_system.THIS, fma_ref, List.from(CollectionConverters.asScala(params)), + java.util.List> event_entries = new java.util.ArrayList<>(); + for (int i = 0; i < col_system.get_total_nr_events(); i++) { + IntegerValue i_val = new IntegerValue<>(BigInt.apply(i), OriGen.create()); + event_entries.add(new SeqSubscript<>(event_state_deref, i_val, new GeneratedBlame<>(), OriGen.create())); + } + MethodInvocation fma_invoke = new MethodInvocation<>(col_system.THIS, fma_ref, List.from(CollectionConverters.asScala(event_entries)), col_system.NO_EXPRS, col_system.NO_TYPES, col_system.NO_GIVEN, col_system.NO_YIELDS, new GeneratedBlame<>(), OriGen.create()); Assign assign_ma = new Assign<>(ma_local, fma_invoke, new GeneratedBlame<>(), OriGen.create()); @@ -1103,9 +1104,7 @@ private Statement create_scheduler_loop_if_body() { // Advance delays in event_state java.util.List> literal_values = new java.util.ArrayList<>(); - for (int i = 0; i < col_system.get_total_nr_events(); i++) { - IntegerValue i_val = new IntegerValue<>(BigInt.apply(i), OriGen.create()); - SeqSubscript ev_i = new SeqSubscript<>(event_state_deref, i_val, new GeneratedBlame<>(), OriGen.create()); + for (Expr ev_i : event_entries) { Less condition = new Less<>(ev_i, col_system.MINUS_ONE, OriGen.create()); Minus minus = new Minus<>(ev_i, ma_local, OriGen.create()); literal_values.add(new Select<>(condition, col_system.MINUS_THREE, minus, OriGen.create())); From c6db2aea1908c1c84e7e7b33e5a158c991ad1bf5 Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 21 Mar 2024 11:18:28 +0100 Subject: [PATCH 83/85] Added mechanism by which variables in function contracts are uncertain if they are not old --- .../vct/rewrite/rasi/AbstractState.scala | 171 ++++++++++-------- .../vct/rewrite/rasi/ConstraintSolver.scala | 15 +- src/rewrite/vct/rewrite/rasi/Utils.scala | 2 +- 3 files changed, 107 insertions(+), 81 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 493bc0b467..614bd220e3 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -89,8 +89,9 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @param assumption Boolean expression expressing a state update * @return A set of abstract states that are a copy of this one, updated according to the given assumption */ - def with_assumption(assumption: Expr[G]): Set[AbstractState[G]] = { - val constraints: Set[ConstraintMap[G]] = new ConstraintSolver(this, valuations.keySet).resolve_assumption(assumption).filter(m => !m.is_impossible) + def with_assumption(assumption: Expr[G], is_contract: Boolean = false): Set[AbstractState[G]] = { + // TODO: An assumption adds constraints to the existing state! A postcondition first havocs all variables for which it has permission. + val constraints: Set[ConstraintMap[G]] = new ConstraintSolver(this, valuations.keySet, is_contract).resolve_assumption(assumption).filter(m => !m.is_impossible) constraints.map(m => m.resolve).map(m => AbstractState(valuations.map(e => e._1 -> m.getOrElse(e._1, e._2)), processes, lock, seq_lengths)) } @@ -103,7 +104,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * arguments */ def with_postcondition(post: AccountedPredicate[G], args: Map[Variable[G], Expr[G]]): Set[AbstractState[G]] = - with_assumption(Utils.unify_expression(Utils.contract_to_expression(post), args)) + with_assumption(Utils.unify_expression(Utils.contract_to_expression(post), args), is_contract = true) /** * Evaluates an expression and returns an uncertain value, depending on the type of expression and the values it can @@ -112,9 +113,9 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @param expr COL expression to resolve * @return An uncertain value of the correct type */ - def resolve_expression(expr: Expr[G]): UncertainValue = expr.t match { - case _: IntType[_] => resolve_integer_expression(expr) - case _: TBool[_] => resolve_boolean_expression(expr) + def resolve_expression(expr: Expr[G], is_old: Boolean = false, is_contract: Boolean = false): UncertainValue = expr.t match { + case _: IntType[_] => resolve_integer_expression(expr, is_old, is_contract) + case _: TBool[_] => resolve_boolean_expression(expr, is_old, is_contract) case _ => throw new IllegalArgumentException(s"Type ${expr.t.toInlineString} is not supported") } @@ -124,21 +125,21 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @param expr integer-type COL expression * @return An uncertain value that represents all possible valuations of the given expression */ - def resolve_integer_expression(expr: Expr[G]): UncertainIntegerValue = expr match { + def resolve_integer_expression(expr: Expr[G], is_old: Boolean = false, is_contract: Boolean = false): UncertainIntegerValue = expr match { case CIntegerValue(value) => UncertainIntegerValue.single(value.intValue) case IntegerValue(value) => UncertainIntegerValue.single(value.intValue) case SizeOf(tname) => UncertainIntegerValue.above(1) // TODO: Can we use more information about sizeof? - case UMinus(arg) => -resolve_integer_expression(arg) - case AmbiguousMult(left, right) => resolve_integer_expression(left) * resolve_integer_expression(right) - case AmbiguousPlus(left, right) => resolve_integer_expression(left) + resolve_integer_expression(right) - case AmbiguousMinus(left, right) => resolve_integer_expression(left) - resolve_integer_expression(right) - case Exp(left, right) => resolve_integer_expression(left).pow(resolve_integer_expression(right)) - case Plus(left, right) => resolve_integer_expression(left) + resolve_integer_expression(right) - case Minus(left, right) => resolve_integer_expression(left) - resolve_integer_expression(right) - case Mult(left, right) => resolve_integer_expression(left) * resolve_integer_expression(right) - case FloorDiv(left, right) => resolve_integer_expression(left) / resolve_integer_expression(right) - case Mod(left, right) => resolve_integer_expression(left) % resolve_integer_expression(right) - // Bit operations destroy any knowledge of integer state TODO: Support bit operations + case UMinus(arg) => -resolve_integer_expression(arg, is_old, is_contract) + case AmbiguousMult(left, right) => resolve_integer_expression(left, is_old, is_contract) * resolve_integer_expression(right, is_old, is_contract) + case AmbiguousPlus(left, right) => resolve_integer_expression(left, is_old, is_contract) + resolve_integer_expression(right, is_old, is_contract) + case AmbiguousMinus(left, right) => resolve_integer_expression(left, is_old, is_contract) - resolve_integer_expression(right, is_old, is_contract) + case Exp(left, right) => resolve_integer_expression(left, is_old, is_contract).pow(resolve_integer_expression(right, is_old, is_contract)) + case Plus(left, right) => resolve_integer_expression(left, is_old, is_contract) + resolve_integer_expression(right, is_old, is_contract) + case Minus(left, right) => resolve_integer_expression(left, is_old, is_contract) - resolve_integer_expression(right, is_old, is_contract) + case Mult(left, right) => resolve_integer_expression(left, is_old, is_contract) * resolve_integer_expression(right, is_old, is_contract) + case FloorDiv(left, right) => resolve_integer_expression(left, is_old, is_contract) / resolve_integer_expression(right, is_old, is_contract) + case Mod(left, right) => resolve_integer_expression(left, is_old, is_contract) % resolve_integer_expression(right, is_old, is_contract) + // Bit operations destroy any knowledge of integer state TODO: Support bit operations? case BitNot(_) => UncertainIntegerValue.uncertain() case AmbiguousComputationalOr(_, _) => UncertainIntegerValue.uncertain() case AmbiguousComputationalXor(_, _) => UncertainIntegerValue.uncertain() @@ -154,17 +155,24 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case BitUShr(_, _) => UncertainIntegerValue.uncertain() case Select(cond, ift, iff) => var value: UncertainIntegerValue = UncertainIntegerValue.empty() - if (resolve_boolean_expression(cond).can_be_true) value = value.union(resolve_integer_expression(ift)).asInstanceOf[UncertainIntegerValue] - if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_integer_expression(iff)).asInstanceOf[UncertainIntegerValue] + if (resolve_boolean_expression(cond, is_old, is_contract).can_be_true) { + value = value.union(resolve_integer_expression(ift, is_old, is_contract)).asInstanceOf[UncertainIntegerValue] + } + if (resolve_boolean_expression(cond, is_old, is_contract).can_be_false) { + value = value.union(resolve_integer_expression(iff, is_old, is_contract)).asInstanceOf[UncertainIntegerValue] + } value case Old(exp, at) => at match { case Some(_) => throw new IllegalArgumentException(s"Cannot resolve labelled old expression ${expr.toInlineString}") - case None => resolve_integer_expression(exp) - } - case Local(_) | DerefHeapVariable(_) | Deref(_, _) | DerefPointer(_) | AmbiguousSubscript(_, _) | SeqSubscript(_, _) | ArraySubscript(_, _) | PointerSubscript(_, _) => variable_from_expr(expr) match { - case Some(v) => valuations(v).asInstanceOf[UncertainIntegerValue] - case None => UncertainIntegerValue.uncertain() + case None => resolve_integer_expression(exp, is_old = true, is_contract) } + case Local(_) | DerefHeapVariable(_) | Deref(_, _) | DerefPointer(_) | AmbiguousSubscript(_, _) | SeqSubscript(_, _) | ArraySubscript(_, _) | PointerSubscript(_, _) => + variable_from_expr(expr) match { + case Some(v) => + if (is_contract && !is_old) UncertainIntegerValue.uncertain() + else valuations(v).asInstanceOf[UncertainIntegerValue] + case None => UncertainIntegerValue.uncertain() + } case Length(arr) => UncertainIntegerValue.above(0) // TODO: Implement array semantics case Size(obj) => resolve_collection_expression(obj).len case ProcedureInvocation(ref, args, _, _, _, _) => @@ -185,37 +193,44 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @param expr boolean-type COL expression * @return An uncertain boolean value that represents all possible values that the given expression can take on */ - def resolve_boolean_expression(expr: Expr[G]): UncertainBooleanValue = expr match { + def resolve_boolean_expression(expr: Expr[G], is_old: Boolean = false, is_contract: Boolean = false): UncertainBooleanValue = expr match { case BooleanValue(value) => UncertainBooleanValue.from(value) case Not(arg) => !resolve_boolean_expression(arg) - case AmbiguousOr(left, right) => resolve_boolean_expression(left) || resolve_boolean_expression(right) - case Star(left, right) => resolve_boolean_expression(left) && resolve_boolean_expression(right) - case And(left, right) => resolve_boolean_expression(left) && resolve_boolean_expression(right) - case Or(left, right) => resolve_boolean_expression(left) || resolve_boolean_expression(right) - case Implies(left, right) => (!resolve_boolean_expression(left)) || resolve_boolean_expression(right) - case Eq(left, right) => handle_equality(left, right) - case Neq(left, right) => !handle_equality(left, right) - case AmbiguousGreater(left, right) => resolve_integer_expression(left) > resolve_integer_expression(right) - case AmbiguousLess(left, right) => resolve_integer_expression(left) < resolve_integer_expression(right) - case AmbiguousGreaterEq(left, right) => resolve_integer_expression(left) >= resolve_integer_expression(right) - case AmbiguousLessEq(left, right) => resolve_integer_expression(left) <= resolve_integer_expression(right) - case Greater(left, right) => resolve_integer_expression(left) > resolve_integer_expression(right) - case Less(left, right) => resolve_integer_expression(left) < resolve_integer_expression(right) - case GreaterEq(left, right) => resolve_integer_expression(left) >= resolve_integer_expression(right) - case LessEq(left, right) => resolve_integer_expression(left) <= resolve_integer_expression(right) + case AmbiguousOr(left, right) => resolve_boolean_expression(left, is_old, is_contract) || resolve_boolean_expression(right, is_old, is_contract) + case Star(left, right) => resolve_boolean_expression(left, is_old, is_contract) && resolve_boolean_expression(right, is_old, is_contract) + case And(left, right) => resolve_boolean_expression(left, is_old, is_contract) && resolve_boolean_expression(right, is_old, is_contract) + case Or(left, right) => resolve_boolean_expression(left, is_old, is_contract) || resolve_boolean_expression(right, is_old, is_contract) + case Implies(left, right) => (!resolve_boolean_expression(left, is_old, is_contract)) || resolve_boolean_expression(right, is_old, is_contract) + case Eq(left, right) => handle_equality(left, right, is_old, is_contract) + case Neq(left, right) => !handle_equality(left, right, is_old, is_contract) + case AmbiguousGreater(left, right) => resolve_integer_expression(left, is_old, is_contract) > resolve_integer_expression(right, is_old, is_contract) + case AmbiguousLess(left, right) => resolve_integer_expression(left, is_old, is_contract) < resolve_integer_expression(right, is_old, is_contract) + case AmbiguousGreaterEq(left, right) => resolve_integer_expression(left, is_old, is_contract) >= resolve_integer_expression(right, is_old, is_contract) + case AmbiguousLessEq(left, right) => resolve_integer_expression(left, is_old, is_contract) <= resolve_integer_expression(right, is_old, is_contract) + case Greater(left, right) => resolve_integer_expression(left, is_old, is_contract) > resolve_integer_expression(right, is_old, is_contract) + case Less(left, right) => resolve_integer_expression(left, is_old, is_contract) < resolve_integer_expression(right, is_old, is_contract) + case GreaterEq(left, right) => resolve_integer_expression(left, is_old, is_contract) >= resolve_integer_expression(right, is_old, is_contract) + case LessEq(left, right) => resolve_integer_expression(left, is_old, is_contract) <= resolve_integer_expression(right, is_old, is_contract) case Select(cond, ift, iff) => - var value: UncertainBooleanValue = UncertainBooleanValue(can_be_true = false, can_be_false = false) - if (resolve_boolean_expression(cond).can_be_true) value = value.union(resolve_boolean_expression(ift)).asInstanceOf[UncertainBooleanValue] - if (resolve_boolean_expression(cond).can_be_false) value = value.union(resolve_boolean_expression(iff)).asInstanceOf[UncertainBooleanValue] + var value: UncertainBooleanValue = UncertainBooleanValue.empty() + if (resolve_boolean_expression(cond, is_old, is_contract).can_be_true) { + value = value.union(resolve_boolean_expression(ift, is_old, is_contract)).asInstanceOf[UncertainBooleanValue] + } + if (resolve_boolean_expression(cond, is_old, is_contract).can_be_false) { + value = value.union(resolve_boolean_expression(iff, is_old, is_contract)).asInstanceOf[UncertainBooleanValue] + } value case Old(exp, at) => at match { case Some(_) => throw new IllegalArgumentException(s"Cannot resolve labelled old expression ${expr.toInlineString}") - case None => resolve_boolean_expression(exp) - } - case Local(_) | DerefHeapVariable(_) | Deref(_, _) | DerefPointer(_) | AmbiguousSubscript(_, _) | SeqSubscript(_, _) | ArraySubscript(_, _) | PointerSubscript(_, _) => variable_from_expr(expr) match { - case Some(v) => valuations(v).asInstanceOf[UncertainBooleanValue] - case None => UncertainBooleanValue.uncertain() + case None => resolve_boolean_expression(exp, is_old = true, is_contract) } + case Local(_) | DerefHeapVariable(_) | Deref(_, _) | DerefPointer(_) | AmbiguousSubscript(_, _) | SeqSubscript(_, _) | ArraySubscript(_, _) | PointerSubscript(_, _) => + variable_from_expr(expr) match { + case Some(v) => + if (is_contract && !is_old) UncertainBooleanValue.uncertain() + else valuations(v).asInstanceOf[UncertainBooleanValue] + case None => UncertainBooleanValue.uncertain() + } case ProcedureInvocation(ref, args, _, _, _, _) => get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainBooleanValue] case MethodInvocation(_, ref, args, _, _, _, _) => @@ -225,7 +240,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case InstanceFunctionInvocation(_, ref, args, _, _, _) => get_subroutine_return(ref.decl.contract.ensures, Map.from(ref.decl.args.zip(args)), ref.decl.returnType).asInstanceOf[UncertainBooleanValue] case Held(_) => UncertainBooleanValue.from(lock.nonEmpty) // TODO: This means that ANY process holds the lock! - case Scale(_, res) => resolve_boolean_expression(res) // TODO: Do anything with permission fraction? + case Scale(_, res) => resolve_boolean_expression(res, is_old, is_contract) // TODO: Do anything with permission fraction? case PredicateApply(ref, args, _) => // TODO: Do anything with permission fraction? if (ref.decl.body.nonEmpty) UncertainBooleanValue.from(true) //resolve_boolean_expression(Utils.unify_expression(ref.decl.body.get, Map.from(ref.decl.args.zip(args)))) else ??? // TODO: Track resource ownership? @@ -245,42 +260,51 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @return An uncertain collection value that represents all possible values that the given expression can take on, * possibly of uncertain length and with uncertain values at uncertain indices */ - def resolve_collection_expression(expr: Expr[G]): UncertainSequence = expr match { + def resolve_collection_expression(expr: Expr[G], is_old: Boolean = false, is_contract: Boolean = false): UncertainSequence = expr match { // Literals case LiteralSeq(element, values) => UncertainSequence(UncertainIntegerValue.single(values.size), - values.zipWithIndex.map(t => UncertainIntegerValue.single(t._2) -> resolve_expression(t._1)), + values.zipWithIndex.map(t => UncertainIntegerValue.single(t._2) -> resolve_expression(t._1, is_old, is_contract)), element) case UntypedLiteralSeq(values) => UncertainSequence(UncertainIntegerValue.single(values.size), - values.zipWithIndex.map(t => UncertainIntegerValue.single(t._2) -> resolve_expression(t._1)), + values.zipWithIndex.map(t => UncertainIntegerValue.single(t._2) -> resolve_expression(t._1, is_old, is_contract)), values.head.t) // Variables - case d: Deref[_] => collection_from_variable(d) + case d: Deref[_] => collection_from_variable(d, is_old, is_contract) // TODO: Implement array semantics case Values(arr, from, to) => ??? case NewArray(element, dims, moreDims, initialize) => ??? // Sequence operations - case Cons(x, xs) => resolve_collection_expression(xs).prepend(resolve_expression(x)) - case Concat(xs, ys) => resolve_collection_expression(xs).concat(resolve_collection_expression(ys)) - case Drop(xs, count) => resolve_collection_expression(xs).drop(resolve_integer_expression(count)) - case Take(xs, count) => resolve_collection_expression(xs).take(resolve_integer_expression(count)) - case SeqUpdate(xs, i, x) => resolve_collection_expression(xs).updated(resolve_integer_expression(i), resolve_expression(x)) - case RemoveAt(xs, i) => resolve_collection_expression(xs).remove(resolve_integer_expression(i)) - case Slice(xs, from, to) => resolve_collection_expression(xs).slice(resolve_integer_expression(from), resolve_integer_expression(to)) + case Cons(x, xs) => + resolve_collection_expression(xs, is_old, is_contract).prepend(resolve_expression(x, is_old, is_contract)) + case Concat(xs, ys) => + resolve_collection_expression(xs, is_old, is_contract).concat(resolve_collection_expression(ys, is_old, is_contract)) + case Drop(xs, count) => + resolve_collection_expression(xs, is_old, is_contract).drop(resolve_integer_expression(count, is_old, is_contract)) + case Take(xs, count) => + resolve_collection_expression(xs, is_old, is_contract).take(resolve_integer_expression(count, is_old, is_contract)) + case SeqUpdate(xs, i, x) => + resolve_collection_expression(xs, is_old, is_contract).updated(resolve_integer_expression(i, is_old, is_contract), + resolve_expression(x, is_old, is_contract)) + case RemoveAt(xs, i) => + resolve_collection_expression(xs, is_old, is_contract).remove(resolve_integer_expression(i, is_old, is_contract)) + case Slice(xs, from, to) => + resolve_collection_expression(xs, is_old, is_contract).slice(resolve_integer_expression(from, is_old, is_contract), + resolve_integer_expression(to, is_old, is_contract)) // Other expressions that can evaluate to a collection case Select(cond, ift, iff) => - val condition: UncertainBooleanValue = resolve_boolean_expression(cond) - val ift_seq: UncertainSequence = resolve_collection_expression(ift) - val iff_seq: UncertainSequence = resolve_collection_expression(iff) + val condition: UncertainBooleanValue = resolve_boolean_expression(cond, is_old, is_contract) + val ift_seq: UncertainSequence = resolve_collection_expression(ift, is_old, is_contract) + val iff_seq: UncertainSequence = resolve_collection_expression(iff, is_old, is_contract) if (condition.can_be_true && condition.can_be_false) ift_seq.union(iff_seq) else if (condition.can_be_true) ift_seq else if (condition.can_be_false) iff_seq else UncertainSequence.empty(ift_seq.t) - case Old(expr, _) => resolve_collection_expression(expr) + case Old(expr, _) => resolve_collection_expression(expr, is_old = true, is_contract) } - private def collection_from_variable(deref: Deref[G]): UncertainSequence = { + private def collection_from_variable(deref: Deref[G], is_old: Boolean, is_contract: Boolean): UncertainSequence = { val affected: Set[IndexedVariable[G]] = valuations.keySet.filter(v => v.is_contained_by(deref, this)).collect { case v: IndexedVariable[_] => v } val len: Option[UncertainIntegerValue] = seq_lengths.get(deref.ref.decl) val t: Type[G] = deref.ref.decl.t match { @@ -288,9 +312,10 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case TSeq(element) => element case _ => throw new IllegalArgumentException(s"Unsupported collection type ${deref.ref.decl.t.toInlineString}") } - UncertainSequence(len.getOrElse(UncertainIntegerValue.above(if (affected.isEmpty) 0 else affected.map(v => v.i).max)), - affected.map(v => UncertainIntegerValue.single(v.i) -> valuations(v)).toSeq, - t) + if (is_contract && !is_old) UncertainSequence.empty(t) + else UncertainSequence(len.getOrElse(UncertainIntegerValue.above(if (affected.isEmpty) 0 else affected.map(v => v.i).max)), + affected.map(v => UncertainIntegerValue.single(v.i) -> valuations(v)).toSeq, + t) } private def get_subroutine_return(post: AccountedPredicate[G], args: Map[Variable[G], Expr[G]], return_type: Type[G]): UncertainValue = @@ -299,14 +324,14 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] private def get_return(contract: Expr[G], return_type: Type[G]): UncertainValue = { val result_var: ResultVariable[G] = ResultVariable(return_type) val result_set: Set[ResolvableVariable[G]] = Set(result_var) - val constraints: Set[ConstraintMap[G]] = new ConstraintSolver(this, result_set).resolve_assumption(contract).filter(m => !m.is_impossible) + val constraints: Set[ConstraintMap[G]] = new ConstraintSolver(this, result_set, true).resolve_assumption(contract).filter(m => !m.is_impossible) val possible_vals: Set[UncertainValue] = constraints.map(m => m.resolve.getOrElse(result_var, UncertainValue.uncertain_of(return_type))) possible_vals.reduce((v1, v2) => v1.union(v2)) } - private def handle_equality(left: Expr[G], right: Expr[G]): UncertainBooleanValue = left.t match { - case _: IntType[_] | TBool() => resolve_expression(left) == resolve_expression(right) - case _ => resolve_collection_expression(left) == resolve_collection_expression(right) + private def handle_equality(left: Expr[G], right: Expr[G], is_old: Boolean, is_contract: Boolean): UncertainBooleanValue = left.t match { + case _: IntType[_] | TBool() => resolve_expression(left, is_old, is_contract) == resolve_expression(right, is_old, is_contract) + case _ => resolve_collection_expression(left, is_old, is_contract) == resolve_collection_expression(right, is_old, is_contract) } private def variable_from_expr(variable: Expr[G]): Option[ConcreteVariable[G]] = diff --git a/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala index e77c8cf487..bfae50db13 100644 --- a/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala +++ b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala @@ -2,7 +2,7 @@ package vct.rewrite.rasi import vct.col.ast._ -class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVariable[G]]) { +class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVariable[G]], is_contract: Boolean) { def resolve_assumption(expr: Expr[G]): Set[ConstraintMap[G]] = resolve(expr) private def resolve(expr: Expr[G], negate: Boolean = false): Set[ConstraintMap[G]] = expr match { @@ -32,8 +32,8 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari } private def handle_and(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { - val left_is_possible: UncertainBooleanValue = state.resolve_boolean_expression(left) - val right_is_possible: UncertainBooleanValue = state.resolve_boolean_expression(right) + val left_is_possible: UncertainBooleanValue = state.resolve_boolean_expression(left, is_old = false, is_contract) + val right_is_possible: UncertainBooleanValue = state.resolve_boolean_expression(right, is_old = false, is_contract) val possible_left: Boolean = (neg_left && left_is_possible.can_be_false) || (!neg_left && left_is_possible.can_be_true) val possible_right: Boolean = (neg_right && right_is_possible.can_be_false) || (!neg_right && right_is_possible.can_be_true) @@ -46,8 +46,8 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari } private def handle_or(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { - val left_is_possible: UncertainBooleanValue = state.resolve_boolean_expression(left) - val right_is_possible: UncertainBooleanValue = state.resolve_boolean_expression(right) + val left_is_possible: UncertainBooleanValue = state.resolve_boolean_expression(left, is_old = false, is_contract) + val right_is_possible: UncertainBooleanValue = state.resolve_boolean_expression(right, is_old = false, is_contract) val possible_left: Boolean = (neg_left && left_is_possible.can_be_false) || (!neg_left && left_is_possible.can_be_true) val possible_right: Boolean = (neg_right && right_is_possible.can_be_false) || (!neg_right && right_is_possible.can_be_true) @@ -68,7 +68,7 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari } private def handle_implies(left: Expr[G], right: Expr[G], neg_left: Boolean = false, neg_right: Boolean = false): Set[ConstraintMap[G]] = { - val resolve_left: UncertainBooleanValue = state.resolve_boolean_expression(left) + val resolve_left: UncertainBooleanValue = state.resolve_boolean_expression(left, is_old = false, is_contract) var res: Set[ConstraintMap[G]] = Set() if (neg_left && resolve_left.can_be_false || (!neg_left) && resolve_left.can_be_true) res = res ++ resolve(right, neg_right) if (neg_left && resolve_left.can_be_true || (!neg_left) && resolve_left.can_be_false) res = res ++ Set(ConstraintMap.empty[G]) @@ -96,7 +96,8 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari private def handle_single_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): Set[ConstraintMap[G]] = { val variable: ResolvableVariable[G] = if (pure_left) get_var(comp.right).get else get_var(comp.left).get - val value: UncertainValue = if (pure_left) state.resolve_expression(comp.left) else state.resolve_expression(comp.right) + val value: UncertainValue = if (pure_left) state.resolve_expression(comp.left, is_old = false, is_contract) + else state.resolve_expression(comp.right, is_old = false, is_contract) comp match { case _: Eq[_] => Set(if (!negate) ConstraintMap.from(variable, value) else ConstraintMap.from(variable, value.complement())) diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala index 430c0a5874..a0f45cab5f 100644 --- a/src/rewrite/vct/rewrite/rasi/Utils.scala +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -22,7 +22,7 @@ case object Utils { AstBuildHelpers.unfoldPredicate(contract).reduce((e1, e2) => Star(e1, e2)(e1.o)) def unify_expression[G](cond: Expr[G], args: Map[Variable[G], Expr[G]]): Expr[G] = - Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> e }).dispatch(cond) + Substitute(args.map[Expr[G], Expr[G]]{ case (v, e) => Local[G](v.ref)(v.o) -> Old(e, None)(e.o)(e.o) }).dispatch(cond) def contains_global_invariant[G](node: Node[G]): Boolean = node match { case InstancePredicateApply(_, ref, _, _) => From 14cd71023910cfa4f1d72c534cdd2e5770a5c8aa Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Thu, 21 Mar 2024 15:10:57 +0100 Subject: [PATCH 84/85] Fixed errors in uncertain value comparisons; removed initial state from RASI --- src/rewrite/vct/rewrite/rasi/AbstractState.scala | 2 +- src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala | 4 ++-- src/rewrite/vct/rewrite/rasi/RASIGenerator.scala | 6 +++++- src/rewrite/vct/rewrite/rasi/UncertainValue.scala | 5 +++-- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 614bd220e3..990927ac68 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -312,7 +312,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] case TSeq(element) => element case _ => throw new IllegalArgumentException(s"Unsupported collection type ${deref.ref.decl.t.toInlineString}") } - if (is_contract && !is_old) UncertainSequence.empty(t) + if (is_contract && !is_old) UncertainSequence.uncertain(t) else UncertainSequence(len.getOrElse(UncertainIntegerValue.above(if (affected.isEmpty) 0 else affected.map(v => v.i).max)), affected.map(v => UncertainIntegerValue.single(v.i) -> valuations(v)).toSeq, t) diff --git a/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala index bfae50db13..427e03d79c 100644 --- a/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala +++ b/src/rewrite/vct/rewrite/rasi/ConstraintSolver.scala @@ -96,8 +96,8 @@ class ConstraintSolver[G](state: AbstractState[G], vars: Set[_ <: ResolvableVari private def handle_single_update(comp: Comparison[G], pure_left: Boolean, negate: Boolean): Set[ConstraintMap[G]] = { val variable: ResolvableVariable[G] = if (pure_left) get_var(comp.right).get else get_var(comp.left).get - val value: UncertainValue = if (pure_left) state.resolve_expression(comp.left, is_old = false, is_contract) - else state.resolve_expression(comp.right, is_old = false, is_contract) + val value: UncertainValue = if (pure_left) state.resolve_expression(comp.left) + else state.resolve_expression(comp.right) comp match { case _: Eq[_] => Set(if (!negate) ConstraintMap.from(variable, value) else ConstraintMap.from(variable, value.complement())) diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index a2205fb444..7a63b7ae02 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -47,6 +47,10 @@ case class RASIGenerator[G]() { found_edges.addAll(successors.map(s => (curr, s))) successors.foreach(s => if (!found_states.contains(s)) {found_states += s; current_branches += s}) } + + // The initial state converts to simply "true", so it would make the RASI trivial + found_states.filterInPlace(s => s.valuations != initial_state.valuations) + found_edges.filterInPlace(t => t._1.valuations != initial_state.valuations && t._2.valuations != initial_state.valuations) } private def get_initial_values(vars: Set[ConcreteVariable[G]]): Map[ConcreteVariable[G], UncertainValue] = { @@ -56,6 +60,6 @@ case class RASIGenerator[G]() { private def reduce_redundant_states(): (Seq[AbstractState[G]], Seq[(AbstractState[G], AbstractState[G])]) = { val state_groups: Map[Map[ConcreteVariable[G], UncertainValue], ArrayBuffer[AbstractState[G]]] = Map.from(found_states.groupBy(s => s.valuations)) val edge_groups: Seq[(AbstractState[G], AbstractState[G])] = Seq.from(found_edges.map(t => (state_groups(t._1.valuations).head, state_groups(t._2.valuations).head)).distinct) - (state_groups.values.toSeq.map(v => v.head), edge_groups) + (state_groups.values.toSeq.map(v => v.head), edge_groups.filter(t => t._1 != t._2)) } } diff --git a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala index 345e58aaee..8596bc2842 100644 --- a/src/rewrite/vct/rewrite/rasi/UncertainValue.scala +++ b/src/rewrite/vct/rewrite/rasi/UncertainValue.scala @@ -157,10 +157,10 @@ case class UncertainIntegerValue(value: Interval) extends UncertainValue { UncertainBooleanValue(can_be_unequal(other), can_be_equal(other)) def >=(other: UncertainIntegerValue): UncertainBooleanValue = UncertainBooleanValue(value.below_max().intersection(other.value).non_empty(), - value.above_min().intersection(other.value).size() >= Finite(1)) + value.above_min().intersection(other.value.below_max()).size() >= Finite(2)) def <=(other: UncertainIntegerValue): UncertainBooleanValue = UncertainBooleanValue(value.above_min().intersection(other.value).non_empty(), - value.below_max().intersection(other.value).size() >= Finite(1)) + value.below_max().intersection(other.value.above_min()).size() >= Finite(2)) def >(other: UncertainIntegerValue): UncertainBooleanValue = !(this <= other) def <(other: UncertainIntegerValue): UncertainBooleanValue = !(this >= other) @@ -252,5 +252,6 @@ case class UncertainSequence(len: UncertainIntegerValue, values: Seq[(UncertainI } } case object UncertainSequence { + def uncertain(t: Type[_]): UncertainSequence = UncertainSequence(UncertainIntegerValue.above(0), Seq(), t) def empty(t: Type[_]): UncertainSequence = UncertainSequence(UncertainIntegerValue.empty(), Seq(), t) } \ No newline at end of file From 6933480d1a3da9e4c15c0acd377633f5900fc63b Mon Sep 17 00:00:00 2001 From: Philip Tasche Date: Fri, 22 Mar 2024 10:40:36 +0100 Subject: [PATCH 85/85] Fixed errors with sequence operations and object comparison; added debug information --- src/rewrite/vct/rewrite/rasi/AbstractState.scala | 9 ++++++--- src/rewrite/vct/rewrite/rasi/RASIGenerator.scala | 15 +++++++++++++-- src/rewrite/vct/rewrite/rasi/Utils.scala | 11 +++++++---- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/src/rewrite/vct/rewrite/rasi/AbstractState.scala b/src/rewrite/vct/rewrite/rasi/AbstractState.scala index 990927ac68..e70decde32 100644 --- a/src/rewrite/vct/rewrite/rasi/AbstractState.scala +++ b/src/rewrite/vct/rewrite/rasi/AbstractState.scala @@ -15,8 +15,7 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] * @return The set of possible successor states */ def successors(): Set[AbstractState[G]] = - // TODO: Only consider the state that holds the global lock if it is locked? - processes.keySet.flatMap(p => p.atomic_step(this)) + processes.keySet.filter(p => lock.isEmpty || lock.get.equals(p)).flatMap(p => p.atomic_step(this)) /** * Updates the state by changing the program counter for a process. @@ -278,6 +277,8 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] // Sequence operations case Cons(x, xs) => resolve_collection_expression(xs, is_old, is_contract).prepend(resolve_expression(x, is_old, is_contract)) + case AmbiguousPlus(xs, ys) => + resolve_collection_expression(xs, is_old, is_contract).concat(resolve_collection_expression(ys, is_old, is_contract)) case Concat(xs, ys) => resolve_collection_expression(xs, is_old, is_contract).concat(resolve_collection_expression(ys, is_old, is_contract)) case Drop(xs, count) => @@ -331,7 +332,9 @@ case class AbstractState[G](valuations: Map[ConcreteVariable[G], UncertainValue] private def handle_equality(left: Expr[G], right: Expr[G], is_old: Boolean, is_contract: Boolean): UncertainBooleanValue = left.t match { case _: IntType[_] | TBool() => resolve_expression(left, is_old, is_contract) == resolve_expression(right, is_old, is_contract) - case _ => resolve_collection_expression(left, is_old, is_contract) == resolve_collection_expression(right, is_old, is_contract) + case _: TSeq[_] | TArray(_) => resolve_collection_expression(left, is_old, is_contract) == resolve_collection_expression(right, is_old, is_contract) + case _ => UncertainBooleanValue.from(true) // TODO: The justification for this is that the program will be verified anyway, + // so object equality does not need to be considered; does that make sense? } private def variable_from_expr(variable: Expr[G]): Option[ConcreteVariable[G]] = diff --git a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala index 7a63b7ae02..4b875910a7 100644 --- a/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala +++ b/src/rewrite/vct/rewrite/rasi/RASIGenerator.scala @@ -1,5 +1,6 @@ package vct.rewrite.rasi +import com.typesafe.scalalogging.LazyLogging import vct.col.ast.{Expr, InstanceField, InstanceMethod, Null, Or} import vct.col.origin.Origin import vct.rewrite.cfg.{CFGEntry, CFGGenerator} @@ -9,7 +10,7 @@ import scala.collection.immutable.HashMap import scala.collection.mutable import scala.collection.mutable.ArrayBuffer -case class RASIGenerator[G]() { +case class RASIGenerator[G]() extends LazyLogging { private val found_states: mutable.ArrayBuffer[AbstractState[G]] = mutable.ArrayBuffer() private val found_edges: mutable.ArrayBuffer[(AbstractState[G], AbstractState[G])] = mutable.ArrayBuffer() private val current_branches: mutable.ArrayBuffer[AbstractState[G]] = mutable.ArrayBuffer() @@ -22,16 +23,20 @@ case class RASIGenerator[G]() { private def generate_rasi(node: CFGEntry[G], vars: Set[ConcreteVariable[G]]): Expr[G] = { explore(node, vars) - found_states.distinctBy(s => s.valuations).map(s => s.to_expression).reduce((e1, e2) => Or(e1, e2)(e1.o)) + val distinct_states = found_states.distinctBy(s => s.valuations) + logger.debug(s"${distinct_states.size} distinct states found") + distinct_states.map(s => s.to_expression).reduce((e1, e2) => Or(e1, e2)(e1.o)) } private def print_state_space(node: CFGEntry[G], vars: Set[ConcreteVariable[G]], out_path: Path): Unit = { explore(node, vars) val (ns, es) = reduce_redundant_states() + logger.debug(s"${ns.size} distinct states found") Utils.print(ns, es, out_path) } private def explore(node: CFGEntry[G], vars: Set[ConcreteVariable[G]]): Unit = { + logger.info("Starting RASI generation") val initial_state = AbstractState(get_initial_values(vars), HashMap((AbstractProcess[G](Null()(Origin(Seq()))), node)), None, @@ -39,6 +44,8 @@ case class RASIGenerator[G]() { found_states += initial_state current_branches += initial_state + var i = 0 + while (current_branches.nonEmpty) { val curr: AbstractState[G] = current_branches.head current_branches -= curr @@ -46,8 +53,12 @@ case class RASIGenerator[G]() { val successors: Set[AbstractState[G]] = curr.successors() found_edges.addAll(successors.map(s => (curr, s))) successors.foreach(s => if (!found_states.contains(s)) {found_states += s; current_branches += s}) + i = i + 1 + if (i % 100 == 0) logger.debug(s"Iteration $i: ${found_states.size} states found, ${current_branches.size} yet to explore") } + logger.info("RASI generation complete") + // The initial state converts to simply "true", so it would make the RASI trivial found_states.filterInPlace(s => s.valuations != initial_state.valuations) found_edges.filterInPlace(t => t._1.valuations != initial_state.valuations && t._2.valuations != initial_state.valuations) diff --git a/src/rewrite/vct/rewrite/rasi/Utils.scala b/src/rewrite/vct/rewrite/rasi/Utils.scala index a0f45cab5f..892b70e45b 100644 --- a/src/rewrite/vct/rewrite/rasi/Utils.scala +++ b/src/rewrite/vct/rewrite/rasi/Utils.scala @@ -34,15 +34,18 @@ case object Utils { def print[G](states: Seq[AbstractState[G]], edges: Seq[(AbstractState[G], AbstractState[G])], out: Path): Unit = { val node_names: Map[AbstractState[G], String] = Map.from(states.zipWithIndex.map(t => (t._1, s"n${t._2}"))) - RWFile(out.toFile).write(w => print_state_space(node_names, edges, w)) + RWFile(out.toFile).write(w => print_state_space(node_names, edges, w, states.head.to_expression.toInlineString.length > 50)) } - private def print_state_space[G](names: Map[AbstractState[G], String], edges: Seq[(AbstractState[G], AbstractState[G])], writer: Writer): Unit = { + private def print_state_space[G](names: Map[AbstractState[G], String], + edges: Seq[(AbstractState[G], AbstractState[G])], + writer: Writer, + shorten_labels: Boolean = false): Unit = { writer.append("digraph {\n") names.foreach(t => writer.append(t._2) .append(s"[label=${"\""}") - .append(t._1.to_expression.toInlineString) - .append(s"${"\""}];\n")) + .append(if (shorten_labels) t._2 else t._1.to_expression.toInlineString) + .append(s"${"\""}];${if (shorten_labels) s" /* ${t._1.to_expression.toInlineString} */" else ""}\n")) edges.foreach(t => writer.append(names(t._1)) .append(" -> ") .append(names(t._2))