com.newrelic.weave.MethodCallInlinerAdapter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of newrelic-weaver Show documentation
Show all versions of newrelic-weaver Show documentation
The Weaver of the Java agent.
/*
*
* * Copyright 2020 New Relic Corporation. All rights reserved.
* * SPDX-License-Identifier: Apache-2.0
*
*/
package com.newrelic.weave;
import com.newrelic.weave.utils.WeaveUtils;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AnalyzerAdapter;
import org.objectweb.asm.commons.LocalVariablesSorter;
import org.objectweb.asm.commons.MethodRemapper;
import org.objectweb.asm.commons.Remapper;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public abstract class MethodCallInlinerAdapter extends LocalVariablesSorter {
/**
* try/catch blocks which originated from the inlined method.
*/
private List inlinedTryCatchBlocks = null;
/*
* Only used if the inlineFrames option is true. Allows the computation of the stack map frames at any instruction
* of the caller method, and in particular at the call sites to methods to be inlined (needed to merge this caller
* stack maps with the callee ones).
*/
private final AnalyzerAdapter analyzerAdapter;
/*
* If a method to be inlined is called several times from the same caller method, we don't want to allocate new
* local variables for each invocation. Instead, we want to share the local slots between all call sites. To do this
* we need to reuse the InliningAdapter instance, which maintains (as a LocalVariablesSorter subclass) the mapping
* from the original locals in the method to be inlined to the locals in the caller method. This is the goal of this
* map.
*/
private Map inliners;
public static class InlinedMethod {
// The code that must be inlined.
public final MethodNode method;
// How the code must be remapped to be inlined.
public final Remapper remapper;
InliningAdapter inliner;
public InlinedMethod(final MethodNode method, final Remapper remapper) {
this.method = method;
this.remapper = remapper;
}
}
static InlinedMethod DO_NOT_INLINE = new InlinedMethod(null, null);
// If inlineFrame is true, frames from the calling method and from
// the inlined methods are merged during inlining, thus avoiding the
// need to compute all frames from scratch. But this requires both
// methods to already have computed frames.
// If inlineFrame is false the frames are not changed at all, thus
// they must be computed from scratch in the ClassWriter.
public MethodCallInlinerAdapter(String owner, int access, String name, String desc, MethodVisitor next,
boolean inlineFrames) {
this(WeaveUtils.ASM_API_LEVEL, owner, access, name, desc, next, inlineFrames);
}
protected MethodCallInlinerAdapter(int api, String owner, int access, String name, String desc, MethodVisitor next,
boolean inlineFrames) {
super(api, access, desc, getNext(owner, access, name, desc, next, inlineFrames));
this.analyzerAdapter = inlineFrames ? (AnalyzerAdapter) mv : null;
this.mv = new InlinedTryCatchBlockSorter(WeaveUtils.ASM_API_LEVEL, this.mv, access, name, desc, null, null);
}
private static MethodVisitor getNext(String owner, int access, String name, String desc, MethodVisitor next,
boolean inlineFrames) {
MethodVisitor mv = next;
if (inlineFrames) {
mv = new AnalyzerAdapter(owner, access, name, desc, mv);
}
return mv;
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
InlinedMethod inliner = getInliner(owner, name, desc);
if (inliner == DO_NOT_INLINE) {
super.visitMethodInsn(opcode, owner, name, desc, itf);
return;
}
if (inliner.inliner == null) {
MethodVisitor mv = this.mv;
if (analyzerAdapter != null) {
mv = new MergeFrameAdapter(api, analyzerAdapter, mv);
}
if (inliner.method.tryCatchBlocks != null && !inliner.method.tryCatchBlocks.isEmpty()) {
if (this.inlinedTryCatchBlocks == null) {
this.inlinedTryCatchBlocks = new ArrayList<>();
}
this.inlinedTryCatchBlocks.addAll(inliner.method.tryCatchBlocks);
}
int access = opcode == Opcodes.INVOKESTATIC ? Opcodes.ACC_STATIC : 0;
inliner.inliner = new InliningAdapter(api, access, desc, this, mv, inliner.remapper);
}
inliner.method.accept(inliner.inliner);
}
protected abstract InlinedMethod mustInline(String owner, String name, String desc);
private InlinedMethod getInliner(String owner, String name, String desc) {
if (inliners == null) {
inliners = new HashMap<>();
}
String key = owner + "." + name + desc;
InlinedMethod method = inliners.get(key);
if (method == null) {
method = mustInline(owner, name, desc);
if (method == null) {
method = DO_NOT_INLINE;
} else {
// Copy the MethodNode before modifying the instructions list (which is not thread safe)
MethodNode methodNodeCopy = WeaveUtils.copy(method.method);
methodNodeCopy.instructions.resetLabels();
InliningAdapter originalInliner = method.inliner;
// Replace the InlinedMethod instance with our new values and set the inliner
method = new InlinedMethod(methodNodeCopy, method.remapper);
method.inliner = originalInliner;
}
inliners.put(key, method);
}
return method;
}
class InliningAdapter extends LocalVariablesSorter {
private final int access;
private final String desc;
private final LocalVariablesSorter caller;
private Label end;
public InliningAdapter(int api, int access, String desc, LocalVariablesSorter caller, MethodVisitor next,
Remapper remapper) {
super(api, access, desc, new MethodRemapper(next, remapper));
this.access = access;
this.desc = desc;
this.caller = caller;
}
@Override
public void visitCode() {
super.visitCode();
int off = (access & Opcodes.ACC_STATIC) != 0 ? 0 : 1;
Type[] args = Type.getArgumentTypes(desc);
int argRegister = off;
for (int i = 0; i < args.length; ++i) {
argRegister += args[i].getSize();
}
for (int i = args.length - 1; i >= 0; i--) {
argRegister -= args[i].getSize();
visitVarInsn(args[i].getOpcode(Opcodes.ISTORE), argRegister);
}
if (off > 0) {
visitVarInsn(Opcodes.ASTORE, 0);
}
end = new Label();
}
@Override
public void visitInsn(int opcode) {
if (opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN) {
super.visitJumpInsn(Opcodes.GOTO, end);
} else {
super.visitInsn(opcode);
}
}
@Override
public void visitVarInsn(final int opcode, final int var) {
// Offset all locals by firstLocal to also remap the method
// arguments
super.visitVarInsn(opcode, var + firstLocal);
}
@Override
public void visitIincInsn(final int var, final int increment) {
// Offset all locals by firstLocal to also remap the method
// arguments
super.visitIincInsn(var + firstLocal, increment);
}
@Override
public void visitLocalVariable(final String name, final String desc, final String signature, final Label start,
final Label end, final int index) {
// Offset all locals by firstLocal to also remap the method
// arguments
super.visitLocalVariable(name, desc, signature, start, end, index + firstLocal);
}
@Override
public void visitMaxs(int stack, int locals) {
super.visitLabel(end);
}
@Override
public void visitEnd() {
// Do nothing
}
@Override
protected int newLocalMapping(final Type type) {
// REVIEW this code originally wanted to reflect to invoke newLocalMapping. That causes
// an access exception with the WebSphere security manager. The newLocal method seems to
// do about the same thing but a little more. anyway, the tests pass..
return caller.newLocal(type);
}
}
static class MergeFrameAdapter extends MethodVisitor {
private final AnalyzerAdapter analyzerAdapter;
public MergeFrameAdapter(int api, AnalyzerAdapter analyzerAdapter, MethodVisitor next) {
super(api, next);
this.analyzerAdapter = analyzerAdapter;
}
@Override
public void visitFrame(int type, int nLocal, Object[] local, int nStack, Object[] stack) {
List
© 2015 - 2024 Weber Informatics LLC | Privacy Policy