blob: e736ea71ddad91972f157d55d0d9a9db74884c5b [file] [log] [blame]
// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.smali;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.code.Const4;
import com.android.tools.r8.code.ConstString;
import com.android.tools.r8.code.ConstWide;
import com.android.tools.r8.code.ConstWideHigh16;
import com.android.tools.r8.code.DivInt;
import com.android.tools.r8.code.DivInt2Addr;
import com.android.tools.r8.code.Goto;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.code.InvokeStatic;
import com.android.tools.r8.code.InvokeStaticRange;
import com.android.tools.r8.code.InvokeVirtual;
import com.android.tools.r8.code.MoveResult;
import com.android.tools.r8.code.MoveResultWide;
import com.android.tools.r8.code.Return;
import com.android.tools.r8.code.ReturnObject;
import com.android.tools.r8.code.ReturnVoid;
import com.android.tools.r8.code.ReturnWide;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.smali.SmaliBuilder.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.InternalOptions.OutlineOptions;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.function.Consumer;
import org.junit.Assert;
import org.junit.Test;
public class OutlineTest extends SmaliTestBase {
private static final String stringBuilderAppendSignature =
"Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;";
private static final String stringBuilderAppendDoubleSignature =
"Ljava/lang/StringBuilder;->append(D)Ljava/lang/StringBuilder;";
private static final String stringBuilderAppendIntSignature =
"Ljava/lang/StringBuilder;->append(I)Ljava/lang/StringBuilder;";
private static final String stringBuilderAppendLongSignature =
"Ljava/lang/StringBuilder;->append(J)Ljava/lang/StringBuilder;";
private static final String stringBuilderToStringSignature =
"Ljava/lang/StringBuilder;->toString()Ljava/lang/String;";
private Consumer<InternalOptions> configureOptions(Consumer<InternalOptions> optionsConsumer) {
return options -> {
// Disable inlining to make sure that code looks as expected.
options.enableInlining = false;
// Disable string concatenation optimization to not bother outlining of StringBuilder usage.
options.enableStringConcatenationOptimization = false;
// Also apply outline options.
optionsConsumer.accept(options);
};
}
private Consumer<InternalOptions> configureOutlineOptions(
Consumer<OutlineOptions> optionsConsumer) {
return options -> {
// Disable inlining to make sure that code looks as expected.
options.enableInlining = false;
// Disable string concatenation optimization to not bother outlining of StringBuilder usage.
options.enableStringConcatenationOptimization = false;
// Also apply outline options.
optionsConsumer.accept(options.outline);
};
}
private String firstOutlineMethodName() {
return OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX + "0";
}
private boolean isOutlineMethodName(String qualifiedName) {
String qualifiedPrefix = OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX;
return qualifiedName.indexOf(qualifiedPrefix) == 0;
}
@Test
public void a() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "java.lang.String";
List<String> parameters = Collections.singletonList("java.lang.StringBuilder");
MethodSignature signature =
builder.addStaticMethod(
returnType,
DEFAULT_METHOD_NAME,
parameters,
2,
" move-object v0, p0",
" const-string v1, \"Test\"",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0 }, " + stringBuilderToStringSignature,
" move-result-object v0",
" return-object v0");
builder.addMainMethod(
2,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" new-instance v1, Ljava/lang/StringBuilder;",
" invoke-direct { v1 }, Ljava/lang/StringBuilder;-><init>()V",
" invoke-static { v1 },"
+ " LTest;->method(Ljava/lang/StringBuilder;)Ljava/lang/String;",
" move-result-object v1",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
" return-void");
for (int i = 2; i < 6; i++) {
final int j = i;
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = j;
outline.maxSize = j;
});
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
DexCode code = method.getCode().asDexCode();
assertTrue(code.instructions[0] instanceof ConstString);
assertTrue(code.instructions[1] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[1];
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("TestTestTestTest", result);
}
}
@Test
public void b() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "java.lang.String";
List<String> parameters = Collections.singletonList("java.lang.StringBuilder");
MethodSignature signature =
builder.addStaticMethod(
returnType,
DEFAULT_METHOD_NAME,
parameters,
2,
" move-object v0, p0",
" const-string v1, \"Test1\"",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" const-string v1, \"Test2\"",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" const-string v1, \"Test3\"",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" const-string v1, \"Test4\"",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0 }, " + stringBuilderToStringSignature,
" move-result-object v0",
" return-object v0");
builder.addMainMethod(
2,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" new-instance v1, Ljava/lang/StringBuilder;",
" invoke-direct { v1 }, Ljava/lang/StringBuilder;-><init>()V",
" invoke-static { v1 },"
+ " LTest;->method(Ljava/lang/StringBuilder;)Ljava/lang/String;",
" move-result-object v1",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
" return-void");
for (int i = 2; i < 6; i++) {
final int finalI = i;
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = finalI;
outline.maxSize = finalI;
});
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
DexCode code = method.getCode().asDexCode();
// Up to 4 const instructions before the invoke of the outline.
int firstOutlineInvoke = Math.min(i, 4);
for (int j = 0; j < firstOutlineInvoke; j++) {
assertTrue(code.instructions[j] instanceof ConstString);
}
assertTrue(code.instructions[firstOutlineInvoke] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[firstOutlineInvoke];
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("Test1Test2Test3Test4", result);
}
}
@Test
public void c() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
// Method with const instructions after the outline.
String returnType = "int";
List<String> parameters = Collections.singletonList("java.lang.StringBuilder");
MethodSignature signature =
builder.addStaticMethod(
returnType,
DEFAULT_METHOD_NAME,
parameters,
2,
" move-object v0, p0",
" const-string v1, \"Test\"",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0 }, " + stringBuilderToStringSignature,
" move-result-object v0",
" const v0, 0",
" const v1, 1",
" add-int v1, v1, v0",
" return v1");
builder.addMainMethod(
2,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" new-instance v1, Ljava/lang/StringBuilder;",
" invoke-direct { v1 }, Ljava/lang/StringBuilder;-><init>()V",
" invoke-static { v1 }, LTest;->method(Ljava/lang/StringBuilder;)I",
" move-result v1",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V",
" return-void"
);
Consumer<InternalOptions> options = configureOutlineOptions(outline -> outline.threshold = 1);
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
DexCode code = method.getCode().asDexCode();
assertTrue(code.instructions[0] instanceof ConstString);
assertTrue(code.instructions[1] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[1];
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("1", result);
}
@Test
public void d() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
// Method with mixed use of arguments and locals.
String returnType = "java.lang.String";
List<String> parameters = ImmutableList.of(
"java.lang.StringBuilder", "java.lang.String", "java.lang.String");
MethodSignature signature =
builder.addStaticMethod(
returnType,
DEFAULT_METHOD_NAME,
parameters,
2,
" invoke-virtual { p0, p1 }, " + stringBuilderAppendSignature,
" move-result-object p0",
" const-string v0, \"Test1\"",
" invoke-virtual { p0, v0 }, " + stringBuilderAppendSignature,
" move-result-object p0",
" invoke-virtual { p0, p2 }, " + stringBuilderAppendSignature,
" move-result-object p0",
" const-string v1, \"Test2\"",
" invoke-virtual { p0, v1 }, " + stringBuilderAppendSignature,
" move-result-object p0",
" const-string v1, \"Test3\"",
" invoke-virtual { p0, v1 }, " + stringBuilderAppendSignature,
" move-result-object p0",
" invoke-virtual { p0 }, " + stringBuilderToStringSignature,
" move-result-object v1",
" return-object v1");
builder.addMainMethod(
4,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" new-instance v1, Ljava/lang/StringBuilder;",
" invoke-direct { v1 }, Ljava/lang/StringBuilder;-><init>()V",
" const-string v2, \"TestX\"",
" const-string v3, \"TestY\"",
" invoke-static { v1, v2, v3 },"
+ " LTest;->method(Ljava/lang/StringBuilder;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",
" move-result-object v1",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
" return-void");
Consumer<InternalOptions> options = configureOutlineOptions(outline -> outline.threshold = 1);
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
DexCode code = method.getCode().asDexCode();
assertTrue(code.instructions[0] instanceof ConstString);
assertTrue(code.instructions[1] instanceof ConstString);
assertTrue(code.instructions[2] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[2];
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("TestXTest1TestYTest2Test3", result);
}
@Test
public void longArguments() throws Throwable {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "java.lang.String";
List<String> parameters = Collections.singletonList("java.lang.StringBuilder");
MethodSignature signature =
builder.addStaticMethod(
returnType,
DEFAULT_METHOD_NAME,
parameters,
3,
" move-object v0, p0",
" const-wide v1, 0x7fffffff00000000L",
" invoke-virtual { v0, v1, v2 }, " + stringBuilderAppendLongSignature,
" move-result-object v0",
" invoke-virtual { v0, v1, v2 }, " + stringBuilderAppendLongSignature,
" move-result-object v0",
" invoke-virtual { v0, v1, v2 }, " + stringBuilderAppendLongSignature,
" move-result-object v0",
" invoke-virtual { v0, v1, v2 }, " + stringBuilderAppendLongSignature,
" move-result-object v0",
" invoke-virtual { v0 }, " + stringBuilderToStringSignature,
" move-result-object v0",
" return-object v0");
builder.addMainMethod(
2,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" new-instance v1, Ljava/lang/StringBuilder;",
" invoke-direct { v1 }, Ljava/lang/StringBuilder;-><init>()V",
" invoke-static { v1 },"
+ " LTest;->method(Ljava/lang/StringBuilder;)Ljava/lang/String;",
" move-result-object v1",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
" return-void");
for (int i = 2; i < 4; i++) {
final int finalI = i;
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = finalI;
outline.maxSize = finalI;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
DexCode code = method.getCode().asDexCode();
assertTrue(code.instructions[0] instanceof ConstWide);
if (i < 3) {
assertTrue(code.instructions[1] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[1];
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
} else {
assertTrue(code.instructions[1] instanceof InvokeVirtual);
assertTrue(code.instructions[2] instanceof InvokeVirtual);
assertTrue(code.instructions[3] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[3];
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
}
// Run code and check result.
String result = runArt(processedApplication);
StringBuilder resultBuilder = new StringBuilder();
for (int j = 0; j < 4; j++) {
resultBuilder.append(0x7fffffff00000000L);
}
assertEquals(resultBuilder.toString(), result);
}
}
@Test
public void doubleArguments() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "java.lang.String";
List<String> parameters = Collections.singletonList("java.lang.StringBuilder");
MethodSignature signature =
builder.addStaticMethod(
returnType,
DEFAULT_METHOD_NAME,
parameters,
3,
" move-object v0, p0",
" const-wide v1, 0x3ff0000000000000L",
" invoke-virtual { v0, v1, v2 }, " + stringBuilderAppendDoubleSignature,
" move-result-object v0",
" invoke-virtual { v0, v1, v2 }, " + stringBuilderAppendDoubleSignature,
" move-result-object v0",
" invoke-virtual { v0, v1, v2 }, " + stringBuilderAppendDoubleSignature,
" move-result-object v0",
" invoke-virtual { v0, v1, v2 }, " + stringBuilderAppendDoubleSignature,
" move-result-object v0",
" invoke-virtual { v0 }, " + stringBuilderToStringSignature,
" move-result-object v0",
" return-object v0");
builder.addMainMethod(
2,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" new-instance v1, Ljava/lang/StringBuilder;",
" invoke-direct { v1 }, Ljava/lang/StringBuilder;-><init>()V",
" invoke-static { v1 },"
+ " LTest;->method(Ljava/lang/StringBuilder;)Ljava/lang/String;",
" move-result-object v1",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
" return-void");
for (int i = 2; i < 4; i++) {
final int finalI = i;
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = finalI;
outline.maxSize = finalI;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
DexCode code = method.getCode().asDexCode();
assertTrue(code.instructions[0] instanceof ConstWideHigh16);
if (i < 3) {
assertTrue(code.instructions[1] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[1];
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
} else {
assertTrue(code.instructions[1] instanceof InvokeVirtual);
assertTrue(code.instructions[2] instanceof InvokeVirtual);
assertTrue(code.instructions[3] instanceof InvokeStatic);
InvokeStatic invoke = (InvokeStatic) code.instructions[3];
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
}
// Run code and check result.
String result = runArt(processedApplication);
StringBuilder resultBuilder = new StringBuilder();
for (int j = 0; j < 4; j++) {
resultBuilder.append(1.0d);
}
assertEquals(resultBuilder.toString(), result);
}
}
@Test
public void invokeStatic() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "void";
List<String> parameters = ImmutableList.of("java.lang.StringBuilder", "int");
builder.addStaticMethod(
returnType,
DEFAULT_METHOD_NAME,
parameters,
1,
" invoke-virtual { p0, p1 }, " + stringBuilderAppendIntSignature,
" move-result-object v0",
" invoke-virtual { p0, p1 }, " + stringBuilderAppendIntSignature,
" move-result-object v0",
" return-void");
MethodSignature mainSignature =
builder.addMainMethod(
2,
" new-instance v0, Ljava/lang/StringBuilder;",
" invoke-direct { v0 }, Ljava/lang/StringBuilder;-><init>()V",
" const/4 v1, 0x1",
" invoke-static { v0, v1 }, LTest;->method(Ljava/lang/StringBuilder;I)V",
" const/4 v1, 0x2",
" invoke-static { v0, v1 }, LTest;->method(Ljava/lang/StringBuilder;I)V",
" invoke-virtual { v0 }, " + stringBuilderToStringSignature,
" move-result-object v1",
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
" return-void");
for (int i = 2; i < 6; i++) {
final int finalI = i;
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = finalI;
outline.maxSize = finalI;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed main method for inspection.
DexEncodedMethod mainMethod = getMethod(processedApplication, mainSignature);
DexCode mainCode = mainMethod.getCode().asDexCode();
if (i == 2 || i == 3) {
assert mainCode.instructions.length == 10;
} else if (i == 4) {
assert mainCode.instructions.length == 9;
} else {
assert i == 5;
assert mainCode.instructions.length == 7;
}
if (i == 2) {
InvokeStatic invoke = (InvokeStatic) mainCode.instructions[4];
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
} else if (i == 3) {
InvokeStatic invoke = (InvokeStatic) mainCode.instructions[1];
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
} else {
assert i == 4 || i == 5;
InvokeStatic invoke = (InvokeStatic) mainCode.instructions[2];
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
}
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("1122", result);
}
}
@Test
public void constructor() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature signature1 =
builder.addStaticMethod(
"java.lang.String",
"method1",
Collections.emptyList(),
3,
" new-instance v0, Ljava/lang/StringBuilder;",
" invoke-direct { v0 }, Ljava/lang/StringBuilder;-><init>()V",
" const-string v1, \"Test1\"",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0 }, " + stringBuilderToStringSignature,
" move-result-object v0",
" return-object v0");
MethodSignature signature2 =
builder.addStaticMethod(
"java.lang.String",
"method2",
Collections.emptyList(),
3,
" const/4 v1, 7",
" new-instance v0, Ljava/lang/StringBuilder;",
" invoke-direct { v0, v1 }, Ljava/lang/StringBuilder;-><init>(I)V",
" const-string v1, \"Test2\"",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0 }, " + stringBuilderToStringSignature,
" move-result-object v0",
" return-object v0");
builder.addMainMethod(
2,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" invoke-static {}, LTest;->method1()Ljava/lang/String;",
" move-result-object v1",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
" invoke-static {}, LTest;->method2()Ljava/lang/String;",
" move-result-object v1",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
" return-void");
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = 7;
outline.maxSize = 7;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
DexCode code1 = getMethod(processedApplication, signature1).getCode().asDexCode();
assertEquals(4, code1.instructions.length);
assertTrue(code1.instructions[1] instanceof InvokeStatic);
InvokeStatic invoke1 = (InvokeStatic) code1.instructions[1];
assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
DexCode code2 = getMethod(processedApplication, signature2).getCode().asDexCode();
assertEquals(5, code2.instructions.length);
assertTrue(code2.instructions[2] instanceof InvokeStatic);
InvokeStatic invoke2 = (InvokeStatic) code2.instructions[2];
assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("Test1Test1Test1Test1Test2Test2Test2Test2", result);
}
@Test
public void constructorDontSplitNewInstanceAndInit() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature signature =
builder.addStaticMethod(
"java.lang.String",
DEFAULT_METHOD_NAME,
ImmutableList.of("java.lang.StringBuilder"),
2,
" const-string v0, \"Test1\"",
" invoke-virtual { p0, v0 }, " + stringBuilderAppendSignature,
" move-result-object p0",
" invoke-virtual { p0, v0 }, " + stringBuilderAppendSignature,
" move-result-object p0",
" new-instance v1, Ljava/lang/StringBuilder;",
" invoke-direct { v1 }, Ljava/lang/StringBuilder;-><init>()V",
" const-string v0, \"Test2\"",
" invoke-virtual { v1, v0 }, " + stringBuilderAppendSignature,
" move-result-object v1",
" invoke-virtual { v1, v0 }, " + stringBuilderAppendSignature,
" move-result-object v1",
" invoke-virtual { v1 }, " + stringBuilderToStringSignature,
" move-result-object v0",
" return-object v0");
builder.addMainMethod(
2,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" new-instance v1, Ljava/lang/StringBuilder;",
" invoke-direct { v1 }, Ljava/lang/StringBuilder;-><init>()V",
" invoke-static { v1 },"
+ " LTest;->method(Ljava/lang/StringBuilder;)Ljava/lang/String;",
" move-result-object v1",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
" return-void");
for (int i = 2; i < 8; i++) {
final int finalI = i;
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = finalI;
outline.maxSize = finalI;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
DexCode code = getMethod(processedApplication, signature).getCode().asDexCode();
int outlineInstructionIndex;
switch (i) {
case 2:
case 4:
outlineInstructionIndex = 1;
break;
case 3:
outlineInstructionIndex = 4;
break;
default:
outlineInstructionIndex = 2;
}
Instruction instruction = code.instructions[outlineInstructionIndex];
if (instruction instanceof InvokeStatic) {
InvokeStatic invoke = (InvokeStatic) instruction;
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
} else {
InvokeStaticRange invoke = (InvokeStaticRange) instruction;
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
}
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("Test2Test2", result);
}
}
@Test
public void outlineWithoutArguments() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature signature1 =
builder.addStaticMethod(
"java.lang.String",
DEFAULT_METHOD_NAME,
Collections.emptyList(),
1,
" new-instance v0, Ljava/lang/StringBuilder;",
" invoke-direct { v0 }, Ljava/lang/StringBuilder;-><init>()V",
" invoke-virtual { v0 }, " + stringBuilderToStringSignature,
" move-result-object v0",
" return-object v0");
builder.addMainMethod(
2,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" invoke-static {}, LTest;->method()Ljava/lang/String;",
" move-result-object v1",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
" return-void");
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = 3;
outline.maxSize = 3;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
DexCode code = getMethod(processedApplication, signature1).getCode().asDexCode();
InvokeStatic invoke;
assertTrue(code.instructions[0] instanceof InvokeStatic);
invoke = (InvokeStatic) code.instructions[0];
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("", result);
}
@Test
public void outlineDifferentReturnType() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
List<String> parameters = Collections.singletonList("java.lang.StringBuilder");
// The naming of the methods in this test is important. The method name that don't use the
// output from StringBuilder.toString must sort before the method name that does.
String returnType1 = "void";
builder.addStaticMethod(
returnType1,
"method1",
parameters,
2,
" move-object v0, p0",
" const-string v1, \"Test\"",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0 }, " + stringBuilderToStringSignature,
" return-void");
String returnType2 = "java.lang.String";
builder.addStaticMethod(
returnType2,
"method2",
parameters,
2,
" move-object v0, p0",
" const-string v1, \"Test\"",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0 }, " + stringBuilderToStringSignature,
" move-result-object v0",
" return-object v0");
builder.addMainMethod(
3,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" new-instance v1, Ljava/lang/StringBuilder;",
" invoke-direct { v1 }, Ljava/lang/StringBuilder;-><init>()V",
" invoke-static { v1 }, LTest;->method1(Ljava/lang/StringBuilder;)V",
" invoke-static { v1 },"
+ " LTest;->method2(Ljava/lang/StringBuilder;)Ljava/lang/String;",
" move-result-object v2",
" invoke-virtual { v0, v2 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
" return-void");
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = 3;
outline.maxSize = 3;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Check that three outlining methods was created.
CodeInspector inspector = new CodeInspector(processedApplication);
ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME);
assertTrue(clazz.isPresent());
assertEquals(3, clazz.getDexClass().directMethods().size());
// Collect the return types of the putlines for the body of method1 and method2.
List<DexType> r = new ArrayList<>();
for (int i = 0; i < clazz.getDexClass().directMethods().size(); i++) {
if (clazz.getDexClass().directMethods().get(i).getCode().asDexCode().instructions[0]
instanceof InvokeVirtual) {
r.add(clazz.getDexClass().directMethods().get(i).method.proto.returnType);
}
}
assert r.size() == 2;
DexType r1 = r.get(0);
DexType r2 = r.get(1);
DexItemFactory factory = inspector.getFactory();
assertTrue(r1 == factory.voidType && r2 == factory.stringType ||
r1 == factory.stringType && r2 == factory.voidType);
// Run the code.
String result = runArt(processedApplication);
assertEquals("TestTestTestTest", result);
}
@Test
public void outlineMultipleTimes() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
String returnType = "java.lang.String";
List<String> parameters = Collections.singletonList("java.lang.StringBuilder");
builder.addStaticMethod(
returnType,
DEFAULT_METHOD_NAME,
parameters,
2,
" move-object v0, p0",
" const-string v1, \"Test\"",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0, v1 }, " + stringBuilderAppendSignature,
" move-result-object v0",
" invoke-virtual { v0 }, " + stringBuilderToStringSignature,
" move-result-object v0",
" return-object v0");
builder.addMainMethod(
2,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" new-instance v1, Ljava/lang/StringBuilder;",
" invoke-direct { v1 }, Ljava/lang/StringBuilder;-><init>()V",
" invoke-static { v1 },"
+ " LTest;->method(Ljava/lang/StringBuilder;)Ljava/lang/String;",
" move-result-object v1",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
" return-void");
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = 3;
outline.maxSize = 3;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
final int count = 10;
// Process the application several times. Each time will outline the previous outline.
for (int i = 0; i < count; i++) {
// Build a new application with the Outliner class.
originalApplication = processedApplication;
processedApplication = processApplication(originalApplication, options);
assertEquals(i + 3, getNumberOfProgramClasses(processedApplication));
}
// Process the application several times. No more outlining as threshold has been raised.
options =
configureOutlineOptions(
outline -> {
outline.threshold = 2;
outline.minSize = 3;
outline.maxSize = 3;
});
for (int i = 0; i < count; i++) {
// Build a new application with the Outliner class.
originalApplication = processedApplication;
processedApplication = processApplication(originalApplication, options);
assertEquals(count - 1 + 3, getNumberOfProgramClasses(processedApplication));
}
// Run the application with several levels of outlining.
String result = runArt(processedApplication);
assertEquals("TestTestTestTest", result);
}
@Test
public void outlineReturnLong() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
MethodSignature signature = builder.addStaticMethod(
"long",
DEFAULT_METHOD_NAME,
ImmutableList.of("int"),
2,
" new-instance v0, Ljava/util/GregorianCalendar;",
" invoke-direct { v0 }, Ljava/util/GregorianCalendar;-><init>()V",
" invoke-virtual { v0, p0, p0 }, Ljava/util/Calendar;->set(II)V",
" invoke-virtual { v0, p0, p0 }, Ljava/util/Calendar;->set(II)V",
" invoke-virtual { v0 }, Ljava/util/Calendar;->getTimeInMillis()J",
" move-result-wide v0",
" return-wide v0"
);
builder.addMainMethod(
3,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" const/4 v1, 0",
" invoke-static { v1 }, LTest;->method(I)J",
" move-result-wide v1",
" invoke-virtual { v0, v1, v2 }, Ljava/io/PrintStream;->print(J)V",
" return-void"
);
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = 5;
outline.maxSize = 5;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
// The calls to set, set and getTimeInMillis was outlined.
DexCode code = method.getCode().asDexCode();
assertEquals(3, code.instructions.length);
assertTrue(code.instructions[0] instanceof InvokeStatic);
assertTrue(code.instructions[1] instanceof MoveResultWide);
assertTrue(code.instructions[2] instanceof ReturnWide);
InvokeStatic invoke = (InvokeStatic) code.instructions[0];
assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
// Run the code and expect a parsable long.
String result = runArt(processedApplication);
Long.parseLong(result);
}
@Test
public void noOutlineSuperCalls() throws Exception {
SmaliBuilder builder = new SmaliBuilder("Super");
builder.addDefaultConstructor();
builder.addInstanceMethod("void", "set", ImmutableList.of("int", "int"), 0,
"return-void");
builder.addInstanceMethod("java.lang.String", "toString", Collections.emptyList(), 1,
"const-string v0, \"Hello\"",
"return-object v0");
builder.addClass(DEFAULT_CLASS_NAME, "Super");
builder.addDefaultConstructor();
builder.addStaticMethod(
"java.lang.String",
DEFAULT_METHOD_NAME,
ImmutableList.of("int"),
2,
" new-instance v0, LTest;",
" invoke-direct { v0 }, LTest;-><init>()V",
" invoke-super { v0, p0, p0 }, LTest;->set(II)V",
" invoke-super { v0, p0, p0 }, LTest;->set(II)V",
" invoke-virtual { v0 }, LTest;->toString()Ljava/lang/String;",
" move-result-object v0",
" return-object v0"
);
builder.addMainMethod(
3,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" const/4 v1, 0",
" invoke-static { v1 }, LTest;->method(I)Ljava/lang/String;",
" move-result-object v1",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/String;)V",
" return-void"
);
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = 5;
outline.maxSize = 5;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
String originalResult = runArt(originalApplication);
String processedResult = runArt(processedApplication);
Assert.assertEquals(originalResult, processedResult);
}
@Test
public void outlineArrayType() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticMethod(
"void",
"addToList",
ImmutableList.of("java.util.List", "int[]"),
0,
" invoke-interface { p0, p1 }, Ljava/util/List;->add(Ljava/lang/Object;)Z",
" return-void "
);
MethodSignature signature = builder.addStaticMethod(
"int[]",
DEFAULT_METHOD_NAME,
ImmutableList.of("int[]", "int[]"),
1,
" new-instance v0, Ljava/util/ArrayList;",
" invoke-direct { v0 }, Ljava/util/ArrayList;-><init>()V",
" invoke-static { v0, p0 }, LTest;->addToList(Ljava/util/List;[I)V",
" invoke-static { v0, p1 }, LTest;->addToList(Ljava/util/List;[I)V",
" return-object p0"
);
builder.addMainMethod(
3,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" const/4 v1, 0",
" invoke-static { v1, v1 }, LTest;->method([I[I)[I",
" move-result-object v1",
" invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(Ljava/lang/Object;)V",
" return-void"
);
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = 4;
outline.maxSize = 4;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
DexCode code = method.getCode().asDexCode();
assertEquals(2, code.instructions.length);
assertTrue(code.instructions[0] instanceof InvokeStatic);
assertTrue(code.instructions[1] instanceof ReturnObject);
InvokeStatic invoke = (InvokeStatic) code.instructions[0];
assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("null", result);
}
@Test
public void outlineArithmeticBinop() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticMethod(
"void",
"addToList",
ImmutableList.of("java.util.List", "int[]"),
0,
" invoke-interface { p0, p1 }, Ljava/util/List;->add(Ljava/lang/Object;)Z",
" return-void "
);
MethodSignature signature1 = builder.addStaticMethod(
"int",
"method1",
ImmutableList.of("int", "int"),
1,
" add-int v0, v1, v2",
" sub-int v0, v0, v1",
" mul-int v0, v2, v0",
" div-int v0, v0, v1",
" return v0"
);
MethodSignature signature2 = builder.addStaticMethod(
"int",
"method2",
ImmutableList.of("int", "int"),
1,
" add-int v0, v2, v1",
" sub-int v0, v0, v1",
" mul-int v0, v0, v2",
" div-int v0, v0, v1",
" return v0"
);
builder.addMainMethod(
4,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" const/4 v1, 1",
" const/4 v2, 2",
" invoke-static { v1, v2 }, LTest;->method1(II)I",
" move-result v4",
" invoke-virtual { v0, v4 }, Ljava/io/PrintStream;->print(I)V",
" invoke-static { v1, v2 }, LTest;->method2(II)I",
" move-result v4",
" invoke-virtual { v0, v4 }, Ljava/io/PrintStream;->print(I)V",
" return-void"
);
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = 4;
outline.maxSize = 4;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method1 = getMethod(processedApplication, signature1);
DexCode code1 = method1.getCode().asDexCode();
assertEquals(3, code1.instructions.length);
assertTrue(code1.instructions[0] instanceof InvokeStatic);
assertTrue(code1.instructions[1] instanceof MoveResult);
assertTrue(code1.instructions[2] instanceof Return);
InvokeStatic invoke1 = (InvokeStatic) code1.instructions[0];
assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName()));
DexEncodedMethod method2 = getMethod(processedApplication, signature2);
DexCode code2 = method2.getCode().asDexCode();
assertTrue(code2.instructions[0] instanceof InvokeStatic);
InvokeStatic invoke2 = (InvokeStatic) code2.instructions[0];
assertEquals(invoke1.getMethod().qualifiedName(), invoke2.getMethod().qualifiedName());
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("44", result);
}
@Test
public void outlineWithHandler() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticMethod(
"void",
"addToList",
ImmutableList.of("java.util.List", "int[]"),
0,
" invoke-interface { p0, p1 }, Ljava/util/List;->add(Ljava/lang/Object;)Z",
" return-void "
);
MethodSignature signature = builder.addStaticMethod(
"int",
"method",
ImmutableList.of("int", "int"),
1,
" :try_start",
// Throwing instruction to ensure the handler range does not get collapsed.
" div-int v0, v1, v2",
" add-int v0, v1, v2",
" sub-int v0, v0, v1",
" mul-int v0, v2, v0",
" div-int v0, v0, v1",
" :try_end",
" :return",
" return v0",
" .catch Ljava/lang/ArithmeticException; {:try_start .. :try_end} :catch",
" :catch",
" const/4 v0, -1",
" goto :return"
);
builder.addMainMethod(
4,
" sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" const/4 v1, 1",
" const/4 v2, 2",
" invoke-static { v1, v2 }, LTest;->method(II)I",
" move-result v4",
" invoke-virtual { v0, v4 }, Ljava/io/PrintStream;->print(I)V",
" return-void"
);
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 1;
outline.minSize = 3; // Outline add, sub and mul.
outline.maxSize = 3;
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
DexCode code = method.getCode().asDexCode();
assertEquals(7, code.instructions.length);
assertTrue(code.instructions[0] instanceof DivInt);
assertTrue(code.instructions[1] instanceof InvokeStatic);
assertTrue(code.instructions[2] instanceof MoveResult);
assertTrue(code.instructions[3] instanceof DivInt2Addr);
assertTrue(code.instructions[4] instanceof Goto);
assertTrue(code.instructions[5] instanceof Const4);
assertTrue(code.instructions[6] instanceof Return);
InvokeStatic invoke = (InvokeStatic) code.instructions[1];
assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName()));
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("4", result);
}
@Test
public void outlineUnusedOutValue() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
// The result from neither the div-int is never used.
MethodSignature signature = builder.addStaticMethod(
"void",
DEFAULT_METHOD_NAME,
ImmutableList.of("int", "int"),
1,
" div-int v0, p0, p1",
" new-instance v0, Ljava/lang/StringBuilder;",
" invoke-direct { v0 }, Ljava/lang/StringBuilder;-><init>()V",
" return-void"
);
builder.addMainMethod(
2,
" const v0, 1",
" const v1, 2",
" invoke-static { v0, v1 }, LTest;->method(II)V",
" return-void"
);
Consumer<InternalOptions> options =
configureOptions(
opts -> {
opts.outline.threshold = 1;
opts.outline.minSize = 3;
opts.outline.maxSize = 3;
// Do not allow dead code elimination of the new-instance and invoke-direct
// instructions.
// This can be achieved by not assuming that StringBuilder is present.
DexItemFactory dexItemFactory = opts.itemFactory;
dexItemFactory.libraryTypesAssumedToBePresent =
new HashSet<>(dexItemFactory.libraryTypesAssumedToBePresent);
dexItemFactory.libraryTypesAssumedToBePresent.remove(
dexItemFactory.stringBuilderType);
// ... and not assuming that StringBuilder.<init>() cannot have side effects.
dexItemFactory.libraryMethodsWithoutSideEffects =
new IdentityHashMap<>(dexItemFactory.libraryMethodsWithoutSideEffects);
dexItemFactory.libraryMethodsWithoutSideEffects.remove(
dexItemFactory.stringBuilderMethods.defaultConstructor);
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
DexCode code = method.getCode().asDexCode();
assertEquals(2, code.instructions.length);
assertTrue(code.instructions[0] instanceof InvokeStatic);
assertTrue(code.instructions[1] instanceof ReturnVoid);
InvokeStatic invoke = (InvokeStatic) code.instructions[0];
assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("", result);
}
@Test
public void outlineUnusedNewInstanceOutValue() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
// The result from the new-instance instructions are never used (<init> is not even called).
MethodSignature signature = builder.addStaticMethod(
"void",
DEFAULT_METHOD_NAME,
ImmutableList.of(),
1,
" new-instance v0, Ljava/lang/StringBuilder;",
" new-instance v0, Ljava/lang/StringBuilder;",
" new-instance v0, Ljava/lang/StringBuilder;",
" return-void"
);
builder.addMainMethod(
0,
" invoke-static { }, LTest;->method()V",
" return-void"
);
Consumer<InternalOptions> options =
configureOptions(
opts -> {
opts.outline.threshold = 1;
opts.outline.minSize = 3;
opts.outline.maxSize = 3;
// Do not allow dead code elimination of the new-instance instructions. This can be
// achieved
// by not assuming that StringBuilder is present.
DexItemFactory dexItemFactory = opts.itemFactory;
opts.itemFactory.libraryTypesAssumedToBePresent =
new HashSet<>(dexItemFactory.libraryTypesAssumedToBePresent);
dexItemFactory.libraryTypesAssumedToBePresent.remove(
dexItemFactory.stringBuilderType);
});
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Return the processed method for inspection.
DexEncodedMethod method = getMethod(processedApplication, signature);
DexCode code = method.getCode().asDexCode();
assertEquals(2, code.instructions.length);
assertTrue(code.instructions[0] instanceof InvokeStatic);
assertTrue(code.instructions[1] instanceof ReturnVoid);
InvokeStatic invoke = (InvokeStatic) code.instructions[0];
assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName());
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("", result);
}
@Test
public void regress33733666() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
builder.addStaticMethod(
"void",
DEFAULT_METHOD_NAME,
Collections.emptyList(),
4,
" new-instance v0, Ljava/util/Hashtable;",
" invoke-direct { v0 }, Ljava/util/Hashtable;-><init>()V",
" const-string v1, \"http://schemas.google.com/g/2005#home\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x01 # 1",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \" http://schemas.google.com/g/2005#work\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x02 # 2",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#other\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x03 # 3",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#primary\"",
" const/4 v2, 0x04 # 4",
" invoke-static { v2 }, Ljava/lang/Byte;->valueOf(B)Ljava/lang/Byte;",
" move-result-object v2",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" sput-object v0, LA;->A:Ljava/util/Hashtable;",
" invoke-static { v0 }, LA;->a(Ljava/util/Hashtable;)Ljava/util/Hashtable;",
" move-result-object v0",
" sput-object v0, LA;->B:Ljava/util/Hashtable;",
" new-instance v0, Ljava/util/Hashtable;",
" invoke-direct { v0 }, Ljava/util/Hashtable;-><init>()V",
" const-string v1, \"http://schemas.google.com/g/2005#home\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x02 # 2",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#mobile\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x01 # 1",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#pager\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x06 # 6",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#work\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x03 # 3",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#home_fax\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x05 # 5",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#work_fax\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x04 # 4",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#other\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x07 # 7",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" sput-object v0, LA;->C:Ljava/util/Hashtable;",
" invoke-static { v0 }, LA;->a(Ljava/util/Hashtable;)Ljava/util/Hashtable;",
" move-result-object v0",
" sput-object v0, LA;->D:Ljava/util/Hashtable;",
" new-instance v0, Ljava/util/Hashtable;",
" invoke-direct { v0 }, Ljava/util/Hashtable;-><init>()V",
" const-string v1, \"http://schemas.google.com/g/2005#home\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x01 # 1",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#work\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x02 # 2",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#other\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x03 # 3",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" sput-object v0, LA;->E:Ljava/util/Hashtable;",
" invoke-static { v0 }, LA;->a(Ljava/util/Hashtable;)Ljava/util/Hashtable;",
" move-result-object v0",
" sput-object v0, LA;->F:Ljava/util/Hashtable;",
" new-instance v0, Ljava/util/Hashtable;",
" invoke-direct { v0 }, Ljava/util/Hashtable;-><init>()V",
" const-string v1, \"http://schemas.google.com/g/2005#home\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x01 # 1",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#work\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x02 # 2",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#other\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x03 # 3",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" sput-object v0, LA;->G:Ljava/util/Hashtable;",
" invoke-static { v0 }, LA;->a(Ljava/util/Hashtable;)Ljava/util/Hashtable;",
" move-result-object v0",
" sput-object v0, LA;->H:Ljava/util/Hashtable;",
" new-instance v0, Ljava/util/Hashtable;",
" invoke-direct { v0 }, Ljava/util/Hashtable;-><init>()V",
" const-string v1, \"http://schemas.google.com/g/2005#work\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x01 # 1",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#other\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x02 # 2",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" sput-object v0, LA;->I:Ljava/util/Hashtable;",
" invoke-static { v0 }, LA;->a(Ljava/util/Hashtable;)Ljava/util/Hashtable;",
" move-result-object v0",
" sput-object v0, LA;->J:Ljava/util/Hashtable;",
" new-instance v0, Ljava/util/Hashtable;",
" invoke-direct { v0 }, Ljava/util/Hashtable;-><init>()V",
" const-string v1, \"http://schemas.google.com/g/2005#AIM\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x02 # 2",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#MSN\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x03 # 3",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#YAHOO\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x04 # 4",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#SKYPE\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x05 # 5",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#QQ\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x06 # 6",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#GOOGLE_TALK\"",
" new-instance v2, Ljava/lang/Byte;",
" const/4 v3, 0x07 # 7",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#ICQ\"",
" new-instance v2, Ljava/lang/Byte;",
" const/16 v3, 0x0008 # 8",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" const-string v1, \"http://schemas.google.com/g/2005#JABBER\"",
" new-instance v2, Ljava/lang/Byte;",
" const/16 v3, 0x0009 # 9",
" invoke-direct { v2, v3 }, Ljava/lang/Byte;-><init>(B)V",
" invoke-virtual { v0, v1, v2 },"
+ " Ljava/util/Hashtable;->put(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;",
" sput-object v0, LA;->K:Ljava/util/Hashtable;",
" invoke-static { v0 }, LA;->a(Ljava/util/Hashtable;)Ljava/util/Hashtable;",
" move-result-object v0",
" sput-object v0, LA;->L:Ljava/util/Hashtable;",
" return-void");
Consumer<InternalOptions> options = configureOutlineOptions(outline -> outline.threshold = 2);
AndroidApp originalApplication = buildApplicationWithAndroidJar(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Verify the code.
runDex2Oat(processedApplication);
}
private static boolean isOutlineInvoke(Instruction instruction) {
return instruction instanceof InvokeStatic
&& instruction.getMethod().holder.toSourceString().equals(OutlineOptions.CLASS_NAME);
}
private void assertHasOutlineInvoke(DexEncodedMethod method) {
assertTrue(
Arrays
.stream(method.getCode().asDexCode().instructions)
.anyMatch(OutlineTest::isOutlineInvoke));
}
@Test
public void b113145696_superClassUseFirst() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
// Code where the outline argument is first used as java.lang.Object and afterwards
// java.util.ArrayList.
List<String> codeToOutline = ImmutableList.of(
" invoke-virtual { v1, v2 }, Ljava/io/PrintStream;->print(Ljava/lang/Object;)V",
" invoke-virtual { v2 }, Ljava/util/ArrayList;->isEmpty()Z",
" move-result v0",
" return v0"
);
String returnType = "boolean";
List<String> parameters = ImmutableList.of("java.io.PrintStream", "java.util.ArrayList");
MethodSignature signature1 = builder.addPrivateInstanceMethod(
returnType,
DEFAULT_METHOD_NAME + "1",
parameters,
0,
codeToOutline.toArray(StringUtils.EMPTY_ARRAY)
);
MethodSignature signature2 = builder.addPrivateInstanceMethod(
returnType,
DEFAULT_METHOD_NAME + "2",
parameters,
0,
codeToOutline.toArray(StringUtils.EMPTY_ARRAY)
);
builder.addMainMethod(
3,
" new-instance v0, LTest;",
" invoke-direct { v0 }, LTest;-><init>()V",
" sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" new-instance v2, Ljava/util/ArrayList;",
" invoke-direct { v2 }, Ljava/util/ArrayList;-><init>()V",
" invoke-virtual { v0, v1, v2 }, " +
" LTest;->method1(Ljava/io/PrintStream;Ljava/util/ArrayList;)Z",
" move-result v3",
" invoke-virtual { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
" invoke-virtual { v0, v1, v2 }, " +
" LTest;->method2(Ljava/io/PrintStream;Ljava/util/ArrayList;)Z",
" move-result v3",
" invoke-virtual { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
" return-void"
);
// Outline 2 times two instructions.
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 2;
outline.minSize = 2;
outline.maxSize = 2;
});
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Check that outlining happened.
assertHasOutlineInvoke(getMethod(processedApplication, signature1));
assertHasOutlineInvoke(getMethod(processedApplication, signature2));
assertThat(
new CodeInspector(processedApplication)
.clazz(OutlineOptions.CLASS_NAME)
.method(
"boolean",
"outline0",
ImmutableList.of("java.io.PrintStream", "java.util.ArrayList")),
isPresent());
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("[]true[]true", result);
}
@Test
public void b113145696_superInterfaceUseFirst() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
List<String> codeToOutline = ImmutableList.of(
" invoke-interface { v1 }, Ljava/lang/Iterable;->iterator()Ljava/util/Iterator;",
" invoke-interface { v1 }, Ljava/util/Collection;->isEmpty()Z",
" move-result v0",
" return v0"
);
String returnType = "boolean";
List<String> parameters = ImmutableList.of("java.util.List");
MethodSignature signature1 = builder.addPrivateInstanceMethod(
returnType,
DEFAULT_METHOD_NAME + "1",
parameters,
0,
codeToOutline.toArray(StringUtils.EMPTY_ARRAY)
);
MethodSignature signature2 = builder.addPrivateInstanceMethod(
returnType,
DEFAULT_METHOD_NAME + "2",
parameters,
0,
codeToOutline.toArray(StringUtils.EMPTY_ARRAY)
);
builder.addMainMethod(
3,
" new-instance v0, LTest;",
" invoke-direct { v0 }, LTest;-><init>()V",
" sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" new-instance v2, Ljava/util/ArrayList;",
" invoke-direct { v2 }, Ljava/util/ArrayList;-><init>()V",
" invoke-virtual { v0, v2 }, LTest;->method1(Ljava/util/List;)Z",
" move-result v3",
" invoke-virtual { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
" invoke-virtual { v0, v2 }, LTest;->method2(Ljava/util/List;)Z",
" move-result v3",
" invoke-virtual { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
" return-void"
);
// Outline 2 times two instructions.
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 2;
outline.minSize = 2;
outline.maxSize = 2;
});
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Check that outlining happened.
assertHasOutlineInvoke(getMethod(processedApplication, signature1));
assertHasOutlineInvoke(getMethod(processedApplication, signature2));
assertThat(
new CodeInspector(processedApplication)
.clazz(OutlineOptions.CLASS_NAME)
.method("boolean", "outline0", ImmutableList.of("java.util.List")),
isPresent());
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("truetrue", result);
}
@Test
public void b113145696_interfaceUseFirst() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
// Code where the outline argument is first used as java.lang.Iterable and afterwards
// java.util.ArrayList.
List<String> codeToOutline = ImmutableList.of(
" invoke-interface { v1 }, Ljava/lang/Iterable;->iterator()Ljava/util/Iterator;",
" invoke-virtual { v1 }, Ljava/util/ArrayList;->isEmpty()Z",
" move-result v0",
" return v0"
);
String returnType = "boolean";
List<String> parameters = ImmutableList.of("java.util.ArrayList");
MethodSignature signature1 = builder.addPrivateInstanceMethod(
returnType,
DEFAULT_METHOD_NAME + "1",
parameters,
0,
codeToOutline.toArray(StringUtils.EMPTY_ARRAY)
);
MethodSignature signature2 = builder.addPrivateInstanceMethod(
returnType,
DEFAULT_METHOD_NAME + "2",
parameters,
0,
codeToOutline.toArray(StringUtils.EMPTY_ARRAY)
);
builder.addMainMethod(
3,
" new-instance v0, LTest;",
" invoke-direct { v0 }, LTest;-><init>()V",
" sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" new-instance v2, Ljava/util/ArrayList;",
" invoke-direct { v2 }, Ljava/util/ArrayList;-><init>()V",
" invoke-virtual { v0, v2 }, LTest;->method1(Ljava/util/ArrayList;)Z",
" move-result v3",
" invoke-virtual { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
" invoke-virtual { v0, v2 }, LTest;->method2(Ljava/util/ArrayList;)Z",
" move-result v3",
" invoke-virtual { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
" return-void"
);
// Outline 2 times two instructions.
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 2;
outline.minSize = 2;
outline.maxSize = 2;
});
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Check that outlining happened.
assertHasOutlineInvoke(getMethod(processedApplication, signature1));
assertHasOutlineInvoke(getMethod(processedApplication, signature2));
assertThat(
new CodeInspector(processedApplication)
.clazz(OutlineOptions.CLASS_NAME)
.method("boolean", "outline0", ImmutableList.of("java.util.ArrayList")),
isPresent());
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("truetrue", result);
}
@Test
public void b113145696_classUseFirst() throws Exception {
SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
// Code where the outline argument is first used as java.lang.Iterable and afterwards
// java.util.ArrayList.
List<String> codeToOutline = ImmutableList.of(
" invoke-virtual { v1 }, Ljava/util/ArrayList;->isEmpty()Z",
" move-result v0",
" invoke-interface { v1 }, Ljava/lang/Iterable;->iterator()Ljava/util/Iterator;",
" return v0"
);
String returnType = "boolean";
List<String> parameters = ImmutableList.of("java.util.ArrayList");
MethodSignature signature1 = builder.addPrivateInstanceMethod(
returnType,
DEFAULT_METHOD_NAME + "1",
parameters,
0,
codeToOutline.toArray(StringUtils.EMPTY_ARRAY)
);
MethodSignature signature2 = builder.addPrivateInstanceMethod(
returnType,
DEFAULT_METHOD_NAME + "2",
parameters,
0,
codeToOutline.toArray(StringUtils.EMPTY_ARRAY)
);
builder.addMainMethod(
3,
" new-instance v0, LTest;",
" invoke-direct { v0 }, LTest;-><init>()V",
" sget-object v1, Ljava/lang/System;->out:Ljava/io/PrintStream;",
" new-instance v2, Ljava/util/ArrayList;",
" invoke-direct { v2 }, Ljava/util/ArrayList;-><init>()V",
" invoke-virtual { v0, v2 }, LTest;->method1(Ljava/util/ArrayList;)Z",
" move-result v3",
" invoke-virtual { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
" invoke-virtual { v0, v2 }, LTest;->method2(Ljava/util/ArrayList;)Z",
" move-result v3",
" invoke-virtual { v1, v3 }, Ljava/io/PrintStream;->print(Z)V",
" return-void"
);
// Outline 2 times two instructions.
Consumer<InternalOptions> options =
configureOutlineOptions(
outline -> {
outline.threshold = 2;
outline.minSize = 2;
outline.maxSize = 2;
});
AndroidApp originalApplication = buildApplication(builder);
AndroidApp processedApplication = processApplication(originalApplication, options);
assertEquals(2, getNumberOfProgramClasses(processedApplication));
// Check that outlining happened.
assertHasOutlineInvoke(getMethod(processedApplication, signature1));
assertHasOutlineInvoke(getMethod(processedApplication, signature2));
assertThat(
new CodeInspector(processedApplication)
.clazz(OutlineOptions.CLASS_NAME)
.method("boolean", "outline0", ImmutableList.of("java.util.ArrayList")),
isPresent());
// Run code and check result.
String result = runArt(processedApplication);
assertEquals("truetrue", result);
}
}