/*
* 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 java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.regex.Pattern;
/**
* The single entry point for class transformation.
* <p>
* When a class is to be transformed all the registered {@linkplain BTraceProbe} instances are
* asked for the appropriate instrumentation. When there are no registered probes or none of
* the registered probes is able to instrument the class it will not be transformed.
* </p>
*
* @since 1.3.5
* @author Jaroslav Bachorik
*/
public final class BTraceTransformer implements ClassFileTransformer {
static class Filter {
static enum Result {
TRUE, FALSE, MAYBE
}
private boolean isFast = true;
private boolean isRegex = false;
private final Map<String, Integer> nameMap = new HashMap<>();
private final Map<Pattern, Integer> nameRegexMap = new HashMap<>();
void add(OnMethod om) {
if (om.isSubtypeMatcher() || om.isClassAnnotationMatcher()) {
isFast = false;
} else {
if (om.isClassRegexMatcher()) {
isRegex = true;
String name = om.getClazz().replace("\\.", "/");
addToMap(nameRegexMap, Pattern.compile(name));
} else {
String name = om.getClazz().replace('.', '/');
addToMap(nameMap, name);
}
}
}
void remove(OnMethod om) {
String name = om.getClazz().replace('.', '/');
if (!(om.isSubtypeMatcher() || om.isClassAnnotationMatcher())) {
if (om.isClassRegexMatcher()) {
removeFromMap(nameRegexMap, Pattern.compile(name));
} else {
removeFromMap(nameMap, name);
}
}
}
private static <K> void addToMap(Map<K, Integer> map, K name) {
synchronized(map) {
Integer i = map.get(name);
if (i == null) {
map.put(name, 1);
} else {
map.put(name, i + 1);
}
}
}
private static <K> void removeFromMap(Map<K, Integer> map, K name) {
synchronized(map) {
Integer i = map.get(name);
if (i == null) {
return;
}
int freq = i - 1;
if (freq == 0) {
map.remove(name);
}
}
}
public Result matchClass(String className) {
if (isFast) {
synchronized(nameMap) {
if (nameMap.containsKey(className)) {
return Result.TRUE;
}
}
if (isRegex) {
synchronized(nameRegexMap) {
for(Pattern p : nameRegexMap.keySet()) {
if (p.matcher(className).matches()) {
return Result.TRUE;
}
}
}
}
return Result.FALSE;
}
return Result.MAYBE;
}
}
private final DebugSupport debug;
private final Collection<BTraceProbe> probes = new LinkedList<>();
private final Filter filter = new Filter();
public BTraceTransformer(DebugSupport d) {
debug = d;
}
public final synchronized void register(BTraceProbe p) {
probes.add(p);
for(OnMethod om : p.onmethods()) {
filter.add(om);
}
}
public final synchronized void unregister(BTraceProbe p) {
probes.remove(p);
for(OnMethod om : p.onmethods()) {
filter.remove(om);
}
}
Filter getFilter() {
return filter;
}
@Override
public synchronized byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (probes.isEmpty()) return null;
className = className != null ? className : "<anonymous>";
if ((loader == null || loader.equals(ClassLoader.getSystemClassLoader())) && isSensitiveClass(className)) {
if (isDebug()) {
debugPrint("skipping transform for BTrace class " + className); // NOI18N
}
return null;
}
if (filter.matchClass(className) == Filter.Result.FALSE) return null;
boolean entered = BTraceRuntime.enter();
try {
BTraceClassReader cr = InstrumentUtils.newClassReader(loader, classfileBuffer);
BTraceClassWriter cw = InstrumentUtils.newClassWriter(cr);
for(BTraceProbe p : probes) {
p.notifyTransform(className);
cw.addInstrumentor(p);
}
byte[] transformed = cw.instrument();
if (transformed == null) {
// no instrumentation necessary
if (isDebug()) {
debugPrint("skipping class " + cr.getJavaClassName());
}
return classfileBuffer;
} else {
if (isDebug()) {
debugPrint("transformed class " + cr.getJavaClassName());
}
if (debug.isDumpClasses()) {
debug.dumpClass(className.replace('.', '/'), transformed);
}
}
return transformed;
} catch (Throwable th) {
debugPrint(th);
throw th;
} finally {
if (entered) {
BTraceRuntime.leave();
}
}
}
/*
* Certain classes like java.lang.ThreadLocal and it's
* inner classes, java.lang.Object cannot be safely
* instrumented with BTrace. This is because BTrace uses
* ThreadLocal class to check recursive entries due to
* BTrace's own functions. But this leads to infinite recursions
* if BTrace instruments java.lang.ThreadLocal for example.
* For now, we avoid such classes till we find a solution.
*/
private static boolean isSensitiveClass(String name) {
return ClassFilter.isSensitiveClass(name);
}
private boolean isDebug() {
return debug.isDebug();
}
private void debugPrint(String msg) {
debug.debug(msg);
}
private void debugPrint(Throwable th) {
debug.debug(th);
}
}