From 3591720b303f2d31742f02363f08b0347dc596f8 Mon Sep 17 00:00:00 2001 From: SioYoo Date: Tue, 2 Dec 2025 22:39:30 +1100 Subject: [PATCH 1/3] feat: Add CLI option to slice all variables in a project and export results as JSON. --- sdg-cli/pom.xml | 5 + .../java/es/upv/mist/slicing/cli/Slicer.java | 180 ++++++++++++++++-- 2 files changed, 168 insertions(+), 17 deletions(-) diff --git a/sdg-cli/pom.xml b/sdg-cli/pom.xml index 4446f02b..3f3e4090 100644 --- a/sdg-cli/pom.xml +++ b/sdg-cli/pom.xml @@ -63,5 +63,10 @@ jgrapht-io 1.5.0 + + com.google.code.gson + gson + 2.10.1 + diff --git a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java index 765baf09..821b7eda 100644 --- a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java +++ b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java @@ -3,10 +3,17 @@ import com.github.javaparser.Problem; import com.github.javaparser.StaticJavaParser; import com.github.javaparser.ast.CompilationUnit; +import com.github.javaparser.ast.Node; import com.github.javaparser.ast.NodeList; +import com.github.javaparser.ast.body.CallableDeclaration; +import com.github.javaparser.ast.body.Parameter; +import com.github.javaparser.ast.body.VariableDeclarator; import com.github.javaparser.ast.comments.BlockComment; import com.github.javaparser.ast.nodeTypes.NodeWithName; +import com.github.javaparser.ast.stmt.Statement; import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; import es.upv.mist.slicing.graphs.augmented.ASDG; import es.upv.mist.slicing.graphs.augmented.PSDG; import es.upv.mist.slicing.graphs.exceptionsensitive.ESSDG; @@ -21,6 +28,8 @@ import java.io.File; import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; import java.io.PrintWriter; import java.util.*; import java.util.logging.Level; @@ -90,6 +99,10 @@ public class Slicer { .builder("h").longOpt("help") .desc("Shows this text") .build()); + OPTIONS.addOption(Option + .builder("a").longOpt("all") + .desc("Slice all variables in the project and export to JSON") + .build()); } private final Set dirIncludeSet = new HashSet<>(); @@ -103,22 +116,30 @@ public Slicer(String... cliArgs) throws ParseException { cliOpts = new DefaultParser().parse(OPTIONS, cliArgs); if (cliOpts.hasOption('h')) printHelp(); - if (cliOpts.hasOption('c')) { - Matcher matcher = SC_PATTERN.matcher(cliOpts.getOptionValue("criterion")); - if (!matcher.matches()) - throw new ParseException("Invalid format for slicing criterion, see --help for more details"); - setScFile(matcher.group("file")); - setScLine(Integer.parseInt(matcher.group("line"))); - String var = matcher.group("var"); - if (var != null) - setScVar(var); - } else if (cliOpts.hasOption('f') && cliOpts.hasOption('l')) { - setScFile(cliOpts.getOptionValue('f')); - setScLine(((Number) cliOpts.getParsedOptionValue("l")).intValue()); - if (cliOpts.hasOption('v')) - setScVar(cliOpts.getOptionValue('v')); + + if (cliOpts.hasOption('a')) { + // In 'all' mode, we don't need specific criterion arguments + if (cliOpts.hasOption('c') || (cliOpts.hasOption('f') && cliOpts.hasOption('l'))) { + System.out.println("Warning: Slicing criterion arguments ignored in 'all' mode."); + } } else { - throw new ParseException("Slicing criterion not specified: either use \"-c\" or \"-f\" and \"-l\"."); + if (cliOpts.hasOption('c')) { + Matcher matcher = SC_PATTERN.matcher(cliOpts.getOptionValue("criterion")); + if (!matcher.matches()) + throw new ParseException("Invalid format for slicing criterion, see --help for more details"); + setScFile(matcher.group("file")); + setScLine(Integer.parseInt(matcher.group("line"))); + String var = matcher.group("var"); + if (var != null) + setScVar(var); + } else if (cliOpts.hasOption('f') && cliOpts.hasOption('l')) { + setScFile(cliOpts.getOptionValue('f')); + setScLine(((Number) cliOpts.getParsedOptionValue("l")).intValue()); + if (cliOpts.hasOption('v')) + setScVar(cliOpts.getOptionValue('v')); + } else { + throw new ParseException("Slicing criterion not specified: either use \"-c\" or \"-f\" and \"-l\"."); + } } if (cliOpts.hasOption('o')) @@ -186,8 +207,11 @@ public void slice() throws ParseException { boolean scFileFound = false; for (File file : (Iterable) findAllJavaFiles(dirIncludeSet)::iterator) scFileFound |= parse(file, units, problems); - if (!scFileFound) + + // In 'all' mode, we might not have scFile, but we need to parse all files in include dirs + if (!cliOpts.hasOption('a') && !scFileFound) parse(scFile, units, problems); + if (!problems.isEmpty()) { for (Problem p : problems) System.out.println(" * " + p.getVerboseMessage()); @@ -207,6 +231,11 @@ public void slice() throws ParseException { Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.INFO, "Building the SDG"); sdg.build(new NodeList<>(units)); + if (cliOpts.hasOption('a')) { + sliceAll(sdg, units); + return; + } + // Slice the SDG Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.INFO, "Searching for criterion and slicing"); SlicingCriterion sc = new FileLineSlicingCriterion(scFile, scLine, scVar); @@ -231,13 +260,130 @@ public void slice() throws ParseException { } } + private void sliceAll(SDG sdg, Set units) { + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.INFO, "Slicing all variables..."); + List> results = new ArrayList<>(); + int eid = 0; + + for (CompilationUnit cu : units) { + if (cu.getStorage().isEmpty()) continue; + String filePath = cu.getStorage().get().getPath().toString(); + String fileName = cu.getStorage().get().getFileName(); + + List callables = cu.findAll(CallableDeclaration.class); + for (CallableDeclaration callable : callables) { + String functionName = callable.getNameAsString(); + String className = ""; + if (callable.getParentNode().isPresent() && callable.getParentNode().get() instanceof com.github.javaparser.ast.body.TypeDeclaration) { + className = ((com.github.javaparser.ast.body.TypeDeclaration) callable.getParentNode().get()).getNameAsString(); + } + + String functionCode = callable.toString(); + List> slices = new ArrayList<>(); + + // Find variables + List vars = callable.findAll(VariableDeclarator.class); + // Also parameters + List params = callable.findAll(Parameter.class); + + List allVars = new ArrayList<>(); + allVars.addAll(vars); + allVars.addAll(params); + + for (com.github.javaparser.ast.Node varNode : allVars) { + String varName = ""; + int line = -1; + if (varNode instanceof VariableDeclarator) { + varName = ((VariableDeclarator) varNode).getNameAsString(); + line = ((VariableDeclarator) varNode).getBegin().map(p -> p.line).orElse(-1); + } else if (varNode instanceof Parameter) { + varName = ((Parameter) varNode).getNameAsString(); + line = ((Parameter) varNode).getBegin().map(p -> p.line).orElse(-1); + } + + if (line == -1) continue; + + try { + SlicingCriterion sc = new FileLineSlicingCriterion(new File(filePath), line, varName); + Slice slice = sdg.slice(sc); + + // Process slice + List> nodesData = new ArrayList<>(); + + // Get all statements in the function to map y_bwd + List statements = callable.findAll(Statement.class); + + // Optimization: Build a set of AST nodes in the slice + Set slicedAstNodes = new HashSet<>(); + for (es.upv.mist.slicing.nodes.GraphNode gn : slice.getGraphNodes()) { + if (gn.getAstNode() != null) { + slicedAstNodes.add(gn.getAstNode()); + } + } + + for (Statement stmt : statements) { + if (!stmt.getBegin().isPresent()) continue; + + boolean inSlice = slicedAstNodes.contains(stmt); + + Map nodeInfo = new HashMap<>(); + nodeInfo.put("line", stmt.getBegin().get().line); + nodeInfo.put("code", stmt.toString()); + nodeInfo.put("y_fwd", 0); // Forward slicing not supported yet + nodeInfo.put("y_bwd", inSlice ? 1 : 0); + nodeInfo.put("id", String.valueOf(stmt.hashCode())); + + nodesData.add(nodeInfo); + } + + Map sliceData = new HashMap<>(); + Map criterion = new HashMap<>(); + criterion.put("variable", varName); + criterion.put("line", line); + sliceData.put("slice_criterion", criterion); + sliceData.put("nodes", nodesData); + sliceData.put("edges", new ArrayList<>()); // Empty for now + + slices.add(sliceData); + + } catch (Exception e) { + System.err.println("Error slicing variable " + varName + " at line " + line + ": " + e.getMessage()); + } + } + + if (!slices.isEmpty()) { + Map funcResult = new HashMap<>(); + funcResult.put("eid", eid++); + funcResult.put("function_name", functionName); + funcResult.put("class_name", className); + funcResult.put("file_name", fileName); + funcResult.put("language", "java"); + funcResult.put("function_code", functionCode); + funcResult.put("slices", slices); + results.add(funcResult); + } + } + } + + // Export to JSON + Gson gson = new GsonBuilder().setPrettyPrinting().create(); + File jsonFile = new File(outputDir, "slicing_result.json"); + outputDir.mkdirs(); + try (FileWriter writer = new FileWriter(jsonFile)) { + gson.toJson(results, writer); + Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.INFO, "Exported JSON to " + jsonFile.getAbsolutePath()); + } catch (IOException e) { + e.printStackTrace(); + } + } + private boolean parse(File file, Set units, List problems) { try { units.add(StaticJavaParser.parse(file)); } catch (FileNotFoundException e) { problems.add(new Problem(e.getLocalizedMessage(), null, e)); } - return Objects.equals(file.getAbsoluteFile(), scFile.getAbsoluteFile()); + return scFile != null && Objects.equals(file.getAbsoluteFile(), scFile.getAbsoluteFile()); } protected Stream findAllJavaFiles(Collection files) { From 3e12bbc18949713b222dabd9fd94f0039765634c Mon Sep 17 00:00:00 2001 From: SioYoo Date: Tue, 2 Dec 2025 22:46:21 +1100 Subject: [PATCH 2/3] feat: Add project name CLI option and use SHA-256 hash for stable EID generation in slice all mode. --- .../java/es/upv/mist/slicing/cli/Slicer.java | 59 ++++++++++++++++++- 1 file changed, 57 insertions(+), 2 deletions(-) diff --git a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java index 821b7eda..164e74d9 100644 --- a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java +++ b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java @@ -31,6 +31,9 @@ import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -103,6 +106,11 @@ public class Slicer { .builder("a").longOpt("all") .desc("Slice all variables in the project and export to JSON") .build()); + OPTIONS.addOption(Option + .builder("p").longOpt("project") + .hasArg().argName("project-name") + .desc("The name of the project (used for EID generation in -a mode)") + .build()); } private final Set dirIncludeSet = new HashSet<>(); @@ -263,12 +271,31 @@ public void slice() throws ParseException { private void sliceAll(SDG sdg, Set units) { Logger.getLogger(Logger.GLOBAL_LOGGER_NAME).log(Level.INFO, "Slicing all variables..."); List> results = new ArrayList<>(); - int eid = 0; + String projectName = cliOpts.getOptionValue("p", "unknown_project"); for (CompilationUnit cu : units) { if (cu.getStorage().isEmpty()) continue; String filePath = cu.getStorage().get().getPath().toString(); String fileName = cu.getStorage().get().getFileName(); + + // Try to make path relative to project root if possible, or just use filename for hash stability? + // User said "project name + file + variable". + // We'll use the file name as it appears in the storage (absolute path usually). + // To be safe across environments, maybe we should try to relativize against include dirs? + // But for now, let's use the full path or just filename if that's what "file" implies. + // Given "project name + file", usually implies relative path within project. + // Let's try to find the relative path from the include directories. + + String relativePath = filePath; + for (File includeDir : dirIncludeSet) { + if (filePath.startsWith(includeDir.getAbsolutePath())) { + relativePath = filePath.substring(includeDir.getAbsolutePath().length()); + if (relativePath.startsWith(File.separator)) { + relativePath = relativePath.substring(1); + } + break; + } + } List callables = cu.findAll(CallableDeclaration.class); for (CallableDeclaration callable : callables) { @@ -352,8 +379,18 @@ private void sliceAll(SDG sdg, Set units) { } if (!slices.isEmpty()) { + // Generate unique EID + // Hash: project + file + variable (we use function name too to be more precise?) + // User said "project name + file + variable". + // But we are grouping by function in the output structure. + // The 'eid' is per function entry in the JSON list. + // So it should be hash of project + file + function. + + String uniqueString = projectName + "|" + relativePath + "|" + functionName; + String eid = generateHash(uniqueString); + Map funcResult = new HashMap<>(); - funcResult.put("eid", eid++); + funcResult.put("eid", eid); funcResult.put("function_name", functionName); funcResult.put("class_name", className); funcResult.put("file_name", fileName); @@ -377,6 +414,24 @@ private void sliceAll(SDG sdg, Set units) { } } + private String generateHash(String input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + byte[] encodedhash = digest.digest(input.getBytes(StandardCharsets.UTF_8)); + StringBuilder hexString = new StringBuilder(2 * encodedhash.length); + for (int i = 0; i < encodedhash.length; i++) { + String hex = Integer.toHexString(0xff & encodedhash[i]); + if(hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + private boolean parse(File file, Set units, List problems) { try { units.add(StaticJavaParser.parse(file)); From a1537a59ff5b3665462d55cba4176ecfaf988483 Mon Sep 17 00:00:00 2001 From: SioYoo Date: Tue, 2 Dec 2025 23:04:11 +1100 Subject: [PATCH 3/3] feat: Add project name to function result map. --- sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java | 1 + 1 file changed, 1 insertion(+) diff --git a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java index 164e74d9..c3360379 100644 --- a/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java +++ b/sdg-cli/src/main/java/es/upv/mist/slicing/cli/Slicer.java @@ -391,6 +391,7 @@ private void sliceAll(SDG sdg, Set units) { Map funcResult = new HashMap<>(); funcResult.put("eid", eid); + funcResult.put("project_name", projectName); funcResult.put("function_name", functionName); funcResult.put("class_name", className); funcResult.put("file_name", fileName);