Commit 0e3dd41eb133563ef7cc5c1d1a2fedde47eb4142
1 parent
d28d5dde
adding javadoc to AccessorTransformer, throw an exception from un-annotated methods
Showing
1 changed file
with
195 additions
and
30 deletions
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 | } | ... | ... |