/* * 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.util.Deque; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.SortedSet; 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.MethodNode; import org.spongepowered.asm.mixin.Debug; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.injection.struct.Target; import org.spongepowered.asm.mixin.transformer.meta.SourceMap; import org.spongepowered.asm.util.Bytecode; import org.spongepowered.asm.util.Annotations; import org.spongepowered.asm.util.ClassSignature; /** * Struct for containing target class information during mixin application */ class TargetClassContext { /** * Transformer session ID */ private final String sessionId; /** * Target class name */ private final String className; /** * Target class as tree */ private final ClassNode classNode; /** * Target class metadata */ private final ClassInfo classInfo; /** * Source map that is generated for target class */ private final SourceMap sourceMap; /** * Target class signature */ private final ClassSignature signature; /** * Mixins to apply */ private final SortedSet<MixinInfo> mixins; /** * Information about methods in the target class, used to keep track of * transformations we apply */ private final Map<String, Target> targetMethods = new HashMap<String, Target>(); /** * Unique method and field indices */ private int nextUniqueMethodIndex, nextUniqueFieldIndex; /** * True once mixins have been applied to this class */ private boolean applied; /** * True if this class is decorated with an {@link Debug} annotation which * instructs an export */ private boolean forceExport; TargetClassContext(String sessionId, String name, ClassNode classNode, SortedSet<MixinInfo> mixins) { this.sessionId = sessionId; this.className = name; this.classNode = classNode; this.classInfo = ClassInfo.fromClassNode(classNode); this.signature = this.classInfo.getSignature(); this.mixins = mixins; this.sourceMap = new SourceMap(classNode.sourceFile); this.sourceMap.addFile(this.classNode); } @Override public String toString() { return this.className; } boolean isApplied() { return this.applied; } boolean isExportForced() { return this.forceExport; } /** * Get the transformer session ID */ String getSessionId() { return this.sessionId; } /** * Get the internal class name */ String getName() { return this.classNode.name; } /** * Get the class name */ String getClassName() { return this.className; } /** * Get the class tree */ ClassNode getClassNode() { return this.classNode; } /** * Get the class methods (from the tree) */ List<MethodNode> getMethods() { return this.classNode.methods; } /** * Get the class fields (from the tree) */ List<FieldNode> getFields() { return this.classNode.fields; } /** * Get the target class metadata */ ClassInfo getClassInfo() { return this.classInfo; } /** * Get the mixins for this target class */ SortedSet<MixinInfo> getMixins() { return this.mixins; } /** * Get the source map that is generated for the target class */ SourceMap getSourceMap() { return this.sourceMap; } /** * Merge the supplied signature into this class's signature * * @param signature signature to merge */ void mergeSignature(ClassSignature signature) { this.signature.merge(signature); } MethodNode findAliasedMethod(Deque<String> aliases, String desc) { String alias = aliases.poll(); if (alias == null) { return null; } for (MethodNode target : this.classNode.methods) { if (target.name.equals(alias) && target.desc.equals(desc)) { return target; } } return this.findAliasedMethod(aliases, desc); } /** * Finds a field in the target class * * @param aliases aliases for the field * @param desc field descriptor * @return Target field or null if not found */ FieldNode findAliasedField(Deque<String> aliases, String desc) { String alias = aliases.poll(); if (alias == null) { return null; } for (FieldNode target : this.classNode.fields) { if (target.name.equals(alias) && target.desc.equals(desc)) { return target; } } return this.findAliasedField(aliases, desc); } /** * 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 */ Target getTargetMethod(MethodNode method) { if (!this.classNode.methods.contains(method)) { throw new IllegalArgumentException("Invalid target method supplied to getTargetMethod()"); } String targetName = method.name + method.desc; Target target = this.targetMethods.get(targetName); if (target == null) { target = new Target(this.classNode, method); this.targetMethods.put(targetName, target); } return target; } String getUniqueName(MethodNode method, boolean preservePrefix) { String uniqueIndex = Integer.toHexString(this.nextUniqueMethodIndex++); String pattern = preservePrefix ? "%2$s_$md$%1$s$%3$s" : "md%s$%s$%s"; return String.format(pattern, this.sessionId.substring(30), method.name, uniqueIndex); } String getUniqueName(FieldNode field) { String uniqueIndex = Integer.toHexString(this.nextUniqueFieldIndex++); return String.format("fd%s$%s$%s", this.sessionId.substring(30), field.name, uniqueIndex); } /** * Apply mixins to this class */ void applyMixins() { if (this.applied) { throw new IllegalStateException("Mixins already applied to target class " + this.className); } this.applied = true; MixinApplicatorStandard applicator = this.createApplicator(); applicator.apply(this.mixins); this.classNode.signature = this.signature.toString(); } private MixinApplicatorStandard createApplicator() { if (this.classInfo.isInterface()) { return new MixinApplicatorInterface(this); } return new MixinApplicatorStandard(this); } /** * Process {@link Debug} annotations on the class after application */ void processDebugTasks() { if (!MixinEnvironment.getCurrentEnvironment().getOption(Option.DEBUG_VERBOSE)) { return; } AnnotationNode classDebugAnnotation = Annotations.getVisible(this.classNode, Debug.class); if (classDebugAnnotation != null) { this.forceExport = Boolean.TRUE.equals(Annotations.<String>getValue(classDebugAnnotation, "export")); if (Boolean.TRUE.equals(Annotations.<String>getValue(classDebugAnnotation, "print"))) { Bytecode.textify(this.classNode, System.err); } } for (MethodNode method : this.classNode.methods) { AnnotationNode methodDebugAnnotation = Annotations.getVisible(method, Debug.class); if (methodDebugAnnotation != null && Boolean.TRUE.equals(Annotations.<String>getValue(methodDebugAnnotation, "print"))) { Bytecode.textify(method, System.err); } } } }