/*
* e-Knife, a program slicing tool for Erlang based on the EDG
* Copyright (c) 2021. David Insa, Sergio PĂ©rez, Josep Silva, Salvador Tamarit.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
*/
package eknife;
import edg.DotFactory;
import edg.EDGFactory;
import edg.PdfFactory;
import edg.graph.EDG;
import edg.graph.Edge;
import edg.graph.LAST;
import edg.graph.Node;
import edg.slicing.*;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
public class EKnife
{
public enum Language { Java, Erlang, Php }
public static void main(String[] args)
{
if (args.length == 0) {
EKnife.printHelp();
return;
}
final Args arguments = EKnife.processArguments(args);
if (!arguments.isValid()) {
EKnife.printHelp();
System.exit(3);
}
EKnife.run(arguments);
}
private static Args processArguments(String[] args)
{
Args kArgs = new Args();
for (int argIndex = 0; argIndex < args.length; argIndex++)
{
final String arg = args[argIndex];
switch (arg)
{
case "-i": case "--input":
case "-o": case "--output":
case "-f": case "--file":
case "-l": case "--line":
case "-v": case "--var":
case "-n": case "--occurrence":
case "-G": case "--print-graph":
if (argIndex == args.length - 1) {
System.out.printf("Parameter %s requires an argument\n", arg);
printHelp();
System.exit(1);
}
}
switch (arg)
{
case "--help":
printHelp();
System.exit(0);
case "-i": case "--input":
kArgs.inputPath = args[argIndex + 1];
break;
case "-o": case "--output":
kArgs.outputFile = new File(args[argIndex + 1]);
break;
case "-f": case "--file":
kArgs.file = args[argIndex + 1];
break;
case "-l": case "--line":
kArgs.line = Integer.parseInt(args[argIndex + 1]);
break;
case "-v": case "--var":
kArgs.name = args[argIndex + 1];
break;
case "-n": case "--occurrence":
kArgs.occurrence = Integer.parseInt(args[argIndex + 1]);
break;
case "-G": case "--print-graph":
kArgs.setGraphFile(new File(args[argIndex + 1]));
break;
}
}
kArgs.completeData();
return kArgs;
}
private static void printHelp()
{
String help = "";
help += "Use the following options:\n";
help += " -i,--input The file/folder where the source code is\n";
help += " -o,--output The file/folder where the slice will be stored\n";
help += " -f,--file The file (relative to -i) where the slicing criterion is\n";
help += " -l,--line The line of the slicing criterion\n";
help += " -v,--var The name of the slicing criterion (must be a variable)\n";
help += " -n,--occurrence The occurrence of the slicing criterion in that line\n";
help += " -G,--print-graph Exports the graph as a dot file\n";
help += " -G,--print-graph Exports the graph as a PDF file\n";
help += " --help Show this message.\n";
System.out.print(help);
}
private static void run(Args a)
{
final LAST last = LASTFactory.createLAST(Language.Erlang, a.inputPath, true);
final EDG edg = new EDGFactory(last).createEDG();
final SlicingCriterion slicingCriterion = new SlicingCriterion(a.file, a.line, a.name, a.occurrence);
final Node SC = edg.getNode(slicingCriterion);
if (SC == null) {
System.out.println("Error: the slicing criterion could not be found! " + slicingCriterion);
System.exit(1);
}
final SlicingAlgorithm slicingAlgorithm = new ConstrainedAlgorithm(edg);
final Set slice = slicingAlgorithm.slice(SC);
if (slice.isEmpty()) {
System.out.println("Warning: no files will be written, as the slice is empty.");
System.exit(2);
}
if (a.graphFile != null) {
switch (a.graphFormat) {
case DOT:
DotFactory.createDot(a.graphFile, edg, SC, slice);
break;
case PDF:
PdfFactory.createPdf(a.graphFile, edg, SC, slice);
break;
}
}
CodeFactory.createCode(Language.Erlang, a.outputFile, edg, slice);
}
private static void timedRun(Args a)
{
// CONFIGURE MEASURED DIMENSIONS
boolean measureGenerationTime = true;
boolean measureNodes = false;
boolean performAllSCs = true;
/*******************************************************/
/*** MEASUREMENT OF GENERATION TIME (100 executions) ***/
/*******************************************************/
int numberOfIterations = 100;
if (measureGenerationTime) {
// double StructuralTime[] = new double[100];
// double CFGTime[] = new double[100];
// double ValueTime[] = new double[100];
// double ControlTime[] = new double[100];
// double FlowTime[] = new double[100];
double totalStructural, totalCFG, totalValue, totalComposite, totalControl, totalFlow;
totalStructural = totalCFG = totalValue = totalComposite = totalControl = totalFlow = 0.0;
for (int i = -1; i < numberOfIterations; i++) {
final LAST last = LASTFactory.createLAST(Language.Erlang, a.inputPath, true);
final EDG edg = new EDGFactory(last).createEDG();
if (i == -1)
continue;
totalStructural += edg.getGenerationTime().getStructureTime();
totalCFG += edg.getGenerationTime().getControlFlowTime();
totalValue += edg.getGenerationTime().getValueTime();
totalComposite += edg.getGenerationTime().getCompositeTime();
totalControl += edg.getGenerationTime().getControlTime();
totalFlow += edg.getGenerationTime().getFlowTime();
// StructuralTime[i] = edg.getGenerationTime().getStructureTime();
// CFGTime[i] = edg.getGenerationTime().getControlFlowTime();
// ValueTime[i] = edg.getGenerationTime().getValueTime();
// ControlTime[i] = edg.getGenerationTime().getControlTime();
// FlowTime[i] = edg.getGenerationTime().getFlowTime();
}
// double totalStructural, totalCFG, totalValue, totalControl, totalFlow;
// totalStructural = totalCFG = totalValue = totalControl = totalFlow = 0.0;
// for (int i = 0; i < 100; i++){
// totalStructural += StructuralTime[i];
// totalCFG += CFGTime[i];
// totalValue += ValueTime[i];
// totalControl += ControlTime[i];
// totalFlow += FlowTime[i];
// }
// INTRAPROCEDURAL GENERATION TIME
// TIME MEASSUREMENT:
printGenerationTime(a.file, numberOfIterations, totalStructural, totalCFG, totalValue,
totalComposite, totalControl, totalFlow, false);
printGenerationTime(a.file, numberOfIterations, totalStructural, totalCFG, totalValue,
totalComposite, totalControl, totalFlow, true);
}
final LAST last = LASTFactory.createLAST(Language.Erlang, a.inputPath, true);
final EDG edg = new EDGFactory(last).createEDG();
/*****************************/
/*** MEASUREMENT OF NODES: ***/
/*****************************/
if (measureNodes) {
int SDGNodeCounter = countSDGNodes(edg);
int EDGNodeCounter = countEDGNodes(edg);
printNodeCounter(a.file, SDGNodeCounter, EDGNodeCounter);
}
/**************************/
/*** MEASUREMENT OF SCs ***/
/**************************/
if (performAllSCs) {
// SC SELECTION
List clauses = edg.getNodes(Node.Type.Clause);
clauses.removeIf(c -> edg.getParent(c).getType() != Node.Type.Routine);
printTitle("/Users/serperu/Desktop/SlicerOutput/Times/slicingStatistics.txt", a.file);
for (Node clause : clauses) {
List descendants = edg.getDescendants(clause);
descendants.add(clause);
List resultNodes = new LinkedList<>();
List nonResultNodes = new LinkedList<>();
for (Node n : descendants)
if (n.getType() == Node.Type.Result) {
if (edg.getNodeFromRes(n).getType() != Node.Type.Callee)
resultNodes.add(n);
} else
nonResultNodes.add(n);
nonResultNodes.add(clause);
resultNodes.add(edg.getResFromNode(clause));
// IGNORE FUNCTIONS WITHOUT COMPOSITE STRUCTURES
if (nonResultNodes.stream()
.noneMatch(
n -> { if (n.getType() == Node.Type.DataConstructor)
return true;
if (n.getType() == Node.Type.List)
if (edg.getChildren(n).size() != 0 || edg.getParent(n).getType() == Node.Type.List)
return true;
return false;})) {
System.out.println("File Name: " + a.file);
int functionArity = edg.getChildrenNonResult(edg.getChild(clause, Node.Type.Parameters)).size();
System.out.println("Function " + edg.getParent(clause).getName() + "/" + functionArity + " has no valid slicing criteria.");
continue;
}
/* FILTER SC NODES BY A CRITERION (REMOVE LITERALS) */
resultNodes.removeIf(n -> edg.getNodeFromRes(n).getType() == Node.Type.Literal);
/* SPECIFIC FILTER FOR BENCH1.ERL */
// TODO: Detect and generalise the scenario
if (a.file.equals("bench1.erl"))
resultNodes.removeIf(sc -> edg.getAncestor(sc, Node.Type.Equality) != null &&
edg.getChild(
edg.getAncestor(sc, Node.Type.Equality), Node.Type.Pattern)
.getName().equals("Database"));
/* *********** */
SlicingCriterionState[] allCriteriaStandard = new SlicingCriterionState[resultNodes.size()];
SlicingCriterionState[] allCriteriaConstrained = new SlicingCriterionState[resultNodes.size()];
// CONFIGURE MEASURED DIMENSIONS
boolean measureSlicingPerformance = false;
boolean measureSlicingTime = true;
final SlicingAlgorithm standardAlgorithm = new AdaptedStandardAlgorithm(edg);
final SlicingAlgorithm constrainedAlgorithm = new ConstrainedAlgorithm(edg);
int scCounter = 0;
int scConstrainedImprovements = 0;
for (Node SC : resultNodes) {
// INITIALIZATIONS
// PERFORMANCE
double standardSlicePercentage = 0.0;
double constrainedSlicePercentage = 0.0;
// TIME
double standardTimeMean = 0.0;
double constrainedTimeMean = 0.0;
// MEASURE TIME FOR EACH SC
if (measureSlicingTime) {
for (int j = -1; j < numberOfIterations; j++) {
long initialStandardTime = System.nanoTime();
standardAlgorithm.slice(SC);
long finalStandardTime = System.nanoTime();
long initialConstrainedTime = System.nanoTime();
constrainedAlgorithm.slice(SC);
long finalConstrainedTime = System.nanoTime();
if (j == -1)
continue;
standardTimeMean += (finalStandardTime - initialStandardTime) / (numberOfIterations * 1000000.0);
constrainedTimeMean += (finalConstrainedTime - initialConstrainedTime) / (numberOfIterations * 1000000.0);
}
standardTimeMean /= numberOfIterations;
constrainedTimeMean /= numberOfIterations;
}
// MEASURE PERFORMARNCE
final Set standardSlice = standardAlgorithm.slice(SC);
final Set constrainedSlice = constrainedAlgorithm.slice(SC);
if (measureSlicingPerformance) {
standardSlice.removeIf(n -> n.getType() == Node.Type.Result ||
n.getType() == Node.Type.Routine || n.getType() == Node.Type.Module
|| n.getType() == Node.Type.Root);
standardSlicePercentage = standardSlice.size() * 100.0 / nonResultNodes.size();
constrainedSlice.removeIf(n -> n.getType() == Node.Type.Result ||
n.getType() == Node.Type.Routine || n.getType() == Node.Type.Module
|| n.getType() == Node.Type.Root);
constrainedSlicePercentage = constrainedSlice.size() * 100.0 / nonResultNodes.size();
if (constrainedSlice.size() < standardSlice.size())
scConstrainedImprovements++;
if (standardSlicePercentage < constrainedSlicePercentage)
throw new IllegalStateException("The SDG cannot be more precise than the EDG");
}
allCriteriaStandard[scCounter] = new SlicingCriterionState(standardSlicePercentage, standardTimeMean);
allCriteriaConstrained[scCounter] = new SlicingCriterionState(constrainedSlicePercentage, constrainedTimeMean);
scCounter++;
}
int arity = edg.getChildrenNonResult(edg.getChild(clause, Node.Type.Parameters)).size();
printSlicingResults(edg.getParent(clause).getName() + "/" + arity,
resultNodes.size(), allCriteriaStandard, allCriteriaConstrained,
scConstrainedImprovements, measureSlicingPerformance, measureSlicingTime);
}
}
// DotFactory.createDot(new File("/Users/serperu/Desktop/SlicerOutput/graphSliced.dot"), edg, SC, slice, edgeFlags);
// CodeFactory.createCode(Language.Erlang, a.outputFile, edg, slice);
}
public static int countSDGNodes(EDG edg) {
Node root = edg.getRootNode();
return countSDGNodes(edg, root);
}
public static int countSDGNodes(EDG edg, Node node) {
int counter = 1;
List children = edg.getChildren(node);
for (Node child : children) {
if (child.getType() == Node.Type.Result)
continue;
if (child.getType() == Node.Type.List ||child.getType() == Node.Type.DataConstructor)
counter++;
else {
counter += countSDGNodes(edg, child);
}
}
return counter;
}
public static int countEDGNodes(EDG edg) {
Set nodes = new HashSet<>();
nodes.addAll(edg.vertexSet());
nodes.removeIf(n -> n.getType() == Node.Type.Result);
return nodes.size();
}
public static void printGenerationTime(String programName, int numberOfIterations, double totalStructural,
double totalCFG, double totalValue, double totalComposite,
double totalControl, double totalFlow, boolean isEDG) {
FileWriter timeFileWriter;
PrintWriter timeWriter = null;
try {
String graphName = isEDG ? "EDG" : "SDG";
timeFileWriter = new FileWriter("/Users/serperu/Desktop/SlicerOutput/Times/generationTime"+graphName+".txt", true);
timeWriter = new PrintWriter(timeFileWriter);
timeWriter.println("---------------------------");
timeWriter.println("-------- "+graphName+" TIME ---------");
timeWriter.println("---------------------------");
timeWriter.println("Program Name: " + programName);
timeWriter.println("---------------------------");
// timeWriter.println("StructuralTime: " + totalStructural / StructuralTime.length + " ms");
// timeWriter.println("ControlFlowTime: " + totalCFG / CFGTime.length + " ms");
// timeWriter.println("ValueTime: " + totalValue / ValueTime.length + " ms");
// timeWriter.println("ControlTime: " + totalControl / ControlTime.length + " ms");
// timeWriter.println("FlowTime: " + totalFlow / FlowTime.length + " ms");
// double TotalTime = (totalStructural + totalCFG + totalValue + totalControl + totalFlow) / StructuralTime.length;
// timeWriter.println("TOTAL TIME: " + TotalTime + " ms\n");
timeWriter.println("StructuralTime: " + totalStructural / numberOfIterations + " ms");
timeWriter.println("ControlFlowTime: " + totalCFG / numberOfIterations + " ms");
if (!isEDG)
totalValue -= totalComposite; // SDG Time ignores Composite Structures Generation
timeWriter.println("ValueTime: " + totalValue / numberOfIterations + " ms");
timeWriter.println("ControlTime: " + totalControl / numberOfIterations + " ms");
timeWriter.println("FlowTime: " + totalFlow / numberOfIterations + " ms");
double TotalTime = (totalStructural + totalCFG + totalValue + totalControl + totalFlow) / numberOfIterations;
timeWriter.println("TOTAL TIME: " + TotalTime + " ms\n");
} catch (IOException e) {
System.out.println("FILE NOT FOUND ERROR");
} finally {
if (timeWriter != null)
timeWriter.close();
}
}
public static void printNodeCounter(String programName, int SDGNodes, int EDGNodes) {
FileWriter timeFileWriter;
PrintWriter timeWriter = null;
try {
timeFileWriter = new FileWriter("/Users/serperu/Desktop/SlicerOutput/Times/programSizes.txt", true);
timeWriter = new PrintWriter(timeFileWriter);
timeWriter.println("---------------------------");
timeWriter.println("Program Name: " + programName);
timeWriter.println("---------------------------");
timeWriter.println(" SDG Nodes: " + SDGNodes);
timeWriter.println(" EDG Nodes: " + EDGNodes + "\n");
} catch (IOException e) {
System.out.println("FILE NOT FOUND ERROR");
} finally {
if (timeWriter != null)
timeWriter.close();
}
}
public static void printTitle(String outputFile, String fileName) {
FileWriter timeFileWriter;
PrintWriter timeWriter = null;
try {
timeFileWriter = new FileWriter(outputFile, true);
timeWriter = new PrintWriter(timeFileWriter);
timeWriter.println("*******************************");
timeWriter.println("* Program Name: " + fileName + " *");
timeWriter.println("*******************************");
} catch (IOException e) {
System.out.println("FILE NOT FOUND ERROR");
} finally {
if (timeWriter != null)
timeWriter.close();
}
}
public static void printSlicingResults(String funName, int numberOfSCs, SlicingCriterionState[] standard,
SlicingCriterionState[] constrained, int improvements, boolean performance,
boolean time) {
FileWriter timeFileWriter;
PrintWriter timeWriter = null;
try {
timeFileWriter = new FileWriter("/Users/serperu/Desktop/SlicerOutput/Times/slicingStatistics.txt", true);
timeWriter = new PrintWriter(timeFileWriter);
timeWriter.println("\t++++++++++++++++++++++++++++");
timeWriter.println("\t+ Function Name: " + funName + " +");
timeWriter.println("\t++++++++++++++++++++++++++++");
timeWriter.println("\t Number of SCs: " + numberOfSCs);
double standardAlgorithmAvgPerformance = 0.0;
double standardAlgorithmAvgTime = 0.0;
for (int k = 0; k < numberOfSCs; k++) {
standardAlgorithmAvgPerformance += standard[k].getPerformance();
standardAlgorithmAvgTime += standard[k].getTime();
}
timeWriter.println("\t\t----------------------");
timeWriter.println("\t\t- Standard Algorithm -");
timeWriter.println("\t\t----------------------");
if (performance)
timeWriter.println("\t\t Average Performance: " + (standardAlgorithmAvgPerformance / numberOfSCs) + " % \n");
if (time)
timeWriter.println("\t\t Average Time: " + (standardAlgorithmAvgTime / numberOfSCs) + " ms \n");
double constrainedAlgorithmAvgPerformance = 0.0;
double constrainedAlgorithmAvgTime = 0.0;
for (int k = 0; k < numberOfSCs; k++) {
constrainedAlgorithmAvgPerformance += constrained[k].getPerformance();
constrainedAlgorithmAvgTime += constrained[k].getTime();
}
timeWriter.println("\t\t-------------------------");
timeWriter.println("\t\t- Constrained Algorithm -");
timeWriter.println("\t\t-------------------------");
if (performance) {
timeWriter.println("\t\t Average Performance: " + (constrainedAlgorithmAvgPerformance / numberOfSCs) + " % \n");
timeWriter.println("\t\t Obtained smaller slices: " + improvements + " times\n");
}
if (time)
timeWriter.println("\t\t Average Time: " + (constrainedAlgorithmAvgTime / numberOfSCs) + " ms\n");
} catch (IOException e) {
System.out.println("FILE NOT FOUND ERROR");
} finally {
if (timeWriter != null)
timeWriter.close();
}
}
static class Args {
enum GraphFormat { PDF, DOT }
String inputPath;
File outputFile;
String file;
int line;
String name;
int occurrence = 1;
File graphFile;
GraphFormat graphFormat;
void setGraphFile(File graphFile) {
if (graphFile.getName().endsWith(".dot"))
graphFormat = GraphFormat.DOT;
else if (graphFile.getName().endsWith(".pdf"))
graphFormat = GraphFormat.PDF;
else {
System.out.println("The graph file must end in .dot or .pdf, you set it to " + graphFile);
System.exit(1);
}
this.graphFile = graphFile;
}
void completeData() {
if (file == null && inputPath != null && new File(inputPath).isFile())
file = new File(inputPath).getName();
}
boolean isValid() {
List errors = new LinkedList<>();
if (inputPath == null)
errors.add("You must specify a file to analyze with '-i'.");
else if (!new File(inputPath).exists())
errors.add("The input file you've specified does not exist or isn't readable(" + inputPath + ").");
else if (file == null)
errors.add("The input file is a folder, so you must specify a the file that contains the slicing criterion with '-f'.");
if (outputFile == null)
errors.add("You must specify a location for the output with '-o'.");
else if (!outputFile.exists() && !outputFile.getAbsoluteFile().getParentFile().isDirectory())
errors.add("The output file's parent folder does not exist, or the output folder does not exist.");
if (line <= 0)
errors.add("You must specify a line number greater than 0 with '-l'.");
if (name == null || name.isEmpty())
errors.add("You must specify a valid variable name with '-v'.");
if (occurrence <= 0)
errors.add("You must specify an occurrence greater than 0 with '-n'.");
for (String error : errors)
System.out.println(error);
return errors.isEmpty();
}
}
private static class SlicingCriterionState{
private final double slicingPerformance;
private final double slicingTime;
public SlicingCriterionState(double performance, double time) {
slicingPerformance = performance;
slicingTime = time;
}
public double getPerformance() { return this.slicingPerformance; }
public double getTime() { return this.slicingTime; }
}
}