--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.services.bytecode.BCMethod\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.services.bytecode;\r
+\r
+import org.apache.derby.iapi.services.compiler.ClassBuilder;\r
+import org.apache.derby.iapi.services.compiler.MethodBuilder;\r
+import org.apache.derby.iapi.services.classfile.ClassFormatOutput;\r
+import org.apache.derby.iapi.services.compiler.LocalField;\r
+import org.apache.derby.iapi.services.classfile.ClassHolder;\r
+import org.apache.derby.iapi.services.classfile.ClassMember;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.services.classfile.VMDescriptor;\r
+import org.apache.derby.iapi.services.classfile.VMOpcode;\r
+\r
+import java.lang.reflect.Modifier;\r
+import java.util.Vector;\r
+import java.io.IOException;\r
+\r
+/**\r
+ * MethodBuilder is used to piece together a method when\r
+ * building a java class definition.\r
+ * <p>\r
+ * When a method is first created, it has:\r
+ * <ul>\r
+ * <li> a return type\r
+ * <li> modifiers\r
+ * <li> a name\r
+ * <li> an empty parameter list\r
+ * <li> an empty throws list\r
+ * <li> an empty statement block\r
+ * </ul>\r
+ * <p>\r
+ * MethodBuilder implementations are required to supply a way for\r
+ * Statements and Expressions to give them code. Most typically, they may have\r
+ * a stream to which their contents writes the code that is of\r
+ * the type to satisfy what the contents represent.\r
+ * MethodBuilder implementations also have to have a way to supply\r
+ * ClassBuilders with their code, that satisfies the type of class\r
+ * builder they are implemented with. This is implementation-dependent,\r
+ * so ClassBuilders, MethodBuilders, Statements, and Expressions all have\r
+ * to be of the same implementation in order to interact to generate a class.\r
+ * <p>\r
+ * Method Builder implementation for generating bytecode.\r
+ *\r
+ */\r
+class BCMethod implements MethodBuilder {\r
+ \r
+ /**\r
+ * Code length at which to split into sub-methods.\r
+ * Normally set to the maximim code length the\r
+ * JVM can support, but for testing the split code\r
+ * it can be reduced so that the standard tests\r
+ * cause some splitting. Tested with value set to 2000.\r
+ */\r
+ static final int CODE_SPLIT_LENGTH = VMOpcode.MAX_CODE_LENGTH;\r
+ \r
+ final BCClass cb;\r
+ protected final ClassHolder modClass; // the class it is in (modifiable fmt)\r
+ final String myReturnType;\r
+ \r
+ /**\r
+ * The original name of the method, this\r
+ * represents how any user would call this method.\r
+ */\r
+ private final String myName;\r
+\r
+ /**\r
+ * Fast access for the parametes, will be null\r
+ * if the method has no parameters.\r
+ */\r
+ BCLocalField[] parameters; \r
+ \r
+ /**\r
+ * List of parameter types with java language class names.\r
+ * Can be null or zero length for no parameters.\r
+ */\r
+ private final String[] parameterTypes;\r
+ \r
+ \r
+ Vector thrownExceptions; // expected to be names of Classes under Throwable\r
+\r
+ CodeChunk myCode;\r
+ protected ClassMember myEntry;\r
+\r
+ private int currentVarNum;\r
+ private int statementNum;\r
+ \r
+ /**\r
+ * True if we are currently switching control\r
+ * over to a sub method to avoid hitting the code generation\r
+ * limit of 65535 bytes per method.\r
+ */\r
+ private boolean handlingOverflow;\r
+ \r
+ /**\r
+ * How many sub-methods we have overflowed to.\r
+ */\r
+ private int subMethodCount;\r
+\r
+ BCMethod(ClassBuilder cb,\r
+ String returnType,\r
+ String methodName,\r
+ int modifiers,\r
+ String[] parms,\r
+ BCJava factory) {\r
+\r
+ this.cb = (BCClass) cb;\r
+ modClass = this.cb.modify();\r
+ myReturnType = returnType;\r
+ myName = methodName;\r
+\r
+ if (SanityManager.DEBUG) {\r
+ this.cb.validateType(returnType);\r
+ }\r
+\r
+ // if the method is not static, allocate for "this".\r
+ if ((modifiers & Modifier.STATIC) == 0 )\r
+ currentVarNum = 1;\r
+\r
+ String[] vmParamterTypes;\r
+\r
+ if (parms != null && parms.length != 0) {\r
+ int len = parms.length;\r
+ vmParamterTypes = new String[len];\r
+ parameters = new BCLocalField[len];\r
+ for (int i = 0; i < len; i++) {\r
+ Type t = factory.type(parms[i]);\r
+ parameters[i] = new BCLocalField(t, currentVarNum);\r
+ currentVarNum += t.width();\r
+\r
+ // convert to vmname for the BCMethodDescriptor.get() call\r
+ vmParamterTypes[i] = t.vmName();\r
+ }\r
+ }\r
+ else\r
+ vmParamterTypes = BCMethodDescriptor.EMPTY;\r
+\r
+ // create a code attribute\r
+ String sig = BCMethodDescriptor.get(vmParamterTypes, factory.type(returnType).vmName(), factory);\r
+\r
+ // stuff the completed information into the class.\r
+ myEntry = modClass.addMember(methodName, sig, modifiers);\r
+\r
+ // get code chunk\r
+ myCode = new CodeChunk(this.cb);\r
+ \r
+ parameterTypes = parms;\r
+ }\r
+ //\r
+ // MethodBuilder interface\r
+ //\r
+\r
+ /**\r
+ * Return the logical name of the method. The current\r
+ * myEntry refers to the sub method we are currently\r
+ * overflowing to. Those sub-methods are hidden from any caller.\r
+ */\r
+ public String getName() {\r
+ return myName;\r
+ }\r
+\r
+ public void getParameter(int id) {\r
+\r
+ int num = parameters[id].cpi;\r
+ short typ = parameters[id].type.vmType();\r
+ if (num < 4)\r
+ myCode.addInstr((short) (CodeChunk.LOAD_VARIABLE_FAST[typ]+num));\r
+ else\r
+ myCode.addInstrWide(CodeChunk.LOAD_VARIABLE[typ], num);\r
+\r
+ growStack(parameters[id].type);\r
+ }\r
+\r
+ /**\r
+ * a throwable can be added to the end of\r
+ * the list of thrownExceptions.\r
+ */\r
+ public void addThrownException(String exceptionClass) {\r
+ \r
+ // cannot add exceptions after code generation has started.\r
+ // Allowing this would cause the method overflow/split to\r
+ // break as the top-level method would not have the exception\r
+ // added in the sub method.\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (myCode.getPC() != 0)\r
+ SanityManager.THROWASSERT("Adding exception after code generation " + exceptionClass\r
+ + " to method " + getName());\r
+ }\r
+\r
+ if (thrownExceptions == null)\r
+ thrownExceptions = new Vector();\r
+ thrownExceptions.addElement(exceptionClass);\r
+ }\r
+\r
+ /**\r
+ * when the method has had all of its parameters\r
+ * and thrown exceptions defined, and its statement\r
+ * block has been completed, it can be completed and\r
+ * its class file information generated.\r
+ * <p>\r
+ * further alterations of the method will not be\r
+ * reflected in the code generated for it.\r
+ */\r
+ public void complete() {\r
+ \r
+ // myCode.getPC() gives the code length since\r
+ // the program counter will be positioned after\r
+ // the last instruction. Note this value can\r
+ // be changed by the splitMethod call.\r
+ \r
+ if (myCode.getPC() > CODE_SPLIT_LENGTH)\r
+ splitMethod();\r
+ \r
+ // write exceptions attribute info\r
+ writeExceptions();\r
+ \r
+ // get the code attribute to put itself into the class\r
+ // provide the final header information needed\r
+ myCode.complete(this, modClass, myEntry, maxStack, currentVarNum);\r
+ }\r
+ \r
+ /**\r
+ * Attempt to split a large method by pushing code out to several\r
+ * sub-methods. Performs a number of steps.\r
+ * <OL>\r
+ * <LI> Split at zero stack depth.\r
+ * <LI> Split at non-zero stack depth (FUTURE)\r
+ * </OL>\r
+ * \r
+ * If the class has already exceeded some limit in building the\r
+ * class file format structures then don't attempt to split.\r
+ * Most likely the number of constant pool entries has been exceeded\r
+ * and thus the built class file no longer has integrity.\r
+ * The split code relies on being able to read the in-memory\r
+ * version of the class file in order to determine descriptors\r
+ * for methods and fields.\r
+ */\r
+ private void splitMethod() {\r
+ \r
+ int split_pc = 0;\r
+ boolean splittingZeroStack = true;\r
+ for (int codeLength = myCode.getPC();\r
+ (cb.limitMsg == null) &&\r
+ (codeLength > CODE_SPLIT_LENGTH);\r
+ codeLength = myCode.getPC())\r
+ {\r
+ int lengthToCheck = codeLength - split_pc;\r
+\r
+ int optimalMinLength;\r
+ if (codeLength < CODE_SPLIT_LENGTH * 2) {\r
+ // minimum required\r
+ optimalMinLength = codeLength - CODE_SPLIT_LENGTH;\r
+ } else {\r
+ // try to split as much as possible\r
+ // need one for the return instruction\r
+ optimalMinLength = CODE_SPLIT_LENGTH - 1;\r
+ }\r
+\r
+ if (optimalMinLength > lengthToCheck)\r
+ optimalMinLength = lengthToCheck;\r
+\r
+ if (splittingZeroStack)\r
+ {\r
+ split_pc = myCode.splitZeroStack(this, modClass, split_pc,\r
+ optimalMinLength);\r
+ }\r
+ else\r
+ {\r
+ // Note the split expression does not re-start split\r
+ // at point left off by the previous split expression.\r
+ // This could be done but would require some level\r
+ // of stack depth history to be kept across calls.\r
+ split_pc = myCode.splitExpressionOut(this, modClass,\r
+ optimalMinLength, maxStack);\r
+\r
+ }\r
+\r
+ // Negative split point returned means that no split\r
+ // was possible. Give up on this approach and goto\r
+ // the next approach.\r
+ if (split_pc < 0) {\r
+ if (!splittingZeroStack)\r
+ break;\r
+ splittingZeroStack = false;\r
+ split_pc = 0;\r
+ }\r
+\r
+ // success, continue on splitting after the call to the\r
+ // sub-method if the method still execeeds the maximum length.\r
+ }\r
+ \r
+ \r
+ }\r
+\r
+ /*\r
+ * class interface\r
+ */\r
+\r
+ /**\r
+ * In their giveCode methods, the parts of the method body will want to get\r
+ * to the constant pool to add their constants. We really only want them\r
+ * treating it like a constant pool inclusion mechanism, we could write a\r
+ * wrapper to limit it to that.\r
+ */\r
+ ClassHolder constantPool() {\r
+ return modClass;\r
+ }\r
+\r
+\r
+ //\r
+ // Class implementation\r
+ //\r
+\r
+\r
+ /**\r
+ * sets exceptionBytes to the attribute_info needed\r
+ * for a method's Exceptions attribute.\r
+ * The ClassUtilities take care of the header 6 bytes for us,\r
+ * so they are not included here.\r
+ * See The Java Virtual Machine Specification Section 4.7.5,\r
+ * Exceptions attribute.\r
+ */\r
+ protected void writeExceptions() {\r
+ if (thrownExceptions == null)\r
+ return;\r
+\r
+ int numExc = thrownExceptions.size();\r
+\r
+ // don't write an Exceptions attribute if there are no exceptions.\r
+ if (numExc != 0) {\r
+\r
+ try{\r
+ ClassFormatOutput eout = new ClassFormatOutput((numExc * 2) + 2);\r
+\r
+ eout.putU2(numExc); // number_of_exceptions\r
+\r
+ for (int i = 0; i < numExc; i++) {\r
+ // put each exception into the constant pool\r
+ String e = thrownExceptions.elementAt(i).toString();\r
+ int ei2 = modClass.addClassReference(e);\r
+\r
+ // add constant pool index to exception attribute_info\r
+ eout.putU2(ei2);\r
+ }\r
+\r
+ myEntry.addAttribute("Exceptions", eout);\r
+\r
+ } catch (IOException ioe) {\r
+ } \r
+ }\r
+ }\r
+\r
+ /*\r
+ ** New push compiler api.\r
+ */\r
+\r
+ /**\r
+ * Array of the current types of the values on the stack.\r
+ * A type that types up two words on the stack, e.g. double\r
+ * will only occupy one element in this array.\r
+ * This array is dynamically re-sized as needed.\r
+ */\r
+ private Type[] stackTypes = new Type[8];\r
+ \r
+ /**\r
+ * Points to the next array offset in stackTypes\r
+ * to be used. Really it's the number of valid entries\r
+ * in stackTypes.\r
+ */\r
+ private int stackTypeOffset;\r
+\r
+ /**\r
+ * Maximum stack depth seen in this method, measured in words.\r
+ * Corresponds to max_stack in the Code attribute of section 4.7.3\r
+ * of the vm spec.\r
+ */\r
+ int maxStack;\r
+ \r
+ /**\r
+ * Current stack depth in this method, measured in words.\r
+ */\r
+ private int stackDepth;\r
+\r
+ private void growStack(int size, Type type) {\r
+ stackDepth += size;\r
+ if (stackDepth > maxStack)\r
+ maxStack = stackDepth;\r
+ \r
+ if (stackTypeOffset >= stackTypes.length) {\r
+\r
+ Type[] newStackTypes = new Type[stackTypes.length + 8];\r
+ System.arraycopy(stackTypes, 0, newStackTypes, 0, stackTypes.length);\r
+ stackTypes = newStackTypes;\r
+ }\r
+\r
+ stackTypes[stackTypeOffset++] = type;\r
+\r
+ if (SanityManager.DEBUG) {\r
+\r
+ int sum = 0;\r
+ for (int i = 0 ; i < stackTypeOffset; i++) {\r
+ sum += stackTypes[i].width();\r
+ }\r
+ if (sum != stackDepth) {\r
+ SanityManager.THROWASSERT("invalid stack depth " + stackDepth + " calc " + sum);\r
+ }\r
+ }\r
+ }\r
+\r
+ private void growStack(Type type) {\r
+ growStack(type.width(), type);\r
+ }\r
+\r
+ private Type popStack() {\r
+ stackTypeOffset--;\r
+ Type topType = stackTypes[stackTypeOffset];\r
+ stackDepth -= topType.width();\r
+ return topType;\r
+\r
+ }\r
+ \r
+ private Type[] copyStack()\r
+ {\r
+ Type[] stack = new Type[stackTypeOffset];\r
+ System.arraycopy(stackTypes, 0, stack, 0, stackTypeOffset);\r
+ return stack;\r
+ }\r
+\r
+ public void pushThis() {\r
+ myCode.addInstr(VMOpcode.ALOAD_0);\r
+ growStack(1, cb.classType);\r
+ }\r
+\r
+ public void push(byte value) {\r
+ push(value, Type.BYTE);\r
+ }\r
+\r
+ public void push(boolean value) {\r
+ push(value ? 1 : 0, Type.BOOLEAN);\r
+ }\r
+\r
+ public void push(short value) {\r
+ push(value, Type.SHORT);\r
+ }\r
+\r
+ public void push(int value) {\r
+ push(value, Type.INT);\r
+ }\r
+\r
+ public void dup() {\r
+ Type dup = popStack();\r
+ myCode.addInstr(dup.width() == 2 ? VMOpcode.DUP2 : VMOpcode.DUP);\r
+ growStack(dup);\r
+ growStack(dup);\r
+\r
+ }\r
+\r
+ public void swap() {\r
+\r
+ // have A,B\r
+ // want B,A\r
+\r
+ Type wB = popStack();\r
+ Type wA = popStack();\r
+ growStack(wB);\r
+ growStack(wA);\r
+\r
+ if (wB.width() == 1) {\r
+ // top value is one word\r
+ if (wA.width() == 1) {\r
+ myCode.addInstr(VMOpcode.SWAP);\r
+ return;\r
+ } else {\r
+ myCode.addInstr(VMOpcode.DUP_X2);\r
+ myCode.addInstr(VMOpcode.POP);\r
+ }\r
+ } else {\r
+ // top value is two words\r
+ if (wA.width() == 1) {\r
+ myCode.addInstr(VMOpcode.DUP2_X1);\r
+ myCode.addInstr(VMOpcode.POP2);\r
+ } else {\r
+ myCode.addInstr(VMOpcode.DUP2_X2);\r
+ myCode.addInstr(VMOpcode.POP2);\r
+ }\r
+ }\r
+\r
+ // all except the simple swap push an extra\r
+ // copy of B which needs to be popped.\r
+ growStack(wB);\r
+ popStack();\r
+\r
+ }\r
+\r
+ /**\r
+ * Push an integer value. Uses the special integer opcodes\r
+ * for the constants -1 to 5, BIPUSH for values that fit in\r
+ * a byte and SIPUSH for values that fit in a short. Otherwise\r
+ * uses LDC with a constant pool entry.\r
+ * \r
+ * @param value Value to be pushed\r
+ * @param type Final type of the value.\r
+ */\r
+ private void push(int value, Type type) {\r
+\r
+ CodeChunk chunk = myCode;\r
+\r
+ if (value >= -1 && value <= 5)\r
+ chunk.addInstr((short)(VMOpcode.ICONST_0+value));\r
+ else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE)\r
+ chunk.addInstrU1(VMOpcode.BIPUSH,value);\r
+ else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE)\r
+ chunk.addInstrU2(VMOpcode.SIPUSH,value);\r
+ else {\r
+ int cpe = modClass.addConstant(value);\r
+ addInstrCPE(VMOpcode.LDC, cpe);\r
+ }\r
+ growStack(type.width(), type);\r
+ \r
+ }\r
+\r
+ /**\r
+ * Push a long value onto the stack.\r
+ * For the values zero and one the LCONST_0 and\r
+ * LCONST_1 instructions are used.\r
+ * For values betwee Short.MIN_VALUE and Short.MAX_VALUE\r
+ * inclusive an byte/short/int value is pushed\r
+ * using push(int, Type) followed by an I2L instruction.\r
+ * This saves using a constant pool entry for such values.\r
+ * All other values use a constant pool entry. For values\r
+ * in the range of an Integer an integer constant pool\r
+ * entry is created to allow sharing with integer constants\r
+ * and to reduce constant pool slot entries.\r
+ */\r
+ public void push(long value) {\r
+ CodeChunk chunk = myCode;\r
+\r
+ if (value == 0L || value == 1L) {\r
+ short opcode = value == 0L ? VMOpcode.LCONST_0 : VMOpcode.LCONST_1;\r
+ chunk.addInstr(opcode);\r
+ } else if (value >= Integer.MIN_VALUE && value <= Integer.MAX_VALUE) {\r
+ // the push(int, Type) method grows the stack for us.\r
+ push((int) value, Type.LONG);\r
+ chunk.addInstr(VMOpcode.I2L);\r
+ return;\r
+ } else {\r
+ int cpe = modClass.addConstant(value);\r
+ chunk.addInstrU2(VMOpcode.LDC2_W, cpe);\r
+ }\r
+ growStack(2, Type.LONG);\r
+ }\r
+ public void push(float value) {\r
+\r
+ CodeChunk chunk = myCode;\r
+ \r
+ if (value == 0.0)\r
+ {\r
+ chunk.addInstr(VMOpcode.FCONST_0);\r
+ }\r
+ else if (value == 1.0)\r
+ {\r
+ chunk.addInstr(VMOpcode.FCONST_1);\r
+ }\r
+ else if (value == 2.0)\r
+ {\r
+ chunk.addInstr(VMOpcode.FCONST_2);\r
+ }\r
+ else \r
+ {\r
+ int cpe = modClass.addConstant(value);\r
+ addInstrCPE(VMOpcode.LDC, cpe);\r
+ }\r
+ growStack(1, Type.FLOAT);\r
+ }\r
+\r
+ public void push(double value) {\r
+ CodeChunk chunk = myCode;\r
+\r
+ if (value == 0.0) {\r
+ chunk.addInstr(VMOpcode.DCONST_0);\r
+ }\r
+ else {\r
+ int cpe = modClass.addConstant(value);\r
+ chunk.addInstrU2(VMOpcode.LDC2_W, cpe);\r
+ }\r
+ growStack(2, Type.DOUBLE);\r
+ }\r
+ public void push(String value) {\r
+ int cpe = modClass.addConstant(value);\r
+ addInstrCPE(VMOpcode.LDC, cpe);\r
+ growStack(1, Type.STRING);\r
+ }\r
+ \r
+\r
+ public void methodReturn() {\r
+\r
+ short opcode; \r
+ if (stackDepth != 0) {\r
+ Type topType = popStack();\r
+ opcode = CodeChunk.RETURN_OPCODE[topType.vmType()];\r
+ } else {\r
+ opcode = VMOpcode.RETURN;\r
+ }\r
+\r
+ myCode.addInstr(opcode);\r
+\r
+ if (SanityManager.DEBUG) {\r
+ if (stackDepth != 0)\r
+ SanityManager.THROWASSERT("items left on stack " + stackDepth);\r
+ }\r
+ }\r
+\r
+ public Object describeMethod(short opcode, String declaringClass, String methodName, String returnType) {\r
+\r
+ Type rt = cb.factory.type(returnType);\r
+\r
+ String methodDescriptor = BCMethodDescriptor.get(BCMethodDescriptor.EMPTY, rt.vmName(), cb.factory);\r
+\r
+ if ((declaringClass == null) && (opcode != VMOpcode.INVOKESTATIC)) {\r
+\r
+ Type dt = stackTypes[stackTypeOffset - 1];\r
+\r
+ if (declaringClass == null)\r
+ declaringClass = dt.javaName();\r
+ }\r
+ \r
+ int cpi = modClass.addMethodReference(declaringClass, methodName,\r
+ methodDescriptor, opcode == VMOpcode.INVOKEINTERFACE);\r
+\r
+ return new BCMethodCaller(opcode, rt, cpi);\r
+ }\r
+\r
+ public int callMethod(Object methodDescriptor) {\r
+\r
+ // pop the reference off the stack\r
+ popStack();\r
+\r
+ BCMethodCaller mc = (BCMethodCaller) methodDescriptor;\r
+\r
+ int cpi = mc.cpi;\r
+ short opcode = mc.opcode;\r
+\r
+ if (opcode == VMOpcode.INVOKEINTERFACE) {\r
+ myCode.addInstrU2U1U1(opcode, cpi, (short) 1, (short) 0);\r
+ }\r
+ else\r
+ myCode.addInstrU2(opcode, cpi);\r
+ \r
+ // this is the return type of the method\r
+ Type rt = mc.type;\r
+ int rw = rt.width();\r
+ if (rw != 0)\r
+ growStack(rw, rt);\r
+ else\r
+ {\r
+ if (stackDepth == 0)\r
+ overflowMethodCheck();\r
+ }\r
+ return cpi;\r
+ }\r
+\r
+ public int callMethod(short opcode, String declaringClass, String methodName,\r
+ String returnType, int numArgs) {\r
+\r
+ Type rt = cb.factory.type(returnType);\r
+\r
+ int initialStackDepth = stackDepth;\r
+\r
+ // get the array of parameter types\r
+\r
+ String [] debugParameterTypes = null;\r
+ String[] vmParameterTypes;\r
+ if (numArgs == 0) {\r
+ vmParameterTypes = BCMethodDescriptor.EMPTY;\r
+ } else {\r
+ if (SanityManager.DEBUG) {\r
+ debugParameterTypes = new String[numArgs];\r
+ }\r
+ vmParameterTypes = new String[numArgs];\r
+ for (int i = numArgs - 1; i >= 0; i--) {\r
+ Type at = popStack();\r
+\r
+ vmParameterTypes[i] = at.vmName();\r
+ if (SanityManager.DEBUG) {\r
+ debugParameterTypes[i] = at.javaName();\r
+ }\r
+ }\r
+ }\r
+ \r
+ String methodDescriptor = BCMethodDescriptor.get(vmParameterTypes, rt.vmName(), cb.factory);\r
+\r
+ Type dt = null;\r
+ if (opcode != VMOpcode.INVOKESTATIC) {\r
+\r
+ dt = popStack();\r
+ }\r
+ Type dtu = vmNameDeclaringClass(declaringClass);\r
+ if (dtu != null)\r
+ dt = dtu;\r
+ \r
+ int cpi = modClass.addMethodReference(dt.vmNameSimple, methodName,\r
+ methodDescriptor, opcode == VMOpcode.INVOKEINTERFACE);\r
+\r
+ if (opcode == VMOpcode.INVOKEINTERFACE) {\r
+ short callArgCount = (short) (initialStackDepth - stackDepth);\r
+ myCode.addInstrU2U1U1(opcode, cpi, callArgCount, (short) 0);\r
+ }\r
+ else\r
+ myCode.addInstrU2(opcode, cpi);\r
+ \r
+ // this is the return type of the method\r
+ int rw = rt.width();\r
+ if (rw != 0)\r
+ growStack(rw, rt);\r
+ else\r
+ {\r
+ if (stackDepth == 0)\r
+ overflowMethodCheck();\r
+ }\r
+ // Check the declared type of the method\r
+ if (SanityManager.DEBUG) {\r
+\r
+ d_BCValidate.checkMethod(opcode, dt, methodName, debugParameterTypes, rt);\r
+ }\r
+\r
+ return cpi;\r
+ }\r
+\r
+ private Type vmNameDeclaringClass(String declaringClass) {\r
+ if (declaringClass == null)\r
+ return null;\r
+ return cb.factory.type(declaringClass);\r
+ }\r
+\r
+ public void callSuper() {\r
+\r
+ pushThis();\r
+ callMethod(VMOpcode.INVOKESPECIAL, cb.getSuperClassName(), "<init>", "void", 0);\r
+ }\r
+\r
+ public void pushNewStart(String className) {\r
+\r
+ int cpi = modClass.addClassReference(className);\r
+\r
+ // Use U2, not CPE, since only wide form exists.\r
+ myCode.addInstrU2(VMOpcode.NEW, cpi);\r
+ myCode.addInstr(VMOpcode.DUP);\r
+\r
+ // Grow the stack twice as we are pushing\r
+ // two instances of newly created reference\r
+ Type nt = cb.factory.type(className);\r
+ growStack(1, nt);\r
+ growStack(1, nt);\r
+ }\r
+\r
+ public void pushNewComplete(int numArgs) {\r
+ callMethod(VMOpcode.INVOKESPECIAL, (String) null, "<init>", "void", numArgs);\r
+ }\r
+\r
+ public void upCast(String className) {\r
+ Type uct = cb.factory.type(className);\r
+\r
+ stackTypes[stackTypeOffset - 1] = uct;\r
+ //popStack();\r
+ //growStack(1, uct);\r
+ }\r
+\r
+ public void cast(String className) {\r
+ \r
+ // Perform a simple optimization to not\r
+ // insert a checkcast when the classname\r
+ // of the cast exactly matches the type name\r
+ // currently on the stack.\r
+ // This can reduce the amount of generated code.\r
+ // This compiler/class generator does not load\r
+ // classes to check relationships or any other\r
+ // information. Thus other optimizations where a cast\r
+ // is not required are not implemented.\r
+ Type tbc = stackTypes[stackTypeOffset - 1];\r
+ \r
+ short sourceType = tbc.vmType();\r
+ \r
+ if (sourceType == BCExpr.vm_reference)\r
+ {\r
+ // Simple optimize step\r
+ if (className.equals(tbc.javaName()))\r
+ {\r
+ // do nothing, exact matching type\r
+ return;\r
+ }\r
+ }\r
+ \r
+ Type ct = cb.factory.type(className);\r
+ popStack();\r
+ \r
+ short targetType = ct.vmType();\r
+\r
+ if (SanityManager.DEBUG) {\r
+\r
+ if (!((sourceType == BCExpr.vm_reference &&\r
+ targetType == BCExpr.vm_reference) ||\r
+ (sourceType != BCExpr.vm_reference &&\r
+ targetType != BCExpr.vm_reference))) {\r
+ SanityManager.THROWASSERT("Both or neither must be object types " + ct.javaName() + " " + tbc.javaName());\r
+ }\r
+ }\r
+\r
+ // if it is an object type, do a checkcast on it.\r
+ if (sourceType == BCExpr.vm_reference) {\r
+\r
+ int cpi = modClass.addClassReference(ct.vmNameSimple);\r
+ myCode.addInstrU2(VMOpcode.CHECKCAST, cpi);\r
+ }\r
+ // otherwise, try to convert it.\r
+ else {\r
+ short opcode = VMOpcode.NOP;\r
+\r
+ // we use the conversionInfo array\r
+ // to determine how to convert; if\r
+ // the result type of the conversion\r
+ // is not our target type, we are not done\r
+ // yet. Make sure there are no\r
+ // infinite loop possibilities in the\r
+ // conversionInfo array!\r
+ while (sourceType!=targetType && opcode!=VMOpcode.BAD) {\r
+ short[] currentConversion = \r
+ CodeChunk.CAST_CONVERSION_INFO[sourceType][targetType];\r
+ sourceType = currentConversion[1];\r
+ opcode = currentConversion[0];\r
+ if (opcode != VMOpcode.NOP) {\r
+ myCode.addInstr(opcode);\r
+ }\r
+ }\r
+ if (SanityManager.DEBUG) {\r
+ SanityManager.ASSERT(opcode != VMOpcode.BAD,\r
+ "BAD VMOpcode not expected in cast");\r
+ }\r
+ }\r
+ growStack(ct);\r
+ }\r
+\r
+ public void isInstanceOf(String className) {\r
+ int cpi = modClass.addClassReference(className);\r
+ myCode.addInstrU2(VMOpcode.INSTANCEOF, cpi);\r
+ popStack();\r
+ growStack(1, Type.BOOLEAN);\r
+ }\r
+\r
+ public void pushNull(String type) {\r
+ myCode.addInstr(VMOpcode.ACONST_NULL);\r
+ growStack(1, cb.factory.type(type));\r
+ }\r
+\r
+\r
+ public void getField(LocalField field) {\r
+\r
+ BCLocalField lf = (BCLocalField) field;\r
+ Type lt = lf.type;\r
+\r
+ pushThis();\r
+ myCode.addInstrU2(VMOpcode.GETFIELD, lf.cpi);\r
+\r
+ popStack();\r
+ growStack(lt);\r
+\r
+ }\r
+\r
+ public void getField(String declaringClass, String fieldName, String fieldType) {\r
+ Type dt = popStack();\r
+\r
+ Type dtu = vmNameDeclaringClass(declaringClass);\r
+ if (dtu != null)\r
+ dt = dtu;\r
+\r
+ getField(VMOpcode.GETFIELD, dt.vmNameSimple, fieldName, fieldType);\r
+ }\r
+ /**\r
+ Push the contents of the described static field onto the stack. \r
+ */\r
+ public void getStaticField(String declaringClass, String fieldName, String fieldType) {\r
+ getField(VMOpcode.GETSTATIC, declaringClass, fieldName, fieldType);\r
+ }\r
+\r
+ private void getField(short opcode, String declaringClass, String fieldName, String fieldType) { \r
+\r
+ Type ft = cb.factory.type(fieldType);\r
+ int cpi = modClass.addFieldReference(vmNameDeclaringClass(declaringClass).vmNameSimple, fieldName, ft.vmName());\r
+ myCode.addInstrU2(opcode, cpi);\r
+\r
+ growStack(ft);\r
+ }\r
+ \r
+ /**\r
+ * Set the field but don't duplicate its value so\r
+ * nothing is left on the stack after this call.\r
+ */\r
+ public void setField(LocalField field) {\r
+ BCLocalField lf = (BCLocalField) field;\r
+ Type lt = lf.type;\r
+\r
+ putField(lf.type, lf.cpi, false);\r
+\r
+ if (stackDepth == 0)\r
+ overflowMethodCheck();\r
+ }\r
+\r
+ /**\r
+ Upon entry the top word(s) on the stack is\r
+ the value to be put into the field. Ie.\r
+ we have\r
+ <PRE>\r
+ word\r
+ </PRE>\r
+\r
+ Before the call we need \r
+ <PRE>\r
+ word\r
+ this\r
+ word\r
+ </PRE>\r
+ word2,word1 -> word2, word1, word2\r
+\r
+ So that we are left with word after the put.\r
+\r
+ */\r
+ public void putField(LocalField field) {\r
+ BCLocalField lf = (BCLocalField) field;\r
+ Type lt = lf.type;\r
+\r
+ putField(lf.type, lf.cpi, true);\r
+ }\r
+\r
+ /**\r
+ Pop the top stack value and store it in the instance field of this class.\r
+ */\r
+ public void putField(String fieldName, String fieldType) {\r
+\r
+ Type ft = cb.factory.type(fieldType);\r
+ int cpi = modClass.addFieldReference(cb.classType.vmNameSimple, fieldName, ft.vmName());\r
+\r
+ putField(ft, cpi, true);\r
+ }\r
+\r
+ private void putField(Type fieldType, int cpi, boolean dup) {\r
+\r
+ // now have ...,value\r
+ if (dup)\r
+ {\r
+ myCode.addInstr(fieldType.width() == 2 ? VMOpcode.DUP2 : VMOpcode.DUP);\r
+ growStack(fieldType);\r
+ }\r
+ // now have\r
+ // dup true: ...,value,value\r
+ // dup false: ...,value,\r
+\r
+ pushThis();\r
+ // now have\r
+ // dup true: ...,value,value,this\r
+ // dup false: ...,value,this\r
+\r
+ swap();\r
+ // now have\r
+ // dup true: ...,value,this,value\r
+ // dup false: ...,this,value\r
+\r
+ myCode.addInstrU2(VMOpcode.PUTFIELD, cpi);\r
+ popStack(); // the value\r
+ popStack(); // this\r
+\r
+ // now have\r
+ // dup true: ...,value\r
+ // dup false: ...\r
+ }\r
+ /**\r
+ Pop the top stack value and store it in the field.\r
+ This call requires the instance to be pushed by the caller.\r
+ */\r
+ public void putField(String declaringClass, String fieldName, String fieldType) {\r
+ Type vt = popStack();\r
+ Type dt = popStack();\r
+\r
+ if (SanityManager.DEBUG) {\r
+ if (dt.width() != 1)\r
+ SanityManager.THROWASSERT("reference expected for field access - is " + dt.javaName());\r
+ }\r
+\r
+ // have objectref,value\r
+ // need value,objectref,value\r
+\r
+ myCode.addInstr(vt.width() == 2 ? VMOpcode.DUP2_X1 : VMOpcode.DUP_X1);\r
+ growStack(vt);\r
+ growStack(dt);\r
+ growStack(vt);\r
+\r
+ Type dtu = vmNameDeclaringClass(declaringClass);\r
+ if (dtu != null)\r
+ dt = dtu;\r
+\r
+ Type ft = cb.factory.type(fieldType);\r
+ int cpi = modClass.addFieldReference(dt.vmNameSimple, fieldName, ft.vmName());\r
+ myCode.addInstrU2(VMOpcode.PUTFIELD, cpi);\r
+\r
+ popStack(); // value\r
+ popStack(); // reference\r
+ }\r
+\r
+ public void conditionalIfNull() {\r
+\r
+ conditionalIf(VMOpcode.IFNONNULL);\r
+ }\r
+\r
+ public void conditionalIf() {\r
+ conditionalIf(VMOpcode.IFEQ);\r
+ }\r
+\r
+ private Conditional condition;\r
+\r
+ private void conditionalIf(short opcode) {\r
+ popStack();\r
+ \r
+ // Save the stack upon entry to the 'then' block of the\r
+ // 'if' so that we can set up the 'else' block with the\r
+ // correct stack on entry.\r
+\r
+ condition = new Conditional(condition, myCode, opcode, copyStack());\r
+ }\r
+\r
+ public void startElseCode() {\r
+ \r
+ // start the else code\r
+ Type[] entryStack = condition.startElse(this, myCode, copyStack());\r
+ \r
+ for (int i = stackDepth = 0; i < entryStack.length; i++)\r
+ {\r
+ stackDepth += (stackTypes[i] = entryStack[i]).width();\r
+ }\r
+ this.stackTypeOffset = entryStack.length;\r
+\r
+ }\r
+ public void completeConditional() {\r
+ condition = condition.end(this, myCode, stackTypes, stackTypeOffset);\r
+ }\r
+ \r
+ public void pop() {\r
+ if (SanityManager.DEBUG) {\r
+ if (stackDepth == 0)\r
+ SanityManager.THROWASSERT("pop when stack is empty!");\r
+ }\r
+ Type toPop = popStack();\r
+\r
+ myCode.addInstr(toPop.width() == 2 ? VMOpcode.POP2 : VMOpcode.POP);\r
+ \r
+ if (stackDepth == 0)\r
+ overflowMethodCheck();\r
+ } \r
+\r
+ public void endStatement() {\r
+ if (stackDepth != 0) {\r
+ pop();\r
+ }\r
+\r
+ //if (SanityManager.DEBUG) {\r
+ // if (stackDepth != 0)\r
+ // SanityManager.THROWASSERT("items left on stack " + stackDepth);\r
+ // }\r
+ }\r
+\r
+ /**\r
+ */\r
+ public void getArrayElement(int element) {\r
+\r
+ push(element);\r
+ popStack(); // int just pushed will be popped by array access\r
+\r
+ Type arrayType = popStack();\r
+\r
+ String arrayJava = arrayType.javaName();\r
+ String componentString = arrayJava.substring(0,arrayJava.length()-2);\r
+\r
+ Type componentType = cb.factory.type(componentString);\r
+\r
+ short typ = componentType.vmType();\r
+\r
+ // boolean has a type id of integer, here it needs to be byte.\r
+ if ((typ == BCExpr.vm_int) && (componentType.vmName().equals("Z")))\r
+ typ = BCExpr.vm_byte;\r
+ myCode.addInstr(CodeChunk.ARRAY_ACCESS[typ]);\r
+\r
+ growStack(componentType);\r
+\r
+ }\r
+ // come in with ref, value\r
+\r
+ public void setArrayElement(int element) {\r
+\r
+ // ref, value\r
+\r
+ push(element);\r
+\r
+ // ref, value, index\r
+ swap();\r
+ \r
+ Type componentType = popStack(); // value\r
+ popStack(); // int just pushed will be popped by array access\r
+ \r
+ popStack(); // array ref.\r
+\r
+ short typ = componentType.vmType();\r
+\r
+ // boolean has a type id of integer, here it needs to be byte.\r
+ if ((typ == BCExpr.vm_int) && (componentType.vmName().equals("Z")))\r
+ typ = BCExpr.vm_byte;\r
+\r
+ myCode.addInstr(CodeChunk.ARRAY_STORE[typ]);\r
+ }\r
+ /**\r
+ this array maps the BCExpr vm_* constants 0..6 to\r
+ the expected VM type constants for the newarray instruction.\r
+ <p>\r
+ Because boolean was mapped to integer for general instructions,\r
+ it will have to be specially matched and mapped to its value\r
+ directly (4).\r
+ */\r
+ private static final byte newArrayElementTypeMap[] = { 8, 9, 10, 11, 6, 7, 5 };\r
+ static final byte T_BOOLEAN = 4;\r
+ /**\r
+ Create an array instance\r
+\r
+ Stack ... =>\r
+ ...,arrayref\r
+ */\r
+ public void pushNewArray(String className, int size) {\r
+\r
+ push(size);\r
+ popStack(); // int just pushed will be popped by array creation\r
+\r
+ Type elementType = cb.factory.type(className);\r
+\r
+ // determine the instruction to use based on the element type\r
+ if (elementType.vmType() == BCExpr.vm_reference) {\r
+\r
+ // For an array of Java class/interface elements, generate:\r
+ // ANEWARRAY #cpei ; where cpei is a constant pool index for the class\r
+\r
+ int cpi = modClass.addClassReference(elementType.javaName());\r
+ // Use U2, not CPE, since only wide form exists.\r
+ myCode.addInstrU2(VMOpcode.ANEWARRAY, cpi);\r
+ } else {\r
+ byte atype;\r
+\r
+ // get the argument for the array type\r
+ // if the element type is boolean, we can't use the map\r
+ // because the type id will say integer.\r
+ // but we can use vm_int test to weed out some tests\r
+ if (elementType.vmType() == BCExpr.vm_int &&\r
+ VMDescriptor.C_BOOLEAN == elementType.vmName().charAt(0))\r
+ atype = T_BOOLEAN;\r
+ else\r
+ atype = newArrayElementTypeMap[elementType.vmType()];\r
+\r
+ // For an array of Java builtin type elements, generate:\r
+ // NEWARRAY #atype ; where atype is a constant for the builtin type\r
+\r
+ myCode.addInstrU1(VMOpcode.NEWARRAY, atype);\r
+ }\r
+\r
+ // an array reference is an object, hence width of 1\r
+ growStack(1, cb.factory.type(className.concat("[]")));\r
+ }\r
+ \r
+ /**\r
+ * Write a instruction that uses a constant pool entry\r
+ * as an operand, add a limit exceeded message if\r
+ * the number of constant pool entries has exceeded\r
+ * the limit.\r
+ */\r
+ private void addInstrCPE(short opcode, int cpe)\r
+ {\r
+ if (cpe >= VMOpcode.MAX_CONSTANT_POOL_ENTRIES)\r
+ cb.addLimitExceeded(this, "constant_pool_count",\r
+ VMOpcode.MAX_CONSTANT_POOL_ENTRIES, cpe);\r
+ \r
+ myCode.addInstrCPE(opcode, cpe);\r
+ }\r
+\r
+ /**\r
+ Tell if statement number in this method builder hits limit. This\r
+ method builder keeps a counter of how many statements are added to it.\r
+ Caller should call this function every time it tries to add a statement\r
+ to this method builder (counter is increased by 1), then the function\r
+ returns whether the accumulated statement number hits a limit.\r
+ The reason of doing this is that Java compiler has a limit of 64K code\r
+ size for each method. We might hit this limit if an extremely long\r
+ insert statement is issued, for example (see beetle 4293). Counting\r
+ statement number is an approximation without too much overhead.\r
+ */\r
+ public boolean statementNumHitLimit(int noStatementsAdded)\r
+ {\r
+ if (statementNum > 2048) // 2K limit\r
+ {\r
+ return true;\r
+ }\r
+ else\r
+ {\r
+ statementNum = statementNum + noStatementsAdded;\r
+ return false;\r
+ }\r
+ }\r
+ \r
+ /**\r
+ * Check to see if the current method byte code is nearing the\r
+ * limit of 65535. If it is start overflowing to a new method.\r
+ * <P>\r
+ * Overflow is handled for a method named e23 as:\r
+ * <CODE>\r
+ public Object e23()\r
+ {\r
+ ... existing code\r
+ // split point\r
+ return e23_0();\r
+ }\r
+ private Object e23_0()\r
+ {\r
+ ... first set overflowed code\r
+ // split point\r
+ return e23_1(); \r
+ }\r
+ private Object e23_1()\r
+ {\r
+ ... second set overflowed code\r
+ // method complete\r
+ return result; \r
+ }\r
+ </CODE>\r
+ <P>\r
+ \r
+ These overflow methods are hidden from the code using this MethodBuilder,\r
+ it continues to think that it is building a single method with the\r
+ original name.\r
+\r
+\r
+ * <BR> Restrictions:\r
+ * <UL>\r
+ * <LI> Only handles methods with no arguments\r
+ * <LI> Stack depth must be zero\r
+ * </UL>\r
+ * \r
+ *\r
+ */\r
+ private void overflowMethodCheck()\r
+ {\r
+ if (handlingOverflow)\r
+ return;\r
+ \r
+ // don't sub method in the middle of a conditional\r
+ if (condition != null)\r
+ return;\r
+ \r
+ int currentCodeSize = myCode.getPC();\r
+ \r
+ // Overflow at >= 55,000 bytes which is someway\r
+ // below the limit of 65,535. Ideally overflow\r
+ // would occur at 65535 minus the few bytes needed\r
+ // to call the sub-method, but the issue is at this level\r
+ // we don't know frequently we are called given the restriction\r
+ // of only being called when the stack depth is zero.\r
+ // Thus split earlier to try ensure most cases are caught.\r
+ // Only downside is that we may split into N methods when N-1 would suffice.\r
+ if (currentCodeSize < 55000)\r
+ return;\r
+ \r
+ // only handle no-arg methods at the moment.\r
+ if (parameters != null)\r
+ {\r
+ if (parameters.length != 0)\r
+ return;\r
+ }\r
+ \r
+ BCMethod subMethod = getNewSubMethod(myReturnType, false);\r
+ \r
+ // stop any recursion\r
+ handlingOverflow = true;\r
+ \r
+ // in this method make a call to the sub method we will\r
+ // be transferring control to.\r
+ callSubMethod(subMethod);\r
+ \r
+ // and return its value, works just as well for a void method!\r
+ this.methodReturn();\r
+ this.complete();\r
+ \r
+ handlingOverflow = false;\r
+ \r
+ // now the tricky bit, make this object take over the\r
+ // code etc. from the sub method. This is done so\r
+ // that any code that has a reference to this MethodBuilder\r
+ // will continue to work. They will be writing code into the\r
+ // new sub method.\r
+ \r
+ this.myEntry = subMethod.myEntry;\r
+ this.myCode = subMethod.myCode;\r
+ this.currentVarNum = subMethod.currentVarNum;\r
+ this.statementNum = subMethod.statementNum;\r
+ \r
+ // copy stack info\r
+ this.stackTypes = subMethod.stackTypes;\r
+ this.stackTypeOffset = subMethod.stackTypeOffset;\r
+ this.maxStack = subMethod.maxStack;\r
+ this.stackDepth = subMethod.stackDepth;\r
+ }\r
+ \r
+ /**\r
+ * Create a sub-method from this method to allow the code builder to split a\r
+ * single logical method into multiple methods to avoid the 64k per-method\r
+ * code size limit. The sub method with inherit the thrown exceptions of\r
+ * this method.\r
+ * \r
+ * @param returnType\r
+ * Return type of the new method\r
+ * @param withParameters\r
+ * True to define the method with matching parameters false to\r
+ * define it with no parameters.\r
+ * @return A valid empty sub method.\r
+ */\r
+ final BCMethod getNewSubMethod(String returnType, boolean withParameters) {\r
+ int modifiers = myEntry.getModifier();\r
+\r
+ // the sub-method can be private to ensure that no-one\r
+ // can call it accidentally from outside the class.\r
+ modifiers &= ~(Modifier.PROTECTED | Modifier.PUBLIC);\r
+ modifiers |= Modifier.PRIVATE;\r
+\r
+ String subMethodName = myName + "_s"\r
+ + Integer.toString(subMethodCount++);\r
+ BCMethod subMethod = (BCMethod) cb.newMethodBuilder(modifiers,\r
+ returnType, subMethodName, withParameters ? parameterTypes\r
+ : null);\r
+ subMethod.thrownExceptions = this.thrownExceptions;\r
+ \r
+ return subMethod;\r
+ }\r
+\r
+ /**\r
+ * Call a sub-method created by getNewSubMethod handling parameters\r
+ * correctly.\r
+ */\r
+ final void callSubMethod(BCMethod subMethod) {\r
+ // in this method make a call to the sub method we will\r
+ // be transferring control to.\r
+ short op;\r
+ if ((myEntry.getModifier() & Modifier.STATIC) == 0) {\r
+ op = VMOpcode.INVOKEVIRTUAL;\r
+ this.pushThis();\r
+ } else {\r
+ op = VMOpcode.INVOKESTATIC;\r
+ }\r
+\r
+ int parameterCount = subMethod.parameters == null ? 0\r
+ : subMethod.parameters.length;\r
+\r
+ // push my parameter values for the call.\r
+ for (int pi = 0; pi < parameterCount; pi++)\r
+ this.getParameter(pi);\r
+\r
+ this.callMethod(op, modClass.getName(), subMethod.getName(),\r
+ subMethod.myReturnType, parameterCount);\r
+ }\r
+}\r
+\r