/** * Verifies that the current state of the local variables array matches the state specified by a * stack map entry. * * @param target the target encapsulating a stack map entry specifying what the current state of * the local variables array should be * @param replaceWithTarget if true, then the current state of the local variable array is updated * to reflect the state recorded in the stack map entry */ public void mergeLocals(Target target, boolean replaceWithTarget) { Klass[] recordedTypes = target.getLocals(); if (recordedTypes.length > localTypes.length) { throw codeParser.verifyError("size of recorded and derived local variable array differs"); } /* * Check the locals */ for (int i = 0; i < recordedTypes.length; i++) { Klass recordedType = recordedTypes[i]; Klass derivedType = localTypes[i]; if (!recordedType.isAssignableFrom(derivedType)) { /* * For some reason, the preverifier occasionally generates * stack map entries for local variables even though the * local variable is dead. What's more, in these cases, * it determines that the type resulting from merging an * object type and an interface type is the interface * type. This makes no sense to me, but the case must be * allowed. */ if (!recordedType.isInterface() || derivedType.isPrimitive()) { throw codeParser.verifyError("invalid type in local variable"); } } if (replaceWithTarget) { localTypes[i] = recordedType; } } }
/** * Verify that a local variable index for a given type is not out of bounds. * * @param type the type of the local variable at <code>index</code> in the local variables array * @param index the index of a local variable */ public void verifyLocalVariableIndex(Klass type, int index) { if (index < 0) { throw codeParser.verifyError("invalid local variable index"); } if (type.isDoubleWord()) { index++; } if (index >= localTypes.length) { throw codeParser.verifyError("invalid local variable index"); } }
/** * Get a <code>Local</code> instance to represent a value of a given type that will be * stored/loaded to/from a given local variable. * * @param type the type of the value * @param index the index of the local variable * @param isParameter true if the local is a parameter * @return the variable at index <code>index</code> in which values of type <code>type</code> are * stored */ private Local allocateLocalPrim(Klass type, int index, boolean isParameter) { Assert.that(localTypes.length < 0xFFFF); Assert.that( index >= 0 && index < localTypes.length, "index=" + index + " localTypes.length=" + localTypes.length); Klass localType = getLocalTypeFor(type); int key = localType.getSuiteID(); /* We need a hard partition between uses of a slot as a reference vs. an Address, Offset, or UWord. * The partitioning of java primitives and objects is accomplished not only by the type passed in here, but * by the bytecode verifier. We can't be sure that some bytecodes are refering to the same local variable * as both a reference and as a Squawk primitive. Without that kind of support we are conservative here * and force a clash whenever javac uses the same local index for a reference and a Squawk primitive. */ if (localType.isSquawkPrimitive()) { key = Klass.REFERENCE.getSuiteID(); } key = key << 16 | index; if (localValues == null) { localValues = new IntHashtable(); } Local local = (Local) localValues.get(key); if (local == null) { local = new Local(localType, index, isParameter); localValues.put(key, local); } /* * Ensure that the original class file does not use the same local variable * for both a Squawk primitive value and any other reference value. This prevents the * translator from having to do a complete liveness analysis to de-multiplex * such a local variable slot. Such de-multiplexing is required as Squawk primitives * are 'magically' translated into integers (or longs on a 64 bit system). */ if (localType.isSquawkPrimitive() || local.getType().isSquawkPrimitive()) { if (localType != local.getType()) { throw codeParser.verifyError( getBadAddressLocalVariableMessage(index, localType, local.getType())); } } // System.out.println("allocated: "+local+" index "+index); /// *if[SCOPEDLOCALVARIABLES]*/ codeParser.localVariableAllocated(codeParser.getCurrentIP(), local); /// *end[SCOPEDLOCALVARIABLES]*/ return local; }
/** * Pops a value off the operand stack. * * @param type the type that the value popped off the operand stack must be assignable to * @return the instruction that produced the popped value */ public StackProducer pop(Klass type) { StackProducer producer; if (type.isDoubleWord()) { if (sp < 2) { throw codeParser.verifyError("operand stack underflow"); } if (!isTopDoubleWord()) { throw codeParser.verifyError("incompatible type on operand stack " + tosKlassName()); } sp -= 2; producer = stack[sp]; } else { if (sp < 1) { throw codeParser.verifyError("operand stack underflow"); } if (isTopDoubleWord()) { throw codeParser.verifyError("incompatible type on operand stack " + tosKlassName()); } producer = stack[--sp]; /* * The primitive one-word, non-float types are all assignment compatible with each other */ if (type.isPrimitive() && type != Klass.FLOAT) { type = Klass.INT; } } Assert.that(producer != null); /* * Interfaces are treated as java.lang.Object in the verifier. */ if (type.isInterface()) { type = Klass.OBJECT; } /* * Verify that the instruction is producing the correct type. */ if (!type.isAssignableFrom(producer.getType())) { throw codeParser.verifyError( "incompatible type: '" + type + "' is not assignable from '" + producer.getType() + "'"); } return producer; }
/** * Emulates loading a value of a given type from a local variable. * * @param index the index of the local variable being loaded from * @param localType the expected type of the variable from which the value is loaded * @return the variable from which the value is loaded */ public Local load(int index, Klass localType) { verifyLocalVariableIndex(localType, index); Klass derivedType = localTypes[index]; if (!localType.isAssignableFrom(derivedType)) { throw codeParser.verifyError("incompatible type in local variable"); } if (localType.isDoubleWord()) { Klass secondWordType = Klass.getSecondWordType(localType); if (!secondWordType.isAssignableFrom(localTypes[index + 1])) { throw codeParser.verifyError("incompatible type in local variable"); } } if (derivedType.isSquawkPrimitive()) { localType = derivedType; } return allocateLocal(localType, index); }
/** * Merges the current state of the operand stack into the saved state at a control flow target. * This method also verifies that the current state of the operand stack matches the expected * state of the operand stack at the target as pecified by a stack map entry. * * @param target the target encapsulting the merged state of the operand stack at a distinct * address * @param replaceWithTarget if true, then the current state of the operand stack is updated to * reflect the state recorded in the stack map entry */ public void mergeStack(Target target, boolean replaceWithTarget) { Klass[] recordedTypes = target.getStack(); /* * Fail if the map sp is different */ if (recordedTypes.length != getStackSize()) { throw codeParser.verifyError("size of recorded and derived stack differs"); } /* * Check the stack items */ for (int r = 0, d = 0; r < recordedTypes.length; ++r, ++d) { Klass recordedType = recordedTypes[r]; Klass derivedType = getStackTypeAt(d); if (!recordedType.isAssignableFrom(derivedType)) { // Interfaces are treated like java.lang.Object in the verifier according to the CLDC spec. if (!recordedType.isInterface() || !Klass.OBJECT.isAssignableFrom(derivedType)) { throw codeParser.verifyError( "invalid type on operand stack @ " + d + ": expected " + recordedType + ", received " + derivedType); } } } /* * Merge the instructions on the stack */ target.merge(this); if (replaceWithTarget) { resetStack(target, false); } }
/** * Pushes a value to the operand stack. * * @param producer the instruction producing the value being pushed */ public void push(StackProducer producer) { Klass type = producer.getType(); Assert.that(type != Klass.VOID); /* * Check for overflow and push the producer. */ if (sp == maxStack) { throw codeParser.verifyError("operand stack overflow"); } stack[sp++] = producer; /* * For long and double check for overflow and then add a null word to the stack. */ if (type.isDoubleWord()) { if (sp == maxStack) { throw codeParser.verifyError("operand stack overflow"); } stack[sp++] = null; } }
/** * Creates and returns the detailed error message when a local variable is used as a Squawk * primitive as well as some value not of exactly the same type. The message includes information * derived from the LocalVariableTable attribute so that the source code can be easily changed. * * @param index the local variable index that is (mis)used * @return the detailed error message */ private String getBadAddressLocalVariableMessage(int index, Klass type1, Klass type2) { Assert.that(type1.isSquawkPrimitive() || type2.isSquawkPrimitive()); if (type2.isSquawkPrimitive()) { Klass otherType = type1; type1 = type2; type2 = otherType; } StringBuffer buf = new StringBuffer( "Stack location " + index + " cannot be used for both a local variable of type " + type1.getName() + " and of type " + type2.getName() + ". Try moving the variable of type " + type1.getName() + " to the top-most scope of the method."); Enumeration e = codeParser.getLocalVariableTableEntries(); if (e != null) { buf.append(" (source code usage: "); while (e.hasMoreElements()) { LocalVariableTableEntry entry = (LocalVariableTableEntry) e.nextElement(); if (entry.getIndex() == index) { int start = codeParser.getSourceLineNumber(entry.getStart().getBytecodeOffset()); int end = codeParser.getSourceLineNumber(entry.getEnd().getBytecodeOffset()); buf.append(entry.getType().getName()) .append(' ') .append(entry.getName()) .append(" from line ") .append(start) .append(" to line ") .append(end) .append(';'); } } } return buf.toString(); }
/** * Tests two given types to ensure that they are both {@link Klass#isSquawkPrimitive() Squawk * primitives} or both not Squawk primitives. If they are both Squawk primitives, then they must * be exactly the same type. This enforces the constraint that Squawk primitive values cannot be * assigned to or compared with any other type. * * @param type1 the first type to compare * @param type2 the second type to compare */ public void verifyUseOfSquawkPrimitive(Klass type1, Klass type2) { if (type1.isSquawkPrimitive() || type2.isSquawkPrimitive()) { if (type1 != type2) { // Offsets are implemented as Words // if (type1.getClassID() + type2.getClassID() != CID.UWORD + CID.OFFSET) { String type = type1.getName(); throw codeParser.verifyError( type + " values can only be written to or compared with other " + type + " values, not with " + type2.getName()); // } } } }
/** * Traces the frame state at the current verification address. * * @param opcode the opcode of the instruction at <code>address</code> * @param address the current verification address */ public void traceFrameState(int opcode, int address) { /* * Trace the recorded and derived types */ if (Translator.TRACING_ENABLED) { Target target = null; try { target = codeParser.getTarget(address); } catch (NoClassDefFoundError e) { /* Just means there is no stack map at this address */ } Tracer.traceln("Frame state @ " + address + " [ " + Opcode.mnemonics[opcode] + " ]"); traceLocals(target); traceStack(target); } }
/** * Resets the max stack limit back to the value specified in the class file for the current * method. */ public void resetMaxStack() { maxStack = codeParser.getMaxStack(); }
/** * Creates a Frame instance to emulate and verify the execution of a single class file method. * * @param codeParser the parser used to parse the "Code" attribute of the method being emulated * @param extraLocals the number of extra local variables needed */ public Frame(CodeParser codeParser, int extraLocals) { this.maxStack = codeParser.getMaxStack(); this.codeParser = codeParser; /* * Initialize the operand stack */ stack = new StackProducer[maxStack]; /* * Initialize the types in the local variables */ localTypes = new Klass[codeParser.getMaxLocals() + extraLocals]; Method method = codeParser.getMethod(); Klass[] parameterTypes = method.getParameterTypes(); Local[] parameterLocals = null; if (Translator.REVERSE_PARAMETERS && method.isInterpreterInvoked()) { parameterLocals = new Local[parameterTypes.length]; } int javacIndex = 0; /* * Initialize 'this' in non-static methods. The type of 'this' is * UNINITIALIZED_THIS if this method is a constructor in any class * except java.lang.Object otherwise it is the class in which the * method was defined. */ if (!method.isStatic() || method.isConstructor()) { Assert.that(parameterLocals == null); Klass thisType = method.getDefiningClass(); if (method.isConstructor() && thisType != Klass.OBJECT) { thisType = Klass.UNINITIALIZED_THIS; } Local thisLocal = allocateParameter(thisType, javacIndex); store(javacIndex, thisType, thisLocal); javacIndex++; } /* * Initialize locals for the parameters. */ int parameterIndex = javacIndex; for (int i = 0; i < parameterTypes.length; i++) { Klass parameterType = parameterTypes[i]; Local parameterLocal = allocateParameter(parameterType, javacIndex); if (parameterLocals != null) { parameterLocals[i] = parameterLocal; } if (Klass.SQUAWK_64) { if (javacIndex != parameterIndex) { Assert.that(parameterIndex < javacIndex); parameterLocal.setParameterIndex(parameterIndex); } parameterIndex++; } store(javacIndex, parameterType, parameterLocal); javacIndex += (parameterType.isDoubleWord() ? 2 : 1); } parameterLocalsCount = javacIndex; /* * Adjust the parameter offsets for parameter order reversal. */ if (parameterLocals != null) { parameterIndex = 0; for (int i = parameterTypes.length - 1; i >= 0; i--) { Klass parameterType = parameterTypes[i]; parameterLocals[i].setParameterIndex(parameterIndex++); if (!Klass.SQUAWK_64 && parameterType.isDoubleWord()) { parameterIndex++; } } } /* * Initialize the remaining local variables to the TOP type */ while (javacIndex < localTypes.length) { localTypes[javacIndex++] = Klass.TOP; } }