Merge "Disable check-cast removal"
diff --git a/src/main/java/com/android/tools/r8/DexSplitterHelper.java b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
index b84b343..0c9fbe8 100644
--- a/src/main/java/com/android/tools/r8/DexSplitterHelper.java
+++ b/src/main/java/com/android/tools/r8/DexSplitterHelper.java
@@ -11,6 +11,7 @@
import com.android.tools.r8.graph.DexApplication;
import com.android.tools.r8.graph.DexApplication.Builder;
import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.naming.ClassNameMapper;
import com.android.tools.r8.naming.NamingLens;
import com.android.tools.r8.utils.FeatureClassMapping;
import com.android.tools.r8.utils.FeatureClassMapping.FeatureMappingException;
@@ -27,7 +28,8 @@
public class DexSplitterHelper {
- public static void run(D8Command command, String featureSplitMapping, String outputArchive)
+ public static void run(
+ D8Command command, String featureSplitMapping, String outputArchive, String proguardMap)
throws IOException, CompilationException, ExecutionException {
InternalOptions options = command.getInternalOptions();
options.enableDesugaring = false;
@@ -45,7 +47,12 @@
new ApplicationReader(command.getInputApp(), options, timing).read(null, executor);
FeatureClassMapping featureClassMapping =
new FeatureClassMapping(Paths.get(featureSplitMapping));
- Map<String, Builder> applications = getDistribution(app, featureClassMapping);
+
+ ClassNameMapper mapper = null;
+ if (proguardMap != null) {
+ mapper = ClassNameMapper.mapperFromFile(Paths.get(proguardMap));
+ }
+ Map<String, Builder> applications = getDistribution(app, featureClassMapping, mapper);
for (Entry<String, Builder> entry : applications.entrySet()) {
DexApplication featureApp = entry.getValue().build();
// We use the same factory, reset sorting.
@@ -88,10 +95,13 @@
}
private static Map<String, Builder> getDistribution(
- DexApplication app, FeatureClassMapping featureClassMapping) throws FeatureMappingException {
+ DexApplication app, FeatureClassMapping featureClassMapping, ClassNameMapper mapper)
+ throws FeatureMappingException {
Map<String, Builder> applications = new HashMap<>();
for (DexProgramClass clazz : app.classes()) {
- String feature = featureClassMapping.featureForClass(clazz.toString());
+ String clazzName =
+ mapper != null ? mapper.deobfuscateClassName(clazz.toString()) : clazz.toString();
+ String feature = featureClassMapping.featureForClass(clazzName);
Builder featureApplication = applications.get(feature);
if (featureApplication == null) {
featureApplication = DexApplication.builder(app.dexItemFactory, app.timing);
diff --git a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
index a9e4a18..8e184c0 100644
--- a/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
+++ b/src/main/java/com/android/tools/r8/dexsplitter/DexSplitter.java
@@ -27,6 +27,7 @@
List<String> inputArchives = new ArrayList<>();
String splitBaseName = DEFAULT_OUTPUT_ARCHIVE_FILENAME;
String featureSplitMapping;
+ String proguardMap;
}
private static Options parseArguments(String[] args) throws IOException {
@@ -43,6 +44,11 @@
options.splitBaseName = output;
continue;
}
+ String proguardMap = OptionsParsing.tryParseSingle(context, "--proguard-map", null);
+ if (proguardMap != null) {
+ options.proguardMap = proguardMap;
+ continue;
+ }
String featureSplit = OptionsParsing.tryParseSingle(context, "--feature-splits", null);
if (featureSplit != null) {
options.featureSplitMapping = featureSplit;
@@ -70,7 +76,8 @@
// We set the actual consumer on the ApplicationWriter when we have calculated the distribution
// since we don't yet know the distribution.
builder.setProgramConsumer(DexIndexedConsumer.emptyConsumer());
- DexSplitterHelper.run(builder.build(), options.featureSplitMapping, options.splitBaseName);
+ DexSplitterHelper.run(
+ builder.build(), options.featureSplitMapping, options.splitBaseName, options.proguardMap);
}
public static void main(String[] args) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 9cd430a..cdb419f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -30,6 +30,7 @@
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeNewArray;
import com.android.tools.r8.ir.code.NewArrayEmpty;
+import com.android.tools.r8.ir.code.NewInstance;
import com.android.tools.r8.ir.code.StaticGet;
import com.android.tools.r8.ir.code.StaticPut;
import com.android.tools.r8.ir.code.Value;
@@ -196,7 +197,15 @@
newArrayEmpty.size(), newType);
iterator.replaceCurrentInstruction(newNewArray);
}
- }
+ } else if (current.isNewInstance()) {
+ NewInstance newInstance= current.asNewInstance();
+ DexType newClazz = graphLense.lookupType(newInstance.clazz, method);
+ if (newClazz != newInstance.clazz) {
+ NewInstance newNewInstance =
+ new NewInstance(newClazz, makeOutValue(newInstance, code));
+ iterator.replaceCurrentInstruction(newNewInstance);
+ }
+ }
}
}
assert code.isConsistentSSA();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 7af0451..24def66 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -73,6 +73,7 @@
public static final int REGISTER_CANDIDATE_NOT_FOUND = -1;
public static final int MIN_CONSTANT_FREE_FOR_POSITIONS = 5;
+ private static final int RESERVED_MOVE_EXCEPTION_REGISTER = 0;
private enum ArgumentReuseMode {
ALLOW_ARGUMENT_REUSE,
@@ -141,6 +142,9 @@
// List of intervals that no register has been allocated to sorted by first live range.
protected PriorityQueue<LiveIntervals> unhandled = new PriorityQueue<>();
+ // List of intervals for the result of move-exception instructions.
+ private List<LiveIntervals> moveExceptionIntervals = new ArrayList<>();
+
// The first register used for parallel moves. After register allocation the parallel move
// temporary registers are [firstParallelMoveTemporary, maxRegisterNumber].
private int firstParallelMoveTemporary = NO_REGISTER;
@@ -153,6 +157,11 @@
// register.
private boolean hasDedicatedMoveExceptionRegister = false;
+ private int getMoveExceptionRegister() {
+ return numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS +
+ RESERVED_MOVE_EXCEPTION_REGISTER;
+ }
+
public LinearScanRegisterAllocator(IRCode code, InternalOptions options) {
this.code = code;
this.options = options;
@@ -663,6 +672,7 @@
active.clear();
inactive.clear();
unhandled.clear();
+ moveExceptionIntervals.clear();
for (LiveIntervals intervals : liveIntervals) {
intervals.clearRegisterAssignment();
}
@@ -752,7 +762,6 @@
// Force all move exception ranges to start out with the exception in a fixed register. Split
// their live ranges which will force another register if used.
int moveExceptionRegister = NO_REGISTER;
- List<LiveIntervals> moveExceptionIntervals = new ArrayList<>();
boolean overlappingMoveExceptionIntervals = false;
for (BasicBlock block : code.blocks) {
for (Instruction instruction : block.getInstructions()) {
@@ -761,8 +770,12 @@
Value exceptionValue = instruction.outValue();
LiveIntervals intervals = exceptionValue.getLiveIntervals();
unhandled.remove(intervals);
+ moveExceptionIntervals.add(intervals);
if (moveExceptionRegister == NO_REGISTER) {
+ assert RESERVED_MOVE_EXCEPTION_REGISTER == 0;
moveExceptionRegister = getFreeConsecutiveRegisters(1);
+ assert moveExceptionRegister == getMoveExceptionRegister();
+ assert !freeRegisters.contains(moveExceptionRegister);
}
intervals.setRegister(moveExceptionRegister);
if (!overlappingMoveExceptionIntervals) {
@@ -770,7 +783,6 @@
overlappingMoveExceptionIntervals |= other.overlaps(intervals);
}
}
- moveExceptionIntervals.add(intervals);
}
}
}
@@ -966,8 +978,14 @@
}
current = current.getNextConsecutive();
}
- // Select registers.
current = unhandledInterval.getStartOfConsecutive();
+ // Exclude move exception register if the first interval overlaps a move exception interval.
+ if (overlapsMoveExceptionInterval(current) &&
+ freeRegisters.remove(getMoveExceptionRegister())) {
+ assert RESERVED_MOVE_EXCEPTION_REGISTER == 0;
+ excludedRegisters.add(getMoveExceptionRegister());
+ }
+ // Select registers.
int numberOfRegister = current.numberOfConsecutiveRegisters();
int firstRegister = getFreeConsecutiveRegisters(numberOfRegister);
for (int i = 0; i < numberOfRegister; i++) {
@@ -1124,6 +1142,54 @@
return false;
}
+ // Intervals overlap a move exception interval if one of the splits of the intervals does.
+ // Since spill and restore moves are always put after the move exception we cannot give
+ // a non-move exception interval the same register as a move exception instruction.
+ //
+ // For example:
+ //
+ // B0:
+ // const v0, 0
+ // invoke throwing_method v0 (catch handler B2)
+ // goto B1
+ // B1:
+ // ...
+ // B2:
+ // move-exception v1
+ // invoke method v0
+ // return
+ //
+ // During register allocation we could split the const number intervals into multiple
+ // parts. We have to avoid assigning the same register to v1 and and v0 in B0 even
+ // if v0 has a different register in B2. That is because the spill/restore move when
+ // transitioning from B0 to B2 has to be after the move-exception instruction.
+ //
+ // Assuming that v0 has register 0 in B0 and register 4 in B2 and v1 has register 0 in B2
+ // we would generate the following incorrect code:
+ //
+ // B0:
+ // const r0, 0
+ // invoke throwing_method r0 (catch handler B2)
+ // goto B1
+ // B1:
+ // ...
+ // B2:
+ // move-exception r0
+ // move r4, r0 // Whoops.
+ // invoke method r4
+ // return
+ private boolean overlapsMoveExceptionInterval(LiveIntervals intervals) {
+ if (!hasDedicatedMoveExceptionRegister) {
+ return false;
+ }
+ for (LiveIntervals moveExceptionInterval : moveExceptionIntervals) {
+ if (intervals.anySplitOverlaps(moveExceptionInterval)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private boolean allocateSingleInterval(LiveIntervals unhandledInterval, ArgumentReuseMode mode) {
int registerConstraint = unhandledInterval.getRegisterLimit();
assert registerConstraint <= Constants.U16BIT_MAX;
@@ -1182,8 +1248,8 @@
// register. If we cannot find a free valid register for the move exception value we have no
// place to put a spill move (because the move exception instruction has to be the
// first instruction in the handler block).
- if (hasDedicatedMoveExceptionRegister) {
- int moveExceptionRegister = numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS;
+ if (overlapsMoveExceptionInterval(unhandledInterval)) {
+ int moveExceptionRegister = getMoveExceptionRegister();
if (moveExceptionRegister <= registerConstraint) {
freePositions.set(moveExceptionRegister, 0);
}
@@ -1530,8 +1596,8 @@
}
// Disallow reuse of the move exception register if we have reserved one.
- if (hasDedicatedMoveExceptionRegister) {
- usePositions.set(numberOfArgumentRegisters + NUMBER_OF_SENTINEL_REGISTERS, 0);
+ if (overlapsMoveExceptionInterval(unhandledInterval)) {
+ usePositions.set(getMoveExceptionRegister(), 0);
}
// Treat active linked argument intervals as pinned. They cannot be given another register
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index b6d8b79..2bbdaf9 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -325,6 +325,19 @@
return nextOverlap(other) != -1;
}
+ public boolean anySplitOverlaps(LiveIntervals other) {
+ LiveIntervals parent = getSplitParent();
+ if (parent.overlaps(other)) {
+ return true;
+ }
+ for (LiveIntervals child : parent.getSplitChildren()) {
+ if (child.overlaps(other)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public int nextOverlap(LiveIntervals other) {
Iterator<LiveRange> it = other.ranges.iterator();
LiveRange otherRange = it.next();
diff --git a/src/test/examples/naming001/keep-rules-106.txt b/src/test/examples/naming001/keep-rules-106.txt
new file mode 100644
index 0000000..ca320eb
--- /dev/null
+++ b/src/test/examples/naming001/keep-rules-106.txt
@@ -0,0 +1,5 @@
+# Copyright (c) 2018, 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.
+
+-applymapping mapping-106.txt
\ No newline at end of file
diff --git a/src/test/examples/naming001/mapping-106.txt b/src/test/examples/naming001/mapping-106.txt
new file mode 100644
index 0000000..1b3606d
--- /dev/null
+++ b/src/test/examples/naming001/mapping-106.txt
@@ -0,0 +1 @@
+naming001.E -> a.a:
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/AsmTestBase.java b/src/test/java/com/android/tools/r8/AsmTestBase.java
index 562abaf..c451402 100644
--- a/src/test/java/com/android/tools/r8/AsmTestBase.java
+++ b/src/test/java/com/android/tools/r8/AsmTestBase.java
@@ -14,7 +14,6 @@
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.InternalOptions;
-import com.google.common.io.ByteStreams;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -105,11 +104,6 @@
ensureSameOutput(main, mergedApp, classes);
}
- protected byte[] asBytes(Class clazz) throws IOException {
- return ByteStreams
- .toByteArray(clazz.getResourceAsStream(clazz.getSimpleName() + ".class"));
- }
-
protected AndroidApp buildAndroidApp(byte[]... classes) throws IOException {
AndroidApp.Builder builder = AndroidApp.builder();
for (byte[] clazz : classes) {
@@ -184,10 +178,6 @@
}
}
- protected static byte[] getBytesFromJavaClass(Class clazz) throws IOException {
- return Files.readAllBytes(ToolHelper.getClassFileForTestClass(clazz));
- }
-
private static class DumpLoader extends ClassLoader {
@SuppressWarnings("deprecation")
diff --git a/src/test/java/com/android/tools/r8/ToolHelper.java b/src/test/java/com/android/tools/r8/ToolHelper.java
index 11bc8cc..83cc931 100644
--- a/src/test/java/com/android/tools/r8/ToolHelper.java
+++ b/src/test/java/com/android/tools/r8/ToolHelper.java
@@ -440,6 +440,10 @@
private static final String ANGLER_DIR = TOOLS + "/linux/art/product/angler";
private static final String ANGLER_BOOT_IMAGE = ANGLER_DIR + "/system/framework/boot.art";
+ public static byte[] getClassAsBytes(Class clazz) throws IOException {
+ return ByteStreams.toByteArray(clazz.getResourceAsStream(clazz.getSimpleName() + ".class"));
+ }
+
public static String getArtDir(DexVm version) {
String dir = ART_DIRS.get(version);
if (dir == null) {
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTest.java b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTest.java
new file mode 100644
index 0000000..76fcd19
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTest.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2018, 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.debug;
+
+public class BreakInOneLineFinallyTest {
+ int i = 0;
+
+ void foo() {
+ try { bar(); }
+ finally { baz(); } // Java will fail to break here on exceptional exit.
+ }
+
+ int bar() {
+ if (i++ % 2 == 0) {
+ System.out.println("bar return " + i);
+ return i;
+ }
+ System.out.println("bar throw " + i);
+ throw new RuntimeException("" + i);
+ }
+
+ void baz() {
+ System.out.println("baz");
+ }
+
+ public static void main(String[] args) {
+ BreakInOneLineFinallyTest test = new BreakInOneLineFinallyTest();
+ test.foo();
+ try {
+ test.foo();
+ } catch (RuntimeException e) {
+ System.out.println("Caught expected exception: " + e.getMessage());
+ return;
+ }
+ throw new RuntimeException("Test failed...");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTestRunner.java b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTestRunner.java
new file mode 100644
index 0000000..6d7d1a9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInOneLineFinallyTestRunner.java
@@ -0,0 +1,52 @@
+// Copyright (c) 2018, 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.debug;
+
+import com.android.tools.r8.ToolHelper;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BreakInOneLineFinallyTestRunner extends DebugTestBase {
+
+ private static final Class CLASS = BreakInOneLineFinallyTest.class;
+ private static final String FILE = CLASS.getSimpleName() + ".java";
+ private static final String NAME = CLASS.getCanonicalName();
+
+ private final DebugTestConfig config;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> setup() {
+ DelayedDebugTestConfig cf =
+ temp -> new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+ DelayedDebugTestConfig d8 =
+ temp -> new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+ return ImmutableList.of(new Object[]{"CF", cf}, new Object[]{"D8", d8});
+ }
+
+ public BreakInOneLineFinallyTestRunner(String name, DelayedDebugTestConfig config) {
+ this.config = config.getConfig(temp);
+ }
+
+ @Test
+ public void testHitBreakpointOnNormalAndExceptionalFlow() throws Throwable {
+ Assume.assumeFalse(
+ "b/72933440 : JavaC doesn't duplicate line-table entries when duplicating finally blocks",
+ config instanceof D8DebugTestConfig);
+ runDebugTest(
+ config,
+ NAME,
+ breakpoint(NAME, "foo", 11),
+ run(),
+ checkLine(FILE, 11), // hit finally on normal flow
+ breakpoint(NAME, "main", 34), // can't hit the exceptional block :-(
+ run(),
+ checkLine(FILE, 34),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTest.java b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTest.java
new file mode 100644
index 0000000..eec9d13
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTest.java
@@ -0,0 +1,40 @@
+// Copyright (c) 2018, 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.debug;
+
+public class BreakInTwoLinesFinallyTest {
+ int i = 0;
+
+ void foo() {
+ try { bar(); }
+ finally {
+ baz();
+ }
+ }
+
+ int bar() {
+ if (i++ % 2 == 0) {
+ System.out.println("bar return " + i);
+ return i;
+ }
+ System.out.println("bar throw " + i);
+ throw new RuntimeException("" + i);
+ }
+
+ void baz() {
+ System.out.println("baz");
+ }
+
+ public static void main(String[] args) {
+ BreakInTwoLinesFinallyTest test = new BreakInTwoLinesFinallyTest();
+ test.foo();
+ try {
+ test.foo();
+ } catch (RuntimeException e) {
+ System.out.println("Caught expected exception: " + e.getMessage());
+ return;
+ }
+ throw new RuntimeException("Test failed...");
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTestRunner.java b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTestRunner.java
new file mode 100644
index 0000000..7691a2f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/BreakInTwoLinesFinallyTestRunner.java
@@ -0,0 +1,53 @@
+// Copyright (c) 2018, 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.debug;
+
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BreakInTwoLinesFinallyTestRunner extends DebugTestBase {
+
+ private static final Class CLASS = BreakInTwoLinesFinallyTest.class;
+ private static final String FILE = CLASS.getSimpleName() + ".java";
+ private static final String NAME = CLASS.getCanonicalName();
+
+ private final DebugTestConfig config;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> setup() {
+ DelayedDebugTestConfig cf =
+ temp -> new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+ DelayedDebugTestConfig d8 =
+ temp -> new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+ return ImmutableList.of(new Object[]{"CF", cf}, new Object[]{"D8", d8});
+ }
+
+ public BreakInTwoLinesFinallyTestRunner(String name, DelayedDebugTestConfig config) {
+ this.config = config.getConfig(temp);
+ }
+
+ @Test
+ public void testHitBreakpointOnNormalAndExceptionalFlow() throws Throwable {
+ Assume.assumeTrue(ToolHelper.getDexVm().isNewerThan(DexVm.ART_6_0_1_HOST));
+ runDebugTest(
+ config,
+ NAME,
+ breakpoint(NAME, "foo", 12),
+ run(),
+ checkLine(FILE, 12), // hit finally on normal flow
+ run(),
+ checkLine(FILE, 12), // hit finally on exceptional flow
+ breakpoint(NAME, "main", 36),
+ run(),
+ checkLine(FILE, 36),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
index d535f72..8480d24 100644
--- a/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/D8DebugTestConfig.java
@@ -9,8 +9,8 @@
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
import java.nio.file.Path;
-import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
@@ -35,6 +35,14 @@
}
}
+ public D8DebugTestConfig compileAndAddClasses(TemporaryFolder temp, Class... classes) {
+ return compileAndAddClasses(temp, Arrays.asList(classes));
+ }
+
+ public D8DebugTestConfig compileAndAddClasses(TemporaryFolder temp, List<Class> classes) {
+ return compileAndAdd(temp, ListUtils.map(classes, ToolHelper::getClassFileForTestClass), null);
+ }
+
public D8DebugTestConfig compileAndAdd(TemporaryFolder temp, Path... paths) {
return compileAndAdd(temp, Arrays.asList(paths), null);
}
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
index aff3708..556e17f 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestConfig.java
@@ -37,12 +37,14 @@
return paths;
}
- public void addPaths(Path... paths) {
+ public DebugTestConfig addPaths(Path... paths) {
addPaths(Arrays.asList(paths));
+ return this;
}
- public void addPaths(List<Path> paths) {
+ public DebugTestConfig addPaths(List<Path> paths) {
this.paths.addAll(paths);
+ return this;
}
/** Proguard map that the debuggee has been translated according to, null if not present. */
@@ -50,8 +52,9 @@
return proguardMap;
}
- public void setProguardMap(Path proguardMap) {
+ public DebugTestConfig setProguardMap(Path proguardMap) {
this.proguardMap = proguardMap;
+ return this;
}
@Override
diff --git a/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTest.java b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTest.java
new file mode 100644
index 0000000..9abc732
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTest.java
@@ -0,0 +1,22 @@
+// Copyright (c) 2018, 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.debug;
+
+public class LocalChangeOnSameLineTest {
+ int i = 0;
+
+ int bar() {
+ System.out.println("bar call " + ++i);
+ return i;
+ }
+
+ void foo() {
+ { int x = bar(); int y = bar(); }
+ { int x = bar(); int y = bar(); }
+ }
+
+ public static void main(String[] args) {
+ new LocalChangeOnSameLineTest().foo();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTestRunner.java b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTestRunner.java
new file mode 100644
index 0000000..b588590
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/LocalChangeOnSameLineTestRunner.java
@@ -0,0 +1,73 @@
+// Copyright (c) 2018, 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.debug;
+
+import com.android.tools.r8.ToolHelper;
+import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import org.junit.Assume;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class LocalChangeOnSameLineTestRunner extends DebugTestBase {
+
+ private static final Class CLASS = LocalChangeOnSameLineTest.class;
+ private static final String FILE = CLASS.getSimpleName() + ".java";
+ private static final String NAME = CLASS.getCanonicalName();
+
+ private final DebugTestConfig config;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static Collection<Object[]> setup() {
+ DelayedDebugTestConfig cf =
+ temp -> new CfDebugTestConfig().addPaths(ToolHelper.getClassPathForTests());
+ DelayedDebugTestConfig d8 =
+ temp -> new D8DebugTestConfig().compileAndAddClasses(temp, CLASS);
+ return ImmutableList.of(new Object[]{"CF", cf}, new Object[]{"D8", d8});
+ }
+
+ public LocalChangeOnSameLineTestRunner(String name, DelayedDebugTestConfig config) {
+ this.config = config.getConfig(temp);
+ }
+
+ /** Test that only hit the break point at line 15 once. */
+ @Test
+ public void testHitBreakpointOnce() throws Throwable {
+ Assume.assumeFalse("b/72933440 : invalid line info table", config instanceof D8DebugTestConfig);
+ runDebugTest(
+ config,
+ NAME,
+ breakpoint(NAME, "foo", 15),
+ run(),
+ checkLine(FILE, 15),
+ breakpoint(NAME, "main", 21),
+ run(),
+ checkLine(FILE, 21),
+ run());
+ }
+
+ /** Test that locals are correct in the frame of foo each time we break in bar. */
+ @Test
+ public void testLocalsOnBreakpoint() throws Throwable {
+ runDebugTest(
+ config,
+ NAME,
+ breakpoint(NAME, "bar"),
+ run(),
+ checkLine(FILE, 10),
+ inspect(t -> t.getFrame(1).checkNoLocal("x")),
+ run(),
+ checkLine(FILE, 10),
+ inspect(t -> t.getFrame(1).checkLocal("x")),
+ run(),
+ checkLine(FILE, 10),
+ inspect(t -> t.getFrame(1).checkNoLocal("x")),
+ run(),
+ checkLine(FILE, 10),
+ inspect(t -> t.getFrame(1).checkLocal("x")),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
index c803867..2e8938a 100644
--- a/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
+++ b/src/test/java/com/android/tools/r8/dexsplitter/DexSplitterTests.java
@@ -6,17 +6,27 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.D8;
import com.android.tools.r8.D8Command;
import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8;
+import com.android.tools.r8.R8Command;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -39,7 +49,7 @@
* therefore can't run without the base being loaded.
*/
@Test
- public void splitFiles() throws CompilationFailedException, IOException {
+ public void splitFilesNoObfuscation() throws CompilationFailedException, IOException {
// Initial normal compile to create dex files.
Path inputDex = temp.newFolder().toPath().resolve("input.zip");
D8.run(
@@ -52,13 +62,7 @@
.build());
Path outputDex = temp.getRoot().toPath().resolve("output");
- Path splitSpec = temp.getRoot().toPath().resolve("split_spec");
- try (PrintWriter out = new PrintWriter(splitSpec.toFile(), "UTF-8")) {
- out.write(
- "dexsplitsample.Class1:base\n"
- + "dexsplitsample.Class2:feature1\n"
- + "dexsplitsample.Class3:feature1");
- }
+ Path splitSpec = createSplitSpec();
DexSplitter.main(
new String[] {
@@ -104,4 +108,83 @@
// We expect this to throw since base is not in the path and Class3 depends on Class1
}
}
+
+ private Path createSplitSpec() throws FileNotFoundException, UnsupportedEncodingException {
+ Path splitSpec = temp.getRoot().toPath().resolve("split_spec");
+ try (PrintWriter out = new PrintWriter(splitSpec.toFile(), "UTF-8")) {
+ out.write(
+ "dexsplitsample.Class1:base\n"
+ + "dexsplitsample.Class2:feature1\n"
+ + "dexsplitsample.Class3:feature1");
+ }
+ return splitSpec;
+ }
+
+ private List<String> getProguardConf() {
+ return ImmutableList.of(
+ "-keep class dexsplitsample.Class3 {",
+ " public static void main(java.lang.String[]);",
+ "}");
+ }
+
+ @Test
+ public void splitFilesObfuscation()
+ throws CompilationFailedException, IOException, ExecutionException {
+ // Initial normal compile to create dex files.
+ Path inputDex = temp.newFolder().toPath().resolve("input.zip");
+ Path proguardMap = temp.getRoot().toPath().resolve("proguard.map");
+
+ R8.run(
+ R8Command.builder()
+ .setOutput(inputDex, OutputMode.DexIndexed)
+ .addProgramFiles(Paths.get(CLASS1_CLASS))
+ .addProgramFiles(Paths.get(CLASS2_CLASS))
+ .addProgramFiles(Paths.get(CLASS3_CLASS))
+ .addProgramFiles(Paths.get(CLASS3_INNER_CLASS))
+ .addLibraryFiles(ToolHelper.getDefaultAndroidJar())
+ .setProguardMapOutputPath(proguardMap)
+ .addProguardConfiguration(getProguardConf(), null)
+ .build());
+
+ Path outputDex = temp.getRoot().toPath().resolve("output");
+ Path splitSpec = createSplitSpec();
+
+ DexSplitter.main(
+ new String[] {
+ "--input", inputDex.toString(),
+ "--output", outputDex.toString(),
+ "--feature-splits", splitSpec.toString(),
+ "--proguard-map", proguardMap.toString()
+ });
+
+ Path base = outputDex.getParent().resolve("output.base.zip");
+ Path feature = outputDex.getParent().resolve("output.feature1.zip");
+ String class3 = "dexsplitsample.Class3";
+ // We should still be able to run the Class3 which we kept, it has a call to the obfuscated
+ // class1 which is in base.
+ ArtCommandBuilder builder = new ArtCommandBuilder();
+ builder.appendClasspath(base.toString());
+ builder.appendClasspath(feature.toString());
+ builder.setMainClass(class3);
+ String out = ToolHelper.runArt(builder);
+ assertEquals(out, "Class3\n");
+
+ // Class1 should not be in the feature, it should still be in base.
+ builder = new ArtCommandBuilder();
+ builder.appendClasspath(feature.toString());
+ builder.setMainClass(class3);
+ try {
+ ToolHelper.runArt(builder);
+ assertFalse(true);
+ } catch (AssertionError assertionError) {
+ // We expect this to throw since base is not in the path and Class3 depends on Class1.
+ }
+
+ // Ensure that the Class1 is actually in the correct split. Note that Class2 would have been
+ // shaken away.
+ DexInspector inspector = new DexInspector(base, proguardMap.toString());
+ ClassSubject subject = inspector.clazz("dexsplitsample.Class1");
+ assertTrue(subject.isPresent());
+ assertTrue(subject.isRenamed());
+ }
}
diff --git a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
index 7b1cf3e..5bf5a36 100644
--- a/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
+++ b/src/test/java/com/android/tools/r8/graph/InvokeSuperTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.graph;
import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.VmTestRunner;
import com.android.tools.r8.VmTestRunner.IgnoreForRangeOfVmVersions;
@@ -26,24 +27,24 @@
@IgnoreForRangeOfVmVersions(from = Version.V5_1_1, to = Version.V6_0_1)
public void testInvokeSuperTargets() throws Exception {
ensureSameOutput(MainClass.class.getCanonicalName(),
- asBytes(MainClass.class),
- asBytes(Consumer.class),
- asBytes(Super.class),
- asBytes(SubLevel1.class),
- asBytes(SubLevel2.class),
+ ToolHelper.getClassAsBytes(MainClass.class),
+ ToolHelper.getClassAsBytes(Consumer.class),
+ ToolHelper.getClassAsBytes(Super.class),
+ ToolHelper.getClassAsBytes(SubLevel1.class),
+ ToolHelper.getClassAsBytes(SubLevel2.class),
InvokerClassDump.dump(),
- asBytes(SubclassOfInvokerClass.class));
+ ToolHelper.getClassAsBytes(SubclassOfInvokerClass.class));
}
@Test
public void testInvokeSuperTargetsNonVerifying() throws Exception {
ensureR8FailsWithCompilationError(MainClassFailing.class.getCanonicalName(),
- asBytes(MainClassFailing.class),
- asBytes(Consumer.class),
- asBytes(Super.class),
- asBytes(SubLevel1.class),
- asBytes(SubLevel2.class),
+ ToolHelper.getClassAsBytes(MainClassFailing.class),
+ ToolHelper.getClassAsBytes(Consumer.class),
+ ToolHelper.getClassAsBytes(Super.class),
+ ToolHelper.getClassAsBytes(SubLevel1.class),
+ ToolHelper.getClassAsBytes(SubLevel2.class),
InvokerClassFailingDump.dump(),
- asBytes(SubclassOfInvokerClass.class));
+ ToolHelper.getClassAsBytes(SubclassOfInvokerClass.class));
}
}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 39cf369..6242b93 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -8,6 +8,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication;
@@ -47,7 +48,7 @@
boolean npeCaught,
BiConsumer<AppInfo, TypeAnalysis> inspector)
throws Exception {
- AndroidApp app = buildAndroidApp(asBytes(mainClass));
+ AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(mainClass));
DexApplication dexApplication =
new ApplicationReader(app, TEST_OPTIONS, new Timing("NullabilityTest.appReader"))
.read().toDirect();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java
index 496ada2..b6347d1 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullMarkerTest.java
@@ -8,6 +8,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.dex.ApplicationReader;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.DexApplication;
@@ -36,7 +37,7 @@
int expectedNumberOfNonNull,
Consumer<IRCode> testAugmentedIRCode)
throws Exception {
- AndroidApp app = buildAndroidApp(asBytes(testClass));
+ AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(testClass));
DexApplication dexApplication =
new ApplicationReader(app, TEST_OPTIONS, new Timing("NonNullMarkerTest.appReader"))
.read().toDirect();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
index c72cdcb..1226876 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/SimplifyIfNotNullTest.java
@@ -6,6 +6,7 @@
import static org.junit.Assert.assertEquals;
import com.android.tools.r8.AsmTestBase;
+import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.code.Format21t;
import com.android.tools.r8.code.Format22t;
import com.android.tools.r8.code.Instruction;
@@ -27,7 +28,7 @@
}
private void buildAndTest(Class<?> testClass, List<MethodSignature> signatures) throws Exception {
- AndroidApp app = buildAndroidApp(asBytes(testClass));
+ AndroidApp app = buildAndroidApp(ToolHelper.getClassAsBytes(testClass));
AndroidApp r8Result = compileWithR8(
app, keepMainProguardConfiguration(testClass), o -> o.inlineAccessors = false);
DexInspector dexInspector = new DexInspector(r8Result);
diff --git a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
index 1631a52..276b763 100644
--- a/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ApplyMappingTest.java
@@ -15,6 +15,8 @@
import com.android.tools.r8.R8Command;
import com.android.tools.r8.StringConsumer;
import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.code.Instruction;
+import com.android.tools.r8.code.NewInstance;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.shaking.ProguardRuleParserException;
@@ -219,13 +221,40 @@
main.iterateInstructions(InstructionSubject::isInvoke);
// mapping-105 simply includes: naming001.D#keep -> peek
// naming001.E extends D, hence its keep() should be renamed to peek as well.
- // Skip E#<init>
- iterator.next();
+ // Check E#<init> is not renamed.
+ InvokeInstructionSubject init = iterator.next();
+ assertEquals("Lnaming001/E;-><init>()V", init.invokedMethod().toSmaliString());
// E#keep() should be replaced with peek by applying the map.
InvokeInstructionSubject m = iterator.next();
assertEquals("peek", m.invokedMethod().name.toSourceString());
- // E could be renamed randomly, though.
- assertNotEquals("naming001.E", m.holder().toString());
+ // D must not be renamed
+ assertEquals("naming001.D", m.holder().toString());
+ }
+
+ @Test
+ public void test_naming001_rule106() throws Exception {
+ // keep rules just to rename E
+ Path flag = Paths.get(ToolHelper.EXAMPLES_DIR, "naming001", "keep-rules-106.txt");
+ Path proguardMap = out.resolve(MAPPING);
+ AndroidApp outputApp =
+ runR8(
+ ToolHelper.addProguardConfigurationConsumer(
+ getCommandForApps(out, flag, NAMING001_JAR).setDisableMinification(true),
+ pgConfig -> {
+ pgConfig.setPrintMapping(true);
+ pgConfig.setPrintMappingFile(proguardMap);
+ })
+ .build());
+
+ // Make sure the given proguard map is indeed applied.
+ DexInspector inspector = new DexInspector(outputApp);
+ MethodSubject main = inspector.clazz("naming001.D").method(DexInspector.MAIN);
+
+ // naming001.E is renamed to a.a, so first instruction must be: new-instance La/a;
+ Instruction[] instructions = main.getMethod().getCode().asDexCode().instructions;
+ assertTrue(instructions[0] instanceof NewInstance);
+ NewInstance newInstance = (NewInstance) instructions[0];
+ assertEquals( "La/a;", newInstance.getType().toSmaliString());
}
@Test
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
index 3725a24..0584aa0 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetExecutionTest.java
@@ -46,7 +46,7 @@
List<byte[]> allBytes = new ArrayList<>();
allBytes.addAll(ASM_CLASSES);
for (Class clazz : CLASSES) {
- allBytes.add(getBytesFromJavaClass(clazz));
+ allBytes.add(ToolHelper.getClassAsBytes(clazz));
}
ensureSameOutput(Main.class.getCanonicalName(),
ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm()),