Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/source launcher #7

Merged
merged 4 commits into from
Jun 28, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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