diff --git a/doc/soot_options.htm b/doc/soot_options.htm
index 96327827c4f..393a5cd6869 100644
--- a/doc/soot_options.htm
+++ b/doc/soot_options.htm
@@ -591,6 +591,8 @@
apk
apk-class-jimple
apk-c-j
+
binary
+ bin
Sets source precedence to
format
@@ -678,6 +680,8 @@
template
a
asm
+ bin
+ binary
|
Set output format for Soot |
diff --git a/eclipse/ca.mcgill.sable.soot/src/ca/mcgill/sable/soot/ui/PhaseOptionsDialog.java b/eclipse/ca.mcgill.sable.soot/src/ca/mcgill/sable/soot/ui/PhaseOptionsDialog.java
index c481d8c1a76..7601831509f 100644
--- a/eclipse/ca.mcgill.sable.soot/src/ca/mcgill/sable/soot/ui/PhaseOptionsDialog.java
+++ b/eclipse/ca.mcgill.sable.soot/src/ca/mcgill/sable/soot/ui/PhaseOptionsDialog.java
@@ -8751,6 +8751,10 @@ private Composite Input_OptionsCreate(Composite parent) {
"apk-class-jimple apk-c-j",
"\nTry to resolve classes first from .apk (Android Package) files \nfound in the Soot classpath. Fall back to .class, or .jimple \nfiles only when unable to find a class in .apk files. Never load \na .java file.",
false),
+ new OptionData("Binary",
+ "binary bin",
+ "\n...",
+ false),
};
@@ -9036,6 +9040,10 @@ private Composite Output_OptionsCreate(Composite parent) {
"a asm",
"\nProduce .asm files as textual bytecode representation generated \nwith the ASM back end.",
false),
+ new OptionData("Binary",
+ "bin binary",
+ "\n...",
+ false),
};
diff --git a/pom.xml b/pom.xml
index fb30bb6a599..b575ae6157a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -66,6 +66,8 @@
true
true
7.1
+ 5.0.0-RC5-SNAPSHOT
+ 0.45
sootclasses-trunk
@@ -420,7 +422,16 @@
asm-commons
${asm.version}
-
+
+ com.esotericsoftware
+ kryo
+ ${kryo.version}
+
+
+ de.javakaffee
+ kryo-serializers
+ ${kryo-serializers.version}
+
org.javassist
javassist
diff --git a/src/main/generated/options/soot/AntTask.java b/src/main/generated/options/soot/AntTask.java
index 537c4edda31..6b20fc506b5 100644
--- a/src/main/generated/options/soot/AntTask.java
+++ b/src/main/generated/options/soot/AntTask.java
@@ -253,6 +253,8 @@ public void setsrc_prec(String arg) {
|| arg.equals( "apk" )
|| arg.equals( "apk-class-jimple" )
|| arg.equals( "apk-c-j" )
+ || arg.equals( "binary" )
+ || arg.equals( "bin" )
) {
addArg("-src-prec");
addArg(arg);
@@ -336,6 +338,8 @@ public void setoutput_format(String arg) {
|| arg.equals( "template" )
|| arg.equals( "a" )
|| arg.equals( "asm" )
+ || arg.equals( "bin" )
+ || arg.equals( "binary" )
) {
addArg("-output-format");
addArg(arg);
diff --git a/src/main/generated/options/soot/options/Options.java b/src/main/generated/options/soot/options/Options.java
index 55e51e93caf..5c576a56c9d 100644
--- a/src/main/generated/options/soot/options/Options.java
+++ b/src/main/generated/options/soot/options/Options.java
@@ -51,6 +51,8 @@ public static Options v() {
public static final int src_prec_apk = 5;
public static final int src_prec_apk_class_jimple = 6;
public static final int src_prec_apk_c_j = 6;
+ public static final int src_prec_binary = 7;
+ public static final int src_prec_bin = 7;
public static final int output_format_J = 1;
public static final int output_format_jimple = 1;
public static final int output_format_j = 2;
@@ -81,6 +83,8 @@ public static Options v() {
public static final int output_format_template = 16;
public static final int output_format_a = 17;
public static final int output_format_asm = 17;
+ public static final int output_format_bin = 18;
+ public static final int output_format_binary = 18;
public static final int java_version_default = 1;
public static final int java_version_1_1 = 2;
public static final int java_version_1 = 2;
@@ -398,6 +402,16 @@ else if (false
}
src_prec = src_prec_apk_c_j;
}
+ else if (false
+ || value.equals("binary")
+ || value.equals("bin")
+ ) {
+ if (src_prec != 0 && src_prec != src_prec_bin) {
+ G.v().out.println("Multiple values given for option " + option);
+ return false;
+ }
+ src_prec = src_prec_bin;
+ }
else {
G.v().out.println(String.format("Invalid value %s given for option -%s", option, value));
return false;
@@ -646,6 +660,16 @@ else if (false
}
output_format = output_format_asm;
}
+ else if (false
+ || value.equals("bin")
+ || value.equals("binary")
+ ) {
+ if (output_format != 0 && output_format != output_format_binary) {
+ G.v().out.println("Multiple values given for option " + option);
+ return false;
+ }
+ output_format = output_format_binary;
+ }
else {
G.v().out.println(String.format("Invalid value %s given for option -%s", option, value));
return false;
@@ -1672,6 +1696,7 @@ public String getUsage() {
+ padVal("java", "Favour Java files as Soot source")
+ padVal("apk", "Favour APK files as Soot source")
+ padVal("apk-class-jimple apk-c-j", "Favour APK files as Soot source, disregard Java files")
+ + padVal("binary bin", "Favour binary serialization files as Soot source.")
+ padOpt("-full-resolver", "Force transitive resolving of referenced classes")
+ padOpt("-allow-phantom-refs", "Allow unresolved classes; may cause errors")
+ padOpt("-allow-phantom-elms", "Allow phantom methods and fields in non-phantom classes")
@@ -1701,6 +1726,7 @@ public String getUsage() {
+ padVal("d dava", "Produce dava-decompiled .java files")
+ padVal("t template", "Produce .java files with Jimple templates.")
+ padVal("a asm", "Produce .asm files as textual bytecode representation generated with the ASM back end.")
+ + padVal("bin binary", "Produce .bin files as binary serialized representation of the current Scene.")
+ padOpt("-java-version ARG", "Force Java version of bytecode generated by Soot.")
+ padVal("default", "Let Soot determine Java version of generated bytecode.")
+ padVal("1.1 1", "Force Java 1.1 as output version.")
diff --git a/src/main/generated/singletons/soot/Singletons.java b/src/main/generated/singletons/soot/Singletons.java
index 18daf70b0a6..b0d21065c7c 100644
--- a/src/main/generated/singletons/soot/Singletons.java
+++ b/src/main/generated/singletons/soot/Singletons.java
@@ -2427,4 +2427,18 @@ public soot.jbco.jimpleTransformations.FieldRenamer soot_jbco_jimpleTransformati
protected void release_soot_jbco_jimpleTransformations_FieldRenamer() {
instance_soot_jbco_jimpleTransformations_FieldRenamer = null;
}
+
+ private soot.serialization.SootSerializer instance_soot_serialization_SootSerializer;
+ public soot.serialization.SootSerializer soot_serialization_SootSerializer() {
+ if (instance_soot_serialization_SootSerializer == null) {
+ synchronized (this) {
+ if (instance_soot_serialization_SootSerializer == null)
+ instance_soot_serialization_SootSerializer = new soot.serialization.SootSerializer(g);
+ }
+ }
+ return instance_soot_serialization_SootSerializer;
+ }
+ protected void release_soot_serialization_SootSerializer() {
+ instance_soot_serialization_SootSerializer = null;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/soot/AbstractSootFieldRef.java b/src/main/java/soot/AbstractSootFieldRef.java
index 4fbb7dc7946..12900e8bd98 100644
--- a/src/main/java/soot/AbstractSootFieldRef.java
+++ b/src/main/java/soot/AbstractSootFieldRef.java
@@ -39,7 +39,7 @@ public class AbstractSootFieldRef implements SootFieldRef {
private static final Logger logger = LoggerFactory.getLogger(AbstractSootFieldRef.class);
public AbstractSootFieldRef(SootClass declaringClass, String name, Type type, boolean isStatic) {
- this.declaringClass = declaringClass;
+ this.declaringClass = declaringClass.getType();
this.name = name;
this.type = type;
this.isStatic = isStatic;
@@ -54,14 +54,14 @@ public AbstractSootFieldRef(SootClass declaringClass, String name, Type type, bo
}
}
- private final SootClass declaringClass;
+ private final RefType declaringClass;
private final String name;
private final Type type;
private final boolean isStatic;
@Override
public SootClass declaringClass() {
- return declaringClass;
+ return declaringClass.getSootClass();
}
@Override
@@ -81,7 +81,7 @@ public boolean isStatic() {
@Override
public String getSignature() {
- return SootField.getSignature(declaringClass, name, type);
+ return SootField.getSignature(declaringClass.getSootClass(), name, type);
}
public class FieldResolutionFailedException extends ResolutionFailedException {
@@ -119,7 +119,7 @@ private SootField checkStatic(SootField ret) {
}
private SootField resolve(StringBuffer trace) {
- SootClass cl = declaringClass;
+ SootClass cl = declaringClass.getSootClass();
while (true) {
if (trace != null) {
trace.append("Looking in " + cl + " which has fields " + cl.getFields() + "\n");
@@ -190,7 +190,7 @@ else if (Scene.v().allowsPhantomRefs() && cl.isPhantom()) {
synchronized (declaringClass) {
// Be careful: Another thread may have already created this
// field in the meantime, so better check twice.
- SootField clField = declaringClass.getFieldByNameUnsafe(name);
+ SootField clField = declaringClass.getSootClass().getFieldByNameUnsafe(name);
if (clField != null) {
if (clField.getType().equals(type)) {
return checkStatic(clField);
@@ -199,7 +199,7 @@ else if (Scene.v().allowsPhantomRefs() && cl.isPhantom()) {
}
} else {
// Add the new phantom field
- declaringClass.addField(sf);
+ declaringClass.getSootClass().addField(sf);
return sf;
}
}
diff --git a/src/main/java/soot/Body.java b/src/main/java/soot/Body.java
index 48f7e050dbd..16d19ce3047 100644
--- a/src/main/java/soot/Body.java
+++ b/src/main/java/soot/Body.java
@@ -72,7 +72,7 @@
public abstract class Body extends AbstractHost implements Serializable {
private static final Logger logger = LoggerFactory.getLogger(Body.class);
/** The method associated with this Body. */
- protected transient SootMethod method = null;
+ protected SootMethod method = null;
/** The chain of locals for this Body. */
protected Chain localChain = new HashChain();
diff --git a/src/main/java/soot/PackManager.java b/src/main/java/soot/PackManager.java
index efa0e373415..a669117162f 100644
--- a/src/main/java/soot/PackManager.java
+++ b/src/main/java/soot/PackManager.java
@@ -22,6 +22,9 @@
* #L%
*/
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+
import heros.solver.CountingThreadPoolExecutor;
import java.io.File;
@@ -119,6 +122,7 @@
import soot.jimple.toolkits.thread.synchronization.LockAllocator;
import soot.jimple.toolkits.typing.TypeAssigner;
import soot.options.Options;
+import soot.serialization.BinaryBackend;
import soot.shimple.Shimple;
import soot.shimple.ShimpleBody;
import soot.shimple.ShimpleTransformer;
@@ -152,6 +156,8 @@ public class PackManager {
private JarOutputStream jarFile = null;
protected DexPrinter dexPrinter = null;
+ private Supplier binaryBackend = Suppliers.memoize(() -> new BinaryBackend());
+
public PackManager(Singletons.Global g) {
PhaseOptions.v().setPackManager(this);
init();
@@ -939,6 +945,8 @@ private void runBodyPacks(SootClass c) {
produceGrimp = Options.v().via_grimp();
produceBaf = !produceGrimp;
break;
+ case Options.output_format_binary:
+ break;
default:
throw new RuntimeException();
}
@@ -1169,6 +1177,9 @@ protected void writeClass(SootClass c) {
case Options.output_format_asm:
createASMBackend(c).generateTextualRepresentation(writerOut);
break;
+ case Options.output_format_binary:
+ binaryBackend.get().write(c,streamOut);
+ break;
default:
throw new RuntimeException();
}
@@ -1182,7 +1193,7 @@ protected void writeClass(SootClass c) {
jarFile.closeEntry();
}
} catch (IOException e) {
- throw new CompilationDeathException("Cannot close output file " + fileName);
+ throw new CompilationDeathException("Cannot close output file " + fileName, e);
}
}
diff --git a/src/main/java/soot/RefType.java b/src/main/java/soot/RefType.java
index fb0a486d654..7e4efe8d856 100644
--- a/src/main/java/soot/RefType.java
+++ b/src/main/java/soot/RefType.java
@@ -48,7 +48,7 @@ public String getClassName() {
return className;
}
- private volatile SootClass sootClass;
+ private volatile transient SootClass sootClass;
private AnySubType anySubType;
private RefType(String className) {
diff --git a/src/main/java/soot/SootClass.java b/src/main/java/soot/SootClass.java
index a3a82d5fb0b..d0a46de2de8 100644
--- a/src/main/java/soot/SootClass.java
+++ b/src/main/java/soot/SootClass.java
@@ -23,9 +23,11 @@
*/
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
+import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -74,11 +76,11 @@ public class SootClass extends AbstractHost implements Numberable {
// methodList is just for keeping the methods in a consistent order. It
// needs to be kept consistent with subSigToMethods.
protected List methodList;
- protected Chain interfaces;
+ protected Chain interfaces;
protected boolean isInScene;
- protected SootClass superClass;
- protected SootClass outerClass;
+ protected RefType superClass;
+ protected RefType outerClass;
protected boolean isPhantom;
@@ -87,7 +89,6 @@ public class SootClass extends AbstractHost implements Numberable {
/**
* Constructs an empty SootClass with the given name and modifiers.
*/
-
public SootClass(String name, int modifiers) {
if (name.charAt(0) == '[') {
throw new RuntimeException("Attempt to make a class whose name starts with [");
@@ -808,7 +809,8 @@ public int getInterfaceCount() {
@SuppressWarnings("unchecked")
public Chain getInterfaces() {
checkLevel(HIERARCHY);
- return interfaces == null ? EmptyChain.v() : interfaces;
+ return interfaces == null ? EmptyChain.v()
+ : interfaces.stream().map(RefType::getSootClass).collect(Collectors.toCollection(() -> new HashChain<>()));
}
/**
@@ -821,8 +823,8 @@ public boolean implementsInterface(String name) {
return false;
}
- for (SootClass sc : interfaces) {
- if (sc.getName().equals(name)) {
+ for (RefType sc : interfaces) {
+ if (sc.getClassName().equals(name)) {
return true;
}
}
@@ -841,7 +843,7 @@ public void addInterface(SootClass interfaceClass) {
if (interfaces == null) {
interfaces = new HashChain<>();
}
- interfaces.add(interfaceClass);
+ interfaces.add(interfaceClass.getType());
}
/**
@@ -877,7 +879,7 @@ public SootClass getSuperclass() {
if (superClass == null && !isPhantom()) {
throw new RuntimeException("no superclass for " + getName());
} else {
- return superClass;
+ return superClass.getSootClass();
}
}
@@ -890,7 +892,7 @@ public SootClass getSuperclass() {
public SootClass getSuperclassUnsafe() {
checkLevel(HIERARCHY);
- return superClass;
+ return superClass == null ? null : superClass.getSootClass();
}
/**
@@ -899,7 +901,7 @@ public SootClass getSuperclassUnsafe() {
public void setSuperclass(SootClass c) {
checkLevel(HIERARCHY);
- superClass = c;
+ superClass = c.getType();
}
public boolean hasOuterClass() {
@@ -912,7 +914,7 @@ public SootClass getOuterClass() {
if (outerClass == null) {
throw new RuntimeException("no outer class");
} else {
- return outerClass;
+ return outerClass.getSootClass();
}
}
@@ -921,12 +923,12 @@ public SootClass getOuterClass() {
*/
public SootClass getOuterClassUnsafe() {
checkLevel(HIERARCHY);
- return outerClass;
+ return outerClass == null ? null : outerClass.getSootClass();
}
public void setOuterClass(SootClass c) {
checkLevel(HIERARCHY);
- outerClass = c;
+ outerClass = c.getType();
}
public boolean isInnerClass() {
@@ -1299,4 +1301,15 @@ public void validate(List exceptionList) {
}
}
+ public RefType getSuperType() {
+ return superClass;
+ }
+
+ public RefType getOuterType() {
+ return outerClass;
+ }
+
+ public Collection getInterfaceTypes() {
+ return interfaces == null ? Collections.EMPTY_LIST : interfaces;
+ }
}
diff --git a/src/main/java/soot/SootMethodRefImpl.java b/src/main/java/soot/SootMethodRefImpl.java
index ec30f0bac36..b7cf3accdc6 100644
--- a/src/main/java/soot/SootMethodRefImpl.java
+++ b/src/main/java/soot/SootMethodRefImpl.java
@@ -51,7 +51,7 @@ public class SootMethodRefImpl implements SootMethodRef {
private static final Logger logger = LoggerFactory.getLogger(SootMethodRefImpl.class);
- private final SootClass declaringClass;
+ private final RefType declaringClass;
private final String name;
protected List parameterTypes;
private final Type returnType;
@@ -85,7 +85,7 @@ public SootMethodRefImpl(SootClass declaringClass, String name, List param
throw new IllegalArgumentException("Attempt to create SootMethodRef with null returnType");
}
- this.declaringClass = declaringClass;
+ this.declaringClass = declaringClass.getType();
this.name = name;
this.parameterTypes = (parameterTypes == null) // initialize with unmodifiable collection
? Collections.emptyList()
@@ -101,7 +101,7 @@ public SootClass declaringClass() {
@Override
public SootClass getDeclaringClass() {
- return declaringClass;
+ return declaringClass.getSootClass();
}
@Override
@@ -146,7 +146,7 @@ public NumberedString getSubSignature() {
@Override
public String getSignature() {
- return SootMethod.getSignature(declaringClass, name, parameterTypes, returnType);
+ return SootMethod.getSignature(declaringClass.getSootClass(), name, parameterTypes, returnType);
}
@Override
@@ -197,7 +197,7 @@ private void checkStatic(SootMethod method) {
}
protected SootMethod tryResolve(final StringBuilder trace) {
- SootClass selectedClass = declaringClass;
+ SootClass selectedClass = declaringClass.getSootClass();
while (selectedClass != null) {
if (trace != null) {
trace.append("Looking in ").append(selectedClass).append(" which has methods ").append(selectedClass.getMethods())
@@ -224,7 +224,7 @@ protected SootMethod tryResolve(final StringBuilder trace) {
selectedClass = selectedClass.getSuperclassUnsafe();
}
- selectedClass = declaringClass;
+ selectedClass = declaringClass.getSootClass();
while (selectedClass != null) {
final Queue queue = new ArrayDeque<>(selectedClass.getInterfaces());
while (!queue.isEmpty()) {
@@ -251,7 +251,7 @@ protected SootMethod tryResolve(final StringBuilder trace) {
if (Scene.v().allowsPhantomRefs() && Options.v().ignore_resolution_errors()) {
SootMethod method = Scene.v().makeSootMethod(name, parameterTypes, returnType, isStatic() ? Modifier.STATIC : 0);
method.setPhantom(true);
- method = declaringClass.getOrAddMethod(method);
+ method = declaringClass.getSootClass().getOrAddMethod(method);
checkStatic(method);
return method;
}
@@ -273,12 +273,12 @@ private SootMethod resolve(final StringBuilder trace) {
// declaring class of dynamic invocations not known at compile time, treat as
// phantom class regardless if phantom classes are enabled
- if (declaringClass.getName().equals(SootClass.INVOKEDYNAMIC_DUMMY_CLASS_NAME)) {
+ if (declaringClass.getClassName().equals(SootClass.INVOKEDYNAMIC_DUMMY_CLASS_NAME)) {
treatAsPhantomClass = true;
}
if (treatAsPhantomClass) {
- return createUnresolvedErrorMethod(declaringClass);
+ return createUnresolvedErrorMethod(declaringClass.getSootClass());
}
if (trace == null) {
diff --git a/src/main/java/soot/SourceLocator.java b/src/main/java/soot/SourceLocator.java
index 818c043ef4b..459bc4b6aff 100755
--- a/src/main/java/soot/SourceLocator.java
+++ b/src/main/java/soot/SourceLocator.java
@@ -48,6 +48,7 @@
import soot.asm.AsmClassProvider;
import soot.dexpler.DexFileProvider;
import soot.options.Options;
+import soot.serialization.BinaryClassProvider;
/**
* Provides utility methods to retrieve an input stream for a class name, given a classfile, or jimple or baf output files.
@@ -80,7 +81,10 @@ public ClassSourceType load(String path) throws Exception {
return ClassSourceType.apk;
} else if (path.endsWith(".dex")) {
return ClassSourceType.dex;
- } else {
+ } else if (path.endsWith(".bin")){
+ return ClassSourceType.bin;
+ }
+ else {
return ClassSourceType.unknown;
}
}
@@ -260,6 +264,12 @@ protected void setupClassProviders() {
classProviders.add(classFileClassProvider);
classProviders.add(new JimpleClassProvider());
break;
+ case Options.src_prec_binary:
+ classProviders.add(new BinaryClassProvider());
+ classProviders.add(classFileClassProvider);
+ classProviders.add(new JimpleClassProvider());
+ classProviders.add(new JavaClassProvider());
+ break;
default:
throw new RuntimeException("Other source precedences are not currently supported.");
}
@@ -412,7 +422,7 @@ public String getFileNameFor(SootClass c, int rep) {
}
if (rep != Options.output_format_dava) {
- if (rep == Options.output_format_class) {
+ if (rep == Options.output_format_class || rep == Options.output_format_bin) {
b.append(c.getName().replace('.', File.separatorChar));
} else if (rep == Options.output_format_template) {
b.append(c.getName().replace('.', '_'));
@@ -519,6 +529,8 @@ public String getExtensionFor(int rep) {
return ".java";
case Options.output_format_asm:
return ".asm";
+ case Options.output_format_binary:
+ return ".bin";
default:
throw new RuntimeException();
}
@@ -681,6 +693,6 @@ public void clearDexClassPathExtensions() {
}
private enum ClassSourceType {
- jar, zip, apk, dex, directory, unknown
+ jar, zip, apk, dex, directory, bin, unknown
}
}
diff --git a/src/main/java/soot/serialization/BinaryBackend.java b/src/main/java/soot/serialization/BinaryBackend.java
new file mode 100644
index 00000000000..da6fbc1baa7
--- /dev/null
+++ b/src/main/java/soot/serialization/BinaryBackend.java
@@ -0,0 +1,23 @@
+package soot.serialization;
+
+import com.esotericsoftware.kryo.io.Output;
+
+import java.io.OutputStream;
+
+import soot.SootClass;
+import soot.SootMethod;
+
+/**
+ * @author Manuel Benz at 2019-08-26
+ */
+public class BinaryBackend {
+
+ public void write(SootClass c, OutputStream outStream) {
+ try (Output out = new Output(outStream)) {
+ // make sure all bodies are loaded before we serialize. we won't have any method sources afterwards
+ c.getMethods().stream().filter(SootMethod::isConcrete).forEach(SootMethod::retrieveActiveBody);
+ // TODO add dependent types as tags so we can have eager loading or stay with lazy loading for bodies?
+ SootSerializer.v().writeObject(out, c);
+ }
+ }
+}
diff --git a/src/main/java/soot/serialization/BinaryClassProvider.java b/src/main/java/soot/serialization/BinaryClassProvider.java
new file mode 100644
index 00000000000..3e0bd6839c0
--- /dev/null
+++ b/src/main/java/soot/serialization/BinaryClassProvider.java
@@ -0,0 +1,66 @@
+package soot.serialization;
+
+import com.esotericsoftware.kryo.io.Input;
+
+import soot.ClassSource;
+import soot.FoundFile;
+import soot.SootClass;
+import soot.SootMethod;
+import soot.SourceLocator;
+import soot.Unit;
+import soot.ValueBox;
+import soot.javaToJimple.IInitialResolver;
+
+/**
+ * @author Manuel Benz at 2019-08-26
+ */
+public class BinaryClassProvider implements soot.ClassProvider {
+
+ @Override
+ public ClassSource find(String className) {
+ String clsFile = className.replace('.', '/') + ".bin";
+ FoundFile foundFile = SourceLocator.v().lookupInClassPath(clsFile);
+ return foundFile == null ? null : new BinaryClassSource(className, foundFile);
+ }
+
+ private class BinaryClassSource extends ClassSource {
+ private FoundFile foundFile;
+
+ public BinaryClassSource(String className, FoundFile foundFile) {
+ super(className);
+ this.foundFile = foundFile;
+ }
+
+ @Override
+ public IInitialResolver.Dependencies resolve(SootClass sc) {
+ try (Input input = new Input(foundFile.inputStream())) {
+ SootClass sootClass = SootSerializer.v().readObject(input, sc);
+
+ IInitialResolver.Dependencies dependencies = new IInitialResolver.Dependencies();
+
+ DependencyCollector collector = new DependencyCollector();
+ // fixme put this into the write out process
+ for (SootMethod method : sootClass.getMethods()) {
+ if (method.isConcrete()) {
+ for (Unit unit : method.retrieveActiveBody().getUnits()) {
+ for (ValueBox box : unit.getUseAndDefBoxes()) {
+ box.getValue().apply(collector);
+ dependencies.typesToHierarchy.addAll(collector.getResult());
+ }
+ }
+ }
+ }
+
+ return dependencies;
+ }
+ }
+
+ @Override
+ public void close() {
+ if (foundFile != null) {
+ foundFile.close();
+ foundFile = null;
+ }
+ }
+ }
+}
diff --git a/src/main/java/soot/serialization/DependencyCollector.java b/src/main/java/soot/serialization/DependencyCollector.java
new file mode 100644
index 00000000000..c24fd430d30
--- /dev/null
+++ b/src/main/java/soot/serialization/DependencyCollector.java
@@ -0,0 +1,139 @@
+package soot.serialization;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import soot.Local;
+import soot.RefType;
+import soot.Type;
+import soot.jimple.AbstractJimpleValueSwitch;
+import soot.jimple.ArrayRef;
+import soot.jimple.CastExpr;
+import soot.jimple.CaughtExceptionRef;
+import soot.jimple.ClassConstant;
+import soot.jimple.DynamicInvokeExpr;
+import soot.jimple.InstanceFieldRef;
+import soot.jimple.InstanceOfExpr;
+import soot.jimple.InterfaceInvokeExpr;
+import soot.jimple.MethodHandle;
+import soot.jimple.MethodType;
+import soot.jimple.NewArrayExpr;
+import soot.jimple.NewExpr;
+import soot.jimple.NewMultiArrayExpr;
+import soot.jimple.SpecialInvokeExpr;
+import soot.jimple.StaticFieldRef;
+import soot.jimple.StaticInvokeExpr;
+import soot.jimple.VirtualInvokeExpr;
+
+/**
+ * @author Manuel Benz at 2019-09-04
+ */
+public class DependencyCollector extends AbstractJimpleValueSwitch {
+
+ private Collection result;
+
+ @Override
+ public Collection getResult() {
+ return result;
+ }
+
+ @Override
+ public void caseArrayRef(ArrayRef v) {
+ super.caseArrayRef(v);
+ }
+
+ @Override
+ public void caseInterfaceInvokeExpr(InterfaceInvokeExpr v) {
+ super.caseInterfaceInvokeExpr(v);
+ }
+
+ @Override
+ public void caseSpecialInvokeExpr(SpecialInvokeExpr v) {
+ super.caseSpecialInvokeExpr(v);
+ }
+
+ @Override
+ public void caseStaticInvokeExpr(StaticInvokeExpr v) {
+ result = Collections.singleton(v.getMethodRef().getDeclaringClass().getType());
+ }
+
+ @Override
+ public void caseVirtualInvokeExpr(VirtualInvokeExpr v) {
+ super.caseVirtualInvokeExpr(v);
+ }
+
+ @Override
+ public void caseDynamicInvokeExpr(DynamicInvokeExpr v) {
+ super.caseDynamicInvokeExpr(v);
+ }
+
+ @Override
+ public void caseCastExpr(CastExpr v) {
+ super.caseCastExpr(v);
+ }
+
+ @Override
+ public void caseInstanceOfExpr(InstanceOfExpr v) {
+ super.caseInstanceOfExpr(v);
+ }
+
+ @Override
+ public void caseNewArrayExpr(NewArrayExpr v) {
+ super.caseNewArrayExpr(v);
+ }
+
+ @Override
+ public void caseNewMultiArrayExpr(NewMultiArrayExpr v) {
+ super.caseNewMultiArrayExpr(v);
+ }
+
+ @Override
+ public void caseNewExpr(NewExpr v) {
+ super.caseNewExpr(v);
+ }
+
+ @Override
+ public void caseInstanceFieldRef(InstanceFieldRef v) {
+ super.caseInstanceFieldRef(v);
+ }
+
+ @Override
+ public void caseCaughtExceptionRef(CaughtExceptionRef v) {
+ super.caseCaughtExceptionRef(v);
+ }
+
+ @Override
+ public void caseStaticFieldRef(StaticFieldRef v) {
+ super.caseStaticFieldRef(v);
+ }
+
+ @Override
+ public void caseClassConstant(ClassConstant v) {
+ super.caseClassConstant(v);
+ }
+
+ @Override
+ public void caseMethodHandle(MethodHandle v) {
+ super.caseMethodHandle(v);
+ }
+
+ @Override
+ public void caseMethodType(MethodType v) {
+ super.caseMethodType(v);
+ }
+
+ @Override
+ public void caseLocal(Local v) {
+ Type type = v.getType();
+ if (type instanceof RefType) {
+ result = Collections.singleton((RefType) type);
+ } else {
+ defaultCase(v);
+ }
+ }
+
+ @Override
+ public void defaultCase(Object v) {
+ result = Collections.EMPTY_SET;
+ }
+}
diff --git a/src/main/java/soot/serialization/SootSerializer.java b/src/main/java/soot/serialization/SootSerializer.java
new file mode 100644
index 00000000000..f035068d815
--- /dev/null
+++ b/src/main/java/soot/serialization/SootSerializer.java
@@ -0,0 +1,139 @@
+package soot.serialization;
+
+import com.esotericsoftware.kryo.Kryo;
+import com.esotericsoftware.kryo.Registration;
+import com.esotericsoftware.kryo.Serializer;
+import com.esotericsoftware.kryo.io.Input;
+import com.esotericsoftware.kryo.io.Output;
+import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy;
+
+import java.lang.reflect.InvocationHandler;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.GregorianCalendar;
+import java.util.Iterator;
+import java.util.List;
+
+import org.objenesis.strategy.StdInstantiatorStrategy;
+
+import soot.G;
+import soot.Scene;
+import soot.Singletons;
+import soot.UnitPatchingChain;
+import soot.util.HashChain;
+import soot.util.Numberable;
+import soot.util.NumberedString;
+import soot.util.SmallNumberedMap;
+
+import de.javakaffee.kryoserializers.ArraysAsListSerializer;
+import de.javakaffee.kryoserializers.CollectionsEmptyListSerializer;
+import de.javakaffee.kryoserializers.CollectionsEmptyMapSerializer;
+import de.javakaffee.kryoserializers.CollectionsEmptySetSerializer;
+import de.javakaffee.kryoserializers.CollectionsSingletonListSerializer;
+import de.javakaffee.kryoserializers.CollectionsSingletonMapSerializer;
+import de.javakaffee.kryoserializers.CollectionsSingletonSetSerializer;
+import de.javakaffee.kryoserializers.GregorianCalendarSerializer;
+import de.javakaffee.kryoserializers.JdkProxySerializer;
+import de.javakaffee.kryoserializers.SynchronizedCollectionsSerializer;
+import de.javakaffee.kryoserializers.UnmodifiableCollectionsSerializer;
+
+/**
+ * Provides serialization functionality for the binary front- and backend
+ *
+ * @author Manuel Benz at 2019-08-27
+ */
+public class SootSerializer extends Kryo {
+
+ public SootSerializer(Singletons.Global g) {
+ this.setReferences(true);
+ this.setRegistrationRequired(false);
+ this.setInstantiatorStrategy(new DefaultInstantiatorStrategy(new StdInstantiatorStrategy()));
+ registerSpecialSerializers();
+ registerSpecialInstantiators();
+ }
+
+ private void registerSpecialInstantiators() {
+ this.getRegistration(UnitPatchingChain.class).setInstantiator(() -> new UnitPatchingChain(new HashChain<>()));
+ }
+
+ private void registerSpecialSerializers() {
+ this.register(Arrays.asList("").getClass(), new ArraysAsListSerializer());
+ this.register(Collections.EMPTY_LIST.getClass(), new CollectionsEmptyListSerializer());
+ this.register(Collections.EMPTY_MAP.getClass(), new CollectionsEmptyMapSerializer());
+ this.register(Collections.EMPTY_SET.getClass(), new CollectionsEmptySetSerializer());
+ this.register(Collections.singletonList("").getClass(), new CollectionsSingletonListSerializer());
+ this.register(Collections.singleton("").getClass(), new CollectionsSingletonSetSerializer());
+ this.register(Collections.singletonMap("", "").getClass(), new CollectionsSingletonMapSerializer());
+ this.register(GregorianCalendar.class, new GregorianCalendarSerializer());
+ this.register(InvocationHandler.class, new JdkProxySerializer());
+ UnmodifiableCollectionsSerializer.registerSerializers(this);
+ SynchronizedCollectionsSerializer.registerSerializers(this);
+
+ this.register(NumberedString.class, new Serializer() {
+ @Override
+ public void write(Kryo kryo, Output output, NumberedString object) {
+ output.writeString(object.getString());
+ }
+
+ @Override
+ public NumberedString read(Kryo kryo, Input input, Class type) {
+ // we have to create a new one and register it to the scene
+ return Scene.v().getSubSigNumberer().findOrAdd(input.readString());
+ }
+ });
+
+ this.register(SmallNumberedMap.class, new Serializer() {
+ @Override
+ public void write(Kryo kryo, Output output, SmallNumberedMap object) {
+ ArrayList keys = new ArrayList<>();
+ ArrayList values = new ArrayList<>();
+
+ Iterator iterator = object.keyIterator();
+ while (iterator.hasNext()) {
+ Numberable key = iterator.next();
+ keys.add(key);
+ values.add(object.get(key));
+ }
+
+ writeObject(output, keys);
+ writeObject(output, values);
+ }
+
+ @Override
+ public SmallNumberedMap read(Kryo kryo, Input input, Class type) {
+ List keys = readObject(input, ArrayList.class);
+ List values = readObject(input, ArrayList.class);
+
+ SmallNumberedMap map = new SmallNumberedMap();
+
+ for (int i = 0; i < keys.size(); i++) {
+ map.put(keys.get(i), values.get(i));
+ }
+
+ return map;
+ }
+ });
+ }
+
+ public T readObject(Input in, T instance) {
+ Class> instanceType = instance.getClass();
+ Registration oldReg = this.register(instanceType);
+
+ // make sure we take the given instance as the "created" one
+ oldReg.setInstantiator(() -> {
+ // reset registration so we do not keep the instantiator for all instances of instanceType.
+ // unregister does not work since the reg is not mapped to an int when we are running in
+ // no-registration-required-mode
+ // this just overwrites the registration and thereby resets the instantiator
+ getClassResolver().registerImplicit(instance.getClass());
+ return instance;
+ });
+
+ return (T) this.readObject(in, instanceType);
+ }
+
+ public static SootSerializer v() {
+ return G.v().soot_serialization_SootSerializer();
+ }
+}
diff --git a/src/main/xml/options/soot_options.xml b/src/main/xml/options/soot_options.xml
index 41d54d5a362..524c86e80a9 100644
--- a/src/main/xml/options/soot_options.xml
+++ b/src/main/xml/options/soot_options.xml
@@ -705,6 +705,15 @@
file.
+
+ Binary
+ binary
+ bin
+ Favour binary serialization files as Soot source.
+
+ ...
+
+
Force complete resolver
@@ -1030,6 +1039,16 @@
Produce .asm files as textual bytecode representation generated with the ASM back end.
+
+ Binary
+ bin
+ binary
+ Produce .bin files as binary serialized representation of the current Scene.
+
+
+ ...
+
+
Java version
diff --git a/src/main/xml/singletons/singletons.xml b/src/main/xml/singletons/singletons.xml
index 59cf3776280..017b3e0db08 100644
--- a/src/main/xml/singletons/singletons.xml
+++ b/src/main/xml/singletons/singletons.xml
@@ -171,4 +171,5 @@
soot.jbco.jimpleTransformations.MethodRenamer
soot.LambdaMetaFactory
soot.jbco.jimpleTransformations.FieldRenamer
+ soot.serialization.SootSerializer
diff --git a/src/systemTest/java/soot/serialization/SerializationTest.java b/src/systemTest/java/soot/serialization/SerializationTest.java
new file mode 100644
index 00000000000..51363bc5bbb
--- /dev/null
+++ b/src/systemTest/java/soot/serialization/SerializationTest.java
@@ -0,0 +1,65 @@
+package soot.serialization;
+
+import com.esotericsoftware.minlog.Log;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Test;
+import soot.PackManager;
+import soot.SootMethod;
+import soot.options.Options;
+import soot.testing.framework.AbstractTestingFramework;
+
+/**
+ * @author Manuel Benz at 2019-08-26
+ */
+public class SerializationTest extends AbstractTestingFramework {
+
+ private static final String TEST_METHOD_NAME = "main";
+ private static final String TEST_METHOD_RET = "void";
+ public static final Path SERIALIZATION_OUT = Paths.get("./sootOutput");
+
+ @Before
+ public void setUp() throws Exception {
+ Options.v().set_output_dir(SERIALIZATION_OUT.toString());
+ FileUtils.deleteDirectory(new File(Options.v().output_dir()));
+ Files.createDirectory(SERIALIZATION_OUT);
+ Log.TRACE();
+ }
+
+ @Override
+ protected void setupSoot() {
+ super.setupSoot();
+ Options.v().set_output_format(Options.output_format_binary);
+ Options.v().set_src_prec(Options.src_prec_binary);
+ prependToProcessDir(SERIALIZATION_OUT.toString());
+ }
+
+ @Override
+ protected void runSoot() {
+ super.runSoot();
+ PackManager.v().writeOutput();
+ }
+
+ @Test
+ public void WriteOut() {
+ String testClass = "soot.lambdaMetaFactory.LambdaNoCaptures";
+
+ final SootMethod target = prepareTarget(methodSigFromComponents(testClass, TEST_METHOD_RET, TEST_METHOD_NAME), testClass,
+ "java.util.function.Function");
+ }
+
+ @Test
+ public void writeAndRead() {
+ String testClass = "soot.lambdaMetaFactory.LambdaNoCaptures";
+
+ SootMethod target = prepareTarget(methodSigFromComponents(testClass, TEST_METHOD_RET, TEST_METHOD_NAME), testClass,
+ "java.util.function.Function");
+
+ target = prepareTarget(methodSigFromComponents(testClass, TEST_METHOD_RET, TEST_METHOD_NAME), testClass,
+ "java.util.function.Function");
+ }
+}
diff --git a/src/systemTest/java/soot/testing/framework/AbstractTestingFramework.java b/src/systemTest/java/soot/testing/framework/AbstractTestingFramework.java
index 95accfb548f..fc94b42df0a 100644
--- a/src/systemTest/java/soot/testing/framework/AbstractTestingFramework.java
+++ b/src/systemTest/java/soot/testing/framework/AbstractTestingFramework.java
@@ -114,7 +114,7 @@ protected SootMethod prepareTarget(String targetMethodSignature, Collection pd = new ArrayList<>(Options.v().process_dir());
+ pd.addAll(Arrays.asList(dirs));
+ Options.v().set_process_dir(pd);
+ }
+
+ protected void prependToProcessDir(String... dirs) {
+ ArrayList pd = new ArrayList<>(Options.v().process_dir());
+ pd.addAll(0, Arrays.asList(dirs));
+ Options.v().set_process_dir(pd);
+ }
+
protected void runSoot() {
PackManager.v().runPacks();
}