/*
* 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.runtime;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import com.sun.btrace.org.objectweb.asm.AnnotationVisitor;
import com.sun.btrace.org.objectweb.asm.Attribute;
import com.sun.btrace.org.objectweb.asm.ClassReader;
import com.sun.btrace.org.objectweb.asm.FieldVisitor;
import com.sun.btrace.org.objectweb.asm.MethodVisitor;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.util.PrefixMap;
import java.lang.ref.Reference;
import java.util.*;
import java.util.regex.PatternSyntaxException;
/**
* This class checks whether a given target class
* matches at least one probe specified in a BTrace
* class.
*
* @author A. Sundararajan
*/
public class ClassFilter {
private static final Class<?> REFERENCE_CLASS = Reference.class;
private static final PrefixMap SENSITIVE_CLASSES = new PrefixMap();
private Set<String> sourceClasses;
private Pattern[] sourceClassPatterns;
private String[] annotationClasses;
private Pattern[] annotationClassPatterns;
// +foo type class pattern in any @OnMethod.
private String[] superTypes;
// same as above but stored in internal name form ('/' instead of '.')
private String[] superTypesInternal;
private final List<OnMethod> onMethods;
static {
ClassReader.class.getClassLoader();
AnnotationVisitor.class.getClassLoader();
FieldVisitor.class.getClassLoader();
MethodVisitor.class.getClassLoader();
Attribute.class.getClassLoader();
SENSITIVE_CLASSES.add("java/lang/Object");
SENSITIVE_CLASSES.add("java/lang/String");
SENSITIVE_CLASSES.add("java/lang/ThreadLocal");
SENSITIVE_CLASSES.add("java/lang/VerifyError");
SENSITIVE_CLASSES.add("java/lang/instrument/");
SENSITIVE_CLASSES.add("java/lang/invoke/");
SENSITIVE_CLASSES.add("java/lang/ref/");
SENSITIVE_CLASSES.add("java/lang/concurrent/");
SENSITIVE_CLASSES.add("sun/reflect");
SENSITIVE_CLASSES.add("sun/misc/Unsafe");
SENSITIVE_CLASSES.add("sun/security/");
SENSITIVE_CLASSES.add("com/sun/btrace/");
}
public ClassFilter(List<OnMethod> onMethods) {
this.onMethods = new ArrayList<>(onMethods);
init();
}
public boolean isCandidate(Class target) {
if (target.isInterface() || target.isPrimitive() || target.isArray()) {
return false;
}
if (REFERENCE_CLASS.equals(target)) {
// instrumenting the <b>java.lang.ref.Reference</b> class will lead
// to StackOverflowError in <b>java.lang.ThreadLocal.get()</b>
return false;
}
try {
// ignore classes annotated with @BTrace -
// We don't want to instrument tracing classes!
if (target.getAnnotation(BTrace.class) != null) {
return false;
}
} catch (Throwable t) {
// thrown from java.lang.Class.initAnnotationsIfNecessary()
// seems to be a case when trying to access non-existing annotations
// on a superclass
// * messed up situation - ignore the class *
return false;
}
String className = target.getName();
if (isNameMatching(className)) {
return true;
}
for (Pattern pat : sourceClassPatterns) {
if (pat.matcher(className).matches()) {
return true;
}
}
for (String st : superTypes) {
if (isSubTypeOf(target, st)) {
return true;
}
}
Annotation[] annotations = target.getAnnotations();
String[] annoTypes = new String[annotations.length];
for (int i = 0; i < annotations.length; i++) {
annoTypes[i] = annotations[i].annotationType().getName();
}
for (String name : annotationClasses) {
for (String annoType : annoTypes) {
if (name.equals(annoType)) {
return true;
}
}
}
for (Pattern pat : annotationClassPatterns) {
for (String annoType : annoTypes) {
if (pat.matcher(annoType).matches()) {
return true;
}
}
}
return false;
}
Collection<OnMethod> getApplicableHandlers(BTraceClassReader cr) {
final Collection<OnMethod> applicables = new ArrayList<>(onMethods.size());
final String targetName = cr.getClassName().replace('/', '.');
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()) {
if (Pattern.matches(probeClass, targetName)) {
applicables.add(om);
continue;
}
}
if (om.isClassAnnotationMatcher()) {
Collection<String> annoTypes = cr.getAnnotationTypes();
if (om.isClassRegexMatcher()) {
Pattern annoCheck = Pattern.compile(probeClass);
for(String annoType : annoTypes) {
if (annoCheck.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 boolean isNameMatching(String clzName) {
if (sourceClasses.contains(clzName)) {
return true;
}
for (Pattern pat : sourceClassPatterns) {
if (pat.matcher(clzName).matches()) {
return true;
}
}
return false;
}
/*
* return whether given Class is subtype of given type name
* Note that we can not use Class.isAssignableFrom because the other
* type is specified by just name and not by Class object.
*/
public static boolean isSubTypeOf(Class clazz, String typeName) {
if (clazz == null) {
return false;
} else if (clazz.getName().equals(typeName)) {
return true;
} else {
for (Class iface : clazz.getInterfaces()) {
if (isSubTypeOf(iface, typeName)) {
return true;
}
}
return isSubTypeOf(clazz.getSuperclass(), typeName);
}
}
/**
* Return whether given Class <i>typeA</i> is subtype of any of the
* given type names.
* @param typeA the type to check
* @param loader the classloader for loading the type (my be null)
* @param types any requested supertypes
**/
public static boolean isSubTypeOf(String typeA, ClassLoader loader, String ... types) {
if (typeA == null || typeA.equals(Constants.OBJECT_INTERNAL)) {
return false;
}
if (types.length == 0) {
return false;
}
boolean internal = types[0].contains("/");
loader = (loader != null ? loader : ClassLoader.getSystemClassLoader());
if (internal) {
typeA = typeA.replace('.', '/');
} else {
typeA = typeA.replace('/', '.');
}
Set<String> typeSet = new HashSet<>(Arrays.asList(types));
if (typeSet.contains(typeA)) {
return true;
}
ClassInfo ci = ClassCache.getInstance().get(loader, typeA);
Collection<String> sTypes = new LinkedList<>();
for (ClassInfo sCi : ci.getSupertypes(false)) {
sTypes.add(internal ? sCi.getClassName() : sCi.getJavaClassName());
}
sTypes.retainAll(typeSet);
return !sTypes.isEmpty();
}
/*
* 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.
*/
public static boolean isSensitiveClass(String name) {
return SENSITIVE_CLASSES.contains(name);
}
private void init() {
List<String> strSrcList = new ArrayList<>();
List<Pattern> patSrcList = new ArrayList<>();
List<String> superTypesList = new ArrayList<>();
List<String> superTypesInternalList = new ArrayList<>();
List<String> strAnoList = new ArrayList<>();
List<Pattern> patAnoList = new ArrayList<>();
for (OnMethod om : onMethods) {
String className = om.getClazz();
if (className.length() == 0) {
continue;
}
if (om.isClassRegexMatcher()) {
try {
Pattern p = Pattern.compile(className);
if (om.isClassAnnotationMatcher()) {
patAnoList.add(p);
} else {
patSrcList.add(p);
}
} catch (PatternSyntaxException pse) {
System.err.println("btrace ERROR: invalid regex pattern - " + className.substring(1, className.length() - 1));
}
} else if (om.isClassAnnotationMatcher()) {
strAnoList.add(className);
} else if (om.isSubtypeMatcher()) {
superTypesList.add(className);
superTypesInternalList.add(className.replace('.', '/'));
} else {
strSrcList.add(className);
}
}
sourceClasses = new HashSet(strSrcList.size());
sourceClasses.addAll(strSrcList);
sourceClassPatterns = new Pattern[patSrcList.size()];
patSrcList.toArray(sourceClassPatterns);
superTypes = new String[superTypesList.size()];
superTypesList.toArray(superTypes);
superTypesInternal = new String[superTypesInternalList.size()];
superTypesInternalList.toArray(superTypesInternal);
annotationClasses = new String[strAnoList.size()];
strAnoList.toArray(annotationClasses);
annotationClassPatterns = new Pattern[patAnoList.size()];
patAnoList.toArray(annotationClassPatterns);
}
}