From 2249675e594644f87b297b1d8071e85c091ed506 Mon Sep 17 00:00:00 2001 From: Anton Bannykh Date: Mon, 21 Aug 2017 20:13:39 +0300 Subject: [PATCH] JS: throw exception when accessing uninitialized lateinit property (KT-14964) --- .../properties/lateinit/accessorException.kt | 3 - .../box/properties/lateinit/exceptionField.kt | 3 - .../properties/lateinit/exceptionGetter.kt | 3 - .../properties/lateinit/overrideException.kt | 3 - .../lateinit/topLevel/accessorException.kt | 2 +- .../topLevel/uninitializedMemberAccess.kt | 2 +- .../lateinit/topLevel/uninitializedRead.kt | 2 +- .../box/properties/lateinit/visibility.kt | 4 +- js/js.libraries/src/core/exceptionUtils.kt | 5 ++ js/js.libraries/src/core/exceptions.kt | 2 + .../js/test/semantics/BoxJsTestGenerated.java | 6 ++ .../semantics/JsCodegenBoxTestGenerated.java | 68 ++----------------- .../kotlin/js/translate/context/Namer.java | 6 ++ .../declaration/PropertyTranslator.kt | 5 ++ .../js/translate/utils/JsDescriptorUtils.java | 3 +- .../initialize/propertyInitializationOrder.kt | 2 +- .../box/initialize/uninitializedLateinit.kt | 62 +++++++++++++++++ 17 files changed, 100 insertions(+), 81 deletions(-) create mode 100644 js/js.translator/testData/box/initialize/uninitializedLateinit.kt diff --git a/compiler/testData/codegen/box/properties/lateinit/accessorException.kt b/compiler/testData/codegen/box/properties/lateinit/accessorException.kt index eb0db631364..8650a0fad11 100644 --- a/compiler/testData/codegen/box/properties/lateinit/accessorException.kt +++ b/compiler/testData/codegen/box/properties/lateinit/accessorException.kt @@ -1,6 +1,3 @@ -// TODO: muted automatically, investigate should it be ran for JS or not -// IGNORE_BACKEND: JS - public class A { fun getMyStr(): String { diff --git a/compiler/testData/codegen/box/properties/lateinit/exceptionField.kt b/compiler/testData/codegen/box/properties/lateinit/exceptionField.kt index 0c8a8eafe47..76120e7eff5 100644 --- a/compiler/testData/codegen/box/properties/lateinit/exceptionField.kt +++ b/compiler/testData/codegen/box/properties/lateinit/exceptionField.kt @@ -1,6 +1,3 @@ -// TODO: muted automatically, investigate should it be ran for JS or not -// IGNORE_BACKEND: JS - class A { private lateinit var str: String diff --git a/compiler/testData/codegen/box/properties/lateinit/exceptionGetter.kt b/compiler/testData/codegen/box/properties/lateinit/exceptionGetter.kt index ca12a2442db..35449a38fad 100644 --- a/compiler/testData/codegen/box/properties/lateinit/exceptionGetter.kt +++ b/compiler/testData/codegen/box/properties/lateinit/exceptionGetter.kt @@ -1,6 +1,3 @@ -// TODO: muted automatically, investigate should it be ran for JS or not -// IGNORE_BACKEND: JS - class A { public lateinit var str: String } diff --git a/compiler/testData/codegen/box/properties/lateinit/overrideException.kt b/compiler/testData/codegen/box/properties/lateinit/overrideException.kt index 9b9ce777d53..ffc98f1f128 100644 --- a/compiler/testData/codegen/box/properties/lateinit/overrideException.kt +++ b/compiler/testData/codegen/box/properties/lateinit/overrideException.kt @@ -1,6 +1,3 @@ -// TODO: muted automatically, investigate should it be ran for JS or not -// IGNORE_BACKEND: JS - interface Intf { val str: String } diff --git a/compiler/testData/codegen/box/properties/lateinit/topLevel/accessorException.kt b/compiler/testData/codegen/box/properties/lateinit/topLevel/accessorException.kt index 30bae3b69c4..8b04f78f948 100644 --- a/compiler/testData/codegen/box/properties/lateinit/topLevel/accessorException.kt +++ b/compiler/testData/codegen/box/properties/lateinit/topLevel/accessorException.kt @@ -1,6 +1,6 @@ // LANGUAGE_VERSION: 1.2 // WITH_RUNTIME -// IGNORE_BACKEND: JS, NATIVE +// IGNORE_BACKEND: NATIVE // FILE: lateinit.kt private lateinit var s: String diff --git a/compiler/testData/codegen/box/properties/lateinit/topLevel/uninitializedMemberAccess.kt b/compiler/testData/codegen/box/properties/lateinit/topLevel/uninitializedMemberAccess.kt index fc3196b47a7..ffdedd8745e 100644 --- a/compiler/testData/codegen/box/properties/lateinit/topLevel/uninitializedMemberAccess.kt +++ b/compiler/testData/codegen/box/properties/lateinit/topLevel/uninitializedMemberAccess.kt @@ -1,6 +1,6 @@ // LANGUAGE_VERSION: 1.2 // WITH_RUNTIME -// IGNORE_BACKEND: JS, NATIVE +// IGNORE_BACKEND: NATIVE import kotlin.UninitializedPropertyAccessException diff --git a/compiler/testData/codegen/box/properties/lateinit/topLevel/uninitializedRead.kt b/compiler/testData/codegen/box/properties/lateinit/topLevel/uninitializedRead.kt index fdf0050122b..c7055a8d13c 100644 --- a/compiler/testData/codegen/box/properties/lateinit/topLevel/uninitializedRead.kt +++ b/compiler/testData/codegen/box/properties/lateinit/topLevel/uninitializedRead.kt @@ -1,6 +1,6 @@ // LANGUAGE_VERSION: 1.2 // WITH_RUNTIME -// IGNORE_BACKEND: JS, NATIVE +// IGNORE_BACKEND: NATIVE import kotlin.UninitializedPropertyAccessException diff --git a/compiler/testData/codegen/box/properties/lateinit/visibility.kt b/compiler/testData/codegen/box/properties/lateinit/visibility.kt index e2ae4d9dc1e..7773f38e555 100644 --- a/compiler/testData/codegen/box/properties/lateinit/visibility.kt +++ b/compiler/testData/codegen/box/properties/lateinit/visibility.kt @@ -1,6 +1,4 @@ -// TODO: muted automatically, investigate should it be ran for JS or not -// IGNORE_BACKEND: JS, NATIVE - +// TARGET_BACKEND: JVM // WITH_RUNTIME // FULL_JDK diff --git a/js/js.libraries/src/core/exceptionUtils.kt b/js/js.libraries/src/core/exceptionUtils.kt index 11120f8bff1..87288ae4085 100644 --- a/js/js.libraries/src/core/exceptionUtils.kt +++ b/js/js.libraries/src/core/exceptionUtils.kt @@ -30,3 +30,8 @@ private fun throwCCE() { private fun throwISE(message: String) { throw IllegalStateException(message) } + +@JsName("throwUPAE") +private fun throwUPAE(propertyName: String) { + throw UninitializedPropertyAccessException("lateinit property ${propertyName} has not been initialized") +} diff --git a/js/js.libraries/src/core/exceptions.kt b/js/js.libraries/src/core/exceptions.kt index 4d305f75ac7..e0c2865f9f5 100644 --- a/js/js.libraries/src/core/exceptions.kt +++ b/js/js.libraries/src/core/exceptions.kt @@ -43,3 +43,5 @@ public open class AssertionError(message: String? = null) : Error(message) public open class NoSuchElementException(message: String? = null) : Exception(message) public open class NoWhenBranchMatchedException(message: String? = null) : RuntimeException(message) + +public open class UninitializedPropertyAccessException(message: String? = null) : RuntimeException(message) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java index 93f636c185a..a04a3a63af5 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/BoxJsTestGenerated.java @@ -3916,6 +3916,12 @@ public class BoxJsTestGenerated extends AbstractBoxJsTest { String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/initialize/rootValInit.kt"); doTest(fileName); } + + @TestMetadata("uninitializedLateinit.kt") + public void testUninitializedLateinit() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("js/js.translator/testData/box/initialize/uninitializedLateinit.kt"); + doTest(fileName); + } } @TestMetadata("js/js.translator/testData/box/inline") 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 4846ea27cb0..693f91abac1 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 @@ -15003,13 +15003,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { @TestMetadata("accessorException.kt") public void testAccessorException() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/accessorException.kt"); - try { - doTest(fileName); - } - catch (Throwable ignore) { - return; - } - throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + doTest(fileName); } public void testAllFilesPresentInLateinit() throws Exception { @@ -15019,25 +15013,13 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { @TestMetadata("exceptionField.kt") public void testExceptionField() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/exceptionField.kt"); - try { - doTest(fileName); - } - catch (Throwable ignore) { - return; - } - throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + doTest(fileName); } @TestMetadata("exceptionGetter.kt") public void testExceptionGetter() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/exceptionGetter.kt"); - try { - doTest(fileName); - } - catch (Throwable ignore) { - return; - } - throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + doTest(fileName); } @TestMetadata("override.kt") @@ -15049,13 +15031,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { @TestMetadata("overrideException.kt") public void testOverrideException() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/overrideException.kt"); - try { - doTest(fileName); - } - catch (Throwable ignore) { - return; - } - throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + doTest(fileName); } @TestMetadata("privateSetter.kt") @@ -15076,18 +15052,6 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { doTest(fileName); } - @TestMetadata("visibility.kt") - public void testVisibility() throws Exception { - String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/visibility.kt"); - try { - doTest(fileName); - } - catch (Throwable ignore) { - return; - } - throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); - } - @TestMetadata("compiler/testData/codegen/box/properties/lateinit/local") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) @@ -15164,13 +15128,7 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { @TestMetadata("accessorException.kt") public void testAccessorException() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/topLevel/accessorException.kt"); - try { - doTest(fileName); - } - catch (Throwable ignore) { - return; - } - throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + doTest(fileName); } @TestMetadata("accessorForTopLevelLateinit.kt") @@ -15192,25 +15150,13 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { @TestMetadata("uninitializedMemberAccess.kt") public void testUninitializedMemberAccess() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/topLevel/uninitializedMemberAccess.kt"); - try { - doTest(fileName); - } - catch (Throwable ignore) { - return; - } - throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + doTest(fileName); } @TestMetadata("uninitializedRead.kt") public void testUninitializedRead() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/properties/lateinit/topLevel/uninitializedRead.kt"); - try { - doTest(fileName); - } - catch (Throwable ignore) { - return; - } - throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + doTest(fileName); } } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java index 984131ad059..1e5ae3c551b 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/context/Namer.java @@ -90,6 +90,7 @@ public final class Namer { private static final String THROW_NPE_FUN_NAME = "throwNPE"; private static final String THROW_CLASS_CAST_EXCEPTION_FUN_NAME = "throwCCE"; private static final String THROW_ILLEGAL_STATE_EXCEPTION_FUN_NAME = "throwISE"; + private static final String THROW_UNINITIALIZED_PROPERTY_ACCESS_EXCEPTION = "throwUPAE"; private static final String PROTOTYPE_NAME = "prototype"; private static final String CAPTURED_VAR_FIELD = "v"; @@ -235,6 +236,11 @@ public final class Namer { return new JsNameRef(THROW_ILLEGAL_STATE_EXCEPTION_FUN_NAME, kotlinObject()); } + @NotNull + public static JsExpression throwUninitializedPropertyAccessExceptionFunRef() { + return new JsNameRef(THROW_UNINITIALIZED_PROPERTY_ACCESS_EXCEPTION, kotlinObject()); + } + @NotNull public static JsNameRef kotlin(@NotNull JsName name) { return pureFqn(name, kotlinObject()); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/PropertyTranslator.kt b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/PropertyTranslator.kt index 14e0f170618..2ffe76ee9c7 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/PropertyTranslator.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/declaration/PropertyTranslator.kt @@ -94,6 +94,11 @@ class DefaultPropertyTranslator( assert(!descriptor.isExtension) { "Unexpected extension property $descriptor}" } assert(descriptor is PropertyDescriptor) { "Property descriptor expected: $descriptor" } val result = backingFieldReference(context(), descriptor as PropertyDescriptor) + if (getterDescriptor is PropertyAccessorDescriptor && getterDescriptor.correspondingProperty.isLateInit) { + function.body.statements += JsIf(JsBinaryOperation(JsBinaryOperator.EQ, result, JsNullLiteral()), + JsReturn(JsInvocation(Namer.throwUninitializedPropertyAccessExceptionFunRef(), + JsStringLiteral(getterDescriptor.correspondingProperty.name.asString())))) + } function.body.statements += JsReturn(result).apply { source = descriptor.source.getPsi() } } diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsDescriptorUtils.java b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsDescriptorUtils.java index ea6c1a3d851..142ce065aa2 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsDescriptorUtils.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/translate/utils/JsDescriptorUtils.java @@ -125,7 +125,8 @@ public final class JsDescriptorUtils { } private static boolean isDefaultAccessor(@Nullable PropertyAccessorDescriptor accessorDescriptor) { - return accessorDescriptor == null || accessorDescriptor.isDefault(); + return accessorDescriptor == null || accessorDescriptor.isDefault() && + !(accessorDescriptor instanceof PropertySetterDescriptor && accessorDescriptor.getCorrespondingProperty().isLateInit()); } public static boolean sideEffectsPossibleOnRead(@NotNull PropertyDescriptor property) { diff --git a/js/js.translator/testData/box/initialize/propertyInitializationOrder.kt b/js/js.translator/testData/box/initialize/propertyInitializationOrder.kt index 2cdce2ecd94..ccea9bf12de 100644 --- a/js/js.translator/testData/box/initialize/propertyInitializationOrder.kt +++ b/js/js.translator/testData/box/initialize/propertyInitializationOrder.kt @@ -39,7 +39,7 @@ fun box(): String { field("e") field("f") field("g") - property("h") + field("h") } if (expectedRegex.find(aBody) == null) return "fail" diff --git a/js/js.translator/testData/box/initialize/uninitializedLateinit.kt b/js/js.translator/testData/box/initialize/uninitializedLateinit.kt new file mode 100644 index 00000000000..bf9ebc7cbb5 --- /dev/null +++ b/js/js.translator/testData/box/initialize/uninitializedLateinit.kt @@ -0,0 +1,62 @@ +// EXPECTED_REACHABLE_NODES: 1059 +// MODULE: lib +// FILE: lib.kt +open class A { + private lateinit var a: Any + open lateinit var c: Any + + fun useA() = a + fun useC() = c + + companion object { + lateinit var b: Any + + fun useB() = b + } +} + +object O { + lateinit var o: Any + + fun useO() = o +} + +fun checkException(propertyName: String, f: () -> T) { + try { + f() + } + catch (e: kotlin.UninitializedPropertyAccessException) { + assertEquals(e.message, "lateinit property ${propertyName} has not been initialized") + } +} + +fun runChecks() { + checkException("a") { A().useA() } + checkException("b") { A.b } + checkException("b") { A.useB() } + checkException("o") { O.o } + checkException("o") { O.useO() } +} + +// MODULE: main(lib) +// FILE: main.kt + +class C: A() { + override lateinit var c: Any +} + +fun box(): String { + runChecks() + + checkException("a") { A().useA() } + checkException("b") { A.b } + checkException("b") { A.useB() } + checkException("o") { O.o } + checkException("o") { O.useO() } + + checkException("a") { C().useA() } + checkException("c") { C().c } + checkException("c") { C().useC() } + + return "OK" +} \ No newline at end of file