/*
* Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.btrace.compiler;
import com.sun.btrace.runtime.*;
import com.sun.btrace.BTraceRuntime;
import com.sun.btrace.annotations.Return;
import static com.sun.btrace.runtime.Constants.*;
import com.sun.btrace.org.objectweb.asm.Opcodes;
import com.sun.btrace.org.objectweb.asm.Type;
import com.sun.btrace.org.objectweb.asm.tree.AbstractInsnNode;
import com.sun.btrace.org.objectweb.asm.tree.AnnotationNode;
import com.sun.btrace.org.objectweb.asm.tree.ClassNode;
import com.sun.btrace.org.objectweb.asm.tree.FieldInsnNode;
import com.sun.btrace.org.objectweb.asm.tree.FieldNode;
import com.sun.btrace.org.objectweb.asm.tree.InsnList;
import com.sun.btrace.org.objectweb.asm.tree.InsnNode;
import com.sun.btrace.org.objectweb.asm.tree.JumpInsnNode;
import com.sun.btrace.org.objectweb.asm.tree.LabelNode;
import com.sun.btrace.org.objectweb.asm.tree.LdcInsnNode;
import com.sun.btrace.org.objectweb.asm.tree.MethodInsnNode;
import com.sun.btrace.org.objectweb.asm.tree.MethodNode;
import com.sun.btrace.org.objectweb.asm.tree.TryCatchBlockNode;
import com.sun.btrace.org.objectweb.asm.tree.TypeInsnNode;
import com.sun.btrace.org.objectweb.asm.tree.VarInsnNode;
import com.sun.btrace.shared.MethodSerializer;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* This class preprocesses a compiled BTrace program.
* This is done after BTrace safety verification but
* before instrumenting the probed classes.
*
* Transformations done here:
*
* 1. add <clinit> method, if one not found
* 2. replace @Export fields by perf counters
* and replace put/get by perf counter update/read
* 3. replace @TLS fields by ThreadLocal fields
* and replace put/get by ThreadLocal set/get
* 4. In <clinit> method, add ThreadLocal creation
* and perf counter creation calls (for @Export and
* @TLS fields respectively)
* 5. Add a field to store BTraceRuntime object and
* initialize the same in <clinit> method
* 6. add prolog and epilogue in each BTrace action method
* to insert BTraceRuntime.enter/leave and also to call
* BTraceRuntime.handleException on exception catch
* 7. initialize and reference any service instances
* 8. add a field to store client's BTraceRuntime instance
* 9. make all fields publicly accessible
*
*
* @author A. Sundararajan
* @author J. Bachorik (Tree API rewrite)
*/
final class Postprocessor1 {
private static enum MethodClassifier {
/**
* No BTrace specific classifier available
*/
NONE,
/**
* An annotated method that will need access to the current
* {@linkplain BTraceRuntime} instance.
*/
RT_AWARE,
/**
* An annotated method that will use the result of
* {@linkplain BTraceRuntime#enter(com.sun.btrace.BTraceRuntime)} to skip
* the execution if already inside a handler. This implies the method is also
* {@linkplain MethodClassifier#RT_AWARE}.
*/
GUARDED
}
private static class LocalVarGenerator {
private int offset = 0;
public LocalVarGenerator(MethodNode mn) {
Type[] args = Type.getArgumentTypes(mn.desc);
for(Type t : args) {
offset += t.getSize();
}
}
public int newVar(Type t) {
int ret = -offset - 1;
offset += t.getSize();
return ret;
}
public static int translateIdx(int idx) {
if (idx < 0) {
return -idx - 1;
}
return idx;
}
}
private static final String ANNOTATIONS_PREFIX = "com/sun/btrace/annotations/";
private static final Type TLS_TYPE = Type.getType("L" + ANNOTATIONS_PREFIX + "TLS;");
private static final Type EXPORT_TYPE = Type.getType("L" + ANNOTATIONS_PREFIX + "Export;");
private static final Type INJECTED_TYPE = Type.getType("L" + ANNOTATIONS_PREFIX + "Injected;");
private static final String SERVICE_INTERNAL = "com/sun/btrace/services/api/Service";
private static final Type ONMETHOD_TYPE = Type.getType(com.sun.btrace.annotations.OnMethod.class);
private static final String NEW_TLS_DESC = "(" + OBJECT_DESC + ")" + THREAD_LOCAL_DESC;
private static final String TLS_SET_DESC = "(" + OBJECT_DESC + ")" + VOID_DESC;
private static final String TLS_GET_DESC = "()" + OBJECT_DESC;
private static final String NEW_PERFCOUNTER_DESC = "(" + OBJECT_DESC + STRING_DESC + STRING_DESC + ")" + VOID_DESC;
private static final String BTRACERT_FOR_CLASS_DESC = "(" + CLASS_DESC + ")" + BTRACERT_DESC;
private static final String BTRACERT_ENTER_DESC = "(" + BTRACERT_DESC + ")" + BOOLEAN_DESC;
private static final String BTRACERT_HANDLE_EXCEPTION_DESC = "(" + THROWABLE_DESC + ")" + VOID_DESC;
private static final String RT_SERVICE_CTR_DESC = "(" + BTRACERT_DESC + ")V";
private static final String SERVICE_CTR_DESC = "(" + STRING_DESC + ")" + VOID_DESC;
private static final Map<String, String> BOX_TYPE_MAP = new HashMap<>();
private static final Set<String> GUARDED_ANNOTS = new HashSet<>();
private static final Set<String> RT_AWARE_ANNOTS = new HashSet<>();
static {
BOX_TYPE_MAP.put("I", INTEGER_BOXED_DESC);
BOX_TYPE_MAP.put("S", SHORT_BOXED_DESC);
BOX_TYPE_MAP.put("J", LONG_BOXED_DESC);
BOX_TYPE_MAP.put("F", FLOAT_BOXED_DESC);
BOX_TYPE_MAP.put("D", DOUBLE_BOXED_DESC);
BOX_TYPE_MAP.put("B", BYTE_BOXED_DESC);
BOX_TYPE_MAP.put("Z", BOOLEAN_BOXED_DESC);
BOX_TYPE_MAP.put("C", CHARACTER_BOXED_DESC);
RT_AWARE_ANNOTS.add(ONMETHOD_DESC);
RT_AWARE_ANNOTS.add(ONTIMER_DESC);
RT_AWARE_ANNOTS.add(ONEVENT_DESC);
RT_AWARE_ANNOTS.add(ONERROR_DESC);
RT_AWARE_ANNOTS.add(ONPROBE_DESC);
GUARDED_ANNOTS.addAll(RT_AWARE_ANNOTS);
// @OnExit is rtAware but not guarded
RT_AWARE_ANNOTS.add(ONEXIT_DESC);
}
public static interface MethodFilter {
public static final MethodFilter ALL = new MethodFilter() {
@Override
public boolean test(String name, String desc) {
return true;
}
};
boolean test(String name, String desc);
}
private MethodNode clinit = null;
private FieldNode rtField = null;
private final Set<String> tlsFldNames = new HashSet<>();
private final Set<String> exportFldNames = new HashSet<>();
private final Map<String, AnnotationNode> injectedFlds = new HashMap<>();
private final Map<String, Integer> serviceLocals = new HashMap<>();
private final CallGraph1 cGraph = new CallGraph1();
public void process(ClassNode cn) {
addLevelField(cn);
processClinit(cn);
processFields(cn);
for(MethodNode mn : getMethods(cn)) {
preprocessMethod(cn, mn);
}
}
private void addLevelField(ClassNode cn) {
if (cn.fields == null) {
cn.fields = new LinkedList();
}
cn.fields.add(new FieldNode(
Opcodes.ASM5,
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC | Opcodes.ACC_VOLATILE, BTRACE_LEVEL_FLD,
INT_DESC,
null,
0
)
);
}
private void preprocessMethod(ClassNode cn, MethodNode mn) {
if (mn.visibleAnnotations != null) {
for (AnnotationNode an : ((List<AnnotationNode>)mn.visibleAnnotations)) {
if (an.desc.startsWith("L" + ANNOTATIONS_PREFIX)) {
cGraph.addStarting(new CallGraph1.Node(CallGraph1.methodId(mn.name, mn.desc)));
break;
}
}
}
// !!! The order of execution is important here !!!
LocalVarGenerator lvg = new LocalVarGenerator(mn);
makePublic(mn);
checkAugmentedReturn(mn);
scanMethodInstructions(cn, mn, lvg);
addBTraceErrorHandler(mn, lvg);
addBTraceRuntimeEnter(cn, mn);
recalculateVars(mn, lvg);
try {
byte[] methodData = MethodSerializer.serialize(mn);
System.out.println("*** " + methodData.length);
} catch (IOException e) {
e.printStackTrace();
}
}
private void makePublic(MethodNode mn) {
if (!mn.name.contains("init>") && isUnannotated(mn)) {
if ((mn.access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC) {
mn.access &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED);
mn.access |= Opcodes.ACC_PUBLIC;
}
}
}
private void processFields(ClassNode cn) {
Iterator<FieldNode> iter = getFields(cn).iterator();
while (iter.hasNext()) {
FieldNode fn = iter.next();
fn.access = (fn.access & ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) | Opcodes.ACC_PUBLIC;
tryProcessTLS(cn, fn);
tryProcessExport(cn, fn);
if (tryProcessInjected(fn) == null) {
iter.remove();
}
}
}
private void tryProcessTLS(ClassNode cn, FieldNode fn) {
AnnotationNode an = null;
if ((an = getAnnotation(fn, TLS_TYPE)) != null) {
fn.visibleAnnotations.remove(an);
String origDesc = fn.desc;
String boxedDesc = boxDesc(origDesc);
fn.desc = THREAD_LOCAL_DESC;
fn.signature = fn.desc.substring(0, fn.desc.length() - 1) +
"<" + boxedDesc + ">;";
initTLS(cn, fn, origDesc);
}
}
private void tryProcessExport(ClassNode cn, FieldNode fn) {
AnnotationNode an = null;
if ((an = getAnnotation(fn, EXPORT_TYPE)) != null) {
fn.visibleAnnotations.remove(an);
String origDesc = fn.desc;
initExport(cn, fn, origDesc);
}
}
private FieldNode tryProcessInjected(FieldNode fn) {
AnnotationNode an = null;
if ((an = getAnnotation(fn, INJECTED_TYPE)) != null) {
if (fn.visibleAnnotations != null) fn.visibleAnnotations.remove(an);
if (fn.invisibleAnnotations != null) fn.invisibleAnnotations.remove(an);
injectedFlds.put(fn.name, an);
return null;
}
return fn;
}
private void initTLS(ClassNode cn, FieldNode fn, String typeDesc) {
tlsFldNames.add(fn.name);
initAnnotatedField(fn, typeDesc, tlsInitSequence(cn, fn.name, fn.desc));
}
private InsnList tlsInitSequence(ClassNode cn, String name, String desc) {
InsnList initList = new InsnList();
initList.add(
new MethodInsnNode(
Opcodes.INVOKESTATIC,
BTRACERT_INTERNAL,
"newThreadLocal",
NEW_TLS_DESC,
false
)
);
initList.add(new FieldInsnNode(Opcodes.PUTSTATIC, cn.name, name, desc));
return initList;
}
private void initExport(ClassNode cn, FieldNode fn, String typeDesc) {
exportFldNames.add(fn.name);
initAnnotatedField(fn, typeDesc, exportInitSequence(cn, fn.name, fn.desc));
}
private InsnList exportInitSequence(ClassNode cn, String name, String desc) {
InsnList init = new InsnList();
init.add(new LdcInsnNode(perfCounterName(cn, name)));
init.add(new LdcInsnNode(desc));
init.add(new MethodInsnNode(
Opcodes.INVOKESTATIC,
BTRACERT_INTERNAL,
"newPerfCounter",
NEW_PERFCOUNTER_DESC,
false
)
);
return init;
}
private void initAnnotatedField(FieldNode fn, String typeDesc, InsnList initList) {
Object initVal = fn.value;
fn.value = null;
fn.access |= Opcodes.ACC_FINAL;
InsnList l = clinit.instructions;
MethodInsnNode boxNode;
if (TypeUtils.isPrimitive(typeDesc)) {
boxNode = boxNode(typeDesc);
initList.insert(boxNode);
}
if (initVal != null) {
initList.insert(new LdcInsnNode(initVal));
} else {
// find first fld store; this is the initialization place
AbstractInsnNode initNode = findNodeInitialization(fn);
if (initNode != null) {
// found the initialization place;
// just replace the FLD_STORE with the TLS init sequence
l.insert(initNode, initList);
l.remove(initNode);
return;
} else {
// no initialization done; use primitive defaults or NULL
addDefaultVal(Type.getType(typeDesc), initList);
}
}
MethodInsnNode rtStart = findBTraceRuntimeStart();
if (rtStart != null) {
l.insertBefore(rtStart, initList);
} else {
l.add(initList);
}
}
private void addDefaultVal(Type t, InsnList l) {
switch (t.getSort()) {
case Type.INT:
case Type.SHORT:
case Type.BOOLEAN:
case Type.BYTE:
case Type.CHAR: {
l.insert(new InsnNode(Opcodes.ICONST_0));
break;
}
case Type.LONG: {
l.insert(new InsnNode(Opcodes.LCONST_0));
break;
}
case Type.FLOAT: {
l.insert(new InsnNode(Opcodes.FCONST_0));
break;
}
case Type.DOUBLE: {
l.insert(new InsnNode(Opcodes.DCONST_0));
break;
}
default: {
l.insert(new InsnNode(Opcodes.ACONST_NULL));
}
}
}
private void checkAugmentedReturn(MethodNode mn) {
if (isUnannotated(mn)) return;
Type retType = Type.getReturnType(mn.desc);
if (retType.getSort() != Type.VOID) {
if (getReturnMethodParameter(mn) == Integer.MIN_VALUE) {
// insert the method return parameter
String oldDesc = mn.desc;
Type[] args = Type.getArgumentTypes(mn.desc);
args = Arrays.copyOf(args, args.length + 1);
args[args.length - 1] = retType;
List<AnnotationNode> annots = new LinkedList<>();
AnnotationNode an = new AnnotationNode(Type.getDescriptor(Return.class));
annots.add(an);
mn.visibleParameterAnnotations = mn.visibleParameterAnnotations != null ?
Arrays.copyOf(mn.visibleParameterAnnotations, args.length) :
new List[args.length];
mn.visibleParameterAnnotations[args.length - 1] = annots;
mn.desc = Type.getMethodDescriptor(retType, args);
if (mn instanceof BTraceMethodNode) {
BTraceMethodNode bmn = (BTraceMethodNode)mn;
OnMethod om = bmn.getOnMethod();
if (om != null && om.getTargetName().equals(mn.name) && om.getTargetDescriptor().equals(oldDesc)) {
om.setReturnParameter(getReturnMethodParameter(mn));
om.setTargetDescriptor(mn.desc);
}
}
}
}
}
private void scanMethodInstructions(ClassNode cn, MethodNode mn, LocalVarGenerator lvg) {
// ignore <init> and <clinit>
if (mn.name.startsWith("<")) return;
serviceLocals.clear(); // initialize the service locals for this particular method
boolean checkFields = !(tlsFldNames.isEmpty() &&
exportFldNames.isEmpty() &&
injectedFlds.isEmpty());
MethodClassifier clsf = getClassifier(mn);
int retopcode = Type.getReturnType(mn.desc).getOpcode(Opcodes.IRETURN);
InsnList l = mn.instructions;
for (AbstractInsnNode n = l.getFirst(); n != null; n = n != null ? n.getNext() : null) {
int type = n.getType();
if (checkFields && type == AbstractInsnNode.FIELD_INSN) {
FieldInsnNode fin = (FieldInsnNode)n;
if (fin.owner.equals(cn.name)) {
if (tlsFldNames.contains(fin.name) &&
!fin.desc.equals(THREAD_LOCAL_DESC)) {
n = updateTLSUsage(fin, l);
} else if (exportFldNames.contains(fin.name)) {
n = updateExportUsage(cn, fin, l);
} else if (injectedFlds.containsKey(fin.name)) {
n = updateInjectedUsage(cn, fin, l, lvg);
}
}
} else if (type == AbstractInsnNode.METHOD_INSN) {
MethodInsnNode min = (MethodInsnNode)n;
cGraph.addEdge(CallGraph1.methodId(mn.name, mn.desc), CallGraph1.methodId(min.name, min.desc));
n = unfoldServiceInstantiation(cn, min, l);
} else if (n.getOpcode() == retopcode && isClassified(clsf, MethodClassifier.RT_AWARE)) {
addBTraceRuntimeExit((InsnNode)n, l, lvg);
}
}
}
private void recalculateVars(MethodNode mn, LocalVarGenerator lvg) {
for(AbstractInsnNode n = mn.instructions.getFirst(); n != null; n = n.getNext()) {
if (n.getType() == AbstractInsnNode.VAR_INSN) {
VarInsnNode vin = (VarInsnNode)n;
vin.var = LocalVarGenerator.translateIdx(vin.var);
}
}
}
private void processClinit(ClassNode cn) {
for(MethodNode mn : getMethods(cn)) {
if (mn.name.equals("<clinit>")) {
clinit = mn;
break;
}
}
if (clinit == null) {
clinit = new MethodNode(
Opcodes.ASM5, (Opcodes.ACC_STATIC | Opcodes.ACC_PUBLIC),
"<clinit>", "()V", null, new String[0]
);
clinit.instructions.add(new InsnNode(Opcodes.RETURN));
cn.methods.add(0, clinit);
}
initRuntime(cn, clinit);
}
private void initRuntime(ClassNode cn, MethodNode clinit) {
addRuntimeNode(cn);
InsnList l = new InsnList();
l.add(new LdcInsnNode(Type.getObjectType(cn.name)));
l.add(new MethodInsnNode(
Opcodes.INVOKESTATIC,
BTRACERT_INTERNAL,
"forClass",
BTRACERT_FOR_CLASS_DESC,
false)
);
l.add(new FieldInsnNode(Opcodes.PUTSTATIC, cn.name, rtField.name, rtField.desc));
LabelNode start = new LabelNode();
l.add(getRuntime(cn));
l.add(new MethodInsnNode(
Opcodes.INVOKESTATIC, BTRACERT_INTERNAL,
"enter", BTRACERT_ENTER_DESC,
false
));
l.add(new JumpInsnNode(Opcodes.IFNE, start));
l.add(getReturnSequence(clinit, true));
l.add(start);
clinit.instructions.insert(l);
startRuntime(clinit);
}
private void startRuntime(MethodNode clinit1) {
for (AbstractInsnNode n = clinit1.instructions.getFirst(); n != null; n = n.getNext()) {
if (n.getOpcode() == Opcodes.RETURN) {
AbstractInsnNode prev = n.getPrevious();
if (prev != null && prev.getType() == AbstractInsnNode.METHOD_INSN) {
MethodInsnNode min = (MethodInsnNode)prev;
if (min.name.equals("leave")) {
// don't start the runtime if we are bailing out (BTraceRuntime.leave())
continue;
}
}
clinit1.instructions.insertBefore(n, new MethodInsnNode(
Opcodes.INVOKESTATIC, BTRACERT_INTERNAL,
"start", "()V", false
));
}
}
}
private FieldInsnNode getRuntime(ClassNode cn) {
return new FieldInsnNode(Opcodes.GETSTATIC, cn.name, rtField.name, rtField.desc);
}
private MethodInsnNode getRuntimeExit() {
return new MethodInsnNode(
Opcodes.INVOKESTATIC, BTRACERT_INTERNAL,
"leave", "()V", false
);
}
private void addRuntimeNode(ClassNode cn) {
rtField = new FieldNode(
Opcodes.ASM5, (Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC),
"runtime", Type.getDescriptor(BTraceRuntime.class),
null, null
);
cn.fields.add(0, rtField);
}
private void addBTraceErrorHandler(MethodNode mn, LocalVarGenerator lvg) {
if (!mn.name.equals("<clinit>") && isUnannotated(mn)) return;
MethodClassifier clsf = getClassifier(mn);
if (isClassified(clsf, MethodClassifier.RT_AWARE)) {
LabelNode from = new LabelNode();
LabelNode to = new LabelNode();
InsnList l = mn.instructions;
l.insert(from);
l.add(to);
l.add(new MethodInsnNode(
Opcodes.INVOKESTATIC, BTRACERT_INTERNAL,
"handleException", BTRACERT_HANDLE_EXCEPTION_DESC,
false
));
l.add(getReturnSequence(mn, true));
mn.tryCatchBlocks.add(new TryCatchBlockNode(from, to, to, THROWABLE_INTERNAL));
}
}
private void addBTraceRuntimeEnter(ClassNode cn, MethodNode mn) {
// no runtime check for <clinit>
if (mn.name.equals("<clinit>")) return;
MethodClassifier clsf = getClassifier(mn);
if (isClassified(clsf, MethodClassifier.RT_AWARE)) {
InsnList entryCheck = new InsnList();
entryCheck.add(getRuntime(cn));
if (isClassified(clsf, MethodClassifier.GUARDED)) {
LabelNode start = new LabelNode();
entryCheck.add(new MethodInsnNode(
Opcodes.INVOKESTATIC, BTRACERT_INTERNAL,
"enter", BTRACERT_ENTER_DESC,
false
));
entryCheck.add(new JumpInsnNode(Opcodes.IFNE, start));
entryCheck.add(getReturnSequence(mn, false));
entryCheck.add(start);
}
mn.instructions.insert(entryCheck);
}
}
private void addBTraceRuntimeExit(InsnNode n, InsnList l, LocalVarGenerator lvg) {
l.insertBefore(n, getRuntimeExit());
}
private List<MethodNode> getMethods(ClassNode cn) {
return (List<MethodNode>)cn.methods;
}
private List<FieldNode> getFields(ClassNode cn) {
return (List<FieldNode>)cn.fields;
}
public static AnnotationNode getAnnotation(FieldNode fn, Type annotation) {
if (fn == null || (fn.visibleAnnotations == null && fn.invisibleAnnotations == null)) return null;
String targetDesc = annotation.getDescriptor();
if (fn.visibleAnnotations != null) {
for(AnnotationNode an : ((List<AnnotationNode>)fn.visibleAnnotations)) {
if (an.desc.equals(targetDesc)) {
return an;
}
}
}
if (fn.invisibleAnnotations != null) {
for(AnnotationNode an : ((List<AnnotationNode>)fn.invisibleAnnotations)) {
if (an.desc.equals(targetDesc)) {
return an;
}
}
}
return null;
}
public static AnnotationNode getAnnotation(MethodNode fn, Type annotation) {
if (fn == null || fn.visibleAnnotations == null) return null;
String targetDesc = annotation.getDescriptor();
for(AnnotationNode an : ((List<AnnotationNode>)fn.visibleAnnotations)) {
if (an.desc.equals(targetDesc)) {
return an;
}
}
return null;
}
private List<AnnotationNode> getAnnotations(MethodNode mn) {
return (List<AnnotationNode>)mn.visibleAnnotations != null ?
mn.visibleAnnotations :
Collections.emptyList();
}
private MethodClassifier getClassifier(MethodNode mn) {
// <clinit> will always be guarded by BTrace error handler
if (mn.name.equals("<clinit>")) return MethodClassifier.GUARDED;
List<AnnotationNode> annots = getAnnotations(mn);
if (!annots.isEmpty()) {
for(AnnotationNode an : annots) {
if (RT_AWARE_ANNOTS.contains(an.desc)) {
if (GUARDED_ANNOTS.contains(an.desc)) {
return MethodClassifier.GUARDED;
} else {
return MethodClassifier.RT_AWARE;
}
}
}
}
return MethodClassifier.NONE;
}
private FieldInsnNode findNodeInitialization(FieldNode fn) {
for (AbstractInsnNode n = clinit.instructions.getFirst(); n != null; n = n.getNext()) {
if (n.getType() == AbstractInsnNode.FIELD_INSN) {
FieldInsnNode fldInsnNode = (FieldInsnNode)n;
if (fldInsnNode.getOpcode() == Opcodes.PUTSTATIC &&
fldInsnNode.name.equals(fn.name)) {
return fldInsnNode;
}
}
}
return null;
}
private MethodInsnNode findBTraceRuntimeStart() {
for (AbstractInsnNode n = clinit.instructions.getFirst(); n != null; n = n.getNext()) {
if (n.getType() == AbstractInsnNode.METHOD_INSN) {
MethodInsnNode min = (MethodInsnNode)n;
if (min.getOpcode() == Opcodes.INVOKESTATIC &&
min.owner.equals(BTRACERT_INTERNAL) &&
min.name.equals("start")) {
return min;
}
}
}
return null;
}
private AbstractInsnNode updateTLSUsage(FieldInsnNode fin, InsnList l) {
String unboxedDesc = fin.desc;
int opcode = fin.getOpcode();
// retrieve the TLS field
fin.setOpcode(Opcodes.GETSTATIC);
// change the desc from the contained type to TLS type
fin.desc = THREAD_LOCAL_DESC;
String boxedDesc = boxDesc(unboxedDesc);
if (opcode == Opcodes.GETSTATIC) {
InsnList toInsert = new InsnList();
MethodInsnNode getNode = new MethodInsnNode(
Opcodes.INVOKEVIRTUAL,
THREAD_LOCAL_INTERNAL,
"get", TLS_GET_DESC,
false
);
toInsert.add(getNode);
String boxedInternal = boxedDesc.substring(1, boxedDesc.length() - 1);
toInsert.add(new TypeInsnNode(Opcodes.CHECKCAST, boxedInternal));
if (!boxedDesc.equals(unboxedDesc)) {
// must unbox
MethodInsnNode unboxNode = unboxNode(boxedDesc, boxedInternal, unboxedDesc);
if (unboxNode != null) {
toInsert.add(unboxNode);
}
}
l.insert(fin, toInsert);
} else if (opcode == Opcodes.PUTSTATIC) {
MethodInsnNode boxNode = null;
if (!boxedDesc.equals(unboxedDesc)) {
// must box
boxNode = boxNode(unboxedDesc, boxedDesc);
l.insert(fin.getPrevious(), boxNode);
}
MethodInsnNode setNode = new MethodInsnNode(
Opcodes.INVOKEVIRTUAL,
THREAD_LOCAL_INTERNAL,
"set", TLS_SET_DESC,
false
);
l.insert(fin, setNode);
/* The stack is
-> ThreadLocal instance
-> value
...
and we need
-> value
-> ThreadLocal instance
...
Therefore we need to swap the topmost 2 elements
*/
l.insertBefore(setNode, new InsnNode(Opcodes.SWAP));
}
return fin;
}
private AbstractInsnNode updateExportUsage(ClassNode cn, FieldInsnNode fin, InsnList l) {
String prefix = null;
boolean isPut = false;
// all the perf related methods start either with 'getPerf' or 'putPerf'
// the second part of the method name is extracted from the field type
if (fin.getOpcode() == Opcodes.GETSTATIC) {
prefix = "getPerf";
} else {
isPut = true;
prefix = "putPerf";
}
String methodName = null;
Type tType = null;
Type t = Type.getType(fin.desc);
switch (t.getSort()) {
case Type.INT:
case Type.SHORT:
case Type.BYTE:
case Type.CHAR:
case Type.BOOLEAN: {
methodName = prefix + "Int";
tType = Type.INT_TYPE;
break;
}
case Type.LONG: {
methodName = prefix + "Long";
tType = Type.LONG_TYPE;
break;
}
case Type.FLOAT: {
methodName = prefix + "Float";
tType = Type.FLOAT_TYPE;
break;
}
case Type.DOUBLE: {
methodName = prefix + "Double";
tType = Type.DOUBLE_TYPE;
break;
}
case Type.OBJECT: {
if (t.equals(STRING_TYPE)) {
methodName = prefix + "String";
tType = STRING_TYPE;
}
break;
}
}
if (methodName == null) {
// if the perf counter is not accessible
// just put null on the stack for GETSTATIC
// and remove the topmost item from the stack for PUTSTATIC
l.insert(fin, isPut ? new InsnNode(Opcodes.POP) :
new InsnNode(Opcodes.ACONST_NULL));
} else {
InsnList toInsert = new InsnList();
toInsert.add(new LdcInsnNode(perfCounterName(cn, fin.name)));
toInsert.add(new MethodInsnNode(
Opcodes.INVOKESTATIC, BTRACERT_INTERNAL,
methodName, isPut ? Type.getMethodDescriptor(Type.VOID_TYPE, tType, STRING_TYPE) :
Type.getMethodDescriptor(tType, STRING_TYPE),
false
));
l.insert(fin, toInsert);
}
AbstractInsnNode ret = fin.getNext();
l.remove(fin);
return ret;
}
private AbstractInsnNode updateInjectedUsage(ClassNode cn, FieldInsnNode fin, InsnList l, LocalVarGenerator lvg) {
if (serviceLocals.containsKey(fin.name)) {
VarInsnNode load = new VarInsnNode(
Type.getType(fin.desc).getOpcode(Opcodes.ILOAD),
serviceLocals.get(fin.name)
);
l.insert(fin, load);
l.remove(fin);
return load;
}
InsnList toInsert = new InsnList();
AnnotationNode an = injectedFlds.get(fin.name);
Type implType = Type.getType(fin.desc);
String svcType = "SIMPLE";
String fctryMethod = null;
if (an.values != null) {
Iterator<Object> iter = an.values.iterator();
while (iter.hasNext()) {
String name = (String)iter.next();
Object val = iter.next();
switch (name) {
case "value":
svcType = (String)((String[])val)[1];
break;
case "factoryMethod":
fctryMethod = (String)val;
break;
}
}
}
int varIdx = lvg.newVar(implType);
if (svcType.equals("SIMPLE")) {
if (fctryMethod == null || fctryMethod.isEmpty()) {
toInsert.add(new TypeInsnNode(Opcodes.NEW, implType.getInternalName()));
toInsert.add(new InsnNode(Opcodes.DUP));
toInsert.add(new InsnNode(Opcodes.DUP));
toInsert.add(new MethodInsnNode(
Opcodes.INVOKESPECIAL, implType.getInternalName(),
"<init>", "()V", false
));
} else {
toInsert.add(new MethodInsnNode(
Opcodes.INVOKESTATIC, implType.getInternalName(), fctryMethod,
Type.getMethodDescriptor(implType), false
));
toInsert.add(new InsnNode(Opcodes.DUP));
}
} else { // RuntimeService here
if (fctryMethod == null || fctryMethod.isEmpty()) {
toInsert.add(new TypeInsnNode(Opcodes.NEW, implType.getInternalName()));
toInsert.add(new InsnNode(Opcodes.DUP));
toInsert.add(new InsnNode(Opcodes.DUP));
toInsert.add(getRuntime(cn));
toInsert.add(new MethodInsnNode(
Opcodes.INVOKESPECIAL, implType.getInternalName(),
"<init>", RT_SERVICE_CTR_DESC, false
));
} else {
toInsert.add(getRuntime(cn));
toInsert.add(new MethodInsnNode(
Opcodes.INVOKESTATIC, implType.getInternalName(), fctryMethod,
Type.getMethodDescriptor(implType, BTRACERT_TYPE), false
));
toInsert.add(new InsnNode(Opcodes.DUP));
}
}
toInsert.add(new VarInsnNode(Opcodes.ASTORE, varIdx));
l.insert(fin, toInsert);
AbstractInsnNode next = fin.getNext();
l.remove(fin);
serviceLocals.put(fin.name, varIdx);
return next;
}
private AbstractInsnNode unfoldServiceInstantiation(ClassNode cn, MethodInsnNode min, InsnList l) {
if (min.owner.equals(SERVICE_INTERNAL)) {
AbstractInsnNode next = min.getNext();
switch (min.name) {
case "simple": {
Type[] args = Type.getArgumentTypes(min.desc);
if (args.length == 1) {
AbstractInsnNode ldcType = min.getPrevious();
if (ldcType.getType() == AbstractInsnNode.LDC_INSN) {
// remove the original sequence
l.remove(min);
l.remove(ldcType);
if (next.getOpcode() == Opcodes.CHECKCAST) {
next = next.getNext();
l.remove(next.getPrevious());
}
// ---
String sType = ((Type)((LdcInsnNode)ldcType).cst).getInternalName();
InsnList toInsert = new InsnList();
toInsert.add(new TypeInsnNode(Opcodes.NEW, sType));
toInsert.add(new InsnNode(Opcodes.DUP));
toInsert.add(new MethodInsnNode(
Opcodes.INVOKESPECIAL, sType,
"<init>", "()V", false
));
l.insertBefore(next, toInsert);
}
} else if (args.length == 2) {
AbstractInsnNode ldcType = min.getPrevious();
AbstractInsnNode ldcFMethod = ldcType.getPrevious();
if (ldcType.getType() == AbstractInsnNode.LDC_INSN &&
ldcFMethod.getType() == AbstractInsnNode.LDC_INSN) {
// remove the original sequence
l.remove(min);
l.remove(ldcType);
l.remove(ldcFMethod);
if (next.getOpcode() == Opcodes.CHECKCAST) {
next = next.getNext();
l.remove(next.getPrevious());
}
// ---
String sType = ((Type)((LdcInsnNode)ldcType).cst).getInternalName();
String fMethod = (String)((LdcInsnNode)ldcFMethod).cst;
InsnList toInsert = new InsnList();
toInsert.add(new TypeInsnNode(Opcodes.NEW, sType));
toInsert.add(new InsnNode(Opcodes.DUP));
toInsert.add(new LdcInsnNode(fMethod));
toInsert.add(new MethodInsnNode(
Opcodes.INVOKESPECIAL, sType,
"<init>", SERVICE_CTR_DESC, false
));
l.insertBefore(next, toInsert);
}
}
break;
}
case "runtime": {
Type[] args = Type.getArgumentTypes(min.desc);
if (args.length == 1) {
AbstractInsnNode ldcType = min.getPrevious();
if (ldcType.getType() == AbstractInsnNode.LDC_INSN) {
// remove the original sequence
l.remove(min);
l.remove(ldcType);
if (next.getOpcode() == Opcodes.CHECKCAST) {
next = next.getNext();
l.remove(next.getPrevious());
}
// ---
String sType = ((Type)((LdcInsnNode)ldcType).cst).getInternalName();
InsnList toInsert = new InsnList();
toInsert.add(new TypeInsnNode(Opcodes.NEW, sType));
toInsert.add(new InsnNode(Opcodes.DUP));
toInsert.add(getRuntime(cn));
toInsert.add(new MethodInsnNode(
Opcodes.INVOKESPECIAL, sType,
"<init>", RT_SERVICE_CTR_DESC, false
));
l.insertBefore(next, toInsert);
}
}
break;
}
}
return next;
}
return min;
}
private InsnList getReturnSequence(MethodNode mn, boolean addRuntimeExit) {
InsnList l = new InsnList();
Type retType = Type.getReturnType(mn.desc);
if (!retType.equals(Type.VOID_TYPE)) {
int retIndex = -1;
if (mn.visibleParameterAnnotations != null) {
int offset = 0;
Type[] params = Type.getArgumentTypes(mn.desc);
for(int i = 0; i < mn.visibleParameterAnnotations.length; i++) {
if (mn.visibleParameterAnnotations[i] != null) {
for(AnnotationNode an : (List<AnnotationNode>)mn.visibleParameterAnnotations[i]) {
if (an.desc.equals(Type.getDescriptor(Return.class))) {
retIndex = offset;
}
}
}
offset += params[i].getSize();
}
}
if (retIndex > -1) {
l.add(new VarInsnNode(retType.getOpcode(Opcodes.ILOAD), retIndex));
} else {
switch (retType.getSort()) {
case Type.INT:
case Type.SHORT:
case Type.BYTE:
case Type.CHAR:
case Type.BOOLEAN: {
l.add(new InsnNode(Opcodes.ICONST_0));
break;
}
case Type.LONG: {
l.add(new InsnNode(Opcodes.LCONST_0));
break;
}
case Type.FLOAT: {
l.add(new InsnNode(Opcodes.FCONST_0));
break;
}
case Type.DOUBLE: {
l.add(new InsnNode(Opcodes.DCONST_0));
break;
}
case Type.ARRAY:
case Type.OBJECT: {
l.add(new InsnNode(Opcodes.ACONST_NULL));
break;
}
}
}
}
if (addRuntimeExit) {
l.add(getRuntimeExit());
}
l.add(new InsnNode(retType.getOpcode(Opcodes.IRETURN)));
return l;
}
// For each @Export field, we create a perf counter
// with the name "btrace.<class name>.<field name>"
private static final String BTRACE_COUNTER_PREFIX = "btrace.";
private String perfCounterName(ClassNode cn, String fieldName) {
return BTRACE_COUNTER_PREFIX + Type.getObjectType(cn.name).getInternalName() + "." + fieldName;
}
private String boxDesc(String desc) {
String boxed_desc = BOX_TYPE_MAP.get(desc);
return boxed_desc != null ? boxed_desc : desc;
}
private MethodInsnNode boxNode(String unboxedDesc) {
String boxedDesc = boxDesc(unboxedDesc);
if (boxedDesc != null) {
return boxNode(unboxedDesc, boxedDesc);
}
return null;
}
private MethodInsnNode boxNode(String unboxedDesc, String boxedDesc) {
return new MethodInsnNode(
Opcodes.INVOKESTATIC,
boxedDesc.substring(1, boxedDesc.length() - 1),
"valueOf",
"(" + unboxedDesc + ")" + boxedDesc,
false
);
}
private MethodInsnNode unboxNode(String boxedDesc, String boxedInternal, String unboxedDesc) {
String mName = null;
switch (boxedDesc) {
case INTEGER_BOXED_DESC: {
mName = "intValue";
break;
}
case SHORT_BOXED_DESC: {
mName = "shortValue";
break;
}
case LONG_BOXED_DESC: {
mName = "longValue";
break;
}
case FLOAT_BOXED_DESC: {
mName = "floatValue";
break;
}
case DOUBLE_BOXED_DESC: {
mName = "doubleValue";
break;
}
case BOOLEAN_BOXED_DESC: {
mName = "booleanValue";
break;
}
case CHARACTER_BOXED_DESC: {
mName = "charValue";
break;
}
}
if (mName != null) {
return new MethodInsnNode(
Opcodes.INVOKEVIRTUAL,
boxedInternal,
mName,
"()" + unboxedDesc,
false
);
}
return null;
}
private int getReturnMethodParameter(MethodNode mn) {
if (mn.visibleParameterAnnotations != null) {
for(int i=0;i<mn.visibleParameterAnnotations.length;i++) {
List paList = (List)mn.visibleParameterAnnotations[i];
if (paList != null) {
for(Object anObj : paList) {
AnnotationNode an = (AnnotationNode)anObj;
if (an.desc.equals(RETURN_DESC)) {
return i;
}
}
}
}
}
return Integer.MIN_VALUE;
}
private static boolean isClassified(MethodClassifier clsf, MethodClassifier requested) {
return clsf.ordinal() >= requested.ordinal();
}
private static boolean isUnannotated(MethodNode mn) {
return mn == null || mn.visibleAnnotations == null || mn.visibleAnnotations.isEmpty();
}
}