/* * 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.Collection; import java.util.List; import org.spongepowered.asm.lib.Opcodes; import org.spongepowered.asm.lib.Type; import org.spongepowered.asm.lib.tree.AbstractInsnNode; import org.spongepowered.asm.lib.tree.InsnList; import org.spongepowered.asm.lib.tree.MethodNode; import org.spongepowered.asm.lib.tree.VarInsnNode; import org.spongepowered.asm.mixin.injection.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.code.Injector; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.Target; import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException; import org.spongepowered.asm.mixin.refmap.IMixinContext; import org.spongepowered.asm.util.Bytecode; import org.spongepowered.asm.util.PrettyPrinter; import org.spongepowered.asm.util.SignaturePrinter; /** * A bytecode injector which allows a single local variable in the target method * to be captured and altered. See also {@link LocalVariableDiscriminator} and * {@link ModifyVariable}. */ public class ModifyVariableInjector extends Injector { /** * Target context information */ static class Context extends LocalVariableDiscriminator.Context { /** * Instructions to inject */ final InsnList insns = new InsnList(); public Context(Type returnType, boolean argsOnly, Target target, AbstractInsnNode node) { super(returnType, argsOnly, target, node); } } /** * Specialised injection point which uses a target-aware search pattern */ abstract static class ContextualInjectionPoint extends InjectionPoint { protected final IMixinContext context; ContextualInjectionPoint(IMixinContext context) { this.context = context; } @Override public boolean find(String desc, InsnList insns, Collection<AbstractInsnNode> nodes) { throw new InvalidInjectionException(this.context, "STORE injection point must be used in conjunction with @ModifyVariable"); } abstract boolean find(Target target, Collection<AbstractInsnNode> nodes); } /** * Print LVT */ private final boolean print; /** * True to consider only method args */ private final LocalVariableDiscriminator discriminator; /** * @param info Injection info * @param print True to print this injector's discovered LVT * @param discriminator discriminator */ public ModifyVariableInjector(InjectionInfo info, boolean print, LocalVariableDiscriminator discriminator) { super(info); this.print = print; this.discriminator = discriminator; } @Override protected boolean findTargetNodes(MethodNode into, InjectionPoint injectionPoint, InsnList insns, Collection<AbstractInsnNode> nodes) { if (injectionPoint instanceof ContextualInjectionPoint) { Target target = this.info.getContext().getTargetMethod(into); return ((ContextualInjectionPoint)injectionPoint).find(target, nodes); } return injectionPoint.find(into.desc, insns, nodes); } /* (non-Javadoc) * @see org.spongepowered.asm.mixin.injection.callback.BytecodeInjector * #sanityCheck(org.spongepowered.asm.mixin.injection.callback.Target, * java.util.List) */ @Override protected void sanityCheck(Target target, List<InjectionPoint> injectionPoints) { super.sanityCheck(target, injectionPoints); if (target.isStatic != this.isStatic) { throw new InvalidInjectionException(this.info, "'static' of variable modifier method does not match target in " + this); } int ordinal = this.discriminator.getOrdinal(); if (ordinal < -1) { throw new InvalidInjectionException(this.info, "Invalid ordinal " + ordinal + " specified in " + this); } if (this.discriminator.getIndex() == 0 && !this.isStatic) { throw new InvalidInjectionException(this.info, "Invalid index 0 specified in non-static variable modifier " + this); } } /** * Do the injection */ @Override protected void inject(Target target, InjectionNode node) { if (node.isReplaced()) { throw new InvalidInjectionException(this.info, "Variable modifier target for " + this + " was removed by another injector"); } Context context = new Context(this.returnType, this.discriminator.isArgsOnly(), target, node.getCurrentTarget()); if (this.print) { this.printLocals(context); } try { int local = this.discriminator.findLocal(context); if (local > -1) { this.inject(context, local); } } catch (InvalidImplicitDiscriminatorException ex) { throw new InvalidInjectionException(this.info, "Implicit variable modifier injection failed in " + this, ex); } target.insns.insertBefore(context.node, context.insns); target.addToStack(this.isStatic ? 1 : 2); } /** * Pretty-print local variable information to stderr */ private void printLocals(final Context context) { SignaturePrinter handlerSig = new SignaturePrinter(this.methodNode.name, this.returnType, this.methodArgs, new String[] { "var" }); handlerSig.setModifiers(this.methodNode); new PrettyPrinter() .kvWidth(20) .kv("Target Class", this.classNode.name.replace('/', '.')) .kv("Target Method", context.target.method.name) .kv("Callback Name", this.methodNode.name) .kv("Capture Type", SignaturePrinter.getTypeName(this.returnType, false)) .kv("Instruction", "%s %s", context.node.getClass().getSimpleName(), Bytecode.getOpcodeName(context.node.getOpcode())).hr() .kv("Match mode", this.discriminator.isImplicit(context) ? "IMPLICIT (match single)" : "EXPLICIT (match by criteria)") .kv("Match ordinal", this.discriminator.getOrdinal() < 0 ? "any" : this.discriminator.getOrdinal()) .kv("Match index", this.discriminator.getIndex() < context.baseArgIndex ? "any" : this.discriminator.getIndex()) .kv("Match name(s)", this.discriminator.hasNames() ? this.discriminator.getNames() : "any") .kv("Args only", this.discriminator.isArgsOnly()).hr() .add(context) .print(System.err); } /** * Perform the injection * * @param context target context * @param local local variable to capture */ private void inject(final Context context, final int local) { if (!this.isStatic) { context.insns.add(new VarInsnNode(Opcodes.ALOAD, 0)); } context.insns.add(new VarInsnNode(this.returnType.getOpcode(Opcodes.ILOAD), local)); this.invokeHandler(context.insns); context.insns.add(new VarInsnNode(this.returnType.getOpcode(Opcodes.ISTORE), local)); } }