Commit 629420d7 authored by Carlos Galindo's avatar Carlos Galindo
Browse files

Empirical evaluation for JLAMP 2022

- Updated README.md
- New module for benchmarks (sdg-bench)
- Added representations for the original JSysDG and for the Allen&Horwitz SDG (graph and slicing algorithm)
- Fixed bugs:
  * Accept abstract methods (without a body)
  * Avoid generating constructors for interfaces
  * Handle calls outside methods
  * Handle methods inside methods
  * Locate and handle all method calls (including on the left-hand side of assignments and in return statements).
  * Correctly determining the type of -output- nodes
  * Various errors related to object tree location and traversal
parent b91f0ef0
Loading
Loading
Loading
Loading
Loading
+2 −4
Original line number Diff line number Diff line
@@ -13,7 +13,7 @@ Warning: all method calls must resolve to a method declaration. If your Java pro

JavaSDGSlicer manages its dependencies through maven, so you need to have the JDK (≥11) and Maven installed, then run 
```
mvn package
mvn package -Dmaven.test.skip
```

A fat jar containing all the project's dependencies can be then located at `./sdg-cli/target/sdg-cli-{version}-jar-with-dependencies.jar`.
@@ -57,7 +57,7 @@ java -jar sdg-cli.jar --help
Our slicer requires the input Java program to be compilable, so all libraries must be provided using the `-i` flag. For the cases where the source code is not available, you may include the required libraries in the Java classpath by using the following call:

```
java -cp sdg-cli.jar:your-libraries.jar es.upv.slicing.cli.Slicer -c Example.java#11:sum
java -cp your-libraries.jar -jar sdg-cli.jar -c Example.java#11:sum
```

This approach produces lower quality slices, as the contents of the library calls are unknown.
@@ -77,6 +77,4 @@ If the graph is of interest, it can be outputted in `dot` or PDF format via `SDG

## Missing Java features

* Object-oriented features: abstract classes, interfaces, class, method and field inheritance, anonymous classes, lambdas.
* Parallel features: threads, shared memory, synchronized methods, etc.
* Exception handling: `finally`, try with resources.
+1 −0
Original line number Diff line number Diff line
@@ -40,5 +40,6 @@
        <module>sdg-core</module>
        <module>sdg-cli</module>
        <module>javaparser-symbol-solver-core</module>
        <module>sdg-bench</module>
    </modules>
</project>

sdg-bench/pom.xml

0 → 100644
+61 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>sdg</artifactId>
        <groupId>es.upv.mist.slicing</groupId>
        <version>1.3.0</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>sdg-bench</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>es.upv.mist.slicing</groupId>
            <artifactId>sdg-core</artifactId>
            <version>${project.version}</version>
        </dependency>
        <dependency>
            <groupId>com.github.javaparser</groupId>
            <artifactId>javaparser-core</artifactId>
            <version>3.23.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <version>3.3.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <mainClass>es.upv.mist.slicing.benchmark.BenchSC</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>assemble-all</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>
 No newline at end of file
+253 −0
Original line number Diff line number Diff line
package es.upv.mist.slicing.benchmark;

import com.github.javaparser.ParseException;
import com.github.javaparser.Problem;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.stmt.ReturnStmt;
import com.github.javaparser.symbolsolver.resolution.typesolvers.JavaParserTypeSolver;
import es.upv.mist.slicing.arcs.pdg.StructuralArc;
import es.upv.mist.slicing.graphs.augmented.ASDG;
import es.upv.mist.slicing.graphs.augmented.PSDG;
import es.upv.mist.slicing.graphs.exceptionsensitive.AllenSDG;
import es.upv.mist.slicing.graphs.exceptionsensitive.ESSDG;
import es.upv.mist.slicing.graphs.jsysdg.JSysDG;
import es.upv.mist.slicing.graphs.jsysdg.OriginalJSysDG;
import es.upv.mist.slicing.graphs.sdg.SDG;
import es.upv.mist.slicing.nodes.GraphNode;
import es.upv.mist.slicing.nodes.SyntheticNode;
import es.upv.mist.slicing.slicing.OriginalJSysDGSlicingAlgorithm;
import es.upv.mist.slicing.slicing.SlicingCriterion;
import es.upv.mist.slicing.utils.NodeHashSet;
import es.upv.mist.slicing.utils.StaticTypeSolver;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintWriter;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class BenchSC {
    protected static final int BUILD_TIMES = 0, SLICE_TIMES = 1, SLICE_SIZES = 2, EXIT = 3;
    protected final String[] dirIncludeSet = System.getProperty("sInclude", "").split(":");
    protected String graphType;

    public void benchmark() {
        // Obtain parameters
        String baselineGraph = System.getProperty("sGraphBaseline");
        String benchGraph = System.getProperty("sGraphBench");
        int minIter = Integer.parseInt(System.getProperty("sMinIter", "100"));
        String outputPrefix = System.getProperty("sOutputPrefix", "result");

        // Files
        File buildBaseTime = new File(outputPrefix + "buildBaseTime.out");
        File buildBenchTime = new File(outputPrefix + "buildBenchTime.out");
        File nodeCount = new File(outputPrefix + "nodesBaseline.out");
        File sliceBaseTime = new File(outputPrefix + "sliceBaseTime.out");
        File sliceBenchTime = new File(outputPrefix + "sliceBenchTime.out");

        // Configure JavaParser
        StaticJavaParser.getConfiguration().setAttributeComments(false);
        StaticTypeSolver.addTypeSolverJRE();
        for (String directory : dirIncludeSet)
            StaticTypeSolver.addTypeSolver(new JavaParserTypeSolver(directory));

        while (true) {
            switch (selectOption()) {
                case BUILD_TIMES:
                    graphType = baselineGraph;
                    timedRun(this::buildGraph, minIter, buildBaseTime);
                    graphType = benchGraph;
                    timedRun(this::buildGraph, minIter, buildBenchTime);
                    break;
                case SLICE_SIZES:
                    try (PrintWriter pw = new PrintWriter(nodeCount)) {
                        graphType = "JSysDG";
                        SDG baseSDG = buildGraph();
                        pw.println("# File format: for each SDG type, the total number of nodes and then the number of ");
                        pw.println(baseSDG.vertexSet().size());
                        Collection<SlicingCriterion> baseCriteria = findSCs(baseSDG);
                        System.out.printf("There are %d return object SCs", findReturnObjectSCs(baseSDG).size());
                        System.out.printf("There are %d real nodes SCs", findRealSCs(baseSDG).size());
                        System.exit(0);
                        for (SlicingCriterion sc : baseCriteria) {
                            int baseNodes = new OriginalJSysDGSlicingAlgorithm((JSysDG) baseSDG).traverse(sc.findNode(baseSDG)).getGraphNodes().size();
                            int benchNodes = baseSDG.slice(sc).getGraphNodes().size();
                            pw.printf("\"%s\",%d,%d\n", sc, baseNodes, benchNodes);
                        }
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                    break;
                case SLICE_TIMES:
                    try {
                        graphType = baselineGraph;
                        SDG sdg1 = buildGraph();
                        try (PrintWriter pw = new PrintWriter(sliceBaseTime)) {
                            pw.println("# SC id, SC time sequence");
                            for (SlicingCriterion sc : findSCs(sdg1))
                                timedRun(() -> sdg1.slice(sc), minIter, pw);
                        }
                        graphType = benchGraph;
                        SDG sdg2 = buildGraph();
                        try (PrintWriter pw = new PrintWriter(sliceBenchTime)) {
                            pw.println("# SC id, SC time sequence");
                            for (SlicingCriterion sc : findSCs(sdg2))
                                timedRun(() -> sdg2.slice(sc), minIter, pw);
                        }
                    } catch (FileNotFoundException e) {
                        throw new RuntimeException(e);
                    }
                    break;
                case EXIT:
                    return;
            }
        }
    }

    protected int selectOption() {
        Scanner in = new Scanner(System.in);
        System.out.println("Select an option:");
        System.out.println("\t[0]: Time the building of the graphs");
        System.out.println("\t[1]: Time the slicing of the graphs");
        System.out.println("\t[2]: Number of nodes per slice");
        System.out.println("\t[3]: Exit");
        System.out.print("> ");
        return in.nextInt();
    }

    protected SDG buildGraph() {
        try {
            // Build the SDG
            Set<CompilationUnit> units = new NodeHashSet<>();
            List<Problem> problems = new LinkedList<>();
            for (File file : (Iterable<File>) findAllJavaFiles(dirIncludeSet)::iterator)
                parse(file, units, problems);
            if (!problems.isEmpty()) {
                for (Problem p : problems)
                    System.out.println(" * " + p.getVerboseMessage());
                throw new ParseException("Some problems were found while parsing files or folders");
            }

            SDG sdg = createGraph(graphType);
            sdg.build(new NodeList<>(units));
            return sdg;
        } catch (ParseException e) {
            throw new RuntimeException(e);
        }
    }

    private void parse(File file, Set<CompilationUnit> units, List<Problem> problems) {
        try {
            units.add(StaticJavaParser.parse(file));
        } catch (FileNotFoundException e) {
            problems.add(new Problem(e.getLocalizedMessage(), null, e));
        }
    }

    protected Stream<File> findAllJavaFiles(String[] files) {
        Stream.Builder<File> builder = Stream.builder();
        for (String fileName : files) {
            File file = new File(fileName);
            if (file.isDirectory())
                findAllJavaFiles(file, builder);
            else
                builder.accept(file);
        }
        return builder.build();
    }

    protected void findAllJavaFiles(File directory, Stream.Builder<File> builder) {
        File[] files = directory.listFiles();
        if (files == null)
            return;
        for (File f : files) {
            if (f.isDirectory())
                findAllJavaFiles(f, builder);
            else if (f.getName().endsWith(".java"))
                builder.accept(f);
        }
    }

    protected SDG createGraph(String graphName) {
        switch (graphName) {
            case "SDG": return new SDG();
            case "ASDG": return new ASDG();
            case "PSDG": return new PSDG();
            case "ESSDG": return new ESSDG();
            case "AllenSDG": return new AllenSDG();
            case "JSysDG": return new JSysDG();
            case "OriginalJSysDG": return new OriginalJSysDG();
            default:
                throw new IllegalArgumentException();
        }
    }

    protected long[] timedRun(Runnable runnable, int iterations) {
        long[] times = new long[iterations];
        long t1, t2;
        for (int i = -1; i < iterations; i++) {
            t1 = System.nanoTime();
            runnable.run();
            t2 = System.nanoTime();
            if (i >= 0)
                times[i] = t2 - t1; // Times stored in nanoseconds
        }
        return times;
    }

    protected void timedRun(Runnable runnable, int minIter, File file) {
        long[] data = timedRun(runnable, minIter);
        try (PrintWriter pw = new PrintWriter(file)) {
            for (long d : data)
                pw.println(d);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    protected void timedRun(Runnable runnable, int minIter, PrintWriter pw) {
        long[] data = timedRun(runnable, minIter);
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < data.length; i++) {
            if (i > 0)
                builder.append(',');
            builder.append(data[i]);
        }
        pw.println(builder);
    }

    protected Collection<SlicingCriterion> findSCs(SDG sdg) {
        return findReturnObjectSCs(sdg);
    }

    protected Collection<SlicingCriterion> findRealSCs(SDG sdg) {
        return sdg.vertexSet().stream()
                .filter(Predicate.not(SyntheticNode.class::isInstance))
                .filter(Predicate.not(GraphNode::isImplicitInstruction))
                .sorted()
                .map(n -> (SlicingCriterion) graph -> Set.of(n))
                .collect(Collectors.toList());
    }

    protected Collection<SlicingCriterion> findReturnObjectSCs(SDG sdg) {
        return sdg.vertexSet().stream()
                .filter(gn -> gn.getAstNode() instanceof ReturnStmt)
                .flatMap(gn -> sdg.outgoingEdgesOf(gn).stream()
                        .filter(StructuralArc.class::isInstance)
                        .map(sdg::getEdgeTarget))
                .filter(gn -> sdg.outgoingEdgesOf(gn).stream().anyMatch(StructuralArc.class::isInstance))
                .map(n -> new SlicingCriterion() {
                    public Set<GraphNode<?>> findNode(SDG sdg) { return Set.of(n); }
                    public String toString() { return n.getLongLabel(); }
                })
                .collect(Collectors.toList());
    }

    public static void main(String... args) {
        new BenchSC().benchmark();
    }
}
+12 −2
Original line number Diff line number Diff line
@@ -130,6 +130,10 @@ public class CallGraph extends DirectedPseudograph<CallGraph.Vertex, CallGraph.E
        return addEdge(findVertexByDeclaration(source), findVertexByDeclaration(target), edge);
    }

    protected boolean addEdge(TypeDeclaration<?> source, CallableDeclaration<?> target, Resolvable<? extends ResolvedMethodLikeDeclaration> call) {
        return false; // TODO: handle static blocks
    }

    /** Find the calls to methods and constructors (edges) in the given list of compilation units. */
    protected void buildEdges(NodeList<CompilationUnit> arg) {
        arg.accept(new VoidVisitorAdapter<Void>() {
@@ -229,6 +233,11 @@ public class CallGraph extends DirectedPseudograph<CallGraph.Vertex, CallGraph.E
            }

            protected void createNormalEdge(CallableDeclaration<?> decl, Resolvable<? extends ResolvedMethodLikeDeclaration> call) {
                if (declStack.isEmpty() && typeStack.isEmpty())
                    throw new IllegalStateException("Trying to link call with empty declaration stack! " + decl.getDeclarationAsString() + " : " + call.toString());
                if (declStack.isEmpty())
                    addEdge(typeStack.peek(), decl, call);
                else
                    addEdge(declStack.peek(), decl, call);
            }

@@ -251,7 +260,7 @@ public class CallGraph extends DirectedPseudograph<CallGraph.Vertex, CallGraph.E
        for (GraphNode<?> node : cfgMap.get(declaration).vertexSet())
            if (node.containsCall(n))
                return node;
        throw new NodeNotFoundException("call " + n + " could not be located!");
        throw new NodeNotFoundException("call " + n + " could not be located! cfg was " + cfgMap.get(declaration).rootNode.getLongLabel() + " and declaration was " + declaration.getDeclarationAsString());
    }

    /** A vertex containing the declaration it represents. It only exists because
@@ -302,6 +311,7 @@ public class CallGraph extends DirectedPseudograph<CallGraph.Vertex, CallGraph.E

        public Edge(T call, GraphNode<?> graphNode) {
            assert call instanceof MethodCallExpr || call instanceof ObjectCreationExpr || call instanceof ExplicitConstructorInvocationStmt;
            assert graphNode.containsCall(call);
            this.call = call;
            this.graphNode = graphNode;
        }
Loading