Commit 0e3dd41eb133563ef7cc5c1d1a2fedde47eb4142

Authored by Mumfrey
1 parent d28d5dde

adding javadoc to AccessorTransformer, throw an exception from un-annotated methods

java/common/com/mumfrey/liteloader/transformers/access/AccessorTransformer.java
... ... @@ -7,7 +7,6 @@ import java.util.List;
7 7  
8 8 import net.minecraft.launchwrapper.Launch;
9 9  
10   -import org.objectweb.asm.ClassReader;
11 10 import org.objectweb.asm.Opcodes;
12 11 import org.objectweb.asm.Type;
13 12 import org.objectweb.asm.tree.AnnotationNode;
... ... @@ -15,8 +14,10 @@ import org.objectweb.asm.tree.ClassNode;
15 14 import org.objectweb.asm.tree.FieldInsnNode;
16 15 import org.objectweb.asm.tree.FieldNode;
17 16 import org.objectweb.asm.tree.InsnNode;
  17 +import org.objectweb.asm.tree.LdcInsnNode;
18 18 import org.objectweb.asm.tree.MethodInsnNode;
19 19 import org.objectweb.asm.tree.MethodNode;
  20 +import org.objectweb.asm.tree.TypeInsnNode;
20 21 import org.objectweb.asm.tree.VarInsnNode;
21 22  
22 23 import com.mumfrey.liteloader.core.runtime.Obf;
... ... @@ -26,7 +27,7 @@ import com.mumfrey.liteloader.transformers.ObfProvider;
26 27 import com.mumfrey.liteloader.util.log.LiteLoaderLogger;
27 28  
28 29 /**
29   - * Transformer which can inject accessor methods into a target class
  30 + * Transformer which can inject accessor methods defined by an annotated interface into a target class
30 31 *
31 32 * @author Adam Mummery-Smith
32 33 */
... ... @@ -59,34 +60,47 @@ public abstract class AccessorTransformer extends ClassTransformer
59 60 */
60 61 private final Obf target;
61 62  
  63 + /**
  64 + * Create a new new accessor using the specified template interface
  65 + *
  66 + * @param iface Template interface
  67 + * @throws IOException Thrown if an problem occurs when loading the interface bytecode
  68 + */
62 69 protected AccessorInjection(String iface) throws IOException
63 70 {
64 71 this(iface, null);
65 72 }
66 73  
  74 + /**
  75 + * Create a new new accessor using the specified template interface
  76 + *
  77 + * @param iface Template interface
  78 + * @param obfProvider Obfuscation provider for this context
  79 + * @throws IOException Thrown if an problem occurs when loading the interface bytecode
  80 + */
67 81 protected AccessorInjection(String iface, ObfProvider obfProvider) throws IOException
68 82 {
69   - ClassNode ifaceNode = this.loadClass(iface);
  83 + ClassNode ifaceNode = ByteCodeUtilities.loadClass(iface, false);
  84 +
  85 + if (ifaceNode.interfaces.size() > 0)
  86 + {
  87 + String interfaceList = ifaceNode.interfaces.toString().replace('/', '.');
  88 + throw new RuntimeException("Accessor interface must not extend other interfaces. Found " + interfaceList + " in " + iface);
  89 + }
  90 +
70 91 this.table = this.setupTable(ifaceNode);
71 92 this.target = this.setupTarget(ifaceNode);
72 93 this.iface = iface;
73 94 this.obfProvider = obfProvider;
74 95 }
75   -
76   - private ClassNode loadClass(String iface) throws IOException
77   - {
78   - byte[] bytes = this.getClassBytes(iface);
79   - ClassReader classReader = new ClassReader(bytes);
80   - ClassNode classNode = new ClassNode();
81   - classReader.accept(classNode, 0);
82   - return classNode;
83   - }
84   -
85   - private byte[] getClassBytes(String iface) throws IOException
86   - {
87   - return Launch.classLoader.getClassBytes(iface);
88   - }
89 96  
  97 + /**
  98 + * Get an obfuscation table mapping by name, first uses any supplied context provider, then any obfuscation table
  99 + * class specified by an {@link ObfTableClass} annotation on the interface itself, and fails over onto the LiteLoader
  100 + * obfuscation table. If the entry is not matched in any of the above locations then an exception is thrown
  101 + *
  102 + * @param name Obfuscation table entry to fetch
  103 + */
90 104 private Obf getObf(String name)
91 105 {
92 106 if (this.obfProvider != null)
... ... @@ -107,11 +121,18 @@ public abstract class AccessorTransformer extends ClassTransformer
107 121 throw new RuntimeException("No obfuscation table entry could be found for '" + name + "'");
108 122 }
109 123  
  124 + /**
  125 + * Get the target class of this injection
  126 + */
110 127 protected Obf getTarget()
111 128 {
112 129 return this.target;
113 130 }
114 131  
  132 + /**
  133 + * Inspects the target class for an {@link ObfTableClass} annotation and attempts to get a handle for the class
  134 + * specified. On failure, the LiteLoader {@link Obf} is returned.
  135 + */
115 136 @SuppressWarnings("unchecked")
116 137 private Class<? extends Obf> setupTable(ClassNode ifaceNode)
117 138 {
... ... @@ -132,12 +153,31 @@ public abstract class AccessorTransformer extends ClassTransformer
132 153 return Obf.class;
133 154 }
134 155  
  156 + /**
  157 + * Locates the {@link Accessor} annotation on the interface in order to determine the target class
  158 + */
135 159 private Obf setupTarget(ClassNode ifaceNode)
136 160 {
137 161 AnnotationNode annotation = ByteCodeUtilities.getInvisibleAnnotation(ifaceNode, Accessor.class);
138   - return this.getObf(ByteCodeUtilities.<String>getAnnotationValue(annotation));
  162 + if (annotation == null)
  163 + {
  164 + throw new RuntimeException("Accessor interfaces must be annotated with an @Accessor annotation specifying the target class");
  165 + }
  166 +
  167 + String targetClass = ByteCodeUtilities.<String>getAnnotationValue(annotation);
  168 + if (targetClass == null || targetClass.isEmpty())
  169 + {
  170 + throw new RuntimeException("Invalid @Accessor annotation, the annotation must specify a target class");
  171 + }
  172 +
  173 + return this.getObf(targetClass);
139 174 }
140 175  
  176 + /**
  177 + * Apply this injection to the specified target ClassNode
  178 + *
  179 + * @param classNode Class tree to apply to
  180 + */
141 181 protected void apply(ClassNode classNode)
142 182 {
143 183 String ifaceRef = this.iface.replace('.', '/');
... ... @@ -166,6 +206,12 @@ public abstract class AccessorTransformer extends ClassTransformer
166 206 }
167 207 }
168 208  
  209 + /**
  210 + * Add a method from the interface to the target class
  211 + *
  212 + * @param classNode Target class
  213 + * @param method Method to add
  214 + */
169 215 private void addMethod(ClassNode classNode, MethodNode method)
170 216 {
171 217 if (!this.addMethodToClass(classNode, method))
... ... @@ -176,26 +222,39 @@ public abstract class AccessorTransformer extends ClassTransformer
176 222  
177 223 LiteLoaderLogger.debug("[AccessorTransformer] Attempting to add %s to %s", method.name, classNode.name);
178 224  
  225 + String targetId = null;
179 226 AnnotationNode accessor = ByteCodeUtilities.getInvisibleAnnotation(method, Accessor.class);
180 227 AnnotationNode invoker = ByteCodeUtilities.getInvisibleAnnotation(method, Invoker.class);
181 228 if (accessor != null)
182 229 {
183   - Obf targetName = this.getObf(ByteCodeUtilities.<String>getAnnotationValue(accessor));
  230 + targetId = ByteCodeUtilities.<String>getAnnotationValue(accessor);
  231 + Obf targetName = this.getObf(targetId);
184 232 if (this.injectAccessor(classNode, method, targetName)) return;
185 233 }
186 234 else if (invoker != null)
187 235 {
188   - Obf targetName = this.getObf(ByteCodeUtilities.<String>getAnnotationValue(invoker));
  236 + targetId = ByteCodeUtilities.<String>getAnnotationValue(invoker);
  237 + Obf targetName = this.getObf(targetId);
189 238 if (this.injectInvoker(classNode, method, targetName)) return;
190 239 }
191 240 else
192 241 {
193 242 LiteLoaderLogger.severe("[AccessorTransformer] Method %s for %s has no @Accessor or @Invoker annotation, the method will be ABSTRACT!", method.name, this.iface);
  243 + this.injectException(classNode, method, "No @Accessor or @Invoker annotation on method");
  244 + return;
194 245 }
195 246  
196 247 LiteLoaderLogger.severe("[AccessorTransformer] Method %s for %s could not locate target member, the method will be ABSTRACT!", method.name, this.iface);
  248 + this.injectException(classNode, method, "Could not locate target class member '" + targetId + "'");
197 249 }
198 250  
  251 + /**
  252 + * Inject an accessor method into the target class
  253 + *
  254 + * @param classNode
  255 + * @param method
  256 + * @param targetName
  257 + */
199 258 private boolean injectAccessor(ClassNode classNode, MethodNode method, Obf targetName)
200 259 {
201 260 FieldNode targetField = this.findField(classNode, targetName);
... ... @@ -217,6 +276,13 @@ public abstract class AccessorTransformer extends ClassTransformer
217 276 return false;
218 277 }
219 278  
  279 + /**
  280 + * Inject an invoke (proxy) method into the target class
  281 + *
  282 + * @param classNode
  283 + * @param method
  284 + * @param targetName
  285 + */
220 286 private boolean injectInvoker(ClassNode classNode, MethodNode method, Obf targetName)
221 287 {
222 288 MethodNode targetMethod = this.findMethod(classNode, targetName, method.desc);
... ... @@ -230,6 +296,13 @@ public abstract class AccessorTransformer extends ClassTransformer
230 296 return false;
231 297 }
232 298  
  299 + /**
  300 + * Populate the bytecode instructions for a getter accessor
  301 + *
  302 + * @param classNode
  303 + * @param method
  304 + * @param field
  305 + */
233 306 private void populateGetter(ClassNode classNode, MethodNode method, FieldNode field)
234 307 {
235 308 Type returnType = Type.getReturnType(method.desc);
... ... @@ -238,24 +311,32 @@ public abstract class AccessorTransformer extends ClassTransformer
238 311 {
239 312 throw new RuntimeException("Incompatible types! Field type: " + fieldType + " Method type: " + returnType);
240 313 }
  314 + boolean isStatic = (field.access & Opcodes.ACC_STATIC) != 0;
241 315  
242 316 method.instructions.clear();
243 317 method.maxLocals = ByteCodeUtilities.getFirstNonArgLocalIndex(method);
244 318 method.maxStack = fieldType.getSize();
245 319  
246   - if ((field.access & Opcodes.ACC_STATIC) == 0)
  320 + if (isStatic)
247 321 {
248   - method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
249   - method.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, field.name, field.desc));
  322 + method.instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, field.name, field.desc));
250 323 }
251 324 else
252 325 {
253   - method.instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, classNode.name, field.name, field.desc));
  326 + method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
  327 + method.instructions.add(new FieldInsnNode(Opcodes.GETFIELD, classNode.name, field.name, field.desc));
254 328 }
255 329  
256 330 method.instructions.add(new InsnNode(returnType.getOpcode(Opcodes.IRETURN)));
257 331 }
258 332  
  333 + /**
  334 + * Populate the bytecode instructions for a setter
  335 + *
  336 + * @param classNode
  337 + * @param method
  338 + * @param field
  339 + */
259 340 private void populateSetter(ClassNode classNode, MethodNode method, FieldNode field)
260 341 {
261 342 Type[] argTypes = Type.getArgumentTypes(method.desc);
... ... @@ -269,26 +350,34 @@ public abstract class AccessorTransformer extends ClassTransformer
269 350 {
270 351 throw new RuntimeException("Incompatible types! Field type: " + fieldType + " Method type: " + argType);
271 352 }
  353 + boolean isStatic = (field.access & Opcodes.ACC_STATIC) != 0;
272 354  
273 355 method.instructions.clear();
274 356 method.maxLocals = ByteCodeUtilities.getFirstNonArgLocalIndex(method);
275 357 method.maxStack = fieldType.getSize();
276 358  
277   - if ((field.access & Opcodes.ACC_STATIC) == 0)
  359 + if (isStatic)
278 360 {
279   - method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
280   - method.instructions.add(new VarInsnNode(argType.getOpcode(Opcodes.ILOAD), 1));
281   - method.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, field.name, field.desc));
  361 + method.instructions.add(new VarInsnNode(argType.getOpcode(Opcodes.ILOAD), 0));
  362 + method.instructions.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, field.name, field.desc));
282 363 }
283 364 else
284 365 {
285   - method.instructions.add(new VarInsnNode(argType.getOpcode(Opcodes.ILOAD), 0));
286   - method.instructions.add(new FieldInsnNode(Opcodes.PUTSTATIC, classNode.name, field.name, field.desc));
  366 + method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
  367 + method.instructions.add(new VarInsnNode(argType.getOpcode(Opcodes.ILOAD), 1));
  368 + method.instructions.add(new FieldInsnNode(Opcodes.PUTFIELD, classNode.name, field.name, field.desc));
287 369 }
288 370  
289 371 method.instructions.add(new InsnNode(Opcodes.RETURN));
290 372 }
291 373  
  374 + /**
  375 + * Populate the bytecode instructions for an invoker (proxy) method
  376 + *
  377 + * @param classNode
  378 + * @param method
  379 + * @param targetMethod
  380 + */
292 381 private void populateInvoker(ClassNode classNode, MethodNode method, MethodNode targetMethod)
293 382 {
294 383 Type[] args = Type.getArgumentTypes(targetMethod.desc);
... ... @@ -313,6 +402,31 @@ public abstract class AccessorTransformer extends ClassTransformer
313 402 method.instructions.add(new InsnNode(returnType.getOpcode(Opcodes.IRETURN)));
314 403 }
315 404  
  405 + /**
  406 + * Populate bytecode instructions for a method which throws an exception
  407 + *
  408 + * @param classNode
  409 + * @param method
  410 + * @param message
  411 + */
  412 + private void injectException(ClassNode classNode, MethodNode method, String message)
  413 + {
  414 + method.instructions.clear();
  415 + method.maxStack = 2;
  416 +
  417 + method.instructions.add(new TypeInsnNode(Opcodes.NEW, "java/lang/RuntimeException"));
  418 + method.instructions.add(new InsnNode(Opcodes.DUP));
  419 + method.instructions.add(new LdcInsnNode(message));
  420 + method.instructions.add(new MethodInsnNode(Opcodes.INVOKESPECIAL, "java/lang/RuntimeException", "<init>", "(Ljava/lang/String;)V", false));
  421 + method.instructions.add(new InsnNode(Opcodes.ATHROW));
  422 + }
  423 +
  424 + /**
  425 + * Find a field in the target class which matches the specified field name
  426 + *
  427 + * @param classNode
  428 + * @param fieldName
  429 + */
316 430 private FieldNode findField(ClassNode classNode, Obf fieldName)
317 431 {
318 432 for (FieldNode field : classNode.fields)
... ... @@ -324,6 +438,13 @@ public abstract class AccessorTransformer extends ClassTransformer
324 438 return null;
325 439 }
326 440  
  441 + /**
  442 + * Find a method in the target class which matches the specified method name and descriptor
  443 + *
  444 + * @param classNode
  445 + * @param methodName
  446 + * @param desc
  447 + */
327 448 private MethodNode findMethod(ClassNode classNode, Obf methodName, String desc)
328 449 {
329 450 for (MethodNode method : classNode.methods)
... ... @@ -335,6 +456,12 @@ public abstract class AccessorTransformer extends ClassTransformer
335 456 return null;
336 457 }
337 458  
  459 + /**
  460 + * Add a method from the template interface to the target class
  461 + *
  462 + * @param classNode
  463 + * @param method
  464 + */
338 465 private boolean addMethodToClass(ClassNode classNode, MethodNode method)
339 466 {
340 467 MethodNode existingMethod = ByteCodeUtilities.findTargetMethod(classNode, method);
... ... @@ -345,18 +472,33 @@ public abstract class AccessorTransformer extends ClassTransformer
345 472 }
346 473 }
347 474  
  475 + /**
  476 + * List of accessors to inject
  477 + */
348 478 private final List<AccessorInjection> accessors = new ArrayList<AccessorInjection>();
349 479  
  480 + /**
  481 + * ctor
  482 + */
350 483 public AccessorTransformer()
351 484 {
352 485 this.addAccessors();
353 486 }
354 487  
  488 + /**
  489 + * @param interfaceName
  490 + */
355 491 public void addAccessor(String interfaceName)
356 492 {
357 493 this.addAccessor(interfaceName, null);
358 494 }
359 495  
  496 + /**
  497 + * Add an accessor to the accessors list
  498 + *
  499 + * @param interfaceName
  500 + * @param obfProvider
  501 + */
360 502 public void addAccessor(String interfaceName, ObfProvider obfProvider)
361 503 {
362 504 try
... ... @@ -369,6 +511,9 @@ public abstract class AccessorTransformer extends ClassTransformer
369 511 }
370 512 }
371 513  
  514 + /* (non-Javadoc)
  515 + * @see net.minecraft.launchwrapper.IClassTransformer#transform(java.lang.String, java.lang.String, byte[])
  516 + */
372 517 @Override
373 518 public byte[] transform(String name, String transformedName, byte[] basicClass)
374 519 {
... ... @@ -385,6 +530,16 @@ public abstract class AccessorTransformer extends ClassTransformer
385 530 return basicClass;
386 531 }
387 532  
  533 + /**
  534 + * Apply this transformer, used when this transformer is acting as a delegate via another transformer
  535 + * (eg. an EventTransformer) and the parent transformer already has a ClassNode for the target class.
  536 + *
  537 + * @param name
  538 + * @param transformedName
  539 + * @param basicClass
  540 + * @param classNode
  541 + * @return
  542 + */
388 543 public ClassNode apply(String name, String transformedName, byte[] basicClass, ClassNode classNode)
389 544 {
390 545 for (Iterator<AccessorInjection> iter = this.accessors.iterator(); iter.hasNext(); )
... ... @@ -403,10 +558,20 @@ public abstract class AccessorTransformer extends ClassTransformer
403 558 return classNode;
404 559 }
405 560  
  561 + /**
  562 + * Subclasses should add their accessors here
  563 + */
406 564 protected void addAccessors()
407 565 {
408 566 }
409 567  
  568 + /**
  569 + * Called after transformation is applied, allows custom transforms to be performed by subclasses
  570 + *
  571 + * @param name
  572 + * @param transformedName
  573 + * @param classNode
  574 + */
410 575 protected void postTransform(String name, String transformedName, ClassNode classNode)
411 576 {
412 577 }
... ...