diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ThirdPartyLibraries.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ThirdPartyLibraries.java index 1f5b9d1d30e..aabee3b67ba 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ThirdPartyLibraries.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/ThirdPartyLibraries.java @@ -7,10 +7,12 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,6 +24,20 @@ public class ThirdPartyLibraries { private static final JsonAdapter ADAPTER = new Moshi.Builder().build().adapter(InternalConfig.class); private static final String FILE_NAME = "/third_party_libraries.json"; + private static final Set DEFAULT_SHADING_IDENTIFIERS = + new HashSet<>( + Arrays.asList( + "shaded", + "thirdparty", + "dependencies", + "relocated", + "bundled", + "embedded", + "vendor", + "repackaged", + "shadow", + "shim", + "wrapper")); private ThirdPartyLibraries() {} @@ -46,6 +62,13 @@ public Set getThirdPartyExcludes(Config config) { .collect(Collectors.toSet()); } + public Set getShadingIdentifiers(Config config) { + Stream configStream = + config.getThirdPartyShadingIdentifiers().stream().filter(s -> !s.isEmpty()); + return Stream.concat(configStream, DEFAULT_SHADING_IDENTIFIERS.stream()) + .collect(Collectors.toSet()); + } + // Add a*, b*, c*, ..., z* to the exclude trie in ClassNameFiltering. Simply adding * does not // work. private static Set getExcludeAll() { diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractionTransformer.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractionTransformer.java index 2d1ac86c87f..0d7b4503c8f 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractionTransformer.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolExtractionTransformer.java @@ -1,6 +1,7 @@ package com.datadog.debugger.symbol; import datadog.trace.bootstrap.debugger.DebuggerContext.ClassNameFilter; +import datadog.trace.util.Strings; import java.lang.instrument.ClassFileTransformer; import java.security.ProtectionDomain; import org.slf4j.Logger; @@ -34,7 +35,7 @@ public byte[] transform( // Don't parse our own classes to avoid duplicate class definition return null; } - if (classNameFiltering.isExcluded(className)) { + if (classNameFiltering.isExcluded(Strings.getClassName(className))) { return null; } symbolAggregator.parseClass(className, classfileBuffer, protectionDomain); diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/ClassNameFiltering.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/ClassNameFiltering.java index 7722746d3da..316c9152181 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/ClassNameFiltering.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/util/ClassNameFiltering.java @@ -14,28 +14,38 @@ public class ClassNameFiltering implements ClassNameFilter { private final ClassNameTrie includeTrie; private final ClassNameTrie excludeTrie; + private final ClassNameTrie shadingTrie; public ClassNameFiltering(Config config) { this( ThirdPartyLibraries.INSTANCE.getThirdPartyLibraries(config), - ThirdPartyLibraries.INSTANCE.getThirdPartyExcludes(config)); + ThirdPartyLibraries.INSTANCE.getThirdPartyExcludes(config), + ThirdPartyLibraries.INSTANCE.getShadingIdentifiers(config)); } public ClassNameFiltering(Set excludes) { - this(excludes, Collections.emptySet()); + this(excludes, Collections.emptySet(), Collections.emptySet()); } - public ClassNameFiltering(Set excludes, Set includes) { + public ClassNameFiltering( + Set excludes, Set includes, Set shadingIdentifiers) { ClassNameTrie.Builder excludeBuilder = new ClassNameTrie.Builder(); excludes.forEach(s -> excludeBuilder.put(s + "*", 1)); this.excludeTrie = excludeBuilder.buildTrie(); ClassNameTrie.Builder includeBuilder = new ClassNameTrie.Builder(); includes.forEach(s -> includeBuilder.put(s + "*", 1)); this.includeTrie = includeBuilder.buildTrie(); + ClassNameTrie.Builder shadingBuilder = new ClassNameTrie.Builder(); + shadingIdentifiers.forEach(s -> shadingBuilder.put(s + "*", 1)); + this.shadingTrie = shadingBuilder.buildTrie(); } + // className is the fully qualified class name with '.' (Java type) notation public boolean isExcluded(String className) { - return (includeTrie.apply(className) < 0 && excludeTrie.apply(className) > 0) + int shadedIdx = shadedIndexOf(className); + shadedIdx = Math.max(shadedIdx, 0); + return (includeTrie.apply(className, shadedIdx) < 0 + && excludeTrie.apply(className, shadedIdx) > 0) || isLambdaProxyClass(className); } @@ -43,7 +53,21 @@ static boolean isLambdaProxyClass(String className) { return LAMBDA_PROXY_CLASS_PATTERN.matcher(className).matches(); } + int shadedIndexOf(String className) { + int idx = 0; + int previousIdx = 0; + while ((idx = className.indexOf('.', previousIdx)) > 0) { + if (shadingTrie.apply(className, previousIdx) > 0) { + return idx + 1; + } + idx++; + previousIdx = idx; + } + return -1; + } + public static ClassNameFiltering allowAll() { - return new ClassNameFiltering(Collections.emptySet(), Collections.emptySet()); + return new ClassNameFiltering( + Collections.emptySet(), Collections.emptySet(), Collections.emptySet()); } } diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ThirdPartyLibrariesTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ThirdPartyLibrariesTest.java index e57315df152..01abb157469 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ThirdPartyLibrariesTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/agent/ThirdPartyLibrariesTest.java @@ -1,5 +1,6 @@ package com.datadog.debugger.agent; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -20,6 +21,7 @@ class ThirdPartyLibrariesTest { void setUp() { when(mockConfig.getThirdPartyIncludes()).thenReturn(Collections.emptySet()); when(mockConfig.getThirdPartyExcludes()).thenReturn(Collections.emptySet()); + when(mockConfig.getThirdPartyShadingIdentifiers()).thenReturn(Collections.emptySet()); } @Test @@ -29,7 +31,6 @@ void testGetExcludesContainsDefaultExclude() { @Test void testGetExcludesWithExplicitExclude() { - when(mockConfig.getThirdPartyIncludes()) .thenReturn(Collections.singleton("com.datadog.debugger")); assertTrue( @@ -72,4 +73,22 @@ void testGetExcludeAll() { Set excludeAll = ThirdPartyLibraries.INSTANCE.getThirdPartyLibraries(null); for (char c : ThirdPartyLibraries.ALPHABET) assertTrue(excludeAll.contains(String.valueOf(c))); } + + @Test + void testEmptyStrings() { + int expectedIncludeDefaultSize = + ThirdPartyLibraries.INSTANCE.getThirdPartyLibraries(mockConfig).size(); + int expectedShadingDefaultSize = + ThirdPartyLibraries.INSTANCE.getShadingIdentifiers(mockConfig).size(); + when(mockConfig.getThirdPartyIncludes()).thenReturn(Collections.singleton("")); + when(mockConfig.getThirdPartyExcludes()).thenReturn(Collections.singleton("")); + when(mockConfig.getThirdPartyShadingIdentifiers()).thenReturn(Collections.singleton("")); + assertEquals( + expectedIncludeDefaultSize, + ThirdPartyLibraries.INSTANCE.getThirdPartyLibraries(mockConfig).size()); + assertTrue(ThirdPartyLibraries.INSTANCE.getThirdPartyExcludes(mockConfig).isEmpty()); + assertEquals( + expectedShadingDefaultSize, + ThirdPartyLibraries.INSTANCE.getShadingIdentifiers(mockConfig).size()); + } } diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymDBEnablementTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymDBEnablementTest.java index c5fefef7534..4a59215c0ed 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymDBEnablementTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymDBEnablementTest.java @@ -169,7 +169,8 @@ public void noDuplicateSymbolExtraction() { ClassNameFiltering classNameFiltering = new ClassNameFiltering( Collections.singleton("org.springframework."), - Collections.singleton("com.datadog.debugger.")); + Collections.singleton("com.datadog.debugger."), + Collections.emptySet()); SymbolAggregator symbolAggregator = new SymbolAggregator(classNameFiltering, mockSymbolSink, 1); SymDBEnablement symDBEnablement = new SymDBEnablement(instr, config, symbolAggregator, classNameFiltering); diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolExtractionTransformerTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolExtractionTransformerTest.java index bffb1748292..86da95d19a4 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolExtractionTransformerTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolExtractionTransformerTest.java @@ -982,7 +982,8 @@ private SymbolExtractionTransformer createTransformer( return createTransformer( symbolSink, symbolFlushThreshold, - new ClassNameFiltering(TRANSFORMER_EXCLUDES, Collections.singleton(SYMBOL_PACKAGE))); + new ClassNameFiltering( + TRANSFORMER_EXCLUDES, Collections.singleton(SYMBOL_PACKAGE), Collections.emptySet())); } private SymbolExtractionTransformer createTransformer( diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/ClassNameFilteringTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/ClassNameFilteringTest.java index bc5dec14c99..6fdf52764a7 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/ClassNameFilteringTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/util/ClassNameFilteringTest.java @@ -6,8 +6,8 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import com.datadog.debugger.agent.ThirdPartyLibraries; import datadog.trace.api.Config; +import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -52,7 +52,8 @@ public void testIncludeOverridesExclude() { ClassNameFiltering classNameFiltering = new ClassNameFiltering( Collections.singleton("com.datadog.debugger"), - Collections.singleton("com.datadog.debugger")); + Collections.singleton("com.datadog.debugger"), + Collections.emptySet()); assertFalse(classNameFiltering.isExcluded("com.datadog.debugger.FooBar")); } @@ -60,7 +61,9 @@ public void testIncludeOverridesExclude() { public void testIncludePrefixOverridesExclude() { ClassNameFiltering classNameFiltering = new ClassNameFiltering( - Collections.singleton("com.datadog.debugger"), Collections.singleton("com.datadog")); + Collections.singleton("com.datadog.debugger"), + Collections.singleton("com.datadog"), + Collections.emptySet()); assertFalse(classNameFiltering.isExcluded("com.datadog.debugger.FooBar")); } @@ -69,7 +72,8 @@ public void testIncludeSomeExcludeSome() { ClassNameFiltering classNameFiltering = new ClassNameFiltering( Stream.of("com.datadog.debugger", "org.junit").collect(Collectors.toSet()), - Collections.singleton("com.datadog.debugger")); + Collections.singleton("com.datadog.debugger"), + Collections.emptySet()); assertFalse(classNameFiltering.isExcluded("com.datadog.debugger.FooBar")); assertTrue(classNameFiltering.isExcluded("org.junit.FooBar")); } @@ -88,11 +92,25 @@ public void testExcludeDefaults(String input) { Config config = mock(Config.class); when(config.getThirdPartyExcludes()).thenReturn(Collections.emptySet()); when(config.getThirdPartyIncludes()).thenReturn(Collections.emptySet()); - ClassNameFiltering classNameFiltering = - new ClassNameFiltering(ThirdPartyLibraries.INSTANCE.getThirdPartyLibraries(config)); + when(config.getThirdPartyShadingIdentifiers()).thenReturn(Collections.emptySet()); + ClassNameFiltering classNameFiltering = new ClassNameFiltering(config); assertTrue(classNameFiltering.isExcluded(input)); } + @Test + public void testShaded() { + Config config = mock(Config.class); + when(config.getThirdPartyExcludes()).thenReturn(Collections.emptySet()); + when(config.getThirdPartyIncludes()) + .thenReturn(new HashSet<>(Arrays.asList("com.google", "org.junit"))); + when(config.getThirdPartyShadingIdentifiers()).thenReturn(Collections.emptySet()); + ClassNameFiltering classNameFiltering = new ClassNameFiltering(config); + assertTrue(classNameFiltering.isExcluded("com.google.FooBar")); + assertTrue(classNameFiltering.isExcluded("shaded.com.google.FooBar")); + assertFalse(classNameFiltering.isExcluded("com.example.shaded.com.example.FooBar")); + assertTrue(classNameFiltering.isExcluded("com.example.shaded.com.google.FooBar")); + } + @Test void lambdaProxyClasses() { // jdk8: at diff --git a/dd-trace-api/src/main/java/datadog/trace/api/config/DebuggerConfig.java b/dd-trace-api/src/main/java/datadog/trace/api/config/DebuggerConfig.java index 5b2e32ad62e..58b90fa4e69 100644 --- a/dd-trace-api/src/main/java/datadog/trace/api/config/DebuggerConfig.java +++ b/dd-trace-api/src/main/java/datadog/trace/api/config/DebuggerConfig.java @@ -61,6 +61,7 @@ public final class DebuggerConfig { public static final String DISTRIBUTED_DEBUGGER_ENABLED = "distributed.debugger.enabled"; public static final String THIRD_PARTY_INCLUDES = "third.party.includes"; public static final String THIRD_PARTY_EXCLUDES = "third.party.excludes"; + public static final String THIRD_PARTY_SHADING_IDENTIFIERS = "third.party.shading.identifiers"; private DebuggerConfig() {} } diff --git a/internal-api/src/main/java/datadog/trace/api/Config.java b/internal-api/src/main/java/datadog/trace/api/Config.java index fd7eca95403..5e0f1b786b9 100644 --- a/internal-api/src/main/java/datadog/trace/api/Config.java +++ b/internal-api/src/main/java/datadog/trace/api/Config.java @@ -430,6 +430,7 @@ public static String getHostName() { private final Set debuggerThirdPartyIncludes; private final Set debuggerThirdPartyExcludes; + private final Set debuggerShadingIdentifiers; private final boolean awsPropagationEnabled; private final boolean sqsPropagationEnabled; @@ -1734,6 +1735,8 @@ PROFILING_DATADOG_PROFILER_ENABLED, isDatadogProfilerSafeInCurrentEnvironment()) debuggerThirdPartyIncludes = tryMakeImmutableSet(configProvider.getList(THIRD_PARTY_INCLUDES)); debuggerThirdPartyExcludes = tryMakeImmutableSet(configProvider.getList(THIRD_PARTY_EXCLUDES)); + debuggerShadingIdentifiers = + tryMakeImmutableSet(configProvider.getList(THIRD_PARTY_SHADING_IDENTIFIERS)); awsPropagationEnabled = isPropagationEnabled(true, "aws", "aws-sdk"); sqsPropagationEnabled = isPropagationEnabled(true, "sqs"); @@ -3299,6 +3302,10 @@ public Set getThirdPartyExcludes() { return debuggerThirdPartyExcludes; } + public Set getThirdPartyShadingIdentifiers() { + return debuggerShadingIdentifiers; + } + private String getFinalDebuggerBaseUrl() { if (agentUrl.startsWith("unix:")) { // provide placeholder agent URL, in practice we'll be tunnelling over UDS diff --git a/internal-api/src/main/java/datadog/trace/util/ClassNameTrie.java b/internal-api/src/main/java/datadog/trace/util/ClassNameTrie.java index 1e7e43ebef9..eff49449fa2 100644 --- a/internal-api/src/main/java/datadog/trace/util/ClassNameTrie.java +++ b/internal-api/src/main/java/datadog/trace/util/ClassNameTrie.java @@ -117,9 +117,17 @@ public int apply(String key) { return apply(trieData, longJumps, key); } + public int apply(String key, int fromIndex) { + return apply(trieData, longJumps, key, fromIndex); + } + public static int apply(char[] data, int[] longJumps, String key) { + return apply(data, longJumps, key, 0); + } + + public static int apply(char[] data, int[] longJumps, String key, int fromIndex) { int keyLength = key.length(); - int keyIndex = 0; + int keyIndex = fromIndex; int dataIndex = 0; int result = -1; @@ -840,6 +848,14 @@ private static void generateJavaFile( } lines.add(" }"); lines.add(""); + lines.add(" public static int apply(String key, int fromIndex) {"); + if (hasLongJumps) { + lines.add(" return ClassNameTrie.apply(TRIE_DATA, LONG_JUMPS, key, fromIndex);"); + } else { + lines.add(" return ClassNameTrie.apply(TRIE_DATA, null, key, fromIndex);"); + } + lines.add(" }"); + lines.add(""); lines.add(" private " + className + "() {}"); lines.add("}"); Files.write(javaPath, lines, StandardCharsets.UTF_8); diff --git a/internal-api/src/test/groovy/datadog/trace/util/ClassNameTrieTest.groovy b/internal-api/src/test/groovy/datadog/trace/util/ClassNameTrieTest.groovy index 6ac2cbf87b0..3440856bc70 100644 --- a/internal-api/src/test/groovy/datadog/trace/util/ClassNameTrieTest.groovy +++ b/internal-api/src/test/groovy/datadog/trace/util/ClassNameTrieTest.groovy @@ -44,6 +44,45 @@ class ClassNameTrieTest extends DDSpecification { // spotless:on } + def 'test class name "#key" mapping with fromIndex'() { + when: + int value = TestClassNamesTrie.apply(key, "garbage.".length()) + then: + value == expected + where: + // spotless:off + key | expected + 'garbage.One' | 1 + 'garbage.com.Two' | 2 + 'garbage.com.foo.Three' | 3 + 'garbage.company.foo.Four' | 4 + 'garbage.com.foobar.Five' | 5 + 'garbage.company.foobar.Six' | 6 + 'garbage.company.foobar.Sixty' | 60 + 'garbage.com.f' | 7 + 'garbage.com.foo.a' | 8 + 'garbage.com.foobar.b' | 9 + 'garbage.company.f' | 10 + 'garbage.company.foo.a' | 11 + 'garbage.company.foobar.S' | 12 + 'garbage.com.Two$f' | 13 + 'garbage.foobar.Two$b' | 14 + 'garbage.' | -1 + 'garbage.O' | -1 + 'garbage._' | -1 + 'garbage.On' | -1 + 'garbage.O_' | -1 + 'garbage.On_' | -1 + 'garbage.OneNoMatch' | -1 + 'garbage.com.Twos' | 7 + 'garbage.com.foo.Threes' | 8 + 'garbage.com.foobar.Fives' | 9 + 'garbage.foobar.Thre' | -1 + 'garbage.foobar.Three' | 15 + 'garbage.foobar.ThreeMore' | 15 + // spotless:on + } + def 'test internal name "#key" mapping'() { when: int value = TestClassNamesTrie.apply(key)