/*
* 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.struct;
import java.util.Iterator;
import org.spongepowered.asm.lib.Type;
import org.spongepowered.asm.lib.tree.AbstractInsnNode;
import org.spongepowered.asm.lib.tree.ClassNode;
import org.spongepowered.asm.lib.tree.InsnList;
import org.spongepowered.asm.lib.tree.MethodNode;
import org.spongepowered.asm.mixin.injection.InjectionNodes;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.util.Bytecode;
/**
* Information about the current injection target, mainly just convenience
* rather than passing a bunch of values around.
*/
public class Target implements Comparable<Target>, Iterable<AbstractInsnNode> {
/**
* Target class node
*/
public final ClassNode classNode;
/**
* Target method
*/
public final MethodNode method;
/**
* Method instructions
*/
public final InsnList insns;
/**
* True if the method is static
*/
public final boolean isStatic;
/**
* Method arguments
*/
public final Type[] arguments;
/**
* Method argument slots
*/
public final int[] argIndices;
/**
* Return type computed from the method descriptor
*/
public final Type returnType;
/**
* Callback method descriptor based on this target
*/
public final String callbackDescriptor;
/**
* Callback info class
*/
public final String callbackInfoClass;
/**
* Nodes targetted by injectors
*/
public final InjectionNodes injectionNodes = new InjectionNodes();
/**
* Method's (original) MAXS
*/
private final int maxStack;
/**
* Method's original max locals
*/
private final int maxLocals;
/**
* Make a new Target for the supplied method
*
* @param method target method
*/
public Target(ClassNode classNode, MethodNode method) {
this.classNode = classNode;
this.method = method;
this.insns = method.instructions;
this.isStatic = Bytecode.methodIsStatic(method);
this.arguments = Type.getArgumentTypes(method.desc);
this.argIndices = this.calcArgIndices(this.isStatic ? 0 : 1);
this.returnType = Type.getReturnType(method.desc);
this.maxStack = method.maxStack;
this.maxLocals = method.maxLocals;
this.callbackInfoClass = CallbackInfo.getCallInfoClassName(this.returnType);
this.callbackDescriptor = String.format("(%sL%s;)V", method.desc.substring(1, method.desc.indexOf(')')), this.callbackInfoClass);
}
/**
* Get the original max locals of the method
*
* @return the original max locals value
*/
public int getMaxLocals() {
return this.maxLocals;
}
/**
* Get the original max stack of the method
*
* @return the original max stack value
*/
public int getMaxStack() {
return this.maxStack;
}
/**
* Get the current max locals of the method
*
* @return the current max local value
*/
public int getCurrentMaxLocals() {
return this.method.maxLocals;
}
/**
* Get the current max stack of the method
*
* @return the current max stack value
*/
public int getCurrentMaxStack() {
return this.method.maxStack;
}
/**
* Allocate a new local variable for the method
*
* @return the allocated local index
*/
public int allocateLocal() {
return this.allocateLocals(1);
}
/**
* Allocate a number of new local variables for this method, returns the
* first local variable index of the allocated range
*
* @param locals number of locals to allocate
* @return the first local variable index of the allocated range
*/
public int allocateLocals(int locals) {
int nextLocal = this.method.maxLocals;
this.method.maxLocals += locals;
return nextLocal;
}
/**
* Allocate a number of new local variables for this method
*
* @param locals number of locals to allocate
*/
public void addToLocals(int locals) {
this.setMaxLocals(this.maxLocals + locals);
}
/**
* Set the maxlocals for this target to the specified value, the specfied
* value must be higher than the original max locals
*
* @param maxLocals max locals value to set
*/
public void setMaxLocals(int maxLocals) {
if (maxLocals > this.method.maxLocals) {
this.method.maxLocals = maxLocals;
}
}
/**
* Allocate a number of new stack variables for this method
*
* @param stack number of stack entries to allocate
*/
public void addToStack(int stack) {
this.setMaxStack(this.maxStack + stack);
}
/**
* Set the max stack size for this target to the specified value, the
* specfied value must be higher than the original max stack
*
* @param maxStack max stack value to set
*/
public void setMaxStack(int maxStack) {
if (maxStack > this.method.maxStack) {
this.method.maxStack = maxStack;
}
}
/**
* Generate an array containing local indexes for the specified args,
* returns an array of identical size to the supplied array with an
* allocated local index in each corresponding position
*
* @param args Argument types
* @param start starting index
* @return array containing a corresponding local arg index for each member
* of the supplied args array
*/
public int[] generateArgMap(Type[] args, int start) {
int local = this.maxLocals;
int[] argMap = new int[args.length];
for (int arg = start; arg < args.length; arg++) {
argMap[arg] = local;
local += args[arg].getSize();
}
return argMap;
}
private int[] calcArgIndices(int local) {
int[] argIndices = new int[this.arguments.length];
for (int arg = 0; arg < this.arguments.length; arg++) {
argIndices[arg] = local;
local += this.arguments[arg].getSize();
}
return argIndices;
}
/**
* Get "simple" callback descriptor (descriptor with only CallbackInfo)
*
* @return generated descriptor
*/
public String getSimpleCallbackDescriptor() {
return String.format("(L%s;)V", this.callbackInfoClass);
}
/**
* Get the callback descriptor
*
* @param locals Local variable types
* @param argumentTypes Argument types
* @return generated descriptor
*/
public String getCallbackDescriptor(final Type[] locals, Type[] argumentTypes) {
return this.getCallbackDescriptor(false, locals, argumentTypes, 0, Short.MAX_VALUE);
}
/**
* Get the callback descriptor
*
* @param captureLocals True if the callback is capturing locals
* @param locals Local variable types
* @param argumentTypes Argument types
* @param startIndex local index to start at
* @param extra extra locals to include
* @return generated descriptor
*/
public String getCallbackDescriptor(final boolean captureLocals, final Type[] locals, Type[] argumentTypes, int startIndex, int extra) {
if (!captureLocals || locals == null) {
return this.callbackDescriptor;
}
String descriptor = this.callbackDescriptor.substring(0, this.callbackDescriptor.indexOf(')'));
for (int l = startIndex; l < locals.length && extra > 0; l++) {
if (locals[l] != null) {
descriptor += locals[l].getDescriptor();
extra--;
}
}
return descriptor + ")V";
}
@Override
public String toString() {
return String.format("%s::%s%s", this.classNode.name, this.method.name, this.method.desc);
}
@Override
public int compareTo(Target o) {
if (o == null) {
return Integer.MAX_VALUE;
}
return this.toString().compareTo(o.toString());
}
/**
* Return the index of the specified instruction in this instruction list
*
* @param insn instruction to locate, must exist in the target
* @return opcode index
*/
public int indexOf(AbstractInsnNode insn) {
return this.insns.indexOf(insn);
}
/**
* Return the instruction at the specified index
*
* @param index opcode index
* @return requested instruction
*/
public AbstractInsnNode get(int index) {
return this.insns.get(index);
}
/* (non-Javadoc)
* @see java.lang.Iterable#iterator()
*/
@Override
public Iterator<AbstractInsnNode> iterator() {
return this.insns.iterator();
}
/**
* Replace an instruction in this target with the specified instruction and
* mark the node as replaced for other injectors
*
* @param location Instruction to replace
* @param insn Instruction to replace with
*/
public void replaceNode(AbstractInsnNode location, AbstractInsnNode insn) {
this.insns.insertBefore(location, insn);
this.insns.remove(location);
this.injectionNodes.replace(location, insn);
}
/**
* Replace an instruction in this target with the specified instructions and
* mark the node as replaced with the specified champion node from the list.
*
* @param location Instruction to replace
* @param champion Instruction which notionally replaces the original insn
* @param insns Instructions to actually insert (must contain champion)
*/
public void replaceNode(AbstractInsnNode location, AbstractInsnNode champion, InsnList insns) {
this.insns.insertBefore(location, insns);
this.insns.remove(location);
this.injectionNodes.replace(location, champion);
}
/**
* Wrap instruction in this target with the specified instructions and mark
* the node as replaced with the specified champion node from the list.
*
* @param location Instruction to replace
* @param champion Instruction which notionally replaces the original insn
* @param before Instructions to actually insert (must contain champion)
* @param after Instructions to insert after the specified location
*/
public void wrapNode(AbstractInsnNode location, AbstractInsnNode champion, InsnList before, InsnList after) {
this.insns.insertBefore(location, before);
this.insns.insert(location, after);
this.injectionNodes.replace(location, champion);
}
/**
* Replace an instruction in this target with the specified instructions and
* mark the original node as removed
*
* @param location Instruction to replace
* @param insns Instructions to replace with
*/
public void replaceNode(AbstractInsnNode location, InsnList insns) {
this.insns.insertBefore(location, insns);
this.removeNode(location);
}
/**
* Remove the specified instruction from the target and mark it as removed
* for injections
*
* @param insn instruction to remove
*/
public void removeNode(AbstractInsnNode insn) {
this.insns.remove(insn);
this.injectionNodes.remove(insn);
}
}