diff --git a/doc/first_analysis_processor.md b/doc/first_analysis_processor.md index 117f206dfc1..df25f4731ca 100644 --- a/doc/first_analysis_processor.md +++ b/doc/first_analysis_processor.md @@ -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). \ No newline at end of file diff --git a/doc/launcher.md b/doc/launcher.md index a145e28fd22..cb6b8c410f8 100644 --- a/doc/launcher.md +++ b/doc/launcher.md @@ -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("", "", ""); +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("", "", "", 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: diff --git a/pom.xml b/pom.xml index 5a9293f6ecd..97f795ce9f8 100644 --- a/pom.xml +++ b/pom.xml @@ -184,6 +184,7 @@ 1.8 target/velocity.log UTF-8 + 0.132.0 @@ -203,13 +204,19 @@ - + maven.inria.fr-snapshot Maven Repository for Spoon Snapshots http://maven.inria.fr/artifactory/spoon-public-snapshot + + + inria + triskell-public-release + http://maven.inria.fr/artifactory/triskell-public-release + @@ -295,6 +302,13 @@ maven-invoker 3.0.1 + + + org.benf + cfr + + 0.132.0 + diff --git a/src/main/java/spoon/JarLauncher.java b/src/main/java/spoon/JarLauncher.java new file mode 100644 index 00000000000..2330d78c04c --- /dev/null +++ b/src/main/java/spoon/JarLauncher.java @@ -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 { + 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); + } + +} diff --git a/src/main/java/spoon/decompiler/CFRDecompiler.java b/src/main/java/spoon/decompiler/CFRDecompiler.java new file mode 100644 index 00000000000..e2ba26b9864 --- /dev/null +++ b/src/main/java/spoon/decompiler/CFRDecompiler.java @@ -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()}); + } +} diff --git a/src/main/java/spoon/decompiler/Decompiler.java b/src/main/java/spoon/decompiler/Decompiler.java new file mode 100644 index 00000000000..3ab80614ff4 --- /dev/null +++ b/src/main/java/spoon/decompiler/Decompiler.java @@ -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); +} diff --git a/src/main/java/spoon/support/compiler/SpoonPom.java b/src/main/java/spoon/support/compiler/SpoonPom.java index b8873cf2108..eef10fc3aef 100644 --- a/src/main/java/spoon/support/compiler/SpoonPom.java +++ b/src/main/java/spoon/support/compiler/SpoonPom.java @@ -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 */ @@ -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 */ diff --git a/src/test/java/spoon/JarLauncherTest.java b/src/test/java/spoon/JarLauncherTest.java new file mode 100644 index 00000000000..4b12c338ef9 --- /dev/null +++ b/src/test/java/spoon/JarLauncherTest.java @@ -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); + } + +} \ No newline at end of file diff --git a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java index d1cb84bd959..bba43575f46 100644 --- a/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java +++ b/src/test/java/spoon/test/architecture/SpoonArchitectureEnforcerTest.java @@ -337,6 +337,7 @@ public void testSpecPackage() { Set officialPackages = new TreeSet<>(); officialPackages.add("spoon.compiler.builder"); officialPackages.add("spoon.compiler"); + officialPackages.add("spoon.decompiler"); officialPackages.add("spoon.support.modelobs.action"); officialPackages.add("spoon.support.modelobs.context"); officialPackages.add("spoon.support.modelobs"); diff --git a/src/test/resources/jarLauncher/helloworld-1.0-SNAPSHOT.jar b/src/test/resources/jarLauncher/helloworld-1.0-SNAPSHOT.jar new file mode 100644 index 00000000000..440ac217cfc Binary files /dev/null and b/src/test/resources/jarLauncher/helloworld-1.0-SNAPSHOT.jar differ diff --git a/src/test/resources/jarLauncher/pom.xml b/src/test/resources/jarLauncher/pom.xml new file mode 100644 index 00000000000..d56845d1043 --- /dev/null +++ b/src/test/resources/jarLauncher/pom.xml @@ -0,0 +1,75 @@ + + + + 4.0.0 + + se.kth.castor + helloworld + 1.0-SNAPSHOT + + helloworld + + http://www.example.com + + + UTF-8 + 1.5 + 1.5 + + + + + junit + junit + 4.11 + test + + + org.json + json + 20090211 + + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 1.8 + 1.8 + + + + + maven-assembly-plugin + + + + se.kth.castor.App + + + + jar-with-dependencies + + + + + make-assembly + + package + + + single + + + + + + + +