22
33import static datadog .instrument .asm .Opcodes .ACC_ABSTRACT ;
44import static datadog .instrument .asm .Opcodes .ACC_NATIVE ;
5+ import static datadog .instrument .asm .Opcodes .ARETURN ;
56import static datadog .instrument .asm .Opcodes .ASM9 ;
7+ import static datadog .instrument .asm .Opcodes .GETSTATIC ;
8+ import static datadog .instrument .asm .Opcodes .ICONST_1 ;
69import static datadog .instrument .asm .Opcodes .INVOKEINTERFACE ;
10+ import static datadog .instrument .asm .Opcodes .POP ;
711import static datadog .instrument .asm .Opcodes .POP2 ;
812
913import datadog .instrument .asm .ClassReader ;
2327 * system class-loader appears to trigger this bug. The workaround is to replace these calls during
2428 * training with opcodes that pop the tracer and argument, and push the expected return value.
2529 *
26- * <p>Note this transformation is not persisted, so in production the original method is invoked.
30+ * <p>Likewise, custom {@code TraceInterceptor} return values are replaced with simple placeholders
31+ * from the boot class-path which are guaranteed not to trigger the AOT bug.
32+ *
33+ * <p>Note these transformations are not persisted, so in production the original code is used.
2734 */
2835final class TraceApiTransformer implements ClassFileTransformer {
2936 private static final ClassLoader SYSTEM_CLASS_LOADER = ClassLoader .getSystemClassLoader ();
3037
38+ static final String TRACE_INTERCEPTOR = "datadog/trace/api/interceptor/TraceInterceptor" ;
39+
40+ static final String PLACEHOLDER_TRACE_INTERCEPTOR =
41+ "datadog/trace/bootstrap/aot/PlaceholderTraceInterceptor" ;
42+
3143 @ Override
3244 public byte [] transform (
3345 ClassLoader loader ,
@@ -42,7 +54,7 @@ public byte[] transform(
4254 ClassReader cr = new ClassReader (bytecode );
4355 ClassWriter cw = new ClassWriter (cr , 0 );
4456 AtomicBoolean modified = new AtomicBoolean ();
45- cr .accept (new CallerPatch (cw , modified ), 0 );
57+ cr .accept (new TraceInterceptorPatch (cw , modified ), 0 );
4658 // only return something when we've modified the bytecode
4759 if (modified .get ()) {
4860 return cw .toByteArray ();
@@ -54,11 +66,18 @@ public byte[] transform(
5466 return null ; // tells the JVM to keep the original bytecode
5567 }
5668
57- /** Patches callers of {@link datadog.trace.api.Tracer#addTraceInterceptor}. */
58- static final class CallerPatch extends ClassVisitor {
69+ /**
70+ * Patches certain references to {@code TraceInterceptor} to workaround AOT bug:
71+ *
72+ * <ul>
73+ * <li>removes direct calls to {@code Tracer.addTraceInterceptor()}
74+ * <li>replaces {@code TraceInterceptor} return values with placeholders
75+ * </ul>
76+ */
77+ static final class TraceInterceptorPatch extends ClassVisitor {
5978 private final AtomicBoolean modified ;
6079
61- CallerPatch (ClassVisitor cv , AtomicBoolean modified ) {
80+ TraceInterceptorPatch (ClassVisitor cv , AtomicBoolean modified ) {
6281 super (ASM9 , cv );
6382 this .modified = modified ;
6483 }
@@ -68,14 +87,17 @@ public MethodVisitor visitMethod(
6887 int access , String name , String descriptor , String signature , String [] exceptions ) {
6988 MethodVisitor mv = super .visitMethod (access , name , descriptor , signature , exceptions );
7089 if ((access & (ACC_ABSTRACT | ACC_NATIVE )) == 0 ) {
90+ if (descriptor .endsWith (")L" + TRACE_INTERCEPTOR + ";" )) {
91+ mv = new ReturnPatch (mv , modified );
92+ }
7193 return new InvokePatch (mv , modified );
7294 } else {
7395 return mv ; // no need to patch abstract/native methods
7496 }
7597 }
7698 }
7799
78- /** Removes calls to {@link datadog.trace.api. Tracer# addTraceInterceptor}. */
100+ /** Removes direct calls to {@code Tracer. addTraceInterceptor() }. */
79101 static final class InvokePatch extends MethodVisitor {
80102 private final AtomicBoolean modified ;
81103
@@ -90,16 +112,45 @@ public void visitMethodInsn(
90112 if (INVOKEINTERFACE == opcode
91113 && "datadog/trace/api/Tracer" .equals (owner )
92114 && "addTraceInterceptor" .equals (name )
93- && "(Ldatadog/trace/api/interceptor/TraceInterceptor ;)Z" .equals (descriptor )) {
94- // pop tracer and trace interceptor argument from call stack
115+ && ( "(L" + TRACE_INTERCEPTOR + " ;)Z") .equals (descriptor )) {
116+ // discard tracer and trace interceptor argument from call stack
95117 mv .visitInsn (POP2 );
96- // push true return value
97- mv .visitLdcInsn ( true );
118+ // push substitute return value (true)
119+ mv .visitInsn ( ICONST_1 );
98120 // flag that we've modified the bytecode
99121 modified .set (true );
100122 } else {
101123 super .visitMethodInsn (opcode , owner , name , descriptor , isInterface );
102124 }
103125 }
104126 }
127+
128+ /** Replaces custom {@code TraceInterceptor} return values with placeholders. */
129+ static final class ReturnPatch extends MethodVisitor {
130+ private final AtomicBoolean modified ;
131+
132+ ReturnPatch (MethodVisitor mv , AtomicBoolean modified ) {
133+ super (ASM9 , mv );
134+ this .modified = modified ;
135+ }
136+
137+ @ Override
138+ public void visitInsn (int opcode ) {
139+ if (ARETURN == opcode ) {
140+ // discard old return value from call stack
141+ mv .visitInsn (POP );
142+ // push our placeholder interceptor instead
143+ mv .visitFieldInsn (
144+ GETSTATIC ,
145+ PLACEHOLDER_TRACE_INTERCEPTOR ,
146+ "INSTANCE" ,
147+ "L" + PLACEHOLDER_TRACE_INTERCEPTOR + ";" );
148+ mv .visitInsn (ARETURN );
149+ // flag that we've modified the bytecode
150+ modified .set (true );
151+ } else {
152+ super .visitInsn (opcode );
153+ }
154+ }
155+ }
105156}
0 commit comments