/* * 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.transformer; import java.io.IOException; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.spongepowered.asm.lib.ClassReader; import org.spongepowered.asm.lib.MethodVisitor; import org.spongepowered.asm.lib.Opcodes; import org.spongepowered.asm.lib.tree.AnnotationNode; import org.spongepowered.asm.lib.tree.ClassNode; import org.spongepowered.asm.lib.tree.FieldNode; import org.spongepowered.asm.lib.tree.InnerClassNode; import org.spongepowered.asm.lib.tree.MethodNode; import org.spongepowered.asm.mixin.Implements; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.MixinEnvironment.CompatibilityLevel; import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.MixinEnvironment.Phase; import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.Pseudo; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.extensibility.IMixinConfig; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; import org.spongepowered.asm.mixin.injection.Surrogate; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.transformer.ClassInfo.Method; import org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException; import org.spongepowered.asm.mixin.transformer.throwables.MixinReloadException; import org.spongepowered.asm.mixin.transformer.throwables.MixinTargetAlreadyLoadedException; import org.spongepowered.asm.util.Annotations; import org.spongepowered.asm.util.launchwrapper.LaunchClassLoaderUtil; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.collect.Lists; import net.minecraft.launchwrapper.Launch; /** * Runtime information bundle about a mixin */ class MixinInfo extends TreeInfo implements Comparable<MixinInfo>, IMixinInfo { /** * A MethodNode in a mixin */ class MixinMethodNode extends MethodNode { private final String originalName; public MixinMethodNode(int access, String name, String desc, String signature, String[] exceptions) { super(Opcodes.ASM5, access, name, desc, signature, exceptions); this.originalName = name; } @Override public String toString() { return String.format("%s%s", this.originalName, this.desc); } public String getOriginalName() { return this.originalName; } public boolean isInjector() { return (this.getInjectorAnnotation() != null || this.isSurrogate()); } public boolean isSurrogate() { return this.getVisibleAnnotation(Surrogate.class) != null; } public AnnotationNode getVisibleAnnotation(Class<? extends Annotation> annotationClass) { return Annotations.getVisible(this, annotationClass); } public AnnotationNode getInjectorAnnotation() { return InjectionInfo.getInjectorAnnotation(MixinInfo.this, this); } public IMixinInfo getOwner() { return MixinInfo.this; } } /** * ClassNode for a MixinInfo */ class MixinClassNode extends ClassNode { public final List<MixinMethodNode> mixinMethods; public MixinClassNode(MixinInfo mixin) { this(Opcodes.ASM5); } @SuppressWarnings("unchecked") public MixinClassNode(int api) { super(api); this.mixinMethods = (List<MixinMethodNode>)(Object)this.methods; } public MixinInfo getMixin() { return MixinInfo.this; } @Override public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { MethodNode method = new MixinMethodNode(access, name, desc, signature, exceptions); this.methods.add(method); return method; } } /** * Mixin preparation/parse/validation state */ class State { /** * Mixin bytes (read once, generate tree on demand) */ private byte[] mixinBytes; /** * Mixin ClassInfo */ private final ClassInfo classInfo; /** * True if the superclass of the mixin is <b>not</b> the direct * superclass of one or more targets */ private boolean detachedSuper; /** * True if this mixin is decorated with {@link Unique} */ private boolean unique; /** * All interfaces implemented by this mixin, including soft * implementations */ protected final Set<String> interfaces = new HashSet<String>(); /** * Interfaces soft-implemented using {@link Implements} */ protected final List<InterfaceInfo> softImplements = new ArrayList<InterfaceInfo>(); /** * Synthetic inner classes */ protected final Set<String> syntheticInnerClasses = new HashSet<String>(); /** * Initial ClassNode created for mixin validation, not used for actual * application */ protected MixinClassNode classNode; State(byte[] mixinBytes) { this(mixinBytes, null); } State(byte[] mixinBytes, ClassInfo classInfo) { this.mixinBytes = mixinBytes; this.connect(); this.classInfo = classInfo != null ? classInfo : ClassInfo.fromClassNode(this.getClassNode()); } private void connect() { this.classNode = this.createClassNode(0); } private void complete() { this.classNode = null; } ClassInfo getClassInfo() { return this.classInfo; } byte[] getClassBytes() { return this.mixinBytes; } MixinClassNode getClassNode() { return this.classNode; } boolean isDetachedSuper() { return this.detachedSuper; } boolean isUnique() { return this.unique; } List<? extends InterfaceInfo> getSoftImplements() { return this.softImplements; } Set<String> getSyntheticInnerClasses() { return this.syntheticInnerClasses; } Set<String> getInterfaces() { return this.interfaces; } /** * Gets a new tree from the bytecode * * @param flags Flags passed into classReader * @return Tree representing the bytecode */ MixinClassNode createClassNode(int flags) { MixinClassNode classNode = new MixinClassNode(MixinInfo.this); ClassReader classReader = new ClassReader(this.mixinBytes); classReader.accept(classNode, flags); return classNode; } /** * Performs pre-flight checks on the mixin * * @param type Mixin Type * @param targetClasses Mixin's target classes */ void validate(SubType type, List<ClassInfo> targetClasses) { MixinPreProcessorStandard preProcessor = type.createPreProcessor(this.getClassNode()).prepare(); for (ClassInfo target : targetClasses) { preProcessor.conform(target); } type.validate(this, targetClasses); this.detachedSuper = type.isDetachedSuper(); this.unique = Annotations.getVisible(this.getClassNode(), Unique.class) != null; // Pre-flight checks this.validateInner(); this.validateClassVersion(); this.validateRemappables(targetClasses); // Read information from the mixin this.readImplementations(type); this.readInnerClasses(); // Takeoff validation this.validateChanges(type, targetClasses); // Null out the validation classnode this.complete(); } private void validateInner() { // isInner (shouldn't) return true for static inner classes if (!this.classInfo.isProbablyStatic()) { throw new InvalidMixinException(MixinInfo.this, "Inner class mixin must be declared static"); } } private void validateClassVersion() { if (this.classNode.version > MixinEnvironment.getCompatibilityLevel().classVersion()) { String helpText = "."; for (CompatibilityLevel level : CompatibilityLevel.values()) { if (level.classVersion() >= this.classNode.version) { helpText = String.format(". Mixin requires compatibility level %s or above.", level.name()); } } throw new InvalidMixinException(MixinInfo.this, "Unsupported mixin class version " + this.classNode.version + helpText); } } private void validateRemappables(List<ClassInfo> targetClasses) { // Can't have remappable fields or methods on a multi-target mixin, because after obfuscation the fields will remap to conflicting names if (targetClasses.size() > 1) { for (FieldNode field : this.classNode.fields) { this.validateRemappable(Shadow.class, field.name, Annotations.getVisible(field, Shadow.class)); } for (MethodNode method : this.classNode.methods) { this.validateRemappable(Shadow.class, method.name, Annotations.getVisible(method, Shadow.class)); AnnotationNode overwrite = Annotations.getVisible(method, Overwrite.class); if (overwrite != null && ((method.access & Opcodes.ACC_STATIC) == 0 || (method.access & Opcodes.ACC_PUBLIC) == 0)) { throw new InvalidMixinException(MixinInfo.this, "Found @Overwrite annotation on " + method.name + " in " + this); } } } } private void validateRemappable(Class<Shadow> annotationClass, String name, AnnotationNode annotation) { if (annotation != null && Annotations.getValue(annotation, "remap", Boolean.TRUE)) { throw new InvalidMixinException(MixinInfo.this, "Found a remappable @" + annotationClass.getSimpleName() + " annotation on " + name + " in " + this); } } /** * Read and process any {@link Implements} annotations on the mixin */ void readImplementations(SubType type) { this.interfaces.addAll(this.classNode.interfaces); this.interfaces.addAll(type.getInterfaces()); AnnotationNode implementsAnnotation = Annotations.getInvisible(this.classNode, Implements.class); if (implementsAnnotation == null) { return; } List<AnnotationNode> interfaces = Annotations.getValue(implementsAnnotation); if (interfaces == null) { return; } for (AnnotationNode interfaceNode : interfaces) { InterfaceInfo interfaceInfo = InterfaceInfo.fromAnnotation(MixinInfo.this, interfaceNode); this.softImplements.add(interfaceInfo); this.interfaces.add(interfaceInfo.getInternalName()); // only add interface if its initial initialisation if (!(this instanceof Reloaded)) { this.classInfo.addInterface(interfaceInfo.getInternalName()); } } } /** * Read inner class definitions for the class and locate any synthetic * inner classes so that we can add them to the passthrough set in our * parent config. */ void readInnerClasses() { for (InnerClassNode inner : this.classNode.innerClasses) { ClassInfo innerClass = ClassInfo.forName(inner.name); if (innerClass.isSynthetic() && innerClass.isProbablyStatic()) { if ((inner.outerName != null && inner.outerName.equals(this.classInfo.getName())) || inner.name.startsWith(this.classNode.name + "$")) { this.syntheticInnerClasses.add(inner.name); } else { throw new InvalidMixinException(MixinInfo.this, "Unhandled synthetic inner class found: " + inner.name); } } } } protected void validateChanges(SubType type, List<ClassInfo> targetClasses) { type.createPreProcessor(this.classNode).prepare(); } } /** * State use when hotswap reloading a mixin */ class Reloaded extends State { /** * The previous validation state to compare the changes to */ private final State previous; Reloaded(State previous, byte[] mixinBytes) { super(mixinBytes, previous.getClassInfo()); this.previous = previous; } /** * Validates that the changes are allowed to be made, these restrictions * only exits while reloading mixins. */ @Override protected void validateChanges(SubType type, List<ClassInfo> targetClasses) { if (!this.syntheticInnerClasses.equals(this.previous.syntheticInnerClasses)) { throw new MixinReloadException(MixinInfo.this, "Cannot change inner classes"); } if (!this.interfaces.equals(this.previous.interfaces)) { throw new MixinReloadException(MixinInfo.this, "Cannot change interfaces"); } if (!new HashSet<InterfaceInfo>(this.softImplements).equals(new HashSet<InterfaceInfo>(this.previous.softImplements))) { throw new MixinReloadException(MixinInfo.this, "Cannot change soft interfaces"); } List<ClassInfo> targets = MixinInfo.this.readTargetClasses(this.classNode, true); if (!new HashSet<ClassInfo>(targets).equals(new HashSet<ClassInfo>(targetClasses))) { throw new MixinReloadException(MixinInfo.this, "Cannot change target classes"); } int priority = MixinInfo.this.readPriority(this.classNode); if (priority != MixinInfo.this.getPriority()) { throw new MixinReloadException(MixinInfo.this, "Cannot change mixin priority"); } } } /** * Mixin sub-type, eg. standard mixin, interface mixin, accessor */ abstract static class SubType { /** * Outer */ protected final MixinInfo mixin; /** * String representation of annotation type, for use in messages */ protected final String annotationType; /** * Target of this mixin subtype must be an interface, false for a class */ protected final boolean targetMustBeInterface; /** * Detached super, parsed out during validation */ protected boolean detached; SubType(MixinInfo info, String annotationType, boolean targetMustBeInterface) { this.mixin = info; this.annotationType = annotationType; this.targetMustBeInterface = targetMustBeInterface; } Collection<String> getInterfaces() { return Collections.<String>emptyList(); } /** * Get whether this mixin is detached super, must call {@link #validate} * first * * @return true if super is detached */ boolean isDetachedSuper() { return this.detached; } /** * True if this mixin class can actually be classloaded * * @return whether this subtype is directly classloadable (supports * classloader pinholing) */ boolean isLoadable() { return false; } /** * Validate a single target before adding * * @param targetName target class name * @param targetInfo information about the target class */ void validateTarget(String targetName, ClassInfo targetInfo) { boolean targetIsInterface = targetInfo.isInterface(); if (targetIsInterface != this.targetMustBeInterface) { String not = targetIsInterface ? "" : "not "; throw new InvalidMixinException(this.mixin, this.annotationType + " target type mismatch: " + targetName + " is " + not + "an interface in " + this); } } abstract void validate(State state, List<ClassInfo> targetClasses); abstract MixinPreProcessorStandard createPreProcessor(MixinClassNode classNode); /** * A standard mixin */ static class Standard extends SubType { Standard(MixinInfo info) { super(info, "@Mixin", false); } @Override void validate(State state, List<ClassInfo> targetClasses) { ClassNode classNode = state.getClassNode(); for (ClassInfo targetClass : targetClasses) { if (classNode.superName.equals(targetClass.getSuperName())) { continue; } if (!targetClass.hasSuperClass(classNode.superName, ClassInfo.Traversal.SUPER)) { ClassInfo superClass = ClassInfo.forName(classNode.superName); if (superClass.isMixin()) { // If superclass is a mixin, check for hierarchy derp for (ClassInfo superTarget : superClass.getTargets()) { if (targetClasses.contains(superTarget)) { throw new InvalidMixinException(this.mixin, "Illegal hierarchy detected. Derived mixin " + this + " targets the same class " + superTarget.getClassName() + " as its superclass " + superClass.getClassName()); } } } throw new InvalidMixinException(this.mixin, "Super class '" + classNode.superName.replace('/', '.') + "' of " + this.mixin.getName() + " was not found in the hierarchy of target class '" + targetClass + "'"); } this.detached = true; } } @Override MixinPreProcessorStandard createPreProcessor(MixinClassNode classNode) { return new MixinPreProcessorStandard(this.mixin, classNode); } } /** * An interface mixin */ static class Interface extends SubType { Interface(MixinInfo info) { super(info, "@Mixin", true); } @Override void validate(State state, List<ClassInfo> targetClasses) { if (!MixinEnvironment.getCompatibilityLevel().supportsMethodsInInterfaces()) { throw new InvalidMixinException(this.mixin, "Interface mixin not supported in current enviromnment"); } ClassNode classNode = state.getClassNode(); if (!"java/lang/Object".equals(classNode.superName)) { throw new InvalidMixinException(this.mixin, "Super class of " + this + " is invalid, found " + classNode.superName.replace('/', '.')); } } @Override MixinPreProcessorStandard createPreProcessor(MixinClassNode classNode) { return new MixinPreProcessorInterface(this.mixin, classNode); } } /** * An accessor mixin */ static class Accessor extends SubType { private final Collection<String> interfaces = new ArrayList<String>(); Accessor(MixinInfo info) { super(info, "@Mixin", false); this.interfaces.add(info.getClassRef()); } @Override boolean isLoadable() { return true; } @Override Collection<String> getInterfaces() { return this.interfaces; } @Override void validateTarget(String targetName, ClassInfo targetInfo) { boolean targetIsInterface = targetInfo.isInterface(); if (targetIsInterface && !MixinEnvironment.getCompatibilityLevel().supportsMethodsInInterfaces()) { throw new InvalidMixinException(this.mixin, "Accessor mixin targetting an interface is not supported in current enviromnment"); } } @Override void validate(State state, List<ClassInfo> targetClasses) { ClassNode classNode = state.getClassNode(); if (!"java/lang/Object".equals(classNode.superName)) { throw new InvalidMixinException(this.mixin, "Super class of " + this + " is invalid, found " + classNode.superName.replace('/', '.')); } } @Override MixinPreProcessorStandard createPreProcessor(MixinClassNode classNode) { return new MixinPreProcessorAccessor(this.mixin, classNode); } } static SubType getTypeFor(MixinInfo mixin) { if (!mixin.getClassInfo().isInterface()) { return new SubType.Standard(mixin); } boolean containsNonAccessorMethod = false; for (Method method : mixin.getClassInfo().getMethods()) { containsNonAccessorMethod |= !method.isAccessor(); } if (containsNonAccessorMethod) { // If the mixin contains any other methods, treat it as a regular interface mixin return new SubType.Interface(mixin); } // The mixin contains no non-accessor methods, so we can treat it as an accessor return new SubType.Accessor(mixin); } } /** * Class loader utility */ private static final LaunchClassLoaderUtil classLoaderUtil = LaunchClassLoaderUtil.forClassLoader(Launch.classLoader); /** * Global order of mixin infos, used to determine ordering between mixins * with equivalent priority */ static int mixinOrder = 0; /** * Logger */ private final transient Logger logger = LogManager.getLogger("mixin"); /** * Parent configuration which declares this mixin */ private final transient MixinConfig parent; /** * Simple name */ private final String name; /** * Name of the mixin class itself, dotted notation */ private final String className; /** * Mixin priority, read from the {@link Mixin} annotation on the mixin class */ private final int priority; /** * True if the mixin is annotated with {@link Pseudo} */ private final boolean virtual; /** * Mixin targets, read from the {@link Mixin} annotation on the mixin class */ private final List<ClassInfo> targetClasses; /** * Names of target classes */ private final List<String> targetClassNames; /** * Intrinsic order (for sorting mixins with identical priority) */ private final transient int order = MixinInfo.mixinOrder++; /** * Configuration plugin */ private final transient IMixinConfigPlugin plugin; /** * The environment phase in which this mixin was initialised */ private final transient Phase phase; /** * Cached class info */ private final transient ClassInfo info; /** * Mixin type */ private final transient SubType type; /** * Strict target checks enabled */ private final transient boolean strict; /** * Holds state that currently is not fully initialised or validated */ private transient State pendingState; /** * Holds the current validated state */ private transient State state; /** * Internal ctor, called by {@link MixinConfig} * * @param parent configuration which owns this mixin, the parent * @param mixinName name of this mixin (class name stub) * @param runTransformers true if this mixin should run transformers on its * bytecode when loading * @param plugin mixin config companion plugin, may be null * @param suppressPlugin true to suppress the plugin from filtering targets * of this mixin */ MixinInfo(MixinConfig parent, String mixinName, boolean runTransformers, IMixinConfigPlugin plugin, boolean suppressPlugin) { this.parent = parent; this.name = mixinName; this.className = parent.getMixinPackage() + mixinName; this.plugin = plugin; this.phase = parent.getEnvironment().getPhase(); this.strict = parent.getEnvironment().getOption(Option.DEBUG_TARGETS); // Read the class bytes and transform try { byte[] mixinBytes = this.loadMixinClass(this.className, runTransformers); this.pendingState = new State(mixinBytes); this.info = this.pendingState.getClassInfo(); this.type = SubType.getTypeFor(this); } catch (InvalidMixinException ex) { throw ex; } catch (Exception ex) { throw new InvalidMixinException(this, ex); } if (!this.type.isLoadable()) { // Inject the mixin class name into the LaunchClassLoader's invalid // classes set so that any classes referencing the mixin directly will // cause the game to crash MixinInfo.classLoaderUtil.registerInvalidClass(this.className); } // Read the class bytes and transform try { this.priority = this.readPriority(this.pendingState.getClassNode()); this.virtual = this.readPseudo(this.pendingState.getClassNode()); this.targetClasses = this.readTargetClasses(this.pendingState.getClassNode(), suppressPlugin); this.targetClassNames = Collections.unmodifiableList(Lists.transform(this.targetClasses, Functions.toStringFunction())); } catch (InvalidMixinException ex) { throw ex; } catch (Exception ex) { throw new InvalidMixinException(this, ex); } } /** * Run validation pass */ void validate() { if (this.pendingState == null) { throw new IllegalStateException("No pending validation state for " + this); } try { this.pendingState.validate(this.type, this.targetClasses); this.state = this.pendingState; } finally { this.pendingState = null; } } /** * Read the target class names from the {@link Mixin} annotation * * @param classNode mixin classnode * @param suppressPlugin true to suppress plugin filtering targets * @return target class list read from classNode */ protected List<ClassInfo> readTargetClasses(MixinClassNode classNode, boolean suppressPlugin) { if (classNode == null) { return Collections.<ClassInfo>emptyList(); } AnnotationNode mixin = Annotations.getInvisible(classNode, Mixin.class); if (mixin == null) { throw new InvalidMixinException(this, String.format("The mixin '%s' is missing an @Mixin annotation", this.className)); } List<ClassInfo> targets = new ArrayList<ClassInfo>(); List<org.spongepowered.asm.lib.Type> publicTargets = Annotations.getValue(mixin, "value"); List<String> privateTargets = Annotations.getValue(mixin, "targets"); if (publicTargets != null) { this.readTargets(targets, Lists.transform(publicTargets, new Function<org.spongepowered.asm.lib.Type, String>() { @Override public String apply(org.spongepowered.asm.lib.Type input) { return input.getClassName(); } }), suppressPlugin, false); } if (privateTargets != null) { this.readTargets(targets, Lists.transform(privateTargets, new Function<String, String>() { @Override public String apply(String input) { return MixinInfo.this.getParent().remapClassName(MixinInfo.this.getClassRef(), input); } }), suppressPlugin, true); } return targets; } /** * Reads a target list into the outTargets list */ private void readTargets(Collection<ClassInfo> outTargets, Collection<String> inTargets, boolean suppressPlugin, boolean checkPublic) { for (String targetRef : inTargets) { String targetName = targetRef.replace('/', '.'); if (MixinInfo.classLoaderUtil.isClassLoaded(targetName) && !this.isReloading()) { String message = String.format("Critical problem: %s target %s was already transformed.", this, targetName); if (this.parent.isRequired()) { throw new MixinTargetAlreadyLoadedException(this, message, targetName); } this.logger.error(message); } if (this.plugin == null || suppressPlugin || this.plugin.shouldApplyMixin(targetName, this.className)) { ClassInfo targetInfo = this.getTarget(targetName, checkPublic); if (targetInfo != null && !outTargets.contains(targetInfo)) { outTargets.add(targetInfo); targetInfo.addMixin(this); } } } } private ClassInfo getTarget(String targetName, boolean checkPublic) throws InvalidMixinException { ClassInfo targetInfo = ClassInfo.forName(targetName); if (targetInfo == null) { if (this.isVirtual()) { this.logger.debug("Skipping virtual target {} for {}", targetName, this); } else { this.handleTargetError(String.format("@Mixin target %s was not found %s", targetName, this)); } return null; } this.type.validateTarget(targetName, targetInfo); if (checkPublic && targetInfo.isPublic() && !this.isVirtual()) { this.handleTargetError(String.format("@Mixin target %s is public in %s and should be specified in value", targetName, this)); } return targetInfo; } private void handleTargetError(String message) { if (this.strict) { this.logger.error(message); throw new InvalidMixinException(this, message); } this.logger.warn(message); } /** * Read the priority from the {@link Mixin} annotation * * @param classNode mixin classnode * @return priority read from classNode */ protected int readPriority(ClassNode classNode) { if (classNode == null) { return this.parent.getDefaultMixinPriority(); } AnnotationNode mixin = Annotations.getInvisible(classNode, Mixin.class); if (mixin == null) { throw new InvalidMixinException(this, String.format("The mixin '%s' is missing an @Mixin annotation", this.className)); } Integer priority = Annotations.getValue(mixin, "priority"); return priority == null ? this.parent.getDefaultMixinPriority() : priority.intValue(); } protected boolean readPseudo(ClassNode classNode) { return Annotations.getInvisible(classNode, Pseudo.class) != null; } private boolean isReloading() { return this.pendingState instanceof Reloaded; } /** * Current state, either the validated state or the uninitialised state if * the mixin is initialising for the first time. Should never return null. */ private State getState() { return this.state != null ? this.state : this.pendingState; } /** * Get the ClassInfo for the mixin class */ ClassInfo getClassInfo() { return this.info; } /** * Get the parent config which declares this mixin */ @Override public IMixinConfig getConfig() { return this.parent; } /** * Get the parent config which declares this mixin */ MixinConfig getParent() { return this.parent; } /** * Get the mixin priority */ @Override public int getPriority() { return this.priority; } /** * Get the simple name of the mixin */ @Override public String getName() { return this.name; } /** * Get the name of the mixin class */ @Override public String getClassName() { return this.className; } /** * Get the ref (internal name) of the mixin class */ @Override public String getClassRef() { return this.getClassInfo().getName(); } /** * Get the class bytecode */ @Override public byte[] getClassBytes() { return this.getState().getClassBytes(); } /** * True if the superclass of the mixin is <b>not</b> the direct superclass * of one or more targets */ @Override public boolean isDetachedSuper() { return this.getState().isDetachedSuper(); } /** * True if this mixin is decorated with {@link Unique} */ public boolean isUnique() { return this.getState().isUnique(); } /** * True if this mixin is decorated with {@link Pseudo} */ public boolean isVirtual() { return this.virtual; } /** * True if the mixin class is actually class-loadable */ public boolean isAccessor() { return this.type instanceof SubType.Accessor; } /** * True if the mixin class is actually class-loadable */ public boolean isLoadable() { return this.type.isLoadable(); } /** * Get the logging level for this mixin */ public Level getLoggingLevel() { return this.parent.getLoggingLevel(); } /** * Get the phase in which this mixin was initialised */ @Override public Phase getPhase() { return this.phase; } /** * Get a new tree for the class bytecode */ @Override public MixinClassNode getClassNode(int flags) { return this.getState().createClassNode(flags); } /** * Get the target class names for this mixin */ @Override public List<String> getTargetClasses() { return this.targetClassNames; } /** * Get the soft implementations for this mixin */ List<InterfaceInfo> getSoftImplements() { return Collections.unmodifiableList(this.getState().getSoftImplements()); } /** * Get the synthetic inner classes for this mixin */ Set<String> getSyntheticInnerClasses() { return Collections.unmodifiableSet(this.getState().getSyntheticInnerClasses()); } /** * Get the target class list for this mixin */ List<ClassInfo> getTargets() { return Collections.unmodifiableList(this.targetClasses); } /** * Get all interfaces for this mixin * * @return mixin interfaces */ Set<String> getInterfaces() { return this.getState().getInterfaces(); } /** * Get a new mixin target context object for the specified target * * @param target target class context * @return new context */ MixinTargetContext createContextFor(TargetClassContext target) { MixinClassNode classNode = this.getClassNode(ClassReader.EXPAND_FRAMES); return this.type.createPreProcessor(classNode).prepare().createContextFor(target); } /** * Load the mixin class bytes * * @param mixinClassName mixin class name * @param runTransformers true to run transformers on the loaded bytecode * @return mixin bytecode * @throws ClassNotFoundException if the mixin bytes could not be found */ private byte[] loadMixinClass(String mixinClassName, boolean runTransformers) throws ClassNotFoundException { byte[] mixinBytes = null; try { mixinBytes = TreeInfo.loadClass(mixinClassName, runTransformers); } catch (ClassNotFoundException ex) { throw new ClassNotFoundException(String.format("The specified mixin '%s' was not found", mixinClassName)); } catch (IOException ex) { this.logger.warn("Failed to load mixin %s, the specified mixin will not be applied", mixinClassName); throw new InvalidMixinException(this, "An error was encountered whilst loading the mixin class", ex); } return mixinBytes; } /** * Updates this mixin with new bytecode * * @param mixinBytes New bytecode */ void reloadMixin(byte[] mixinBytes) { if (this.pendingState != null) { throw new IllegalStateException("Cannot reload mixin while it is initialising"); } this.pendingState = new Reloaded(this.state, mixinBytes); this.validate(); } /* (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) */ @Override public int compareTo(MixinInfo other) { if (other == null) { return 0; } if (other.priority == this.priority) { return this.order - other.order; } return (this.priority - other.priority); } /** * Called immediately before the mixin is applied to targetClass */ public void preApply(String transformedName, ClassNode targetClass) { if (this.plugin != null) { this.plugin.preApply(transformedName, targetClass, this.className, this); } } /** * Called immediately after the mixin is applied to targetClass */ public void postApply(String transformedName, ClassNode targetClass) { if (this.plugin != null) { this.plugin.postApply(transformedName, targetClass, this.className, this); } this.parent.postApply(transformedName, targetClass); } /* (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return String.format("%s:%s", this.parent.getName(), this.name); } }