diff --git a/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelExtensionHandler.java b/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelExtensionHandler.java index 06a6f316882..4411a342c40 100644 --- a/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelExtensionHandler.java +++ b/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelExtensionHandler.java @@ -1,10 +1,16 @@ package datadog.opentelemetry.tooling; import datadog.trace.agent.tooling.ExtensionHandler; +import datadog.trace.agent.tooling.muzzle.Reference; +import datadog.trace.agent.tooling.muzzle.ReferenceMatcher; import java.net.URL; import java.net.URLConnection; import java.util.jar.JarEntry; import java.util.jar.JarFile; +import net.bytebuddy.jar.asm.ClassWriter; +import net.bytebuddy.jar.asm.MethodVisitor; +import net.bytebuddy.jar.asm.Opcodes; +import net.bytebuddy.jar.asm.Type; /** Handles OpenTelemetry instrumentations, so they can be loaded into the Datadog tracer. */ public final class OtelExtensionHandler extends ExtensionHandler { @@ -23,6 +29,8 @@ public JarEntry mapEntry(JarFile jar, String file) { if (DATADOG_MODULE_DESCRIPTOR.equals(file)) { // redirect request to include OpenTelemetry instrumentations return super.mapEntry(jar, OPENTELEMETRY_MODULE_DESCRIPTOR); + } else if (file.endsWith("$Muzzle.class")) { + return new JarEntry(file); // pretend we have a static Muzzle class } else { return super.mapEntry(jar, file); } @@ -30,10 +38,64 @@ public JarEntry mapEntry(JarFile jar, String file) { @Override public URLConnection mapContent(URL url, JarFile jar, JarEntry entry) { - if (entry.getName().endsWith(".class")) { + String file = entry.getName(); + if (file.endsWith("$Muzzle.class")) { + return new EmptyMuzzleConnection(url); // generate an empty static Muzzle class + } else if (file.endsWith(".class")) { return new ClassMappingConnection(url, jar, entry, OtelInstrumentationMapper::new); } else { return new JarFileConnection(url, jar, entry); } } + + /** Generates an empty static muzzle class for OpenTelemetry instrumentations. */ + static final class EmptyMuzzleConnection extends ClassMappingConnection { + + private static final String REFERENCE_MATCHER_CLASS = + Type.getInternalName(ReferenceMatcher.class); + + private static final String REFERENCE_CLASS = Type.getInternalName(Reference.class); + + public EmptyMuzzleConnection(URL url) { + super(url, null, null, null); + } + + @Override + protected byte[] doMapBytecode(String unused) { + String file = url.getFile(); + // remove .class suffix and optional forward-slash prefix + String className = file.substring(file.charAt(0) == '/' ? 1 : 0, file.length() - 6); + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + cw.visit( + Opcodes.V1_8, + Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, + className, + null, + "java/lang/Object", + null); + MethodVisitor mv = + cw.visitMethod( + Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, + "create", + "()L" + REFERENCE_MATCHER_CLASS + ";", + null, + null); + mv.visitCode(); + mv.visitTypeInsn(Opcodes.NEW, REFERENCE_MATCHER_CLASS); + mv.visitInsn(Opcodes.DUP); + mv.visitInsn(Opcodes.ICONST_0); + mv.visitTypeInsn(Opcodes.ANEWARRAY, REFERENCE_CLASS); + mv.visitMethodInsn( + Opcodes.INVOKESPECIAL, + REFERENCE_MATCHER_CLASS, + "", + "([L" + REFERENCE_CLASS + ";)V", + false); + mv.visitInsn(Opcodes.ARETURN); + mv.visitMaxs(0, 0); + mv.visitEnd(); + cw.visitEnd(); + return cw.toByteArray(); + } + } } diff --git a/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelInstrumentationMapper.java b/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelInstrumentationMapper.java index 80b84df839f..ae4638d0af5 100644 --- a/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelInstrumentationMapper.java +++ b/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelInstrumentationMapper.java @@ -24,9 +24,6 @@ public final class OtelInstrumentationMapper extends ClassRemapper { Collections.singleton( "io/opentelemetry/javaagent/tooling/muzzle/InstrumentationModuleMuzzle"); - private static final Set UNSUPPORTED_METHODS = - Collections.singleton("getMuzzleReferences"); - public OtelInstrumentationMapper(ClassVisitor classVisitor) { super(classVisitor, Renamer.INSTANCE); } @@ -47,15 +44,6 @@ public void visit( super.visit(version, access, name, signature, superName, removeUnsupportedTypes(interfaces)); } - @Override - public MethodVisitor visitMethod( - int access, String name, String descriptor, String signature, String[] exceptions) { - if (UNSUPPORTED_METHODS.contains(name)) { - return null; // remove unsupported method - } - return super.visitMethod(access, name, descriptor, signature, exceptions); - } - private String[] removeUnsupportedTypes(String[] interfaces) { List filtered = null; for (int i = interfaces.length - 1; i >= 0; i--) { @@ -75,6 +63,8 @@ static final class Renamer extends Remapper { private static final String OTEL_JAVAAGENT_SHADED_PREFIX = "io/opentelemetry/javaagent/shaded/io/opentelemetry/"; + private static final String ASM_PREFIX = "org/objectweb/asm/"; + /** Datadog equivalent of OpenTelemetry instrumentation classes. */ private static final Map RENAMED_TYPES = new HashMap<>(); @@ -97,6 +87,30 @@ static final class Renamer extends Remapper { RENAMED_TYPES.put( "io/opentelemetry/javaagent/shaded/instrumentation/api/util/VirtualField", Type.getInternalName(ContextStore.class)); + RENAMED_TYPES.put( + "io/opentelemetry/javaagent/tooling/muzzle/references/ClassRefBuilder", + Type.getInternalName(OtelMuzzleRefBuilder.class)); + RENAMED_TYPES.put( + "io/opentelemetry/javaagent/tooling/muzzle/references/ClassRef", + Type.getInternalName(OtelMuzzleRefBuilder.ClassRef.class)); + RENAMED_TYPES.put( + "io/opentelemetry/javaagent/tooling/muzzle/references/Flag", + Type.getInternalName(OtelMuzzleRefBuilder.Flag.class)); + RENAMED_TYPES.put( + "io/opentelemetry/javaagent/tooling/muzzle/references/Flag$VisibilityFlag", + Type.getInternalName(OtelMuzzleRefBuilder.Flag.class)); + RENAMED_TYPES.put( + "io/opentelemetry/javaagent/tooling/muzzle/references/Flag$MinimumVisibilityFlag", + Type.getInternalName(OtelMuzzleRefBuilder.Flag.class)); + RENAMED_TYPES.put( + "io/opentelemetry/javaagent/tooling/muzzle/references/Flag$ManifestationFlag", + Type.getInternalName(OtelMuzzleRefBuilder.Flag.class)); + RENAMED_TYPES.put( + "io/opentelemetry/javaagent/tooling/muzzle/references/Flag$OwnershipFlag", + Type.getInternalName(OtelMuzzleRefBuilder.Flag.class)); + RENAMED_TYPES.put( + "io/opentelemetry/javaagent/tooling/muzzle/references/Source", + Type.getInternalName(OtelMuzzleRefBuilder.Source.class)); RENAMED_TYPES.put( "io/opentelemetry/javaagent/bootstrap/Java8BytecodeBridge", "datadog/trace/bootstrap/otel/Java8BytecodeBridge"); @@ -113,6 +127,10 @@ public String map(String internalName) { return "datadog/trace/bootstrap/otel/" + internalName.substring(OTEL_JAVAAGENT_SHADED_PREFIX.length()); } + // map unshaded ASM types to the shaded copy in byte-buddy + if (internalName.startsWith(ASM_PREFIX)) { + return "net/bytebuddy/jar/asm/" + internalName.substring(ASM_PREFIX.length()); + } return MAP_LOGGING.apply(internalName); } } diff --git a/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelInstrumenterModule.java b/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelInstrumenterModule.java index 2477518cc22..52f74215432 100644 --- a/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelInstrumenterModule.java +++ b/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelInstrumenterModule.java @@ -1,12 +1,16 @@ package datadog.opentelemetry.tooling; +import datadog.opentelemetry.tooling.OtelMuzzleRefBuilder.ClassRef; import datadog.trace.agent.tooling.InstrumenterModule; +import datadog.trace.agent.tooling.muzzle.Reference; +import datadog.trace.agent.tooling.muzzle.ReferenceProvider; import datadog.trace.api.InstrumenterConfig; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import net.bytebuddy.pool.TypePool; /** * Replaces OpenTelemetry's {@code InstrumentationModule} when mapping extensions. @@ -36,8 +40,17 @@ private static String[] namespace(String[] names) { return namespaced; } + private volatile String[] helperClassNames; + @Override public String[] helperClassNames() { + if (null == helperClassNames) { + helperClassNames = buildHelperClassNames(); + } + return helperClassNames; + } + + private String[] buildHelperClassNames() { List helperClassNames; List additionalClassNames = getAdditionalHelperClassNames(); if (additionalClassNames.isEmpty()) { @@ -85,4 +98,29 @@ public void registerMuzzleVirtualFields(VirtualFieldBuilder builder) {} public interface VirtualFieldBuilder { VirtualFieldBuilder register(String typeName, String fieldTypeName); } + + @Override + public ReferenceProvider runtimeMuzzleReferences() { + return new ReferenceProvider() { + private volatile Iterable muzzleReferences; + + @Override + @SuppressWarnings({"unchecked", "rawtypes"}) + public Iterable buildReferences(TypePool ignored) { + if (null == muzzleReferences) { + Map muzzleMap = getMuzzleReferences(); + for (String helper : helperClassNames()) { + muzzleMap.remove(helper); + } + muzzleReferences = (Iterable) muzzleMap.values(); + } + return muzzleReferences; + } + }; + } + + /** This method is generated by OpenTelemetry's muzzle plugin at build time. */ + public Map getMuzzleReferences() { + return Collections.emptyMap(); + } } diff --git a/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelMuzzleRefBuilder.java b/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelMuzzleRefBuilder.java new file mode 100644 index 00000000000..88bc87e0370 --- /dev/null +++ b/dd-java-agent/agent-otel/otel-tooling/src/main/java/datadog/opentelemetry/tooling/OtelMuzzleRefBuilder.java @@ -0,0 +1,138 @@ +package datadog.opentelemetry.tooling; + +import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_INTERFACE; +import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_NON_FINAL; +import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_NON_INTERFACE; +import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_NON_PRIVATE; +import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_NON_STATIC; +import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_PUBLIC; +import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_PUBLIC_OR_PROTECTED; +import static datadog.trace.agent.tooling.muzzle.Reference.EXPECTS_STATIC; + +import datadog.trace.agent.tooling.muzzle.Reference; +import java.util.Collection; +import net.bytebuddy.jar.asm.Type; + +/** Maps OpenTelemetry muzzle references to the Datadog equivalent. */ +public final class OtelMuzzleRefBuilder { + private final Reference.Builder builder; + + OtelMuzzleRefBuilder(String className) { + this.builder = new Reference.Builder(className); + } + + public OtelMuzzleRefBuilder setSuperClassName(String superName) { + builder.withSuperName(superName); + return this; + } + + public OtelMuzzleRefBuilder addInterfaceNames(Collection interfaceNames) { + for (String interfaceName : interfaceNames) { + builder.withInterface(interfaceName); + } + return this; + } + + public OtelMuzzleRefBuilder addInterfaceName(String interfaceName) { + builder.withInterface(interfaceName); + return this; + } + + public OtelMuzzleRefBuilder addSource(String sourceName) { + builder.withSource(sourceName, 0); + return this; + } + + public OtelMuzzleRefBuilder addSource(String sourceName, int line) { + builder.withSource(sourceName, line); + return this; + } + + public OtelMuzzleRefBuilder addFlag(Flag flag) { + builder.withFlag(flag.bit); + return this; + } + + public OtelMuzzleRefBuilder addField( + Source[] sources, Flag[] flags, String fieldName, Type fieldType, boolean isDeclared) { + builder.withField(Source.flatten(sources), Flag.flatten(flags), fieldName, fieldType); + return this; + } + + public OtelMuzzleRefBuilder addMethod( + Source[] sources, Flag[] flags, String methodName, Type returnType, Type... argumentTypes) { + builder.withMethod( + Source.flatten(sources), Flag.flatten(flags), methodName, returnType, argumentTypes); + return this; + } + + public ClassRef build() { + return new ClassRef(builder.build()); + } + + public static final class ClassRef extends Reference { + public ClassRef(Reference ref) { + super( + ref.sources, + ref.flags, + ref.className, + ref.superName, + ref.interfaces, + ref.fields, + ref.methods); + } + + public static OtelMuzzleRefBuilder builder(String className) { + return new OtelMuzzleRefBuilder(className); + } + } + + public enum Flag { + PUBLIC(EXPECTS_PUBLIC), + PROTECTED_OR_HIGHER(EXPECTS_PUBLIC_OR_PROTECTED), + PROTECTED(EXPECTS_PUBLIC_OR_PROTECTED), + PACKAGE_OR_HIGHER(EXPECTS_NON_PRIVATE), + PACKAGE(EXPECTS_NON_PRIVATE), + PRIVATE_OR_HIGHER(0), + PRIVATE(0), + ABSTRACT(0), + FINAL(0), + NON_FINAL(EXPECTS_NON_FINAL), + INTERFACE(EXPECTS_INTERFACE), + NON_INTERFACE(EXPECTS_NON_INTERFACE), + STATIC(EXPECTS_STATIC), + NON_STATIC(EXPECTS_NON_STATIC); + + final int bit; + + Flag(int bit) { + this.bit = bit; + } + + public static int flatten(Flag[] flags) { + int bits = 0; + for (Flag flag : flags) { + bits |= flag.bit; + } + return bits; + } + } + + public static final class Source { + final String name; + final int line; + + public Source(String name, int line) { + this.name = name; + this.line = line; + } + + public static String[] flatten(Source[] sources) { + String[] locations = new String[sources.length]; + for (int i = 0; i < sources.length; i++) { + locations[i] = sources[i].name + ":" + sources[i].line; + } + return locations; + } + } +} diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ExtensionHandler.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ExtensionHandler.java index 9ffcb36dc3a..1451debd20c 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ExtensionHandler.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/ExtensionHandler.java @@ -111,7 +111,7 @@ private byte[] mapBytecode() { return BYTECODE_CACHE.computeIfAbsent(url.getFile(), this::doMapBytecode); } - private byte[] doMapBytecode(String unused) { + protected byte[] doMapBytecode(String unused) { try (InputStream in = super.getInputStream()) { ClassReader cr = new ClassReader(in); ClassWriter cw = new ClassWriter(cr, 0); diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleCheck.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleCheck.java index 75524197516..2544e2732c8 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleCheck.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/muzzle/MuzzleCheck.java @@ -2,6 +2,7 @@ import datadog.trace.agent.tooling.InstrumenterModule; import datadog.trace.agent.tooling.InstrumenterState; +import datadog.trace.agent.tooling.Utils; import java.util.List; import net.bytebuddy.matcher.ElementMatcher; import org.slf4j.Logger; @@ -55,7 +56,7 @@ private ReferenceMatcher muzzle() { if (null == muzzle) { muzzle = InstrumenterModule.loadStaticMuzzleReferences( - getClass().getClassLoader(), instrumentationClass) + Utils.getExtendedClassLoader(), instrumentationClass) .withReferenceProvider(runtimeMuzzleReferences); } return muzzle;