diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/icode/Checkers.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/icode/Checkers.scala | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala b/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala new file mode 100644 index 0000000000..8aba76fa97 --- /dev/null +++ b/src/compiler/scala/tools/nsc/backend/icode/Checkers.scala @@ -0,0 +1,591 @@ +/* NSC -- new scala compiler + * Copyright 2005 LAMP/EPFL + * @author Martin Odersky + */ + +// $Id$ + +package scala.tools.nsc.backend.icode; + +import scala.collection.mutable.{Buffer, ListBuffer, Map, HashMap}; +import scala.tools.nsc.symtab._; + +abstract class Checkers { + val global: Global; + import global._; + import global.icodes.toTypeKind; + + /** + * This class performs a set of checks similar to what the bytecode + * verifier does. For each basic block, it checks that: + * + * - for primitive operations: the type and numer of operands match + * the type of the operation + * + * - for method calls: the method exists in the type of the receiver + * and the number and type of arguments match the declared type of + * the method. + * + * - for object creation: the constructor can be called. + * + * - for load/stores: the field/local/param exists and the type + * of the value matches that of the target. + * + * For a control flow graph it checks that type stacks at entry to + * each basic block 'agree': + * + * - they have the same length + * - there exists a lub for all types at the same position in stacks. + * + * TODO: Better checks for MONITOR_ENTER/EXIT + * Better checks for local var initializations + */ + class ICodeChecker { + import icodes._; + import opcodes._; + + var clasz: IClass = _; + var method: IMethod = _; + var code: Code = _; + + val in: Map[BasicBlock, TypeStack] = new HashMap(); + val out: Map[BasicBlock, TypeStack] = new HashMap(); + + val emptyStack = new TypeStack(); + + val STRING = REFERENCE(definitions.StringClass); + val SCALA_ALL = REFERENCE(definitions.AllClass); + val SCALA_ALL_REF = REFERENCE(definitions.AllRefClass); + val CASE_CLASS = REFERENCE(definitions.getClass("scala.CaseClass")); + + def checkICodes: Unit = { + Console.println("[[consistency check at beginning of phase " + globalPhase.name + "]]"); + classes foreach check; + } + + def check(cls: IClass): Unit = { + log("Checking class " + cls); + clasz = cls; + + for (val f1 <- cls.fields; val f2 <- cls.fields; f1 ne f2) + if (f1.symbol.name == f2.symbol.name) + Checkers.this.global.error("Repetitive field name: " + + f1.symbol.fullNameString); + + for (val m1 <- cls.methods; val m2 <- cls.methods; m1 ne m2) + if (m1.symbol.name == m2.symbol.name && + m1.symbol.tpe =:= m2.symbol.tpe) + Checkers.this.global.error("Repetitive method: " + + m1.symbol.fullNameString); + clasz.methods.foreach(check); + } + + /** Apply the give funtion to each pair of the cartesian product of + * l1 x l2. + */ + def pairwise[a](l1: List[a], l2: List[a])(f: (a, a) => Unit) = + l1 foreach { x => + l2 foreach { y => f(x, y) } + } + + def check(m: IMethod): Unit = { + log("Checking method " + m); + method = m; + if (!m.isDeferred) + check(m.code); + } + + def check(c: Code): Unit = { + var worklist: Buffer[BasicBlock] = new ListBuffer(); + + def append(elems: List[BasicBlock]) = elems foreach appendBlock; + def appendBlock(bl: BasicBlock) = + if ( !worklist.exists(bl.==) ) + worklist + bl; + + in.clear; out.clear; + code = c; + worklist + c.startBlock; + c.blocks foreach ( bl => { in += bl -> emptyStack; + out += bl -> emptyStack } ); + + while (worklist.length > 0) { + val block = worklist(0); worklist.trimStart(1); + val output = check(block, in(block)); + if (output != out(block) || + (out(block) eq emptyStack)) { + log("Output changed for block: " + block.fullString); + out(block) = output; + append(block.successors); + block.successors foreach meet; + } + } + } + + /** + * Apply the meet operator of the stack lattice on bl's predecessors. + * :-). Compute the input to bl by checking that all stacks have the + * same length, and taking the lub of types at the same positions. + */ + def meet(bl: BasicBlock): Unit = { + val preds = bl.predecessors; + + def meet2(s1: TypeStack, s2: TypeStack): TypeStack = { + if (s1 eq emptyStack) s2 + else if (s2 eq emptyStack) s1 + else { + if (s1.length != s2.length) + throw new CheckerError("Incompatible stacks: " + s1 + " and " + s2 + " in " + method + " at entry to block: " + bl); + new TypeStack(List.map2(s1.types, s2.types) (lub)) + } + } + + if (preds != Nil) { + in(bl) = (preds map out.apply) reduceLeft meet2; + log("Input changed for block: " + bl +" to: " + in(bl)); + } + } + + + private var typeStack: TypeStack = null; + private var instruction: Instruction = null; + private var basicBlock: BasicBlock = null; + + /** + * Check the basic block to be type correct and return the + * produced type stack. + */ + def check(b: BasicBlock, initial: TypeStack): TypeStack = { + log("** Checking block:\n" + b.fullString + " with initial stack:\n" + initial); + var stack = new TypeStack(initial); + + this.typeStack = stack; + this.basicBlock = b; + + def typeError(k1: TypeKind, k2: TypeKind): Unit = + error(" expected: " + k1 + " but " + k2 + " found"); + + b traverse (instr => { + + def checkStack(len: Int) = + if (stack.length < len) + ICodeChecker.this.error("Expected at least " + len + " elements on the stack", stack); + else + (); + + def checkLocal(local: Local) = + method.lookupLocal(local.sym.name) match { + case None => error(" " + local + " is not defined in method " + method); + case _ => () + } + + def checkField(obj: TypeKind, field: Symbol) = + obj match { + case REFERENCE(sym) => + if (sym.info.member(field.name) == NoSymbol) + error(" " + field + " is not defined in class " + clasz); + case _ => + error(" expected reference type, but " + obj + " found"); + } + + /** Checks that tpe is a subtype of one of the allowed types */ + def checkType(tpe: TypeKind, allowed: TypeKind*) = + if (isOneOf(tpe, allowed: _*)) + () + else + error(tpe.toString() + " is not one of: " + allowed); + + /** Checks that the 2 topmost elements on stack are of the + * kind TypeKind. + */ + def checkBinop(kind: TypeKind) = { + val Pair(a, b) = stack.pop2; + checkType(a, kind); + checkType(b, kind); + } + + /** Check that arguments on the stack match method params. */ + def checkMethodArgs(method: Symbol) = { + val params = method.info.paramTypes; + checkStack(params.length); + params.reverse.foreach( (tpe) => checkType(stack.pop, toTypeKind(tpe))); + } + + /** Checks that the object passed as receiver has a method + * 'method' and that it is callable from the current method. + */ + def checkMethod(receiver: TypeKind, method: Symbol) = + receiver match { + case REFERENCE(sym) => + checkBool(sym.info.member(method.name) != NoSymbol, + "Method " + method + " does not exist in " + sym.fullNameString); + if (method hasFlag Flags.PRIVATE) + checkBool(method.owner == clasz.symbol, + "Cannot call private method of " + method.owner.fullNameString + + " from " + clasz.symbol.fullNameString); + else if (method hasFlag Flags.PROTECTED) + checkBool(clasz.symbol isSubClass method.owner, + "Cannot call protected method of " + method.owner.fullNameString + + " from " + clasz.symbol.fullNameString); + + case ARRAY(_) => + checkBool(receiver.toType.member(method.name) != NoSymbol, + "Method " + method + " does not exist in " + receiver); + + case t => + error("Not a reference type: " + t); + } + + def checkBool(cond: Boolean, msg: String) = + if (cond) () else error(msg); + + this.instruction = instr; + + if (settings.debug.value) { + log("PC: " + instr); + log("stack: " + stack); + log("================"); + } + instr match { + case THIS(clasz) => + stack push toTypeKind(clasz.tpe); + + case CONSTANT(const) => + stack push toTypeKind(const.tpe); + + case LOAD_ARRAY_ITEM(kind) => + checkStack(2); + stack.pop2 match { + case Pair(INT, ARRAY(elem)) => + if (!(elem <:< kind)) + typeError(kind, elem); + stack.push(elem); + case Pair(a, b) => + error(" expected and INT and a array reference, but " + + a + ", " + b + " found"); + } + + case LOAD_LOCAL(local, isArg) => + checkLocal(local); + stack.push(local.kind); + + case LOAD_FIELD(field, isStatic) => + if (isStatic) { + // the symbol's owner should contain it's field, but + // this is already checked by the type checker, no need + // to redo that here + } else { + checkStack(1); + val obj = stack.pop; + checkField(obj, field); + } + stack.push(toTypeKind(field.tpe)); + + case LOAD_MODULE(module) => + checkBool((module.isModule || module.isModuleClass), + "Expected module: " + module + " flags: " + Flags.flagsToString(module.flags)); + stack.push(toTypeKind(module.tpe)); + + case STORE_ARRAY_ITEM(kind) => + checkStack(3); + stack.pop3 match { + case Triple(k, INT, ARRAY(elem)) => + if (!(k <:< kind)) + typeError(kind, k); + if (!(k <:< elem)) + typeError(elem, k); + case Triple(a, b, c) => + error(" expected and array reference, and int and " + kind + + " but " + a + ", " + b + ", " + c + " found"); + } + + case STORE_LOCAL(local, isArg) => + checkLocal(local); + checkStack(1); + + val actualType = stack.pop; + if (!(actualType <:< local.kind) && + actualType != CASE_CLASS && + local.kind != SCALA_ALL_REF) + typeError(local.kind, actualType); + + case STORE_FIELD(field, isStatic) => + if (isStatic) { + checkStack(1); + val fieldType = toTypeKind(field.tpe); + val actualType = stack.pop; + if (!(actualType <:< fieldType)) + typeError(fieldType, actualType); + } else { + checkStack(2); + stack.pop2 match { + case Pair(value, obj) => + checkField(obj, field); + val fieldType = toTypeKind(field.tpe); + if (fieldType != SCALA_ALL_REF && !(value <:< fieldType)) + typeError(fieldType, value); + } + } + + case CALL_PRIMITIVE(primitive) => + checkStack(instr.consumed); + primitive match { + case Negation(kind) => + checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE); + checkType(stack.pop, kind); + stack push kind; + + case Test(op, kind, zero) => + if (zero) { + val actualType = stack.pop; + checkType(actualType, kind); + } else + checkBinop(kind); + stack push BOOL; + + case Comparison(op, kind) => + checkType(kind, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE); + checkBinop(kind); + stack push INT; + + case Arithmetic(op, kind) => + checkType(kind, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE); + if (op == NOT) + checkType(stack.pop, kind) + else + checkBinop(kind); + stack push kind; + + case Logical(op, kind) => + checkType(kind, BOOL, BYTE, CHAR, SHORT, INT, LONG); + checkBinop(kind); + stack push kind; + + case Shift(op, kind) => + checkType(kind, BYTE, CHAR, SHORT, INT, LONG); + val Pair(a, b) = stack.pop2; + checkType(a, INT); + checkType(b, kind); + stack push kind; + + case Conversion(src, dst) => + checkType(src, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE); + checkType(dst, BYTE, CHAR, SHORT, INT, LONG, FLOAT, DOUBLE); + checkType(stack.pop, src); + stack push dst; + + case ArrayLength(kind) => + val arr = stack.pop; + arr match { + case ARRAY(elem) => + checkType(elem, kind); + case _ => + error(" array reference expected, but " + arr + " found"); + } + stack push INT; + + case StartConcat => + stack.push(ConcatClass); + + case EndConcat => + checkType(stack.pop, ConcatClass); + stack.push(STRING); + + case StringConcat(el) => + checkType(stack.pop, el); + checkType(stack.pop, ConcatClass); + stack push ConcatClass; + } + + case CALL_METHOD(method, style) => + style match { + case Dynamic => + checkStack(1 + method.info.paramTypes.length); + checkMethodArgs(method); + checkMethod(stack.pop, method); + stack.push(toTypeKind(method.info.resultType)); + + case Static(onInstance) => + if (onInstance) { + checkStack(1 + method.info.paramTypes.length); + checkBool(method.hasFlag(Flags.PRIVATE) || method.isConstructor, + "Static call to non-private method."); + checkMethodArgs(method); + checkMethod(stack.pop, method); + if (!method.isConstructor) + stack.push(toTypeKind(method.info.resultType)); + } else { + checkStack(method.info.paramTypes.length); + checkMethodArgs(method); + stack.push(toTypeKind(method.info.resultType)); + } + + case SuperCall(mixin) => + checkStack(1 + method.info.paramTypes.length); + checkMethodArgs(method); + checkMethod(stack.pop, method); + stack.push(toTypeKind(method.info.resultType)); + + } + + case NEW(kind) => + kind match { + case REFERENCE(cls) => + stack.push(kind); + + case _ => error("NEW call to non-reference type: " + kind); + } + + case CREATE_ARRAY(elem) => + checkStack(1); + checkType(stack.pop, INT); + stack.push(ARRAY(elem)); + + case IS_INSTANCE(tpe) => + val ref = stack.pop; + checkBool(ref.isReferenceType || ref.isArrayType, + "IS_INSTANCE on primitive type: " + ref); + checkBool(tpe.isReferenceType || tpe.isArrayType, + "IS_INSTANCE to primitive type: " + tpe); + stack.push(BOOL); + + case CHECK_CAST(tpe) => + val ref = stack.pop; + checkBool(ref.isReferenceType || ref.isArrayType, + "CHECK_CAST on primitive type: " + ref); + checkBool(tpe.isReferenceType || tpe.isArrayType, + "CHECK_CAST to primitive type: " + tpe); + stack.push(tpe); + + case SWITCH(tags, labels) => + checkType(stack.pop, INT); + checkBool(tags.length == labels.length - 1, + "The number of tags and labels does not coincide."); + checkBool(labels forall (b => code.blocks contains b), + "Switch target cannot be found in code."); + + case JUMP(where) => + checkBool(code.blocks contains where, + "Jump to non-existant block " + where); + + case CJUMP(success, failure, cond, kind) => + checkBool(code.blocks contains success, + "Jump to non-existant block " + success); + checkBool(code.blocks contains failure, + "Jump to non-existant block " + failure); + checkBinop(kind); + + case CZJUMP(success, failure, cond, kind) => + checkBool(code.blocks contains success, + "Jump to non-existant block " + success); + checkBool(code.blocks contains failure, + "Jump to non-existant block " + failure); + checkType(stack.pop, kind); + + case RETURN(kind) => + kind match { + case UNIT => (); + + case REFERENCE(_) | ARRAY(_) => + checkStack(1); + val top = stack.pop; + checkBool(top.isReferenceType || top.isArrayType, + "" + kind + " is a reference type, but " + top + " is not"); + case _ => + checkStack(1); + val top = stack.pop; + checkType(top, kind); + } + + case THROW() => + val thrown = stack.pop; + checkBool(thrown.toType <:< definitions.ThrowableClass.tpe, + "Element on top of stack should implement 'Throwable': " + thrown); + stack.push(SCALA_ALL); + + case DROP(kind) => + checkType(stack.pop, kind); + + case DUP(kind) => + val top = stack.pop; + checkType(top, kind); + stack.push(top); + stack.push(top); + + case MONITOR_ENTER() => + checkStack(1); + checkBool(stack.pop.isReferenceType, + "MONITOR_ENTER on non-reference type"); + + case MONITOR_EXIT() => + checkStack(1); + checkBool(stack.pop.isReferenceType, + "MONITOR_EXIT on non-reference type"); + + case _ => abort("Unknown instruction: " + instr); + } + }); + stack + } + + //////////////// Error reporting ///////////////////////// + + def error(msg: String): Unit = { + System.out.println(method.toString() + " in block: " + basicBlock.label); + printLastIntructions; + + Checkers.this.global.error("ICode checker: " + method + ": " + msg); + } + + /** Prints the last 4 instructions. */ + def printLastIntructions = { + var printed = 0; + var buf: List[Instruction] = Nil; + + basicBlock.traverseBackwards( (i) => + if (i == instruction || (printed > 0 && printed < 3)) { + buf = i :: buf; + printed = printed + 1; + }); + buf foreach System.out.println; + Console.println("at: " + clasz.cunit.position(buf.head.pos)); + } + + def error(msg: String, stack: TypeStack): Unit = { + error(msg + "\n type stack: " + stack); + } + + + //////////////////// Checking ///////////////////////////// + + /** Return true if k1 is a subtype of any of the following types. */ + def isOneOf(k1: TypeKind, kinds: TypeKind*) = + kinds.exists( k => k1 <:< k); + + + /** + * Dummy TypeKind to represent the ConcatClass in a platform-independent + * way. For JVM it would have been a REFERENCE to 'StringBuffer'. + */ + case object ConcatClass extends TypeKind { + override def toString() = "ConcatClass"; + + /** + * Approximate `lub'. The common type of two references is + * always AnyRef. For 'real' least upper bound wrt to subclassing + * use method 'lub'. + */ + override def maxType(other: TypeKind): TypeKind = + other match { + case REFERENCE(_) => REFERENCE(definitions.AnyRefClass); + case _ => + abort("Uncomparbale type kinds: ConcatClass with " + other); + } + + /** Checks subtyping relationship. */ + override def <:<(other: TypeKind): Boolean = (this eq other); + + override def isReferenceType: Boolean = false; + } + } +} |