diff --git a/README.md b/README.md index 1610371c1..25607e531 100644 --- a/README.md +++ b/README.md @@ -31,14 +31,13 @@ Building To build the jar, use `./gradlew shadowJar` - Troubleshooting --------------- Simplify is still in early stages of development. If you encounter a failure, try these recommendations, in order: -1. Limit the target methods and classes to just the essentials with `-it`. Simplify will try and run over the entire app by default, which means more possibility for failure. Limiting to just one class or a few important methods improves chances significantly. -2. If methods fail to simplify because of errors related to maximum visits exceeded, try using higher `--max-address-visits`, `--max-call-depth`, and `--max-method-visits`. +1. Limit to just a few methods or classes `-it`. +2. If failure is because of maximum visits exceeded, try using higher `--max-address-visits`, `--max-call-depth`, and `--max-method-visits`. 3. Try with `-v` or `-vv` and report the issue. 4. Try again, but do not break eye contact. Simpify can sense fear. @@ -46,28 +45,18 @@ Simplify is still in early stages of development. If you encounter a failure, tr Reporting Issues ---------------- -Two main things are needed to reproduce and fix problems: - -1. The APK. If it's legal and possible, please link to the APK. +1. If you can, link the **APK** or **DEX**. 2. The full command used. +3. *Optional*: verbose logs -Optional, but still useful, is a verbose log output around the error. - - -Reporting Success ------------------ - -Did Simplify just save you a few hours of suffering? Send an e-mail to calebjfenton (AT) gmail [dot] com. If that's not your thing, send a pull request with bug fixes. - - - -### Optimization Example +Optimization Example +-------------------- +### Before Optimization ```smali .method public static test1()I .locals 2 new-instance v0, Ljava/lang/Integer; - const/4 v1, 0x1 invoke-direct {v0, v1}, Ljava/lang/Integer;->(I)V @@ -78,33 +67,30 @@ Did Simplify just save you a few hours of suffering? Send an e-mail to calebjfen .end method ``` -The above code is an obtuse way to say `v0 = 1`. This is sometimes used as an obfuscation technique. +All this does is `v0 = 1`. -###After Constant Propagation +### After Constant Propagation ```smali .method public static test1()I .locals 2 new-instance v0, Ljava/lang/Integer; - const/4 v1, 0x1 invoke-direct {v0, v1}, Ljava/lang/Integer;->(I)V invoke-virtual {v0}, Ljava/lang/Integer;->intValue()I - # move-result replaced with const/4 - const/4 v0, 0x1 - - # known return register value prepended with const const/4 v0, 0x1 return v0 .end method ``` -Some single assignment instructions can be replaced with constant instructions when there is consensus of the value being assigned of all the possible executions of that instruction. If the instruction is outside of a loop, there will only be one node in the graph for that instruction. -In the above example, `move-result` is constantized, so is `return`, because there is only one possible value (consensus) and it is not unknown. +The `move-result v0` is replaced with `const/4 v0, 0x1`. This is because there is only one possible return value for `intValue()I` and the return type can be made a constant. The arguments `v0` and `v1` are unambiguous and do not change. That is to say, there's a consensus of values for every possible execution path at `intValue()I`. Other types of values that can be turned into constants: +* numbers - `const/4`, `const/16`, etc. +* strings - `const-string` +* classes - `const-class` ###After Dead Code Removal ```smali @@ -117,11 +103,13 @@ In the above example, `move-result` is constantized, so is `return`, because the .end method ``` -Dead code includes: +Because the code above `const/4 v0, 0x1` does not affect state outside of the method (no side-effects) it can be removed without changing behavior. If there was a method call that wrote something to the file system or network, it couldn't be removed because it affects state outside the method. Or if `test()I` took a mutable argument, such as a `LinkedList`, any instructions that accessed it couldn't be considered dead. + +Some other examples of dead code: + +* unreferenced assignments - assigning registers and not using them +* unreached / unreachable instructions - `if (false) { dead_code(); }` -* unreferenced assignments - assigning something and never using it -* method calls with no side-effects - `Ljava/lang/Integer;->intValue()I` has no side-effects -* unreached / unreachable instructions - code inside of an `if (false)` block, none in this example Related Works ------------- diff --git a/simplify/src/main/java/org/cf/simplify/Optimizer.java b/simplify/src/main/java/org/cf/simplify/Optimizer.java index 81afbc7ef..8d1e4eea4 100644 --- a/simplify/src/main/java/org/cf/simplify/Optimizer.java +++ b/simplify/src/main/java/org/cf/simplify/Optimizer.java @@ -11,7 +11,7 @@ import org.cf.simplify.strategy.DeadRemovalStrategy; import org.cf.simplify.strategy.OptimizationStrategy; import org.cf.simplify.strategy.PeepholeStrategy; -import org.cf.simplify.strategy.ReflectionRemovalStrategy; +import org.cf.simplify.strategy.UnreflectionStrategy; import org.cf.smalivm.VirtualMachine; import org.cf.smalivm.context.ExecutionGraph; import org.jf.dexlib2.util.ReferenceUtil; @@ -52,7 +52,7 @@ public Optimizer(ExecutionGraph graph, BuilderMethod method, VirtualMachine vm, performRepeatedlyStrategies.add(strategy); methodReexecuteStrategies = new LinkedList(); - methodReexecuteStrategies.add(new ReflectionRemovalStrategy(mbgraph)); + methodReexecuteStrategies.add(new UnreflectionStrategy(mbgraph)); allStrategies = new LinkedList(); allStrategies.addAll(performOnceStrategies); diff --git a/simplify/src/main/java/org/cf/simplify/strategy/ReflectionRemovalStrategy.java b/simplify/src/main/java/org/cf/simplify/strategy/UnreflectionStrategy.java similarity index 99% rename from simplify/src/main/java/org/cf/simplify/strategy/ReflectionRemovalStrategy.java rename to simplify/src/main/java/org/cf/simplify/strategy/UnreflectionStrategy.java index b79198e75..2a388c009 100644 --- a/simplify/src/main/java/org/cf/simplify/strategy/ReflectionRemovalStrategy.java +++ b/simplify/src/main/java/org/cf/simplify/strategy/UnreflectionStrategy.java @@ -43,10 +43,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class ReflectionRemovalStrategy implements OptimizationStrategy { +public class UnreflectionStrategy implements OptimizationStrategy { @SuppressWarnings("unused") - private static final Logger log = LoggerFactory.getLogger(ReflectionRemovalStrategy.class.getSimpleName()); + private static final Logger log = LoggerFactory.getLogger(UnreflectionStrategy.class.getSimpleName()); private static final String MethodInvokeSignature = "Ljava/lang/reflect/Method;->invoke(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"; @@ -120,7 +120,7 @@ static Opcode getGetOpcode(String type, boolean isStatic) { private boolean madeChanges; - public ReflectionRemovalStrategy(MethodBackedGraph mbgraph) { + public UnreflectionStrategy(MethodBackedGraph mbgraph) { this.mbgraph = mbgraph; unreflectCount = 0; } diff --git a/simplify/src/test/java/org/cf/simplify/strategy/TestReflectionRemovalStrategy.java b/simplify/src/test/java/org/cf/simplify/strategy/TestReflectionRemovalStrategy.java index ab2a7ff7c..b08310fa1 100644 --- a/simplify/src/test/java/org/cf/simplify/strategy/TestReflectionRemovalStrategy.java +++ b/simplify/src/test/java/org/cf/simplify/strategy/TestReflectionRemovalStrategy.java @@ -343,27 +343,27 @@ public static class TestProtectedMethods { @Test public void testInstanceGetOpcodes() { boolean isStatic = false; - assertEquals(Opcode.IGET, ReflectionRemovalStrategy.getGetOpcode("I", isStatic)); - assertEquals(Opcode.IGET_BOOLEAN, ReflectionRemovalStrategy.getGetOpcode("Z", isStatic)); - assertEquals(Opcode.IGET_BYTE, ReflectionRemovalStrategy.getGetOpcode("B", isStatic)); - assertEquals(Opcode.IGET_CHAR, ReflectionRemovalStrategy.getGetOpcode("C", isStatic)); - assertEquals(Opcode.IGET_OBJECT, ReflectionRemovalStrategy.getGetOpcode("Ljava/lang/Object;", isStatic)); - assertEquals(Opcode.IGET_SHORT, ReflectionRemovalStrategy.getGetOpcode("S", isStatic)); - assertEquals(Opcode.IGET_WIDE, ReflectionRemovalStrategy.getGetOpcode("J", isStatic)); - assertEquals(Opcode.IGET_WIDE, ReflectionRemovalStrategy.getGetOpcode("D", isStatic)); + assertEquals(Opcode.IGET, UnreflectionStrategy.getGetOpcode("I", isStatic)); + assertEquals(Opcode.IGET_BOOLEAN, UnreflectionStrategy.getGetOpcode("Z", isStatic)); + assertEquals(Opcode.IGET_BYTE, UnreflectionStrategy.getGetOpcode("B", isStatic)); + assertEquals(Opcode.IGET_CHAR, UnreflectionStrategy.getGetOpcode("C", isStatic)); + assertEquals(Opcode.IGET_OBJECT, UnreflectionStrategy.getGetOpcode("Ljava/lang/Object;", isStatic)); + assertEquals(Opcode.IGET_SHORT, UnreflectionStrategy.getGetOpcode("S", isStatic)); + assertEquals(Opcode.IGET_WIDE, UnreflectionStrategy.getGetOpcode("J", isStatic)); + assertEquals(Opcode.IGET_WIDE, UnreflectionStrategy.getGetOpcode("D", isStatic)); } @Test public void testStaticGetOpcodes() { boolean isStatic = true; - assertEquals(Opcode.SGET, ReflectionRemovalStrategy.getGetOpcode("I", isStatic)); - assertEquals(Opcode.SGET_BOOLEAN, ReflectionRemovalStrategy.getGetOpcode("Z", isStatic)); - assertEquals(Opcode.SGET_BYTE, ReflectionRemovalStrategy.getGetOpcode("B", isStatic)); - assertEquals(Opcode.SGET_CHAR, ReflectionRemovalStrategy.getGetOpcode("C", isStatic)); - assertEquals(Opcode.SGET_OBJECT, ReflectionRemovalStrategy.getGetOpcode("Ljava/lang/Object;", isStatic)); - assertEquals(Opcode.SGET_SHORT, ReflectionRemovalStrategy.getGetOpcode("S", isStatic)); - assertEquals(Opcode.SGET_WIDE, ReflectionRemovalStrategy.getGetOpcode("J", isStatic)); - assertEquals(Opcode.SGET_WIDE, ReflectionRemovalStrategy.getGetOpcode("D", isStatic)); + assertEquals(Opcode.SGET, UnreflectionStrategy.getGetOpcode("I", isStatic)); + assertEquals(Opcode.SGET_BOOLEAN, UnreflectionStrategy.getGetOpcode("Z", isStatic)); + assertEquals(Opcode.SGET_BYTE, UnreflectionStrategy.getGetOpcode("B", isStatic)); + assertEquals(Opcode.SGET_CHAR, UnreflectionStrategy.getGetOpcode("C", isStatic)); + assertEquals(Opcode.SGET_OBJECT, UnreflectionStrategy.getGetOpcode("Ljava/lang/Object;", isStatic)); + assertEquals(Opcode.SGET_SHORT, UnreflectionStrategy.getGetOpcode("S", isStatic)); + assertEquals(Opcode.SGET_WIDE, UnreflectionStrategy.getGetOpcode("J", isStatic)); + assertEquals(Opcode.SGET_WIDE, UnreflectionStrategy.getGetOpcode("D", isStatic)); } } @@ -391,7 +391,7 @@ private static Method getMethod(Class klazz, String methodName, Class[] pa private static MethodBackedGraph getOptimizedGraph(String methodName, Object... args) { TIntObjectMap initial = VMTester.buildRegisterState(args); MethodBackedGraph mbgraph = OptimizerTester.getMethodBackedGraph(CLASS_NAME, methodName, initial); - ReflectionRemovalStrategy strategy = new ReflectionRemovalStrategy(mbgraph); + UnreflectionStrategy strategy = new UnreflectionStrategy(mbgraph); strategy.perform(); return mbgraph;