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: add support for Jar files analysis with JarLauncher (decompile then build AST with spoon) #2455

Merged
merged 28 commits into from
Sep 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0d7c009
refactor(MavenLauncher)
nharrand Sep 3, 2018
8c4037d
merge
nharrand Sep 3, 2018
c09c060
vanity
nharrand Sep 4, 2018
7a2fd36
removes parameter m2repository as it has become useless
nharrand Sep 4, 2018
2e88419
override last modified date when classpath is evaluated but has not c…
nharrand Sep 4, 2018
ee3fd53
doc(MavenLauncher)
nharrand Sep 4, 2018
0932e53
untrack .tmp files and cosmetic
nharrand Sep 4, 2018
11ca5a0
fixes a bug when compiler plugin exist but has no configuration child…
nharrand Sep 4, 2018
1a43b2d
fixes a bug which made InheritanceModel incorectly read properties co…
nharrand Sep 4, 2018
c974268
re add deprecated MavenLauncher constructor and add a forceRefresh op…
nharrand Sep 4, 2018
178bb9d
style
nharrand Sep 5, 2018
1567cf5
Merge branch 'master' of https://github.com/INRIA/spoon
nharrand Sep 7, 2018
ad5414b
JarLauncher poc
nharrand Sep 7, 2018
545ca73
update licenses
nharrand Sep 10, 2018
4bdeddc
add a local repository for cfr
nharrand Sep 10, 2018
a1f72de
wip
nharrand Sep 10, 2018
3440581
Refactoring(InheritanceModel, MavenLauncher)
nharrand Sep 10, 2018
d538953
merge
nharrand Sep 12, 2018
d39d2db
merge
nharrand Sep 20, 2018
c456307
merge upstream
nharrand Sep 20, 2018
6227699
small fixes, remove local jar as it is deployed on inria maven repo
nharrand Sep 20, 2018
2c68b52
add triskell repo to pom
nharrand Sep 20, 2018
a9457f2
Activate LarLauncherTest
nharrand Sep 20, 2018
7bd26e4
merge
nharrand Sep 21, 2018
0e6c5eb
add documentation
nharrand Sep 21, 2018
9b75b4d
@Experimental everywhere!
nharrand Sep 21, 2018
4ddfb79
dependency scope = provided for dirty cfr
monperrus Sep 21, 2018
92e6602
Update pom.xml
monperrus Sep 27, 2018
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
3 changes: 3 additions & 0 deletions doc/first_analysis_processor.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,6 @@ $ java -classpath /path/to/binary/of/your/processor.jar:spoon-core-{{site.spoon_
2. Specify your processors in fully qualified name (here `processors.CatchProcessor`).
{{site.data.alerts.end}}

## Bytecode analysis

Note that spoon also supports the analysis of bytecode through decompilation. See [javadoc](http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/JarLauncher.html).
24 changes: 24 additions & 0 deletions doc/launcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,30 @@ CtModel model = launcher.getModel();
```
To avoid invoking maven over and over to build a classpath that has not changed, it is stored in a file `spoon.classpath.tmp` (or depending on the scope `spoon.classpath-app.tmp` or `spoon.classpath-test.tmp`) in the same folder as the `pom.xml`. This classpath will be refreshed is the file is deleted or if it has not been modified since 1h.

## The JarLauncher class

The Spoon `JarLauncher` ([JavaDoc](http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/JarLauncher.html)) is used to create the AST model from a jar.
It automatically decompiles class files contained in the jar and analyzes them.
If a pom file corresponding to the jar is provided, it will be used to build the classpath containing all dependencies.

```java
//More constructors are available, check the JavaDOc for more information.
JarLauncher launcher = JarLauncher("<path_to_jar>", "<path_to_output_src_dir>", "<path_to_pom>");
launcher.buildModel();
CtModel model = launcher.getModel();
```

Note that the default decompiler [CFR](http://www.benf.org/other/cfr/) can be changed by providing an instance implementing `spoon.decompiler.Decompiler` as a parameter.

```java
JarLauncher launcher = new JarLauncher("<path_to_jar>", "<path_to_output_src_dir>", "<path_to_pom>", new Decompiler() {
@Override
public void decompile(String jarPath) {
//Custom decompiler call
}
});
```

## About the classpath

Spoon analyzes source code. However, this source code may refer to libraries (as a field, parameter, or method return type). There are two cases:
Expand Down
16 changes: 15 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@
<java.test.version>1.8</java.test.version>
<runtime.log>target/velocity.log</runtime.log>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<version.cfr>0.132.0</version.cfr>
</properties>

<distributionManagement>
Expand All @@ -203,13 +204,19 @@
</site>
</distributionManagement>

<!-- This repository is actually needed for revapi to compare against the last Spoon Snapshot -->
<repositories>
<!-- This repository is actually needed for revapi to compare against the last Spoon Snapshot -->
<repository>
<id>maven.inria.fr-snapshot</id>
<name>Maven Repository for Spoon Snapshots</name>
<url>http://maven.inria.fr/artifactory/spoon-public-snapshot</url>
</repository>
<!-- This repository is needed for cfr -->
<repository>
<id>inria</id>
<name>triskell-public-release</name>
<url>http://maven.inria.fr/artifactory/triskell-public-release</url>
</repository>
</repositories>

<dependencies>
Expand Down Expand Up @@ -295,6 +302,13 @@
<artifactId>maven-invoker</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<!-- released on maven.inria.fr -->
<groupId>org.benf</groupId>
<artifactId>cfr</artifactId>
<!-- 0.132 is the version of July 2018 -->
<version>0.132.0</version>
</dependency>
</dependencies>

<build>
Expand Down
168 changes: 168 additions & 0 deletions src/main/java/spoon/JarLauncher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/**
* Copyright (C) 2006-2018 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* 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 CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon;

import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import spoon.decompiler.CFRDecompiler;
import spoon.decompiler.Decompiler;
import spoon.support.Experimental;
import spoon.support.compiler.SpoonPom;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;

@Experimental
public class JarLauncher extends Launcher {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File pom;
File jar;
File decompiledRoot;
File decompiledSrc;
Decompiler decompiler;
boolean decompile = false;

/**
* JarLauncher basic constructor. Uses the defauld Decompiler (CFR)
*
* @param jarPath path to the jar to be analyzed
*/
public JarLauncher(String jarPath) {
this(jarPath, null, (String) null);
}


/**
* JarLauncher basic constructor. Uses the defauld Decompiler (CFR)
*
* @param jarPath path to the jar to be analyzed
* @param decompiledSrcPath path to directory where decompiled source will be outputted
*/
public JarLauncher(String jarPath, String decompiledSrcPath) {
this(jarPath, decompiledSrcPath, (String) null);
}

/**
* JarLauncher basic constructor. Uses the defauld Decompiler (CFR)
*
* @param jarPath path to the jar to be analyzed
* @param decompiledSrcPath path to directory where decompiled source will be outputted
* @param pom path to pom associated with the jar to be analyzed
*/
public JarLauncher(String jarPath, String decompiledSrcPath, String pom) {
this(jarPath, decompiledSrcPath, pom, null);
}

/**
* JarLauncher basic constructor. Uses the defauld Decompiler (CFR)
*
* @param jarPath path to the jar to be analyzed
* @param decompiledSrcPath path to directory where decompiled source will be outputted
* @param decompiler Instance implementing {@link spoon.decompiler.Decompiler} to be used
*/
public JarLauncher(String jarPath, String decompiledSrcPath, Decompiler decompiler) {
this(jarPath, decompiledSrcPath, null, decompiler);
}

/**
* JarLauncher constructor. Uses the defauld Decompiler (CFR)
*
* @param jarPath path to the jar to be analyzed
* @param decompiledSrcPath path to directory where decompiled source will be outputted
* @param pom path to pom associated with the jar to be analyzed
* @param decompiler Instance implementing {@link spoon.decompiler.Decompiler} to be used
*/
public JarLauncher(String jarPath, String decompiledSrcPath, String pom, Decompiler decompiler) {
this.decompiler = decompiler;
if (decompiledSrcPath == null) {
decompiledSrcPath = System.getProperty("java.io.tmpdir") + System.getProperty("file.separator") + "spoon-tmp";
decompile = true;
}
this.decompiledRoot = new File(decompiledSrcPath);
if (decompiledRoot.exists() && !decompiledRoot.canWrite()) {
throw new SpoonException("Dir " + decompiledRoot.getPath() + " already exists and is not deletable.");
} else if (decompiledRoot.exists() && decompile) {
decompiledRoot.delete();
}
if (!decompiledRoot.exists()) {
decompiledRoot.mkdirs();
decompile = true;
}
decompiledSrc = new File(decompiledRoot, "src/main/java");
if (!decompiledSrc.exists()) {
decompiledSrc.mkdirs();
decompile = true;
}

if (decompiler == null) {
this.decompiler = getDefaultDecompiler();
}

jar = new File(jarPath);
if (!jar.exists() || !jar.isFile()) {
throw new SpoonException("Jar " + jar.getPath() + "not found.");
}

//We call the decompiler only if jar has changed since last decompilation.
if (jar.lastModified() > decompiledSrc.lastModified()) {
decompile = true;
}
init(pom);
}

private void init(String pomPath) {
//We call the decompiler only if jar has changed since last decompilation.
if (decompile) {
decompiler.decompile(jar.getAbsolutePath());
}


if (pomPath != null) {
File srcPom = new File(pomPath);
if (!srcPom.exists() || !srcPom.isFile()) {
throw new SpoonException("Pom " + srcPom.getPath() + "not found.");
}
try {
pom = new File(decompiledRoot, "pom.xml");
Files.copy(srcPom.toPath(), pom.toPath(), REPLACE_EXISTING);
} catch (IOException e) {
throw new SpoonException("Unable to write " + pom.getPath());
}
try {
SpoonPom pomModel = new SpoonPom(pom.getPath(), null, MavenLauncher.SOURCE_TYPE.APP_SOURCE, getEnvironment());
if (pomModel == null) {
throw new SpoonException("Unable to create the model, pom not found?");
}
getEnvironment().setComplianceLevel(pomModel.getSourceVersion());
String[] classpath = pomModel.buildClassPath(null, MavenLauncher.SOURCE_TYPE.APP_SOURCE, LOGGER, false);
// dependencies
this.getModelBuilder().setSourceClasspath(classpath);
} catch (IOException | XmlPullParserException e) {
throw new SpoonException("Failed to read classpath file.");
}
addInputResource(decompiledSrc.getAbsolutePath());
} else {
addInputResource(decompiledSrc.getAbsolutePath());
}
}

protected Decompiler getDefaultDecompiler() {
return new CFRDecompiler(decompiledSrc);
}

}
37 changes: 37 additions & 0 deletions src/main/java/spoon/decompiler/CFRDecompiler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright (C) 2006-2018 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* 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 CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.decompiler;

import java.io.File;

import org.benf.cfr.reader.Main;
import spoon.support.Experimental;

@Experimental
public class CFRDecompiler implements Decompiler {

File outputDir;

public CFRDecompiler(File outputDir) {
this.outputDir = outputDir;
}

@Override
public void decompile(String jarPath) {
Main.main(new String[]{jarPath, "--outputdir", outputDir.getPath()});
}
}
31 changes: 31 additions & 0 deletions src/main/java/spoon/decompiler/Decompiler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* Copyright (C) 2006-2018 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* 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 CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.decompiler;

import spoon.support.Experimental;

@Experimental
public interface Decompiler {

/**
* Sets the output directory for source generated.
*
* @param jarPath
* Path to jar to be analyzed.
*/
void decompile(String jarPath);
}
2 changes: 0 additions & 2 deletions src/main/java/spoon/support/compiler/SpoonPom.java
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ public class SpoonPom implements SpoonResource {
/**
* Extract the information from the pom
* @param path the path to the pom
* @return the extracted model
* @throws IOException when the file does not exist
* @throws XmlPullParserException when the file is corrupted
*/
Expand All @@ -83,7 +82,6 @@ public SpoonPom(String path, MavenLauncher.SOURCE_TYPE sourceType, Environment e
* Extract the information from the pom
* @param path the path to the pom
* @param parent the parent pom
* @return the extracted model
* @throws IOException when the file does not exist
* @throws XmlPullParserException when the file is corrupted
*/
Expand Down
60 changes: 60 additions & 0 deletions src/test/java/spoon/JarLauncherTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright (C) 2006-2018 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* 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 CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon;

import org.junit.Ignore;
import org.junit.Test;
import spoon.reflect.CtModel;
import spoon.reflect.code.CtLocalVariable;
import spoon.reflect.code.CtTry;
import spoon.reflect.declaration.CtConstructor;

import java.io.File;

import static org.junit.Assert.*;

public class JarLauncherTest {

@Test
public void testJarLauncher() {

File baseDir = new File("src/test/resources/jarLauncher");
File pom = new File(baseDir, "pom.xml");
File jar = new File(baseDir, "helloworld-1.0-SNAPSHOT.jar");
JarLauncher launcher = new JarLauncher(jar.getAbsolutePath(), null, pom.getAbsolutePath());
launcher.getEnvironment().setAutoImports(true);
launcher.buildModel();
CtModel model = launcher.getModel();
assertEquals(model.getAllTypes().size(), 5);
CtConstructor constructor = (CtConstructor) model.getRootPackage().getFactory().Type().get("se.kth.castor.UseJson").getTypeMembers().get(0);
CtTry tryStmt = (CtTry) constructor.getBody().getStatement(1);
CtLocalVariable var = (CtLocalVariable) tryStmt.getBody().getStatement(0);
assertNotNull(var.getType().getTypeDeclaration());
}

@Test
public void testJarLauncherNoPom() {
File baseDir = new File("src/test/resources/jarLauncher");
File jar = new File(baseDir, "helloworld-1.0-SNAPSHOT.jar");
JarLauncher launcher = new JarLauncher(jar.getAbsolutePath(), null);
launcher.getEnvironment().setAutoImports(true);
launcher.buildModel();
CtModel model = launcher.getModel();
assertEquals(model.getAllTypes().size(),5);
}

}
Loading