Skip to content

Soot migration

Alexey Volkov edited this page Dec 20, 2022 · 10 revisions

Soot migration

Terminology:

Soot JacoDB
bytecode storage - JacoDB
scope of visible classes Scene JcClasspath
class SootClass JcClassOrInterface
class method SootMethod JcMethod
class field SootField JcField
type (with generics substitution) - JcJvmType
3-address bytecode representation JimpleBody JcRawInstList
control flow graph ClassicCompleteUnitGraph JcGraph
class hierarchy Hierarchy HierarchyExt
call graph CallGraph UsagesExt

Recommendations

  1. Remember to close the resource: JcClasspath, JacoDB.
  2. Creating classpath is a heavy operation involving I/O. All the JAR files or folders passed to a classpath are checked. We check:
    • whether they have been processed already,
    • whether they have been changed since having been processed before,
    • whether they are being processed right now as they have not been processed already. As a result, the code should try to reuse a classpath instance. After that, we recommend to call the close method as JacoDB is able to delete the processed resources which seem to be out of date with regard to the file system.
  3. If there is a chance that the code calls for class hierarchies, then it's better to install the InMemoryHierarchy feature.
  4. Use persisted data in the file system if your code base is huge enough or if living after the process restart is possible.
  5. Install only those features that are required for this database.

Operations

create storage

Soot
// points to specific runtime version
G.v().initJdk(new G.JreInfo(location, version)); 
Options options = Options.v();
options.set_soot_classpath(files);
Scene.v().loadNecessaryClasses();
PackManager.v().runPacks();
JacoDB(Java)
var db = JacoDB.async(new JcSettings()
    // points to specific runtime version
     .useJavaRuntime(runtimeFolder)
    // jars to process
     .loadByteCode(Arrays.asList(jar1, jar2))
    // persist all information to improve performance between restarts
     .persistent("/home/user/jcdb.db", false)
).get();
var classpath = db.asyncClasspath(listOf(jar1)).get();
JacoDB(Kotlin)
val db = jacodb {
    // points to specific runtime version
    useJavaRuntime(runtimeFolder)
    // jars to process
    loadByteCode(listOf(jar1, jar2))
    // persist all information to improve performance between restarts
    persistent(location = "/home/user/jcdb.db", clearOnStart = false)
}
val classpath = db.classpath(listOf(jar1))

find class

Soot
SootClass clazz = Scene.v().getSootClass("java.lang.String");
JacoDB(Java)
var clazz = classpath.findClassOrNull("java.lang.String")
JacoDB(Kotlin)
val clazz = classpath.findClassOrNull("java.lang.String")

Get 3-address bytecode representation

Soot
SootClass clazz = Scene.v().getSootClass("java.lang.String");
clazz.getMethod("length", Lists.emptyList()).retrieveActiveBody()
JacoDB(Java)
var clazz = classpath.findClassOrNull("java.lang.String");
Api.findMethodOrNull(clazz, "length").instructionList();
JacoDB(Kotlin)
val clazz = classpath.findClassOrNull("java.lang.String") ?: throw  IllegalStateException()
classpath.findMethodOrNull("length").instructionList()

Get control flow graph

Soot
new ClassicCompleteUnitGraph(sootMethod.getActiveBody());
JacoDB(Java)
var cfg = jcMethod.instructionList().graph()
JacoDB(Kotlin)
val cfg = jcMethod.instructionList().graph()

Hierarchy

Soot
Hierarchy h = new Hierarchy();
h.getDirectSubclassesOf(clazz);
h.getDirectSubinterfacesOf(clazz);
JacoDB(Java)
var db = JacoDB.async(new JcSettings()
  .install(InMemoryHierarchy.INSTANCE)
).get();
val ext = classpath.asyncHierarchy().get();
ext.findSubClasses(clazz, allHierarchy = true)
ext.findOverrides(method)
JacoDB(Kotlin)
val db = jacodb {
    // highly recommend to install this extension
    install(InMemoryHierarchy)
}

val ext = classpath.hierarchyExt()
ext.findSubClasses(clazz, allHierarchy = true)
ext.findOverrides(method)

Call graph/Usages

Soot
CallGraph cg = new CallGraph();
cg.edgesInto(edge);
cg.edgesOutOf(edge);
JacoDB(Java)
var db = JacoDB.async(new JcSettings()
  // highly recommend to install InMemoryHierarchy extension
  .install(InMemoryHierarchy.INSTANCE, Usages.INSTANCE)
  ).get(); 
  
var ext = classpath.asyncUsages();
ext.findUsages(field, FieldUsageMode.READ);
ext.findUsages(field, FieldUsageMode.WRITE);
ext.findUsages(method);
JacoDB(Kotlin)
val db = jacodb {
    // highly recommend to install InMemoryHierarchy extension
    install(Usages, InMemoryHierarchy)
}

val ext = classpath.usagesExt()
ext.findUsages(field, FieldUsageMode.READ)
ext.findUsages(field, FieldUsageMode.WRITE)
ext.findUsages(method)