/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered <https://www.spongepowered.org> * 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 org.spongepowered.asm.mixin.injection.InjectionPoint.Selector; /** * A <tt>Slice</tt> identifies a section of a method to search for injection * points. * * <p>Using slices provides for a much more expressive way of identifying target * injection points in a method, which has two advantages:</p> * * <ol> * <li>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.</li> * <li>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.</li> * </ol> * * <p>Consider the following example:</p> * * <blockquote><pre> * private void foo(Bar bar) { * bar.update(); * List<Thing> list = bar.getThings(); * for (Thing thing : list) { * thing.<b>processStuff</b>(); * } * * Thing specialThing = bar.getSpecialThing(); * if (specialThing.isReady()) { * specialThing.<b>processStuff</b>(); * bar.update(); * } * * bar.notifyFoo(); * }</pre></blockquote> * * <p>Let's assume we are interested in applying a {@link Redirect} to the * <tt>processStuff()</tt> method call within the <tt>if</tt> block. Using * <tt>ordinal</tt> we can achieve this as follows:</p> * * <blockquote><pre> * @At(value = "INVOKE", target = "processStuff", ordinal = 1)</pre> * </blockquote> * * <p>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 * <tt>processStuff()</tt> is added:</p> * * <blockquote><pre> * private void foo(Bar bar) { * bar.update(); * List<Thing> list = bar.getThings(); * for (Thing thing : list) { * thing.<b>processStuff</b>(); * } * * // A new call to processStuff, which now has the ordinal 1 * bar.getActiveThing().<b>processStuff</b>(); * * Thing specialThing = bar.getSpecialThing(); * if (specialThing.isReady()) { * specialThing.<b>processStuff</b>(); * bar.update(); * } * * bar.notifyFoo(); * }</pre></blockquote> * * <p>It's still pretty easy for a human to identify the correct point since the * original <tt>if</tt> hasn't changed. However the use of ordinals means that * the injection is now wrong and must be fixed by hand.</p> * * <p>We can make the injection point more expressive and reliable by using a * <tt>Slice</tt>. We know in this example that the call to <tt>processStuff() * </tt> we want is the one immediately after a call to <tt>isReady()</tt>. We * can <em>slice</em> the method at the call to <tt>isReady()</tt> and modify * the injector accordingly:</p> * * <blockquote><pre> * @Inject( * slice = @Slice( * from = @At(value = "INVOKE", target = "isReady") * ) * at = @At(value = "INVOKE", target = "processStuff", ordinal = 0) * )</pre></blockquote> * * <p>The <tt>ordinal</tt> is specified as <tt>0</tt> because the scope of the * injection point is now the region of the method defined by the <em>slice</em> * .</p> * * <p>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.</p> */ @Retention(RetentionPolicy.RUNTIME) public @interface Slice { /** * The identifier for this slice, specified using the {@link At#slice} value * (if omitted, this slice becomes the <em>default slice</em> and applies to * all undecorated {@link At} queries). * * <p>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 <tt>id</tt> which can then be * referenced in the corresponding <tt>At</tt>.</p> * * <p>There are no specifications or restrictions for valid <tt>id</tt>s, * however it is recommended that the <tt>id</tt> in some way describe the * slice, thus allowing the <tt>At</tt> query to be read without necessarily * reading the slice itself. For example, if the slice is selecting <em>all * instructions before the first call to some method <tt>init</tt></em>, * then using an id <tt>"beforeInit</tt> makes sense. Using <em>before, * after</em> and <em>between</em> prefixes as a loose standard is * considered good practice.</p> * * <p>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 <tt>id</tt> or <tt>slice</tt> 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.</p> * * @return The identifier for this slice */ public String id() default ""; /** * Injection point which specifies the <em>start</em> of the slice region. * {@link At}s supplied here should generally specify a {@link Selector} * in order to identify which instruction should be used for queries which * return multiple results. The selector is specified by appending the * selector type to the injection point type as follows: * * <blockquote><pre>@At(value = "INVOKE:LAST", ... )</pre></blockquote> * * <p>If <tt>from</tt> is not supplied then {@link #to} must be supplied. It * is allowed to specify <b>both</b> <tt>from</tt> and <tt>to</tt> as long * as the points select a region with positive size (eg the insn returned by * <tt>to</tt> must appear after that selected by <tt>from</tt> in the * method body).</p> * * @return the start point of the slice */ public At from() default @At("HEAD"); /** * Injection point which specifies the <em>end</em> of the slice region. * Like {@link #from}, {@link At}s supplied here should generally specify a * {@link Selector} in order to identify which instruction should be used * for queries which return multiple results. The selector is specified by * appending the selector type to the injection point type as follows: * * <blockquote><pre>@At(value = "INVOKE:LAST", ... )</pre></blockquote> * * <p>If <tt>to</tt> is not supplied then {@link #from} must be supplied. It * is allowed to specify <b>both</b> <tt>from</tt> and <tt>to</tt> as long * as the points select a region with positive size (eg the insn returned by * <tt>to</tt> must appear <em>after</em> that selected by <tt>from</tt> in * the method body).</p> * * @return the start point of the slice */ public At to() default @At("TAIL"); }