/** * 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; } } }
/** * Gets the super class of this class. * * @return the super class of this class */ public final ProxyType getSuperclass() throws IOException, SDWPException { Klass superClass = klass.getSuperclass(); // the "-bytecode-" class actually inherits from INT, but don't tell jdwp that! if (superClass != null && !superClass.isPrimitive()) { return ptm.lookup(superClass, true); } return null; }
@SuppressWarnings("unchecked") private void addFields(List list, boolean isStatic) { int count = klass.getFieldCount(isStatic); for (int i = 0; i != count; ++i) { Field field = klass.getField(i, isStatic); FieldID fid = new FieldID(JDWP.getTag(field.getType()), field.getOffset(), isStatic, getID()); ProxyField proxyField = new ProxyField(fid, field); list.add(proxyField); } }
/** * Gets the class file corresponding to a given instance class. The <code>klass</code> must not * yet be converted and it must not be a {@link Klass#isSynthetic() synthetic} class. * * @param klass the instance class for which a class file is requested * @return the class file for <code>klass</code> */ ClassFile getClassFile(Klass klass) { Assert.that(!klass.isSynthetic(), "synthethic class has no classfile"); String name = klass.getName(); ClassFile classFile = (ClassFile) classFiles.get(name); if (classFile == null) { classFile = new ClassFile(klass); classFiles.put(name, classFile); } return classFile; }
/** * Emulates the storing of a value to a local variable. * * @param index the index of the local variable being stored to * @param type the type of the value * @param local the variable to which the value is stored */ public void store(int index, Klass type, Local local) { Klass localType = local.getType(); Assert.that(localType.isAssignableFrom(type) || localType == getLocalTypeFor(type)); verifyLocalVariableIndex(localType, index); localTypes[index] = type; if (localType.isDoubleWord()) { localTypes[index + 1] = Klass.getSecondWordType(localType); } else { verifyUseOfSquawkPrimitive(localType, type); } }
@SuppressWarnings("unchecked") private void addMethods(List list, boolean isStatic) { int count = klass.getMethodCount(isStatic); for (int i = 0; i != count; ++i) { Method method = klass.getMethod(i, isStatic); if (!method.isHosted()) { MethodID mid = new MethodID(method.getOffset(), isStatic); ProxyMethod proxyMethod = new ProxyMethod(mid, method); list.add(proxyMethod); } } }
/** * Gets the fully qualified name of this type. The returned name is formatted as it might appear * in a Java programming language declaration for objects of this type. * * <p>For primitive classes the returned name is the name of the corresponding primitive type; for * example, "int" is returned as the name of the class represented by {@link * java.lang.Integer#TYPE Integer.TYPE}. * * @return a string containing the type name. */ public String getName() { String name = klass.getName(); if (!klass.isArray()) { return name; } int dimensions = 0; while (name.charAt(dimensions) == '[') { ++dimensions; } name = name.substring(dimensions); char first = name.charAt(0); if (first == 'L') { name = name.substring(1, name.length() - 2).replace('/', '.'); } else { switch (first) { case 'I': name = "int"; break; case 'J': name = "long"; break; case 'F': name = "float"; break; case 'D': name = "double"; break; case 'Z': name = "boolean"; break; case 'C': name = "char"; break; case 'S': name = "short"; break; case 'B': name = "byte"; break; case 'V': name = "void"; break; } } while (dimensions-- != 0) { name += "[]"; } return name; }
/** * Determines if the operand stack or local variable array currently contains an entry whose type * is equal to or a subtype of a given type. * * @param type the type to search for * @return true if an entry was found */ public boolean containsType(Klass type) { for (int i = 0; i < localTypes.length; i++) { if (type.isAssignableFrom(localTypes[i])) { return true; } } for (int i = 0; i < sp; i++) { StackProducer producer = stack[i]; if (producer != null && type.isAssignableFrom(producer.getType())) { return true; } } return false; }
/** {@inheritDoc} */ public void load(Klass klass) { Assert.that(VM.isHosted() || VM.getCurrentIsolate().getLeafSuite() == suite); int state = klass.getState(); if (state < Klass.STATE_LOADED) { if (klass.isArray()) { load(klass.getComponentType()); } else { lastClassNameStack.push(klass.getName()); ClassFile classFile = getClassFile(klass); load(classFile); lastClassNameStack.pop(); } } }
/** * 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; }
/** * Generate squawk code for methods of <code>klass</code> when doing whole-suite translation * (inlining, etc.) * * @param klass the klass to generate code for */ void convertPhase2(Klass klass) { Assert.that(translationStrategy != BY_METHOD); convert(klass); if (klass.getState() < Klass.STATE_CONVERTED) { if (!VM .isVerbose()) { // "squawk -verbose" will show the class names as it finishes loading // them, which is progress enough traceProgress(); } lastClassNameStack.push(klass.getName()); ClassFile classFile = getClassFile(klass); classFile.convertPhase2(this, false); classFiles.remove(klass.getName()); lastClassNameStack.pop(); } }
/** * Returns the runtime class of an object. That <tt>Class</tt> object is the object that is locked * by <tt>static synchronized</tt> methods of the represented class. * * @return the object of type <code>Class</code> that represents the runtime class of the object. */ public final Class getClass() { Klass klass = GC.getKlass(this); if (klass == Klass.STRING_OF_BYTES) { klass = Klass.STRING; } return Klass.asClass(klass); }
/** * Gets the type of a local variable that is used to store a value of a given class. This method * partitions all classes into one of the following categories: * * <p> * * <blockquote> * * <pre> * * Local Variable Type | Types * ---------------------+------- * INT | boolean, byte, short, int * FLOAT | float * LONG | long * DOUBLE | double * ADDRESS | Address * UWORD | UWord * OFFSET | Offset * REFERENCE | types in java.lang.Object hierarchy * * </pre> * * </blockquote> * * <p> * * @param type the type of a value that will be stored in a local variable * @return the local variable type for storing values of type <code>type</code> */ public static Klass getLocalTypeFor(Klass type) { switch (type.getSystemID()) { case CID.BOOLEAN: case CID.BYTE: case CID.SHORT: case CID.CHAR: case CID.INT: { return Klass.INT; } case CID.FLOAT: case CID.LONG: case CID.DOUBLE: { return type; } case CID.UWORD: case CID.OFFSET: case CID.ADDRESS: { return type; } default: { Assert.that(Klass.REFERENCE.isAssignableFrom(type)); return Klass.REFERENCE; } } }
/** * 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()); // } } } }
/** * Gets the type of the value at a given index on the operand stack. * * @param index the operand stack index * @return the type of the value at index <code>index</code> on the operand stack */ public Klass getStackTypeAt(int index) { Assert.that(index < sp, "index out of bounds"); if (stack[index] == null) { return Klass.getSecondWordType(stack[index - 1].getType()); } else { return stack[index].getType(); } }
/** * Returns interfaces directly implemented by this type. * * @return a List of the interfaces directly implemented by this type */ @SuppressWarnings("unchecked") public List getInterfaces() throws IOException, SDWPException { Klass[] interfaces = klass.getInterfaces(); List list = new ArrayList(interfaces.length); for (int i = 0; i != interfaces.length; ++i) { list.add(ptm.lookup(interfaces[i], true)); } return list; }
/** * 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"); } }
/** * Returns a list containing each {@link Method} declared directly in this type. Inherited methods * are not included. Constructors, the initialization method if any, and any synthetic methods * created by the compiler are included in the list. * * <p>For arrays and primitive classes, the returned list is always empty. * * @return a list {@link Method} objects; the list has length 0 if no methods exist. */ public List getMethods() { if (methods == null) { if (klass.isArray()) { methods = Collections.EMPTY_LIST; } else { methods = new ArrayList(); addMethods(methods, true); addMethods(methods, false); } } return methods; }
/** * 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); } }
/** {@inheritDoc} */ public void convert(Klass klass) { lastClassNameStack.push(klass.getName()); int state = klass.getState(); if (state < Klass.STATE_CONVERTING) { if (klass.isArray()) { convert(Klass.OBJECT); klass.changeState(Klass.STATE_CONVERTED); } else { traceProgress(); ClassFile classFile = getClassFile(klass); classFile.convertPhase1(this, translationStrategy != BY_METHOD); if (klass.hasGlobalStatics()) { // record globals now. recordGlobalStatics(klass); } if (translationStrategy == BY_METHOD || translationStrategy == BY_CLASS) { // if NOT inlining, then generate squawk code now. classFile.convertPhase2(this, translationStrategy == BY_METHOD); classFiles.remove(klass.getName()); } } } lastClassNameStack.pop(); }
/** Load and converts the closure of classes in the current suite. */ public void computeClosure() { boolean changed = true; while (changed) { changed = false; for (int cno = 0; cno < suite.getClassCount(); cno++) { Klass klass = suite.getKlass(cno); if (klass == null) { continue; // if not floats, or if deferred errors, there can be some missing classes } if (klass.getState() < Klass.STATE_LOADED) { load(klass); changed = true; } if (klass.getState() < Klass.STATE_CONVERTING) { convert(klass); changed = true; } } } }
/** * 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; } }
/** * Traces a type on the operand stack or in a local variable. * * @param type the type to trace * @param prefix the prefix to use if <code>isDerived</code> is true otherwise a prefix of spaces * the same length as <code>prefix</code> is used instead * @param isDerived specifies if this a type derived by the verifer or is specified by a stack map * entry */ private void traceType(Klass type, String prefix, boolean isDerived) { if (Translator.TRACING_ENABLED) { if (!isDerived) { char[] spaces = new char[prefix.length()]; Arrays.fill(spaces, ' '); Tracer.trace(new String(spaces)); } else { Tracer.trace(prefix); } String name = (type == null ? "-T-" : type.getInternalName()); if (isDerived) { Tracer.traceln(" " + name); } else { Tracer.traceln("{" + name + "}"); } } }
/** * 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); }
/** * 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(); }
/** * Gets an identifying name for the source corresponding to the declaration of this type. * Interpretation of this string is the responsibility of the source repository mechanism. * * <p>The returned name is dependent on VM's default stratum ({@link * VirtualMachine#getDefaultStratum()}). In the reference implementation, when using the base * stratum, the returned string is the unqualified name of the source file containing the * declaration of this type. In other strata the returned source name is the first source name for * that stratum. Since other languages may have more than one source name for a reference type, * the use of {@link Location#sourceName()} or {@link #sourceNames(String)} is preferred. * * <p> * * @return the string source file name or null if it is not known */ public String getSourceName() { return klass.getSourceFileName(); }
/** * Returns the modifiers for the reference type. ACC_PUBLIC, ACC_FINAL, etc. Undefined for arrays * and primitive type. * * @return the class modifiers in JVM Spec format. */ public int getModifiers() { return klass.getModifiers() & Modifier.getJVMClassModifiers(); }
/** * 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; } }
/** * Like <code>klass</code> must not yet be converted and it must not be a {@link * Klass#isSynthetic() synthetic} class. * * @param klass the instance class for which a class file is requested * @return the class file for <code>klass</code>, or null if that ClassFile has not been * translated by this translator. */ ClassFile lookupClassFile(Klass klass) { Assert.that(!klass.isSynthetic(), "synthethic class has no classfile"); ClassFile classFile = (ClassFile) classFiles.get(klass.getInternalName()); return classFile; }