diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java index 872f579cb44..0339bd67c7f 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/agent/DebuggerAgent.java @@ -11,8 +11,12 @@ import com.datadog.debugger.sink.ProbeStatusSink; import com.datadog.debugger.sink.SnapshotSink; import com.datadog.debugger.sink.SymbolSink; +import com.datadog.debugger.symbol.AvroFilter; +import com.datadog.debugger.symbol.ProtoFilter; +import com.datadog.debugger.symbol.ScopeFilter; import com.datadog.debugger.symbol.SymDBEnablement; import com.datadog.debugger.symbol.SymbolAggregator; +import com.datadog.debugger.symbol.WireFilter; import com.datadog.debugger.uploader.BatchUploader; import com.datadog.debugger.util.ClassNameFiltering; import com.datadog.debugger.util.DebuggerMetrics; @@ -41,7 +45,9 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.time.Duration; +import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import java.util.zip.ZipOutputStream; @@ -155,9 +161,14 @@ public static void startDynamicInstrumentation() { if (configurationPoller != null) { if (config.isSymbolDatabaseEnabled()) { initClassNameFilter(); + List scopeFilters = + Arrays.asList(new AvroFilter(), new ProtoFilter(), new WireFilter()); SymbolAggregator symbolAggregator = new SymbolAggregator( - classNameFilter, sink.getSymbolSink(), config.getSymbolDatabaseFlushThreshold()); + classNameFilter, + scopeFilters, + sink.getSymbolSink(), + config.getSymbolDatabaseFlushThreshold()); symbolAggregator.start(); symDBEnablement = new SymDBEnablement(instrumentation, config, symbolAggregator, classNameFilter); diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/AvroFilter.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/AvroFilter.java new file mode 100644 index 00000000000..8a7b1130d08 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/AvroFilter.java @@ -0,0 +1,32 @@ +package com.datadog.debugger.symbol; + +public class AvroFilter implements ScopeFilter { + @Override + public boolean filterOut(Scope scope) { + if (scope == null) { + return false; + } + LanguageSpecifics languageSpecifics = scope.getLanguageSpecifics(); + if (languageSpecifics != null) { + String superClass = languageSpecifics.getSuperClass(); + // Allow Avro data classes that extend SpecificRecordBase. + if ("org.apache.avro.specific.SpecificRecordBase".equals(superClass)) { + return false; + } + } + // Filter out classes that appear to be just schema wrappers. + if (scope.getScopeType() == ScopeType.CLASS + && scope.getSymbols() != null + && scope.getSymbols().stream() + .anyMatch( + it -> + it.getSymbolType() == SymbolType.STATIC_FIELD + && "SCHEMA$".equals(it.getName()) + && it.getType() != null + && it.getType().contains("org.apache.avro.Schema"))) { + return true; + } + // Otherwise, do not filter. + return false; + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/ProtoFilter.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/ProtoFilter.java new file mode 100644 index 00000000000..b5b94627122 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/ProtoFilter.java @@ -0,0 +1,57 @@ +package com.datadog.debugger.symbol; + +import java.util.List; + +public class ProtoFilter implements ScopeFilter { + @Override + public boolean filterOut(Scope scope) { + if (scope == null) { + return false; + } + LanguageSpecifics languageSpecifics = scope.getLanguageSpecifics(); + if (languageSpecifics != null) { + List interfaces = languageSpecifics.getInterfaces(); + if (interfaces != null) { + if (interfaces.contains("com.google.protobuf.MessageOrBuilder")) { + // MessageOrBuilder is an interface implemented by both message classes and their + // builders. + // Scopes implementing this interface are filtered out because they do not represent + // concrete data structures but rather interfaces for accessing or building messages. + return true; + } + } + String superClass = languageSpecifics.getSuperClass(); + if ("com.google.protobuf.AbstractParser".equals(superClass)) { + // AbstractParser is a base class for parsing protobuf messages. Scopes with this super + // class are filtered out because they are utility classes for parsing and do not contain + // actual data fields. + return true; + } + if ("com.google.protobuf.GeneratedMessageV3$Builder".equals(superClass)) { + // GeneratedMessageV3$Builder is a builder class for constructing GeneratedMessageV3 + // instances. These scopes are filtered out because they are used for building messages and + // do not represent the final data structure. + return true; + } + } + // If none of the above matched, see if the class has a proto descriptor field. This is the case + // for wrapper + // classes (`OuterClass`) and `Enum` classes. They contain metadata, not data. + if (hasProtoDescriptorField(scope)) { + return true; + } + // Probably no protobuf, pass + return false; + } + + private boolean hasProtoDescriptorField(Scope scope) { + return scope.getScopeType() == ScopeType.CLASS + && scope.getSymbols() != null + && scope.getSymbols().stream() + .anyMatch( + it -> + it.getSymbolType() == SymbolType.STATIC_FIELD + && it.getType() != null + && it.getType().contains("com.google.protobuf.Descriptors")); + } +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/ScopeFilter.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/ScopeFilter.java new file mode 100644 index 00000000000..b87c550b254 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/ScopeFilter.java @@ -0,0 +1,6 @@ +package com.datadog.debugger.symbol; + +public interface ScopeFilter { + /** returns true if the scope should be excluded */ + boolean filterOut(Scope scope); +} diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolAggregator.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolAggregator.java index ccae4e0916e..11a998271c3 100644 --- a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolAggregator.java +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymbolAggregator.java @@ -38,6 +38,7 @@ public class SymbolAggregator { private static final int CLASSFILE_BUFFER_SIZE = 8192; private final DebuggerContext.ClassNameFilter classNameFilter; + private final List scopeFilters; private final SymbolSink sink; private final int symbolFlushThreshold; private final Map jarScopesByName = new HashMap<>(); @@ -51,8 +52,12 @@ public class SymbolAggregator { private final Set alreadyScannedJars = ConcurrentHashMap.newKeySet(); public SymbolAggregator( - DebuggerContext.ClassNameFilter classNameFilter, SymbolSink sink, int symbolFlushThreshold) { + DebuggerContext.ClassNameFilter classNameFilter, + List scopeFilters, + SymbolSink sink, + int symbolFlushThreshold) { this.classNameFilter = classNameFilter; + this.scopeFilters = scopeFilters; this.sink = sink; this.symbolFlushThreshold = symbolFlushThreshold; } @@ -119,10 +124,18 @@ public void parseClass( } LOGGER.debug("Extracting Symbols from: {}, located in: {}", className, jarName); Scope jarScope = SymbolExtractor.extract(classfileBuffer, jarName); + jarScope = applyFilters(jarScope); addJarScope(jarScope, false); symDBReport.incClassCount(jarName); } + private Scope applyFilters(Scope jarScope) { + for (ScopeFilter filter : scopeFilters) { + jarScope.getScopes().removeIf(filter::filterOut); + } + return jarScope; + } + private void flushRemainingScopes(SymbolAggregator symbolAggregator) { synchronized (jarScopeLock) { if (jarScopesByName.isEmpty()) { diff --git a/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/WireFilter.java b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/WireFilter.java new file mode 100644 index 00000000000..9359860eb68 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/WireFilter.java @@ -0,0 +1,35 @@ +package com.datadog.debugger.symbol; + +import java.util.List; + +public class WireFilter implements ScopeFilter { + @Override + public boolean filterOut(Scope scope) { + // Filter out classes generated by Square Wire: https://square.github.io/wire/ + if (scope == null) { + return false; + } + LanguageSpecifics languageSpecifics = scope.getLanguageSpecifics(); + if (languageSpecifics == null) { + return false; + } + List interfaces = languageSpecifics.getInterfaces(); + if (interfaces != null) { + if (interfaces.contains("com.squareup.wire.Message")) { + // Pass-through for Message since it contains data + return false; + } + if (interfaces.stream().anyMatch(it -> it.startsWith("com.squareup.wire"))) { + return true; + } + } + String superClass = languageSpecifics.getSuperClass(); + if (superClass != null) { + if (superClass.startsWith("com.squareup.wire")) { + return true; + } + } + // Probably no protobuf, pass + return false; + } +} diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/AvroFilterTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/AvroFilterTest.java new file mode 100644 index 00000000000..ef04f4299e8 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/AvroFilterTest.java @@ -0,0 +1,34 @@ +package com.datadog.debugger.symbol; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class AvroFilterTest { + @Test + void filterOut() { + AvroFilter avroFilter = new AvroFilter(); + assertFalse(avroFilter.filterOut(null)); + Scope scope = Scope.builder(ScopeType.CLASS, "", 0, 0).build(); + assertFalse(avroFilter.filterOut(scope)); + scope = Scope.builder(ScopeType.CLASS, "", 0, 0).name("org.apache.avro.MyClass").build(); + assertFalse(avroFilter.filterOut(scope)); + scope = + Scope.builder(ScopeType.CLASS, "", 0, 0) + .languageSpecifics( + new LanguageSpecifics.Builder() + .superClass("org.apache.avro.specific.SpecificRecordBase") + .build()) + .build(); + assertFalse(avroFilter.filterOut(scope)); + scope = + Scope.builder(ScopeType.CLASS, "", 0, 0) + .symbols( + asList( + new Symbol( + SymbolType.STATIC_FIELD, "SCHEMA$", 0, "org.apache.avro.Schema", null))) + .build(); + assertTrue(avroFilter.filterOut(scope)); + } +} diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/ProtoFilterTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/ProtoFilterTest.java new file mode 100644 index 00000000000..e82c3cd751b --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/ProtoFilterTest.java @@ -0,0 +1,54 @@ +package com.datadog.debugger.symbol; + +import static java.util.Arrays.asList; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class ProtoFilterTest { + @Test + void filterOut() { + ProtoFilter protoFilter = new ProtoFilter(); + assertFalse(protoFilter.filterOut(null)); + Scope scope = Scope.builder(ScopeType.CLASS, "", 0, 0).build(); + assertFalse(protoFilter.filterOut(scope)); + scope = Scope.builder(ScopeType.CLASS, "", 0, 0).name("com.google.protobuf.MyClass").build(); + assertFalse(protoFilter.filterOut(scope)); + scope = + Scope.builder(ScopeType.CLASS, "", 0, 0) + .languageSpecifics( + new LanguageSpecifics.Builder() + .addInterfaces(asList("com.google.protobuf.MessageOrBuilder")) + .build()) + .build(); + assertTrue(protoFilter.filterOut(scope)); + scope = + Scope.builder(ScopeType.CLASS, "", 0, 0) + .languageSpecifics( + new LanguageSpecifics.Builder() + .superClass("com.google.protobuf.AbstractParser") + .build()) + .build(); + assertTrue(protoFilter.filterOut(scope)); + scope = + Scope.builder(ScopeType.CLASS, "", 0, 0) + .languageSpecifics( + new LanguageSpecifics.Builder() + .superClass("com.google.protobuf.GeneratedMessageV3$Builder") + .build()) + .build(); + assertTrue(protoFilter.filterOut(scope)); + scope = + Scope.builder(ScopeType.CLASS, "", 0, 0) + .symbols( + asList( + new Symbol( + SymbolType.STATIC_FIELD, + "SCHEMA$", + 0, + "com.google.protobuf.Descriptors", + null))) + .build(); + assertTrue(protoFilter.filterOut(scope)); + } +} 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 4a59215c0ed..547c525d63e 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 @@ -1,5 +1,6 @@ package com.datadog.debugger.symbol; +import static java.util.Collections.emptyList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -63,7 +64,7 @@ public void enableDisableSymDBThroughRC() throws Exception { new SymDBEnablement( instr, config, - new SymbolAggregator(classNameFiltering, symbolSink, 1), + new SymbolAggregator(classNameFiltering, emptyList(), symbolSink, 1), classNameFiltering); symDBEnablement.accept(ParsedConfigKey.parse(CONFIG_KEY), UPlOAD_SYMBOL_TRUE, null); waitForUpload(symDBEnablement); @@ -79,7 +80,7 @@ public void removeSymDBConfig() throws Exception { new SymDBEnablement( instr, config, - new SymbolAggregator(classNameFiltering, symbolSink, 1), + new SymbolAggregator(classNameFiltering, emptyList(), symbolSink, 1), classNameFiltering); symDBEnablement.accept(ParsedConfigKey.parse(CONFIG_KEY), UPlOAD_SYMBOL_TRUE, null); waitForUpload(symDBEnablement); @@ -96,7 +97,7 @@ public void noIncludesFilterOutDatadogClass() { new SymDBEnablement( instr, config, - new SymbolAggregator(classNameFiltering, symbolSink, 1), + new SymbolAggregator(classNameFiltering, emptyList(), symbolSink, 1), classNameFiltering); symDBEnablement.startSymbolExtraction(); ArgumentCaptor captor = @@ -122,7 +123,7 @@ public void parseLoadedClass() throws ClassNotFoundException, IOException { .collect(Collectors.toSet())); ClassNameFiltering classNameFiltering = ClassNameFiltering.allowAll(); SymbolAggregator symbolAggregator = - spy(new SymbolAggregator(classNameFiltering, symbolSink, 1)); + spy(new SymbolAggregator(classNameFiltering, emptyList(), symbolSink, 1)); SymDBEnablement symDBEnablement = new SymDBEnablement(instr, config, symbolAggregator, classNameFiltering); symDBEnablement.startSymbolExtraction(); @@ -150,7 +151,7 @@ public void parseLoadedClassFromDirectory() .collect(Collectors.toSet())); ClassNameFiltering classNameFiltering = ClassNameFiltering.allowAll(); SymbolAggregator symbolAggregator = - spy(new SymbolAggregator(classNameFiltering, symbolSink, 1)); + spy(new SymbolAggregator(classNameFiltering, emptyList(), symbolSink, 1)); SymDBEnablement symDBEnablement = new SymDBEnablement(instr, config, symbolAggregator, classNameFiltering); symDBEnablement.startSymbolExtraction(); @@ -171,7 +172,8 @@ public void noDuplicateSymbolExtraction() { Collections.singleton("org.springframework."), Collections.singleton("com.datadog.debugger."), Collections.emptySet()); - SymbolAggregator symbolAggregator = new SymbolAggregator(classNameFiltering, mockSymbolSink, 1); + SymbolAggregator symbolAggregator = + new SymbolAggregator(classNameFiltering, emptyList(), mockSymbolSink, 1); SymDBEnablement symDBEnablement = new SymDBEnablement(instr, config, symbolAggregator, classNameFiltering); doAnswer( diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolAggregatorTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolAggregatorTest.java index ffa270c6e0b..cbd3c82df52 100644 --- a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolAggregatorTest.java +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymbolAggregatorTest.java @@ -1,12 +1,17 @@ package com.datadog.debugger.symbol; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static utils.TestClassFileHelper.getClassFileBytes; import com.datadog.debugger.sink.SymbolSink; import com.datadog.debugger.util.ClassNameFiltering; @@ -14,6 +19,7 @@ import java.security.CodeSource; import java.security.ProtectionDomain; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIf; import org.mockito.ArgumentCaptor; class SymbolAggregatorTest { @@ -22,7 +28,7 @@ class SymbolAggregatorTest { void testScanQueuedJars() { SymbolSink symbolSink = mock(SymbolSink.class); SymbolAggregator symbolAggregator = - spy(new SymbolAggregator(ClassNameFiltering.allowAll(), symbolSink, 1)); + spy(new SymbolAggregator(ClassNameFiltering.allowAll(), emptyList(), symbolSink, 1)); URL jarFileUrl = getClass().getResource("/debugger-symbol.jar"); CodeSource codeSource = new CodeSource(jarFileUrl, (java.security.cert.Certificate[]) null); ProtectionDomain protectionDomain = new ProtectionDomain(codeSource, null); @@ -39,4 +45,28 @@ void testScanQueuedJars() { "BOOT-INF/classes/org/springframework/samples/petclinic/vet/VetController.class", captor.getAllValues().get(2)); } + + @Test + @DisabledIf(value = "datadog.trace.api.Platform#isJ9", disabledReason = "Flaky on J9 JVMs") + void testScopeFilter() { + ScopeFilter mockFilter = mock(ScopeFilter.class); + when(mockFilter.filterOut(any())).thenReturn(true); + SymbolSink symbolSink = mock(SymbolSink.class); + doAnswer( + invocation -> { + Object[] args = invocation.getArguments(); + assertEquals(1, args.length); + assertInstanceOf(Scope.class, args[0]); + Scope scope = (Scope) args[0]; + assertTrue(scope.getScopes().isEmpty()); + return null; + }) + .when(symbolSink) + .addScope(any()); + SymbolAggregator symbolAggregator = + new SymbolAggregator(ClassNameFiltering.allowAll(), asList(mockFilter), symbolSink, 1); + symbolAggregator.parseClass( + SymDBReport.NO_OP, String.class.getTypeName(), getClassFileBytes(String.class), null); + verify(mockFilter).filterOut(any()); + } } 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 86da95d19a4..26a30d412f5 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 @@ -1,6 +1,7 @@ package com.datadog.debugger.symbol; import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; @@ -888,7 +889,8 @@ public void filterOutClassesFromExcludedPackages() throws IOException, URISyntax new ClassNameFiltering(Collections.singleton(EXCLUDED_PACKAGE)); SymbolExtractionTransformer transformer = new SymbolExtractionTransformer( - new SymbolAggregator(classNameFiltering, symbolSinkMock, 1), classNameFiltering); + new SymbolAggregator(classNameFiltering, emptyList(), symbolSinkMock, 1), + classNameFiltering); instr.addTransformer(transformer); Class testClass = compileAndLoadClass(CLASS_NAME); Reflect.on(testClass).call("main", "1").get(); @@ -989,7 +991,7 @@ private SymbolExtractionTransformer createTransformer( private SymbolExtractionTransformer createTransformer( SymbolSink symbolSink, int symbolFlushThreshold, ClassNameFiltering classNameFiltering) { return new SymbolExtractionTransformer( - new SymbolAggregator(classNameFiltering, symbolSink, symbolFlushThreshold), + new SymbolAggregator(classNameFiltering, emptyList(), symbolSink, symbolFlushThreshold), classNameFiltering); } diff --git a/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/WireFilterTest.java b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/WireFilterTest.java new file mode 100644 index 00000000000..8158874b849 --- /dev/null +++ b/dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/WireFilterTest.java @@ -0,0 +1,42 @@ +package com.datadog.debugger.symbol; + +import static org.codehaus.groovy.runtime.InvokerHelper.asList; +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +class WireFilterTest { + @Test + void filterOut() { + WireFilter wireFilter = new WireFilter(); + assertFalse(wireFilter.filterOut(null)); + Scope scope = Scope.builder(ScopeType.CLASS, "", 0, 0).build(); + assertFalse(wireFilter.filterOut(scope)); + scope = Scope.builder(ScopeType.CLASS, "", 0, 0).name("com.squareup.wire.MyClass").build(); + assertFalse(wireFilter.filterOut(scope)); + scope = + Scope.builder(ScopeType.CLASS, "", 0, 0) + .languageSpecifics( + new LanguageSpecifics.Builder() + .addInterfaces(asList("com.squareup.wire.Message")) + .build()) + .build(); + assertFalse(wireFilter.filterOut(scope)); + scope = + Scope.builder(ScopeType.CLASS, "", 0, 0) + .languageSpecifics( + new LanguageSpecifics.Builder() + .superClass("com.squareup.wire.ProtoAdapter") + .build()) + .build(); + assertTrue(wireFilter.filterOut(scope)); + scope = + Scope.builder(ScopeType.CLASS, "", 0, 0) + .languageSpecifics( + new LanguageSpecifics.Builder() + .addInterfaces(asList("com.squareup.wire.ProtoAdapter")) + .build()) + .build(); + assertTrue(wireFilter.filterOut(scope)); + } +}