IR: add an emptiness check to all unsigned until loops

Unlike signed integers, a larger unsigned type does not mean a lower
minimum value, so `x - 1` can overflow even if `x` is casted to a larger
type.

 #KT-42186 Fixed
This commit is contained in:
pyos
2020-10-05 15:20:57 +02:00
committed by Alexander Udalov
parent 4a03006162
commit adcbfc7b4c
10 changed files with 283 additions and 86 deletions
@@ -2746,11 +2746,6 @@ public class FirBytecodeTextTestGenerated extends AbstractFirBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilULongMinValue.kt"); runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilULongMinValue.kt");
} }
@TestMetadata("forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt")
public void testForInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt");
}
@TestMetadata("illegalStepConst.kt") @TestMetadata("illegalStepConst.kt")
public void testIllegalStepConst() throws Exception { public void testIllegalStepConst() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/illegalStepConst.kt"); runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/illegalStepConst.kt");
@@ -45,14 +45,10 @@ internal sealed class ProgressionType(
irType.isSubtypeOfClass(symbols.charProgression) -> CharProgressionType(symbols) irType.isSubtypeOfClass(symbols.charProgression) -> CharProgressionType(symbols)
irType.isSubtypeOfClass(symbols.intProgression) -> IntProgressionType(symbols) irType.isSubtypeOfClass(symbols.intProgression) -> IntProgressionType(symbols)
irType.isSubtypeOfClass(symbols.longProgression) -> LongProgressionType(symbols) irType.isSubtypeOfClass(symbols.longProgression) -> LongProgressionType(symbols)
symbols.uIntProgression != null && irType.isSubtypeOfClass(symbols.uIntProgression) -> UIntProgressionType( symbols.uIntProgression != null && irType.isSubtypeOfClass(symbols.uIntProgression) ->
symbols, UIntProgressionType(symbols, allowUnsignedBounds)
allowUnsignedBounds symbols.uLongProgression != null && irType.isSubtypeOfClass(symbols.uLongProgression) ->
) ULongProgressionType(symbols, allowUnsignedBounds)
symbols.uLongProgression != null && irType.isSubtypeOfClass(symbols.uLongProgression) -> ULongProgressionType(
symbols,
allowUnsignedBounds
)
else -> null else -> null
} }
} }
@@ -153,9 +153,8 @@ internal class UntilHandler(private val context: CommonBackendContext) :
// infix fun UInt.until(to: UInt): UIntRange // infix fun UInt.until(to: UInt): UIntRange
// infix fun ULong.until(to: ULong): ULongRange // infix fun ULong.until(to: ULong): ULongRange
// //
// The combinations where the range element type is strictly larger than the argument type do NOT need the additional condition. // The additional condition is only needed when the argument casted to the range element type and then decremented can produce
// In such combinations, there is no possibility of underflow when the argument (casted to the range element type) is decremented. // negative overflow - in other words, if the minimum value of `to` is the same as the minimum value for the progression type.
// For unexpected combinations that currently don't exist (e.g., Int until Char), we assume the check is needed to be safe.
return with(context.irBuiltIns) { return with(context.irBuiltIns) {
when (receiverType) { when (receiverType) {
charType -> true charType -> true
@@ -167,11 +166,9 @@ internal class UntilHandler(private val context: CommonBackendContext) :
byteType, shortType, intType -> false byteType, shortType, intType -> false
else -> true else -> true
} }
uByteType -> false // 1. All unsigned types regardless of size "bottom out" at 0, so they all need the check.
uShortType -> false // 2. In case new types are added to stdlib, conservatively add a check as well.
uIntType -> true else -> true
uLongType -> true
else -> true // Default in case a new `until` overload is added to stdlib and this function was not updated.
} }
} }
} }
@@ -4,27 +4,89 @@
val MinI = Int.MIN_VALUE val MinI = Int.MIN_VALUE
val MinB = Byte.MIN_VALUE
val MinS = Short.MIN_VALUE
val MinL = Long.MIN_VALUE val MinL = Long.MIN_VALUE
fun box(): String { fun box(): String {
val list1 = ArrayList<Int>() val list1 = ArrayList<Int>()
val range1 = 0..MinI step 3 val range1 = 0.toByte()..MinB step 3
for (i in range1) { for (i in range1) {
list1.add(i) list1.add(i)
if (list1.size > 23) break if (list1.size > 23) break
} }
if (list1 != listOf<Int>()) { if (list1 != listOf<Int>()) {
return "Wrong elements for 0..MinI step 3: $list1" return "Wrong elements for 0.toByte()..MinB step 3: $list1"
} }
val list2 = ArrayList<Long>() val list2 = ArrayList<Int>()
val range2 = 0L..MinL step 3 val range2 = 0.toShort()..MinS step 3
for (i in range2) { for (i in range2) {
list2.add(i) list2.add(i)
if (list2.size > 23) break if (list2.size > 23) break
} }
if (list2 != listOf<Long>()) { if (list2 != listOf<Int>()) {
return "Wrong elements for 0L..MinL step 3: $list2" return "Wrong elements for 0.toShort()..MinS step 3: $list2"
}
val list3 = ArrayList<Int>()
val range3 = 0..MinI step 3
for (i in range3) {
list3.add(i)
if (list3.size > 23) break
}
if (list3 != listOf<Int>()) {
return "Wrong elements for 0..MinI step 3: $list3"
}
val list4 = ArrayList<Long>()
val range4 = 0L..MinL step 3
for (i in range4) {
list4.add(i)
if (list4.size > 23) break
}
if (list4 != listOf<Long>()) {
return "Wrong elements for 0L..MinL step 3: $list4"
}
val list5 = ArrayList<Int>()
val range5 = 0.toByte() until MinB step 3
for (i in range5) {
list5.add(i)
if (list5.size > 23) break
}
if (list5 != listOf<Int>()) {
return "Wrong elements for 0.toByte() until MinB step 3: $list5"
}
val list6 = ArrayList<Int>()
val range6 = 0.toShort() until MinS step 3
for (i in range6) {
list6.add(i)
if (list6.size > 23) break
}
if (list6 != listOf<Int>()) {
return "Wrong elements for 0.toShort() until MinS step 3: $list6"
}
val list7 = ArrayList<Int>()
val range7 = 0 until MinI step 3
for (i in range7) {
list7.add(i)
if (list7.size > 23) break
}
if (list7 != listOf<Int>()) {
return "Wrong elements for 0 until MinI step 3: $list7"
}
val list8 = ArrayList<Long>()
val range8 = 0L until MinL step 3
for (i in range8) {
list8.add(i)
if (list8.size > 23) break
}
if (list8 != listOf<Long>()) {
return "Wrong elements for 0L until MinL step 3: $list8"
} }
return "OK" return "OK"
@@ -4,25 +4,81 @@
val MinI = Int.MIN_VALUE val MinI = Int.MIN_VALUE
val MinB = Byte.MIN_VALUE
val MinS = Short.MIN_VALUE
val MinL = Long.MIN_VALUE val MinL = Long.MIN_VALUE
fun box(): String { fun box(): String {
val list1 = ArrayList<Int>() val list1 = ArrayList<Int>()
for (i in 0..MinI step 3) { for (i in 0.toByte()..MinB step 3) {
list1.add(i) list1.add(i)
if (list1.size > 23) break if (list1.size > 23) break
} }
if (list1 != listOf<Int>()) { if (list1 != listOf<Int>()) {
return "Wrong elements for 0..MinI step 3: $list1" return "Wrong elements for 0.toByte()..MinB step 3: $list1"
} }
val list2 = ArrayList<Long>() val list2 = ArrayList<Int>()
for (i in 0L..MinL step 3) { for (i in 0.toShort()..MinS step 3) {
list2.add(i) list2.add(i)
if (list2.size > 23) break if (list2.size > 23) break
} }
if (list2 != listOf<Long>()) { if (list2 != listOf<Int>()) {
return "Wrong elements for 0L..MinL step 3: $list2" return "Wrong elements for 0.toShort()..MinS step 3: $list2"
}
val list3 = ArrayList<Int>()
for (i in 0..MinI step 3) {
list3.add(i)
if (list3.size > 23) break
}
if (list3 != listOf<Int>()) {
return "Wrong elements for 0..MinI step 3: $list3"
}
val list4 = ArrayList<Long>()
for (i in 0L..MinL step 3) {
list4.add(i)
if (list4.size > 23) break
}
if (list4 != listOf<Long>()) {
return "Wrong elements for 0L..MinL step 3: $list4"
}
val list5 = ArrayList<Int>()
for (i in 0.toByte() until MinB step 3) {
list5.add(i)
if (list5.size > 23) break
}
if (list5 != listOf<Int>()) {
return "Wrong elements for 0.toByte() until MinB step 3: $list5"
}
val list6 = ArrayList<Int>()
for (i in 0.toShort() until MinS step 3) {
list6.add(i)
if (list6.size > 23) break
}
if (list6 != listOf<Int>()) {
return "Wrong elements for 0.toShort() until MinS step 3: $list6"
}
val list7 = ArrayList<Int>()
for (i in 0 until MinI step 3) {
list7.add(i)
if (list7.size > 23) break
}
if (list7 != listOf<Int>()) {
return "Wrong elements for 0 until MinI step 3: $list7"
}
val list8 = ArrayList<Long>()
for (i in 0L until MinL step 3) {
list8.add(i)
if (list8.size > 23) break
}
if (list8 != listOf<Long>()) {
return "Wrong elements for 0L until MinL step 3: $list8"
} }
return "OK" return "OK"
@@ -4,27 +4,89 @@
val MinUI = UInt.MIN_VALUE val MinUI = UInt.MIN_VALUE
val MinUB = UByte.MIN_VALUE
val MinUS = UShort.MIN_VALUE
val MinUL = ULong.MIN_VALUE val MinUL = ULong.MIN_VALUE
fun box(): String { fun box(): String {
val list1 = ArrayList<UInt>() val list1 = ArrayList<UInt>()
val range1 = 1u..MinUI step 3 val range1 = 1u.toUByte()..MinUB step 3
for (i in range1) { for (i in range1) {
list1.add(i) list1.add(i)
if (list1.size > 23) break if (list1.size > 23) break
} }
if (list1 != listOf<UInt>()) { if (list1 != listOf<UInt>()) {
return "Wrong elements for 1u..MinUI step 3: $list1" return "Wrong elements for 1u.toUByte()..MinUB step 3: $list1"
} }
val list2 = ArrayList<ULong>() val list2 = ArrayList<UInt>()
val range2 = 1uL..MinUL step 3 val range2 = 1u.toUShort()..MinUS step 3
for (i in range2) { for (i in range2) {
list2.add(i) list2.add(i)
if (list2.size > 23) break if (list2.size > 23) break
} }
if (list2 != listOf<ULong>()) { if (list2 != listOf<UInt>()) {
return "Wrong elements for 1uL..MinUL step 3: $list2" return "Wrong elements for 1u.toUShort()..MinUS step 3: $list2"
}
val list3 = ArrayList<UInt>()
val range3 = 1u..MinUI step 3
for (i in range3) {
list3.add(i)
if (list3.size > 23) break
}
if (list3 != listOf<UInt>()) {
return "Wrong elements for 1u..MinUI step 3: $list3"
}
val list4 = ArrayList<ULong>()
val range4 = 1uL..MinUL step 3
for (i in range4) {
list4.add(i)
if (list4.size > 23) break
}
if (list4 != listOf<ULong>()) {
return "Wrong elements for 1uL..MinUL step 3: $list4"
}
val list5 = ArrayList<UInt>()
val range5 = 1u.toUByte() until MinUB step 3
for (i in range5) {
list5.add(i)
if (list5.size > 23) break
}
if (list5 != listOf<UInt>()) {
return "Wrong elements for 1u.toUByte() until MinUB step 3: $list5"
}
val list6 = ArrayList<UInt>()
val range6 = 1u.toUShort() until MinUS step 3
for (i in range6) {
list6.add(i)
if (list6.size > 23) break
}
if (list6 != listOf<UInt>()) {
return "Wrong elements for 1u.toUShort() until MinUS step 3: $list6"
}
val list7 = ArrayList<UInt>()
val range7 = 1u until MinUI step 3
for (i in range7) {
list7.add(i)
if (list7.size > 23) break
}
if (list7 != listOf<UInt>()) {
return "Wrong elements for 1u until MinUI step 3: $list7"
}
val list8 = ArrayList<ULong>()
val range8 = 1uL until MinUL step 3
for (i in range8) {
list8.add(i)
if (list8.size > 23) break
}
if (list8 != listOf<ULong>()) {
return "Wrong elements for 1uL until MinUL step 3: $list8"
} }
return "OK" return "OK"
@@ -4,25 +4,81 @@
val MinUI = UInt.MIN_VALUE val MinUI = UInt.MIN_VALUE
val MinUB = UByte.MIN_VALUE
val MinUS = UShort.MIN_VALUE
val MinUL = ULong.MIN_VALUE val MinUL = ULong.MIN_VALUE
fun box(): String { fun box(): String {
val list1 = ArrayList<UInt>() val list1 = ArrayList<UInt>()
for (i in 1u..MinUI step 3) { for (i in 1u.toUByte()..MinUB step 3) {
list1.add(i) list1.add(i)
if (list1.size > 23) break if (list1.size > 23) break
} }
if (list1 != listOf<UInt>()) { if (list1 != listOf<UInt>()) {
return "Wrong elements for 1u..MinUI step 3: $list1" return "Wrong elements for 1u.toUByte()..MinUB step 3: $list1"
} }
val list2 = ArrayList<ULong>() val list2 = ArrayList<UInt>()
for (i in 1uL..MinUL step 3) { for (i in 1u.toUShort()..MinUS step 3) {
list2.add(i) list2.add(i)
if (list2.size > 23) break if (list2.size > 23) break
} }
if (list2 != listOf<ULong>()) { if (list2 != listOf<UInt>()) {
return "Wrong elements for 1uL..MinUL step 3: $list2" return "Wrong elements for 1u.toUShort()..MinUS step 3: $list2"
}
val list3 = ArrayList<UInt>()
for (i in 1u..MinUI step 3) {
list3.add(i)
if (list3.size > 23) break
}
if (list3 != listOf<UInt>()) {
return "Wrong elements for 1u..MinUI step 3: $list3"
}
val list4 = ArrayList<ULong>()
for (i in 1uL..MinUL step 3) {
list4.add(i)
if (list4.size > 23) break
}
if (list4 != listOf<ULong>()) {
return "Wrong elements for 1uL..MinUL step 3: $list4"
}
val list5 = ArrayList<UInt>()
for (i in 1u.toUByte() until MinUB step 3) {
list5.add(i)
if (list5.size > 23) break
}
if (list5 != listOf<UInt>()) {
return "Wrong elements for 1u.toUByte() until MinUB step 3: $list5"
}
val list6 = ArrayList<UInt>()
for (i in 1u.toUShort() until MinUS step 3) {
list6.add(i)
if (list6.size > 23) break
}
if (list6 != listOf<UInt>()) {
return "Wrong elements for 1u.toUShort() until MinUS step 3: $list6"
}
val list7 = ArrayList<UInt>()
for (i in 1u until MinUI step 3) {
list7.add(i)
if (list7.size > 23) break
}
if (list7 != listOf<UInt>()) {
return "Wrong elements for 1u until MinUI step 3: $list7"
}
val list8 = ArrayList<ULong>()
for (i in 1uL until MinUL step 3) {
list8.add(i)
if (list8.size > 23) break
}
if (list8 != listOf<ULong>()) {
return "Wrong elements for 1uL until MinUL step 3: $list8"
} }
return "OK" return "OK"
@@ -1,36 +0,0 @@
// TARGET_BACKEND: JVM_IR
// WITH_RUNTIME
fun testUByteUntilUByte(a: UByte, b: UByte): Int {
var sum = 0
for (i in a until b) {
sum += i.toInt()
}
return sum
}
fun testUShortUntilUShort(a: UShort, b: UShort): Int {
var sum = 0
for (i in a until b) {
sum += i.toInt()
}
return sum
}
// For "until" progressions in JVM IR, there is typically a check that the range is not empty: upper bound != MIN_VALUE.
// However, this check is not needed when the upper bound is smaller than the range element type.
// Here are the available `until` extension functions with mixed bounds that return UIntRange:
//
// infix fun UByte.until(to: UByte): UIntRange // NO bound check needed
// infix fun UShort.until(to: UShort): UIntRange // NO bound check needed
// 0 iterator
// 0 getStart
// 0 getEnd
// 0 getFirst
// 0 getLast
// 0 getStep
// 2 IFGT
// 2 IFLE
// 4 IF
// 0 INVOKESTATIC kotlin/UInt.constructor-impl
// 0 INVOKE\w+ kotlin/UInt.(un)?box-impl
@@ -2746,11 +2746,6 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilULongMinValue.kt"); runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilULongMinValue.kt");
} }
@TestMetadata("forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt")
public void testForInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt");
}
@TestMetadata("illegalStepConst.kt") @TestMetadata("illegalStepConst.kt")
public void testIllegalStepConst() throws Exception { public void testIllegalStepConst() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/illegalStepConst.kt"); runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/illegalStepConst.kt");
@@ -467,12 +467,26 @@ public class RangeIterationTest : RangeIterationTestBase() {
} }
@Test fun overflowZeroToMinValue() { @Test fun overflowZeroToMinValue() {
doTest(0.toByte()..MinB step 3, 0, MinB.toInt(), 3, listOf())
doTest(0.toShort()..MinS step 3, 0, MinS.toInt(), 3, listOf())
doTest(0..MinI step 3, 0, MinI, 3, listOf()) doTest(0..MinI step 3, 0, MinI, 3, listOf())
doTest(0L..MinL step 3, 0, MinL, 3L, listOf()) doTest(0L..MinL step 3, 0, MinL, 3L, listOf())
doTest(0.toByte() until MinB step 3, 0, MinB.toInt() - 1, 3, listOf())
doTest(0.toShort() until MinS step 3, 0, MinS.toInt() - 1, 3, listOf())
doTest(0 until MinI step 3, 1, 0, 3, listOf())
doTest(0L until MinL step 3, 1L, 0L, 3L, listOf())
// 1u as used as a start since min value is 0u for unsigned types // 1u as used as a start since min value is 0u for unsigned types
doTest(1u.toUByte()..MinUB step 3, 1u, MinUB.toUInt(), 3, listOf())
doTest(1u.toUShort()..MinUS step 3, 1u, MinUS.toUInt(), 3, listOf())
doTest(1u..MinUI step 3, 1u, MinUI, 3, listOf()) doTest(1u..MinUI step 3, 1u, MinUI, 3, listOf())
doTest(1uL..MinUL step 3, 1u, MinUL, 3L, listOf()) doTest(1uL..MinUL step 3, 1u, MinUL, 3L, listOf())
doTest(1u.toUByte() until MinUB step 3, MaxUI, MinUI, 3, listOf())
doTest(1u.toUShort() until MinUS step 3, MaxUI, MinUI, 3, listOf())
doTest(1u until MinUI step 3, MaxUI, MinUI, 3, listOf())
doTest(1uL until MinUL step 3, MaxUL, MinUL, 3L, listOf())
} }
@Test fun progressionDownToMinValue() { @Test fun progressionDownToMinValue() {