/* * This file is part of the Jikes RVM project (http://jikesrvm.org). * * This file is licensed to You under the Eclipse Public License (EPL); * You may not use this file except in compliance with the License. You * may obtain a copy of the License at * * http://www.opensource.org/licenses/eclipse-1.0.php * * See the COPYRIGHT.txt file distributed with this work for information * regarding copyright ownership. */ package org.mmtk.harness.lang; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.mmtk.harness.lang.Trace.Item; import org.mmtk.harness.lang.ast.*; import org.mmtk.harness.lang.parser.MethodTable; import org.mmtk.harness.lang.type.Field; import org.mmtk.harness.lang.type.Type; import org.mmtk.harness.lang.type.UserType; /** * A type-checker visitor for MMTk scripts */ public class Checker extends Visitor { /** * Type check a script, represented by its method table. * @param methods */ public static void typeCheck(MethodTable methods) { Checker checker = new Checker(); for (Method m : methods.normalMethods()) { m.accept(checker); } } /** * The type of the current method */ private Type returnType; /** * Variable initialisation status */ private boolean[] isInitialized; /** * Visit an expression and return its type. * @param expr * @return */ private Type getTypeOf(Expression expr) { Type type = (Type)expr.accept(this); Trace.trace(Item.CHECKER,"Type of %s is %s%n",PrettyPrinter.format(expr),type.toString()); return type; } /** * Visit an expression, and return true if the result type is in the given * list of types. * @param expr * @param types * @return */ private boolean checkType(Expression expr, Type...types) { Type type = getTypeOf(expr); for (Type t : types) { if (type == t) { return true; } } return false; } /** * Report an error and exit * @param ast * @param message */ private static void fail(AST ast, String message, Object...params) { String fullMessage = String.format(message,params); System.err.printf("Error at line %d: %s%n",ast.getLine(),fullMessage); PrettyPrinter.print(System.err, ast); System.err.println(); throw new CheckerException(fullMessage); } private void checkParams(AST marker, List<Type> actualTypes, List<Type> formalTypes) { Iterator<Type> actualTypeIter = actualTypes.iterator(); for (Type actualParamType : formalTypes) { Type formalParamType = actualTypeIter.next(); if (!formalParamType.isCompatibleWith(actualParamType)) { fail(marker,"Actual parameter of type "+actualParamType+ " is incompatible with formal param of type "+formalParamType); } } } /****************************************************************************** * * Visitor methods * */ @Override public Object visit(Alloc alloc) { Type p1Type = getTypeOf(alloc.getArg(0)); if (p1Type.equals(Type.INT)) { alloc.setTyped(false); // untyped 'object' allocation - int,int[,boolean] if (!checkType(alloc.getArg(1),Type.INT)) { fail(alloc,"Allocation data count must be integer"); } if (alloc.numArgs() >= 3) { if (!checkType(alloc.getArg(2),Type.BOOLEAN)) { fail(alloc,"Allocation double align must be boolean"); } } return Type.OBJECT; } else if (p1Type.isUserType()) { alloc.setTyped(true); alloc.setType((UserType)p1Type); if (alloc.numArgs() >= 2) { if (!checkType(alloc.getArg(1),Type.BOOLEAN)) { fail(alloc,"Allocation double align must be boolean"); } } return p1Type; } else { fail(alloc,"Can't allocate a %s using alloc(...)",alloc.getType().toString()); throw new AssertionError("this line is unreachable"); } } @Override public Object visit(Assert ass) { checkType(ass.getPredicate(),Type.BOOLEAN); return Type.VOID; } @Override public Object visit(Assignment a) { isInitialized[a.getSlot()] = true; Type lhsType = a.getSymbol().getType(); checkType(a.getRhs(),lhsType); return Type.VOID; } @Override public Object visit(BinaryExpression exp) { Type lhsType = getTypeOf(exp.getLhs()); Type rhsType = getTypeOf(exp.getRhs()); Operator op = exp.getOperator(); boolean ok = true; if (lhsType != rhsType) { // Allow boolean/object comparisons if (op == Operator.EQ || op == Operator.NE) { if ((lhsType == Type.BOOLEAN && rhsType.isObject()) || (lhsType.isObject() && rhsType == Type.BOOLEAN)) { ok = true; } else if (lhsType.isObject() && rhsType.isObject()){ ok = true; } else { ok = false; } } else { ok = false; } if (!ok) { fail(exp,"Type mismatch between "+lhsType+" and "+rhsType); } } if (Operator.booleanOperators.contains(op)) { return Type.BOOLEAN; } else { return lhsType; } } @Override public Object visit(Call call) { Method m = call.getMethod(); if (call.getParams().size() != m.getParamCount()) { fail(call,"Wrong number of parameters"); } List<Type> actualTypes = new ArrayList<Type>(); /* Type-check the actual parameter expressions */ for (Expression param : call.getParams()) { actualTypes.add(getTypeOf(param)); } checkParams(call, actualTypes, m.getParamTypes()); if (call.isExpression()) { return call.getMethod().getReturnType(); } return Type.VOID; } @Override public Object visit(Constant c) { return c.value.type(); } @Override public Object visit(Empty e) { return Type.VOID; } @Override public Object visit(Expect exc) { return Type.VOID; } @Override public Object visit(IfStatement conditional) { for (Expression e : conditional.getConds()) { if (!checkType(e,Type.BOOLEAN)) { fail(e,"Conditional must have type BOOLEAN"); } } for (Statement s : conditional.getStmts()) { s.accept(this); } return Type.VOID; } @Override public Object visit(LoadField load) { if (load.getObjectSymbol().getType() != Type.OBJECT) { fail(load,"Target of loadfield must be an Object"); } if (getTypeOf(load.getIndex()) != Type.INT) { fail(load,"Loadfield index must have type INTEGER"); } return load.getFieldType(); } @Override public Object visit(LoadNamedField load) { Type t = load.getObjectSymbol().getType(); if (!t.isObject()) { fail(load,"Target of loadfield must be an Object type"); } UserType objectType = (UserType)t; Field field = objectType.getField(load.getFieldName()); if (field == null) { fail(load,"Type %s does not have a field called %s",t,load.getFieldName()); } return field.getType(); } @Override public Object visit(NormalMethod method) { isInitialized = new boolean[method.getDecls().size()]; for (Declaration decl : method.getParams()) { isInitialized[decl.getSlot()] = true; } returnType = method.getReturnType(); method.getBody().accept(this); return returnType; } @Override public Object visit(PrintStatement print) { for (Expression exp : print.getArgs()) { exp.accept(this); } return Type.VOID; } /** * Checks that * - The expression returned is internally consistent * - The type of the return value is compatible with the * declared type of the method */ @Override public Object visit(Return ret) { if (ret.hasReturnValue()) { Type type = getTypeOf(ret.getRhs()); if (!returnType.isCompatibleWith(type)) { fail(ret,"Returning a "+type+" in a method declared as "+returnType); } return type; } else if (returnType != Type.VOID) { fail(ret,"Returning from a non-Object method requires a return value"); } return Type.VOID; } @Override public Object visit(Sequence seq) { return super.visit(seq); } /** * Check * - Actual parameter expressions * - Actual parameters against method formal parameters */ @Override public Object visit(Spawn sp) { List<Type> actualTypes = new ArrayList<Type>(); for (Expression expr : sp.getArgs()) { actualTypes.add(getTypeOf(expr)); } checkParams(sp, actualTypes, sp.getMethod().getParamTypes()); return Type.VOID; } @Override public Object visit(StoreField store) { if (store.getObjectSymbol().getType() != Type.OBJECT) { fail(store,"Target of storefield must be an Object"); } if (getTypeOf(store.getIndex()) != Type.INT) { fail(store,"Storefield index must have type INTEGER"); } Type rhsType = getTypeOf(store.getRhs()); Type fieldType = store.getFieldType(); if (!fieldType.isCompatibleWith(rhsType)) { fail(store,"Storefield to a "+fieldType+" must have type "+fieldType+", not "+rhsType); } return Type.VOID; } @Override public Object visit(StoreNamedField store) { assert store.getObjectSymbol() != null; assert store.getObjectSymbol().getType() != null; Type t = store.getObjectSymbol().getType(); if (!t.isObject()) { fail(store,"Target of storefield.name (%s) must be an object type, not %s", store.getObjectSymbol().getName(),t.getName()); } UserType objectType = (UserType)t; Field field = objectType.getField(store.getFieldName()); if (field == null) { fail(store,"The type "+objectType+" does not have a field called "+store.getFieldName()); } Type fieldType = field.getType(); if (!fieldType.isCompatibleWith(getTypeOf(store.getRhs()))) { fail(store,"Storefield to a "+fieldType+" must have type "+fieldType); } return Type.VOID; } @Override public Object visit(TypeLiteral type) { return type.getType(); } @Override public Object visit(UnaryExpression exp) { /* Unary operators preserve type */ Type type = getTypeOf(exp.getOperand()); /* With this one exception ... */ if (exp.getOperator() == Operator.NOT && type == Type.OBJECT) { return Type.BOOLEAN; } return type; } @Override public Object visit(Variable var) { if (!isInitialized[var.getSlot()]) { fail(var,"Variable "+var.getSymbol().getName()+" is not initialized before use"); } return var.getSymbol().getType(); } @Override public Object visit(WhileStatement w) { if (!checkType(w.getCond(),Type.BOOLEAN,Type.OBJECT)) { fail(w,"While condition must have type BOOLEAN"); } w.getBody().accept(this); return Type.VOID; } }