Skip to content

Commit a1a5f35

Browse files
committed
feat: Improve lambda indexing, external class decompilation, and add depth configuration
1 parent d1a49f9 commit a1a5f35

5 files changed

Lines changed: 104 additions & 28 deletions

File tree

src/main/java/com/neuvem/java2graph/Java2GraphConfig.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ public enum DecompilerType {
1919

2020
private boolean indexAllJarEntries = true;
2121
private boolean decompile = true;
22-
private DecompilerType decompilerType = DecompilerType.CFR;
22+
private DecompilerType decompilerType = DecompilerType.VINEFLOWER;
2323
private List<Path> incrementalFiles;
2424
private List<Path> incrementalJars;
2525
private Path mutationPlanPath;
2626
private boolean gitCheckpoint;
2727
private Path dbPath;
28+
private int maxDecompileDepth = 3;
2829

2930

3031
public Java2GraphConfig() {}
@@ -77,6 +78,9 @@ public Java2GraphConfig() {}
7778
public Path getDbPath() { return dbPath; }
7879
public void setDbPath(Path dbPath) { this.dbPath = dbPath; }
7980

81+
public int getMaxDecompileDepth() { return maxDecompileDepth; }
82+
public void setMaxDecompileDepth(int maxDecompileDepth) { this.maxDecompileDepth = maxDecompileDepth; }
83+
8084

8185
public static Builder builder() { return new Builder(); }
8286

@@ -98,6 +102,7 @@ public static class Builder {
98102
public Builder mutationPlanPath(Path mutationPlanPath) { config.mutationPlanPath = mutationPlanPath; return this; }
99103
public Builder gitCheckpoint(boolean gitCheckpoint) { config.gitCheckpoint = gitCheckpoint; return this; }
100104
public Builder dbPath(Path dbPath) { config.dbPath = dbPath; return this; }
105+
public Builder maxDecompileDepth(int maxDecompileDepth) { config.maxDecompileDepth = maxDecompileDepth; return this; }
101106
public Java2GraphConfig build() { return config; }
102107

103108
}

src/main/java/com/neuvem/java2graph/Main.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,13 @@ public class Main implements Callable<Integer> {
5050
@Option(names = {"--no-decompile"}, description = "Disable high-fidelity decompilation of external methods", fallbackValue = "true", defaultValue = "false")
5151
private boolean noDecompile;
5252

53+
@Option(names = {"--max-decompile-depth"}, description = "Maximum depth to recursively decompile 3rd party libraries", defaultValue = "3")
54+
private int maxDecompileDepth;
55+
5356
@Option(names = {"--cache-dir"}, description = "Directory to cache decompiled source files")
5457
private Path cacheDir;
5558

56-
@Option(names = {"--decompiler"}, description = "Decompiler to use: CFR, VINEFLOWER", defaultValue = "CFR")
59+
@Option(names = {"--decompiler"}, description = "Decompiler to use: CFR, VINEFLOWER", defaultValue = "VINEFLOWER")
5760
private Java2GraphConfig.DecompilerType decompilerType;
5861

5962
@Option(names = {"--incremental"}, split = ",", description = "List of files to re-index (incremental mode)")
@@ -131,6 +134,7 @@ public Integer call() throws Exception {
131134
.cacheDir(cacheDir)
132135
.incrementalFiles(incrementalFiles)
133136
.incrementalJars(incrementalJars)
137+
.maxDecompileDepth(maxDecompileDepth)
134138
.build();
135139

136140
GraphContext context = new GraphContext();

src/main/java/com/neuvem/java2graph/passes/ExportPass.java

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import java.nio.file.Files;
1515
import java.nio.file.Path;
1616
import java.util.HashSet;
17+
import java.util.List;
1718
import java.util.Set;
1819

1920
public class ExportPass implements Pass {
@@ -114,7 +115,7 @@ private void exportMethodsCsv(Path dir, GraphContext context) throws IOException
114115
try (FileWriter out = new FileWriter(dir.resolve("methods.csv").toFile());
115116
CSVPrinter printer = new CSVPrinter(out,
116117
CSVFormat.Builder.create().setHeader("id", "fqn", "name", "signature", "isExternal",
117-
"annotations", "sourceCode", "containingClassFqn", "isLambda", "filePath").build())) {
118+
"annotations", "sourceCode", "isLambda", "filePath").build())) {
118119
for (MethodNode node : context.methods.values()) {
119120
if (node.getId() != null && !node.getId().isBlank()) {
120121
String annotations = String.join(";", node.getAnnotations());
@@ -228,27 +229,37 @@ private void exportLadybug(Java2GraphConfig config, GraphContext context) {
228229
executeOrThrow(conn, "CREATE REL TABLE Calls(FROM Method TO Method)");
229230

230231
Path tempDir = Files.createTempDirectory("ladybug_import");
232+
233+
// Take consistent snapshots to prevent race conditions from background threads
234+
// that may still be adding to the ConcurrentHashMap.
235+
List<ClassNode> classSnapshot = new java.util.ArrayList<>(context.classes.values());
236+
List<MethodNode> methodSnapshot = new java.util.ArrayList<>(context.methods.values());
237+
Set<String> classIds = new HashSet<>();
238+
Set<String> methodIds = new HashSet<>();
239+
231240
Path classesCsv = tempDir.resolve("classes.csv");
232241
try (FileWriter out = new FileWriter(classesCsv.toFile());
233242
CSVPrinter printer = new CSVPrinter(out, CSVFormat.DEFAULT)) {
234-
for (ClassNode node : context.classes.values()) {
243+
for (ClassNode node : classSnapshot) {
235244
if (node.getId() != null && !node.getId().isBlank()) {
236245
String annotations = String.join(";", node.getAnnotations());
237246
printer.printRecord(node.getId(), node.getFqn(), node.getName(), node.isInterface(),
238247
node.isExternal(), annotations, node.getDeclarationCode(), node.getFilePath());
248+
classIds.add(node.getId());
239249
}
240250
}
241251
}
242252

243253
Path methodsCsv = tempDir.resolve("methods.csv");
244254
try (FileWriter out = new FileWriter(methodsCsv.toFile());
245255
CSVPrinter printer = new CSVPrinter(out, CSVFormat.DEFAULT)) {
246-
for (MethodNode node : context.methods.values()) {
256+
for (MethodNode node : methodSnapshot) {
247257
if (node.getId() != null && !node.getId().isBlank()) {
248258
String annotations = String.join(";", node.getAnnotations());
249259
printer.printRecord(node.getId(), node.getFqn(), node.getName(), node.getSignature(),
250260
node.isExternal(), annotations, node.getSourceCode(), node.isLambda(),
251261
node.getFilePath());
262+
methodIds.add(node.getId());
252263
}
253264
}
254265
}
@@ -261,7 +272,9 @@ private void exportLadybug(Java2GraphConfig config, GraphContext context) {
261272
CSVPrinter implPrinter = new CSVPrinter(implOut, CSVFormat.DEFAULT)) {
262273
for (InheritanceEdge edge : context.inheritanceEdges) {
263274
if (edge.getChildFqn() != null && !edge.getChildFqn().isBlank() &&
264-
edge.getParentFqn() != null && !edge.getParentFqn().isBlank()) {
275+
edge.getParentFqn() != null && !edge.getParentFqn().isBlank() &&
276+
classIds.contains(edge.getChildFqn()) &&
277+
classIds.contains(edge.getParentFqn())) {
265278
if ("EXTENDS".equals(edge.getType())) {
266279
extPrinter.printRecord(edge.getChildFqn(), edge.getParentFqn());
267280
} else {
@@ -276,7 +289,9 @@ private void exportLadybug(Java2GraphConfig config, GraphContext context) {
276289
CSVPrinter printer = new CSVPrinter(out, CSVFormat.DEFAULT)) {
277290
for (MethodCallEdge edge : context.callEdges) {
278291
if (edge.getCallerMethodFqn() != null && !edge.getCallerMethodFqn().isBlank() &&
279-
edge.getCalledMethodFqn() != null && !edge.getCalledMethodFqn().isBlank()) {
292+
edge.getCalledMethodFqn() != null && !edge.getCalledMethodFqn().isBlank() &&
293+
methodIds.contains(edge.getCallerMethodFqn()) &&
294+
methodIds.contains(edge.getCalledMethodFqn())) {
280295
printer.printRecord(edge.getCallerMethodFqn(), edge.getCalledMethodFqn());
281296
}
282297
}
@@ -285,10 +300,12 @@ private void exportLadybug(Java2GraphConfig config, GraphContext context) {
285300
Path definesCsv = tempDir.resolve("defines.csv");
286301
try (FileWriter out = new FileWriter(definesCsv.toFile());
287302
CSVPrinter printer = new CSVPrinter(out, CSVFormat.DEFAULT)) {
288-
for (MethodNode node : context.methods.values()) {
303+
for (MethodNode node : methodSnapshot) {
289304
if (node.getContainingClassFqn() != null && !node.getContainingClassFqn().isBlank() &&
290-
node.getFqn() != null && !node.getFqn().isBlank()) {
291-
printer.printRecord(node.getContainingClassFqn(), node.getFqn());
305+
node.getId() != null && !node.getId().isBlank() &&
306+
classIds.contains(node.getContainingClassFqn()) &&
307+
methodIds.contains(node.getId())) {
308+
printer.printRecord(node.getContainingClassFqn(), node.getId());
292309
}
293310
}
294311
}
@@ -446,10 +463,11 @@ private void exportLadybug(Java2GraphConfig config, GraphContext context) {
446463
}
447464
}
448465
for (MethodNode node : context.methods.values()) {
449-
if (node.getContainingClassFqn() != null) {
466+
if (node.getContainingClassFqn() != null && node.getId() != null &&
467+
!node.getId().isBlank()) {
450468
addBatch.accept(String.format(
451469
"MATCH (a:Class {id: '%s'}), (b:Method {id: '%s'}) MERGE (a)-[r:Defines]->(b)",
452-
escape(node.getContainingClassFqn()), escape(node.getFqn())));
470+
escape(node.getContainingClassFqn()), escape(node.getId())));
453471
edgeCount++;
454472
}
455473
}

src/main/java/com/neuvem/java2graph/passes/ParsePass.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ private void indexJarSurfaceArea(GraphContext context, Path path) {
357357
if (Files.isDirectory(path)) {
358358
try (Stream<Path> paths = Files.walk(path)) {
359359
paths.filter(p -> p.toString().endsWith(".jar"))
360-
.filter(p -> !p.toString().contains(".java2graph") && !p.toString().contains(".decypher"))
360+
.filter(p -> !p.toString().contains("/.java2graph/") && !p.toString().contains("/.decypher/graph"))
361361
.forEach(p -> scanJar(context, p));
362362
} catch (IOException e) {
363363
System.err.println("Warning: Failed to walk jar directory: " + path);
@@ -392,7 +392,7 @@ private void scanAndAddJars(java.util.List<java.net.URL> urls, Path path) {
392392
if (Files.isDirectory(path)) {
393393
try (Stream<Path> paths = Files.walk(path)) {
394394
paths.filter(p -> p.toString().endsWith(".jar"))
395-
.filter(p -> !p.toString().contains(".java2graph") && !p.toString().contains(".decypher"))
395+
.filter(p -> !p.toString().contains("/.java2graph/") && !p.toString().contains("/.decypher/graph"))
396396
.forEach(p -> {
397397
try {
398398
urls.add(p.toUri().toURL());

0 commit comments

Comments
 (0)