// 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 wyc.builder; import wyc.lang.Expr; import wyc.lang.Stmt; import wyc.lang.WhileyFile; import wycc.util.Triple; import static wybs.lang.SyntaxError.*; import static wyil.util.ErrorMessages.VARIABLE_POSSIBLY_UNITIALISED; import static wyil.util.ErrorMessages.errorMessage; import java.util.HashSet; import java.util.List; import java.util.Map; import wybs.lang.SyntaxError; /** * <p> * Responsible for checking that all variables are defined before they are used. * The algorithm for checking this involves a depth-first search through the * control-flow graph of the method. Throughout this, a list of the defined * variables is maintained. For example: * </p> * * <pre> * function f() -> int: * int z * return z + 1 * </pre> * * <p> * In the above example, variable z is used in the return statement before it * has been defined any value. This is considered a syntax error in whiley. * </p> * * * @author David J. Pearce * */ public class DefiniteAssignmentAnalysis { /** * The whiley source file being checked for definite assignment. */ private final WhileyFile file; public DefiniteAssignmentAnalysis(WhileyFile file) { this.file = file; } public void check() { for (WhileyFile.Declaration d : file.declarations) { check(d); } } /** * Perform definite assignment analysis on a single declaration. * * @param declaration */ private void check(WhileyFile.Declaration declaration) { if(declaration instanceof WhileyFile.Import) { // There isn't anything to do here. This is because imports cannot // use variables anyway. } else if(declaration instanceof WhileyFile.Constant) { // There isn't anything to do here. This is because constants cannot // use variables anyway. } else if(declaration instanceof WhileyFile.Type) { // There isn't anything to do here either. This is because variables // used in type invariants are already checked by the // FlowTypeChecker to ensure they are declared. } else if(declaration instanceof WhileyFile.FunctionOrMethodOrProperty) { check((WhileyFile.FunctionOrMethodOrProperty) declaration); } else { throw new InternalFailure("unknown declaration encountered",file.getEntry(),declaration); } } /** * Check a function or method declaration for definite assignment. * * @param declaration * @return */ private void check(WhileyFile.FunctionOrMethodOrProperty declaration) { // Initialise set of definitely assigned variables to include all // parameters. DefintelyAssignedSet defs = new DefintelyAssignedSet(); for(WhileyFile.Parameter p : declaration.parameters) { defs = defs.add(p.name()); } // Iterate through each statement in the body of the function or method, // updating the set of definitely assigned variables as appropriate. checkStatements(declaration.statements,defs); } /** * Check that all variables used in a given list of statements are * definitely assigned. Furthermore, update the set of definitely assigned * variables to include any which are definitely assigned at the end of * these statements. * * @param statements * The list of statements to check. * @param environment * The set of variables which are definitely assigned. */ private ControlFlow checkStatements(List<Stmt> statements, DefintelyAssignedSet environment) { DefintelyAssignedSet nextEnvironment = environment; DefintelyAssignedSet breakEnvironment = null; for (Stmt s : statements) { ControlFlow nf = checkStatement(s, nextEnvironment); nextEnvironment = nf.nextEnvironment; breakEnvironment = join(breakEnvironment,nf.breakEnvironment); } return new ControlFlow(nextEnvironment,breakEnvironment); } /** * Check that all variables used in a given statement are definitely * assigned. Furthermore, update the set of definitely assigned variables to * include any which are definitely assigned after this statement. * * @param statement * The statement to check. * @param environment * The set of variables which are definitely assigned. * @return The updated set of variables which are now definitely assigned, * or null if the method has terminated. */ private ControlFlow checkStatement(Stmt statement, DefintelyAssignedSet environment) { try { if(statement instanceof Stmt.Assert) { return checkAssert((Stmt.Assert) statement, environment); } else if(statement instanceof Stmt.Assign) { return checkAssign((Stmt.Assign) statement, environment); } else if(statement instanceof Stmt.Assume) { return checkAssume((Stmt.Assume) statement, environment); } else if(statement instanceof Stmt.Break) { return checkBreak((Stmt.Break) statement, environment); } else if(statement instanceof Stmt.Continue) { return checkContinue((Stmt.Continue) statement, environment); } else if(statement instanceof Stmt.Debug) { return checkDebug((Stmt.Debug) statement, environment); } else if(statement instanceof Stmt.DoWhile) { return checkDoWhile((Stmt.DoWhile) statement, environment); } else if(statement instanceof Stmt.Fail) { return check((Stmt.Fail) statement, environment); } else if(statement instanceof Expr.FunctionOrMethodCall) { return checkFunctionOrMethodCall((Expr.FunctionOrMethodCall) statement, environment); } else if(statement instanceof Stmt.IfElse) { return checkIfElse((Stmt.IfElse) statement, environment); } else if(statement instanceof Expr.IndirectFunctionOrMethodCall) { return checkIndirectFunctionOrMethodCall((Expr.IndirectFunctionOrMethodCall) statement, environment); } else if(statement instanceof Stmt.NamedBlock) { return checkNamedBlock((Stmt.NamedBlock) statement, environment); } else if(statement instanceof Stmt.Return) { return checkReturn((Stmt.Return) statement, environment); } else if(statement instanceof Stmt.Skip) { return checkSkip((Stmt.Skip) statement, environment); } else if(statement instanceof Stmt.Switch) { return checkSwitch((Stmt.Switch) statement, environment); } else if(statement instanceof Stmt.VariableDeclaration) { return checkVariableDeclaration((Stmt.VariableDeclaration) statement, environment); } else if(statement instanceof Stmt.While) { return checkWhile((Stmt.While) statement, environment); } else { throw new InternalFailure("unknown statement encountered",file.getEntry(),statement); } } catch(SyntaxError e) { throw e; } catch(Throwable t) { throw new InternalFailure(t.getMessage(),file.getEntry(),statement,t); } } private ControlFlow checkAssert(Stmt.Assert stmt, DefintelyAssignedSet environment) { checkExpression(stmt.expr, environment); return new ControlFlow(environment,null); } private ControlFlow checkAssign(Stmt.Assign stmt, DefintelyAssignedSet environment) { // left-hand side for (Expr lval : stmt.lvals) { if (lval instanceof Expr.LocalVariable) { // Skip local variables since they are being assigned } else { checkExpression(lval, environment); } } // right-hand side for (Expr rval : stmt.rvals) { checkExpression(rval, environment); } // Update the environment as necessary for (Expr lval : stmt.lvals) { if (lval instanceof Expr.LocalVariable) { Expr.LocalVariable lv = (Expr.LocalVariable) lval; environment = environment.add(lv.var); } } // return new ControlFlow(environment, null); } private ControlFlow checkAssume(Stmt.Assume stmt, DefintelyAssignedSet environment) { checkExpression(stmt.expr, environment); return new ControlFlow(environment,null); } private ControlFlow checkBreak(Stmt.Break stmt, DefintelyAssignedSet environment) { return new ControlFlow(null,environment); } private ControlFlow checkContinue(Stmt.Continue stmt, DefintelyAssignedSet environment) { // Here we can just treat a continue in the same way as a return // statement. It makes no real difference. return new ControlFlow(null,null); } private ControlFlow checkDebug(Stmt.Debug stmt, DefintelyAssignedSet environment) { checkExpression(stmt.expr, environment); return new ControlFlow(environment,null); } private ControlFlow checkDoWhile(Stmt.DoWhile stmt, DefintelyAssignedSet environment) { // ControlFlow flow = checkStatements(stmt.body, environment); // checkExpression(stmt.condition, flow.nextEnvironment); // for(Expr e : stmt.invariants) { checkExpression(e,flow.nextEnvironment); } // environment = join(flow.nextEnvironment,flow.breakEnvironment); // return new ControlFlow(environment,null); } private ControlFlow check(Stmt.Fail stmt, DefintelyAssignedSet environment) { return new ControlFlow(null,null); } private ControlFlow checkIfElse(Stmt.IfElse stmt, DefintelyAssignedSet environment) { checkExpression(stmt.condition, environment); // ControlFlow left = checkStatements(stmt.trueBranch, environment); ControlFlow right = checkStatements(stmt.falseBranch, environment); // Now, merge all generated control-flow paths together return left.merge(right); } private ControlFlow checkNamedBlock(Stmt.NamedBlock stmt, DefintelyAssignedSet environment) { return checkStatements(stmt.body,environment); } private ControlFlow checkReturn(Stmt.Return stmt, DefintelyAssignedSet environment) { for(Expr e : stmt.returns) { checkExpression(e, environment); } return new ControlFlow(null,null); } private ControlFlow checkSkip(Stmt.Skip stmt, DefintelyAssignedSet environment) { return new ControlFlow(environment,null); } private ControlFlow checkSwitch(Stmt.Switch stmt, DefintelyAssignedSet environment) { DefintelyAssignedSet caseEnvironment = null; DefintelyAssignedSet breakEnvironment = null; checkExpression(stmt.expr, environment); // boolean hasDefault = false; for(Stmt.Case c : stmt.cases) { ControlFlow cf = checkStatements(c.stmts, environment); caseEnvironment = join(caseEnvironment,cf.nextEnvironment); breakEnvironment = join(breakEnvironment,cf.breakEnvironment); if(c.expr.isEmpty()) { hasDefault = true; } } // if(hasDefault) { // Having a default makes a big difference. Without one, then // everything that wasn't defined beforehand remains undefined. So, // it's only in the case that there is a default statement that // individual case statements can have an effect on the resulting // set of definitely assigned variables. environment = caseEnvironment; } // return new ControlFlow(environment,breakEnvironment); } private ControlFlow checkVariableDeclaration(Stmt.VariableDeclaration stmt, DefintelyAssignedSet environment) { if (stmt.expr != null) { checkExpression(stmt.expr, environment); environment = environment.add(stmt.parameter.name); } return new ControlFlow(environment,null); } private ControlFlow checkWhile(Stmt.While stmt, DefintelyAssignedSet environment) { checkExpression(stmt.condition, environment); // for(Expr e : stmt.invariants) { checkExpression(e,environment); } // checkStatements(stmt.body, environment); // return new ControlFlow(environment,null); } /** * Check that all variables used in a given expression are definitely * assigned. * * @param expr * The expression to check. * @param environment * The set of variables which are definitely assigned. */ private void checkExpression(Expr expression, DefintelyAssignedSet environment) { try { if(expression instanceof Expr.ArrayInitialiser) { checkArrayInitialiser((Expr.ArrayInitialiser) expression, environment); } else if(expression instanceof Expr.ArrayGenerator) { checkArrayGenerator((Expr.ArrayGenerator) expression, environment); } else if(expression instanceof Expr.BinOp) { checkBinOp((Expr.BinOp) expression, environment); } else if(expression instanceof Expr.Cast) { checkCast((Expr.Cast) expression, environment); } else if(expression instanceof Expr.Constant) { checkConstant((Expr.Constant) expression, environment); } else if(expression instanceof Expr.ConstantAccess) { checkConstantAccess((Expr.ConstantAccess) expression, environment); } else if(expression instanceof Expr.Dereference) { checkDereference((Expr.Dereference) expression, environment); } else if(expression instanceof Expr.FieldAccess) { checkFieldAccess((Expr.FieldAccess) expression, environment); } else if(expression instanceof Expr.FunctionOrMethod) { checkFunctionOrMethod((Expr.FunctionOrMethod) expression, environment); } else if(expression instanceof Expr.FunctionOrMethodCall) { checkFunctionOrMethodCall((Expr.FunctionOrMethodCall) expression, environment); } else if(expression instanceof Expr.IndexOf) { checkIndexOf((Expr.IndexOf) expression, environment); } else if(expression instanceof Expr.IndirectFunctionOrMethodCall) { checkIndirectFunctionOrMethodCall((Expr.IndirectFunctionOrMethodCall) expression, environment); } else if(expression instanceof Expr.Lambda) { checkLambda((Expr.Lambda) expression, environment); } else if(expression instanceof Expr.LocalVariable) { checkLocalVariable((Expr.LocalVariable) expression, environment); } else if(expression instanceof Expr.New) { checkNew((Expr.New) expression, environment); } else if(expression instanceof Expr.Quantifier) { checkQuantifier((Expr.Quantifier) expression, environment); } else if(expression instanceof Expr.Record) { checkRecord((Expr.Record) expression, environment); } else if(expression instanceof Expr.TypeVal) { checkTypeVal((Expr.TypeVal) expression, environment); } else if(expression instanceof Expr.UnOp) { checkUnOp((Expr.UnOp) expression, environment); } else { throw new InternalFailure("unknown expression encountered",file.getEntry(),expression); } } catch(SyntaxError e) { throw e; } catch(Throwable t) { throw new InternalFailure("internal failure",file.getEntry(),expression,t); } } private void checkArrayInitialiser(Expr.ArrayInitialiser expression, DefintelyAssignedSet environment) { for(Expr e : expression.arguments) { checkExpression(e,environment); } } private void checkArrayGenerator(Expr.ArrayGenerator expression, DefintelyAssignedSet environment) { checkExpression(expression.element,environment); checkExpression(expression.count,environment); } private void checkBinOp(Expr.BinOp expression, DefintelyAssignedSet environment) { checkExpression(expression.lhs,environment); checkExpression(expression.rhs,environment); } private void checkCast(Expr.Cast expression, DefintelyAssignedSet environment) { checkExpression(expression.expr,environment); } private void checkConstant(Expr.Constant expression, DefintelyAssignedSet environment) { } private void checkConstantAccess(Expr.ConstantAccess expression, DefintelyAssignedSet environment) { } private void checkDereference(Expr.Dereference expression, DefintelyAssignedSet environment) { checkExpression(expression.src,environment); } private void checkFieldAccess(Expr.FieldAccess expression, DefintelyAssignedSet environment) { checkExpression(expression.src,environment); } private void checkFunctionOrMethod(Expr.FunctionOrMethod expression, DefintelyAssignedSet environment) { } private ControlFlow checkFunctionOrMethodCall(Expr.FunctionOrMethodCall expression, DefintelyAssignedSet environment) { for(Expr p : expression.arguments) { checkExpression(p,environment); } return new ControlFlow(environment,null); } private void checkIndexOf(Expr.IndexOf expression, DefintelyAssignedSet environment) { checkExpression(expression.src,environment); checkExpression(expression.index,environment); } private ControlFlow checkIndirectFunctionOrMethodCall(Expr.IndirectFunctionOrMethodCall expression, DefintelyAssignedSet environment) { checkExpression(expression.src,environment); for(Expr p : expression.arguments) { checkExpression(p,environment); } return new ControlFlow(environment,null); } private void checkLambda(Expr.Lambda expression, DefintelyAssignedSet environment) { // Add lambda parameters to the set of definitely assigned variables. for(WhileyFile.Parameter p : expression.parameters) { environment = environment.add(p.name()); } // Check body of the lambda checkExpression(expression.body,environment); } private void checkLocalVariable(Expr.LocalVariable expression, DefintelyAssignedSet environment) { if (!environment.contains(expression.var)) { throw new SyntaxError(errorMessage(VARIABLE_POSSIBLY_UNITIALISED), file.getEntry(), expression); } } private void checkNew(Expr.New expression, DefintelyAssignedSet environment) { checkExpression(expression.expr,environment); } private void checkQuantifier(Expr.Quantifier expression, DefintelyAssignedSet environment) { for(Triple<String,Expr,Expr> p : expression.sources) { checkExpression(p.second(),environment); checkExpression(p.third(),environment); environment = environment.add(p.first()); } checkExpression(expression.condition,environment); } private void checkRecord(Expr.Record expression, DefintelyAssignedSet environment) { for(Map.Entry<String,Expr> e : expression.fields.entrySet()) { checkExpression(e.getValue(),environment); } } private void checkTypeVal(Expr.TypeVal expression, DefintelyAssignedSet environment) { } private void checkUnOp(Expr.UnOp expression, DefintelyAssignedSet environment) { checkExpression(expression.mhs,environment); } private class ControlFlow { /** * The set of definitely assigned variables on this path which fall * through to the next logical statement. */ public final DefintelyAssignedSet nextEnvironment; /** * The set of definitely assigned variables on this path which are on * the control-flow path caused by a break statement. */ public final DefintelyAssignedSet breakEnvironment; public ControlFlow(DefintelyAssignedSet nextEnvironment, DefintelyAssignedSet breakEnvironment) { this.nextEnvironment = nextEnvironment; this.breakEnvironment = breakEnvironment; } public ControlFlow merge(ControlFlow other) { DefintelyAssignedSet n = join(nextEnvironment,other.nextEnvironment); DefintelyAssignedSet b = join(breakEnvironment,other.breakEnvironment); return new ControlFlow(n,b); } } /** * join two sets of definitely assigned variables together. This allows for * the possibility that either or both arguments are null. The join itself * is corresponds to the intersection of both sets. * * @param left * @param right * @return */ private static DefintelyAssignedSet join(DefintelyAssignedSet left, DefintelyAssignedSet right) { if(left == null && right == null) { return null; } else if(left == null) { return right; } else if(right == null) { return left; } else { return left.join(right); } } /** * A simple class representing an immutable set of definitely assigned * variables. * * @author David J. Pearce * */ private class DefintelyAssignedSet { private HashSet<String> variables; public DefintelyAssignedSet() { this.variables = new HashSet<String>(); } public DefintelyAssignedSet(DefintelyAssignedSet defs) { this.variables = new HashSet<String>(defs.variables); } public boolean contains(String var) { return variables.contains(var); } /** * Add a variable to the set of definitely assigned variables, producing * an updated set. * * @param var * @return */ public DefintelyAssignedSet add(String var) { DefintelyAssignedSet r = new DefintelyAssignedSet(this); r.variables.add(var); return r; } /** * Remove a variable from the set of definitely assigned variables, producing * an updated set. * * @param var * @return */ public DefintelyAssignedSet remove(String var) { DefintelyAssignedSet r = new DefintelyAssignedSet(this); r.variables.remove(var); return r; } /** * Join two sets together, where the result contains a variable only if * it is definitely assigned on both branches. * * @param other * @return */ public DefintelyAssignedSet join(DefintelyAssignedSet other) { DefintelyAssignedSet r = new DefintelyAssignedSet(); for (String var : variables) { if (other.contains(var)) { r.variables.add(var); } } return r; } /** * Useful for debugging */ @Override public String toString() { return variables.toString(); } } }