Version 1.3.51.
Merge: Do not reuse move-exception for different exception types.
CL: https://r8-review.googlesource.com/c/r8/+/32821
R=ricow@google.com, zerny@google.com
Bug: 120164595
Change-Id: I973f36ee1792b91ee464e6713167f18f025054a9
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 51ed473..e71310b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -15,6 +15,7 @@
import com.android.tools.r8.ir.conversion.DexBuilder;
import com.android.tools.r8.ir.conversion.IRBuilder;
import com.android.tools.r8.utils.CfgPrinter;
+import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ListUtils;
import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.StringUtils.BraceType;
@@ -1170,10 +1171,15 @@
return block;
}
- public static BasicBlock createRethrowBlock(IRCode code, Position position) {
+
+
+ public static BasicBlock createRethrowBlock(
+ IRCode code, Position position, DexType guard, InternalOptions options) {
BasicBlock block = new BasicBlock();
- MoveException moveException =
- new MoveException(new Value(code.valueNumberGenerator.next(), ValueType.OBJECT, null));
+ MoveException moveException = new MoveException(
+ new Value(code.valueNumberGenerator.next(), ValueType.OBJECT, null),
+ guard,
+ options);
moveException.setPosition(position);
Throw throwInstruction = new Throw(moveException.outValue);
throwInstruction.setPosition(position);
@@ -1182,6 +1188,7 @@
block.close(null);
block.setNumber(code.getHighestBlockNumber() + 1);
return block;
+
}
public boolean isTrivialGoto() {
@@ -1391,7 +1398,10 @@
* Clone catch successors from `fromBlock` into this block.
*/
public void copyCatchHandlers(
- IRCode code, ListIterator<BasicBlock> blockIterator, BasicBlock fromBlock) {
+ IRCode code,
+ ListIterator<BasicBlock> blockIterator,
+ BasicBlock fromBlock,
+ InternalOptions options) {
if (catchHandlers != null && catchHandlers.hasCatchAll()) {
return;
}
@@ -1411,7 +1421,8 @@
catchSuccessor.splitCriticalExceptionEdges(
code.getHighestBlockNumber() + 1,
code.valueNumberGenerator,
- blockIterator::add);
+ blockIterator::add,
+ options);
}
}
@@ -1431,14 +1442,17 @@
public int splitCriticalExceptionEdges(
int nextBlockNumber,
ValueNumberGenerator valueNumberGenerator,
- Consumer<BasicBlock> onNewBlock) {
- List<BasicBlock> predecessors = this.getPredecessors();
+ Consumer<BasicBlock> onNewBlock,
+ InternalOptions options) {
+ List<BasicBlock> predecessors = getPredecessors();
boolean hasMoveException = entry().isMoveException();
+ DexType exceptionType = null;
MoveException move = null;
Position position = entry().getPosition();
if (hasMoveException) {
// Remove the move-exception instruction.
move = entry().asMoveException();
+ exceptionType = move.getExceptionType();
assert move.getDebugValues().isEmpty();
getInstructions().remove(0);
}
@@ -1456,7 +1470,7 @@
if (hasMoveException) {
Value value = new Value(valueNumberGenerator.next(), ValueType.OBJECT, move.getLocalInfo());
values.add(value);
- MoveException newMove = new MoveException(value);
+ MoveException newMove = new MoveException(value, exceptionType, options);
newBlock.add(newMove);
newMove.setPosition(position);
}
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 08d7a07..1dbf098 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -280,7 +280,7 @@
} else {
nextBlock = null;
}
- currentBlock.copyCatchHandlers(code, blocksIterator, invokeBlock);
+ currentBlock.copyCatchHandlers(code, blocksIterator, invokeBlock, code.options);
if (nextBlock != null) {
BasicBlock b = blocksIterator.next();
assert b == nextBlock;
@@ -316,7 +316,7 @@
if (inlinedBlock.hasCatchHandlers()) {
// The block already has catch handlers, so it has only one throwing instruction, and no
// splitting is required.
- inlinedBlock.copyCatchHandlers(code, blocksIterator, invokeBlock);
+ inlinedBlock.copyCatchHandlers(code, blocksIterator, invokeBlock, code.options);
} else {
// The block does not have catch handlers, so it can have several throwing instructions.
// Therefore the block must be split after each throwing instruction, and the catch
diff --git a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
index 99ce3cc..c203c0a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CatchHandlers.java
@@ -9,6 +9,7 @@
import com.google.common.collect.ImmutableSet;
import java.util.List;
import java.util.Set;
+import java.util.function.BiConsumer;
public class CatchHandlers<T> {
@@ -61,6 +62,12 @@
getGuards().get(getGuards().size() - 1) == DexItemFactory.catchAllType;
}
+ public void forEach(BiConsumer<DexType, T> consumer) {
+ for (int i = 0; i < size(); ++i) {
+ consumer.accept(guards.get(i), targets.get(i));
+ }
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index c92ac50..9781f13 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -7,7 +7,6 @@
import com.android.tools.r8.cf.TypeVerificationHelper;
import com.android.tools.r8.dex.Constants;
import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.conversion.CfBuilder;
@@ -15,14 +14,15 @@
import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
import com.android.tools.r8.ir.optimize.InliningConstraints;
import com.android.tools.r8.utils.InternalOptions;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
public class MoveException extends Instruction {
+ private final DexType exceptionType;
+ private final InternalOptions options;
- public MoveException(Value dest) {
+ public MoveException(Value dest, DexType exceptionType, InternalOptions options) {
super(dest);
+ this.exceptionType = exceptionType;
+ this.options = options;
dest.markNeverNull();
}
@@ -49,7 +49,13 @@
@Override
public boolean identicalNonValueNonPositionParts(Instruction other) {
- return other.isMoveException();
+ if (!other.isMoveException()) {
+ return false;
+ }
+ if (options.canHaveExceptionTypeBug()) {
+ return other.asMoveException().exceptionType == exceptionType;
+ }
+ return true;
}
@Override
@@ -94,35 +100,17 @@
return true;
}
- private Set<DexType> collectExceptionTypes(DexItemFactory dexItemFactory) {
- Set<DexType> exceptionTypes = new HashSet<>(getBlock().getPredecessors().size());
- for (BasicBlock block : getBlock().getPredecessors()) {
- int size = block.getCatchHandlers().size();
- List<BasicBlock> targets = block.getCatchHandlers().getAllTargets();
- List<DexType> guards = block.getCatchHandlers().getGuards();
- for (int i = 0; i < size; i++) {
- if (targets.get(i) == getBlock()) {
- DexType guard = guards.get(i);
- exceptionTypes.add(
- guard == dexItemFactory.catchAllType
- ? dexItemFactory.throwableType
- : guard);
- }
- }
- }
- return exceptionTypes;
- }
-
@Override
public DexType computeVerificationType(TypeVerificationHelper helper) {
- return helper.join(collectExceptionTypes(helper.getFactory()));
+ return exceptionType;
}
@Override
public TypeLatticeElement evaluate(AppInfo appInfo) {
- Set<DexType> exceptionTypes = collectExceptionTypes(appInfo.dexItemFactory);
- return TypeLatticeElement.join(
- appInfo,
- exceptionTypes.stream().map(t -> TypeLatticeElement.fromDexType(appInfo, t, false)));
+ return TypeLatticeElement.fromDexType(appInfo, exceptionType, false);
+ }
+
+ public DexType getExceptionType() {
+ return exceptionType;
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index d855e0e..93aa151 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -88,6 +88,7 @@
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.Pair;
+import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
@@ -103,7 +104,6 @@
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.IdentityHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -140,11 +140,14 @@
}
private static class MoveExceptionWorklistItem extends WorklistItem {
+ private final DexType guard;
private final int sourceOffset;
private final int targetOffset;
- private MoveExceptionWorklistItem(BasicBlock block, int sourceOffset, int targetOffset) {
+ private MoveExceptionWorklistItem(
+ BasicBlock block, DexType guard, int sourceOffset, int targetOffset) {
super(block, -1);
+ this.guard = guard;
this.sourceOffset = sourceOffset;
this.targetOffset = targetOffset;
}
@@ -597,12 +600,9 @@
// construction and prior to building the IR.
for (BlockInfo info : targets.values()) {
if (info != null && info.block == block) {
- assert info.predecessorCount() == block.getPredecessors().size();
+ assert info.predecessorCount() == nonSplitPredecessorCount(block);
assert info.normalSuccessors.size() == block.getNormalSuccessors().size();
- if (block.hasCatchHandlers()) {
- assert info.exceptionalSuccessors.size()
- == block.getCatchHandlers().getUniqueTargets().size();
- } else {
+ if (!block.hasCatchHandlers()) {
assert !block.canThrow()
|| info.exceptionalSuccessors.isEmpty()
|| (info.exceptionalSuccessors.size() == 1
@@ -616,6 +616,38 @@
return true;
}
+ private int nonSplitPredecessorCount(BasicBlock block) {
+ Set<BasicBlock> set = Sets.newIdentityHashSet();
+ for (BasicBlock predecessor : block.getPredecessors()) {
+ if (offsets.containsKey(predecessor)) {
+ set.add(predecessor);
+ } else {
+ assert predecessor.getSuccessors().size() == 1;
+ assert predecessor.getPredecessors().size() == 1;
+ assert trivialGotoBlockPotentiallyWithMoveException(predecessor);
+ // Combine the exceptional edges to just one, for normal edges that have been split
+ // record them separately. That means that we are checking that there are the expected
+ // number of normal edges and some number of exceptional edges (which we count as one edge).
+ if (predecessor.getPredecessors().get(0).hasCatchSuccessor(predecessor)) {
+ set.add(predecessor.getPredecessors().get(0));
+ } else {
+ set.add(predecessor);
+ }
+ }
+ }
+ return set.size();
+ }
+
+ // Check that all instructions are either move-exception, goto or debug instructions.
+ private boolean trivialGotoBlockPotentiallyWithMoveException(BasicBlock block) {
+ for (Instruction instruction : block.getInstructions()) {
+ assert instruction.isMoveException()
+ || instruction.isGoto()
+ || instruction.isDebugInstruction();
+ }
+ return true;
+ }
+
private void processWorklist() {
for (WorklistItem item = ssaWorklist.poll(); item != null; item = ssaWorklist.poll()) {
if (item.block.isFilled()) {
@@ -673,7 +705,7 @@
Position position = source.getCanonicalDebugPositionAtOffset(moveExceptionItem.targetOffset);
if (moveExceptionDest >= 0) {
Value out = writeRegister(moveExceptionDest, ValueType.OBJECT, ThrowingInfo.NO_THROW, null);
- MoveException moveException = new MoveException(out);
+ MoveException moveException = new MoveException(out, moveExceptionItem.guard, options);
moveException.setPosition(position);
currentBlock.add(moveException);
}
@@ -1961,21 +1993,22 @@
assert !throwingInstructionInCurrentBlock;
throwingInstructionInCurrentBlock = true;
List<BasicBlock> targets = new ArrayList<>(catchHandlers.getAllTargets().size());
- // Construct unique move-exception header blocks for each unique target.
- Map<BasicBlock, BasicBlock> moveExceptionHeaders =
- new IdentityHashMap<>(catchHandlers.getUniqueTargets().size());
- for (int targetOffset : catchHandlers.getAllTargets()) {
- BasicBlock target = getTarget(targetOffset);
- BasicBlock header = moveExceptionHeaders.get(target);
- if (header == null) {
- header = new BasicBlock();
- header.incrementUnfilledPredecessorCount();
- moveExceptionHeaders.put(target, header);
- ssaWorklist.add(
- new MoveExceptionWorklistItem(header, currentInstructionOffset, targetOffset));
- }
+ Set<BasicBlock> moveExceptionTargets = Sets.newIdentityHashSet();
+ catchHandlers.forEach((type, targetOffset) -> {
+ DexType exceptionType = type == options.itemFactory.catchAllType
+ ? options.itemFactory.throwableType
+ : type;
+ BasicBlock header = new BasicBlock();
+ header.incrementUnfilledPredecessorCount();
+ ssaWorklist.add(
+ new MoveExceptionWorklistItem(
+ header, exceptionType, currentInstructionOffset, targetOffset));
targets.add(header);
- }
+ BasicBlock target = getTarget(targetOffset);
+ if (!moveExceptionTargets.add(target)) {
+ target.incrementUnfilledPredecessorCount();
+ }
+ });
currentBlock.linkCatchSuccessors(catchHandlers.getGuards(), targets);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index c87c849..367d35d 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -325,6 +325,6 @@
BasicBlock currentBlock = newInstance.getBlock();
BasicBlock nextBlock = instructions.split(code, blocks);
assert !instructions.hasNext();
- nextBlock.copyCatchHandlers(code, blocks, currentBlock);
+ nextBlock.copyCatchHandlers(code, blocks, currentBlock, code.options);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
index 399ae92..e6ad575 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
@@ -383,7 +383,7 @@
}
// Copy catch handlers after all blocks are split.
for (BasicBlock newBlock : newBlocks) {
- newBlock.copyCatchHandlers(code, blocks, currentBlock);
+ newBlock.copyCatchHandlers(code, blocks, currentBlock, code.options);
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 809ef70..899e25d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -289,8 +289,12 @@
splitIterator.previous();
BasicBlock newBlock = splitIterator.split(code, 1);
// Generate rethrow block.
- BasicBlock rethrowBlock =
- BasicBlock.createRethrowBlock(code, lastSelfRecursiveCall.getPosition());
+ DexType guard = options.itemFactory.throwableType;
+ BasicBlock rethrowBlock = BasicBlock.createRethrowBlock(
+ code,
+ lastSelfRecursiveCall.getPosition(),
+ guard,
+ options);
code.blocks.add(rethrowBlock);
// Add catch handler to the block containing the last recursive call.
newBlock.addCatchHandler(rethrowBlock, options.itemFactory.throwableType);
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 9b7fbf4..ec157e9 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -806,4 +806,17 @@
// Marshmallow and Nougat arm64 devices and they do not have the bug.
return minApiLevel < AndroidApiLevel.M.getLevel();
}
+
+ // The Art VM for Android N through P has a bug in the JIT that means that if the same
+ // exception block with a move-exception instruction is targeted with more than one type
+ // of exception the JIT will incorrectly assume that the exception object has one of these
+ // types and will optimize based on that one type instead of taking all the types into account.
+ //
+ // In order to workaround that, we always generate distinct move-exception instructions for
+ // distinct dex types.
+ //
+ // See b/120164595.
+ public boolean canHaveExceptionTypeBug() {
+ return minApiLevel < AndroidApiLevel.Q.getLevel();
+ }
}
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 0cf2f53..9982c4d 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -196,7 +196,7 @@
public void runCatchHandlerTest(boolean codeThrows, boolean twoGuards) throws Exception {
final int secondBlockInstructions = 4;
- final int initialBlockCount = 6;
+ final int initialBlockCount = twoGuards ? 7 : 6;
// Try split between all instructions in second block.
for (int i = 1; i < secondBlockInstructions; i++) {
TestApplication test = codeWithCatchHandlers(codeThrows, twoGuards);
@@ -235,7 +235,7 @@
public void runCatchHandlerSplitThreeTest(boolean codeThrows, boolean twoGuards)
throws Exception {
final int secondBlockInstructions = 4;
- final int initialBlockCount = 6;
+ final int initialBlockCount = twoGuards ? 7 : 6;
// Try split out all instructions in second block.
for (int i = 1; i < secondBlockInstructions - 1; i++) {
TestApplication test = codeWithCatchHandlers(codeThrows, twoGuards);
diff --git a/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java b/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
new file mode 100644
index 0000000..69b3a96
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b120164595/B120164595.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2019 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.regress.b120164595;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.io.IOException;
+import org.junit.Test;
+
+/**
+ * Regression test for art issue with multi catch-handlers.
+ * The problem is that if d8/r8 creates the same target address for two different exceptions,
+ * art will use that address to check if it was already handled.
+ */
+class TestClass {
+ public static void main(String[] args) {
+ for (int i = 0; i < 20000; i++) {
+ toBeOptimized();
+ }
+ }
+
+ private static void toBeOptimized() {
+ try {
+ willThrow();
+ } catch (IllegalStateException | NullPointerException e) {
+ if (e instanceof NullPointerException) {
+ return;
+ }
+ throw new Error("Expected NullPointerException");
+ }
+ }
+
+ private static void willThrow() {
+ throw new NullPointerException();
+ }
+}
+
+public class B120164595 extends TestBase {
+ @Test
+ public void testD8()
+ throws IOException, CompilationFailedException {
+ TestCompileResult d8Result = testForD8()
+ .setMinApi(AndroidApiLevel.N_MR1)
+ .addProgramClasses(TestClass.class)
+ .compile();
+ checkArt(d8Result);
+ }
+
+ @Test
+ public void testR8()
+ throws IOException, CompilationFailedException {
+ TestCompileResult r8Result = testForR8(Backend.DEX)
+ .setMinApi(AndroidApiLevel.N_MR1)
+ .addProgramClasses(TestClass.class)
+ .addKeepAllClassesRule()
+ .compile();
+ checkArt(r8Result);
+ }
+
+ private void checkArt(TestCompileResult result) throws IOException {
+ ProcessResult artResult = runOnArtRaw(
+ result.app,
+ TestClass.class.getCanonicalName(),
+ builder -> {
+ builder.appendArtOption("-Xusejit:true");
+ },
+ DexVm.ART_7_0_0_HOST
+ );
+ assertEquals(0, artResult.exitCode);
+ assertFalse(artResult.stderr.contains("Expected NullPointerException"));
+ }
+}