/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed 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.glowroot.agent.weaving;
import java.lang.instrument.ClassFileTransformer;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class WeavingClassFileTransformer implements ClassFileTransformer {
private static final Logger logger = LoggerFactory.getLogger(WeavingClassFileTransformer.class);
private final Weaver weaver;
private final boolean weaveBootstrapClassLoader;
// because of the crazy pre-initialization of javaagent classes (see
// org.glowroot.core.weaving.PreInitializeClasses), all inputs into this class should be
// concrete, non-subclassed types so that the correct set of used classes can be computed (see
// calculation in the test class org.glowroot.weaving.preinit.GlobalCollector, and
// hard-coded results in org.glowroot.weaving.PreInitializeWeavingClassesTest)
// note: an exception is made for WeavingTimerService, see PreInitializeWeavingClassesTest for
// explanation
public WeavingClassFileTransformer(Weaver weaver) {
this.weaver = weaver;
// can only weave classes in bootstrap class loader if glowroot is in bootstrap class
// loader, otherwise woven bootstrap classes will generate NoClassDefFoundError since
// the woven code will not be able to see glowroot classes
// (e.g. woven code will not be able to see org.glowroot.agent.plugin.api.Agent)
weaveBootstrapClassLoader = isInBootstrapClassLoader();
}
// From the javadoc on ClassFileTransformer.transform():
// "throwing an exception has the same effect as returning null"
//
// so all exceptions must be caught and logged here or they will be lost
@Override
public byte /*@Nullable*/[] transform(@Nullable ClassLoader loader, @Nullable String className,
@Nullable Class<?> classBeingRedefined, @Nullable ProtectionDomain protectionDomain,
byte[] bytes) {
// internal subclasses of MethodHandle are passed in with null className
// (see integration test MethodHandleWeavingTest for more detail)
// also, more importantly, Java 8 lambdas are passed in with null className, which need to
// be woven by executor plugin
String nonNullClassName = className == null ? "unnamed" : className;
try {
return transformInternal(loader, nonNullClassName, protectionDomain, bytes);
} catch (Throwable t) {
// see method-level comment
logger.error("error weaving {}: {}", nonNullClassName, t.getMessage(), t);
return null;
}
}
private byte /*@Nullable*/[] transformInternal(@Nullable ClassLoader loader, String className,
@Nullable ProtectionDomain protectionDomain, byte[] bytes) {
if (ignoreClass(className)) {
return null;
}
if (loader == null && !weaveBootstrapClassLoader) {
// can only weave classes in bootstrap class loader if glowroot is in bootstrap class
// loader, otherwise woven bootstrap classes will generate NoClassDefFoundError since
// the woven code will not be able to see glowroot classes
// (e.g. woven code will not be able to see org.glowroot.agent.plugin.api.Agent)
return null;
}
CodeSource codeSource = protectionDomain == null ? null : protectionDomain.getCodeSource();
return weaver.weave(bytes, className, codeSource, loader);
}
private static boolean ignoreClass(String className) {
if (isGlowrootAgentClass(className)) {
// don't weave glowroot core classes, including shaded classes like h2 jdbc driver
return true;
}
if (className.startsWith("sun/reflect/Generated")) {
// optimization, no need to try to weave the many classes generated for reflection:
// sun/reflect/GeneratedSerializationConstructorAccessor..
// sun/reflect/GeneratedConstructorAccessor..
// sun/reflect/GeneratedMethodAccessor..
return true;
}
// proxies under JDK 6 start with $Proxy
// proxies under JDK 7+ start with com/sun/proxy/$Proxy
if (className.startsWith("com/sun/proxy/$Proxy") || className.startsWith("$Proxy")) {
// optimization, especially for jdbc plugin to avoid weaving proxy wrappers when dealing
// with connection pools
return true;
}
return false;
}
private static boolean isGlowrootAgentClass(String className) {
if (!className.startsWith("org/glowroot")) {
// optimization for common case
return false;
}
return className.startsWith("org/glowroot/common/")
|| className.startsWith("org/glowroot/wire/api/")
|| className.startsWith("org/glowroot/agent/api/")
|| className.startsWith("org/glowroot/agent/plugin/api/")
|| className.startsWith("org/glowroot/agent/central/")
|| className.startsWith("org/glowroot/agent/collector/")
|| className.startsWith("org/glowroot/agent/config/")
|| className.startsWith("org/glowroot/agent/embedded/init/")
|| className.startsWith("org/glowroot/agent/embedded/repo/")
|| className.startsWith("org/glowroot/agent/embedded/util/")
|| className.startsWith("org/glowroot/agent/impl/")
|| className.startsWith("org/glowroot/agent/init/")
|| className.startsWith("org/glowroot/agent/jul/")
|| className.startsWith("org/glowroot/agent/live/")
|| className.startsWith("org/glowroot/agent/model/")
|| className.startsWith("org/glowroot/agent/util/")
|| className.startsWith("org/glowroot/agent/weaving/");
}
private static boolean isInBootstrapClassLoader() {
return WeavingClassFileTransformer.class.getClassLoader() == null;
}
}