Workaround aget-wide Art bug in arm32 interpreter.
For instructions of the form 'aget-wide regA, regA, regB' where
regB is out of bounds of non-null array in regA, Art would throw a
null pointer exception instead of an ArrayIndexOutOfBounds exception.
We work around that bug by disallowing aget-wide with the same array
and result register.
R=zerny@google.com
Bug: 68761724
Change-Id: I123e8793df0ea52c5f2f5dc18b19f03aeb046758
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index 0fbac8f..18634c2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -58,6 +58,8 @@
case LONG:
case DOUBLE:
case LONG_OR_DOUBLE:
+ assert builder.getOptions().canUseSameArrayAndResultRegisterInArrayGetWide()
+ || dest != array;
instruction = new AgetWide(dest, array, index);
break;
case OBJECT:
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 928d3c1..e7677b3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -713,6 +713,10 @@
return handlers;
}
+ public InternalOptions getOptions() {
+ return options;
+ }
+
// Dex instruction wrapper with information to compute instruction sizes and offsets for jumps.
private static abstract class Info {
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 5de9f00..3c3030b 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
@@ -986,6 +986,49 @@
return position % 2 == 1 ? position : position - 1;
}
+ // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
+ // where an aget-wide instruction using the same register for the array
+ // and the first register of the result could lead to the wrong exception
+ // being thrown on out of bounds.
+ //
+ // For instructions of the form 'aget-wide regA, regA, regB' where
+ // regB is out of bounds of non-null array in regA, Art would throw a null
+ // pointer exception instead of an ArrayIndexOutOfBounds exception.
+ //
+ // We work around that bug by disallowing aget-wide with the same array
+ // and result register.
+ private boolean needsArrayGetWideWorkaround(LiveIntervals intervals) {
+ if (options.canUseSameArrayAndResultRegisterInArrayGetWide()) {
+ return false;
+ }
+ if (intervals.requiredRegisters() == 1) {
+ // Not the live range for a wide value and therefore not the output of aget-wide.
+ return false;
+ }
+ if (intervals.getValue().isPhi()) {
+ // If this writes a new register pair it will be via a move and not an aget-wide operation.
+ return false;
+ }
+ if (intervals.getSplitParent() != intervals) {
+ // This is a split of a parent interval and therefore if this leads to a write of a
+ // register pair it will be via a move and not an aget-wide operation.
+ return false;
+ }
+ Instruction definition = intervals.getValue().definition;
+ return definition.isArrayGet() && definition.asArrayGet().outType().isWide();
+ }
+
+ // Is the array-get array register the same as the first register we are
+ // allocating for the result?
+ private boolean isArrayGetArrayRegister(LiveIntervals intervals, int register) {
+ assert needsArrayGetWideWorkaround(intervals);
+ Value array = intervals.getValue().definition.asArrayGet().array();
+ int arrayReg =
+ array.getLiveIntervals().getSplitCovering(intervals.getStart()).getRegister();
+ assert arrayReg != NO_REGISTER;
+ return arrayReg == register;
+ }
+
// The dalvik jit had a bug where the long operations add, sub, or, xor and and would write
// the first part of the result long before reading the second part of the input longs.
//
@@ -1268,6 +1311,11 @@
hasOverlappingLongRegisters(unhandledInterval, register)) {
return false;
}
+ // Check for aget-wide bug in recent Art VMs.
+ if (needsArrayGetWideWorkaround(unhandledInterval) &&
+ isArrayGetArrayRegister(unhandledInterval, register)) {
+ return false;
+ }
assignRegisterToUnhandledInterval(unhandledInterval, needsRegisterPair, register);
return true;
}
@@ -1371,6 +1419,21 @@
lastCandidate = candidate;
}
}
+ if (needsArrayGetWideWorkaround(unhandledInterval)) {
+ int lastCandidate = candidate;
+ while (isArrayGetArrayRegister(unhandledInterval, candidate)) {
+ // Make the overlapping register unavailable for allocation and try again.
+ freePositions.set(candidate, 0);
+ candidate = getLargestCandidate(registerConstraint, freePositions, needsRegisterPair, type);
+ // If there are only invalid candidates of the give type we will end up with the same
+ // candidate returned again once we have tried them all. In that case we didn't find a
+ // valid register candidate and we need to broaden the search to other types.
+ if (lastCandidate == candidate) {
+ return REGISTER_CANDIDATE_NOT_FOUND;
+ }
+ lastCandidate = candidate;
+ }
+ }
return candidate;
}
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 62dac5f..5105a46 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -342,4 +342,12 @@
public boolean canUseFilledNewArrayOfObjects() {
return minApiLevel >= AndroidApiLevel.K.getLevel();
}
+
+ // Art had a bug (b/68761724) for Android N and O in the arm32 interpreter
+ // where an aget-wide instruction using the same register for the array
+ // and the first register of the result could lead to the wrong exception
+ // being thrown on out of bounds.
+ public boolean canUseSameArrayAndResultRegisterInArrayGetWide() {
+ return minApiLevel > AndroidApiLevel.O_MR1.getLevel();
+ }
}