Commit e68a2d42 authored by Carlos Galindo's avatar Carlos Galindo
Browse files

Slicer CLI entrypoint

* Includes minor fixes to slicing criterion and slice to AST conversion
* Minor changes in logging, to avoid logging without error/warning
parent 31f15bcc
Loading
Loading
Loading
Loading
+0 −90
Original line number Diff line number Diff line
package tfm.cli;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.MethodDeclaration;
import tfm.graphs.Graph;
import tfm.graphs.cfg.CFG;
import tfm.graphs.pdg.PDG;
import tfm.graphs.sdg.SDG;
import tfm.utils.Logger;
import tfm.utils.Utils;

import java.io.File;
import java.io.IOException;
import java.util.Objects;
import java.util.Optional;

public class Main {

    public static final String PROGRAM = Utils.PROGRAMS_FOLDER + "sdg/Example1.java";
    public static final String GRAPH = GraphLog.PDG;
    public static final String METHOD = "main";

    public static void main(String[] args) throws IOException {
        JavaParser.getStaticConfiguration().setAttributeComments(false);

        // File
        File file = new File(PROGRAM);
        Node root = JavaParser.parse(file);

        if (!METHOD.isEmpty()) {
            Optional<MethodDeclaration> methodDeclarationOptional = root.findFirst(MethodDeclaration.class,
                    methodDeclaration -> Objects.equals(methodDeclaration.getNameAsString(), METHOD));

            if (!methodDeclarationOptional.isPresent()) {
                Logger.format("Method '%s' not found in '%s'. Exiting...", METHOD, PROGRAM);
                return;
            }

            root = methodDeclarationOptional.get();
        }

        // GraphLog
        long t0 = System.nanoTime();
        Graph graph = getBuiltGraph(args.length == 1 ? args[0] : GRAPH, (MethodDeclaration) root);
        long tf = System.nanoTime();
        long tt = tf - t0;

        GraphLog<?> graphLog = getGraphLog(graph);
        graphLog.log();
        graphLog.openVisualRepresentation();

        Logger.log();
        Logger.format("Graph generated in %.2f ms", tt / 10e6);
    }

    private static Graph getBuiltGraph(String graph, MethodDeclaration method) {
        switch (graph) {
            case GraphLog.CFG:
                CFG cfg = new CFG();
                cfg.build(method);
                return cfg;
            case GraphLog.PDG:
                PDG pdg = new PDG();
                pdg.build(method);
                return pdg;
            case GraphLog.SDG:
                SDG sdg = new SDG();
                sdg.build(new NodeList<>(method.findCompilationUnit().get()));
                return sdg;
            default:
                Logger.log("Unkown graph type");
                System.exit(1);
                return null;
        }
    }

    private static GraphLog<?> getGraphLog(Graph graph) {
        if (graph instanceof CFG)
            return new CFGLog((CFG) graph);
        else if (graph instanceof PDG)
            return new PDGLog((PDG) graph);
        else if (graph instanceof SDG)
            return new SDGLog((SDG) graph);
        Logger.log("Unknown graph type");
        System.exit(1);
        return null;
    }
}
+258 −0
Original line number Diff line number Diff line
package tfm.cli;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.comments.BlockComment;
import com.github.javaparser.ast.nodeTypes.NodeWithName;
import com.github.javaparser.symbolsolver.JavaSymbolSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver;
import org.apache.commons.cli.*;
import tfm.graphs.sdg.SDG;
import tfm.slicing.FileLineSlicingCriterion;
import tfm.slicing.Slice;
import tfm.slicing.SlicingCriterion;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Slicer {
    protected static final Pattern SC_PATTERN;
    protected static final File DEFAULT_OUTPUT_DIR = new File("./slice/");
    protected static final Options OPTIONS = new Options();

    static {
        String fileRe = "(?<file>[^#]+\\.java)";
        String lineRe = "(?<line>[1-9]\\d*)";
        String varsRe = "(?<vars>[a-zA-Z_]\\w*(?:,[a-zA-Z_]\\w*)*)";
        String numsRe = "(?<nums>[1-9]\\d*(?:,[1-9]\\d*)*)";
        SC_PATTERN = Pattern.compile(fileRe + "#" + lineRe + "(?::" + varsRe + "(?:!" + numsRe + ")?)?");
    }

    static {
        OptionGroup criterionOptionGroup = new OptionGroup();
        criterionOptionGroup.addOption(Option
                .builder("f").longOpt("file")
                .hasArg().argName("CriterionFile.java").type(File.class)
                .desc("The file that contains the slicing criterion.")
                .build());
        criterionOptionGroup.addOption(Option
                .builder("l").longOpt("line")
                .hasArg().argName("lineNumber").type(Integer.class)
                .desc("The line that contains the statement of the slicing criterion.")
                .build());
        criterionOptionGroup.addOption(Option
                .builder("v").longOpt("var")
                .hasArgs().argName("variableName").valueSeparator(',')
                .desc("The name of the variable of the slicing criterion. Not setting this option is" +
                        " equivalent to selecting an empty set; setting multiple variables is allowed," +
                        " separated by commas")
                .build());
        criterionOptionGroup.addOption(Option
                .builder("n").longOpt("number")
                .hasArgs().argName("occurrenceNumber").valueSeparator(',')
                .desc("The occurrence number of the variable(s) selected. If this argument is not set, it will" +
                        " default to the first appearance of each variable. If the occurrence number must be set" +
                        " for every variable in the same order.")
                .build());
        OPTIONS.addOptionGroup(criterionOptionGroup);
        OPTIONS.addOption(Option
                .builder("c").longOpt("criterion")
                .hasArg().argName("file#line[:var[!occurrence]]")
                .desc("The slicing criterion, in the format \"file#line:var\". Optionally, the occurrence can be" +
                        " appended as \"!occurrence\". This option replaces \"-f\", \"-l\", \"-v\" and \"-n\", and" +
                        " functions in a similar way: the variable and occurrence may be skipped or declared multiple times.")
                .build());
        OPTIONS.addOption(Option
                .builder("i").longOpt("include")
                .hasArgs().argName("directory[,directory,...]").valueSeparator(',')
                .desc("Includes the directories listed in the search for methods called from the slicing criterion " +
                        "(directly or transitively). Methods that are not included here or part of the JRE, including" +
                        " third party libraries will not be analyzed, resulting in less precise slicing.")
                .build());
        OPTIONS.addOption(Option
                .builder("o").longOpt("output")
                .hasArg().argName("outputDir")
                .desc("The directory where the sliced source code should be placed. By default, it is placed at " +
                        DEFAULT_OUTPUT_DIR)
                .build());
        OPTIONS.addOption(Option
                .builder("h").longOpt("help")
                .desc("Shows this text")
                .build());
    }

    private final Set<File> dirIncludeSet = new HashSet<>();
    private File outputDir = DEFAULT_OUTPUT_DIR;
    private File scFile;
    private int scLine;
    private final List<String> scVars = new ArrayList<>();
    private final List<Integer> scVarOccurrences = new ArrayList<>();

    public Slicer(String... cliArgs) throws ParseException {
        CommandLine cl = new DefaultParser().parse(OPTIONS, cliArgs);
        if (cl.hasOption('h'))
            throw new ParseException(OPTIONS.toString());
        if (cl.hasOption('c')) {
            Matcher matcher = SC_PATTERN.matcher(cl.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 vars = matcher.group("vars");
            String nums = matcher.group("nums");
            if (vars != null) {
                if (nums != null)
                    setScVars(vars.split(","), nums.split(","));
                else
                    setScVars(vars.split(","));
            }
        } else if (cl.hasOption('f') && cl.hasOption('l')) {
            setScFile(cl.getOptionValue('f'));
            setScLine((Integer) cl.getParsedOptionValue("l"));
            if (cl.hasOption('v')) {
                if (cl.hasOption('n'))
                    setScVars(cl.getOptionValues('v'), cl.getOptionValues('n'));
                else
                    setScVars(cl.getOptionValues('v'));
            }
        } else {
            throw new ParseException("Slicing criterion not specified: either use \"-c\" or \"-f\" and \"l\".");
        }

        if (cl.hasOption('o'))
            outputDir = (File) cl.getParsedOptionValue("o");

        if (cl.hasOption('i')) {
            for (String str : cl.getOptionValues('i')) {
                File dir = new File(str);
                if (!dir.isDirectory())
                    throw new ParseException("One of the include directories is not a directory or isn't accesible: " + str);
                dirIncludeSet.add(dir);
            }
        }
    }

    private void setScFile(String fileName) throws ParseException {
        File file = new File(fileName);
        if (!(file.exists() && file.isFile()))
            throw new ParseException("Slicing criterion file is not an existing file.");
        scFile = file;
    }

    private void setScLine(int line) throws ParseException {
        if (line <= 0)
            throw new ParseException("The line of the slicing criterion must be strictly greater than zero.");
        scLine = line;
    }

    private void setScVars(String[] scVars) throws ParseException {
        String[] array = new String[scVars.length];
        Arrays.fill(array, "1");
        setScVars(scVars, array);
    }

    private void setScVars(String[] scVars, String[] scOccurrences) throws ParseException {
        if (scVars.length != scOccurrences.length)
            throw new ParseException("If the number of occurrence is specified, it must be specified once per variable.");
        try {
            for (int i = 0; i < scVars.length; i++) {
                this.scVars.add(scVars[i]);
                int n = Integer.parseUnsignedInt(scOccurrences[i]);
                if (n <= 0)
                    throw new ParseException("The number of occurrence must be larger than 0.");
                this.scVarOccurrences.add(n);
            }
        } catch (NumberFormatException e) {
            throw new ParseException(e.getMessage());
        }
    }

    public Set<File> getDirIncludeSet() {
        return Collections.unmodifiableSet(dirIncludeSet);
    }

    public File getOutputDir() {
        return outputDir;
    }

    public File getScFile() {
        return scFile;
    }

    public int getScLine() {
        return scLine;
    }

    public List<String> getScVars() {
        return Collections.unmodifiableList(scVars);
    }

    public List<Integer> getScVarOccurrences() {
        return Collections.unmodifiableList(scVarOccurrences);
    }

    public void slice() throws ParseException {
        // Configure JavaParser
        CombinedTypeSolver combinedTypeSolver = new CombinedTypeSolver();
        combinedTypeSolver.add(new ReflectionTypeSolver(true));
        for (File directory : dirIncludeSet)
            combinedTypeSolver.add(new JavaParserTypeSolver(directory));
        JavaParser.getStaticConfiguration().setSymbolResolver(new JavaSymbolSolver(combinedTypeSolver));
        JavaParser.getStaticConfiguration().setAttributeComments(false);

        // Build the SDG
        NodeList<CompilationUnit> units = new NodeList<>();
        try {
            for (File directory : dirIncludeSet)
                units.add(JavaParser.parse(directory));
            CompilationUnit scUnit = JavaParser.parse(scFile);
            if (!units.contains(scUnit))
                units.add(scUnit);
        } catch (FileNotFoundException e) {
            throw new ParseException(e.getMessage());
        }
        SDG sdg = new SDG();
        sdg.build(units);

        // Slice the SDG
        SlicingCriterion sc = new FileLineSlicingCriterion(scFile, scLine);
        Slice slice = sdg.slice(sc);

        // Convert the slice to code and output the result to `outputDir`
        for (CompilationUnit cu : slice.toAst()) {
            if (cu.getStorage().isEmpty())
                throw new IllegalStateException("A synthetic CompilationUnit was discovered, with no file associated to it.");
            String packagePath = cu.getPackageDeclaration().map(NodeWithName::getNameAsString).orElse("").replace(".", "/");
            File packageDir = new File(outputDir, packagePath);
            packageDir.mkdirs();
            File javaFile = new File(packageDir, cu.getStorage().get().getFileName());
            try (PrintWriter pw = new PrintWriter(javaFile)) {
                pw.print(new BlockComment(getDisclaimer(cu.getStorage().get())));
                pw.print(cu);
            } catch (FileNotFoundException e) {
                System.err.println("Could not write file " + javaFile);
            }
        }
    }

    protected String getDisclaimer(CompilationUnit.Storage s) {
        return String.format("\n\tThis file was automatically generated as part of a slice with criterion" +
                        "\n\tfile: %s, line: %d, variable(s): %s\n\tOriginal file: %s\n",
                scFile, scLine, String.join(", ", scVars), s.getPath());
    }

    public static void main(String... args) {
        try {
            new Slicer(args).slice();
        } catch (ParseException e) {
            System.err.println("Error parsing the arguments!\n" + e.getMessage());
        }
    }
}
+0 −4
Original line number Diff line number Diff line
@@ -10,7 +10,6 @@ import com.github.javaparser.ast.expr.*;
import com.github.javaparser.ast.stmt.*;
import com.github.javaparser.ast.visitor.VoidVisitorAdapter;
import tfm.arcs.Arc;
import tfm.arcs.pdg.DataDependencyArc;
import tfm.graphs.cfg.CFG;
import tfm.nodes.GraphNode;
import tfm.nodes.TypeNodeFactory;
@@ -19,7 +18,6 @@ import tfm.utils.Context;
import tfm.utils.Logger;

import java.util.*;
import java.util.stream.Collectors;

class MethodCallReplacerVisitor extends VoidVisitorAdapter<Context> {

@@ -94,8 +92,6 @@ class MethodCallReplacerVisitor extends VoidVisitorAdapter<Context> {
//                .filter(Expression::isMethodCallExpr)
//                .forEach(expression -> expression.accept(this, context));

        Logger.log("MethodCallReplacerVisitor", context);

        Optional<GraphNode<MethodDeclaration>> optMethodDeclGraphNode = methodCallExpr.resolve().toAst()
                .flatMap(sdg::findNodeByASTNode);

+1 −1
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ public class FileLineSlicingCriterion extends LineNumberCriterion {
        for (CompilationUnit cu : cus) {
            Optional<CompilationUnit.Storage> optStorage = cu.getStorage();
            if (optStorage.isPresent() && optStorage.get().getFileName().equals(file.getName())
                    && optStorage.get().getDirectory().equals(file.getParentFile().toPath()))
                    && optStorage.get().getDirectory().toAbsolutePath().equals(file.getParentFile().toPath().toAbsolutePath()))
                return Optional.of(cu);
        }
        return Optional.empty();
+8 −10
Original line number Diff line number Diff line
@@ -52,15 +52,14 @@ public class Slice {
     */
    public NodeList<CompilationUnit> toAst() {
        Map<CompilationUnit, Set<Node>> cuMap = new HashMap<>();
        // Build key set
        nodes.stream().filter(n -> n instanceof CompilationUnit)
                .forEach(cu -> cuMap.put((CompilationUnit) cu, new HashSet<>()));
        // Add each node to the corresponding bucket of the map
        // Nodes may not belong to a compilation unit (fictional nodes), and they are skipped for the slice.
        for (Node node : nodes)
            node.findCompilationUnit()
                    .flatMap(n -> Optional.ofNullable(cuMap.get(n)))
                    .ifPresent(set -> set.add(node));
        for (Node node : nodes) {
            Optional<CompilationUnit> cu = node.findCompilationUnit();
            if (cu.isEmpty()) continue;
            cuMap.putIfAbsent(cu.get(), new HashSet<>());
            cuMap.get(cu.get()).add(node);
        }
        // Traverse the AST of each compilation unit, creating a copy and
        // removing any element not present in the slice.
        NodeList<CompilationUnit> cus = new NodeList<>();
@@ -70,9 +69,8 @@ public class Slice {
            CompilationUnit clone = (CompilationUnit) entry.getKey().accept(cloneVisitor, null);
            assert entry.getKey().getStorage().isPresent();
            clone.setStorage(entry.getKey().getStorage().get().getPath());
            Visitable sliced = clone.accept(sliceVisitor, entry.getValue());
            assert sliced instanceof CompilationUnit;
            cus.add((CompilationUnit) sliced);
            clone.accept(sliceVisitor, entry.getValue());
            cus.add(clone);
        }
        return cus;
    }
Loading