Refactor SmaliTestBase to go through the normal R8 entry points. Bug: Change-Id: Id3e8114ffc2a562790a7043ade5de05be187f449
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java index a9f3329..18096f4 100644 --- a/src/main/java/com/android/tools/r8/R8.java +++ b/src/main/java/com/android/tools/r8/R8.java
@@ -62,7 +62,6 @@ import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; public class R8 { @@ -106,51 +105,6 @@ } } - static DexApplication optimize( - DexApplication application, - AppInfoWithSubtyping appInfo, - InternalOptions options) - throws ApiLevelException, ExecutionException, IOException { - return new R8(options).optimize(application, appInfo); - } - - private DexApplication optimize(DexApplication application, AppInfoWithSubtyping appInfo) - throws IOException, ApiLevelException, ExecutionException { - return optimize(application, appInfo, GraphLense.getIdentityLense(), - Executors.newSingleThreadExecutor()); - } - - private DexApplication optimize( - DexApplication application, - AppInfoWithSubtyping appInfo, - GraphLense graphLense, - ExecutorService executorService) - throws IOException, ApiLevelException, ExecutionException { - final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null; - - timing.begin("Create IR"); - try { - IRConverter converter = new IRConverter( - appInfo, options, timing, printer, graphLense); - application = converter.optimize(application, executorService); - } finally { - timing.end(); - } - - if (options.printCfg) { - if (options.printCfgFile == null || options.printCfgFile.isEmpty()) { - System.out.print(printer.toString()); - } else { - try (OutputStreamWriter writer = new OutputStreamWriter( - new FileOutputStream(options.printCfgFile), - StandardCharsets.UTF_8)) { - writer.write(printer.toString()); - } - } - } - return application; - } - private Set<DexType> filterMissingClasses(Set<DexType> missingClasses, ProguardClassFilter dontWarnPatterns) { Set<DexType> result = new HashSet<>(missingClasses); @@ -278,7 +232,26 @@ graphLense = new BridgeMethodAnalysis(graphLense, appInfo.withSubtyping()).run(); - application = optimize(application, appInfo, graphLense, executorService); + timing.begin("Create IR"); + CfgPrinter printer = options.printCfg ? new CfgPrinter() : null; + try { + IRConverter converter = new IRConverter(appInfo, options, timing, printer, graphLense); + application = converter.optimize(application, executorService); + } finally { + timing.end(); + } + + if (options.printCfg) { + if (options.printCfgFile == null || options.printCfgFile.isEmpty()) { + System.out.print(printer.toString()); + } else { + try (OutputStreamWriter writer = new OutputStreamWriter( + new FileOutputStream(options.printCfgFile), + StandardCharsets.UTF_8)) { + writer.write(printer.toString()); + } + } + } // Overwrite SourceFile if specified. This step should be done after IR conversion. timing.begin("Rename SourceFile");
diff --git a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java index 44ac9c8..7f7ec36 100644 --- a/src/main/java/com/android/tools/r8/graph/SmaliWriter.java +++ b/src/main/java/com/android/tools/r8/graph/SmaliWriter.java
@@ -1,11 +1,15 @@ package com.android.tools.r8.graph; +import com.android.tools.r8.dex.ApplicationReader; import com.android.tools.r8.errors.CompilationError; +import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.Timing; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintStream; import java.nio.charset.StandardCharsets; +import java.util.concurrent.ExecutionException; public class SmaliWriter extends DexByteCodeWriter { @@ -15,12 +19,15 @@ } /** Return smali source for the application code. */ - public static String smali(DexApplication application, InternalOptions options) { - SmaliWriter writer = new SmaliWriter(application, options); + public static String smali(AndroidApp application, InternalOptions options) { ByteArrayOutputStream os = new ByteArrayOutputStream(); try (PrintStream ps = new PrintStream(os)) { + DexApplication dexApplication = new ApplicationReader(application, options, + new Timing("SmaliWriter")) + .read(); + SmaliWriter writer = new SmaliWriter(dexApplication, options); writer.write(ps); - } catch (IOException e) { + } catch (IOException | ExecutionException e) { throw new CompilationError("Failed to generate smali sting", e); } return new String(os.toByteArray(), StandardCharsets.UTF_8);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java index 00787ca..a9735c4 100644 --- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java +++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -39,6 +39,7 @@ import com.android.tools.r8.utils.CfgPrinter; import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.InternalOptions.OutlineOptions; import com.android.tools.r8.utils.StringDiagnostic; import com.android.tools.r8.utils.ThreadUtils; import com.android.tools.r8.utils.Timing; @@ -417,7 +418,7 @@ DexType result; int count = 0; do { - String name = options.outline.className + (count == 0 ? "" : Integer.toString(count)); + String name = OutlineOptions.CLASS_NAME + (count == 0 ? "" : Integer.toString(count)); count++; result = appInfo.dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(name)); } while (appInfo.definitionFor(result) != null);
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java index 67e58cb..9709cf0 100644 --- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java +++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -48,6 +48,7 @@ import com.android.tools.r8.ir.conversion.SourceCode; import com.android.tools.r8.naming.ClassNameMapper; 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.StringUtils.BraceType; import com.google.common.collect.Sets; @@ -1062,7 +1063,7 @@ MethodAccessFlags methodAccess = MethodAccessFlags.fromSharedAccessFlags( Constants.ACC_PUBLIC | Constants.ACC_STATIC, false); - DexString methodName = dexItemFactory.createString(options.outline.methodPrefix + count); + DexString methodName = dexItemFactory.createString(OutlineOptions.METHOD_PREFIX + count); DexMethod method = outline.buildMethod(type, methodName); direct[count] = new DexEncodedMethod(method, methodAccess, DexAnnotationSet.empty(), DexAnnotationSetRefList.empty(), new OutlineCode(outline));
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java index c494c08..36ea1b3 100644 --- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java +++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -17,6 +17,7 @@ import java.io.Writer; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.LinkedList; import java.util.List; import org.objectweb.asm.Type; @@ -291,6 +292,12 @@ this.parameters = parameters; } + public MethodSignature(String name, String type, Collection<String> parameters) { + super(name); + this.type = type; + this.parameters = parameters.toArray(new String[parameters.size()]); + } + public static MethodSignature fromDexMethod(DexMethod method) { String[] paramNames = new String[method.getArity()]; DexType[] values = method.proto.parameters.values;
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApp.java b/src/main/java/com/android/tools/r8/utils/AndroidApp.java index 67aa288..ddac51f 100644 --- a/src/main/java/com/android/tools/r8/utils/AndroidApp.java +++ b/src/main/java/com/android/tools/r8/utils/AndroidApp.java
@@ -608,7 +608,7 @@ * Set dead-code data. */ public Builder setDeadCode(byte[] content) { - deadCode = content == null ? null : Resource.fromBytes(null, content); + deadCode = content == null ? null : Resource.fromBytes(Origin.unknown(), content); return this; } @@ -631,7 +631,7 @@ * Set proguard-map data. */ public Builder setProguardMapData(byte[] content) { - proguardMap = content == null ? null : Resource.fromBytes(null, content); + proguardMap = content == null ? null : Resource.fromBytes(Origin.unknown(), content); return this; } @@ -639,7 +639,7 @@ * Set proguard-seeds data. */ public Builder setProguardSeedsData(byte[] content) { - proguardSeeds = content == null ? null : Resource.fromBytes(null, content); + proguardSeeds = content == null ? null : Resource.fromBytes(Origin.unknown(), content); return this; } @@ -685,7 +685,7 @@ * Set the main-dex list output data. */ public Builder setMainDexListOutputData(byte[] content) { - mainDexListOutput = content == null ? null : Resource.fromBytes(null, content); + mainDexListOutput = content == null ? null : Resource.fromBytes(Origin.unknown(), content); return this; }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java index d7006af..62dac5f 100644 --- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java +++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -267,9 +267,10 @@ public static class OutlineOptions { + public static final String CLASS_NAME = "r8.GeneratedOutlineSupport"; + public static final String METHOD_PREFIX = "outline"; + public boolean enabled = true; - public static final String className = "r8.GeneratedOutlineSupport"; - public String methodPrefix = "outline"; public int minSize = 3; public int maxSize = 99; public int threshold = 20;
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java index 2128663..60bcc5b 100644 --- a/src/test/java/com/android/tools/r8/TestBase.java +++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -5,18 +5,20 @@ package com.android.tools.r8; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import com.android.tools.r8.ToolHelper.ProcessResult; -import com.android.tools.r8.dex.ApplicationReader; -import com.android.tools.r8.graph.DexApplication; +import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.graph.SmaliWriter; import com.android.tools.r8.shaking.FilteredClassPath; import com.android.tools.r8.shaking.ProguardRuleParserException; import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.DexInspector; +import com.android.tools.r8.utils.DexInspector.ClassSubject; +import com.android.tools.r8.utils.DexInspector.MethodSubject; import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.OutputMode; -import com.android.tools.r8.utils.Timing; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; @@ -368,8 +370,34 @@ */ protected void disassemble(AndroidApp app) throws Exception { InternalOptions options = new InternalOptions(); - DexApplication dexApplication = new ApplicationReader(app, options, new Timing("XX")).read(); - System.out.println(SmaliWriter.smali(dexApplication, options)); + System.out.println(SmaliWriter.smali(app, options)); + } + + protected DexEncodedMethod getMethod( + DexInspector inspector, + String className, + String returnType, + String methodName, + List<String> parameters) { + ClassSubject clazz = inspector.clazz(className); + assertTrue(clazz.isPresent()); + MethodSubject method = clazz.method(returnType, methodName, parameters); + assertTrue(method.isPresent()); + return method.getMethod(); + } + + protected DexEncodedMethod getMethod( + AndroidApp application, + String className, + String returnType, + String methodName, + List<String> parameters) { + try { + DexInspector inspector = new DexInspector(application); + return getMethod(inspector, className, returnType, methodName, parameters); + } catch (Exception e) { + return null; + } } public enum MinifyMode {
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java index 2c4bc98..34230de 100644 --- a/src/test/java/com/android/tools/r8/ToolHelper.java +++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -10,7 +10,6 @@ import com.android.tools.r8.DeviceRunner.DeviceRunnerConfigurationException; import com.android.tools.r8.ToolHelper.DexVm.Kind; import com.android.tools.r8.dex.ApplicationReader; -import com.android.tools.r8.graph.AppInfoWithSubtyping; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexItemFactory; import com.android.tools.r8.shaking.FilteredClassPath; @@ -697,14 +696,6 @@ return compatSink.build(); } - public static DexApplication optimizeWithR8( - DexApplication application, - InternalOptions options) - throws CompilationException, ExecutionException, IOException { - application = application.toDirect(); - return R8.optimize(application, new AppInfoWithSubtyping(application), options); - } - public static AndroidApp runD8(AndroidApp app) throws CompilationException, IOException { return runD8(app, null); }
diff --git a/src/test/java/com/android/tools/r8/bisect/BisectTest.java b/src/test/java/com/android/tools/r8/bisect/BisectTest.java index ba49e64..fc6e4c1 100644 --- a/src/test/java/com/android/tools/r8/bisect/BisectTest.java +++ b/src/test/java/com/android/tools/r8/bisect/BisectTest.java
@@ -10,8 +10,8 @@ import com.android.tools.r8.dex.ApplicationReader; import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexProgramClass; -import com.android.tools.r8.smali.SmaliTestBase.MethodSignature; -import com.android.tools.r8.smali.SmaliTestBase.SmaliBuilder; +import com.android.tools.r8.smali.SmaliBuilder; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.DexInspector; import com.android.tools.r8.utils.InternalOptions;
diff --git a/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java b/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java index 66652bd..7a911be 100644 --- a/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java +++ b/src/test/java/com/android/tools/r8/debug/SmaliDebugTest.java
@@ -12,15 +12,13 @@ import com.android.tools.r8.debuginfo.DebugInfoInspector; import com.android.tools.r8.graph.DexDebugEntry; import com.android.tools.r8.naming.MemberNaming.MethodSignature; -import com.android.tools.r8.smali.SmaliTestBase.SmaliBuilder; +import com.android.tools.r8.smali.SmaliBuilder; import com.android.tools.r8.utils.AndroidApp; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collections; import java.util.List; import org.apache.harmony.jpda.tests.framework.jdwp.Value; -import org.junit.Assume; -import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder;
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java index b344c8c..8d93b31 100644 --- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java +++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -10,9 +10,10 @@ import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.DexVm; -import com.android.tools.r8.ToolHelper.DexVm.Version; +import com.android.tools.r8.smali.SmaliBuilder; import com.android.tools.r8.smali.SmaliTestBase; -import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.DexInspector; import com.google.common.collect.ImmutableList; import org.junit.Test; @@ -62,10 +63,11 @@ " goto :return" ); - DexApplication application = buildApplication(builder); - AppInfo appInfo = new AppInfo(application); - DexEncodedMethod method = - getMethod(application, DEFAULT_CLASS_NAME, "int", "x", ImmutableList.of()); + AndroidApp application = buildApplication(builder); + AppInfo appInfo = getAppInfo(application); + DexInspector inspector = new DexInspector(appInfo.app); + DexEncodedMethod method = getMethod(inspector, DEFAULT_CLASS_NAME, "int", "x", + ImmutableList.of()); assertNull(appInfo.lookupVirtualTarget(method.method.holder, method.method)); assertNull(appInfo.lookupDirectTarget(method.method)); assertNotNull(appInfo.lookupStaticTarget(method.method)); @@ -74,12 +76,12 @@ // Dalvik rejects at verification time instead of producing the // expected IncompatibleClassChangeError. try { - runArt(application, new InternalOptions()); + runArt(application); } catch (AssertionError e) { assert e.toString().contains("VerifyError"); } } else { - assertEquals("OK", runArt(application, new InternalOptions())); + assertEquals("OK", runArt(application)); } } @@ -131,20 +133,21 @@ " goto :return" ); - DexApplication application = buildApplication(builder); - AppInfo appInfo = new AppInfo(application); + AndroidApp application = buildApplication(builder); + AppInfo appInfo = getAppInfo(application); + DexInspector inspector = new DexInspector(appInfo.app); DexMethod methodXOnTestSuper = - getMethod(application, "TestSuper", "int", "x", ImmutableList.of()).method; + getMethod(inspector, "TestSuper", "int", "x", ImmutableList.of()).method; DexMethod methodYOnTest = - getMethod(application, "Test", "int", "y", ImmutableList.of()).method; + getMethod(inspector, "Test", "int", "y", ImmutableList.of()).method; DexType classTestSuper = methodXOnTestSuper.getHolder(); DexType classTest = methodYOnTest.getHolder(); DexProto methodXProto = methodXOnTestSuper.proto; DexString methodXName = methodXOnTestSuper.name; DexMethod methodXOnTest = - application.dexItemFactory.createMethod(classTest, methodXProto, methodXName); + appInfo.dexItemFactory.createMethod(classTest, methodXProto, methodXName); assertNull(appInfo.lookupVirtualTarget(classTestSuper, methodXOnTestSuper)); assertNull(appInfo.lookupVirtualTarget(classTest, methodXOnTestSuper)); @@ -156,7 +159,7 @@ assertNotNull(appInfo.lookupStaticTarget(methodXOnTestSuper)); assertNotNull(appInfo.lookupStaticTarget(methodXOnTest)); - assertEquals("OK", runArt(application, new InternalOptions())); + assertEquals("OK", runArt(application)); } }
diff --git a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java index ad9a26a..2d73d1f 100644 --- a/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java +++ b/src/test/java/com/android/tools/r8/ir/BasicBlockIteratorTest.java
@@ -4,13 +4,15 @@ package com.android.tools.r8.ir; -import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.ValueNumberGenerator; +import com.android.tools.r8.smali.SmaliBuilder; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; import com.android.tools.r8.smali.SmaliTestBase; +import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.InternalOptions; import com.google.common.collect.ImmutableList; import java.util.List; @@ -45,8 +47,7 @@ " return p0" ); - InternalOptions options = new InternalOptions(); - DexApplication application = buildApplication(builder, options); + AndroidApp application = buildApplication(builder); // Build the code, and split the code into three blocks. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java index 27a3ce9..b190933 100644 --- a/src/test/java/com/android/tools/r8/ir/InlineTest.java +++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -13,7 +13,8 @@ import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.ValueNumberGenerator; -import com.android.tools.r8.smali.SmaliTestBase; +import com.android.tools.r8.smali.SmaliBuilder; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; import com.android.tools.r8.utils.InternalOptions; import com.google.common.collect.ImmutableList; import java.util.ArrayList; @@ -22,7 +23,7 @@ import java.util.ListIterator; import org.junit.Test; -public class InlineTest extends SmaliTestBase { +public class InlineTest extends IrInjectionTestBase { TestApplication codeForMethodReplaceTest(int a, int b) throws Exception { SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); @@ -84,7 +85,7 @@ DexEncodedMethod methodB = getMethod(application, signatureB); IRCode codeB = methodB.buildIR(new InternalOptions(), valueNumberGenerator); - return new SmaliTestBase.TestApplication(application, method, code, + return new TestApplication(application, method, code, ImmutableList.of(codeA, codeB), valueNumberGenerator, options); }
diff --git a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java index a688ffe..5d3b0fb 100644 --- a/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java +++ b/src/test/java/com/android/tools/r8/ir/InstructionIteratorTest.java
@@ -4,14 +4,16 @@ package com.android.tools.r8.ir; -import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Instruction; import com.android.tools.r8.ir.code.InstructionListIterator; import com.android.tools.r8.ir.code.ValueNumberGenerator; +import com.android.tools.r8.smali.SmaliBuilder; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; import com.android.tools.r8.smali.SmaliTestBase; +import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.InternalOptions; import com.google.common.collect.ImmutableList; import java.util.List; @@ -46,8 +48,7 @@ " return p0" ); - InternalOptions options = new InternalOptions(); - DexApplication application = buildApplication(builder, options); + AndroidApp application = buildApplication(builder); // Build the code, and split the code into three blocks. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator();
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java new file mode 100644 index 0000000..9f3e0ac --- /dev/null +++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -0,0 +1,146 @@ +// Copyright (c) 2017, 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.ir; + +import com.android.tools.r8.R8; +import com.android.tools.r8.dex.ApplicationReader; +import com.android.tools.r8.errors.DexOverflowException; +import com.android.tools.r8.graph.AppInfo; +import com.android.tools.r8.graph.DexApplication; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.ir.code.BasicBlock; +import com.android.tools.r8.ir.code.IRCode; +import com.android.tools.r8.ir.code.Instruction; +import com.android.tools.r8.ir.code.InstructionListIterator; +import com.android.tools.r8.ir.code.ValueNumberGenerator; +import com.android.tools.r8.ir.conversion.IRConverter; +import com.android.tools.r8.naming.NamingLens; +import com.android.tools.r8.smali.SmaliBuilder; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; +import com.android.tools.r8.smali.SmaliTestBase; +import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.AndroidAppOutputSink; +import com.android.tools.r8.utils.DexInspector; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.Timing; +import java.io.IOException; +import java.util.List; +import java.util.ListIterator; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import org.antlr.runtime.RecognitionException; + +public class IrInjectionTestBase extends SmaliTestBase { + + protected DexApplication buildApplication(SmaliBuilder builder, InternalOptions options) { + try { + return buildApplication(AndroidApp.fromDexProgramData(builder.compile()), options); + } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) { + throw new RuntimeException(e); + } + } + + protected DexApplication buildApplication(AndroidApp input, InternalOptions options) { + try { + options.itemFactory.resetSortedIndices(); + return new ApplicationReader(input, options, new Timing("IrInjectionTest")).read(); + } catch (IOException | ExecutionException e) { + throw new RuntimeException(e); + } + } + + protected DexEncodedMethod getMethod(DexApplication application, MethodSignature signature) { + return getMethod(application, + signature.clazz, signature.returnType, signature.name, signature.parameterTypes); + } + + protected DexEncodedMethod getMethod( + DexApplication application, + String className, + String returnType, + String methodName, + List<String> parameters) { + DexInspector inspector = new DexInspector(application); + return getMethod(inspector, className, returnType, methodName, parameters); + } + + public class TestApplication { + + public final DexApplication application; + public final DexEncodedMethod method; + public final IRCode code; + public final List<IRCode> additionalCode; + public final ValueNumberGenerator valueNumberGenerator; + public final InternalOptions options; + + public TestApplication( + DexApplication application, + DexEncodedMethod method, + IRCode code, + ValueNumberGenerator valueNumberGenerator, + InternalOptions options) { + this(application, method, code, null, valueNumberGenerator, options); + } + + public TestApplication( + DexApplication application, + DexEncodedMethod method, + IRCode code, + List<IRCode> additionalCode, + ValueNumberGenerator valueNumberGenerator, + InternalOptions options) { + this.application = application; + this.method = method; + this.code = code; + this.additionalCode = additionalCode; + this.valueNumberGenerator = valueNumberGenerator; + this.options = options; + } + + public int countArgumentInstructions() { + int count = 0; + ListIterator<Instruction> iterator = code.blocks.get(0).listIterator(); + while (iterator.next().isArgument()) { + count++; + } + return count; + } + + public InstructionListIterator listIteratorAt(BasicBlock block, int index) { + InstructionListIterator iterator = block.listIterator(); + for (int i = 0; i < index; i++) { + iterator.next(); + } + return iterator; + } + + private AndroidApp writeDex(DexApplication application, InternalOptions options) + throws DexOverflowException { + try { + AndroidAppOutputSink compatSink = new AndroidAppOutputSink(); + R8.writeApplication( + Executors.newSingleThreadExecutor(), + application, + compatSink, + null, + NamingLens.getIdentityLens(), + null, + options); + compatSink.close(); + return compatSink.build(); + } catch (ExecutionException | IOException e) { + throw new RuntimeException(e); + } + } + + public String run() throws DexOverflowException, IOException { + AppInfo appInfo = new AppInfo(application); + IRConverter converter = new IRConverter(appInfo, options); + converter.replaceCodeForTesting(method, code); + AndroidApp app = writeDex(application, options); + return runOnArtRaw(app, DEFAULT_MAIN_CLASS_NAME).stdout; + } + } + +}
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java index 5165432..25a237d 100644 --- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java +++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -21,14 +21,15 @@ import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueNumberGenerator; import com.android.tools.r8.ir.code.ValueType; -import com.android.tools.r8.smali.SmaliTestBase; +import com.android.tools.r8.smali.SmaliBuilder; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; import com.android.tools.r8.utils.InternalOptions; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; import org.junit.Test; -public class SplitBlockTest extends SmaliTestBase { +public class SplitBlockTest extends IrInjectionTestBase { TestApplication codeWithoutCatchHandlers() throws Exception { SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME);
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java index 1d17e31..74a91a8 100644 --- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java +++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -3,13 +3,15 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.ir.regalloc; -import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Value; import com.android.tools.r8.ir.code.ValueNumberGenerator; import com.android.tools.r8.ir.code.ValueType; +import com.android.tools.r8.smali.SmaliBuilder; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; import com.android.tools.r8.smali.SmaliTestBase; +import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.InternalOptions; import com.google.common.collect.ImmutableList; import java.util.PriorityQueue; @@ -43,7 +45,7 @@ ImmutableList.of(), 1, " return-void"); - DexApplication application = buildApplication(builder, options); + AndroidApp application = buildApplication(builder); // Build the code, and split the code into three blocks. ValueNumberGenerator valueNumberGenerator = new ValueNumberGenerator(); DexEncodedMethod method = getMethod(application, signature);
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java index 7421046..5579380 100644 --- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java +++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -4,18 +4,15 @@ package com.android.tools.r8.jasmin; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; import com.android.tools.r8.Resource; +import com.android.tools.r8.TestBase; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.ToolHelper.ProcessResult; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder; import com.android.tools.r8.naming.MemberNaming.MethodSignature; import com.android.tools.r8.utils.AndroidApp; -import com.android.tools.r8.utils.DexInspector; -import com.android.tools.r8.utils.DexInspector.ClassSubject; -import com.android.tools.r8.utils.DexInspector.MethodSubject; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.OutputMode; import com.android.tools.r8.utils.StringUtils; @@ -33,13 +30,8 @@ import java.util.Arrays; import java.util.List; import java.util.function.Consumer; -import org.junit.Rule; -import org.junit.rules.TemporaryFolder; -public class JasminTestBase { - - @Rule - public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest(); +public class JasminTestBase extends TestBase { public static String getPathFromDescriptor(String classDescriptor) { assert classDescriptor.startsWith("L"); @@ -95,6 +87,10 @@ return runOnArt(compileWithD8(builder), main); } + protected AndroidApp compileWithR8(JasminBuilder builder) throws Exception { + return compileWithR8(builder, null); + } + protected AndroidApp compileWithR8(JasminBuilder builder, Consumer<InternalOptions> optionsConsumer) throws Exception { @@ -169,20 +165,6 @@ protected DexEncodedMethod getMethod(AndroidApp application, String clazz, MethodSignature signature) { return getMethod(application, - clazz, signature.type, signature.name, signature.parameters); - } - - protected DexEncodedMethod getMethod(AndroidApp application, String className, - String returnType, String methodName, String[] parameters) { - try { - DexInspector inspector = new DexInspector(application); - ClassSubject clazz = inspector.clazz(className); - assertTrue(clazz.isPresent()); - MethodSubject method = clazz.method(returnType, methodName, Arrays.asList(parameters)); - assertTrue(method.isPresent()); - return method.getMethod(); - } catch (Exception e) { - return null; - } + clazz, signature.type, signature.name, Arrays.asList(signature.parameters)); } }
diff --git a/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java b/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java index acb0a51..de654b8 100644 --- a/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java +++ b/src/test/java/com/android/tools/r8/naming/RenameSourceFileSmaliTest.java
@@ -17,6 +17,8 @@ import com.android.tools.r8.graph.DexDebugEvent.SetFile; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.shaking.ProguardConfiguration; +import com.android.tools.r8.smali.SmaliBuilder; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; import com.android.tools.r8.smali.SmaliTestBase; import com.android.tools.r8.utils.FileUtils; import com.android.tools.r8.utils.StringUtils;
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java index 4d1d619..51772fe 100644 --- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java +++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/StaticValuesTest.java
@@ -11,7 +11,6 @@ import com.android.tools.r8.code.Instruction; import com.android.tools.r8.code.Sput; import com.android.tools.r8.code.SputObject; -import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexCode; import com.android.tools.r8.graph.DexValue; import com.android.tools.r8.graph.DexValue.DexValueBoolean; @@ -23,11 +22,11 @@ import com.android.tools.r8.graph.DexValue.DexValueLong; import com.android.tools.r8.graph.DexValue.DexValueShort; import com.android.tools.r8.graph.DexValue.DexValueString; +import com.android.tools.r8.smali.SmaliBuilder; import com.android.tools.r8.smali.SmaliTestBase; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.DexInspector; import com.android.tools.r8.utils.DexInspector.MethodSubject; -import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.StringUtils; import org.junit.Test; @@ -92,9 +91,8 @@ "return-void" ); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); DexInspector inspector = new DexInspector(processedApplication); // Test is running without tree-shaking, so the empty <clinit> is not removed. @@ -147,7 +145,7 @@ assertTrue(value instanceof DexValueString); assertEquals(("8"), ((DexValueString) value).getValue().toString()); - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals(StringUtils.lines("true", "1", "2", "3", "4", "5.0", "6.0", "7", "8"), result); } @@ -177,16 +175,15 @@ "return-void" ); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); DexInspector inspector = new DexInspector(processedApplication); MethodSubject clinit = inspector.clazz("Test").clinit(); // Nothing changed in the class initializer. assertEquals(5, clinit.getMethod().getCode().asDexCode().instructions.length); - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals(StringUtils.lines("0", "1"), result); } @@ -219,16 +216,15 @@ "return-void" ); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); DexInspector inspector = new DexInspector(processedApplication); // Test is running without tree-shaking, so the empty <clinit> is not removed. assertTrue( inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod()); - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals(StringUtils.lines("null", "null", "null"), result); } @@ -262,16 +258,15 @@ "return-void" ); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); DexInspector inspector = new DexInspector(processedApplication); // Test is running without tree-shaking, so the empty <clinit> is not removed. assertTrue( inspector.clazz("Test").clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod()); - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals(StringUtils.lines("Value1", "Value2", "Value2"), result); } @@ -313,9 +308,8 @@ "return-void" ); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); DexInspector inspector = new DexInspector(processedApplication); // Test is running without tree-shaking, so the empty <clinit> is not removed. @@ -333,7 +327,7 @@ assertTrue(value instanceof DexValueString); assertEquals(("7"), ((DexValueString) value).getValue().toString()); - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals(StringUtils.lines("3", "7") , result); } @@ -387,9 +381,8 @@ "return-void" ); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); DexInspector inspector = new DexInspector(processedApplication); assertTrue(inspector.clazz("Test").clinit().isPresent()); @@ -417,7 +410,7 @@ } } - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals(StringUtils.lines("3", "7"), result); } @@ -475,9 +468,8 @@ "return-void" ); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); DexInspector inspector = new DexInspector(processedApplication); assertTrue(inspector.clazz(className).isPresent()); @@ -485,7 +477,7 @@ assertTrue( inspector.clazz(className).clinit().getMethod().getCode().asDexCode().isEmptyVoidMethod()); - String result = runArt(processedApplication, options, className); + String result = runArt(processedApplication, className); assertEquals(StringUtils.lines("Test", className, "Test", className, "Test", className), result); @@ -525,15 +517,14 @@ builder.addClass("org.example.Test2"); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); DexInspector inspector = new DexInspector(processedApplication); assertTrue(inspector.clazz(className).isPresent()); assertTrue(inspector.clazz(className).clinit().isPresent()); - String result = runArt(processedApplication, options, className); + String result = runArt(processedApplication, className); assertEquals(StringUtils.lines("Test2", "org.example.Test2"), result); } @@ -559,16 +550,15 @@ builder.addClass("Other"); builder.addStaticField("field", "I", "1"); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); DexInspector inspector = new DexInspector(processedApplication); MethodSubject clinit = inspector.clazz("Test").clinit(); // Nothing changed in the class initializer. assertEquals(3, clinit.getMethod().getCode().asDexCode().instructions.length); - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals(StringUtils.lines("2"), result); }
diff --git a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java index d1a2514..76c24f9 100644 --- a/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java +++ b/src/test/java/com/android/tools/r8/rewrite/staticvalues/inlibraries/StaticLibraryValuesChangeTest.java
@@ -11,7 +11,7 @@ import com.android.tools.r8.ToolHelper.DexVm.Version; import com.android.tools.r8.jasmin.JasminBuilder; import com.android.tools.r8.shaking.FilteredClassPath; -import com.android.tools.r8.smali.SmaliTestBase.SmaliBuilder; +import com.android.tools.r8.smali.SmaliBuilder; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.PreloadedClassFileProvider; import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java b/src/test/java/com/android/tools/r8/rewrite/switches/CheckSwitchInTestClass.java similarity index 96% rename from src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java rename to src/test/java/com/android/tools/r8/rewrite/switches/CheckSwitchInTestClass.java index f47025a..93b7c19 100644 --- a/src/test/java/com/android/tools/r8/smali/CheckSwitchInTestClass.java +++ b/src/test/java/com/android/tools/r8/rewrite/switches/CheckSwitchInTestClass.java
@@ -2,7 +2,7 @@ // 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; +package com.android.tools.r8.rewrite.switches; import java.lang.reflect.Method; import java.util.ArrayList;
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java new file mode 100644 index 0000000..66cf986 --- /dev/null +++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingJarTest.java
@@ -0,0 +1,344 @@ +// Copyright (c) 2017, 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.rewrite.switches; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.ToolHelper; +import com.android.tools.r8.code.IfEq; +import com.android.tools.r8.code.IfEqz; +import com.android.tools.r8.code.Instruction; +import com.android.tools.r8.code.PackedSwitch; +import com.android.tools.r8.code.SparseSwitch; +import com.android.tools.r8.graph.DexCode; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.jasmin.JasminBuilder; +import com.android.tools.r8.jasmin.JasminTestBase; +import com.android.tools.r8.naming.MemberNaming.MethodSignature; +import com.android.tools.r8.shaking.FilteredClassPath; +import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.DexInspector; +import com.android.tools.r8.utils.DexInspector.MethodSubject; +import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.util.List; +import java.util.stream.Collectors; +import org.junit.Test; + +public class SwitchRewritingJarTest extends JasminTestBase { + + private void runSingleCaseJarTest(boolean packed, int key) throws Exception { + JasminBuilder builder = new JasminBuilder(); + JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); + + String switchCode; + if (packed) { + switchCode = StringUtils.join( + "\n", + " tableswitch " + key, + " case_0", + " default : case_default"); + } else { + switchCode = StringUtils.join( + "\n", + " lookupswitch", + " " + key + " : case_0", + " default : case_default"); + } + + clazz.addStaticMethod("test", ImmutableList.of("I"), "I", + " .limit stack 1", + " .limit locals 1", + " iload 0", + switchCode, + " case_0:", + " iconst_3", + " goto return_", + " case_default:", + " ldc 5", + " return_:", + " ireturn"); + + clazz.addMainMethod( + " .limit stack 2", + " .limit locals 1", + " getstatic java/lang/System/out Ljava/io/PrintStream;", + " ldc 2", + " invokestatic Test/test(I)I", + " invokevirtual java/io/PrintStream/print(I)V", + " return"); + + AndroidApp app = builder.build(); + app = ToolHelper.runR8(app); + + MethodSignature signature = new MethodSignature("test", "int", ImmutableList.of("int")); + DexEncodedMethod method = getMethod(app, "Test", signature); + DexCode code = method.getCode().asDexCode(); + if (key == 0) { + assertEquals(5, code.instructions.length); + assertTrue(code.instructions[0] instanceof IfEqz); + } else { + assertEquals(6, code.instructions.length); + assertTrue(code.instructions[1] instanceof IfEq); + } + } + + @Test + public void singleCaseJar() throws Exception { + for (boolean packed : new boolean[]{true, false}) { + runSingleCaseJarTest(packed, Integer.MIN_VALUE); + runSingleCaseJarTest(packed, -1); + runSingleCaseJarTest(packed, 0); + runSingleCaseJarTest(packed, 1); + runSingleCaseJarTest(packed, Integer.MAX_VALUE); + } + } + + private void runTwoCaseSparseToPackedJarTest(int key1, int key2) throws Exception { + JasminBuilder builder = new JasminBuilder(); + JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); + + clazz.addStaticMethod("test", ImmutableList.of("I"), "I", + " .limit stack 1", + " .limit locals 1", + " iload 0", + " lookupswitch", + " " + key1 + " : case_1", + " " + key2 + " : case_2", + " default : case_default", + " case_1:", + " iconst_3", + " goto return_", + " case_2:", + " iconst_4", + " goto return_", + " case_default:", + " iconst_5", + " return_:", + " ireturn"); + + clazz.addMainMethod( + " .limit stack 2", + " .limit locals 1", + " getstatic java/lang/System/out Ljava/io/PrintStream;", + " ldc 2", + " invokestatic Test/test(I)I", + " invokevirtual java/io/PrintStream/print(I)V", + " return"); + + AndroidApp app = compileWithR8(builder); + + MethodSignature signature = new MethodSignature("test", "int", ImmutableList.of("int")); + DexEncodedMethod method = getMethod(app, "Test", signature); + DexCode code = method.getCode().asDexCode(); + if (SwitchRewritingTest.twoCaseWillUsePackedSwitch(key1, key2)) { + assertTrue(code.instructions[0] instanceof PackedSwitch); + } else { + if (key1 == 0) { + assertTrue(code.instructions[0] instanceof IfEqz); + } else { + // Const instruction before if. + assertTrue(code.instructions[1] instanceof IfEq); + } + } + } + + @Test + public void twoCaseSparseToPackedJar() throws Exception { + for (int delta = 1; delta <= 3; delta++) { + runTwoCaseSparseToPackedJarTest(0, delta); + runTwoCaseSparseToPackedJarTest(-delta, 0); + runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta); + runTwoCaseSparseToPackedJarTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE); + } + runTwoCaseSparseToPackedJarTest(-1, 1); + runTwoCaseSparseToPackedJarTest(-2, 1); + runTwoCaseSparseToPackedJarTest(-1, 2); + runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE); + runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE); + runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1); + } + + private void runLargerSwitchJarTest(int firstKey, int keyStep, int totalKeys, + Integer additionalLastKey) throws Exception { + JasminBuilder builder = new JasminBuilder(); + JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); + + StringBuilder switchSource = new StringBuilder(); + StringBuilder targetCode = new StringBuilder(); + for (int i = 0; i < totalKeys; i++) { + String caseLabel = "case_" + i; + switchSource.append(" " + (firstKey + i * keyStep) + " : " + caseLabel + "\n"); + targetCode.append(" " + caseLabel + ":\n"); + targetCode.append(" ldc " + i + "\n"); + targetCode.append(" goto return_\n"); + } + if (additionalLastKey != null) { + String caseLabel = "case_" + totalKeys; + switchSource.append(" " + additionalLastKey + " : " + caseLabel + "\n"); + targetCode.append(" " + caseLabel + ":\n"); + targetCode.append(" ldc " + totalKeys + "\n"); + targetCode.append(" goto return_\n"); + } + + clazz.addStaticMethod("test", ImmutableList.of("I"), "I", + " .limit stack 1", + " .limit locals 1", + " iload 0", + " lookupswitch", + switchSource.toString(), + " default : case_default", + targetCode.toString(), + " case_default:", + " iconst_5", + " return_:", + " ireturn"); + + clazz.addMainMethod( + " .limit stack 2", + " .limit locals 1", + " getstatic java/lang/System/out Ljava/io/PrintStream;", + " ldc 2", + " invokestatic Test/test(I)I", + " invokevirtual java/io/PrintStream/print(I)V", + " return"); + + AndroidApp app = compileWithR8(builder); + + MethodSignature signature = new MethodSignature("test", "int", ImmutableList.of("int")); + DexEncodedMethod method = getMethod(app, "Test", signature); + DexCode code = method.getCode().asDexCode(); + int packedSwitchCount = 0; + int sparseSwitchCount = 0; + for (Instruction instruction : code.instructions) { + if (instruction instanceof PackedSwitch) { + packedSwitchCount++; + } + if (instruction instanceof SparseSwitch) { + sparseSwitchCount++; + } + } + if (keyStep <= 2) { + assertEquals(1, packedSwitchCount); + assertEquals(0, sparseSwitchCount); + } else { + assertEquals(0, packedSwitchCount); + assertEquals(1, sparseSwitchCount); + } + } + + @Test + public void largerSwitchJar() throws Exception { + runLargerSwitchJarTest(0, 1, 100, null); + runLargerSwitchJarTest(0, 2, 100, null); + runLargerSwitchJarTest(0, 3, 100, null); + runLargerSwitchJarTest(100, 100, 100, null); + runLargerSwitchJarTest(-10000, 100, 100, null); + runLargerSwitchJarTest(-10000, 200, 100, 10000); + runLargerSwitchJarTest( + Integer.MIN_VALUE, (int) ((-(long) Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE); + + // This is the maximal value possible with Jasmin with the generated code above. It depends on + // the source, so making smaller source can raise this limit. However we never get close to the + // class file max. + runLargerSwitchJarTest(0, 1, 5503, null); + } + + private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs, + int expectedPackedSwitches, int expectedSparceSwitches) throws Exception { + JasminBuilder builder = new JasminBuilder(); + JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); + + StringBuilder x = new StringBuilder(); + StringBuilder y = new StringBuilder(); + for (Integer key : keys) { + x.append(key).append(" : case_").append(key).append("\n"); + y.append("case_").append(key).append(":\n"); + y.append(" ldc ").append(key).append("\n"); + y.append(" goto return_\n"); + } + + clazz.addStaticMethod("test", ImmutableList.of("I"), "I", + " .limit stack 1", + " .limit locals 1", + " iload_0", + " lookupswitch", + x.toString(), + " default : case_default", + y.toString(), + " case_default:", + " ldc " + defaultValue, + " return_:", + " ireturn"); + + // Add the Jasmin class and a class from Java source with the main method. + AndroidApp.Builder appBuilder = AndroidApp.builder(); + appBuilder.addClassProgramData(builder.buildClasses()); + appBuilder.addProgramFiles(FilteredClassPath + .unfiltered(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class))); + AndroidApp app = compileWithR8(appBuilder.build()); + + DexInspector inspector = new DexInspector(app); + MethodSubject method = inspector.clazz("Test").method("int", "test", ImmutableList.of("int")); + DexCode code = method.getMethod().getCode().asDexCode(); + + int packedSwitches = 0; + int sparseSwitches = 0; + int ifs = 0; + for (Instruction instruction : code.instructions) { + if (instruction instanceof PackedSwitch) { + packedSwitches++; + } + if (instruction instanceof SparseSwitch) { + sparseSwitches++; + } + if (instruction instanceof IfEq || instruction instanceof IfEqz) { + ifs++; + } + } + + assertEquals(expectedPackedSwitches, packedSwitches); + assertEquals(expectedSparceSwitches, sparseSwitches); + assertEquals(expectedIfs, ifs); + + // Run the code + List<String> args = keys.stream().map(Object::toString).collect(Collectors.toList()); + args.add(Integer.toString(defaultValue)); + runOnArt(app, CheckSwitchInTestClass.class, args); + } + + @Test + public void convertCasesToIf() throws Exception { + // Switches that are completely converted to ifs. + runConvertCasesToIf(ImmutableList.of(0, 1000), -100, 2, 0, 0); + runConvertCasesToIf(ImmutableList.of(0, 1000, 2000), -100, 3, 0, 0); + runConvertCasesToIf(ImmutableList.of(0, 1000, 2000, 3000), -100, 4, 0, 0); + + // Switches that are completely converted to ifs and one switch. + runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0); + runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1, 1, 0); + runConvertCasesToIf(ImmutableList.of( + Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0); + runConvertCasesToIf(ImmutableList.of( + 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1, 1, 0); + runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 1, 0); + runConvertCasesToIf(ImmutableList.of( + Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2, 1, 0); + + // Switches that are completely converted to ifs and two switches. + runConvertCasesToIf(ImmutableList.of( + 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 0, 2, 0); + runConvertCasesToIf(ImmutableList.of( + -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 1, 2, 0); + runConvertCasesToIf(ImmutableList.of( + -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 2, 0); + + // Switches that are completely converted two switches (one sparse and one packed). + runConvertCasesToIf(ImmutableList.of( + -1000, -900, -800, -700, -600, -500, -400, -300, + 1000, 1001, 1002, 1003, 1004, + 2000, 2100, 2200, 2300, 2400, 2500), -100, 0, 1, 1); + } +}
diff --git a/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java new file mode 100644 index 0000000..3d921b6 --- /dev/null +++ b/src/test/java/com/android/tools/r8/rewrite/switches/SwitchRewritingTest.java
@@ -0,0 +1,250 @@ +// Copyright (c) 2017, 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.rewrite.switches; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import com.android.tools.r8.code.Const; +import com.android.tools.r8.code.Const4; +import com.android.tools.r8.code.ConstHigh16; +import com.android.tools.r8.code.IfEq; +import com.android.tools.r8.code.IfEqz; +import com.android.tools.r8.code.Instruction; +import com.android.tools.r8.code.PackedSwitch; +import com.android.tools.r8.code.SparseSwitch; +import com.android.tools.r8.graph.DexCode; +import com.android.tools.r8.graph.DexEncodedMethod; +import com.android.tools.r8.smali.SmaliBuilder; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; +import com.android.tools.r8.smali.SmaliTestBase; +import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.util.function.Consumer; +import org.junit.Test; + +public class SwitchRewritingTest extends SmaliTestBase { + + static boolean twoCaseWillUsePackedSwitch(int key1, int key2) { + assert key1 != key2; + return Math.abs((long) key1 - (long) key2) == 1; + } + + private boolean some16BitConst(Instruction instruction) { + return instruction instanceof Const4 + || instruction instanceof ConstHigh16 + || instruction instanceof Const; + } + private void runSingleCaseDexTest(boolean packed, int key) { + SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); + String switchInstruction; + String switchData; + if (packed) { + switchInstruction = "packed-switch"; + switchData = StringUtils.join( + "\n", + " :switch_data", + " .packed-switch " + key, + " :case_0", + " .end packed-switch"); + } else { + switchInstruction = "sparse-switch"; + switchData = StringUtils.join( + "\n", + " :switch_data", + " .sparse-switch", + " " + key + " -> :case_0", + " .end sparse-switch"); + } + MethodSignature signature = builder.addStaticMethod( + "int", + DEFAULT_METHOD_NAME, + ImmutableList.of("int"), + 0, + " " + switchInstruction + " p0, :switch_data", + " const/4 p0, 0x5", + " goto :return", + " :case_0", + " const/4 p0, 0x3", + " :return", + " return p0", + switchData); + + builder.addMainMethod( + 2, + " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", + " const/4 v1, 0", + " invoke-static { v1 }, LTest;->method(I)I", + " move-result v1", + " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V", + " return-void" + ); + + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); + DexEncodedMethod method = getMethod(processedApplication, signature); + DexCode code = method.getCode().asDexCode(); + + if (key == 0) { + assertEquals(5, code.instructions.length); + assertTrue(code.instructions[0] instanceof IfEqz); + } else { + assertEquals(6, code.instructions.length); + assertTrue(some16BitConst(code.instructions[0])); + assertTrue(code.instructions[1] instanceof IfEq); + } + } + + @Test + public void singleCaseDex() { + for (boolean packed : new boolean[]{true, false}) { + runSingleCaseDexTest(packed, Integer.MIN_VALUE); + runSingleCaseDexTest(packed, -1); + runSingleCaseDexTest(packed, 0); + runSingleCaseDexTest(packed, 1); + runSingleCaseDexTest(packed, Integer.MAX_VALUE); + } + } + + private void runTwoCaseSparseToPackedOrIfsDexTest(int key1, int key2) { + SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); + + MethodSignature signature = builder.addStaticMethod( + "int", + DEFAULT_METHOD_NAME, + ImmutableList.of("int"), + 0, + " sparse-switch p0, :sparse_switch_data", + " const/4 v0, 0x5", + " goto :return", + " :case_1", + " const/4 v0, 0x3", + " goto :return", + " :case_2", + " const/4 v0, 0x4", + " :return", + " return v0", + " :sparse_switch_data", + " .sparse-switch", + " " + key1 + " -> :case_1", + " " + key2 + " -> :case_2", + " .end sparse-switch"); + + builder.addMainMethod( + 2, + " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", + " const/4 v1, 0", + " invoke-static { v1 }, LTest;->method(I)I", + " move-result v1", + " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V", + " return-void" + ); + + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); + DexEncodedMethod method = getMethod(processedApplication, signature); + DexCode code = method.getCode().asDexCode(); + if (twoCaseWillUsePackedSwitch(key1, key2)) { + assertTrue(code.instructions[0] instanceof PackedSwitch); + } else { + if (key1 == 0) { + assertTrue(code.instructions[0] instanceof IfEqz); + } else { + // Const instruction before if. + assertTrue(code.instructions[1] instanceof IfEq); + } + } + } + + @Test + public void twoCaseSparseToPackedOrIfsDex() { + for (int delta = 1; delta <= 3; delta++) { + runTwoCaseSparseToPackedOrIfsDexTest(0, delta); + runTwoCaseSparseToPackedOrIfsDexTest(-delta, 0); + runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta); + runTwoCaseSparseToPackedOrIfsDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE); + } + runTwoCaseSparseToPackedOrIfsDexTest(-1, 1); + runTwoCaseSparseToPackedOrIfsDexTest(-2, 1); + runTwoCaseSparseToPackedOrIfsDexTest(-1, 2); + runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE); + runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE); + runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1); + } + + private void runLargerSwitchDexTest(int firstKey, int keyStep, int totalKeys, + Integer additionalLastKey) throws Exception { + SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); + + StringBuilder switchSource = new StringBuilder(); + StringBuilder targetCode = new StringBuilder(); + for (int i = 0; i < totalKeys; i++) { + String caseLabel = "case_" + i; + switchSource.append(" " + (firstKey + i * keyStep) + " -> :" + caseLabel + "\n"); + targetCode.append(" :" + caseLabel + "\n"); + targetCode.append(" goto :return\n"); + } + if (additionalLastKey != null) { + String caseLabel = "case_" + totalKeys; + switchSource.append(" " + additionalLastKey + " -> :" + caseLabel + "\n"); + targetCode.append(" :" + caseLabel + "\n"); + targetCode.append(" goto :return\n"); + } + + MethodSignature signature = builder.addStaticMethod( + "void", + DEFAULT_METHOD_NAME, + ImmutableList.of("int"), + 0, + " sparse-switch p0, :sparse_switch_data", + " goto :return", + targetCode.toString(), + " :return", + " return-void", + " :sparse_switch_data", + " .sparse-switch", + switchSource.toString(), + " .end sparse-switch"); + + builder.addMainMethod( + 2, + " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", + " const/4 v1, 0", + " invoke-static { v1 }, LTest;->method(I)V", + " return-void" + ); + + Consumer<InternalOptions> optionsConsumer = options -> { + options.verbose = true; + options.printTimes = true; + }; + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication, optionsConsumer); + DexEncodedMethod method = getMethod(processedApplication, signature); + DexCode code = method.getCode().asDexCode(); + if (keyStep <= 2) { + assertTrue(code.instructions[0] instanceof PackedSwitch); + } else { + assertTrue(code.instructions[0] instanceof SparseSwitch); + } + } + + @Test + public void twoMonsterSparseToPackedDex() throws Exception { + runLargerSwitchDexTest(0, 1, 100, null); + runLargerSwitchDexTest(0, 2, 100, null); + runLargerSwitchDexTest(0, 3, 100, null); + runLargerSwitchDexTest(100, 100, 100, null); + runLargerSwitchDexTest(-10000, 100, 100, null); + runLargerSwitchDexTest(-10000, 200, 100, 10000); + runLargerSwitchDexTest( + Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE); + + // TODO(63090177): Currently this is commented out as R8 gets really slow for large switches. + // runLargerSwitchDexTest(0, 1, Constants.U16BIT_MAX, null); + } +} \ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java index 909f574..d2391ff 100644 --- a/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java +++ b/src/test/java/com/android/tools/r8/smali/CatchSuccessorFallthroughTest.java
@@ -6,13 +6,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexEncodedMethod; import com.android.tools.r8.ir.code.BasicBlock; import com.android.tools.r8.ir.code.IRCode; import com.android.tools.r8.ir.code.Phi; import com.android.tools.r8.ir.code.Return; import com.android.tools.r8.ir.code.Value; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; +import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.InternalOptions; import java.util.Arrays; import java.util.Collections; @@ -62,13 +63,11 @@ " invoke-virtual {v1, v0}, Ljava/io/PrintStream;->println(I)V", " return-void"); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - + AndroidApp originalApplication = buildApplication(builder); DexEncodedMethod method = getMethod(originalApplication, methodSig); // Get the IR pre-optimization. - IRCode code = method.buildIR(options); + IRCode code = method.buildIR(new InternalOptions()); // Find the exit block and assert that the value is a phi merging the exceptional edge // with the normal edge.
diff --git a/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java b/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java index 64aa946..de8b832 100644 --- a/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java +++ b/src/test/java/com/android/tools/r8/smali/ComputeBlockTryRangeTest.java
@@ -3,9 +3,9 @@ // BSD-style license that can be found in the LICENSE file. package com.android.tools.r8.smali; -import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; +import com.android.tools.r8.utils.AndroidApp; import java.util.Arrays; import java.util.Collections; import org.junit.Test; @@ -48,9 +48,8 @@ " goto :in_try" ); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); DexEncodedMethod method = getMethod(processedApplication, methodSig); assert method.getCode().asDexCode().tries.length > 0;
diff --git a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java index 8d4b841..0395177 100644 --- a/src/test/java/com/android/tools/r8/smali/JumboStringTest.java +++ b/src/test/java/com/android/tools/r8/smali/JumboStringTest.java
@@ -6,9 +6,8 @@ import static org.junit.Assert.assertEquals; -import com.android.tools.r8.ToolHelper; -import com.android.tools.r8.graph.DexApplication; -import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; +import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.StringUtils; import com.google.common.collect.ImmutableList; import org.junit.Test; @@ -56,10 +55,9 @@ " return-void" ); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(smaliBuilder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - String result = runArt(processedApplication, options); + AndroidApp originalApplication = buildApplication(smaliBuilder); + AndroidApp processedApplication = processApplication(originalApplication); + String result = runArt(processedApplication); assertEquals(expectedBuilder.toString(), result); }
diff --git a/src/test/java/com/android/tools/r8/smali/OutlineTest.java b/src/test/java/com/android/tools/r8/smali/OutlineTest.java index 1aa9be7..d7f16e8 100644 --- a/src/test/java/com/android/tools/r8/smali/OutlineTest.java +++ b/src/test/java/com/android/tools/r8/smali/OutlineTest.java
@@ -21,26 +21,26 @@ 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.errors.DexOverflowException; import com.android.tools.r8.graph.DexApplication; 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.DexMethod; 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.DexInspector; import com.android.tools.r8.utils.DexInspector.ClassSubject; import com.android.tools.r8.utils.DexInspector.MethodSubject; import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.InternalOptions.OutlineOptions; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.function.Consumer; import java.util.stream.Collectors; -import org.antlr.runtime.RecognitionException; import org.junit.Test; public class OutlineTest extends SmaliTestBase { @@ -52,6 +52,15 @@ return result; } + private Consumer<InternalOptions> configureOptions(Consumer<OutlineOptions> optionsConsumer) { + return options -> { + // Disable inlining to make sure that code looks as expected. + options.inlineAccessors = false; + // Also apply outline options. + optionsConsumer.accept(options.outline); + }; + } + DexEncodedMethod getInvokedMethod(DexApplication application, InvokeStatic invoke) { DexInspector inspector = new DexInspector(application); ClassSubject clazz = inspector.clazz(invoke.getMethod().holder.toSourceString()); @@ -62,31 +71,19 @@ invokedMethod.proto.returnType.toSourceString(), invokedMethod.name.toString(), Arrays.stream(invokedMethod.proto.parameters.values) - .map(p -> p.toSourceString()) + .map(DexType::toSourceString) .collect(Collectors.toList())); assertTrue(method.isPresent()); return method.getMethod(); } - String firstOutlineMethodName(InternalOptions options) { - StringBuilder builder = new StringBuilder(options.outline.className); - builder.append('.'); - builder.append(options.outline.methodPrefix); - builder.append("0"); - return builder.toString(); + private String firstOutlineMethodName() { + return OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX + "0"; } - MethodSignature firstOutlineMethodSignature( - String returnType, List<String> parameterTypes, InternalOptions options) { - return new MethodSignature( - options.outline.className, options.outline.methodPrefix + "0", returnType, parameterTypes); - } - - boolean isOutlineMethodName(InternalOptions options, String qualifiedName) { - StringBuilder builder = new StringBuilder(options.outline.className); - builder.append('.'); - builder.append(options.outline.methodPrefix); - return qualifiedName.indexOf(builder.toString()) == 0; + private boolean isOutlineMethodName(String qualifiedName) { + String qualifiedPrefix = OutlineOptions.CLASS_NAME + '.' + OutlineOptions.METHOD_PREFIX; + return qualifiedName.indexOf(qualifiedPrefix) == 0; } @Test @@ -127,14 +124,16 @@ ); for (int i = 2; i < 6; i++) { - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = i; - options.outline.maxSize = i; + final int j = i; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = j; + outline.maxSize = j; + }); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication, options); + assertEquals(2, getNumberOfProgramClasses(processedApplication)); // Return the processed method for inspection. DexEncodedMethod method = getMethod(processedApplication, signature); @@ -143,10 +142,10 @@ assertTrue(code.instructions[0] instanceof ConstString); assertTrue(code.instructions[1] instanceof InvokeStatic); InvokeStatic invoke = (InvokeStatic) code.instructions[1]; - assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName())); // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("TestTestTestTest", result); } } @@ -192,14 +191,16 @@ ); for (int i = 2; i < 6; i++) { - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = i; - options.outline.maxSize = i; + final int finalI = i; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = finalI; + outline.maxSize = finalI; + }); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication, options); + assertEquals(2, getNumberOfProgramClasses(processedApplication)); // Return the processed method for inspection. DexEncodedMethod method = getMethod(processedApplication, signature); @@ -213,10 +214,10 @@ } assertTrue(code.instructions[firstOutlineInvoke] instanceof InvokeStatic); InvokeStatic invoke = (InvokeStatic) code.instructions[firstOutlineInvoke]; - assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName())); // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("Test1Test2Test3Test4", result); } } @@ -258,11 +259,12 @@ " return-void" ); - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + Consumer<InternalOptions> options = configureOptions(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); @@ -271,10 +273,10 @@ assertTrue(code.instructions[0] instanceof ConstString); assertTrue(code.instructions[1] instanceof InvokeStatic); InvokeStatic invoke = (InvokeStatic) code.instructions[1]; - assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName())); // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("1", result); } @@ -322,11 +324,12 @@ " return-void" ); - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + Consumer<InternalOptions> options = configureOptions(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); @@ -336,10 +339,10 @@ assertTrue(code.instructions[1] instanceof ConstString); assertTrue(code.instructions[2] instanceof InvokeStatic); InvokeStatic invoke = (InvokeStatic) code.instructions[2]; - assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName())); // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("TestXTest1TestYTest2Test3", result); } @@ -381,14 +384,16 @@ ); for (int i = 2; i < 4; i++) { - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = i; - options.outline.maxSize = i; + final int finalI = i; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = finalI; + outline.maxSize = finalI; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplicationWithAndroidJar(builder); + AndroidApp processedApplication = processApplication(originalApplication, options); + assertEquals(2, getNumberOfProgramClasses(processedApplication)); // Return the processed method for inspection. DexEncodedMethod method = getMethod(processedApplication, signature); @@ -398,17 +403,17 @@ if (i < 3) { assertTrue(code.instructions[1] instanceof InvokeStatic); InvokeStatic invoke = (InvokeStatic) code.instructions[1]; - assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName())); + 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(options, invoke.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName())); } // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); StringBuilder resultBuilder = new StringBuilder(); for (int j = 0; j < 4; j++) { resultBuilder.append(0x7fffffff00000000L); @@ -455,14 +460,16 @@ ); for (int i = 2; i < 4; i++) { - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = i; - options.outline.maxSize = i; + final int finalI = i; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = finalI; + outline.maxSize = finalI; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplicationWithAndroidJar(builder); + AndroidApp processedApplication = processApplication(originalApplication, options); + assertEquals(2, getNumberOfProgramClasses(processedApplication)); // Return the processed method for inspection. DexEncodedMethod method = getMethod(processedApplication, signature); @@ -472,17 +479,17 @@ if (i < 3) { assertTrue(code.instructions[1] instanceof InvokeStatic); InvokeStatic invoke = (InvokeStatic) code.instructions[1]; - assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName())); + 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(options, invoke.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName())); } // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); StringBuilder resultBuilder = new StringBuilder(); for (int j = 0; j < 4; j++) { resultBuilder.append(1.0d); @@ -525,14 +532,16 @@ ); for (int i = 2; i < 6; i++) { - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = i; - options.outline.maxSize = i; + final int finalI = i; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = finalI; + outline.maxSize = finalI; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + 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); @@ -548,18 +557,18 @@ } if (i == 2) { InvokeStatic invoke = (InvokeStatic) mainCode.instructions[4]; - assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName())); } else if (i == 3) { InvokeStatic invoke = (InvokeStatic) mainCode.instructions[1]; - assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName())); } else { assert i == 4 || i == 5; InvokeStatic invoke = (InvokeStatic) mainCode.instructions[2]; - assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName())); } // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("1122", result); } } @@ -623,29 +632,30 @@ " return-void" ); - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = 7; - options.outline.maxSize = 7; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = 7; + outline.maxSize = 7; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + 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(options, invoke1.getMethod().qualifiedName())); + 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(options, invoke1.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName())); // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("Test1Test1Test1Test1Test2Test2Test2Test2", result); } @@ -687,14 +697,16 @@ ); for (int i = 2; i < 8; i++) { - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = i; - options.outline.maxSize = i; + final int finalI = i; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = finalI; + outline.maxSize = finalI; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplicationWithAndroidJar(builder); + AndroidApp processedApplication = processApplication(originalApplication, options); + assertEquals(2, getNumberOfProgramClasses(processedApplication)); DexCode code = getMethod(processedApplication, signature).getCode().asDexCode(); InvokeStatic invoke; @@ -711,10 +723,10 @@ outlineInstructionIndex = 2; } invoke = (InvokeStatic) code.instructions[outlineInstructionIndex]; - assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName())); // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("Test2Test2", result); } } @@ -744,23 +756,24 @@ " return-void" ); - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = 3; - options.outline.maxSize = 3; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = 3; + outline.maxSize = 3; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + 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(options, invoke.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName())); // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("", result); } @@ -817,18 +830,19 @@ " return-void" ); - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = 3; - options.outline.maxSize = 3; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = 3; + outline.maxSize = 3; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplicationWithAndroidJar(builder); + AndroidApp processedApplication = processApplication(originalApplication, options); + assertEquals(2, getNumberOfProgramClasses(processedApplication)); // Check that three outlining methods was created. DexInspector inspector = new DexInspector(processedApplication); - ClassSubject clazz = inspector.clazz(options.outline.className); + ClassSubject clazz = inspector.clazz(OutlineOptions.CLASS_NAME); assertTrue(clazz.isPresent()); assertEquals(3, clazz.getDexClass().directMethods().length); // Collect the return types of the putlines for the body of method1 and method2. @@ -842,12 +856,12 @@ assert r.size() == 2; DexType r1 = r.get(0); DexType r2 = r.get(1); - DexItemFactory factory = processedApplication.dexItemFactory; + DexItemFactory factory = inspector.getFactory(); assertTrue(r1 == factory.voidType && r2 == factory.stringType || r1 == factory.stringType && r2 == factory.voidType); // Run the code. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("TestTestTestTest", result); } @@ -888,37 +902,40 @@ " return-void" ); - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = 3; - options.outline.maxSize = 3; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = 3; + outline.maxSize = 3; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + 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. - DexApplication.Builder appBuilder = processedApplication.builder(); - originalApplication = appBuilder.build(); + originalApplication = processedApplication; processedApplication = processApplication(originalApplication, options); - assertEquals(i + 3, Iterables.size(processedApplication.classes())); + assertEquals(i + 3, getNumberOfProgramClasses(processedApplication)); } // Process the application several times. No more outlining as threshold has been raised. - options.outline.threshold = 2; + options = configureOptions(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. - DexApplication.Builder appBuilder = processedApplication.builder(); - originalApplication = appBuilder.build(); + originalApplication = processedApplication; processedApplication = processApplication(originalApplication, options); - assertEquals(count - 1 + 3, Iterables.size(processedApplication.classes())); + assertEquals(count - 1 + 3, getNumberOfProgramClasses(processedApplication)); } // Run the application with several levels of outlining. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("TestTestTestTest", result); } @@ -950,14 +967,15 @@ " return-void" ); - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = 5; - options.outline.maxSize = 5; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = 5; + outline.maxSize = 5; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplicationWithAndroidJar(builder); + AndroidApp processedApplication = processApplication(originalApplication, options); + assertEquals(2, getNumberOfProgramClasses(processedApplication)); // Return the processed method for inspection. DexEncodedMethod method = getMethod(processedApplication, signature); @@ -968,10 +986,10 @@ assertTrue(code.instructions[1] instanceof MoveResultWide); assertTrue(code.instructions[2] instanceof ReturnWide); InvokeStatic invoke = (InvokeStatic) code.instructions[0]; - assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName()); + assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName()); // Run the code and expect a parsable long. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); Long.parseLong(result); } @@ -1010,14 +1028,15 @@ " return-void" ); - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = 4; - options.outline.maxSize = 4; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = 4; + outline.maxSize = 4; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplicationWithAndroidJar(builder); + AndroidApp processedApplication = processApplication(originalApplication, options); + assertEquals(2, getNumberOfProgramClasses(processedApplication)); // Return the processed method for inspection. DexEncodedMethod method = getMethod(processedApplication, signature); @@ -1026,10 +1045,10 @@ assertTrue(code.instructions[0] instanceof InvokeStatic); assertTrue(code.instructions[1] instanceof ReturnObject); InvokeStatic invoke = (InvokeStatic) code.instructions[0]; - assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName()); + assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName()); // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("null", result); } @@ -1084,14 +1103,15 @@ " return-void" ); - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = 4; - options.outline.maxSize = 4; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = 4; + outline.maxSize = 4; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplicationWithAndroidJar(builder); + AndroidApp processedApplication = processApplication(originalApplication, options); + assertEquals(2, getNumberOfProgramClasses(processedApplication)); // Return the processed method for inspection. DexEncodedMethod method1 = getMethod(processedApplication, signature1); @@ -1101,7 +1121,7 @@ assertTrue(code1.instructions[1] instanceof MoveResult); assertTrue(code1.instructions[2] instanceof Return); InvokeStatic invoke1 = (InvokeStatic) code1.instructions[0]; - assertTrue(isOutlineMethodName(options, invoke1.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke1.getMethod().qualifiedName())); DexEncodedMethod method2 = getMethod(processedApplication, signature2); DexCode code2 = method2.getCode().asDexCode(); @@ -1110,7 +1130,7 @@ assertEquals(invoke1.getMethod().qualifiedName(), invoke2.getMethod().qualifiedName()); // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("44", result); } @@ -1159,14 +1179,15 @@ " return-void" ); - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = 3; // Outline add, sub and mul. - options.outline.maxSize = 3; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = 3; // Outline add, sub and mul. + outline.maxSize = 3; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplicationWithAndroidJar(builder); + AndroidApp processedApplication = processApplication(originalApplication, options); + assertEquals(2, getNumberOfProgramClasses(processedApplication)); // Return the processed method for inspection. DexEncodedMethod method = getMethod(processedApplication, signature); @@ -1180,10 +1201,10 @@ assertTrue(code.instructions[5] instanceof Const4); assertTrue(code.instructions[6] instanceof Return); InvokeStatic invoke = (InvokeStatic) code.instructions[1]; - assertTrue(isOutlineMethodName(options, invoke.getMethod().qualifiedName())); + assertTrue(isOutlineMethodName(invoke.getMethod().qualifiedName())); // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("4", result); } @@ -1211,14 +1232,15 @@ " return-void" ); - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = 3; - options.outline.maxSize = 3; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = 3; + outline.maxSize = 3; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplicationWithAndroidJar(builder); + AndroidApp processedApplication = processApplication(originalApplication, options); + assertEquals(2, getNumberOfProgramClasses(processedApplication)); // Return the processed method for inspection. DexEncodedMethod method = getMethod(processedApplication, signature); @@ -1227,10 +1249,10 @@ assertTrue(code.instructions[0] instanceof InvokeStatic); assertTrue(code.instructions[1] instanceof ReturnVoid); InvokeStatic invoke = (InvokeStatic) code.instructions[0]; - assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName()); + assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName()); // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("", result); } @@ -1256,14 +1278,15 @@ " return-void" ); - InternalOptions options = createInternalOptions(); - options.outline.threshold = 1; - options.outline.minSize = 3; - options.outline.maxSize = 3; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 1; + outline.minSize = 3; + outline.maxSize = 3; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplicationWithAndroidJar(builder); + AndroidApp processedApplication = processApplication(originalApplication, options); + assertEquals(2, getNumberOfProgramClasses(processedApplication)); // Return the processed method for inspection. DexEncodedMethod method = getMethod(processedApplication, signature); @@ -1272,10 +1295,10 @@ assertTrue(code.instructions[0] instanceof InvokeStatic); assertTrue(code.instructions[1] instanceof ReturnVoid); InvokeStatic invoke = (InvokeStatic) code.instructions[0]; - assertEquals(firstOutlineMethodName(options), invoke.getMethod().qualifiedName()); + assertEquals(firstOutlineMethodName(), invoke.getMethod().qualifiedName()); // Run code and check result. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals("", result); } @@ -1462,14 +1485,15 @@ " return-void" ); - InternalOptions options = createInternalOptions(); - options.outline.threshold = 2; + Consumer<InternalOptions> options = configureOptions(outline -> { + outline.threshold = 2; + }); - DexApplication originalApplication = buildApplicationWithAndroidJar(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(2, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplicationWithAndroidJar(builder); + AndroidApp processedApplication = processApplication(originalApplication, options); + assertEquals(2, getNumberOfProgramClasses(processedApplication)); // Verify the code. - runDex2Oat(processedApplication, options); + runDex2Oat(processedApplication); } }
diff --git a/src/test/java/com/android/tools/r8/smali/Regress38014736.java b/src/test/java/com/android/tools/r8/smali/Regress38014736.java index bf89f7f..1f0cd78 100644 --- a/src/test/java/com/android/tools/r8/smali/Regress38014736.java +++ b/src/test/java/com/android/tools/r8/smali/Regress38014736.java
@@ -5,9 +5,7 @@ import static org.junit.Assert.assertTrue; -import com.android.tools.r8.ToolHelper; -import com.android.tools.r8.graph.DexApplication; -import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.StringUtils; import org.junit.Test; @@ -48,10 +46,9 @@ "invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->println(Ljava/lang/Object;)V", "return-void"); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - String result = runArt(processedApplication, options); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); + String result = runArt(processedApplication); // The art runtime changed the way exceptions are printed. Therefore, we only check // for the type of the exception and that the message mentions null. assertTrue(result.startsWith(StringUtils.joinLines("0", "java.lang.NumberFormatException:")));
diff --git a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java index 214a3b4..4d13ae0 100644 --- a/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java +++ b/src/test/java/com/android/tools/r8/smali/RunArtSmokeTest.java
@@ -12,12 +12,11 @@ import com.android.tools.r8.code.InvokeVirtual; import com.android.tools.r8.code.ReturnVoid; import com.android.tools.r8.code.SgetObject; -import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexCode; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.utils.InternalOptions; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; +import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.StringUtils; -import com.google.common.collect.Iterables; import org.junit.Test; public class RunArtSmokeTest extends SmaliTestBase { @@ -34,10 +33,9 @@ " return-void" ); - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - assertEquals(1, Iterables.size(processedApplication.classes())); + AndroidApp originalApplication = buildApplication(builder); + AndroidApp processedApplication = processApplication(originalApplication); + assertEquals(1, getNumberOfProgramClasses(processedApplication)); // Return the processed method for inspection. DexEncodedMethod main = getMethod(processedApplication, mainSignature); @@ -50,7 +48,7 @@ assertTrue(code.instructions[3] instanceof ReturnVoid); // Run the generated code in Art. - String result = runArt(processedApplication, options); + String result = runArt(processedApplication); assertEquals(StringUtils.lines("Hello, world!"), result); } }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java index b3a4060..8e946b6 100644 --- a/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java +++ b/src/test/java/com/android/tools/r8/smali/SmaliBuildTest.java
@@ -7,20 +7,24 @@ import static org.junit.Assert.assertEquals; import com.android.tools.r8.ToolHelper; -import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.shaking.FilteredClassPath; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.DexInspector; import com.android.tools.r8.utils.DexInspector.ClassSubject; -import com.android.tools.r8.utils.InternalOptions; +import java.io.IOException; +import java.util.concurrent.ExecutionException; import org.junit.Test; public class SmaliBuildTest extends SmaliTestBase { - private void checkJavaLangString(DexApplication application, boolean present) { - DexInspector inspector = new DexInspector(application); - ClassSubject clazz = inspector.clazz("java.lang.String"); - assertEquals(present, clazz.isPresent()); + private void checkJavaLangString(AndroidApp application, boolean present) { + try { + DexInspector inspector = new DexInspector(application); + ClassSubject clazz = inspector.clazz("java.lang.String"); + assertEquals(present, clazz.isPresent()); + } catch (IOException | ExecutionException e) { + throw new RuntimeException(e); + } } @Test @@ -35,12 +39,11 @@ " return-void" ); - InternalOptions options = new InternalOptions(); // No libraries added - java.lang.String is not present. - DexApplication originalApplication = buildApplication(builder, options); + AndroidApp originalApplication = buildApplication(builder); checkJavaLangString(originalApplication, false); - DexApplication processedApplication = processApplication(originalApplication, options); + AndroidApp processedApplication = processApplication(originalApplication); checkJavaLangString(processedApplication, false); } @@ -56,17 +59,17 @@ " return-void" ); - InternalOptions options = new InternalOptions(); - AndroidApp app = AndroidApp.builder() + AndroidApp originalApp = AndroidApp.builder() .addDexProgramData(builder.compile()) .addLibraryFiles(FilteredClassPath.unfiltered(ToolHelper.getDefaultAndroidJar())) .build(); // Java standard library added - java.lang.String is present. - DexApplication originalApplication = buildApplication(app, options); - checkJavaLangString(originalApplication, true); + checkJavaLangString(originalApp, true); - DexApplication processedApplication = processApplication(originalApplication, options); - checkJavaLangString(processedApplication, true); + AndroidApp processedApplication = processApplication(originalApp); + + // The library method is not part of the output. + checkJavaLangString(processedApplication, false); } }
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java new file mode 100644 index 0000000..ad1e097 --- /dev/null +++ b/src/test/java/com/android/tools/r8/smali/SmaliBuilder.java
@@ -0,0 +1,349 @@ +// Copyright (c) 2017, 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 com.android.tools.r8.errors.DexOverflowException; +import com.android.tools.r8.utils.AndroidApp; +import com.android.tools.r8.utils.DescriptorUtils; +import com.android.tools.r8.utils.Smali; +import com.android.tools.r8.utils.StringUtils; +import com.google.common.collect.ImmutableList; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import org.antlr.runtime.RecognitionException; + +public class SmaliBuilder { + + public static class MethodSignature { + + public final String clazz; + public final String name; + public final String returnType; + public final List<String> parameterTypes; + + public MethodSignature(String clazz, String name, String returnType, + List<String> parameterTypes) { + this.clazz = clazz; + this.name = name; + this.returnType = returnType; + this.parameterTypes = parameterTypes; + } + + public static MethodSignature staticInitializer(String clazz) { + return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of()); + } + + @Override + public String toString() { + return returnType + " " + clazz + "." + name + + "(" + StringUtils.join(parameterTypes, ",") + ")"; + } + } + + abstract class Builder { + + String name; + String superName; + List<String> implementedInterfaces; + String sourceFile = null; + List<String> source = new ArrayList<>(); + + Builder(String name, String superName, List<String> implementedInterfaces) { + this.name = name; + this.superName = superName; + this.implementedInterfaces = implementedInterfaces; + } + + protected void appendSuper(StringBuilder builder) { + builder.append(".super "); + builder.append(DescriptorUtils.javaTypeToDescriptor(superName)); + builder.append("\n"); + } + + protected void appendImplementedInterfaces(StringBuilder builder) { + for (String implementedInterface : implementedInterfaces) { + builder.append(".implements "); + builder.append(DescriptorUtils.javaTypeToDescriptor(implementedInterface)); + builder.append("\n"); + } + } + + protected void writeSource(StringBuilder builder) { + for (String sourceLine : source) { + builder.append(sourceLine); + builder.append("\n"); + } + } + } + + public class ClassBuilder extends Builder { + + ClassBuilder(String name, String superName, List<String> implementedInterfaces) { + super(name, superName, implementedInterfaces); + } + + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(".class public "); + builder.append(DescriptorUtils.javaTypeToDescriptor(name)); + builder.append("\n"); + appendSuper(builder); + appendImplementedInterfaces(builder); + builder.append("\n"); + if (sourceFile != null) { + builder.append(".source \"").append(sourceFile).append("\"\n"); + } + writeSource(builder); + return builder.toString(); + } + } + + public class InterfaceBuilder extends Builder { + + InterfaceBuilder(String name, String superName) { + super(name, superName, ImmutableList.of()); + } + + public String toString() { + StringBuilder builder = new StringBuilder(); + builder.append(".class public interface abstract "); + builder.append(DescriptorUtils.javaTypeToDescriptor(name)); + builder.append("\n"); + appendSuper(builder); + appendImplementedInterfaces(builder); + builder.append("\n"); + writeSource(builder); + return builder.toString(); + } + } + + private String currentClassName; + private final Map<String, Builder> classes = new HashMap<>(); + + public SmaliBuilder() { + // No default class. + } + + public SmaliBuilder(String name) { + addClass(name); + } + + public SmaliBuilder(String name, String superName) { + addClass(name, superName); + } + + private List<String> getSource(String clazz) { + return classes.get(clazz).source; + } + + public String getCurrentClassName() { + return currentClassName; + } + + public String getCurrentClassDescriptor() { + return DescriptorUtils.javaTypeToDescriptor(currentClassName); + } + + public void addClass(String name) { + addClass(name, "java.lang.Object"); + } + + public void addClass(String name, String superName) { + addClass(name, superName, ImmutableList.of()); + } + + public void addClass(String name, String superName, List<String> implementedInterfaces) { + assert !classes.containsKey(name); + currentClassName = name; + classes.put(name, new ClassBuilder(name, superName, implementedInterfaces)); + } + + public void addInterface(String name) { + addInterface(name, "java.lang.Object"); + } + + public void addInterface(String name, String superName) { + assert !classes.containsKey(name); + currentClassName = name; + classes.put(name, new InterfaceBuilder(name, superName)); + } + + public void setSourceFile(String file) { + classes.get(currentClassName).sourceFile = file; + } + + public void addDefaultConstructor() { + String superDescriptor = + DescriptorUtils.javaTypeToDescriptor(classes.get(currentClassName).superName); + addMethodRaw( + " .method public constructor <init>()V", + " .locals 0", + " invoke-direct {p0}, " + superDescriptor + "-><init>()V", + " return-void", + " .end method" + ); + } + + public void addStaticField(String name, String type, String defaultValue) { + StringBuilder builder = new StringBuilder(); + builder.append(".field static "); + builder.append(name); + builder.append(":"); + builder.append(type); + if (defaultValue != null) { + builder.append(" = "); + if (type.equals("Ljava/lang/String;")) { + builder.append('"'); + builder.append(defaultValue); + builder.append('"'); + } else { + builder.append(defaultValue); + } + } + getSource(currentClassName).add(builder.toString()); + } + + public void addStaticField(String name, String type) { + addStaticField(name, type, null); + } + + public void addInstanceField(String name, String type) { + StringBuilder builder = new StringBuilder(); + builder.append(".field "); + builder.append(name); + builder.append(":"); + builder.append(type); + getSource(currentClassName).add(builder.toString()); + } + + private MethodSignature addMethod(String flags, String returnType, String name, + List<String> parameters, int locals, String code) { + StringBuilder builder = new StringBuilder(); + builder.append(".method "); + if (flags != null && flags.length() > 0) { + builder.append(flags); + builder.append(" "); + } + builder.append(name); + builder.append("("); + for (String parameter : parameters) { + builder.append(DescriptorUtils.javaTypeToDescriptor(parameter)); + } + builder.append(")"); + builder.append(DescriptorUtils.javaTypeToDescriptor(returnType)); + builder.append("\n"); + if (locals >= 0) { + builder.append(".locals "); + builder.append(locals); + builder.append("\n\n"); + assert code != null; + builder.append(code); + } else { + assert code == null; + } + builder.append(".end method"); + getSource(currentClassName).add(builder.toString()); + return new MethodSignature(currentClassName, name, returnType, parameters); + } + + public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters, + int locals, String... instructions) { + StringBuilder builder = new StringBuilder(); + for (String instruction : instructions) { + builder.append(instruction); + builder.append("\n"); + } + return addStaticMethod(returnType, name, parameters, locals, builder.toString()); + } + + public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters, + int locals, String code) { + return addStaticMethod("", returnType, name, parameters, locals, code); + } + + public MethodSignature addStaticInitializer(int locals, String... instructions) { + StringBuilder builder = new StringBuilder(); + for (String instruction : instructions) { + builder.append(instruction); + builder.append("\n"); + } + return addStaticInitializer(locals, builder.toString()); + } + + public MethodSignature addStaticInitializer(int locals, String code) { + return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code); + } + + private MethodSignature addStaticMethod(String flags, String returnType, String name, + List<String> parameters, int locals, String code) { + StringBuilder builder = new StringBuilder(); + return addMethod("public static " + flags, returnType, name, parameters, locals, code); + } + + public MethodSignature addAbstractMethod( + String returnType, String name, List<String> parameters) { + return addMethod("public abstract", returnType, name, parameters, -1, null); + } + + public MethodSignature addInstanceMethod(String returnType, String name, + List<String> parameters, + int locals, String... instructions) { + StringBuilder builder = new StringBuilder(); + for (String instruction : instructions) { + builder.append(instruction); + builder.append("\n"); + } + return addInstanceMethod(returnType, name, parameters, locals, builder.toString()); + } + + public MethodSignature addInstanceMethod(String returnType, String name, + List<String> parameters, + int locals, String code) { + return addMethod("public", returnType, name, parameters, locals, code); + } + + public MethodSignature addMainMethod(int locals, String... instructions) { + return addStaticMethod( + "void", "main", Collections.singletonList("java.lang.String[]"), locals, instructions); + } + + public void addMethodRaw(String... source) { + StringBuilder builder = new StringBuilder(); + for (String line : source) { + builder.append(line); + builder.append("\n"); + } + getSource(currentClassName).add(builder.toString()); + } + + public List<String> buildSource() { + List<String> result = new ArrayList<>(classes.size()); + for (String clazz : classes.keySet()) { + Builder classBuilder = classes.get(clazz); + result.add(classBuilder.toString()); + } + return result; + } + + public byte[] compile() + throws IOException, RecognitionException, DexOverflowException, ExecutionException { + return Smali.compile(buildSource()); + } + + public AndroidApp build() + throws IOException, RecognitionException, DexOverflowException, ExecutionException { + return AndroidApp.fromDexProgramData(compile()); + } + + + @Override + public String toString() { + return String.join("\n\n", buildSource()); + } +}
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java index b35e768..3fe8da9 100644 --- a/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java +++ b/src/test/java/com/android/tools/r8/smali/SmaliDisassembleTest.java
@@ -6,16 +6,12 @@ import static org.junit.Assert.assertEquals; -import com.android.tools.r8.dex.ApplicationReader; import com.android.tools.r8.errors.DexOverflowException; -import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.SmaliWriter; import com.android.tools.r8.utils.AndroidApp; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.Smali; -import com.android.tools.r8.utils.Timing; import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import java.io.IOException; import java.util.Collections; import java.util.concurrent.ExecutionException; @@ -27,12 +23,7 @@ // Run the provided smali through R8 smali disassembler and expect the exact same output. void roundTripRawSmali(String smali) { try { - DexApplication application = - new ApplicationReader( - AndroidApp.fromDexProgramData(Smali.compile(smali)), - new InternalOptions(), - new Timing("SmaliTest")) - .read(); + AndroidApp application = AndroidApp.fromDexProgramData(Smali.compile(smali)); assertEquals(smali, SmaliWriter.smali(application, new InternalOptions())); } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) { throw new RuntimeException(e); @@ -41,7 +32,7 @@ @Test public void simpleSmokeTest() { - DexApplication application = singleMethodApplication( + AndroidApp application = singleMethodApplication( "int", Collections.singletonList("int"), 4, " const/4 v0, 1 ", @@ -81,7 +72,7 @@ @Test public void sparseSwitchTest() { - DexApplication application = singleMethodApplication( + AndroidApp application = singleMethodApplication( "int", Collections.singletonList("int"), 0, " sparse-switch v0, :sparse_switch_data", @@ -137,7 +128,7 @@ @Test public void packedSwitchTest() { - DexApplication application = singleMethodApplication( + AndroidApp application = singleMethodApplication( "int", Collections.singletonList("int"), 0, " packed-switch v0, :packed_switch_data", @@ -193,7 +184,7 @@ @Test public void fillArrayDataTest8Bit() { - DexApplication application = singleMethodApplication( + AndroidApp application = singleMethodApplication( "int[]", ImmutableList.of(), 2, " const/4 v1, 3", @@ -236,7 +227,7 @@ @Test public void fillArrayDataTest16Bit() { - DexApplication application = singleMethodApplication( + AndroidApp application = singleMethodApplication( "int[]", ImmutableList.of(), 2, " const/4 v1, 3", @@ -279,7 +270,7 @@ @Test public void fillArrayDataTest32Bit() { - DexApplication application = singleMethodApplication( + AndroidApp application = singleMethodApplication( "int[]", ImmutableList.of(), 2, " const/4 v1, 3", @@ -322,7 +313,7 @@ @Test public void fillArrayDataTest64Bit() { - DexApplication application = singleMethodApplication( + AndroidApp application = singleMethodApplication( "int[]", ImmutableList.of(), 2, " const/4 v1, 3", @@ -368,8 +359,7 @@ SmaliBuilder builder = new SmaliBuilder(); builder.addInterface("Test"); builder.addAbstractMethod("int", "test", ImmutableList.of()); - DexApplication application = buildApplication(builder); - assertEquals(1, Iterables.size(application.classes())); + AndroidApp application = buildApplication(builder); String expected = ".class public interface abstract LTest;\n" + @@ -391,8 +381,7 @@ SmaliBuilder builder = new SmaliBuilder(); builder.addClass("Test", "java.lang.Object", ImmutableList.of("java.util.List")); builder.addAbstractMethod("int", "test", ImmutableList.of()); - DexApplication application = buildApplication(builder); - assertEquals(1, Iterables.size(application.classes())); + AndroidApp application = buildApplication(builder); String expected = ".class public LTest;\n" +
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java index 2d2f887..4949153 100644 --- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java +++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -8,8 +8,8 @@ import com.android.tools.r8.CompilationException; import com.android.tools.r8.CompilationMode; -import com.android.tools.r8.R8; import com.android.tools.r8.R8Command; +import com.android.tools.r8.Resource; import com.android.tools.r8.TestBase; import com.android.tools.r8.ToolHelper; import com.android.tools.r8.dex.ApplicationReader; @@ -18,39 +18,21 @@ import com.android.tools.r8.graph.DexApplication; import com.android.tools.r8.graph.DexClass; import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.ir.code.BasicBlock; -import com.android.tools.r8.ir.code.IRCode; -import com.android.tools.r8.ir.code.Instruction; -import com.android.tools.r8.ir.code.InstructionListIterator; -import com.android.tools.r8.ir.code.ValueNumberGenerator; -import com.android.tools.r8.ir.conversion.IRConverter; -import com.android.tools.r8.naming.NamingLens; import com.android.tools.r8.shaking.FilteredClassPath; import com.android.tools.r8.shaking.ProguardConfiguration; +import com.android.tools.r8.smali.SmaliBuilder.MethodSignature; import com.android.tools.r8.utils.AndroidApp; -import com.android.tools.r8.utils.AndroidAppOutputSink; -import com.android.tools.r8.utils.DescriptorUtils; import com.android.tools.r8.utils.DexInspector; import com.android.tools.r8.utils.DexInspector.ClassSubject; -import com.android.tools.r8.utils.DexInspector.MethodSubject; import com.android.tools.r8.utils.InternalOptions; import com.android.tools.r8.utils.OutputMode; -import com.android.tools.r8.utils.Smali; -import com.android.tools.r8.utils.StringUtils; import com.android.tools.r8.utils.Timing; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; +import java.util.Collection; import java.util.List; -import java.util.ListIterator; -import java.util.Map; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executors; import java.util.function.Consumer; import org.antlr.runtime.RecognitionException; @@ -60,414 +42,20 @@ public static final String DEFAULT_MAIN_CLASS_NAME = DEFAULT_CLASS_NAME; public static final String DEFAULT_METHOD_NAME = "method"; - public static class MethodSignature { - - public final String clazz; - public final String name; - public final String returnType; - public final List<String> parameterTypes; - - public MethodSignature(String clazz, String name, String returnType, - List<String> parameterTypes) { - this.clazz = clazz; - this.name = name; - this.returnType = returnType; - this.parameterTypes = parameterTypes; - } - - public static MethodSignature staticInitializer(String clazz) { - return new MethodSignature(clazz, "<clinit>", "void", ImmutableList.of()); - } - - @Override - public String toString() { - return returnType + " " + clazz + "." + name - + "(" + StringUtils.join(parameterTypes, ",") + ")"; - } - } - - public static class SmaliBuilder { - - abstract class Builder { - - String name; - String superName; - List<String> implementedInterfaces; - String sourceFile = null; - List<String> source = new ArrayList<>(); - - Builder(String name, String superName, List<String> implementedInterfaces) { - this.name = name; - this.superName = superName; - this.implementedInterfaces = implementedInterfaces; - } - - protected void appendSuper(StringBuilder builder) { - builder.append(".super "); - builder.append(DescriptorUtils.javaTypeToDescriptor(superName)); - builder.append("\n"); - } - - protected void appendImplementedInterfaces(StringBuilder builder) { - for (String implementedInterface : implementedInterfaces) { - builder.append(".implements "); - builder.append(DescriptorUtils.javaTypeToDescriptor(implementedInterface)); - builder.append("\n"); - } - } - - protected void writeSource(StringBuilder builder) { - for (String sourceLine : source) { - builder.append(sourceLine); - builder.append("\n"); - } - } - } - - public class ClassBuilder extends Builder { - - ClassBuilder(String name, String superName, List<String> implementedInterfaces) { - super(name, superName, implementedInterfaces); - } - - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(".class public "); - builder.append(DescriptorUtils.javaTypeToDescriptor(name)); - builder.append("\n"); - appendSuper(builder); - appendImplementedInterfaces(builder); - builder.append("\n"); - if (sourceFile != null) { - builder.append(".source \"").append(sourceFile).append("\"\n"); - } - writeSource(builder); - return builder.toString(); - } - } - - public class InterfaceBuilder extends Builder { - - InterfaceBuilder(String name, String superName) { - super(name, superName, ImmutableList.of()); - } - - public String toString() { - StringBuilder builder = new StringBuilder(); - builder.append(".class public interface abstract "); - builder.append(DescriptorUtils.javaTypeToDescriptor(name)); - builder.append("\n"); - appendSuper(builder); - appendImplementedInterfaces(builder); - builder.append("\n"); - writeSource(builder); - return builder.toString(); - } - } - - private String currentClassName; - private final Map<String, Builder> classes = new HashMap<>(); - - public SmaliBuilder() { - // No default class. - } - - public SmaliBuilder(String name) { - addClass(name); - } - - public SmaliBuilder(String name, String superName) { - addClass(name, superName); - } - - private List<String> getSource(String clazz) { - return classes.get(clazz).source; - } - - public String getCurrentClassName() { - return currentClassName; - } - - public String getCurrentClassDescriptor() { - return DescriptorUtils.javaTypeToDescriptor(currentClassName); - } - - public void addClass(String name) { - addClass(name, "java.lang.Object"); - } - - public void addClass(String name, String superName) { - addClass(name, superName, ImmutableList.of()); - } - - public void addClass(String name, String superName, List<String> implementedInterfaces) { - assert !classes.containsKey(name); - currentClassName = name; - classes.put(name, new ClassBuilder(name, superName, implementedInterfaces)); - } - - public void addInterface(String name) { - addInterface(name, "java.lang.Object"); - } - - public void addInterface(String name, String superName) { - assert !classes.containsKey(name); - currentClassName = name; - classes.put(name, new InterfaceBuilder(name, superName)); - } - - public void setSourceFile(String file) { - classes.get(currentClassName).sourceFile = file; - } - - public void addDefaultConstructor() { - String superDescriptor = - DescriptorUtils.javaTypeToDescriptor(classes.get(currentClassName).superName); - addMethodRaw( - " .method public constructor <init>()V", - " .locals 0", - " invoke-direct {p0}, " + superDescriptor + "-><init>()V", - " return-void", - " .end method" - ); - } - - public void addStaticField(String name, String type, String defaultValue) { - StringBuilder builder = new StringBuilder(); - builder.append(".field static "); - builder.append(name); - builder.append(":"); - builder.append(type); - if (defaultValue != null) { - builder.append(" = "); - if (type.equals("Ljava/lang/String;")) { - builder.append('"'); - builder.append(defaultValue); - builder.append('"'); - } else { - builder.append(defaultValue); - } - } - getSource(currentClassName).add(builder.toString()); - } - - public void addStaticField(String name, String type) { - addStaticField(name, type, null); - } - - public void addInstanceField(String name, String type) { - StringBuilder builder = new StringBuilder(); - builder.append(".field "); - builder.append(name); - builder.append(":"); - builder.append(type); - getSource(currentClassName).add(builder.toString()); - } - - private MethodSignature addMethod(String flags, String returnType, String name, - List<String> parameters, int locals, String code) { - StringBuilder builder = new StringBuilder(); - builder.append(".method "); - if (flags != null && flags.length() > 0) { - builder.append(flags); - builder.append(" "); - } - builder.append(name); - builder.append("("); - for (String parameter : parameters) { - builder.append(DescriptorUtils.javaTypeToDescriptor(parameter)); - } - builder.append(")"); - builder.append(DescriptorUtils.javaTypeToDescriptor(returnType)); - builder.append("\n"); - if (locals >= 0) { - builder.append(".locals "); - builder.append(locals); - builder.append("\n\n"); - assert code != null; - builder.append(code); - } else { - assert code == null; - } - builder.append(".end method"); - getSource(currentClassName).add(builder.toString()); - return new MethodSignature(currentClassName, name, returnType, parameters); - } - - public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters, - int locals, String... instructions) { - StringBuilder builder = new StringBuilder(); - for (String instruction : instructions) { - builder.append(instruction); - builder.append("\n"); - } - return addStaticMethod(returnType, name, parameters, locals, builder.toString()); - } - - public MethodSignature addStaticMethod(String returnType, String name, List<String> parameters, - int locals, String code) { - return addStaticMethod("", returnType, name, parameters, locals, code); - } - - public MethodSignature addStaticInitializer(int locals, String... instructions) { - StringBuilder builder = new StringBuilder(); - for (String instruction : instructions) { - builder.append(instruction); - builder.append("\n"); - } - return addStaticInitializer(locals, builder.toString()); - } - - public MethodSignature addStaticInitializer(int locals, String code) { - return addStaticMethod("constructor", "void", "<clinit>", ImmutableList.of(), locals, code); - } - - private MethodSignature addStaticMethod(String flags, String returnType, String name, - List<String> parameters, int locals, String code) { - StringBuilder builder = new StringBuilder(); - return addMethod("public static " + flags, returnType, name, parameters, locals, code); - } - - public MethodSignature addAbstractMethod( - String returnType, String name, List<String> parameters) { - return addMethod("public abstract", returnType, name, parameters, -1, null); - } - - public MethodSignature addInstanceMethod(String returnType, String name, - List<String> parameters, - int locals, String... instructions) { - StringBuilder builder = new StringBuilder(); - for (String instruction : instructions) { - builder.append(instruction); - builder.append("\n"); - } - return addInstanceMethod(returnType, name, parameters, locals, builder.toString()); - } - - public MethodSignature addInstanceMethod(String returnType, String name, - List<String> parameters, - int locals, String code) { - return addMethod("public", returnType, name, parameters, locals, code); - } - - public MethodSignature addMainMethod(int locals, String... instructions) { - return addStaticMethod( - "void", "main", Collections.singletonList("java.lang.String[]"), locals, instructions); - } - - public void addMethodRaw(String... source) { - StringBuilder builder = new StringBuilder(); - for (String line : source) { - builder.append(line); - builder.append("\n"); - } - getSource(currentClassName).add(builder.toString()); - } - - public List<String> buildSource() { - List<String> result = new ArrayList<>(classes.size()); - for (String clazz : classes.keySet()) { - Builder classBuilder = classes.get(clazz); - result.add(classBuilder.toString()); - } - return result; - } - - public byte[] compile() - throws IOException, RecognitionException, DexOverflowException, ExecutionException { - return Smali.compile(buildSource()); - } - - public AndroidApp build() - throws IOException, RecognitionException, DexOverflowException, ExecutionException { - return AndroidApp.fromDexProgramData(compile()); - } - - - @Override - public String toString() { - return String.join("\n\n", buildSource()); - } - } - - public class TestApplication { - - public final DexApplication application; - public final DexEncodedMethod method; - public final IRCode code; - public final List<IRCode> additionalCode; - public final ValueNumberGenerator valueNumberGenerator; - public final InternalOptions options; - - public TestApplication( - DexApplication application, - DexEncodedMethod method, - IRCode code, - ValueNumberGenerator valueNumberGenerator, - InternalOptions options) { - this(application, method, code, null, valueNumberGenerator, options); - } - - public TestApplication( - DexApplication application, - DexEncodedMethod method, - IRCode code, - List<IRCode> additionalCode, - ValueNumberGenerator valueNumberGenerator, - InternalOptions options) { - this.application = application; - this.method = method; - this.code = code; - this.additionalCode = additionalCode; - this.valueNumberGenerator = valueNumberGenerator; - this.options = options; - } - - public int countArgumentInstructions() { - int count = 0; - ListIterator<Instruction> iterator = code.blocks.get(0).listIterator(); - while (iterator.next().isArgument()) { - count++; - } - return count; - } - - public InstructionListIterator listIteratorAt(BasicBlock block, int index) { - InstructionListIterator iterator = block.listIterator(); - for (int i = 0; i < index; i++) { - iterator.next(); - } - return iterator; - } - - public String run() throws DexOverflowException { - AppInfo appInfo = new AppInfo(application); - IRConverter converter = new IRConverter(appInfo, options); - converter.replaceCodeForTesting(method, code); - return runArt(application, options); - } - } - - protected DexApplication buildApplication(SmaliBuilder builder) { - return buildApplication(builder, new InternalOptions()); - } - - protected DexApplication buildApplication(SmaliBuilder builder, InternalOptions options) { + protected AndroidApp buildApplication(SmaliBuilder builder) { try { - return buildApplication(AndroidApp.fromDexProgramData(builder.compile()), options); - } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) { + return AndroidApp.fromDexProgramData(builder.compile()); + } catch (IOException | RecognitionException | DexOverflowException | ExecutionException e) { throw new RuntimeException(e); } } - protected DexApplication buildApplicationWithAndroidJar( - SmaliBuilder builder, InternalOptions options) { + protected AndroidApp buildApplicationWithAndroidJar(SmaliBuilder builder) { try { - AndroidApp input = AndroidApp.builder() + return AndroidApp.builder() .addDexProgramData(builder.compile()) .addLibraryFiles(FilteredClassPath.unfiltered(ToolHelper.getDefaultAndroidJar())) .build(); - return buildApplication(input, options); } catch (IOException | RecognitionException | ExecutionException | DexOverflowException e) { throw new RuntimeException(e); } @@ -482,10 +70,15 @@ } } - protected DexApplication processApplication(DexApplication application, InternalOptions options) { + protected AndroidApp processApplication(AndroidApp application) { + return processApplication(application, null); + } + + protected AndroidApp processApplication(AndroidApp application, + Consumer<InternalOptions> optionsConsumer) { try { - return ToolHelper.optimizeWithR8(application, options); - } catch (IOException | CompilationException | ExecutionException e) { + return ToolHelper.runR8(application, optionsConsumer); + } catch (IOException | CompilationException e) { throw new RuntimeException(e); } } @@ -535,29 +128,6 @@ } } - protected DexEncodedMethod getMethod( - DexInspector inspector, - String className, - String returnType, - String methodName, - List<String> parameters) { - ClassSubject clazz = inspector.clazz(className); - assertTrue(clazz.isPresent()); - MethodSubject method = clazz.method(returnType, methodName, parameters); - assertTrue(method.isPresent()); - return method.getMethod(); - } - - protected DexEncodedMethod getMethod( - DexApplication application, - String className, - String returnType, - String methodName, - List<String> parameters) { - DexInspector inspector = new DexInspector(application); - return getMethod(inspector, className, returnType, methodName, parameters); - } - protected DexEncodedMethod getMethod(Path appPath, MethodSignature signature) { try { DexInspector inspector = new DexInspector(appPath); @@ -572,7 +142,7 @@ } } - protected DexEncodedMethod getMethod(DexApplication application, MethodSignature signature) { + protected DexEncodedMethod getMethod(AndroidApp application, MethodSignature signature) { return getMethod(application, signature.clazz, signature.returnType, signature.name, signature.parameterTypes); } @@ -588,16 +158,45 @@ * @param instructions instructions for the method * @return the processed method for inspection */ - public DexApplication singleMethodApplication(String returnType, List<String> parameters, + public AndroidApp singleMethodApplication(String returnType, List<String> parameters, int locals, String... instructions) { // Build a one class method. SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); builder.addStaticMethod(returnType, DEFAULT_METHOD_NAME, parameters, locals, instructions); // Read the one class method as an application. - DexApplication application = buildApplication(builder); - assertEquals(1, Iterables.size(application.classes())); - return application; + return buildApplication(builder); + } + + private int getNumberOfClassesForResources(Iterable<Resource> resources) { + int count = 0; + for (Resource resource : resources) { + Collection<String> descriptors = resource.getClassDescriptors(); + if (descriptors == null) { + throw new IllegalStateException("Cannot count classes in application without descriptors."); + } + count += descriptors.size(); + } + return count; + } + + protected int getNumberOfProgramClasses(AndroidApp application) { + try { + return getNumberOfClassesForResources(application.getClassProgramResources()) + + getNumberOfClassesForResources(application.getDexProgramResources()); + } catch (IOException e) { + return -1; + } + } + + protected AppInfo getAppInfo(AndroidApp application) { + try { + DexApplication dexApplication = new ApplicationReader(application, new InternalOptions(), + new Timing("SmaliTest.getAppInfo")).read(); + return new AppInfo(dexApplication); + } catch (IOException | ExecutionException e) { + throw new RuntimeException(e); + } } /** @@ -616,30 +215,29 @@ InternalOptions options = new InternalOptions(); // Build a one class application. - DexApplication application = singleMethodApplication( + AndroidApp application = singleMethodApplication( returnType, parameters, locals, instructions); // Process the application with R8. - DexApplication processdApplication = processApplication(application, options); - assertEquals(1, Iterables.size(processdApplication.classes())); + AndroidApp processdApplication = processApplication(application); + assertEquals(1, getNumberOfProgramClasses(processdApplication)); // Return the processed method for inspection. return getMethod( processdApplication, DEFAULT_CLASS_NAME, returnType, DEFAULT_METHOD_NAME, parameters); } - public String runArt(DexApplication application, InternalOptions options) - throws DexOverflowException { - return runArt(application, options, DEFAULT_MAIN_CLASS_NAME); + public String runArt(AndroidApp application) throws DexOverflowException { + return runArt(application, DEFAULT_MAIN_CLASS_NAME); } - public String runArt(DexApplication application, InternalOptions options, String mainClass) + + public String runArt(AndroidApp application, String mainClass) throws DexOverflowException { try { - AndroidApp app = writeDex(application, options); Path out = temp.getRoot().toPath().resolve("run-art-input.zip"); // TODO(sgjesse): Pass in a unique temp directory for each run. - app.writeToZip(out, OutputMode.Indexed); + application.writeToZip(out, OutputMode.Indexed); return ToolHelper.runArtNoVerificationErrors(out.toString(), mainClass); } catch (IOException e) { throw new RuntimeException(e); @@ -654,36 +252,15 @@ } } - public void runDex2Oat(DexApplication application, InternalOptions options) + public void runDex2Oat(AndroidApp application) throws DexOverflowException { try { - AndroidApp app = writeDex(application, options); Path dexOut = temp.getRoot().toPath().resolve("run-dex2oat-input.zip"); Path oatFile = temp.getRoot().toPath().resolve("oat-file"); - app.writeToZip(dexOut, OutputMode.Indexed); + application.writeToZip(dexOut, OutputMode.Indexed); ToolHelper.runDex2Oat(dexOut, oatFile); } catch (IOException e) { throw new RuntimeException(e); } } - - public AndroidApp writeDex(DexApplication application, InternalOptions options) - throws DexOverflowException { - try { - AndroidAppOutputSink compatSink = new AndroidAppOutputSink(); - R8.writeApplication( - Executors.newSingleThreadExecutor(), - application, - compatSink, - null, - NamingLens.getIdentityLens(), - null, - options); - compatSink.close(); - return compatSink.build(); - } catch (ExecutionException | IOException e) { - throw new RuntimeException(e); - } - } - }
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java deleted file mode 100644 index 3fc329c..0000000 --- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java +++ /dev/null
@@ -1,570 +0,0 @@ -// 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 org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; - -import com.android.tools.r8.ToolHelper; -import com.android.tools.r8.code.Const; -import com.android.tools.r8.code.Const4; -import com.android.tools.r8.code.ConstHigh16; -import com.android.tools.r8.code.IfEq; -import com.android.tools.r8.code.IfEqz; -import com.android.tools.r8.code.Instruction; -import com.android.tools.r8.code.PackedSwitch; -import com.android.tools.r8.code.SparseSwitch; -import com.android.tools.r8.graph.DexApplication; -import com.android.tools.r8.graph.DexCode; -import com.android.tools.r8.graph.DexEncodedMethod; -import com.android.tools.r8.jasmin.JasminBuilder; -import com.android.tools.r8.shaking.FilteredClassPath; -import com.android.tools.r8.utils.AndroidApp; -import com.android.tools.r8.utils.DexInspector; -import com.android.tools.r8.utils.DexInspector.MethodSubject; -import com.android.tools.r8.utils.InternalOptions; -import com.android.tools.r8.utils.StringUtils; -import com.google.common.collect.ImmutableList; -import java.util.List; -import java.util.stream.Collectors; -import org.junit.Test; - -public class SwitchRewritingTest extends SmaliTestBase { - - private boolean twoCaseWillUsePackedSwitch(int key1, int key2) { - assert key1 != key2; - return Math.abs((long) key1 - (long) key2) == 1; - } - - private boolean some16BitConst(Instruction instruction) { - return instruction instanceof Const4 - || instruction instanceof ConstHigh16 - || instruction instanceof Const; - } - private void runSingleCaseDexTest(boolean packed, int key) { - SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); - String switchInstruction; - String switchData; - if (packed) { - switchInstruction = "packed-switch"; - switchData = StringUtils.join( - "\n", - " :switch_data", - " .packed-switch " + key, - " :case_0", - " .end packed-switch"); - } else { - switchInstruction = "sparse-switch"; - switchData = StringUtils.join( - "\n", - " :switch_data", - " .sparse-switch", - " " + key + " -> :case_0", - " .end sparse-switch"); - } - MethodSignature signature = builder.addStaticMethod( - "int", - DEFAULT_METHOD_NAME, - ImmutableList.of("int"), - 0, - " " + switchInstruction + " p0, :switch_data", - " const/4 p0, 0x5", - " goto :return", - " :case_0", - " const/4 p0, 0x3", - " :return", - " return p0", - switchData); - - builder.addMainMethod( - 2, - " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", - " const/4 v1, 0", - " invoke-static { v1 }, LTest;->method(I)I", - " move-result v1", - " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V", - " return-void" - ); - - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - DexEncodedMethod method = getMethod(processedApplication, signature); - DexCode code = method.getCode().asDexCode(); - - if (key == 0) { - assertEquals(5, code.instructions.length); - assertTrue(code.instructions[0] instanceof IfEqz); - } else { - assertEquals(6, code.instructions.length); - assertTrue(some16BitConst(code.instructions[0])); - assertTrue(code.instructions[1] instanceof IfEq); - } - } - - @Test - public void singleCaseDex() { - for (boolean packed : new boolean[]{true, false}) { - runSingleCaseDexTest(packed, Integer.MIN_VALUE); - runSingleCaseDexTest(packed, -1); - runSingleCaseDexTest(packed, 0); - runSingleCaseDexTest(packed, 1); - runSingleCaseDexTest(packed, Integer.MAX_VALUE); - } - } - - private void runTwoCaseSparseToPackedOrIfsDexTest(int key1, int key2) { - SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); - - MethodSignature signature = builder.addStaticMethod( - "int", - DEFAULT_METHOD_NAME, - ImmutableList.of("int"), - 0, - " sparse-switch p0, :sparse_switch_data", - " const/4 v0, 0x5", - " goto :return", - " :case_1", - " const/4 v0, 0x3", - " goto :return", - " :case_2", - " const/4 v0, 0x4", - " :return", - " return v0", - " :sparse_switch_data", - " .sparse-switch", - " " + key1 + " -> :case_1", - " " + key2 + " -> :case_2", - " .end sparse-switch"); - - builder.addMainMethod( - 2, - " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", - " const/4 v1, 0", - " invoke-static { v1 }, LTest;->method(I)I", - " move-result v1", - " invoke-virtual { v0, v1 }, Ljava/io/PrintStream;->print(I)V", - " return-void" - ); - - InternalOptions options = new InternalOptions(); - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - DexEncodedMethod method = getMethod(processedApplication, signature); - DexCode code = method.getCode().asDexCode(); - if (twoCaseWillUsePackedSwitch(key1, key2)) { - assertTrue(code.instructions[0] instanceof PackedSwitch); - } else { - if (key1 == 0) { - assertTrue(code.instructions[0] instanceof IfEqz); - } else { - // Const instruction before if. - assertTrue(code.instructions[1] instanceof IfEq); - } - } - } - - @Test - public void twoCaseSparseToPackedOrIfsDex() { - for (int delta = 1; delta <= 3; delta++) { - runTwoCaseSparseToPackedOrIfsDexTest(0, delta); - runTwoCaseSparseToPackedOrIfsDexTest(-delta, 0); - runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta); - runTwoCaseSparseToPackedOrIfsDexTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE); - } - runTwoCaseSparseToPackedOrIfsDexTest(-1, 1); - runTwoCaseSparseToPackedOrIfsDexTest(-2, 1); - runTwoCaseSparseToPackedOrIfsDexTest(-1, 2); - runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE); - runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE); - runTwoCaseSparseToPackedOrIfsDexTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1); - } - - private void runLargerSwitchDexTest(int firstKey, int keyStep, int totalKeys, - Integer additionalLastKey) throws Exception { - SmaliBuilder builder = new SmaliBuilder(DEFAULT_CLASS_NAME); - - StringBuilder switchSource = new StringBuilder(); - StringBuilder targetCode = new StringBuilder(); - for (int i = 0; i < totalKeys; i++) { - String caseLabel = "case_" + i; - switchSource.append(" " + (firstKey + i * keyStep) + " -> :" + caseLabel + "\n"); - targetCode.append(" :" + caseLabel + "\n"); - targetCode.append(" goto :return\n"); - } - if (additionalLastKey != null) { - String caseLabel = "case_" + totalKeys; - switchSource.append(" " + additionalLastKey + " -> :" + caseLabel + "\n"); - targetCode.append(" :" + caseLabel + "\n"); - targetCode.append(" goto :return\n"); - } - - MethodSignature signature = builder.addStaticMethod( - "void", - DEFAULT_METHOD_NAME, - ImmutableList.of("int"), - 0, - " sparse-switch p0, :sparse_switch_data", - " goto :return", - targetCode.toString(), - " :return", - " return-void", - " :sparse_switch_data", - " .sparse-switch", - switchSource.toString(), - " .end sparse-switch"); - - builder.addMainMethod( - 2, - " sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;", - " const/4 v1, 0", - " invoke-static { v1 }, LTest;->method(I)V", - " return-void" - ); - - InternalOptions options = new InternalOptions(); - options.verbose = true; - options.printTimes = true; - DexApplication originalApplication = buildApplication(builder, options); - DexApplication processedApplication = processApplication(originalApplication, options); - DexEncodedMethod method = getMethod(processedApplication, signature); - DexCode code = method.getCode().asDexCode(); - if (keyStep <= 2) { - assertTrue(code.instructions[0] instanceof PackedSwitch); - } else { - assertTrue(code.instructions[0] instanceof SparseSwitch); - } - } - - @Test - public void twoMonsterSparseToPackedDex() throws Exception { - runLargerSwitchDexTest(0, 1, 100, null); - runLargerSwitchDexTest(0, 2, 100, null); - runLargerSwitchDexTest(0, 3, 100, null); - runLargerSwitchDexTest(100, 100, 100, null); - runLargerSwitchDexTest(-10000, 100, 100, null); - runLargerSwitchDexTest(-10000, 200, 100, 10000); - runLargerSwitchDexTest( - Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE); - - // TODO(63090177): Currently this is commented out as R8 gets really slow for large switches. - // runLargerSwitchDexTest(0, 1, Constants.U16BIT_MAX, null); - } - - private void runSingleCaseJarTest(boolean packed, int key) throws Exception { - JasminBuilder builder = new JasminBuilder(); - JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); - - String switchCode; - if (packed) { - switchCode = StringUtils.join( - "\n", - " tableswitch " + key, - " case_0", - " default : case_default"); - } else { - switchCode = StringUtils.join( - "\n", - " lookupswitch", - " " + key + " : case_0", - " default : case_default"); - } - - clazz.addStaticMethod("test", ImmutableList.of("I"), "I", - " .limit stack 1", - " .limit locals 1", - " iload 0", - switchCode, - " case_0:", - " iconst_3", - " goto return_", - " case_default:", - " ldc 5", - " return_:", - " ireturn"); - - clazz.addMainMethod( - " .limit stack 2", - " .limit locals 1", - " getstatic java/lang/System/out Ljava/io/PrintStream;", - " ldc 2", - " invokestatic Test/test(I)I", - " invokevirtual java/io/PrintStream/print(I)V", - " return"); - - DexApplication app = builder.read(); - app = ToolHelper.optimizeWithR8(app, new InternalOptions()); - - MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int")); - DexEncodedMethod method = getMethod(app, signature); - DexCode code = method.getCode().asDexCode(); - if (key == 0) { - assertEquals(5, code.instructions.length); - assertTrue(code.instructions[0] instanceof IfEqz); - } else { - assertEquals(6, code.instructions.length); - assertTrue(code.instructions[1] instanceof IfEq); - } - } - - @Test - public void singleCaseJar() throws Exception { - for (boolean packed : new boolean[]{true, false}) { - runSingleCaseJarTest(packed, Integer.MIN_VALUE); - runSingleCaseJarTest(packed, -1); - runSingleCaseJarTest(packed, 0); - runSingleCaseJarTest(packed, 1); - runSingleCaseJarTest(packed, Integer.MAX_VALUE); - } - } - - private void runTwoCaseSparseToPackedJarTest(int key1, int key2) throws Exception { - JasminBuilder builder = new JasminBuilder(); - JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); - - clazz.addStaticMethod("test", ImmutableList.of("I"), "I", - " .limit stack 1", - " .limit locals 1", - " iload 0", - " lookupswitch", - " " + key1 + " : case_1", - " " + key2 + " : case_2", - " default : case_default", - " case_1:", - " iconst_3", - " goto return_", - " case_2:", - " iconst_4", - " goto return_", - " case_default:", - " iconst_5", - " return_:", - " ireturn"); - - clazz.addMainMethod( - " .limit stack 2", - " .limit locals 1", - " getstatic java/lang/System/out Ljava/io/PrintStream;", - " ldc 2", - " invokestatic Test/test(I)I", - " invokevirtual java/io/PrintStream/print(I)V", - " return"); - - DexApplication app = builder.read(); - app = ToolHelper.optimizeWithR8(app, new InternalOptions()); - - MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int")); - DexEncodedMethod method = getMethod(app, signature); - DexCode code = method.getCode().asDexCode(); - if (twoCaseWillUsePackedSwitch(key1, key2)) { - assertTrue(code.instructions[0] instanceof PackedSwitch); - } else { - if (key1 == 0) { - assertTrue(code.instructions[0] instanceof IfEqz); - } else { - // Const instruction before if. - assertTrue(code.instructions[1] instanceof IfEq); - } - } - } - - @Test - public void twoCaseSparseToPackedJar() throws Exception { - for (int delta = 1; delta <= 3; delta++) { - runTwoCaseSparseToPackedJarTest(0, delta); - runTwoCaseSparseToPackedJarTest(-delta, 0); - runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MIN_VALUE + delta); - runTwoCaseSparseToPackedJarTest(Integer.MAX_VALUE - delta, Integer.MAX_VALUE); - } - runTwoCaseSparseToPackedJarTest(-1, 1); - runTwoCaseSparseToPackedJarTest(-2, 1); - runTwoCaseSparseToPackedJarTest(-1, 2); - runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE); - runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE + 1, Integer.MAX_VALUE); - runTwoCaseSparseToPackedJarTest(Integer.MIN_VALUE, Integer.MAX_VALUE - 1); - } - - private void runLargerSwitchJarTest(int firstKey, int keyStep, int totalKeys, - Integer additionalLastKey) throws Exception { - JasminBuilder builder = new JasminBuilder(); - JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); - - StringBuilder switchSource = new StringBuilder(); - StringBuilder targetCode = new StringBuilder(); - for (int i = 0; i < totalKeys; i++) { - String caseLabel = "case_" + i; - switchSource.append(" " + (firstKey + i * keyStep) + " : " + caseLabel + "\n"); - targetCode.append(" " + caseLabel + ":\n"); - targetCode.append(" ldc " + i + "\n"); - targetCode.append(" goto return_\n"); - } - if (additionalLastKey != null) { - String caseLabel = "case_" + totalKeys; - switchSource.append(" " + additionalLastKey + " : " + caseLabel + "\n"); - targetCode.append(" " + caseLabel + ":\n"); - targetCode.append(" ldc " + totalKeys + "\n"); - targetCode.append(" goto return_\n"); - } - - clazz.addStaticMethod("test", ImmutableList.of("I"), "I", - " .limit stack 1", - " .limit locals 1", - " iload 0", - " lookupswitch", - switchSource.toString(), - " default : case_default", - targetCode.toString(), - " case_default:", - " iconst_5", - " return_:", - " ireturn"); - - clazz.addMainMethod( - " .limit stack 2", - " .limit locals 1", - " getstatic java/lang/System/out Ljava/io/PrintStream;", - " ldc 2", - " invokestatic Test/test(I)I", - " invokevirtual java/io/PrintStream/print(I)V", - " return"); - - DexApplication app = builder.read(); - app = ToolHelper.optimizeWithR8(app, new InternalOptions()); - - MethodSignature signature = new MethodSignature("Test", "test", "int", ImmutableList.of("int")); - DexEncodedMethod method = getMethod(app, signature); - DexCode code = method.getCode().asDexCode(); - int packedSwitchCount = 0; - int sparseSwitchCount = 0; - for (Instruction instruction : code.instructions) { - if (instruction instanceof PackedSwitch) { - packedSwitchCount++; - } - if (instruction instanceof SparseSwitch) { - sparseSwitchCount++; - } - } - if (keyStep <= 2) { - assertEquals(1, packedSwitchCount); - assertEquals(0, sparseSwitchCount); - } else { - assertEquals(0, packedSwitchCount); - assertEquals(1, sparseSwitchCount); - } - } - - @Test - public void largerSwitchJar() throws Exception { - runLargerSwitchJarTest(0, 1, 100, null); - runLargerSwitchJarTest(0, 2, 100, null); - runLargerSwitchJarTest(0, 3, 100, null); - runLargerSwitchJarTest(100, 100, 100, null); - runLargerSwitchJarTest(-10000, 100, 100, null); - runLargerSwitchJarTest(-10000, 200, 100, 10000); - runLargerSwitchJarTest( - Integer.MIN_VALUE, (int) ((-(long)Integer.MIN_VALUE) / 16), 32, Integer.MAX_VALUE); - - // This is the maximal value possible with Jasmin with the generated code above. It depends on - // the source, so making smaller source can raise this limit. However we never get close to the - // class file max. - runLargerSwitchJarTest(0, 1, 5503, null); - } - - private void runConvertCasesToIf(List<Integer> keys, int defaultValue, int expectedIfs, - int expectedPackedSwitches, int expectedSparceSwitches) throws Exception { - JasminBuilder builder = new JasminBuilder(); - JasminBuilder.ClassBuilder clazz = builder.addClass("Test"); - - StringBuilder x = new StringBuilder(); - StringBuilder y = new StringBuilder(); - for (Integer key : keys) { - x.append(key).append(" : case_").append(key).append("\n"); - y.append("case_").append(key).append(":\n"); - y.append(" ldc ").append(key).append("\n"); - y.append(" goto return_\n"); - } - - clazz.addStaticMethod("test", ImmutableList.of("I"), "I", - " .limit stack 1", - " .limit locals 1", - " iload_0", - " lookupswitch", - x.toString(), - " default : case_default", - y.toString(), - " case_default:", - " ldc " + defaultValue, - " return_:", - " ireturn"); - - // Add the Jasmin class and a class from Java source with the main method. - AndroidApp.Builder appBuilder = AndroidApp.builder(); - appBuilder.addClassProgramData(builder.buildClasses()); - appBuilder.addProgramFiles(FilteredClassPath - .unfiltered(ToolHelper.getClassFileForTestClass(CheckSwitchInTestClass.class))); - AndroidApp app = compileWithR8(appBuilder.build()); - - DexInspector inspector = new DexInspector(app); - MethodSubject method = inspector.clazz("Test").method("int", "test", ImmutableList.of("int")); - DexCode code = method.getMethod().getCode().asDexCode(); - - int packedSwitches = 0; - int sparseSwitches = 0; - int ifs = 0; - for (Instruction instruction : code.instructions) { - if (instruction instanceof PackedSwitch) { - packedSwitches++; - } - if (instruction instanceof SparseSwitch) { - sparseSwitches++; - } - if (instruction instanceof IfEq || instruction instanceof IfEqz) { - ifs++; - } - } - - assertEquals(expectedPackedSwitches, packedSwitches); - assertEquals(expectedSparceSwitches, sparseSwitches); - assertEquals(expectedIfs, ifs); - - // Run the code - List<String> args = keys.stream().map(Object::toString).collect(Collectors.toList()); - args.add(Integer.toString(defaultValue)); - runOnArt(app, CheckSwitchInTestClass.class, args); - } - - @Test - public void convertCasesToIf() throws Exception { - // Switches that are completely converted to ifs. - runConvertCasesToIf(ImmutableList.of(0, 1000), -100, 2, 0, 0); - runConvertCasesToIf(ImmutableList.of(0, 1000, 2000), -100, 3, 0, 0); - runConvertCasesToIf(ImmutableList.of(0, 1000, 2000, 3000), -100, 4, 0, 0); - - // Switches that are completely converted to ifs and one switch. - runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0); - runConvertCasesToIf(ImmutableList.of(1000, 1001, 1002, 1003, 1004, 2000), -100, 1, 1, 0); - runConvertCasesToIf(ImmutableList.of( - Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004), -100, 1, 1, 0); - runConvertCasesToIf(ImmutableList.of( - 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 1, 1, 0); - runConvertCasesToIf(ImmutableList.of(0, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 1, 0); - runConvertCasesToIf(ImmutableList.of( - Integer.MIN_VALUE, 1000, 1001, 1002, 1003, 1004, Integer.MAX_VALUE), -100, 2, 1, 0); - - // Switches that are completely converted to ifs and two switches. - runConvertCasesToIf(ImmutableList.of( - 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 0, 2, 0); - runConvertCasesToIf(ImmutableList.of( - -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004), -100, 1, 2, 0); - runConvertCasesToIf(ImmutableList.of( - -1000, 0, 1, 2, 3, 4, 1000, 1001, 1002, 1003, 1004, 2000), -100, 2, 2, 0); - - // Switches that are completely converted two switches (one sparse and one packed). - runConvertCasesToIf(ImmutableList.of( - -1000, -900, -800, -700, -600, -500, -400, -300, - 1000, 1001, 1002, 1003, 1004, - 2000, 2100, 2200, 2300, 2400, 2500), -100, 0, 1, 1); - } -} \ No newline at end of file