/* * Copyright (c) 1999, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package org.hotswap.agent.plugin.proxy.java; import java.io.ByteArrayOutputStream; import java.io.DataOutputStream; import java.io.IOException; import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; import java.util.Map; import java.util.UUID; import org.hotswap.agent.javassist.ClassPool; import org.hotswap.agent.javassist.CtClass; import org.hotswap.agent.javassist.CtMethod; import org.hotswap.agent.javassist.CtPrimitiveType; import org.hotswap.agent.javassist.NotFoundException; import org.hotswap.agent.javassist.bytecode.Descriptor; /** * ProxyGenerator contains the code to generate a dynamic proxy class for the java.lang.reflect.Proxy API. * * The external interfaces to ProxyGenerator is the static "generateProxyClass" method. * * @author Peter Jones * @since 1.3 */ /** * Converted to use ClassPool and CtClass. Added static field init-code to proxy methods. * * @author Erki Ehtla */ public class CtClassJavaProxyGenerator { /* * In the comments below, "JVMS" refers to The Java Virtual Machine Specification Second Edition and "JLS" refers to * the original version of The Java Language Specification, unless otherwise specified. */ /* generate 1.5-era class file version */ private static final int CLASSFILE_MAJOR_VERSION = 49; private static final int CLASSFILE_MINOR_VERSION = 0; /* * beginning of constants copied from sun.tools.java.RuntimeConstants (which no longer exists): */ /* constant pool tags */ private static final int CONSTANT_UTF8 = 1; // private static final int CONSTANT_UNICODE = 2; private static final int CONSTANT_INTEGER = 3; private static final int CONSTANT_FLOAT = 4; private static final int CONSTANT_LONG = 5; private static final int CONSTANT_DOUBLE = 6; private static final int CONSTANT_CLASS = 7; private static final int CONSTANT_STRING = 8; private static final int CONSTANT_FIELD = 9; private static final int CONSTANT_METHOD = 10; private static final int CONSTANT_INTERFACEMETHOD = 11; private static final int CONSTANT_NAMEANDTYPE = 12; /* access and modifier flags */ private static final int ACC_PUBLIC = 0x00000001; private static final int ACC_PRIVATE = 0x00000002; // private static final int ACC_PROTECTED = 0x00000004; private static final int ACC_STATIC = 0x00000008; private static final int ACC_FINAL = 0x00000010; // private static final int ACC_SYNCHRONIZED = 0x00000020; // private static final int ACC_VOLATILE = 0x00000040; // private static final int ACC_TRANSIENT = 0x00000080; // private static final int ACC_NATIVE = 0x00000100; // private static final int ACC_INTERFACE = 0x00000200; // private static final int ACC_ABSTRACT = 0x00000400; private static final int ACC_SUPER = 0x00000020; // private static final int ACC_STRICT = 0x00000800; /* opcodes */ // private static final int opc_nop = 0; private static final int opc_aconst_null = 1; // private static final int opc_iconst_m1 = 2; private static final int opc_iconst_0 = 3; private static final int opc_iconst_1 = 4; // private static final int opc_iconst_2 = 5; // private static final int opc_iconst_3 = 6; // private static final int opc_iconst_4 = 7; // private static final int opc_iconst_5 = 8; // private static final int opc_lconst_0 = 9; // private static final int opc_lconst_1 = 10; // private static final int opc_fconst_0 = 11; // private static final int opc_fconst_1 = 12; // private static final int opc_fconst_2 = 13; // private static final int opc_dconst_0 = 14; // private static final int opc_dconst_1 = 15; private static final int opc_bipush = 16; private static final int opc_sipush = 17; private static final int opc_ldc = 18; private static final int opc_ldc_w = 19; // private static final int opc_ldc2_w = 20; private static final int opc_iload = 21; private static final int opc_lload = 22; private static final int opc_fload = 23; private static final int opc_dload = 24; private static final int opc_aload = 25; private static final int opc_iload_0 = 26; // private static final int opc_iload_1 = 27; // private static final int opc_iload_2 = 28; // private static final int opc_iload_3 = 29; private static final int opc_lload_0 = 30; // private static final int opc_lload_1 = 31; // private static final int opc_lload_2 = 32; // private static final int opc_lload_3 = 33; private static final int opc_fload_0 = 34; // private static final int opc_fload_1 = 35; // private static final int opc_fload_2 = 36; // private static final int opc_fload_3 = 37; private static final int opc_dload_0 = 38; // private static final int opc_dload_1 = 39; // private static final int opc_dload_2 = 40; // private static final int opc_dload_3 = 41; private static final int opc_aload_0 = 42; // private static final int opc_aload_1 = 43; // private static final int opc_aload_2 = 44; // private static final int opc_aload_3 = 45; // private static final int opc_iaload = 46; // private static final int opc_laload = 47; // private static final int opc_faload = 48; // private static final int opc_daload = 49; // private static final int opc_aaload = 50; // private static final int opc_baload = 51; // private static final int opc_caload = 52; // private static final int opc_saload = 53; // private static final int opc_istore = 54; // private static final int opc_lstore = 55; // private static final int opc_fstore = 56; // private static final int opc_dstore = 57; private static final int opc_astore = 58; // private static final int opc_istore_0 = 59; // private static final int opc_istore_1 = 60; // private static final int opc_istore_2 = 61; // private static final int opc_istore_3 = 62; // private static final int opc_lstore_0 = 63; // private static final int opc_lstore_1 = 64; // private static final int opc_lstore_2 = 65; // private static final int opc_lstore_3 = 66; // private static final int opc_fstore_0 = 67; // private static final int opc_fstore_1 = 68; // private static final int opc_fstore_2 = 69; // private static final int opc_fstore_3 = 70; // private static final int opc_dstore_0 = 71; // private static final int opc_dstore_1 = 72; // private static final int opc_dstore_2 = 73; // private static final int opc_dstore_3 = 74; private static final int opc_astore_0 = 75; // private static final int opc_astore_1 = 76; // private static final int opc_astore_2 = 77; // private static final int opc_astore_3 = 78; // private static final int opc_iastore = 79; // private static final int opc_lastore = 80; // private static final int opc_fastore = 81; // private static final int opc_dastore = 82; private static final int opc_aastore = 83; // private static final int opc_bastore = 84; // private static final int opc_castore = 85; // private static final int opc_sastore = 86; private static final int opc_pop = 87; // private static final int opc_pop2 = 88; private static final int opc_dup = 89; // private static final int opc_dup_x1 = 90; // private static final int opc_dup_x2 = 91; // private static final int opc_dup2 = 92; // private static final int opc_dup2_x1 = 93; // private static final int opc_dup2_x2 = 94; // private static final int opc_swap = 95; // private static final int opc_iadd = 96; // private static final int opc_ladd = 97; // private static final int opc_fadd = 98; // private static final int opc_dadd = 99; // private static final int opc_isub = 100; // private static final int opc_lsub = 101; // private static final int opc_fsub = 102; // private static final int opc_dsub = 103; // private static final int opc_imul = 104; // private static final int opc_lmul = 105; // private static final int opc_fmul = 106; // private static final int opc_dmul = 107; // private static final int opc_idiv = 108; // private static final int opc_ldiv = 109; // private static final int opc_fdiv = 110; // private static final int opc_ddiv = 111; // private static final int opc_irem = 112; // private static final int opc_lrem = 113; // private static final int opc_frem = 114; // private static final int opc_drem = 115; // private static final int opc_ineg = 116; // private static final int opc_lneg = 117; // private static final int opc_fneg = 118; // private static final int opc_dneg = 119; // private static final int opc_ishl = 120; // private static final int opc_lshl = 121; // private static final int opc_ishr = 122; // private static final int opc_lshr = 123; // private static final int opc_iushr = 124; // private static final int opc_lushr = 125; // private static final int opc_iand = 126; // private static final int opc_land = 127; // private static final int opc_ior = 128; // private static final int opc_lor = 129; // private static final int opc_ixor = 130; // private static final int opc_lxor = 131; // private static final int opc_iinc = 132; // private static final int opc_i2l = 133; // private static final int opc_i2f = 134; // private static final int opc_i2d = 135; // private static final int opc_l2i = 136; // private static final int opc_l2f = 137; // private static final int opc_l2d = 138; // private static final int opc_f2i = 139; // private static final int opc_f2l = 140; // private static final int opc_f2d = 141; // private static final int opc_d2i = 142; // private static final int opc_d2l = 143; // private static final int opc_d2f = 144; // private static final int opc_i2b = 145; // private static final int opc_i2c = 146; // private static final int opc_i2s = 147; // private static final int opc_lcmp = 148; // private static final int opc_fcmpl = 149; // private static final int opc_fcmpg = 150; // private static final int opc_dcmpl = 151; // private static final int opc_dcmpg = 152; // private static final int opc_ifeq = 153; private static final int opc_ifne = 154; // private static final int opc_iflt = 155; // private static final int opc_ifge = 156; // private static final int opc_ifgt = 157; // private static final int opc_ifle = 158; // private static final int opc_if_icmpeq = 159; // private static final int opc_if_icmpne = 160; // private static final int opc_if_icmplt = 161; // private static final int opc_if_icmpge = 162; // private static final int opc_if_icmpgt = 163; // private static final int opc_if_icmple = 164; // private static final int opc_if_acmpeq = 165; // private static final int opc_if_acmpne = 166; // private static final int opc_goto = 167; // private static final int opc_jsr = 168; // private static final int opc_ret = 169; // private static final int opc_tableswitch = 170; // private static final int opc_lookupswitch = 171; private static final int opc_ireturn = 172; private static final int opc_lreturn = 173; private static final int opc_freturn = 174; private static final int opc_dreturn = 175; private static final int opc_areturn = 176; private static final int opc_return = 177; private static final int opc_getstatic = 178; private static final int opc_putstatic = 179; private static final int opc_getfield = 180; // private static final int opc_putfield = 181; private static final int opc_invokevirtual = 182; private static final int opc_invokespecial = 183; private static final int opc_invokestatic = 184; private static final int opc_invokeinterface = 185; private static final int opc_new = 187; // private static final int opc_newarray = 188; private static final int opc_anewarray = 189; // private static final int opc_arraylength = 190; private static final int opc_athrow = 191; private static final int opc_checkcast = 192; // private static final int opc_instanceof = 193; // private static final int opc_monitorenter = 194; // private static final int opc_monitorexit = 195; private static final int opc_wide = 196; // private static final int opc_multianewarray = 197; // private static final int opc_ifnull = 198; // private static final int opc_ifnonnull = 199; // private static final int opc_goto_w = 200; // private static final int opc_jsr_w = 201; // end of constants copied from sun.tools.java.RuntimeConstants /** name of the superclass of proxy classes */ private final static String superclassName = "java/lang/reflect/Proxy"; /** name of field for storing a proxy instance's invocation handler */ private final static String handlerFieldName = "h"; /** debugging flag for saving generated class files */ // private final static boolean saveGeneratedFiles = java.security.AccessController.doPrivileged( // new GetBooleanAction("sun.misc.ProxyGenerator.saveGeneratedFiles")).booleanValue(); /** * Generate a public proxy class given a name and a list of proxy interfaces. * * @param loader */ public static byte[] generateProxyClass(final String name, CtClass[] interfaces, ClassPool cp) { return generateProxyClass(name, interfaces, cp, (ACC_PUBLIC | ACC_FINAL | ACC_SUPER)); } /** * Generate a proxy class given a name and a list of proxy interfaces. * * @param name * the class name of the proxy class * @param interfaces * proxy interfaces * @param cp * @param accessFlags * access flags of the proxy class */ public static byte[] generateProxyClass(final String name, CtClass[] interfaces, ClassPool cp, int accessFlags) { CtClassJavaProxyGenerator gen = new CtClassJavaProxyGenerator(name, interfaces, cp, accessFlags); final byte[] classFile = gen.generateClassFile(); // if (saveGeneratedFiles) { // java.security.AccessController.doPrivileged(new java.security.PrivilegedAction<Void>() { // public Void run() { // try { // int i = name.lastIndexOf('.'); // Path path; // if (i > 0) { // Path dir = Paths.get(name.substring(0, i).replace('.', File.separatorChar)); // Files.createDirectories(dir); // path = dir.resolve(name.substring(i + 1, name.length()) + ".class"); // } else { // path = Paths.get(name + ".class"); // } // Files.write(path, classFile); // return null; // } catch (IOException e) { // throw new InternalError("I/O exception saving generated file: " + e); // } // } // }); // } return classFile; } /* preloaded Method objects for methods in java.lang.Object */ private static class CtClassProxyFields { private CtMethod hashCodeMethod; private CtMethod equalsMethod; private CtMethod toStringMethod; private CtClass oclp; private CtClass error; private CtClass runtimee; private CtClass throwable; } // private static Map<ClassPool, WeakReference<CtClassProxyFields>> fieldsMap = new WeakHashMap<ClassPool, // WeakReference<CtClassProxyFields>>( // 2); private CtClassProxyFields f; private void initFields(ClassPool clp) { // WeakReference<CtClassProxyFields> ref = fieldsMap.get(clp); CtClassProxyFields f = null; // if (f == null || (f = ref.get()) == null) { // synchronized (fieldsMap) { // ref = fieldsMap.get(clp); // if (ref == null || (f = ref.get()) == null) { f = new CtClassProxyFields(); try { f.oclp = clp.get(Object.class.getName()); f.error = clp.get(Error.class.getName()); f.runtimee = clp.get(RuntimeException.class.getName()); f.throwable = clp.get(Throwable.class.getName()); f.hashCodeMethod = f.oclp.getDeclaredMethod("hashCode"); f.equalsMethod = f.oclp.getDeclaredMethod("equals"); f.toStringMethod = f.oclp.getDeclaredMethod("toString"); } catch (NotFoundException e) { throw new RuntimeException(e); } // fieldsMap.put(clp, new WeakReference<CtClassProxyFields>(f)); // } // } // } this.f = f; } /** name of proxy class */ private String className; private String random = UUID.randomUUID().toString().replace("-", ""); private String initFieldName = "clinitCalled" + random; private String initMethodName = "clinitMethodByJavaAgentForHotSwap"; /** proxy interfaces */ private CtClass[] interfaces; /** proxy class access flags */ private int accessFlags; /** constant pool of class being generated */ private ConstantPool cp = new ConstantPool(); /** FieldInfo struct for each field of generated class */ private List<FieldInfo> fields = new ArrayList<>(); /** MethodInfo struct for each method of generated class */ private List<MethodInfo> methods = new ArrayList<>(); /** * maps method signature string to list of ProxyMethod objects for proxy methods with that signature */ private Map<String, List<ProxyMethod>> proxyMethods = new HashMap<>(); /** count of ProxyMethod objects added to proxyMethods */ private int proxyMethodCount = 0; /** * Construct a ProxyGenerator to generate a proxy class with the specified name and for the given interfaces. * * A ProxyGenerator object contains the state for the ongoing generation of a particular proxy class. * * @param cp */ private CtClassJavaProxyGenerator(String className, CtClass[] interfaces, ClassPool cp, int accessFlags) { this.className = className; this.interfaces = interfaces; this.accessFlags = accessFlags; initFields(cp); } private void addInterfacesWithSuperInterfaces(Collection<CtClass> iWithSuper, CtClass[] interfaces) { iWithSuper.addAll(Arrays.asList(interfaces)); for (CtClass intf : interfaces) { try { addInterfacesWithSuperInterfaces(iWithSuper, intf.getInterfaces()); } catch (NotFoundException e) { e.printStackTrace(); } } } /** * Generate a class file for the proxy class. This method drives the class file generation process. */ private byte[] generateClassFile() { /* * ============================================================ Step 1: Assemble ProxyMethod objects for all * methods to generate proxy dispatching code for. */ /* * Record that proxy methods are needed for the hashCode, equals, and toString methods of java.lang.Object. This * is done before the methods from the proxy interfaces so that the methods from java.lang.Object take * precedence over duplicate methods in the proxy interfaces. */ addProxyMethod(f.hashCodeMethod, f.oclp); addProxyMethod(f.equalsMethod, f.oclp); addProxyMethod(f.toStringMethod, f.oclp); Collection<CtClass> iWithSuper = new HashSet<>(); addInterfacesWithSuperInterfaces(iWithSuper, interfaces); /* * Now record all of the methods from the proxy interfaces, giving earlier interfaces precedence over later ones * with duplicate methods. */ for (CtClass intf : iWithSuper) { for (CtMethod m : intf.getDeclaredMethods()) { addProxyMethod(m, intf); } } /* * For each set of proxy methods with the same signature, verify that the methods' return types are compatible. */ for (List<ProxyMethod> sigmethods : proxyMethods.values()) { checkReturnTypes(sigmethods); } /* * ============================================================ Step 2: Assemble FieldInfo and MethodInfo * structs for all of fields and methods in the class we are generating. */ try { methods.add(generateConstructor()); for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { // add static field for method's Method object fields.add(new FieldInfo(pm.methodFieldName, "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC)); } } fields.add(new FieldInfo(initFieldName, "Z", ACC_PRIVATE | ACC_STATIC)); for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { // generate code for proxy method and add it methods.add(pm.generateMethod()); } } methods.add(generateStaticInitializer()); methods.add(generateStaticInitializerCaller()); } catch (IOException e) { throw new InternalError("unexpected I/O Exception"); } if (methods.size() > 65535) { throw new IllegalArgumentException("method limit exceeded"); } if (fields.size() > 65535) { throw new IllegalArgumentException("field limit exceeded"); } /* * ============================================================ Step 3: Write the final class file. */ /* * Make sure that constant pool indexes are reserved for the following items before starting to write the final * class file. */ cp.getClass(dotToSlash(className)); cp.getClass(superclassName); for (CtClass intf : interfaces) { cp.getClass(dotToSlash(intf.getName())); } /* * Disallow new constant pool additions beyond this point, since we are about to write the final constant pool * table. */ cp.setReadOnly(); ByteArrayOutputStream bout = new ByteArrayOutputStream(); DataOutputStream dout = new DataOutputStream(bout); try { /* * Write all the items of the "ClassFile" structure. See JVMS section 4.1. */ // u4 magic; dout.writeInt(0xCAFEBABE); // u2 minor_version; dout.writeShort(CLASSFILE_MINOR_VERSION); // u2 major_version; dout.writeShort(CLASSFILE_MAJOR_VERSION); cp.write(dout); // (write constant pool) // u2 access_flags; dout.writeShort(accessFlags); // u2 this_class; dout.writeShort(cp.getClass(dotToSlash(className))); // u2 super_class; dout.writeShort(cp.getClass(superclassName)); // u2 interfaces_count; dout.writeShort(interfaces.length); // u2 interfaces[interfaces_count]; for (CtClass intf : interfaces) { dout.writeShort(cp.getClass(dotToSlash(intf.getName()))); } // u2 fields_count; dout.writeShort(fields.size()); // field_info fields[fields_count]; for (FieldInfo f : fields) { f.write(dout); } // u2 methods_count; dout.writeShort(methods.size()); // method_info methods[methods_count]; for (MethodInfo m : methods) { m.write(dout); } // u2 attributes_count; dout.writeShort(0); // (no ClassFile attributes for proxy classes) } catch (IOException e) { throw new InternalError("unexpected I/O Exception"); } return bout.toByteArray(); } /** * Add another method to be proxied, either by creating a new ProxyMethod object or augmenting an old one for a * duplicate method. * * "fromClass" indicates the proxy interface that the method was found through, which may be different from (a * subinterface of) the method's "declaring class". Note that the first Method object passed for a given name and * descriptor identifies the Method object (and thus the declaring class) that will be passed to the invocation * handler's "invoke" method for a given set of duplicate methods. */ private void addProxyMethod(CtMethod m, CtClass fromClass) { String name = m.getName(); CtClass[] parameterTypes; CtClass returnType; CtClass[] exceptionTypes; try { parameterTypes = m.getParameterTypes(); returnType = m.getReturnType(); exceptionTypes = m.getExceptionTypes(); } catch (NotFoundException e) { throw new RuntimeException(e); } String sig = name + getParameterDescriptors(parameterTypes); List<ProxyMethod> sigmethods = proxyMethods.get(sig); if (sigmethods != null) { for (ProxyMethod pm : sigmethods) { if (returnType == pm.returnType || returnType.getName().equals(pm.returnType.getName())) { /* * Found a match: reduce exception types to the greatest set of exceptions that can thrown * compatibly with the throws clauses of both overridden methods. */ List<CtClass> legalExceptions = new ArrayList<>(); collectCompatibleTypes(exceptionTypes, pm.exceptionTypes, legalExceptions); collectCompatibleTypes(pm.exceptionTypes, exceptionTypes, legalExceptions); pm.exceptionTypes = new CtClass[legalExceptions.size()]; pm.exceptionTypes = legalExceptions.toArray(pm.exceptionTypes); return; } } } else { sigmethods = new ArrayList<>(3); proxyMethods.put(sig, sigmethods); } sigmethods.add(new ProxyMethod(name, parameterTypes, returnType, exceptionTypes, fromClass)); } /** * For a given set of proxy methods with the same signature, check that their return types are compatible according * to the Proxy specification. * * Specifically, if there is more than one such method, then all of the return types must be reference types, and * there must be one return type that is assignable to each of the rest of them. */ private static void checkReturnTypes(List<ProxyMethod> methods) { /* * If there is only one method with a given signature, there cannot be a conflict. This is the only case in * which a primitive (or void) return type is allowed. */ if (methods.size() < 2) { return; } /* * List of return types that are not yet known to be assignable from ("covered" by) any of the others. */ LinkedList<CtClass> uncoveredReturnTypes = new LinkedList<>(); nextNewReturnType: for (ProxyMethod pm : methods) { CtClass newReturnType = pm.returnType; if (newReturnType.isPrimitive()) { throw new IllegalArgumentException("methods with same signature " + getFriendlyMethodSignature(pm.methodName, pm.parameterTypes) + " but incompatible return types: " + newReturnType.getName() + " and others"); } boolean added = false; /* * Compare the new return type to the existing uncovered return types. */ ListIterator<CtClass> liter = uncoveredReturnTypes.listIterator(); while (liter.hasNext()) { CtClass uncoveredReturnType = liter.next(); /* * If an existing uncovered return type is assignable to this new one, then we can forget the new one. */ if (isAssignableFrom(newReturnType, uncoveredReturnType)) { assert !added; continue nextNewReturnType; } /* * If the new return type is assignable to an existing uncovered one, then should replace the existing * one with the new one (or just forget the existing one, if the new one has already be put in the * list). */ if (isAssignableFrom(uncoveredReturnType, newReturnType)) { // (we can assume that each return type is unique) if (!added) { liter.set(newReturnType); added = true; } else { liter.remove(); } } } /* * If we got through the list of existing uncovered return types without an assignability relationship, then * add the new return type to the list of uncovered ones. */ if (!added) { uncoveredReturnTypes.add(newReturnType); } } /* * We shouldn't end up with more than one return type that is not assignable from any of the others. */ if (uncoveredReturnTypes.size() > 1) { ProxyMethod pm = methods.get(0); throw new IllegalArgumentException("methods with same signature " + getFriendlyMethodSignature(pm.methodName, pm.parameterTypes) + " but incompatible return types: " + uncoveredReturnTypes); } } /** * A FieldInfo object contains information about a particular field in the class being generated. The class mirrors * the data items of the "field_info" structure of the class file format (see JVMS 4.5). */ private class FieldInfo { public int accessFlags; public String name; public String descriptor; public FieldInfo(String name, String descriptor, int accessFlags) { this.name = name; this.descriptor = descriptor; this.accessFlags = accessFlags; /* * Make sure that constant pool indexes are reserved for the following items before starting to write the * final class file. */ cp.getUtf8(name); cp.getUtf8(descriptor); } public void write(DataOutputStream out) throws IOException { /* * Write all the items of the "field_info" structure. See JVMS section 4.5. */ // u2 access_flags; out.writeShort(accessFlags); // u2 name_index; out.writeShort(cp.getUtf8(name)); // u2 descriptor_index; out.writeShort(cp.getUtf8(descriptor)); // u2 attributes_count; out.writeShort(0); // (no field_info attributes for proxy classes) } } /** * An ExceptionTableEntry object holds values for the data items of an entry in the "exception_table" item of the * "Code" attribute of "method_info" structures (see JVMS 4.7.3). */ private static class ExceptionTableEntry { public short startPc; public short endPc; public short handlerPc; public short catchType; public ExceptionTableEntry(short startPc, short endPc, short handlerPc, short catchType) { this.startPc = startPc; this.endPc = endPc; this.handlerPc = handlerPc; this.catchType = catchType; } }; /** * A MethodInfo object contains information about a particular method in the class being generated. This class * mirrors the data items of the "method_info" structure of the class file format (see JVMS 4.6). */ private class MethodInfo { public int accessFlags; public String name; public String descriptor; public short maxStack; public short maxLocals; public ByteArrayOutputStream code = new ByteArrayOutputStream(); public List<ExceptionTableEntry> exceptionTable = new ArrayList<ExceptionTableEntry>(); public short[] declaredExceptions; public MethodInfo(String name, String descriptor, int accessFlags) { this.name = name; this.descriptor = descriptor; this.accessFlags = accessFlags; /* * Make sure that constant pool indexes are reserved for the following items before starting to write the * final class file. */ cp.getUtf8(name); cp.getUtf8(descriptor); cp.getUtf8("Code"); cp.getUtf8("Exceptions"); } public void write(DataOutputStream out) throws IOException { /* * Write all the items of the "method_info" structure. See JVMS section 4.6. */ // u2 access_flags; out.writeShort(accessFlags); // u2 name_index; out.writeShort(cp.getUtf8(name)); // u2 descriptor_index; out.writeShort(cp.getUtf8(descriptor)); // u2 attributes_count; out.writeShort(2); // (two method_info attributes:) // Write "Code" attribute. See JVMS section 4.7.3. // u2 attribute_name_index; out.writeShort(cp.getUtf8("Code")); // u4 attribute_length; out.writeInt(12 + code.size() + 8 * exceptionTable.size()); // u2 max_stack; out.writeShort(maxStack); // u2 max_locals; out.writeShort(maxLocals); // u2 code_length; out.writeInt(code.size()); // u1 code[code_length]; code.writeTo(out); // u2 exception_table_length; out.writeShort(exceptionTable.size()); for (ExceptionTableEntry e : exceptionTable) { // u2 start_pc; out.writeShort(e.startPc); // u2 end_pc; out.writeShort(e.endPc); // u2 handler_pc; out.writeShort(e.handlerPc); // u2 catch_type; out.writeShort(e.catchType); } // u2 attributes_count; out.writeShort(0); // write "Exceptions" attribute. See JVMS section 4.7.4. // u2 attribute_name_index; out.writeShort(cp.getUtf8("Exceptions")); // u4 attributes_length; out.writeInt(2 + 2 * declaredExceptions.length); // u2 number_of_exceptions; out.writeShort(declaredExceptions.length); // u2 exception_index_table[number_of_exceptions]; for (short value : declaredExceptions) { out.writeShort(value); } } } /** * A ProxyMethod object represents a proxy method in the proxy class being generated: a method whose implementation * will encode and dispatch invocations to the proxy instance's invocation handler. */ private class ProxyMethod { /** * */ public String methodName; public CtClass[] parameterTypes; public CtClass returnType; public CtClass[] exceptionTypes; public CtClass fromClass; public String methodFieldName; private ProxyMethod(String methodName, CtClass[] parameterTypes, CtClass returnType, CtClass[] exceptionTypes, CtClass fromClass) { this.methodName = methodName; this.parameterTypes = parameterTypes; this.returnType = returnType; this.exceptionTypes = exceptionTypes; this.fromClass = fromClass; this.methodFieldName = "m" + proxyMethodCount++; } /** * Return a MethodInfo object for this method, including generating the code and exception table entry. */ private MethodInfo generateMethod() throws IOException { String desc = getMethodDescriptor(parameterTypes, returnType); MethodInfo minfo = new MethodInfo(methodName, desc, ACC_PUBLIC | ACC_FINAL); int[] parameterSlot = new int[parameterTypes.length]; int nextSlot = 1; for (int i = 0; i < parameterSlot.length; i++) { parameterSlot[i] = nextSlot; nextSlot += getWordsPerType(parameterTypes[i]); } int localSlot0 = nextSlot; short pc, tryBegin = 0, tryEnd; DataOutputStream out = new DataOutputStream(minfo.code); out.writeByte(opc_getstatic); out.writeShort(cp.getFieldRef(dotToSlash(className), initFieldName, "Z")); out.writeByte(opc_ifne); out.writeShort(6); out.writeByte(opc_invokestatic); out.writeShort(cp.getMethodRef(dotToSlash(className), initMethodName, "()V")); code_aload(0, out); out.writeByte(opc_getfield); out.writeShort(cp.getFieldRef(superclassName, handlerFieldName, "Ljava/lang/reflect/InvocationHandler;")); code_aload(0, out); out.writeByte(opc_getstatic); out.writeShort(cp.getFieldRef(dotToSlash(className), methodFieldName, "Ljava/lang/reflect/Method;")); if (parameterTypes.length > 0) { code_ipush(parameterTypes.length, out); out.writeByte(opc_anewarray); out.writeShort(cp.getClass("java/lang/Object")); for (int i = 0; i < parameterTypes.length; i++) { out.writeByte(opc_dup); code_ipush(i, out); codeWrapArgument(parameterTypes[i], parameterSlot[i], out); out.writeByte(opc_aastore); } } else { out.writeByte(opc_aconst_null); } out.writeByte(opc_invokeinterface); out.writeShort(cp.getInterfaceMethodRef("java/lang/reflect/InvocationHandler", "invoke", "(Ljava/lang/Object;Ljava/lang/reflect/Method;" + "[Ljava/lang/Object;)Ljava/lang/Object;")); out.writeByte(4); out.writeByte(0); if (returnType == CtClass.voidType) { out.writeByte(opc_pop); out.writeByte(opc_return); } else { codeUnwrapReturnValue(returnType, out); } tryEnd = pc = (short) minfo.code.size(); List<CtClass> catchList = computeUniqueCatchList(exceptionTypes); if (catchList.size() > 0) { for (CtClass ex : catchList) { minfo.exceptionTable.add(new ExceptionTableEntry(tryBegin, tryEnd, pc, cp.getClass(dotToSlash(ex .getName())))); } out.writeByte(opc_athrow); pc = (short) minfo.code.size(); minfo.exceptionTable.add(new ExceptionTableEntry(tryBegin, tryEnd, pc, cp .getClass("java/lang/Throwable"))); code_astore(localSlot0, out); out.writeByte(opc_new); out.writeShort(cp.getClass("java/lang/reflect/UndeclaredThrowableException")); out.writeByte(opc_dup); code_aload(localSlot0, out); out.writeByte(opc_invokespecial); out.writeShort(cp.getMethodRef("java/lang/reflect/UndeclaredThrowableException", "<init>", "(Ljava/lang/Throwable;)V")); out.writeByte(opc_athrow); } if (minfo.code.size() > 65535) { throw new IllegalArgumentException("code size limit exceeded"); } minfo.maxStack = 10; minfo.maxLocals = (short) (localSlot0 + 1); minfo.declaredExceptions = new short[exceptionTypes.length]; for (int i = 0; i < exceptionTypes.length; i++) { minfo.declaredExceptions[i] = cp.getClass(dotToSlash(exceptionTypes[i].getName())); } return minfo; } /** * Generate code for wrapping an argument of the given type whose value can be found at the specified local * variable index, in order for it to be passed (as an Object) to the invocation handler's "invoke" method. The * code is written to the supplied stream. */ private void codeWrapArgument(CtClass type, int slot, DataOutputStream out) throws IOException { if (type.isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); if (type == CtClass.intType || type == CtClass.booleanType || type == CtClass.byteType || type == CtClass.charType || type == CtClass.shortType) { code_iload(slot, out); } else if (type == CtClass.longType) { code_lload(slot, out); } else if (type == CtClass.floatType) { code_fload(slot, out); } else if (type == CtClass.doubleType) { code_dload(slot, out); } else { throw new AssertionError(); } out.writeByte(opc_invokestatic); out.writeShort(cp.getMethodRef(prim.wrapperClassName, "valueOf", prim.wrapperValueOfDesc)); } else { code_aload(slot, out); } } /** * Generate code for unwrapping a return value of the given type from the invocation handler's "invoke" method * (as type Object) to its correct type. The code is written to the supplied stream. */ private void codeUnwrapReturnValue(CtClass type, DataOutputStream out) throws IOException { if (type.isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(type); out.writeByte(opc_checkcast); out.writeShort(cp.getClass(prim.wrapperClassName)); out.writeByte(opc_invokevirtual); out.writeShort(cp.getMethodRef(prim.wrapperClassName, prim.unwrapMethodName, prim.unwrapMethodDesc)); if (type == CtClass.intType || type == CtClass.booleanType || type == CtClass.byteType || type == CtClass.charType || type == CtClass.shortType) { out.writeByte(opc_ireturn); } else if (type == CtClass.longType) { out.writeByte(opc_lreturn); } else if (type == CtClass.floatType) { out.writeByte(opc_freturn); } else if (type == CtClass.doubleType) { out.writeByte(opc_dreturn); } else { throw new AssertionError(); } } else { out.writeByte(opc_checkcast); out.writeShort(cp.getClass(dotToSlash(type.getName()))); out.writeByte(opc_areturn); } } /** * Generate code for initializing the static field that stores the Method object for this proxy method. The code * is written to the supplied stream. */ private void codeFieldInitialization(DataOutputStream out) throws IOException { codeClassForName(fromClass, out); code_ldc(cp.getString(methodName), out); code_ipush(parameterTypes.length, out); out.writeByte(opc_anewarray); out.writeShort(cp.getClass("java/lang/Class")); for (int i = 0; i < parameterTypes.length; i++) { out.writeByte(opc_dup); code_ipush(i, out); if (parameterTypes[i].isPrimitive()) { PrimitiveTypeInfo prim = PrimitiveTypeInfo.get(parameterTypes[i]); out.writeByte(opc_getstatic); out.writeShort(cp.getFieldRef(prim.wrapperClassName, "TYPE", "Ljava/lang/Class;")); } else { codeClassForName(parameterTypes[i], out); } out.writeByte(opc_aastore); } out.writeByte(opc_invokevirtual); out.writeShort(cp.getMethodRef("java/lang/Class", "getMethod", "(Ljava/lang/String;[Ljava/lang/Class;)" + "Ljava/lang/reflect/Method;")); out.writeByte(opc_putstatic); out.writeShort(cp.getFieldRef(dotToSlash(className), methodFieldName, "Ljava/lang/reflect/Method;")); } } /** * Generate the constructor method for the proxy class. */ private MethodInfo generateConstructor() throws IOException { MethodInfo minfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", ACC_PUBLIC); DataOutputStream out = new DataOutputStream(minfo.code); code_aload(0, out); code_aload(1, out); out.writeByte(opc_invokespecial); out.writeShort(cp.getMethodRef(superclassName, "<init>", "(Ljava/lang/reflect/InvocationHandler;)V")); out.writeByte(opc_return); minfo.maxStack = 10; minfo.maxLocals = 2; minfo.declaredExceptions = new short[0]; return minfo; } /** * Generate the static initializer method for the proxy class. */ private MethodInfo generateStaticInitializerCaller() throws IOException { MethodInfo minfo = new MethodInfo("<clinit>", "()V", ACC_STATIC); DataOutputStream out = new DataOutputStream(minfo.code); out.writeByte(opc_invokestatic); out.writeShort(cp.getMethodRef(dotToSlash(className), initMethodName, "()V")); out.writeByte(opc_return); minfo.declaredExceptions = new short[0]; return minfo; } /** * Generate the static initializer method for the proxy class. */ private MethodInfo generateStaticInitializer() throws IOException { MethodInfo minfo = new MethodInfo(initMethodName, "()V", ACC_STATIC); int localSlot0 = 1; short pc, tryBegin = 0, tryEnd; DataOutputStream out = new DataOutputStream(minfo.code); for (List<ProxyMethod> sigmethods : proxyMethods.values()) { for (ProxyMethod pm : sigmethods) { pm.codeFieldInitialization(out); } } out.writeByte(opc_iconst_1); out.writeByte(opc_putstatic); out.writeShort(cp.getFieldRef(dotToSlash(className), initFieldName, "Z")); out.writeByte(opc_return); tryEnd = pc = (short) minfo.code.size(); minfo.exceptionTable.add(new ExceptionTableEntry(tryBegin, tryEnd, pc, cp .getClass("java/lang/NoSuchMethodException"))); code_astore(localSlot0, out); out.writeByte(opc_new); out.writeShort(cp.getClass("java/lang/NoSuchMethodError")); out.writeByte(opc_dup); code_aload(localSlot0, out); out.writeByte(opc_invokevirtual); out.writeShort(cp.getMethodRef("java/lang/Throwable", "getMessage", "()Ljava/lang/String;")); out.writeByte(opc_invokespecial); out.writeShort(cp.getMethodRef("java/lang/NoSuchMethodError", "<init>", "(Ljava/lang/String;)V")); out.writeByte(opc_athrow); pc = (short) minfo.code.size(); minfo.exceptionTable.add(new ExceptionTableEntry(tryBegin, tryEnd, pc, cp .getClass("java/lang/ClassNotFoundException"))); code_astore(localSlot0, out); out.writeByte(opc_new); out.writeShort(cp.getClass("java/lang/NoClassDefFoundError")); out.writeByte(opc_dup); code_aload(localSlot0, out); out.writeByte(opc_invokevirtual); out.writeShort(cp.getMethodRef("java/lang/Throwable", "getMessage", "()Ljava/lang/String;")); out.writeByte(opc_invokespecial); out.writeShort(cp.getMethodRef("java/lang/NoClassDefFoundError", "<init>", "(Ljava/lang/String;)V")); out.writeByte(opc_athrow); if (minfo.code.size() > 65535) { throw new IllegalArgumentException("code size limit exceeded"); } minfo.maxStack = 10; minfo.maxLocals = (short) (localSlot0 + 1); minfo.declaredExceptions = new short[0]; return minfo; } /* * =============== Code Generation Utility Methods =============== */ /* * The following methods generate code for the load or store operation indicated by their name for the given local * variable. The code is written to the supplied stream. */ private void code_iload(int lvar, DataOutputStream out) throws IOException { codeLocalLoadStore(lvar, opc_iload, opc_iload_0, out); } private void code_lload(int lvar, DataOutputStream out) throws IOException { codeLocalLoadStore(lvar, opc_lload, opc_lload_0, out); } private void code_fload(int lvar, DataOutputStream out) throws IOException { codeLocalLoadStore(lvar, opc_fload, opc_fload_0, out); } private void code_dload(int lvar, DataOutputStream out) throws IOException { codeLocalLoadStore(lvar, opc_dload, opc_dload_0, out); } private void code_aload(int lvar, DataOutputStream out) throws IOException { codeLocalLoadStore(lvar, opc_aload, opc_aload_0, out); } // private void code_istore(int lvar, DataOutputStream out) // throws IOException // { // codeLocalLoadStore(lvar, opc_istore, opc_istore_0, out); // } // private void code_lstore(int lvar, DataOutputStream out) // throws IOException // { // codeLocalLoadStore(lvar, opc_lstore, opc_lstore_0, out); // } // private void code_fstore(int lvar, DataOutputStream out) // throws IOException // { // codeLocalLoadStore(lvar, opc_fstore, opc_fstore_0, out); // } // private void code_dstore(int lvar, DataOutputStream out) // throws IOException // { // codeLocalLoadStore(lvar, opc_dstore, opc_dstore_0, out); // } private void code_astore(int lvar, DataOutputStream out) throws IOException { codeLocalLoadStore(lvar, opc_astore, opc_astore_0, out); } /** * Generate code for a load or store instruction for the given local variable. The code is written to the supplied * stream. * * "opcode" indicates the opcode form of the desired load or store instruction that takes an explicit local variable * index, and "opcode_0" indicates the corresponding form of the instruction with the implicit index 0. */ private void codeLocalLoadStore(int lvar, int opcode, int opcode_0, DataOutputStream out) throws IOException { assert lvar >= 0 && lvar <= 0xFFFF; if (lvar <= 3) { out.writeByte(opcode_0 + lvar); } else if (lvar <= 0xFF) { out.writeByte(opcode); out.writeByte(lvar & 0xFF); } else { /* * Use the "wide" instruction modifier for local variable indexes that do not fit into an unsigned byte. */ out.writeByte(opc_wide); out.writeByte(opcode); out.writeShort(lvar & 0xFFFF); } } /** * Generate code for an "ldc" instruction for the given constant pool index (the "ldc_w" instruction is used if the * index does not fit into an unsigned byte). The code is written to the supplied stream. */ private void code_ldc(int index, DataOutputStream out) throws IOException { assert index >= 0 && index <= 0xFFFF; if (index <= 0xFF) { out.writeByte(opc_ldc); out.writeByte(index & 0xFF); } else { out.writeByte(opc_ldc_w); out.writeShort(index & 0xFFFF); } } /** * Generate code to push a constant integer value on to the operand stack, using the "iconst_<i>", "bipush", or * "sipush" instructions depending on the size of the value. The code is written to the supplied stream. */ private void code_ipush(int value, DataOutputStream out) throws IOException { if (value >= -1 && value <= 5) { out.writeByte(opc_iconst_0 + value); } else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) { out.writeByte(opc_bipush); out.writeByte(value & 0xFF); } else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) { out.writeByte(opc_sipush); out.writeShort(value & 0xFFFF); } else { throw new AssertionError(); } } /** * Generate code to invoke the Class.forName with the name of the given class to get its Class object at runtime. * The code is written to the supplied stream. Note that the code generated by this method may caused the checked * ClassNotFoundException to be thrown. */ private void codeClassForName(CtClass cl, DataOutputStream out) throws IOException { if (cl.isArray()) { code_ldc(cp.getString(Descriptor.of(cl).replace("/", ".")), out); } else code_ldc(cp.getString(cl.getName()), out); out.writeByte(opc_invokestatic); out.writeShort(cp.getMethodRef("java/lang/Class", "forName", "(Ljava/lang/String;)Ljava/lang/Class;")); } /* * ==================== General Utility Methods ==================== */ /** * Convert a fully qualified class name that uses '.' as the package separator, the external representation used by * the Java language and APIs, to a fully qualified class name that uses '/' as the package separator, the * representation used in the class file format (see JVMS section 4.2). */ private static String dotToSlash(String name) { return name.replace('.', '/'); } /** * Return the "method descriptor" string for a method with the given parameter types and return type. See JVMS * section 4.3.3. */ private static String getMethodDescriptor(CtClass[] parameterTypes, CtClass returnType) { return getParameterDescriptors(parameterTypes) + ((returnType == CtClass.voidType) ? "V" : getFieldType(returnType)); } /** * Return the list of "parameter descriptor" strings enclosed in parentheses corresponding to the given parameter * types (in other words, a method descriptor without a return descriptor). This string is useful for constructing * string keys for methods without regard to their return type. */ private static String getParameterDescriptors(CtClass[] parameterTypes) { StringBuilder desc = new StringBuilder("("); for (int i = 0; i < parameterTypes.length; i++) { desc.append(getFieldType(parameterTypes[i])); } desc.append(')'); return desc.toString(); } /** * Return the "field type" string for the given type, appropriate for a field descriptor, a parameter descriptor, or * a return descriptor other than "void". See JVMS section 4.3.2. */ private static String getFieldType(CtClass type) { if (type.isPrimitive()) { return PrimitiveTypeInfo.get(type).baseTypeString; } else if (type.isArray()) { /* * According to JLS 20.3.2, the getName() method on Class does return the VM type descriptor format for * array classes (only); using that should be quicker than the otherwise obvious code: * * return "[" + getTypeDescriptor(type.getComponentType()); */ return Descriptor.of(type); } else { return "L" + dotToSlash(type.getName()) + ";"; } } /** * Returns a human-readable string representing the signature of a method with the given name and parameter types. */ private static String getFriendlyMethodSignature(String name, CtClass[] parameterTypes) { StringBuilder sig = new StringBuilder(name); sig.append('('); for (int i = 0; i < parameterTypes.length; i++) { if (i > 0) { sig.append(','); } CtClass parameterType = parameterTypes[i]; int dimensions = 0; while (parameterType.isArray()) { try { parameterType = parameterType.getComponentType(); } catch (NotFoundException e) { throw new RuntimeException(e); } dimensions++; } sig.append(parameterType.getName()); while (dimensions-- > 0) { sig.append("[]"); } } sig.append(')'); return sig.toString(); } /** * Return the number of abstract "words", or consecutive local variable indexes, required to contain a value of the * given type. See JVMS section 3.6.1. * * Note that the original version of the JVMS contained a definition of this abstract notion of a "word" in section * 3.4, but that definition was removed for the second edition. */ private static int getWordsPerType(CtClass type) { if (type == CtClass.longType || type == CtClass.doubleType) { return 2; } else { return 1; } } /** * Add to the given list all of the types in the "from" array that are not already contained in the list and are * assignable to at least one of the types in the "with" array. * * This method is useful for computing the greatest common set of declared exceptions from duplicate methods * inherited from different interfaces. */ private static void collectCompatibleTypes(CtClass[] from, CtClass[] with, List<CtClass> list) { for (CtClass fc : from) { if (!list.contains(fc)) { for (CtClass wc : with) { if (isAssignableFrom(wc, fc)) { list.add(fc); break; } } } } } /** * Given the exceptions declared in the throws clause of a proxy method, compute the exceptions that need to be * caught from the invocation handler's invoke method and rethrown intact in the method's implementation before * catching other Throwables and wrapping them in UndeclaredThrowableExceptions. * * The exceptions to be caught are returned in a List object. Each exception in the returned list is guaranteed to * not be a subclass of any of the other exceptions in the list, so the catch blocks for these exceptions may be * generated in any order relative to each other. * * Error and RuntimeException are each always contained by the returned list (if none of their superclasses are * contained), since those unchecked exceptions should always be rethrown intact, and thus their subclasses will * never appear in the returned list. * * The returned List will be empty if java.lang.Throwable is in the given list of declared exceptions, indicating * that no exceptions need to be caught. */ private List<CtClass> computeUniqueCatchList(CtClass[] exceptions) { List<CtClass> uniqueList = new ArrayList<>(); // unique exceptions to catch uniqueList.add(f.error); // always catch/rethrow these uniqueList.add(f.runtimee); nextException: for (CtClass ex : exceptions) { if (isAssignableFrom(ex, f.throwable)) { /* * If Throwable is declared to be thrown by the proxy method, then no catch blocks are necessary, * because the invoke can, at most, throw Throwable anyway. */ uniqueList.clear(); break; } else if (!isAssignableFrom(f.throwable, ex)) { /* * Ignore types that cannot be thrown by the invoke method. */ continue; } /* * Compare this exception against the current list of exceptions that need to be caught: */ for (int j = 0; j < uniqueList.size();) { CtClass ex2 = uniqueList.get(j); if (isAssignableFrom(ex2, ex)) { /* * if a superclass of this exception is already on the list to catch, then ignore this one and * continue; */ continue nextException; } else if (isAssignableFrom(ex, ex2)) { /* * if a subclass of this exception is on the list to catch, then remove it; */ uniqueList.remove(j); } else { j++; // else continue comparing. } } // This exception is unique (so far): add it to the list to catch. uniqueList.add(ex); } return uniqueList; } /** * @param ex2 * @param ex * @return */ private static boolean isAssignableFrom(CtClass ex2, CtClass ex) { try { return ex.subtypeOf(ex2); } catch (NotFoundException e) { throw new RuntimeException(e); } } /** * A PrimitiveTypeInfo object contains assorted information about a primitive type in its public fields. The struct * for a particular primitive type can be obtained using the static "get" method. */ private static class PrimitiveTypeInfo { /** "base type" used in various descriptors (see JVMS section 4.3.2) */ public String baseTypeString; /** name of corresponding wrapper class */ public String wrapperClassName; /** method descriptor for wrapper class "valueOf" factory method */ public String wrapperValueOfDesc; /** name of wrapper class method for retrieving primitive value */ public String unwrapMethodName; /** descriptor of same method */ public String unwrapMethodDesc; private static Map<CtClass, PrimitiveTypeInfo> table = new HashMap<>(); // static { // add(byte.class, Byte.class); // add(char.class, Character.class); // add(double.class, Double.class); // add(float.class, Float.class); // add(int.class, Integer.class); // add(long.class, Long.class); // add(short.class, Short.class); // add(boolean.class, Boolean.class); // } static { add(CtClass.byteType); add(CtClass.charType); add(CtClass.doubleType); add(CtClass.floatType); add(CtClass.intType); add(CtClass.longType); add(CtClass.shortType); add(CtClass.booleanType); } private static void add(CtClass primitiveClass) { table.put(primitiveClass, new PrimitiveTypeInfo(primitiveClass)); } private PrimitiveTypeInfo(CtClass primitiveClass) { assert primitiveClass.isPrimitive(); // try { // baseTypeString = Array.newInstance(primitiveClass.toClass(), 0).getClass().getName().substring(1); // } catch (NegativeArraySizeException | CannotCompileException e) { // throw new RuntimeException(); // } baseTypeString = String.valueOf(((CtPrimitiveType) primitiveClass).getDescriptor()); wrapperClassName = dotToSlash(((CtPrimitiveType) primitiveClass).getWrapperName()); wrapperValueOfDesc = "(" + baseTypeString + ")L" + wrapperClassName + ";"; unwrapMethodName = primitiveClass.getName() + "Value"; unwrapMethodDesc = "()" + baseTypeString; } public static PrimitiveTypeInfo get(CtClass cl) { return table.get(cl); } } /** * A ConstantPool object represents the constant pool of a class file being generated. This representation of a * constant pool is designed specifically for use by ProxyGenerator; in particular, it assumes that constant pool * entries will not need to be resorted (for example, by their type, as the Java compiler does), so that the final * index value can be assigned and used when an entry is first created. * * Note that new entries cannot be created after the constant pool has been written to a class file. To prevent such * logic errors, a ConstantPool instance can be marked "read only", so that further attempts to add new entries will * fail with a runtime exception. * * See JVMS section 4.4 for more information about the constant pool of a class file. */ private static class ConstantPool { /** * list of constant pool entries, in constant pool index order. * * This list is used when writing the constant pool to a stream and for assigning the next index value. Note * that element 0 of this list corresponds to constant pool index 1. */ private List<Entry> pool = new ArrayList<>(32); /** * maps constant pool data of all types to constant pool indexes. * * This map is used to look up the index of an existing entry for values of all types. */ private Map<Object, Short> map = new HashMap<>(16); /** true if no new constant pool entries may be added */ private boolean readOnly = false; /** * Get or assign the index for a CONSTANT_Utf8 entry. */ public short getUtf8(String s) { if (s == null) { throw new NullPointerException(); } return getValue(s); } // /** // * Get or assign the index for a CONSTANT_Integer entry. // */ // public short getInteger(int i) { // return getValue(new Integer(i)); // } // // /** // * Get or assign the index for a CONSTANT_Float entry. // */ // public short getFloat(float f) { // return getValue(new Float(f)); // } /** * Get or assign the index for a CONSTANT_Class entry. */ public short getClass(String name) { short utf8Index = getUtf8(name); return getIndirect(new IndirectEntry(CONSTANT_CLASS, utf8Index)); } /** * Get or assign the index for a CONSTANT_String entry. */ public short getString(String s) { short utf8Index = getUtf8(s); return getIndirect(new IndirectEntry(CONSTANT_STRING, utf8Index)); } /** * Get or assign the index for a CONSTANT_FieldRef entry. */ public short getFieldRef(String className, String name, String descriptor) { short classIndex = getClass(className); short nameAndTypeIndex = getNameAndType(name, descriptor); return getIndirect(new IndirectEntry(CONSTANT_FIELD, classIndex, nameAndTypeIndex)); } /** * Get or assign the index for a CONSTANT_MethodRef entry. */ public short getMethodRef(String className, String name, String descriptor) { short classIndex = getClass(className); short nameAndTypeIndex = getNameAndType(name, descriptor); return getIndirect(new IndirectEntry(CONSTANT_METHOD, classIndex, nameAndTypeIndex)); } /** * Get or assign the index for a CONSTANT_InterfaceMethodRef entry. */ public short getInterfaceMethodRef(String className, String name, String descriptor) { short classIndex = getClass(className); short nameAndTypeIndex = getNameAndType(name, descriptor); return getIndirect(new IndirectEntry(CONSTANT_INTERFACEMETHOD, classIndex, nameAndTypeIndex)); } /** * Get or assign the index for a CONSTANT_NameAndType entry. */ public short getNameAndType(String name, String descriptor) { short nameIndex = getUtf8(name); short descriptorIndex = getUtf8(descriptor); return getIndirect(new IndirectEntry(CONSTANT_NAMEANDTYPE, nameIndex, descriptorIndex)); } /** * Set this ConstantPool instance to be "read only". * * After this method has been called, further requests to get an index for a non-existent entry will cause an * InternalError to be thrown instead of creating of the entry. */ public void setReadOnly() { readOnly = true; } /** * Write this constant pool to a stream as part of the class file format. * * This consists of writing the "constant_pool_count" and "constant_pool[]" items of the "ClassFile" structure, * as described in JVMS section 4.1. */ public void write(OutputStream out) throws IOException { DataOutputStream dataOut = new DataOutputStream(out); // constant_pool_count: number of entries plus one dataOut.writeShort(pool.size() + 1); for (Entry e : pool) { e.write(dataOut); } } /** * Add a new constant pool entry and return its index. */ private short addEntry(Entry entry) { pool.add(entry); /* * Note that this way of determining the index of the added entry is wrong if this pool supports * CONSTANT_Long or CONSTANT_Double entries. */ if (pool.size() >= 65535) { throw new IllegalArgumentException("constant pool size limit exceeded"); } return (short) pool.size(); } /** * Get or assign the index for an entry of a type that contains a direct value. The type of the given object * determines the type of the desired entry as follows: * * java.lang.String CONSTANT_Utf8 java.lang.Integer CONSTANT_Integer java.lang.Float CONSTANT_Float * java.lang.Long CONSTANT_Long java.lang.Double CONSTANT_DOUBLE */ private short getValue(Object key) { Short index = map.get(key); if (index != null) { return index.shortValue(); } else { if (readOnly) { throw new InternalError("late constant pool addition: " + key); } short i = addEntry(new ValueEntry(key)); map.put(key, new Short(i)); return i; } } /** * Get or assign the index for an entry of a type that contains references to other constant pool entries. */ private short getIndirect(IndirectEntry e) { Short index = map.get(e); if (index != null) { return index.shortValue(); } else { if (readOnly) { throw new InternalError("late constant pool addition"); } short i = addEntry(e); map.put(e, new Short(i)); return i; } } /** * Entry is the abstact superclass of all constant pool entry types that can be stored in the "pool" list; its * purpose is to define a common method for writing constant pool entries to a class file. */ private static abstract class Entry { public abstract void write(DataOutputStream out) throws IOException; } /** * ValueEntry represents a constant pool entry of a type that contains a direct value (see the comments for the * "getValue" method for a list of such types). * * ValueEntry objects are not used as keys for their entries in the Map "map", so no useful hashCode or equals * methods are defined. */ private static class ValueEntry extends Entry { private Object value; public ValueEntry(Object value) { this.value = value; } public void write(DataOutputStream out) throws IOException { if (value instanceof String) { out.writeByte(CONSTANT_UTF8); out.writeUTF((String) value); } else if (value instanceof Integer) { out.writeByte(CONSTANT_INTEGER); out.writeInt(((Integer) value).intValue()); } else if (value instanceof Float) { out.writeByte(CONSTANT_FLOAT); out.writeFloat(((Float) value).floatValue()); } else if (value instanceof Long) { out.writeByte(CONSTANT_LONG); out.writeLong(((Long) value).longValue()); } else if (value instanceof Double) { out.writeDouble(CONSTANT_DOUBLE); out.writeDouble(((Double) value).doubleValue()); } else { throw new InternalError("bogus value entry: " + value); } } } /** * IndirectEntry represents a constant pool entry of a type that references other constant pool entries, i.e., * the following types: * * CONSTANT_Class, CONSTANT_String, CONSTANT_Fieldref, CONSTANT_Methodref, CONSTANT_InterfaceMethodref, and * CONSTANT_NameAndType. * * Each of these entry types contains either one or two indexes of other constant pool entries. * * IndirectEntry objects are used as the keys for their entries in the Map "map", so the hashCode and equals * methods are overridden to allow matching. */ private static class IndirectEntry extends Entry { private int tag; private short index0; private short index1; /** * Construct an IndirectEntry for a constant pool entry type that contains one index of another entry. */ public IndirectEntry(int tag, short index) { this.tag = tag; this.index0 = index; this.index1 = 0; } /** * Construct an IndirectEntry for a constant pool entry type that contains two indexes for other entries. */ public IndirectEntry(int tag, short index0, short index1) { this.tag = tag; this.index0 = index0; this.index1 = index1; } public void write(DataOutputStream out) throws IOException { out.writeByte(tag); out.writeShort(index0); /* * If this entry type contains two indexes, write out the second, too. */ if (tag == CONSTANT_FIELD || tag == CONSTANT_METHOD || tag == CONSTANT_INTERFACEMETHOD || tag == CONSTANT_NAMEANDTYPE) { out.writeShort(index1); } } public int hashCode() { return tag + index0 + index1; } public boolean equals(Object obj) { if (obj instanceof IndirectEntry) { IndirectEntry other = (IndirectEntry) obj; if (tag == other.tag && index0 == other.index0 && index1 == other.index1) { return true; } } return false; } } } }