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");
}
@TestMetadata("forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt")
public void testForInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt");
}
@TestMetadata("illegalStepConst.kt")
public void testIllegalStepConst() throws Exception {
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.intProgression) -> IntProgressionType(symbols)
irType.isSubtypeOfClass(symbols.longProgression) -> LongProgressionType(symbols)
symbols.uIntProgression != null && irType.isSubtypeOfClass(symbols.uIntProgression) -> UIntProgressionType(
symbols,
allowUnsignedBounds
)
symbols.uLongProgression != null && irType.isSubtypeOfClass(symbols.uLongProgression) -> ULongProgressionType(
symbols,
allowUnsignedBounds
)
symbols.uIntProgression != null && irType.isSubtypeOfClass(symbols.uIntProgression) ->
UIntProgressionType(symbols, allowUnsignedBounds)
symbols.uLongProgression != null && irType.isSubtypeOfClass(symbols.uLongProgression) ->
ULongProgressionType(symbols, allowUnsignedBounds)
else -> null
}
}
@@ -153,9 +153,8 @@ internal class UntilHandler(private val context: CommonBackendContext) :
// infix fun UInt.until(to: UInt): UIntRange
// 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.
// In such combinations, there is no possibility of underflow when the argument (casted to the range element type) is decremented.
// For unexpected combinations that currently don't exist (e.g., Int until Char), we assume the check is needed to be safe.
// The additional condition is only needed when the argument casted to the range element type and then decremented can produce
// negative overflow - in other words, if the minimum value of `to` is the same as the minimum value for the progression type.
return with(context.irBuiltIns) {
when (receiverType) {
charType -> true
@@ -167,11 +166,9 @@ internal class UntilHandler(private val context: CommonBackendContext) :
byteType, shortType, intType -> false
else -> true
}
uByteType -> false
uShortType -> false
uIntType -> true
uLongType -> true
else -> true // Default in case a new `until` overload is added to stdlib and this function was not updated.
// 1. All unsigned types regardless of size "bottom out" at 0, so they all need the check.
// 2. In case new types are added to stdlib, conservatively add a check as well.
else -> true
}
}
}
@@ -4,27 +4,89 @@
val MinI = Int.MIN_VALUE
val MinB = Byte.MIN_VALUE
val MinS = Short.MIN_VALUE
val MinL = Long.MIN_VALUE
fun box(): String {
val list1 = ArrayList<Int>()
val range1 = 0..MinI step 3
val range1 = 0.toByte()..MinB step 3
for (i in range1) {
list1.add(i)
if (list1.size > 23) break
}
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 range2 = 0L..MinL step 3
val list2 = ArrayList<Int>()
val range2 = 0.toShort()..MinS step 3
for (i in range2) {
list2.add(i)
if (list2.size > 23) break
}
if (list2 != listOf<Long>()) {
return "Wrong elements for 0L..MinL step 3: $list2"
if (list2 != listOf<Int>()) {
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"
@@ -4,25 +4,81 @@
val MinI = Int.MIN_VALUE
val MinB = Byte.MIN_VALUE
val MinS = Short.MIN_VALUE
val MinL = Long.MIN_VALUE
fun box(): String {
val list1 = ArrayList<Int>()
for (i in 0..MinI step 3) {
for (i in 0.toByte()..MinB step 3) {
list1.add(i)
if (list1.size > 23) break
}
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>()
for (i in 0L..MinL step 3) {
val list2 = ArrayList<Int>()
for (i in 0.toShort()..MinS step 3) {
list2.add(i)
if (list2.size > 23) break
}
if (list2 != listOf<Long>()) {
return "Wrong elements for 0L..MinL step 3: $list2"
if (list2 != listOf<Int>()) {
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"
@@ -4,27 +4,89 @@
val MinUI = UInt.MIN_VALUE
val MinUB = UByte.MIN_VALUE
val MinUS = UShort.MIN_VALUE
val MinUL = ULong.MIN_VALUE
fun box(): String {
val list1 = ArrayList<UInt>()
val range1 = 1u..MinUI step 3
val range1 = 1u.toUByte()..MinUB step 3
for (i in range1) {
list1.add(i)
if (list1.size > 23) break
}
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 range2 = 1uL..MinUL step 3
val list2 = ArrayList<UInt>()
val range2 = 1u.toUShort()..MinUS step 3
for (i in range2) {
list2.add(i)
if (list2.size > 23) break
}
if (list2 != listOf<ULong>()) {
return "Wrong elements for 1uL..MinUL step 3: $list2"
if (list2 != listOf<UInt>()) {
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"
@@ -4,25 +4,81 @@
val MinUI = UInt.MIN_VALUE
val MinUB = UByte.MIN_VALUE
val MinUS = UShort.MIN_VALUE
val MinUL = ULong.MIN_VALUE
fun box(): String {
val list1 = ArrayList<UInt>()
for (i in 1u..MinUI step 3) {
for (i in 1u.toUByte()..MinUB step 3) {
list1.add(i)
if (list1.size > 23) break
}
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>()
for (i in 1uL..MinUL step 3) {
val list2 = ArrayList<UInt>()
for (i in 1u.toUShort()..MinUS step 3) {
list2.add(i)
if (list2.size > 23) break
}
if (list2 != listOf<ULong>()) {
return "Wrong elements for 1uL..MinUL step 3: $list2"
if (list2 != listOf<UInt>()) {
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"
@@ -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");
}
@TestMetadata("forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt")
public void testForInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/forInUntilWithMixedTypeBoundsNoBoundCheckNeededForUIntRangeIR.kt");
}
@TestMetadata("illegalStepConst.kt")
public void testIllegalStepConst() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/forLoop/unsigned/illegalStepConst.kt");
@@ -467,12 +467,26 @@ public class RangeIterationTest : RangeIterationTestBase() {
}
@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(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
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(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() {