From 7b37a681ee237bcb2c8b9ed30e01494445055cb0 Mon Sep 17 00:00:00 2001
From: Mumfrey This annotation has two usages applicable to Callback Injectors (defined
* using {@link Inject @Inject}. For local capture injectors, it indicates
* that the injector should coerce top-level primitive types (int) to covariant
- * types defined on the handler. For other injectors, it can be used on a
- * reference type to indicate that the intended type is covariant over the
- * argument type (or that the argument type is contravariant on the target class
- * type). This can be used for multi-target injectors with a bounded type
- * argument on the class or target method.
During LVT generation it is not always possible to inflect the exact local + *
@Coerce also has an important usage with primitive types. It + * is not always possible during LVT generation to determine the exact local * type for types represented internally as integers, for example booleans and * shorts. However adding a surrogate for these cases is overkill when the type * is known for certain by the injector. Since the bytecode for all types stored @@ -56,8 +59,15 @@ * indicate that an incoming parameter can be consumed as a valid supertype, up * to and including {@link Object}. This is particularly useful when an argument * on the target method invocation is inaccessible or unknown.
+ * + *Since Mixin 0.8 it is also possible to use @Coerce + * on the return type of a field gettter or method invocation + * redirector. To do so simply annotate the method itself. Note that doing so + * will cast the return type back to the original return type as part of the + * injection, so the object must be of an appropriate type regardless of + * visibility.
*/ -@Target({ ElementType.PARAMETER }) +@Target({ ElementType.PARAMETER, ElementType.METHOD }) public @interface Coerce { } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/Constant.java b/src/main/java/org/spongepowered/asm/mixin/injection/Constant.java index c8764dab0..83da5fdc5 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/Constant.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/Constant.java @@ -26,6 +26,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.objectweb.asm.Opcodes; @@ -39,6 +40,7 @@ * appropriate argument. Specifying values of different types will cause an * error to be raised by the injector. */ +@Target({ /* No targets allowed */ }) @Retention(RetentionPolicy.RUNTIME) public @interface Constant { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/Inject.java b/src/main/java/org/spongepowered/asm/mixin/injection/Inject.java index 3e11f64b2..1fcaf47a7 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/Inject.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/Inject.java @@ -31,6 +31,7 @@ import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector; import org.spongepowered.asm.mixin.injection.throwables.InjectionError; @@ -41,6 +42,65 @@ * Specifies that this mixin method should inject a callback (or * callbacks) to itself in the target method(s) identified by * {@link #method}. + * + *Callbacks are simple injectors which simply inject a call to the decorated + * method (the handler) in the target method (or methods) + * selected by the selectors specified in {@link #method}. Callback Injectors + * can also capture arguments and local variables from the target for + * use in the handler.
+ * + *Callback handler methods should always return void and should + * have the same static-ness as their target (though it is allowable to + * have a static callback injected into an instance method, and for + * obvious reasons the inverse is not permitted).
+ * + *The simplest usage of @Inject captures no context from the + * target scope. This is particularly useful if the injector is targetting + * multiple methods with different signatures. In this case only the + * {@link CallbackInfo} (or {@link CallbackInfoReturnable} as appropriate) is + * required.
+ * + *private void onSomeEvent(CallbackInfo ci)+ * + *
Callbacks can also capture the arguments passed to the target method. To + * do so specify the target arguments before the {@link CallbackInfo}:
+ * + *private void onSomeEvent(int arg1, String arg2, + * CallbackInfo ci)+ * + *
If injecting into multiple methods with different target arguments it is + * obviously possible to ignore the target arguments (see "Basic Usage" above) + * but this may be unsuitable if arguments from the target are required. If you + * need to inject into multiple methods but also wish to capture method + * arguments you may provide a surrogate method with the alternative + * signature. In fact you may provide as many surrogates as required by the + * injection. Surrogate methods much have the same name as the handler method + * and must be decorated with {@link Surrogate}. A surrogate may also be + * required where the LVT of a method with local capture (see below) is + * known to change between different environments or injection points.
+ * + *In addition to capturing the target method arguments, it may be desirable + * to capture locally-scoped variables from the target method at the point of + * injection. This is usually executed in two stages:
+ * + *For more details see {@link #locals()}.
+ * */ @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) @@ -114,6 +174,13 @@ * surrogate targets for the orphaned injector by annotating them with an * {@link Surrogate} annotation. * + *You can improve the robustness of your local capture injection by only + * specifying locals up to the last variable you wish to use. For example if + * the target LVT contains <int, int, int, float, String> and + * you only need the float value, you can choose to omit the unused + * String and changes to the LVT beyond that point will not affect + * your injection.
+ * *It is also important to nominate the failure behaviour to follow when * local capture fails and so all {@link LocalCapture} behaviours which * specify a capture action imply a particular behaviour for handling diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/Redirect.java b/src/main/java/org/spongepowered/asm/mixin/injection/Redirect.java index 0821599dc..39309c281 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/Redirect.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/Redirect.java @@ -31,6 +31,8 @@ import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.injection.points.BeforeFieldAccess; +import org.spongepowered.asm.mixin.injection.points.BeforeInvoke; +import org.spongepowered.asm.mixin.injection.points.BeforeNew; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector; import org.spongepowered.asm.mixin.injection.throwables.InjectionError; import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException; @@ -41,7 +43,7 @@ * method call, field access or object construction (via the new * keyword) to the method decorated with this annotation.
* - *The handler method signature must match the hooked method precisely * but prepended with an arg of the owning object's type to accept the @@ -65,7 +67,8 @@ * * *
For obvious reasons this does not apply for static methods, for static - * methods it is sufficient that the signature simply match the hooked method. + * methods it is sufficient that the signature simply match the redirected + * method. *
* *It is also possible to capture the arguments of the target method in @@ -78,7 +81,12 @@ * int someInt, String someString) * * - *
All arguments of a method redirect handler, and the return type, can be + * decorated with {@link Coerce} if - for example - a package-private class + * needs to be coerced to a duck interface or to (for example) Object. + *
+ * + *The handler method signature varies depending on whether the redirector is * handling a field write (PUTFIELD, PUTSTATIC) or a @@ -91,21 +99,22 @@ * *
private FieldType getFieldValue()
private static FieldType getFieldValue()
private FieldType getFieldValue(OwnerType
- * owner)
private void setFieldValue(FieldType value)
private static void setFieldValue(FieldType value)
+ *
private void setFieldValue(OwnerType
- * owner, FieldType value)
All arguments of a field redirect handler, including the field type + * itself, can be decorated with {@link Coerce} if - for example - a + * package-private class needs to be coerced to a duck interface or to (for + * example) Object. + *
+ * + *For fields of an array type, it is possible to redirect the access to the * actual array field itself using the behaviour above. However it is also @@ -155,7 +170,7 @@ * {@link BeforeFieldAccess BeforeFieldAccess args} for details * on matching array accesses using the FIELD injection point.
* - *For fields of an array type, it is possible to redirect the call to the * builtin pseudo-property length. To do so, specify the argument @@ -174,28 +189,68 @@ * private int getLength(ElementType[][] array, int baseDim) * * - *
Redirecting object creation requires redirection of the NEW + * operation and constructor call. Your handler method should target the NEW + * opcode using the {@link BeforeNew} Injection Point and construct an + * appropriate object. The factory method must return a new object and + * must not return null since the contract of the new + * opcode does not allow for the possibility of null. Your factory may + * throw an exception however.
+ * + *Note that {@link BeforeNew} differs from {@link BeforeInvoke} in that the + * shape of target should contain only an owner (to + * target any ctor of the specified class) or only a descriptor + * consisting of the constructor descriptor with the return type changed + * from void (V) to the type of object being constructed. In + * other words to redirect an object creation for an object Foo:
+ * + *+ * + *package baz.bar; + * + *class Foo { + * private final int x, y; + * + * // ctor we wish to hook + * Foo(int x, int y) { + * this.x = x; + * this.y = y; + * } + *}+ *
Valid signatures for this ctor would be:
+ * + *The handler method signature must match the constructor being redirected * and the return type must match the type of object being constructed. For - * example to redirect the following constructor call:
+ * example to redirect the following object creation call: * - *public void baz(int someInt, String someString) { + ** *public void exampleFunc(String someString, int dx, int dy) { * // Hooking this constructor - * Foo someObject = new Foo("bar"); + * Foo someFoo = new Foo(dx * 10, dy * 10); *}*The signature of the handler method should be:
* *- ** *public Foo constructFoo(String arg1)+ *public Foo constructFoo(int x, int y)*Note that like other redirectors, it is possible to capture the target - * method's arguments by appending them to the handler method's signature.
+ * method's arguments by appending them to the handler method's signature: + * + *+ * public Foo constructFoo(int x, int y, String someString, int dx, + * int dy) + ** - *A note on static modifiers for handler methods
+ *A note on static modifiers for handler methods
* *In general, when declaring a redirect handler the static modifier * of the handler method must always match the target method. The exception to diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java b/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java index 2624ab2ec..769b1555d 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java @@ -26,6 +26,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import org.spongepowered.asm.mixin.injection.InjectionPoint.Selector; @@ -123,6 +124,7 @@ * can distinguish different slices using {@link #id} and then specify the slice * to use in the {@link At#slice} argument.
*/ +@Target({ /* No targets allowed */ }) @Retention(RetentionPolicy.RUNTIME) public @interface Slice { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java index 72c248893..d36ed952e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java @@ -531,6 +531,9 @@ private String generateBadLVTMessage(final Callback callback) { int position = callback.target.indexOf(callback.node); Listexpected = CallbackInjector.summariseLocals(this.methodNode.desc, callback.target.arguments.length + 1); List found = CallbackInjector.summariseLocals(callback.getDescriptor(), callback.frameSize + (callback.target.isStatic ? 1 : 0)); + if (expected.equals(found)) { + return String.format("Invalid descriptor on %s! Expected %s but found %s", this.info, callback.getDescriptor(), this.methodNode.desc); + } return String.format("LVT in %s has incompatible changes at opcode %d in callback %s.\nExpected: %s\n Found: %s", callback.target, position, this, expected, found); } @@ -564,7 +567,7 @@ private MethodNode generateErrorMethod(Callback callback, String errorClass, Str private void printLocals(final Callback callback) { Type[] args = Type.getArgumentTypes(callback.getDescriptorWithAllLocals()); SignaturePrinter methodSig = new SignaturePrinter(callback.target.method, callback.argNames); - SignaturePrinter handlerSig = new SignaturePrinter(this.methodNode.name, callback.target.returnType, args, callback.argNames); + SignaturePrinter handlerSig = new SignaturePrinter(this.info.getMethodName(), callback.target.returnType, args, callback.argNames); handlerSig.setModifiers(this.methodNode); PrettyPrinter printer = new PrettyPrinter(); @@ -572,7 +575,7 @@ private void printLocals(final Callback callback) { printer.kv("Target Method", methodSig); printer.kv("Target Max LOCALS", callback.target.getMaxLocals()); printer.kv("Initial Frame Size", callback.frameSize); - printer.kv("Callback Name", this.methodNode.name); + printer.kv("Callback Name", this.info.getMethodName()); printer.kv("Instruction", "%s %s", callback.node.getClass().getSimpleName(), Bytecode.getOpcodeName(callback.node.getCurrentTarget().getOpcode())); printer.hr(); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java index 2b4c3257e..dd8f6bd4a 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java @@ -49,8 +49,8 @@ import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; -import org.spongepowered.asm.mixin.injection.struct.Target; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; +import org.spongepowered.asm.mixin.injection.struct.Target; import org.spongepowered.asm.mixin.injection.throwables.InjectionError; import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException; import org.spongepowered.asm.mixin.refmap.IMixinContext; @@ -161,7 +161,7 @@ public Injector(InjectionInfo info, String annotationType) { @Override public String toString() { - return String.format("%s::%s", this.classNode.name, this.methodNode.name); + return String.format("%s::%s", this.classNode.name, this.info.getMethodName()); } /** @@ -377,7 +377,12 @@ protected void throwException(InsnList insns, String exceptionType, String messa * @return true if from can be coerced to to */ public static boolean canCoerce(Type from, Type to) { - if (from.getSort() == Type.OBJECT && to.getSort() == Type.OBJECT) { + int fromSort = from.getSort(); + int toSort = to.getSort(); + if (fromSort >= Type.ARRAY && toSort >= Type.ARRAY && fromSort == toSort) { + if (fromSort == Type.ARRAY && from.getDimensions() != to.getDimensions()) { + return false; + } return Injector.canCoerce(ClassInfo.forType(from, TypeLookup.ELEMENT_TYPE), ClassInfo.forType(to, TypeLookup.ELEMENT_TYPE)); } @@ -422,7 +427,7 @@ public static boolean canCoerce(char from, char to) { * @return true if from can be coerced to to */ private static boolean canCoerce(ClassInfo from, ClassInfo to) { - return from != null && to != null && (to == from || to.hasSuperClass(from, Traversal.NONE, true)); + return from != null && to != null && (to == from || to.hasSuperClass(from, Traversal.ALL, true)); } } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/InvokeInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/InvokeInjector.java index be64c0f27..a8e778178 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/InvokeInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/InvokeInjector.java @@ -24,25 +24,95 @@ */ package org.spongepowered.asm.mixin.injection.invoke; +import java.util.Arrays; import java.util.List; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.VarInsnNode; +import org.spongepowered.asm.mixin.MixinEnvironment.Option; +import org.spongepowered.asm.mixin.injection.Coerce; import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.code.Injector; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.Target; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; +import org.spongepowered.asm.mixin.injection.struct.Target.Extension; import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException; +import org.spongepowered.asm.util.Annotations; +import org.spongepowered.asm.util.Bytecode; +import org.spongepowered.asm.util.SignaturePrinter; + +import com.google.common.collect.ObjectArrays; /** * Base class for injectors which inject at method invokes */ public abstract class InvokeInjector extends Injector { + + /** + * Redirection data bundle base. No this isn't meant to be pretty, it's a + * way of passing a bunch of state around the injector without having dumb + * method signatures. + */ + static class InjectorData { + + /** + * Redirect target + */ + final Target target; + + /** + * Mutable description. The bundle is be passed to different types of + * handler and the handler decorates the bundle with a description of + * the type of injection, purely for use in error messages. + */ + String description; + + /** + * When passing through {@link RedirectInjector#validateParams} this + * switch determines whether coercion is supported for both the primary + * handler args and captured target args, or for target args only. + */ + boolean allowCoerceArgs; + + /** + * Number of arguments to capture from the target, determined by the + * number of extra args on the handler method + */ + int captureTargetArgs = 0; + + /** + * True if the method itself is decorated with {@link Coerce} and the + * return type is coerced. Instructs the injector to add a CHECKCAST + * following the handler. + */ + boolean coerceReturnType = false; + + InjectorData(Target target) { + this(target, "handler"); + } + + InjectorData(Target target, String description) { + this(target, description, true); + } + + InjectorData(Target target, String description, boolean allowCoerceArgs) { + this.target = target; + this.description = description; + this.allowCoerceArgs = allowCoerceArgs; + } + + @Override + public String toString() { + return this.description; + } + + } /** * @param info Information about this injection @@ -154,6 +224,7 @@ protected void storeArgs(Type[] args, InsnList insns, int[] argMap, int start, i /** * Load args onto the stack from their positions allocated in argMap + * * @param args argument types * @param insns instruction list to generate insns into * @param argMap generated argmap containing local indices for all args @@ -161,8 +232,134 @@ protected void storeArgs(Type[] args, InsnList insns, int[] argMap, int start, i * @param end Ending index */ protected void pushArgs(Type[] args, InsnList insns, int[] argMap, int start, int end) { - for (int arg = start; arg < end; arg++) { + this.pushArgs(args, insns, argMap, start, end, null); + } + + /** + * Load args onto the stack from their positions allocated in argMap + * + * @param args argument types + * @param insns instruction list to generate insns into + * @param argMap generated argmap containing local indices for all args + * @param start Starting index + * @param end Ending index + */ + protected void pushArgs(Type[] args, InsnList insns, int[] argMap, int start, int end, Extension extension) { + for (int arg = start; arg < end && arg < args.length; arg++) { insns.add(new VarInsnNode(args[arg].getOpcode(Opcodes.ILOAD), argMap[arg])); + if (extension != null) { + extension.add(args[arg].getSize()); + } + } + } + + /** + * Collects all the logic from old validateParams/checkDescriptor so that we + * can consistently apply coercion logic to method params, and also provide + * more detailed errors when something doesn't line up. + * + * The supplied return type and argument list will be verified first. Any + * arguments on the handler beyond the base arguments consume arguments from + * the target. The flag allowCoerceArgs on the redirect + * instance determines whether coercion is supported for the base args and + * return type, coercion is always allowed for captured target args.
+ * + *Following validation, the captureTargetArgs and + * coerceReturnType values will be set on the bundle and the + * calling injector function should adjust itself accordingly.
+ * + * @param injector Data bundle for the injector + * @param returnType Return type for the handler, must not be null + * @param args Array of handler args, must not be null + */ + protected final void validateParams(InjectorData injector, Type returnType, Type... args) { + String description = String.format("%s %s method %s from %s", this.annotationType, injector, this, this.info.getContext()); + int argIndex = 0; + try { + injector.coerceReturnType = this.checkCoerce(-1, returnType, description, injector.allowCoerceArgs); + + for (Type arg : args) { + if (arg != null) { + this.checkCoerce(argIndex, arg, description, injector.allowCoerceArgs); + argIndex++; + } + } + + if (argIndex == this.methodArgs.length) { + return; + } + + for (int targetArg = 0; targetArg < injector.target.arguments.length && argIndex < this.methodArgs.length; targetArg++, argIndex++) { + this.checkCoerce(argIndex, injector.target.arguments[targetArg], description, true); + injector.captureTargetArgs++; + } + } catch (InvalidInjectionException ex) { + String expected = this.methodArgs.length > args.length + ? Bytecode.generateDescriptor(returnType, ObjectArrays.concat(args, injector.target.arguments, Type.class)) + : Bytecode.generateDescriptor(returnType, args); + throw new InvalidInjectionException(this.info, String.format("%s. Handler signature: %s Expected signature: %s", ex.getMessage(), + this.methodNode.desc, expected)); + } + + if (argIndex < this.methodArgs.length) { + Type[] extraArgs = Arrays.copyOfRange(this.methodArgs, argIndex, this.methodArgs.length); + throw new InvalidInjectionException(this.info, String.format( + "%s has an invalid signature. Found %d unexpected additional method arguments: %s", + description, this.methodArgs.length - argIndex, new SignaturePrinter(extraArgs).getFormattedArgs())); + } + } + + /** + * Called inside {@link #validateParams} but can also be used directly. This + * method checks whether the supplied type is compatible with the specified + * handler argument, apply coercion logic where necessary. + * + * @param index Handler argument index, pass in a negative value (by + * convention -1) to specify handler return type + * @param toType Desired type based on the injector contract + * @param description human-readable description of the handler method, used + * in raised exception + * @param allowCoercion True if coercion logic can be applied to this + * argument, false to only allow a precise match + * @return false if the argument matched perfectly, true + * if coercion is required for the argument + */ + protected final boolean checkCoerce(int index, Type toType, String description, boolean allowCoercion) { + Type fromType = index < 0 ? this.returnType : this.methodArgs[index]; + if (index >= this.methodArgs.length) { + throw new InvalidInjectionException(this.info, String.format( + "%s has an invalid signature. Not enough arguments: expected argument type %s at index %d", + description, SignaturePrinter.getTypeName(toType), index)); + } + + AnnotationNode coerce = Annotations.getInvisibleParameter(this.methodNode, Coerce.class, index); + boolean isReturn = index < 0; + String argType = isReturn ? "return" : "argument"; + Object argIndex = isReturn ? "" : " at index " + index; + + if (fromType.equals(toType)) { + if (coerce != null && this.info.getContext().getOption(Option.DEBUG_VERBOSE)) { + Injector.logger.info("Possibly-redundant @Coerce on {} {} type{}, {} is identical to {}", description, argType, argIndex, + SignaturePrinter.getTypeName(toType), SignaturePrinter.getTypeName(fromType)); + } + return false; + } + + if (coerce == null || !allowCoercion) { + String coerceWarning = coerce != null ? ". @Coerce not allowed here" : ""; + throw new InvalidInjectionException(this.info, String.format( + "%s has an invalid signature. Found unexpected %s type %s%s, expected %s%s", description, argType, + SignaturePrinter.getTypeName(fromType), argIndex, SignaturePrinter.getTypeName(toType), coerceWarning)); } + + boolean canCoerce = Injector.canCoerce(fromType, toType); + if (!canCoerce) { + throw new InvalidInjectionException(this.info, String.format( + "%s has an invalid signature. Cannot @Coerce %s type %s%s to %s", description, argType, + SignaturePrinter.getTypeName(toType), argIndex, SignaturePrinter.getTypeName(fromType))); + } + + return true; } + } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyConstantInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyConstantInjector.java index 35732fb21..f851544f4 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyConstantInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyConstantInjector.java @@ -33,6 +33,7 @@ import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.LocalVariableNode; +import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.VarInsnNode; import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.injection.code.Injector; @@ -82,6 +83,11 @@ protected void inject(Target target, InjectionNode node) { return; } + if (targetNode instanceof TypeInsnNode) { + // TODO inject at type constant + throw new InvalidInjectionException(this.info, "Inject at TypeInsnNode not implemented"); + } + if (Bytecode.isConstant(targetNode)) { this.checkTargetModifiers(target, false); this.injectConstantModifier(target, targetNode); @@ -130,17 +136,16 @@ private void injectConstantModifier(Target target, AbstractInsnNode constNode) { } private AbstractInsnNode invokeConstantHandler(Type constantType, Target target, Extension extraStack, InsnList before, InsnList after) { - final String handlerDesc = Bytecode.generateDescriptor(constantType, constantType); - final boolean withArgs = this.checkDescriptor(handlerDesc, target, "getter"); + InjectorData handler = new InjectorData(target, "constant modifier"); + this.validateParams(handler, constantType, constantType); if (!this.isStatic) { before.insert(new VarInsnNode(Opcodes.ALOAD, 0)); extraStack.add(); } - if (withArgs) { - this.pushArgs(target.arguments, after, target.getArgIndices(), 0, target.arguments.length); - extraStack.add(target.arguments); + if (handler.captureTargetArgs > 0) { + this.pushArgs(target.arguments, after, target.getArgIndices(), 0, handler.captureTargetArgs, extraStack); } return this.invokeHandler(after); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java index d7e1c3474..90175d468 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java @@ -31,26 +31,31 @@ import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; -import org.objectweb.asm.tree.*; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.VarInsnNode; import org.spongepowered.asm.mixin.Final; -import org.spongepowered.asm.mixin.MixinEnvironment.Option; -import org.spongepowered.asm.mixin.injection.Coerce; import org.spongepowered.asm.mixin.injection.InjectionPoint; -import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; +import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.code.Injector; import org.spongepowered.asm.mixin.injection.points.BeforeFieldAccess; import org.spongepowered.asm.mixin.injection.points.BeforeNew; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; -import org.spongepowered.asm.mixin.injection.struct.Target.Extension; import org.spongepowered.asm.mixin.injection.struct.Target; +import org.spongepowered.asm.mixin.injection.struct.Target.Extension; import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException; import org.spongepowered.asm.util.Annotations; import org.spongepowered.asm.util.Bytecode; import org.spongepowered.asm.util.Constants; -import com.google.common.base.Joiner; import com.google.common.collect.ObjectArrays; import com.google.common.primitives.Ints; @@ -90,6 +95,8 @@ */ public class RedirectInjector extends InvokeInjector { + private static final String NPE = "java/lang/NullPointerException"; + private static final String KEY_NOMINATORS = "nominators"; private static final String KEY_FUZZ = "fuzz"; private static final String KEY_OPCODE = "opcode"; @@ -129,36 +136,94 @@ static class ConstructorRedirectData { public static final String KEY = "ctor"; - public boolean wildcard = false; + boolean wildcard = false; + + int injected = 0; - public int injected = 0; + InvalidInjectionException lastException; + + public void throwOrCollect(InvalidInjectionException ex) { + if (!this.wildcard) { + throw ex; + } + this.lastException = ex; + } } /** * Data bundle for invoke redirectors */ - static class RedirectedInvoke { + static class RedirectedInvokeData extends InjectorData { - final Target target; final MethodInsnNode node; final Type returnType; - final Type[] args; - final Type[] locals; - - boolean captureTargetArgs = false; + final Type[] targetArgs; + final Type[] handlerArgs; - RedirectedInvoke(Target target, MethodInsnNode node) { - this.target = target; + RedirectedInvokeData(Target target, MethodInsnNode node) { + super(target); this.node = node; this.returnType = Type.getReturnType(node.desc); - this.args = Type.getArgumentTypes(node.desc); - this.locals = node.getOpcode() == Opcodes.INVOKESTATIC - ? this.args - : ObjectArrays.concat(Type.getType("L" + node.owner + ";"), this.args); + this.targetArgs = Type.getArgumentTypes(node.desc); + this.handlerArgs = node.getOpcode() == Opcodes.INVOKESTATIC + ? this.targetArgs + : ObjectArrays.concat(Type.getObjectType(node.owner), this.targetArgs); } + } + /** + * Data bundle for field redirectors + */ + static class RedirectedFieldData extends InjectorData { + + final FieldInsnNode node; + final int opcode; + final Type owner; + final Type type; + final int dimensions; + final boolean isStatic; + final boolean isGetter; + final boolean isSetter; + + // This is actually the return type for array access, might be int for + // array length redirectors + Type elementType; + int extraDimensions = 1; + + RedirectedFieldData(Target target, FieldInsnNode node) { + super(target); + this.node = node; + this.opcode = node.getOpcode(); + this.owner = Type.getObjectType(node.owner); + this.type = Type.getType(node.desc); + this.dimensions = (this.type.getSort() == Type.ARRAY) ? this.type.getDimensions() : 0; + this.isStatic = this.opcode == Opcodes.GETSTATIC || this.opcode == Opcodes.PUTSTATIC; + this.isGetter = this.opcode == Opcodes.GETSTATIC || this.opcode == Opcodes.GETFIELD; + this.isSetter = this.opcode == Opcodes.PUTSTATIC || this.opcode == Opcodes.PUTFIELD; + this.description = this.isGetter ? "field getter" : this.isSetter ? "field setter" : "handler"; + } + + int getTotalDimensions() { + return this.dimensions + this.extraDimensions; + } + + Type[] getArrayArgs(Type... extra) { + int dimensions = this.getTotalDimensions(); + Type[] args = new Type[dimensions + extra.length]; + for (int i = 0; i < args.length; i++) { + args[i] = i == 0 ? this.type : i < dimensions ? Type.INT_TYPE : extra[dimensions - i]; + } + return args; + } + + } + + /** + * Meta is used to decorate the target node with information about this + * injection + */ protected Meta meta; private MapctorRedirectors = new HashMap (); @@ -297,7 +362,8 @@ protected void postInject(Target target, InjectionNode node) { if (node.getOriginalTarget() instanceof TypeInsnNode && node.getOriginalTarget().getOpcode() == Opcodes.NEW) { ConstructorRedirectData meta = node. getDecoration(ConstructorRedirectData.KEY); if (meta.wildcard && meta.injected == 0) { - throw new InvalidInjectionException(this.info, String.format("%s ctor invocation was not found in %s", this.annotationType, target)); + throw new InvalidInjectionException(this.info, String.format("%s ctor invocation was not found in %s", this.annotationType, target), + meta.lastException); } } } @@ -307,150 +373,99 @@ protected void postInject(Target target, InjectionNode node) { */ @Override protected void injectAtInvoke(Target target, InjectionNode node) { - RedirectedInvoke invoke = new RedirectedInvoke(target, (MethodInsnNode)node.getCurrentTarget()); + RedirectedInvokeData invoke = new RedirectedInvokeData(target, (MethodInsnNode)node.getCurrentTarget()); - this.validateParams(invoke); + this.validateParams(invoke, invoke.returnType, invoke.handlerArgs); InsnList insns = new InsnList(); - Extension extraLocals = target.extendLocals().add(invoke.locals).add(1); + Extension extraLocals = target.extendLocals().add(invoke.handlerArgs).add(1); Extension extraStack = target.extendStack().add(1); // Normally only need 1 extra stack pos to store target ref - int[] argMap = this.storeArgs(target, invoke.locals, insns, 0); - if (invoke.captureTargetArgs) { - int argSize = Bytecode.getArgsSize(target.arguments); + int[] argMap = this.storeArgs(target, invoke.handlerArgs, insns, 0); + if (invoke.captureTargetArgs > 0) { + int argSize = Bytecode.getArgsSize(target.arguments, 0, invoke.captureTargetArgs); extraLocals.add(argSize); extraStack.add(argSize); + // No need to truncate target arg indices, pushArgs ignores args which don't exist argMap = Ints.concat(argMap, target.getArgIndices()); } - AbstractInsnNode insn = this.invokeHandlerWithArgs(this.methodArgs, insns, argMap); - target.replaceNode(invoke.node, insn, insns); + AbstractInsnNode champion = this.invokeHandlerWithArgs(this.methodArgs, insns, argMap); + if (invoke.coerceReturnType && invoke.returnType.getSort() >= Type.ARRAY) { + insns.add(new TypeInsnNode(Opcodes.CHECKCAST, invoke.returnType.getInternalName())); + } + target.replaceNode(invoke.node, champion, insns); extraLocals.apply(); extraStack.apply(); } - /** - * Perform validation of an invoke handler parameters, each parameter in the - * handler must match the expected type or be annotated with {@link Coerce} - * and be a supported supertype of the incoming type. - * - * @param invoke invocation being redirected - */ - protected void validateParams(RedirectedInvoke invoke) { - int argc = this.methodArgs.length; - - String description = String.format("%s handler method %s", this.annotationType, this); - if (!invoke.returnType.equals(this.returnType)) { - throw new InvalidInjectionException(this.info, String.format("%s has an invalid signature. Expected return type %s found %s", - description, this.returnType, invoke.returnType)); - } - - for (int index = 0; index < argc; index++) { - Type toType = null; - if (index >= this.methodArgs.length) { - throw new InvalidInjectionException(this.info, String.format( - "%s has an invalid signature. Not enough arguments found for capture of target method args, expected %d but found %d", - description, argc, this.methodArgs.length)); - } - - Type fromType = this.methodArgs[index]; - - if (index < invoke.locals.length) { - toType = invoke.locals[index]; - } else { - invoke.captureTargetArgs = true; - argc = Math.max(argc, invoke.locals.length + invoke.target.arguments.length); - int arg = index - invoke.locals.length; - if (arg >= invoke.target.arguments.length) { - throw new InvalidInjectionException(this.info, String.format( - "%s has an invalid signature. Found unexpected additional target argument with type %s at index %d", - description, fromType, index)); - } - toType = invoke.target.arguments[arg]; - } - - AnnotationNode coerce = Annotations.getInvisibleParameter(this.methodNode, Coerce.class, index); - - if (fromType.equals(toType)) { - if (coerce != null && this.info.getContext().getOption(Option.DEBUG_VERBOSE)) { - Injector.logger.warn("Redundant @Coerce on {} argument {}, {} is identical to {}", description, index, toType, fromType); - } - - continue; - } - - boolean canCoerce = Injector.canCoerce(fromType, toType); - if (coerce == null) { - throw new InvalidInjectionException(this.info, String.format( - "%s has an invalid signature. Found unexpected argument type %s at index %d, expected %s", - description, fromType, index, toType)); - } - - if (!canCoerce) { - throw new InvalidInjectionException(this.info, String.format( - "%s has an invalid signature. Cannot @Coerce argument type %s at index %d to %s", - description, toType, index, fromType)); - } - } - } - /** * Redirect a field get or set operation, or an array element access */ private void injectAtFieldAccess(Target target, InjectionNode node) { - final FieldInsnNode fieldNode = (FieldInsnNode)node.getCurrentTarget(); - final int opCode = fieldNode.getOpcode(); - final Type ownerType = Type.getType("L" + fieldNode.owner + ";"); - final Type fieldType = Type.getType(fieldNode.desc); + RedirectedFieldData field = new RedirectedFieldData(target, (FieldInsnNode)node.getCurrentTarget()); - int targetDimensions = (fieldType.getSort() == Type.ARRAY) ? fieldType.getDimensions() : 0; int handlerDimensions = (this.returnType.getSort() == Type.ARRAY) ? this.returnType.getDimensions() : 0; - if (handlerDimensions > targetDimensions) { + if (handlerDimensions > field.dimensions) { throw new InvalidInjectionException(this.info, "Dimensionality of handler method is greater than target array on " + this); - } else if (handlerDimensions == 0 && targetDimensions > 0) { + } else if (handlerDimensions == 0 && field.dimensions > 0) { int fuzz = node. getDecoration(RedirectInjector.KEY_FUZZ).intValue(); int opcode = node. getDecoration(RedirectInjector.KEY_OPCODE).intValue(); - this.injectAtArrayField(target, fieldNode, opCode, ownerType, fieldType, fuzz, opcode); + this.injectAtArrayField(field, fuzz, opcode); } else { - this.injectAtScalarField(target, fieldNode, opCode, ownerType, fieldType); + this.injectAtScalarField(field); } } /** * Redirect an array element access */ - private void injectAtArrayField(Target target, FieldInsnNode fieldNode, int opCode, Type ownerType, Type fieldType, int fuzz, int opcode) { - Type elementType = fieldType.getElementType(); - if (opCode != Opcodes.GETSTATIC && opCode != Opcodes.GETFIELD) { + private void injectAtArrayField(RedirectedFieldData field, int fuzz, int opcode) { + Type elementType = field.type.getElementType(); + if (field.opcode != Opcodes.GETSTATIC && field.opcode != Opcodes.GETFIELD) { throw new InvalidInjectionException(this.info, String.format("Unspported opcode %s for array access %s", - Bytecode.getOpcodeName(opCode), this.info)); + Bytecode.getOpcodeName(field.opcode), this.info)); } else if (this.returnType.getSort() != Type.VOID) { if (opcode != Opcodes.ARRAYLENGTH) { opcode = elementType.getOpcode(Opcodes.IALOAD); } - AbstractInsnNode varNode = BeforeFieldAccess.findArrayNode(target.insns, fieldNode, opcode, fuzz); - this.injectAtGetArray(target, fieldNode, varNode, ownerType, fieldType); + AbstractInsnNode varNode = BeforeFieldAccess.findArrayNode(field.target.insns, field.node, opcode, fuzz); + this.injectAtGetArray(field, varNode); } else { - AbstractInsnNode varNode = BeforeFieldAccess.findArrayNode(target.insns, fieldNode, elementType.getOpcode(Opcodes.IASTORE), fuzz); - this.injectAtSetArray(target, fieldNode, varNode, ownerType, fieldType); + AbstractInsnNode varNode = BeforeFieldAccess.findArrayNode(field.target.insns, field.node, elementType.getOpcode(Opcodes.IASTORE), fuzz); + this.injectAtSetArray(field, varNode); } } /** * Array element read (xALOAD) or array.length (ARRAYLENGTH) */ - private void injectAtGetArray(Target target, FieldInsnNode fieldNode, AbstractInsnNode varNode, Type ownerType, Type fieldType) { - String handlerDesc = RedirectInjector.getGetArrayHandlerDescriptor(varNode, this.returnType, fieldType); - boolean withArgs = this.checkDescriptor(handlerDesc, target, "array getter"); - this.injectArrayRedirect(target, fieldNode, varNode, withArgs, "array getter"); + private void injectAtGetArray(RedirectedFieldData field, AbstractInsnNode varNode) { + field.description = "array getter"; + field.elementType = field.type.getElementType(); + + if (varNode != null && varNode.getOpcode() == Opcodes.ARRAYLENGTH) { + field.elementType = Type.INT_TYPE; + field.extraDimensions = 0; + } + + this.validateParams(field, field.elementType, field.getArrayArgs()); + this.injectArrayRedirect(field, varNode, "array getter"); } /** * Array element write (xASTORE) */ - private void injectAtSetArray(Target target, FieldInsnNode fieldNode, AbstractInsnNode varNode, Type ownerType, Type fieldType) { - String handlerDesc = Bytecode.generateDescriptor(null, (Object[])RedirectInjector.getArrayArgs(fieldType, 1, fieldType.getElementType())); - boolean withArgs = this.checkDescriptor(handlerDesc, target, "array setter"); - this.injectArrayRedirect(target, fieldNode, varNode, withArgs, "array setter"); + private void injectAtSetArray(RedirectedFieldData field, AbstractInsnNode varNode) { + field.description = "array setter"; + Type elementType = field.type.getElementType(); + int valueArgIndex = field.getTotalDimensions(); + if (this.checkCoerce(valueArgIndex, elementType, String.format("%s array setter method %s from %s", + this.annotationType, this, this.info.getContext()), true)) { + elementType = this.methodArgs[valueArgIndex]; + } + + this.validateParams(field, Type.VOID_TYPE, field.getArrayArgs(elementType)); + this.injectArrayRedirect(field, varNode, "array setter"); } /** @@ -459,35 +474,38 @@ private void injectAtSetArray(Target target, FieldInsnNode fieldNode, AbstractIn * actual handler signature, the correct arguments are already on the stack * thanks to the nature of xALOAD and xASTORE. * - * @param target target method - * @param fieldNode field node * @param varNode array access node - * @param withArgs true if the descriptor includes captured arguments from - * the target method signature * @param type description of access type for use in error messages + * @param target target method + * @param fieldNode field node */ - private void injectArrayRedirect(Target target, FieldInsnNode fieldNode, AbstractInsnNode varNode, boolean withArgs, String type) { + private void injectArrayRedirect(RedirectedFieldData field, AbstractInsnNode varNode, String type) { if (varNode == null) { String advice = ""; throw new InvalidInjectionException(this.info, String.format( "Array element %s on %s could not locate a matching %s instruction in %s. %s", - this.annotationType, this, type, target, advice)); + this.annotationType, this, type, field.target, advice)); } - Extension extraStack = target.extendStack(); + Extension extraStack = field.target.extendStack(); if (!this.isStatic) { - target.insns.insertBefore(fieldNode, new VarInsnNode(Opcodes.ALOAD, 0)); + VarInsnNode loadThis = new VarInsnNode(Opcodes.ALOAD, 0); + field.target.insns.insert(field.node, loadThis); + field.target.insns.insert(loadThis, new InsnNode(Opcodes.SWAP)); extraStack.add(); } - InsnList invokeInsns = new InsnList(); - if (withArgs) { - this.pushArgs(target.arguments, invokeInsns, target.getArgIndices(), 0, target.arguments.length); - extraStack.add(target.arguments); + InsnList insns = new InsnList(); + if (field.captureTargetArgs > 0) { + this.pushArgs(field.target.arguments, insns, field.target.getArgIndices(), 0, field.captureTargetArgs, extraStack); } extraStack.apply(); - target.replaceNode(varNode, this.invokeHandler(invokeInsns), invokeInsns); + AbstractInsnNode champion = this.invokeHandler(insns); + if (field.coerceReturnType && field.type.getSort() >= Type.ARRAY) { + insns.add(new TypeInsnNode(Opcodes.CHECKCAST, field.elementType.getInternalName())); + } + field.target.replaceNode(varNode, champion, insns); } /** @@ -499,18 +517,19 @@ private void injectArrayRedirect(Target target, FieldInsnNode fieldNode, Abstrac * @param ownerType type of the field owner * @param fieldType field type */ - private void injectAtScalarField(Target target, final FieldInsnNode fieldNode, int opCode, Type ownerType, Type fieldType) { + private void injectAtScalarField(RedirectedFieldData field) { AbstractInsnNode invoke = null; InsnList insns = new InsnList(); - if (opCode == Opcodes.GETSTATIC || opCode == Opcodes.GETFIELD) { - invoke = this.injectAtGetField(insns, target, fieldNode, opCode == Opcodes.GETSTATIC, ownerType, fieldType); - } else if (opCode == Opcodes.PUTSTATIC || opCode == Opcodes.PUTFIELD) { - invoke = this.injectAtPutField(insns, target, fieldNode, opCode == Opcodes.PUTSTATIC, ownerType, fieldType); + if (field.isGetter) { + invoke = this.injectAtGetField(field, insns); + } else if (field.isSetter) { + invoke = this.injectAtPutField(field, insns); } else { - throw new InvalidInjectionException(this.info, String.format("Unspported opcode %s for %s", Bytecode.getOpcodeName(opCode), this.info)); + throw new InvalidInjectionException(this.info, String.format("Unspported opcode %s for %s", + Bytecode.getOpcodeName(field.opcode), this.info)); } - target.replaceNode(fieldNode, invoke, insns); + field.target.replaceNode(field.node, invoke, insns); } /** @@ -519,27 +538,29 @@ private void injectAtScalarField(Target target, final FieldInsnNode fieldNode, i * possible scenarios based on the possible combinations of static on the * handler and the field itself. */ - private AbstractInsnNode injectAtGetField(InsnList insns, Target target, FieldInsnNode node, boolean staticField, Type owner, Type fieldType) { - final String handlerDesc = staticField ? Bytecode.generateDescriptor(fieldType) : Bytecode.generateDescriptor(fieldType, owner); - final boolean withArgs = this.checkDescriptor(handlerDesc, target, "getter"); + private AbstractInsnNode injectAtGetField(RedirectedFieldData field, InsnList insns) { + this.validateParams(field, field.type, field.isStatic ? null : field.owner); - Extension extraStack = target.extendStack(); + Extension extraStack = field.target.extendStack(); if (!this.isStatic) { extraStack.add(); insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); - if (!staticField) { + if (!field.isStatic) { insns.add(new InsnNode(Opcodes.SWAP)); } } - if (withArgs) { - this.pushArgs(target.arguments, insns, target.getArgIndices(), 0, target.arguments.length); - extraStack.add(target.arguments); + if (field.captureTargetArgs > 0) { + this.pushArgs(field.target.arguments, insns, field.target.getArgIndices(), 0, field.captureTargetArgs, extraStack); } extraStack.apply(); - return this.invokeHandler(insns); + AbstractInsnNode champion = this.invokeHandler(insns); + if (field.coerceReturnType && field.type.getSort() >= Type.ARRAY) { + insns.add(new TypeInsnNode(Opcodes.CHECKCAST, field.type.getInternalName())); + } + return champion; } /** @@ -548,60 +569,33 @@ private AbstractInsnNode injectAtGetField(InsnList insns, Target target, FieldIn * possible scenarios based on the possible combinations of static on the * handler and the field itself. */ - private AbstractInsnNode injectAtPutField(InsnList insns, Target target, FieldInsnNode node, boolean staticField, Type owner, Type fieldType) { - String handlerDesc = staticField ? Bytecode.generateDescriptor(null, fieldType) : Bytecode.generateDescriptor(null, owner, fieldType); - boolean withArgs = this.checkDescriptor(handlerDesc, target, "setter"); + private AbstractInsnNode injectAtPutField(RedirectedFieldData field, InsnList insns) { + this.validateParams(field, Type.VOID_TYPE, field.isStatic ? null : field.owner, field.type); - Extension extraStack = target.extendStack(); + Extension extraStack = field.target.extendStack(); if (!this.isStatic) { - if (staticField) { + if (field.isStatic) { insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); insns.add(new InsnNode(Opcodes.SWAP)); } else { extraStack.add(); - int marshallVar = target.allocateLocals(fieldType.getSize()); - insns.add(new VarInsnNode(fieldType.getOpcode(Opcodes.ISTORE), marshallVar)); + int marshallVar = field.target.allocateLocals(field.type.getSize()); + insns.add(new VarInsnNode(field.type.getOpcode(Opcodes.ISTORE), marshallVar)); insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); insns.add(new InsnNode(Opcodes.SWAP)); - insns.add(new VarInsnNode(fieldType.getOpcode(Opcodes.ILOAD), marshallVar)); + insns.add(new VarInsnNode(field.type.getOpcode(Opcodes.ILOAD), marshallVar)); } } - if (withArgs) { - this.pushArgs(target.arguments, insns, target.getArgIndices(), 0, target.arguments.length); - extraStack.add(target.arguments); + if (field.captureTargetArgs > 0) { + this.pushArgs(field.target.arguments, insns, field.target.getArgIndices(), 0, field.captureTargetArgs, extraStack); } extraStack.apply(); return this.invokeHandler(insns); } - /** - * Check that the handler descriptor matches the calculated descriptor for - * the access being redirected. - * - * @param desc computed descriptor - * @param target target method - * @param type redirector type in human-readable text, for error messages - * @return true if the descriptor was found and includes target method args, - * false if the descriptor was found and does not capture target args - */ - protected boolean checkDescriptor(String desc, Target target, String type) { - if (this.methodNode.desc.equals(desc)) { - return false; - } - - int pos = desc.indexOf(')'); - String alternateDesc = String.format("%s%s%s", desc.substring(0, pos), Joiner.on("").join(target.arguments), desc.substring(pos)); - if (this.methodNode.desc.equals(alternateDesc)) { - return true; - } - - throw new InvalidInjectionException(this.info, String.format("%s method %s %s has an invalid signature. Expected %s but found %s", - this.annotationType, type, this, desc, this.methodNode.desc)); - } - protected void injectAtConstructor(Target target, InjectionNode node) { ConstructorRedirectData meta = node. getDecoration(ConstructorRedirectData.KEY); @@ -616,22 +610,19 @@ protected void injectAtConstructor(Target target, InjectionNode node) { final MethodInsnNode initNode = target.findInitNodeFor(newNode); if (initNode == null) { - if (!meta.wildcard) { - throw new InvalidInjectionException(this.info, String.format("%s ctor invocation was not found in %s", this.annotationType, target)); - } + meta.throwOrCollect(new InvalidInjectionException(this.info, String.format("%s ctor invocation was not found in %s", + this.annotationType, target))); return; } // True if the result of the object construction is being assigned boolean isAssigned = dupNode.getOpcode() == Opcodes.DUP; - String desc = initNode.desc.replace(")V", ")L" + newNode.desc + ";"); - boolean withArgs = false; + RedirectedInvokeData ctor = new RedirectedInvokeData(target, initNode); + ctor.description = "factory"; try { - withArgs = this.checkDescriptor(desc, target, "constructor"); + this.validateParams(ctor, Type.getObjectType(newNode.desc), ctor.targetArgs); } catch (InvalidInjectionException ex) { - if (!meta.wildcard) { - throw ex; - } + meta.throwOrCollect(ex); return; } @@ -647,12 +638,14 @@ protected void injectAtConstructor(Target target, InjectionNode node) { Extension extraStack = target.extendStack(); InsnList insns = new InsnList(); - if (withArgs) { - this.pushArgs(target.arguments, insns, target.getArgIndices(), 0, target.arguments.length); - extraStack.add(target.arguments); + if (ctor.captureTargetArgs > 0) { + this.pushArgs(target.arguments, insns, target.getArgIndices(), 0, ctor.captureTargetArgs, extraStack); } this.invokeHandler(insns); + if (ctor.coerceReturnType) { + insns.add(new TypeInsnNode(Opcodes.CHECKCAST, newNode.desc)); + } extraStack.apply(); if (isAssigned) { @@ -662,7 +655,7 @@ protected void injectAtConstructor(Target target, InjectionNode node) { LabelNode nullCheckSucceeded = new LabelNode(); insns.add(new InsnNode(Opcodes.DUP)); insns.add(new JumpInsnNode(Opcodes.IFNONNULL, nullCheckSucceeded)); - this.throwException(insns, "java/lang/NullPointerException", String.format("%s constructor handler %s returned null for %s", + this.throwException(insns, RedirectInjector.NPE, String.format("%s constructor handler %s returned null for %s", this.annotationType, this, newNode.desc.replace('/', '.'))); insns.add(nullCheckSucceeded); extraStack.add(); @@ -676,20 +669,4 @@ protected void injectAtConstructor(Target target, InjectionNode node) { meta.injected++; } - private static String getGetArrayHandlerDescriptor(AbstractInsnNode varNode, Type returnType, Type fieldType) { - if (varNode != null && varNode.getOpcode() == Opcodes.ARRAYLENGTH) { - return Bytecode.generateDescriptor(Type.INT_TYPE, (Object[])RedirectInjector.getArrayArgs(fieldType, 0)); - } - return Bytecode.generateDescriptor(returnType, (Object[])RedirectInjector.getArrayArgs(fieldType, 1)); - } - - private static Type[] getArrayArgs(Type fieldType, int extraDimensions, Type... extra) { - int dimensions = fieldType.getDimensions() + extraDimensions; - Type[] args = new Type[dimensions + extra.length]; - for (int i = 0; i < args.length; i++) { - args[i] = i == 0 ? fieldType : i < dimensions ? Type.INT_TYPE : extra[dimensions - i]; - } - return args; - } - } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/modify/ModifyVariableInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/modify/ModifyVariableInjector.java index 1678b2930..3cf6a7afb 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/modify/ModifyVariableInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/modify/ModifyVariableInjector.java @@ -147,7 +147,7 @@ protected void inject(Target target, InjectionNode node) { Context context = new Context(this.returnType, this.discriminator.isArgsOnly(), target, node.getCurrentTarget()); if (this.discriminator.printLVT()) { - this.printLocals(context); + this.printLocals(target, context); } String handlerDesc = Bytecode.getDescriptor(new Type[] { this.returnType }, this.returnType); @@ -178,17 +178,18 @@ protected void inject(Target target, InjectionNode node) { /** * Pretty-print local variable information to stderr */ - private void printLocals(final Context context) { - SignaturePrinter handlerSig = new SignaturePrinter(this.methodNode.name, this.returnType, this.methodArgs, new String[] { "var" }); + private void printLocals(Target target, Context context) { + SignaturePrinter handlerSig = new SignaturePrinter(this.info.getMethodName(), this.returnType, this.methodArgs, new String[] { "var" }); handlerSig.setModifiers(this.methodNode); new PrettyPrinter() .kvWidth(20) .kv("Target Class", this.classNode.name.replace('/', '.')) .kv("Target Method", context.target.method.name) - .kv("Callback Name", this.methodNode.name) + .kv("Callback Name", this.info.getMethodName()) .kv("Capture Type", SignaturePrinter.getTypeName(this.returnType, false)) - .kv("Instruction", "%s %s", context.node.getClass().getSimpleName(), Bytecode.getOpcodeName(context.node.getOpcode())).hr() + .kv("Instruction", "[%d] %s %s", target.insns.indexOf(context.node), context.node.getClass().getSimpleName(), + Bytecode.getOpcodeName(context.node.getOpcode())).hr() .kv("Match mode", this.discriminator.isImplicit(context) ? "IMPLICIT (match single)" : "EXPLICIT (match by criteria)") .kv("Match ordinal", this.discriminator.getOrdinal() < 0 ? "any" : this.discriminator.getOrdinal()) .kv("Match index", this.discriminator.getIndex() < context.baseArgIndex ? "any" : this.discriminator.getIndex()) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ITargetSelector.java b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ITargetSelector.java index 4760ff22e..1dffd05b9 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ITargetSelector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ITargetSelector.java @@ -35,7 +35,7 @@ * defining the query parameters. They are used by injectors and other * components which need to identify a target element using a string. * - * Explicit Target Selectors
+ *Explicit Target Selectors
* *Explicit Target Selectors are handled internally using * {@link MemberInfo} structs, see the javadoc for {@link MemberInfo} for the @@ -43,6 +43,13 @@ */ public interface ITargetSelector { + /** + * Get the next target selector in this path (or null if this + * selector is the last selector in the chain. Called at recurse points in + * the subject in order to match against the child subject. + */ + public abstract ITargetSelector next(); + /** * Configure and return a modified version of this selector by consuming the * supplied arguments. Results from this method should be idempotent in diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java index ceee6ed41..3f93fe2c4 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java @@ -278,7 +278,7 @@ protected void readAnnotation() { protected Set
parseTargets(String type) { List methods = Annotations. getValue(this.annotation, "method", false); if (methods == null) { - throw new InvalidInjectionException(this, String.format("%s annotation on %s is missing method name", type, this.method.name)); + throw new InvalidInjectionException(this, String.format("%s annotation on %s is missing method name", type, this.methodName)); } Set selectors = new LinkedHashSet (); @@ -287,14 +287,14 @@ protected Set parseTargets(String type) { selectors.add(TargetSelector.parseAndValidate(method, this.mixin).attach(this.mixin)); } catch (InvalidMemberDescriptorException ex) { throw new InvalidInjectionException(this, String.format("%s annotation on %s, has invalid target descriptor: \"%s\". %s", type, - this.method.name, method, this.mixin.getReferenceMapper().getStatus())); + this.methodName, method, this.mixin.getReferenceMapper().getStatus())); } catch (TargetNotSupportedException ex) { throw new InvalidInjectionException(this, - String.format("%s annotation on %s specifies a target class '%s', which is not supported", type, this.method.name, + String.format("%s annotation on %s specifies a target class '%s', which is not supported", type, this.methodName, ex.getMessage())); } catch (InvalidSelectorException ex) { throw new InvalidInjectionException(this, - String.format("%s annotation on %s is decorated with an invalid selector: %s", type, this.method.name, + String.format("%s annotation on %s is decorated with an invalid selector: %s", type, this.methodName, ex.getMessage())); } } @@ -304,7 +304,7 @@ protected Set parseTargets(String type) { protected List readInjectionPoints(String type) { List ats = Annotations. getValue(this.annotation, this.atKey, false); if (ats == null) { - throw new InvalidInjectionException(this, String.format("%s annotation on %s is missing '%s' value(s)", type, this.method.name, + throw new InvalidInjectionException(this, String.format("%s annotation on %s is missing '%s' value(s)", type, this.methodName, this.atKey)); } return ats; @@ -385,17 +385,17 @@ public void postInject() { if ((this.mixin.getEnvironment().getOption(Option.DEBUG_INJECTORS) && this.injectedCallbackCount < this.expectedCallbackCount)) { throw new InvalidInjectionException(this, String.format("Injection validation failed: %s %s%s in %s expected %d invocation(s) but %d succeeded. %s%s", description, - this.method.name, this.method.desc, this.mixin, this.expectedCallbackCount, this.injectedCallbackCount, refMapStatus, + this.methodName, this.method.desc, this.mixin, this.expectedCallbackCount, this.injectedCallbackCount, refMapStatus, dynamicInfo)); } else if (this.injectedCallbackCount < this.requiredCallbackCount) { throw new InjectionError( String.format("Critical injection failure: %s %s%s in %s failed injection check, (%d/%d) succeeded. %s%s", description, - this.method.name, this.method.desc, this.mixin, this.injectedCallbackCount, this.requiredCallbackCount, refMapStatus, + this.methodName, this.method.desc, this.mixin, this.injectedCallbackCount, this.requiredCallbackCount, refMapStatus, dynamicInfo)); } else if (this.injectedCallbackCount > this.maxCallbackCount) { throw new InjectionError( String.format("Critical injection failure: %s %s%s in %s failed injection check, %d succeeded of %d allowed.%s", - description, this.method.name, this.method.desc, this.mixin, this.injectedCallbackCount, this.maxCallbackCount, dynamicInfo)); + description, this.methodName, this.method.desc, this.mixin, this.injectedCallbackCount, this.maxCallbackCount, dynamicInfo)); } } @@ -524,7 +524,7 @@ private void findMethods(Set selectors, String type) { if (this.targets.size() == 0) { throw new InvalidInjectionException(this, String.format("%s annotation on %s could not find any targets matching %s in the target class %s. %s%s", - type, this.method.name, InjectionInfo.namesOf(selectors), this.mixin.getTarget(), + type, this.methodName, InjectionInfo.namesOf(selectors), this.mixin.getTarget(), this.mixin.getReferenceMapper().getStatus(), this.getDynamicInfo())); } } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/MemberInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/MemberInfo.java index d1eb26bf0..e4a932fbe 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/MemberInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/MemberInfo.java @@ -261,6 +261,11 @@ private MemberInfo(MemberInfo original, String owner) { this.unparsed = null; } + @Override + public ITargetSelector next() { + return null; // Not supported yet + } + @Override public String getOwner() { return this.owner; diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/throwables/InvalidInjectionException.java b/src/main/java/org/spongepowered/asm/mixin/injection/throwables/InvalidInjectionException.java index 129e3ea43..b80ddb07e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/throwables/InvalidInjectionException.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/throwables/InvalidInjectionException.java @@ -26,6 +26,7 @@ import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.refmap.IMixinContext; +import org.spongepowered.asm.mixin.transformer.ActivityStack; import org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException; /** @@ -43,31 +44,61 @@ public InvalidInjectionException(IMixinContext context, String message) { this.info = null; } + public InvalidInjectionException(IMixinContext context, String message, ActivityStack activityContext) { + super(context, message, activityContext); + this.info = null; + } + public InvalidInjectionException(InjectionInfo info, String message) { super(info.getContext(), message); this.info = info; } + public InvalidInjectionException(InjectionInfo info, String message, ActivityStack activityContext) { + super(info.getContext(), message, activityContext); + this.info = info; + } + public InvalidInjectionException(IMixinContext context, Throwable cause) { super(context, cause); this.info = null; } + public InvalidInjectionException(IMixinContext context, Throwable cause, ActivityStack activityContext) { + super(context, cause, activityContext); + this.info = null; + } + public InvalidInjectionException(InjectionInfo info, Throwable cause) { super(info.getContext(), cause); this.info = info; } + public InvalidInjectionException(InjectionInfo info, Throwable cause, ActivityStack activityContext) { + super(info.getContext(), cause, activityContext); + this.info = info; + } + public InvalidInjectionException(IMixinContext context, String message, Throwable cause) { super(context, message, cause); this.info = null; } + public InvalidInjectionException(IMixinContext context, String message, Throwable cause, ActivityStack activityContext) { + super(context, message, cause, activityContext); + this.info = null; + } + public InvalidInjectionException(InjectionInfo info, String message, Throwable cause) { super(info.getContext(), message, cause); this.info = info; } + public InvalidInjectionException(InjectionInfo info, String message, Throwable cause, ActivityStack activityContext) { + super(info.getContext(), message, cause, activityContext); + this.info = info; + } + public InjectionInfo getInjectionInfo() { return this.info; } diff --git a/src/main/java/org/spongepowered/asm/mixin/struct/SpecialMethodInfo.java b/src/main/java/org/spongepowered/asm/mixin/struct/SpecialMethodInfo.java index e6f2a0138..457df09c2 100644 --- a/src/main/java/org/spongepowered/asm/mixin/struct/SpecialMethodInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/struct/SpecialMethodInfo.java @@ -30,6 +30,7 @@ import org.spongepowered.asm.mixin.injection.IInjectionPointContext; import org.spongepowered.asm.mixin.refmap.IMixinContext; import org.spongepowered.asm.mixin.transformer.MixinTargetContext; +import org.spongepowered.asm.util.asm.MethodNodeEx; /** * Information about a special mixin method such as an injector or accessor @@ -50,6 +51,11 @@ public abstract class SpecialMethodInfo implements IInjectionPointContext { * Annotated method */ protected final MethodNode method; + + /** + * Original name of the method, if available + */ + protected final String methodName; /** * Mixin data @@ -61,6 +67,7 @@ public SpecialMethodInfo(MixinTargetContext mixin, MethodNode method, Annotation this.method = method; this.annotation = annotation; this.classNode = mixin.getTargetClassNode(); + this.methodName = method instanceof MethodNodeEx ? ((MethodNodeEx)this.method).getOriginalName() : method.name; } /** @@ -101,5 +108,12 @@ public final ClassNode getClassNode() { public final MethodNode getMethod() { return this.method; } + + /** + * Get the original name of the method, if available + */ + public String getMethodName() { + return this.methodName; + } } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java index ae53c0b96..141d4f2b4 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java @@ -69,6 +69,7 @@ import org.spongepowered.asm.util.Annotations; import org.spongepowered.asm.util.Bytecode; import org.spongepowered.asm.util.asm.ASM; +import org.spongepowered.asm.util.asm.MethodNodeEx; import org.spongepowered.asm.util.perf.Profiler; import org.spongepowered.asm.util.perf.Profiler.Section; @@ -84,22 +85,10 @@ class MixinInfo implements Comparable , IMixinInfo { /** * A MethodNode in a mixin */ - class MixinMethodNode extends MethodNode { - - private final String originalName; + class MixinMethodNode extends MethodNodeEx { public MixinMethodNode(int access, String name, String desc, String signature, String[] exceptions) { - super(ASM.API_VERSION, access, name, desc, signature, exceptions); - this.originalName = name; - } - - @Override - public String toString() { - return String.format("%s%s", this.originalName, this.desc); - } - - public String getOriginalName() { - return this.originalName; + super(access, name, desc, signature, exceptions, MixinInfo.this); } public boolean isInjector() { @@ -121,10 +110,6 @@ public AnnotationNode getVisibleAnnotation(Class extends Annotation> annotatio public AnnotationNode getInjectorAnnotation() { return InjectionInfo.getInjectorAnnotation(MixinInfo.this, this); } - - public IMixinInfo getOwner() { - return MixinInfo.this; - } } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionClassExporter.java b/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionClassExporter.java index 038a8586b..2c40ee97c 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionClassExporter.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionClassExporter.java @@ -158,7 +158,7 @@ public File dumpClass(String fileName, ClassNode classNode) { File outputFile = new File(this.classExportDir, fileName + ".class"); outputFile.getParentFile().mkdirs(); try { - byte[] bytecode = ExtensionClassExporter.getClassBytes(classNode); + byte[] bytecode = ExtensionClassExporter.getClassBytes(classNode, true); if (bytecode != null) { Files.write(bytecode, outputFile); } @@ -168,12 +168,22 @@ public File dumpClass(String fileName, ClassNode classNode) { return outputFile; } - private static byte[] getClassBytes(ClassNode classNode) { + private static byte[] getClassBytes(ClassNode classNode, boolean computeFrames) { byte[] bytes = null; try { - MixinClassWriter cw = new MixinClassWriter(ClassWriter.COMPUTE_FRAMES); + MixinClassWriter cw = new MixinClassWriter(computeFrames ? ClassWriter.COMPUTE_FRAMES : 0); classNode.accept(cw); bytes = cw.toByteArray(); + } catch (NegativeArraySizeException ex) { + // Try again with compute frames turned off, this gives us a better chance + // of successful export when the class is corrupt which - given we are + // exporting for debugging purposes - is worthwhile so that we have a + // the bytecode to inspect! + if (computeFrames) { + ExtensionClassExporter.logger.warn("Exporting class {} with COMPUTE_FRAMES failed! Trying a raw export.", classNode.name); + return ExtensionClassExporter.getClassBytes(classNode, false); + } + ex.printStackTrace(); } catch (Exception ex) { // well, damn ex.printStackTrace(); diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinInner.java b/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinInner.java index 8eb18428b..b9372f7b4 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinInner.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinInner.java @@ -24,7 +24,6 @@ */ package org.spongepowered.asm.mixin.transformer.meta; -import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -33,7 +32,7 @@ * Decoration annotation used by the mixin inner class generator to mark inner * classes which have been generated from an existing inner class in a mixin */ -@Target(ElementType.TYPE) +@Target({ /* No targets allowed */ }) @Retention(RetentionPolicy.CLASS) public @interface MixinInner { diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinMerged.java b/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinMerged.java index b38aac7c4..ebdf19857 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinMerged.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinMerged.java @@ -24,7 +24,6 @@ */ package org.spongepowered.asm.mixin.transformer.meta; -import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -36,7 +35,7 @@ * Decoration annotation used by the mixin applicator to mark methods in a * class which have been added or overwritten by a mixin.
*/ -@Target({ ElementType.METHOD }) +@Target({ /* No targets allowed */ }) @Retention(RetentionPolicy.RUNTIME) public @interface MixinMerged { diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinProxy.java b/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinProxy.java index 1fa786808..a3c57ed55 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinProxy.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinProxy.java @@ -24,7 +24,6 @@ */ package org.spongepowered.asm.mixin.transformer.meta; -import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -33,7 +32,7 @@ * Decoration annotation used by postprocessor to mark methods which have had * their contents replaced with a proxy. */ -@Target({ ElementType.METHOD }) +@Target({ /* No targets allowed */ }) @Retention(RetentionPolicy.RUNTIME) public @interface MixinProxy { diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinRenamed.java b/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinRenamed.java index 7a5634cf7..c24fe140e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinRenamed.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/meta/MixinRenamed.java @@ -24,7 +24,6 @@ */ package org.spongepowered.asm.mixin.transformer.meta; -import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @@ -35,7 +34,7 @@ *Decoration annotation used by MixinInfo and MixinPreProcessor to mark * methods in a mixin which have been renamed prior to being merged.
*/ -@Target({ ElementType.METHOD }) +@Target({ /* No targets allowed */ }) @Retention(RetentionPolicy.RUNTIME) public @interface MixinRenamed { diff --git a/src/main/java/org/spongepowered/asm/util/Annotations.java b/src/main/java/org/spongepowered/asm/util/Annotations.java index 91c88b7b5..e50725f79 100644 --- a/src/main/java/org/spongepowered/asm/util/Annotations.java +++ b/src/main/java/org/spongepowered/asm/util/Annotations.java @@ -257,10 +257,14 @@ public static AnnotationNode getInvisible(ClassNode classNode, Class extends A * * @param method Source method * @param annotationClass Type of annotation to search for - * @param paramIndex Index of the parameter to fetch annotation for + * @param paramIndex Index of the parameter to fetch annotation for, or the + * method itself if less than zero * @return the annotation, or null if not present */ public static AnnotationNode getVisibleParameter(MethodNode method, Class extends Annotation> annotationClass, int paramIndex) { + if (paramIndex < 0) { + return Annotations.getVisible(method, annotationClass); + } return Annotations.getParameter(method.visibleParameterAnnotations, Type.getDescriptor(annotationClass), paramIndex); } @@ -270,10 +274,14 @@ public static AnnotationNode getVisibleParameter(MethodNode method, Class exte * * @param method Source method * @param annotationClass Type of annotation to search for - * @param paramIndex Index of the parameter to fetch annotation for + * @param paramIndex Index of the parameter to fetch annotation for, or the + * method itself if less than zero * @return the annotation, or null if not present */ public static AnnotationNode getInvisibleParameter(MethodNode method, Class extends Annotation> annotationClass, int paramIndex) { + if (paramIndex < 0) { + return Annotations.getInvisible(method, annotationClass); + } return Annotations.getParameter(method.invisibleParameterAnnotations, Type.getDescriptor(annotationClass), paramIndex); } @@ -506,12 +514,16 @@ public static void setValue(AnnotationNode annotation, String key, Object value) } int existingIndex = 0; - for (int pos = 0; pos < annotation.values.size() - 1; pos += 2) { - String keyName = annotation.values.get(pos).toString(); - if (key.equals(keyName)) { - existingIndex = pos + 1; - break; + if (annotation.values != null) { + for (int pos = 0; pos < annotation.values.size() - 1; pos += 2) { + String keyName = annotation.values.get(pos).toString(); + if (key.equals(keyName)) { + existingIndex = pos + 1; + break; + } } + } else { + annotation.values = new ArrayList