/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered <https://www.spongepowered.org> * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.spongepowered.asm.mixin.injection.modify; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.spongepowered.asm.lib.Type; import org.spongepowered.asm.lib.tree.AbstractInsnNode; import org.spongepowered.asm.lib.tree.AnnotationNode; import org.spongepowered.asm.lib.tree.LocalVariableNode; import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator.Context.Local; import org.spongepowered.asm.mixin.injection.struct.Target; import org.spongepowered.asm.util.Bytecode; import org.spongepowered.asm.util.Annotations; import org.spongepowered.asm.util.Locals; import org.spongepowered.asm.util.PrettyPrinter; import org.spongepowered.asm.util.SignaturePrinter; /** * Encapsulates logic for identifying a local variable in a target method using * 3 criteria: <em>ordinal</em>, <em>index</em> and <em>name</em>. This is used * by the {@link ModifyVariableInjector} and its associated injection points. */ public class LocalVariableDiscriminator { /** * Discriminator context information, wraps all relevant information about * a target location for use when performing discrimination */ public static class Context implements org.spongepowered.asm.util.PrettyPrinter.IPrettyPrintable { /** * Information about a local variable in the LVT, used during * discrimination */ public class Local { /** * Ordinal value of this local variable type */ int ord = 0; /** * Local variable name */ String name; /** * Local variable type */ Type type; public Local(String name, Type type) { this.name = name; this.type = type; } @Override public String toString() { return String.format("Local[ordinal=%d, name=%s, type=%s]", this.ord, this.name, this.type); } } /** * Target method for this context */ final Target target; /** * The return type of the handler in question, also the type of the * local variable that we care about */ final Type returnType; /** * Injection point */ final AbstractInsnNode node; /** * Base argument index, for static methods this is 0, for instance * methods this is 1 */ final int baseArgIndex; /** * Enumerated locals in this context */ final Local[] locals; /** * True if the handler (and target) are static */ private final boolean isStatic; public Context(Type returnType, boolean argsOnly, Target target, AbstractInsnNode node) { this.isStatic = Bytecode.methodIsStatic(target.method); this.returnType = returnType; this.target = target; this.node = node; this.baseArgIndex = this.isStatic ? 0 : 1; this.locals = this.initLocals(target, argsOnly, node); this.initOrdinals(); } private Local[] initLocals(Target target, boolean argsOnly, AbstractInsnNode node) { if (!argsOnly) { LocalVariableNode[] locals = Locals.getLocalsAt(target.classNode, target.method, node); if (locals != null) { Local[] lvt = new Local[locals.length]; for (int l = 0; l < locals.length; l++) { if (locals[l] != null) { lvt[l] = new Local(locals[l].name, Type.getType(locals[l].desc)); } } return lvt; } } Local[] lvt = new Local[this.baseArgIndex + target.arguments.length]; if (!this.isStatic) { lvt[0] = new Local("this", Type.getType(target.classNode.name)); } for (int local = this.baseArgIndex; local < lvt.length; local++) { Type arg = target.arguments[local - this.baseArgIndex]; lvt[local] = new Local("arg" + local, arg); } return lvt; } private void initOrdinals() { Map<Type, Integer> ordinalMap = new HashMap<Type, Integer>(); for (int l = 0; l < this.locals.length; l++) { Integer ordinal = Integer.valueOf(0); if (this.locals[l] != null) { ordinal = ordinalMap.get(this.locals[l].type); ordinalMap.put(this.locals[l].type, ordinal = Integer.valueOf(ordinal == null ? 0 : ordinal.intValue() + 1)); this.locals[l].ord = ordinal.intValue(); } } } @Override public void print(PrettyPrinter printer) { printer.add("%5s %7s %30s %-50s %s", "INDEX", "ORDINAL", "TYPE", "NAME", "CANDIDATE"); for (int l = this.baseArgIndex; l < this.locals.length; l++) { Local local = this.locals[l]; if (local != null) { Type localType = local.type; String localName = local.name; int ordinal = local.ord; String candidate = this.returnType.equals(localType) ? "YES" : "-"; printer.add("[%3d] [%3d] %30s %-50s %s", l, ordinal, SignaturePrinter.getTypeName(localType, false), localName, candidate); } else if (l > 0) { Local prevLocal = this.locals[l - 1]; boolean isTop = prevLocal != null && prevLocal.type != null && prevLocal.type.getSize() > 1; printer.add("[%3d] %30s", l, isTop ? "<top>" : "-"); } } } } /** * True to consider only method args */ private final boolean argsOnly; /** * Ordinal of the target variable or -1 to fail over to {@link index} */ private final int ordinal; /** * Ordinal of the target variable or -1 to fail over to {@link names} */ private final int index; /** * Candidate names for the local variable, if empty fails over to matching * single local by type */ private final Set<String> names; /** * @param argsOnly true to only search within the method arguments * @param ordinal target variable ordinal * @param index target variable index * @param names target variable names */ public LocalVariableDiscriminator(boolean argsOnly, int ordinal, int index, Set<String> names) { this.argsOnly = argsOnly; this.ordinal = ordinal; this.index = index; this.names = Collections.<String>unmodifiableSet(names); } /** * True if this discriminator will examine only the target method args and * won't consider the rest of the LVT at the target location */ public boolean isArgsOnly() { return this.argsOnly; } /** * Get the local variable ordinal (nth variable of type) */ public int getOrdinal() { return this.ordinal; } /** * Get the local variable absolute index */ public int getIndex() { return this.index; } /** * Get valid names for consideration */ public Set<String> getNames() { return this.names; } /** * Returns true if names is not empty */ public boolean hasNames() { return !this.names.isEmpty(); } /** * If the user specifies no values for <tt>ordinal</tt>, <tt>index</tt> or * <tt>names</tt> then we are considered to be operating in "implicit mode" * where only a single local variable of the specified type is expected to * exist. * * @param context Target context * @return true if operating in implicit mode */ protected boolean isImplicit(final Context context) { return this.ordinal < 0 && this.index < context.baseArgIndex && this.names.isEmpty(); } /** * Find a matching local variable in the specified target * * @param returnType variable tyoe * @param argsOnly only match in the method args * @param target target method * @param node current instruction * @return index of local or -1 if not matched */ public int findLocal(Type returnType, boolean argsOnly, Target target, AbstractInsnNode node) { try { return this.findLocal(new Context(returnType, argsOnly, target, node)); } catch (InvalidImplicitDiscriminatorException ex) { return -1; } } /** * Find a local variable for the specified context * * @param context search context * @return index of local or -1 if not found */ public int findLocal(Context context) { if (this.isImplicit(context)) { return this.findImplicitLocal(context); } return this.findExplicitLocal(context); } /** * Find an implicit local, this means that we expect exactly 1 variable with * the specified type in scope, if more than one is found then we throw an * {@link InvalidImplicitDiscriminatorException} * * @param context search context * @return local variable index */ private int findImplicitLocal(final Context context) { int found = 0; int count = 0; for (int index = context.baseArgIndex; index < context.locals.length; index++) { Local local = context.locals[index]; if (local == null || !local.type.equals(context.returnType)) { continue; } count++; found = index; } if (count == 1) { return found; } throw new InvalidImplicitDiscriminatorException("Found " + count + " candidate variables but exactly 1 is required."); } /** * Find an explicit local variable in the local variable table. Returns -1 * if no variables match the discriminator * * @param context search context * @return variable index or -1 if not found */ private int findExplicitLocal(final Context context) { for (int index = context.baseArgIndex; index < context.locals.length; index++) { Local local = context.locals[index]; if (local == null || !local.type.equals(context.returnType)) { continue; } if (this.ordinal > -1) { if (this.ordinal == local.ord) { return index; } continue; } if (this.index >= context.baseArgIndex) { if (this.index == index) { return index; } continue; } if (this.names.contains(local.name)) { return index; } } return -1; } /** * Parse a local variable discriminator from the supplied annotation * * @param annotation annotation to parse * @return discriminator configured using values from the annoation */ public static LocalVariableDiscriminator parse(AnnotationNode annotation) { boolean argsOnly = Annotations.<Boolean>getValue(annotation, "argsOnly", Boolean.FALSE).booleanValue(); int ordinal = Annotations.<Integer>getValue(annotation, "ordinal", -1); int index = Annotations.<Integer>getValue(annotation, "index", -1); Set<String> names = new HashSet<String>(); List<String> namesList = Annotations.<List<String>>getValue(annotation, "name", (List<String>)null); if (namesList != null) { names.addAll(namesList); } return new LocalVariableDiscriminator(argsOnly, ordinal, index, names); } }