JS: throw exception when accessing uninitialized lateinit property (KT-14964)

This commit is contained in:
Anton Bannykh
2017-08-21 20:13:39 +03:00
parent 49bc9249a1
commit 2249675e59
17 changed files with 100 additions and 81 deletions
@@ -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 {
@@ -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
@@ -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
}
@@ -1,6 +1,3 @@
// TODO: muted automatically, investigate should it be ran for JS or not
// IGNORE_BACKEND: JS
interface Intf {
val str: String
}
@@ -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
@@ -1,6 +1,6 @@
// LANGUAGE_VERSION: 1.2
// WITH_RUNTIME
// IGNORE_BACKEND: JS, NATIVE
// IGNORE_BACKEND: NATIVE
import kotlin.UninitializedPropertyAccessException
@@ -1,6 +1,6 @@
// LANGUAGE_VERSION: 1.2
// WITH_RUNTIME
// IGNORE_BACKEND: JS, NATIVE
// IGNORE_BACKEND: NATIVE
import kotlin.UninitializedPropertyAccessException
@@ -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
@@ -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")
}
+2
View File
@@ -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)
@@ -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")
@@ -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);
}
}
}
@@ -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());
@@ -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() }
}
@@ -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) {
@@ -39,7 +39,7 @@ fun box(): String {
field("e")
field("f")
field("g")
property("h")
field("h")
}
if (expectedRegex.find(aBody) == null) return "fail"
@@ -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 <T> 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"
}