/* * Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Business Objects nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* * AsmJavaBytecodeGenerator.java * Created: Dec 21, 2004 * By: Bo Ilic */ package org.openquark.cal.internal.javamodel; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.Label; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.openquark.cal.internal.javamodel.JavaExpression.ArrayAccess; import org.openquark.cal.internal.javamodel.JavaExpression.ArrayCreationExpression; import org.openquark.cal.internal.javamodel.JavaExpression.ArrayLength; import org.openquark.cal.internal.javamodel.JavaExpression.Assignment; import org.openquark.cal.internal.javamodel.JavaExpression.CastExpression; import org.openquark.cal.internal.javamodel.JavaExpression.ClassInstanceCreationExpression; import org.openquark.cal.internal.javamodel.JavaExpression.ClassLiteral; import org.openquark.cal.internal.javamodel.JavaExpression.InstanceOf; import org.openquark.cal.internal.javamodel.JavaExpression.JavaField; import org.openquark.cal.internal.javamodel.JavaExpression.LiteralWrapper; import org.openquark.cal.internal.javamodel.JavaExpression.LocalName; import org.openquark.cal.internal.javamodel.JavaExpression.LocalVariable; import org.openquark.cal.internal.javamodel.JavaExpression.MethodInvocation; import org.openquark.cal.internal.javamodel.JavaExpression.MethodVariable; import org.openquark.cal.internal.javamodel.JavaExpression.OperatorExpression; import org.openquark.cal.internal.javamodel.JavaExpression.PlaceHolder; import org.openquark.cal.internal.javamodel.JavaStatement.AssertStatement; import org.openquark.cal.internal.javamodel.JavaStatement.Block; import org.openquark.cal.internal.javamodel.JavaStatement.Comment; import org.openquark.cal.internal.javamodel.JavaStatement.ExpressionStatement; import org.openquark.cal.internal.javamodel.JavaStatement.IfThenElseStatement; import org.openquark.cal.internal.javamodel.JavaStatement.LabelledContinue; import org.openquark.cal.internal.javamodel.JavaStatement.LocalVariableDeclaration; import org.openquark.cal.internal.javamodel.JavaStatement.ReturnStatement; import org.openquark.cal.internal.javamodel.JavaStatement.SwitchStatement; import org.openquark.cal.internal.javamodel.JavaStatement.SynchronizedMethodInvocation; import org.openquark.cal.internal.javamodel.JavaStatement.ThrowStatement; import org.openquark.cal.internal.javamodel.JavaStatement.UnconditionalLoop; import org.openquark.cal.internal.javamodel.JavaStatement.SwitchStatement.IntCaseGroup; import org.openquark.cal.internal.runtime.lecc.LECCMachineConfiguration; /** * A class to generate Java bytecode given an object model representation of Java source. * This class makes use of the ASM bytecode generator library (http://asm.objectweb.org/). This is * a BSD-licensed library and is thread safe (see the FAQ on the ASM website for the precise limitations * of this). * * <P>The original Java source model to bytecode generator was written by Edward Lam used the BCEL library. * Some differences between ASM and BCEL: * -ASM is lower level, requiring us to specify the precise byte-code instruction to encode. * -ASM is thread-safe, BCEL is not. * -ASM requires that byte codes be generated in order. BCEL creates intermediate instruction list objects that can be * reordered and further manipulated before being converted to byte codes. * -ASM is significantly faster than BCEL * -After its implementation, the ASM bytecode generator was further refined to make the byte code it * produces closer to what javac produces. There were quite a few optimizations added. While in principle * these could also be done with the BCEL generator, they were not. * * <P>The BCEL generator was then deleted to remove the necessity of including the BCEL jars in the * distribution of CAL. It can be restored from the file JavaByteCodeGenerator.java if needed. * * @author Bo Ilic */ public final class AsmJavaBytecodeGenerator { /** * Holds information for describing the context for code generation for a method. * For example, it contains information about method variables, local variables, and labels (to continue to) in scope. * @author Bo Ilic */ private static final class GenerationContext { /** The parent context. */ private final GenerationContext parentContext; /** use this MethodVisitor for encoding the generated byte codes. */ private final MethodVisitor mv; /** the next local variable will be at this index in the JVM frame. It may take 1 or 2 slots, depending on it type. */ private int nextAvailableVarIndex; /** map from local var name to its index in the JVM frame. */ private final Map<String, Integer> localVarNameToIndexMap; /** map from method var name to its index in the JVM frame. */ private final Map<String, Integer> methodVarToIndexMap; /** map from method var name to its type. Includes "this" for non-static methods. */ private final Map<String, JavaTypeName> methodVarToTypeMap; /** map from a label name to the corresponding Label object. Used to implement labelled continue statements. */ private final Map<String, Label> labelNameToLabelMap; /** Labels in this context, or in descendant contexts. */ private final Set<Label> containedStatementEscapeLabelSet; /** (List of JumpReturnLabelInfo) Info about relevant return and jump instruction labels enclosed by this context, * in the order in which they appear. */ private final List<JumpReturnLabelInfo> jumpReturnLabelList = new ArrayList<JumpReturnLabelInfo>(); /** JavaTypeName of the class being generated. */ private final JavaTypeName classTypeName; /** * Constructor for the outermost GenerationContext for a method. * @param methodVarToTypeMap * @param methodVarToIndexMap * @param nextAvailableVarIndex * @param mv * @param classTypeName */ GenerationContext(Map<String, JavaTypeName> methodVarToTypeMap, Map<String, Integer> methodVarToIndexMap, int nextAvailableVarIndex, MethodVisitor mv, JavaTypeName classTypeName) { this.parentContext = null; this.methodVarToTypeMap = methodVarToTypeMap; this.methodVarToIndexMap = methodVarToIndexMap; this.nextAvailableVarIndex = nextAvailableVarIndex; this.mv = mv; this.classTypeName = classTypeName; this.localVarNameToIndexMap = new HashMap<String, Integer>(); this.labelNameToLabelMap = new HashMap<String, Label>(); this.containedStatementEscapeLabelSet = new HashSet<Label>(); } /** * Constructor for a GenerationContext from a parent context. * @param parentContext */ GenerationContext(GenerationContext parentContext) { this.parentContext = parentContext; this.methodVarToTypeMap = parentContext.methodVarToTypeMap; this.methodVarToIndexMap = parentContext.methodVarToIndexMap; this.nextAvailableVarIndex = parentContext.nextAvailableVarIndex; this.mv = parentContext.mv; this.classTypeName = parentContext.classTypeName; this.localVarNameToIndexMap = new HashMap<String, Integer>(parentContext.localVarNameToIndexMap); this.labelNameToLabelMap = new HashMap<String, Label>(parentContext.labelNameToLabelMap); this.containedStatementEscapeLabelSet = new HashSet<Label>(); } int addLocalVar(String localVarName, JavaTypeName localVarType) { if (localVarNameToIndexMap.put(localVarName, Integer.valueOf(nextAvailableVarIndex)) != null) { throw new IllegalArgumentException("Duplicate local variable declaration: " + localVarName); } int indexOfVar = nextAvailableVarIndex; nextAvailableVarIndex += getTypeSize(localVarType); return indexOfVar; } int getLocalVarIndex(String varName) { Integer index = localVarNameToIndexMap.get(varName); if (index != null) { return index.intValue(); } return -1; } /** * Add a label at the scope of this context for a jump instruction generated for the purposes of statement control flow. * (not for exception labels, nor for jumps resulting from boolean valued operator evaluation, etc.). * @param label the label to add */ void addStatementJumpLabel(Label label) { // add the label to this context and parent contexts. for (GenerationContext context = GenerationContext.this; context != null; context = context.parentContext) { context.containedStatementEscapeLabelSet.add(label); } } /** * Add a named label at the scope of this context for a jump instruction generated for the purposes of statement control flow. * (not for exception labels, nor for jumps resulting from boolean valued operator evaluation, etc.). * @param labelName the name of the label * @param label the label to add */ void addStatementJumpLabel(String labelName, Label label) { if (labelNameToLabelMap.put(labelName, label) != null) { throw new IllegalArgumentException("Repeated addition of a label in a single context: " + labelName); } addStatementJumpLabel(label); } Label getNamedStatementJumpLabel(String labelName) { return labelNameToLabelMap.get(labelName); } /** * @param label the label to find * @return true if both of these conditions apply: * 1) it exists within the scope of this context (ie. in this context or child contexts). * 2) it is a label for a jump instruction whose destination lies outside the scope of the associated statement. */ boolean containsStatementJumpLabel(Label label) { return containedStatementEscapeLabelSet.contains(label); } /** * Add a JumpReturnLabelInfo generated (in order) within the scope of this context. * @param jumpReturnLabelInfo the info to add */ void addJumpReturnLabelInfo(JumpReturnLabelInfo jumpReturnLabelInfo) { jumpReturnLabelList.add(jumpReturnLabelInfo); } /** * Add any JumpReturnLabelInfo generated (in order) by a child context. * @param childContext the child context whose labels to add. */ void addJumpReturnLabelInfo(GenerationContext childContext) { jumpReturnLabelList.addAll(childContext.jumpReturnLabelList); } /** * @return (List of JumpReturnLabelInfo) * The list of relevant return and jump instruction labels generated within this context, in the order in which they appear. */ List<JumpReturnLabelInfo> getJumpReturnLabelInfoList() { return Collections.unmodifiableList(jumpReturnLabelList); } MethodVisitor getMethodVisitor() { return mv; } int getMethodVarIndex(String varName) { Integer index = methodVarToIndexMap.get(varName); if (index != null) { return index.intValue(); } return -1; } JavaTypeName getMethodVarType(String varName) { return methodVarToTypeMap.get(varName); } } /** * Info about a jump or return instruction which has been labeled in order to handle exception table entries * for try-catch statements with try blocks which span the code generating this instruction. * * Such instructions are excluded from any exception handling blocks if they cause execution to escape the bounds of the try statement. * eg. if there is a return statement in a try block in a try-catch statement, the corresponding return instruction will * be excluded from the exception table entry. * if there is a jump (eg. goto) statement in a try block in a try-catch statement, the jump instruction will be * excluded from the exception table entry if the jump destination lies outside the try block. * * @author Edward Lam */ private static class JumpReturnLabelInfo { private final Label instructionLabel; private final Label afterInstructionLabel; private final Label destinationLabel; /** * Constructor for a JumpReturnLabelInfo. * @param instructionLabel the label for the jump or return instruction. * @param afterInstructionLabel the label after the jump or return instruction. * @param destinationLabel the destination label for a jump instruction, or null if it's a return instruction. */ private JumpReturnLabelInfo(Label instructionLabel, Label afterInstructionLabel, Label destinationLabel) { this.instructionLabel = instructionLabel; this.afterInstructionLabel = afterInstructionLabel; this.destinationLabel = destinationLabel; } /** * @return the label for the instruction. */ public Label getInstructionLabel() { return instructionLabel; } /** * @return the label after the instruction. */ public Label getAfterInstructionLabel() { return afterInstructionLabel; } /** * @return the destination label of the jump or null if it's a return instruction. */ public Label getDestinationLabel() { return destinationLabel; } } /** * Constructor for a JavaBytecodeGenerator */ AsmJavaBytecodeGenerator() { } /** * Encodes bytecode for a given class. * @param classRep the representation for the top-level class. * @return byte[] the byte code for the class. * @throws JavaGenerationException */ public static byte[] encodeClass(JavaClassRep classRep) throws JavaGenerationException { //get ASM to compute max stack and max locals. Don't compute stack map frames yet. final ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); encodeClassAcceptingVisitor(classRep, cw); byte[] result = cw.toByteArray(); // TODO: It would be faster to not have to read the bytecode back out of the array. // We do this since we require ClassWriter to computer the maximum locals and stack // before the analysis, which is required since the analysis's Frame class needs to // know these maximums since it stores the values of locals and the stack in an array. if (LECCMachineConfiguration.bytecodeSpaceOptimization()) { final ClassReader cr = new ClassReader(result); final ClassWriter cw2 = new ClassWriter(ClassWriter.COMPUTE_MAXS); final ClassVisitor cv = new NullingClassAdapter(cw2); cr.accept(cv, 0); result = cw2.toByteArray(); } return result; } /** * Accepts a visitor and fills it with information for a given class. * @param classRep the representation for the top-level class. * @param cv the class visitor to accept. * @throws JavaGenerationException */ private static void encodeClassAcceptingVisitor(JavaClassRep classRep, ClassVisitor cv) throws JavaGenerationException { // Get the fully-qualified internal class and superclass names. final JavaTypeName classRepTypeName = classRep.getClassName(); final String className = classRepTypeName.getJVMInternalName(); final String superclassName = classRep.getSuperclassName().getJVMInternalName(); // Determine if the class or any inner class contains assert statements. int assertPresence = classRep.getAssertionContainment(); if ((assertPresence & JavaClassRep.ASSERTS_UNKNOWN) > 0) { assertPresence = AsmJavaBytecodeGenerator.containsAsserts(classRep); } // Create the interfaces[] array. final int nInterfaces = classRep.getNInterfaces(); final String[] interfaces = new String[nInterfaces]; for (int i = 0; i < nInterfaces; i++) { interfaces[i] = classRep.getInterface(i).getJVMInternalName(); } //ACC_SUPER flag should always be set for the flags defining a class file. //(see the Java language specification under ACC_SUPER in the index. The flag is not set only //by older Java compilers and exists for backwards compatibility reasons). int classModifiers = classRep.getModifiers() | Opcodes.ACC_SUPER; //static inner classes are marked with the static modifier, but this is not a valid access flag for a class. classModifiers &= ~Modifier.STATIC; // We aren't generating or using generics, so the signature can be null String classSignature = null; cv.visit(Opcodes.V1_5, classModifiers, className, classSignature, superclassName, interfaces); //sourcefileName = null, since this class was not compiled from a Java source file. //However, if we are debugging byte codes, use a "fake" source file name as if this class were generated from a Java source file. //This eliminates a trivial difference between the byte code generated by ASM and that of javac and makes inspecting the //differences in a differencing tool easier. String sourceFileName = null; // if (AsmJavaBytecodeGenerator.DEBUG_GENERATED_BYTECODE) { String unqualifiedName = classRepTypeName.getUnqualifiedJavaSourceName(); int dotPosition = unqualifiedName.indexOf('.'); if (dotPosition != -1) { //get the top level class name. unqualifiedName = unqualifiedName.substring(0, dotPosition); } sourceFileName = unqualifiedName + ".java"; // } cv.visitSource(sourceFileName, null); //add the fields for (int i = 0, nFields = classRep.getNFieldDeclarations(); i < nFields; ++i) { JavaFieldDeclaration fieldDeclaration = classRep.getFieldDeclaration(i); //todoBI it may be more efficient to handle initializers for static-fields here in the cases where it is possible //(int, long, float, double, String). cv.visitField(fieldDeclaration.getModifiers(), fieldDeclaration.getFieldName(), fieldDeclaration.getFieldType().getJVMDescriptor(), null, null); } /* * When dealing with assert statements there is possibly an additional field that needs to be * added. * If a class contains an assert statement a static final synthetic boolean field called '$assertionsDisabled' is * added. This field is initialized in the class static initializer and is used to determine whether to * check or skip assertions. */ if (assertPresence != JavaClassRep.ASSERTS_NONE) { if ((assertPresence & JavaClassRep.ASSERTS_IN_CLASS) > 0) { // We need to add a static final synthetic boolean field to indicate the enabled/disabled state of assertions. cv.visitField(Opcodes.ACC_FINAL + Opcodes.ACC_STATIC + Opcodes.ACC_SYNTHETIC, "$assertionsDisabled", "Z", null, null); } } //add the constructors final int nConstructors = classRep.getNConstructors(); if (nConstructors == 0) { //if empty, add the default constructor. JavaConstructor defaultConstructor = new JavaConstructor(Modifier.PUBLIC, ((JavaTypeName.Reference.Object)classRepTypeName).getBaseName()); defaultConstructor.addStatement(new JavaStatement.ReturnStatement()); encodeConstructor(classRep, defaultConstructor, cv); } else { for (int i = 0; i < nConstructors; ++i) { encodeConstructor(classRep, classRep.getConstructor(i), cv); } } //add the methods for (int i = 0, nMethods = classRep.getNMethods(); i < nMethods; ++i) { encodeMethod(classRep, classRep.getMethod(i), cv); } //add the initializers for the static fields encodeClassInitializer(classRep, cv, (assertPresence & JavaClassRep.ASSERTS_IN_CLASS) > 0); //add the inner classes (these are basically just references to the inner classes) //if classRep itself is an inner class, call visitInnerClass on itself. This is what the eclipse java compiler does. //javac annotates for every inner class reference occurring within the class file e.g. a field declared of inner class type, //an instance of expression of inner class type, a throws declaration on a method where an inner class is thrown. if (classRepTypeName.isInnerClass()) { JavaTypeName.Reference.Object classTypeName = (JavaTypeName.Reference.Object)classRepTypeName; String internalImportName = classTypeName.getImportName().replace('.', '/'); cv.visitInnerClass(classTypeName.getJVMInternalName(), internalImportName, classTypeName.getBaseName(), classRep.getModifiers()); } /* * Previously we would call visitInnerClass for any inner classes associated with this class. However, * we are no longer doing this. * Bytecode is generated in different scenarios (i.e. static generation, dynamic generation, etc). In some * scenarios inner classes are generated separately from the containing class. In order to keep the generated * bytecode consistent between the dynamic and static scenarios wer are not adding the attributes for contained * inner classes to the bytecode. * for (int i = 0, nInnerClasses = classRep.getNInnerClasses(); i < nInnerClasses; ++i) { JavaClassRep innerClass = classRep.getInnerClass(i); JavaTypeName.Reference.Object innerClassTypeName = (JavaTypeName.Reference.Object)innerClass.getClassName(); cw.visitInnerClass(innerClassTypeName.getJVMInternalName(), className, innerClassTypeName.getBaseName(), innerClass.getModifiers()); } */ cv.visitEnd(); } /** * Add the <init> method for the given constructor. This also includes initializing the non-static fields that * have initializers. * @param classRep * @param javaConstructor * @param cv * @throws JavaGenerationException */ private static void encodeConstructor(JavaClassRep classRep, JavaConstructor javaConstructor, ClassVisitor cv) throws JavaGenerationException { // gather info on the thrown exceptions final int nThrownExceptions = javaConstructor.getNThrownExceptions(); String[] thrownExceptions = new String[nThrownExceptions]; for (int i = 0; i < nThrownExceptions; ++i) { thrownExceptions[i] = javaConstructor.getThrownException(i).getJVMInternalName(); } MethodVisitor mv = cv.visitMethod(javaConstructor.getModifiers(), "<init>", javaConstructor.getJVMMethodDescriptor(), null, thrownExceptions); final Map<String, JavaTypeName> methodVarToTypeMap = new HashMap<String, JavaTypeName>(); final Map<String, Integer> methodVarToIndexMap = new HashMap<String, Integer>(); methodVarToTypeMap.put("this", classRep.getClassName()); methodVarToIndexMap.put("this", Integer.valueOf(0)); int indexOfNextSlot = 1; for (int i = 0, nParams = javaConstructor.getNParams(); i < nParams; ++i) { JavaTypeName varType = javaConstructor.getParamType(i); final int typeSize = getTypeSize(varType); String varName = javaConstructor.getParamName(i); methodVarToTypeMap.put(varName, varType); methodVarToIndexMap.put(varName, Integer.valueOf(indexOfNextSlot)); indexOfNextSlot += typeSize; } // Start the method's code. mv.visitCode(); // Invoke the superclass initializer. if (javaConstructor.getSuperConstructorParamTypes().length == 0) { //push the "this" reference on the stack mv.visitVarInsn(Opcodes.ALOAD, 0); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, classRep.getSuperclassName().getJVMInternalName(), "<init>", "()V"); } else { GenerationContext context = new GenerationContext(methodVarToTypeMap, methodVarToIndexMap, indexOfNextSlot, mv, classRep.getClassName()); MethodInvocation mi = new MethodInvocation.Instance(null, "<init>", classRep.getSuperclassName(), javaConstructor.getSuperConstructorParamValues(), javaConstructor.getSuperConstructorParamTypes(), JavaTypeName.VOID, MethodInvocation.InvocationType.SPECIAL); encodeMethodInvocationExpr(mi, context); } // Add initializers for the non-statically-initialized fields. final int nFields = classRep.getNFieldDeclarations(); for (int i = 0; i < nFields; ++i) { JavaFieldDeclaration fieldDecl = classRep.getFieldDeclaration(i); JavaExpression initializer = fieldDecl.getInitializer(); if (initializer != null && !Modifier.isStatic(fieldDecl.getModifiers())) { GenerationContext context = new GenerationContext(methodVarToTypeMap, methodVarToIndexMap, indexOfNextSlot, mv, classRep.getClassName()); //push the "this" reference mv.visitVarInsn(Opcodes.ALOAD, 0); //evaluate the initializer encodeExpr(initializer, context); //do the assignment mv.visitFieldInsn(Opcodes.PUTFIELD, classRep.getClassName().getJVMInternalName(), fieldDecl.getFieldName(), fieldDecl.getFieldType().getJVMDescriptor()); } } // Add the body code for the constructor GenerationContext context = new GenerationContext(methodVarToTypeMap, methodVarToIndexMap, indexOfNextSlot, mv, classRep.getClassName()); boolean isTerminating = encodeStatement(javaConstructor.getBodyCode(), context); // Add any implicit "return" statement. if (!isTerminating) { mv.visitInsn(Opcodes.RETURN); } //mark the end of encoding the constructor mv.visitMaxs(0, 0); // End the method. mv.visitEnd(); } /** * Add the <clinit> method. This initializes the static fields that have initializers. * @param classRep * @param cv * @param initializeForAsserts * @throws JavaGenerationException */ private static void encodeClassInitializer(JavaClassRep classRep, ClassVisitor cv, boolean initializeForAsserts) throws JavaGenerationException { // Add initializers for the statically-initialized fields. final int nFields = classRep.getNFieldDeclarations(); if (!classRep.hasInitializedStaticField() && !initializeForAsserts) { //we don't need to bother adding a static initializer if there are no static fields to initialize. //note that javac also has this optimization. return; } MethodVisitor mv = cv.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null); // Start the method's code. mv.visitCode(); final JavaTypeName classRepTypeName = classRep.getClassName(); /* * If this class contains assert statements we need to initialize the static final synthetic boolean field * '$assertionsDisabled'. * This is done by loading the Class constant for the top level class. The method 'desiredAssertionStatus()' * is then invoked on this Class object and the returned value is used to initialize $assertionsDisabled. */ if (initializeForAsserts) { // Get the fully-qualified internal class name. final String className = classRepTypeName.getJVMInternalName(); // Get the top level class name. String topLevelClassName = getTopLevelClassJVMInternalName(className); // Load the Class constant for the top level class mv.visitLdcInsn(Type.getType("L" + topLevelClassName + ";")); // Now that we have the Class constant for the top level class we invoke the method // desiredAssertionStatus on it and use the resulting value to initialize the static field $assertionsDisabled in this class. mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "desiredAssertionStatus", "()Z"); Label l0 = new Label(); mv.visitJumpInsn(Opcodes.IFNE, l0); mv.visitInsn(Opcodes.ICONST_1); Label l1 = new Label(); mv.visitJumpInsn(Opcodes.GOTO, l1); mv.visitLabel(l0); mv.visitInsn(Opcodes.ICONST_0); mv.visitLabel(l1); mv.visitFieldInsn(Opcodes.PUTSTATIC, className, "$assertionsDisabled", "Z"); } for (int i = 0; i < nFields; ++i) { JavaFieldDeclaration fieldDecl = classRep.getFieldDeclaration(i); JavaExpression initializer = fieldDecl.getInitializer(); if (initializer != null && Modifier.isStatic(fieldDecl.getModifiers())) { GenerationContext context = new GenerationContext(new HashMap<String, JavaTypeName>(), new HashMap<String, Integer>(), 0, mv, classRepTypeName); //evaluate the initializer encodeExpr(initializer, context); //do the assignment mv.visitFieldInsn(Opcodes.PUTSTATIC, classRep.getClassName().getJVMInternalName(), fieldDecl.getFieldName(), fieldDecl.getFieldType().getJVMDescriptor()); } } // return. mv.visitInsn(Opcodes.RETURN); //mark the end of the method with a call to visitMaxs mv.visitMaxs(0, 0); // End the method. mv.visitEnd(); } /** * @param jvmInternalClassName the internal name of the class (potentially containing '$' if an inner class) * @return the internal name of the top-level class containing the named class. */ private static String getTopLevelClassJVMInternalName(final String jvmInternalClassName) { String topLevelClassName = jvmInternalClassName; if (jvmInternalClassName.indexOf('$') >= 0) { topLevelClassName = jvmInternalClassName.substring(0, jvmInternalClassName.indexOf('$')); } return topLevelClassName; } /** * Encode java byte code for the given method. * @param classRep model for the class in which the method is defined. * @param javaMethod * @param cv * @throws JavaGenerationException */ private static void encodeMethod(JavaClassRep classRep, JavaMethod javaMethod, ClassVisitor cv) throws JavaGenerationException { // gather info on the thrown exceptions final int nThrownExceptions = javaMethod.getNThrownExceptions(); String[] thrownExceptions = new String[nThrownExceptions]; for (int i = 0; i < nThrownExceptions; ++i) { thrownExceptions[i] = javaMethod.getThrownException(i).getJVMInternalName(); } //visit the method itself MethodVisitor mv = cv.visitMethod(javaMethod.getModifiers(), javaMethod.getMethodName(), javaMethod.getJVMMethodDescriptor(), null, thrownExceptions); // Add the code. mv.visitCode(); final Map<String, JavaTypeName> methodVarToTypeMap = new HashMap<String, JavaTypeName>(); final Map<String, Integer> methodVarToIndexMap = new HashMap<String, Integer>(); int indexOfNextSlot = 0; if (!Modifier.isStatic(javaMethod.getModifiers())) { methodVarToTypeMap.put("this", classRep.getClassName()); methodVarToIndexMap.put("this", Integer.valueOf(0)); indexOfNextSlot = 1; } for (int i = 0, nParams = javaMethod.getNParams(); i < nParams; ++i) { JavaTypeName varType = javaMethod.getParamType(i); final int typeSize = getTypeSize(varType); String varName = javaMethod.getParamName(i); methodVarToTypeMap.put(varName, varType); methodVarToIndexMap.put(varName, Integer.valueOf(indexOfNextSlot)); indexOfNextSlot += typeSize; } GenerationContext context = new GenerationContext(methodVarToTypeMap, methodVarToIndexMap, indexOfNextSlot, mv, classRep.getClassName()); boolean isTerminating = encodeStatement(javaMethod.getBodyCode(), context); // Add any implicit "return" statement. if (!isTerminating && javaMethod.getReturnType().equals(JavaTypeName.VOID)) { context.getMethodVisitor().visitInsn(Opcodes.RETURN); } // Apply peep hole optimizations. //todoBI no peephole optimizations with ASM... //PeepHoleOptimizer.peepHoleOptimize(il); //The ClassWriter was constructed with a parameter to automatically compute max stack and max locals. //However, we must still call visitMaxs to mark off the end of the method. The arguments are ignored. mv.visitMaxs(0, 0); // End the method. mv.visitEnd(); } private static boolean encodeLocalVariableDeclaration(JavaStatement.LocalVariableDeclaration declaration, GenerationContext context) throws JavaGenerationException{ MethodVisitor mv = context.getMethodVisitor(); LocalVariable localVariable = declaration.getLocalVariable(); // encode the instructions for the initializer if any. JavaExpression initializer = declaration.getInitializer(); if (initializer != null) { encodeExpr(initializer, context); } int localVarIndex = context.addLocalVar(localVariable.getName(), localVariable.getTypeName()); // Store the value from the initializer (if any) into the local var. if (initializer != null) { //encode the store instruction mv.visitVarInsn(getStoreOpCode(localVariable.getTypeName()), localVarIndex); } return false; } private static boolean encodeExprStatement(JavaStatement.ExpressionStatement expressionStatement, GenerationContext context) throws JavaGenerationException { MethodVisitor mv = context.getMethodVisitor(); // get the result of evaluating the expression. The value should not be left on the stack. JavaExpression expr = expressionStatement.getJavaExpression(); if (expr instanceof Assignment) { //assign without leaving a value on the stack (since it will be immediately popped off by the statement terminating semicolon. encodeAssignmentExpr((Assignment)expr, context, false); } else { JavaTypeName exprType = encodeExpr(expr, context); //pop the resulting value off the stack if (!exprType.equals(JavaTypeName.VOID)) { mv.visitInsn(getPopOpCode(exprType)); } } return false; } /** * @param type * @return the POP or POP2 instruction, depending on type. */ private static int getPopOpCode (JavaTypeName type) { switch (type.getTag()) { case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: case JavaTypeName.FLOAT_TAG: case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: return Opcodes.POP; case JavaTypeName.LONG_TAG: case JavaTypeName.DOUBLE_TAG: return Opcodes.POP2; case JavaTypeName.VOID_TAG: default: { throw new IllegalArgumentException("invalid type: " + type); } } } private static boolean encodeReturnStatement(JavaStatement.ReturnStatement returnStatement, GenerationContext context) throws JavaGenerationException { MethodVisitor mv = context.getMethodVisitor(); JavaExpression returnExpression = returnStatement.getReturnExpression(); // Label the return instruction and the instruction after the return. // This instruction will be excluded from relevant ranges covered in the exception table. int returnOpCode; if (returnExpression == null) { returnOpCode = Opcodes.RETURN; } else { // encode the result of evaluating the return expression. JavaTypeName returnType = encodeExpr(returnExpression, context); returnOpCode = getReturnOpCode(returnType); } // Add a label for the return instruction. Label returnLabel = new Label(); mv.visitLabel(returnLabel); // Visit the return instruction. mv.visitInsn(returnOpCode); // Add a label following the return instruction. Label afterReturnLabel = new Label(); mv.visitLabel(afterReturnLabel); // Add info about the generated label. context.addJumpReturnLabelInfo(new JumpReturnLabelInfo(returnLabel, afterReturnLabel, null)); // return statements are terminating. return true; } /** * @param type * @return the RETURN, IRETURN,... op code, depending on type. */ private static int getReturnOpCode (JavaTypeName type) { switch (type.getTag()) { case JavaTypeName.VOID_TAG: return Opcodes.RETURN; case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: return Opcodes.IRETURN; case JavaTypeName.LONG_TAG: return Opcodes.LRETURN; case JavaTypeName.DOUBLE_TAG: return Opcodes.DRETURN; case JavaTypeName.FLOAT_TAG: return Opcodes.FRETURN; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: return Opcodes.ARETURN; default: { throw new IllegalArgumentException("invalid type: " + type); } } } private static boolean encodeIfThenElseStatement(JavaStatement.IfThenElseStatement iteStatement, GenerationContext context) throws JavaGenerationException { MethodVisitor mv = context.getMethodVisitor(); JavaExpression conditionExpr = iteStatement.getCondition(); JavaStatement thenStatement = iteStatement.getThenStatement(); JavaStatement elseStatement = iteStatement.getElseStatement(); if (conditionExpr instanceof JavaExpression.OperatorExpression) { //generate more efficient code in the case of if (boolean-valued-operator)... //This case exists to handle the special case where an operator occurs as the child of an if-then-else (or ternary operator) //conditional. For example, in the situation: // //if (x != null) {...} else {...} // // we do not want to evaluate x != null to a boolean value, push that value on the stack, // and then test it prior to selecting the correct branch. Rather, we can combine the evaluation // and jump operations into a single step. JavaExpression.OperatorExpression operatorExpr = (JavaExpression.OperatorExpression)conditionExpr; JavaOperator operator = operatorExpr.getJavaOperator(); if (operator.isLogicalOp() || operator.isRelationalOp()) { Label trueContinuation = new Label(); context.addStatementJumpLabel(trueContinuation); Label falseContinuation = new Label(); context.addStatementJumpLabel(falseContinuation); encodeBooleanValuedOperatorHelper(operatorExpr, context, trueContinuation, falseContinuation); return encodeThenStatementElseStatement(trueContinuation, falseContinuation, thenStatement, elseStatement, context); } throw new JavaGenerationException("Unrecognized boolean-valued conditional operator " + operator.getSymbol() + "."); } //encode the condition. It will be boolean valued. encodeExpr(iteStatement.getCondition(), context); //if false, jump to falseContinuation Label falseContinuation = new Label(); mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation); return encodeThenStatementElseStatement(null, falseContinuation, thenStatement, elseStatement, context); } private static boolean encodeThenStatementElseStatement(Label trueContinuation, Label falseContinuation, JavaStatement thenStatement, JavaStatement elseStatement, GenerationContext context) throws JavaGenerationException { MethodVisitor mv = context.getMethodVisitor(); if (trueContinuation != null) { mv.visitLabel(trueContinuation); } //in general the "then" part will have its own inner scope, but we optimize this out in the case of ExpressionStatements which //can't introduce new variables. GenerationContext thenContext = (thenStatement instanceof JavaStatement.ExpressionStatement) ? context : new GenerationContext(context); boolean thenIsTerminating = encodeStatement(thenStatement, thenContext); context.addJumpReturnLabelInfo(thenContext); if (elseStatement.emptyStatement()) { //don't bother to encode the goto skipping over the else part if there is not else part. mv.visitLabel(falseContinuation); //the if-then-else is not terminating, because the else part, which is just {}, is not terminating return false; } Label label2 = null; if (!thenIsTerminating) { label2 = new Label(); context.addStatementJumpLabel(label2); mv.visitJumpInsn(Opcodes.GOTO, label2); } mv.visitLabel(falseContinuation); //in general the "else" part will have its own inner scope, but we optimize this out in the case of ExpressionStatements which //can't introduce new variables. GenerationContext elseContext = (elseStatement instanceof JavaStatement.ExpressionStatement) ? context : new GenerationContext(context); boolean elseIsTerminating = encodeStatement(elseStatement, elseContext); context.addJumpReturnLabelInfo(elseContext); if (!thenIsTerminating) { mv.visitLabel(label2); } return thenIsTerminating && elseIsTerminating; } private static boolean encodeSwitchStatement(JavaStatement.SwitchStatement switchStatement, GenerationContext context) throws JavaGenerationException { MethodVisitor mv = context.getMethodVisitor(); // Append the instructions to evaluate the condition, leaving the result on the operand stack. JavaExpression condition = switchStatement.getCondition(); encodeExpr(condition, context); // Follow (what seems to be) javac's rule for deciding between a lookup switch and a table switch: // Use a table switch if allowed by maxTableSize, where // maxTableSize = (# cases - 2) * 5 , (# cases) doesn't include the default. // // eg. if there are 2 case alternatives (+ default): // maxTableSize = ((2 - 2) * 5) = 0, so always use a lookup switch. // if there are cases {1, 2, 3, 4, 15}: // maxTableSize = ((5 - 2) * 5) = 15. A table would contain 15 entries {1, 2, .., 15}, so use a table switch. // if there are cases {1, 2, 3, 4, 16}: // A table would contain 16 entries {1, 2, .., 16}, so use a lookup switch. List<IntCaseGroup> caseGroups = switchStatement.getCaseGroups(); int nTotalCases = 0; // Calculate the first and last values. int firstValue = Integer.MAX_VALUE; int lastValue = Integer.MIN_VALUE; for (int caseN = 0; caseN < caseGroups.size(); ++caseN) { SwitchStatement.IntCaseGroup switchCaseGroup = caseGroups.get(caseN); for (int i = 0, nCaseLabels = switchCaseGroup.getNCaseLabels(); i < nCaseLabels; i++) { nTotalCases++; int caseLabel = switchCaseGroup.getNthCaseLabel(i); if (firstValue > caseLabel) { firstValue = caseLabel; } if (lastValue < caseLabel) { lastValue = caseLabel; } } } if (nTotalCases == 0) { // If the switch statement contains no cases, javac's behaviour is to generate a pop instruction // to pop the condition value off the operand stack. mv.visitInsn(Opcodes.POP); } else { int nSpannedCases = lastValue - firstValue + 1; int maxTableSize = (nTotalCases - 2) * 5; boolean useTableSwitch = !(nSpannedCases > maxTableSize); // Create the default label and declare the other case labels. Label defaultLabel = new Label(); // (SwitchStatement.IntCaseGroup->Label) A map from case group to label. Map<IntCaseGroup, Label> caseGroupToLabelMap = new HashMap<IntCaseGroup, Label>(); if (!useTableSwitch) { // A lookup switch. Each case match gets its own label. // Map from case label to label, sorted by case label. SortedMap<Integer, Label> caseLabelToLabelMap = new TreeMap<Integer, Label>(); // A count of the number of case labels. int nCaseLabels = 0; // Create the labels, and populate the map. for (int caseN = 0; caseN < caseGroups.size(); ++caseN) { SwitchStatement.IntCaseGroup switchCaseGroup = caseGroups.get(caseN); // The label for this case group. Label label = new Label(); caseGroupToLabelMap.put(switchCaseGroup, label); for (int i = 0, nCaseGroupLabels = switchCaseGroup.getNCaseLabels(); i < nCaseGroupLabels; i++) { int caseLabel = switchCaseGroup.getNthCaseLabel(i); caseLabelToLabelMap.put(Integer.valueOf(caseLabel), label); nCaseLabels++; } } // Create the array for case matches and the corresponding labels. int[] caseMatches = new int[nCaseLabels]; Label[] labels = new Label[nCaseLabels]; int index = 0; for (final Map.Entry<Integer, Label> entry : caseLabelToLabelMap.entrySet()) { Integer caseLabelInteger = entry.getKey(); caseMatches[index] = caseLabelInteger.intValue(); labels[index] = entry.getValue(); index++; } // Visit the lookup switch instruction. mv.visitLookupSwitchInsn(defaultLabel, caseMatches, labels); } else { // A table switch // The cases which aren't given should be set to the default case. Label[] labels = new Label[nSpannedCases]; // Initially set all elements to the default label, if they won't all be set to something else later. if (nSpannedCases != nTotalCases) { Arrays.fill(labels, defaultLabel); } // For each switch case provided, create a new label. for (int caseN = 0; caseN < caseGroups.size(); ++caseN) { SwitchStatement.IntCaseGroup switchCaseGroup = caseGroups.get(caseN); // The label for this case group. Label label = new Label(); caseGroupToLabelMap.put(switchCaseGroup, label); for (int i = 0, nCaseGroupLabels = switchCaseGroup.getNCaseLabels(); i < nCaseGroupLabels; i++) { int caseLabel = switchCaseGroup.getNthCaseLabel(i); int labelArrayIndex = caseLabel - firstValue; labels[labelArrayIndex] = label; } } // Visit the table switch instruction. mv.visitTableSwitchInsn(firstValue, lastValue, defaultLabel, labels); } // Iterate over the cases. for (int caseN = 0; caseN < caseGroups.size(); ++caseN) { SwitchStatement.IntCaseGroup switchCase = caseGroups.get(caseN); JavaStatement caseStatementGroup = switchCase.getStatement(); // Add a local scope.. GenerationContext innerContext = new GenerationContext(context); // Get the label to visit.. Label labelToVisit = caseGroupToLabelMap.get(switchCase); //mark the location of the code for the i'th case. mv.visitLabel(labelToVisit); // get the instructions for the case statement. boolean caseStatementIsTerminating = encodeStatement(caseStatementGroup, innerContext); context.addJumpReturnLabelInfo(innerContext); // We don't (yet) handle non-terminating code in a case block. if (!caseStatementIsTerminating) { throw new JavaGenerationException("Can't (yet) handle non-terminating code in a case block."); } } JavaStatement defaultStatementGroup = switchStatement.getDefaultStatement(); //mark the location of the default statements (even if there are none, this means just progress to the next instruction..) mv.visitLabel(defaultLabel); if (defaultStatementGroup != null) { // Add a local scope.. GenerationContext innerContext = new GenerationContext(context); boolean defaultStatementIsTerminating = encodeStatement(defaultStatementGroup, innerContext); context.addJumpReturnLabelInfo(innerContext); // We don't (yet) handle non-terminating code in a case block. if (!defaultStatementIsTerminating) { throw new JavaGenerationException("Can't (yet) handle non-terminating code in a case block."); } } } // Note: we should add GOTO instructions to each case block to jump to after the switch statement if necessary. // But for now it's not necessary since all cases are terminated. boolean isTerminating = true; return isTerminating; } private static boolean encodeBlockStatement(JavaStatement.Block block, GenerationContext context) throws JavaGenerationException { List<JavaExceptionHandler> exceptionHandlers = block.getExceptionHandlers(); if (!exceptionHandlers.isEmpty()) { return encodeTryCatchStatement(block, context); } //do not spawn a new context for the block. //todoBI it would probably be better to spawn a new context for a block to limit the scoping of introduced variables. //however, then one would not use a block in place of a list of statements... boolean isTerminating = false; // starts out false (for the case with no statements..) // Append the instructions comprising the block's component statements. int nStatements = block.getNBlockStatements(); for (int i = 0; i < nStatements; i++) { JavaStatement blockStatement = block.getNthBlockStatement(i); // skip comments. if (blockStatement instanceof Comment) { continue; } // is terminating if the last statement is terminating. isTerminating = encodeStatement(blockStatement, context); } return isTerminating; } private static boolean encodeTryCatchStatement(JavaStatement.Block block, GenerationContext context) throws JavaGenerationException { MethodVisitor mv = context.getMethodVisitor(); List<JavaExceptionHandler> exceptionHandlers = block.getExceptionHandlers(); if (exceptionHandlers.isEmpty()) { throw new IllegalStateException(); } // Spawn a child context for the try block. GenerationContext tryBlockContext = new GenerationContext(context); boolean isTerminating = false; // starts out false (for the case with no statements..) Label tryStartLabel = new Label(); mv.visitLabel(tryStartLabel); // Append the instructions comprising the block's component statements. int nStatements = block.getNBlockStatements(); for (int i = 0; i < nStatements; i++) { JavaStatement blockStatement = block.getNthBlockStatement(i); // skip comments. if (blockStatement instanceof Comment) { continue; } // is terminating if the last statement is terminating. isTerminating = encodeStatement(blockStatement, tryBlockContext); context.addJumpReturnLabelInfo(tryBlockContext); } // Add an instruction to jump to after the exception handlers, if the statement block isn't terminating. Label tryCatchEndLabel = new Label(); context.addStatementJumpLabel(tryCatchEndLabel); if (!isTerminating) { mv.visitJumpInsn(Opcodes.GOTO, tryCatchEndLabel); } Label tryEndLabel = new Label(); mv.visitLabel(tryEndLabel); // Add exception handlers as necessary. // Now set handlers to handle any exceptions. for (int i = 0, nExceptionHandlers = exceptionHandlers.size(); i < nExceptionHandlers; ++i) { JavaExceptionHandler eh = exceptionHandlers.get(i); JavaTypeName exceptionType = JavaTypeName.make(eh.getExceptionClass()); // exception class is a non-array reference type. //encode the start of the catch block. Label catchLabel = new Label(); mv.visitLabel(catchLabel); // Create a child context for the catch block. GenerationContext catchContext = new GenerationContext(context); // The thrown exception object will be the only item on the stack. // Store it into a new local variable. String exceptionVarName = eh.getExceptionVarName(); int exceptionVarIndex = catchContext.addLocalVar(exceptionVarName, exceptionType); mv.visitVarInsn(Opcodes.ASTORE, exceptionVarIndex); boolean catchIsTerminating = encodeStatement(eh.getHandlerCode(), catchContext); context.addJumpReturnLabelInfo(catchContext); if (!catchIsTerminating) { mv.visitJumpInsn(Opcodes.GOTO, tryCatchEndLabel); } // In the end, we're only terminating if all the catch blocks are terminating. isTerminating &= catchIsTerminating; //encode the try/catch block. This can be done in any order, any time after all labels passed as arguments have been visited, // between visitCode() and visitMaxs(). encodeTryCatchBlock(tryStartLabel, tryEndLabel, catchLabel, exceptionType, tryBlockContext); } //mark the end of the whole try/catch code mv.visitLabel(tryCatchEndLabel); return isTerminating; } /** * Generate the exception table entry(/entries) for a try/catch block. * This can be done in any order, any time after all labels passed as arguments have been visited. * * Jump and return instructions are excluded from any exception handling blocks if they cause the next executed instruction * to escape the bounds of the try statement. * * eg. if there is a return statement in a try block in a try-catch statement, the corresponding return instruction will * be excluded from the exception table entry. * if there is a jump (eg. goto) statement in a try block of a try-catch statement, the jump instruction will be * excluded from the exception table entry if the jump destination lies outside the try statement. * * Exclusion of the instruction will cause the exception table entry to be split or reduced: * one entry will include the part before the excluded instruction, * the other will include the part after it, if it is not the instruction at the end of the block. * * * An interesting case to consider is a try/catch nested with respect to a while(true) statement, with a continue statement in the body: * * try { * while (true) { * (statements) * continue; * (more statements) * } * } catch (Exception e) { * } * * versus * * while (true) { * try { * (statements) * continue; * (more statements) * } catch (Exception e) { * } * } * * In both cases, the continue is encoded as a goto instruction whose destination is the first instruction in (statements). * In the first case, the target "while" statement is nested within the try block, therefore the goto is not excluded. * In the second case, the target "while" statement is outside of the try block, therefore the goto is excluded. * ie. even though the instructions for the try body are the same, and the instructions for (statements) lies both within * the while block and the try block, a jump to the first statement may or may not be viewed as escaping the try block, * depending on which case was compiled. * * * @param tryStartLabel * @param tryEndLabel * @param catchLabel * @param exceptionType * @param tryBlockContext */ private static void encodeTryCatchBlock(Label tryStartLabel, Label tryEndLabel, Label catchLabel, JavaTypeName exceptionType, GenerationContext tryBlockContext) { MethodVisitor mv = tryBlockContext.getMethodVisitor(); // The start label for the next table entry to be visited. Label exceptionEntryStartLabel = tryStartLabel; for (final JumpReturnLabelInfo jumpReturnLabelInfo : tryBlockContext.getJumpReturnLabelInfoList()) { // Only exclude the jump/return instruction (ie. split the exception block) // if the destination label is outside the scope of the try block Label destinationLabel = jumpReturnLabelInfo.getDestinationLabel(); if (destinationLabel != null && tryBlockContext.containsStatementJumpLabel(destinationLabel)) { continue; } // The next entry goes from the start label (inclusive) to the return or continue label (exclusive). Label returnContinueLabel = jumpReturnLabelInfo.getInstructionLabel(); if (exceptionEntryStartLabel.getOffset() != returnContinueLabel.getOffset()) { // When there are consecutive instructions which jump/return outside the scope of the try block, // after the first iteration of the loop we will have the exception entry start label and the // return continue label both pointing to the same offset (ie. the offset of the nth consecutive // jump/return instruction on iteration n). mv.visitTryCatchBlock(exceptionEntryStartLabel, returnContinueLabel, catchLabel, exceptionType.getJVMInternalName()); } // Set the start label for the next table entry to be visited. exceptionEntryStartLabel = jumpReturnLabelInfo.getAfterInstructionLabel(); // afterReturnLabel } // Check for the case where the last return instruction comes at the end of the try block. if (exceptionEntryStartLabel.getOffset() != tryEndLabel.getOffset()) { // Add the final entry, which ends at the tryEndLabel. mv.visitTryCatchBlock(exceptionEntryStartLabel, tryEndLabel, catchLabel, exceptionType.getJVMInternalName()); } } private static boolean encodeThrowStatement(JavaStatement.ThrowStatement throwStatement, GenerationContext context) throws JavaGenerationException { MethodVisitor mv = context.getMethodVisitor(); // get the result of evaluating the thrown expression. encodeExpr(throwStatement.getThrownExpression(), context); // throw the result. mv.visitInsn(Opcodes.ATHROW); // throw statements are terminating. return true; } private static boolean encodeUnconditionalLoop(JavaStatement.UnconditionalLoop ul, GenerationContext context) throws JavaGenerationException { //implemented as: //label label1: //{ body statements} //goto label1 MethodVisitor mv = context.getMethodVisitor(); Label label1 = new Label(); mv.visitLabel(label1); context.addStatementJumpLabel(ul.getLabel(), label1); //the body defines a new scope. GenerationContext bodyContext = new GenerationContext(context); boolean bodyIsTerminating = encodeStatement(ul.getBody(), bodyContext); context.addJumpReturnLabelInfo(bodyContext); if (!bodyIsTerminating) { mv.visitJumpInsn(Opcodes.GOTO, label1); } return true; } /** * Generate the bytecode for an assert statement. * Basically an assert statement like: assert conditionExpr : onFailureExpr; * is encoded as: * if (!$assertionsDisabled) { * if (!conditionExpr) { * throw new AssertionError (onFailureExpr); * } * } * @param assertStatement * @param context * @return true if the statement is terminating. * @throws JavaGenerationException */ private static boolean encodeAssertStatement (JavaStatement.AssertStatement assertStatement, GenerationContext context) throws JavaGenerationException { // Build up a ClassInstanceCreationExpression for the AssertionError instance. JavaExpression createAssertionError = null; if (assertStatement.getOnFailureExpr() != null) { JavaTypeName onFailureExprType = assertStatement.getOnFailureExprType(); if (onFailureExprType instanceof JavaTypeName.Reference) { onFailureExprType = JavaTypeName.OBJECT; } createAssertionError = new ClassInstanceCreationExpression (JavaTypeName.ASSERTION_ERROR, assertStatement.getOnFailureExpr(), onFailureExprType); } else { createAssertionError = new ClassInstanceCreationExpression(JavaTypeName.ASSERTION_ERROR); } JavaStatement.ThrowStatement throwAssertError = new ThrowStatement(createAssertionError); // if (!conditionExpr) {throw new AssertionError();} IfThenElseStatement ifThen = new IfThenElseStatement(new OperatorExpression.Unary(JavaOperator.LOGICAL_NEGATE, assertStatement.getConditionExpr()), throwAssertError); // if (!$assertionsDisabled) {if (!conditionExpr) {throw new AssertionError;}} JavaExpression assertionsDisabledField = new JavaExpression.JavaField.Static(context.classTypeName, "$assertionsDisabled", JavaTypeName.BOOLEAN); JavaExpression checkAssertionEnabled = new JavaExpression.OperatorExpression.Unary(JavaOperator.LOGICAL_NEGATE, assertionsDisabledField); ifThen = new IfThenElseStatement (checkAssertionEnabled, ifThen); return encodeIfThenElseStatement(ifThen, context); } /** * Encode a method invocation that is wrapped in a synchronized block. * See Sun bug id #4414101 for a discussion of this generated code. * * @param smi - the SynchronizedMethodInvocation object. * @param context - the context of the code generation. * @return - true if the SynchronizedMethodInvocation is terminating. * @throws JavaGenerationException */ private static boolean encodeSynchronizedMethodInvocation (JavaStatement.SynchronizedMethodInvocation smi, GenerationContext context) throws JavaGenerationException { MethodVisitor mv = context.getMethodVisitor(); // Get the JavaExpression for the object to synchronize on and generate the bytecode. JavaExpression objectToSynchOn = smi.getSynchronizingObject(); encodeExpr(objectToSynchOn, context); // The object to synchronize on is now on the stack. Duplicate it, // move one to storage as a local variable, and to MONITORENTER on the other. mv.visitInsn(Opcodes.DUP); int mutexIndex = context.addLocalVar("$mutex", JavaTypeName.OBJECT); mv.visitVarInsn(Opcodes.ASTORE, mutexIndex); mv.visitInsn(Opcodes.MONITORENTER); // We need to wrap the method invocation in a try/catch block so // the monitor can be exited properly if the method invocation throws // an exception. Label tryCatchStart = new Label(); mv.visitLabel(tryCatchStart); // Note: if this is generalized to handle any synchronized statement (for example, a synchronized block), // then the scope of entries in the exception table here will have to be modified to exclude return instructions. // See encodeTryCatchStatement() for how to do this. // Here, the only statement in the try block is a single expressionStatement, which has no return instructions, // so we don't have to worry about handling this case. // Get the method invocation and generate the corresponding bytecode. MethodInvocation methodInvocation = smi.getMethodInvocation(); encodeExpr (methodInvocation, context); // Load the mutex object back onto the stack and do MonitorExit. mv.visitVarInsn(Opcodes.ALOAD, mutexIndex); mv.visitInsn(Opcodes.MONITOREXIT); // Label the end of the try block around the method invocation. Label tryEnd = new Label(); mv.visitLabel(tryEnd); // At this point we want to code an instruction to jump past the exception handling // code. Label tryCatchEnd = new Label(); mv.visitJumpInsn(Opcodes.GOTO, tryCatchEnd); // Label the start of the exception handling code. Label catchStart = new Label(); mv.visitLabel(catchStart); // The exception is on the stack. Store it as a local. int exceptionIndex = context.addLocalVar("exception", JavaTypeName.OBJECT); mv.visitVarInsn(Opcodes.ASTORE, exceptionIndex); // Retrieve the mutex object and do MONITOREXIT. mv.visitVarInsn(Opcodes.ALOAD, mutexIndex); mv.visitInsn(Opcodes.MONITOREXIT); // Label the end of the exception handling code that deals with the monitor. Label exceptionMonitorExitEnd = new Label(); mv.visitLabel(exceptionMonitorExitEnd); // Retrieve the exception and throw it. mv.visitVarInsn(Opcodes.ALOAD, exceptionIndex); mv.visitInsn(Opcodes.ATHROW); // Vist the label to mark the end of the try/catch. mv.visitLabel(tryCatchEnd); // Set up the try/catch block to catch exceptions thrown by the method invocation // and handle exiting the monitor. mv.visitTryCatchBlock(tryCatchStart, tryEnd, catchStart, null); // Set up a try catch block so that if an exception is thrown by trying to exit // the monitor in the case of an exception from the method invocation we will keep trying to // exit the monitor. mv.visitTryCatchBlock(catchStart, exceptionMonitorExitEnd, catchStart, null); return false; } private static boolean encodeLabelledContinue(JavaStatement.LabelledContinue lc, GenerationContext context) { MethodVisitor mv = context.getMethodVisitor(); Label label = context.getNamedStatementJumpLabel(lc.getLabel()); if (label == null) { throw new IllegalStateException("label to continue to cannot be null."); } // Label the goto instruction and the instruction after the goto. // This instruction will be excluded from relevant ranges covered in the exception table. // Add a label for the goto instruction. Label gotoLabel = new Label(); mv.visitLabel(gotoLabel); // Visit the goto instruction. mv.visitJumpInsn(Opcodes.GOTO, label); // Add a label following the goto instruction. Label afterGotoLabel = new Label(); mv.visitLabel(afterGotoLabel); context.addJumpReturnLabelInfo(new JumpReturnLabelInfo(gotoLabel, afterGotoLabel, label)); return true; } /** * Get the Java code for a given java statement. * @param statement the java statement for which to generate source. * @param context the generation context. * @return boolean whether the statement is terminating. * @throws JavaGenerationException */ private static boolean encodeStatement(JavaStatement statement, GenerationContext context) throws JavaGenerationException { if (statement instanceof LocalVariableDeclaration) { return encodeLocalVariableDeclaration((LocalVariableDeclaration)statement, context); } else if (statement instanceof ExpressionStatement) { return encodeExprStatement((ExpressionStatement)statement, context); } else if (statement instanceof ReturnStatement) { return encodeReturnStatement((ReturnStatement)statement, context); } else if (statement instanceof IfThenElseStatement) { return encodeIfThenElseStatement((IfThenElseStatement)statement, context); } else if (statement instanceof SwitchStatement) { return encodeSwitchStatement((SwitchStatement)statement, context); } else if (statement instanceof Block) { return encodeBlockStatement((Block)statement, context); } else if (statement instanceof ThrowStatement) { return encodeThrowStatement((ThrowStatement)statement, context); } else if (statement instanceof UnconditionalLoop) { return encodeUnconditionalLoop((UnconditionalLoop)statement, context); } else if (statement instanceof LabelledContinue) { return encodeLabelledContinue((LabelledContinue)statement, context); } else if (statement instanceof SynchronizedMethodInvocation) { return encodeSynchronizedMethodInvocation((SynchronizedMethodInvocation)statement, context); } else if (statement instanceof Comment) { throw new JavaGenerationException("Attempt to generate bytecode for a comment."); } else if (statement instanceof AssertStatement) { return encodeAssertStatement ((AssertStatement) statement, context); } else { throw new JavaGenerationException("Unrecognized statement type: " + statement.getClass()); } } /** * Creates instructions to evaluate the expression, and push the result onto the operand stack. * @param expression the java expression * @param context * @return JavaTypeName the type of the result on the operand stack. * @throws JavaGenerationException */ private static JavaTypeName encodeExpr(JavaExpression expression, GenerationContext context) throws JavaGenerationException { if (expression instanceof PlaceHolder) { return encodeExpr (((PlaceHolder)expression).getActualExpression(), context); } else if (expression instanceof MethodInvocation) { return encodeMethodInvocationExpr((MethodInvocation)expression, context); } else if (expression instanceof OperatorExpression) { return encodeOperatorExpr((OperatorExpression)expression, context); } else if (expression instanceof Assignment) { return encodeAssignmentExpr((Assignment)expression, context, true); } else if (expression instanceof LiteralWrapper) { return encodeLiteralExpr((LiteralWrapper)expression, context); } else if (expression instanceof ArrayCreationExpression) { return encodeArrayCreationExpr((ArrayCreationExpression)expression, context); } else if (expression instanceof ClassInstanceCreationExpression) { return encodeClassInstanceCreationExpr((ClassInstanceCreationExpression)expression, context); } else if (expression instanceof JavaField) { return encodeJavaFieldExpr((JavaField)expression, context); } else if (expression instanceof MethodVariable) { return encodeMethodVariableExpr((MethodVariable)expression, context); } else if (expression instanceof LocalVariable) { return encodeLocalVariableExpr((LocalVariable)expression, context); } else if (expression instanceof ArrayAccess) { return encodeArrayAccessExpr((ArrayAccess)expression, context); } else if (expression instanceof ArrayLength) { return encodeArrayLengthExpr((ArrayLength)expression, context); } else if (expression instanceof LocalName) { return encodeLocalNameExpr((LocalName)expression, context); } else if (expression instanceof InstanceOf) { return encodeInstanceOfExpr((InstanceOf)expression, context); } else if (expression instanceof CastExpression) { return encodeCastExpr((CastExpression)expression, context); } else if (expression instanceof ClassLiteral) { return encodeClassLiteralExpr((ClassLiteral)expression, context); } else { throw new JavaGenerationException("Unrecognized expression type: " + expression.getClass()); } } /** * Creates the code that references a Java field within an expression. * @param javaField the java field * @param context * @return JavaTypeName the type of the javaField argument. * @throws JavaGenerationException */ private static JavaTypeName encodeJavaFieldExpr(JavaField javaField, GenerationContext context) throws JavaGenerationException { if (javaField instanceof JavaField.This) { //the special 'this' field. return encodeThis(context); } MethodVisitor mv = context.getMethodVisitor(); if (javaField instanceof JavaField.Instance) { JavaField.Instance javaInstanceField = (JavaField.Instance)javaField; JavaExpression instance = javaInstanceField.getInstance(); JavaTypeName instanceType; if (instance == null) { //use 'this' as the instance expression, so if the field is 'foo', then it is 'this.foo'. instanceType = encodeThis(context); } else { instanceType = encodeExpr(instance, context); } JavaTypeName fieldType = javaField.getFieldType(); mv.visitFieldInsn(Opcodes.GETFIELD, instanceType.getJVMInternalName(), javaField.getFieldName(), fieldType.getJVMDescriptor()); return fieldType; } if (javaField instanceof JavaField.Static) { JavaField.Static javaStaticField = (JavaField.Static)javaField; JavaTypeName fieldType = javaField.getFieldType(); mv.visitFieldInsn(Opcodes.GETSTATIC, javaStaticField.getInvocationClass().getJVMInternalName(), javaField.getFieldName(), fieldType.getJVMDescriptor()); return fieldType; } throw new IllegalStateException(); } /** * Pushes the value of the local name (which is either a local variable, method variable, * or reference to a field using the this object. * @param localName * @param context * @return JavaTypeName the type of the result on the operand stack. * @throws JavaGenerationException */ private static JavaTypeName encodeLocalNameExpr(JavaExpression.LocalName localName, GenerationContext context) throws JavaGenerationException { // Determine what type of creature this name represents. // Note that because of the order of lookups, this takes scoping into account. String varName = localName.getName(); if (context.getLocalVarIndex(varName) != -1) { return encodeLocalVariableExpr(new LocalVariable(varName, localName.getTypeName()), context); } else if (context.getMethodVarIndex(varName) != -1) { return encodeMethodVariableExpr(new MethodVariable(varName), context); } else { return encodeJavaFieldExpr(new JavaField.Instance(null, varName, localName.getTypeName()), context); } } /** * Encodes instructions to push the value of the local variable onto the operand stack. * @param variable * @param context * @return JavaTypeName the type of the result on the operand stack. */ private static JavaTypeName encodeLocalVariableExpr(JavaExpression.LocalVariable variable, GenerationContext context) { MethodVisitor mv = context.getMethodVisitor(); //get the local variable index from the context. int localVarIndex = context.getLocalVarIndex(variable.getName()); JavaTypeName varType = variable.getTypeName(); mv.visitVarInsn(getLoadOpCode(varType), localVarIndex); return varType; } private static int getLoadOpCode(JavaTypeName varType) { //ASM automatically handles replacing ILOAD 0, ILOAD 1, ILOAD 2 and ILOAD 3 by the special //0 argument op codes ILOAD_0, ILOAD_1, ILOAD_2, and ILOAD_3 and similarly for the other //types. switch (varType.getTag()) { case JavaTypeName.VOID_TAG: throw new IllegalArgumentException("Cannot load a local variable of void type."); case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: return Opcodes.ILOAD; case JavaTypeName.LONG_TAG: return Opcodes.LLOAD; case JavaTypeName.DOUBLE_TAG: return Opcodes.DLOAD; case JavaTypeName.FLOAT_TAG: return Opcodes.FLOAD; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: return Opcodes.ALOAD; default: { throw new IllegalArgumentException("Cannot load a local variable of type " + varType); } } } private static int getStoreOpCode(JavaTypeName varType) { //ASM automatically handles replacing ISTORE 0, ISTORE 1, ISTORE 2 and ISTORE 3 by the special //0 argument op codes ISTORE_0, ISTORE_1, ISTORE_2, and ISTORE_3 and similarly for the other //types. switch (varType.getTag()) { case JavaTypeName.VOID_TAG: throw new IllegalArgumentException("Cannot load a local variable of void type."); case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: return Opcodes.ISTORE; case JavaTypeName.LONG_TAG: return Opcodes.LSTORE; case JavaTypeName.DOUBLE_TAG: return Opcodes.DSTORE; case JavaTypeName.FLOAT_TAG: return Opcodes.FSTORE; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: return Opcodes.ASTORE; default: { throw new IllegalArgumentException("Cannot load a local variable of type " + varType); } } } /** * @param variable * @param context * @return the type of the result on the operand stack. */ private static JavaTypeName encodeMethodVariableExpr(JavaExpression.MethodVariable variable, GenerationContext context) { MethodVisitor mv = context.getMethodVisitor(); String varName = variable.getName(); //get the local variable index from the context. int methodVarIndex = context.getMethodVarIndex(varName); JavaTypeName varType = context.getMethodVarType(varName); mv.visitVarInsn(getLoadOpCode(varType), methodVarIndex); return varType; } /** * @param assignment * @param context * @param retainValue whether to leave on the stack the result of evaluating the expression. This is an optimization. * The normal case for an assignment expression is with retainValue = true. However, often assignments are used in * statement form in which case the resulting value is immediately popped off the stack. Hence there is no point to * push it onto the stack in the first place. * @return the type of the result on the operand stack (assuming retainValue is true). * @throws JavaGenerationException */ private static JavaTypeName encodeAssignmentExpr(JavaExpression.Assignment assignment, GenerationContext context, boolean retainValue) throws JavaGenerationException { MethodVisitor mv = context.getMethodVisitor(); JavaExpression.Nameable lhs = assignment.getLeftHandSide(); if (lhs instanceof LocalName) { // Determine what type of creature this name represents. // Note that because of the order of lookups, this takes scoping into account. LocalName localName = (LocalName)lhs; String varName = localName.getName(); if (context.getLocalVarIndex(varName) != -1) { lhs = new LocalVariable(varName, localName.getTypeName()); } else if (context.getMethodVarIndex(varName) != -1) { lhs = new MethodVariable(varName); } else { lhs = new JavaField.Instance(null, varName, localName.getTypeName()); } } // assign the value if (lhs instanceof JavaField.Instance) { JavaField.Instance javaInstanceField = (JavaField.Instance)lhs; //push the reference to the Java field itself. We can't just call encodeJavaField because we need the type of the instance object. JavaExpression instance = javaInstanceField.getInstance(); JavaTypeName instanceType; if (instance == null) { //use 'this' as the instance expression, so if the field is 'foo', then it is 'this.foo'. instanceType = encodeThis(context); } else { instanceType = encodeExpr(instance, context); } //push the rhs expression // the type of the assignment takes on the type of the value (not the assigned variable). JavaTypeName returnType = encodeExpr(assignment.getValue(), context); if (retainValue) { //duplicate the value so that a copy will be left over after assignment //field, value ---> //value, field, value mv.visitInsn(getDupX1OpCode(returnType)); } //do the assignment mv.visitFieldInsn(Opcodes.PUTFIELD, instanceType.getJVMInternalName(), javaInstanceField.getFieldName(), javaInstanceField.getFieldType().getJVMDescriptor()); return returnType; } else if (lhs instanceof JavaField.Static) { JavaField.Static javaStaticField = (JavaField.Static)lhs; //push the rhs expression // the type of the assignment takes on the type of the value (not the assigned variable). JavaTypeName returnType = encodeExpr(assignment.getValue(), context); if (retainValue) { //duplicate the value so that a copy will be left over after assignment //value ---> //value, value mv.visitInsn(getDupOpCode(returnType)); } //do the assignment mv.visitFieldInsn(Opcodes.PUTSTATIC, javaStaticField.getInvocationClass().getJVMInternalName(), javaStaticField.getFieldName(), javaStaticField.getFieldType().getJVMDescriptor()); return returnType; } else if (lhs instanceof LocalVariable) { LocalVariable localVariable = (LocalVariable)lhs; int localVarIndex = context.getLocalVarIndex(localVariable.getName()); //push the rhs expression JavaTypeName returnType = encodeExpr(assignment.getValue(), context); if (retainValue) { //duplicate the value so that a copy will be left over after assignment //value ---> //value, value mv.visitInsn(getDupOpCode(returnType)); } //encode the store instruction mv.visitVarInsn(getStoreOpCode(localVariable.getTypeName()), localVarIndex); return returnType; } else if (lhs instanceof MethodVariable) { MethodVariable methodVariable = (MethodVariable)lhs; String varName = methodVariable.getName(); int methodVarIndex = context.getMethodVarIndex(varName); JavaTypeName methodVarType = context.getMethodVarType(varName); //push the rhs expression JavaTypeName returnType = encodeExpr(assignment.getValue(), context); if (retainValue) { //duplicate the value so that a copy will be left over after assignment //value ---> //value, value mv.visitInsn(getDupOpCode(returnType)); } //encode the store instruction mv.visitVarInsn(getStoreOpCode(methodVarType), methodVarIndex); return returnType; } else if (lhs instanceof ArrayAccess) { ArrayAccess arrayAccess = (ArrayAccess)lhs; // Add the instructions to get the array ref. encodeExpr(arrayAccess.getArrayReference(), context); // Add the instructions to evaluate the array index. encodeExpr(arrayAccess.getArrayIndex(), context); // add the instructions to evaluate the expression to assign. JavaTypeName returnType = encodeExpr(assignment.getValue(), context); if (retainValue) { //duplicate the value so that a copy will be left over after assignment //arrayRef index value ---> //value arrayRef index value mv.visitInsn(getDupX2OpCode(returnType)); } //encode the store array element instruction mv.visitInsn(getArrayStoreOpCode(returnType)); return returnType; } throw new JavaGenerationException("Cannot assign to this type of expression: " + lhs); } /** * @param type * @return the DUP or DUP2 instruction, depending on type. */ private static int getDupOpCode (JavaTypeName type) { switch (type.getTag()) { case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: case JavaTypeName.FLOAT_TAG: case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: return Opcodes.DUP; case JavaTypeName.LONG_TAG: case JavaTypeName.DOUBLE_TAG: return Opcodes.DUP2; case JavaTypeName.VOID_TAG: default: { throw new IllegalArgumentException("invalid type: " + type); } } } /** * @param type * @return the DUP_X1 or DUP2_X1 instruction, depending on type. */ private static int getDupX1OpCode (JavaTypeName type) { switch (type.getTag()) { case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: case JavaTypeName.FLOAT_TAG: case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: return Opcodes.DUP_X1; case JavaTypeName.LONG_TAG: case JavaTypeName.DOUBLE_TAG: return Opcodes.DUP2_X1; case JavaTypeName.VOID_TAG: default: { throw new IllegalArgumentException("invalid type: " + type); } } } /** * @param type * @return the DUP_X2 or DUP2_X2 instruction, depending on type. */ private static int getDupX2OpCode (JavaTypeName type) { switch (type.getTag()) { case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: case JavaTypeName.FLOAT_TAG: case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: return Opcodes.DUP_X2; case JavaTypeName.LONG_TAG: case JavaTypeName.DOUBLE_TAG: return Opcodes.DUP2_X2; case JavaTypeName.VOID_TAG: default: { throw new IllegalArgumentException("invalid type: " + type); } } } /** * Encodes the Java code for a given Java operator expression. * * @param operatorExpr the java operator expression * @param context * @return JavaTypeName * @throws JavaGenerationException */ private static JavaTypeName encodeOperatorExpr(JavaExpression.OperatorExpression operatorExpr, GenerationContext context) throws JavaGenerationException{ MethodVisitor mv = context.getMethodVisitor(); JavaOperator operator = operatorExpr.getJavaOperator(); String symbol = operator.getSymbol(); JavaTypeName valueType = operator.getValueType(); // Now carry out the operation according to its type. if (operator.isArithmeticOp()) { // Add the instructions to evaluate the first argument. JavaTypeName arg1Type = encodeExpr(operatorExpr.getArgument(0), context); if (operatorExpr instanceof OperatorExpression.Unary) { if (symbol.equals("-")) { // number negation mv.visitInsn(getNegateOpCode(arg1Type)); return arg1Type; } throw new JavaGenerationException("Unknown unary arithmetic operator " + symbol + "."); } // Add an instruction to widen the argument if necessary. int wideningOpCode = getWideningOpCode(arg1Type, valueType); if (wideningOpCode != Opcodes.NOP) { mv.visitInsn(wideningOpCode); } // Add the instructions to evaluate the second argument. JavaTypeName arg2Type = encodeExpr(operatorExpr.getArgument(1), context); // Add an instruction to widen the argument if necessary. wideningOpCode = getWideningOpCode(arg2Type, valueType); if (wideningOpCode != Opcodes.NOP) { mv.visitInsn(wideningOpCode); } // Evaluate. mv.visitInsn(getArithmeticBinaryOpCode(symbol, valueType)); return valueType; } if (operator.isBitOp()) { // Add the instructions to evaluate the first argument. JavaTypeName arg1Type = encodeExpr(operatorExpr.getArgument(0), context); // Add an instruction to widen the argument if necessary. int wideningOpCode = getWideningOpCode(arg1Type, valueType); if (wideningOpCode != Opcodes.NOP) { mv.visitInsn(wideningOpCode); } if (operatorExpr instanceof OperatorExpression.Unary) { if (symbol.equals("~")) { // number negation if (valueType == JavaTypeName.INT) { mv.visitInsn(Opcodes.ICONST_M1); mv.visitInsn(Opcodes.IXOR); return valueType; } else if (valueType == JavaTypeName.LONG) { encodePushLongValue (new Long(-1), context); mv.visitInsn(Opcodes.LXOR); return valueType; } } throw new JavaGenerationException("Unknown unary arithmetic operator " + symbol + "."); } // Add the instructions to evaluate the second argument. JavaTypeName arg2Type = encodeExpr(operatorExpr.getArgument(1), context); // If this is >>, >>>, or << we may need to narrow the second argument to an int if (symbol.equals(">>") || symbol.equals(">>>") || symbol.equals("<<")) { if (arg2Type == JavaTypeName.LONG) { mv.visitInsn(Opcodes.L2I); } } else { // Add an instruction to widen the argument if necessary. wideningOpCode = getWideningOpCode(arg2Type, valueType); if (wideningOpCode != Opcodes.NOP) { mv.visitInsn(wideningOpCode); } } // Evaluate. mv.visitInsn(getArithmeticBinaryOpCode(symbol, valueType)); return valueType; } if (operator.isLogicalOp() || operator.isRelationalOp()) { // Logical op: {"!", "&&", "||"} // Relational op: {">", ">=", "<", "<=", "==", "!="} Label trueContinuation = new Label(); Label falseContinuation = new Label(); encodeBooleanValuedOperatorHelper(operatorExpr, context, trueContinuation, falseContinuation); return encodeThenTrueElseFalse(trueContinuation, falseContinuation, context); } if (operator == JavaOperator.STRING_CONCATENATION) { // Create an uninitialized StringBuilder, duplicate the reference (so we can invoke the initializer). mv.visitTypeInsn(Opcodes.NEW, "java/lang/StringBuilder"); mv.visitInsn(Opcodes.DUP); mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V"); // append the first arg. JavaTypeName firstArgType = encodeExpr(operatorExpr.getArgument(0), context); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", getAppendJVMDescriptor(firstArgType)); // Append the results of evaluation of the second arg. // Note that, conveniently, StringBuilder has an append() method for all the different types. JavaTypeName secondArgType = encodeExpr(operatorExpr.getArgument(1), context); mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "append", getAppendJVMDescriptor(secondArgType)); // Call toString() on the result. mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;"); return JavaTypeName.STRING; } return encodeTernaryOperatorExpr((OperatorExpression.Ternary)operatorExpr, context); } private static JavaTypeName encodeThenTrueElseFalse(Label trueContinuation, Label falseContinuation, GenerationContext context) { MethodVisitor mv = context.getMethodVisitor(); mv.visitLabel(trueContinuation); mv.visitInsn(Opcodes.ICONST_1); Label nextLabel = new Label(); mv.visitJumpInsn(Opcodes.GOTO, nextLabel); mv.visitLabel(falseContinuation); mv.visitInsn(Opcodes.ICONST_0); mv.visitLabel(nextLabel); return JavaTypeName.BOOLEAN; } /** * @param expr * @return true if JavaExpression represents the int literal 0. */ private static boolean isLiteralIntZeroExpr(JavaExpression expr) { if (expr instanceof JavaExpression.LiteralWrapper) { Object literalValue = ((JavaExpression.LiteralWrapper)expr).getLiteralObject(); return literalValue instanceof Integer && ((Integer)literalValue).intValue() == 0; } return false; } /** * @param expr * @return true if expr is an operator expression for one of !, &&, ||, ==, !=, <, <=, > or >=. */ private static boolean isBooleanValuedOperatorExpr(JavaExpression expr) { if (expr instanceof JavaExpression.OperatorExpression) { JavaOperator operator = ((JavaExpression.OperatorExpression)expr).getJavaOperator(); return operator.isLogicalOp() || operator.isRelationalOp(); } return false; } private static void encodeBooleanValuedOperatorHelper(JavaExpression.OperatorExpression operatorExpr, GenerationContext context, Label trueContinuation, Label falseContinuation) throws JavaGenerationException{ encodeBooleanValuedOperatorHelper(operatorExpr, context, trueContinuation, falseContinuation, true); } /** * Boolean valued operators (!, &&, ||, ==, !=, <, <=, > and >=) are highly optimized during compilation to bytecode. * Here is a quick outline of the optimizations used: * -not (e1 && e2) is compiled as a single notAnd operator * -not (e1 || e2) is compiled as a single notOr operator * -not (not e) is optimized out. * -not (x < y) is compiled as x >= y for integral comparisons. A similar thing is done for not (double <), but it is not quite double >= because * of NaN. However, there is special java bytecode support for treatment of this. * -Comparisons where the right-hand-side is an int 0 are treated more efficiently i.e. x > 0. * -Comparisons to null are treated specially i.e. x != null, x == null. * -if the result of a boolean valued operator is used by the condition part of an if-then-else statement (or ternary operator) then * the resulting true or false value is not pushed onto the stack and then tested. Rather we directly branch to the appropriate * continuation. * -the most complicated optimization is that "trees" of boolean valued operators are effectively compiled as a single operator. * What this means is that the resulting "true" and "false" values are not popped onto the stack and consumed by subsequent operators * but rather a "continuation style" is employed where we just jump to the correct next comparison. * This saves an extra comparison per operator, as well as unecessary pushes of trues and falses compared to the naive compilation scheme. * The precise bytecode instructions used in the compilation schemes varies depending on context (see the endsWithTrueForm argument). * * @param operatorExpr * @param context * @param trueContinuation label to jump to if the expression has a true value * @param falseContinuation label to jump to if the expression has a false value * @param endsWithTrueForm operators are encoded as a series of tests with jumps where if none of the jumps are taken the operator slips * through to the default case. This is usually "true" but if the "endsWithTrueForm" flag is set to false, then the default case will * be false. For example, this is useful when encoding a boolean-valued operator that is the left argument of the || operator. * In that case we want the default case to proceed to evaluation of the second argument of ||. * @throws JavaGenerationException */ private static void encodeBooleanValuedOperatorHelper(JavaExpression.OperatorExpression operatorExpr, GenerationContext context, Label trueContinuation, Label falseContinuation, boolean endsWithTrueForm) throws JavaGenerationException{ MethodVisitor mv = context.getMethodVisitor(); JavaOperator operator = operatorExpr.getJavaOperator(); String symbol = operator.getSymbol(); JavaTypeName valueType = operator.getValueType(); if (operator.isLogicalOp()) { // Logical op: {"!", "&&", "||"} // Note: conditional statements should not be handled here.. // eg. "if" conditional evaluation happens during "if" source generation. // We can get here if, eg. printing the result of a conditional. // boolean negation if (symbol.equals("!")) { JavaExpression arg0Expr = operatorExpr.getArgument(0); //attempt to optimize a variety of cases where not is composed with another boolean valued operator. if (arg0Expr instanceof JavaExpression.OperatorExpression) { if (arg0Expr instanceof JavaExpression.OperatorExpression.Binary) { JavaExpression.OperatorExpression.Binary arg0BinaryOperatorExpr = (JavaExpression.OperatorExpression.Binary)arg0Expr; JavaOperator arg0BinaryOperator = arg0BinaryOperatorExpr.getJavaOperator(); //not (expr1 && expr2) is encoded in a special way. Effectively there is a notAnd operator. if (arg0BinaryOperator == JavaOperator.CONDITIONAL_AND) { //x notAnd y //is encoded as //if x == false then goto trueContinuation //if y == true then goto falseContinuation ////what follows is a sample continuation in the case when a literal value is pushed onto the stack //label trueContinuation: //push true //goto next //label falseContinuation: //push false //next: JavaExpression andOpArg0Expr = arg0BinaryOperatorExpr.getArgument(0); if (isBooleanValuedOperatorExpr(andOpArg0Expr)) { Label innerTrueContinuation = new Label(); encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)andOpArg0Expr, context, innerTrueContinuation, trueContinuation); mv.visitLabel(innerTrueContinuation); } else { encodeExpr(andOpArg0Expr, context); mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation); } JavaExpression andOpArg1Expr = arg0BinaryOperatorExpr.getArgument(1); if (isBooleanValuedOperatorExpr(andOpArg1Expr)) { encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)andOpArg1Expr, context, falseContinuation, trueContinuation, !endsWithTrueForm); } else { encodeExpr(andOpArg1Expr, context); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFNE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation); } } return; } //not (expr1 || expr2) is encoded in a special way. Effectively there is a notOr operator. if (arg0BinaryOperator == JavaOperator.CONDITIONAL_OR) { //x notOr y //is encoded as //if x == true then goto falseContinuation //if y == true then goto falseContinuation ////what follows is a sample continuation in the case when a literal value is pushed onto the stack //label trueContinuation: //push true //goto next //label falseContinuation: //push false //next: JavaExpression orOpArg0Expr = arg0BinaryOperatorExpr.getArgument(0); if (isBooleanValuedOperatorExpr(orOpArg0Expr)) { Label innerFalseContinuation = new Label(); //if x evaluates to false, we want to continue with evaluating y, this is why the "endsWithTrueForm" argument is false here. //if x evaluates to false, then x notOr y returns true without needing to evaluate y. That is why the trueContinuation for x, is //the falseContinuation for the call that encodes x. encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)orOpArg0Expr, context, falseContinuation, innerFalseContinuation, false); mv.visitLabel(innerFalseContinuation); } else { encodeExpr(orOpArg0Expr, context); mv.visitJumpInsn(Opcodes.IFNE, falseContinuation); } JavaExpression orOpArg1Expr = arg0BinaryOperatorExpr.getArgument(1); if (isBooleanValuedOperatorExpr(orOpArg1Expr)) { encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)orOpArg1Expr, context, falseContinuation, trueContinuation, !endsWithTrueForm); } else { encodeExpr(orOpArg1Expr, context); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFNE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation); } } return; } //try to optimize not composed with a boolean valued operator as a single operation //for example, for int operators, not (x < y) is actually encoded as x >= y. JavaExpression.OperatorExpression.Binary notComposedOperatorExpr = arg0BinaryOperatorExpr.getNotComposedOperatorExpr(); if (notComposedOperatorExpr != null) { encodeBooleanValuedOperatorHelper(notComposedOperatorExpr, context, trueContinuation, falseContinuation, endsWithTrueForm); return; } //not (x Double.< y) is encoded like x Double.>= y except that the opposite DCMP instruction is used. //this is to handle NAN. Similar for the others. if (arg0BinaryOperator == JavaOperator.LESS_THAN_DOUBLE || arg0BinaryOperator == JavaOperator.LESS_THAN_EQUALS_DOUBLE || arg0BinaryOperator == JavaOperator.GREATER_THAN_DOUBLE || arg0BinaryOperator == JavaOperator.GREATER_THAN_EQUALS_DOUBLE) { //encode the first argument JavaTypeName firstArgType = encodeExpr(arg0BinaryOperatorExpr.getArgument(0), context); // Add instructions to widen the first argument if necessary. int wideningOpCode = getWideningOpCode(firstArgType, JavaTypeName.DOUBLE); if (wideningOpCode != Opcodes.NOP) { mv.visitInsn(wideningOpCode); } //endcode the second argument JavaExpression secondArgExpr = arg0BinaryOperatorExpr.getArgument(1); JavaTypeName secondArgType = encodeExpr(secondArgExpr, context); wideningOpCode = getWideningOpCode(secondArgType, JavaTypeName.DOUBLE); if (wideningOpCode != Opcodes.NOP) { mv.visitInsn(wideningOpCode); } if (arg0BinaryOperator == JavaOperator.LESS_THAN_DOUBLE) { mv.visitInsn(Opcodes.DCMPG); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFLT, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFGE, trueContinuation); } } else if (arg0BinaryOperator == JavaOperator.LESS_THAN_EQUALS_DOUBLE) { mv.visitInsn(Opcodes.DCMPG); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFLE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFGT, trueContinuation); } } else if (arg0BinaryOperator == JavaOperator.GREATER_THAN_DOUBLE) { mv.visitInsn(Opcodes.DCMPL); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFGT, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFLE, trueContinuation); } } else if (arg0BinaryOperator == JavaOperator.GREATER_THAN_EQUALS_DOUBLE) { mv.visitInsn(Opcodes.DCMPL); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFGE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFLT, trueContinuation); } } else { throw new JavaGenerationException("Expecting one of the double operators <, >, <= or >=."); } return; } //fall through to the unoptimized case... } else if (arg0Expr instanceof JavaExpression.OperatorExpression.Unary) { //"not (not expr)" is encoded as "id expr" JavaExpression.OperatorExpression.Unary arg0UnaryOperatorExpr = (JavaExpression.OperatorExpression.Unary)arg0Expr; if (arg0UnaryOperatorExpr.getJavaOperator() != JavaOperator.LOGICAL_NEGATE) { throw new JavaGenerationException("Unary logical negation expected."); } JavaExpression expr = arg0UnaryOperatorExpr.getArgument(0); if (isBooleanValuedOperatorExpr(expr)) { encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)expr, context, trueContinuation, falseContinuation, endsWithTrueForm); } else { encodeExpr(expr, context); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFNE, trueContinuation); } } return; } } //!x //is encoded as //if x == true then goto falseContinuation; ////what follows is a sample continuation in the case when a literal value is pushed onto the stack //push true; //goto next; //falseContinuation: //push false; //label next: encodeExpr(arg0Expr, context); if (endsWithTrueForm) { //Note that IFNE consumes a value on the stack. mv.visitJumpInsn(Opcodes.IFNE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation); } return; } if (symbol.equals("&&")) { //x && y //is encoded as //if x == false then goto falseContinuation //if y == false then goto falseContinuation ////what follows is a sample continuation in the case when a literal value is pushed onto the stack //push true //goto next //label falseContinuation: //push false //label next: JavaExpression arg0Expr = operatorExpr.getArgument(0); if (isBooleanValuedOperatorExpr(arg0Expr)) { Label innerTrueContinuation = new Label(); encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)arg0Expr, context, innerTrueContinuation, falseContinuation); mv.visitLabel(innerTrueContinuation); } else { encodeExpr(arg0Expr, context); mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation); } JavaExpression arg1Expr = operatorExpr.getArgument(1); if (isBooleanValuedOperatorExpr(arg1Expr)) { encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)arg1Expr, context, trueContinuation, falseContinuation, endsWithTrueForm); } else { encodeExpr(arg1Expr, context); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFNE, trueContinuation); } } return; } if (symbol.equals("||")) { //x || y //is encoded as //if x == true then goto trueContinuation //if y == false then goto falseContinuation ////what follows is a sample continuation in the case when a literal value is pushed onto the stack //push true //goto next //label falseContinuation: //push false //label next: JavaExpression arg0Expr = operatorExpr.getArgument(0); if (isBooleanValuedOperatorExpr(arg0Expr)) { Label innerFalseContinuation = new Label(); //if x evaluates to false, we want to continue with evaluating y, this is why the "endsWithTrueForm" argument is false here. encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)arg0Expr, context, trueContinuation, innerFalseContinuation, false); mv.visitLabel(innerFalseContinuation); } else { encodeExpr(arg0Expr, context); mv.visitJumpInsn(Opcodes.IFNE, trueContinuation); } JavaExpression arg1Expr = operatorExpr.getArgument(1); if (isBooleanValuedOperatorExpr(arg1Expr)){ encodeBooleanValuedOperatorHelper((JavaExpression.OperatorExpression)arg1Expr, context, trueContinuation, falseContinuation, endsWithTrueForm); } else { encodeExpr(arg1Expr, context); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFNE, trueContinuation); } } return; } throw new JavaGenerationException("Unknown logical operator " + symbol + "."); } // if(operator.isLogicalOp()) // A relational operator //one comment on the bytecode sequences: there is some subtle points here because of the treatment of special values e.g. such //as not a number, plus infinity, minus 0 etc in the double and float types. The code below is based on copying what the Java //compiler generates for simple functions such as: //double foo(double x, double y) {double z = x < y; return z;} //encode the first argument JavaTypeName firstArgType = encodeExpr(operatorExpr.getArgument(0), context); // Add instructions to widen the first argument if necessary. int wideningOpCode = getWideningOpCode(firstArgType, valueType); if (wideningOpCode != Opcodes.NOP) { mv.visitInsn(wideningOpCode); } //Deal with comparisons to null as a special case. Don't push the second argument, since the null is //implicit in the bytecode instruction. JavaExpression secondArgExpr = operatorExpr.getArgument(1); final boolean compareToNull = secondArgExpr == LiteralWrapper.NULL; //Deal with comparisons to int zero as a special case. There are special 1 argument operators for this case. //javac makes use of this optimization. Interestingly, javac does not optimize the case when the first argument //is a literal int zero i.e. 0 < x, is not converted to x > 0 which then can make use of the 1 argument comparison. final boolean compareToIntZero = isInternalIntType(valueType) && isLiteralIntZeroExpr(secondArgExpr); if (!compareToNull && !compareToIntZero) { //endcode the second argument JavaTypeName secondArgType = encodeExpr(secondArgExpr, context); wideningOpCode = getWideningOpCode(secondArgType, valueType); if (wideningOpCode != Opcodes.NOP) { mv.visitInsn(wideningOpCode); } } // relational symbols: {">", ">=", "<", "<=", "==", "!="} if (symbol.equals(">")) { switch (valueType.getTag()) { case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: { if (endsWithTrueForm) { if (compareToIntZero) { mv.visitJumpInsn(Opcodes.IFLE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ICMPLE, falseContinuation); } } else { if (compareToIntZero) { mv.visitJumpInsn(Opcodes.IFGT, trueContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ICMPGT, trueContinuation); } } break; } case JavaTypeName.LONG_TAG: { mv.visitInsn(Opcodes.LCMP); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFLE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFGT, trueContinuation); } break; } case JavaTypeName.DOUBLE_TAG: { mv.visitInsn(Opcodes.DCMPL); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFLE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFGT, trueContinuation); } break; } case JavaTypeName.FLOAT_TAG: { mv.visitInsn(Opcodes.FCMPL); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFLE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFGT, trueContinuation); } break; } default: throw new IllegalArgumentException("Unsupported operand type for JVM > operator."); } } else if (symbol.equals(">=")) { switch (valueType.getTag()) { case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: { if (endsWithTrueForm) { if (compareToIntZero) { mv.visitJumpInsn(Opcodes.IFLT, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ICMPLT, falseContinuation); } } else { if (compareToIntZero) { mv.visitJumpInsn(Opcodes.IFGE, trueContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ICMPGE, trueContinuation); } } break; } case JavaTypeName.LONG_TAG: { mv.visitInsn(Opcodes.LCMP); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFLT, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFGE, trueContinuation); } break; } case JavaTypeName.DOUBLE_TAG: { mv.visitInsn(Opcodes.DCMPL); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFLT, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFGE, trueContinuation); } break; } case JavaTypeName.FLOAT_TAG: { mv.visitInsn(Opcodes.FCMPL); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFLT, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFGE, trueContinuation); } break; } default: throw new IllegalArgumentException("Unsupported operand type for JVM >= operator."); } } else if (symbol.equals("<")) { switch (valueType.getTag()) { case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: { if (endsWithTrueForm) { if (compareToIntZero) { mv.visitJumpInsn(Opcodes.IFGE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ICMPGE, falseContinuation); } } else { if (compareToIntZero) { mv.visitJumpInsn(Opcodes.IFLT, trueContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ICMPLT, trueContinuation); } } break; } case JavaTypeName.LONG_TAG: { mv.visitInsn(Opcodes.LCMP); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFGE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFLT, trueContinuation); } break; } case JavaTypeName.DOUBLE_TAG: { mv.visitInsn(Opcodes.DCMPG); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFGE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFLT, trueContinuation); } break; } case JavaTypeName.FLOAT_TAG: { mv.visitInsn(Opcodes.FCMPG); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFGE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFLT, trueContinuation); } break; } default: throw new IllegalArgumentException("Unsupported operand type for JVM < operator."); } } else if (symbol.equals("<=")) { switch (valueType.getTag()) { case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: { if (endsWithTrueForm) { if (compareToIntZero) { mv.visitJumpInsn(Opcodes.IFGT, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ICMPGT, falseContinuation); } } else { if (compareToIntZero) { mv.visitJumpInsn(Opcodes.IFLE, trueContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ICMPLE, trueContinuation); } } break; } case JavaTypeName.LONG_TAG: { mv.visitInsn(Opcodes.LCMP); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFGT, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFLE, trueContinuation); } break; } case JavaTypeName.DOUBLE_TAG: { mv.visitInsn(Opcodes.DCMPG); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFGT, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFLE, trueContinuation); } break; } case JavaTypeName.FLOAT_TAG: { mv.visitInsn(Opcodes.FCMPG); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFGT, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFLE, trueContinuation); } break; } default: throw new IllegalArgumentException("Unsupported operand type for JVM <= operator."); } } else if (symbol.equals("==")) { switch (valueType.getTag()) { case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: { if (endsWithTrueForm) { if (compareToIntZero) { mv.visitJumpInsn(Opcodes.IFNE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ICMPNE, falseContinuation); } } else { if (compareToIntZero) { mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ICMPEQ, trueContinuation); } } break; } case JavaTypeName.LONG_TAG: { mv.visitInsn(Opcodes.LCMP); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFNE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation); } break; } case JavaTypeName.DOUBLE_TAG: { mv.visitInsn(Opcodes.DCMPL); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFNE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation); } break; } case JavaTypeName.FLOAT_TAG: { mv.visitInsn(Opcodes.FCMPL); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFNE, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFEQ, trueContinuation); } break; } case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: { if (endsWithTrueForm) { if (compareToNull) { mv.visitJumpInsn(Opcodes.IFNONNULL, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ACMPNE, falseContinuation); } } else { if (compareToNull) { mv.visitJumpInsn(Opcodes.IFNULL, trueContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ACMPEQ, trueContinuation); } } break; } default: throw new IllegalArgumentException("Unsupported operand type for JVM == operator."); } } else if (symbol.equals("!=")) { switch (valueType.getTag()) { case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: { if (endsWithTrueForm) { if (compareToIntZero) { mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ICMPEQ, falseContinuation); } } else { if (compareToIntZero) { mv.visitJumpInsn(Opcodes.IFNE, trueContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ICMPNE, trueContinuation); } } break; } case JavaTypeName.LONG_TAG: { mv.visitInsn(Opcodes.LCMP); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFNE, trueContinuation); } break; } case JavaTypeName.DOUBLE_TAG: { mv.visitInsn(Opcodes.DCMPL); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFNE, trueContinuation); } break; } case JavaTypeName.FLOAT_TAG: { mv.visitInsn(Opcodes.FCMPL); if (endsWithTrueForm) { mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IFNE, trueContinuation); } break; } case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: { if (endsWithTrueForm) { if (compareToNull) { mv.visitJumpInsn(Opcodes.IFNULL, falseContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ACMPEQ, falseContinuation); } } else { if (compareToNull) { mv.visitJumpInsn(Opcodes.IFNONNULL, trueContinuation); } else { mv.visitJumpInsn(Opcodes.IF_ACMPNE, trueContinuation); } } break; } default: throw new IllegalArgumentException("Unsupported operand type for JVM != operator."); } } else { throw new JavaGenerationException("Unknown relational operator " + symbol + "."); } } /** * A helper function to get the JVM descriptor of the right overload of StringBuilder.append(). * @param typeToAppend the type of the argument supplied to the StringBuilder.append method. * @return the JVM descriptor of the appropriate append overload. */ private static String getAppendJVMDescriptor(JavaTypeName typeToAppend) { switch (typeToAppend.getTag()) { case JavaTypeName.VOID_TAG: throw new IllegalArgumentException(); case JavaTypeName.BOOLEAN_TAG: return "(Z)Ljava/lang/StringBuilder;"; case JavaTypeName.BYTE_TAG: return "(B)Ljava/lang/StringBuilder;"; case JavaTypeName.SHORT_TAG: return "(S)Ljava/lang/StringBuilder;"; case JavaTypeName.CHAR_TAG: return "(C)Ljava/lang/StringBuilder;"; case JavaTypeName.INT_TAG: return "(I)Ljava/lang/StringBuilder;"; case JavaTypeName.LONG_TAG: return "(J)Ljava/lang/StringBuilder;"; case JavaTypeName.DOUBLE_TAG: return "(D)Ljava/lang/StringBuilder;"; case JavaTypeName.FLOAT_TAG: return "(F)Ljava/lang/StringBuilder;"; case JavaTypeName.ARRAY_TAG: { //there is a more efficient overload for char[] arguments (it does not copy the argument). if (typeToAppend.equals(JavaTypeName.CHAR_ARRAY)) { return "([C)Ljava/lang/StringBuilder;"; } return "(Ljava/lang/Object;)Ljava/lang/StringBuilder;"; } case JavaTypeName.OBJECT_TAG: { //there are more efficient overloads for String and StringBuilder arguments if (typeToAppend.equals(JavaTypeName.STRING)) { return "(Ljava/lang/String;)Ljava/lang/StringBuilder;"; } else if (typeToAppend.equals(JavaTypeName.STRING_BUILDER)) { return "(Ljava/lang/StringBuilder;)Ljava/lang/StringBuilder;"; } return "(Ljava/lang/Object;)Ljava/lang/StringBuilder;"; } default: throw new IllegalArgumentException(); } } private static JavaTypeName encodeTernaryOperatorExpr(JavaExpression.OperatorExpression.Ternary ternaryOperatorExpr, GenerationContext context) throws JavaGenerationException{ MethodVisitor mv = context.getMethodVisitor(); JavaExpression conditionExpr = ternaryOperatorExpr.getArgument(0); JavaExpression thenExpr = ternaryOperatorExpr.getArgument(1); JavaExpression elseExpr = ternaryOperatorExpr.getArgument(2); if (conditionExpr instanceof JavaExpression.OperatorExpression) { //generate more efficient code in the case of (boolean-valued-operator) ? thenExpr : elseExpr //This case exists to handle the special case where an operator occurs as the child of an if-then-else (or ternary operator) //conditional. For example, in the situation: // // (x != null) ? thenExpr : elseExpr // // we do not want to evaluate x != null to a boolean value, push that value on the stack, // and then test it prior to selecting the correct branch. Rather, we can combine the evaluation // and jump operations into a single step. JavaExpression.OperatorExpression operatorExpr = (JavaExpression.OperatorExpression)conditionExpr; JavaOperator operator = operatorExpr.getJavaOperator(); String symbol = operator.getSymbol(); Label trueContinuation = new Label(); Label falseContinuation = new Label(); if (operator.isLogicalOp() || operator.isRelationalOp()) { encodeBooleanValuedOperatorHelper(operatorExpr, context, trueContinuation, falseContinuation); return encodeThenExprElseExpr(trueContinuation, falseContinuation, thenExpr, elseExpr, context); } throw new JavaGenerationException("Unrecognized boolean-valued conditional operator " + symbol + "."); } //encode the boolean conditional encodeExpr(conditionExpr, context); Label falseContinuation = new Label(); mv.visitJumpInsn(Opcodes.IFEQ, falseContinuation); return encodeThenExprElseExpr(null, falseContinuation, thenExpr, elseExpr, context); } private static JavaTypeName encodeThenExprElseExpr(Label trueContinuation, Label falseContinuation, JavaExpression thenExpr, JavaExpression elseExpr, GenerationContext context) throws JavaGenerationException { MethodVisitor mv = context.getMethodVisitor(); if (trueContinuation != null) { mv.visitLabel(trueContinuation); } //encode the then-part expression JavaTypeName thenType = encodeExpr(thenExpr, context); Label nextLabel = new Label(); mv.visitJumpInsn(Opcodes.GOTO, nextLabel); mv.visitLabel(falseContinuation); //encode the else-part expression JavaTypeName elseType = encodeExpr(elseExpr, context); mv.visitLabel(nextLabel); //The Java language specification section 15.25 defines the rules for the return types of ternary operators. //These are complicated, and we don't support this. The basic case supported is when both static types are the same. //There is a hack to support the case when the 2 types are different: //This case happens a bit in our code generation. The proper fix would be to not create this situation during code generation. if (thenType.equals(elseType)) { return thenType; } else if (thenType.equals(JavaTypeName.RTVALUE)) { //this is a hack, see the above comment return thenType; } else if (elseType.equals(JavaTypeName.RTVALUE)) { //this is a hack, see the above comment return elseType; } else { throw new JavaGenerationException("The '?' operator must have then parts and else parts of exactly the same static types."); } } /** * Creates the byte code for a given array access expression. * The returned instruction list will cause the expression result to be pushed onto the stack. * @param arrayAccess the array access * @param context * @return ExpressionCode the type of the result on the operand stack. * @throws JavaGenerationException */ private static JavaTypeName encodeArrayAccessExpr(JavaExpression.ArrayAccess arrayAccess, GenerationContext context) throws JavaGenerationException { //encode the instructions to evaluate the array reference JavaTypeName.Reference.Array arrayType = (JavaTypeName.Reference.Array)encodeExpr(arrayAccess.getArrayReference(), context); //encode the instructions to evaluate the array index encodeExpr(arrayAccess.getArrayIndex(), context); JavaTypeName elementType = arrayType.getIncrementalElementType(); //load the array element context.getMethodVisitor().visitInsn(getArrayLoadOpCode(elementType)); return elementType; } /** * Creates the byte code for a given array length expression. * The returned instruction list will cause the expression result to be pushed onto the stack. * @param arrayLength the array length expression * @param context * @return ExpressionCode the type of the result on the operand stack. * @throws JavaGenerationException */ private static JavaTypeName encodeArrayLengthExpr(JavaExpression.ArrayLength arrayLength, GenerationContext context) throws JavaGenerationException { //encode the instructions to evaluate the array reference encodeExpr(arrayLength.getArrayReference(), context); //encode the arrayLength instruction context.getMethodVisitor().visitInsn(Opcodes.ARRAYLENGTH); return JavaTypeName.INT; } /** * Pushes a reference to "this" onto the stack. * @param context * @return JavaTypeName the type of the result on the operand stack. */ private static JavaTypeName encodeThis(GenerationContext context) { context.getMethodVisitor().visitVarInsn(Opcodes.ALOAD, 0); return context.getMethodVarType("this"); } /** * Create the Java code for a given method invocation. * The expression result to be pushed onto the stack. * @param mi the method invocation * @param context * @return JavaTypeName the type of the result on the operand stack. * @throws JavaGenerationException */ private static JavaTypeName encodeMethodInvocationExpr(JavaExpression.MethodInvocation mi, GenerationContext context) throws JavaGenerationException { MethodInvocation.InvocationType invocationType = mi.getInvocationType(); int invocationCode; JavaTypeName invocationClassType; if (invocationType != MethodInvocation.InvocationType.STATIC) { JavaExpression invocationTarget = ((MethodInvocation.Instance)mi).getInvocationTarget(); if (invocationTarget == null) { //push a reference to 'this' onto the stack. invocationClassType = encodeThis(context); } else { //push a reference to the invoking expression onto the stack invocationClassType = encodeExpr(invocationTarget, context); } // The MethodInvocation may contain an explicit invoking class type. If it does // use it in preference to the type of the invocation target. if (mi instanceof MethodInvocation.Instance && ((MethodInvocation.Instance)mi).getDeclaringClass() != null) { invocationClassType = ((MethodInvocation.Instance)mi).getDeclaringClass(); } if (invocationType == MethodInvocation.InvocationType.VIRTUAL) { invocationCode = Opcodes.INVOKEVIRTUAL; } else if (invocationType == MethodInvocation.InvocationType.INTERFACE) { if (invocationClassType.isInterface()) { invocationCode = Opcodes.INVOKEINTERFACE; } else { //if we invoke an interface method on a reference that is known to be a Class, then we must use invoke virtual //to avoid getting a IncompatibleClassChangeError. invocationCode = Opcodes.INVOKEVIRTUAL; } } else if (invocationType == MethodInvocation.InvocationType.SPECIAL) { invocationCode = Opcodes.INVOKESPECIAL; } else { throw new JavaGenerationException("Unknown invocation type: " + invocationType); } } else { //static invocation. No object reference needs to be pushed. invocationCode = Opcodes.INVOKESTATIC; invocationClassType = ((MethodInvocation.Static)mi).getInvocationClass(); } //push the arguments onto the stack for (int i = 0, nArgs = mi.getNArgs(); i < nArgs; ++i) { encodeExpr(mi.getArg(i), context); } //invoke the method context.getMethodVisitor().visitMethodInsn(invocationCode, invocationClassType.getJVMInternalName(), mi.getMethodName(), mi.getJVMMethodDescriptor()); return mi.getReturnType(); } /** * Create the Java code for a given cast expression. This will cause the resulting casted object reference to * be pushed onto the operand stack. * * @param castExpression the cast expression * @param context * @return JavaTypeName the type of the result on the operand stack. * @throws JavaGenerationException */ private static JavaTypeName encodeCastExpr(JavaExpression.CastExpression castExpression, GenerationContext context) throws JavaGenerationException { MethodVisitor mv = context.getMethodVisitor(); final JavaTypeName expressionToCastType = encodeExpr(castExpression.getExpressionToCast(), context); final JavaTypeName castType = castExpression.getCastType(); if (expressionToCastType.equals(castType)) { //no operation needed if the types are the same. return castType; } if (castType instanceof JavaTypeName.Reference) { //when the cast type is a object or array type, use the CHECKCAST instruction. This will fail bytecode verification if //the expressionToCast type is a primitive type mv.visitTypeInsn(Opcodes.CHECKCAST, castType.getJVMInternalName()); return castType; } //casting between primitive types. //There are 15 supported primitive conversions: I2L, I2F, I2D, L2I, L2F, L2D, F2I, F2L, F2D, D2I, D2L, D2F, I2B, I2C, I2S //Depending upon the expressionToCastType and castType, choose the appropriate instruction. final int conversionOpCode; switch (expressionToCastType.getTag()) { case JavaTypeName.VOID_TAG: case JavaTypeName.BOOLEAN_TAG: throw new JavaGenerationException("Unsupported primitive cast."); case JavaTypeName.BYTE_TAG: { switch (castType.getTag()) { case JavaTypeName.VOID_TAG: case JavaTypeName.BOOLEAN_TAG: throw new JavaGenerationException("Unsupported primitive cast."); case JavaTypeName.BYTE_TAG: //should be handled above as a no-op. throw new IllegalArgumentException(); case JavaTypeName.SHORT_TAG: conversionOpCode = Opcodes.I2S; break; case JavaTypeName.CHAR_TAG: conversionOpCode = Opcodes.I2C; break; case JavaTypeName.INT_TAG: //no-op return castType; case JavaTypeName.LONG_TAG: conversionOpCode = Opcodes.I2L; break; case JavaTypeName.DOUBLE_TAG: conversionOpCode = Opcodes.I2D; break; case JavaTypeName.FLOAT_TAG: conversionOpCode = Opcodes.I2F; break; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: throw new JavaGenerationException("Cannot cast a primitive type to a reference type."); default: { throw new IllegalArgumentException(); } } break; } case JavaTypeName.SHORT_TAG: { switch (castType.getTag()) { case JavaTypeName.VOID_TAG: case JavaTypeName.BOOLEAN_TAG: throw new JavaGenerationException("Unsupported primitive cast."); case JavaTypeName.BYTE_TAG: conversionOpCode = Opcodes.I2B; break; case JavaTypeName.SHORT_TAG: //should be handled above as a no-op. throw new IllegalArgumentException(); case JavaTypeName.CHAR_TAG: conversionOpCode = Opcodes.I2C; break; case JavaTypeName.INT_TAG: //no-op return castType; case JavaTypeName.LONG_TAG: conversionOpCode = Opcodes.I2L; break; case JavaTypeName.DOUBLE_TAG: conversionOpCode = Opcodes.I2D; break; case JavaTypeName.FLOAT_TAG: conversionOpCode = Opcodes.I2F; break; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: throw new JavaGenerationException("Cannot cast a primitive type to a reference type."); default: { throw new IllegalArgumentException(); } } break; } case JavaTypeName.CHAR_TAG: { switch (castType.getTag()) { case JavaTypeName.VOID_TAG: case JavaTypeName.BOOLEAN_TAG: throw new JavaGenerationException("Unsupported primitive cast."); case JavaTypeName.BYTE_TAG: conversionOpCode = Opcodes.I2B; break; case JavaTypeName.SHORT_TAG: conversionOpCode = Opcodes.I2S; break; case JavaTypeName.CHAR_TAG: //should be handled above as a no-op. throw new IllegalArgumentException(); case JavaTypeName.INT_TAG: //no-op return castType; case JavaTypeName.LONG_TAG: conversionOpCode = Opcodes.I2L; break; case JavaTypeName.DOUBLE_TAG: conversionOpCode = Opcodes.I2D; break; case JavaTypeName.FLOAT_TAG: conversionOpCode = Opcodes.I2F; break; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: throw new JavaGenerationException("Cannot cast a primitive type to a reference type."); default: { throw new IllegalArgumentException(); } } break; } case JavaTypeName.INT_TAG: { switch (castType.getTag()) { case JavaTypeName.VOID_TAG: case JavaTypeName.BOOLEAN_TAG: throw new JavaGenerationException("Unsupported primitive cast."); case JavaTypeName.BYTE_TAG: conversionOpCode = Opcodes.I2B; break; case JavaTypeName.SHORT_TAG: conversionOpCode = Opcodes.I2S; break; case JavaTypeName.CHAR_TAG: conversionOpCode = Opcodes.I2C; break; case JavaTypeName.INT_TAG: //should be handled above as a no-op. throw new IllegalArgumentException(); case JavaTypeName.LONG_TAG: conversionOpCode = Opcodes.I2L; break; case JavaTypeName.DOUBLE_TAG: conversionOpCode = Opcodes.I2D; break; case JavaTypeName.FLOAT_TAG: conversionOpCode = Opcodes.I2F; break; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: throw new JavaGenerationException("Cannot cast a primitive type to a reference type."); default: { throw new IllegalArgumentException(); } } break; } case JavaTypeName.LONG_TAG: { switch (castType.getTag()) { case JavaTypeName.VOID_TAG: case JavaTypeName.BOOLEAN_TAG: throw new JavaGenerationException("Unsupported primitive cast."); case JavaTypeName.BYTE_TAG: mv.visitInsn(Opcodes.L2I); conversionOpCode = Opcodes.I2B; break; case JavaTypeName.SHORT_TAG: mv.visitInsn(Opcodes.L2I); conversionOpCode = Opcodes.I2S; break; case JavaTypeName.CHAR_TAG: mv.visitInsn(Opcodes.L2I); conversionOpCode = Opcodes.I2C; break; case JavaTypeName.INT_TAG: conversionOpCode = Opcodes.L2I; break; case JavaTypeName.LONG_TAG: //should be handled above as a no-op. throw new IllegalArgumentException(); case JavaTypeName.DOUBLE_TAG: conversionOpCode = Opcodes.L2D; break; case JavaTypeName.FLOAT_TAG: conversionOpCode = Opcodes.L2F; break; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: throw new JavaGenerationException("Cannot cast a primitive type to a reference type."); default: { throw new IllegalArgumentException(); } } break; } case JavaTypeName.DOUBLE_TAG: { switch (castType.getTag()) { case JavaTypeName.VOID_TAG: case JavaTypeName.BOOLEAN_TAG: throw new JavaGenerationException("Unsupported primitive cast."); case JavaTypeName.BYTE_TAG: mv.visitInsn(Opcodes.D2I); conversionOpCode = Opcodes.I2B; break; case JavaTypeName.SHORT_TAG: mv.visitInsn(Opcodes.D2I); conversionOpCode = Opcodes.I2S; break; case JavaTypeName.CHAR_TAG: mv.visitInsn(Opcodes.D2I); conversionOpCode = Opcodes.I2C; break; case JavaTypeName.INT_TAG: conversionOpCode = Opcodes.D2I; break; case JavaTypeName.LONG_TAG: conversionOpCode = Opcodes.D2L; break; case JavaTypeName.DOUBLE_TAG: //should be handled above as a no-op. throw new IllegalArgumentException(); case JavaTypeName.FLOAT_TAG: conversionOpCode = Opcodes.D2F; break; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: throw new JavaGenerationException("Cannot cast a primitive type to a reference type."); default: { throw new IllegalArgumentException(); } } break; } case JavaTypeName.FLOAT_TAG: { switch (castType.getTag()) { case JavaTypeName.VOID_TAG: case JavaTypeName.BOOLEAN_TAG: throw new JavaGenerationException("Unsupported primitive cast."); case JavaTypeName.BYTE_TAG: mv.visitInsn(Opcodes.F2I); conversionOpCode = Opcodes.I2B; break; case JavaTypeName.SHORT_TAG: mv.visitInsn(Opcodes.F2I); conversionOpCode = Opcodes.I2S; break; case JavaTypeName.CHAR_TAG: mv.visitInsn(Opcodes.F2I); conversionOpCode = Opcodes.I2C; break; case JavaTypeName.INT_TAG: conversionOpCode = Opcodes.F2I; break; case JavaTypeName.LONG_TAG: conversionOpCode = Opcodes.F2L; break; case JavaTypeName.DOUBLE_TAG: conversionOpCode = Opcodes.F2D; break; case JavaTypeName.FLOAT_TAG: //should be handled above as a no-op. throw new IllegalArgumentException(); case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: throw new JavaGenerationException("Cannot cast a primitive type to a reference type."); default: { throw new IllegalArgumentException(); } } break; } case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: throw new JavaGenerationException("Cannot cast a reference type to a primitive type."); default: { throw new IllegalArgumentException(); } } mv.visitInsn(conversionOpCode); return castType; } /** * Creates and initializes a 1-dimensional array (as specified by arrayCreationExpr) and then pushes a reference * to the array onto the stack. * * @param arrayCreationExpr * @param context * @return JavaTypeName the type of the result on the operand stack. * @throws JavaGenerationException */ private static JavaTypeName encodeArrayCreationExpr(JavaExpression.ArrayCreationExpression arrayCreationExpr, GenerationContext context) throws JavaGenerationException { MethodVisitor mv = context.getMethodVisitor(); final int nArrayElements = arrayCreationExpr.getNElementValues(); //push the n of elements of the array onto the stack. encodePushIntValue(nArrayElements, context); JavaTypeName arrayElementType = arrayCreationExpr.getArrayElementTypeName(); switch (arrayElementType.getTag()) { case JavaTypeName.VOID_TAG: throw new JavaGenerationException("Cannot have an array of with void element types."); case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: case JavaTypeName.LONG_TAG: case JavaTypeName.DOUBLE_TAG: case JavaTypeName.FLOAT_TAG: { //push the instruction to create a 1-dimensonal array of primitive values mv.visitIntInsn(Opcodes.NEWARRAY, getNewArrayArgCode(arrayElementType)); break; } case JavaTypeName.ARRAY_TAG: { throw new JavaGenerationException("JavaExpression.ArrayCreationExpression supports only 1 dimensional arrays."); } case JavaTypeName.OBJECT_TAG: { //push the instruction to create a 1-dimensonal array of reference values mv.visitTypeInsn(Opcodes.ANEWARRAY, arrayElementType.getJVMInternalName()); break; } default: { throw new IllegalArgumentException(); } } final int arrayElemStoreCode = getArrayStoreOpCode(arrayElementType); //now initialize the elements of the array for (int i = 0; i < nArrayElements; ++i) { //duplicate the array reference on the stack mv.visitInsn(Opcodes.DUP); //push i encodePushIntValue(i, context); //push the code to evalaute the ith element expression encodeExpr(arrayCreationExpr.getElementValue(i), context); //array[i] = elementExpr mv.visitInsn(arrayElemStoreCode); } return arrayElementType.makeArrayType(); } /** * Create the Java code for a given class instance creation expression. The new object reference will be pushed onto * the operand stack. * * @param cice the class instance creation expression * @param context * @return JavaTypeName * @throws JavaGenerationException */ private static JavaTypeName encodeClassInstanceCreationExpr(final JavaExpression.ClassInstanceCreationExpression cice, final GenerationContext context) throws JavaGenerationException { final MethodVisitor mv = context.getMethodVisitor(); final JavaTypeName classType = cice.getClassName(); if (classType instanceof JavaTypeName.Reference.Array) { final JavaTypeName.Reference.Array arrayType = (JavaTypeName.Reference.Array)classType; final int nSizedDims = cice.getNArgs(); if (nSizedDims == 1) { //for example, new String[10][][] will hit this case since it has 1 sized dimension (even though a multi-dimensional array is //being created //push the size of the dimension encodeExpr(cice.getArg(0), context); final JavaTypeName arrayElementType = arrayType.getIncrementalElementType(); switch (arrayElementType.getTag()) { case JavaTypeName.VOID_TAG: throw new JavaGenerationException("Cannot have an array of with void element types."); case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: case JavaTypeName.LONG_TAG: case JavaTypeName.DOUBLE_TAG: case JavaTypeName.FLOAT_TAG: { //push the instruction to create a 1-dimensonal array of primitive values mv.visitIntInsn(Opcodes.NEWARRAY, getNewArrayArgCode(arrayElementType)); break; } case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: { //push the instruction to create a 1-dimensonal array of reference values mv.visitTypeInsn(Opcodes.ANEWARRAY, arrayElementType.getJVMInternalName()); break; } default: { throw new IllegalArgumentException(); } } return arrayType; } else { //the case of multi-dimensional arrays where more than 1 sizing dimension is supplied. // push args onto the stack for (int i = 0; i < nSizedDims; i++) { encodeExpr(cice.getArg(i), context); } mv.visitMultiANewArrayInsn(arrayType.getJVMInternalName(), nSizedDims); return arrayType; } } else if (classType instanceof JavaTypeName.Reference.Object) { String internalClassName = classType.getJVMInternalName(); // create uninitialized object, duplicate the ref. mv.visitTypeInsn(Opcodes.NEW, internalClassName); mv.visitInsn(Opcodes.DUP); // push args onto the stack for (int i = 0, nArgs = cice.getNArgs(); i < nArgs; i++) { encodeExpr(cice.getArg(i), context); } //descriptor for the constructor StringBuilder descriptor = new StringBuilder("("); for (int i = 0, nArgs = cice.getNArgs(); i < nArgs; ++i) { descriptor.append(cice.getParamType(i).getJVMDescriptor()); } descriptor.append(")V"); // initialize - consumes the args and the duplicate reference. mv.visitMethodInsn(Opcodes.INVOKESPECIAL, internalClassName, "<init>", descriptor.toString()); return classType; } else { throw new JavaGenerationException("cannot create a new instance of a primitive type."); } } /** * Create the Java code for a given instanceof expression, pushing the result onto the operand stack. * * @param instanceOf the instanceof expression * @param context * @return JavaTypeName the type of the result on the operand stack. * @throws JavaGenerationException */ private static JavaTypeName encodeInstanceOfExpr(JavaExpression.InstanceOf instanceOf, GenerationContext context) throws JavaGenerationException { //push the expression to test onto the operand stack encodeExpr(instanceOf.getJavaExpression(), context); //endcode the INSTANCEOF instruction context.getMethodVisitor().visitTypeInsn(Opcodes.INSTANCEOF, instanceOf.getReferenceType().getJVMInternalName()); return JavaTypeName.BOOLEAN; } /** * Creates the Java code for a given class literal expression, pushing the result onto the operand stack. * @param classLiteral the class literal expression. * @param context the generation context. * @return the type of the result on the operand stack. */ private static JavaTypeName encodeClassLiteralExpr(ClassLiteral classLiteral, GenerationContext context) { final JavaTypeName referentType = classLiteral.getReferentType(); final MethodVisitor methodVisitor = context.getMethodVisitor(); switch (referentType.getTag()) { // The primitive types (and void) are handled specially by javac - it generates code to access the static TYPE field // in the corresponding boxed type. case JavaTypeName.VOID_TAG: methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, JavaTypeName.VOID_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor()); break; case JavaTypeName.BOOLEAN_TAG: methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, JavaTypeName.BOOLEAN_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor()); break; case JavaTypeName.BYTE_TAG: methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, JavaTypeName.BYTE_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor()); break; case JavaTypeName.SHORT_TAG: methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, JavaTypeName.SHORT_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor()); break; case JavaTypeName.CHAR_TAG: methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, JavaTypeName.CHARACTER_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor()); break; case JavaTypeName.INT_TAG: methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, JavaTypeName.INTEGER_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor()); break; case JavaTypeName.LONG_TAG: methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, JavaTypeName.LONG_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor()); break; case JavaTypeName.DOUBLE_TAG: methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, JavaTypeName.DOUBLE_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor()); break; case JavaTypeName.FLOAT_TAG: methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, JavaTypeName.FLOAT_OBJECT.getJVMInternalName(), "TYPE", JavaTypeName.CLASS.getJVMDescriptor()); break; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: // For array and reference types, we generate the Class constant via Java 5's upgraded ldc opcode methodVisitor.visitLdcInsn(Type.getType(referentType.getJVMDescriptor())); break; default: throw new IllegalArgumentException("Unrecognized java type: " + referentType); } return JavaTypeName.CLASS; } /** * Get the Java code for a given literal wrapper. * The returned instruction list will cause the expression result to be pushed onto the stack. * @param literalWrapper the literal wrapper * @param context the generation context * @return JavaTypeName the type of the result on the operand stack. * @throws JavaGenerationException */ private static JavaTypeName encodeLiteralExpr(JavaExpression.LiteralWrapper literalWrapper, GenerationContext context) throws JavaGenerationException { // Integer, Double, String, Character, Boolean, Byte, Short, Float, Long, null; //note that byte, short, char, and boolean are implicitly converted and pushed as int values on the stack. Object literalObject = literalWrapper.getLiteralObject(); if (literalObject instanceof Integer) { return encodePushIntegerValue((Integer)literalObject, context); } else if (literalObject instanceof Boolean) { return encodePushBooleanValue((Boolean)literalObject, context); } else if (literalObject instanceof Double) { return encodePushDoubleValue((Double)literalObject, context); } else if (literalObject instanceof String) { return encodePushStringValue((String)literalObject, context); } else if (literalObject instanceof Character) { encodePushIntValue(((Character)literalObject).charValue(), context); return JavaTypeName.CHAR; } else if (literalObject instanceof Long) { return encodePushLongValue((Long)literalObject, context); } else if (literalObject instanceof Byte) { encodePushIntValue(((Byte)literalObject).byteValue(), context); return JavaTypeName.BYTE; } else if (literalObject instanceof Short) { encodePushIntValue(((Short)literalObject).shortValue(), context); return JavaTypeName.SHORT; } else if (literalObject instanceof Float) { return encodePushFloatValue((Float)literalObject, context); } else if (literalObject == null) { return encodePushNullValue(context); } else { throw new JavaGenerationException("Unrecognized literal type: " + literalObject.getClass()); } } /** * Encodes instructions to push an int value onto the operand stack. * Does not reallocate the wrapper argument 'value'. * @param value to be pushed * @param context * @return JavaTypeName the type of the result on the operand stack. */ private static JavaTypeName encodePushIntegerValue(Integer value, GenerationContext context) { MethodVisitor mv = context.getMethodVisitor(); int v = value.intValue(); if (v >= -1 && v <= 5) { // Use ICONST_n mv.visitInsn(Opcodes.ICONST_0 + v); } else if (v >= Byte.MIN_VALUE && v <= Byte.MAX_VALUE) { // Use BIPUSH mv.visitIntInsn(Opcodes.BIPUSH, (byte)v); } else if (v >= Short.MIN_VALUE && v <= Short.MAX_VALUE) { // Use SIPUSH mv.visitIntInsn(Opcodes.SIPUSH, (short)v); } else { // If everything fails create a Constant pool entry mv.visitLdcInsn(value); } return JavaTypeName.INT; } /** * Encodes instructions to push an int value onto the operand stack. * May need to box the argument 'value' if it is sufficiently large that * it needs to go into the constant pool. * @param value to be pushed * @param context * @return JavaTypeName the type of the result on the operand stack. */ private static JavaTypeName encodePushIntValue(int value, GenerationContext context) { MethodVisitor mv = context.getMethodVisitor(); if (value >= -1 && value <= 5) { // Use ICONST_n mv.visitInsn(Opcodes.ICONST_0 + value); } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { // Use BIPUSH mv.visitIntInsn(Opcodes.BIPUSH, (byte)value); } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { // Use SIPUSH mv.visitIntInsn(Opcodes.SIPUSH, (short)value); } else { // If everything fails create a Constant pool entry mv.visitLdcInsn(Integer.valueOf(value)); } return JavaTypeName.INT; } /** * Encodes instructions to push a boolean value onto the operand stack. * @param value to be pushed * @param context * @return JavaTypeName the type of the result on the operand stack. */ private static JavaTypeName encodePushBooleanValue(Boolean value, GenerationContext context) { MethodVisitor mv = context.getMethodVisitor(); boolean v = value.booleanValue(); if (v) { mv.visitInsn(Opcodes.ICONST_1); } else { mv.visitInsn(Opcodes.ICONST_0); } return JavaTypeName.BOOLEAN; } /** * Encodes instructions to push a float value onto the operand stack. * @param value to be pushed * @param context * @return JavaTypeName the type of the result on the operand stack. */ private static JavaTypeName encodePushFloatValue(Float value, GenerationContext context) { MethodVisitor mv = context.getMethodVisitor(); float v = value.floatValue(); if (v == 0.0) { mv.visitInsn(Opcodes.FCONST_0); } else if (v == 1.0) { mv.visitInsn(Opcodes.FCONST_1); } else if (v == 2.0) { mv.visitInsn(Opcodes.FCONST_2); } else { //Create a Constant pool entry mv.visitLdcInsn(value); } return JavaTypeName.FLOAT; } /** * Encodes instructions to push a long value onto the operand stack. * @param value to be pushed * @param context * @return JavaTypeName the type of the result on the operand stack. */ private static JavaTypeName encodePushLongValue(Long value, GenerationContext context) { MethodVisitor mv = context.getMethodVisitor(); long v = value.longValue(); if (v == 0) { mv.visitInsn(Opcodes.LCONST_0); } else if (v == 1) { mv.visitInsn(Opcodes.LCONST_1); } else { // Create a Constant pool entry mv.visitLdcInsn(value); } return JavaTypeName.LONG; } /** * Encodes instructions to push a double value onto the operand stack. * @param value to be pushed * @param context * @return JavaTypeName the type of the result on the operand stack. */ private static JavaTypeName encodePushDoubleValue(Double value, GenerationContext context) { MethodVisitor mv = context.getMethodVisitor(); double v = value.doubleValue(); if (v == 0.0) { mv.visitInsn(Opcodes.DCONST_0); } else if (v == 1.0) { mv.visitInsn(Opcodes.DCONST_1); } else { // Create a Constant pool entry mv.visitLdcInsn(value); } return JavaTypeName.DOUBLE; } /** * Encodes instructions to push a String value onto the operand stack. * @param value to be pushed. Must not be null. * @param context * @return JavaTypeName the type of the result on the operand stack. */ private static JavaTypeName encodePushStringValue(String value, GenerationContext context) { MethodVisitor mv = context.getMethodVisitor(); // Create a Constant pool entry mv.visitLdcInsn(value); return JavaTypeName.STRING; } /** * Encodes instructions to push a null object reference onto the operand stack. * @param context * @return JavaTypeName the type of the result on the operand stack. */ private static JavaTypeName encodePushNullValue(GenerationContext context) { MethodVisitor mv = context.getMethodVisitor(); mv.visitInsn(Opcodes.ACONST_NULL); return JavaTypeName.OBJECT; } /** * @param elemType element type of the array. * @return java op-code to use for loading arrays with elements of the specified type. */ private static int getArrayLoadOpCode(JavaTypeName elemType) { switch (elemType.getTag()) { case JavaTypeName.VOID_TAG: throw new IllegalArgumentException(); case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: return Opcodes.BALOAD; case JavaTypeName.SHORT_TAG: return Opcodes.SALOAD; case JavaTypeName.CHAR_TAG: return Opcodes.CALOAD; case JavaTypeName.INT_TAG: return Opcodes.IALOAD; case JavaTypeName.LONG_TAG: return Opcodes.LALOAD; case JavaTypeName.DOUBLE_TAG: return Opcodes.DALOAD; case JavaTypeName.FLOAT_TAG: return Opcodes.FALOAD; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: return Opcodes.AALOAD; default: { throw new IllegalArgumentException(); } } } /** * @param elemType element type of the array. * @return java op-code to use for storing into arrays with elements of the specified type. */ private static int getArrayStoreOpCode(JavaTypeName elemType) { switch (elemType.getTag()) { case JavaTypeName.VOID_TAG: throw new IllegalArgumentException(); case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: return Opcodes.BASTORE; case JavaTypeName.SHORT_TAG: return Opcodes.SASTORE; case JavaTypeName.CHAR_TAG: return Opcodes.CASTORE; case JavaTypeName.INT_TAG: return Opcodes.IASTORE; case JavaTypeName.LONG_TAG: return Opcodes.LASTORE; case JavaTypeName.DOUBLE_TAG: return Opcodes.DASTORE; case JavaTypeName.FLOAT_TAG: return Opcodes.FASTORE; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: return Opcodes.AASTORE; default: { throw new IllegalArgumentException(); } } } /** * @param elemType element type of the array. Must be a primitive type. * @return java op-code to use as the argument for the NEWARRAY op. */ private static int getNewArrayArgCode(JavaTypeName elemType) { switch (elemType.getTag()) { case JavaTypeName.VOID_TAG: throw new IllegalArgumentException(); case JavaTypeName.BOOLEAN_TAG: return Opcodes.T_BOOLEAN; case JavaTypeName.BYTE_TAG: return Opcodes.T_BYTE; case JavaTypeName.SHORT_TAG: return Opcodes.T_SHORT; case JavaTypeName.CHAR_TAG: return Opcodes.T_CHAR; case JavaTypeName.INT_TAG: return Opcodes.T_INT; case JavaTypeName.LONG_TAG: return Opcodes.T_LONG; case JavaTypeName.DOUBLE_TAG: return Opcodes.T_DOUBLE; case JavaTypeName.FLOAT_TAG: return Opcodes.T_FLOAT; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: default: { throw new IllegalArgumentException(); } } } /** * @param elemType element type to negate * @return java op-code to use for numerical negation. */ private static int getNegateOpCode(JavaTypeName elemType) { switch (elemType.getTag()) { case JavaTypeName.VOID_TAG: case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: throw new IllegalArgumentException(); case JavaTypeName.INT_TAG: return Opcodes.INEG; case JavaTypeName.LONG_TAG: return Opcodes.LNEG; case JavaTypeName.DOUBLE_TAG: return Opcodes.DNEG; case JavaTypeName.FLOAT_TAG: return Opcodes.FNEG; case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: default: { throw new IllegalArgumentException(); } } } private static final int getBinaryIntOpCode(String op) { switch(op.charAt(0)) { case '-' : return Opcodes.ISUB; case '+' : return Opcodes.IADD; case '%' : return Opcodes.IREM; case '*' : return Opcodes.IMUL; case '/' : return Opcodes.IDIV; case '&' : return Opcodes.IAND; case '|' : return Opcodes.IOR; case '^' : return Opcodes.IXOR; case '<' : return Opcodes.ISHL; case '>' : return op.equals(">>>")? Opcodes.IUSHR : Opcodes.ISHR; default: throw new IllegalArgumentException("Invalid operand " + op); } } private static final int getBinaryLongOpCode(String op) { switch(op.charAt(0)) { case '-' : return Opcodes.LSUB; case '+' : return Opcodes.LADD; case '%' : return Opcodes.LREM; case '*' : return Opcodes.LMUL; case '/' : return Opcodes.LDIV; case '&' : return Opcodes.LAND; case '|' : return Opcodes.LOR; case '^' : return Opcodes.LXOR; case '<' : return Opcodes.LSHL; case '>' : return op.equals(">>>")? Opcodes.LUSHR : Opcodes.LSHR; default: throw new IllegalArgumentException("Invalid operand " + op); } } private static final int getBinaryFloatOpCode(String op) { switch(op.charAt(0)) { case '-' : return Opcodes.FSUB; case '+' : return Opcodes.FADD; case '*' : return Opcodes.FMUL; case '/' : return Opcodes.FDIV; case '%' : return Opcodes.FREM; default: throw new IllegalArgumentException("Invalid operand " + op); } } private static final int getBinaryDoubleOpCode(String op) { switch(op.charAt(0)) { case '-' : return Opcodes.DSUB; case '+' : return Opcodes.DADD; case '*' : return Opcodes.DMUL; case '/' : return Opcodes.DDIV; case '%' : return Opcodes.DREM; default: throw new IllegalArgumentException("Invalid operand " + op); } } /** * Gets the constant representing the Java op code for the binary numeric operators * * @param op operation, such as "+", "*", "<<", etc. * @param type result type of the operation * @return java op code, as defined in org.objectweb.asm.Opcodes. */ private static int getArithmeticBinaryOpCode(String op, JavaTypeName type) { switch (type.getTag()) { case JavaTypeName.VOID_TAG: case JavaTypeName.BOOLEAN_TAG: throw new IllegalArgumentException("Invalid type for getNumericBinaryOpCode" + type); case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: return getBinaryIntOpCode(op); case JavaTypeName.LONG_TAG: return getBinaryLongOpCode(op); case JavaTypeName.DOUBLE_TAG: return getBinaryDoubleOpCode(op); case JavaTypeName.FLOAT_TAG: return getBinaryFloatOpCode(op); case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: default: { throw new IllegalArgumentException("Invalid type for getNumericBinaryOpCode" + type); } } } /** * Determine whether the given type is handled internally (by the JVM) as an int. * @param type the type in question. * @return whether the type is handled internally as an int. */ private static boolean isInternalIntType(JavaTypeName type) { //note that boolean is not an internal int type. switch (type.getTag()) { case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: return true; default: return false; } } /** * Gets the op-code to widen a value of a given type to a value of another type. * @param typeToWiden the type to widen. * @param valueType the type to which the typeToWiden should be widened. * @return int the widening op code, as defined in org.objectweb.asm.Opcodes. Opcodes.NOP us used for the no-op. */ private static int getWideningOpCode(JavaTypeName typeToWiden, JavaTypeName valueType) { if (typeToWiden.equals(valueType)) { return Opcodes.NOP; } // Widen from int-type values -> float, long, double if (isInternalIntType(typeToWiden)) { switch (valueType.getTag()) { case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: return Opcodes.NOP; case JavaTypeName.LONG_TAG: return Opcodes.I2L; case JavaTypeName.DOUBLE_TAG: return Opcodes.I2D; case JavaTypeName.FLOAT_TAG: return Opcodes.I2F; default: throw new IllegalArgumentException("Invalid widening conversion."); } // Widen from long -> float, double } else if (typeToWiden.equals(JavaTypeName.LONG)) { switch (valueType.getTag()) { case JavaTypeName.DOUBLE_TAG: return Opcodes.L2D; case JavaTypeName.FLOAT_TAG: return Opcodes.L2F; default: throw new IllegalArgumentException("Invalid widening conversion."); } // Widen from float -> double } else if (typeToWiden.equals(JavaTypeName.FLOAT)) { if (valueType.equals(JavaTypeName.DOUBLE)) { return Opcodes.F2D; } throw new IllegalArgumentException("Invalid widening conversion."); } //throw new IllegalArgumentException("Invalid widening conversion."); return Opcodes.NOP; } /** * @param type * @return number of slots required for a variable of the given type (1 or 2) */ private static int getTypeSize(JavaTypeName type) { switch (type.getTag()) { case JavaTypeName.BOOLEAN_TAG: case JavaTypeName.BYTE_TAG: case JavaTypeName.SHORT_TAG: case JavaTypeName.CHAR_TAG: case JavaTypeName.INT_TAG: case JavaTypeName.FLOAT_TAG: case JavaTypeName.ARRAY_TAG: case JavaTypeName.OBJECT_TAG: return 1; case JavaTypeName.LONG_TAG: case JavaTypeName.DOUBLE_TAG: return 2; case JavaTypeName.VOID_TAG: default: { throw new IllegalArgumentException(); } } } /** * Determine if a class rep or any inner classes contains any assert statements. * @param classRep * @return one or a combination of the constants: ASSERTS_UNKNOWN, NO_ASSERTS, ASSERTS_IN_CLASS, ASSERTS_IN_INNER_CLASS * from JavaClassRep */ private static int containsAsserts (JavaClassRep classRep) { final class AssertFinder extends JavaModelTraverser<Void, Void> { boolean classContainsAsserts = false; boolean innerClassContainsAsserts = false; boolean inInnerClass = false; @Override public Void visitAssertStatement(AssertStatement assertStatement, Void arg) { setContinueTraversal(false); if (inInnerClass) { innerClassContainsAsserts = true; } else { classContainsAsserts = true; } return null; } @Override public Void visitJavaClassRep(JavaClassRep classRep, Void arg) { for (int i = 0, n = classRep.getNFieldDeclarations(); i < n && getContinueTraversal(); ++i) { JavaFieldDeclaration fieldDeclaration = classRep.getFieldDeclaration(i); fieldDeclaration.accept(this, arg); } for (int i = 0, n = classRep.getNConstructors(); i < n && getContinueTraversal(); ++i) { JavaConstructor javaConstructor = classRep.getConstructor(i); javaConstructor.accept(this, arg); } for (int i = 0, n = classRep.getNMethods(); i < n && getContinueTraversal(); ++i) { JavaMethod method = classRep.getMethod(i); method.accept(this, arg); } inInnerClass = true; setContinueTraversal(true); for (int i = 0, n = classRep.getNInnerClasses(); i < n && getContinueTraversal(); ++i) { JavaClassRep innerClass = classRep.getInnerClass(i); innerClass.accept(this, arg); setContinueTraversal(true); } return null; } } AssertFinder assertFinder = new AssertFinder(); assertFinder.visitJavaClassRep(classRep, null); if (assertFinder.classContainsAsserts && assertFinder.innerClassContainsAsserts) { return JavaClassRep.ASSERTS_IN_CLASS | JavaClassRep.ASSERTS_IN_INNER_CLASS; } else if (assertFinder.classContainsAsserts) { return JavaClassRep.ASSERTS_IN_CLASS; } else if (assertFinder.innerClassContainsAsserts) { return JavaClassRep.ASSERTS_IN_INNER_CLASS; } else { return JavaClassRep.ASSERTS_NONE; } } }