From 2f91337f7b5f9f3606995aebc16e73f23be5a3f1 Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Wed, 1 Oct 2025 12:47:51 +0100 Subject: [PATCH 1/2] Simplify type outlines to not implement type.getDeclaringType() and type.getEnclosingType() These methods are not used for matching purposes, so don't need to be maintained in outlines. There is one use of type.isAnonymousType() in IAST, but this use is under a feature flag that is off by default. To satisfy that use we provide a simple isAnonymousType() implementation which is based on the '$number' convention for anonymous class names. Note: if anything does touch type.getDeclaringType() / type.getEnclosingType() later on when doing the final transformation then the outline type is automatically inflated to a full type. (This is already the case for other methods that aren't available in type outlines.) We also drop type.getClassFileVersion() from outlines for the same reason. --- .../bytebuddy/outline/OutlineTypeParser.java | 26 +---------- .../bytebuddy/outline/TypeFactory.java | 4 +- .../bytebuddy/outline/TypeOutline.java | 45 +++++-------------- 3 files changed, 15 insertions(+), 60 deletions(-) diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/OutlineTypeParser.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/OutlineTypeParser.java index 1cea5ffb864..798f1ffa005 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/OutlineTypeParser.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/OutlineTypeParser.java @@ -7,7 +7,6 @@ import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.lang.reflect.Method; -import net.bytebuddy.ClassFileVersion; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.jar.asm.AnnotationVisitor; import net.bytebuddy.jar.asm.ClassReader; @@ -34,17 +33,11 @@ public TypeDescription parse(Class loadedType) { TypeOutline typeOutline = new TypeOutline( - ClassFileVersion.ofThisVm().getMinorMajorVersion(), loadedType.getModifiers(), loadedType.getName(), null != superClass ? superClass.getName() : null, extractTypeNames(loadedType.getInterfaces())); - Class declaringClass = loadedType.getDeclaringClass(); - if (null != declaringClass) { - typeOutline.declaredBy(declaringClass.getName()); - } - for (Annotation a : loadedType.getDeclaredAnnotations()) { typeOutline.declare(annotationOutline(Type.getDescriptor(a.annotationType()))); } @@ -91,7 +84,6 @@ static final class OutlineTypeExtractor extends ClassVisitor { TypeOutline typeOutline; FieldOutline fieldOutline; MethodOutline methodOutline; - boolean selfContained = true; OutlineTypeExtractor() { super(OpenedClassReader.ASM_API); @@ -105,23 +97,7 @@ public void visit( String signature, String superName, String[] interfaces) { - typeOutline = new TypeOutline(version, access, name, superName, interfaces); - } - - @Override - public void visitOuterClass(String owner, String name, String descriptor) { - selfContained = false; - } - - @Override - public void visitInnerClass(String name, String outerName, String innerName, int access) { - if (typeOutline.getInternalName().equals(name)) { - if (null != outerName) { - typeOutline.declaredBy(outerName); - } else if (null == innerName && !selfContained) { - typeOutline.anonymousType(); - } - } + typeOutline = new TypeOutline(access, name, superName, interfaces); } @Override diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeFactory.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeFactory.java index 222ff7e3a91..765872f8300 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeFactory.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeFactory.java @@ -384,8 +384,8 @@ public TypeList.Generic getInterfaces() { } @Override - public TypeDescription getDeclaringType() { - return outline().getDeclaringType(); + public boolean isAnonymousType() { + return outline().isAnonymousType(); } @Override diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeOutline.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeOutline.java index de47e15a055..ba4f60933b2 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeOutline.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeOutline.java @@ -4,7 +4,6 @@ import java.util.ArrayList; import java.util.List; -import net.bytebuddy.ClassFileVersion; import net.bytebuddy.description.annotation.AnnotationDescription; import net.bytebuddy.description.annotation.AnnotationList; import net.bytebuddy.description.field.FieldDescription; @@ -27,21 +26,17 @@ final class TypeOutline extends WithName { private static final MethodList NO_METHODS = new MethodList.Empty<>(); - private final int classFileVersion; private final int modifiers; private final String superName; private final String[] interfaces; - private String declaringName; - private boolean anonymousType; private List declaredAnnotations; private final List declaredFields = new ArrayList<>(); private final List declaredMethods = new ArrayList<>(); - TypeOutline(int version, int access, String internalName, String superName, String[] interfaces) { + TypeOutline(int access, String internalName, String superName, String[] interfaces) { super(internalName.replace('/', '.')); - this.classFileVersion = version; this.modifiers = access & ALLOWED_TYPE_MODIFIERS; this.superName = superName; this.interfaces = interfaces; @@ -72,19 +67,6 @@ public TypeList.Generic getInterfaces() { return new TypeList.Generic.Explicit(outlines); } - @Override - public TypeDescription getDeclaringType() { - if (null != declaringName) { - return findType(declaringName.replace('/', '.')); - } - return null; - } - - @Override - public TypeDescription getEnclosingType() { - return getDeclaringType(); // equivalent for outline purposes - } - @Override public int getModifiers() { return modifiers; @@ -114,11 +96,6 @@ private boolean matchesMask(int mask) { return (this.getModifiers() & mask) == mask; } - @Override - public ClassFileVersion getClassFileVersion() { - return ClassFileVersion.ofMinorMajor(classFileVersion); - } - @Override public AnnotationList getDeclaredAnnotations() { return null == declaredAnnotations @@ -138,11 +115,17 @@ public MethodList getDeclaredMethods() { @Override public boolean isAnonymousType() { - return anonymousType; - } - - void declaredBy(String declaringName) { - this.declaringName = declaringName; + // this method is only used when dd.iast.anonymous-classes.enabled=false + // so take simple approach and look for '$number' at the end of the name + for (int end = name.length() - 1, i = end; i > 0; i--) { + char c = name.charAt(i); + if (c == '$' && i < end) { + return true; // only seen digits so far, assume anonymous + } else if (c < '0' || c > '9') { + break; // non-digit character found, assume not anonymous + } + } + return false; } void declare(AnnotationDescription annotation) { @@ -165,8 +148,4 @@ void declare(MethodDescription.InDefinedShape method) { declaredMethods.add(method); } } - - void anonymousType() { - anonymousType = true; - } } From 8f62fe5b7134c2783c535ab4363827f7669cd0d0 Mon Sep 17 00:00:00 2001 From: Stuart McCulloch Date: Wed, 1 Oct 2025 19:06:47 +0100 Subject: [PATCH 2/2] Move isAnonymousType check to the only place that uses it: IastInstrumentation --- .../bytebuddy/outline/TypeFactory.java | 5 ----- .../bytebuddy/outline/TypeOutline.java | 15 ------------- .../outline/OutlineTypeParserTest.groovy | 5 +++-- .../iastinstrumenter/IastInstrumentation.java | 21 ++++++++++++++++++- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeFactory.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeFactory.java index 765872f8300..ba9b29438af 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeFactory.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeFactory.java @@ -383,11 +383,6 @@ public TypeList.Generic getInterfaces() { return outline().getInterfaces(); } - @Override - public boolean isAnonymousType() { - return outline().isAnonymousType(); - } - @Override public boolean isPublic() { return isPublicFilter.contains(name) || super.isPublic(); diff --git a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeOutline.java b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeOutline.java index ba4f60933b2..f49cad267a1 100644 --- a/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeOutline.java +++ b/dd-java-agent/agent-tooling/src/main/java/datadog/trace/agent/tooling/bytebuddy/outline/TypeOutline.java @@ -113,21 +113,6 @@ public MethodList getDeclaredMethods() { return declaredMethods.isEmpty() ? NO_METHODS : new MethodList.Explicit<>(declaredMethods); } - @Override - public boolean isAnonymousType() { - // this method is only used when dd.iast.anonymous-classes.enabled=false - // so take simple approach and look for '$number' at the end of the name - for (int end = name.length() - 1, i = end; i > 0; i--) { - char c = name.charAt(i); - if (c == '$' && i < end) { - return true; // only seen digits so far, assume anonymous - } else if (c < '0' || c > '9') { - break; // non-digit character found, assume not anonymous - } - } - return false; - } - void declare(AnnotationDescription annotation) { if (null != annotation) { if (null == declaredAnnotations) { diff --git a/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/bytebuddy/outline/OutlineTypeParserTest.groovy b/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/bytebuddy/outline/OutlineTypeParserTest.groovy index 9a74729b884..77f8345f72d 100644 --- a/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/bytebuddy/outline/OutlineTypeParserTest.groovy +++ b/dd-java-agent/agent-tooling/src/test/groovy/datadog/trace/agent/tooling/bytebuddy/outline/OutlineTypeParserTest.groovy @@ -5,7 +5,7 @@ import spock.lang.Specification class OutlineTypeParserTest extends Specification { - void 'test modifiers are correct and anonymous classes are detected'() { + void 'test modifiers are correct'() { setup: final parser = new OutlineTypeParser() final locator = ClassFileLocators.classFileLocator(Thread.currentThread().contextClassLoader) @@ -15,12 +15,13 @@ class OutlineTypeParserTest extends Specification { final outline = parser.parse(bytes) then: - outline.anonymousType == anonymous outline.interface == isinterface outline.abstract == isabstract outline.annotation == annotation outline.enum == isenum + // isAnonymousType is no longer supported in outlines for performance reasons + where: clazz | anonymous | isinterface | isabstract | annotation | isenum 'datadog.trace.agent.test.EnclosedClasses' | false | false | false | false | false diff --git a/dd-java-agent/instrumentation/iast-instrumenter/src/main/java/datadog/trace/instrumentation/iastinstrumenter/IastInstrumentation.java b/dd-java-agent/instrumentation/iast-instrumenter/src/main/java/datadog/trace/instrumentation/iastinstrumenter/IastInstrumentation.java index c77ebe90dbb..12174161bec 100644 --- a/dd-java-agent/instrumentation/iast-instrumenter/src/main/java/datadog/trace/instrumentation/iastinstrumenter/IastInstrumentation.java +++ b/dd-java-agent/instrumentation/iast-instrumenter/src/main/java/datadog/trace/instrumentation/iastinstrumenter/IastInstrumentation.java @@ -76,11 +76,30 @@ protected boolean doMatch(TypeDescription target) { } }; + // this deliberately only considers anonymous types following the Java naming convention + private static final ElementMatcher.Junction ANONYMOUS_TYPE_MATCHER = + new ElementMatcher.Junction.ForNonNullValues() { + @Override + protected boolean doMatch(TypeDescription target) { + String name = target.getName(); + // search the name in reverse until we find a $ or non-digit + for (int end = name.length() - 1, i = end; i > 0; i--) { + char c = name.charAt(i); + if (c == '$' && i < end) { + return true; // only seen digits so far, assume anonymous + } else if (c < '0' || c > '9') { + break; // non-digit character found, assume not anonymous + } + } + return false; + } + }; + static { if (Config.get().isIastAnonymousClassesEnabled()) { INSTANCE = TRIE_MATCHER; } else { - INSTANCE = TRIE_MATCHER.and(not(TypeDescription::isAnonymousType)); + INSTANCE = TRIE_MATCHER.and(not(ANONYMOUS_TYPE_MATCHER)); } } }