Annotation Type Slice


  • @Target({})
    @Retention(RUNTIME)
    public @interface Slice
    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. 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.

    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 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 from() point, a to() point, or both. Callback Injectors using multiple injection points can distinguish different slices using id() and then specify the slice to use in the At.slice() argument.

    • Optional Element Summary

      Optional Elements 
      Modifier and Type Optional Element Description
      At from
      Injection point which specifies the start of the slice region.
      java.lang.String id
      The identifier for this slice, specified using the At.slice() value (if omitted, this slice becomes the default slice and applies to all undecorated At queries).
      At to
      Injection point which specifies the end of the slice region.
    • Element Detail

      • id

        java.lang.String id
        The identifier for this slice, specified using the At.slice() value (if omitted, this slice becomes the default slice and applies to all undecorated At queries).

        This value can be safely ignored for injector types which only accept a single query (eg. Redirect and others). However since Inject injectors can have multiple 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 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.

        Returns:
        The identifier for this slice
        Default:
        ""
      • from

        At from
        Injection point which specifies the start of the slice region. Ats supplied here should generally use a InjectionPoint.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 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).

        Returns:
        the start point of the slice
        Default:
        @org.spongepowered.asm.mixin.injection.At("HEAD")
      • to

        At to
        Injection point which specifies the end of the slice region. Like from(), Ats supplied here should generally specify a InjectionPoint.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 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).

        Returns:
        the start point of the slice
        Default:
        @org.spongepowered.asm.mixin.injection.At("TAIL")