Skip to content

Commit 89ede03

Browse files
committed
Map OpenTelemetry muzzle references to Datadog equivalent (#7142)
* Use extended class-loader to lookup static Muzzle classes * Generate empty static Muzzle classes for OpenTelemetry extensions (they will contribute Muzzle references via the dynamic provider) * Retain original getMuzzleReferences method * Map unshaded ASM package to the shaded copy in byte-buddy * Provide Datadog equivalent of OpenTelemetry ClassRefBuilder * Map OpenTelemetry ClassRefBuilder to OtelMuzzleRefBuilder and related types * Lazily build OpenTelemetry muzzle references * Remove injected helper classes from OpenTelemetry's generated muzzle map
1 parent 2c7012c commit 89ede03

File tree

6 files changed

+272
-15
lines changed

6 files changed

+272
-15
lines changed

dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelExtensionHandler.java

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package datadog.opentelemetry.tooling;
22

33
import datadog.trace.agent.tooling.ExtensionHandler;
4+
import datadog.trace.agent.tooling.muzzle.Reference;
5+
import datadog.trace.agent.tooling.muzzle.ReferenceMatcher;
46
import java.net.URL;
57
import java.net.URLConnection;
68
import java.util.jar.JarEntry;
79
import java.util.jar.JarFile;
10+
import net.bytebuddy.jar.asm.ClassWriter;
11+
import net.bytebuddy.jar.asm.MethodVisitor;
12+
import net.bytebuddy.jar.asm.Opcodes;
13+
import net.bytebuddy.jar.asm.Type;
814

915
/** Handles OpenTelemetry instrumentations, so they can be loaded into the Datadog tracer. */
1016
public final class OtelExtensionHandler extends ExtensionHandler {
@@ -23,17 +29,73 @@ public JarEntry mapEntry(JarFile jar, String file) {
2329
if (DATADOG_MODULE_DESCRIPTOR.equals(file)) {
2430
// redirect request to include OpenTelemetry instrumentations
2531
return super.mapEntry(jar, OPENTELEMETRY_MODULE_DESCRIPTOR);
32+
} else if (file.endsWith("$Muzzle.class")) {
33+
return new JarEntry(file); // pretend we have a static Muzzle class
2634
} else {
2735
return super.mapEntry(jar, file);
2836
}
2937
}
3038

3139
@Override
3240
public URLConnection mapContent(URL url, JarFile jar, JarEntry entry) {
33-
if (entry.getName().endsWith(".class")) {
41+
String file = entry.getName();
42+
if (file.endsWith("$Muzzle.class")) {
43+
return new EmptyMuzzleConnection(url); // generate an empty static Muzzle class
44+
} else if (file.endsWith(".class")) {
3445
return new ClassMappingConnection(url, jar, entry, OtelInstrumentationMapper::new);
3546
} else {
3647
return new JarFileConnection(url, jar, entry);
3748
}
3849
}
50+
51+
/** Generates an empty static muzzle class for OpenTelemetry instrumentations. */
52+
static final class EmptyMuzzleConnection extends ClassMappingConnection {
53+
54+
private static final String REFERENCE_MATCHER_CLASS =
55+
Type.getInternalName(ReferenceMatcher.class);
56+
57+
private static final String REFERENCE_CLASS = Type.getInternalName(Reference.class);
58+
59+
public EmptyMuzzleConnection(URL url) {
60+
super(url, null, null, null);
61+
}
62+
63+
@Override
64+
protected byte[] doMapBytecode(String unused) {
65+
String file = url.getFile();
66+
// remove .class suffix and optional forward-slash prefix
67+
String className = file.substring(file.charAt(0) == '/' ? 1 : 0, file.length() - 6);
68+
ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
69+
cw.visit(
70+
Opcodes.V1_8,
71+
Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL,
72+
className,
73+
null,
74+
"java/lang/Object",
75+
null);
76+
MethodVisitor mv =
77+
cw.visitMethod(
78+
Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC,
79+
"create",
80+
"()L" + REFERENCE_MATCHER_CLASS + ";",
81+
null,
82+
null);
83+
mv.visitCode();
84+
mv.visitTypeInsn(Opcodes.NEW, REFERENCE_MATCHER_CLASS);
85+
mv.visitInsn(Opcodes.DUP);
86+
mv.visitInsn(Opcodes.ICONST_0);
87+
mv.visitTypeInsn(Opcodes.ANEWARRAY, REFERENCE_CLASS);
88+
mv.visitMethodInsn(
89+
Opcodes.INVOKESPECIAL,
90+
REFERENCE_MATCHER_CLASS,
91+
"<init>",
92+
"([L" + REFERENCE_CLASS + ";)V",
93+
false);
94+
mv.visitInsn(Opcodes.ARETURN);
95+
mv.visitMaxs(0, 0);
96+
mv.visitEnd();
97+
cw.visitEnd();
98+
return cw.toByteArray();
99+
}
100+
}
39101
}

dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelInstrumentationMapper.java

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,6 @@ public final class OtelInstrumentationMapper extends ClassRemapper {
2424
Collections.singleton(
2525
"io/opentelemetry/javaagent/tooling/muzzle/InstrumentationModuleMuzzle");
2626

27-
private static final Set<String> UNSUPPORTED_METHODS =
28-
Collections.singleton("getMuzzleReferences");
29-
3027
public OtelInstrumentationMapper(ClassVisitor classVisitor) {
3128
super(classVisitor, Renamer.INSTANCE);
3229
}
@@ -47,15 +44,6 @@ public void visit(
4744
super.visit(version, access, name, signature, superName, removeUnsupportedTypes(interfaces));
4845
}
4946

50-
@Override
51-
public MethodVisitor visitMethod(
52-
int access, String name, String descriptor, String signature, String[] exceptions) {
53-
if (UNSUPPORTED_METHODS.contains(name)) {
54-
return null; // remove unsupported method
55-
}
56-
return super.visitMethod(access, name, descriptor, signature, exceptions);
57-
}
58-
5947
private String[] removeUnsupportedTypes(String[] interfaces) {
6048
List<String> filtered = null;
6149
for (int i = interfaces.length - 1; i >= 0; i--) {
@@ -75,6 +63,8 @@ static final class Renamer extends Remapper {
7563
private static final String OTEL_JAVAAGENT_SHADED_PREFIX =
7664
"io/opentelemetry/javaagent/shaded/io/opentelemetry/";
7765

66+
private static final String ASM_PREFIX = "org/objectweb/asm/";
67+
7868
/** Datadog equivalent of OpenTelemetry instrumentation classes. */
7969
private static final Map<String, String> RENAMED_TYPES = new HashMap<>();
8070

@@ -97,6 +87,30 @@ static final class Renamer extends Remapper {
9787
RENAMED_TYPES.put(
9888
"io/opentelemetry/javaagent/shaded/instrumentation/api/util/VirtualField",
9989
Type.getInternalName(ContextStore.class));
90+
RENAMED_TYPES.put(
91+
"io/opentelemetry/javaagent/tooling/muzzle/references/ClassRefBuilder",
92+
Type.getInternalName(OtelMuzzleRefBuilder.class));
93+
RENAMED_TYPES.put(
94+
"io/opentelemetry/javaagent/tooling/muzzle/references/ClassRef",
95+
Type.getInternalName(OtelMuzzleRefBuilder.ClassRef.class));
96+
RENAMED_TYPES.put(
97+
"io/opentelemetry/javaagent/tooling/muzzle/references/Flag",
98+
Type.getInternalName(OtelMuzzleRefBuilder.Flag.class));
99+
RENAMED_TYPES.put(
100+
"io/opentelemetry/javaagent/tooling/muzzle/references/Flag$VisibilityFlag",
101+
Type.getInternalName(OtelMuzzleRefBuilder.Flag.class));
102+
RENAMED_TYPES.put(
103+
"io/opentelemetry/javaagent/tooling/muzzle/references/Flag$MinimumVisibilityFlag",
104+
Type.getInternalName(OtelMuzzleRefBuilder.Flag.class));
105+
RENAMED_TYPES.put(
106+
"io/opentelemetry/javaagent/tooling/muzzle/references/Flag$ManifestationFlag",
107+
Type.getInternalName(OtelMuzzleRefBuilder.Flag.class));
108+
RENAMED_TYPES.put(
109+
"io/opentelemetry/javaagent/tooling/muzzle/references/Flag$OwnershipFlag",
110+
Type.getInternalName(OtelMuzzleRefBuilder.Flag.class));
111+
RENAMED_TYPES.put(
112+
"io/opentelemetry/javaagent/tooling/muzzle/references/Source",
113+
Type.getInternalName(OtelMuzzleRefBuilder.Source.class));
100114
RENAMED_TYPES.put(
101115
"io/opentelemetry/javaagent/bootstrap/Java8BytecodeBridge",
102116
"datadog/trace/bootstrap/otel/Java8BytecodeBridge");
@@ -113,6 +127,10 @@ public String map(String internalName) {
113127
return "datadog/trace/bootstrap/otel/"
114128
+ internalName.substring(OTEL_JAVAAGENT_SHADED_PREFIX.length());
115129
}
130+
// map unshaded ASM types to the shaded copy in byte-buddy
131+
if (internalName.startsWith(ASM_PREFIX)) {
132+
return "net/bytebuddy/jar/asm/" + internalName.substring(ASM_PREFIX.length());
133+
}
116134
return MAP_LOGGING.apply(internalName);
117135
}
118136
}

dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelInstrumenterModule.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package datadog.opentelemetry.tooling;
22

3+
import datadog.opentelemetry.tooling.OtelMuzzleRefBuilder.ClassRef;
34
import datadog.trace.agent.tooling.InstrumenterModule;
5+
import datadog.trace.agent.tooling.muzzle.Reference;
6+
import datadog.trace.agent.tooling.muzzle.ReferenceProvider;
47
import datadog.trace.api.InstrumenterConfig;
58
import java.util.ArrayList;
69
import java.util.Collections;
710
import java.util.HashMap;
811
import java.util.List;
912
import java.util.Map;
13+
import net.bytebuddy.pool.TypePool;
1014

1115
/**
1216
* Replaces OpenTelemetry's {@code InstrumentationModule} when mapping extensions.
@@ -36,8 +40,17 @@ private static String[] namespace(String[] names) {
3640
return namespaced;
3741
}
3842

43+
private volatile String[] helperClassNames;
44+
3945
@Override
4046
public String[] helperClassNames() {
47+
if (null == helperClassNames) {
48+
helperClassNames = buildHelperClassNames();
49+
}
50+
return helperClassNames;
51+
}
52+
53+
private String[] buildHelperClassNames() {
4154
List<String> helperClassNames;
4255
List<String> additionalClassNames = getAdditionalHelperClassNames();
4356
if (additionalClassNames.isEmpty()) {
@@ -85,4 +98,29 @@ public void registerMuzzleVirtualFields(VirtualFieldBuilder builder) {}
8598
public interface VirtualFieldBuilder {
8699
VirtualFieldBuilder register(String typeName, String fieldTypeName);
87100
}
101+
102+
@Override
103+
public ReferenceProvider runtimeMuzzleReferences() {
104+
return new ReferenceProvider() {
105+
private volatile Iterable<Reference> muzzleReferences;
106+
107+
@Override
108+
@SuppressWarnings({"unchecked", "rawtypes"})
109+
public Iterable<Reference> buildReferences(TypePool ignored) {
110+
if (null == muzzleReferences) {
111+
Map<String, ClassRef> muzzleMap = getMuzzleReferences();
112+
for (String helper : helperClassNames()) {
113+
muzzleMap.remove(helper);
114+
}
115+
muzzleReferences = (Iterable) muzzleMap.values();
116+
}
117+
return muzzleReferences;
118+
}
119+
};
120+
}
121+
122+
/** This method is generated by OpenTelemetry's muzzle plugin at build time. */
123+
public Map<String, ClassRef> getMuzzleReferences() {
124+
return Collections.emptyMap();
125+
}
88126
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package datadog.opentelemetry.tooling;
2+
3+
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_INTERFACE;
4+
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_NON_FINAL;
5+
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_NON_INTERFACE;
6+
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_NON_PRIVATE;
7+
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_NON_STATIC;
8+
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_PUBLIC;
9+
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_PUBLIC_OR_PROTECTED;
10+
import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_STATIC;
11+
12+
import datadog.trace.agent.tooling.muzzle.Reference;
13+
import java.util.Collection;
14+
import net.bytebuddy.jar.asm.Type;
15+
16+
/** Maps OpenTelemetry muzzle references to the Datadog equivalent. */
17+
public final class OtelMuzzleRefBuilder {
18+
private final Reference.Builder builder;
19+
20+
OtelMuzzleRefBuilder(String className) {
21+
this.builder = new Reference.Builder(className);
22+
}
23+
24+
public OtelMuzzleRefBuilder setSuperClassName(String superName) {
25+
builder.withSuperName(superName);
26+
return this;
27+
}
28+
29+
public OtelMuzzleRefBuilder addInterfaceNames(Collection<String> interfaceNames) {
30+
for (String interfaceName : interfaceNames) {
31+
builder.withInterface(interfaceName);
32+
}
33+
return this;
34+
}
35+
36+
public OtelMuzzleRefBuilder addInterfaceName(String interfaceName) {
37+
builder.withInterface(interfaceName);
38+
return this;
39+
}
40+
41+
public OtelMuzzleRefBuilder addSource(String sourceName) {
42+
builder.withSource(sourceName, 0);
43+
return this;
44+
}
45+
46+
public OtelMuzzleRefBuilder addSource(String sourceName, int line) {
47+
builder.withSource(sourceName, line);
48+
return this;
49+
}
50+
51+
public OtelMuzzleRefBuilder addFlag(Flag flag) {
52+
builder.withFlag(flag.bit);
53+
return this;
54+
}
55+
56+
public OtelMuzzleRefBuilder addField(
57+
Source[] sources, Flag[] flags, String fieldName, Type fieldType, boolean isDeclared) {
58+
builder.withField(Source.flatten(sources), Flag.flatten(flags), fieldName, fieldType);
59+
return this;
60+
}
61+
62+
public OtelMuzzleRefBuilder addMethod(
63+
Source[] sources, Flag[] flags, String methodName, Type returnType, Type... argumentTypes) {
64+
builder.withMethod(
65+
Source.flatten(sources), Flag.flatten(flags), methodName, returnType, argumentTypes);
66+
return this;
67+
}
68+
69+
public ClassRef build() {
70+
return new ClassRef(builder.build());
71+
}
72+
73+
public static final class ClassRef extends Reference {
74+
public ClassRef(Reference ref) {
75+
super(
76+
ref.sources,
77+
ref.flags,
78+
ref.className,
79+
ref.superName,
80+
ref.interfaces,
81+
ref.fields,
82+
ref.methods);
83+
}
84+
85+
public static OtelMuzzleRefBuilder builder(String className) {
86+
return new OtelMuzzleRefBuilder(className);
87+
}
88+
}
89+
90+
public enum Flag {
91+
PUBLIC(EXPECTS_PUBLIC),
92+
PROTECTED_OR_HIGHER(EXPECTS_PUBLIC_OR_PROTECTED),
93+
PROTECTED(EXPECTS_PUBLIC_OR_PROTECTED),
94+
PACKAGE_OR_HIGHER(EXPECTS_NON_PRIVATE),
95+
PACKAGE(EXPECTS_NON_PRIVATE),
96+
PRIVATE_OR_HIGHER(0),
97+
PRIVATE(0),
98+
ABSTRACT(0),
99+
FINAL(0),
100+
NON_FINAL(EXPECTS_NON_FINAL),
101+
INTERFACE(EXPECTS_INTERFACE),
102+
NON_INTERFACE(EXPECTS_NON_INTERFACE),
103+
STATIC(EXPECTS_STATIC),
104+
NON_STATIC(EXPECTS_NON_STATIC);
105+
106+
final int bit;
107+
108+
Flag(int bit) {
109+
this.bit = bit;
110+
}
111+
112+
public static int flatten(Flag[] flags) {
113+
int bits = 0;
114+
for (Flag flag : flags) {
115+
bits |= flag.bit;
116+
}
117+
return bits;
118+
}
119+
}
120+
121+
public static final class Source {
122+
final String name;
123+
final int line;
124+
125+
public Source(String name, int line) {
126+
this.name = name;
127+
this.line = line;
128+
}
129+
130+
public static String[] flatten(Source[] sources) {
131+
String[] locations = new String[sources.length];
132+
for (int i = 0; i < sources.length; i++) {
133+
locations[i] = sources[i].name + ":" + sources[i].line;
134+
}
135+
return locations;
136+
}
137+
}
138+
}

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ExtensionHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ private byte[] mapBytecode() {
111111
return BYTECODE_CACHE.computeIfAbsent(url.getFile(), this::doMapBytecode);
112112
}
113113

114-
private byte[] doMapBytecode(String unused) {
114+
protected byte[] doMapBytecode(String unused) {
115115
try (InputStream in = super.getInputStream()) {
116116
ClassReader cr = new ClassReader(in);
117117
ClassWriter cw = new ClassWriter(cr, 0);

dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleCheck.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import datadog.trace.agent.tooling.InstrumenterModule;
44
import datadog.trace.agent.tooling.InstrumenterState;
5+
import datadog.trace.agent.tooling.Utils;
56
import java.util.List;
67
import net.bytebuddy.matcher.ElementMatcher;
78
import org.slf4j.Logger;
@@ -55,7 +56,7 @@ private ReferenceMatcher muzzle() {
5556
if (null == muzzle) {
5657
muzzle =
5758
InstrumenterModule.loadStaticMuzzleReferences(
58-
getClass().getClassLoader(), instrumentationClass)
59+
Utils.getExtendedClassLoader(), instrumentationClass)
5960
.withReferenceProvider(runtimeMuzzleReferences);
6061
}
6162
return muzzle;

0 commit comments

Comments
 (0)