66
77package datadog .instrument .classinject ;
88
9+ import static java .lang .ClassLoader .getSystemClassLoader ;
910import static java .util .Collections .singletonMap ;
1011import static org .objectweb .asm .Opcodes .*;
1112
@@ -130,65 +131,122 @@ private static BiFunction classDefiner() {
130131 * @throws UnsupportedOperationException if class injection is not available
131132 */
132133 public static void enableClassInjection (Instrumentation inst ) {
133- if (defineClassGlue != null ) {
134- return ;
135- }
136- try {
137- InjectGlue injectGlue = new InjectGlue ();
134+ if (defineClassGlue == null ) {
138135 try {
139- // temporary transformation to install our glue to access 'defineClass'
140- inst . addTransformer ( injectGlue , true );
141- inst . retransformClasses ( Class . class );
142- defineClassGlue = ( BiFunction ) Class . forName ( DefineClassGlue . ID ). newInstance ();
143- } finally {
144- inst . removeTransformer ( injectGlue );
145- inst . retransformClasses ( Class . class );
136+ defineClassGlue = injectViaLoadClass ( inst ); // faster, non-static approach
137+ } catch ( Throwable ignore ) {
138+ try {
139+ defineClassGlue = injectViaForName ( inst ); // slower, static approach
140+ } catch ( Throwable e ) {
141+ throw new UnsupportedOperationException ( "Class injection not available" , e );
142+ }
146143 }
147- } catch (Throwable e ) {
148- throw new UnsupportedOperationException ("Class injection not available" , e );
149144 }
150145 }
151146
147+ /**
148+ * Instruments {@link ClassLoader#loadClass(String)} to inject glue to access 'defineClass'.
149+ *
150+ * @param inst the instrumentation instance
151+ * @return glue that can access 'defineClass'
152+ * @throws Exception if the instrumentation fails
153+ */
154+ private static BiFunction injectViaLoadClass (Instrumentation inst ) throws Exception {
155+ InjectGlue injectGlue = new InjectGlue ("java/lang/ClassLoader" , "loadClass" );
156+ try {
157+ inst .addTransformer (injectGlue , true );
158+ inst .retransformClasses (ClassLoader .class );
159+ return (BiFunction ) getSystemClassLoader ().loadClass (DefineClassGlue .ID ).newInstance ();
160+ } finally {
161+ // remove temporary ClassLoader.loadClass patch
162+ inst .removeTransformer (injectGlue );
163+ inst .retransformClasses (ClassLoader .class );
164+ }
165+ }
166+
167+ /**
168+ * Instruments {@link Class#forName(String)} to inject glue to access 'defineClass'.
169+ *
170+ * @param inst the instrumentation instance
171+ * @return glue that can access 'defineClass'
172+ * @throws Exception if the instrumentation fails
173+ */
174+ private static BiFunction injectViaForName (Instrumentation inst ) throws Exception {
175+ InjectGlue injectGlue = new InjectGlue ("java/lang/Class" , "forName" );
176+ try {
177+ inst .addTransformer (injectGlue , true );
178+ inst .retransformClasses (Class .class );
179+ return (BiFunction ) Class .forName (DefineClassGlue .ID ).newInstance ();
180+ } finally {
181+ // remove temporary Class.forName patch
182+ inst .removeTransformer (injectGlue );
183+ inst .retransformClasses (Class .class );
184+ }
185+ }
186+
187+ /** Uses instrumentation to inject glue to access 'defineClass'. */
152188 static final class InjectGlue implements ClassFileTransformer {
189+ private final String targetClass ;
190+ private final String targetMethod ;
191+
192+ /**
193+ * Patches the given method in the given class to load the 'defineClass' glue.
194+ *
195+ * @param targetClass the target class
196+ * @param targetMethod the target method
197+ */
198+ InjectGlue (String targetClass , String targetMethod ) {
199+ this .targetClass = targetClass ;
200+ this .targetMethod = targetMethod ;
201+ }
202+
153203 @ Override
154204 public byte [] transform (
155205 ClassLoader loader ,
156206 String className ,
157207 Class <?> classBeingRedefined ,
158208 ProtectionDomain protectionDomain ,
159209 byte [] bytecode ) {
160- if ("java/lang/Class" .equals (className )) {
210+ if (targetClass .equals (className )) {
161211 ClassReader cr = new ClassReader (bytecode );
162212 ClassWriter cw = new ClassWriter (cr , 0 );
163- cr .accept (new ClassPatch (cw ), 0 );
213+ cr .accept (new ClassPatch (cw , targetMethod ), 0 );
164214 return cw .toByteArray ();
165215 } else {
166216 return null ;
167217 }
168218 }
169219 }
170220
221+ /** Patches a class that declares a method that loads classes by name. */
171222 static final class ClassPatch extends ClassVisitor {
172- ClassPatch (ClassVisitor cv ) {
223+ private final String targetMethod ;
224+
225+ ClassPatch (ClassVisitor cv , String targetMethod ) {
173226 super (ASM9 , cv );
227+ this .targetMethod = targetMethod ;
174228 }
175229
176230 @ Override
177231 public MethodVisitor visitMethod (
178232 int access , String name , String descriptor , String signature , String [] exceptions ) {
179233 MethodVisitor mv = cv .visitMethod (access , name , descriptor , signature , exceptions );
180- if (( access & ACC_STATIC ) != 0
181- && "forName" . equals ( name )
182- && "(Ljava/lang/String; )Ljava/lang/Class;". equals ( descriptor )) {
183- return new ForNamePatch (mv );
234+ if (targetMethod . equals ( name )
235+ && descriptor . startsWith ( "(Ljava/lang/String;" )
236+ && descriptor . endsWith ( " )Ljava/lang/Class;" )) {
237+ return new MethodPatch (mv , access );
184238 }
185239 return mv ;
186240 }
187241 }
188242
189- static final class ForNamePatch extends MethodVisitor {
190- ForNamePatch (MethodVisitor mv ) {
243+ /** Patches a method that takes a class-name as the first argument and returns a class. */
244+ static final class MethodPatch extends MethodVisitor {
245+ private final int access ;
246+
247+ MethodPatch (MethodVisitor mv , int access ) {
191248 super (ASM9 , mv );
249+ this .access = access ;
192250 }
193251
194252 @ Override
@@ -197,9 +255,10 @@ public void visitCode() {
197255
198256 Label notDatadogGlueRequest = new Label ();
199257
200- // add branch at start of Class.forName method to define our glue as a hidden/anonymous class
258+ // add branch at start of the method to define our glue as a hidden/anonymous class
201259 mv .visitLdcInsn (DefineClassGlue .ID );
202- mv .visitVarInsn (ALOAD , 0 );
260+ // first argument in var 0 for static methods, otherwise 1
261+ mv .visitVarInsn (ALOAD , (access & ACC_STATIC ) != 0 ? 0 : 1 );
203262 mv .visitMethodInsn (
204263 INVOKEVIRTUAL , "java/lang/String" , "equals" , "(Ljava/lang/Object;)Z" , false );
205264 mv .visitJumpInsn (IFEQ , notDatadogGlueRequest );
@@ -277,7 +336,7 @@ public void visitCode() {
277336
278337 mv .visitInsn (ARETURN );
279338
280- // otherwise this is a standard Class.forName request, handle it as before
339+ // otherwise this is a standard request, handle it as before
281340 mv .visitLabel (notDatadogGlueRequest );
282341 mv .visitFrame (F_SAME , 0 , null , 0 , null );
283342 }
0 commit comments