blob: 5784c95dceb721591fca9bcf3af427ad14a2188f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sirona.javaagent;
import org.apache.sirona.javaagent.classloader.LoadFirstClassLoader;
import org.apache.sirona.javaagent.logging.SironaAgentLogging;
import org.apache.sirona.util.ClassLoaders;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
public class SironaTransformer implements ClassFileTransformer {
private static final String DELEGATING_CLASS_LOADER = "sun.reflect.DelegatingClassLoader";
// used to not load classes while loading and create linkage errors
private final ConcurrentMap<ClassLoader, ClassLoader> tempClassLoaders = new ConcurrentHashMap<ClassLoader, ClassLoader>();
private final boolean debug;
private final String[] autoClassLoaderExcludes;
private final boolean skipTempLoader;
public SironaTransformer(final boolean debug, final boolean skipTempLoader, final String tempClassLoaders) {
this.debug = debug || Boolean.getBoolean("sirona.javaagent.debug");
this.skipTempLoader = skipTempLoader || Boolean.getBoolean("sirona.javaagent.skipTempLoader");
final String excludes = System.getProperty(
"sirona.javaagent.dontAutoClassLoaderExclude",
tempClassLoaders != null ?
tempClassLoaders :
"org.apache.openjpa.lib.util.TemporaryClassLoader,org.apache.openejb.core.TempClassLoader");
this.autoClassLoaderExcludes = excludes.split(" *, *");
}
public void evictClassLoaders() { // we will recreate them if needed
tempClassLoaders.clear();
}
@Override
public byte[] transform(final ClassLoader loader, final String className, final Class<?> classBeingRedefined,
final ProtectionDomain protectionDomain, final byte[] classfileBuffer) throws IllegalClassFormatException {
if (shouldTransform(className, loader) && !isExcludedLoader(loader)) {
return doTransform(className, classfileBuffer);
}
return classfileBuffer;
}
private boolean isExcludedLoader(final ClassLoader loader) {
if (loader == null) {
return false;
}
final String name = loader.getClass().getName();
for (int i = 0; i < autoClassLoaderExcludes.length; i++) {
if (autoClassLoaderExcludes[i].equals(name)) {
return true;
}
}
return LoadFirstClassLoader.class.getName().equals(name); // of course we exclude our internal loader
}
protected byte[] doTransform(final String className, final byte[] classfileBuffer) {
try {
final ClassReader reader = new ClassReader(classfileBuffer);
final ClassWriter writer = new SironaClassWriter(className == null ? null : className.replace('/', '.'),
skipTempLoader ? null : tempClassLoaders, reader, ClassWriter.COMPUTE_FRAMES);
final SironaClassVisitor advisor = new SironaClassVisitor(writer, className, classfileBuffer);
reader.accept(advisor, ClassReader.SKIP_FRAMES);
if (advisor.wasAdviced()) {
final byte[] bytes = writer.toByteArray();
if (debug) {
final File dump = new File(System.getProperty("java.io.tmpdir"), "sirona-dump/" + className + ".class");
dump.getParentFile().mkdirs();
FileOutputStream w = null;
try {
w = new FileOutputStream(dump);
w.write(bytes);
} finally {
if (w != null) {
w.close();
}
}
}
return bytes;
}
return classfileBuffer;
} catch (final Throwable e) {
if (SironaAgentLogging.AGENT_DEBUG) {
SironaAgentLogging.debug("fail transforming class {0} : {1}", className, e.getMessage());
e.printStackTrace();
}
//throw new RuntimeException( e.getMessage(), e );
System.out.println("fail to transform class:" + className + ", " + e.getMessage());
e.printStackTrace();
return classfileBuffer;
}
}
public static class SironaClassWriter extends ClassWriter {
private final ConcurrentMap<ClassLoader, ClassLoader> tempClassLoaders;
private final String currentClass;
public SironaClassWriter(final String currentClass,
final ConcurrentMap<ClassLoader, ClassLoader> tempClassLoaders,
final ClassReader classReader, final int flags) {
super(classReader, flags);
this.currentClass = currentClass;
this.tempClassLoaders = tempClassLoaders;
}
/**
* copy paste code from asm as we need a different way to load classes
*
* @param type1
* @param type2
* @return
*/
@Override
protected String getCommonSuperClass(final String type1, final String type2) {
final ClassLoader loader = createTempLoader();
Class<?> c, d;
try {
c = findClass(loader, type1.replace('/', '.'));
d = findClass(loader, type2.replace('/', '.'));
} catch (final Exception e) {
throw new RuntimeException(e.toString());
} catch (final ClassCircularityError e) {
return "java/lang/Object";
}
if (c.isAssignableFrom(d)) {
return type1;
}
if (d.isAssignableFrom(c)) {
return type2;
}
if (c.isInterface() || d.isInterface()) {
return "java/lang/Object";
} else {
do {
c = c.getSuperclass();
} while (!c.isAssignableFrom(d));
return c.getName().replace('.', '/');
}
}
private ClassLoader createTempLoader() {
final ClassLoader tccl = ClassLoaders.current();
if (tempClassLoaders != null) {
ClassLoader temp = tempClassLoaders.get(tccl);
if (temp == null) {
temp = new LoadFirstClassLoader(tccl);
final ClassLoader existing = tempClassLoaders.putIfAbsent(tccl, temp);
if (existing != null) {
temp = existing;
}
}
return temp;
}
return tccl;
}
protected Class<?> findClass(final ClassLoader tccl, final String className)
throws ClassNotFoundException {
try {
return currentClass.equals(className) ? Object.class : Class.forName(className, false, tccl);
} catch (ClassNotFoundException e) {
return Class.forName(className, false, getClass().getClassLoader());
}
}
}
protected boolean shouldTransform(final String className, final ClassLoader loader) {
return !(className == null // framework with bug
|| (loader != null && loader.getClass().getName().equals(DELEGATING_CLASS_LOADER))
|| className.startsWith("sun/reflect")
|| className.startsWith("com/sun/proxy")
|| className.startsWith("org/apache/sirona"));
}
}