Improve bytebuddy class enhance for retransform classes (#561)

* SWAuxiliaryTypeNamingStrategy
Auxiliary type name pattern: <origin_class_name>$<name_trait>$auxiliary$<auxiliary_type_instance_hash>

* DelegateNamingResolver
Interceptor delegate field name pattern: <name_trait>$delegate$<class_name_hash>$<plugin_define_hash>$<intercept_point_hash>

* SWMethodNameTransformer
Renamed origin method pattern: <name_trait>$original$<method_name>$<method_description_hash>

* SWImplementationContextFactory
Method cache value field pattern: cachedValue$<name_trait>$<origin_class_name_hash>$<field_value_hash>
Accessor method name pattern:  <renamed_origin_method>$accessor$<name_trait>$<origin_class_name_hash>

Here is an example of manipulated enhanced class with new naming policies of auxiliary classes, fields, and methods

```java
 import sample.mybatis.controller.HotelController$sw$auxiliary$19cja42;
 import sample.mybatis.controller.HotelController$sw$auxiliary$p257su0;
 import sample.mybatis.domain.Hotel;
 import sample.mybatis.service.HotelService;

 @RequestMapping(value={"/hotel"})
 @RestController
 public class HotelController
 implements EnhancedInstance {
     @Autowired
     @Lazy
     private HotelService hotelService;
     private volatile Object _$EnhancedClassField_ws;

     // Interceptor delegate fields
     public static volatile /* synthetic */ InstMethodsInter sw$delegate$td03673$ain2do0$8im5jm1;
     public static volatile /* synthetic */ InstMethodsInter sw$delegate$td03673$ain2do0$edkmf61;
     public static volatile /* synthetic */ ConstructorInter sw$delegate$td03673$ain2do0$qs9unv1;
     public static volatile /* synthetic */ InstMethodsInter sw$delegate$td03673$fl4lnk1$m3ia3a2;
     public static volatile /* synthetic */ InstMethodsInter sw$delegate$td03673$fl4lnk1$sufrvp1;
     public static volatile /* synthetic */ ConstructorInter sw$delegate$td03673$fl4lnk1$cteu7s1;

     // Origin method cache value field
     private static final /* synthetic */ Method cachedValue$sw$td03673$g5sobj1;

     public HotelController() {
         this(null);
         sw$delegate$td03673$ain2do0$qs9unv1.intercept(this, new Object[0]);
     }

     private /* synthetic */ HotelController(sw.auxiliary.p257su0 p257su02) {
     }

     @GetMapping(value={"city/{cityId}"})
     public Hotel selectByCityId(@PathVariable(value="cityId") int n) {
         // call interceptor with auxiliary type and parameters and origin method object
         return (Hotel)sw$delegate$td03673$ain2do0$8im5jm1.intercept(this, new Object[]{n}, new HotelController$sw$auxiliary$19cja42(this, n), cachedValue$sw$td03673$g5sobj1);
     }

     // Renamed origin method
     private /* synthetic */ Hotel sw$origin$selectByCityId$a8458p3(int cityId) {
/*22*/         return this.hotelService.selectByCityId(cityId);
     }

     // Accessor of renamed origin method, calling from auxiliary type
     final /* synthetic */ Hotel sw$origin$selectByCityId$a8458p3$accessor$sw$td03673(int n) {
         // Calling renamed origin method
         return this.sw$origin$selectByCityId$a8458p3(n);
     }

     @Override
     public Object getSkyWalkingDynamicField() {
         return this._$EnhancedClassField_ws;
     }

     @Override
     public void setSkyWalkingDynamicField(Object object) {
         this._$EnhancedClassField_ws = object;
     }

     static {
         ClassLoader.getSystemClassLoader().loadClass("org.apache.skywalking.apm.dependencies.net.bytebuddy.dynamic.Nexus").getMethod("initialize", Class.class, Integer.TYPE).invoke(null, HotelController.class, -1072476370);
         // Method object
         cachedValue$sw$td03673$g5sobj1 = HotelController.class.getMethod("selectByCityId", Integer.TYPE);
     }
 }
```

Auxiliary type of Constructor : 
```java
class HotelController$sw$auxiliary$p257su0 {
}
```

Auxiliary type of  `selectByCityId` method: 
```java
class HotelController$sw$auxiliary$19cja42
implements Runnable,
Callable {
    private HotelController argument0;
    private int argument1;

    public Object call() throws Exception {
        return this.argument0.sw$origin$selectByCityId$a8458p3$accessor$sw$td03673(this.argument1);
    }

    @Override
    public void run() {
        this.argument0.sw$origin$selectByCityId$a8458p3$accessor$sw$td03673(this.argument1);
    }

    HotelController$sw$auxiliary$19cja42(HotelController hotelController, int n) {
        this.argument0 = hotelController;
        this.argument1 = n;
    }
}
```
This commit is contained in:
Gong Dewei 2023-06-24 20:50:10 +08:00 committed by GitHub
parent b72e984289
commit 267aba8fad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 2313 additions and 258 deletions

View File

@ -12,6 +12,7 @@ Release Notes.
* Fix the conflict between the logging kernel and the JDK threadpool plugin.
* Fix the thread safety bug of finishing operation for the span named "SpringCloudGateway/sendRequest"
* Fix NPE in guava-eventbus-plugin.
* Support re-transform/hot-swap classes with other java agents, and remove the obsolete cache enhanced class feature.
#### Documentation

View File

@ -25,7 +25,6 @@ import org.apache.skywalking.apm.agent.core.logging.core.LogLevel;
import org.apache.skywalking.apm.agent.core.logging.core.LogOutput;
import org.apache.skywalking.apm.agent.core.logging.core.ResolverType;
import org.apache.skywalking.apm.agent.core.logging.core.WriterFactory;
import org.apache.skywalking.apm.agent.core.plugin.bytebuddy.ClassCacheMode;
import org.apache.skywalking.apm.util.Length;
/**
@ -110,19 +109,6 @@ public class Config {
*/
public static boolean IS_OPEN_DEBUGGING_CLASS = false;
/**
* If true, SkyWalking agent will cache all instrumented classes to memory or disk files (decided by class cache
* mode), allow other javaagent to enhance those classes that enhanced by SkyWalking agent.
*/
public static boolean IS_CACHE_ENHANCED_CLASS = false;
/**
* The instrumented classes cache mode: MEMORY or FILE MEMORY: cache class bytes to memory, if instrumented
* classes is too many or too large, it may take up more memory FILE: cache class bytes in `/class-cache`
* folder, automatically clean up cached class files when the application exits
*/
public static ClassCacheMode CLASS_CACHE_MODE = ClassCacheMode.MEMORY;
/**
* The identifier of the instance
*/

View File

@ -32,4 +32,10 @@ public class Constants {
public static String EVENT_LAYER_NAME = "GENERAL";
public static int NULL_VALUE = 0;
/**
* The name trait for auxiliary type names, field names and method names which generated by ByteBuddy.
*/
public static final String NAME_TRAIT = "sw$";
}

View File

@ -55,6 +55,17 @@ public class AnnotationTypeNameMatch<T extends AnnotationDescription> implements
return target.getAnnotationType().asErasure().getName().equals(annotationTypeName);
}
/**
* To ensure that the hashCode for recreating the XxxInterceptPoint instance is the same as the previous instance,
* each ElementMatcher implementation class needs to implement toString() method.
*/
@Override
public String toString() {
return "AnnotationTypeNameMatch{" +
"annotationTypeName='" + annotationTypeName + '\'' +
'}';
}
/**
* The static method to create {@link AnnotationTypeNameMatch} This is a delegate method to follow byte-buddy {@link
* ElementMatcher}'s code style.

View File

@ -68,6 +68,18 @@ public class ArgumentTypeNameMatch implements ElementMatcher<MethodDescription>
return false;
}
/**
* To ensure that the hashCode for recreating the XxxInterceptPoint instance is the same as the previous instance,
* each ElementMatcher implementation class needs to implement toString() method.
*/
@Override
public String toString() {
return "ArgumentTypeNameMatch{" +
"index=" + index +
", argumentTypeName='" + argumentTypeName + '\'' +
'}';
}
/**
* The static method to create {@link ArgumentTypeNameMatch} This is a delegate method to follow byte-buddy {@link
* ElementMatcher}'s code style.

View File

@ -1,195 +0,0 @@
/*
* 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.skywalking.apm.agent.core.plugin.bytebuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
import net.bytebuddy.utility.RandomString;
import org.apache.skywalking.apm.agent.core.boot.AgentPackageNotFoundException;
import org.apache.skywalking.apm.agent.core.boot.AgentPackagePath;
import org.apache.skywalking.apm.agent.core.logging.api.ILog;
import org.apache.skywalking.apm.agent.core.logging.api.LogManager;
import org.apache.skywalking.apm.agent.core.util.FileUtils;
import org.apache.skywalking.apm.agent.core.util.IOUtils;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Wrapper classFileTransformer of ByteBuddy, save the enhanced bytecode to memory cache or file cache,
* and automatically load the previously generated bytecode during the second retransform,
* to solve the problem that ByteBuddy generates auxiliary classes with different random names every time.
* Allow other javaagent to enhance those classes that enhanced by SkyWalking agent.
*/
public class CacheableTransformerDecorator implements AgentBuilder.TransformerDecorator {
private static final ILog LOGGER = LogManager.getLogger(CacheableTransformerDecorator.class);
private final ClassCacheMode cacheMode;
private ClassCacheResolver cacheResolver;
public CacheableTransformerDecorator(ClassCacheMode cacheMode) throws IOException {
this.cacheMode = cacheMode;
initClassCache();
}
private void initClassCache() throws IOException {
if (this.cacheMode.equals(ClassCacheMode.FILE)) {
String cacheDirBase = null;
try {
cacheDirBase = AgentPackagePath.getPath() + "/class-cache";
} catch (AgentPackageNotFoundException e) {
throw new IOException("Can't find the root path for creating /class-cache folder.");
}
File cacheDir = new File(cacheDirBase + "/class-cache-" + RandomString.make());
if (!cacheDir.exists()) {
cacheDir.mkdirs();
}
if (!cacheDir.exists()) {
throw new IOException("Create class cache dir failure");
}
cacheResolver = new FileCacheResolver(cacheDir);
} else {
cacheResolver = new MemoryCacheResolver();
}
}
@Override
public ResettableClassFileTransformer decorate(ResettableClassFileTransformer classFileTransformer) {
return new ResettableClassFileTransformer.WithDelegation(classFileTransformer) {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
// load from cache
byte[] classCache = cacheResolver.getClassCache(loader, className);
if (classCache != null) {
return classCache;
}
//transform class
classfileBuffer = classFileTransformer.transform(loader, className, classBeingRedefined, protectionDomain, classfileBuffer);
// save to cache
if (classfileBuffer != null) {
cacheResolver.putClassCache(loader, className, classfileBuffer);
}
return classfileBuffer;
}
};
}
private static String getClassLoaderHash(ClassLoader loader) {
String classloader;
if (loader != null) {
classloader = Integer.toHexString(loader.hashCode());
} else {
//classloader is null for BootstrapClassLoader
classloader = "00000000";
}
return classloader;
}
interface ClassCacheResolver {
byte[] getClassCache(ClassLoader loader, String className);
void putClassCache(ClassLoader loader, String className, byte[] classfileBuffer);
}
static class MemoryCacheResolver implements ClassCacheResolver {
// classloaderHashcode@className -> class bytes
private Map<String, byte[]> classCacheMap = new ConcurrentHashMap<String, byte[]>();
@Override
public byte[] getClassCache(ClassLoader loader, String className) {
String cacheKey = getCacheKey(loader, className);
return classCacheMap.get(cacheKey);
}
@Override
public void putClassCache(ClassLoader loader, String className, byte[] classfileBuffer) {
String cacheKey = getCacheKey(loader, className);
classCacheMap.put(cacheKey, classfileBuffer);
}
private String getCacheKey(ClassLoader loader, String className) {
return getClassLoaderHash(loader) + "@" + className;
}
}
static class FileCacheResolver implements ClassCacheResolver {
private final File cacheDir;
FileCacheResolver(File cacheDir) {
this.cacheDir = cacheDir;
//clean cache dir on exit
FileUtils.deleteDirectoryOnExit(cacheDir);
}
@Override
public byte[] getClassCache(ClassLoader loader, String className) {
// load from cache
File cacheFile = getCacheFile(loader, className);
if (cacheFile.exists()) {
FileInputStream fileInputStream = null;
try {
fileInputStream = new FileInputStream(cacheFile);
return IOUtils.toByteArray(fileInputStream);
} catch (IOException e) {
LOGGER.error("load class bytes from cache file failure", e);
} finally {
IOUtils.closeQuietly(fileInputStream);
}
}
return null;
}
@Override
public void putClassCache(ClassLoader loader, String className, byte[] classfileBuffer) {
File cacheFile = getCacheFile(loader, className);
cacheFile.getParentFile().mkdirs();
FileOutputStream output = null;
try {
output = new FileOutputStream(cacheFile);
IOUtils.copy(new ByteArrayInputStream(classfileBuffer), output);
} catch (IOException e) {
LOGGER.error("save class bytes to cache file failure", e);
} finally {
IOUtils.closeQuietly(output);
}
}
private File getCacheFile(ClassLoader loader, String className) {
String filename = getClassLoaderHash(loader) + "/" + className.replace('.', '/') + ".class";
return new File(cacheDir, filename);
}
}
}

View File

@ -53,6 +53,17 @@ public class ReturnTypeNameMatch implements ElementMatcher<MethodDescription> {
return target.getReturnType().asErasure().getName().equals(returnTypeName);
}
/**
* To ensure that the hashCode for recreating the XxxInterceptPoint instance is the same as the previous instance,
* each ElementMatcher implementation class needs to implement toString() method.
*/
@Override
public String toString() {
return "ReturnTypeNameMatch{" +
"returnTypeName='" + returnTypeName + '\'' +
'}';
}
/**
* The static method to create {@link ReturnTypeNameMatch} This is a delegate method to follow byte-buddy {@link
* ElementMatcher}'s code style.

View File

@ -21,6 +21,8 @@ package org.apache.skywalking.apm.agent.core.plugin.interceptor;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
import java.util.Objects;
/**
* One of the three "Intercept Point". "Intercept Point" is a definition about where and how intercept happens. In this
* "Intercept Point", the definition targets class's constructors, and the interceptor.
@ -41,4 +43,13 @@ public interface ConstructorInterceptPoint {
* org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.InstanceConstructorInterceptor}
*/
String getConstructorInterceptor();
/**
* To ensure that the hashCode for recreating the XxxInterceptPoint instance is the same as the previous instance,
* each ElementMatcher implementation class needs to implement toString() method.
* @return hashCode of this intercept point
*/
default int computeHashCode() {
return Objects.hash(this.getClass().getName(), this.getConstructorMatcher().toString(), this.getConstructorInterceptor());
}
}

View File

@ -21,6 +21,8 @@ package org.apache.skywalking.apm.agent.core.plugin.interceptor;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
import java.util.Objects;
/**
* One of the three "Intercept Point". "Intercept Point" is a definition about where and how intercept happens. In this
* "Intercept Point", the definition targets class's instance methods, and the interceptor.
@ -42,4 +44,13 @@ public interface InstanceMethodsInterceptPoint {
String getMethodsInterceptor();
boolean isOverrideArgs();
/**
* To ensure that the hashCode for recreating the XxxInterceptPoint instance is the same as the previous instance,
* each ElementMatcher implementation class needs to implement toString() method.
* @return hashCode of this intercept point
*/
default int computeHashCode() {
return Objects.hash(this.getClass().getName(), this.getMethodsMatcher().toString(), this.getMethodsInterceptor(), this.isOverrideArgs());
}
}

View File

@ -21,6 +21,8 @@ package org.apache.skywalking.apm.agent.core.plugin.interceptor;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
import java.util.Objects;
/**
* One of the three "Intercept Point". "Intercept Point" is a definition about where and how intercept happens. In this
* "Intercept Point", the definition targets class's static methods, and the interceptor.
@ -42,4 +44,13 @@ public interface StaticMethodsInterceptPoint {
String getMethodsInterceptor();
boolean isOverrideArgs();
/**
* To ensure that the hashCode for recreating the XxxInterceptPoint instance is the same as the previous instance,
* each ElementMatcher implementation class needs to implement toString() method.
* @return hashCode of this intercept point
*/
default int computeHashCode() {
return Objects.hash(this.getClass().getName(), this.getMethodsMatcher().toString(), this.getMethodsInterceptor(), this.isOverrideArgs());
}
}

View File

@ -78,6 +78,7 @@ public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePlugi
if (instanceMethodsInterceptPoints != null && instanceMethodsInterceptPoints.length > 0) {
existedMethodsInterceptPoints = true;
}
DelegateNamingResolver delegateNamingResolver = new DelegateNamingResolver(typeDescription.getTypeName(), this);
/**
* nothing need to be enhanced in class instance, maybe need enhance static methods.
@ -121,7 +122,7 @@ public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePlugi
newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher())
.intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration()
.to(new ConstructorInter(constructorInterceptPoint
.getConstructorInterceptor(), classLoader))));
.getConstructorInterceptor(), classLoader), delegateNamingResolver.resolve(constructorInterceptPoint))));
}
}
}
@ -149,7 +150,7 @@ public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePlugi
newClassBuilder = newClassBuilder.method(junction)
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(OverrideCallable.class))
.to(new InstMethodsInterWithOverrideArgs(interceptor, classLoader)));
.to(new InstMethodsInterWithOverrideArgs(interceptor, classLoader), delegateNamingResolver.resolve(instanceMethodsInterceptPoint)));
}
} else {
if (isBootstrapInstrumentation()) {
@ -159,7 +160,7 @@ public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePlugi
} else {
newClassBuilder = newClassBuilder.method(junction)
.intercept(MethodDelegation.withDefaultConfiguration()
.to(new InstMethodsInter(interceptor, classLoader)));
.to(new InstMethodsInter(interceptor, classLoader), delegateNamingResolver.resolve(instanceMethodsInterceptPoint)));
}
}
}
@ -183,6 +184,7 @@ public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePlugi
if (staticMethodsInterceptPoints == null || staticMethodsInterceptPoints.length == 0) {
return newClassBuilder;
}
DelegateNamingResolver delegateNamingResolver = new DelegateNamingResolver(typeDescription.getTypeName(), this);
for (StaticMethodsInterceptPoint staticMethodsInterceptPoint : staticMethodsInterceptPoints) {
String interceptor = staticMethodsInterceptPoint.getMethodsInterceptor();
@ -200,7 +202,7 @@ public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePlugi
newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher()))
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(OverrideCallable.class))
.to(new StaticMethodsInterWithOverrideArgs(interceptor)));
.to(new StaticMethodsInterWithOverrideArgs(interceptor), delegateNamingResolver.resolve(staticMethodsInterceptPoint)));
}
} else {
if (isBootstrapInstrumentation()) {
@ -210,7 +212,7 @@ public abstract class ClassEnhancePluginDefine extends AbstractClassEnhancePlugi
} else {
newClassBuilder = newClassBuilder.method(isStatic().and(staticMethodsInterceptPoint.getMethodsMatcher()))
.intercept(MethodDelegation.withDefaultConfiguration()
.to(new StaticMethodsInter(interceptor)));
.to(new StaticMethodsInter(interceptor), delegateNamingResolver.resolve(staticMethodsInterceptPoint)));
}
}

View File

@ -0,0 +1,75 @@
/*
* 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.skywalking.apm.agent.core.plugin.interceptor.enhance;
import net.bytebuddy.utility.RandomString;
import org.apache.skywalking.apm.agent.core.conf.Constants;
import org.apache.skywalking.apm.agent.core.plugin.AbstractClassEnhancePluginDefine;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.StaticMethodsInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.ConstructorInterceptV2Point;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.InstanceMethodsInterceptV2Point;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.StaticMethodsInterceptV2Point;
import java.util.Objects;
/**
* Generate fixed delegate field name for MethodDelegation
*/
public class DelegateNamingResolver {
private static final String PREFIX = "delegate$";
private final String fieldNamePrefix;
public DelegateNamingResolver(String className, AbstractClassEnhancePluginDefine pluginDefine) {
// Interceptor delegate field name pattern: <name_trait>$delegate$<class_name_hash>$<plugin_define_hash>$<intercept_point_hash>
// something like: InstMethodsInter sw$delegate$td03673$sib0lj0$5n874b1;
this.fieldNamePrefix = Constants.NAME_TRAIT + PREFIX + RandomString.hashOf(className.hashCode()) + "$" + RandomString.hashOf(pluginDefine.hashCode()) + "$";
}
public String resolve(ConstructorInterceptPoint interceptPoint) {
Objects.requireNonNull(interceptPoint, "interceptPoint cannot be null");
return fieldNamePrefix + RandomString.hashOf(interceptPoint.computeHashCode());
}
public String resolve(ConstructorInterceptV2Point interceptPoint) {
Objects.requireNonNull(interceptPoint, "interceptPoint cannot be null");
return fieldNamePrefix + RandomString.hashOf(interceptPoint.computeHashCode());
}
public String resolve(InstanceMethodsInterceptPoint interceptPoint) {
Objects.requireNonNull(interceptPoint, "interceptPoint cannot be null");
return fieldNamePrefix + RandomString.hashOf(interceptPoint.computeHashCode());
}
public String resolve(InstanceMethodsInterceptV2Point interceptPoint) {
Objects.requireNonNull(interceptPoint, "interceptPoint cannot be null");
return fieldNamePrefix + RandomString.hashOf(interceptPoint.computeHashCode());
}
public String resolve(StaticMethodsInterceptPoint interceptPoint) {
Objects.requireNonNull(interceptPoint, "interceptPoint cannot be null");
return fieldNamePrefix + RandomString.hashOf(interceptPoint.computeHashCode());
}
public String resolve(StaticMethodsInterceptV2Point interceptPoint) {
Objects.requireNonNull(interceptPoint, "interceptPoint cannot be null");
return fieldNamePrefix + RandomString.hashOf(interceptPoint.computeHashCode());
}
}

View File

@ -35,6 +35,7 @@ import org.apache.skywalking.apm.agent.core.plugin.interceptor.EnhanceException;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.StaticMethodsInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.ConstructorInter;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.DelegateNamingResolver;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.EnhancedInstance;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.OverrideCallable;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.v2.ConstructorInterceptV2Point;
@ -65,6 +66,7 @@ public abstract class ClassEnhancePluginDefineV2 extends AbstractClassEnhancePlu
if (staticMethodsInterceptV2Points == null || staticMethodsInterceptV2Points.length == 0) {
return newClassBuilder;
}
DelegateNamingResolver delegateNamingResolver = new DelegateNamingResolver(typeDescription.getTypeName(), this);
for (StaticMethodsInterceptV2Point staticMethodsInterceptV2Point : staticMethodsInterceptV2Points) {
String interceptor = staticMethodsInterceptV2Point.getMethodsInterceptorV2();
@ -85,7 +87,7 @@ public abstract class ClassEnhancePluginDefineV2 extends AbstractClassEnhancePlu
isStatic().and(staticMethodsInterceptV2Point.getMethodsMatcher()))
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(OverrideCallable.class))
.to(new StaticMethodsInterV2WithOverrideArgs(interceptor)));
.to(new StaticMethodsInterV2WithOverrideArgs(interceptor), delegateNamingResolver.resolve(staticMethodsInterceptV2Point)));
}
} else {
if (isBootstrapInstrumentation()) {
@ -97,7 +99,7 @@ public abstract class ClassEnhancePluginDefineV2 extends AbstractClassEnhancePlu
newClassBuilder = newClassBuilder.method(
isStatic().and(staticMethodsInterceptV2Point.getMethodsMatcher()))
.intercept(MethodDelegation.withDefaultConfiguration()
.to(new StaticMethodsInterV2(interceptor)));
.to(new StaticMethodsInterV2(interceptor), delegateNamingResolver.resolve(staticMethodsInterceptV2Point)));
}
}
@ -113,6 +115,7 @@ public abstract class ClassEnhancePluginDefineV2 extends AbstractClassEnhancePlu
ConstructorInterceptPoint[] constructorInterceptPoints = getConstructorsInterceptPoints();
InstanceMethodsInterceptV2Point[] instanceMethodsInterceptV2Points = getInstanceMethodsInterceptV2Points();
String enhanceOriginClassName = typeDescription.getTypeName();
DelegateNamingResolver fieldNamingResolver = new DelegateNamingResolver(typeDescription.getTypeName(), this);
boolean existedConstructorInterceptPoint = false;
if (constructorInterceptPoints != null && constructorInterceptPoints.length > 0) {
@ -149,7 +152,7 @@ public abstract class ClassEnhancePluginDefineV2 extends AbstractClassEnhancePlu
newClassBuilder = newClassBuilder.constructor(constructorInterceptPoint.getConstructorMatcher())
.intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.withDefaultConfiguration()
.to(new ConstructorInter(constructorInterceptPoint
.getConstructorInterceptor(), classLoader))));
.getConstructorInterceptor(), classLoader), fieldNamingResolver.resolve(constructorInterceptPoint))));
}
}
}
@ -176,7 +179,7 @@ public abstract class ClassEnhancePluginDefineV2 extends AbstractClassEnhancePlu
newClassBuilder = newClassBuilder.method(junction)
.intercept(MethodDelegation.withDefaultConfiguration()
.withBinders(Morph.Binder.install(OverrideCallable.class))
.to(new InstMethodsInterV2WithOverrideArgs(interceptor, classLoader)));
.to(new InstMethodsInterV2WithOverrideArgs(interceptor, classLoader), fieldNamingResolver.resolve(instanceMethodsInterceptV2Point)));
}
} else {
if (isBootstrapInstrumentation()) {
@ -186,7 +189,7 @@ public abstract class ClassEnhancePluginDefineV2 extends AbstractClassEnhancePlu
} else {
newClassBuilder = newClassBuilder.method(junction)
.intercept(MethodDelegation.withDefaultConfiguration()
.to(new InstMethodsInterV2(interceptor, classLoader)));
.to(new InstMethodsInterV2(interceptor, classLoader), fieldNamingResolver.resolve(instanceMethodsInterceptV2Point)));
}
}
}

View File

@ -20,6 +20,8 @@ package org.apache.skywalking.apm.agent.core.plugin.interceptor.v2;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
import java.util.Objects;
public interface ConstructorInterceptV2Point {
/**
@ -35,4 +37,13 @@ public interface ConstructorInterceptV2Point {
*/
String getConstructorInterceptorV2();
/**
* To ensure that the hashCode for recreating the XxxInterceptPoint instance is the same as the previous instance,
* each ElementMatcher implementation class needs to implement toString() method.
* @return hashCode of this intercept point
*/
default int computeHashCode() {
return Objects.hash(this.getClass().getName(), this.getConstructorMatcher().toString(), this.getConstructorInterceptorV2());
}
}

View File

@ -22,6 +22,8 @@ import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
import java.util.Objects;
/**
* One of the three "Intercept Point". "Intercept Point" is a definition about where and how intercept happens. In this
* "Intercept Point", the definition targets class's instance methods, and the interceptor.
@ -43,4 +45,13 @@ public interface InstanceMethodsInterceptV2Point {
String getMethodsInterceptorV2();
boolean isOverrideArgs();
/**
* To ensure that the hashCode for recreating the XxxInterceptPoint instance is the same as the previous instance,
* each ElementMatcher implementation class needs to implement toString() method.
* @return hashCode of this intercept point
*/
default int computeHashCode() {
return Objects.hash(this.getClass().getName(), this.getMethodsMatcher().toString(), this.getMethodsInterceptorV2(), this.isOverrideArgs());
}
}

View File

@ -22,6 +22,8 @@ import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.ConstructorInterceptPoint;
import java.util.Objects;
/**
* One of the three "Intercept Point". "Intercept Point" is a definition about where and how intercept happens. In this
* "Intercept Point", the definition targets class's static methods, and the interceptor.
@ -43,4 +45,13 @@ public interface StaticMethodsInterceptV2Point {
String getMethodsInterceptorV2();
boolean isOverrideArgs();
/**
* To ensure that the hashCode for recreating the XxxInterceptPoint instance is the same as the previous instance,
* each ElementMatcher implementation class needs to implement toString() method.
* @return hashCode of this intercept point
*/
default int computeHashCode() {
return Objects.hash(this.getClass().getName(), this.getMethodsMatcher().toString(), this.getMethodsInterceptorV2(), this.isOverrideArgs());
}
}

View File

@ -97,4 +97,9 @@ public class MethodInheritanceAnnotationMatcher<T extends MethodDescription> ext
ElementMatcher<? super TypeDescription> matcher) {
return new MethodInheritanceAnnotationMatcher(new CollectionItemMatcher<>(annotationType(matcher)));
}
@Override
public String toString() {
return "MethodInheritanceAnnotationMatcher(" + matcher + ')';
}
}

View File

@ -52,4 +52,9 @@ public class ProtectiveShieldMatcher<T> extends ElementMatcher.Junction.Abstract
return false;
}
}
@Override
public String toString() {
return "ProtectiveShieldMatcher(" + matcher + ')';
}
}

View File

@ -0,0 +1,106 @@
/*
* 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.skywalking.apm.agent.core.bytebuddy;
import com.google.common.collect.ImmutableSet;
import com.google.common.reflect.ClassPath;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
/**
* Scan the classes, and notify the listener(s)
*/
public class ClassScan {
private final List<ListenerCache> listeners;
public ClassScan() {
this.listeners = new LinkedList<>();
}
/**
* Register the callback listener
*
* @param listener to be called after class found
*/
public void registerListener(ClassScanListener listener) {
listeners.add(new ListenerCache(listener));
}
/**
* Begin to scan classes.
*/
public void scan() throws Exception {
ClassPath classpath = ClassPath.from(this.getClass().getClassLoader());
ImmutableSet<ClassPath.ClassInfo> classes = classpath.getAllClasses();
String packagePrefix = "org.apache.skywalking.";
for (ClassPath.ClassInfo classInfo : classes) {
if (!classInfo.getName().startsWith(packagePrefix)) {
continue;
}
Class<?> aClass = classInfo.load();
for (ListenerCache listener : listeners) {
if (listener.classMatch().matches(TypeDescription.ForLoadedType.of(aClass))) {
listener.addMatch(aClass);
}
}
}
for (ListenerCache listener : listeners) {
listener.complete();
}
}
public interface ClassScanListener {
ElementMatcher<TypeDescription> classMatch();
void notify(Class aClass) throws Exception;
}
private class ListenerCache {
private ClassScanListener listener;
private List<Class<?>> matchedClass;
private ListenerCache(ClassScanListener listener) {
this.listener = listener;
matchedClass = new LinkedList<>();
}
private ElementMatcher<TypeDescription> classMatch() {
return this.listener.classMatch();
}
private void addMatch(Class aClass) {
matchedClass.add(aClass);
}
private void complete() throws Exception {
matchedClass.sort(Comparator.comparing(Class::getName));
for (Class<?> aClass : matchedClass) {
listener.notify(aClass);
}
}
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.skywalking.apm.agent.core.bytebuddy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.InstanceMethodsInterceptPoint;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.DelegateNamingResolver;
import org.junit.Assert;
import org.junit.Test;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import static net.bytebuddy.matcher.ElementMatchers.hasSuperType;
import static net.bytebuddy.matcher.ElementMatchers.isAbstract;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.not;
/**
* Verify subclass of ElementMatcher make sure to impl 'toString()' method.
* see {@link InstanceMethodsInterceptPoint#computeHashCode()}, {@link DelegateNamingResolver}
*/
public class ElementMatcherSubclassVerifyTest {
// ignore nest subclass inside PluginFinder
private final ElementMatcher<TypeDescription> ignoredClass = nameStartsWith("org.apache.skywalking.apm.agent.core.plugin.PluginFinder$");
@Test
public void test() throws Exception {
List<Class<?>> matchedCLasses = new ArrayList<>();
ClassScan classScan = new ClassScan();
classScan.registerListener(new ClassScan.ClassScanListener() {
@Override
public ElementMatcher<TypeDescription> classMatch() {
return hasSuperType(named(ElementMatcher.class.getName())).and(not(isAbstract()));
}
@Override
public void notify(Class aClass) throws Exception {
if (ignoredClass.matches(TypeDescription.ForLoadedType.of(aClass))) {
return;
}
try {
// make sure subclass of ElementMatcher has implement "toString" method
Method toStringMethod = aClass.getDeclaredMethod("toString");
} catch (NoSuchMethodException e) {
matchedCLasses.add(aClass);
}
}
});
classScan.scan();
// print matched classes
PrintStream err = System.err;
for (Class<?> aClass : matchedCLasses) {
String className = aClass.getName();
if (!className.equals(BadMatcher.class.getName())) {
err.println("Requiring toString() method for subclass of ElementMatcher: " + className);
}
}
Assert.assertEquals(1, matchedCLasses.size());
}
public static class BadMatcher<T> implements ElementMatcher<T> {
@Override
public boolean matches(T target) {
return false;
}
}
}

View File

@ -47,6 +47,11 @@
<artifactId>apm-agent-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>bytebuddy-patch</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<build>

View File

@ -25,13 +25,18 @@ import java.util.List;
import java.util.Map;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.SWAgentBuilderDefault;
import net.bytebuddy.agent.builder.SWNativeMethodStrategy;
import net.bytebuddy.description.NamedElement;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.DynamicType;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import org.apache.skywalking.apm.agent.bytebuddy.SWAuxiliaryTypeNamingStrategy;
import net.bytebuddy.implementation.SWImplementationContextFactory;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import org.apache.skywalking.apm.agent.bytebuddy.SWMethodNameTransformer;
import org.apache.skywalking.apm.agent.core.boot.AgentPackageNotFoundException;
import org.apache.skywalking.apm.agent.core.boot.ServiceManager;
import org.apache.skywalking.apm.agent.core.conf.Config;
@ -46,12 +51,13 @@ import org.apache.skywalking.apm.agent.core.plugin.PluginBootstrap;
import org.apache.skywalking.apm.agent.core.plugin.PluginException;
import org.apache.skywalking.apm.agent.core.plugin.PluginFinder;
import org.apache.skywalking.apm.agent.core.plugin.bootstrap.BootstrapInstrumentBoost;
import org.apache.skywalking.apm.agent.core.plugin.bytebuddy.CacheableTransformerDecorator;
import org.apache.skywalking.apm.agent.core.plugin.interceptor.enhance.DelegateNamingResolver;
import org.apache.skywalking.apm.agent.core.plugin.jdk9module.JDK9ModuleExporter;
import static net.bytebuddy.matcher.ElementMatchers.nameContains;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.not;
import static org.apache.skywalking.apm.agent.core.conf.Constants.NAME_TRAIT;
/**
* The main entrance of sky-walking agent, based on javaagent mechanism.
@ -69,7 +75,7 @@ public class SkyWalkingAgent {
} catch (Exception e) {
// try to resolve a new logger, and use the new logger to write the error log here
LogManager.getLogger(SkyWalkingAgent.class)
.error(e, "SkyWalking agent initialized failure. Shutting down.");
.error(e, "SkyWalking agent initialized failure. Shutting down.");
return;
} finally {
// refresh logger again after initialization finishes
@ -91,9 +97,9 @@ public class SkyWalkingAgent {
return;
}
final ByteBuddy byteBuddy = new ByteBuddy().with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS));
LOGGER.info("Skywalking agent begin to install transformer ...");
AgentBuilder agentBuilder = new AgentBuilder.Default(byteBuddy).ignore(
AgentBuilder agentBuilder = newAgentBuilder().ignore(
nameStartsWith("net.bytebuddy.")
.or(nameStartsWith("org.slf4j."))
.or(nameStartsWith("org.groovy."))
@ -119,15 +125,6 @@ public class SkyWalkingAgent {
return;
}
if (Config.Agent.IS_CACHE_ENHANCED_CLASS) {
try {
agentBuilder = agentBuilder.with(new CacheableTransformerDecorator(Config.Agent.CLASS_CACHE_MODE));
LOGGER.info("SkyWalking agent class cache [{}] activated.", Config.Agent.CLASS_CACHE_MODE);
} catch (Exception e) {
LOGGER.error(e, "SkyWalking agent can't active class cache.");
}
}
agentBuilder.type(pluginFinder.buildMatch())
.transform(new Transformer(pluginFinder))
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
@ -137,6 +134,8 @@ public class SkyWalkingAgent {
PluginFinder.pluginInitCompleted();
LOGGER.info("Skywalking agent transformer has installed.");
try {
ServiceManager.INSTANCE.boot();
} catch (Exception e) {
@ -147,6 +146,21 @@ public class SkyWalkingAgent {
.addShutdownHook(new Thread(ServiceManager.INSTANCE::shutdown, "skywalking service shutdown thread"));
}
/**
* Create a new agent builder through customized {@link ByteBuddy} powered by
* {@link SWAuxiliaryTypeNamingStrategy} {@link DelegateNamingResolver} {@link SWMethodNameTransformer} and {@link SWImplementationContextFactory}
*/
private static AgentBuilder newAgentBuilder() {
final ByteBuddy byteBuddy = new ByteBuddy()
.with(TypeValidation.of(Config.Agent.IS_OPEN_DEBUGGING_CLASS))
.with(new SWAuxiliaryTypeNamingStrategy(NAME_TRAIT))
.with(new SWImplementationContextFactory(NAME_TRAIT));
SWNativeMethodStrategy nativeMethodStrategy = new SWNativeMethodStrategy(NAME_TRAIT);
return new SWAgentBuilderDefault(byteBuddy, nativeMethodStrategy)
.with(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST);
}
private static class Transformer implements AgentBuilder.Transformer {
private PluginFinder pluginFinder;

View File

@ -35,6 +35,8 @@
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<finagle.version>6.34.0</finagle.version>
<scala.library.version>2.11.12</scala.library.version>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
~
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.apache.skywalking</groupId>
<artifactId>java-agent-sniffer</artifactId>
<version>8.17.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>bytebuddy-patch</artifactId>
<dependencies>
<dependency>
<groupId>org.apache.skywalking</groupId>
<artifactId>apm-agent-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>5.0.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ow2.asm</groupId>
<artifactId>asm-util</artifactId>
<version>5.0.3</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,84 @@
/*
* 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 net.bytebuddy.agent.builder;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.NamingStrategy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.matcher.ElementMatchers;
import java.util.Collections;
import java.util.List;
import static net.bytebuddy.matcher.ElementMatchers.any;
import static net.bytebuddy.matcher.ElementMatchers.isBootstrapClassLoader;
import static net.bytebuddy.matcher.ElementMatchers.isExtensionClassLoader;
import static net.bytebuddy.matcher.ElementMatchers.isSynthetic;
import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
import static net.bytebuddy.matcher.ElementMatchers.not;
/**
* A custom AgentBuilder.Default for changing NativeMethodStrategy
*/
public class SWAgentBuilderDefault extends AgentBuilder.Default {
/**
* The default circularity lock that assures that no agent created by any agent builder within this
* class loader causes a class loading circularity.
*/
private static final CircularityLock DEFAULT_LOCK = new CircularityLock.Default();
public SWAgentBuilderDefault(ByteBuddy byteBuddy, NativeMethodStrategy nativeMethodStrategy) {
this(byteBuddy,
Listener.NoOp.INSTANCE,
DEFAULT_LOCK,
PoolStrategy.Default.FAST,
TypeStrategy.Default.REBASE,
LocationStrategy.ForClassLoader.STRONG,
ClassFileLocator.NoOp.INSTANCE,
nativeMethodStrategy,
WarmupStrategy.NoOp.INSTANCE,
TransformerDecorator.NoOp.INSTANCE,
new InitializationStrategy.SelfInjection.Split(),
RedefinitionStrategy.DISABLED,
RedefinitionStrategy.DiscoveryStrategy.SinglePass.INSTANCE,
RedefinitionStrategy.BatchAllocator.ForTotal.INSTANCE,
RedefinitionStrategy.Listener.NoOp.INSTANCE,
RedefinitionStrategy.ResubmissionStrategy.Disabled.INSTANCE,
InjectionStrategy.UsingReflection.INSTANCE,
LambdaInstrumentationStrategy.DISABLED,
DescriptionStrategy.Default.HYBRID,
FallbackStrategy.ByThrowableType.ofOptionalTypes(),
ClassFileBufferStrategy.Default.RETAINING,
InstallationListener.NoOp.INSTANCE,
new RawMatcher.Disjunction(
new RawMatcher.ForElementMatchers(any(), isBootstrapClassLoader().or(isExtensionClassLoader())),
new RawMatcher.ForElementMatchers(nameStartsWith("net.bytebuddy.")
.and(not(ElementMatchers.nameStartsWith(NamingStrategy.BYTE_BUDDY_RENAME_PACKAGE + ".")))
.or(nameStartsWith("sun.reflect.").or(nameStartsWith("jdk.internal.reflect.")))
.<TypeDescription>or(isSynthetic()))),
Collections.<Transformation>emptyList());
}
protected SWAgentBuilderDefault(ByteBuddy byteBuddy, Listener listener, CircularityLock circularityLock, PoolStrategy poolStrategy, TypeStrategy typeStrategy, LocationStrategy locationStrategy, ClassFileLocator classFileLocator, NativeMethodStrategy nativeMethodStrategy, WarmupStrategy warmupStrategy, TransformerDecorator transformerDecorator, InitializationStrategy initializationStrategy, RedefinitionStrategy redefinitionStrategy, RedefinitionStrategy.DiscoveryStrategy redefinitionDiscoveryStrategy, RedefinitionStrategy.BatchAllocator redefinitionBatchAllocator, RedefinitionStrategy.Listener redefinitionListener, RedefinitionStrategy.ResubmissionStrategy redefinitionResubmissionStrategy, InjectionStrategy injectionStrategy, LambdaInstrumentationStrategy lambdaInstrumentationStrategy, DescriptionStrategy descriptionStrategy, FallbackStrategy fallbackStrategy, ClassFileBufferStrategy classFileBufferStrategy, InstallationListener installationListener, RawMatcher ignoreMatcher, List<Transformation> transformations) {
super(byteBuddy, listener, circularityLock, poolStrategy, typeStrategy, locationStrategy, classFileLocator, nativeMethodStrategy, warmupStrategy, transformerDecorator, initializationStrategy, redefinitionStrategy, redefinitionDiscoveryStrategy, redefinitionBatchAllocator, redefinitionListener, redefinitionResubmissionStrategy, injectionStrategy, lambdaInstrumentationStrategy, descriptionStrategy, fallbackStrategy, classFileBufferStrategy, installationListener, ignoreMatcher, transformations);
}
}

View File

@ -0,0 +1,43 @@
/*
* 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 net.bytebuddy.agent.builder;
import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
import org.apache.skywalking.apm.agent.bytebuddy.SWMethodNameTransformer;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
public class SWNativeMethodStrategy implements AgentBuilder.Default.NativeMethodStrategy {
private String nameTrait;
public SWNativeMethodStrategy(String nameTrait) {
this.nameTrait = nameTrait;
}
@Override
public MethodNameTransformer resolve() {
return new SWMethodNameTransformer(nameTrait);
}
@Override
public void apply(Instrumentation instrumentation, ClassFileTransformer classFileTransformer) {
}
}

View File

@ -0,0 +1,61 @@
/*
* 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 net.bytebuddy.implementation;
import net.bytebuddy.ClassFileVersion;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.scaffold.TypeInitializer;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
import net.bytebuddy.utility.RandomString;
/**
* Support custom suffix name trait, using in cache value field name, field getter/setter delegation, accessor method and so on.
*/
public class SWImplementationContextFactory implements Implementation.Context.Factory {
private String suffixNameTrait;
public SWImplementationContextFactory(String suffixNameTrait) {
this.suffixNameTrait = suffixNameTrait;
}
@Override
public Implementation.Context.ExtractableView make(TypeDescription instrumentedType,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
TypeInitializer typeInitializer,
ClassFileVersion classFileVersion,
ClassFileVersion auxiliaryClassFileVersion) {
return this.make(instrumentedType, auxiliaryTypeNamingStrategy, typeInitializer, classFileVersion,
auxiliaryClassFileVersion, Implementation.Context.FrameGeneration.GENERATE);
}
@Override
public Implementation.Context.ExtractableView make(TypeDescription instrumentedType,
AuxiliaryType.NamingStrategy auxiliaryTypeNamingStrategy,
TypeInitializer typeInitializer,
ClassFileVersion classFileVersion,
ClassFileVersion auxiliaryClassFileVersion,
Implementation.Context.FrameGeneration frameGeneration) {
// Method cache value field pattern: cachedValue$<name_trait>$<origin_class_name_hash>$<field_value_hash>
// Accessor method name pattern: <renamed_origin_method>$accessor$<name_trait>$<origin_class_name_hash>
return new Implementation.Context.Default(instrumentedType, classFileVersion, auxiliaryTypeNamingStrategy,
typeInitializer, auxiliaryClassFileVersion, frameGeneration,
suffixNameTrait + RandomString.hashOf(instrumentedType.getName().hashCode()));
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.skywalking.apm.agent.bytebuddy;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.auxiliary.AuxiliaryType;
import net.bytebuddy.utility.RandomString;
/**
* Generate predicated auxiliary type name for delegate method.
*/
public class SWAuxiliaryTypeNamingStrategy implements AuxiliaryType.NamingStrategy {
private static final String DEFAULT_SUFFIX = "auxiliary$";
private String suffix;
public SWAuxiliaryTypeNamingStrategy(String nameTrait) {
this.suffix = nameTrait + DEFAULT_SUFFIX;
}
@Override
public String name(TypeDescription instrumentedType, AuxiliaryType auxiliaryType) {
// Auxiliary type name pattern: <origin_class_name>$<name_trait>$auxiliary$<auxiliary_type_instance_hash>
return instrumentedType.getName() + "$" + suffix + RandomString.hashOf(auxiliaryType.hashCode());
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.skywalking.apm.agent.bytebuddy;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.dynamic.scaffold.inline.MethodNameTransformer;
import net.bytebuddy.utility.RandomString;
/**
* Generate fixed origin method name with method description hash code
*/
public class SWMethodNameTransformer implements MethodNameTransformer {
private static final String DEFAULT_PREFIX = "original$";
private String prefix;
public SWMethodNameTransformer(String nameTrait) {
this.prefix = nameTrait + DEFAULT_PREFIX;
}
@Override
public String transform(MethodDescription methodDescription) {
// Origin method rename pattern: <name_trait>$original$<method_name>$<method_description_hash>
return prefix + methodDescription.getInternalName() + "$" + RandomString.hashOf(methodDescription.toString().hashCode());
}
}

View File

@ -0,0 +1,54 @@
/*
* 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.skywalking.apm.agent.bytebuddy;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.This;
import java.util.Arrays;
/**
* The actual byte-buddy's interceptor to intercept constructor methods. In this class, it provides a bridge between
* byte-buddy and sky-walking plugin.
*/
public class ConstructorInter {
private String interceptorClassName;
private ClassLoader classLoader;
/**
* @param interceptorClassName class full name.
*/
public ConstructorInter(String interceptorClassName, ClassLoader classLoader) {
this.interceptorClassName = interceptorClassName;
this.classLoader = classLoader;
}
/**
* Intercept the target constructor.
*
* @param obj target class instance.
* @param allArguments all constructor arguments
*/
@RuntimeType
public void intercept(@This Object obj, @AllArguments Object[] allArguments) {
EnhanceHelper.addInterceptor(interceptorClassName);
Log.info(String.format("ConstructorInterceptorClass: %s, target: %s, args: %s", interceptorClassName, obj, Arrays.asList(allArguments)));
}
}

View File

@ -0,0 +1,76 @@
/*
* 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.skywalking.apm.agent.bytebuddy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class EnhanceHelper {
private static final List<String> INTERCEPTORS = new ArrayList<>();
private static final List<Map.Entry<String, Throwable>> ERRORS = new ArrayList<>();
public static void onError(String message, Throwable error) {
ERRORS.add(new MapEntry<>(message, error));
}
public static void addInterceptor(String interceptor) {
INTERCEPTORS.add(interceptor);
}
public static List<String> getInterceptors() {
return INTERCEPTORS;
}
public static List<Map.Entry<String, Throwable>> getErrors() {
return ERRORS;
}
public static void clear() {
ERRORS.clear();
INTERCEPTORS.clear();
}
private static class MapEntry<T, P> implements Map.Entry<T, P> {
private final T key;
private P value;
public MapEntry(T key, P value) {
this.key = key;
this.value = value;
}
@Override
public T getKey() {
return key;
}
@Override
public P getValue() {
return value;
}
@Override
public P setValue(P value) {
this.value = value;
return value;
}
}
}

View File

@ -0,0 +1,63 @@
/*
* 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.skywalking.apm.agent.bytebuddy;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;
import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.SuperCall;
import net.bytebuddy.implementation.bind.annotation.This;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.concurrent.Callable;
public class InstMethodsInter {
private String interceptorClassName;
private ClassLoader classLoader;
public InstMethodsInter(String interceptorClassName, ClassLoader classLoader) {
this.interceptorClassName = interceptorClassName;
this.classLoader = classLoader;
}
@RuntimeType
public Object intercept(@This Object obj, @AllArguments Object[] allArguments, @SuperCall Callable<?> zuper,
@Origin Method method) throws Throwable {
EnhanceHelper.addInterceptor(interceptorClassName);
Object originResult = zuper.call();
Object finalResult;
if (originResult instanceof String) {
String result = (String) originResult;
result = result.replaceAll("Joe", "John");
finalResult = result;
} else if (originResult instanceof Integer) {
Integer result = (Integer) originResult;
finalResult = result + 1;
} else {
finalResult = originResult;
}
Log.info(String.format("InstMethodInterceptorClass: %s, target: %s, args: %s, SuperCall: %s, method: %s, originResult: %s, finalResult: %s",
interceptorClassName, obj, Arrays.asList(allArguments), zuper, method, originResult, finalResult));
return finalResult;
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.skywalking.apm.agent.bytebuddy;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
public class Log {
private static ByteArrayOutputStream OUTPUT;
private static ByteArrayOutputStream ERR_OUTPUT;
private static PrintStream PRINT;
private static PrintStream ERROR_PRINT;
public static void info(String msg, Object... args) {
msg = formatLog(msg, args);
PRINT.println(msg);
}
public static void error(String msg, Object... args) {
msg = formatLog(msg, args);
ERROR_PRINT.println(msg);
}
private static String formatLog(String msg, Object[] args) {
msg = msg.replaceAll("\\{}", "%s");
msg = String.format(msg, args);
return msg;
}
public static void clear() {
OUTPUT = new ByteArrayOutputStream(128);
ERR_OUTPUT = new ByteArrayOutputStream(128);
PRINT = new PrintStream(OUTPUT, true);
ERROR_PRINT = new PrintStream(ERR_OUTPUT, true);
}
public static void printToConsole() {
PrintStream out = System.out;
PrintStream err = System.err;
out.println(OUTPUT.toString());
err.println(ERR_OUTPUT.toString());
}
}

View File

@ -0,0 +1,120 @@
/*
* 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.skywalking.apm.agent.bytebuddy;
import net.bytebuddy.asm.AsmVisitorWrapper;
import net.bytebuddy.description.field.FieldDescription;
import net.bytebuddy.description.field.FieldList;
import net.bytebuddy.description.method.MethodList;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.implementation.Implementation;
import net.bytebuddy.jar.asm.ClassVisitor;
import net.bytebuddy.jar.asm.FieldVisitor;
import net.bytebuddy.jar.asm.MethodVisitor;
import net.bytebuddy.jar.asm.Opcodes;
import net.bytebuddy.pool.TypePool;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Remove duplicated fields of instrumentedType
*/
public class SWAsmVisitorWrapper implements AsmVisitorWrapper {
private String nameTrait = "$";
public SWAsmVisitorWrapper() {
}
@Override
public int mergeWriter(int flags) {
return flags;
}
@Override
public int mergeReader(int flags) {
return flags;
}
@Override
public ClassVisitor wrap(TypeDescription instrumentedType, ClassVisitor classVisitor, Implementation.Context implementationContext,
TypePool typePool, FieldList<FieldDescription.InDefinedShape> fields, MethodList<?> methods,
int writerFlags, int readerFlags) {
if (classVisitor instanceof RemoveDuplicatedElementsVisitor) {
return classVisitor;
}
return new RemoveDuplicatedElementsVisitor(Opcodes.ASM8, classVisitor);
}
class RemoveDuplicatedElementsVisitor extends ClassVisitor {
private Map<String, Map<String, String>> fieldCache = new ConcurrentHashMap<>();
private Map<String, Map<String, List<String>>> methodCache = new ConcurrentHashMap<>();
private String className;
public RemoveDuplicatedElementsVisitor(int api, ClassVisitor classVisitor) {
super(api, classVisitor);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces);
this.className = name;
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
if (name.contains(nameTrait)) {
Map<String, String> fieldData = getFieldData(className);
String prevDescriptor = fieldData.get(name);
if (prevDescriptor != null && prevDescriptor.equals(descriptor)) {
// ignore duplicated field of class
return null;
}
fieldData.put(name, descriptor);
}
return super.visitField(access, name, descriptor, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
if (name.equals("<init>") || name.contains(nameTrait)) {
Map<String, List<String>> methodData = getMethodData(className);
List<String> descriptorList = methodData.computeIfAbsent(name, k -> new ArrayList<>());
if (descriptorList.contains(descriptor)) {
// ignore duplicated method of class
return null;
}
descriptorList.add(descriptor);
}
return super.visitMethod(access, name, descriptor, signature, exceptions);
}
private Map<String, String> getFieldData(String className) {
return fieldCache.computeIfAbsent(className, k -> new ConcurrentHashMap<>());
}
private Map<String, List<String>> getMethodData(String className) {
return methodCache.computeIfAbsent(className, k -> new ConcurrentHashMap<>());
}
}
}

View File

@ -0,0 +1,173 @@
/*
* 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.skywalking.apm.agent.bytebuddy;
import net.bytebuddy.dynamic.ClassFileLocator;
import org.apache.skywalking.apm.agent.core.logging.api.ILog;
import org.apache.skywalking.apm.agent.core.logging.api.LogManager;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
/**
* Resolve auxiliary type from Instrumentation.getAllLoadedClasses().
* Get class bytecode from separate thread to bypass jdk limitation or bug: https://github.com/raphw/byte-buddy/issues/1434
*/
public class SWClassFileLocator implements ClassFileLocator {
private static final ILog LOGGER = LogManager.getLogger(SWClassFileLocator.class);
private static final String[] TYPE_NAME_TRAITS = {"auxiliary$", "ByteBuddy$", "sw$"};
private static final int DEFAULT_TIMEOUT_SECONDS = 2;
private final ForInstrumentation.ClassLoadingDelegate classLoadingDelegate;
private final Instrumentation instrumentation;
private final ClassLoader classLoader;
private final BlockingQueue<ResolutionFutureTask> queue = new LinkedBlockingDeque<>();
private final Thread thread;
private final int timeoutSeconds;
private volatile boolean closed;
public SWClassFileLocator(Instrumentation instrumentation, ClassLoader classLoader) {
this(instrumentation, classLoader, DEFAULT_TIMEOUT_SECONDS);
}
public SWClassFileLocator(Instrumentation instrumentation, ClassLoader classLoader, int resolveTimeoutSeconds) {
this.instrumentation = instrumentation;
this.classLoader = classLoader;
this.timeoutSeconds = resolveTimeoutSeconds;
classLoadingDelegate = ForInstrumentation.ClassLoadingDelegate.ForDelegatingClassLoader.of(classLoader);
// Use thread instead of ExecutorService here, avoiding conflicts with apm-jdk-threadpool-plugin
thread = new Thread(() -> {
while (!closed) {
try {
ResolutionFutureTask task = queue.poll(5, TimeUnit.SECONDS);
if (task != null) {
try {
Resolution resolution = getResolution(task.getClassName());
task.getFuture().complete(resolution);
} catch (Throwable e) {
task.getFuture().completeExceptionally(e);
}
}
} catch (InterruptedException e) {
// ignore interrupted error
} catch (Throwable e) {
LOGGER.error(e, "Resolve bytecode of class failure");
}
}
}, "SWClassFileLocator");
thread.setDaemon(true);
thread.start();
}
@Override
public Resolution locate(String name) throws IOException {
if (!match(name)) {
return new Resolution.Illegal(name);
}
if (closed) {
throw new IOException("resolve class failure: closed");
}
// get class binary representation in a clean thread, avoiding nest calling transformer!
ResolutionFutureTask futureTask = new ResolutionFutureTask(name);
queue.offer(futureTask);
try {
return futureTask.getFuture().get(timeoutSeconds, TimeUnit.SECONDS);
} catch (Exception e) {
throw new IOException("resolve class failure: " + name, e);
}
}
private boolean match(String name) {
boolean matched = false;
for (String typeNameTrait : TYPE_NAME_TRAITS) {
if (name.contains(typeNameTrait)) {
matched = true;
break;
}
}
return matched;
}
private Resolution getResolution(String name) throws Exception {
SWExtractionClassFileTransformer classFileTransformer = new SWExtractionClassFileTransformer(name);
try {
instrumentation.addTransformer(classFileTransformer, true);
Class aClass = locateClass(name);
if (aClass == null) {
return new Resolution.Illegal(name);
}
// trigger re-transforming the target class, and receive bytecode in SWExtractionClassFileTransformer
instrumentation.retransformClasses(new Class[]{aClass});
} finally {
instrumentation.removeTransformer(classFileTransformer);
}
return classFileTransformer.getBinaryRepresentation() != null ?
new Resolution.Explicit(classFileTransformer.getBinaryRepresentation()) :
new Resolution.Illegal(name);
}
private Class locateClass(String className) {
// find class in classloader
try {
return classLoadingDelegate.locate(className);
} catch (ClassNotFoundException e) {
}
// find class in instrumentation
Class[] allLoadedClasses = instrumentation.getAllLoadedClasses();
for (int i = 0; i < allLoadedClasses.length; i++) {
Class aClass = allLoadedClasses[i];
if (className.equals(aClass.getName())) {
return aClass;
}
}
return null;
}
@Override
public void close() {
closed = true;
queue.clear();
thread.interrupt();
}
private class ResolutionFutureTask {
private CompletableFuture<Resolution> future;
private String className;
public ResolutionFutureTask(String className) {
this.className = className;
future = new CompletableFuture<>();
}
public CompletableFuture<Resolution> getFuture() {
return future;
}
public String getClassName() {
return className;
}
}
}

View File

@ -0,0 +1,71 @@
/*
* 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.skywalking.apm.agent.bytebuddy;
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
public class SWExtractionClassFileTransformer implements ClassFileTransformer {
/**
* An indicator that an attempted class file transformation did not alter the handed class file.
*/
private static final byte[] DO_NOT_TRANSFORM = null;
/**
* The name of the type to look up.
*/
private final String typeName;
/**
* The binary representation of the looked-up class.
*/
private volatile byte[] binaryRepresentation;
/**
* Creates a class file transformer for the purpose of extraction.
*
* @param typeName The name of the type to look up.
*/
public SWExtractionClassFileTransformer(String typeName) {
this.typeName = typeName;
}
@Override
public byte[] transform(ClassLoader classLoader,
String internalName,
Class<?> redefinedType,
ProtectionDomain protectionDomain,
byte[] binaryRepresentation) {
if (internalName != null && typeName.equals(internalName.replace('/', '.'))) {
this.binaryRepresentation = binaryRepresentation.clone();
}
return DO_NOT_TRANSFORM;
}
/**
* Returns the binary representation of the class file that was looked up. The returned array must never be modified.
*
* @return The binary representation of the class file or {@code null} if no such class file could
* be located.
*/
public byte[] getBinaryRepresentation() {
return binaryRepresentation;
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.skywalking.apm.agent.bytebuddy.biz;
public class BizFoo {
private String name;
public BizFoo() {
this("Tom");
}
public BizFoo(String name) {
this.name = name;
}
public String sayHello(String someone) {
return "Hello to " + someone + " from " + name;
}
public int sayHello(int uid) {
return uid;
}
public String greeting(String man) {
return "Greet " + man;
}
}

View File

@ -16,11 +16,30 @@
*
*/
package org.apache.skywalking.apm.agent.core.plugin.bytebuddy;
package org.apache.skywalking.apm.agent.bytebuddy.biz;
/**
* ByteBuddy class cache mode
*/
public enum ClassCacheMode {
FILE, MEMORY
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class ProjectDO {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View File

@ -0,0 +1,39 @@
/*
* 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.skywalking.apm.agent.bytebuddy.biz;
import java.util.ArrayList;
import java.util.List;
public class ProjectService {
private List<ProjectDO> cache = new ArrayList<>();
public void add(ProjectDO projectDO) {
this.cache.add(projectDO);
}
public ProjectDO getById(int id) {
return cache.stream().filter(projectDO -> projectDO.getId() == id).findAny().orElse(null);
}
public List<ProjectDO> list() {
return cache;
}
}

View File

@ -0,0 +1,212 @@
/*
* 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.skywalking.apm.agent.bytebuddy.cases;
import net.bytebuddy.ByteBuddy;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.agent.builder.SWAgentBuilderDefault;
import net.bytebuddy.agent.builder.SWNativeMethodStrategy;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.SWImplementationContextFactory;
import net.bytebuddy.implementation.SuperMethodCall;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import org.apache.skywalking.apm.agent.bytebuddy.ConstructorInter;
import org.apache.skywalking.apm.agent.bytebuddy.EnhanceHelper;
import org.apache.skywalking.apm.agent.bytebuddy.InstMethodsInter;
import org.apache.skywalking.apm.agent.bytebuddy.Log;
import org.apache.skywalking.apm.agent.bytebuddy.SWAsmVisitorWrapper;
import org.apache.skywalking.apm.agent.bytebuddy.SWAuxiliaryTypeNamingStrategy;
import org.apache.skywalking.apm.agent.bytebuddy.SWClassFileLocator;
import org.apache.skywalking.apm.agent.bytebuddy.biz.BizFoo;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.util.TraceClassVisitor;
import java.io.ByteArrayOutputStream;
import java.io.PrintWriter;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import java.util.Arrays;
import java.util.List;
public class AbstractInterceptTest {
public static final String BIZ_FOO_CLASS_NAME = "org.apache.skywalking.apm.agent.bytebuddy.biz.BizFoo";
public static final String PROJECT_SERVICE_CLASS_NAME = "org.apache.skywalking.apm.agent.bytebuddy.biz.ProjectService";
public static final String DOC_SERVICE_CLASS_NAME = "org.apache.skywalking.apm.agent.bytebuddy.biz.DocService";
public static final String SAY_HELLO_METHOD = "sayHello";
public static final int BASE_INT_VALUE = 100;
public static final String CONSTRUCTOR_INTERCEPTOR_CLASS = "constructorInterceptorClass";
public static final String METHOD_INTERCEPTOR_CLASS = "methodInterceptorClass";
protected List<String> nameTraits = Arrays.asList("sw2023", "sw2024");
protected boolean deleteDuplicatedFields = false;
@BeforeClass
public static void setUp() {
EnhanceHelper.clear();
Log.clear();
}
@Rule
public TestWatcher watcher = new TestWatcher() {
@Override
protected void failed(Throwable e, Description description) {
Log.error("Test failure: {}.{}(), error: {}", description.getTestClass(), description.getMethodName(), e);
Log.printToConsole();
}
};
protected static void callBizFoo(int round) {
Log.info("-------------");
Log.info("callBizFoo: " + round);
// load target class
int intResult = new BizFoo().sayHello(BASE_INT_VALUE);
Log.info("result: " + intResult);
String result = new BizFoo("Smith").sayHello("Joe");
Log.info(result);
Assert.assertEquals("Int value is unexpected", BASE_INT_VALUE + round, intResult);
Assert.assertEquals("String value is unexpected", "Hello to John from Smith", result);
}
protected static void checkMethodInterceptor(String method, int round) {
List<String> interceptors = EnhanceHelper.getInterceptors();
String interceptorName = METHOD_INTERCEPTOR_CLASS + "$" + method + "$" + round;
Assert.assertTrue("Not found interceptor: " + interceptorName, interceptors.contains(interceptorName));
Log.info("Found interceptor: " + interceptorName);
}
protected static void checkConstructorInterceptor(String className, int round) {
List<String> interceptors = EnhanceHelper.getInterceptors();
String interceptorName = CONSTRUCTOR_INTERCEPTOR_CLASS + "$" + className + "$" + round;
Assert.assertTrue("Not found interceptor: " + interceptorName, interceptors.contains(interceptorName));
Log.info("Found interceptor: " + interceptorName);
}
protected static void checkErrors() {
Assert.assertEquals("Error occurred in transform", 0, EnhanceHelper.getErrors().size());
}
protected void installMethodInterceptor(String className, String methodName, int round) {
this.installMethodInterceptorWithMethodDelegation(className, methodName, round);
}
protected void installMethodInterceptorWithMethodDelegation(String className, String methodName, int round) {
String interceptorClassName = METHOD_INTERCEPTOR_CLASS + "$" + methodName + "$" + round;
String nameTrait = getNameTrait(round);
String fieldName = nameTrait + "_delegate$" + methodName + round;
newAgentBuilder(nameTrait).type(ElementMatchers.named(className))
.transform((builder, typeDescription, classLoader, module, protectionDomain) -> {
if (deleteDuplicatedFields) {
builder = builder.visit(new SWAsmVisitorWrapper());
}
return builder
.method(ElementMatchers.nameContainsIgnoreCase(methodName))
.intercept(MethodDelegation.withDefaultConfiguration()
.to(new InstMethodsInter(interceptorClassName, classLoader), fieldName))
;
}
)
.with(getListener(interceptorClassName))
.installOn(ByteBuddyAgent.install());
}
protected void installConstructorInterceptor(String className, int round) {
installConstructorInterceptorWithMethodDelegation(className, round);
}
protected void installConstructorInterceptorWithMethodDelegation(String className, int round) {
String interceptorClassName = CONSTRUCTOR_INTERCEPTOR_CLASS + "$" + className + "$" + round;
String nameTrait = getNameTrait(round);
String fieldName = nameTrait + "_delegate$constructor" + round;
AgentBuilder agentBuilder = newAgentBuilder(nameTrait);
agentBuilder.type(ElementMatchers.named(className))
.transform((builder, typeDescription, classLoader, module, protectionDomain) -> {
if (deleteDuplicatedFields) {
builder = builder.visit(new SWAsmVisitorWrapper());
}
return builder
.constructor(ElementMatchers.any())
.intercept(SuperMethodCall.INSTANCE.andThen(
MethodDelegation.withDefaultConfiguration().to(
new ConstructorInter(interceptorClassName, classLoader), fieldName)
));
}
)
.with(getListener(interceptorClassName))
.installOn(ByteBuddyAgent.install());
}
protected String getNameTrait(int round) {
return nameTraits.get(round - 1);
}
protected AgentBuilder newAgentBuilder(String nameTrait) {
ByteBuddy byteBuddy = new ByteBuddy()
.with(new SWAuxiliaryTypeNamingStrategy(nameTrait))
.with(new SWImplementationContextFactory(nameTrait));
return new SWAgentBuilderDefault(byteBuddy, new SWNativeMethodStrategy(nameTrait))
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.with(AgentBuilder.DescriptionStrategy.Default.POOL_FIRST)
.with(new SWClassFileLocator(ByteBuddyAgent.install(), getClassLoader()));
}
protected void installTraceClassTransformer(String msg) {
ClassFileTransformer classFileTransformer = new ClassFileTransformer() {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (className.endsWith("BizFoo") || className.endsWith("ProjectService") || className.endsWith("DocService")) {
Log.error(msg + className);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
ClassReader cr = new ClassReader(classfileBuffer);
cr.accept(new TraceClassVisitor(new PrintWriter(outputStream)), ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
Log.error(outputStream.toString());
}
return null;
}
};
ByteBuddyAgent.install().addTransformer(classFileTransformer, true);
}
private static ClassLoader getClassLoader() {
return AbstractInterceptTest.class.getClassLoader();
}
private static AgentBuilder.Listener.Adapter getListener(String interceptorClassName) {
return new AgentBuilder.Listener.Adapter() {
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
String msg = String.format("Transform Error: interceptorClassName: %s, typeName: %s, classLoader: %s, module: %s, loaded: %s", interceptorClassName, typeName, classLoader, module, loaded);
EnhanceHelper.onError(msg, throwable);
System.err.println(msg);
throwable.printStackTrace();
}
};
}
}

View File

@ -0,0 +1,55 @@
/*
* 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.skywalking.apm.agent.bytebuddy.cases;
import org.apache.skywalking.apm.agent.bytebuddy.Log;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
public class AbstractReTransformTest extends AbstractInterceptTest {
protected static void reTransform(Instrumentation instrumentation, Class clazz) throws Exception {
Log.info("-------------");
Log.info("Begin to re-transform class: " + clazz.getName() + " ..");
ClassFileTransformer transformer = new ClassFileTransformer() {
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
Log.info(String.format("transform: className=%s, classBeingRedefined=%s, classloader=%s, protectionDomain=%s, classfileBuffer=%d",
className, classBeingRedefined, loader, protectionDomain.getCodeSource(), classfileBuffer.length));
return null;
}
};
try {
instrumentation.addTransformer(transformer, true);
instrumentation.retransformClasses(clazz);
Log.info("ReTransform class " + clazz.getName() + " successful.");
Log.info("-------------");
} catch (Throwable e) {
Log.info("ReTransform class " + clazz.getName() + " failure: " + e);
Log.info("-------------");
throw e;
} finally {
instrumentation.removeTransformer(transformer);
}
}
}

View File

@ -0,0 +1,42 @@
/*
* 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.skywalking.apm.agent.bytebuddy.cases;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.junit.Test;
public class Intercept1Test extends AbstractInterceptTest {
@Test
public void test1() {
ByteBuddyAgent.install();
// install transformer
installMethodInterceptor(BIZ_FOO_CLASS_NAME, SAY_HELLO_METHOD, 1);
installConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
callBizFoo(1);
// check interceptors
checkMethodInterceptor(SAY_HELLO_METHOD, 1);
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
checkErrors();
}
}

View File

@ -0,0 +1,45 @@
/*
* 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.skywalking.apm.agent.bytebuddy.cases;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.junit.Test;
public class Intercept2Test extends AbstractInterceptTest {
@Test
public void test2() {
ByteBuddyAgent.install();
// install transformer
installMethodInterceptor(BIZ_FOO_CLASS_NAME, SAY_HELLO_METHOD, 1);
installMethodInterceptor(BIZ_FOO_CLASS_NAME, SAY_HELLO_METHOD, 2);
installConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
// load target class
callBizFoo(2);
// check interceptors
checkMethodInterceptor(SAY_HELLO_METHOD, 1);
checkMethodInterceptor(SAY_HELLO_METHOD, 2);
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
checkErrors();
}
}

View File

@ -0,0 +1,46 @@
/*
* 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.skywalking.apm.agent.bytebuddy.cases;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.junit.Test;
public class Intercept3Test extends AbstractInterceptTest {
@Test
public void test3() {
ByteBuddyAgent.install();
// install transformer
installMethodInterceptor(BIZ_FOO_CLASS_NAME, SAY_HELLO_METHOD, 1);
installConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
installMethodInterceptor(BIZ_FOO_CLASS_NAME, SAY_HELLO_METHOD, 2);
// load target class
callBizFoo(2);
// check interceptors
checkMethodInterceptor(SAY_HELLO_METHOD, 1);
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
checkMethodInterceptor(SAY_HELLO_METHOD, 2);
checkErrors();
}
}

View File

@ -0,0 +1,44 @@
/*
* 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.skywalking.apm.agent.bytebuddy.cases;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.junit.Test;
public class Intercept4Test extends AbstractInterceptTest {
@Test
public void test4() {
ByteBuddyAgent.install();
// install transformer
installConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
installMethodInterceptor(BIZ_FOO_CLASS_NAME, SAY_HELLO_METHOD, 1);
// load target class
callBizFoo(1);
// check interceptors
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
checkMethodInterceptor(SAY_HELLO_METHOD, 1);
checkErrors();
}
}

View File

@ -0,0 +1,47 @@
/*
* 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.skywalking.apm.agent.bytebuddy.cases;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.junit.Test;
public class Intercept5Test extends AbstractInterceptTest {
@Test
public void test5() {
ByteBuddyAgent.install();
// install transformer
installConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
installMethodInterceptor(BIZ_FOO_CLASS_NAME, SAY_HELLO_METHOD, 1);
installConstructorInterceptor(BIZ_FOO_CLASS_NAME, 2);
installMethodInterceptor(BIZ_FOO_CLASS_NAME, SAY_HELLO_METHOD, 2);
// load target class
callBizFoo(2);
// check interceptors
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
checkMethodInterceptor(SAY_HELLO_METHOD, 1);
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 2);
checkMethodInterceptor(SAY_HELLO_METHOD, 2);
checkErrors();
}
}

View File

@ -0,0 +1,48 @@
/*
* 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.skywalking.apm.agent.bytebuddy.cases;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.junit.Test;
public class Intercept6Test extends AbstractInterceptTest {
@Test
public void test6() {
ByteBuddyAgent.install();
// install transformer
installMethodInterceptor(BIZ_FOO_CLASS_NAME, SAY_HELLO_METHOD, 1);
installConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
installMethodInterceptor(BIZ_FOO_CLASS_NAME, SAY_HELLO_METHOD, 2);
installConstructorInterceptor(BIZ_FOO_CLASS_NAME, 2);
// load target class
callBizFoo(2);
// check interceptors
checkMethodInterceptor(SAY_HELLO_METHOD, 1);
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
checkMethodInterceptor(SAY_HELLO_METHOD, 2);
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 2);
checkErrors();
}
}

View File

@ -0,0 +1,101 @@
/*
* 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.skywalking.apm.agent.bytebuddy.cases;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.matcher.ElementMatchers;
import net.bytebuddy.utility.JavaModule;
import org.apache.skywalking.apm.agent.bytebuddy.InstMethodsInter;
import org.apache.skywalking.apm.agent.bytebuddy.SWAsmVisitorWrapper;
import org.apache.skywalking.apm.agent.core.util.FileUtils;
import org.junit.Test;
import java.io.File;
public class MultipleInterceptorTest extends AbstractInterceptTest {
String dumpFolder = "target/class-dump";
@Test
public void test1() {
String className = BIZ_FOO_CLASS_NAME;
String methodName = SAY_HELLO_METHOD;
String nameTrait = getNameTrait(1);
enableClassDump();
//newAgentBuilder(nameTrait)
new AgentBuilder.Default()
.type(ElementMatchers.named(className))
.transform((builder, typeDescription, classLoader, module, protectionDomain) -> {
int round = 1;
String interceptorClassName = METHOD_INTERCEPTOR_CLASS + "$" + methodName + "$" + round;
String fieldName = nameTrait + "_delegate$" + methodName + round;
if (deleteDuplicatedFields) {
builder = builder.visit(new SWAsmVisitorWrapper());
}
return builder
.method(ElementMatchers.nameContainsIgnoreCase(methodName))
.intercept(MethodDelegation.withDefaultConfiguration()
.to(new InstMethodsInter(interceptorClassName, classLoader), fieldName))
;
}
)
.type(ElementMatchers.nameContains(className))
.transform((builder, typeDescription, classLoader, module, protectionDomain) -> {
int round = 2;
String interceptorClassName = METHOD_INTERCEPTOR_CLASS + "$" + methodName + "$" + round;
String fieldName = nameTrait + "_delegate$" + methodName + round;
if (deleteDuplicatedFields) {
builder = builder.visit(new SWAsmVisitorWrapper());
}
return builder
.method(ElementMatchers.nameContainsIgnoreCase(methodName))
.intercept(MethodDelegation.withDefaultConfiguration()
.to(new InstMethodsInter(interceptorClassName, classLoader), fieldName))
;
}
)
.with(new AgentBuilder.Listener.Adapter() {
@Override
public void onError(String typeName, ClassLoader classLoader, JavaModule module, boolean loaded, Throwable throwable) {
System.err.println(String.format("Transform Error: typeName: %s, classLoader: %s, module: %s, loaded: %s", typeName, classLoader, module, loaded));
throwable.printStackTrace();
}
})
.installOn(ByteBuddyAgent.install());
callBizFoo(1);
// check interceptors
// checkMethodInterceptor(SAY_HELLO_METHOD, 1);
checkMethodInterceptor(SAY_HELLO_METHOD, 2);
checkErrors();
}
private void enableClassDump() {
System.setProperty("net.bytebuddy.dump", dumpFolder);
File dumpDir = new File(dumpFolder);
FileUtils.deleteDirectory(dumpDir);
dumpDir.mkdirs();
}
}

View File

@ -0,0 +1,86 @@
/*
* 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.skywalking.apm.agent.bytebuddy.cases;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.apache.skywalking.apm.agent.bytebuddy.biz.BizFoo;
import org.apache.skywalking.apm.agent.bytebuddy.biz.ProjectDO;
import org.apache.skywalking.apm.agent.bytebuddy.biz.ProjectService;
import org.junit.Assert;
import org.junit.Test;
import java.lang.instrument.Instrumentation;
public class ReTransform1Test extends AbstractReTransformTest {
@Test
public void testInterceptConstructor() throws Exception {
Instrumentation instrumentation = ByteBuddyAgent.install();
// install transformer
installMethodInterceptor(BIZ_FOO_CLASS_NAME, SAY_HELLO_METHOD, 1);
installConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
// project service
installMethodInterceptor(PROJECT_SERVICE_CLASS_NAME, "add", 1);
installMethodInterceptor(PROJECT_SERVICE_CLASS_NAME, "get", 1);
installMethodInterceptor(PROJECT_SERVICE_CLASS_NAME, "list", 1);
installConstructorInterceptor(PROJECT_SERVICE_CLASS_NAME, 1);
// call target class
callBizFoo(1);
// check interceptors
checkMethodInterceptor(SAY_HELLO_METHOD, 1);
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
checkErrors();
ProjectService projectService = new ProjectService();
ProjectDO originProjectDO = ProjectDO.builder()
.name("test")
.id(1)
.build();
projectService.add(originProjectDO);
ProjectDO projectDO = projectService.getById(1);
projectService.list();
// installTraceClassTransformer("Trace class: ");
// do retransform
reTransform(instrumentation, BizFoo.class);
reTransform(instrumentation, ProjectService.class);
// test again
callBizFoo(1);
// check interceptors
checkMethodInterceptor(SAY_HELLO_METHOD, 1);
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
projectDO = projectService.getById(1);
Assert.assertEquals(originProjectDO, projectDO);
checkConstructorInterceptor(PROJECT_SERVICE_CLASS_NAME, 1);
checkMethodInterceptor("add", 1);
checkMethodInterceptor("get", 1);
checkMethodInterceptor("list", 1);
checkErrors();
}
}

View File

@ -0,0 +1,72 @@
/*
* 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.skywalking.apm.agent.bytebuddy.cases;
import net.bytebuddy.agent.ByteBuddyAgent;
import org.apache.skywalking.apm.agent.bytebuddy.Log;
import org.apache.skywalking.apm.agent.bytebuddy.biz.BizFoo;
import org.junit.Test;
import java.lang.instrument.Instrumentation;
public class ReTransform2Test extends AbstractReTransformTest {
@Test
public void testInterceptConstructor() throws Exception {
Instrumentation instrumentation = ByteBuddyAgent.install();
//this.deleteDuplicatedFields = true;
// install transformer 1
installMethodInterceptor(BIZ_FOO_CLASS_NAME, SAY_HELLO_METHOD, 1);
installConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
// install transformer 2
installConstructorInterceptor(BIZ_FOO_CLASS_NAME, 2);
installMethodInterceptor(BIZ_FOO_CLASS_NAME, SAY_HELLO_METHOD, 2);
installMethodInterceptor(BIZ_FOO_CLASS_NAME, "greeting", 2);
// call target class
callBizFoo(2);
// check interceptors
Log.info("-------------");
Log.info("Check interceptors ..");
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 2);
checkMethodInterceptor(SAY_HELLO_METHOD, 1);
checkMethodInterceptor(SAY_HELLO_METHOD, 2);
// do retransform
reTransform(instrumentation, BizFoo.class);
// test again
callBizFoo(2);
// check interceptors
Log.info("-------------");
Log.info("Check interceptors ..");
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 1);
checkConstructorInterceptor(BIZ_FOO_CLASS_NAME, 2);
checkMethodInterceptor(SAY_HELLO_METHOD, 1);
checkMethodInterceptor(SAY_HELLO_METHOD, 2);
checkErrors();
}
}

View File

@ -46,15 +46,6 @@ agent.ignore_suffix=${SW_AGENT_IGNORE_SUFFIX:.jpg,.jpeg,.js,.css,.png,.bmp,.gif,
# SkyWalking team may ask for these files in order to resolve compatible problem.
agent.is_open_debugging_class=${SW_AGENT_OPEN_DEBUG:false}
# If true, SkyWalking agent will cache all instrumented classes files to memory or disk files (decided by class cache mode),
# allow other javaagent to enhance those classes that enhanced by SkyWalking agent.
agent.is_cache_enhanced_class=${SW_AGENT_CACHE_CLASS:false}
# The instrumented classes cache mode: MEMORY or FILE
# MEMORY: cache class bytes to memory, if instrumented classes is too many or too large, it may take up more memory
# FILE: cache class bytes in `/class-cache` folder, automatically clean up cached class files when the application exits
agent.class_cache_mode=${SW_AGENT_CLASS_CACHE_MODE:MEMORY}
# Instance name is the identity of an instance, should be unique in the service. If empty, SkyWalking agent will
# generate an 32-bit uuid. BY Default, SkyWalking uses UUID@hostname as the instance name. Max length is 50(UTF-8 char)
agent.instance_name=${SW_AGENT_INSTANCE_NAME:}

View File

@ -37,6 +37,7 @@
<module>bootstrap-plugins</module>
<module>optional-plugins</module>
<module>optional-reporter-plugins</module>
<module>bytebuddy-patch</module>
</modules>
<properties>

View File

@ -13,8 +13,6 @@ This is the properties list supported in `agent/config/agent.config`.
| `agent.span_limit_per_segment` | The max number of spans in a single segment. Through this config item, SkyWalking keep your application memory cost estimated. | SW_AGENT_SPAN_LIMIT | 300 |
| `agent.ignore_suffix` | If the operation name of the first span is included in this set, this segment should be ignored. | SW_AGENT_IGNORE_SUFFIX | Not set |
| `agent.is_open_debugging_class` | If true, skywalking agent will save all instrumented classes files in `/debugging` folder. SkyWalking team may ask for these files in order to resolve compatible problem. | SW_AGENT_OPEN_DEBUG | Not set |
| `agent.is_cache_enhanced_class` | If true, SkyWalking agent will cache all instrumented classes files to memory or disk files (decided by class cache mode), allow another java agent to enhance those classes that enhanced by SkyWalking agent. To use some Java diagnostic tools (such as BTrace, Arthas) to diagnose applications or add a custom java agent to enhance classes, you need to enable this feature. | SW_AGENT_CACHE_CLASS | `false` |
| `agent.class_cache_mode` | The instrumented classes cache mode: `MEMORY` or `FILE`. `MEMORY`: cache class bytes to memory, if instrumented classes is too many or too large, it may take up more memory. `FILE`: cache class bytes in `/class-cache` folder, automatically clean up cached class files when the application exits. | SW_AGENT_CLASS_CACHE_MODE | `MEMORY` |
| `agent.instance_name` | Instance name is the identity of an instance, should be unique in the service. If empty, SkyWalking agent will generate an 32-bit uuid. Default, use `UUID`@`hostname` as the instance name. Max length is 50(UTF-8 char) | SW_AGENT_INSTANCE_NAME | `""` |
| `agent.instance_properties_json={"key":"value"}` | Add service instance custom properties in json format. | SW_INSTANCE_PROPERTIES_JSON | Not set |
| `agent.cause_exception_depth` | How depth the agent goes, when log all cause exceptions. | SW_AGENT_CAUSE_EXCEPTION_DEPTH | `5` |

11
pom.xml
View File

@ -86,7 +86,7 @@
<lombok.version>1.18.20</lombok.version>
<!-- core lib dependency -->
<bytebuddy.version>1.12.19</bytebuddy.version>
<bytebuddy.version>1.14.4</bytebuddy.version>
<grpc.version>1.50.0</grpc.version>
<netty.version>4.1.86.Final</netty.version>
<gson.version>2.8.9</gson.version>
@ -238,11 +238,15 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy</artifactId>
<version>${bytebuddy.version}</version>
</dependency>
<dependency>
<groupId>net.bytebuddy</groupId>
<artifactId>byte-buddy-agent</artifactId>
<version>${bytebuddy.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.objenesis</groupId>
@ -419,7 +423,8 @@
org/apache/skywalking/oap/server/exporter/grpc/*.java,
org/apache/skywalking/oap/server/configuration/service/*.java,
**/jmh_generated/*_jmhType*.java,
**/jmh_generated/*_jmhTest.java
**/jmh_generated/*_jmhTest.java,
net/bytebuddy/**
</excludes>
<propertyExpansion>
import.control=${maven.multiModuleProjectDirectory}/apm-checkstyle/importControl.xml

View File

@ -18,7 +18,4 @@
home="$(cd "$(dirname $0)"; pwd)"
# enable class cache feature
export agent_opts="$agent_opts -Dskywalking.agent.is_cache_enhanced_class=true -Dskywalking.agent.class_cache_mode=MEMORY"
java -jar ${agent_opts} ${home}/../libs/retransform-class-scenario.jar &

View File

@ -20,3 +20,4 @@ healthCheck: http://localhost:8080/case/healthCheck
startScript: ./bin/startup.sh
environment:
dependencies:
withPlugins: apm-spring-*.jar, apm-jdk-*.jar

View File

@ -17,7 +17,5 @@
type: tomcat
entryService: http://localhost:8080/retransform-class-tomcat-scenario/case/retransform-class
healthCheck: http://localhost:8080/retransform-class-tomcat-scenario/case/healthCheck
environment:
- CATALINA_OPTS="-Dskywalking.agent.is_cache_enhanced_class=true -Dskywalking.agent.class_cache_mode=FILE"
dependencies:
withPlugins: apm-spring-annotation-plugin-*.jar
withPlugins: apm-spring-*.jar, apm-jdk-*.jar