All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.paralleluniverse.fibers.instrument.MethodDatabase Maven / Gradle / Ivy

Go to download

The core library for Fibers on Java, compatible with Java 11-16. Forked from puniverse/quasar

There is a newer version: 10.0.6
Show newest version
/*
 * Quasar: lightweight threads and actors for the JVM.
 * Copyright (c) 2013-2018, 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 co.paralleluniverse.common.reflection.ClassLoaderUtil;
import static co.paralleluniverse.fibers.instrument.QuasarInstrumentor.ASMAPI;
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.lang.ref.WeakReference;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;

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 { private final WeakReference clRef; private final SuspendableClassifier classifier; private final NavigableMap classes; private final HashMap superClasses; private final QuasarInstrumentor instrumentor; public MethodDatabase(QuasarInstrumentor instrumentor, ClassLoader classloader, SuspendableClassifier classifier) { this.instrumentor = instrumentor; this.clRef = classloader != null ? new WeakReference<>(classloader) : null; this.classifier = classifier; classes = new TreeMap<>(); superClasses = new HashMap<>(); } boolean isAllowMonitors() { return instrumentor.isAllowMonitors(); } boolean isAllowBlocking() { return instrumentor.isAllowBlocking(); } public SuspendableClassifier getClassifier() { return classifier; } public void log(LogLevel level, String msg, Object... args) { instrumentor.log(level, msg, args); } public void error(String msg, Throwable ex) { instrumentor.error(msg, ex); } public boolean isDebug() { return instrumentor.isDebug(); } public boolean isVerbose() { return instrumentor.isVerbose(); } public Log getLog() { return instrumentor.getLog(); } public String 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()); return civ.getName(); } } } return null; } catch (UnableToInstrumentException ex) { throw ex; } catch (Exception ex) { error(f.getPath(), ex); return null; } } private static final int UNKNOWN = 0; private static final int JDK = 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/")) { log(LogLevel.INFO, "Method: %s#%s marked non-suspendable because it is Netbeans library", className, methodName); return SuspendableType.NON_SUSPENDABLE; } int res = isMethodSuspendable0(className, methodName, methodDesc, opcode); switch (res) { case UNKNOWN: return null; case JDK: if (!className.startsWith("java/")) log(LogLevel.INFO, "Method: %s#%s not in 'java' package but marked non-suspendable anyway because it is a probably part of the JDK", 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 (isJDK(className)) return JDK; // 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 > JDK) 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(className, superType); classes.put(className, ce); } return ce; } // this method is used by Pulsar 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 (this is usually related to classloading)", className); return false; } className = superClass; } } protected ClassEntry checkClass(String className) { ClassLoader cl = null; if (clRef != null) { cl = clRef.get(); if (cl == null) { log(LogLevel.INFO, "Can't check class: %s", className); return null; } } log(LogLevel.INFO, "Reading class: %s", className); try (final InputStream is = ClassLoaderUtil.getResourceAsStream(cl, 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); } return entry; } catch(IOException e) { throw new RuntimeException("While opening " + className, e); // return null; } } private CheckInstrumentationVisitor checkFileAndClose(InputStream is, String name) throws IOException { try { ClassReader r = new ClassReader(is); CheckInstrumentationVisitor civ = new CheckInstrumentationVisitor(this); r.accept(civ, ClassReader.SKIP_FRAMES | ClassReader.SKIP_CODE); return civ; } finally { is.close(); } } private String extractSuperClass(String className) { ClassLoader cl = null; if (clRef != null) { cl = clRef.get(); if (cl == null) { return null; } } try (final InputStream is = ClassLoaderUtil.getResourceAsStream(cl, className + ".class")) { ClassReader r = new ClassReader(is); ExtractSuperClass esc = new ExtractSuperClass(); r.accept(esc, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); return esc.superClass; } 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 isJDK(String className) { return className.startsWith("java/") || className.startsWith("javax/") || className.startsWith("sun/") || className.startsWith("jdk/") || (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; public final HashSet bridges; private String sourceName; private String sourceDebugInfo; private boolean isInterface; private String[] interfaces; private final String superName; private final String name; private boolean instrumented; private volatile boolean requiresInstrumentation; public ClassEntry(String name, String superName) { this.name = name; this.superName = superName; this.methods = new HashMap<>(); this.bridges = new HashSet<>(); } public void set(String name, String desc, SuspendableType suspendable, boolean bridge) { String nameAndDesc = key(name, desc); methods.put(nameAndDesc, suspendable); if (bridge) bridges.add(nameAndDesc); } public String getName() { return name; } public boolean implementsInterface(String name, MethodDatabase db) { for(String interf : interfaces){ if(interf.equals(name)) return true; } if(superName != null){ ClassEntry superClass = db.getOrLoadClassEntry(superName); if(superClass != null && superClass.implementsInterface(name, db)) return true; } for(String interf : interfaces){ ClassEntry superClass = db.getOrLoadClassEntry(interf); if(superClass != null && superClass.implementsInterface(name, db)) return true; } return false; } public ClassEntry getClassImplementingMethod(String methodNameAndParams, MethodDatabase db) { for (Map.Entry entry : methods.entrySet()) { String key = entry.getKey(); if (key.substring(0, key.indexOf(')')+1).equals(methodNameAndParams) && !bridges.contains(key)) return this; } if(superName != null){ ClassEntry superClass = db.getOrLoadClassEntry(superName); if(superClass != null) { ClassEntry ret = superClass.getClassImplementingMethod(methodNameAndParams, db); if (ret != null) return ret; } } for(String interf : interfaces){ ClassEntry superClass = db.getOrLoadClassEntry(interf); if(superClass != null) { ClassEntry ret = superClass.getClassImplementingMethod(methodNameAndParams, db); if (ret != null) return ret; } } return null; } public String getReturnType(String methodNameAndParams) { for (Map.Entry entry : methods.entrySet()) { String key = entry.getKey(); int retIndex = key.indexOf(")")+1; if (key.substring(0, retIndex).equals(methodNameAndParams) && !bridges.contains(key)) return key.substring(retIndex); } return null; } 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; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy