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 @@

Input Options


apk 
apk-class-jimple  apk-c-j  +
binary  + bin 
Sets source precedence to format @@ -678,6 +680,8 @@

Output Options

template 
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(); }