@Target(value=METHOD)
@Retention(value=RUNTIME)
public @interface Redirect
Specifies that this mixin method should redirect the specified 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 object instance the method was going to be invoked on. For example when hooking the following call:
public void baz(int someInt, String someString) {
int abc = 0;
int def = 1;
Foo someObject = new Foo();
// Hooking this method
boolean xyz = someObject.bar(abc, def);
}
The signature of the redirected method should be:
public boolean barProxy(Foo someObject, int abc, int def)
For obvious reasons this does not apply for static methods, for static 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 addition to the arguments being passed to the method call (for example in the code above this would be the someInt and someString arguments) by appending the arguments to the method signature:
public boolean barProxy(Foo someObject, int abc, int def,
int someInt, String someString)
All arguments of a method redirect handler, and the return type, can be
decorated with 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 field read (GETFIELD, GETSTATIC).
| Operation (OPCODE) | Handler signature |
|---|---|
| Read static field (GETSTATIC) | private static FieldType getFieldValue() |
| Read instance field (GETFIELD) | private FieldType getFieldValue(OwnerType
owner) |
| Write static field (PUTSTATIC) | private static void setFieldValue(FieldType value)
|
| Write instance field (PUTFIELD) | private void setFieldValue(OwnerType
owner, FieldType value) |
It is also possible to capture the arguments of the target method in addition to the arguments being passed to the method call (for example in the code above this would be the someInt and someString arguments) by appending the arguments to the method signature.
All arguments of a field redirect handler, including the field type
itself, can be decorated with 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 possible to redirect access to individual array elements. Consider the following example:
private String[] strings = { "foo", "bar", "baz" };
public void print(int index) {
System.err.println(this.strings[index]);
}
It may be desirable to redirect the access to this array element. To do so, declare a redirect handler which takes the array and the index (or indices, for multi-dimensional arrays) and returns the element type (in this case String) as follows:
| Operation | Handler signature |
|---|---|
| Read element | private ElementType getElement(ElementType[]
array, int index) |
| Write element | private void setElement(ElementType[] array, int index,
ElementType value) |
The handler receives a reference to the array itself (read from the field)
and the indices being accessed. See the
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 array=length and configure your handler signature to return int and consume the array as an argument:
private int getLength(ElementType[] array)
For multi-dimensional arrays, provide one int argument for each additional dimension of the array:
// Multidimensional array
private ElementType[][] array;
// Handler signature:
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 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 BeforeNew differs from 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 object creation call:
public void exampleFunc(String someString, int dx, int dy) {
// Hooking this constructor
Foo someFoo = new Foo(dx * 10, dy * 10);
}
The signature of the handler method should be:
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:
public Foo constructFoo(int x, int y, String someString, int dx, int dy)
An instanceof check is a boolean operation where an object on the stack is compared with a class literal compiled directly into the bytecode. The check can be redirected in one of two ways: logic redirection or class type redirection. For the following example code:
if (reference instanceof Foo) {
To perform a logic redirection, use a handler with the following signature returning boolean:
public boolean onInstanceOf(Object reference, Class clFoo) {
return reference instanceof ADifferentClass;
}
Note that the entire check is moved into the redirect method, and the boolean return value is used in place of the previous instanceof check. To perform a class type redirection instead, construct your handler as follows:
public Class onInstanceOf(Object reference, Class clFoo) {
return ADifferentClass.class;
}
Note that this results in the injected code being less efficient, because the instanceof opcode is replaced with discrete code similar to reference != null && yourClass.isAssignableFrom(reference.getClass()) in the target method body.
The arguments to an instanceof redirect handler must always be Object, Class even if the variable type is known. This is because the type of variable on the stack cannot be determined without relatively expensive analysis, which is redundant given the redirect handler's role. If you need to cast-down the reference then you should do so inside your handler method.
In general, when declaring a redirect handler the static modifier of the handler method must always match the target method. The exception to this rule is application of @Redirect to instructions which are effectively before the call to super() in a constructor.
Consider the following code:
class Foo : Bar {
Foo(int arg) {
super(Foo.isEven(arg));
}
static int isEven(int arg) {
return arg % 2 == 0;
}
}
In this example, the inline call to Foo.isEven takes place before the call to super() and (per Java rules) this method must be static (because the instance (this) is unavailable until the initialiser is complete. Thus if you wish to @Redirect the call to isEven then the handler method must also be static. The injection subsystem will raise an exception for non-static handlers in this situation to indicate that the staticness of the handler is incorrect.
| Modifier and Type | Required Element and Description |
|---|---|
At |
at
An
At annotation which describes the InjectionPoint in
the target method. |
java.lang.String[] |
method
String representation of one or more
target selectors which identify the target
methods. |
| Modifier and Type | Optional Element and Description |
|---|---|
int |
allow
Injection points are in general expected to match every candidate
instruction in the target method or slice, except in cases where options
such as
At.ordinal() are specified which naturally limit the number
of results. |
java.lang.String |
constraints
Returns constraints which must be validated for this injector to
succeed.
|
int |
expect
Like
require() but only enabled if the
mixin.debug.countInjections option is set
to true and defaults to 1. |
boolean |
remap
|
int |
require
In general, injectors are intended to "fail soft" in that a failure to
locate the injection point in the target method is not considered an
error condition.
|
Slice |
slice
|
public abstract java.lang.String[] method
target selectors which identify the target
methods.public abstract At at
At annotation which describes the InjectionPoint in
the target method. The specified InjectionPoint must only
return MethodInsnNode and an
exception will be thrown if this is not the case.At which identifies the target method invocationpublic abstract Slice slice
public abstract boolean remap
Redirect methods since it is
anticipated that in general the target of a Redirect annotation
will be an obfuscated method in the target class. However since it is
possible to also apply mixins to non-obfuscated targets (or non-
obfuscated methods in obfuscated targets, such as methods added by Forge)
it may be necessary to suppress the compiler error which would otherwise
be generated. Setting this value to false will cause the
annotation processor to skip this annotation when attempting to build the
obfuscation table for the mixin.public abstract int require
However, this behaviour is not always desirable. For example, if your
application depends on a particular injection succeeding you may wish to
detect the injection failure as an error condition. This argument is thus
provided to allow you to stipulate a minimum number of successful
injections for this callback handler. If the number of injections
specified is not achieved then an InjectionError is thrown at
application time. Use this option with care.
public abstract int expect
require() but only enabled if the
mixin.debug.countInjections option is set
to true and defaults to 1. Use this option during debugging to
perform simple checking of your injectors. Causes the injector to throw
a InvalidInjectionException if the expected number of injections
is not realised.public abstract int allow
At.ordinal() are specified which naturally limit the number
of results.
This option allows for sanity-checking to be performed on the results
of an injection point by specifying a maximum allowed number of matches,
similar to that afforded by Group.max(). For example if your
injection is expected to match 4 invocations of a target method, but
instead matches 5, this can become a detectable tamper condition by
setting this value to 4.
Setting any value 1 or greater is allowed. Values less than 1 or less
than require() are ignored. require() supercedes this
argument such that if allow is less than require the
value of require is always used.
Note that this option is not a limit on the query behaviour of this injection point. It is only a sanity check used to ensure that the number of matches is not too high
public abstract java.lang.String constraints
ConstraintParser.Constraint for details of constraint formats.