|
| 1 | +package de.oceanlabs.mcp.mcinjector.adaptors; |
| 2 | + |
| 3 | +import static org.objectweb.asm.Opcodes.*; |
| 4 | + |
| 5 | +import java.util.Arrays; |
| 6 | +import java.util.List; |
| 7 | +import java.util.logging.Logger; |
| 8 | + |
| 9 | +import org.objectweb.asm.ClassVisitor; |
| 10 | +import org.objectweb.asm.Opcodes; |
| 11 | +import org.objectweb.asm.Type; |
| 12 | +import org.objectweb.asm.tree.AnnotationNode; |
| 13 | +import org.objectweb.asm.tree.ClassNode; |
| 14 | +import org.objectweb.asm.tree.InnerClassNode; |
| 15 | +import org.objectweb.asm.tree.MethodNode; |
| 16 | + |
| 17 | +import de.oceanlabs.mcp.mcinjector.MCInjectorImpl; |
| 18 | + |
| 19 | +public class ParameterAnnotationFixer extends ClassVisitor |
| 20 | +{ |
| 21 | + private static final Logger LOGGER = Logger.getLogger("MCInjector"); |
| 22 | + |
| 23 | + public ParameterAnnotationFixer(ClassVisitor cn, MCInjectorImpl mci) |
| 24 | + { |
| 25 | + super(Opcodes.ASM6, cn); |
| 26 | + // Extra version check, since these were added in ASM 6.1 and there |
| 27 | + // isn't a constant for it |
| 28 | + try { |
| 29 | + MethodNode.class.getField("visibleAnnotableParameterCount"); |
| 30 | + MethodNode.class.getField("invisibleAnnotableParameterCount"); |
| 31 | + } catch (Exception ex) { |
| 32 | + throw new IllegalArgumentException( |
| 33 | + "AnnotableParameterCount fields are not present -- wrong ASM version?", |
| 34 | + ex); |
| 35 | + } |
| 36 | + } |
| 37 | + |
| 38 | + @Override |
| 39 | + public void visitEnd() |
| 40 | + { |
| 41 | + super.visitEnd(); |
| 42 | + |
| 43 | + ClassNode cls = MCInjectorImpl.getClassNode(cv); |
| 44 | + Type[] syntheticParams = getExpectedSyntheticParams(cls); |
| 45 | + if (syntheticParams != null) |
| 46 | + { |
| 47 | + for (MethodNode mn : cls.methods) |
| 48 | + { |
| 49 | + if (mn.name.equals("<init>")) |
| 50 | + { |
| 51 | + processConstructor(cls, mn, syntheticParams); |
| 52 | + } |
| 53 | + } |
| 54 | + } |
| 55 | + } |
| 56 | + |
| 57 | + /** |
| 58 | + * Checks if the given class might have synthetic parameters in the |
| 59 | + * constructor. There are two cases where this might happen: |
| 60 | + * <ol> |
| 61 | + * <li>If the given class is an inner class, the first parameter is the |
| 62 | + * instance of the outer class.</li> |
| 63 | + * <li>If the given class is an enum, the first parameter is the enum |
| 64 | + * constant name and the second parameter is its ordinal.</li> |
| 65 | + * </ol> |
| 66 | + * |
| 67 | + * @return An array of types for synthetic parameters if the class can have |
| 68 | + * synthetic parameters, otherwise null. |
| 69 | + */ |
| 70 | + private Type[] getExpectedSyntheticParams(ClassNode cls) |
| 71 | + { |
| 72 | + // Check for enum |
| 73 | + // http://hg.openjdk.java.net/jdk8/jdk8/langtools/file/1ff9d5118aae/src/share/classes/com/sun/tools/javac/comp/Lower.java#l2866 |
| 74 | + if ((cls.access & ACC_ENUM) != 0) |
| 75 | + { |
| 76 | + LOGGER.fine(" Considering " + cls.name |
| 77 | + + " for extra parameter annotations as it is an enum"); |
| 78 | + return new Type[] { Type.getObjectType("java/lang/String"), Type.INT_TYPE }; |
| 79 | + } |
| 80 | + |
| 81 | + // Check for inner class |
| 82 | + InnerClassNode info = null; |
| 83 | + for (InnerClassNode node : cls.innerClasses) // note: cls.innerClasses is never null |
| 84 | + { |
| 85 | + if (node.name.equals(cls.name)) |
| 86 | + { |
| 87 | + info = node; |
| 88 | + break; |
| 89 | + } |
| 90 | + } |
| 91 | + // http://hg.openjdk.java.net/jdk8/jdk8/langtools/file/1ff9d5118aae/src/share/classes/com/sun/tools/javac/code/Symbol.java#l398 |
| 92 | + if (info == null) |
| 93 | + { |
| 94 | + LOGGER.fine(" Not considering " + cls.name |
| 95 | + + " for extra parameter annotations as it is not an inner class"); |
| 96 | + return null; // It's not an inner class |
| 97 | + } |
| 98 | + if ((info.access & (ACC_STATIC | ACC_INTERFACE)) != 0) |
| 99 | + { |
| 100 | + LOGGER.fine(" Not considering " + cls.name |
| 101 | + + " for extra parameter annotations as is an interface or static"); |
| 102 | + return null; // It's static or can't have a constructor |
| 103 | + } |
| 104 | + |
| 105 | + // http://hg.openjdk.java.net/jdk8/jdk8/langtools/file/1ff9d5118aae/src/share/classes/com/sun/tools/javac/jvm/ClassReader.java#l2011 |
| 106 | + if (info.innerName == null) |
| 107 | + { |
| 108 | + LOGGER.fine(" Not considering " + cls.name |
| 109 | + + " for extra parameter annotations as it is annonymous"); |
| 110 | + return null; // It's an anonymous class |
| 111 | + } |
| 112 | + |
| 113 | + LOGGER.fine(" Considering " + cls.name |
| 114 | + + " for extra parameter annotations as it is an inner class of " |
| 115 | + + info.outerName); |
| 116 | + |
| 117 | + return new Type[] { Type.getObjectType(info.outerName) }; |
| 118 | + } |
| 119 | + |
| 120 | + /** |
| 121 | + * Removes the parameter annotations for the given synthetic parameters, |
| 122 | + * if there are parameter annotations and the synthetic parameters exist. |
| 123 | + */ |
| 124 | + private void processConstructor(ClassNode cls, MethodNode mn, Type[] syntheticParams) { |
| 125 | + String methodInfo = mn.name + mn.desc + " in " + cls.name; |
| 126 | + Type[] params = Type.getArgumentTypes(mn.desc); |
| 127 | + |
| 128 | + if (beginsWith(params, syntheticParams)) |
| 129 | + { |
| 130 | + mn.visibleParameterAnnotations = process(methodInfo, |
| 131 | + "RuntimeVisibleParameterAnnotations", params.length, |
| 132 | + syntheticParams.length, mn.visibleParameterAnnotations); |
| 133 | + mn.invisibleParameterAnnotations = process(methodInfo, |
| 134 | + "RuntimeInvisibleParameterAnnotations", params.length, |
| 135 | + syntheticParams.length, mn.invisibleParameterAnnotations); |
| 136 | + // ASM uses this value, not the length of the array |
| 137 | + // Note that this was added in ASM 6.1 |
| 138 | + if (mn.visibleParameterAnnotations != null) |
| 139 | + { |
| 140 | + mn.visibleAnnotableParameterCount = mn.visibleParameterAnnotations.length; |
| 141 | + } |
| 142 | + if (mn.invisibleParameterAnnotations != null) |
| 143 | + { |
| 144 | + mn.invisibleAnnotableParameterCount = mn.invisibleParameterAnnotations.length; |
| 145 | + } |
| 146 | + } |
| 147 | + else |
| 148 | + { |
| 149 | + LOGGER.warning("Unexpected lack of synthetic args to the constructor: expected " |
| 150 | + + Arrays.toString(syntheticParams) + " at the start of " + methodInfo); |
| 151 | + } |
| 152 | + } |
| 153 | + |
| 154 | + private boolean beginsWith(Type[] values, Type[] prefix) |
| 155 | + { |
| 156 | + if (values.length < prefix.length) |
| 157 | + { |
| 158 | + return false; |
| 159 | + } |
| 160 | + for (int i = 0; i < prefix.length; i++) |
| 161 | + { |
| 162 | + if (!values[i].equals(prefix[i])) |
| 163 | + { |
| 164 | + return false; |
| 165 | + } |
| 166 | + } |
| 167 | + return true; |
| 168 | + } |
| 169 | + |
| 170 | + /** |
| 171 | + * Removes annotation nodes corresponding to synthetic parameters, after |
| 172 | + * the existence of synthetic parameters has already been checked. |
| 173 | + * |
| 174 | + * @param methodInfo |
| 175 | + * A description of the method, for logging |
| 176 | + * @param attributeName |
| 177 | + * The name of the attribute, for logging |
| 178 | + * @param numParams |
| 179 | + * The number of parameters in the method |
| 180 | + * @param numSynthetic |
| 181 | + * The number of synthetic parameters (should not be 0) |
| 182 | + * @param annotations |
| 183 | + * The current array of annotation nodes, may be null |
| 184 | + * @return The new array of annotation nodes, may be null |
| 185 | + */ |
| 186 | + private List<AnnotationNode>[] process(String methodInfo, |
| 187 | + String attributeName, int numParams, int numSynthetic, |
| 188 | + List<AnnotationNode>[] annotations) |
| 189 | + { |
| 190 | + if (annotations == null) |
| 191 | + { |
| 192 | + LOGGER.finer(" " + methodInfo + " does not have a " |
| 193 | + + attributeName + " attribute"); |
| 194 | + return null; |
| 195 | + } |
| 196 | + |
| 197 | + int numAnnotations = annotations.length; |
| 198 | + if (numParams == numAnnotations) |
| 199 | + { |
| 200 | + LOGGER.info("Found extra " + attributeName + " entries in " |
| 201 | + + methodInfo + ": removing " + numSynthetic); |
| 202 | + return Arrays.copyOfRange(annotations, numSynthetic, |
| 203 | + numAnnotations); |
| 204 | + } |
| 205 | + else if (numParams == numAnnotations - numSynthetic) |
| 206 | + { |
| 207 | + LOGGER.info("Number of " + attributeName + " entries in " |
| 208 | + + methodInfo + " is already as we want"); |
| 209 | + return annotations; |
| 210 | + } |
| 211 | + else |
| 212 | + { |
| 213 | + LOGGER.warning("Unexpected number of " + attributeName |
| 214 | + + " entries in " + methodInfo + ": " + numAnnotations); |
| 215 | + return annotations; |
| 216 | + } |
| 217 | + } |
| 218 | +} |
0 commit comments