Skip to content

Commit d097e7b

Browse files
authored
Use ClassLoader.loadClass first then fall back to Class.forName when enabling class-injection (#30)
1 parent 522c1f9 commit d097e7b

File tree

1 file changed

+85
-26
lines changed

1 file changed

+85
-26
lines changed

class-inject/src/main/java/datadog/instrument/classinject/ClassInjector.java

Lines changed: 85 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
package datadog.instrument.classinject;
88

9+
import static java.lang.ClassLoader.getSystemClassLoader;
910
import static java.util.Collections.singletonMap;
1011
import 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

Comments
 (0)