/*
* 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.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
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.Handle;
import org.spongepowered.asm.lib.Opcodes;
import org.spongepowered.asm.lib.Type;
import org.spongepowered.asm.lib.tree.*;
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.SoftOverride;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
import org.spongepowered.asm.mixin.gen.AccessorInfo;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.InjectorGroupInfo;
import org.spongepowered.asm.mixin.injection.struct.InjectionInfo;
import org.spongepowered.asm.mixin.injection.struct.Target;
import org.spongepowered.asm.mixin.injection.throwables.InjectionError;
import org.spongepowered.asm.mixin.injection.throwables.InjectionValidationException;
import org.spongepowered.asm.mixin.refmap.IMixinContext;
import org.spongepowered.asm.mixin.refmap.ReferenceMapper;
import org.spongepowered.asm.mixin.transformer.ClassInfo.Field;
import org.spongepowered.asm.mixin.transformer.ClassInfo.SearchType;
import org.spongepowered.asm.mixin.transformer.ClassInfo.Traversal;
import org.spongepowered.asm.mixin.transformer.meta.MixinMerged;
import org.spongepowered.asm.mixin.transformer.meta.SourceMap.File;
import org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException;
import org.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError;
import org.spongepowered.asm.obfuscation.RemapperChain;
import org.spongepowered.asm.util.Bytecode;
import org.spongepowered.asm.util.Annotations;
import org.spongepowered.asm.util.ClassSignature;
import org.spongepowered.asm.util.Constants;
/**
* This object keeps track of data for applying a mixin to a specific target
* class <em>during</em> a mixin application. This is a single-use object which
* acts as both a handle information we need when applying the mixin (such as
* the actual mixin ClassNode and the target ClassNode) and a gateway to
* context-sensitive operations such as re-targetting method and field accesses
* in the mixin to the appropriate members in the target class hierarchy.
*/
public class MixinTargetContext implements IMixinContext {
/**
* Logger
*/
private static final Logger logger = LogManager.getLogger("mixin");
/**
* Mixin info
*/
private final MixinInfo mixin;
/**
* Tree
*/
private final ClassNode classNode;
/**
*
*/
private final TargetClassContext targetClass;
/**
* Session ID from context
*/
private final String sessionId;
/**
* Target ClassInfo
*/
private final ClassInfo targetClassInfo;
/**
* Shadow method list
*/
private final List<MethodNode> shadowMethods = new ArrayList<MethodNode>();
/**
* Shadow field list
*/
private final Map<FieldNode, Field> shadowFields = new LinkedHashMap<FieldNode, Field>();
/**
* List of methods successfully merged from this mixin
*/
private final List<MethodNode> mergedMethods = new ArrayList<MethodNode>();
/**
* Injector groups
*/
private final InjectorGroupInfo.Map injectorGroups = new InjectorGroupInfo.Map();
/**
* Injectors for this target
*/
private final List<InjectionInfo> injectors = new ArrayList<InjectionInfo>();
/**
* Accessor method list
*/
private final List<AccessorInfo> accessors = new ArrayList<AccessorInfo>();
/**
* True if this mixin inherits from a mixin at any point in its hierarchy
*/
private final boolean inheritsFromMixin;
/**
* True if this mixin's superclass is detached from the target superclass
*/
private final boolean detachedSuper;
/**
* SourceMap stratum
*/
private final File stratum;
/**
* Minimum class version required to apply this mixin, target class will be
* upgraded if the version is below this value
*/
private int minRequiredClassVersion = CompatibilityLevel.JAVA_6.classVersion();
/**
* ctor
*
* @param mixin Mixin information
* @param classNode Mixin classnode
* @param context target class
*/
MixinTargetContext(MixinInfo mixin, ClassNode classNode, TargetClassContext context) {
this.mixin = mixin;
this.classNode = classNode;
this.targetClass = context;
this.targetClassInfo = ClassInfo.forName(this.targetClass.getName());
this.stratum = context.getSourceMap().addFile(this.classNode);
this.inheritsFromMixin = mixin.getClassInfo().hasMixinInHierarchy() || this.targetClassInfo.hasMixinTargetInHierarchy();
this.detachedSuper = !this.classNode.superName.equals(this.targetClass.getClassNode().superName);
this.sessionId = context.getSessionId();
this.requireVersion(classNode.version);
}
/**
* Add a shadow method to this mixin context, called by the preprocessor
*
* @param method shadow method to add
*/
void addShadowMethod(MethodNode method) {
this.shadowMethods.add(method);
}
/**
* Add a shadow field to this mixin context, called by the preprocessor
*
* @param fieldNode field node
* @param fieldInfo field info
*/
void addShadowField(FieldNode fieldNode, Field fieldInfo) {
this.shadowFields.put(fieldNode, fieldInfo);
}
/**
* Add an accessor method to this mixin context, called by the preprocessor
*
* @param method method to add
* @param type annotation type
*/
void addAccessorMethod(MethodNode method, Class<? extends Annotation> type) {
this.accessors.add(AccessorInfo.of(this, method, type));
}
/**
* Callback from the applicator which notifies us that a method was merged
*
* @param method merged method
*/
void addMergedMethod(MethodNode method) {
this.mergedMethods.add(method);
this.targetClassInfo.addMethod(method);
Annotations.setVisible(method, MixinMerged.class,
"mixin", this.getClassName(),
"priority", this.getPriority(),
"sessionId", this.sessionId);
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return this.mixin.toString();
}
/**
* Get the environment of the owning mixin config
*
* @return mixin parent environment
*/
public MixinEnvironment getEnvironment() {
return this.mixin.getParent().getEnvironment();
}
/* (non-Javadoc)
* @see org.spongepowered.asm.mixin.refmap.IMixinContext
* #getOption(org.spongepowered.asm.mixin.MixinEnvironment.Option)
*/
@Override
public boolean getOption(Option option) {
return this.getEnvironment().getOption(option);
}
/**
* Get the mixin tree
*
* @return mixin tree
*/
public ClassNode getClassNode() {
return this.classNode;
}
/**
* Get the mixin class name
*
* @return the mixin class name
*/
public String getClassName() {
return this.mixin.getClassName();
}
/* (non-Javadoc)
* @see org.spongepowered.asm.mixin.transformer.IReferenceMapperContext
* #getClassRef()
*/
@Override
public String getClassRef() {
return this.mixin.getClassRef();
}
/**
* Get the target class context
*
* @return the target class context
*/
public TargetClassContext getTarget() {
return this.targetClass;
}
/**
* Get the target class reference
*
* @return the reference of the target class (only valid on single-target
* mixins)
*/
public String getTargetClassRef() {
return this.targetClass.getName();
}
/**
* Get the target class
*
* @return the target class
*/
public ClassNode getTargetClassNode() {
return this.targetClass.getClassNode();
}
/**
* Get the target classinfo
*
* @return the target class info
*/
public ClassInfo getTargetClassInfo() {
return this.targetClassInfo;
}
/**
* Get the signature for this mixin class
*
* @return signature
*/
public ClassSignature getSignature() {
return this.mixin.getClassInfo().getSignature();
}
/**
* Get the SourceMap stratum for this mixin
*
* @return stratum
*/
public File getStratum() {
return this.stratum;
}
/**
* Get the minimum required class version for this mixin
*/
public int getMinRequiredClassVersion() {
return this.minRequiredClassVersion;
}
/**
* Get the defined value for the {@link Inject#require} parameter on
* injectors defined in mixins in this configuration.
*
* @return default require value
*/
public int getDefaultRequiredInjections() {
return this.mixin.getParent().getDefaultRequiredInjections();
}
/**
* Get the defined injector group for injectors
*
* @return default group name
*/
public String getDefaultInjectorGroup() {
return this.mixin.getParent().getDefaultInjectorGroup();
}
/**
* Get the injector groups for this target
*
* @return injector groups
*/
public InjectorGroupInfo.Map getInjectorGroups() {
return this.injectorGroups;
}
/**
* Find the corresponding class type for the supplied mixin class in this
* mixin target's hierarchy
*
* @param mixin Mixin class to discover
* @return Transformed
*/
public ClassInfo findRealType(ClassInfo mixin) {
if (mixin == this.mixin.getClassInfo()) {
return this.targetClassInfo;
}
ClassInfo type = this.targetClassInfo.findCorrespondingType(mixin);
if (type == null) {
throw new InvalidMixinException(this, "Resolution error: unable to find corresponding type for "
+ mixin + " in hierarchy of " + this.targetClassInfo);
}
return type;
}
/**
* Handles "re-parenting" the method supplied, changes all references to the
* mixin class to refer to the target class (for field accesses and method
* invocations) and also handles fixing up the targets of INVOKESPECIAL
* opcodes for mixins with detached targets.
*
* @param method Method to transform
*/
public void transformMethod(MethodNode method) {
this.validateMethod(method);
this.transformDescriptor(method);
// Offset line numbers per the stratum
this.stratum.applyOffset(method);
AbstractInsnNode lastInsn = null;
for (Iterator<AbstractInsnNode> iter = method.instructions.iterator(); iter.hasNext();) {
AbstractInsnNode insn = iter.next();
if (insn instanceof MethodInsnNode) {
this.transformMethodRef(method, iter, new MemberRef.Method((MethodInsnNode)insn));
} else if (insn instanceof FieldInsnNode) {
this.transformFieldRef(method, iter, new MemberRef.Field((FieldInsnNode)insn));
this.checkFinal(method, iter, (FieldInsnNode)insn);
} else if (insn instanceof TypeInsnNode) {
this.transformTypeNode(method, iter, (TypeInsnNode)insn, lastInsn);
} else if (insn instanceof LdcInsnNode) {
this.transformConstantNode(method, iter, (LdcInsnNode)insn);
} else if (insn instanceof InvokeDynamicInsnNode) {
this.transformInvokeDynamicNode(method, iter, (InvokeDynamicInsnNode)insn);
}
lastInsn = insn;
}
}
/**
* Pre-flight checks on a method to be transformed, checks the validity of
* {@link SoftOverride} annotations and any other required validation tasks
*
* @param method Method node to validate
*/
private void validateMethod(MethodNode method) {
// Any method tagged with @SoftOverride must have an implementation visible from
if (Annotations.getInvisible(method, SoftOverride.class) != null) {
ClassInfo.Method superMethod = this.targetClassInfo.findMethodInHierarchy(method.name, method.desc, SearchType.SUPER_CLASSES_ONLY,
Traversal.SUPER);
if (superMethod == null || !superMethod.isInjected()) {
throw new InvalidMixinException(this, "Mixin method " + method.name + method.desc + " is tagged with @SoftOverride but no "
+ "valid method was found in superclasses of " + this.targetClass.getName());
}
}
}
/**
* Transforms a method invocation/reference in the method. Updates static
* and dynamic bindings.
*
* @param method Method being processed
* @param iter Insn interator
* @param methodRef Method reference to transform
*/
private void transformMethodRef(MethodNode method, Iterator<AbstractInsnNode> iter, MemberRef methodRef) {
this.transformDescriptor(methodRef);
if (methodRef.getOwner().equals(this.getClassRef())) {
methodRef.setOwner(this.targetClass.getName());
} else if ((this.detachedSuper || this.inheritsFromMixin)) {
if (methodRef.getOpcode() == Opcodes.INVOKESPECIAL) {
this.updateStaticBinding(method, methodRef);
} else if (methodRef.getOpcode() == Opcodes.INVOKEVIRTUAL && ClassInfo.forName(methodRef.getOwner()).isMixin()) {
this.updateDynamicBinding(method, methodRef);
}
}
}
/**
* Transforms field access/reference in the method. Handles imaginary super
* accesses and converts them to real super-invocations and rewrites field
* accesses which refer to mixin or supermixin classes to their relevant
* targets.
*
* @param method Method being processed
* @param iter Insn interator
* @param fieldRef Field Reference to transform
*/
private void transformFieldRef(MethodNode method, Iterator<AbstractInsnNode> iter, MemberRef fieldRef) {
if (Constants.IMAGINARY_SUPER.equals(fieldRef.getName())) {
if (fieldRef instanceof MemberRef.Field) {
this.processImaginarySuper(method, ((MemberRef.Field) fieldRef).insn);
iter.remove();
} else {
throw new InvalidMixinException(this.mixin, "Cannot call imaginary super from method handle.");
}
}
this.transformDescriptor(fieldRef);
if (fieldRef.getOwner().equals(this.getClassRef())) {
fieldRef.setOwner(this.targetClass.getName());
Field field = this.mixin.getClassInfo().findField(fieldRef.getName(), fieldRef.getDesc(), ClassInfo.INCLUDE_ALL);
// Fixes a problem with Forge not remapping static field references properly
if (field != null && field.isRenamed() && field.getOriginalName().equals(fieldRef.getName()) && field.isStatic()) {
fieldRef.setName(field.getName());
}
} else {
ClassInfo fieldOwner = ClassInfo.forName(fieldRef.getOwner());
if (fieldOwner.isMixin()) {
ClassInfo actualOwner = this.targetClassInfo.findCorrespondingType(fieldOwner);
fieldRef.setOwner(actualOwner != null ? actualOwner.getName() : this.targetClass.getName());
}
}
}
private void checkFinal(MethodNode method, Iterator<AbstractInsnNode> iter, FieldInsnNode fieldNode) {
if (!fieldNode.owner.equals(this.targetClass.getName())) {
return;
}
int opcode = fieldNode.getOpcode();
if (opcode == Opcodes.GETFIELD || opcode == Opcodes.GETSTATIC) {
return;
}
for (Entry<FieldNode, Field> shadow : this.shadowFields.entrySet()) {
FieldNode shadowFieldNode = shadow.getKey();
if (!shadowFieldNode.desc.equals(fieldNode.desc) || !shadowFieldNode.name.equals(fieldNode.name)) {
continue;
}
Field shadowField = shadow.getValue();
if (shadowField.isDecoratedFinal()) {
if (shadowField.isDecoratedMutable()) {
if (this.mixin.getParent().getEnvironment().getOption(Option.DEBUG_VERBOSE)) {
MixinTargetContext.logger.warn("Write access to @Mutable @Final field {} in {}::{}", shadowField, this.mixin, method.name);
}
} else {
if (Constants.CTOR.equals(method.name) || Constants.CLINIT.equals(method.name)) {
MixinTargetContext.logger.warn("@Final field {} in {} should be final", shadowField, this.mixin);
} else {
MixinTargetContext.logger.error("Write access detected to @Final field {} in {}::{}", shadowField, this.mixin, method.name);
if (this.mixin.getParent().getEnvironment().getOption(Option.DEBUG_VERIFY)) {
throw new InvalidMixinException(this.mixin, "Write access detected to @Final field " + shadowField + " in " + this.mixin
+ "::" + method.name);
}
}
}
}
return;
}
}
/**
* Transforms type operations (eg. cast, instanceof) in the method being
* processed. Changes references to mixin classes to that of the appropriate
* class for this context.
*
* @param method Method being processed
* @param iter Insn interator
* @param typeInsn Insn to transform
* @param lastNode Last insn in the method
*/
private void transformTypeNode(MethodNode method, Iterator<AbstractInsnNode> iter, TypeInsnNode typeInsn, AbstractInsnNode lastNode) {
if (typeInsn.getOpcode() == Opcodes.CHECKCAST
&& typeInsn.desc.equals(this.targetClass.getName())
&& lastNode.getOpcode() == Opcodes.ALOAD
&& ((VarInsnNode)lastNode).var == 0) {
iter.remove();
return;
}
if (typeInsn.desc.equals(this.getClassRef())) {
typeInsn.desc = this.targetClass.getName();
}
this.transformDescriptor(typeInsn);
}
/**
* Transforms class literals and method handle loads in the method being
* processed.
*
* @param method Method being processed
* @param iter Insn interator
* @param ldcInsn Insn to transform
*/
private void transformConstantNode(MethodNode method, Iterator<AbstractInsnNode> iter, LdcInsnNode ldcInsn) {
ldcInsn.cst = this.transformConstant(method, iter, ldcInsn.cst);
}
/**
* Transforms a invoke dynamic instruction in the method being processed.
*
* @param method Method being processed
* @param iter Insn interator
* @param dynInsn Insn to transform
*/
private void transformInvokeDynamicNode(MethodNode method, Iterator<AbstractInsnNode> iter, InvokeDynamicInsnNode dynInsn) {
this.requireVersion(Opcodes.V1_7);
dynInsn.desc = this.transformMethodDescriptor(dynInsn.desc);
dynInsn.bsm = this.transformHandle(method, iter, dynInsn.bsm);
for (int i = 0; i < dynInsn.bsmArgs.length; i++) {
dynInsn.bsmArgs[i] = this.transformConstant(method, iter, dynInsn.bsmArgs[i]);
}
}
/**
* Transforms a constant in the constant pool.
*
* @param method Method being processed
* @param iter Insn interator
* @param constant Consatnt pool entry
* @return Transformed constant
*/
private Object transformConstant(MethodNode method, Iterator<AbstractInsnNode> iter, Object constant) {
if (constant instanceof Type) {
Type type = (Type)constant;
String desc = this.transformDescriptor(type);
if (!type.toString().equals(desc)) {
return Type.getType(desc);
}
return constant;
} else if (constant instanceof Handle) {
return this.transformHandle(method, iter, (Handle)constant);
}
return constant;
}
/**
* Transforms a method handle that is referenced from the method being
* processed.
*
* @param method Method being processed
* @param iter Insn interator
* @param handle Handle to transform
*/
private Handle transformHandle(MethodNode method, Iterator<AbstractInsnNode> iter, Handle handle) {
MemberRef.Handle memberRef = new MemberRef.Handle(handle);
if (memberRef.isField()) {
this.transformFieldRef(method, iter, memberRef);
} else {
this.transformMethodRef(method, iter, memberRef);
}
return memberRef.getMethodHandle();
}
/**
* Handle "imaginary super" invocations, these are invocations in
* non-derived mixins for accessing methods known to exist in a supermixin
* which is not directly inherited by this mixix. The method can only call
* its <b>own</b> super-implmentation and the methd must also be tagged with
* {@link SoftOverride} to indicate that the method must exist in a super
* class.
*
* @param method Method being processed
* @param fieldInsn the GETFIELD insn which access the pseudo-field which is
* used as a handle to the superclass
*/
private void processImaginarySuper(MethodNode method, FieldInsnNode fieldInsn) {
if (fieldInsn.getOpcode() != Opcodes.GETFIELD) {
if (Constants.CTOR.equals(method.name)) {
throw new InvalidMixinException(this, "Illegal imaginary super declaration: field " + fieldInsn.name
+ " must not specify an initialiser");
}
throw new InvalidMixinException(this, "Illegal imaginary super access: found " + Bytecode.getOpcodeName(fieldInsn.getOpcode())
+ " opcode in " + method.name + method.desc);
}
if ((method.access & Opcodes.ACC_PRIVATE) != 0 || (method.access & Opcodes.ACC_STATIC) != 0) {
throw new InvalidMixinException(this, "Illegal imaginary super access: method " + method.name + method.desc
+ " is private or static");
}
if (Annotations.getInvisible(method, SoftOverride.class) == null) {
throw new InvalidMixinException(this, "Illegal imaginary super access: method " + method.name + method.desc
+ " is not decorated with @SoftOverride");
}
for (Iterator<AbstractInsnNode> methodIter = method.instructions.iterator(method.instructions.indexOf(fieldInsn)); methodIter.hasNext();) {
AbstractInsnNode insn = methodIter.next();
if (insn instanceof MethodInsnNode) {
MethodInsnNode methodNode = (MethodInsnNode)insn;
if (methodNode.owner.equals(this.getClassRef()) && methodNode.name.equals(method.name) && methodNode.desc.equals(method.desc)) {
methodNode.setOpcode(Opcodes.INVOKESPECIAL);
this.updateStaticBinding(method, new MemberRef.Method(methodNode));
return;
}
}
}
throw new InvalidMixinException(this, "Illegal imaginary super access: could not find INVOKE for " + method.name + method.desc);
}
/**
* Update INVOKESPECIAL opcodes to target the topmost class in the hierarchy
* which contains the specified method.
*
* @param method Method containing the instruction
* @param methodRef Unbound reference to the method
*/
private void updateStaticBinding(MethodNode method, MemberRef methodRef) {
this.updateBinding(method, methodRef, Traversal.SUPER);
}
/**
* Update INVOKEVIRTUAL opcodes to target the topmost class in the hierarchy
* which contains the specified method.
*
* @param method Method containing the instruction
* @param methodRef Unbound reference to the method
*/
private void updateDynamicBinding(MethodNode method, MemberRef methodRef) {
this.updateBinding(method, methodRef, Traversal.ALL);
}
private void updateBinding(MethodNode method, MemberRef methodRef, Traversal traversal) {
if (Constants.CTOR.equals(method.name)
|| methodRef.getOwner().equals(this.targetClass.getName())
|| this.targetClass.getName().startsWith("<")) {
return;
}
ClassInfo.Method superMethod = this.targetClassInfo.findMethodInHierarchy(methodRef.getName(), methodRef.getDesc(),
traversal.getSearchType(), traversal);
if (superMethod != null) {
if (superMethod.getOwner().isMixin()) {
throw new InvalidMixinException(this, "Invalid " + methodRef + " in " + this + " resolved " + superMethod.getOwner()
+ " but is mixin.");
}
methodRef.setOwner(superMethod.getImplementor().getName());
} else if (ClassInfo.forName(methodRef.getOwner()).isMixin()) {
throw new MixinTransformerError("Error resolving " + methodRef + " in " + this);
}
}
/**
* Transforms a field descriptor in the context of this mixin target
*
* @param field Field node to transform
*/
public void transformDescriptor(FieldNode field) {
if (!this.inheritsFromMixin) {
return;
}
field.desc = this.transformSingleDescriptor(field.desc, false);
}
/**
* Transforms a method descriptor in the context of this mixin target
*
* @param method Method node to transform
*/
public void transformDescriptor(MethodNode method) {
if (!this.inheritsFromMixin) {
return;
}
method.desc = this.transformMethodDescriptor(method.desc);
}
/**
* Transforms a method or field reference descriptor in the context of this
* mixin target
*
* @param member Reference to the method or field
*/
public void transformDescriptor(MemberRef member) {
if (!this.inheritsFromMixin) {
return;
}
if (member.isField()) {
member.setDesc(this.transformSingleDescriptor(member.getDesc(), false));
} else {
member.setDesc(this.transformMethodDescriptor(member.getDesc()));
}
}
/**
* Transforms a type insn descriptor in the context of this mixin target
*
* @param typeInsn Type instruction node to transform
*/
public void transformDescriptor(TypeInsnNode typeInsn) {
if (!this.inheritsFromMixin) {
return;
}
typeInsn.desc = this.transformSingleDescriptor(typeInsn.desc, true);
}
private String transformDescriptor(Type type) {
if (type.getSort() == Type.METHOD) {
return this.transformMethodDescriptor(type.getDescriptor());
}
return this.transformSingleDescriptor(type);
}
private String transformSingleDescriptor(Type type) {
if (type.getSort() < Type.ARRAY) {
return type.toString();
}
return this.transformSingleDescriptor(type.toString(), false);
}
private String transformSingleDescriptor(String desc, boolean isObject) {
String type = desc;
while (type.startsWith("[") || type.startsWith("L")) {
if (type.startsWith("[")) {
type = type.substring(1);
continue;
}
type = type.substring(1, type.indexOf(";"));
isObject = true;
}
if (!isObject) {
return desc;
}
ClassInfo typeInfo = ClassInfo.forName(type);
if (!typeInfo.isMixin()) {
return desc;
}
return desc.replace(type, this.findRealType(typeInfo).toString());
}
private String transformMethodDescriptor(String desc) {
StringBuilder newDesc = new StringBuilder();
newDesc.append('(');
for (Type arg : Type.getArgumentTypes(desc)) {
newDesc.append(this.transformSingleDescriptor(arg));
}
return newDesc.append(')').append(this.transformSingleDescriptor(Type.getReturnType(desc))).toString();
}
/**
* Get a target method handle from the target class
*
* @param method method to get a target handle for
* @return new or existing target handle for the supplied method
*/
@Override
public Target getTargetMethod(MethodNode method) {
return this.targetClass.getTargetMethod(method);
}
MethodNode findMethod(MethodNode method, AnnotationNode annotation) {
Deque<String> aliases = new LinkedList<String>();
aliases.add(method.name);
if (annotation != null) {
List<String> aka = Annotations.<List<String>>getValue(annotation, "aliases");
if (aka != null) {
aliases.addAll(aka);
}
}
return this.targetClass.findAliasedMethod(aliases, method.desc);
}
MethodNode findRemappedMethod(MethodNode method) {
RemapperChain remapperChain = MixinEnvironment.getCurrentEnvironment().getRemappers();
String remappedName = remapperChain.mapMethodName(this.targetClass.getName(), method.name, method.desc);
if (remappedName.equals(method.name)) {
return null;
}
Deque<String> aliases = new LinkedList<String>();
aliases.add(remappedName);
return this.targetClass.findAliasedMethod(aliases, method.desc);
}
FieldNode findField(FieldNode field, AnnotationNode shadow) {
Deque<String> aliases = new LinkedList<String>();
aliases.add(field.name);
if (shadow != null) {
List<String> aka = Annotations.<List<String>>getValue(shadow, "aliases");
if (aka != null) {
aliases.addAll(aka);
}
}
return this.targetClass.findAliasedField(aliases, field.desc);
}
FieldNode findRemappedField(FieldNode field) {
RemapperChain remapperChain = MixinEnvironment.getCurrentEnvironment().getRemappers();
String remappedName = remapperChain.mapFieldName(this.targetClass.getName(), field.name, field.desc);
if (remappedName.equals(field.name)) {
return null;
}
Deque<String> aliases = new LinkedList<String>();
aliases.add(remappedName);
return this.targetClass.findAliasedField( aliases, field.desc);
}
/**
* Mark this mixin as requiring the specified class version in the context
* of the current target
*
* @param version version to require
*/
protected void requireVersion(int version) {
this.minRequiredClassVersion = Math.max(this.minRequiredClassVersion, version);
// This validation is done on the mixin beforehand, however it's still
// possible that an upstream transformer can inject java 7 instructions
// without updating the class version.
if (version > MixinEnvironment.getCompatibilityLevel().classVersion()) {
throw new InvalidMixinException(this, "Unsupported mixin class version " + version);
}
}
/* (non-Javadoc)
* @see org.spongepowered.asm.mixin.refmap.IMixinContext
* #getMixin()
*/
@Override
public IMixinInfo getMixin() {
return this.mixin;
}
/**
* Get the mixin info for this mixin
*/
MixinInfo getInfo() {
return this.mixin;
}
/**
* Get the mixin priority
*
* @return the priority (only meaningful in relation to other mixins)
*/
@Override
public int getPriority() {
return this.mixin.getPriority();
}
/**
* Get all interfaces for this mixin
*
* @return mixin interfaces
*/
public Set<String> getInterfaces() {
return this.mixin.getInterfaces();
}
/**
* Get shadow methods in this mixin
*
* @return shadow methods in the mixin
*/
public Collection<MethodNode> getShadowMethods() {
return this.shadowMethods;
}
/**
* Get methods to mixin
*
* @return non-shadow methods in the mixin
*/
public List<MethodNode> getMethods() {
return this.classNode.methods;
}
/**
* Get shadow fields in this mixin
*
* @return shadow fields in the mixin
*/
public Set<Entry<FieldNode, Field>> getShadowFields() {
return this.shadowFields.entrySet();
}
/**
* Get fields to mixin
*
* @return non-shadow fields in the mixin
*/
public List<FieldNode> getFields() {
return this.classNode.fields;
}
/**
* Get the logging level for this mixin
*
* @return the logging level
*/
public Level getLoggingLevel() {
return this.mixin.getLoggingLevel();
}
/**
* Get whether to propogate the source file attribute from a mixin onto the
* target class
*
* @return true if the sourcefile property should be set on the target class
*/
public boolean shouldSetSourceFile() {
return this.mixin.getParent().shouldSetSourceFile();
}
/**
* Return the source file name for the mixin
*
* @return mixin source file
*/
public String getSourceFile() {
return this.classNode.sourceFile;
}
/* (non-Javadoc)
* @see org.spongepowered.asm.mixin.transformer.IReferenceMapperContext
* #getReferenceMapper()
*/
@Override
public ReferenceMapper getReferenceMapper() {
return this.mixin.getParent().getReferenceMapper();
}
/**
* Called immediately before the mixin is applied to targetClass
*
* @param transformedName Target class's transformed name
* @param targetClass Target class
*/
public void preApply(String transformedName, ClassNode targetClass) {
this.mixin.preApply(transformedName, targetClass);
}
/**
* Called immediately after the mixin is applied to targetClass
*
* @param transformedName Target class's transformed name
* @param targetClass Target class
*/
public void postApply(String transformedName, ClassNode targetClass) {
try {
this.injectorGroups.validateAll();
} catch (InjectionValidationException ex) {
InjectorGroupInfo group = ex.getGroup();
throw new InjectionError(
String.format("Critical injection failure: Callback group %s in %s failed injection check: %s",
group, this.mixin, ex.getMessage()));
}
this.mixin.postApply(transformedName, targetClass);
}
/**
* Obtain a unique name for the specified method from the target class
* context
*
* @param method method to obtain a name for
* @param preservePrefix true to preserve the method prefix (decorate as
* postfix) otherwise decorates as infix
* @return unique method name
*/
public String getUniqueName(MethodNode method, boolean preservePrefix) {
return this.targetClass.getUniqueName(method, preservePrefix);
}
/**
* Obtain a unique name for the specified field from the target class
* context
*
* @param field field to obtain a name for
* @return unique field name
*/
public String getUniqueName(FieldNode field) {
return this.targetClass.getUniqueName(field);
}
/**
* Scans the target class for injector methods and prepares discovered
* injectors
*/
public void prepareInjections() {
this.injectors.clear();
for (MethodNode method : this.mergedMethods) {
InjectionInfo injectInfo = InjectionInfo.parse(this, method);
if (injectInfo == null) {
continue;
}
if (injectInfo.isValid()) {
injectInfo.prepare();
this.injectors.add(injectInfo);
}
method.visibleAnnotations.remove(injectInfo.getAnnotation());
}
}
/**
* Apply injectors discovered in the {@link #prepareInjections()} pass
*/
public void applyInjections() {
for (InjectionInfo injectInfo : this.injectors) {
injectInfo.inject();
}
for (InjectionInfo injectInfo : this.injectors) {
injectInfo.postInject();
}
this.injectors.clear();
}
/**
* Expand accessor methods mixed into the target class by populating the
* method bodies
*/
public List<MethodNode> generateAccessors() {
for (AccessorInfo accessor : this.accessors) {
accessor.locate();
}
List<MethodNode> methods = new ArrayList<MethodNode>();
for (AccessorInfo accessor : this.accessors) {
methods.add(accessor.generate());
}
return methods;
}
}