/*
* Copyright (c) 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.runtime;
import com.sun.btrace.BTraceRuntime;
import com.sun.btrace.DebugSupport;
import com.sun.btrace.VerifierException;
import com.sun.btrace.comm.RetransformClassNotification;
import com.sun.btrace.org.objectweb.asm.*;
import com.sun.btrace.org.objectweb.asm.tree.ClassNode;
import com.sun.btrace.org.objectweb.asm.tree.MethodNode;
import static com.sun.btrace.runtime.Constants.INJECTED_DESC;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.Collections;
import java.util.Iterator;
import static com.sun.btrace.runtime.ClassFilter.isSubTypeOf;
/**
*
* @author Jaroslav Bachorik
*/
public final class BTraceProbe extends ClassNode {
private final List<OnMethod> onMethods;
private final List<OnProbe> onProbes;
private boolean trustedScript = false;
private boolean classRenamed = false;
private final CallGraph graph;
protected final Set<String> injectedFields;
private final Map<String, BTraceMethodNode> idmap;
private final Preprocessor prep = new Preprocessor();
private final BTraceProbeFactory factory;
private volatile BTraceRuntime rt = null;
private final DebugSupport debug;
private ClassFilter filter = null;
private String className, origName;
private BTraceTransformer transformer;
private VerifierException verifierException = null;
private BTraceProbe(BTraceProbeFactory factory) {
super(Opcodes.ASM5);
this.factory = factory;
this.debug = new DebugSupport(factory.getSettings());
this.onMethods = new ArrayList<>();
this.onProbes = new ArrayList<>();
this.injectedFields = new HashSet<>();
this.idmap = new HashMap<>();
this.graph = new CallGraph();
}
BTraceProbe(BTraceProbeFactory factory, byte[] code) {
this(factory);
initialize(code);
}
BTraceProbe(BTraceProbeFactory factory, InputStream code) throws IOException {
this(factory);
initialize(code);
}
public boolean isTransforming() {
return onMethods != null && onMethods.size() > 0;
}
@Override
public void visit(int version, int access, String name, String sig, String superType, String[] itfcs) {
this.origName = name;
this.className = BTraceRuntime.getClientName(name);
classRenamed = !className.equals(name);
super.visit(version, access, className, sig, superType, itfcs);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] exceptions) {
super.visitMethod(access, name, desc, sig, exceptions);
MethodNode mn = (MethodNode)methods.remove(methods.size() - 1);
BTraceMethodNode bmn = new BTraceMethodNode(mn, this);
methods.add(bmn);
idmap.put(CallGraph.methodId(name, desc), bmn);
return isTrusted() ? bmn : new MethodVerifier(bmn, access, this.origName, name, desc);
}
@Override
public FieldVisitor visitField(int access, final String name, String desc, String signature, Object value) {
return new FieldVisitor(Opcodes.ASM5, super.visitField(access, name, desc, signature, value)) {
@Override
public AnnotationVisitor visitAnnotation(String type, boolean aVisible) {
if (type.equals(INJECTED_DESC)) {
injectedFields.add(name);
}
return super.visitAnnotation(type, aVisible);
}
};
}
public Collection<OnMethod> getApplicableHandlers(BTraceClassReader cr) {
final Collection<OnMethod> applicables = new ArrayList<>(onMethods.size());
final String targetName = cr.getJavaClassName();
outer:
for(OnMethod om : onMethods) {
String probeClass = om.getClazz();
if (probeClass == null && probeClass.isEmpty()) continue;
if (probeClass.equals(targetName)) {
applicables.add(om);
continue;
}
// Check regex match
if (om.isClassRegexMatcher() && !om.isClassAnnotationMatcher()) {
Pattern p = Pattern.compile(probeClass);
if (p.matcher(targetName).matches()) {
applicables.add(om);
continue;
}
}
if (om.isClassAnnotationMatcher()) {
Collection<String> annoTypes = cr.getAnnotationTypes();
if (om.isClassRegexMatcher()) {
Pattern p = Pattern.compile(probeClass);
for(String annoType : annoTypes) {
if (p.matcher(annoType).matches()) {
applicables.add(om);
continue outer;
}
}
} else {
if (annoTypes.contains(probeClass)) {
applicables.add(om);
continue;
}
}
}
// And, finally, check the class hierarchy
if (om.isSubtypeMatcher()) {
// internal name of super type.
if (isSubTypeOf(cr.getClassName(), cr.getClassLoader(), probeClass)) {
applicables.add(om);
}
}
}
return applicables;
}
public Iterable<OnMethod> onmethods() {
return new Iterable<OnMethod>() {
@Override
public Iterator<OnMethod> iterator() {
return Collections.unmodifiableCollection(onMethods).iterator();
}
};
}
public String getClassName() {
return getClassName(false);
}
public String getClassName(boolean internal) {
return internal ? className : className.replace("/", ".");
}
String translateOwner(String owner) {
if (owner.equals(origName)) {
return this.className;
}
return owner;
}
public Class register(BTraceRuntime rt, BTraceTransformer t) {
byte[] code = getBytecode(true);
if (debug.isDumpClasses()) {
debug.dumpClass(name + "_bcp", code);
}
Class clz = defineClass(rt, code);
t.register(this);
this.transformer = t;
this.rt = rt;
return clz;
}
public void unregister() {
if (transformer != null && isTransforming()) {
if (debug.isDebug()) {
debug.debug("onExit: removing transformer for " + getClassName());
}
transformer.unregister(this);
}
this.rt = null;
}
public byte[] getBytecode(boolean onlyBcpMethods) {
ClassWriter cw = InstrumentUtils.newClassWriter();
ClassVisitor cv = cw;
if (onlyBcpMethods) {
cv = new ClassVisitor(Opcodes.ASM5, cw) {
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] exceptions) {
if (name.startsWith("<")) {
// never check constructor and static initializer
return super.visitMethod(access, name, desc, sig, exceptions);
}
BTraceMethodNode bmn = idmap.get(CallGraph.methodId(name, desc));
if (bmn != null) {
if (bmn.isBcpRequired()) {
return super.visitMethod(access, name, desc, sig, exceptions);
}
for(BTraceMethodNode c : bmn.getCallers()) {
if (c.isBcpRequired()) {
return super.visitMethod(access, name, desc, sig, exceptions);
}
}
return null;
}
return super.visitMethod(access, name, desc, sig, exceptions);
}
};
}
this.accept(cv);
return cw.toByteArray();
}
/**
* Collects all the methods reachable from this particular method
* @param name the method name
* @param desc the method descriptor
* @return the callee reachability closure
*/
Set<BTraceMethodNode> callees(String name, String desc) {
Set<String> closure = new HashSet<>();
graph.callees(name, desc, closure);
return fromIdSet(closure);
}
/**
* Collects all the methods from which this particular method is reachable
* @param name the method name
* @param desc the method descriptor
* @return the caller reachability closure
*/
Set<BTraceMethodNode> callers(String name, String desc) {
Set<String> closure = new HashSet<>();
graph.callers(name, desc, closure);
return fromIdSet(closure);
}
public boolean willInstrument(Class clz) {
return filter.isCandidate(clz);
}
public boolean isClassRenamed() {
return classRenamed;
}
public boolean isVerified() {
return verifierException == null;
}
public VerifierException getVerifierException() {
return verifierException;
}
boolean isFieldInjected(String name) {
return injectedFields.contains(name);
}
void addOnMethod(OnMethod om) {
onMethods.add(om);
}
void addOnProbe(OnProbe op) {
onProbes.add(op);
}
void setTrusted() {
trustedScript = true;
}
boolean isTrusted() {
return trustedScript;
}
CallGraph getGraph() {
return graph;
}
void notifyTransform(String className) {
if (rt != null && factory.getSettings().isTrackRetransforms()) {
rt.send(new RetransformClassNotification(className.replace('/', '.')));
}
}
/**
* Maps a list of @OnProbe's to a list @OnMethod's using
* probe descriptor XML files.
*/
private void mapOnProbes() {
ProbeDescriptorLoader pdl = getProbeDescriptorLoader();
if (pdl == null) return;
for (OnProbe op : onProbes) {
String ns = op.getNamespace();
if (debug.isDebug()) debug.debug("about to load probe descriptor for " + ns);
// load probe descriptor for this namespace
ProbeDescriptor probeDesc = pdl.load(ns);
if (probeDesc == null) {
if (debug.isDebug()) debug.debug("failed to find probe descriptor for " + ns);
continue;
}
// find particular probe mappings using "local" name
OnProbe foundProbe = probeDesc.findProbe(op.getName());
if (foundProbe == null) {
if (debug.isDebug()) debug.debug("no probe mappings for " + op.getName());
continue;
}
if (debug.isDebug()) debug.debug("found probe mappings for " + op.getName());
Collection<OnMethod> omColl = foundProbe.getOnMethods();
for (OnMethod om : omColl) {
// copy the info in a new OnMethod so that
// we can set target method name and descriptor
// Note that the probe descriptor cache is used
// across BTrace sessions. So, we should not update
// cached OnProbes (and their OnMethods).
OnMethod omn = new OnMethod(op.getMethodNode());
omn.copyFrom(om);
omn.setTargetName(op.getTargetName());
omn.setTargetDescriptor(op.getTargetDescriptor());
omn.setClassNameParameter(op.getClassNameParameter());
omn.setMethodParameter(op.getMethodParameter());
omn.setDurationParameter(op.getDurationParameter());
omn.setMethodFqn(op.isMethodFqn());
omn.setReturnParameter(op.getReturnParameter());
omn.setSelfParameter(op.getSelfParameter());
omn.setTargetInstanceParameter(op.getTargetInstanceParameter());
omn.setTargetMethodOrFieldFqn(op.isTargetMethodOrFieldFqn());
omn.setTargetMethodOrFieldParameter(op.getTargetMethodOrFieldParameter());
addOnMethod(omn);
}
}
}
private ProbeDescriptorLoader getProbeDescriptorLoader() {
String path = factory.getSettings().getProbeDescPath();
return new ProbeDescriptorLoader(path, debug);
}
private void initialize(byte[] code) {
ClassReader cr = new ClassReader(code);
if (debug.isDumpClasses()) {
debug.dumpClass(cr.getClassName() + "_orig", code);
}
initialize(cr);
}
private void initialize(InputStream code) throws IOException {
initialize(new ClassReader(code));
}
private void initialize(ClassReader cr) {
try {
Verifier v = new Verifier(this, factory.getSettings().isTrusted());
if (debug.isDebug()) {
debug.debug("verifying BTrace class ...");
}
cr.accept(v, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
if (debug.isDebug()) {
debug.debug("BTrace class " + getClassName() + " verified");
debug.debug("preprocessing BTrace class " + getClassName() + " ...");
}
prep.process(this);
if (debug.isDebug()) {
debug.debug("... preprocessed");
}
mapOnProbes();
this.filter = new ClassFilter(onMethods);
} catch (VerifierException e) {
verifierException = e;
} finally {
if (debug.isDumpClasses()) {
debug.dumpClass(name, getBytecode(false));
}
}
}
private Set<BTraceMethodNode> fromIdSet(Set<String> ids) {
Set<BTraceMethodNode> methods = new HashSet<>();
for(String id : ids) {
BTraceMethodNode mn = idmap.get(id);
if (mn != null) {
methods.add(mn);
}
}
return methods;
}
private Class defineClass(BTraceRuntime rt, byte[] code) {
// This extra BTraceRuntime.enter is needed to
// check whether we have already entered before.
boolean enteredHere = BTraceRuntime.enter();
try {
// The trace class static initializer needs to be run
// without BTraceRuntime.enter(). Please look at the
// static initializer code of trace class.
BTraceRuntime.leave();
if (debug.isDebug()) {
debug.debug("about to defineClass " + className);
}
Class clz = rt.defineClass(code, isTransforming());
if (debug.isDebug()) {
debug.debug("defineClass succeeded for " + className);
}
return clz;
} finally {
// leave BTraceRuntime enter state as it was before
// we started executing this method.
if (! enteredHere) BTraceRuntime.enter();
}
}
@Override
public String toString() {
return "BTraceProbe{" + "onMethods=" + onMethods + ", onProbes=" + onProbes + ", trustedScript=" + trustedScript + ", classRenamed=" + classRenamed + ", injectedFields=" + injectedFields + ", className=" + className + '}';
}
}