// Copyright (c) 2011, David J. Pearce (djp@ecs.vuw.ac.nz) // All rights reserved. // // This software may be modified and distributed under the terms // of the BSD license. See the LICENSE file for details. package wyil.builders; import static wyil.util.ErrorMessages.errorMessage; import java.math.BigInteger; import java.util.*; import wybs.lang.Attribute; import wybs.lang.NameID; import wybs.lang.SyntacticElement; import wybs.lang.SyntaxError.InternalFailure; import wybs.util.ResolveError; import wycc.util.Pair; import wycc.util.ArrayUtils; import wyal.lang.SyntacticItem; import wyal.lang.WyalFile; import wyal.lang.WyalFile.Declaration; import wyal.lang.WyalFile.Expr; import wyal.lang.WyalFile.Opcode; //import wyal.lang.WyalFile.Type; import wyal.lang.WyalFile.Value; import wyal.lang.WyalFile.Declaration.Named; import wyfs.lang.Path; import wyfs.lang.Path.ID; import wyfs.util.Trie; import wyil.lang.Bytecode; import wyil.lang.Bytecode.*; import wyil.lang.SyntaxTree; import wyil.lang.SyntaxTree.Location; import wyil.lang.Constant; import wyil.lang.Type; import wyil.lang.WyilFile; import wyil.util.ErrorMessages; import wyil.util.SyntaxTrees; import wyil.util.TypeSystem; /** * <p> * Responsible for generating verification conditions from a given WyIL file. A * verification condition is a logical condition which must be shown to hold in * order for the underlying WyIL program to considered "correct". The * Verification Condition Generator (VCG) examines in turn each function or * method in a given WyIL file. The VCG traverses each control-flow graph * emitting verification conditions as it discovers them. The following * illustrates: * </p> * * <pre> * function abs(int x) -> (int r) * ensures r >= 0: * // * if x >= 0: * return x * else: * return -x * </pre> * * <p> * The above function can be viewed in a slightly more precise fashion as * follows, where the block structure is indicated: * </p> * * <pre> * +-----------------------------+ (1) * |function abs(int x) -> (int r) * |ensures r >= 0: * | +--------------------------+ (2) * | | // * | | if x >= 0: * | | +----------------------+ (3) * | | | return x * | | +----------------------+ * | | else: * | | +----------------------+ (4) * | | | return -x * | | +----------------------+ * | +--------------------------+ * +-----------------------------+ * </pre> * * <p> * The VCG will generate exactly two verification conditions from this function * corresponding to the paths "1,2,3" and "1,2,4". These verification conditions * are required to ensure that, given the information know at the point of each * return, we can establish the post-condition holds. The two verification * conditions are: * </p> * * <ul> * <li><b>1,2,3:</b> <code>x >= 0 ==> x >= 0</code>. This verification * corresponds to the case where the if condition is known to be true.</li> * <li><b>1,2,4:</b> <code>x < 0 ==> -x >= 0</code>. This verification * corresponds to the case where the if condition is known to be false.</li> * </ul> * * <p> * The VCG attempts to generate verification conditions which are easier to read * by making use of macros as much as possible. For example, each clause of a * function/method's precondition or postcondition is turned into a distinct * (named) macro. * </p> * * @author David J. Pearce * */ public class VerificationConditionGenerator { private final Wyil2WyalBuilder builder; private final TypeSystem typeSystem; public VerificationConditionGenerator(Wyil2WyalBuilder builder) { this.builder = builder; this.typeSystem = new TypeSystem(builder.project()); } // =============================================================================== // Top-Level Controller // =============================================================================== /** * Translate a WyilFile into a WyalFile which contains the verification * conditions necessary to establish that all functions and methods in the * WyilFile meet their specifications, and that no array-out-of-bounds or * division-by-zero exceptions are possible (amongst other things). * * @param wyilFile * The input file to be translated * @return */ public WyalFile translate(WyilFile wyilFile, Path.Entry<WyalFile> target) { // WyalFile wyalFile = new WyalFile(target); for (WyilFile.Block b : wyilFile.blocks()) { if (b instanceof WyilFile.Constant) { translateConstantDeclaration((WyilFile.Constant) b, wyalFile); } else if (b instanceof WyilFile.Type) { translateTypeDeclaration((WyilFile.Type) b, wyalFile); } else if (b instanceof WyilFile.Property) { translatePropertyDeclaration((WyilFile.Property) b, wyalFile); } else if (b instanceof WyilFile.FunctionOrMethod) { WyilFile.FunctionOrMethod method = (WyilFile.FunctionOrMethod) b; translateFunctionOrMethodDeclaration(method, wyalFile); } } return wyalFile; } /** * Translate a constant declaration into WyAL. At the moment, this does * nothing because constant declarations are not supported in WyAL files. * * @param declaration * The type declaration being translated. * @param wyalFile * The WyAL file being constructed */ private void translateConstantDeclaration(WyilFile.Constant decl, WyalFile wyalFile) { // FIXME: WyAL file format should support constants } /** * Transform a type declaration into verification conditions as necessary. * In particular, the type should be "inhabitable". This means, for example, * that the invariant does not contradict itself. Furthermore, we need to * translate the type invariant into a macro block. * * @param declaration * The type declaration being translated. * @param wyalFile * The WyAL file being constructed */ private void translateTypeDeclaration(WyilFile.Type declaration, WyalFile wyalFile) { SyntaxTree tree = declaration.getTree(); List<Location<Bytecode.Expr>> invariants = declaration.getInvariant(); WyalFile.Stmt.Block[] invariant = new WyalFile.Stmt.Block[invariants.size()]; WyalFile.Type type = convert(declaration.type(), declaration); WyalFile.VariableDeclaration var; if(invariants.size() > 0) { Location<VariableDeclaration> v = (Location<VariableDeclaration>) tree.getLocation(0); // First, translate the invariant (if applicable) GlobalEnvironment globalEnvironment = new GlobalEnvironment(declaration); LocalEnvironment localEnvironment = new LocalEnvironment(globalEnvironment); var = localEnvironment.read(v.getIndex()); for (int i = 0; i != invariant.length; ++i) { invariant[i] = translateAsBlock(invariants.get(i), localEnvironment); } } else { var = new WyalFile.VariableDeclaration(type,new WyalFile.Identifier("this")); } // Done WyalFile.Identifier name = new WyalFile.Identifier(declaration.name()); WyalFile.Declaration td = new WyalFile.Declaration.Named.Type(name, var, invariant); td.attributes().addAll(declaration.attributes()); wyalFile.allocate(td); } private void translatePropertyDeclaration(WyilFile.Property declaration, WyalFile wyalFile) { // GlobalEnvironment globalEnvironment = new GlobalEnvironment(declaration); LocalEnvironment localEnvironment = new LocalEnvironment(globalEnvironment); WyalFile.VariableDeclaration[] type = generatePreconditionParameters(declaration, localEnvironment); List<Location<Bytecode.Expr>> invariants = declaration.getPrecondition(); // WyalFile.Stmt[] stmts = new WyalFile.Stmt[invariants.size()]; // for (int i = 0; i != invariants.size(); ++i) { // Translate expression itself stmts[i] = translateAsBlock(invariants.get(i), localEnvironment); } // WyalFile.Stmt.Block block = new WyalFile.Stmt.Block(stmts); WyalFile.Declaration d = new WyalFile.Declaration.Named.Macro(new WyalFile.Identifier(declaration.name()), type, block); wyalFile.allocate(d); } /** * Transform a function or method declaration into verification conditions * as necessary. This is done by traversing the control-flow graph of the * function or method in question. Verifications are emitted when conditions * are encountered which must be checked. For example, that the * preconditions are met at a function invocation. * * @param declaration * The function or method declaration being translated. * @param wyalFile * The WyAL file being constructed */ private void translateFunctionOrMethodDeclaration(WyilFile.FunctionOrMethod declaration, WyalFile wyalFile) { // Create the prototype for this function or method. This is the // function or method declaration which can be used within verification // conditions to refer to this function or method. This does not include // a body, since function or methods are treated as being // "uninterpreted" for the purposes of verification. createFunctionOrMethodPrototype(declaration, wyalFile); // Create macros representing the individual clauses of the function or // method's precondition and postcondition. These macros can then be // called either to assume the precondition/postcondition or to check // them. Using individual clauses helps to provide better error // messages. translatePreconditionMacros(declaration, wyalFile); translatePostconditionMacros(declaration, wyalFile); // The environments are needed to prevent clashes between variable // versions across verification conditions, and also to type variables // used in verification conditions. GlobalEnvironment globalEnvironment = new GlobalEnvironment(declaration); LocalEnvironment localEnvironment = new LocalEnvironment(globalEnvironment); // Generate the initial assumption set for a given function or method, // which roughly corresponds to its precondition. AssumptionSet assumptions = generateFunctionOrMethodAssumptionSet(declaration, localEnvironment); // Generate verification conditions by propagating forwards through the // control-flow graph of the function or method in question. For each // statement encountered, generate the preconditions which must hold // true at that point. Furthermore, generate the effect of this // statement on the current state. List<VerificationCondition> vcs = new ArrayList<>(); Context context = new Context(wyalFile, assumptions, localEnvironment, localEnvironment, null, vcs); translateStatementBlock(declaration.getBody(), context); // // Translate each generated verification condition into an assertion in // the underlying WyalFile. createAssertions(declaration, vcs, globalEnvironment, wyalFile); } /** * Translate the sequence of invariant expressions which constitute the * precondition of a function or method into corresponding macro * declarations. * * @param declaration * @param environment * @param wyalFile */ private void translatePreconditionMacros(WyilFile.FunctionOrMethodOrProperty declaration, WyalFile wyalFile) { List<Location<Bytecode.Expr>> invariants = declaration.getPrecondition(); // String prefix = declaration.name() + "_requires_"; // for (int i = 0; i != invariants.size(); ++i) { String name = prefix + i; // Construct fresh environment for this macro. This is necessary to // avoid name clashes with subsequent macros. GlobalEnvironment globalEnvironment = new GlobalEnvironment(declaration); LocalEnvironment localEnvironment = new LocalEnvironment(globalEnvironment); WyalFile.VariableDeclaration[] type = generatePreconditionParameters(declaration, localEnvironment); // Translate expression itself WyalFile.Stmt.Block clause = translateAsBlock(invariants.get(i), localEnvironment); // Capture any free variables. This is necessary to deal with any // variable aliases introduced by type test operators. clause = captureFreeVariables(declaration, globalEnvironment, clause); // WyalFile.Declaration d = new WyalFile.Declaration.Named.Macro(new WyalFile.Identifier(name), type, clause); wyalFile.allocate(d); } } /** * Translate the sequence of invariant expressions which constitute the * postcondition of a function or method into corresponding macro * declarations. * * @param declaration * @param environment * @param wyalFile */ private void translatePostconditionMacros(WyilFile.FunctionOrMethod declaration, WyalFile wyalFile) { List<Location<Bytecode.Expr>> invariants = declaration.getPostcondition(); // String prefix = declaration.name() + "_ensures_"; // for (int i = 0; i != invariants.size(); ++i) { String name = prefix + i; // Construct fresh environment for this macro. This is necessary to // avoid name clashes with subsequent macros. GlobalEnvironment globalEnvironment = new GlobalEnvironment(declaration); LocalEnvironment localEnvironment = new LocalEnvironment(globalEnvironment); WyalFile.VariableDeclaration[] type = generatePostconditionTypePattern(declaration, localEnvironment); WyalFile.Stmt.Block clause = translateAsBlock(invariants.get(i), localEnvironment.clone()); // Capture any free variables. This is necessary to deal with any // variable aliases introduced by type test operators. clause = captureFreeVariables(declaration, globalEnvironment, clause); // WyalFile.Declaration d = new WyalFile.Declaration.Named.Macro(new WyalFile.Identifier(name), type, clause); wyalFile.allocate(d); } } private WyalFile.Stmt.Block captureFreeVariables(WyilFile.Declaration declaration, GlobalEnvironment globalEnvironment, WyalFile.Stmt.Block clause) { HashSet<WyalFile.VariableDeclaration> freeVariables = new HashSet<>(); HashSet<WyalFile.VariableDeclaration> freeAliases = new HashSet<>(); freeVariables(clause,freeVariables); for (WyalFile.VariableDeclaration var : freeVariables) { if (globalEnvironment.getParent(var) != null) { // This indicates that the given variable is an alias, rather // than a top-level declaration. freeAliases.add(var); } } // Determine any variable aliases as necessary. if (freeAliases.size() > 0) { // This indicates there are one or more free variables in the // clause. Hence, these must be universally quantified to ensure the // clause is well=typed. WyalFile.VariableDeclaration[] types = generateExpressionTypePattern(declaration, globalEnvironment, freeAliases); Expr aliases = determineVariableAliases(globalEnvironment, freeAliases); // WyalFile.Stmt.Block body = new WyalFile.Stmt.Block(implies(aliases, clause)); return new WyalFile.Stmt.Block(new WyalFile.Stmt.UniversalQuantifier(types, body)); } return clause; } /** * Generate the initial assumption set for a function or method. This is * essentially made up of the precondition(s) for that function or method. * * @param declaration * @return */ private AssumptionSet generateFunctionOrMethodAssumptionSet(WyilFile.FunctionOrMethod declaration, LocalEnvironment environment) { SyntaxTree tree = declaration.getTree(); String prefix = declaration.name() + "_requires_"; Expr[] preconditions = new Expr[declaration.getPrecondition().size()]; Expr[] arguments = new Expr[declaration.type().params().length]; // Translate parameters as arguments to invocation for (int i = 0; i != arguments.length; ++i) { Location<VariableDeclaration> var = (Location<VariableDeclaration>) tree.getLocation(i); WyalFile.VariableDeclaration versionedVar = environment.read(var.getIndex()); arguments[i] = new Expr.VariableAccess(versionedVar); } // for (int i = 0; i != preconditions.length; ++i) { WyalFile.Name name = new WyalFile.Name(new WyalFile.Identifier(prefix + i)); preconditions[i] = new Expr.Invoke(null, name, null, arguments); } // Add all the preconditions as assupmtions return AssumptionSet.ROOT.add(preconditions); } // ========================================================================= // Statements // ========================================================================= private Context translateStatementBlock(Location<Block> block, Context context) { for (int i = 0; i != block.numberOfOperands(); ++i) { Location<?> stmt = block.getOperand(i); context = translateStatement(stmt, context); if (stmt.getBytecode() instanceof Bytecode.Return) { return null; } } return context; } @SuppressWarnings("unchecked") private Context translateStatement(Location<?> stmt, Context context) { SyntaxTree tree = stmt.getEnclosingTree(); WyilFile.Declaration decl = tree.getEnclosingDeclaration(); // try { switch (stmt.getOpcode()) { case Bytecode.OPCODE_assert: return translateAssert((Location<Assert>) stmt, context); case Bytecode.OPCODE_assign: return translateAssign((Location<Assign>) stmt, context); case Bytecode.OPCODE_assume: return translateAssume((Location<Assume>) stmt, context); case Bytecode.OPCODE_break: return translateBreak((Location<Break>) stmt, context); case Bytecode.OPCODE_continue: return translateContinue((Location<Continue>) stmt, context); case Bytecode.OPCODE_debug: return context; case Bytecode.OPCODE_dowhile: return translateDoWhile((Location<DoWhile>) stmt, context); case Bytecode.OPCODE_fail: return translateFail((Location<Fail>) stmt, context); case Bytecode.OPCODE_if: case Bytecode.OPCODE_ifelse: return translateIf((Location<If>) stmt, context); case Bytecode.OPCODE_indirectinvoke: checkExpressionPreconditions(stmt, context); translateIndirectInvoke((Location<IndirectInvoke>) stmt, context.getEnvironment()); return context; case Bytecode.OPCODE_invoke: checkExpressionPreconditions(stmt, context); translateInvoke((Location<Invoke>) stmt, null, context.getEnvironment()); return context; case Bytecode.OPCODE_namedblock: return translateNamedBlock((Location<NamedBlock>) stmt, context); case Bytecode.OPCODE_return: return translateReturn((Location<Return>) stmt, context); case Bytecode.OPCODE_skip: return translateSkip((Location<Skip>) stmt, context); case Bytecode.OPCODE_switch: return translateSwitch((Location<Switch>) stmt, context); case Bytecode.OPCODE_while: return translateWhile((Location<While>) stmt, context); case Bytecode.OPCODE_vardecl: case Bytecode.OPCODE_vardeclinit: return translateVariableDeclaration((Location<VariableDeclaration>) stmt, context); default: throw new InternalFailure("unknown statement encountered (" + stmt + ")", decl.parent().getEntry(), stmt); } } catch (InternalFailure e) { throw e; } catch (Throwable e) { throw new InternalFailure(e.getMessage(), decl.parent().getEntry(), stmt, e); } } /** * Translate an assert statement. This emits a verification condition which * ensures the assert condition holds, given the current context. * * @param stmt * @param wyalFile */ private Context translateAssert(Location<Assert> stmt, Context context) { Location<?> operand = stmt.getOperand(0); Pair<Expr, Context> p = translateExpressionWithChecks(operand, null, context); Expr condition = p.first(); context = p.second(); // VerificationCondition verificationCondition = new VerificationCondition("assertion failed", context.assumptions, condition, operand.attributes()); context.emit(verificationCondition); // return context.assume(condition); } /** * Translate an assign statement. This updates the version number of the * underlying assigned variable. * * @param stmt * @param wyalFile */ private Context translateAssign(Location<Assign> stmt, Context context) { Location<?>[] lhs = stmt.getOperandGroup(0); Location<?>[] rhs = stmt.getOperandGroup(1); for (int i = 0, j = 0; i != rhs.length; ++i) { Location<?> rval = rhs[i]; Location<?>[] lval = java.util.Arrays.copyOfRange(lhs, j, rval.numberOfTypes()); context = translateAssign(lval, rval, context); j = j + rval.numberOfTypes(); } // Done return context; } /** * Translate an individual assignment from one rval to one or more lvals. If * there are multiple lvals, then a tuple is created to represent the * left-hand side. * * @param lval * One or more expressions representing the left-hand side * @param rval * A single expression representing the right-hand side * @param context * @return */ private Context translateAssign(Location<?>[] lval, Location<?> rval, Context context) { Expr[] ls = new Expr[lval.length]; for (int i = 0; i != ls.length; ++i) { Expr rhs; if(i == 0) { // First time around, we should generate appropriate // precondition checks. We also need to determine whether the // selector is null or not. Integer selector = ls.length == 1 ? null : i; Pair<Expr, Context> rp = translateExpressionWithChecks(rval, selector, context); context = rp.second(); rhs = rp.first(); } else { // Second time around, don't need to regenerate the precondition // checks. We also know that the selector should be non-null. rhs = translateExpression(rval, i, context.getEnvironment()); } Location<?> lhs = lval[i]; generateTypeInvariantCheck(lhs.getType(),rhs,context); context = translateSingleAssignment(lval[i], rhs, context); } return context; } /** * Translate an individual assignment from one rval to exactly one lval. * * @param lval * A single location representing the left-hand side * @param rval * A single expression representing the right-hand side * @param context * @return */ private Context translateSingleAssignment(Location<?> lval, Expr rval, Context context) { // FIXME: this method is a bit of a kludge. It would be nicer, // eventually, to have all right-hand side expression represented in // WyTP directly. This could potentially be done by including an update // operation in WyTP ... ? switch (lval.getOpcode()) { case Bytecode.OPCODE_arrayindex: return translateArrayAssign((Location<Operator>) lval, rval, context); case Bytecode.OPCODE_dereference: return translateDereference((Location<Operator>) lval, rval, context); case Bytecode.OPCODE_fieldload: return translateRecordAssign((Location<FieldLoad>) lval, rval, context); case Bytecode.OPCODE_varmove: case Bytecode.OPCODE_varcopy: return translateVariableAssign((Location<VariableAccess>) lval, rval, context); default: throw new InternalFailure("unknown lval encountered (" + lval + ")", context.getEnclosingFile().getEntry(), lval); } } /** * Translate an assignment to a field. * * @param lval * The field access expression * @param result * The value being assigned to the given array element * @param context * The enclosing context * @return */ private Context translateRecordAssign(Location<FieldLoad> lval, Expr rval,Context context) { // Translate src and index expressions Pair<Expr, Context> p1 = translateExpressionWithChecks(lval.getOperand(0), null, context); Expr source = p1.first(); WyalFile.Identifier field = new WyalFile.Identifier(lval.getBytecode().fieldName()); // Construct record update for "pass thru" Expr update = new Expr.RecordUpdate(source, field, rval); return translateSingleAssignment(lval.getOperand(0),update,p1.second()); } /** * Translate an assignment to an array element. * * @param lval * The array assignment expression * @param result * The value being assigned to the given array element * @param context * The enclosing context * @return */ private Context translateArrayAssign(Location<Operator> lval, Expr rval, Context context) { // Translate src and index expressions Pair<Expr, Context> p1 = translateExpressionWithChecks(lval.getOperand(0), null, context); Pair<Expr, Context> p2 = translateExpressionWithChecks(lval.getOperand(1), null, p1.second()); Expr source = p1.first(); Expr index = p2.first(); // Emit verification conditions to check access in bounds checkIndexOutOfBounds(lval, p2.second()); // Construct array update for "pass thru" Expr.Operator update = new Expr.ArrayUpdate(source, index, rval); return translateSingleAssignment(lval.getOperand(0),update,p2.second()); } /** * Translate an indirect assignment through a reference. * * @param lval * The array assignment expression * @param result * The value being assigned to the given array element * @param context * The enclosing context * @return */ private Context translateDereference(Location<Operator> lval, Expr rval, Context context) { Expr e = translateDereference(lval,context.getEnvironment()); return context.assume(new Expr.Equal(e, rval)); } /** * Translate an assignment to a variable * * @param lval * The array assignment expression * @param result * The value being assigned to the given array element * @param context * The enclosing context * @return */ private Context translateVariableAssign(Location<VariableAccess> lval, Expr rval,Context context) { Location<VariableDeclaration> decl = (Location<VariableDeclaration>) lval.getOperand(0); context = context.havoc(decl.getIndex()); WyalFile.VariableDeclaration nVersionedVar = context.read(decl); Expr.VariableAccess var = new Expr.VariableAccess(nVersionedVar); return context.assume(new Expr.Equal(var, rval)); } /** * Determine the variable at the root of a given sequence of assignments, or * return null if there is no statically determinable variable. * * @param lval * @return */ private Location<VariableAccess> extractAssignedVariable(Location<?> lval) { SyntaxTree tree = lval.getEnclosingTree(); WyilFile.Declaration decl = tree.getEnclosingDeclaration(); // switch (lval.getOpcode()) { case Bytecode.OPCODE_arrayindex: return extractAssignedVariable(lval.getOperand(0)); case Bytecode.OPCODE_dereference: return null; case Bytecode.OPCODE_fieldload: return extractAssignedVariable(lval.getOperand(0)); case Bytecode.OPCODE_varmove: case Bytecode.OPCODE_varcopy: return (Location<VariableAccess>) lval; default: throw new InternalFailure("unknown lval encountered (" + lval + ")", decl.parent().getEntry(), lval); } } /** * Translate an assume statement. This simply updates the current context to * assume that the given condition holds true (i.e. regardless of whether it * does or not). The purpose of assume statements is to allow some level of * interaction between the programmer and the verifier. That is, the * programmer can assume things which he/she knows to be true which the * verifier cannot prove (for whatever reason). * * @param stmt * @param wyalFile */ private Context translateAssume(Location<Assume> stmt, Context context) { Pair<Expr, Context> p = translateExpressionWithChecks(stmt.getOperand(0), null, context); Expr condition = p.first(); context = p.second(); return context.assume(condition); } /** * Translate a break statement. This takes the current context and pushes it * into the enclosing loop scope. It will then be extracted later and used. * * @param stmt * @param wyalFile */ private Context translateBreak(Location<Break> stmt, Context context) { LoopScope enclosingLoop = context.getEnclosingLoopScope(); enclosingLoop.addBreakContext(context); return null; } /** * Translate a continue statement. This takes the current context and pushes * it into the enclosing loop scope. It will then be extracted later and * used. * * @param stmt * @param wyalFile */ private Context translateContinue(Location<Continue> stmt, Context context) { LoopScope enclosingLoop = context.getEnclosingLoopScope(); enclosingLoop.addContinueContext(context); return null; } /** * Translate a DoWhile statement. * * @param stmt * @param context * @return */ private Context translateDoWhile(Location<DoWhile> stmt, Context context) { WyilFile.Declaration declaration = context.getEnvironment().getParent().enclosingDeclaration; Location<?>[] loopInvariant = stmt.getOperandGroup(0); // Translate the loop invariant and generate appropriate macro translateLoopInvariantMacros(loopInvariant, declaration, context.wyalFile); // Rule 1. Check loop invariant after first iteration LoopScope firstScope = new LoopScope(); Context beforeFirstBodyContext = context.newLoopScope(firstScope); Context afterFirstBodyContext = translateStatementBlock(stmt.getBlock(0), beforeFirstBodyContext); // Join continue contexts together since they must also preserve the // loop invariant afterFirstBodyContext = joinDescendants(beforeFirstBodyContext, afterFirstBodyContext, firstScope.continueContexts); // checkLoopInvariant("loop invariant not established by first iteration", loopInvariant, afterFirstBodyContext); // Rule 2. Check loop invariant preserved on subsequence iterations. On // entry to the loop body we must havoc all modified variables. This is // necessary as such variables should retain their values from before // the loop. LoopScope arbitraryScope = new LoopScope(); Context beforeArbitraryBodyContext = context.newLoopScope(arbitraryScope).havoc(stmt.getOperandGroup(1)); beforeArbitraryBodyContext = assumeLoopInvariant(loopInvariant, beforeArbitraryBodyContext); Pair<Expr, Context> p = translateExpressionWithChecks(stmt.getOperand(0), null, beforeArbitraryBodyContext); Expr trueCondition = p.first(); beforeArbitraryBodyContext = p.second().assume(trueCondition); Context afterArbitraryBodyContext = translateStatementBlock(stmt.getBlock(0), beforeArbitraryBodyContext); // Join continue contexts together since they must also preserve the // loop invariant afterArbitraryBodyContext = joinDescendants(beforeArbitraryBodyContext, afterArbitraryBodyContext, arbitraryScope.continueContexts); // checkLoopInvariant("loop invariant not restored", loopInvariant, afterArbitraryBodyContext); // Rule 3. Assume loop invariant holds. Context exitContext = context.havoc(stmt.getOperandGroup(1)); exitContext = assumeLoopInvariant(loopInvariant, exitContext); Expr falseCondition = invertCondition(translateExpression(stmt.getOperand(0), null, exitContext.getEnvironment()), stmt.getOperand(0)); exitContext = exitContext.assume(falseCondition); // // Finally, need to join any break contexts from either first iteration // or arbitrary iteration exitContext = joinDescendants(context, exitContext, firstScope.breakContexts, arbitraryScope.breakContexts); // return exitContext; } /** * Translate a fail statement. Execution should never reach such a * statement. Hence, we need to emit a verification condition to ensure this * is the case. * * @param stmt * @param context * @return */ private Context translateFail(Location<Fail> stmt, Context context) { Expr condition = new Expr.Constant(new Value.Bool(false)); // VerificationCondition verificationCondition = new VerificationCondition("possible panic", context.assumptions, condition, stmt.attributes()); context.emit(verificationCondition); // return null; } /** * Translate an if statement. This translates the true and false branches * and then recombines them together to form an updated environment. This is * challenging when the environments are updated independently in both * branches. * * @param stmt * @param wyalFile */ private Context translateIf(Location<If> stmt, Context context) { // Pair<Expr, Context> p = translateExpressionWithChecks(stmt.getOperand(0), null, context); Expr trueCondition = p.first(); // FIXME: this is broken as includes assumptions propagated through // logical &&'s context = p.second(); Expr falseCondition = invertCondition(trueCondition, stmt.getOperand(0)); // Context trueContext = context.assume(trueCondition); Context falseContext = context.assume(falseCondition); // trueContext = translateStatementBlock(stmt.getBlock(0), trueContext); if (stmt.numberOfBlocks() > 1) { falseContext = translateStatementBlock(stmt.getBlock(1), falseContext); } // Finally, we must join the two context's back together. This ensures // that information from either side is properly preserved return joinDescendants(context, new Context[] { trueContext, falseContext }); } /** * Translate a named block * * @param stmt * @param wyalFile */ private Context translateNamedBlock(Location<NamedBlock> stmt, Context context) { return translateStatementBlock(stmt.getBlock(0), context); } /** * Translate a return statement. If a return value is given, then this must * ensure that the post-condition of the enclosing function or method is met * * @param stmt * @param wyalFile */ private Context translateReturn(Location<Return> stmt, Context context) { // Location<?>[] returns = stmt.getOperands(); // if (returns.length > 0) { // There is at least one return value. Therefore, we need to check // any preconditions for those return expressions and, potentially, // ensure any postconditions of the cnlosing function/method are // met. Pair<Expr[], Context> p = translateExpressionsWithChecks(returns, context); Expr[] exprs = p.first(); context = p.second(); // generateReturnTypeInvariantCheck(stmt,exprs,context); generatePostconditionChecks(stmt,exprs,context); } // Return null to signal that execution does not continue after this // return statement. return null; } /** * Generate a return type check in the case that it is necessary. For * example, if the return type contains a type invariant then it is likely * to be necessary. However, in the special case that the value being * returned is already of appropriate type, then it is not. * * @param stmt * @param exprs * @param context * @throws ResolveError */ private void generateReturnTypeInvariantCheck(Location<Return> stmt, Expr[] exprs, Context context) { SyntaxTree tree = stmt.getEnclosingTree(); WyilFile.FunctionOrMethod declaration = (WyilFile.FunctionOrMethod) tree.getEnclosingDeclaration(); Type[] returnTypes = declaration.type().returns(); // for (int i = 0; i != exprs.length; ++i) { Type returnType = returnTypes[i]; // FIXME: at this point, we want to determine whether or not the // check is actually required. To do this, we need to check whether // the actualType is a true subtype of the returnType. generateTypeInvariantCheck(returnType, exprs[i], context); } } private void generateTypeInvariantCheck(Type lhs, Expr rhs, Context context) { WyalFile.Type typeTest = convert(lhs, context.getEnvironment().getParent().enclosingDeclaration); Expr clause = new Expr.Is(rhs, typeTest); context.emit(new VerificationCondition("type invariant not satisfied", context.assumptions, clause, rhs.attributes())); } /** * Generate the post-condition checks necessary at a return statement in a * function or method. * * @param stmt * @param exprs * @param context */ private void generatePostconditionChecks(Location<Return> stmt, Expr[] exprs, Context context) { SyntaxTree tree = stmt.getEnclosingTree(); WyilFile.FunctionOrMethod declaration = (WyilFile.FunctionOrMethod) tree.getEnclosingDeclaration(); List<Location<Bytecode.Expr>> postcondition = declaration.getPostcondition(); Type.FunctionOrMethod type = declaration.type(); // First, check whether or not there are any postconditions! if (postcondition.size() > 0) { // There is at least one return value and at least one // postcondition clause. Therefore, we need to check the return // values against the post condition(s). One of the difficulties // here is that the postcondition will refer to parameters as // they were on entry to the function/method, not as they are // now. Expr[] arguments = new Expr[type.params().length + type.returns().length]; // Translate parameters as arguments to post-condition // invocation for (int i = 0; i != type.params().length; ++i) { Location<VariableDeclaration> var = (Location<VariableDeclaration>) tree.getLocation(i); WyalFile.VariableDeclaration vd = context.readFirst(var); arguments[i] = new Expr.VariableAccess(vd); } // Copy over return expressions as arguments for invocation(s) System.arraycopy(exprs, 0, arguments, type.params().length, exprs.length); // String prefix = declaration.name() + "_ensures_"; // Finally, generate an appropriate verification condition to // check each postcondition clause for (int i = 0; i != postcondition.size(); ++i) { WyalFile.Name name = new WyalFile.Name(new WyalFile.Identifier(prefix + i)); Expr clause = new Expr.Invoke(null, name, null, arguments); context.emit(new VerificationCondition("postcondition not satisfied", context.assumptions, clause, stmt.attributes())); } } } /** * Translate a skip statement, which obviously does nothing * * @param stmt * @param wyalFile */ private Context translateSkip(Location<Skip> stmt, Context context) { return context; } /** * Translate a switch statement. * * @param stmt * @param wyalFile */ private Context translateSwitch(Location<Switch> stmt, Context context) { Bytecode.Switch bytecode = stmt.getBytecode(); Bytecode.Case[] cases = bytecode.cases(); // Pair<Expr, Context> p = translateExpressionWithChecks(stmt.getOperand(0), null, context); Expr value = p.first(); context = p.second(); // WyalFile.Stmt defaultValue = null; Context[] descendants = new Context[cases.length + 1]; Context defaultContext = null; // for (int i = 0; i != cases.length; ++i) { Bytecode.Case caSe = cases[i]; Context caseContext; // Setup knowledge from case values if (!caSe.isDefault()) { WyalFile.Stmt e = null; for (Constant constant : caSe.values()) { Expr v = convert(constant, stmt, context.getEnvironment()); e = or(e, new Expr.Equal(value, v)); defaultValue = and(defaultValue, new Expr.NotEqual(value, v)); } caseContext = context.assume(e); descendants[i] = translateStatementBlock(stmt.getBlock(i), caseContext); } else { defaultContext = context.assume(defaultValue); defaultContext = translateStatementBlock(stmt.getBlock(i), defaultContext); } } // Sort out default context if (defaultContext == null) { // indicates no default block was present, so we just assume what we // know and treat it as a fall through. defaultContext = context.assume(defaultValue); } descendants[descendants.length - 1] = defaultContext; // return joinDescendants(context, descendants); } /** * Translate a While statement. * * @param stmt * @param context * @return */ private Context translateWhile(Location<While> stmt, Context context) { WyilFile.Declaration declaration = context.getEnvironment().getParent().enclosingDeclaration; Location<?>[] loopInvariant = stmt.getOperandGroup(0); // Translate the loop invariant and generate appropriate macro translateLoopInvariantMacros(loopInvariant, declaration, context.wyalFile); // Rule 1. Check loop invariant on entry checkLoopInvariant("loop invariant does not hold on entry", loopInvariant, context); // Rule 2. Check loop invariant preserved. On entry to the loop body we // must havoc all modified variables. This is necessary as such // variables should retain their values from before the loop. LoopScope scope = new LoopScope(); Context beforeBodyContext = context.newLoopScope(scope).havoc(stmt.getOperandGroup(1)); beforeBodyContext = assumeLoopInvariant(loopInvariant, beforeBodyContext); Pair<Expr, Context> p = translateExpressionWithChecks(stmt.getOperand(0), null, beforeBodyContext); Expr trueCondition = p.first(); beforeBodyContext = p.second().assume(trueCondition); Context afterBodyContext = translateStatementBlock(stmt.getBlock(0), beforeBodyContext); // Join continue contexts together since they must also preserve the // loop invariant afterBodyContext = joinDescendants(beforeBodyContext, afterBodyContext, scope.continueContexts); checkLoopInvariant("loop invariant not restored", loopInvariant, afterBodyContext); // Rule 3. Assume loop invariant holds. Context exitContext = context.havoc(stmt.getOperandGroup(1)); exitContext = assumeLoopInvariant(loopInvariant, exitContext); Expr falseCondition = invertCondition( translateExpression(stmt.getOperand(0), null, exitContext.getEnvironment()), stmt.getOperand(0)); exitContext = exitContext.assume(falseCondition); // // Finally, need to join any break contexts exitContext = joinDescendants(context, exitContext, scope.breakContexts); // return exitContext; } /** * Translate the sequence of invariant expressions which constitute the loop * invariant of a loop into one or more macros * * @param loopInvariant * The clauses making up the loop invariant * @param environment * @param wyalFile */ private void translateLoopInvariantMacros(Location<?>[] loopInvariant, WyilFile.Declaration declaration, WyalFile wyalFile) { // String prefix = declaration.name() + "_loopinvariant_"; // for (int i = 0; i != loopInvariant.length; ++i) { Location<?> clause = loopInvariant[i]; WyalFile.Identifier name = new WyalFile.Identifier(prefix + clause.getIndex()); // Construct fresh environment for this macro. This is necessary to // avoid name clashes with subsequent macros. GlobalEnvironment globalEnvironment = new GlobalEnvironment(declaration); LocalEnvironment localEnvironment = new LocalEnvironment(globalEnvironment); WyalFile.VariableDeclaration[] vars = generateLoopInvariantParameterDeclarations(declaration, loopInvariant, localEnvironment); WyalFile.Stmt.Block e = translateAsBlock(clause, localEnvironment.clone()); Named.Macro macro = new Named.Macro(name, vars, e); wyalFile.allocate(macro); } } /** * Emit verification condition(s) to ensure that the clauses of loop * invariant hold at a given point * * @param loopInvariant * The clauses making up the loop invariant * @param context */ private void checkLoopInvariant(String msg, Location<?>[] loopInvariant, Context context) { // LocalEnvironment environment = context.getEnvironment(); WyilFile.FunctionOrMethod declaration = (WyilFile.FunctionOrMethod) environment .getParent().enclosingDeclaration; SyntaxTree tree = declaration.getTree(); // FIXME: this is completely broken in the case of multiple loops. The // problem is that we need to distinguish the macro names based on some // kind of block identifier. String prefix = declaration.name() + "_loopinvariant_"; // Construct argument to invocation int[] localVariables = SyntaxTrees.determineUsedVariables(loopInvariant); Expr[] arguments = new Expr[localVariables.length]; for (int i = 0; i != arguments.length; ++i) { Location<VariableAccess> var = (Location<VariableAccess>) tree.getLocation(localVariables[i]); arguments[i] = new Expr.VariableAccess(environment.read(var.getIndex())); } // for (int i = 0; i != loopInvariant.length; ++i) { Location<?> clause = loopInvariant[i]; WyalFile.Name name = new WyalFile.Name(new WyalFile.Identifier(prefix + clause.getIndex())); Expr macroCall = new Expr.Invoke(null, name, null, arguments); context.emit(new VerificationCondition(msg, context.assumptions, macroCall, clause.attributes())); } } private Context assumeLoopInvariant(Location<?>[] loopInvariant, Context context) { // LocalEnvironment environment = context.getEnvironment(); WyilFile.FunctionOrMethod declaration = (WyilFile.FunctionOrMethod) environment .getParent().enclosingDeclaration; SyntaxTree tree = declaration.getTree(); // FIXME: this is completely broken in the case of multiple loops. The // problem is that we need to distinguish the macro names based on some // kind of block identifier. String prefix = declaration.name() + "_loopinvariant_"; // Construct argument to invocation int[] localVariables = SyntaxTrees.determineUsedVariables(loopInvariant); Expr[] arguments = new Expr[localVariables.length]; for (int i = 0; i != arguments.length; ++i) { Location<VariableAccess> var = (Location<VariableAccess>) tree.getLocation(localVariables[i]); arguments[i] = new Expr.VariableAccess(environment.read(var.getIndex())); } // for (int i = 0; i != loopInvariant.length; ++i) { Location<?> clause = loopInvariant[i]; WyalFile.Name name = new WyalFile.Name(new WyalFile.Identifier(prefix + clause.getIndex())); Expr macroCall = new Expr.Invoke(null, name, null, arguments); context = context.assume(macroCall); } // return context; } /** * Translate a variable declaration. * * @param stmt * @param context * @return */ private Context translateVariableDeclaration(Location<VariableDeclaration> stmt, Context context) { if (stmt.numberOfOperands() > 0) { Pair<Expr, Context> p = translateExpressionWithChecks(stmt.getOperand(0), null, context); context = p.second(); generateTypeInvariantCheck(stmt.getType(),p.first(),context); context = context.write(stmt.getIndex(), p.first()); } // return context; } // ========================================================================= // Checked Expressions // ========================================================================= /** * Translate zero or more expressions into their equivalent WyAL * expressions. At the same time, emit verification conditions to check that * the expression's preconditions. For example, in the expression * <code>x[i] + 1</code> we need to check that <code>i</code> is within * bounds. * * @param expr * --- Expression to be translated * @param context * --- Context in which translation is occurring * @return */ private Pair<Expr[], Context> translateExpressionsWithChecks(Location<?>[] exprs, Context context) { // Generate expression preconditions as verification conditions for (Location<?> expr : exprs) { checkExpressionPreconditions(expr, context); } // Gather up any postconditions from function invocations for (Location<?> expr : exprs) { context = assumeExpressionPostconditions(expr, context); } // Translate expression in the normal fashion return new Pair<>(translateExpressions(exprs, context.getEnvironment()), context); } /** * Translate a given expression into its equivalent WyAL expression. At the * same time, emit verification conditions to check that the expression's * preconditions. For example, in the expression <code>x[i] + 1</code> we * need to check that <code>i</code> is within bounds. * * @param expr * --- Expression to be translated * @param context * --- Context in which translation is occurring * @return */ private Pair<Expr, Context> translateExpressionWithChecks(Location<?> expr, Integer selector, Context context) { // Generate expression preconditions as verification conditions checkExpressionPreconditions(expr, context); // Gather up any postconditions from function invocations context = assumeExpressionPostconditions(expr, context); // Translate expression in the normal fashion return new Pair<>(translateExpression(expr, selector, context.getEnvironment()), context); } @SuppressWarnings("unchecked") private void checkExpressionPreconditions(Location<?> expr, Context context) { WyilFile.Declaration decl = expr.getEnclosingTree().getEnclosingDeclaration(); try { // First, recurse all subexpressions int opcode = expr.getOpcode(); if (opcode == Bytecode.OPCODE_logicaland) { // In the case of a logical and condition we need to propagate // the left-hand side as an assumption into the right-hand side. // This is an artifact of short-circuiting whereby terms on the // right-hand side only execute when the left-hand side is known // to hold. for (int i = 0; i != expr.numberOfOperands(); ++i) { checkExpressionPreconditions(expr.getOperand(i), context); Expr e = translateExpression(expr.getOperand(i), null, context.getEnvironment()); context = context.assume(e); } } else if (opcode != Bytecode.OPCODE_varcopy && opcode != Bytecode.OPCODE_varmove) { // In the case of a general expression, we just recurse any // subexpressions without propagating information forward. We // must ignore variable accesses here, because they refer back // to the relevant variable declaration. for (int i = 0; i != expr.numberOfOperands(); ++i) { checkExpressionPreconditions(expr.getOperand(i), context); } for (int i = 0; i != expr.numberOfOperandGroups(); ++i) { Location<?>[] group = expr.getOperandGroup(i); for (Location<?> e : group) { checkExpressionPreconditions(e, context); } } } // Second, perform actual precondition checks switch (expr.getOpcode()) { case Bytecode.OPCODE_invoke: checkInvokePreconditions((Location<Invoke>) expr, context); break; case Bytecode.OPCODE_div: case Bytecode.OPCODE_rem: checkDivideByZero((Location<Operator>) expr, context); break; case Bytecode.OPCODE_arrayindex: checkIndexOutOfBounds((Location<Operator>) expr, context); break; case Bytecode.OPCODE_arraygen: checkArrayGeneratorLength((Location<Operator>) expr, context); break; } } catch (InternalFailure e) { throw e; } catch (Throwable e) { throw new InternalFailure(e.getMessage(), decl.parent().getEntry(), expr, e); } } private void checkInvokePreconditions(Location<Invoke> expr, Context context) throws Exception { WyilFile.Declaration declaration = expr.getEnclosingTree().getEnclosingDeclaration(); Bytecode.Invoke bytecode = expr.getBytecode(); Type[] parameterTypes = bytecode.type().params(); // WyilFile.FunctionOrMethodOrProperty fm = lookupFunctionOrMethodOrProperty(bytecode.name(), bytecode.type(), expr); int numPreconditions = fm.getPrecondition().size(); // // There is at least one precondition for the function/method being // called. Therefore, we need to generate a verification condition // which will check that the precondition holds. // Expr[] arguments = translateExpressions(expr.getOperands(), context.getEnvironment()); String prefix = bytecode.name().name() + "_requires_"; // Finally, generate an appropriate verification condition to check // each precondition clause for (int i = 0; i != numPreconditions; ++i) { // FIXME: name needs proper path information WyalFile.Name name = new WyalFile.Name(new WyalFile.Identifier(prefix + i)); Expr clause = new Expr.Invoke(null, name, null, arguments); context.emit(new VerificationCondition("precondition not satisfied", context.assumptions, clause, expr.attributes())); } // Perform parameter checks for(int i=0;i!=parameterTypes.length;++i) { generateTypeInvariantCheck(parameterTypes[i],arguments[i],context); } } private void checkDivideByZero(Location<Operator> expr, Context context) { Expr rhs = translateExpression(expr.getOperand(1), null, context.getEnvironment()); Value zero = new Value.Int(BigInteger.ZERO); Expr.Constant constant = new Expr.Constant(zero); Expr neqZero = new Expr.NotEqual(rhs, constant); // context.emit(new VerificationCondition("division by zero", context.assumptions, neqZero, expr.attributes())); } private void checkIndexOutOfBounds(Location<Operator> expr, Context context) { Expr src = translateExpression(expr.getOperand(0), null, context.getEnvironment()); Expr idx = translateExpression(expr.getOperand(1), null, context.getEnvironment()); Expr zero = new Expr.Constant(new Value.Int(BigInteger.ZERO)); Expr length = new Expr.ArrayLength(src); // Expr negTest = new Expr.GreaterThanOrEqual(idx, zero); Expr lenTest = new Expr.LessThan(idx, length); // context.emit(new VerificationCondition("index out of bounds (negative)", context.assumptions, negTest, expr.attributes())); context.emit(new VerificationCondition("index out of bounds (not less than length)", context.assumptions, lenTest, expr.attributes())); } private void checkArrayGeneratorLength(Location<Operator> expr, Context context) { Expr rhs = translateExpression(expr.getOperand(1), null, context.getEnvironment()); Value zero = new Value.Int(BigInteger.ZERO); Expr.Constant constant = new Expr.Constant(zero); Expr neqZero = new Expr.GreaterThanOrEqual(rhs, constant); // context.emit( new VerificationCondition("negative length possible", context.assumptions, neqZero, expr.attributes())); } private Context assumeExpressionPostconditions(Location<?> expr, Context context) { WyilFile.Declaration decl = expr.getEnclosingTree().getEnclosingDeclaration(); try { // First, propagate through all subexpressions for (int i = 0; i != expr.numberOfOperands(); ++i) { context = assumeExpressionPostconditions(expr.getOperand(i), context); } for (int i = 0; i != expr.numberOfOperandGroups(); ++i) { Location<?>[] group = expr.getOperandGroup(i); for (Location<?> e : group) { context = assumeExpressionPostconditions(e, context); } } switch (expr.getOpcode()) { case Bytecode.OPCODE_invoke: context = assumeInvokePostconditions((Location<Invoke>) expr, context); break; } return context; } catch (InternalFailure e) { throw e; } catch (Throwable e) { throw new InternalFailure(e.getMessage(), decl.parent().getEntry(), expr, e); } } private Context assumeInvokePostconditions(Location<Invoke> expr, Context context) throws Exception { WyilFile.Declaration declaration = expr.getEnclosingTree().getEnclosingDeclaration(); Bytecode.Invoke bytecode = expr.getBytecode(); // WyilFile.FunctionOrMethodOrProperty fmp = lookupFunctionOrMethodOrProperty(bytecode.name(), bytecode.type(), expr); if(fmp instanceof WyilFile.FunctionOrMethod) { WyilFile.FunctionOrMethod fm = (WyilFile.FunctionOrMethod) fmp; int numPostconditions = fm.getPostcondition().size(); // if (numPostconditions > 0) { // There is at least one postcondition for the function/method being // called. Therefore, we need to generate a verification condition // which will check that the precondition holds. // Type.FunctionOrMethod fmt = fm.type(); Expr[] parameters = translateExpressions(expr.getOperands(), context.getEnvironment()); Expr[] arguments = java.util.Arrays.copyOf(parameters, parameters.length + fm.type().returns().length); // for (int i = 0; i != fmt.returns().length; ++i) { Integer selector = fmt.returns().length > 1 ? i : null; arguments[parameters.length + i] = translateInvoke(expr, selector, context.getEnvironment()); } // String prefix = bytecode.name().name() + "_ensures_"; // Finally, generate an appropriate verification condition to check // each precondition clause for (int i = 0; i != numPostconditions; ++i) { // FIXME: name needs proper path information WyalFile.Name name = new WyalFile.Name(new WyalFile.Identifier(prefix + i)); Expr clause = new Expr.Invoke(null, name, null, arguments); context = context.assume(clause); } } } // return context; } private WyalFile.Stmt.Block translateAsBlock(Location<?> loc, LocalEnvironment environment) { WyalFile.Stmt stmt = translateAsStatement(loc, environment); return new WyalFile.Stmt.Block(stmt); } private WyalFile.Stmt translateAsStatement(Location<?> loc, LocalEnvironment environment) { return translateExpression(loc, null, environment); } // ========================================================================= // Expression // ========================================================================= private Expr[] translateExpressions(Location<?>[] loc, LocalEnvironment environment) { ArrayList<Expr> results = new ArrayList<>(); for (int i = 0; i != loc.length; ++i) { Type[] types = loc[i].getTypes(); if (types.length == 1) { results.add(translateExpression(loc[i], null, environment)); } else { for (int j = 0; j != types.length; ++j) { results.add(translateExpression(loc[i], j, environment)); } } } return results.toArray(new Expr[results.size()]); } /** * Transform a given bytecode location into its equivalent WyAL expression. * * @param location * The bytecode location to be translated * @return */ @SuppressWarnings("unchecked") private Expr translateExpression(Location<?> loc, Integer selector, LocalEnvironment environment) { WyilFile.Declaration decl = loc.getEnclosingTree().getEnclosingDeclaration(); Expr result; try { switch (loc.getOpcode()) { case Bytecode.OPCODE_const: result = translateConstant((Location<Const>) loc, environment); break; case Bytecode.OPCODE_convert: result = translateConvert((Location<Convert>) loc, environment); break; case Bytecode.OPCODE_fieldload: result = translateFieldLoad((Location<FieldLoad>) loc, environment); break; case Bytecode.OPCODE_indirectinvoke: result = translateIndirectInvoke((Location<IndirectInvoke>) loc, environment); break; case Bytecode.OPCODE_invoke: result = translateInvoke((Location<Invoke>) loc, selector, environment); break; case Bytecode.OPCODE_lambda: result = translateLambda((Location<Lambda>) loc, environment); break; case Bytecode.OPCODE_some: case Bytecode.OPCODE_all: result = translateQuantifier((Location<Quantifier>) loc, environment); break; case Bytecode.OPCODE_varmove: case Bytecode.OPCODE_varcopy: result = translateVariableAccess((Location<VariableAccess>) loc, environment); break; default: result = translateOperator((Location<Operator>) loc, environment); } } catch (InternalFailure e) { throw e; } catch (Throwable e) { throw new InternalFailure(e.getMessage(), decl.parent().getEntry(), loc, e); } result.attributes().addAll(loc.attributes()); return result; } private Expr translateConstant(Location<Const> expr, LocalEnvironment environment) { Bytecode.Const bytecode = expr.getBytecode(); // FIXME: the following is something of a hack to avoid having to // translate function pointers. However, it's not a general fix, and i'm // not sure what the right solution is. Perhaps having an "unknown // value" as the WyCS level might work (and be useful to replace // translateAsUnknown). if (bytecode.constant() instanceof Constant.FunctionOrMethod) { return translateAsUnknown(expr, environment); } return convert(bytecode.constant(), expr, environment); } private Expr translateConvert(Location<Convert> expr, LocalEnvironment environment) { // TODO: check whether need to do any more here return translateExpression(expr.getOperand(0), null, environment); } private Expr translateFieldLoad(Location<FieldLoad> expr, LocalEnvironment environment) { try { Bytecode.FieldLoad bytecode = expr.getBytecode(); Location<?> srcOperand = expr.getOperand(0); Type.EffectiveRecord er = typeSystem.expandAsEffectiveRecord(srcOperand.getType()); // Now, translate source expression Expr src = translateExpression(srcOperand, null, environment); // Generate field name identifier WyalFile.Identifier field = new WyalFile.Identifier(bytecode.fieldName()); // Done return new Expr.RecordAccess(src, field); } catch (ResolveError e) { SyntaxTree tree = expr.getEnclosingTree(); throw new InternalFailure(e.getMessage(), tree.getEnclosingDeclaration().parent().getEntry(), expr, e); } } private Expr translateIndirectInvoke(Location<IndirectInvoke> expr, LocalEnvironment environment) { // FIXME: need to implement this return translateAsUnknown(expr, environment); } private Expr translateInvoke(Location<Invoke> expr, Integer selector, LocalEnvironment environment) { Bytecode.Invoke bytecode = expr.getBytecode(); Expr[] operands = translateExpressions(expr.getOperands(), environment); // FIXME: name needs proper path information WyalFile.Name name = convert(bytecode.name()); return new Expr.Invoke(null, name, selector, operands); } private Expr translateLambda(Location<Lambda> expr, LocalEnvironment environment) { // FIXME: need to implement this return translateAsUnknown(expr, environment); } private Expr translateOperator(Location<Operator> expr, LocalEnvironment environment) { Bytecode.Operator bytecode = expr.getBytecode(); Bytecode.OperatorKind kind = bytecode.kind(); switch (kind) { case NOT: return translateNotOperator(expr, environment); case NEG: return translateArithmeticNegation(expr, environment); case ADD: case SUB: case MUL: case DIV: case REM: case EQ: case NEQ: case LT: case LTEQ: case GT: case GTEQ: case AND: case OR: return translateBinaryOperator(binaryOperatorMap.get(kind), expr, environment); case IS: return translateIs(expr, environment); case ARRAYINDEX: return translateArrayIndex(expr, environment); case ARRAYCONSTRUCTOR: return translateArrayInitialiser(expr, environment); case ARRAYGENERATOR: return translateArrayGenerator(expr, environment); case RECORDCONSTRUCTOR: return translateRecordInitialiser(expr, environment); case ARRAYLENGTH: return translateArrayLength(expr, environment); case DEREFERENCE: return translateDereference(expr, environment); case RIGHTSHIFT: case LEFTSHIFT: case BITWISEAND: case BITWISEOR: case BITWISEXOR: case BITWISEINVERT: case NEW: return translateAsUnknown(expr, environment); default: // FIXME: need to implement this throw new RuntimeException("Implement me! " + kind); } } private Expr translateNotOperator(Location<Operator> expr, LocalEnvironment environment) { Expr e = translateExpression(expr.getOperand(0), null, environment); return invertCondition(e, expr.getOperand(0)); } private Expr translateArithmeticNegation(Location<Operator> expr, LocalEnvironment environment) { Expr e = translateExpression(expr.getOperand(0), null, environment); return new Expr.Negation(e); } private Expr translateBinaryOperator(WyalFile.Opcode op, Location<Operator> expr, LocalEnvironment environment) { Expr lhs = translateExpression(expr.getOperand(0), null, environment); Expr rhs = translateExpression(expr.getOperand(1), null, environment); switch(op) { case EXPR_add: return new Expr.Addition(lhs, rhs); case EXPR_sub: return new Expr.Subtraction(lhs, rhs); case EXPR_mul: return new Expr.Multiplication(lhs, rhs); case EXPR_div: return new Expr.Division(lhs, rhs); case EXPR_rem: return new Expr.Remainder(lhs, rhs); case EXPR_eq: return new Expr.Equal(lhs, rhs); case EXPR_neq: return new Expr.NotEqual(lhs, rhs); case EXPR_lt: return new Expr.LessThan(lhs, rhs); case EXPR_lteq: return new Expr.LessThanOrEqual(lhs, rhs); case EXPR_gt: return new Expr.GreaterThan(lhs, rhs); case EXPR_gteq: return new Expr.GreaterThanOrEqual(lhs, rhs); case EXPR_and: return new Expr.LogicalAnd(lhs, rhs); case EXPR_or: return new Expr.LogicalOr(lhs, rhs); default: throw new RuntimeException("Internal failure --- dead code reached"); } } private Expr translateIs(Location<Operator> expr, LocalEnvironment environment) { Expr lhs = translateExpression(expr.getOperand(0), null, environment); Location<Const> rhs = (Location<Const>) expr.getOperand(1); Bytecode.Const bytecode = rhs.getBytecode(); Constant.Type constant = (Constant.Type) bytecode.constant(); WyalFile.Type typeTest = convert(constant.value(), environment.getParent().enclosingDeclaration); return new Expr.Is(lhs, typeTest); } private Expr translateArrayIndex(Location<Operator> expr, LocalEnvironment environment) { Expr lhs = translateExpression(expr.getOperand(0), null, environment); Expr rhs = translateExpression(expr.getOperand(1), null, environment); return new Expr.ArrayAccess(lhs, rhs); } private Expr translateArrayGenerator(Location<Operator> expr, LocalEnvironment environment) { Expr element = translateExpression(expr.getOperand(0), null, environment); Expr count = translateExpression(expr.getOperand(1), null, environment); environment = environment.write(expr.getIndex()); return new Expr.ArrayGenerator(element, count); } private Expr translateArrayInitialiser(Location<Operator> expr, LocalEnvironment environment) { Expr[] vals = translateExpressions(expr.getOperands(), environment); return new Expr.ArrayInitialiser(vals); } private Expr translateArrayLength(Location<Operator> expr, LocalEnvironment environment) { Expr e = translateExpression(expr.getOperand(0), null, environment); return new Expr.ArrayLength(e); } private Expr translateDereference(Location<Operator> expr, LocalEnvironment environment) { Expr e = translateExpression(expr.getOperand(0), null, environment); return new Expr.Dereference(e); } private Expr translateQuantifier(Location<Quantifier> expr, LocalEnvironment environment) { Bytecode.Quantifier bytecode = expr.getBytecode(); // Determine the type and names of each quantified variable. WyalFile.VariableDeclaration[] pattern = generateQuantifierTypePattern(expr, environment); // Apply quantifier ranges Expr ranges = generateQuantifierRanges(expr, environment); // Generate quantifier body Expr body = translateExpression(expr.getOperand(0), null, environment); // Generate quantifier expression switch (bytecode.kind()) { case ALL: body = new Expr.LogicalImplication(ranges, body); return new Expr.UniversalQuantifier(pattern, body); case SOME: default: body = new Expr.LogicalAnd(ranges, body); return new Expr.ExistentialQuantifier(pattern, body); } } private Expr translateRecordInitialiser(Location<Operator> expr, LocalEnvironment environment) { SyntaxTree tree = expr.getEnclosingTree(); WyilFile.Declaration decl = tree.getEnclosingDeclaration(); try { Type.EffectiveRecord t = typeSystem.expandAsEffectiveRecord(expr.getType()); String[] fields = t.getFieldNames(); Expr[] vals = translateExpressions(expr.getOperands(), environment); WyalFile.Pair<WyalFile.Identifier, Expr>[] pairs = new WyalFile.Pair[vals.length]; // for (int i = 0; i != vals.length; ++i) { pairs[i] = new WyalFile.Pair<>(new WyalFile.Identifier(fields[i]), vals[i]); } return new Expr.RecordInitialiser(pairs); } catch (ResolveError e) { throw new InternalFailure(e.getMessage(), decl.parent().getEntry(), expr, e); } } /** * Translating as unknown basically means we're not representing the * operation in question at the verification level. This could be something * that we'll implement in the future, or maybe not. * * @param expr * @param environment * @return */ private Expr translateAsUnknown(Location<?> expr, LocalEnvironment environment) { // What we're doing here is creating a completely fresh variable to // represent the return value. This is basically saying the return value // could be anything, and we don't care what. environment = environment.write(expr.getIndex()); WyalFile.VariableDeclaration r = environment.read(expr.getIndex()); return new Expr.VariableAccess(r); } /** * Generate a type pattern representing the type and name of all quantifier * variables described by this quantifier. * * @param expr * @return */ private WyalFile.VariableDeclaration[] generateQuantifierTypePattern(Location<Quantifier> expr, LocalEnvironment environment) { SyntaxTree tree = expr.getEnclosingTree(); WyilFile.Declaration decl = tree.getEnclosingDeclaration(); // WyalFile.VariableDeclaration[] vardecls = new WyalFile.VariableDeclaration[expr.numberOfOperandGroups()]; for (int i = 0; i != expr.numberOfOperandGroups(); ++i) { Location<?>[] group = expr.getOperandGroup(i); Location<VariableDeclaration> var = (Location<VariableDeclaration>) group[0]; vardecls[i] = environment.read(var.getIndex()); } return vardecls; } /** * Generate a logical conjunction which represents the given ranges of all * quantified variables. That is a conjunction of the form * <code>start <= var && var < end</code>. * * @return */ private Expr generateQuantifierRanges(Location<Quantifier> expr, LocalEnvironment environment) { Expr ranges = null; for (int i = 0; i != expr.numberOfOperandGroups(); ++i) { Location<?>[] group = expr.getOperandGroup(i); Location<VariableDeclaration> var = (Location<VariableDeclaration>) group[0]; WyalFile.VariableDeclaration varDecl = environment.read(var.getIndex()); Expr.VariableAccess varExpr = new Expr.VariableAccess(varDecl); Expr startExpr = translateExpression(group[1], null, environment); Expr endExpr = translateExpression(group[2], null, environment); Expr lhs = new Expr.LessThanOrEqual(startExpr, varExpr); Expr rhs = new Expr.LessThan(varExpr, endExpr); ranges = and(ranges, and(lhs, rhs)); } return ranges; } private Expr translateVariableAccess(Location<VariableAccess> expr, LocalEnvironment environment) { Location<?> decl = expr.getOperand(0); Bytecode bytecode = decl.getBytecode(); WyalFile.VariableDeclaration var; if (bytecode instanceof VariableDeclaration) { // In this case, we have a direct read of top-level variable. var = environment.read(decl.getIndex()); } else { // In this case, we are reading an alias of a top-level variable. We // need to record this information to preserve the equality between // these two variables later on. AliasDeclaration alias = (AliasDeclaration) bytecode; var = environment.readAlias(decl.getIndex(), alias.getOperand(0)); } // return new Expr.VariableAccess(var); } // ========================================================================= // Helpers // ========================================================================= /** * Construct an implication from one expression to another * * @param antecedent * @param consequent * @return */ private WyalFile.Stmt implies(WyalFile.Stmt antecedent, WyalFile.Stmt consequent) { if (antecedent == null) { return consequent; } else { WyalFile.Stmt.Block antecedentBlock = new WyalFile.Stmt.Block(antecedent); WyalFile.Stmt.Block consequentBlock = new WyalFile.Stmt.Block(consequent); return new WyalFile.Stmt.IfThen(antecedentBlock, consequentBlock); } } /** * Construct a conjunction of two expressions * * @param lhs * @param rhs * @return */ private WyalFile.Stmt and(WyalFile.Stmt lhs, WyalFile.Stmt rhs) { if (lhs == null) { return rhs; } else if (rhs == null) { return rhs; } else { return new WyalFile.Stmt.Block(lhs, rhs); } } private Expr and(Expr lhs, Expr rhs) { if (lhs == null) { return rhs; } else if (rhs == null) { return rhs; } else { return new Expr.LogicalAnd(lhs, rhs); } } /** * Construct a disjunct of two expressions * * @param lhs * @param rhs * @return */ private WyalFile.Stmt or(WyalFile.Stmt lhs, WyalFile.Stmt rhs) { if (lhs == null) { return rhs; } else if (rhs == null) { return rhs; } else { WyalFile.Stmt.Block lhsBlock = new WyalFile.Stmt.Block(lhs); WyalFile.Stmt.Block rhsBlock = new WyalFile.Stmt.Block(rhs); return new WyalFile.Stmt.CaseOf(lhsBlock,rhsBlock); } } /** * Join one or more descendant context's together. To understand this, * consider the following snippet, annotated with context information: * * <pre> * // Context: y >= 0 * if x >= 0: * x = x + 1 * // Context: y >= 0 && x >= 0 && x$1 == x + 1 * else: * x = -x * // Context: y >= 0 && x < 0 && x$2 == x + 1 * // * Context: ? * </pre> * * At this point, we have two goals in combining the contextual information * back together. Firstly, we want to factor out the parts common to both * (e.g. <code>y >= 0</code> above). Secondly, we need to determine the * appropriate version for variables modified on one or both branches (e.g. * <code>x</code> above). Thus, the joined context for the above would be: * * <pre> * y >= 0 && ((x >= 0 && x$1 == x + 1 && x$3 == x$1) || (x < 0 && x$2 == -x && x$3 == x$2)) * </pre> * * In the resulting environment, the current version of <code>x</code> would * then be <code>x$3</code>. To determine affected variables we simplify * identify any variable with a different version between at least two * context's. * * @param ancestor * Distinguished context for join, which is an ancestor of those * context's being joined. * @param descendants * Descendant context's being joined. Again, these maybe null for * branches which terminate (e.g. via return). * @return */ private Context joinDescendants(Context ancestor, Context[] descendants) { // Santity check parameters, as they maybe null. This happens in case of // branches which terminate (e.g. via return or break). descendants = removeNull(descendants); if (descendants.length == 0) { // In this case, the are actually no active descendants. Hence, the // resulting context is null to indicate no branches escape this // meet point. return null; } else if (descendants.length == 1) { // If there's only one, then we don't need to join it. return descendants[0]; } // LocalEnvironment joinedEnvironment = joinEnvironments(descendants); // AssumptionSet[] descendentAssumptions = new AssumptionSet[descendants.length]; for (int i = 0; i != descendants.length; ++i) { Context ithContext = descendants[i]; LocalEnvironment ithEnvironment = ithContext.environment; AssumptionSet ithAssumptions = ithContext.assumptions; descendentAssumptions[i] = updateVariableVersions(ithAssumptions, ithEnvironment, joinedEnvironment); } // AssumptionSet joinedAssumptions = ancestor.assumptions.joinDescendants(descendentAssumptions); // return new Context(ancestor.wyalFile, joinedAssumptions, joinedEnvironment, ancestor.initialEnvironment, ancestor.enclosingLoop, ancestor.verificationConditions); } private Context joinDescendants(Context ancestor, Context firstDescendant, List<Context> descendants1, List<Context> descendants2) { ArrayList<Context> descendants = new ArrayList<>(descendants1); descendants.addAll(descendants2); return joinDescendants(ancestor, firstDescendant, descendants); } private Context joinDescendants(Context ancestor, Context firstDescendant, List<Context> descendants) { Context[] ds = descendants.toArray(new Context[descendants.size() + 1]); ds[descendants.size()] = firstDescendant; return joinDescendants(ancestor, ds); } /** * Bring a given assumption set which is consistent with an original * environment up-to-date with a new environment. * * @param assumptions * The assumption set associated with a given context being * joined together. * @param original * The original environment associated with the given context. * This maps from location indices to version numbers and is * consistent with the given assumption set. * @param updated * The updated mapping from location indices to version numbers. * In many cases, these will be the same as in the original * environment. However, some versions will have been updated * because they were modified in one or more context's being * joined. In such case, the given assumption set must be brought * up to date with the new version numbers. * @return */ private AssumptionSet updateVariableVersions(AssumptionSet assumptions, LocalEnvironment original, LocalEnvironment updated) { for (Map.Entry<Integer, WyalFile.VariableDeclaration> e : updated.locals.entrySet()) { Integer varIndex = e.getKey(); WyalFile.VariableDeclaration newVarVersionedName = e.getValue(); WyalFile.VariableDeclaration oldVarVersionedName = original.read(varIndex); if (!oldVarVersionedName.equals(newVarVersionedName)) { // indicates a version change of the given variable. Expr.VariableAccess oldVar = new Expr.VariableAccess(oldVarVersionedName); Expr.VariableAccess newVar = new Expr.VariableAccess(newVarVersionedName); assumptions = assumptions.add(new Expr.Equal(newVar, oldVar)); } } return assumptions; } /** * Join the local environments of one or more context's together. This means * retaining variable versions which are the same for all context's, * allocating new versions for those which are different in at least one * case, and removing those which aren't present it at least one. * * @param contexts * Array of at least one non-null Context * @return */ private LocalEnvironment joinEnvironments(Context... contexts) { // Context head = contexts[0]; GlobalEnvironment global = head.getEnvironment().getParent(); HashSet<Integer> modified = new HashSet<>(); HashSet<Integer> deleted = new HashSet<>(); Map<Integer, WyalFile.VariableDeclaration> headLocals = head.environment.locals; // Compute the modified and deleted sets for (int i = 1; i < contexts.length; ++i) { Context ithContext = contexts[i]; Map<Integer, WyalFile.VariableDeclaration> ithLocals = ithContext.environment.locals; // First check env against head for (Map.Entry<Integer, WyalFile.VariableDeclaration> e : ithLocals.entrySet()) { Integer key = e.getKey(); WyalFile.VariableDeclaration s1 = e.getValue(); WyalFile.VariableDeclaration s2 = headLocals.get(key); if (s1 == null) { deleted.add(key); } else if (!s1.equals(s2)) { modified.add(key); } } // Second, check head against env for (Map.Entry<Integer, WyalFile.VariableDeclaration> e : headLocals.entrySet()) { Integer key = e.getKey(); WyalFile.VariableDeclaration s1 = e.getValue(); WyalFile.VariableDeclaration s2 = ithLocals.get(key); if (s1 == null) { deleted.add(key); } else if (!s1.equals(s2)) { modified.add(key); } } } // Finally, construct the combined local map HashMap<Integer, WyalFile.VariableDeclaration> combinedLocals = new HashMap<>(); for (Map.Entry<Integer, WyalFile.VariableDeclaration> e : headLocals.entrySet()) { Integer key = e.getKey(); WyalFile.VariableDeclaration value = e.getValue(); if (deleted.contains(key)) { // Ignore this entry. This must be checked before we look at // modified (since variable can be marked both). continue; } else if (modified.contains(key)) { // Update version number value = global.allocateVersion(key); } combinedLocals.put(key, value); } // Now, use the modified and deleted sets to build the new environment return new LocalEnvironment(global, combinedLocals); } /** * Construct a function or method prototype with a given name and type. The * function or method can then be called elsewhere as an uninterpreted * function. The function or method doesn't have a body but is used as a * name to be referred to from assertions. * * @param declaration * --- the function or method declaration in question * @param wyalFile * --- the file onto which this function is created. * @return */ private void createFunctionOrMethodPrototype(WyilFile.FunctionOrMethod declaration, WyalFile wyalFile) { SyntaxTree tree = declaration.getTree(); Type[] params = declaration.type().params(); Type[] returns = declaration.type().returns(); // WyalFile.VariableDeclaration[] parameters = new WyalFile.VariableDeclaration[params.length]; // second, set initial environment int loc = 0; for (int i = 0; i != params.length; ++i, ++loc) { Location<VariableDeclaration> var = (Location<VariableDeclaration>) tree.getLocation(loc); WyalFile.Type parameterType = convert(params[i], declaration); WyalFile.Identifier parameterName = new WyalFile.Identifier(var.getBytecode().getName()); parameters[i] = new WyalFile.VariableDeclaration(parameterType, parameterName); } WyalFile.VariableDeclaration[] wyalReturns = new WyalFile.VariableDeclaration[returns.length]; // second, set initial environment for (int i = 0; i != returns.length; ++i, ++loc) { Location<VariableDeclaration> var = (Location<VariableDeclaration>) tree.getLocation(loc); WyalFile.Type returnType = convert(returns[i], declaration); WyalFile.Identifier returnName = new WyalFile.Identifier(var.getBytecode().getName()); wyalReturns[i] = new WyalFile.VariableDeclaration(returnType, returnName); } // WyalFile.Identifier name = new WyalFile.Identifier(declaration.name()); wyalFile.allocate(new Declaration.Named.Function(name, parameters, wyalReturns)); } /** * Turn each verification condition into an assertion in the underlying * WyalFile being generated. The main challenge here is to ensure that all * variables used in the assertion are properly typed. * * @param declaration * The enclosing function or method declaration * @param vcs * The list of verification conditions which have been generated * @param environment * The global environment which maps all versioned variables to * their underlying locations. This is necessary to determine the * type of all free variables. * @param wyalFile * The WyAL file being generated */ private void createAssertions(WyilFile.FunctionOrMethod declaration, List<VerificationCondition> vcs, GlobalEnvironment environment, WyalFile wyalFile) { // FIXME: should be logged somehow? for (int i = 0; i != vcs.size(); ++i) { VerificationCondition vc = vcs.get(i); // Build the actual verification condition WyalFile.Stmt.Block verificationCondition = buildVerificationCondition(declaration, environment, vc); // Add generated verification condition as assertion WyalFile.Declaration.Assert assrt = new WyalFile.Declaration.Assert(verificationCondition, vc.description); assrt.attributes().addAll(vc.attributes()); wyalFile.allocate(assrt); } } /** * Construct a fully typed and quantified expression for representing a * verification condition. Aside from flattening the various components, it * must also determine appropriate variable types, including those for * aliased variables. * * @param vc * @param environment * @return */ public WyalFile.Stmt.Block buildVerificationCondition(WyilFile.FunctionOrMethod declaration, GlobalEnvironment environment, VerificationCondition vc) { WyalFile.Stmt antecedent = flatten(vc.antecedent); Expr consequent = vc.consequent; HashSet<WyalFile.VariableDeclaration> freeVariables = new HashSet<>(); freeVariables(antecedent,freeVariables); freeVariables(consequent,freeVariables); // Determine any variable aliases as necessary. Expr aliases = determineVariableAliases(environment, freeVariables); // Construct the initial condition WyalFile.Stmt verificationCondition = implies(and(aliases, antecedent), vc.consequent); // Now, generate type information for any free variables if (freeVariables.size() > 0) { // This indicates there are one or more free variables in the // verification condition. Hence, these must be universally // quantified to ensure the vc is well=typed. WyalFile.VariableDeclaration[] parameters = freeVariables .toArray(new WyalFile.VariableDeclaration[freeVariables.size()]); WyalFile.Stmt.Block qfBody = new WyalFile.Stmt.Block(verificationCondition); verificationCondition = new WyalFile.Stmt.UniversalQuantifier(parameters, qfBody); } // Done return new WyalFile.Stmt.Block(verificationCondition); } /** * Flatten a given assumption set into a single logical condition. The key * challenge here is to try and do this as efficiency as possible. * * @param assumptions * @return */ private WyalFile.Stmt flatten(AssumptionSet assumptions) { WyalFile.Stmt result = flattenUpto(assumptions, null); if (result == null) { return new Expr.Constant(new Value.Bool(true)); } else { return result; } } /** * Flatten an assumption set upto a given ancestor. That is, do not include * the ancestor or any of its ancestors in the results. This is a little * like taking the difference of the given assumptions and the given * ancestor's assumptions. * * @param assumptions * The assumption set to be flattened * @param ancestor * An ancestor of the given assumption set, or null to indicate * all ancestors should be included * @return */ private WyalFile.Stmt flattenUpto(AssumptionSet assumptions, AssumptionSet ancestor) { if (assumptions == ancestor) { // We have reached the ancestor return null; } else { // Flattern parent assumptions AssumptionSet[] parents = assumptions.parents; WyalFile.Stmt e = null; switch (parents.length) { case 0: // do nothing break; case 1: // easy e = flattenUpto(parents[0], ancestor); break; default: // harder AssumptionSet lca = assumptions.commonAncestor; WyalFile.Stmt factor = flattenUpto(lca, ancestor); for (int i = 0; i != parents.length; ++i) { e = or(e, flattenUpto(parents[i], lca)); } e = and(factor, e); } // Combine with local assumptions (if applicable) WyalFile.Stmt[] local = assumptions.assumptions; for (int i = 0; i != local.length; ++i) { e = and(e, local[i]); } // return e; } } /** * Determine any variable aliases which need to be accounted for. This is * done by adding an equality between the aliased variables to ensure they * have the same value. * * @param environment * @param freeVariables * @return */ private Expr determineVariableAliases(GlobalEnvironment environment, Set<WyalFile.VariableDeclaration> freeVariables) { Expr aliases = null; for (WyalFile.VariableDeclaration var : freeVariables) { WyalFile.VariableDeclaration parent = environment.getParent(var); if (parent != null) { // This indicates a variable alias, so construct the necessary // equality. Expr.VariableAccess lhs = new Expr.VariableAccess(var); Expr.VariableAccess rhs = new Expr.VariableAccess(parent); Expr aliasEquality = new Expr.Equal(lhs, rhs); // aliases = and(aliases, aliasEquality); } } return aliases; } /** * Packaged the set of free variables up into a type pattern (for now). * * @param declaration * The enclosing function or method declaration * @param environment * The global environment which maps all versioned variables to * their underlying locations. This is necessary to determine the * type of all free variables. * @param freeVariables * Set of free variables to allocate * @return */ private WyalFile.VariableDeclaration[] generateExpressionTypePattern(WyilFile.Declaration declaration, GlobalEnvironment environment, Set<WyalFile.VariableDeclaration> freeVariables) { WyalFile.VariableDeclaration[] patterns = new WyalFile.VariableDeclaration[freeVariables.size()]; int index = 0; for (WyalFile.VariableDeclaration var : freeVariables) { patterns[index++] = var; } return patterns; } /** * Convert the parameter types for a given function or method declaration * into a corresponding list of type patterns. This is primarily useful for * generating declarations from functions or method. * * @param params * @param declaration * @return */ private WyalFile.VariableDeclaration[] generatePreconditionParameters(WyilFile.FunctionOrMethodOrProperty declaration, LocalEnvironment environment) { Type[] params = declaration.type().params(); int[] parameterLocations = ArrayUtils.range(0, params.length); return generateParameterDeclarations(declaration, environment, parameterLocations); } /** * Convert the return types for a given function or method declaration into * a corresponding list of type patterns. This is primarily useful for * generating declarations from functions or method. * * @param params * @param declaration * @return */ private WyalFile.VariableDeclaration[] generatePostconditionTypePattern(WyilFile.FunctionOrMethod declaration, LocalEnvironment environment) { Type[] params = declaration.type().params(); Type[] returns = declaration.type().returns(); int[] parameterLocations = ArrayUtils.range(0, params.length); int[] returnLocations = ArrayUtils.range(parameterLocations.length, parameterLocations.length + returns.length); return generateParameterDeclarations(declaration, environment, parameterLocations, returnLocations); } /** * Convert the types of local variables in scope at a given position within * a function or method into a type pattern. This is primarily useful for * determining the types for a loop invariant macro. * * @param params * @param declaration * @return */ private WyalFile.VariableDeclaration[] generateLoopInvariantParameterDeclarations(WyilFile.Declaration declaration, Location<?>[] loopInvariant, LocalEnvironment environment) { int[] localVariableLocations = SyntaxTrees.determineUsedVariables(loopInvariant); return generateParameterDeclarations(declaration, environment, localVariableLocations); } /** * Convert a list of types from a given declaration into a corresponding * list of type patterns. This is primarily useful for generating * declarations from functions or method. * * @param types * @param declaration * @return */ private WyalFile.VariableDeclaration[] generateParameterDeclarations(WyilFile.Declaration declaration, LocalEnvironment environment, int[]... groups) { // SyntaxTree tree = declaration.getTree(); int[] locations = flattern(groups); WyalFile.VariableDeclaration[] patterns = new WyalFile.VariableDeclaration[locations.length]; // second, set initial environment for (int i = 0; i != locations.length; ++i) { Location<VariableDeclaration> var = (Location<VariableDeclaration>) tree.getLocation(locations[i]); patterns[i] = environment.read(var.getIndex()); } // return patterns; } /** * Convert a Name identifier from a WyIL into one suitable for a WyAL file. * * @param id * @return */ private static WyalFile.Name convert(NameID id) { Path.ID prefix = id.module(); WyalFile.Identifier[] components = new WyalFile.Identifier[prefix.size() + 1]; for (int i = 0; i != prefix.size(); ++i) { components[i] = new WyalFile.Identifier(prefix.get(i)); } components[prefix.size()] = new WyalFile.Identifier(id.name()); return new WyalFile.Name(components); } /** * Convert a WyIL constant into its equivalent WyCS constant. In some cases, * this is a direct translation. In other cases, WyIL constants are encoded * using more primitive WyCS values. * * @param c * --- The WyIL constant to be converted. * @param context * Additional contextual information associated with the point of * this conversion. These are used for debugging purposes to * associate any errors generated with a source line. * @return */ private Expr convert(Constant c, SyntaxTree.Location<?> context, LocalEnvironment environment) { Value v; if (c instanceof Constant.Null) { v = new WyalFile.Value.Null(); } else if (c instanceof Constant.Bool) { Constant.Bool cb = (Constant.Bool) c; v = new WyalFile.Value.Bool(cb.value()); } else if (c instanceof Constant.Byte) { Constant.Byte cb = (Constant.Byte) c; v = new WyalFile.Value.Int(cb.value()); } else if (c instanceof Constant.Integer) { Constant.Integer cb = (Constant.Integer) c; v = new WyalFile.Value.Int(cb.value()); } else if (c instanceof Constant.Array) { Constant.Array cb = (Constant.Array) c; List<Constant> cb_values = cb.values(); Expr[] items = new Expr[cb_values.size()]; for (int i = 0; i != cb_values.size(); ++i) { items[i] = convert(cb_values.get(i), context, environment); } return new Expr.ArrayInitialiser(items); } else if (c instanceof Constant.Record) { Constant.Record cr = (Constant.Record) c; HashMap<String, Constant> fields = cr.values(); WyalFile.Pair<WyalFile.Identifier, Expr>[] pairs = new WyalFile.Pair[fields.size()]; // int i = 0; for (Map.Entry<String, Constant> e : fields.entrySet()) { WyalFile.Expr val = convert(e.getValue(), context, environment); pairs[i++] = new WyalFile.Pair<>(new WyalFile.Identifier(e.getKey()), val); } return new Expr.RecordInitialiser(pairs); } else { // Constant.Lambda --- basically just treat as unknown variable return translateAsUnknown(context,environment); } // return new Expr.Constant(v); } /** * Convert a WyIL type into its equivalent WyCS type. In some cases, this is * a direct translation. In other cases, WyIL types are encoded using more * primitive WyCS types. * * @param type * The WyIL type to be converted. * @param context * Additional contextual information associated with the point of * this conversion. These are used for debugging purposes to * associate any errors generated with a source line. * @return */ private static WyalFile.Type convert(Type type, WyilFile.Block context) { // FIXME: this is fundamentally broken in the case of recursive types. // See Issue #298. WyalFile.Type result; if (type == Type.T_ANY) { result = new WyalFile.Type.Any(); } else if (type == Type.T_VOID) { result = new WyalFile.Type.Void(); } else if (type == Type.T_NULL) { result = new WyalFile.Type.Null(); } else if (type == Type.T_BOOL) { result = new WyalFile.Type.Bool(); } else if (type == Type.T_BYTE) { // FIXME: implement WyalFile.Type.Byte // return new WyalFile.Type.Byte(attributes(branch); result = new WyalFile.Type.Int(); } else if (type == Type.T_INT) { result = new WyalFile.Type.Int(); } else if (type instanceof Type.Array) { Type.Array lt = (Type.Array) type; WyalFile.Type element = convert(lt.element(), context); result = new WyalFile.Type.Array(element); } else if (type instanceof Type.Record) { Type.Record rt = (Type.Record) type; String[] names = rt.getFieldNames(); WyalFile.FieldDeclaration[] elements = new WyalFile.FieldDeclaration[names.length]; for (int i = 0; i != names.length; ++i) { String fieldName = names[i]; WyalFile.Type fieldType = convert(rt.getField(fieldName), context); elements[i] = new WyalFile.FieldDeclaration(fieldType, new WyalFile.Identifier(fieldName)); } result = new WyalFile.Type.Record(rt.isOpen(),elements); } else if (type instanceof Type.Reference) { Type.Reference lt = (Type.Reference) type; WyalFile.Type element = convert(lt.element(), context); result = new WyalFile.Type.Reference(element); } else if (type instanceof Type.Union) { Type.Union tu = (Type.Union) type; Type[] tu_elements = tu.bounds(); WyalFile.Type[] elements = new WyalFile.Type[tu_elements.length]; for (int i = 0; i != tu_elements.length; ++i) { elements[i] = convert(tu_elements[i], context); } result = new WyalFile.Type.Union(elements); } else if (type instanceof Type.Intersection) { Type.Intersection t = (Type.Intersection) type; Type[] t_elements = t.bounds(); WyalFile.Type[] elements = new WyalFile.Type[t_elements.length]; for (int i = 0; i != t_elements.length; ++i) { elements[i] = convert(t_elements[i], context); } result = new WyalFile.Type.Intersection(elements); } else if (type instanceof Type.Negation) { Type.Negation nt = (Type.Negation) type; WyalFile.Type element = convert(nt.element(), context); result = new WyalFile.Type.Negation(element); } else if (type instanceof Type.FunctionOrMethod) { Type.FunctionOrMethod ft = (Type.FunctionOrMethod) type; // FIXME: need to do something better here result = new WyalFile.Type.Any(); } else if (type instanceof Type.Nominal) { Type.Nominal nt = (Type.Nominal) type; NameID nid = nt.name(); Path.ID mid = nid.module(); result = new WyalFile.Type.Nominal(convert(nid)); } else { throw new InternalFailure("unknown type encountered (" + type.getClass().getName() + ")", context.parent().getEntry(), context); } // result.attributes().addAll(context.attributes()); // return result; } /** * Determine all free variables which are used within the given expression. * A free variable is one which is not bound within the expression itself. * * @param e * @param freeVars */ public void freeVariables(SyntacticItem e, Set<WyalFile.VariableDeclaration> freeVars) { if(e instanceof Expr.VariableAccess) { Expr.VariableAccess va = (Expr.VariableAccess)e; freeVars.add(va.getVariableDeclaration()); } else if(e instanceof Expr.Quantifier) { Expr.Quantifier q = (Expr.Quantifier) e; freeVariables(q.getBody(), freeVars); // Remove any bound variables for (WyalFile.VariableDeclaration vd : q.getParameters().getOperands()) { freeVars.remove(vd); } } else { for(int i=0;i!=e.size();++i) { SyntacticItem item = e.getOperand(i); if(item != null) { freeVariables(item,freeVars); } } } } /** * Lookup a given function or method. This maybe contained in the same file, * or in a different file. This may require loading that file in memory to * access this information. * * @param name * --- Fully qualified name of function * @param fun * --- Type of fucntion. * @param block * --- Enclosing block (for debugging purposes). * @param branch * --- Enclosing branch (for debugging purposes). * @return * @throws Exception */ public WyilFile.FunctionOrMethodOrProperty lookupFunctionOrMethodOrProperty(NameID name, Type.FunctionOrMethod fun, SyntaxTree.Location<?> stmt) throws Exception { SyntaxTree tree = stmt.getEnclosingTree(); WyilFile.Declaration decl = tree.getEnclosingDeclaration(); // Path.Entry<WyilFile> e = builder.project().get(name.module(), WyilFile.ContentType); if (e == null) { throw new InternalFailure(errorMessage(ErrorMessages.RESOLUTION_ERROR, name.module().toString()), decl.parent().getEntry(), stmt); } WyilFile m = e.read(); return m.functionOrMethodOrProperty(name.name(), fun); } /** * Generate the logically inverted expression corresponding to a given * comparator. For example, inverting "<=" gives ">", inverting "==" gives * "!=", etc. * * @param test * --- the binary comparator being inverted. * @return */ public Expr invertCondition(Expr expr, Location<?> elem) { if (expr instanceof Expr.Operator) { Expr.Operator binTest = (Expr.Operator) expr; switch (binTest.getOpcode()) { case EXPR_eq: return new Expr.NotEqual(binTest.getOperands()); case EXPR_neq: return new Expr.Equal(binTest.getOperands()); case EXPR_gteq: return new Expr.LessThan(binTest.getOperands()); case EXPR_gt: return new Expr.LessThanOrEqual(binTest.getOperands()); case EXPR_lteq: return new Expr.GreaterThan(binTest.getOperands()); case EXPR_lt: return new Expr.GreaterThanOrEqual(binTest.getOperands()); case EXPR_and: { Expr[] operands = invertConditions(binTest.getOperands(), elem); return new Expr.LogicalOr(operands); } case EXPR_or: { Expr[] operands = invertConditions(binTest.getOperands(), elem); return new Expr.LogicalAnd(operands); } } } else if (expr instanceof Expr.Is) { Expr.Is ei = (Expr.Is) expr; WyalFile.Type type = ei.getTestType(); return new Expr.Is(ei.getTestExpr(), new WyalFile.Type.Negation(type)); } // Otherwise, compare against false // FIXME: this is just wierd and needs to be fixed. return new Expr.LogicalNot(expr); } public Expr[] invertConditions(Expr[] expr, Location<?> elem) { Expr[] rs = new Expr[expr.length]; for (int i = 0; i != expr.length; ++i) { rs[i] = invertCondition(expr[i], elem); } return rs; } /** * Create exact copy of a given array, but with evey null element removed. * * @param items * @return */ private static <T> T[] removeNull(T[] items) { int count = 0; for (int i = 0; i != items.length; ++i) { if (items[i] == null) { count = count + 1; } } if (count == 0) { return items; } else { T[] rs = java.util.Arrays.copyOf(items, items.length - count); for (int i = 0, j = 0; i != items.length; ++i) { T item = items[i]; if (item != null) { rs[j++] = item; } } return rs; } } public static int[] flattern(int[][] groups) { int length = 0; for (int i = 0; i != groups.length; ++i) { length += groups[i].length; } // int[] result = new int[length]; for (int i = 0, j = 0; i != groups.length; ++i) { int[] group = groups[i]; System.arraycopy(group, 0, result, j, group.length); j = j + group.length; } // return result; } // ============================================================= // Assumptions // ============================================================= /** * Provides an immutable assumption set which (in principle) can be factored * more precisely than a flat collection. * * @author David J. Pearce * */ private static class AssumptionSet { /** * The least common ancestor for all parents sets. A parent may be the * common ancestor. Making this explicit is not strictly necessary, but * helps with the flattening process. */ private final AssumptionSet commonAncestor; /** * The set of parent sets from which this assumption set is derived. */ private final AssumptionSet[] parents; /** * The set of assumptions explicitly provided by this assumption set. * The complete set of assumptions includes those of the parents as * well. */ private final WyalFile.Stmt[] assumptions; private AssumptionSet(AssumptionSet commonAncestor, AssumptionSet[] parents, WyalFile.Stmt... assumptions) { this.commonAncestor = commonAncestor; this.parents = parents; this.assumptions = assumptions; } public AssumptionSet add(WyalFile.Stmt... assumptions) { return new AssumptionSet(this, new AssumptionSet[] { this }, assumptions); } public AssumptionSet joinDescendants(AssumptionSet... descendants) { if (descendants.length == 1) { return descendants[0]; } else { return new AssumptionSet(this, descendants); } } public static final AssumptionSet ROOT = new AssumptionSet(null, new AssumptionSet[0]); } // ============================================================= // Verification Conditions // ============================================================= /** * Provides a simple structure representing a verification condition. This * will be turned into an assertion of some form. A verification is always * of the form "X ==> Y", where X is the "antecedent" and Y the * "consequent". More specifically, X represents the knowledge known at the * given point and Y is the condition we are attempting to assert. * * @author David J. Pearce * */ private static class VerificationCondition extends SyntacticElement.Impl { private final String description; private final AssumptionSet antecedent; private final Expr consequent; private VerificationCondition(String description, AssumptionSet antecedent, Expr consequent, List<Attribute> attributes) { super(attributes); this.description = description; this.antecedent = antecedent; this.consequent = consequent; } } // ============================================================= // Environments // ============================================================= /** * The global environment provides a global allocation of "versioned" * variables. This ensures that across any related set of environments, no * version clashes are possible between variables of the same name. This * also means that we can determine the underlying location that each * variable corresponds to. * * @author David J. Pearce * */ private class GlobalEnvironment { /** * Provides a link back to the enclosing declaration. This is necessary * to enable us to convert location indices into variable names and * types. */ private final WyilFile.Declaration enclosingDeclaration; /** * Maps versioned variable strings to their underlying location index. A * version variable string is of the form "x$1" where "x" is the * variable name and "1" the version number. */ private final Map<String, Integer> allocation; /** * Maps aliased variables to their parent variable. That is the variable * which is being aliased. */ private final Map<WyalFile.VariableDeclaration, WyalFile.VariableDeclaration> parents; /** * Provides a global mapping of all local variable names to the next * unused version numbers. This is done with variable names rather than * location indices because it is possible two have different variables * with the same name, */ private final Map<String, Integer> versions; public GlobalEnvironment(WyilFile.Declaration enclosingDeclaration) { this.enclosingDeclaration = enclosingDeclaration; this.allocation = new HashMap<>(); this.parents = new HashMap<>(); this.versions = new HashMap<>(); } /** * Get the parent for a potential variable alias, or null if there is no * alias. * * @param alias * @return */ public WyalFile.VariableDeclaration getParent(WyalFile.VariableDeclaration alias) { return parents.get(alias); } /** * Get the location index from a versioned variable name of the form * "x$1" * * @param versionedVariable * @return */ public int resolve(String versionedVariable) { return allocation.get(versionedVariable); } /** * Allocation a new versioned variable name of the form "x$1" for a * given location index * * @param index * @return */ public WyalFile.VariableDeclaration allocateVersion(int index) { SyntaxTree tree = enclosingDeclaration.getTree(); Location<?> loc = tree.getLocation(index); WyalFile.Type type = convert(loc.getType(), enclosingDeclaration); Bytecode bytecode = loc.getBytecode(); String name; if (bytecode instanceof Bytecode.VariableDeclaration) { Bytecode.VariableDeclaration v = (Bytecode.VariableDeclaration) bytecode; name = v.getName(); } else if (bytecode instanceof Bytecode.AliasDeclaration) { Location<Bytecode.VariableDeclaration> v = getVariableDeclaration(loc); // This indicates an alias declaration name = v.getBytecode().getName(); } else { // This indicates an unnamed location name = "$" + index; } // Allocate a new version number for this variable Integer version = versions.get(name); String versionedVar; if (version == null) { version = 0; // Variables with version 0 just take the original name. This is // not necessary, but it makes for slightly nicer verification // conditions. versionedVar = name; } else { version = version + 1; versionedVar = name + "$" + version; } versions.put(name, version); // Create the versioned variable name and remember which location it // corresponds to. allocation.put(versionedVar, index); // return new WyalFile.VariableDeclaration(type, new WyalFile.Identifier(versionedVar)); } /** * Add a new variable alias for a variable to its parent * * @param leftVar * @param rightVar */ public void addVariableAlias(WyalFile.VariableDeclaration alias, WyalFile.VariableDeclaration parent) { parents.put(alias, parent); } } /** * The local environment provides a mapping from local variables in the * current scope to their current version number. Local environments are * transitively immutable objects, except for the global environment they * refer to. * * @author David J. Pearce * */ private class LocalEnvironment { /** * Provides access to the global environment */ private final GlobalEnvironment global; /** * Maps all local variables in scope to their current versioned variable * names */ private final Map<Integer, WyalFile.VariableDeclaration> locals; public LocalEnvironment(GlobalEnvironment global) { this.global = global; this.locals = new HashMap<>(); } public LocalEnvironment(GlobalEnvironment global, Map<Integer, WyalFile.VariableDeclaration> locals) { this.global = global; this.locals = new HashMap<>(locals); } /** * Get the enclosing global environment for this local environment * * @return */ public GlobalEnvironment getParent() { return global; } /** * Read the current versioned variable name for a given location index. * * @param index * @return */ public WyalFile.VariableDeclaration read(int index) { WyalFile.VariableDeclaration vv = locals.get(index); if (vv == null) { vv = global.allocateVersion(index); locals.put(index, vv); } return vv; } /** * Read the current versioned variable name for a given (aliased) * location index. * * @param alias * The variable being read (which is an alias) * @param parent * The variable which this is an alias of * @return */ public WyalFile.VariableDeclaration readAlias(int alias, int parent) { WyalFile.VariableDeclaration aliasedVariable = read(alias); WyalFile.VariableDeclaration parentVariable = read(parent); global.addVariableAlias(aliasedVariable, parentVariable); return aliasedVariable; } /** * Create a new version for each variable in a sequence of variables. * This create a completely new local environment. * * @param index */ public LocalEnvironment write(int... indices) { LocalEnvironment nenv = new LocalEnvironment(global, locals); for (int i = 0; i != indices.length; ++i) { nenv.locals.put(indices[i], global.allocateVersion(indices[i])); } return nenv; } @Override public LocalEnvironment clone() { return new LocalEnvironment(global, locals); } } public Location<VariableDeclaration> getVariableDeclaration(Location<?> decl) { switch (decl.getOpcode()) { case Bytecode.OPCODE_aliasdecl: case Bytecode.OPCODE_varmove: case Bytecode.OPCODE_varcopy: return getVariableDeclaration(decl.getOperand(0)); case Bytecode.OPCODE_vardecl: case Bytecode.OPCODE_vardeclinit: return (Location<VariableDeclaration>) decl; default: throw new RuntimeException("internal failure --- dead code reached"); } } // ============================================================= // LoopScope // ============================================================= /** * Represents the enclosing "loop scope". This is needed for dealing with * break and continue statements. Basically, as a way of taking the context * at the point of the statement in question and moving it out to the * enclosing loop. * * @author David J. Pearce * */ private static class LoopScope { private List<Context> breakContexts; private List<Context> continueContexts; public LoopScope() { this.breakContexts = new ArrayList<>(); this.continueContexts = new ArrayList<>(); } public List<Context> breakContexts() { return breakContexts; } public List<Context> continueContexts() { return continueContexts; } public void addBreakContext(Context context) { breakContexts.add(context); } public void addContinueContext(Context context) { continueContexts.add(context); } } // ============================================================= // Context // ============================================================= /** * Represents a given translation context. * * @author David J. Pearce * */ private static class Context { /** * Represents the wyalfile being generated. This is useful if we want to * add macro definitions, etc. */ private final WyalFile wyalFile; /** * The list of generated verification conditions. */ private final List<VerificationCondition> verificationConditions; /** * The set of assumptions which are known to hold at a given point * during generation. */ private final AssumptionSet assumptions; /** * The local environment mapping variables to their current version * numbers */ private final LocalEnvironment environment; /** * The initial environment mapping variables to their initial version * numbers. This is useful for determining the "first" version of a variable. */ private final LocalEnvironment initialEnvironment; /** * A reference to the enclosing loop scope, or null if no such scope. */ private final LoopScope enclosingLoop; public Context(WyalFile wyalFile, AssumptionSet assumptions, LocalEnvironment environment, LocalEnvironment initial, LoopScope enclosingLoop, List<VerificationCondition> vcs) { this.wyalFile = wyalFile; this.assumptions = assumptions; this.environment = environment; this.initialEnvironment = initial; this.verificationConditions = vcs; this.enclosingLoop = enclosingLoop; } public WyilFile getEnclosingFile() { return environment.getParent().enclosingDeclaration.parent(); } /** * Get the local environment associated witht his context * * @return */ public LocalEnvironment getEnvironment() { return environment; } /** * Get the enclosing loop scope. * * @return */ public LoopScope getEnclosingLoopScope() { return enclosingLoop; } /** * Generate a new context from this one where a give condition is * assumed to hold. * * @param condition * @return */ public Context assume(WyalFile.Stmt... conditions) { AssumptionSet nAssumptions = assumptions.add(conditions); return new Context(wyalFile, nAssumptions, environment, initialEnvironment, enclosingLoop, verificationConditions); } /** * Emit a verification condition which ensures a given assertion holds * true in this translation context. * * @param vc * The verification condition to be emitted * @return */ public void emit(VerificationCondition vc) { verificationConditions.add(vc); } /** * Assign an expression to a given variable. This results in the version * number for that variable being increased. Thus, any historical * references to that variable in the set of assumptions remain valid. * * @param lhs * The index of the location being assigned * @param rhs * @return */ public Context write(int lhs, Expr rhs) { // Update version number of the assigned variable LocalEnvironment nEnvironment = environment.write(lhs); WyalFile.VariableDeclaration nVersionedVar = nEnvironment.read(lhs); // Update assumption sets to reflect the "assigment" Expr.VariableAccess var = new Expr.VariableAccess(nVersionedVar); Expr condition = new Expr.Equal(var, rhs); AssumptionSet nAssumptions = assumptions.add(condition); // return new Context(wyalFile, nAssumptions, nEnvironment, initialEnvironment, enclosingLoop, verificationConditions); } public WyalFile.VariableDeclaration read(Location<?> expr) { return environment.read(expr.getIndex()); } public WyalFile.VariableDeclaration readFirst(Location<?> expr) { return initialEnvironment.read(expr.getIndex()); } public Context havoc(int lhs) { LocalEnvironment nEnvironment = environment.write(lhs); // return new Context(wyalFile, assumptions, nEnvironment, initialEnvironment, enclosingLoop, verificationConditions); } /** * Havoc a number of variable accesses. This results in the version * numbers for those variables being increased. Thus, any historical * references to those variables in the set of assumptions remain valid. * * @param lhs * The variable accesses being havoced * @return */ public Context havoc(Location<?>... exprs) { // Update version number of the assigned variables int[] vars = new int[exprs.length]; for (int i = 0; i != exprs.length; ++i) { // At this point, we're assuming only variable accesses can be // havoced. However, potentially, it might make sense to open // this up a little. Location<VariableAccess> va = (Location<VariableAccess>) exprs[i]; vars[i] = va.getOperand(0).getIndex(); } LocalEnvironment nEnvironment = environment.write(vars); // done return new Context(wyalFile, assumptions, nEnvironment, initialEnvironment, enclosingLoop, verificationConditions); } /** * Construct a context within a given loop scope. * * @param scope * @return */ public Context newLoopScope(LoopScope scope) { return new Context(wyalFile, assumptions, environment, initialEnvironment, scope, verificationConditions); } } // ============================================================= // Operator Maps // ============================================================= /** * Maps unary bytecodes into unary expression opcodes. */ private static Map<Bytecode.OperatorKind, WyalFile.Opcode> unaryOperatorMap; /** * Maps binary bytecodes into binary expression opcodes. */ private static Map<Bytecode.OperatorKind, WyalFile.Opcode> binaryOperatorMap; static { // Configure operator maps. This is done using maps to ensure that // changes in one operator kind does not have knock-on effects. This // used to be a problem when an array was used to implement the mapping. // ===================================================================== // Unary operator map // ===================================================================== unaryOperatorMap = new HashMap<>(); // Arithmetic unaryOperatorMap.put(Bytecode.OperatorKind.NEG, WyalFile.Opcode.EXPR_neg); // Logical unaryOperatorMap.put(Bytecode.OperatorKind.NOT, WyalFile.Opcode.EXPR_not); // Array unaryOperatorMap.put(Bytecode.OperatorKind.ARRAYLENGTH, WyalFile.Opcode.EXPR_arrlen); // ===================================================================== // Binary operator map // ===================================================================== binaryOperatorMap = new HashMap<>(); // Arithmetic binaryOperatorMap.put(Bytecode.OperatorKind.ADD, WyalFile.Opcode.EXPR_add); binaryOperatorMap.put(Bytecode.OperatorKind.SUB, WyalFile.Opcode.EXPR_sub); binaryOperatorMap.put(Bytecode.OperatorKind.MUL, WyalFile.Opcode.EXPR_mul); binaryOperatorMap.put(Bytecode.OperatorKind.DIV, WyalFile.Opcode.EXPR_div); binaryOperatorMap.put(Bytecode.OperatorKind.REM, WyalFile.Opcode.EXPR_rem); // Equality binaryOperatorMap.put(Bytecode.OperatorKind.EQ, WyalFile.Opcode.EXPR_eq); binaryOperatorMap.put(Bytecode.OperatorKind.NEQ, WyalFile.Opcode.EXPR_neq); // Relational binaryOperatorMap.put(Bytecode.OperatorKind.LT, WyalFile.Opcode.EXPR_lt); binaryOperatorMap.put(Bytecode.OperatorKind.GT, WyalFile.Opcode.EXPR_gt); binaryOperatorMap.put(Bytecode.OperatorKind.LTEQ, WyalFile.Opcode.EXPR_lteq); binaryOperatorMap.put(Bytecode.OperatorKind.GTEQ, WyalFile.Opcode.EXPR_gteq); // Logical binaryOperatorMap.put(Bytecode.OperatorKind.AND, WyalFile.Opcode.EXPR_and); binaryOperatorMap.put(Bytecode.OperatorKind.OR, WyalFile.Opcode.EXPR_or); } }