
co.paralleluniverse.fibers.instrument.MethodDatabase Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of quasar-core Show documentation
Show all versions of quasar-core Show documentation
Fibers, Channels and Actors for the JVM
/*
* Quasar: lightweight threads and actors for the JVM.
* Copyright (c) 2013-2015, Parallel Universe Software Co. All rights reserved.
*
* This program and the accompanying materials are dual-licensed under
* either the terms of the Eclipse Public License v1.0 as published by
* the Eclipse Foundation
*
* or (per the licensee's choosing)
*
* under the terms of the GNU Lesser General Public License version 3.0
* as published by the Free Software Foundation.
*/
/*
* Copyright (c) 2008-2013, Matthias Mann
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Matthias Mann nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package co.paralleluniverse.fibers.instrument;
import static co.paralleluniverse.fibers.instrument.Classes.isYieldMethod;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Opcodes;
/**
*
* Collects information about classes and their suspendable methods.
*
* Provides access to configuration parameters and to logging
*
* @author Matthias Mann
* @author pron
*/
public class MethodDatabase implements Log {
private static final int ASMAPI = Opcodes.ASM5;
private final ClassLoader cl;
private final SuspendableClassifier classifier;
private final NavigableMap classes;
private final HashMap superClasses;
private final ArrayList workList;
private Log log;
private boolean verbose;
private boolean debug;
private boolean allowMonitors;
private boolean allowBlocking;
private int logLevelMask;
public MethodDatabase(ClassLoader classloader, SuspendableClassifier classifier) {
if (classloader == null)
throw new NullPointerException("classloader");
this.cl = classloader;
this.classifier = classifier;
classes = new TreeMap<>();
superClasses = new HashMap<>();
workList = new ArrayList<>();
setLogLevelMask();
}
public boolean isAllowMonitors() {
return allowMonitors;
}
public void setAllowMonitors(boolean allowMonitors) {
this.allowMonitors = allowMonitors;
}
public boolean isAllowBlocking() {
return allowBlocking;
}
public void setAllowBlocking(boolean allowBlocking) {
this.allowBlocking = allowBlocking;
}
public SuspendableClassifier getClassifier() {
return classifier;
}
public Log getLog() {
return log;
}
public void setLog(Log log) {
this.log = log;
}
public boolean isVerbose() {
return verbose;
}
public void setVerbose(boolean verbose) {
this.verbose = verbose;
setLogLevelMask();
}
public boolean isDebug() {
return debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
setLogLevelMask();
}
private void setLogLevelMask() {
logLevelMask = (1 << LogLevel.WARNING.ordinal());
if (verbose || debug) {
logLevelMask |= (1 << LogLevel.INFO.ordinal());
}
if (debug) {
logLevelMask |= (1 << LogLevel.DEBUG.ordinal());
}
}
@Override
public void log(LogLevel level, String msg, Object... args) {
if (log != null && (logLevelMask & (1 << level.ordinal())) != 0)
log.log(level, msg, args);
}
@Override
public void error(String msg, Throwable ex) {
if (log != null)
log.error(msg, ex);
}
public void checkClass(File f) {
try {
FileInputStream fis = new FileInputStream(f);
CheckInstrumentationVisitor civ = checkFileAndClose(fis, f.getPath());
if (civ != null) {
recordSuspendableMethods(civ.getName(), civ.getClassEntry());
if (civ.needsInstrumentation()) {
if (civ.isAlreadyInstrumented()) {
log(LogLevel.INFO, "Found instrumented class: %s", f.getPath());
if (JavaAgent.isActive())
throw new AssertionError();
} else {
log(LogLevel.INFO, "Found class: %s", f.getPath());
if (!JavaAgent.isActive())
workList.add(new WorkListEntry(civ.getName(), f));
}
}
}
} catch (UnableToInstrumentException ex) {
throw ex;
} catch (Exception ex) {
error(f.getPath(), ex);
}
}
private static final int UNKNOWN = 0;
private static final int MAYBE_CORE = 1;
private static final int NONSUSPENDABLE = 2;
private static final int SUSPENDABLE_ABSTRACT = 3;
private static final int SUSPENDABLE = 4;
public SuspendableType isMethodSuspendable(String className, String methodName, String methodDesc, int opcode) {
if (className.startsWith("org/netbeans/lib/"))
return SuspendableType.NON_SUSPENDABLE;
int res = isMethodSuspendable0(className, methodName, methodDesc, opcode);
switch (res) {
case UNKNOWN:
return null;
case MAYBE_CORE:
if (!className.startsWith("java/"))
log(LogLevel.INFO, "Method: %s#%s presumed non-suspendable: probably java core", className, methodName);
// fallthrough
case NONSUSPENDABLE:
return SuspendableType.NON_SUSPENDABLE;
case SUSPENDABLE_ABSTRACT:
return SuspendableType.SUSPENDABLE_SUPER;
case SUSPENDABLE:
return SuspendableType.SUSPENDABLE;
default:
throw new AssertionError();
}
}
public ClassEntry getOrLoadClassEntry(String className) {
ClassEntry entry = getClassEntry(className);
if (entry == null)
entry = checkClass(className);
return entry;
}
private int isMethodSuspendable0(String className, String methodName, String methodDesc, int opcode) {
if (methodName.charAt(0) == '<')
return NONSUSPENDABLE; // special methods are never suspendable
if (isYieldMethod(className, methodName))
return SUSPENDABLE;
final ClassEntry entry = getOrLoadClassEntry(className);
if (entry == null) {
if (isJavaCore(className))
return MAYBE_CORE;
// if (JavaAgent.isActive())
// throw new AssertionError();
return UNKNOWN;
}
SuspendableType susp1 = entry.check(methodName, methodDesc);
int suspendable = UNKNOWN;
if (susp1 == null)
suspendable = UNKNOWN;
else if (susp1 == SuspendableType.SUSPENDABLE)
suspendable = SUSPENDABLE;
else if (susp1 == SuspendableType.SUSPENDABLE_SUPER)
suspendable = SUSPENDABLE_ABSTRACT;
else if (susp1 == SuspendableType.NON_SUSPENDABLE)
suspendable = NONSUSPENDABLE;
if (suspendable == UNKNOWN) {
if (opcode == Opcodes.INVOKEVIRTUAL || opcode == Opcodes.INVOKESTATIC || opcode == Opcodes.INVOKESPECIAL) {
if (entry.getSuperName() != null)
suspendable = isMethodSuspendable0(entry.getSuperName(), methodName, methodDesc, opcode);
}
if (opcode == Opcodes.INVOKEINTERFACE || opcode == Opcodes.INVOKEVIRTUAL) { // can be INVOKEVIRTUAL on an abstract class implementing the interface
for (String iface : entry.getInterfaces()) {
int s = isMethodSuspendable0(iface, methodName, methodDesc, opcode);
if (s > suspendable)
suspendable = s;
if (suspendable > MAYBE_CORE)
break;
}
}
}
return suspendable;
}
public synchronized ClassEntry getClassEntry(String className) {
return classes.get(className);
}
public synchronized ClassEntry getOrCreateClassEntry(String className, String superType) {
ClassEntry ce = classes.get(className);
if (ce == null) {
ce = new ClassEntry(superType);
classes.put(className, ce);
}
return ce;
}
public synchronized Map getInnerClassesEntries(String className) {
Map tailMap = classes.tailMap(className, true);
HashMap map = new HashMap<>();
for (Map.Entry entry : tailMap.entrySet()) {
if (entry.getKey().equals(className) || entry.getKey().startsWith(className + '$'))
map.put(entry.getKey(), entry.getValue());
}
return Collections.unmodifiableMap(map);
}
void recordSuspendableMethods(String className, ClassEntry entry) {
ClassEntry oldEntry;
synchronized (this) {
oldEntry = classes.put(className, entry);
}
if (oldEntry != null && oldEntry != entry) {
if (!oldEntry.equals(entry)) {
log(LogLevel.WARNING, "Duplicate class entries with different data for class: %s", className);
}
}
}
public String getCommonSuperClass(String classA, String classB) {
ArrayList listA = getSuperClasses(classA);
ArrayList listB = getSuperClasses(classB);
if (listA == null || listB == null) {
return null;
}
int idx = 0;
int num = Math.min(listA.size(), listB.size());
for (; idx < num; idx++) {
String superClassA = listA.get(idx);
String superClassB = listB.get(idx);
if (!superClassA.equals(superClassB)) {
break;
}
}
if (idx > 0) {
return listA.get(idx - 1);
}
return null;
}
public boolean isException(String className) {
for (;;) {
if ("java/lang/Throwable".equals(className))
return true;
if ("java/lang/Object".equals(className))
return false;
String superClass = getDirectSuperClass(className);
if (superClass == null) {
log(isProblematicClass(className) ? LogLevel.INFO : LogLevel.WARNING, "Can't determine super class of %s", className);
return false;
}
className = superClass;
}
}
public ArrayList getWorkList() {
return workList;
}
protected ClassEntry checkClass(String className) {
if (cl == null) {
log(LogLevel.INFO, "Can't check class: %s", className);
return null;
}
log(LogLevel.INFO, "Reading class: %s", className);
final InputStream is = cl.getResourceAsStream(className + ".class");
if (is == null) {
log(LogLevel.INFO, "Class not found: %s", className);
return null;
}
ClassEntry entry = getClassEntry(className); // getResourceAsStream may have triggered instrumentation
if (entry == null) {
final CheckInstrumentationVisitor civ = checkFileAndClose(is, className);
if (civ != null) {
entry = civ.getClassEntry();
recordSuspendableMethods(className, entry);
} else
log(LogLevel.INFO, "Class not found: %s", className);
} else {
try {
is.close();
} catch (IOException e) {
error(className, e);
}
}
return entry;
}
private CheckInstrumentationVisitor checkFileAndClose(InputStream is, String name) {
try {
try {
ClassReader r = new ClassReader(is);
CheckInstrumentationVisitor civ = new CheckInstrumentationVisitor(this);
r.accept(civ, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE);
return civ;
} finally {
is.close();
}
} catch (UnableToInstrumentException ex) {
throw ex;
} catch (Exception ex) {
error(name, ex);
}
return null;
}
private String extractSuperClass(String className) {
final InputStream is = cl.getResourceAsStream(className + ".class");
if (is != null) {
try {
try {
ClassReader r = new ClassReader(is);
ExtractSuperClass esc = new ExtractSuperClass();
r.accept(esc, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
return esc.superClass;
} finally {
is.close();
}
} catch (IOException ex) {
error(className, ex);
}
}
return null;
}
private ArrayList getSuperClasses(String className) {
ArrayList result = new ArrayList();
for (;;) {
result.add(0, className);
if ("java/lang/Object".equals(className)) {
return result;
}
String superClass = getDirectSuperClass(className);
if (superClass == null) {
log(isProblematicClass(className) ? LogLevel.INFO : LogLevel.WARNING, "Can't determine super class of %s", className);
return null;
}
className = superClass;
}
}
protected String getDirectSuperClass(String className) {
ClassEntry entry = getClassEntry(className);
if (entry != null && entry != CLASS_NOT_FOUND)
return entry.getSuperName();
String superClass;
synchronized (this) {
superClass = superClasses.get(className);
}
if (superClass == null) {
superClass = extractSuperClass(className);
if (superClass != null) {
String oldSuperClass;
synchronized (this) {
oldSuperClass = superClasses.put(className, superClass);
}
if (oldSuperClass != null) {
if (!oldSuperClass.equals(superClass))
log(LogLevel.WARNING, "Duplicate super class entry with different value: %s vs %s", oldSuperClass, superClass);
}
}
}
return superClass;
}
public static boolean isReflectInvocation(String className, String methodName) {
return "java/lang/reflect/Method".equals(className) && "invoke".equals(methodName);
}
public static boolean isSyntheticAccess(String className, String methodName) {
return methodName.startsWith("access$");
}
public static boolean isInvocationHandlerInvocation(String className, String methodName) {
return className.equals("java/lang/reflect/InvocationHandler") && methodName.equals("invoke");
}
public static boolean isMethodHandleInvocation(String className, String methodName) {
return className.equals("java/lang/invoke/MethodHandle") && methodName.startsWith("invoke");
}
public static boolean isJavaCore(String className) {
return className.startsWith("java/") || className.startsWith("javax/")
|| className.startsWith("sun/") || (className.startsWith("com/sun/") && !className.startsWith("com/sun/jersey"));
}
public static boolean isProblematicClass(String className) {
return className.startsWith("org/gradle/")
|| className.startsWith("javax/jms/")
|| className.startsWith("ch/qos/logback/")
|| className.startsWith("org/apache/logging/log4j/")
|| className.startsWith("org/apache/log4j/");
}
private static final ClassEntry CLASS_NOT_FOUND = new ClassEntry("");
public enum SuspendableType {
NON_SUSPENDABLE, SUSPENDABLE_SUPER, SUSPENDABLE
};
public static final class ClassEntry {
private final HashMap methods;
private String sourceName;
private String sourceDebugInfo;
private boolean isInterface;
private String[] interfaces;
private final String superName;
private boolean instrumented;
private volatile boolean requiresInstrumentation;
public ClassEntry(String superName) {
this.superName = superName;
this.methods = new HashMap<>();
}
public void set(String name, String desc, SuspendableType suspendable) {
String nameAndDesc = key(name, desc);
methods.put(nameAndDesc, suspendable);
}
public String getSourceName() {
return sourceName;
}
public void setSourceName(String sourceName) {
this.sourceName = sourceName;
}
public String getSourceDebugInfo() {
return sourceDebugInfo;
}
public void setSourceDebugInfo(String sourceDebugInfo) {
this.sourceDebugInfo = sourceDebugInfo;
}
public boolean isInterface() {
return isInterface;
}
public void setIsInterface(boolean isInterface) {
this.isInterface = isInterface;
}
public String getSuperName() {
return superName;
}
public void setAll(SuspendableType suspendable) {
for (Map.Entry entry : methods.entrySet())
entry.setValue(suspendable);
}
public String[] getInterfaces() {
return interfaces;
}
public void setInterfaces(String[] interfaces) {
this.interfaces = interfaces;
}
public SuspendableType check(String name, String desc) {
return methods.get(key(name, desc));
}
// only for instrumentation verification
public boolean isSuspendable(String name) {
for (Map.Entry entry : methods.entrySet()) {
String key = entry.getKey();
if (key.substring(0, key.indexOf('(')).equals(name) && entry.getValue() != SuspendableType.NON_SUSPENDABLE)
return true;
}
return false;
}
public boolean requiresInstrumentation() {
return requiresInstrumentation;
}
public void setRequiresInstrumentation(boolean requiresInstrumentation) {
this.requiresInstrumentation = requiresInstrumentation;
}
@Override
public int hashCode() {
return superName.hashCode() * 67 + methods.hashCode();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ClassEntry)) {
return false;
}
final ClassEntry other = (ClassEntry) obj;
return superName.equals(other.superName) && methods.equals(other.methods);
}
private static String key(String methodName, String methodDesc) {
return methodName.concat(methodDesc);
}
public boolean isInstrumented() {
return instrumented;
}
public void setInstrumented(boolean instrumented) {
this.instrumented = instrumented;
}
}
public static class ExtractSuperClass extends ClassVisitor {
String superClass;
public ExtractSuperClass() {
super(ASMAPI);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.superClass = superName;
}
}
public static class WorkListEntry {
public final String name;
public final File file;
public WorkListEntry(String name, File file) {
this.name = name;
this.file = file;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy