All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.expression.spel.CodeFlow Maven / Gradle / Ivy

There is a newer version: 6.2.0
Show newest version
/*
 * Copyright 2002-2023 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.expression.spel;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Deque;
import java.util.List;

import org.springframework.asm.ClassWriter;
import org.springframework.asm.MethodVisitor;
import org.springframework.asm.Opcodes;
import org.springframework.lang.Nullable;
import org.springframework.util.CollectionUtils;

/**
 * Manages the class being generated by the compilation process.
 *
 * 

Records intermediate compilation state as the bytecode is generated. * Also includes various bytecode generation helper functions. * * @author Andy Clement * @author Juergen Hoeller * @since 4.1 */ public class CodeFlow implements Opcodes { /** * Name of the class being generated. Typically used when generating code * that accesses freshly generated fields on the generated type. */ private final String className; /** * The current class being generated. */ private final ClassWriter classWriter; /** * Record the type of what is on top of the bytecode stack (i.e. the type of the * output from the previous expression component). New scopes are used to evaluate * sub-expressions like the expressions for the argument values in a method invocation * expression. */ private final Deque> compilationScopes; /** * As SpEL AST nodes are called to generate code for the main evaluation method * they can register to add a field to this class. Any registered FieldAdders * will be called after the main evaluation function has finished being generated. */ @Nullable private List fieldAdders; /** * As SpEL AST nodes are called to generate code for the main evaluation method * they can register to add code to a static initializer in the class. Any * registered ClinitAdders will be called after the main evaluation function * has finished being generated. */ @Nullable private List clinitAdders; /** * When code generation requires holding a value in a class level field, this * is used to track the next available field id (used as a name suffix). */ private int nextFieldId = 1; /** * When code generation requires an intermediate variable within a method, * this method records the next available variable (variable 0 is 'this'). */ private int nextFreeVariableId = 1; /** * Construct a new {@code CodeFlow} for the given class. * @param className the name of the class * @param classWriter the corresponding ASM {@code ClassWriter} */ public CodeFlow(String className, ClassWriter classWriter) { this.className = className; this.classWriter = classWriter; this.compilationScopes = new ArrayDeque<>(); this.compilationScopes.add(new ArrayList()); } /** * Push the byte code to load the target (i.e. what was passed as the first argument * to CompiledExpression.getValue(target, context)) * @param mv the method visitor into which the load instruction should be inserted */ public void loadTarget(MethodVisitor mv) { mv.visitVarInsn(ALOAD, 1); } /** * Push the bytecode to load the EvaluationContext (the second parameter passed to * the compiled expression method). * @param mv the method visitor into which the load instruction should be inserted * @since 4.3.4 */ public void loadEvaluationContext(MethodVisitor mv) { mv.visitVarInsn(ALOAD, 2); } /** * Record the descriptor for the most recently evaluated expression element. * @param descriptor type descriptor for most recently evaluated element */ public void pushDescriptor(@Nullable String descriptor) { if (descriptor != null) { this.compilationScopes.element().add(descriptor); } } /** * Enter a new compilation scope, usually due to nested expression evaluation. For * example when the arguments for a method invocation expression are being evaluated, * each argument will be evaluated in a new scope. */ public void enterCompilationScope() { this.compilationScopes.push(new ArrayList<>()); } /** * Exit a compilation scope, usually after a nested expression has been evaluated. For * example after an argument for a method invocation has been evaluated this method * returns us to the previous (outer) scope. */ public void exitCompilationScope() { this.compilationScopes.pop(); } /** * Return the descriptor for the item currently on top of the stack (in the current scope). */ @Nullable public String lastDescriptor() { return CollectionUtils.lastElement(this.compilationScopes.peek()); } /** * If the codeflow shows the last expression evaluated to java.lang.Boolean then * insert the necessary instructions to unbox that to a boolean primitive. * @param mv the method visitor into which new instructions should be inserted */ public void unboxBooleanIfNecessary(MethodVisitor mv) { if ("Ljava/lang/Boolean".equals(lastDescriptor())) { mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false); } } /** * Called after the main expression evaluation method has been generated, this * method will call back any registered FieldAdders or ClinitAdders to add any * extra information to the class representing the compiled expression. */ public void finish() { if (this.fieldAdders != null) { for (FieldAdder fieldAdder : this.fieldAdders) { fieldAdder.generateField(this.classWriter, this); } } if (this.clinitAdders != null) { MethodVisitor mv = this.classWriter.visitMethod(ACC_PUBLIC | ACC_STATIC, "", "()V", null, null); mv.visitCode(); this.nextFreeVariableId = 0; // to 0 because there is no 'this' in a clinit for (ClinitAdder clinitAdder : this.clinitAdders) { clinitAdder.generateCode(mv, this); } mv.visitInsn(RETURN); mv.visitMaxs(0,0); // not supplied due to COMPUTE_MAXS mv.visitEnd(); } } /** * Register a FieldAdder which will add a new field to the generated * class to support the code produced by an AST node's primary * generateCode() method. */ public void registerNewField(FieldAdder fieldAdder) { if (this.fieldAdders == null) { this.fieldAdders = new ArrayList<>(); } this.fieldAdders.add(fieldAdder); } /** * Register a ClinitAdder which will add code to the static * initializer in the generated class to support the code * produced by an AST node's primary generateCode() method. */ public void registerNewClinit(ClinitAdder clinitAdder) { if (this.clinitAdders == null) { this.clinitAdders = new ArrayList<>(); } this.clinitAdders.add(clinitAdder); } public int nextFieldId() { return this.nextFieldId++; } public int nextFreeVariableId() { return this.nextFreeVariableId++; } public String getClassName() { return this.className; } /** * Insert any necessary cast and value call to convert from a boxed type to a * primitive value. * @param mv the method visitor into which instructions should be inserted * @param ch the primitive type desired as output * @param stackDescriptor the descriptor of the type on top of the stack */ public static void insertUnboxInsns(MethodVisitor mv, char ch, @Nullable String stackDescriptor) { if (stackDescriptor == null) { return; } switch (ch) { case 'Z' -> { if (!stackDescriptor.equals("Ljava/lang/Boolean")) { mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean"); } mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false); } case 'B' -> { if (!stackDescriptor.equals("Ljava/lang/Byte")) { mv.visitTypeInsn(CHECKCAST, "java/lang/Byte"); } mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false); } case 'C' -> { if (!stackDescriptor.equals("Ljava/lang/Character")) { mv.visitTypeInsn(CHECKCAST, "java/lang/Character"); } mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false); } case 'D' -> { if (!stackDescriptor.equals("Ljava/lang/Double")) { mv.visitTypeInsn(CHECKCAST, "java/lang/Double"); } mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false); } case 'F' -> { if (!stackDescriptor.equals("Ljava/lang/Float")) { mv.visitTypeInsn(CHECKCAST, "java/lang/Float"); } mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false); } case 'I' -> { if (!stackDescriptor.equals("Ljava/lang/Integer")) { mv.visitTypeInsn(CHECKCAST, "java/lang/Integer"); } mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false); } case 'J' -> { if (!stackDescriptor.equals("Ljava/lang/Long")) { mv.visitTypeInsn(CHECKCAST, "java/lang/Long"); } mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false); } case 'S' -> { if (!stackDescriptor.equals("Ljava/lang/Short")) { mv.visitTypeInsn(CHECKCAST, "java/lang/Short"); } mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false); } default -> throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + ch + "'"); } } /** * For numbers, use the appropriate method on the number to convert it to the primitive type requested. * @param mv the method visitor into which instructions should be inserted * @param targetDescriptor the primitive type desired as output * @param stackDescriptor the descriptor of the type on top of the stack */ public static void insertUnboxNumberInsns( MethodVisitor mv, char targetDescriptor, @Nullable String stackDescriptor) { if (stackDescriptor == null) { return; } switch (targetDescriptor) { case 'D' -> { if (stackDescriptor.equals("Ljava/lang/Object")) { mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); } mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "doubleValue", "()D", false); } case 'F' -> { if (stackDescriptor.equals("Ljava/lang/Object")) { mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); } mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "floatValue", "()F", false); } case 'J' -> { if (stackDescriptor.equals("Ljava/lang/Object")) { mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); } mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "longValue", "()J", false); } case 'I' -> { if (stackDescriptor.equals("Ljava/lang/Object")) { mv.visitTypeInsn(CHECKCAST, "java/lang/Number"); } mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Number", "intValue", "()I", false); } // does not handle Z, B, C, S default -> throw new IllegalArgumentException("Unboxing should not be attempted for descriptor '" + targetDescriptor + "'"); } } /** * Insert any necessary numeric conversion bytecodes based upon what is on the * stack and the desired target type. * @param mv the method visitor into which instructions should be placed * @param targetDescriptor the (primitive) descriptor of the target type * @param stackDescriptor the descriptor of the operand on top of the stack */ public static void insertAnyNecessaryTypeConversionBytecodes(MethodVisitor mv, char targetDescriptor, String stackDescriptor) { if (!CodeFlow.isPrimitive(stackDescriptor)) { return; } char stackTop = stackDescriptor.charAt(0); switch (stackTop){ case 'I', 'B', 'S', 'C' -> { switch (targetDescriptor) { case 'D' -> mv.visitInsn(I2D); case 'F' -> mv.visitInsn(I2F); case 'J' -> mv.visitInsn(I2L); case 'I' -> { /* no-op */ } default -> throw new IllegalStateException("Cannot get from " + stackTop + " to " + targetDescriptor); } } case 'J' -> { switch (targetDescriptor) { case 'D' -> mv.visitInsn(L2D); case 'F' -> mv.visitInsn(L2F); case 'J' -> { /* no-op */ } case 'I' -> mv.visitInsn(L2I); default -> throw new IllegalStateException("Cannot get from " + stackTop + " to " + targetDescriptor); } } case 'F' -> { switch (targetDescriptor) { case 'D' -> mv.visitInsn(F2D); case 'F' -> { /* no-op */ } case 'J' -> mv.visitInsn(F2L); case 'I' -> mv.visitInsn(F2I); default -> throw new IllegalStateException("Cannot get from " + stackTop + " to " + targetDescriptor); } } case 'D' -> { switch (targetDescriptor) { case 'D' -> { /* no-op */ } case 'F' -> mv.visitInsn(D2F); case 'J' -> mv.visitInsn(D2L); case 'I' -> mv.visitInsn(D2I); default -> throw new IllegalStateException("Cannot get from " + stackDescriptor + " to " + targetDescriptor); } } } } /** * Create the JVM signature descriptor for a method. This consists of the descriptors * for the method parameters surrounded with parentheses, followed by the * descriptor for the return type. Note the descriptors here are JVM descriptors, * unlike the other descriptor forms the compiler is using which do not include the * trailing semicolon. * @param method the method * @return a String signature descriptor (e.g. "(ILjava/lang/String;)V") */ public static String createSignatureDescriptor(Method method) { Class[] params = method.getParameterTypes(); StringBuilder sb = new StringBuilder(); sb.append('('); for (Class param : params) { sb.append(toJvmDescriptor(param)); } sb.append(')'); sb.append(toJvmDescriptor(method.getReturnType())); return sb.toString(); } /** * Create the JVM signature descriptor for a constructor. This consists of the * descriptors for the constructor parameters surrounded with parentheses, followed by * the descriptor for the return type, which is always "V". Note the * descriptors here are JVM descriptors, unlike the other descriptor forms the * compiler is using which do not include the trailing semicolon. * @param ctor the constructor * @return a String signature descriptor (e.g. "(ILjava/lang/String;)V") */ public static String createSignatureDescriptor(Constructor ctor) { Class[] params = ctor.getParameterTypes(); StringBuilder sb = new StringBuilder(); sb.append('('); for (Class param : params) { sb.append(toJvmDescriptor(param)); } sb.append(")V"); return sb.toString(); } /** * Determine the JVM descriptor for a specified class. Unlike the other descriptors * used in the compilation process, this is the one the JVM wants, so this one * includes any necessary trailing semicolon (e.g. Ljava/lang/String; rather than * Ljava/lang/String) * @param clazz a class * @return the JVM descriptor for the class */ public static String toJvmDescriptor(Class clazz) { StringBuilder sb = new StringBuilder(); if (clazz.isArray()) { while (clazz.isArray()) { sb.append('['); clazz = clazz.getComponentType(); } } if (clazz.isPrimitive()) { if (clazz == Boolean.TYPE) { sb.append('Z'); } else if (clazz == Byte.TYPE) { sb.append('B'); } else if (clazz == Character.TYPE) { sb.append('C'); } else if (clazz == Double.TYPE) { sb.append('D'); } else if (clazz == Float.TYPE) { sb.append('F'); } else if (clazz == Integer.TYPE) { sb.append('I'); } else if (clazz == Long.TYPE) { sb.append('J'); } else if (clazz == Short.TYPE) { sb.append('S'); } else if (clazz == Void.TYPE) { sb.append('V'); } } else { sb.append('L'); sb.append(clazz.getName().replace('.', '/')); sb.append(';'); } return sb.toString(); } /** * Determine the descriptor for an object instance (or {@code null}). * @param value an object (possibly {@code null}) * @return the type descriptor for the object * (descriptor is "Ljava/lang/Object" for {@code null} value) */ public static String toDescriptorFromObject(@Nullable Object value) { if (value == null) { return "Ljava/lang/Object"; } else { return toDescriptor(value.getClass()); } } /** * Determine whether the descriptor is for a boolean primitive or boolean reference type. * @param descriptor type descriptor * @return {@code true} if the descriptor is boolean compatible */ public static boolean isBooleanCompatible(@Nullable String descriptor) { return (descriptor != null && (descriptor.equals("Z") || descriptor.equals("Ljava/lang/Boolean"))); } /** * Determine whether the descriptor is for a primitive type or {@code void}. * @param descriptor type descriptor * @return {@code true} if a primitive type or {@code void} */ public static boolean isPrimitive(@Nullable String descriptor) { return (descriptor != null && descriptor.length() == 1); } /** * Determine whether the descriptor is for a primitive array (e.g. "[[I"). * @param descriptor the descriptor for a possible primitive array * @return {@code true} if the descriptor a primitive array */ public static boolean isPrimitiveArray(@Nullable String descriptor) { if (descriptor == null) { return false; } boolean primitive = true; for (int i = 0, max = descriptor.length(); i < max; i++) { char ch = descriptor.charAt(i); if (ch == '[') { continue; } primitive = (ch != 'L'); break; } return primitive; } /** * Determine whether boxing/unboxing can get from one type to the other. *

Assumes at least one of the types is in boxed form (i.e. single char descriptor). * @return {@code true} if it is possible to get (via boxing) from one descriptor to the other */ public static boolean areBoxingCompatible(String desc1, String desc2) { if (desc1.equals(desc2)) { return true; } if (desc1.length() == 1) { return checkPairs(desc1, desc2); } else if (desc2.length() == 1) { return checkPairs(desc2, desc1); } return false; } private static boolean checkPairs(String desc1, String desc2) { return switch (desc1) { case "Z" -> desc2.equals("Ljava/lang/Boolean"); case "D" -> desc2.equals("Ljava/lang/Double"); case "F" -> desc2.equals("Ljava/lang/Float"); case "I" -> desc2.equals("Ljava/lang/Integer"); case "J" -> desc2.equals("Ljava/lang/Long"); default -> false; }; } /** * Determine if the supplied descriptor is for a supported number type or boolean. The * compilation process only (currently) supports certain number types. These are * double, float, long and int. * @param descriptor the descriptor for a type * @return {@code true} if the descriptor is for a supported numeric type or boolean */ public static boolean isPrimitiveOrUnboxableSupportedNumberOrBoolean(@Nullable String descriptor) { if (descriptor == null) { return false; } if (isPrimitiveOrUnboxableSupportedNumber(descriptor)) { return true; } return ("Z".equals(descriptor) || descriptor.equals("Ljava/lang/Boolean")); } /** * Determine if the supplied descriptor is for a supported number. The compilation * process only (currently) supports certain number types. These are double, float, * long and int. * @param descriptor the descriptor for a type * @return {@code true} if the descriptor is for a supported numeric type */ public static boolean isPrimitiveOrUnboxableSupportedNumber(@Nullable String descriptor) { if (descriptor == null) { return false; } if (descriptor.length() == 1) { return "DFIJ".contains(descriptor); } if (descriptor.startsWith("Ljava/lang/")) { String name = descriptor.substring("Ljava/lang/".length()); return (name.equals("Double") || name.equals("Float") || name.equals("Integer") || name.equals("Long")); } return false; } /** * Determine whether the given number is to be considered as an integer * for the purposes of a numeric operation at the bytecode level. * @param number the number to check * @return {@code true} if it is an {@link Integer}, {@link Short} or {@link Byte} */ public static boolean isIntegerForNumericOp(Number number) { return (number instanceof Integer || number instanceof Short || number instanceof Byte); } /** * Convert a type descriptor to the single character primitive descriptor. * @param descriptor a descriptor for a type that should have a primitive representation * @return the single character descriptor for a primitive input descriptor */ public static char toPrimitiveTargetDesc(String descriptor) { if (descriptor.length() == 1) { return descriptor.charAt(0); } return switch (descriptor) { case "Ljava/lang/Double" -> 'D'; case "Ljava/lang/Float" -> 'F'; case "Ljava/lang/Integer" -> 'I'; case "Ljava/lang/Long" -> 'J'; case "Ljava/lang/Boolean" -> 'Z'; case "Ljava/lang/Character" -> 'C'; case "Ljava/lang/Byte" -> 'B'; case "Ljava/lang/Short" -> 'S'; default -> throw new IllegalStateException("No primitive for '" + descriptor + "'"); }; } /** * Insert the appropriate CHECKCAST instruction for the supplied descriptor. * @param mv the method visitor into which the instruction should be inserted * @param descriptor the descriptor of the type to cast to */ public static void insertCheckCast(MethodVisitor mv, @Nullable String descriptor) { if (descriptor != null && descriptor.length() != 1) { if (descriptor.charAt(0) == '[') { if (isPrimitiveArray(descriptor)) { mv.visitTypeInsn(CHECKCAST, descriptor); } else { mv.visitTypeInsn(CHECKCAST, descriptor + ";"); } } else { if (!descriptor.equals("Ljava/lang/Object")) { // This is chopping off the 'L' to leave us with "java/lang/String" mv.visitTypeInsn(CHECKCAST, descriptor.substring(1)); } } } } /** * Determine the appropriate boxing instruction for a specific type (if it needs * boxing) and insert the instruction into the supplied visitor. * @param mv the method visitor for the new instructions * @param descriptor the descriptor of a type that may or may not need boxing */ public static void insertBoxIfNecessary(MethodVisitor mv, @Nullable String descriptor) { if (descriptor != null && descriptor.length() == 1) { insertBoxIfNecessary(mv, descriptor.charAt(0)); } } /** * Determine the appropriate boxing instruction for a specific type (if it needs * boxing) and insert the instruction into the supplied visitor. * @param mv the method visitor for the new instructions * @param ch the descriptor of the type that might need boxing */ public static void insertBoxIfNecessary(MethodVisitor mv, char ch) { switch (ch) { case 'Z' -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false); case 'B' -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false); case 'C' -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false); case 'D' -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false); case 'F' -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false); case 'I' -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false); case 'J' -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false); case 'S' -> mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false); case 'L', 'V', '[' -> { // no box needed } default -> throw new IllegalArgumentException("Boxing should not be attempted for descriptor '" + ch + "'"); } } /** * Deduce the descriptor for a type. Descriptors are like JVM type names but missing the * trailing ';' so for Object the descriptor is "Ljava/lang/Object" for int it is "I". * @param type the type (may be primitive) for which to determine the descriptor * @return the descriptor */ public static String toDescriptor(Class type) { String name = type.getName(); if (type.isPrimitive()) { switch (name.length()) { case 3: return "I"; case 4: return switch (name) { case "byte" -> "B"; case "char" -> "C"; case "long" -> "J"; case "void" -> "V"; default -> throw new IllegalArgumentException("Unknown primitive type: " + name); }; case 5: if (name.equals("float")) { return "F"; } else if (name.equals("short")) { return "S"; } break; case 6: if (name.equals("double")) { return "D"; } break; case 7: if (name.equals("boolean")) { return "Z"; } break; default: throw new IllegalArgumentException("Unknown primitive type: " + name); } } else { if (name.charAt(0) != '[') { return "L" + type.getName().replace('.', '/'); } else { if (name.endsWith(";")) { return name.substring(0, name.length() - 1).replace('.', '/'); } else { return name; // array has primitive component type } } } return ""; } /** * Create an array of descriptors representing the parameter types for the supplied * method. Returns a zero sized array if there are no parameters. * @param method a Method * @return a String array of descriptors, one entry for each method parameter */ public static String[] toParamDescriptors(Method method) { return toDescriptors(method.getParameterTypes()); } /** * Create an array of descriptors representing the parameter types for the supplied * constructor. Returns a zero sized array if there are no parameters. * @param ctor a Constructor * @return a String array of descriptors, one entry for each constructor parameter */ public static String[] toParamDescriptors(Constructor ctor) { return toDescriptors(ctor.getParameterTypes()); } /** * Create an array of descriptors from an array of classes. * @param types the input array of classes * @return an array of descriptors */ public static String[] toDescriptors(Class[] types) { int typesCount = types.length; String[] descriptors = new String[typesCount]; for (int p = 0; p < typesCount; p++) { descriptors[p] = toDescriptor(types[p]); } return descriptors; } /** * Create the optimal instruction for loading a number on the stack. * @param mv where to insert the bytecode * @param value the value to be loaded */ public static void insertOptimalLoad(MethodVisitor mv, int value) { if (value < 6) { mv.visitInsn(ICONST_0+value); } else if (value < Byte.MAX_VALUE) { mv.visitIntInsn(BIPUSH, value); } else if (value < Short.MAX_VALUE) { mv.visitIntInsn(SIPUSH, value); } else { mv.visitLdcInsn(value); } } /** * Produce appropriate bytecode to store a stack item in an array. The * instruction to use varies depending on whether the type * is a primitive or reference type. * @param mv where to insert the bytecode * @param arrayElementType the type of the array elements */ public static void insertArrayStore(MethodVisitor mv, String arrayElementType) { if (arrayElementType.length() == 1) { switch (arrayElementType.charAt(0)) { case 'B', 'Z' -> mv.visitInsn(BASTORE); case 'I' -> mv.visitInsn(IASTORE); case 'J' -> mv.visitInsn(LASTORE); case 'F' -> mv.visitInsn(FASTORE); case 'D' -> mv.visitInsn(DASTORE); case 'C' -> mv.visitInsn(CASTORE); case 'S' -> mv.visitInsn(SASTORE); default -> throw new IllegalArgumentException("Unexpected array type " + arrayElementType.charAt(0)); } } else { mv.visitInsn(AASTORE); } } /** * Determine the appropriate T tag to use for the NEWARRAY bytecode. * @param arrayType the array primitive component type * @return the T tag to use for NEWARRAY */ public static int arrayCodeFor(String arrayType) { return switch (arrayType.charAt(0)) { case 'I' -> T_INT; case 'J' -> T_LONG; case 'F' -> T_FLOAT; case 'D' -> T_DOUBLE; case 'B' -> T_BYTE; case 'C' -> T_CHAR; case 'S' -> T_SHORT; case 'Z' -> T_BOOLEAN; default -> throw new IllegalArgumentException("Unexpected array type " + arrayType.charAt(0)); }; } /** * Return if the supplied array type has a core component reference type. */ public static boolean isReferenceTypeArray(String arrayType) { int length = arrayType.length(); for (int i = 0; i < length; i++) { char ch = arrayType.charAt(i); if (ch == '[') { continue; } return (ch == 'L'); } return false; } /** * Produce the correct bytecode to build an array. The opcode to use and the * signature to pass along with the opcode can vary depending on the signature * of the array type. * @param mv the method visitor into which code should be inserted * @param size the size of the array * @param arrayType the type of the array */ public static void insertNewArrayCode(MethodVisitor mv, int size, String arrayType) { insertOptimalLoad(mv, size); if (arrayType.length() == 1) { mv.visitIntInsn(NEWARRAY, CodeFlow.arrayCodeFor(arrayType)); } else { if (arrayType.charAt(0) == '[') { // Handling the nested array case here. // If vararg is [[I then we want [I and not [I; if (CodeFlow.isReferenceTypeArray(arrayType)) { mv.visitTypeInsn(ANEWARRAY, arrayType + ";"); } else { mv.visitTypeInsn(ANEWARRAY, arrayType); } } else { mv.visitTypeInsn(ANEWARRAY, arrayType.substring(1)); } } } /** * For use in mathematical operators, handles converting from a (possibly boxed) * number on the stack to a primitive numeric type. *

For example, from an Integer to a double, just need to call 'Number.doubleValue()' * but from an int to a double, need to use the bytecode 'i2d'. * @param mv the method visitor when instructions should be appended * @param stackDescriptor a descriptor of the operand on the stack * @param targetDescriptor a primitive type descriptor */ public static void insertNumericUnboxOrPrimitiveTypeCoercion( MethodVisitor mv, @Nullable String stackDescriptor, char targetDescriptor) { if (!CodeFlow.isPrimitive(stackDescriptor)) { CodeFlow.insertUnboxNumberInsns(mv, targetDescriptor, stackDescriptor); } else { CodeFlow.insertAnyNecessaryTypeConversionBytecodes(mv, targetDescriptor, stackDescriptor); } } public static String toBoxedDescriptor(String primitiveDescriptor) { return switch (primitiveDescriptor.charAt(0)) { case 'I' -> "Ljava/lang/Integer"; case 'J' -> "Ljava/lang/Long"; case 'F' -> "Ljava/lang/Float"; case 'D' -> "Ljava/lang/Double"; case 'B' -> "Ljava/lang/Byte"; case 'C' -> "Ljava/lang/Character"; case 'S' -> "Ljava/lang/Short"; case 'Z' -> "Ljava/lang/Boolean"; default -> throw new IllegalArgumentException("Unexpected non primitive descriptor " + primitiveDescriptor); }; } /** * Interface used to generate fields. */ @FunctionalInterface public interface FieldAdder { void generateField(ClassWriter cw, CodeFlow codeflow); } /** * Interface used to generate {@code clinit} static initializer blocks. */ @FunctionalInterface public interface ClinitAdder { void generateCode(MethodVisitor mv, CodeFlow codeflow); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy