From 5b5d9dd5a08a208f46d441c899f6adb0847766d0 Mon Sep 17 00:00:00 2001 From: Mikhail Zarechenskiy Date: Tue, 29 May 2018 03:00:39 +0300 Subject: [PATCH] Allow unsigned integers overflow values of corresponding signed numbers --- .../evaluate/ConstantExpressionEvaluator.kt | 33 +++++++---- .../unsignedLiteralsWithSignedOverflow.kt | 10 ++++ .../unsignedLiteralsOverflowSignedBorder.kt | 25 +++++++++ .../unsignedLiteralsOverflowSignedBorder.txt | 14 +++++ .../ir/IrBlackBoxCodegenTestGenerated.java | 5 ++ ...DiagnosticsWithUnsignedTypesGenerated.java | 5 ++ .../codegen/BlackBoxCodegenTestGenerated.java | 5 ++ .../LightAnalysisModeTestGenerated.java | 5 ++ .../resolve/constants/ConstantValueFactory.kt | 10 +++- .../constants/IntegerValueTypeConstructor.kt | 56 ++++++++++++------- .../semantics/JsCodegenBoxTestGenerated.java | 5 ++ 11 files changed, 138 insertions(+), 35 deletions(-) create mode 100644 compiler/testData/codegen/box/inlineClasses/unsignedLiteralsWithSignedOverflow.kt create mode 100644 compiler/testData/diagnostics/testsWithUnsignedTypes/unsignedLiteralsOverflowSignedBorder.kt create mode 100644 compiler/testData/diagnostics/testsWithUnsignedTypes/unsignedLiteralsOverflowSignedBorder.txt diff --git a/compiler/frontend/src/org/jetbrains/kotlin/resolve/constants/evaluate/ConstantExpressionEvaluator.kt b/compiler/frontend/src/org/jetbrains/kotlin/resolve/constants/evaluate/ConstantExpressionEvaluator.kt index 2199ce2e6d3..313edf26034 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/resolve/constants/evaluate/ConstantExpressionEvaluator.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/resolve/constants/evaluate/ConstantExpressionEvaluator.kt @@ -941,11 +941,17 @@ private class ConstantExpressionEvaluatorVisitor( if (integerValue != null) { return integerValue.wrap(parameters) } - return when (value) { - value.toInt().toLong() -> - if (parameters.isUnsigned) UIntValue(value.toInt()) else IntValue(value.toInt()) - else -> - if (parameters.isUnsigned) ULongValue(value) else LongValue(value) + + return if (parameters.isUnsigned) { + when (value) { + value.toInt().fromUIntToLong() -> UIntValue(value.toInt()) + else -> ULongValue(value) + } + } else { + when (value) { + value.toInt().toLong() -> IntValue(value.toInt()) + else -> LongValue(value) + } }.wrap(parameters) } @@ -975,14 +981,19 @@ private fun parseNumericLiteral(text: String, type: IElementType): Any? { } private fun parseLong(text: String): Long? { - try { - fun substringSuffix(s: String) = if (hasLongSuffix(text) || hasUnsignedSuffix(text)) s.substring(0, s.length - 1) else s - fun parseLong(text: String, radix: Int) = java.lang.Long.parseLong(substringSuffix(text), radix) + return try { + val hasUnsignedSuffix = hasUnsignedSuffix(text) + val hasLongSuffix = hasLongSuffix(text) + val textWithoutSuffix = if (hasUnsignedSuffix || hasLongSuffix) text.substring(0, text.length - 1) else text + val (number, radix) = extractRadix(textWithoutSuffix) - val (number, radix) = extractRadix(text) - return parseLong(number, radix) + if (hasUnsignedSuffix) { + java.lang.Long.parseUnsignedLong(number, radix) + } else { + java.lang.Long.parseLong(number, radix) + } } catch (e: NumberFormatException) { - return null + null } } diff --git a/compiler/testData/codegen/box/inlineClasses/unsignedLiteralsWithSignedOverflow.kt b/compiler/testData/codegen/box/inlineClasses/unsignedLiteralsWithSignedOverflow.kt new file mode 100644 index 00000000000..e0af06e0b91 --- /dev/null +++ b/compiler/testData/codegen/box/inlineClasses/unsignedLiteralsWithSignedOverflow.kt @@ -0,0 +1,10 @@ +// !LANGUAGE: +InlineClasses +// !SKIP_METADATA_VERSION_CHECK +// WITH_UNSIGNED + +fun box(): String { + val u1: UByte = 255u + if (u1.toByte().toInt() != -1) return "fail" + + return "OK" +} diff --git a/compiler/testData/diagnostics/testsWithUnsignedTypes/unsignedLiteralsOverflowSignedBorder.kt b/compiler/testData/diagnostics/testsWithUnsignedTypes/unsignedLiteralsOverflowSignedBorder.kt new file mode 100644 index 00000000000..944006e0514 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithUnsignedTypes/unsignedLiteralsOverflowSignedBorder.kt @@ -0,0 +1,25 @@ +// !LANGUAGE: +InlineClasses +// !SKIP_METADATA_VERSION_CHECK +// !DIAGNOSTICS: -UNUSED_PARAMETER + +const val u1: UByte = 0xFFu +const val u2: UShort = 0xFFFFu +const val u3: UInt = 0xFFFF_FFFFu +const val u4: ULong = 0xFFFF_FFFF_FFFF_FFFFu +const val u5: ULong = 18446744073709551615u + +const val u6 = 0xFFFF_FFFF_FFFF_FFFFu +const val u7 = 18446744073709551615u + +val u8: Comparable<*> = 0xFFFF_FFFF_FFFF_FFFFu + +fun takeUByte(ubyte: UByte) {} + +fun test() { + takeUByte(200u) + takeUByte(255u) + takeUByte(0xFFu) +} + +val s1: UByte = 256u +val s2 = 18446744073709551616u \ No newline at end of file diff --git a/compiler/testData/diagnostics/testsWithUnsignedTypes/unsignedLiteralsOverflowSignedBorder.txt b/compiler/testData/diagnostics/testsWithUnsignedTypes/unsignedLiteralsOverflowSignedBorder.txt new file mode 100644 index 00000000000..e5da6b85570 --- /dev/null +++ b/compiler/testData/diagnostics/testsWithUnsignedTypes/unsignedLiteralsOverflowSignedBorder.txt @@ -0,0 +1,14 @@ +package + +public val s1: kotlin.UByte = 256.toUInt() +public val s2: kotlin.Int +public const val u1: kotlin.UByte = -1.toUByte() +public const val u2: kotlin.UShort = -1.toUShort() +public const val u3: kotlin.UInt = -1.toUInt() +public const val u4: kotlin.ULong = -1.toULong() +public const val u5: kotlin.ULong = -1.toULong() +public const val u6: kotlin.ULong = -1.toULong() +public const val u7: kotlin.ULong = -1.toULong() +public val u8: kotlin.Comparable<*> +public fun takeUByte(/*0*/ ubyte: kotlin.UByte): kotlin.Unit +public fun test(): kotlin.Unit diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index f53cbfba631..0bc1accaba3 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -11304,6 +11304,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/inlineClasses/UIntArraySortExample.kt"); } + @TestMetadata("unsignedLiteralsWithSignedOverflow.kt") + public void testUnsignedLiteralsWithSignedOverflow() throws Exception { + runTest("compiler/testData/codegen/box/inlineClasses/unsignedLiteralsWithSignedOverflow.kt"); + } + @TestMetadata("useInlineClassesInsideElvisOperator.kt") public void testUseInlineClassesInsideElvisOperator() throws Exception { runTest("compiler/testData/codegen/box/inlineClasses/useInlineClassesInsideElvisOperator.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithUnsignedTypesGenerated.java b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithUnsignedTypesGenerated.java index 00eb3aa1e59..076ce4320e3 100644 --- a/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithUnsignedTypesGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/checkers/DiagnosticsWithUnsignedTypesGenerated.java @@ -34,6 +34,11 @@ public class DiagnosticsWithUnsignedTypesGenerated extends AbstractDiagnosticsWi runTest("compiler/testData/diagnostics/testsWithUnsignedTypes/unsignedLiteralsInsideConstVals.kt"); } + @TestMetadata("unsignedLiteralsOverflowSignedBorder.kt") + public void testUnsignedLiteralsOverflowSignedBorder() throws Exception { + runTest("compiler/testData/diagnostics/testsWithUnsignedTypes/unsignedLiteralsOverflowSignedBorder.kt"); + } + @TestMetadata("unsignedLiteralsTypeCheck.kt") public void testUnsignedLiteralsTypeCheck() throws Exception { runTest("compiler/testData/diagnostics/testsWithUnsignedTypes/unsignedLiteralsTypeCheck.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index ed265c2d887..905442e9165 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -11304,6 +11304,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { runTest("compiler/testData/codegen/box/inlineClasses/UIntArraySortExample.kt"); } + @TestMetadata("unsignedLiteralsWithSignedOverflow.kt") + public void testUnsignedLiteralsWithSignedOverflow() throws Exception { + runTest("compiler/testData/codegen/box/inlineClasses/unsignedLiteralsWithSignedOverflow.kt"); + } + @TestMetadata("useInlineClassesInsideElvisOperator.kt") public void testUseInlineClassesInsideElvisOperator() throws Exception { runTest("compiler/testData/codegen/box/inlineClasses/useInlineClassesInsideElvisOperator.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 3610b60b3c6..73b1ac1b39d 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -11304,6 +11304,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes runTest("compiler/testData/codegen/box/inlineClasses/UIntArraySortExample.kt"); } + @TestMetadata("unsignedLiteralsWithSignedOverflow.kt") + public void testUnsignedLiteralsWithSignedOverflow() throws Exception { + runTest("compiler/testData/codegen/box/inlineClasses/unsignedLiteralsWithSignedOverflow.kt"); + } + @TestMetadata("useInlineClassesInsideElvisOperator.kt") public void testUseInlineClassesInsideElvisOperator() throws Exception { runTest("compiler/testData/codegen/box/inlineClasses/useInlineClassesInsideElvisOperator.kt"); diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/ConstantValueFactory.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/ConstantValueFactory.kt index cb8def2a96c..290f5a681bd 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/ConstantValueFactory.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/ConstantValueFactory.kt @@ -71,9 +71,9 @@ object ConstantValueFactory { val notNullExpected = TypeUtils.makeNotNullable(expectedType) return if (isUnsigned) { when { - KotlinBuiltIns.isUByte(notNullExpected) && value == value.toByte().toLong() -> UByteValue(value.toByte()) - KotlinBuiltIns.isUShort(notNullExpected) && value == value.toShort().toLong() -> UShortValue(value.toShort()) - KotlinBuiltIns.isUInt(notNullExpected) && value == value.toInt().toLong() -> UIntValue(value.toInt()) + KotlinBuiltIns.isUByte(notNullExpected) && value == value.toByte().fromUByteToLong() -> UByteValue(value.toByte()) + KotlinBuiltIns.isUShort(notNullExpected) && value == value.toShort().fromUShortToLong() -> UShortValue(value.toShort()) + KotlinBuiltIns.isUInt(notNullExpected) && value == value.toInt().fromUIntToLong() -> UIntValue(value.toInt()) KotlinBuiltIns.isULong(notNullExpected) -> ULongValue(value) else -> null } @@ -89,3 +89,7 @@ object ConstantValueFactory { } } } + +fun Byte.fromUByteToLong(): Long = this.toLong() and 0xFF +fun Short.fromUShortToLong(): Long = this.toLong() and 0xFFFF +fun Int.fromUIntToLong(): Long = this.toLong() and 0xFFFF_FFFF diff --git a/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/IntegerValueTypeConstructor.kt b/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/IntegerValueTypeConstructor.kt index d8fb3a7a98a..ec445f986e1 100644 --- a/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/IntegerValueTypeConstructor.kt +++ b/core/descriptors/src/org/jetbrains/kotlin/resolve/constants/IntegerValueTypeConstructor.kt @@ -17,6 +17,7 @@ package org.jetbrains.kotlin.resolve.constants import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.builtins.UnsignedTypes import org.jetbrains.kotlin.descriptors.ModuleDescriptor import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies @@ -45,30 +46,14 @@ class IntegerValueTypeConstructor( } } - // TODO: fix value ranges for unsigned types - checkBoundsAndAddSuperType( - value, - Integer.MIN_VALUE.toLong(), - Integer.MAX_VALUE.toLong(), - if (isUnsigned) unsignedType(KotlinBuiltIns.FQ_NAMES.uInt) else builtIns.intType - ) - checkBoundsAndAddSuperType( - value, - java.lang.Byte.MIN_VALUE.toLong(), - java.lang.Byte.MAX_VALUE.toLong(), - if (isUnsigned) unsignedType(KotlinBuiltIns.FQ_NAMES.uByte) else builtIns.byteType - ) - checkBoundsAndAddSuperType( - value, - java.lang.Short.MIN_VALUE.toLong(), - java.lang.Short.MAX_VALUE.toLong(), - if (isUnsigned) unsignedType(KotlinBuiltIns.FQ_NAMES.uShort) else builtIns.shortType - ) + checkBoundsAndAddSuperType(value, if (isUnsigned) unsignedType(KotlinBuiltIns.FQ_NAMES.uInt) else builtIns.intType) + checkBoundsAndAddSuperType(value, if (isUnsigned) unsignedType(KotlinBuiltIns.FQ_NAMES.uByte) else builtIns.byteType) + checkBoundsAndAddSuperType(value, if (isUnsigned) unsignedType(KotlinBuiltIns.FQ_NAMES.uShort) else builtIns.shortType) supertypes.add(if (isUnsigned) unsignedType(KotlinBuiltIns.FQ_NAMES.uLong) else builtIns.longType) } - private fun checkBoundsAndAddSuperType(value: Long, minValue: Long, maxValue: Long, kotlinType: KotlinType) { - if (value >= minValue && value <= maxValue) { + private fun checkBoundsAndAddSuperType(value: Long, kotlinType: KotlinType) { + if (value in kotlinType.minValue()..kotlinType.maxValue()) { supertypes.add(kotlinType) } } @@ -93,3 +78,32 @@ class IntegerValueTypeConstructor( override fun toString() = "IntegerValueType($value)" } + +private fun KotlinType.minValue(): Long { + if (UnsignedTypes.isUnsignedType(this)) return 0 + return when { + KotlinBuiltIns.isByte(this) -> Byte.MIN_VALUE.toLong() + KotlinBuiltIns.isShort(this) -> Short.MIN_VALUE.toLong() + KotlinBuiltIns.isInt(this) -> Int.MIN_VALUE.toLong() + else -> error("Can't get min value for type: $this") + } +} + +private fun KotlinType.maxValue(): Long { + return when { + KotlinBuiltIns.isByte(this) -> Byte.MAX_VALUE.toLong() + KotlinBuiltIns.isShort(this) -> Short.MAX_VALUE.toLong() + KotlinBuiltIns.isInt(this) -> Int.MAX_VALUE.toLong() + + KotlinBuiltIns.isUByte(this) -> UBYTE_MAX_VALUE + KotlinBuiltIns.isUShort(this) -> USHORT_MAX_VALUE + KotlinBuiltIns.isUInt(this) -> UINT_MAX_VALUE + + else -> error("Can't get max value for type: $this") + } +} + +private val UBYTE_MAX_VALUE = (-1).toByte().fromUByteToLong() +private val USHORT_MAX_VALUE = (-1).toShort().fromUShortToLong() +private val UINT_MAX_VALUE = (-1).fromUIntToLong() + diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index 8e07baf71eb..91bdbf317cd 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -9840,6 +9840,11 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { runTest("compiler/testData/codegen/box/inlineClasses/UIntArraySortExample.kt"); } + @TestMetadata("unsignedLiteralsWithSignedOverflow.kt") + public void testUnsignedLiteralsWithSignedOverflow() throws Exception { + runTest("compiler/testData/codegen/box/inlineClasses/unsignedLiteralsWithSignedOverflow.kt"); + } + @TestMetadata("useInlineClassesInsideElvisOperator.kt") public void testUseInlineClassesInsideElvisOperator() throws Exception { runTest("compiler/testData/codegen/box/inlineClasses/useInlineClassesInsideElvisOperator.kt");