Skip to content

Commit

Permalink
Merge pull request #7 from lucacampanella/feature/source-launcher
Browse files Browse the repository at this point in the history
Feature/source launcher
  • Loading branch information
lucacampanella authored Jun 28, 2019
2 parents 249f406 + 5f945fa commit c272631
Show file tree
Hide file tree
Showing 13 changed files with 302 additions and 33 deletions.
6 changes: 6 additions & 0 deletions graph-builder/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ processResources {
from sourceSets.main.java.filter {it.toString().endsWith("MatcherContainer.java")}.first().toString()
}

task copySourceResForTests(type: Copy){
from sourceSets.test.java.filter {it.toString().contains("subclassestests")}
into file(sourceSets.test.output.resourcesDir.path + "/subclassestests")
}
processTestResources.dependsOn copySourceResForTests

sourceSets {
test.java.srcDirs += 'src/test/kotlin'
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public static void drawAllStartableClasses(AnalyzerWithModel analyzerWithModel,

AsciiDocIndexBuilder asciiDocIndexBuilder = new AsciiDocIndexBuilder(analyzerWithModel.getAnalysisName());

final List<CtClass> startableByRPCClasses = analyzerWithModel.getClassesByAnnotation(StartableByRPC.class);
final List<CtClass> startableByRPCClasses = analyzerWithModel.getClassesToBeAnalyzed();
LOGGER.info("Found these classes annotated with @StartableByRPC: ");
Paths.get(outPath, "images").toFile().mkdirs(); //create all directories necessary for the output
for (CtClass klass : startableByRPCClasses) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.github.lucacampanella.callgraphflows;

import com.github.lucacampanella.callgraphflows.staticanalyzer.DecompilerEnum;
import com.github.lucacampanella.callgraphflows.staticanalyzer.JarAnalyzer;
import com.github.lucacampanella.callgraphflows.staticanalyzer.SourceAndJarAnalyzer;
import org.slf4j.LoggerFactory;
import picocli.CommandLine;

Expand All @@ -10,11 +12,10 @@

public class Main implements Callable<Integer> {

@CommandLine.Parameters(arity = "1", index="0", paramLabel = "JarFile", description = "Jar file to process.")
private String inputJarPath;

@CommandLine.Parameters(arity = "0..*", index="1..*", paramLabel = "AdditionalJarFiles", description = "Additional jars to be added to classpath")
private String[] additionalJarsPath;
@CommandLine.Parameters(arity = "1..*", index="0..*", paramLabel = "filesToAnalyze",
description = "The paths to the files that need to be analyzed, they can be " +
".java files, folders or .jar files")
private String[] filesPaths;

@CommandLine.Option(names = {"-o", "--output"}, defaultValue = "graphs", description = "Output folder path")
private String outputPath;
Expand All @@ -25,6 +26,10 @@ public class Main implements Callable<Integer> {
@CommandLine.Option(names = {"-l", "--draw-line-numbers"}, description = "draw the line numbers")
boolean drawLineNumbers = false;

@CommandLine.Option(names = {"-s", "--only-source-files"}, description = "analyze only the source files and not " +
"the decompiled code")
boolean analyzeOnlySources = false;

public static void main(String []args) throws IOException {

final Main app = CommandLine.populateCommand(new Main(), args);
Expand All @@ -40,14 +45,8 @@ public Integer call() throws IOException {
System.setProperty("org.slf4j.simpleLogger.defaultLogLevel", "error");
}
LoggerFactory.getLogger(Main.class).trace("Logger level = {}", loggerLevel);
JarAnalyzer analyzer;

if(additionalJarsPath == null) {
analyzer = new JarAnalyzer(decompilerName, inputJarPath);
}
else {
analyzer = new JarAnalyzer(decompilerName, inputJarPath, additionalJarsPath);
}
SourceAndJarAnalyzer analyzer = new SourceAndJarAnalyzer(filesPaths,
DecompilerEnum.fromStringOrDefault(decompilerName), analyzeOnlySources);

LoggerFactory.getLogger(Main.class).trace("drawLineNumbers = {}", drawLineNumbers);
DrawerUtil.setDrawLineNumbers(drawLineNumbers);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.github.lucacampanella.callgraphflows.AnalysisErrorException;
import com.github.lucacampanella.callgraphflows.staticanalyzer.matchers.MatcherHelper;
import net.corda.core.flows.InitiatedBy;
import net.corda.core.flows.StartableByRPC;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spoon.reflect.CtModel;
Expand Down Expand Up @@ -126,7 +127,12 @@ public List<CtClass> getClassesByAnnotation(Class annotationClass) {
.collect(Collectors.toList());
}

public CtClass getDeeperClassInitiatedBy(CtClass initiatingClass) {
public List<CtClass> getClassesToBeAnalyzed() {
return getClassesByAnnotation(StartableByRPC.class);
}


public CtClass getDeeperClassInitiatedBy(CtClass initiatingClass) {
CtClass deeperInitiatedByClass = null;
final List<CtClass> generalInitiatedByList = getClassesByAnnotation(InitiatedBy.class);
for(CtClass klass : generalInitiatedByList) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,6 @@ public class CustomJarLauncher extends Launcher {

private static final Logger LOGGER = LoggerFactory.getLogger(CustomJarLauncher.class);

public enum DecompilerEnum {
CFR, FERNFLOWER;

public Decompiler getDecompiler(File decompiledSrc) {
if(this == FERNFLOWER) {
return new FernflowerDecompiler(decompiledSrc);
}
return new CFRDecompiler(decompiledSrc);
}
}

public static class Builder {
String decompiledSrcPath = Paths.get(System.getProperty("java.io.tmpdir"), "spoon-tmp", "decompiledSrc").toString();
DecompilerEnum decompilerEnum = DecompilerEnum.CFR;
Expand Down Expand Up @@ -118,7 +107,6 @@ private CustomJarLauncher(){
//use builder
}


// public CustomJarLauncher(List<String> jarPaths) {
// this(jarPaths, (String)null, null);
// }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.github.lucacampanella.callgraphflows.staticanalyzer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spoon.decompiler.CFRDecompiler;
import spoon.decompiler.Decompiler;
import spoon.decompiler.FernflowerDecompiler;

import java.io.File;

public enum DecompilerEnum {
CFR, FERNFLOWER;

private static final Logger LOGGER = LoggerFactory.getLogger(DecompilerEnum.class);

public Decompiler getDecompiler(File decompiledSrc) {
if(this == FERNFLOWER) {
return new FernflowerDecompiler(decompiledSrc);
}
return new CFRDecompiler(decompiledSrc);
}

public static DecompilerEnum getDefault() {
return CFR;
}

public static DecompilerEnum fromStringOrDefault(String value) {

DecompilerEnum result;
try {
result = DecompilerEnum.valueOf(value.toUpperCase());
} catch (Exception e) {
result = getDefault();
LOGGER.error("Could not find decompiler {}, defaulting on {}", value, result);
}
return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ public JarAnalyzer(String decompilerName, String pathToJar, String[] additionalJ
if(decompilerName != null && decompilerName.equalsIgnoreCase("CFR")) {
LOGGER.trace("Using CFR (default) decompiler");
jr = new CustomJarLauncher.Builder(jarsList)
.withDecompilerEnum(CustomJarLauncher.DecompilerEnum.CFR).build();
.withDecompilerEnum(DecompilerEnum.CFR).build();
}
else if(decompilerName != null && decompilerName.equalsIgnoreCase("Fernflower")) {
LOGGER.trace("Using Fernflower decompiler");
jr = new CustomJarLauncher.Builder(jarsList)
.withDecompilerEnum(CustomJarLauncher.DecompilerEnum.FERNFLOWER).build();
.withDecompilerEnum(DecompilerEnum.FERNFLOWER).build();
}
else {
LOGGER.error("Decompiler name {} not recognised, using default decompiler", decompilerName);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package com.github.lucacampanella.callgraphflows.staticanalyzer;

import net.corda.core.flows.StartableByRPC;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.LineIterator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import spoon.Launcher;
import spoon.SpoonException;
import spoon.decompiler.Decompiler;
import spoon.reflect.declaration.CtClass;

import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;

public class SourceAndJarAnalyzer extends AnalyzerWithModel {

private boolean analyzeOnlySources = false;
private Set<String> srcClassNamesSet;

private static final Logger LOGGER = LoggerFactory.getLogger(SourceAndJarAnalyzer.class);

public SourceAndJarAnalyzer(List<String> pathsToFoldersOrSrc) throws IOException {
init(pathsToFoldersOrSrc, null, null, false);
}

public SourceAndJarAnalyzer(String[] unsortedTypesFiles, DecompilerEnum decompilerEnum,
boolean analyzeOnlySources) throws IOException {
List<String> jarPaths = new ArrayList<>();
List<String> otherPaths = new ArrayList<>();
for(String path : unsortedTypesFiles) {
if(path.endsWith(".jar")) {
jarPaths.add(path);
}
else {
otherPaths.add(path);
}
}
init(otherPaths, jarPaths, decompilerEnum, analyzeOnlySources);
}

public SourceAndJarAnalyzer(List<String> pathsToFoldersOrSrc, List<String> pathsToJars,
DecompilerEnum decompilerEnum, boolean analyzeOnlySources) throws IOException {
init(pathsToFoldersOrSrc, pathsToJars, decompilerEnum, analyzeOnlySources);
}

private void init(List<String> pathsToFoldersOrSrc, List<String> pathsToJars, DecompilerEnum decompilerEnum,
boolean analyzeOnlySources) throws IOException {

this.analyzeOnlySources = analyzeOnlySources;

analysisName = pathsToFoldersOrSrc.stream().map(
pathToJar -> pathToJar.substring(pathToJar.lastIndexOf(System.getProperty("file.separator"))+1)).
collect(Collectors.joining(", "));

Set<String> addedClassesNamesSet = new HashSet<>();

Launcher spoon = new Launcher();

if(pathsToFoldersOrSrc != null) {
for (String path : pathsToFoldersOrSrc) {
final File folderOrSrc = new File(path);
if (!folderOrSrc.exists()) {
throw new RuntimeException("File or folder " + folderOrSrc.getPath() + " does not exist");
}
if (folderOrSrc.isDirectory()) {
addFolderToModel(addedClassesNamesSet, spoon, folderOrSrc);
} else {
addSingleFileToModel(addedClassesNamesSet, spoon, folderOrSrc);
}
}
}

if(analyzeOnlySources) {
srcClassNamesSet = new HashSet<>(addedClassesNamesSet); //we save which classes derive from sources and
//not decompilation
}

if(pathsToJars != null) {
String decompiledSrcPath =
Paths.get(System.getProperty("java.io.tmpdir"), "spoon-camp-tmp", "decompiledSrc").toString();
final File decompiledSrcFolder = new File(decompiledSrcPath);
FileUtils.deleteDirectory(decompiledSrcFolder);
for(String path : pathsToJars) {
decompileJarToFolder(path, decompiledSrcPath, decompilerEnum);
}
addFolderToModel(addedClassesNamesSet, spoon, decompiledSrcFolder);
}
spoon.buildModel();
model = spoon.getModel();
}

private static void addFolderToModel(Set<String> addedClassesNamesSet, Launcher spoon, File folder) throws IOException {
final Collection<File> sourceFiles =
FileUtils.listFiles(folder, new String[]{"java"}, true);
for (File srcFile : sourceFiles) {
addSingleFileToModel(addedClassesNamesSet, spoon, srcFile);
}
}

private static void addSingleFileToModel(Set<String> addedClassesNamesSet, Launcher spoon, File srcFile) throws IOException {
final String qualifiedName = findQualifiedName(srcFile);
if(!addedClassesNamesSet.contains(qualifiedName)) {
addedClassesNamesSet.add(qualifiedName);
spoon.addInputResource(srcFile.getAbsolutePath());
}
else {
LOGGER.trace("File {} represents class {}, which was already added to the model, skipping", srcFile, qualifiedName);
}
}

public static void decompileJarToFolder(String jarPath,
String ouputDir,
DecompilerEnum decompilerEnum) {
File decompiledDirectory = new File(ouputDir);
if (decompiledDirectory.exists() && !decompiledDirectory.canWrite()) {
throw new SpoonException("Dir " + decompiledDirectory.getPath() + " already exists and is not deletable.");
}

if (!decompiledDirectory.exists()) {
decompiledDirectory.mkdirs();
}

Decompiler decompiler = decompilerEnum.getDecompiler(decompiledDirectory);

File jar = new File(jarPath);
if (jar.exists() && jar.isFile()) {
decompiler.decompile(jar.getAbsolutePath());
} else {
throw new SpoonException("Jar " + jar.getPath() + " not found.");
}
}

public static String findQualifiedName(File srcFile) throws IOException {
String res = "";
try (LineIterator lineIt = FileUtils.lineIterator(srcFile)) {
while (lineIt.hasNext()) {
String line = lineIt.nextLine();
if(line.contains("package ")) {
res = line.substring(
line.indexOf("package ") + "package ".length(),
line.indexOf(';'));
break;
}
}
}
final String path = srcFile.getPath();
res += "." + path
.substring(path
.lastIndexOf(System.getProperty("file.separator"))+1,
path.indexOf(".java"));

return res;
}

@Override
public List<CtClass> getClassesToBeAnalyzed() {
if(!analyzeOnlySources) {
return super.getClassesToBeAnalyzed();
}
return super.getClassesToBeAnalyzed().stream().filter(klass ->
srcClassNamesSet.contains(klass.getTopLevelType().getQualifiedName())).collect(Collectors.toList());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtType;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

Expand All @@ -23,7 +24,7 @@ class AnalyzerWithModelTest {
static AnalyzerWithModel analyzerWithModel;

@BeforeAll
static void setUp() {
static void setUp() throws IOException {
analyzerWithModel = new SourceClassAnalyzer(fromClassSrcToPath(InitiatorBaseFlow.class),
fromClassSrcToPath(ExtendingSuperclassTestFlow.class),
fromClassSrcToPath(DoubleExtendingSuperclassTestFlow.class));
Expand Down
Loading

0 comments on commit c272631

Please sign in to comment.