/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.spongepowered.asm.mixin.injection; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; /** * A Slice identifies a section of a method to search for injection * points. * *

Using slices provides for a much more expressive way of identifying target * injection points in a method, which has two advantages:

* *
    *
  1. Encoding assumptions about the structure of a method makes it easier to * detect (and fail-fast) when a method has changed in an unexpected way.
  2. *
  3. Injection points using slices are less brittle than points specified * with large ordinals because they allow injection points to be specified * with reference to characteristics of the method rather than arbitrary * ordinal values.
  4. *
* *

Consider the following example:

* *
 * private void foo(Bar bar) {
 *     bar.update();
 *     List<Thing> list = bar.getThings();
 *     for (Thing thing : list) {
 *         thing.processStuff();
 *     }
 *     
 *     Thing specialThing = bar.getSpecialThing();
 *     if (specialThing.isReady()) {
 *         specialThing.processStuff();
 *         bar.update();
 *     }
 *     
 *     bar.notifyFoo();
 * }
* *

Let's assume we are interested in applying a {@link Redirect} to the * processStuff() method call within the if block. Using * ordinal we can achieve this as follows:

* *
 * @At(value = "INVOKE", target = "processStuff", ordinal = 1)
*
* *

This will work fine initially. However consider the case that in a future * version of the target library the method is altered and an additional call to * processStuff() is added:

* *
 * private void foo(Bar bar) {
 *     bar.update();
 *     List<Thing> list = bar.getThings();
 *     for (Thing thing : list) {
 *         thing.processStuff();
 *     }
 *     
 *     // A new call to processStuff, which now has the ordinal 1
 *     bar.getActiveThing().processStuff();
 *     
 *     Thing specialThing = bar.getSpecialThing();
 *     if (specialThing.isReady()) {
 *         specialThing.processStuff();
 *         bar.update();
 *     }
 *     
 *     bar.notifyFoo();
 * }
* *

It's still pretty easy for a human to identify the correct point since the * original if hasn't changed. However the use of ordinals means that * the injection is now wrong and must be fixed by hand.

* *

We can make the injection point more expressive and reliable by using a * Slice. We know in this example that the call to processStuff() * we want is the one immediately after a call to isReady(). We * can slice the method at the call to isReady() and modify * the injector accordingly:

* *
 * @Inject(
 *     slice = @Slice(
 *         from = @At(value = "INVOKE", target = "isReady")
 *     )
 *     at = @At(value = "INVOKE", target = "processStuff", ordinal = 0)
 * )
* *

The ordinal is specified as 0 because the scope of the * injection point is now the region of the method defined by the slice * .

* *

Slices can be specified using a {@link #from} point, a {@link #to} point, * or both. {@link Inject Callback Injectors} using multiple injection points * 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 { /** * The identifier for this slice, specified using the {@link At#slice} value * (if omitted, this slice becomes the default slice and applies to * all undecorated {@link At} queries). * *

This value can be safely ignored for injector types which only accept * a single query (eg. {@link Redirect} and others). However since * {@link Inject} injectors can have multiple {@link At} queries, it may be * desirable to have multiple slices as well. When specifying multiple * slices, each should be designed a unique id which can then be * referenced in the corresponding At.

* *

There are no specifications or restrictions for valid ids, * however it is recommended that the id in some way describe the * slice, thus allowing the At query to be read without necessarily * reading the slice itself. For example, if the slice is selecting all * instructions before the first call to some method init, * then using an id "beforeInit makes sense. Using before, * after and between prefixes as a loose standard is * considered good practice.

* *

This value defaults to an empty string, the empty string is used as a * default identifier throughout the injection subsystem, and any {@link At} * which doesn't specify a slice explicitly will use this identifer. * Specifying id or slice for injectors which only support * a single slice is ignored internally, so you may use this field to give a * descriptive name to the slice if you wish.

* * @return The identifier for this slice */ public String id() default ""; /** * Injection point which specifies the start of the slice region. * {@link At}s supplied here should generally use a {@link Specifier} * in order to identify which instruction should be used for queries which * return multiple results. The specifier is supplied by appending the * specifier type to the injection point type as follows: * *
@At(value = "INVOKE:LAST", ... )
* *

If from is not supplied then {@link #to} must be supplied. It * is allowed to specify both from and to as long * as the points select a region with positive size (eg the insn returned by * to must appear after that selected by from in the * method body).

* * @return the start point of the slice */ public At from() default @At("HEAD"); /** * Injection point which specifies the end of the slice region. * Like {@link #from}, {@link At}s supplied here should generally specify a * {@link Specifier} in order to identify which instruction should be used * for queries which return multiple results. The specifier is supplied by * appending the specifier type to the injection point type as follows: * *
@At(value = "INVOKE:LAST", ... )
* *

If to is not supplied then {@link #from} must be supplied. It * is allowed to specify both from and to as long * as the points select a region with positive size (eg the insn returned by * to must appear after that selected by from in * the method body).

* * @return the start point of the slice */ public At to() default @At("TAIL"); }