Skip to content

Commit 65e472f

Browse files
authored
Add support for SymDB to scan directories (#8306)
When the location of loaded class is a directory (directory provided into the classpath or war extracted into a temp dir) we need to walk the directory for scanning class files. We avoid following the file link to prevent cycles.
1 parent 0b52bd6 commit 65e472f

File tree

3 files changed

+67
-9
lines changed

3 files changed

+67
-9
lines changed

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymDBEnablement.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.lang.instrument.Instrumentation;
2020
import java.net.URISyntaxException;
2121
import java.nio.file.Files;
22+
import java.nio.file.LinkOption;
2223
import java.nio.file.Path;
2324
import java.time.Instant;
2425
import java.time.LocalDateTime;
@@ -163,8 +164,8 @@ private void extractSymbolForLoadedClasses(SymDBReport symDBReport) {
163164
}
164165
File jarPathFile = jarPath.toFile();
165166
if (jarPathFile.isDirectory()) {
166-
// we are not supporting class directories (classpath) but only jar files
167-
symDBReport.addDirectoryJar(jarPath.toString());
167+
scanDirectory(jarPath, alreadyScannedJars, baos, buffer, symDBReport);
168+
alreadyScannedJars.add(jarPath.toString());
168169
continue;
169170
}
170171
if (alreadyScannedJars.contains(jarPath.toString())) {
@@ -188,6 +189,46 @@ private void extractSymbolForLoadedClasses(SymDBReport symDBReport) {
188189
}
189190
}
190191

192+
private void scanDirectory(
193+
Path jarPath,
194+
Set<String> alreadyScannedJars,
195+
ByteArrayOutputStream baos,
196+
byte[] buffer,
197+
SymDBReport symDBReport) {
198+
try {
199+
Files.walk(jarPath)
200+
// explicitly no follow links walking the directory to avoid cycles
201+
.filter(path -> Files.isRegularFile(path, LinkOption.NOFOLLOW_LINKS))
202+
.filter(path -> path.toString().endsWith(".class"))
203+
.filter(
204+
path ->
205+
!classNameFilter.isExcluded(
206+
Strings.getClassName(trimPrefixes(jarPath.relativize(path).toString()))))
207+
.forEach(path -> parseFileEntry(path, jarPath, baos, buffer));
208+
alreadyScannedJars.add(jarPath.toString());
209+
} catch (IOException e) {
210+
symDBReport.addIOException(jarPath.toString(), e);
211+
throw new RuntimeException(e);
212+
}
213+
}
214+
215+
private void parseFileEntry(Path path, Path jarPath, ByteArrayOutputStream baos, byte[] buffer) {
216+
LOGGER.debug("parsing file class: {}", path.toString());
217+
try {
218+
try (InputStream inputStream = Files.newInputStream(path)) {
219+
int readBytes;
220+
baos.reset();
221+
while ((readBytes = inputStream.read(buffer)) != -1) {
222+
baos.write(buffer, 0, readBytes);
223+
}
224+
symbolAggregator.parseClass(
225+
path.getFileName().toString(), baos.toByteArray(), jarPath.toString());
226+
}
227+
} catch (IOException ex) {
228+
LOGGER.debug("Exception during parsing file class: {}", path, ex);
229+
}
230+
}
231+
191232
private void parseJarEntry(
192233
JarEntry jarEntry, JarFile jarFile, Path jarPath, ByteArrayOutputStream baos, byte[] buffer) {
193234
LOGGER.debug("parsing jarEntry class: {}", jarEntry.getName());

dd-java-agent/agent-debugger/src/main/java/com/datadog/debugger/symbol/SymDBReport.java

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,13 @@ public class SymDBReport {
1414
private static final Logger LOGGER = LoggerFactory.getLogger(SymDBReport.class);
1515

1616
private final Set<String> missingJars = new HashSet<>();
17-
private final Set<String> directoryJars = new HashSet<>();
1817
private final Map<String, String> ioExceptions = new HashMap<>();
1918
private final List<String> locationErrors = new ArrayList<>();
2019

2120
public void addMissingJar(String jarPath) {
2221
missingJars.add(jarPath);
2322
}
2423

25-
public void addDirectoryJar(String jarPath) {
26-
directoryJars.add(jarPath);
27-
}
28-
2924
public void addIOException(String jarPath, IOException e) {
3025
ioExceptions.put(jarPath, e.toString());
3126
}
@@ -40,8 +35,6 @@ public void report() {
4035
+ locationErrors
4136
+ " Missing jars: "
4237
+ missingJars
43-
+ " Directory jars: "
44-
+ directoryJars
4538
+ " IOExceptions: "
4639
+ ioExceptions;
4740
LOGGER.info(content);

dd-java-agent/agent-debugger/src/test/java/com/datadog/debugger/symbol/SymDBEnablementTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
import static org.junit.jupiter.api.Assertions.assertFalse;
55
import static org.junit.jupiter.api.Assertions.assertTrue;
66
import static org.mockito.ArgumentMatchers.any;
7+
import static org.mockito.ArgumentMatchers.anyString;
78
import static org.mockito.ArgumentMatchers.eq;
9+
import static org.mockito.Mockito.atLeastOnce;
810
import static org.mockito.Mockito.doAnswer;
911
import static org.mockito.Mockito.mock;
1012
import static org.mockito.Mockito.times;
@@ -119,6 +121,28 @@ public void parseLoadedClass() throws ClassNotFoundException, IOException, URISy
119121
captor.getAllValues().get(1));
120122
}
121123

124+
@Test
125+
public void parseLoadedClassFromDirectory()
126+
throws ClassNotFoundException, IOException, URISyntaxException {
127+
URL classFilesUrl = getClass().getResource("/");
128+
URLClassLoader urlClassLoader = new URLClassLoader(new URL[] {classFilesUrl}, null);
129+
Class<?> testClass = urlClassLoader.loadClass(getClass().getTypeName());
130+
when(instr.getAllLoadedClasses()).thenReturn(new Class[] {testClass});
131+
when(config.getThirdPartyIncludes())
132+
.thenReturn(
133+
Stream.of("com.datadog.debugger.", "org.springframework.samples.")
134+
.collect(Collectors.toSet()));
135+
SymbolAggregator symbolAggregator = mock(SymbolAggregator.class);
136+
SymDBEnablement symDBEnablement =
137+
new SymDBEnablement(instr, config, symbolAggregator, ClassNameFiltering.allowAll());
138+
symDBEnablement.startSymbolExtraction();
139+
verify(instr).addTransformer(any(SymbolExtractionTransformer.class));
140+
ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
141+
verify(symbolAggregator, atLeastOnce()).parseClass(captor.capture(), any(), anyString());
142+
// verify that we called parseClass on this test class
143+
assertTrue(captor.getAllValues().contains(getClass().getSimpleName() + ".class"));
144+
}
145+
122146
@Test
123147
public void noDuplicateSymbolExtraction() {
124148
final String CLASS_NAME_PATH = "com/datadog/debugger/symbol/SymbolExtraction01";

0 commit comments

Comments
 (0)