Skip to content

Commit 1c33411

Browse files
authored
Updates j.l.ClassLoader to ensure superclasses and interfaces are loaded before classes that extend/implement them. (#6677)
1 parent aee3ca5 commit 1c33411

5 files changed

Lines changed: 136 additions & 0 deletions

File tree

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package datadog.trace.bootstrap.instrumentation.classloading;
2+
3+
/** Provides a way for a single optional observer to be notified before a class is defined. */
4+
public final class ClassDefining {
5+
private static volatile Observer OBSERVER = (loader, bytecode, offset, length) -> {};
6+
7+
/** Registers the given observer to get notifications about class definitions. */
8+
public static void observe(Observer observer) {
9+
OBSERVER = observer;
10+
}
11+
12+
/** Called from advice added to j.l.ClassLoader by DefineClassInstrumentation. */
13+
public static void begin(ClassLoader loader, byte[] bytecode, int offset, int length) {
14+
OBSERVER.beforeDefineClass(loader, bytecode, offset, length);
15+
}
16+
17+
/** Observer of class definitions. */
18+
public interface Observer {
19+
void beforeDefineClass(ClassLoader loader, byte[] bytecode, int offset, int length);
20+
}
21+
}

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/memoize/MemoizedMatchers.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
/** Supplies memoized matchers. */
2424
public final class MemoizedMatchers implements HierarchyMatchers.Supplier {
2525
public static void registerAsSupplier() {
26+
PreloadHierarchy.observeClassDefinitions();
2627
HierarchyMatchers.registerIfAbsent(new MemoizedMatchers());
2728
Memoizer.resetState();
2829
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package datadog.trace.agent.tooling.bytebuddy.memoize;
2+
3+
import datadog.trace.bootstrap.instrumentation.classloading.ClassDefining;
4+
import net.bytebuddy.jar.asm.ClassReader;
5+
6+
/** Ensures superclasses and interfaces are loaded before classes that extend/implement them. */
7+
final class PreloadHierarchy implements ClassDefining.Observer {
8+
private static final PreloadHierarchy PRELOADER = new PreloadHierarchy();
9+
10+
static void observeClassDefinitions() {
11+
ClassDefining.observe(PRELOADER);
12+
}
13+
14+
private static final int RECENTLY_CHECKED_MASK = (1 << 5) - 1;
15+
private final String[] recentlyChecked = new String[RECENTLY_CHECKED_MASK + 1];
16+
17+
@Override
18+
public void beforeDefineClass(ClassLoader loader, byte[] bytecode, int offset, int length) {
19+
try {
20+
// check first byte matches the standard class header
21+
if (bytecode[offset] != (byte) 0xCA) {
22+
return; // ignore non-standard formats like J9 ROMs
23+
}
24+
// minimal parsing of bytecode to get name of superclass and any interfaces
25+
ClassReader cr = new ClassReader(bytecode, offset, length);
26+
String superName = cr.getSuperName();
27+
if (null != superName && !"java/lang/Object".equals(superName)) {
28+
preload(loader, superName);
29+
}
30+
for (String interfaceName : cr.getInterfaces()) {
31+
preload(loader, interfaceName);
32+
}
33+
} catch (Throwable ignore) {
34+
// stop preloading as soon as we encounter any issue
35+
}
36+
}
37+
38+
/** Attempts to preload the named class using same class-loader as the original request. */
39+
private void preload(ClassLoader loader, String internalName) throws ClassNotFoundException {
40+
int slot = internalName.hashCode() & RECENTLY_CHECKED_MASK;
41+
if (!internalName.equals(recentlyChecked[slot])) {
42+
recentlyChecked[slot] = internalName;
43+
String name = internalName.replace('/', '.');
44+
// take advantage of no-match filter to avoid preloading known uninteresting classes
45+
if (!NoMatchFilter.contains(name)) {
46+
loader.loadClass(name);
47+
}
48+
}
49+
}
50+
}

dd-java-agent/agent-tooling/src/main/resources/datadog/trace/agent/tooling/bytebuddy/matcher/ignored_class_name.trie

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
1 io.micronaut.tracing.*
4242
1 io.opentelemetry.javaagent.*
4343
1 java.*
44+
0 java.lang.ClassLoader
4445
# allow exception profiling instrumentation
4546
0 java.lang.Exception
4647
0 java.lang.Error
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package datadog.trace.instrumentation.classloading;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
5+
import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
6+
7+
import com.google.auto.service.AutoService;
8+
import datadog.trace.agent.tooling.Instrumenter;
9+
import datadog.trace.agent.tooling.InstrumenterModule;
10+
import datadog.trace.api.InstrumenterConfig;
11+
import datadog.trace.bootstrap.DatadogClassLoader;
12+
import datadog.trace.bootstrap.instrumentation.classloading.ClassDefining;
13+
import java.security.ProtectionDomain;
14+
import net.bytebuddy.asm.Advice;
15+
16+
/** Updates j.l.ClassLoader to notify the tracer when classes are about to be defined. */
17+
@AutoService(Instrumenter.class)
18+
public final class DefineClassInstrumentation extends InstrumenterModule.Tracing
19+
implements Instrumenter.ForBootstrap, Instrumenter.ForSingleType {
20+
public DefineClassInstrumentation() {
21+
super("defineclass");
22+
}
23+
24+
@Override
25+
public boolean isEnabled() {
26+
// only enable this when memoizing type hierarchies, where it provides the most ROI
27+
return super.isEnabled() && InstrumenterConfig.get().isResolverMemoizingEnabled();
28+
}
29+
30+
@Override
31+
public String instrumentedType() {
32+
return "java.lang.ClassLoader";
33+
}
34+
35+
@Override
36+
public void methodAdvice(MethodTransformer transformer) {
37+
transformer.applyAdvice(
38+
isMethod()
39+
.and(
40+
named("defineClass")
41+
.and(
42+
takesArguments(
43+
String.class,
44+
byte[].class,
45+
int.class,
46+
int.class,
47+
ProtectionDomain.class))),
48+
DefineClassInstrumentation.class.getName() + "$DefineClassAdvice");
49+
}
50+
51+
public static class DefineClassAdvice {
52+
@Advice.OnMethodEnter(suppress = Throwable.class)
53+
public static void onEnter(
54+
@Advice.This ClassLoader loader,
55+
@Advice.Argument(1) byte[] bytecode,
56+
@Advice.Argument(2) int offset,
57+
@Advice.Argument(3) int length) {
58+
if (null != loader && loader.getClass() != DatadogClassLoader.class) {
59+
ClassDefining.begin(loader, bytecode, offset, length);
60+
}
61+
}
62+
}
63+
}

0 commit comments

Comments
 (0)