// 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 java.util.*;
import wybs.lang.Build;
import wybs.lang.SyntacticElement;
import wybs.lang.SyntaxError;
import wybs.lang.SyntaxError.InternalFailure;
import wyc.lang.Expr;
import wyc.lang.Stmt;
import wyc.lang.WhileyFile;
import wycc.util.Pair;
import wycc.util.Triple;
import wyil.lang.Type;
import static wyil.util.ErrorMessages.*;
/**
* <p>
* Performs a number of simplistic checks that a module is syntactically
* correct. This includes the following
* </p>
* <ul>
* <li><b>Functions cannot have side-effects</b>. This includes sending messages
* to actors, calling headless methods and spawning processes.</li>
* <li><b>Functions/Methods cannot throw exceptions unless they are
* declared</b>. Thus, if a method or function throws an exception an
* appropriate throws clause is required.
* <li><b>Every catch handler must catch something</b>. It is a syntax error if
* a catch handler exists which can never catch anything (i.e. it is dead-code).
* </li>
* </ul>
*
* @author David J. Pearce
*
*/
public class ModuleCheck {
private WhileyFile file;
public ModuleCheck(WhileyFile file) {
this.file = file;
}
public void check() {
for (WhileyFile.Declaration d : file.declarations) {
check(d);
}
}
public void check(WhileyFile.Declaration declaration) {
if(declaration instanceof WhileyFile.Type) {
checkTypeDeclaration((WhileyFile.Type) declaration);
} else if(declaration instanceof WhileyFile.Property) {
checkPropertyDeclaration((WhileyFile.Property) declaration);
} else if(declaration instanceof WhileyFile.Function) {
checkFunctionDeclaration((WhileyFile.Function) declaration);
} else if(declaration instanceof WhileyFile.Method) {
checkMethodDeclaration((WhileyFile.Method) declaration);
} else {
// Ignore others
}
}
private enum Context {
METHOD,
FUNCTION,
INVARIANT,
REQUIRES,
ENSURES,
ASSERTION
}
private void checkTypeDeclaration(WhileyFile.Type declaration) {
for(Expr e : declaration.invariant) {
checkExpression(e, Context.INVARIANT);
}
}
private void checkPropertyDeclaration(WhileyFile.Property declaration) {
for(Expr e : declaration.requires) {
checkExpression(e, Context.INVARIANT);
}
}
private void checkFunctionDeclaration(WhileyFile.Function declaration) {
for(Expr e : declaration.requires) {
checkExpression(e, Context.REQUIRES);
}
for(Expr e : declaration.ensures) {
checkExpression(e, Context.ENSURES);
}
checkStatements(declaration.statements,Context.FUNCTION);
}
private void checkMethodDeclaration(WhileyFile.Method declaration) {
for(Expr e : declaration.requires) {
checkExpression(e, Context.REQUIRES);
}
for(Expr e : declaration.ensures) {
checkExpression(e, Context.ENSURES);
}
checkStatements(declaration.statements,Context.METHOD);
}
private void checkStatements(List<Stmt> statements, Context context) {
for (Stmt s : statements) {
checkStatement(s, context);
}
}
private void checkStatement(Stmt statement, Context context) {
try {
if(statement instanceof Stmt.Assert) {
checkAssert((Stmt.Assert) statement, context);
} else if(statement instanceof Stmt.Assign) {
checkAssign((Stmt.Assign) statement, context);
} else if(statement instanceof Stmt.Assume) {
checkAssume((Stmt.Assume) statement, context);
} else if(statement instanceof Stmt.Break) {
checkBreak((Stmt.Break) statement, context);
} else if(statement instanceof Stmt.Continue) {
checkContinue((Stmt.Continue) statement, context);
} else if(statement instanceof Stmt.Debug) {
checkDebug((Stmt.Debug) statement, context);
} else if(statement instanceof Stmt.DoWhile) {
checkDoWhile((Stmt.DoWhile) statement, context);
} else if(statement instanceof Stmt.Fail) {
check((Stmt.Fail) statement, context);
} else if(statement instanceof Expr.FunctionOrMethodCall) {
checkFunctionOrMethodCall((Expr.FunctionOrMethodCall) statement, context);
} else if(statement instanceof Stmt.IfElse) {
checkIfElse((Stmt.IfElse) statement, context);
} else if(statement instanceof Expr.IndirectFunctionOrMethodCall) {
checkIndirectFunctionOrMethodCall((Expr.IndirectFunctionOrMethodCall) statement, context);
} else if(statement instanceof Stmt.NamedBlock) {
checkNamedBlock((Stmt.NamedBlock) statement, context);
} else if(statement instanceof Stmt.Return) {
checkReturn((Stmt.Return) statement, context);
} else if(statement instanceof Stmt.Skip) {
checkSkip((Stmt.Skip) statement, context);
} else if(statement instanceof Stmt.Switch) {
checkSwitch((Stmt.Switch) statement, context);
} else if(statement instanceof Stmt.VariableDeclaration) {
checkVariableDeclaration((Stmt.VariableDeclaration) statement, context);
} else if(statement instanceof Stmt.While) {
checkWhile((Stmt.While) statement, context);
} 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 void checkAssert(Stmt.Assert stmt, Context context) {
checkExpression(stmt.expr, context);
}
private void checkAssign(Stmt.Assign stmt, Context context) {
if(context != Context.METHOD) {
// left-hand side
for (Expr lval : stmt.lvals) {
if (lval instanceof Expr.LocalVariable) {
// Skip local variables since they are being assigned
} else {
checkExpression(lval, context);
}
}
// right-hand side
for (Expr rval : stmt.rvals) {
checkExpression(rval, context);
}
}
}
private void checkAssume(Stmt.Assume stmt, Context context) {
checkExpression(stmt.expr, Context.ASSERTION);
}
private void checkBreak(Stmt.Break stmt, Context context) {
}
private void checkContinue(Stmt.Continue stmt, Context context) {
}
private void checkDebug(Stmt.Debug stmt, Context context) {
if(context != Context.METHOD) {
checkExpression(stmt.expr, context);
}
}
private void checkDoWhile(Stmt.DoWhile stmt, Context context) {
//
checkStatements(stmt.body, context);
//
if(context != Context.METHOD) {
checkExpression(stmt.condition, context);
}
//
for(Expr e : stmt.invariants) {
checkExpression(e,Context.INVARIANT);
}
}
private void check(Stmt.Fail stmt, Context context) {
}
private void checkIfElse(Stmt.IfElse stmt, Context context) {
if(context != Context.METHOD) {
checkExpression(stmt.condition, context);
}
//
checkStatements(stmt.trueBranch, context);
checkStatements(stmt.falseBranch, context);
}
private void checkNamedBlock(Stmt.NamedBlock stmt, Context context) {
checkStatements(stmt.body,context);
}
private void checkReturn(Stmt.Return stmt, Context context) {
if(context != Context.METHOD) {
for(Expr e : stmt.returns) {
checkExpression(e, context);
}
}
}
private void checkSkip(Stmt.Skip stmt, Context context) {
}
private void checkSwitch(Stmt.Switch stmt, Context context) {
if(context != Context.METHOD) {
checkExpression(stmt.expr, context);
}
//
for(Stmt.Case c : stmt.cases) {
checkStatements(c.stmts, context);
}
}
private void checkVariableDeclaration(Stmt.VariableDeclaration stmt, Context context) {
if (stmt.expr != null && context != Context.METHOD) {
checkExpression(stmt.expr, context);
}
}
private void checkWhile(Stmt.While stmt, Context context) {
if(context != Context.METHOD) {
checkExpression(stmt.condition, context);
}
//
for(Expr e : stmt.invariants) {
checkExpression(e,Context.INVARIANT);
}
//
checkStatements(stmt.body, context);
}
private void checkLVal(Expr.LVal expression, Context context) {
try {
if(expression instanceof Expr.Dereference) {
checkDereferenceLVal((Expr.Dereference) expression, context);
} else if(expression instanceof Expr.FieldAccess) {
checkFieldAccessLVal((Expr.FieldAccess) expression, context);
} else if(expression instanceof Expr.IndexOf) {
checkIndexOfLVal((Expr.IndexOf) expression, context);
} else if(expression instanceof Expr.LocalVariable) {
checkLocalVariableLVal((Expr.LocalVariable) expression, context);
} 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 checkDereferenceLVal(Expr.Dereference expression, Context context) {
if(context != Context.METHOD) {
invalidReferenceAccess(expression,context);
}
checkLVal((Expr.LVal) expression.src,context);
}
private void checkFieldAccessLVal(Expr.FieldAccess expression, Context context) {
checkExpression(expression.src,context);
}
private void checkIndexOfLVal(Expr.IndexOf expression, Context context) {
checkLVal((Expr.LVal) expression.src,context);
checkExpression(expression.index,context);
}
private void checkLocalVariableLVal(Expr.LocalVariable expression, Context context) {
}
private void checkExpression(Expr expression, Context context) {
try {
if(expression instanceof Expr.ArrayInitialiser) {
checkArrayInitialiser((Expr.ArrayInitialiser) expression, context);
} else if(expression instanceof Expr.ArrayGenerator) {
checkArrayGenerator((Expr.ArrayGenerator) expression, context);
} else if(expression instanceof Expr.BinOp) {
checkBinOp((Expr.BinOp) expression, context);
} else if(expression instanceof Expr.Cast) {
checkCast((Expr.Cast) expression, context);
} else if(expression instanceof Expr.Constant) {
checkConstant((Expr.Constant) expression, context);
} else if(expression instanceof Expr.ConstantAccess) {
checkConstantAccess((Expr.ConstantAccess) expression, context);
} else if(expression instanceof Expr.Dereference) {
checkDereference((Expr.Dereference) expression, context);
} else if(expression instanceof Expr.FieldAccess) {
checkFieldAccess((Expr.FieldAccess) expression, context);
} else if(expression instanceof Expr.FunctionOrMethod) {
checkFunctionOrMethod((Expr.FunctionOrMethod) expression, context);
} else if(expression instanceof Expr.FunctionOrMethodCall) {
checkFunctionOrMethodCall((Expr.FunctionOrMethodCall) expression, context);
} else if(expression instanceof Expr.IndexOf) {
checkIndexOf((Expr.IndexOf) expression, context);
} else if(expression instanceof Expr.IndirectFunctionOrMethodCall) {
checkIndirectFunctionOrMethodCall((Expr.IndirectFunctionOrMethodCall) expression, context);
} else if(expression instanceof Expr.Lambda) {
checkLambda((Expr.Lambda) expression, context);
} else if(expression instanceof Expr.LocalVariable) {
checkLocalVariable((Expr.LocalVariable) expression, context);
} else if(expression instanceof Expr.New) {
checkNew((Expr.New) expression, context);
} else if(expression instanceof Expr.Quantifier) {
checkQuantifier((Expr.Quantifier) expression, context);
} else if(expression instanceof Expr.Record) {
checkRecord((Expr.Record) expression, context);
} else if(expression instanceof Expr.TypeVal) {
checkTypeVal((Expr.TypeVal) expression, context);
} else if(expression instanceof Expr.UnOp) {
checkUnOp((Expr.UnOp) expression, context);
} 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, Context context) {
for(Expr e : expression.arguments) {
checkExpression(e,context);
}
}
private void checkArrayGenerator(Expr.ArrayGenerator expression, Context context) {
checkExpression(expression.element,context);
checkExpression(expression.count,context);
}
private void checkBinOp(Expr.BinOp expression, Context context) {
checkExpression(expression.lhs,context);
checkExpression(expression.rhs,context);
}
private void checkCast(Expr.Cast expression, Context context) {
checkExpression(expression.expr,context);
}
private void checkConstant(Expr.Constant expression, Context context) {
}
private void checkConstantAccess(Expr.ConstantAccess expression, Context context) {
}
private void checkDereference(Expr.Dereference expression, Context context) {
checkExpression(expression.src,context);
}
private void checkFieldAccess(Expr.FieldAccess expression, Context context) {
checkExpression(expression.src,context);
}
private void checkFunctionOrMethod(Expr.FunctionOrMethod expression, Context context) {
}
private void checkFunctionOrMethodCall(Expr.FunctionOrMethodCall expression, Context context) {
if(context != Context.METHOD && expression.type() instanceof Type.Method) {
invalidMethodCall(expression,context);
}
for(Expr p : expression.arguments) {
checkExpression(p,context);
}
}
private void checkIndexOf(Expr.IndexOf expression, Context context) {
checkExpression(expression.src,context);
checkExpression(expression.index,context);
}
private void checkIndirectFunctionOrMethodCall(Expr.IndirectFunctionOrMethodCall expression, Context context) {
if(context != Context.METHOD && expression.type() instanceof Type.Method) {
invalidMethodCall(expression,context);
}
checkExpression(expression.src,context);
for(Expr p : expression.arguments) {
checkExpression(p,context);
}
}
private void checkLambda(Expr.Lambda expression, Context context) {
// Check body of the lambda
checkExpression(expression.body,context);
}
private void checkLocalVariable(Expr.LocalVariable expression, Context context) {
}
private void checkNew(Expr.New expression, Context context) {
if(context != Context.METHOD) {
invalidObjectAllocation(expression,context);
}
checkExpression(expression.expr,context);
}
private void checkQuantifier(Expr.Quantifier expression, Context context) {
for(Triple<String,Expr,Expr> p : expression.sources) {
checkExpression(p.second(),context);
checkExpression(p.third(),context);
}
checkExpression(expression.condition,context);
}
private void checkRecord(Expr.Record expression, Context context) {
for(Map.Entry<String,Expr> e : expression.fields.entrySet()) {
checkExpression(e.getValue(),context);
}
}
private void checkTypeVal(Expr.TypeVal expression, Context context) {
}
private void checkUnOp(Expr.UnOp expression, Context context) {
checkExpression(expression.mhs,context);
}
private void invalidObjectAllocation(SyntacticElement expression, Context context) {
String msg = errorMessage(ALLOCATION_NOT_PERMITTED);
throw new SyntaxError(msg, file.getEntry(), expression);
}
private void invalidMethodCall(SyntacticElement expression, Context context) {
String msg = errorMessage(METHODCALL_NOT_PERMITTED);
throw new SyntaxError(msg, file.getEntry(), expression);
}
private void invalidReferenceAccess(SyntacticElement expression, Context context) {
String msg= errorMessage(REFERENCE_ACCESS_NOT_PERMITTED);
throw new SyntaxError(msg, file.getEntry(), expression);
}
}