Skip to content

Commit

Permalink
Overhaul RedirectInjector for more consistent coerce behaviour
Browse files Browse the repository at this point in the history
Also improve some javadoc. Closes SpongePowered#350
  • Loading branch information
Mumfrey committed Oct 23, 2019
1 parent 03e8ad5 commit 7b37a68
Show file tree
Hide file tree
Showing 32 changed files with 856 additions and 326 deletions.
1 change: 1 addition & 0 deletions src/main/java/org/spongepowered/asm/mixin/Implements.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,5 @@
* @return list of interfaces to implement
*/
public Interface[] value();

}
6 changes: 3 additions & 3 deletions src/main/java/org/spongepowered/asm/mixin/Interface.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@
*/
package org.spongepowered.asm.mixin;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* I'm probably going to the special hell for this
* This annotation is used to define information for a soft-implemented
* interface. See {@link Implements} for details.
*/
@Target(ElementType.TYPE)
@Target({ /* No targets allowed */ })
@Retention(RetentionPolicy.CLASS)
public @interface Interface {

Expand Down
14 changes: 14 additions & 0 deletions src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java
Original file line number Diff line number Diff line change
Expand Up @@ -1029,6 +1029,20 @@ public List<String> getMixinConfigs() {
return mixinConfigs;
}

/**
* Add a mixin configuration to the blackboard
*
* @param config Name of configuration resource to add
* @return fluent interface
* @deprecated use Mixins::addConfiguration instead
*/
@Deprecated
public MixinEnvironment addConfiguration(String config) {
MixinEnvironment.logger.warn("MixinEnvironment::addConfiguration is deprecated and will be removed. Use Mixins::addConfiguration instead!");
Mixins.addConfiguration(config, this);
return this;
}

void registerConfig(String config) {
List<String> configs = this.getMixinConfigs();
if (!configs.contains(config)) {
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/spongepowered/asm/mixin/Shadow.java
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,5 @@
* @return Aliases for this member
*/
public String[] aliases() default { };

}
2 changes: 2 additions & 0 deletions src/main/java/org/spongepowered/asm/mixin/injection/At.java
Original file line number Diff line number Diff line change
Expand Up @@ -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.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.points.*;
Expand All @@ -44,6 +45,7 @@
* should be consulted for the meaning of the argument to that particular class.
* A general description of each parameter is provided below.
*/
@Target({ /* No targets allowed */ })
@Retention(RetentionPolicy.RUNTIME)
public @interface At {

Expand Down
24 changes: 17 additions & 7 deletions src/main/java/org/spongepowered/asm/mixin/injection/Coerce.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,16 @@
* <p>This annotation has two usages applicable to Callback Injectors (defined
* using {@link Inject &#064;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.</p>
* types defined on the handler. It can also 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. It is also acceptable to use an interface directly or
* indirectly implemented by the relevant class, including interfaces applied by
* other mixins.</p>
*
* <p>During LVT generation it is not always possible to inflect the exact local
* <p><tt>&#064;Coerce</tt> 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
Expand All @@ -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.</p>
*
* <p>Since Mixin <tt>0.8</tt> it is also possible to use <tt>&#064;Coerce</tt>
* on the return type of a <em>field gettter</em> or <em>method invocation</em>
* 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.</p>
*/
@Target({ ElementType.PARAMETER })
@Target({ ElementType.PARAMETER, ElementType.METHOD })
public @interface Coerce {

}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.objectweb.asm.Opcodes;

Expand All @@ -39,6 +40,7 @@
* appropriate argument. Specifying values of different types will cause an
* error to be raised by the injector.</p>
*/
@Target({ /* No targets allowed */ })
@Retention(RetentionPolicy.RUNTIME)
public @interface Constant {

Expand Down
67 changes: 67 additions & 0 deletions src/main/java/org/spongepowered/asm/mixin/injection/Inject.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -41,6 +42,65 @@
* Specifies that this mixin method should inject a callback (or
* callback<b>s</b>) to itself in the target method(s) identified by
* {@link #method}.
*
* <p>Callbacks are simple injectors which simply inject a call to the decorated
* method (the <em>handler</em>) in the <em>target</em> method (or methods)
* selected by the selectors specified in {@link #method}. Callback Injectors
* can also capture arguments and local variables from the <em>target</em> for
* use in the handler.</p>
*
* <p>Callback handler methods should always return <tt>void</tt> and should
* have the same <tt>static</tt>-ness as their target (though it is allowable to
* have a <tt>static</tt> callback injected into an instance method, and for
* obvious reasons the inverse is not permitted).</p>
*
* <h3>Basic usage</h3>
*
* <p>The simplest usage of <tt>&#064;Inject</tt> 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.</p>
*
* <blockquote><tt>private void onSomeEvent(CallbackInfo ci)</tt></blockquote>
*
* <h3>Capture target arguments</h3>
*
* <p>Callbacks can also capture the arguments passed to the target method. To
* do so specify the target arguments before the {@link CallbackInfo}:</p>
*
* <blockquote><tt>private void onSomeEvent(int arg1, String arg2,
* CallbackInfo ci)</tt></blockquote>
*
* <h3>Surrogate methods</h3>
*
* <p>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 <em>surrogate</em> 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 <em>local capture</em> (see below) is
* known to change between different environments or injection points.</p>
*
* <h3>Capture local variables</h3>
*
* <p>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:</p>
*
* <ol>
* <li>Set the {@link #locals()} value of your injection to
* {@link LocalCapture#PRINT} and run the application.</li>
* <li>When the injector is processed, a listing of the LVT is produced
* accompanied by a generated signature for your handler method including
* the discovered args. Modify your handler signature accordingly.</li>
* </ol>
*
* <p>For more details see {@link #locals()}.</p>
*
*/
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
Expand Down Expand Up @@ -114,6 +174,13 @@
* surrogate targets for the orphaned injector by annotating them with an
* {@link Surrogate} annotation.</p>
*
* <p>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 <tt>&lt;int, int, int, float, String&gt;</tt> and
* you only need the <tt>float</tt> value, you can choose to omit the unused
* <tt>String</tt> and changes to the LVT beyond that point will not affect
* your injection.</p>
*
* <p>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
Expand Down
87 changes: 71 additions & 16 deletions src/main/java/org/spongepowered/asm/mixin/injection/Redirect.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -41,7 +43,7 @@
* method call, field access or object construction (via the <tt>new</tt>
* keyword) to the method decorated with this annotation.</p>
*
* <h4>Method Redirect Mode</h4>
* <h3>Method Redirect Mode</h3>
*
* <p>The handler method signature must match the hooked method precisely
* <b>but</b> prepended with an arg of the owning object's type to accept the
Expand All @@ -65,7 +67,8 @@
* </blockquote>
*
* <p>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.
* </p>
*
* <p>It is also possible to capture the arguments of the target method in
Expand All @@ -78,7 +81,12 @@
* int someInt, String someString)</pre>
* </blockquote>
*
* <h4>Field Access Redirect Mode</h4>
* <p>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) <tt>Object</tt>.
* </p>
*
* <h3>Field Access Redirect Mode</h3>
*
* <p>The handler method signature varies depending on whether the redirector is
* handling a field <b>write</b> (<tt>PUTFIELD</tt>, <tt>PUTSTATIC</tt>) or a
Expand All @@ -91,21 +99,22 @@
* </tr>
* <tr>
* <td>Read static field (<tt>GETSTATIC</tt>)</td>
* <td><code>private <b>FieldType</b> getFieldValue()</code></td>
* <td><code>private static <b>FieldType</b> getFieldValue()</code></td>
* </tr>
* <tr>
* <td>Read instance field (<tt>GETFIELD</tt>)</td>
* <td><code>private <b>FieldType</b> getFieldValue(<b>OwnerType</b>
* owner)</code></td>
* owner)</code></td>
* </tr>
* <tr>
* <td>Write static field (<tt>PUTSTATIC</tt>)</td>
* <td><code>private void setFieldValue(<b>FieldType</b> value)</code></td>
* <td><code>private static void setFieldValue(<b>FieldType</b> value)
* </code></td>
* </tr>
* <tr>
* <td>Write instance field (<tt>PUTFIELD</tt>)</td>
* <td><code>private void setFieldValue(<b>OwnerType</b>
* owner, <b>FieldType</b> value)</code></td>
* owner, <b>FieldType</b> value)</code></td>
* </tr>
* </table>
*
Expand All @@ -114,7 +123,13 @@
* the code above this would be the <em>someInt</em> and <em>someString</em>
* arguments) by appending the arguments to the method signature.</p>
*
* <h4>Array Element Access Redirect Mode</h4>
* <p>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) <tt>Object</tt>.
* </p>
*
* <h3>Array Element Access Redirect Mode</h3>
*
* <p>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
Expand Down Expand Up @@ -155,7 +170,7 @@
* {@link BeforeFieldAccess BeforeFieldAccess args} for details
* on matching array accesses using the <tt>FIELD</tt> injection point.</p>
*
* <h4>Array Length Redirect Mode</h4>
* <h3>Array Length Redirect Mode</h3>
*
* <p>For fields of an array type, it is possible to redirect the call to the
* builtin pseudo-property <tt>length</tt>. To do so, specify the argument
Expand All @@ -174,28 +189,68 @@
* private <b>int</b> getLength(<b>ElementType</b>[][] array, int baseDim)
* </code></blockquote>
*
* <h4>Constructor Redirect Mode</h4>
* <h3>New Object Redirect Mode (Factory Mode)</h3>
*
* <p>Redirecting object creation requires redirection of the <tt>NEW</tt>
* operation and constructor call. Your handler method should target the <tt>NEW
* </tt> opcode using the {@link BeforeNew} Injection Point and construct an
* appropriate object. The factory method <b>must return a new object</b> and
* <b>must not</b> return <tt>null</tt> since the contract of the <tt>new</tt>
* opcode does not allow for the possibility of <tt>null</tt>. Your factory may
* throw an exception however.</p>
*
* <p>Note that {@link BeforeNew} differs from {@link BeforeInvoke} in that the
* shape of <tt>target</tt> should contain <em>only</em> an <tt>owner</tt> (to
* target any ctor of the specified class) or <em>only</em> a <tt>descriptor
* </tt> consisting of the constructor descriptor with the return type changed
* from <tt>void</tt> (<tt>V</tt>) to the type of object being constructed. In
* other words to redirect an object creation for an object <tt>Foo</tt>:</p>
*
* <blockquote><pre>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;
* }
*}</pre>
* </blockquote>
*
* <p>Valid signatures for this ctor would be:</p>
*
* <ul>
* <li><tt>Lbaz/bar/Foo;</tt> - valid because Foo only has one ctor</li>
* <li><tt>(II)Lbaz/bar/Foo;</tt> - includes signature of specific ctor</li>
* </ul>
*
* <p>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:</p>
* example to redirect the following object creation call:</p>
*
* <blockquote><pre>public void baz(int someInt, String someString) {
* <blockquote><pre>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);
*}</pre>
* </blockquote>
*
* <p>The signature of the handler method should be:</p>
*
* <blockquote>
* <pre>public Foo constructFoo(String arg1)</pre>
* <pre>public Foo constructFoo(int x, int y)</pre>
* </blockquote>
*
* <p>Note that like other redirectors, it is possible to capture the target
* method's arguments by appending them to the handler method's signature.</p>
* method's arguments by appending them to the handler method's signature:</p>
*
* <blockquote>
* <tt>public Foo constructFoo(int x, int y, String someString, int dx,
* int dy)</tt>
* </blockquote>
*
* <h4>A note on <tt>static</tt> modifiers for handler methods</h4>
* <h3>A note on <tt>static</tt> modifiers for handler methods</h3>
*
* <p>In general, when declaring a redirect handler the <tt>static</tt> modifier
* of the handler method must always match the target method. The exception to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -123,6 +124,7 @@
* can distinguish different slices using {@link #id} and then specify the slice
* to use in the {@link At#slice} argument.</p>
*/
@Target({ /* No targets allowed */ })
@Retention(RetentionPolicy.RUNTIME)
public @interface Slice {

Expand Down
Loading

0 comments on commit 7b37a68

Please sign in to comment.