Skip to content

Commit

Permalink
Improve bytebuddy class enhance for retransform classes (apache#561)
Browse files Browse the repository at this point in the history
* 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;
    }
}
```
  • Loading branch information
kylixs committed Jun 24, 2023
1 parent 24bff9e commit d37c6c1
Show file tree
Hide file tree
Showing 57 changed files with 2,313 additions and 258 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
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;

/**
Expand Down Expand Up @@ -110,19 +109,6 @@ public static class Agent {
*/
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
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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$";

}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ public boolean matches(T target) {
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,18 @@ public boolean matches(MethodDescription target) {
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.
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ public boolean matches(MethodDescription target) {
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
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.
Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
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.
Expand All @@ -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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
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.
Expand All @@ -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());
}
}
Loading

0 comments on commit d37c6c1

Please sign in to comment.