/* * 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.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.ListIterator; import java.util.Locale; import java.util.Map; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodNode; import org.spongepowered.asm.mixin.FabricUtil; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.injection.callback.CallbackInjector; import org.spongepowered.asm.mixin.injection.modify.AfterStoreLocal; import org.spongepowered.asm.mixin.injection.modify.BeforeLoadLocal; import org.spongepowered.asm.mixin.injection.points.*; import org.spongepowered.asm.mixin.injection.struct.InjectionPointAnnotationContext; import org.spongepowered.asm.mixin.injection.struct.InjectionPointData; import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException; import org.spongepowered.asm.mixin.refmap.IMixinContext; import org.spongepowered.asm.mixin.struct.AnnotatedMethodInfo; import org.spongepowered.asm.mixin.transformer.MixinTargetContext; import org.spongepowered.asm.service.MixinService; import org.spongepowered.asm.util.Annotations; import org.spongepowered.asm.util.IMessageSink; import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; /** *

Base class for injection point discovery classes. Each subclass describes * a strategy for locating code injection points within an instruction list, * with the {@link #find find} method populating a collection with insn nodes * from the supplied list which satisfy its strategy.

* *

This base class also contains composite strategy factory methods such as * {@link #and and} and {@link #or or} which allow strategies to be combined * using intersection (and) or union (or) relationships to allow multiple * strategies to be easily combined.

* *

Built-in Injection Points

* *

The following built-in Injection Points are available:

* * * *

Additionally, the two special injection points are available which are * only supported for use with {@link ModifyVariable @ModifyVariable}:

* * * *

See the javadoc for each type for more details on the scheme used by each * injection point.

* *

Custom Injection Points

* *

You are free to create your own injection point subclasses. Once defined, * they can be used by your mixins in one of two ways:

* *
    *
  1. Specify the fully-qualified name of the injection point class in the * {@link At#value @At.value}.
  2. *
  3. Decorate your injection point class with {@link AtCode @AtCode} * annotation which specifies a namespace and shortcode for the injection * point, and register the class in your mixin config. You can then specify * the namespaced code (eg. MYMOD:CUSTOMPOINT) in {@link At#value * @At.value}.
  4. *
* *

When writing custom injection points, note that the general contract of * injection points is that they be entirely - or at least behaviourally - * stateless. It is allowed for a single InjectionPoint instance to be * used by the mixin processor for multiple injections and thus implementing * classes MUST NOT cache the insn list, event, or nodes instance * passed to the {@link #find find} method, as each call to {@link #find find} * must be considered a separate contract and the InjectionPoint's lifespan is * not linked to the discovery lifespan. It is therefore important that the * InjectionPoint implementation is fully stateless and that calls to * {@link #find find} are idempotent.

*/ public abstract class InjectionPoint { /** * Injection point code for {@link At} annotations to use */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface AtCode { /** * Namespace for this code. Final selectors will be specified as * <namespace>:<code> in order to avoid overlaps * between consumer-provided injection points. Uses namespace from * parent config if not specified. */ public String namespace() default ""; /** * The string code used to specify the annotated injection point in At * annotations, prefixed with namespace from the annotation or from the * declaring configuration. */ public String value(); } /** * Additional specifier for injection points. Specifiers can be * supplied in {@link At} annotations by including a colon (:) * character followed by the specifier type (case-sensitive), eg: * *
@At(value = "INVOKE:LAST", ... )
*/ public enum Specifier { /** * Use all instructions from the query result. */ ALL, /** * Use the first instruction from the query result. */ FIRST, /** * Use the last instruction from the query result. */ LAST, /** * The query must return exactly one instruction, if it returns * more than one instruction this should be considered a fail-fast error * state and a runtime exception will be thrown. */ ONE, /** * Use the default setting as defined by the consumer. For slices this * is {@link #FIRST}, for all other consumers this is {@link #ALL} */ DEFAULT; } /** * Target restriction level for different injection point types when used * by restricted injectors (eg. {@link Inject}). */ public enum RestrictTargetLevel { /** * Injection point is valid for instructions in methods only */ METHODS_ONLY, /** * Injection point is valid for instructions in methods and in * constructors but only after the delegate constructor call. */ CONSTRUCTORS_AFTER_DELEGATE, /** * Injection point is valid for instructions in both methods and * constructors, both before and after the delegate call */ ALLOW_ALL } /** * Behaviour for when the defined allowed value of {@link At#by} is exceeded */ enum ShiftByViolationBehaviour { /** * Shift-by violations are ignored */ IGNORE, /** * Shift-by violations cause a warning message */ WARN, /** * Shift-by violations throw an exception */ ERROR } /** * Boolean extensions for the At parser, mainly to avoid having to add many * booleans in the future */ public static final class Flags { /** * Change the default target restriction from METHODS_ONLY to ALLOW_ALL */ public static final int UNSAFE = 1; public static int parse(At at) { int flags = 0; if (at.unsafe()) { flags |= Flags.UNSAFE; } return flags; } public static int parse(AnnotationNode at) { int flags = 0; if (Annotations.getValue(at, "unsafe", Boolean.TRUE)) { flags |= InjectionPoint.Flags.UNSAFE; } return flags; } } /** * Initial limit on the value of {@link At#by} which triggers warning/error * (based on environment) */ public static final int DEFAULT_ALLOWED_SHIFT_BY = 0; /** * Hard limit on the value of {@link At#by} which triggers error */ public static final int MAX_ALLOWED_SHIFT_BY = 5; /** * Available injection point types */ private static Map> types = new HashMap>(); static { // Standard Injection Points InjectionPoint.registerBuiltIn(BeforeFieldAccess.class); InjectionPoint.registerBuiltIn(BeforeInvoke.class); InjectionPoint.registerBuiltIn(BeforeNew.class); InjectionPoint.registerBuiltIn(BeforeReturn.class); InjectionPoint.registerBuiltIn(BeforeStringInvoke.class); InjectionPoint.registerBuiltIn(JumpInsnPoint.class); InjectionPoint.registerBuiltIn(MethodHead.class); InjectionPoint.registerBuiltIn(AfterInvoke.class); InjectionPoint.registerBuiltIn(BeforeLoadLocal.class); InjectionPoint.registerBuiltIn(AfterStoreLocal.class); InjectionPoint.registerBuiltIn(BeforeFinalReturn.class); InjectionPoint.registerBuiltIn(BeforeConstant.class); InjectionPoint.registerBuiltIn(ConstructorHead.class); } private final String slice; private final Specifier specifier; private final String id; private final IMessageSink messageSink; private RestrictTargetLevel targetRestriction; protected InjectionPoint() { this("", Specifier.DEFAULT, null); } protected InjectionPoint(InjectionPointData data) { this(data.getSlice(), data.getSpecifier(), data.getId(), data.getMessageSink(), data.getTargetRestriction()); } public InjectionPoint(String slice, Specifier specifier, String id) { this(slice, specifier, id, null); } public InjectionPoint(String slice, Specifier specifier, String id, IMessageSink messageSink) { this(slice, specifier, id, messageSink, RestrictTargetLevel.METHODS_ONLY); } public InjectionPoint(String slice, Specifier specifier, String id, IMessageSink messageSink, RestrictTargetLevel targetRestriction) { this.slice = slice; this.specifier = specifier; this.id = id; this.messageSink = messageSink; this.targetRestriction = targetRestriction; } public String getSlice() { return this.slice; } public Specifier getSpecifier(Specifier defaultSpecifier) { return this.specifier == Specifier.DEFAULT ? defaultSpecifier : this.specifier; } public String getId() { return this.id; } /** * Notify method for subclasses to log when notable but non-fatal failures * occur, for example allows subclasses to add notes when they return no * results. * * @param format Message format * @param args Format args */ protected void addMessage(String format, Object... args) { if (this.messageSink != null) { this.messageSink.addMessage(format, args); } } /** * Runs a priority check in the context of this injection point. A priority * check should return true if the injection point is allowed to * inject given the relative priorities of the target (a method * merged by another mixin with targetPriority) and the incoming * mixin with priority mixinPriority. * * @param targetPriority Priority of the mixin which originally merged the * target method in question * @param mixinPriority Priority of the mixin which owns the owning injector * @return true if the priority check succeeds */ public boolean checkPriority(int targetPriority, int mixinPriority) { return targetPriority < mixinPriority; } /** * Set a new target restriction level for this injection point */ protected void setTargetRestriction(RestrictTargetLevel targetRestriction) { this.targetRestriction = targetRestriction; } /** * Returns the target restriction level for this injection point. This level * defines whether an injection point is valid in its current state when * being used by a restricted injector (currently {@link CallbackInjector}). * * @param context injection-specific context * @return restriction level */ public RestrictTargetLevel getTargetRestriction(IInjectionPointContext context) { return this.targetRestriction; } /** * Find injection points in the supplied insn list * * @param desc Method descriptor, supplied to allow return types and * arguments etc. to be determined * @param insns Insn list to search in, the strategy MUST ONLY add nodes * from this list to the {@code nodes} collection * @param nodes Collection of nodes to populate. Injectors should NOT make * any assumptions about the state of this collection and should only * call the add() method * @return true if one or more injection points were found */ public abstract boolean find(String desc, InsnList insns, Collection nodes); /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return String.format("@At(\"%s\")", this.getAtCode()); } /** * Get the insn immediately following the specified insn, or return the same * insn if the insn is the last insn in the list * * @param insns Insn list to fetch from * @param insn Insn node * @return Next insn or the same insn if last in the list */ protected static AbstractInsnNode nextNode(InsnList insns, AbstractInsnNode insn) { int index = insns.indexOf(insn) + 1; if (index > 0 && index < insns.size()) { return insns.get(index); } return insn; } /** * Composite injection point */ abstract static class CompositeInjectionPoint extends InjectionPoint { protected final InjectionPoint[] components; protected CompositeInjectionPoint(InjectionPoint... components) { if (components == null || components.length < 2) { throw new IllegalArgumentException("Must supply two or more component injection points for composite point!"); } this.components = components; } @Override public RestrictTargetLevel getTargetRestriction(IInjectionPointContext context) { RestrictTargetLevel level = RestrictTargetLevel.METHODS_ONLY; for (InjectionPoint component : this.components) { RestrictTargetLevel componentLevel = component.getTargetRestriction(context); if (componentLevel.ordinal() > level.ordinal()) { level = componentLevel; } } return level; } /* (non-Javadoc) * @see org.spongepowered.asm.mixin.injection.InjectionPoint#toString() */ @Override public String toString() { return "CompositeInjectionPoint(" + this.getClass().getSimpleName() + ")[" + Joiner.on(',').join(this.components) + "]"; } } /** * Intersection of several injection points, returns common nodes that * appear in all children */ static final class Intersection extends InjectionPoint.CompositeInjectionPoint { public Intersection(InjectionPoint... points) { super(points); } @SuppressWarnings("unchecked") @Override public boolean find(String desc, InsnList insns, Collection nodes) { boolean found = false; ArrayList[] allNodes = (ArrayList[]) Array.newInstance(ArrayList.class, this.components.length); for (int i = 0; i < this.components.length; i++) { allNodes[i] = new ArrayList(); this.components[i].find(desc, insns, allNodes[i]); } ArrayList alpha = allNodes[0]; for (int nodeIndex = 0; nodeIndex < alpha.size(); nodeIndex++) { AbstractInsnNode node = alpha.get(nodeIndex); boolean in = true; for (int b = 1; b < allNodes.length; b++) { if (!allNodes[b].contains(node)) { break; } } if (!in) { continue; } nodes.add(node); found = true; } return found; } } /** * Union of several injection points, returns all insns returned from all * injections */ static final class Union extends InjectionPoint.CompositeInjectionPoint { public Union(InjectionPoint... points) { super(points); } @Override public boolean find(String desc, InsnList insns, Collection nodes) { LinkedHashSet allNodes = new LinkedHashSet(); for (int i = 0; i < this.components.length; i++) { this.components[i].find(desc, insns, allNodes); } nodes.addAll(allNodes); return allNodes.size() > 0; } } /** * Shift injection point, takes an input injection point and shifts all * returned nodes by a fixed amount */ static final class Shift extends InjectionPoint { private final InjectionPoint input; private final int shift; private final boolean respectSpecifier; public Shift(InjectionPoint input, int shift) { this(input, shift, FabricUtil.COMPATIBILITY_LATEST); } public Shift(InjectionPoint input, int shift, int fabricCompatibility) { if (input == null) { throw new IllegalArgumentException("Must supply an input injection point for SHIFT"); } this.input = input; this.shift = shift; this.respectSpecifier = fabricCompatibility >= FabricUtil.COMPATIBILITY_0_16_5; } /* (non-Javadoc) * @see org.spongepowered.asm.mixin.injection.InjectionPoint#toString() */ @Override public String toString() { return "InjectionPoint(" + this.getClass().getSimpleName() + ")[" + this.input + "]"; } @Override public RestrictTargetLevel getTargetRestriction(IInjectionPointContext context) { return this.input.getTargetRestriction(context); } @Override public boolean find(String desc, InsnList insns, Collection nodes) { List list = (nodes instanceof List) ? (List) nodes : new ArrayList(nodes); this.input.find(desc, insns, nodes); for (ListIterator iter = list.listIterator(); iter.hasNext();) { int sourceIndex = insns.indexOf(iter.next()); int newIndex = sourceIndex + this.shift; if (newIndex >= 0 && newIndex < insns.size()) { iter.set(insns.get(newIndex)); } else { // Shifted beyond the start or end of the insnlist, into the dark void iter.remove(); // Decorate the injector with the info in case it fails int absShift = Math.abs(this.shift); char operator = absShift != this.shift ? '-' : '+'; this.input.addMessage( "@At.shift offset outside the target bounds: Index (index(%d) %s offset(%d) = %d) is outside the allowed range (0-%d)", sourceIndex, operator, absShift, newIndex, insns.size()); } } if (nodes != list) { nodes.clear(); nodes.addAll(list); } return nodes.size() > 0; } @Override public Specifier getSpecifier(Specifier defaultSpecifier) { return this.respectSpecifier ? this.input.getSpecifier(defaultSpecifier) : super.getSpecifier(defaultSpecifier); } } /** * Returns a composite injection point which returns the intersection of * nodes from all component injection points * * @param operands injection points to perform intersection * @return adjusted InjectionPoint */ public static InjectionPoint and(InjectionPoint... operands) { return new InjectionPoint.Intersection(operands); } /** * Returns a composite injection point which returns the union of nodes from * all component injection points * * @param operands injection points to perform union * @return adjusted InjectionPoint */ public static InjectionPoint or(InjectionPoint... operands) { return new InjectionPoint.Union(operands); } /** * Returns an injection point which returns all insns immediately following * insns from the supplied injection point * * @param point injection points to perform shift * @return adjusted InjectionPoint */ public static InjectionPoint after(InjectionPoint point) { return new InjectionPoint.Shift(point, 1); } /** * Returns an injection point which returns all insns immediately prior to * insns from the supplied injection point * * @param point injection points to perform shift * @return adjusted InjectionPoint */ public static InjectionPoint before(InjectionPoint point) { return new InjectionPoint.Shift(point, -1); } /** * Returns an injection point which returns all insns offset by the * specified "count" from insns from the supplied injection point * * @param point injection points to perform shift * @param count amount to shift by * @return adjusted InjectionPoint */ public static InjectionPoint shift(InjectionPoint point, int count) { return new InjectionPoint.Shift(point, count); } /** * Parse a collection of InjectionPoints from the supplied {@link At} * annotations * * @param context Data for the mixin containing the annotation, used to * obtain the refmap, amongst other things * @param method The annotated handler method * @param parent The parent annotation which owns this {@link At} annotation * @param ats {@link At} annotations to parse information from * @return InjectionPoint parsed from the supplied data or null if parsing * failed */ public static List parse(IMixinContext context, MethodNode method, AnnotationNode parent, List ats) { return InjectionPoint.parse(new AnnotatedMethodInfo(context, method, parent), ats); } /** * Parse a collection of InjectionPoints from the supplied {@link At} * annotations * * @param context Data for the mixin containing the annotation, used to obtain * the refmap, amongst other things * @param ats {@link At} annotations to parse information from * @return InjectionPoint parsed from the supplied data or null if parsing * failed */ public static List parse(IInjectionPointContext context, List ats) { Builder injectionPoints = ImmutableList.builder(); for (AnnotationNode at : ats) { InjectionPoint injectionPoint = InjectionPoint.parse(new InjectionPointAnnotationContext(context, at, "at"), at); if (injectionPoint != null) { injectionPoints.add(injectionPoint); } } return injectionPoints.build(); } /** * Parse an InjectionPoint from the supplied {@link At} annotation * * @param context Data for the mixin containing the annotation, used to obtain * the refmap, amongst other things * @param at {@link At} annotation to parse information from * @return InjectionPoint parsed from the supplied data or null if parsing * failed */ public static InjectionPoint parse(IInjectionPointContext context, At at) { return InjectionPoint.parse(context, at.value(), at.shift(), at.by(), Arrays.asList(at.args()), at.target(), at.slice(), at.ordinal(), at.opcode(), at.id(), InjectionPoint.Flags.parse(at)); } /** * Parse an InjectionPoint from the supplied {@link At} annotation * * @param context Data for the mixin containing the annotation, used to * obtain the refmap, amongst other things * @param method The annotated handler method * @param parent The parent annotation which owns this {@link At} annotation * @param at {@link At} annotation to parse information from * @return InjectionPoint parsed from the supplied data or null if parsing * failed */ public static InjectionPoint parse(IMixinContext context, MethodNode method, AnnotationNode parent, At at) { return InjectionPoint.parse(new AnnotatedMethodInfo(context, method, parent), at.value(), at.shift(), at.by(), Arrays.asList(at.args()), at.target(), at.slice(), at.ordinal(), at.opcode(), at.id(), InjectionPoint.Flags.parse(at)); } /** * Parse an InjectionPoint from the supplied {@link At} annotation supplied * as an AnnotationNode instance * * @param context Data for the mixin containing the annotation, used to * obtain the refmap, amongst other things * @param method The annotated handler method * @param parent The parent annotation which owns this {@link At} annotation * @param at {@link At} annotation to parse information from * @return InjectionPoint parsed from the supplied data or null if parsing * failed */ public static InjectionPoint parse(IMixinContext context, MethodNode method, AnnotationNode parent, AnnotationNode at) { return InjectionPoint.parse(new InjectionPointAnnotationContext(new AnnotatedMethodInfo(context, method, parent), at, "at"), at); } /** * Parse an InjectionPoint from the supplied {@link At} annotation supplied * as an AnnotationNode instance * * @param context Data for the mixin containing the annotation, used to obtain * the refmap, amongst other things * @param at {@link At} annotation to parse information from * @return InjectionPoint parsed from the supplied data or null if parsing * failed */ public static InjectionPoint parse(IInjectionPointContext context, AnnotationNode at) { String value = Annotations.getValue(at, "value"); List args = Annotations.>getValue(at, "args"); String target = Annotations.getValue(at, "target", ""); String slice = Annotations.getValue(at, "slice", ""); At.Shift shift = Annotations.getValue(at, "shift", At.Shift.class, At.Shift.NONE); int by = Annotations.getValue(at, "by", Integer.valueOf(0)); int ordinal = Annotations.getValue(at, "ordinal", Integer.valueOf(-1)); int opcode = Annotations.getValue(at, "opcode", Integer.valueOf(0)); String id = Annotations.getValue(at, "id"); int flags = InjectionPoint.Flags.parse(at); if (args == null) { args = ImmutableList.of(); } return InjectionPoint.parse(context, value, shift, by, args, target, slice, ordinal, opcode, id, flags); } /** * Parse and instantiate an InjectionPoint from the supplied information. * Returns null if an InjectionPoint could not be created. * * @param context Data for the mixin containing the annotation, used to * obtain the refmap, amongst other things * @param method The annotated handler method * @param parent The parent annotation which owns this {@link At} annotation * @param at Injection point specifier * @param shift Shift type to apply * @param by Amount of shift to apply for the BY shift type * @param args Named parameters * @param target Target for supported injection points * @param slice Slice id for injectors which support multiple slices * @param ordinal Ordinal offset for supported injection points * @param opcode Bytecode opcode for supported injection points * @param id Injection point id from annotation * @param flags Additional flags * @return InjectionPoint parsed from the supplied data or null if parsing * failed */ public static InjectionPoint parse(IMixinContext context, MethodNode method, AnnotationNode parent, String at, At.Shift shift, int by, List args, String target, String slice, int ordinal, int opcode, String id, int flags) { return InjectionPoint.parse(new AnnotatedMethodInfo(context, method, parent), at, shift, by, args, target, slice, ordinal, opcode, id, flags); } /** * Parse and instantiate an InjectionPoint from the supplied information. * Returns null if an InjectionPoint could not be created. * * @param context The injection point context which owns this {@link At} * annotation * @param at Injection point specifier * @param shift Shift type to apply * @param by Amount of shift to apply for the BY shift type * @param args Named parameters * @param target Target for supported injection points * @param slice Slice id for injectors which support multiple slices * @param ordinal Ordinal offset for supported injection points * @param opcode Bytecode opcode for supported injection points * @param id Injection point id from annotation * @param flags Additional flags * @return InjectionPoint parsed from the supplied data or null if parsing * failed */ public static InjectionPoint parse(IInjectionPointContext context, String at, At.Shift shift, int by, List args, String target, String slice, int ordinal, int opcode, String id, int flags) { InjectionPointData data = new InjectionPointData(context, at, args, target, slice, ordinal, opcode, id, flags); Class ipClass = InjectionPoint.findClass(context.getMixin(), data); InjectionPoint point = InjectionPoint.create(context.getMixin(), data, ipClass); return InjectionPoint.shift(context, point, shift, by); } @SuppressWarnings("unchecked") private static Class findClass(IMixinContext context, InjectionPointData data) { String type = data.getType(); Class ipClass = InjectionPoint.types.get(type.toUpperCase(Locale.ROOT)); if (ipClass == null) { if (type.matches("^([A-Za-z_][A-Za-z0-9_]*[\\.\\$])+[A-Za-z_][A-Za-z0-9_]*$")) { try { ipClass = (Class)MixinService.getService().getClassProvider().findClass(type); InjectionPoint.types.put(type, ipClass); } catch (Exception ex) { throw new InvalidInjectionException(context, data + " could not be loaded or is not a valid InjectionPoint", ex); } } else { throw new InvalidInjectionException(context, data + " is not a valid injection point specifier"); } } return ipClass; } private static InjectionPoint create(IMixinContext context, InjectionPointData data, Class ipClass) { Constructor ipCtor = null; try { ipCtor = ipClass.getDeclaredConstructor(InjectionPointData.class); ipCtor.setAccessible(true); } catch (NoSuchMethodException ex) { throw new InvalidInjectionException(context, ipClass.getName() + " must contain a constructor which accepts an InjectionPointData", ex); } InjectionPoint point = null; try { point = ipCtor.newInstance(data); } catch (InvocationTargetException ex) { throw new InvalidInjectionException(context, "Error whilst instancing injection point " + ipClass.getName() + " for " + data.getAt(), ex.getCause()); } catch (Exception ex) { throw new InvalidInjectionException(context, "Error whilst instancing injection point " + ipClass.getName() + " for " + data.getAt(), ex); } return point; } private static InjectionPoint shift(IInjectionPointContext context, InjectionPoint point, At.Shift shift, int by) { int fabricCompatibility = FabricUtil.getCompatibility(context); if (point != null) { if (shift == At.Shift.BEFORE) { return new InjectionPoint.Shift(point, -1, fabricCompatibility); } else if (shift == At.Shift.AFTER) { return new InjectionPoint.Shift(point, 1, fabricCompatibility); } else if (shift == At.Shift.BY) { InjectionPoint.validateByValue(context.getMixin(), context.getMethod(), context.getAnnotationNode(), point, by); return new InjectionPoint.Shift(point, by, fabricCompatibility); } } return point; } private static void validateByValue(IMixinContext context, MethodNode method, AnnotationNode parent, InjectionPoint point, int by) { MixinEnvironment env = context.getMixin().getConfig().getEnvironment(); ShiftByViolationBehaviour err = env.getOption(Option.SHIFT_BY_VIOLATION_BEHAVIOUR, ShiftByViolationBehaviour.WARN); if (err == ShiftByViolationBehaviour.IGNORE) { return; } String limitBreached = "the maximum allowed value: "; String advice = "Increase the value of maxShiftBy to suppress this warning."; int allowed = InjectionPoint.DEFAULT_ALLOWED_SHIFT_BY; if (context instanceof MixinTargetContext) { allowed = ((MixinTargetContext)context).getMaxShiftByValue(); } if (by <= allowed) { return; } if (by > InjectionPoint.MAX_ALLOWED_SHIFT_BY) { limitBreached = "MAX_ALLOWED_SHIFT_BY="; advice = "You must use an alternate query or a custom injection point."; allowed = InjectionPoint.MAX_ALLOWED_SHIFT_BY; } String message = String.format("@%s(%s) Shift.BY=%d on %s::%s exceeds %s%d. %s", Annotations.getSimpleName(parent), point, by, context, method.name, limitBreached, allowed, advice); if (err == ShiftByViolationBehaviour.WARN && allowed < InjectionPoint.MAX_ALLOWED_SHIFT_BY) { MixinService.getService().getLogger("mixin").warn(message); return; } throw new InvalidInjectionException(context, message); } protected String getAtCode() { AtCode code = this.getClass().getAnnotation(AtCode.class); return code == null ? this.getClass().getName() : code.value().toUpperCase(); } /** * Register an injection point class. The supplied class must be decorated * with an {@link AtCode} annotation for registration purposes. * * @param type injection point type to register */ @Deprecated public static void register(Class type) { InjectionPoint.register(type, null); } /** * Register an injection point class. The supplied class must be decorated * with an {@link AtCode} annotation for registration purposes. * * @param type injection point type to register * @param namespace namespace for AtCode */ public static void register(Class type, String namespace) { AtCode code = type.getAnnotation(AtCode.class); if (code == null) { throw new IllegalArgumentException("Injection point class " + type + " is not annotated with @AtCode"); } String annotationNamespace = code.namespace(); if (!Strings.isNullOrEmpty(annotationNamespace)) { namespace = annotationNamespace; } Class existing = InjectionPoint.types.get(code.value()); if (existing != null && !existing.equals(type)) { MixinService.getService().getLogger("mixin").debug("Overriding InjectionPoint {} with {} (previously {})", code.value(), type.getName(), existing.getName()); } else if (Strings.isNullOrEmpty(namespace)) { MixinService.getService().getLogger("mixin").warn("Registration of InjectionPoint {} with {} without specifying namespace is deprecated.", code.value(), type.getName()); } String id = code.value().toUpperCase(Locale.ROOT); if (!Strings.isNullOrEmpty(namespace)) { id = namespace.toUpperCase(Locale.ROOT) + ":" + id; } InjectionPoint.types.put(id, type); } /** * Register a built-in injection point class. Skips validation and * namespacing checks * * @param type injection point type to register */ private static void registerBuiltIn(Class type) { String code = type.getAnnotation(AtCode.class).value().toUpperCase(Locale.ROOT); InjectionPoint.types.put(code, type); InjectionPoint.types.put("MIXIN:" + code, type); } }