How can I set up Soot when using it as a library? - java

I want to use Soot library to build an SSA from *.java file. But all the examples I found use Soot as standalone tool, not library. Can anyone give me example hot to do it in program?
For a start I am just trying to load my class from the source file and print it (TestClass.class is in the directory A/home/abdullin/workspace/test):
import soot.G
import soot.Scene
import soot.options.Options
fun main(args: Array<String>) {
G.reset();
Options.v().set_whole_program(true)
Scene.v().loadBasicClasses()
Scene.v().sootClassPath = "${Scene.v().defaultClassPath()}:/home/abdullin/workspace/test"
val sc = Scene.v().getSootClass("TestClass")
Scene.v().loadNecessaryClasses()
sc.setApplicationClass()
println(sc.name)
sc.methods.forEach {
println(it)
}
}
But when I run this, I get runtime exception Aborting: can't find classfile TestClass. If I change Scene.v().getSootClass("TestClass") to Scene.v().loadClassAndSupport("TestClass") as they do in some of their tutorials, soot finds my class, but it is not complete. It prints me signatures of class methods, but can't find their bodies, activeBody field is null.
TestClass
<TestClass: void <init>()>
<TestClass: void main(java.lang.String[])>
<TestClass: void f1()>

First, make sure that the Soot jar is in the classpath.
Then, set up Soot using the classes soot.G and soot.options.Options (G.reset() and Options.v().parse() are methods of interest, also see command line options).
Using soot.Scene.v().setSootClassPath() and similar you can tell Soot where to find the class files of the code you want to analyze.
You can then use Scene.v().getSootClass() to obtain SootClass objects. Make sure that Soot loads all classes after setting the class you want to analyze as main class:
mySootClass.setApplicationClass();
Scene.v().loadNecessaryClasses();
After this, you can use Soot to obtain various types of graphs and run you analyses, as described in the Survivor's guide

You can read this post (https://o2lab.github.io/710/p/a1.html). But if you try to analyze a jar file, you should unzip it and get a set of class files. Then you should add your classes directory into the soot_class_path.
Demo:
public static void main(String[] args) {
//spotbugs -- testing
String classesDir = "D:\\wkspace\\seed8\\dir\\spotbugs";
String mainClass = "edu.umd.cs.findbugs.LaunchAppropriateUI";
//set classpath
String jreDir = System.getProperty("java.home") + "\\lib\\jce.jar";
String jceDir = System.getProperty("java.home") + "\\lib\\rt.jar";
String path = jreDir + File.pathSeparator + jceDir + File.pathSeparator + classesDir;
Scene.v().setSootClassPath(path);
//add an intra-procedural analysis phase to Soot
TestCallGraphSootJar_3 analysis = new TestCallGraphSootJar_3();
PackManager.v().getPack("wjtp").add(new Transform("wjtp.TestSootCallGraph", analysis));
excludeJDKLibrary();
Options.v().set_process_dir(Arrays.asList(classesDir));
Options.v().set_whole_program(true);
//Options.v().set_app(true);
SootClass appClass = Scene.v().loadClassAndSupport(mainClass);
Scene.v().setMainClass(appClass);
Scene.v().loadNecessaryClasses();
//enableCHACallGraph();
enableSparkCallGraph();
PackManager.v().runPacks();
}
If you replace
SootClass appclass = Scene.v().loadClassAndSupport(mainclass);
Scene.v().setMainClass(appclass);
Scene.v().loadNecessaryClasses();
by
Scene.v().loadNecessaryClasses();
SootClass appclass = Scene.v().getSootClass(mainclass);
Scene.v().setMainClass(appclass);
, the program also works.

Related

How to use Java Compile API to compile recursively? [duplicate]

I use the class javax.tools.JavaCompiler (jdk6) to compile a source file, but the source file depends on some jar file. How to set the classpath of the javax.tools.JavaCompiler?
The javax.tools.JavaCompiler#getTask() method takes an options parameter that allows to set compiler options. The following message describes an easy way to set them in order to access the calling program's classpath:
You need to configure the standard
java file manager to know about the
jar files(s) - you use the compiler
options argument to do that.
By default the java compiler object
only seems to know about the default
locations for bootclasspath, extdirs
and endorseddirs directories in terms
of its classpath.
You need to add the calling program's
current classpath to the java compiler
instance's which gets passed on the
the standard file manager, which will
then find classes in the jar files.
Here's how I do it in the compiler
wrapper I wrote
List<String> optionList = new ArrayList<String>();
// set compiler's classpath to be same as the runtime's
optionList.addAll(Arrays.asList("-classpath",System.getProperty("java.class.path")));
// any other options you want
optionList.addAll(Arrays.asList(options));
JavaCompiler.CompilationTask task = compiler.getTask(out,jfm,diagnostics,optionList,null,jfos);
All you'll need then is to get the proper classpath set when running the calling program.
The same problem occurred to me recently, finally I found two workarounds. You can set the class path either by invoke StandardJavaFileManager.setLocation(StandardLocation.CLASS_PATH, "YOUR_CLASS_PATH") or Compiler.getTask(ARG_0, ARG_1, ARG_2, CLASS_PATH_OPTIONS, just as the first answer posted here says.
I needed something simpler than the examples above.
The following is a self-contained example of using the built-in Java compiler, and setting the classpath for the compiler to use.
It is equivalent to creating a source file called HelloPrinter.java and then compiling it as follows:
javac -classpath C:\Users\dab\Testing\a.jar;c:\path\etc org\abc\another\HelloPrinter.java
Note how the classpath can be set using a String[] of options. This should be familiar if you're already used to running javac on the command line (as above).
This code is compatible with Java 6. You will need a JDK, not a JRE, for this to run. This example doesn't actually use the classpath. It all does is print "Hello". You can add an import statement to the generated source and call a method in an external Jar file to test this properly.
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
public class JavaCompilerExample {
public static void main(String[] args) throws Exception {
String className = "HelloPrinter";
String directoryName = "org/abc/another";
new File(directoryName).mkdirs();
FileOutputStream fos = new FileOutputStream(directoryName+"/"+className+".java");
PrintStream ps = new PrintStream(fos);
ps.println(
"package "+directoryName.replace("/", ".") + " ; "
+ "public class " +className +
"{ public static void main(String[] args){System.out.println(\"Hello\");} }");
ps.close();
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
String javacOpts[] = {"-classpath",
"C:\\Users\\dab\\Testing\\a.jar;c:\\path\\etc;",
directoryName+"/"+className + ".java"};
if ( javac.run(null, null, null, javacOpts)!=0 ) {
System.err.println("Error");
System.exit(1);
}
}
}

ClassLoader always returns null when called from within a jar

I ran into library loading problems after creating a jar from my code via maven. I use intelliJ idea on Ubuntu. I broke the problem down to this situation:
Calling the following code from within idea it prints the path correctly.
package com.myproject;
public class Starter {
public static void main(String[] args) {
File classpathRoot = new File(Starter.class.getResource("/").getPath());
System.out.println(classpathRoot.getPath());
}
}
Output is:
/home/ted/java/myproject/target/classes
When I called mvn install and try to run it from command line using the following command I'm getting a NullPointerException since class.getResource() returns null:
cd /home/ted/java/myproject/target/
java -cp myproject-0.1-SNAPSHOT.jar com.myproject.Starter
same for calling:
cd /home/ted/java/myproject/target/
java -Djava.library.path=. -cp myproject-0.1-SNAPSHOT.jar com.myproject.Starter
It doesn't matter if I use class.getClassLoader().getRessource("") instead. Same problem when accessing single files inside of the target directory instead via class.getClassLoader().getRessource("file.txt").
I want to use this way to load native files in the same directory (not from inside the jar). What's wrong with my approach?
The classpath loading mechanism in the JVM is highly extensible, so it's often hard to guarantee a single method that would work in all cases. e.g. What works in your IDE may not work when running in a container because your IDE and your container probably have highly specialized class loaders with different requirements.
You could take a two tiered approach. If the method above fails, you could get the classpath from the system properties, and scan it for the jar file you're interested in and then extract the directory from that entry.
e.g.
public static void main(String[] args) {
File f = findJarLocation("jaxb-impl.jar");
System.out.println(f);
}
public static File findJarLocation(String entryName) {
String pathSep = System.getProperty("path.separator");
String[] pathEntries = System.getProperty("java.class.path").split(pathSep);
for(String entry : pathEntries) {
File f = new File(entry);
if(f.getName().equals(entryName)) {
return f.getParentFile();
}
}
return null;
}

Building a ServiceLoader file with gradle: howto?

I am starting to switch from a well-known Java build system to Gradle to build all my projects, and after barely two hours into it I have already been able to publish a new version of one of my projects without a problem -- a breeze.
But now I encounter a difficulty. In short, I need to replicate the functionality of this Maven plugin which generates the necessary files for a ServiceLoader-enabled service.
In short: given a base class foo.bar.MyClass, it generates a file named META-INF/services/foo.bar.MyClass whose content is a set of classes in the current project which implement that interface/extend that base class. Such a file would look like:
com.mycompany.MyClassImpl
org.othercompany.MyClassImpl
In order to do this, it uses I don't know what as a classloader, loads the Class objects for com.myCompany.MyClassImpl or whatever and checks whether this class implements the wanted interface.
I am trying to do the same in Gradle. Hours of googling led me to this plugin, but after discussing with its author a little, it appears this plugin is able to merge such files, not create them. So, I have to do that myself...
And I am a real beginner both with Gradle and Groovy, which does not help! Here is my current code, link to the full build.gradle here; output (which I managed to get somehow; doesn't work from a clean dir) shown below (and please bear with me... I do Java, and I am final happy; Groovy is totally new to me):
/*
* TEST CODE
*/
final int CLASS_SUFFIX = ".class".length();
final URLClassLoader classLoader = this.class.classLoader;
// Where the classes are: OK
final File classesDir = sourceSets.main.output.classesDir;
final String basePath = classesDir.getCanonicalPath();
// Add them to the classloader: OK
classLoader.addURL(classesDir.toURI().toURL())
// Recurse over each file
classesDir.eachFileRecurse {
// You "return" from a closure, you do not "continue"...
if (!isPotentialClass(it))
return;
// Transform into a class name
final String path = it.getAbsolutePath();
final String name = path.substring(basePath.length() + 1);
final String className = name.substring(0, name.length() - CLASS_SUFFIX)
.replace('/', '.');
// Try and load it
try {
classLoader.loadClass(className);
println(className);
} catch (NoClassDefFoundError ignored) {
println("failed to load " + className + ": " + ignored);
}
}
boolean isPotentialClass(final File file)
{
return file.isFile() && file.name.endsWith(".class")
}
The output:
com.github.fge.msgsimple.InternalBundle
failed to load com.github.fge.msgsimple.bundle.MessageBundle: java.lang.NoClassDefFoundError: com/github/fge/Frozen
failed to load com.github.fge.msgsimple.bundle.MessageBundleBuilder: java.lang.NoClassDefFoundError: com/github/fge/Thawed
com.github.fge.msgsimple.bundle.PropertiesBundle$1
com.github.fge.msgsimple.bundle.PropertiesBundle
com.github.fge.msgsimple.provider.MessageSourceProvider
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider$1
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider$2
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider$3
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider$Builder
com.github.fge.msgsimple.provider.LoadingMessageSourceProvider
com.github.fge.msgsimple.provider.MessageSourceLoader
com.github.fge.msgsimple.provider.StaticMessageSourceProvider$Builder
com.github.fge.msgsimple.provider.StaticMessageSourceProvider$1
com.github.fge.msgsimple.provider.StaticMessageSourceProvider
com.github.fge.msgsimple.source.MessageSource
com.github.fge.msgsimple.source.MapMessageSource$Builder
com.github.fge.msgsimple.source.MapMessageSource$1
com.github.fge.msgsimple.source.MapMessageSource
com.github.fge.msgsimple.source.PropertiesMessageSource
com.github.fge.msgsimple.locale.LocaleUtils
com.github.fge.msgsimple.serviceloader.MessageBundleFactory
com.github.fge.msgsimple.serviceloader.MessageBundleProvider
:compileJava UP-TO-DATE
The problem is in the two first lines: Frozen and Thawed are in a different project, which is in the compile classpath but not in the classpath I managed to grab so far... As such, these classes cannot even load.
How do I modify that code so as to have the full compile classpath availabe? Is my first question. Second question: how do I plug that code, when it works, into the build process?
Here are some hints:
Create a new URLClassLoader, rather than reusing an existing one.
Initialize the class loader with sourceSets.main.compileClasspath (which is an Iterable<File>) rather than classesDir.
Turn the code into a Gradle task class. For more information, see "Writing a simple task class" in the Gradle User Guide.
Ideally, you'd use a library like ASM to analyze the code, rather than using a class loader. To avoid the case where you cannot load a class because it internally references a class that's not on the compile class path, you may want to initialize the class loader with sourceSets.main.runtimeClasspath instead.

Use Jsch to implement scp and additionally not reinvent the wheel

"How to copy a file using Jsch?" was the question first in place. As using Jsch is complicated and error-prone and also works very low-level, you need to program several lines to get a simple scp working.
So, how do I implement a scp (or even sftp) with as few lines of code as possible in Java and not violate the DRY principle?
You can use the libraries used by the Ant scp task:
package org.example.scp;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.optional.ssh.Scp;
public class ScpCopyExample {
public void downloadFile( String remoteFilePath, String localFilePath ) {
Scp scp = new Scp();
scp.setFile("username:password#host.example.org:" + remoteFilePath);
scp.setLocalTofile(localFilePath);
scp.setProject(new Project()); // prevent a NPE (Ant works with projects)
scp.setTrust(true); // workaround for not supplying known hosts file
scp.execute();
}
public static void main(String[] args) {
ScpCopyExample scpDemo = new ScpCopyExample();
scpDemo.downloadFile("~/test.txt", "testlocal.txt");
}
}
I did this with following jars in my classpath:
jsch-0.1.48.jar
ant-jsch-1.6.5.jar
ant-1.7.0.jar
ant-launcher-1.7.0.jar
This example can easily be extended to upload files or use SFTP instead.
Few lines as possible? Try this groovy example, which leverages the ANT scp task.
#Grapes([
#Grab(group='org.apache.ant', module='ant-jsch', version='1.8.4'),
#GrabConfig(systemClassLoader=true)
])
def ant = new AntBuilder()
ant.scp(file:"helloworld.doc", todir:"mark#remotehost:/home/mark/docs", password:"sEcReT")
The Grape annotations will download the jar dependencies at run-time.

How to set classpath when I use javax.tools.JavaCompiler compile the source?

I use the class javax.tools.JavaCompiler (jdk6) to compile a source file, but the source file depends on some jar file. How to set the classpath of the javax.tools.JavaCompiler?
The javax.tools.JavaCompiler#getTask() method takes an options parameter that allows to set compiler options. The following message describes an easy way to set them in order to access the calling program's classpath:
You need to configure the standard
java file manager to know about the
jar files(s) - you use the compiler
options argument to do that.
By default the java compiler object
only seems to know about the default
locations for bootclasspath, extdirs
and endorseddirs directories in terms
of its classpath.
You need to add the calling program's
current classpath to the java compiler
instance's which gets passed on the
the standard file manager, which will
then find classes in the jar files.
Here's how I do it in the compiler
wrapper I wrote
List<String> optionList = new ArrayList<String>();
// set compiler's classpath to be same as the runtime's
optionList.addAll(Arrays.asList("-classpath",System.getProperty("java.class.path")));
// any other options you want
optionList.addAll(Arrays.asList(options));
JavaCompiler.CompilationTask task = compiler.getTask(out,jfm,diagnostics,optionList,null,jfos);
All you'll need then is to get the proper classpath set when running the calling program.
The same problem occurred to me recently, finally I found two workarounds. You can set the class path either by invoke StandardJavaFileManager.setLocation(StandardLocation.CLASS_PATH, "YOUR_CLASS_PATH") or Compiler.getTask(ARG_0, ARG_1, ARG_2, CLASS_PATH_OPTIONS, just as the first answer posted here says.
I needed something simpler than the examples above.
The following is a self-contained example of using the built-in Java compiler, and setting the classpath for the compiler to use.
It is equivalent to creating a source file called HelloPrinter.java and then compiling it as follows:
javac -classpath C:\Users\dab\Testing\a.jar;c:\path\etc org\abc\another\HelloPrinter.java
Note how the classpath can be set using a String[] of options. This should be familiar if you're already used to running javac on the command line (as above).
This code is compatible with Java 6. You will need a JDK, not a JRE, for this to run. This example doesn't actually use the classpath. It all does is print "Hello". You can add an import statement to the generated source and call a method in an external Jar file to test this properly.
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import javax.tools.JavaCompiler;
import javax.tools.ToolProvider;
public class JavaCompilerExample {
public static void main(String[] args) throws Exception {
String className = "HelloPrinter";
String directoryName = "org/abc/another";
new File(directoryName).mkdirs();
FileOutputStream fos = new FileOutputStream(directoryName+"/"+className+".java");
PrintStream ps = new PrintStream(fos);
ps.println(
"package "+directoryName.replace("/", ".") + " ; "
+ "public class " +className +
"{ public static void main(String[] args){System.out.println(\"Hello\");} }");
ps.close();
JavaCompiler javac = ToolProvider.getSystemJavaCompiler();
String javacOpts[] = {"-classpath",
"C:\\Users\\dab\\Testing\\a.jar;c:\\path\\etc;",
directoryName+"/"+className + ".java"};
if ( javac.run(null, null, null, javacOpts)!=0 ) {
System.err.println("Error");
System.exit(1);
}
}
}

Categories