1157 lines
40 KiB
Java
1157 lines
40 KiB
Java
package dev.pfaff.recipe_nope.injector;
|
|
|
|
import com.google.common.collect.ObjectArrays;
|
|
import dev.pfaff.recipe_nope.injector.util.InsnConsumer;
|
|
import dev.pfaff.recipe_nope.injector.util.ReflectUtil;
|
|
import org.apache.logging.log4j.LogManager;
|
|
import org.apache.logging.log4j.Logger;
|
|
import org.jetbrains.annotations.NotNull;
|
|
import org.objectweb.asm.Opcodes;
|
|
import org.objectweb.asm.Type;
|
|
import org.objectweb.asm.tree.*;
|
|
import org.objectweb.asm.util.Textifier;
|
|
import org.objectweb.asm.util.TraceMethodVisitor;
|
|
import org.spongepowered.asm.mixin.Final;
|
|
import org.spongepowered.asm.mixin.injection.InjectionPoint;
|
|
import org.spongepowered.asm.mixin.injection.code.Injector;
|
|
import org.spongepowered.asm.mixin.injection.code.InsnListReadOnly;
|
|
import org.spongepowered.asm.mixin.injection.invoke.InvokeInjector;
|
|
import org.spongepowered.asm.mixin.injection.points.BeforeFieldAccess;
|
|
import org.spongepowered.asm.mixin.injection.points.BeforeNew;
|
|
import org.spongepowered.asm.mixin.injection.struct.InjectionInfo;
|
|
import org.spongepowered.asm.mixin.injection.struct.InjectionNodes;
|
|
import org.spongepowered.asm.mixin.injection.struct.Target;
|
|
import org.spongepowered.asm.mixin.injection.throwables.InjectionError;
|
|
import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException;
|
|
import org.spongepowered.asm.util.Annotations;
|
|
import org.spongepowered.asm.util.Bytecode;
|
|
import org.spongepowered.asm.util.Constants;
|
|
import org.spongepowered.asm.util.SignaturePrinter;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.ListIterator;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.StringJoiner;
|
|
|
|
import static java.lang.invoke.MethodType.methodType;
|
|
|
|
public final class UnconstrainedRedirectInjector extends InvokeInjector {
|
|
private static final Logger LOGGER = LogManager.getLogger();
|
|
|
|
private static final String GET_CLASS_METHOD = "getClass";
|
|
private static final String IS_ASSIGNABLE_FROM_METHOD = "isAssignableFrom";
|
|
|
|
private static final String NPE = Type.getInternalName(NullPointerException.class);
|
|
|
|
private static final String KEY_NOMINATORS = "nominators";
|
|
private static final String KEY_FUZZ = "fuzz";
|
|
private static final String KEY_OPCODE = "opcode";
|
|
|
|
private static final String SHADOW_CONSTRUCTOR_DESC = Type.getDescriptor(ShadowConstructor.class);
|
|
|
|
private static final InsnListReadOnly INSN_DEAD_METHOD;
|
|
|
|
private static final boolean ENFORCE_CONTRACTS = false;
|
|
|
|
static {
|
|
var insns = new InsnList();
|
|
var owner = Type.getInternalName(UnsupportedOperationException.class);
|
|
insns.add(new TypeInsnNode(Opcodes.NEW, owner));
|
|
insns.add(new InsnNode(Opcodes.DUP));
|
|
insns.add(new LdcInsnNode("Dead method (inlined or otherwise consumed by mixin)"));
|
|
insns.add(new MethodInsnNode(Opcodes.INVOKESPECIAL,
|
|
owner,
|
|
"<init>",
|
|
ReflectUtil.methodDescriptor(methodType(void.class, String.class), false)
|
|
));
|
|
insns.add(new InsnNode(Opcodes.ATHROW));
|
|
INSN_DEAD_METHOD = new InsnListReadOnly(insns);
|
|
}
|
|
|
|
/**
|
|
* Meta decoration object for redirector target nodes
|
|
*/
|
|
class Meta {
|
|
|
|
public static final String KEY = "redirector";
|
|
|
|
final int priority;
|
|
|
|
final boolean isFinal;
|
|
|
|
final String name;
|
|
|
|
final String desc;
|
|
|
|
public Meta(int priority, boolean isFinal, String name, String desc) {
|
|
this.priority = priority;
|
|
this.isFinal = isFinal;
|
|
this.name = name;
|
|
this.desc = desc;
|
|
}
|
|
|
|
UnconstrainedRedirectInjector getOwner() {
|
|
return UnconstrainedRedirectInjector.this;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Meta decoration for wildcard ctor redirects
|
|
*/
|
|
static class ConstructorRedirectData {
|
|
|
|
public static final String KEY = "ctor";
|
|
|
|
boolean wildcard = false;
|
|
|
|
int injected = 0;
|
|
|
|
InvalidInjectionException lastException;
|
|
|
|
public void throwOrCollect(InvalidInjectionException ex) {
|
|
if (!this.wildcard) {
|
|
throw ex;
|
|
}
|
|
this.lastException = ex;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Data bundle for invoke redirectors
|
|
*/
|
|
static class RedirectedInvokeData extends InjectorData {
|
|
|
|
final MethodInsnNode node;
|
|
final Type returnType;
|
|
final Type[] targetArgs;
|
|
final Type[] handlerArgs;
|
|
|
|
RedirectedInvokeData(Target target, MethodInsnNode node) {
|
|
super(target);
|
|
this.node = node;
|
|
this.returnType = Type.getReturnType(node.desc);
|
|
this.targetArgs = Type.getArgumentTypes(node.desc);
|
|
this.handlerArgs = node.getOpcode() == Opcodes.INVOKESTATIC
|
|
? this.targetArgs
|
|
: ObjectArrays.concat(Type.getObjectType(node.owner), this.targetArgs);
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Data bundle for field redirectors
|
|
*/
|
|
static class RedirectedFieldData extends InjectorData {
|
|
|
|
final FieldInsnNode node;
|
|
final int opcode;
|
|
final Type owner;
|
|
final Type type;
|
|
final int dimensions;
|
|
final boolean isStatic;
|
|
final boolean isGetter;
|
|
final boolean isSetter;
|
|
|
|
// This is actually the return type for array access, might be int for
|
|
// array length redirectors
|
|
Type elementType;
|
|
int extraDimensions = 1;
|
|
|
|
RedirectedFieldData(Target target, FieldInsnNode node) {
|
|
super(target);
|
|
this.node = node;
|
|
this.opcode = node.getOpcode();
|
|
this.owner = Type.getObjectType(node.owner);
|
|
this.type = Type.getType(node.desc);
|
|
this.dimensions = (this.type.getSort() == Type.ARRAY) ? this.type.getDimensions() : 0;
|
|
this.isStatic = this.opcode == Opcodes.GETSTATIC || this.opcode == Opcodes.PUTSTATIC;
|
|
this.isGetter = this.opcode == Opcodes.GETSTATIC || this.opcode == Opcodes.GETFIELD;
|
|
this.isSetter = this.opcode == Opcodes.PUTSTATIC || this.opcode == Opcodes.PUTFIELD;
|
|
this.description = this.isGetter ? "field getter" : this.isSetter ? "field setter" : "handler";
|
|
}
|
|
|
|
int getTotalDimensions() {
|
|
return this.dimensions + this.extraDimensions;
|
|
}
|
|
|
|
Type[] getArrayArgs(Type... extra) {
|
|
int dimensions = this.getTotalDimensions();
|
|
Type[] args = new Type[dimensions + extra.length];
|
|
for (int i = 0; i < args.length; i++) {
|
|
args[i] = i == 0 ? this.type : i < dimensions ? Type.INT_TYPE : extra[dimensions - i];
|
|
}
|
|
return args;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Meta is used to decorate the target node with information about this injection
|
|
*/
|
|
private final Meta meta;
|
|
|
|
private final Map<BeforeNew, ConstructorRedirectData> ctorRedirectors = new HashMap<>();
|
|
|
|
public UnconstrainedRedirectInjector(InjectionInfo info, String annotationType) {
|
|
super(info, annotationType);
|
|
|
|
int priority = info.getMixin().getPriority();
|
|
boolean isFinal = Annotations.getVisible(this.methodNode, Final.class) != null;
|
|
this.meta = new Meta(priority, isFinal, this.info.toString(), this.methodNode.desc);
|
|
}
|
|
|
|
@Override
|
|
protected void checkTarget(Target target) {
|
|
// Overridden so we can do this check later in a location-aware manner
|
|
}
|
|
|
|
@Override
|
|
protected void addTargetNode(Target target,
|
|
List<InjectionNodes.InjectionNode> myNodes,
|
|
AbstractInsnNode insn,
|
|
Set<InjectionPoint> nominators) {
|
|
InjectionNodes.InjectionNode node = target.getInjectionNode(insn);
|
|
ConstructorRedirectData ctorData = null;
|
|
int fuzz = BeforeFieldAccess.ARRAY_SEARCH_FUZZ_DEFAULT;
|
|
int opcode = 0;
|
|
|
|
if (node != null) {
|
|
Meta other = node.getDecoration(Meta.KEY);
|
|
|
|
if (other != null && other.getOwner() != this) {
|
|
if (other.priority >= this.meta.priority) {
|
|
Injector.logger.warn(
|
|
"{} conflict. Skipping {} with priority {}, already redirected by {} with priority {}",
|
|
this.annotationType,
|
|
this.info,
|
|
this.meta.priority,
|
|
other.name,
|
|
other.priority
|
|
);
|
|
return;
|
|
} else if (other.isFinal) {
|
|
throw new InvalidInjectionException(
|
|
this.info,
|
|
this.annotationType + " conflict: " + this + " failed because target was already remapped by " + other.name
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (InjectionPoint ip : nominators) {
|
|
if (ip instanceof BeforeNew) {
|
|
ctorData = this.getCtorRedirect((BeforeNew) ip);
|
|
ctorData.wildcard = !((BeforeNew) ip).hasDescriptor();
|
|
} else if (ip instanceof BeforeFieldAccess bfa) {
|
|
fuzz = bfa.getFuzzFactor();
|
|
opcode = bfa.getArrayOpcode();
|
|
}
|
|
}
|
|
|
|
InjectionNodes.InjectionNode targetNode = target.addInjectionNode(insn);
|
|
targetNode.decorate(Meta.KEY, this.meta);
|
|
targetNode.decorate(KEY_NOMINATORS, nominators);
|
|
if (insn instanceof TypeInsnNode && insn.getOpcode() == Opcodes.NEW) {
|
|
targetNode.decorate(ConstructorRedirectData.KEY, ctorData);
|
|
} else {
|
|
targetNode.decorate(KEY_FUZZ, fuzz);
|
|
targetNode.decorate(KEY_OPCODE, opcode);
|
|
}
|
|
myNodes.add(targetNode);
|
|
}
|
|
|
|
private ConstructorRedirectData getCtorRedirect(BeforeNew ip) {
|
|
ConstructorRedirectData ctorRedirect = this.ctorRedirectors.get(ip);
|
|
if (ctorRedirect == null) {
|
|
ctorRedirect = new ConstructorRedirectData();
|
|
this.ctorRedirectors.put(ip, ctorRedirect);
|
|
}
|
|
return ctorRedirect;
|
|
}
|
|
|
|
@Override
|
|
protected void inject(Target target, InjectionNodes.InjectionNode node) {
|
|
if (!this.preInject(node)) {
|
|
return;
|
|
}
|
|
|
|
if (node.isReplaced()) {
|
|
throw new UnsupportedOperationException("Redirector target failure for " + this.info);
|
|
}
|
|
|
|
if (node.getCurrentTarget() instanceof MethodInsnNode) {
|
|
this.checkTargetForNode(target, node, InjectionPoint.RestrictTargetLevel.ALLOW_ALL);
|
|
this.injectAtInvoke(target, node);
|
|
return;
|
|
}
|
|
|
|
if (node.getCurrentTarget() instanceof FieldInsnNode) {
|
|
this.checkTargetForNode(target, node, InjectionPoint.RestrictTargetLevel.ALLOW_ALL);
|
|
this.injectAtFieldAccess(target, node);
|
|
return;
|
|
}
|
|
|
|
if (node.getCurrentTarget() instanceof TypeInsnNode) {
|
|
int opcode = node.getCurrentTarget().getOpcode();
|
|
if (opcode == Opcodes.NEW) {
|
|
if (!this.isStatic && target.isStatic) {
|
|
throw new InvalidInjectionException(
|
|
this.info,
|
|
"non-static callback method " + this + " has a static target which is not supported"
|
|
);
|
|
}
|
|
this.injectAtConstructor(target, node);
|
|
return;
|
|
} else if (opcode == Opcodes.INSTANCEOF) {
|
|
this.checkTargetModifiers(target, false);
|
|
this.injectAtInstanceOf(target, node);
|
|
return;
|
|
}
|
|
}
|
|
|
|
throw new InvalidInjectionException(
|
|
this.info,
|
|
this.annotationType + " annotation on is targetting an invalid insn in " + target + " in " + this
|
|
);
|
|
}
|
|
|
|
@Override
|
|
protected void checkTargetForNode(Target target,
|
|
InjectionNodes.InjectionNode node,
|
|
InjectionPoint.RestrictTargetLevel targetLevel) {
|
|
if (target.isCtor) {
|
|
if (targetLevel == InjectionPoint.RestrictTargetLevel.METHODS_ONLY) {
|
|
throw new InvalidInjectionException(
|
|
this.info,
|
|
"Found " + this.annotationType + " targeting a constructor in injector " + this
|
|
);
|
|
}
|
|
|
|
Bytecode.DelegateInitialiser superCall = target.findDelegateInitNode();
|
|
if (!superCall.isPresent) {
|
|
throw new InjectionError("Delegate constructor lookup failed for " + this.annotationType + " target on " + this.info);
|
|
}
|
|
|
|
if (node.getCurrentTarget() instanceof MethodInsnNode methodInsn && Constants.CTOR.equals(methodInsn.name)) {
|
|
// the redirect is targeting the `this` or `super` invocation. Skip the rest of the checks.
|
|
this.checkTargetModifiers(target, true);
|
|
return;
|
|
}
|
|
int superCallIndex = target.indexOf(superCall.insn);
|
|
int targetIndex = target.indexOf(node.getCurrentTarget());
|
|
if (targetIndex <= superCallIndex) {
|
|
if (targetLevel == InjectionPoint.RestrictTargetLevel.CONSTRUCTORS_AFTER_DELEGATE) {
|
|
throw new InvalidInjectionException(
|
|
this.info,
|
|
"Found " + this.annotationType + " targeting a constructor before " + superCall + "() in injector " + this
|
|
);
|
|
}
|
|
|
|
if (!this.isStatic) {
|
|
throw new InvalidInjectionException(
|
|
this.info,
|
|
this.annotationType + " handler before " + superCall + "() invocation must be static in injector " + this
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
this.checkTargetModifiers(target, true);
|
|
}
|
|
|
|
private boolean preInject(InjectionNodes.InjectionNode node) {
|
|
Meta other = node.getDecoration(Meta.KEY);
|
|
if (other.getOwner() != this) {
|
|
Injector.logger.warn("{} conflict. Skipping {} with priority {}, already redirected by {} with priority {}",
|
|
this.annotationType,
|
|
this.info,
|
|
this.meta.priority,
|
|
other.name,
|
|
other.priority
|
|
);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
protected void postInject(Target target, InjectionNodes.InjectionNode node) {
|
|
super.postInject(target, node);
|
|
if (node.getOriginalTarget() instanceof TypeInsnNode && node.getOriginalTarget().getOpcode() == Opcodes.NEW) {
|
|
ConstructorRedirectData meta = node.getDecoration(ConstructorRedirectData.KEY);
|
|
if (meta.wildcard && meta.injected == 0) {
|
|
throw new InvalidInjectionException(
|
|
this.info,
|
|
this.annotationType + " ctor invocation was not found in " + target,
|
|
meta.lastException
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Redirect a method invocation
|
|
*/
|
|
@Override
|
|
protected void injectAtInvoke(Target target, InjectionNodes.InjectionNode node) {
|
|
RedirectedInvokeData invoke = new RedirectedInvokeData(target, (MethodInsnNode) node.getCurrentTarget());
|
|
|
|
LOGGER.info(() -> "Redirecting\n" + "call to " + methodInsnToString(invoke.node) + "\n\tin " + target.classNode.name + '.' + methodNodeToString(target.method) + "\nwith " + this.classNode.name + '.' + methodNodeToString(this.methodNode));
|
|
|
|
LOGGER.info(() -> "Target method LVT (pre replace): " + localVariablesToString(target.method.localVariables));
|
|
|
|
boolean isCtor;
|
|
if (node.getCurrentTarget() instanceof MethodInsnNode methodInsn && Constants.CTOR.equals(methodInsn.name)) {
|
|
if (this.isStatic) {
|
|
throw new InvalidInjectionException(this.info,
|
|
"Illegal " + this.annotationType + " of constructor specified on " + this + ": Is static"
|
|
);
|
|
}
|
|
isCtor = true;
|
|
} else {
|
|
isCtor = false;
|
|
}
|
|
|
|
this.validateParams(invoke,
|
|
invoke.returnType,
|
|
isCtor
|
|
? invoke.targetArgs
|
|
: invoke.handlerArgs
|
|
);
|
|
|
|
var clonedLabels = cloneLabelsFresh(this.methodNode.instructions);
|
|
var replacementInsns = new InsnList();
|
|
|
|
var labelStart = new LabelNode();
|
|
var labelEnd = new LabelNode();
|
|
|
|
// add the label for new lvt allocation "frame"
|
|
replacementInsns.add(labelStart);
|
|
|
|
for (var insn : this.methodNode.instructions) {
|
|
replacementInsns.add(insn.clone(clonedLabels));
|
|
}
|
|
|
|
if (isCtor) {
|
|
final var mixinTargetDesc = this.info.getClassNode().name;
|
|
boolean seenCtor = false;
|
|
// this initializer is duplicative, but the compiler insists.
|
|
for (var insn : replacementInsns) {
|
|
if ((insn instanceof MethodInsnNode methodInsn)) {
|
|
// remap constructor invocations
|
|
if (methodInsn.owner.equals(mixinTargetDesc)) {
|
|
var method = this.classNode.methods.stream()
|
|
.filter(m -> methodInsn.name.equals(m.name)
|
|
&& methodInsn.desc.equals(m.desc))
|
|
.findFirst()
|
|
.get();
|
|
LOGGER.info(" visible annotations: " + method.visibleAnnotations.stream().map(a -> a.desc).toList());
|
|
LOGGER.info("invisible annotations: " + method.invisibleAnnotations.stream().map(a -> a.desc).toList());
|
|
var annotation = method.invisibleAnnotations.stream()
|
|
.filter(a -> a.desc.equals(SHADOW_CONSTRUCTOR_DESC))
|
|
.findFirst();
|
|
if (annotation.isPresent()) {
|
|
var vals = annotation.get().values;
|
|
if (vals.size() != 2) {
|
|
throw new InvalidInjectionException(this.info,
|
|
"Illegal " + this.annotationType + " of constructor specified on " + this + ": Invoked this or super constructor is invalid");
|
|
}
|
|
if (!"type".equals(vals.get(0))) {
|
|
throw new InvalidInjectionException(this.info,
|
|
"Illegal " + this.annotationType + " of constructor specified on " + this + ": Invoked this or super constructor is invalid");
|
|
}
|
|
var enumValue = vals.get(1);
|
|
if (!(enumValue instanceof String[] a)) {
|
|
throw new InvalidInjectionException(this.info,
|
|
"Illegal " + this.annotationType + " of constructor specified on " + this + ": Invoked this or super constructor is invalid");
|
|
}
|
|
var owner = switch (a[1]) {
|
|
case "This" -> this.classNode.name;
|
|
case "Super" -> this.classNode.superName;
|
|
default -> throw new AssertionError();
|
|
};
|
|
seenCtor = true;
|
|
replacementInsns.set(insn, new MethodInsnNode(Opcodes.INVOKESPECIAL,
|
|
owner,
|
|
"<init>",
|
|
methodInsn.desc
|
|
));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!seenCtor) {
|
|
throw new InvalidInjectionException(this.info,
|
|
"Illegal " + this.annotationType + " of constructor specified on " + this + ": Missing super or this constructor invocation"
|
|
);
|
|
}
|
|
}
|
|
|
|
// get the args for the handler
|
|
Type[] handlerArgs = this.methodArgs;
|
|
//if (isCtor) {
|
|
// // skip the `this` argument
|
|
// handlerArgs = invoke.targetArgs;
|
|
//} else {
|
|
// handlerArgs = invoke.handlerArgs;
|
|
//}
|
|
LOGGER.info(() -> "Handler args: size=" + Bytecode.getArgsSize(handlerArgs) + ' ' + Arrays.toString(handlerArgs));
|
|
|
|
int skipLv = isStatic || isCtor ? 0 : 0;
|
|
|
|
{
|
|
// handled down below
|
|
// allocate N locals for the args
|
|
//target.allocateLocals(Bytecode.getArgsSize(handlerArgs));
|
|
|
|
// no need to extend the stack past that point, because we already have those slots from the original constructor invocation.
|
|
int n = this.methodNode.maxStack - handlerArgs.length;
|
|
if (n > 0) {
|
|
target.extendStack().add(n).apply();
|
|
}
|
|
|
|
for (int j = 0; j < handlerArgs.length; j++) {
|
|
Type arg = handlerArgs[j];
|
|
// handled down below
|
|
//// add a local variable for each
|
|
//target.addLocalVariable(target.getMaxLocals() + j,
|
|
// "__redirectionParam" + j,
|
|
// arg.getDescriptor(),
|
|
// labelStart,
|
|
// labelEnd
|
|
//);
|
|
// we need these in reverse order, so insert at labelStart (thus before previous) works perfectly.
|
|
replacementInsns.insert(labelStart,
|
|
new VarInsnNode(ReflectUtil.varStoreInsn(arg), target.method.maxLocals + j)
|
|
);
|
|
}
|
|
}
|
|
|
|
int varOffset = target.allocateLocals(this.methodNode.maxLocals - skipLv);
|
|
List<LocalVariableNode> localVariables = this.methodNode.localVariables;
|
|
for (int i = skipLv; i < localVariables.size(); i++) {
|
|
LocalVariableNode lv = localVariables.get(i);
|
|
int i1 = i;
|
|
int i2 = varOffset + lv.index - skipLv;
|
|
LOGGER.info(() -> "LV[" + i1 + " -> " + i2 + "] " + lv.name + ": " + lv.desc);
|
|
target.addLocalVariable(i2, "__redirectionLv" + i2 + '_' + lv.name, lv.desc, lv.start, lv.end);
|
|
}
|
|
int firstExplicit = isStatic ? 0 : 1;
|
|
// no instance argument
|
|
if (this.methodArgs.length == invoke.targetArgs.length) {
|
|
// pop unused instance
|
|
replacementInsns.add(new InsnNode(Opcodes.POP));
|
|
}
|
|
for (var insn : replacementInsns) {
|
|
if ((insn instanceof VarInsnNode varInsn)) {
|
|
int i = varInsn.var;
|
|
// i == 0 when accessing `this`. Don't change that.
|
|
if (isStatic || i != 0) varInsn.var = i + varOffset - skipLv;
|
|
}
|
|
}
|
|
|
|
if (!isCtor && invoke.coerceReturnType && invoke.returnType.getSort() >= Type.ARRAY) {
|
|
replacementInsns.add(new TypeInsnNode(Opcodes.CHECKCAST, invoke.returnType.getInternalName()));
|
|
}
|
|
|
|
// ~~replace return instructions with store and break~~ maybe not. just remove them for now.
|
|
for (ListIterator<AbstractInsnNode> iterator = replacementInsns.iterator(); iterator.hasNext(); ) {
|
|
AbstractInsnNode insn = iterator.next();
|
|
if ((insn instanceof InsnNode insn1) && insn1.getOpcode() >= Opcodes.IRETURN && insn1.getOpcode() <= Opcodes.RETURN) {
|
|
// does break pop from the stack? I suppose only if it creates a new stack frame? Might need to introduce new locals for compat
|
|
//iterator.remove();
|
|
// skip the immediate jump if we're at the end of the injected code
|
|
if (iterator.hasNext()) {
|
|
replacementInsns.set(insn, new JumpInsnNode(Opcodes.GOTO, labelEnd));
|
|
}
|
|
}
|
|
}
|
|
|
|
// add the label to end the lvt allocation "frame"
|
|
replacementInsns.add(labelEnd);
|
|
|
|
// useful for debugging
|
|
//new ClassReader(b).accept(new TraceClassVisitor(new PrintWriter(System.out)), 0)
|
|
LOGGER.trace(() -> "Target method instructions (pre replace): " + instructionsToString(target.insns));
|
|
target.insertBefore(invoke.node, replacementInsns);
|
|
target.removeNode(invoke.node);
|
|
// TODO: why does this cause the method to have *no* instructions?
|
|
//this.methodNode.instructions = INSN_DEAD_METHOD;
|
|
this.info.addCallbackInvocation(null);
|
|
|
|
LOGGER.info(() -> "Handler method LVT: " + localVariablesToString(this.methodNode.localVariables));
|
|
LOGGER.info(() -> "Handler method maxes: " + this.methodNode.maxLocals + ", " + this.methodNode.maxStack);
|
|
LOGGER.trace(() -> "Handler method instructions (dead): " + instructionsToString(this.methodNode.instructions));
|
|
LOGGER.info(() -> "Target method LVT: " + localVariablesToString(target.method.localVariables));
|
|
LOGGER.info(() -> "Target method maxes: " + target.method.maxLocals + ", " + target.method.maxStack);
|
|
LOGGER.trace(() -> "Target method instructions: " + instructionsToString(target.insns));
|
|
}
|
|
|
|
@NotNull
|
|
private static String localVariablesToString(List<LocalVariableNode> localVariables) {
|
|
return "size=" + localVariables.size() + ' ' + Arrays.toString(localVariables
|
|
.stream()
|
|
.map(lv -> "[" + lv.index + ']' + lv.name + ": " + lv.desc)
|
|
.toArray(String[]::new));
|
|
}
|
|
|
|
private static String methodInsnToString(MethodInsnNode node) {
|
|
return node.owner + '.' + node.name + node.desc;
|
|
}
|
|
|
|
private static String methodNodeToString(MethodNode node) {
|
|
return node.name + node.desc;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
@NotNull
|
|
private static String instructionsToString(InsnList theInsns) {
|
|
var printer = new Textifier();
|
|
var visitor = new TraceMethodVisitor(printer);
|
|
Arrays.stream(theInsns.toArray()).forEach(node1 -> node1.accept(visitor));
|
|
var joiner = new StringJoiner("\n");
|
|
((List<String>) (List<?>) printer.text).forEach(joiner::add);
|
|
return joiner.toString();
|
|
}
|
|
|
|
/**
|
|
* Redirect a field get or set operation, or an array element access
|
|
*/
|
|
private void injectAtFieldAccess(Target target, InjectionNodes.InjectionNode node) {
|
|
RedirectedFieldData field = new RedirectedFieldData(target, (FieldInsnNode) node.getCurrentTarget());
|
|
|
|
int handlerDimensions = (this.returnType.getSort() == Type.ARRAY) ? this.returnType.getDimensions() : 0;
|
|
|
|
if (handlerDimensions > field.dimensions) {
|
|
throw new InvalidInjectionException(this.info,
|
|
"Dimensionality of handler method is greater than target array on " + this
|
|
);
|
|
} else if (handlerDimensions == 0 && field.dimensions > 0) {
|
|
int fuzz = node.<Integer>getDecoration(KEY_FUZZ);
|
|
int opcode = node.<Integer>getDecoration(KEY_OPCODE);
|
|
this.injectAtArrayField(field, fuzz, opcode);
|
|
} else {
|
|
this.injectAtScalarField(field);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Redirect an array element access
|
|
*/
|
|
private void injectAtArrayField(RedirectedFieldData field, int fuzz, int opcode) {
|
|
Type elementType = field.type.getElementType();
|
|
if (field.opcode != Opcodes.GETSTATIC && field.opcode != Opcodes.GETFIELD) {
|
|
throw new InvalidInjectionException(
|
|
this.info,
|
|
"Unsupported opcode " + Bytecode.getOpcodeName(field.opcode) + " for array access " + this.info
|
|
);
|
|
} else if (this.returnType.getSort() != Type.VOID) {
|
|
if (opcode != Opcodes.ARRAYLENGTH) {
|
|
opcode = elementType.getOpcode(Opcodes.IALOAD);
|
|
}
|
|
AbstractInsnNode varNode = BeforeFieldAccess.findArrayNode(field.target.insns, field.node, opcode, fuzz);
|
|
this.injectAtGetArray(field, varNode);
|
|
} else {
|
|
AbstractInsnNode varNode = BeforeFieldAccess.findArrayNode(field.target.insns,
|
|
field.node,
|
|
elementType.getOpcode(Opcodes.IASTORE),
|
|
fuzz
|
|
);
|
|
this.injectAtSetArray(field, varNode);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Array element read (xALOAD) or array.length (ARRAYLENGTH)
|
|
*/
|
|
private void injectAtGetArray(RedirectedFieldData field, AbstractInsnNode varNode) {
|
|
field.description = "array getter";
|
|
field.elementType = field.type.getElementType();
|
|
|
|
if (varNode != null && varNode.getOpcode() == Opcodes.ARRAYLENGTH) {
|
|
field.elementType = Type.INT_TYPE;
|
|
field.extraDimensions = 0;
|
|
}
|
|
|
|
this.validateParams(field, field.elementType, field.getArrayArgs());
|
|
this.injectArrayRedirect(field, varNode, "array getter");
|
|
}
|
|
|
|
/**
|
|
* Array element write (xASTORE)
|
|
*/
|
|
private void injectAtSetArray(RedirectedFieldData field, AbstractInsnNode varNode) {
|
|
field.description = "array setter";
|
|
Type elementType = field.type.getElementType();
|
|
int valueArgIndex = field.getTotalDimensions();
|
|
if (this.checkCoerce(
|
|
valueArgIndex,
|
|
elementType,
|
|
this.annotationType + " array setter method " + this + " from " + this.info.getMixin(),
|
|
true
|
|
)) {
|
|
elementType = this.methodArgs[valueArgIndex];
|
|
}
|
|
|
|
this.validateParams(field, Type.VOID_TYPE, field.getArrayArgs(elementType));
|
|
this.injectArrayRedirect(field, varNode, "array setter");
|
|
}
|
|
|
|
/**
|
|
* The code for actually redirecting the array element is the same regardless of whether it's a read or write
|
|
* because it just depends on the actual handler signature, the correct arguments are already on the stack thanks to
|
|
* the nature of xALOAD and xASTORE.
|
|
*
|
|
* @param varNode array access node
|
|
* @param type description of access type for use in error messages
|
|
* @param target target method
|
|
* @param fieldNode field node
|
|
*/
|
|
private void injectArrayRedirect(RedirectedFieldData field, AbstractInsnNode varNode, String type) {
|
|
if (varNode == null) {
|
|
throw new InvalidInjectionException(this.info, "Array element " + this.annotationType + " on " + this + " could not locate a matching " + type + " instruction in " + field.target + '.');
|
|
}
|
|
|
|
InsnList insns = new InsnList();
|
|
var consumer = InsnConsumer.into(insns, field.target);
|
|
|
|
if (!this.isStatic) {
|
|
VarInsnNode loadThis = new VarInsnNode(Opcodes.ALOAD, 0);
|
|
field.target.insns.insert(field.node, loadThis);
|
|
field.target.insns.insert(loadThis, new InsnNode(Opcodes.SWAP));
|
|
consumer.extraStack(1);
|
|
}
|
|
|
|
this.info.addCallbackInvocation(null);
|
|
AbstractInsnNode champion = invokeHandlerWithArgsAndCoerce(field, field.elementType, consumer);
|
|
field.target.replaceNode(varNode, champion, insns);
|
|
}
|
|
|
|
/**
|
|
* Redirect a field get or set
|
|
*
|
|
* @param target target method
|
|
* @param fieldNode field access node
|
|
* @param opCode field access type
|
|
* @param ownerType type of the field owner
|
|
* @param fieldType field type
|
|
*/
|
|
private void injectAtScalarField(RedirectedFieldData field) {
|
|
AbstractInsnNode invoke;
|
|
InsnList insns = new InsnList();
|
|
if (field.isGetter) {
|
|
invoke = this.injectAtGetField(field, InsnConsumer.into(insns, field.target));
|
|
} else if (field.isSetter) {
|
|
invoke = this.injectAtPutField(field, InsnConsumer.into(insns, field.target));
|
|
} else {
|
|
throw new InvalidInjectionException(
|
|
this.info,
|
|
"Unsupported opcode " + Bytecode.getOpcodeName(field.opcode) + " for " + this.info
|
|
);
|
|
}
|
|
|
|
field.target.replaceNode(field.node, invoke, insns);
|
|
}
|
|
|
|
/**
|
|
* Inject opcodes to redirect a field getter. The injection will vary based on the static-ness of the field and the
|
|
* handler, thus there are four possible scenarios based on the possible combinations of static on the handler and
|
|
* the field itself.
|
|
*/
|
|
private AbstractInsnNode injectAtGetField(RedirectedFieldData field, InsnConsumer consumer) {
|
|
this.validateParams(field, field.type, field.isStatic ? null : field.owner);
|
|
|
|
if (!this.isStatic) {
|
|
consumer.extraStack(1);
|
|
consumer.accept(new VarInsnNode(Opcodes.ALOAD, 0));
|
|
if (!field.isStatic) {
|
|
consumer.accept(new InsnNode(Opcodes.SWAP));
|
|
}
|
|
}
|
|
|
|
this.info.addCallbackInvocation(null);
|
|
return invokeHandlerWithArgsAndCoerce(field, field.type, consumer);
|
|
}
|
|
|
|
/**
|
|
* Inject opcodes to redirect a field setter. The injection will vary based on the static-ness of the field and the
|
|
* handler, thus there are four possible scenarios based on the possible combinations of static on the handler and
|
|
* the field itself.
|
|
*/
|
|
private AbstractInsnNode injectAtPutField(RedirectedFieldData field, InsnConsumer consumer) {
|
|
this.validateParams(field, Type.VOID_TYPE, field.isStatic ? null : field.owner, field.type);
|
|
|
|
if (!this.isStatic) {
|
|
if (field.isStatic) {
|
|
consumer.accept(new VarInsnNode(Opcodes.ALOAD, 0));
|
|
consumer.accept(new InsnNode(Opcodes.SWAP));
|
|
} else {
|
|
consumer.extraStack(1);
|
|
int marshallVar = field.target.allocateLocals(field.type.getSize());
|
|
consumer.accept(new VarInsnNode(field.type.getOpcode(Opcodes.ISTORE), marshallVar));
|
|
consumer.accept(new VarInsnNode(Opcodes.ALOAD, 0));
|
|
consumer.accept(new InsnNode(Opcodes.SWAP));
|
|
consumer.accept(new VarInsnNode(field.type.getOpcode(Opcodes.ILOAD), marshallVar));
|
|
}
|
|
}
|
|
|
|
if (field.captureTargetArgs > 0) {
|
|
pushArgs(field.target.arguments,
|
|
consumer,
|
|
field.target.getArgIndices(),
|
|
0,
|
|
field.captureTargetArgs
|
|
);
|
|
}
|
|
|
|
this.info.addCallbackInvocation(null);
|
|
return this.invokeHandler(consumer, this.methodNode);
|
|
}
|
|
|
|
private void injectAtConstructor(Target target, InjectionNodes.InjectionNode node) {
|
|
ConstructorRedirectData meta = node.getDecoration(ConstructorRedirectData.KEY);
|
|
|
|
if (meta == null) {
|
|
// This should never happen, but let's display a less obscure error if it does
|
|
throw new InvalidInjectionException(
|
|
this.info,
|
|
this.annotationType + " ctor redirector has no metadata, the injector failed a preprocessing phase"
|
|
);
|
|
}
|
|
|
|
final TypeInsnNode newNode = (TypeInsnNode) node.getCurrentTarget();
|
|
final AbstractInsnNode dupNode = target.get(target.indexOf(newNode) + 1);
|
|
final MethodInsnNode initNode = target.findInitNodeFor(newNode);
|
|
|
|
if (initNode == null) {
|
|
meta.throwOrCollect(new InvalidInjectionException(
|
|
this.info,
|
|
this.annotationType + " ctor invocation was not found in " + target
|
|
));
|
|
return;
|
|
}
|
|
|
|
// True if the result of the object construction is being assigned
|
|
boolean isAssigned = dupNode.getOpcode() == Opcodes.DUP;
|
|
RedirectedInvokeData ctor = new RedirectedInvokeData(target, initNode);
|
|
ctor.description = "factory";
|
|
try {
|
|
this.validateParams(ctor, Type.getObjectType(newNode.desc), ctor.targetArgs);
|
|
} catch (InvalidInjectionException ex) {
|
|
meta.throwOrCollect(ex);
|
|
return;
|
|
}
|
|
|
|
if (isAssigned) {
|
|
target.removeNode(dupNode);
|
|
}
|
|
|
|
if (this.isStatic) {
|
|
target.removeNode(newNode);
|
|
} else {
|
|
target.replaceNode(newNode, new VarInsnNode(Opcodes.ALOAD, 0));
|
|
}
|
|
|
|
InsnList insns = new InsnList();
|
|
var consumer = InsnConsumer.into(insns, target);
|
|
if (ctor.captureTargetArgs > 0) {
|
|
pushArgs(target.arguments, consumer, target.getArgIndices(), 0, ctor.captureTargetArgs);
|
|
}
|
|
|
|
this.invokeHandler(insns);
|
|
if (ctor.coerceReturnType) {
|
|
insns.add(new TypeInsnNode(Opcodes.CHECKCAST, newNode.desc));
|
|
}
|
|
|
|
if (isAssigned) {
|
|
if (ENFORCE_CONTRACTS) {
|
|
// Do a null-check following the redirect to ensure that the handler
|
|
// didn't return null. Since NEW cannot return null, this would break
|
|
// the contract of the target method!
|
|
this.doNullCheck(consumer,
|
|
"constructor handler",
|
|
newNode.desc.replace('/', '.')
|
|
);
|
|
}
|
|
} else {
|
|
// Result is not assigned, so just pop it from the operand stack
|
|
insns.add(new InsnNode(Opcodes.POP));
|
|
}
|
|
|
|
target.replaceNode(initNode, insns);
|
|
meta.injected++;
|
|
}
|
|
|
|
private void injectAtInstanceOf(Target target, InjectionNodes.InjectionNode node) {
|
|
this.injectAtInstanceOf(target, (TypeInsnNode) node.getCurrentTarget());
|
|
}
|
|
|
|
private void injectAtInstanceOf(Target target, TypeInsnNode typeNode) {
|
|
if (this.returnType.getSort() == Type.BOOLEAN) {
|
|
this.redirectInstanceOf(target, typeNode, false);
|
|
return;
|
|
}
|
|
|
|
if (this.returnType.equals(Type.getType(Constants.CLASS_DESC))) {
|
|
this.redirectInstanceOf(target, typeNode, true);
|
|
return;
|
|
}
|
|
|
|
// This syntax is neat but the inconsistency might be a step too far
|
|
// if (this.returnType.getSort() >= Type.ARRAY) {
|
|
// this.modifyInstanceOfType(target, typeNode);
|
|
// return;
|
|
// }
|
|
|
|
throw new InvalidInjectionException(this.info, this.annotationType + " on " + this + " has an invalid signature. Found unexpected return type " + SignaturePrinter.getTypeName(this.returnType) + ". INSTANCEOF" + " handler expects (Ljava/lang/Object;Ljava/lang/Class;)Z or (Ljava/lang/Object;Ljava/lang/Class;)Ljava/lang/Class;");
|
|
}
|
|
|
|
private void redirectInstanceOf(Target target, TypeInsnNode typeNode, boolean dynamic) {
|
|
final InsnList insns = new InsnList();
|
|
var consumer = InsnConsumer.into(insns, target);
|
|
InjectorData handler = new InjectorData(target, "instanceof handler", false /* do not coerce args */);
|
|
this.validateParams(handler,
|
|
this.returnType,
|
|
Type.getType(Constants.OBJECT_DESC),
|
|
Type.getType(Constants.CLASS_DESC)
|
|
);
|
|
|
|
if (dynamic) {
|
|
insns.add(new InsnNode(Opcodes.DUP));
|
|
consumer.extraStack(1);
|
|
}
|
|
|
|
if (!this.isStatic) {
|
|
insns.add(new VarInsnNode(Opcodes.ALOAD, 0));
|
|
insns.add(new InsnNode(Opcodes.SWAP));
|
|
consumer.extraStack(1);
|
|
}
|
|
|
|
// Add the class type from the original instanceof check
|
|
insns.add(new LdcInsnNode(Type.getObjectType(typeNode.desc)));
|
|
consumer.extraStack(1);
|
|
|
|
if (handler.captureTargetArgs > 0) {
|
|
pushArgs(target.arguments, consumer, target.getArgIndices(), 0, handler.captureTargetArgs);
|
|
}
|
|
|
|
AbstractInsnNode champion = this.invokeHandler(insns);
|
|
|
|
if (dynamic) {
|
|
// removed this duplicative null check.
|
|
//// First null-check the class value returned by the handler, if it's
|
|
//// null then the rest is going to go badly
|
|
//this.doNullCheck(InsnConsumer.into(insns, extraStack), "instanceof handler", "class type");
|
|
|
|
// Now do a null-check on the reference and isAssignableFrom check
|
|
checkIsAssignableFrom(consumer);
|
|
}
|
|
|
|
target.replaceNode(typeNode, champion, insns);
|
|
}
|
|
|
|
private static void checkIsAssignableFrom(InsnConsumer consumer) {
|
|
LabelNode objectIsNull = new LabelNode();
|
|
LabelNode checkComplete = new LabelNode();
|
|
|
|
// Swap the values (we duped the ref above) and check for null. If
|
|
// the reference is null, load FALSE per the contract of instanceof
|
|
consumer.accept(new InsnNode(Opcodes.SWAP));
|
|
consumer.accept(new InsnNode(Opcodes.DUP));
|
|
consumer.extraStack(1);
|
|
consumer.accept(new JumpInsnNode(Opcodes.IFNULL, objectIsNull));
|
|
// If it's not null, call getClass on the reference and then use
|
|
// isAssignableFrom on the result
|
|
consumer.accept(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
|
|
Constants.OBJECT,
|
|
GET_CLASS_METHOD,
|
|
"()" + Constants.CLASS_DESC,
|
|
false));
|
|
consumer.accept(new MethodInsnNode(Opcodes.INVOKEVIRTUAL,
|
|
Constants.CLASS,
|
|
IS_ASSIGNABLE_FROM_METHOD,
|
|
'(' + Constants.CLASS_DESC + ")Z",
|
|
false));
|
|
consumer.accept(new JumpInsnNode(Opcodes.GOTO, checkComplete));
|
|
|
|
consumer.accept(objectIsNull);
|
|
consumer.accept(new InsnNode(Opcodes.POP)); // remove ref
|
|
consumer.accept(new InsnNode(Opcodes.POP)); // remove class
|
|
consumer.accept(new InsnNode(Opcodes.ICONST_0));
|
|
consumer.accept(checkComplete);
|
|
consumer.extraStack(1);
|
|
}
|
|
|
|
// private void modifyInstanceOfType(Target target, TypeInsnNode typeNode) {
|
|
// if (this.methodArgs.length > 0) {
|
|
// throw new InvalidInjectionException(this.info, String.format("%s on %s has an invalid signature. Found %d unexpected additional method"
|
|
// + "arguments, expected 0. INSTANCEOF handler expects ()Lthe/replacement/Type; or (Ljava/lang/Object;Ljava/lang/Class;)Z",
|
|
// this.annotationType, this, this.methodArgs.length));
|
|
// }
|
|
//
|
|
// // Already know that returnType is an object or array so no need to check again
|
|
// typeNode.desc = this.returnType.getInternalName();
|
|
// this.info.addCallbackInvocation(this.methodNode);
|
|
// }
|
|
|
|
private static void throwException(InsnConsumer consumer, String exceptionType, String message) {
|
|
consumer.accept(new TypeInsnNode(Opcodes.NEW, exceptionType));
|
|
consumer.accept(new InsnNode(Opcodes.DUP));
|
|
consumer.accept(new LdcInsnNode(message));
|
|
consumer.accept(new MethodInsnNode(Opcodes.INVOKESPECIAL,
|
|
exceptionType,
|
|
"<init>",
|
|
"(Ljava/lang/String;)V",
|
|
false
|
|
));
|
|
consumer.accept(new InsnNode(Opcodes.ATHROW));
|
|
consumer.extraStack(3);
|
|
}
|
|
|
|
private void doNullCheck(InsnConsumer consumer, String type, String value) {
|
|
LabelNode nullCheckSucceeded = new LabelNode();
|
|
consumer.accept(new InsnNode(Opcodes.DUP));
|
|
consumer.accept(new JumpInsnNode(Opcodes.IFNONNULL, nullCheckSucceeded));
|
|
throwException(consumer, NPE, this.annotationType + ' ' + type + ' ' + this + " returned null for " + value);
|
|
consumer.accept(nullCheckSucceeded);
|
|
consumer.extraStack(1);
|
|
}
|
|
|
|
private AbstractInsnNode invokeHandler(InsnConsumer consumer, MethodNode handler) {
|
|
boolean isInterface = Bytecode.hasFlag(classNode, Opcodes.ACC_INTERFACE);
|
|
boolean isPrivate = (handler.access & Opcodes.ACC_PRIVATE) != 0;
|
|
int invokeOpcode = this.isStatic ? Opcodes.INVOKESTATIC : isInterface ? Opcodes.INVOKEINTERFACE : isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEVIRTUAL;
|
|
MethodInsnNode insn = new MethodInsnNode(invokeOpcode, this.classNode.name, handler.name, handler.desc, isInterface);
|
|
consumer.accept(insn);
|
|
return insn;
|
|
}
|
|
|
|
private AbstractInsnNode invokeHandlerWithArgs(Type[] args, InsnConsumer consumer, int[] argMap) {
|
|
return invokeHandlerWithArgs(args, consumer, argMap, 0, args.length);
|
|
}
|
|
|
|
private AbstractInsnNode invokeHandlerWithArgs(Type[] args, InsnConsumer consumer, int[] argMap, int startArg, int endArg) {
|
|
if (!this.isStatic) {
|
|
consumer.accept(new VarInsnNode(Opcodes.ALOAD, 0));
|
|
}
|
|
pushArgs(args, consumer, argMap, startArg, endArg);
|
|
return invokeHandler(consumer, this.methodNode);
|
|
}
|
|
|
|
private AbstractInsnNode invokeHandlerWithArgsAndCoerce(RedirectedFieldData field,
|
|
Type coerceType,
|
|
InsnConsumer consumer) {
|
|
if (field.captureTargetArgs > 0) {
|
|
pushArgs(
|
|
field.target.arguments,
|
|
consumer,
|
|
field.target.getArgIndices(),
|
|
0,
|
|
field.captureTargetArgs
|
|
);
|
|
}
|
|
AbstractInsnNode champion = this.invokeHandler(consumer, this.methodNode);
|
|
if (field.coerceReturnType && field.type.getSort() >= Type.ARRAY) {
|
|
consumer.accept(new TypeInsnNode(Opcodes.CHECKCAST, coerceType.getInternalName()));
|
|
}
|
|
return champion;
|
|
}
|
|
|
|
private static void pushArgs(Type[] args, InsnConsumer consumer, int[] argMap, int start, int end) {
|
|
for (int arg = start; arg < end && arg < args.length; arg++) {
|
|
consumer.accept(new VarInsnNode(args[arg].getOpcode(Opcodes.ILOAD), argMap[arg]));
|
|
consumer.extraStack(args[arg].getSize());
|
|
}
|
|
}
|
|
|
|
//@Deprecated
|
|
//@Override
|
|
//protected AbstractInsnNode invokeHandler(InsnList insns) {
|
|
// throw new AssertionError();
|
|
//}
|
|
|
|
//@Deprecated
|
|
//@Override
|
|
//protected AbstractInsnNode invokeHandler(InsnList insns, MethodNode handler) {
|
|
// throw new AssertionError();
|
|
//}
|
|
|
|
@Deprecated
|
|
@Override
|
|
protected AbstractInsnNode invokeHandlerWithArgs(Type[] args, InsnList insns, int[] argMap) {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
@Deprecated
|
|
@Override
|
|
protected AbstractInsnNode invokeHandlerWithArgs(Type[] args,
|
|
InsnList insns,
|
|
int[] argMap,
|
|
int startArg,
|
|
int endArg) {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
@Deprecated
|
|
@Override
|
|
protected int[] storeArgs(Target target, Type[] args, InsnList insns, int start) {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
@Deprecated
|
|
@Override
|
|
protected int[] storeArgs(Target target, Type[] args, InsnList insns, int start, LabelNode from, LabelNode to) {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
@Deprecated
|
|
@Override
|
|
protected void storeArgs(Target target, Type[] args, InsnList insns, int[] argMap, int start, int end) {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
@Deprecated
|
|
@Override
|
|
protected void storeArgs(Target target,
|
|
Type[] args,
|
|
InsnList insns,
|
|
int[] argMap,
|
|
int start,
|
|
int end,
|
|
LabelNode from,
|
|
LabelNode to) {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
@Deprecated
|
|
@Override
|
|
protected void pushArgs(Type[] args, InsnList insns, int[] argMap, int start, int end) {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
@Deprecated
|
|
@Override
|
|
protected void pushArgs(Type[] args, InsnList insns, int[] argMap, int start, int end, Target.Extension extension) {
|
|
throw new AssertionError();
|
|
}
|
|
|
|
private static Map<LabelNode, LabelNode> cloneLabelsFresh(InsnList source) {
|
|
Map<LabelNode, LabelNode> labels = new HashMap<LabelNode, LabelNode>();
|
|
|
|
for (AbstractInsnNode insn : source) {
|
|
if (insn instanceof LabelNode labelInsn) {
|
|
labels.put(labelInsn, new LabelNode());
|
|
}
|
|
}
|
|
|
|
return labels;
|
|
}
|
|
}
|