diff --git a/compiler/testData/codegen/box/callableReference/serializability/boundWithNotSerializableReceiver.kt b/compiler/testData/codegen/box/callableReference/serializability/boundWithNotSerializableReceiver.kt new file mode 100644 index 00000000000..63e25710c5e --- /dev/null +++ b/compiler/testData/codegen/box/callableReference/serializability/boundWithNotSerializableReceiver.kt @@ -0,0 +1,19 @@ +// IGNORE_BACKEND: JS +// WITH_REFLECT +// FULL_JDK + +import java.io.* +import kotlin.test.* + +class Foo(val value: String) + +fun box(): String { + val oos = ObjectOutputStream(ByteArrayOutputStream()) + try { + oos.writeObject(Foo("abacaba")::value) + return "Fail: Foo is not Serializable and thus writeObject should have thrown an exception" + } + catch (e: NotSerializableException) { + return "OK" + } +} diff --git a/compiler/testData/codegen/box/callableReference/serializability/boundWithSerializableReceiver.kt b/compiler/testData/codegen/box/callableReference/serializability/boundWithSerializableReceiver.kt new file mode 100644 index 00000000000..64a7d8d3d91 --- /dev/null +++ b/compiler/testData/codegen/box/callableReference/serializability/boundWithSerializableReceiver.kt @@ -0,0 +1,21 @@ +// IGNORE_BACKEND: JS +// WITH_REFLECT + +import java.io.* +import kotlin.test.* + +data class Foo(val value: String) : Serializable + +fun box(): String { + val baos = ByteArrayOutputStream() + val oos = ObjectOutputStream(baos) + oos.writeObject(Foo("abacaba")::value) + oos.close() + + val bais = ByteArrayInputStream(baos.toByteArray()) + val ois = ObjectInputStream(bais) + assertEquals(Foo("abacaba")::value, ois.readObject()) + ois.close() + + return "OK" +} diff --git a/compiler/testData/codegen/box/callableReference/serializability/noReflect.kt b/compiler/testData/codegen/box/callableReference/serializability/noReflect.kt new file mode 100644 index 00000000000..253b8267772 --- /dev/null +++ b/compiler/testData/codegen/box/callableReference/serializability/noReflect.kt @@ -0,0 +1,27 @@ +// IGNORE_BACKEND: JS +// WITH_RUNTIME + +import java.io.* +import kotlin.test.* + +class Foo(val prop: String) { + fun method() {} +} + +fun box(): String { + val baos = ByteArrayOutputStream() + val oos = ObjectOutputStream(baos) + oos.writeObject(Foo::prop) + oos.writeObject(Foo::method) + oos.writeObject(::Foo) + oos.close() + + val bais = ByteArrayInputStream(baos.toByteArray()) + val ois = ObjectInputStream(bais) + assertEquals(Foo::prop, ois.readObject()) + assertEquals(Foo::method, ois.readObject()) + assertEquals(::Foo, ois.readObject()) + ois.close() + + return "OK" +} diff --git a/compiler/testData/codegen/box/callableReference/serializability/reflectedIsNotSerialized.kt b/compiler/testData/codegen/box/callableReference/serializability/reflectedIsNotSerialized.kt new file mode 100644 index 00000000000..dca58c20b69 --- /dev/null +++ b/compiler/testData/codegen/box/callableReference/serializability/reflectedIsNotSerialized.kt @@ -0,0 +1,25 @@ +// IGNORE_BACKEND: JS +// WITH_REFLECT + +import java.io.* +import kotlin.test.* + +fun bar() {} + +fun box(): String { + val baos = ByteArrayOutputStream() + val oos = ObjectOutputStream(baos) + oos.writeObject(::bar) + oos.close() + + val bais = ByteArrayInputStream(baos.toByteArray()) + val ois = ObjectInputStream(bais) + val o = ois.readObject() + ois.close() + + // Test that we don't serialize the reflected view of the reference: it's not needed because it can be restored at runtime + val field = kotlin.jvm.internal.CallableReference::class.java.getDeclaredField("reflected").apply { isAccessible = true } + assertNull(field.get(o)) + + return "OK" +} diff --git a/compiler/testData/codegen/box/callableReference/serializability/withReflect.kt b/compiler/testData/codegen/box/callableReference/serializability/withReflect.kt new file mode 100644 index 00000000000..4b1369b240c --- /dev/null +++ b/compiler/testData/codegen/box/callableReference/serializability/withReflect.kt @@ -0,0 +1,27 @@ +// IGNORE_BACKEND: JS +// WITH_REFLECT + +import java.io.* +import kotlin.test.* + +class Foo(val prop: String) { + fun method() {} +} + +fun box(): String { + val baos = ByteArrayOutputStream() + val oos = ObjectOutputStream(baos) + oos.writeObject(Foo::prop) + oos.writeObject(Foo::method) + oos.writeObject(::Foo) + oos.close() + + val bais = ByteArrayInputStream(baos.toByteArray()) + val ois = ObjectInputStream(bais) + assertEquals(Foo::prop, ois.readObject()) + assertEquals(Foo::method, ois.readObject()) + assertEquals(::Foo, ois.readObject()) + ois.close() + + return "OK" +} diff --git a/compiler/testData/codegen/light-analysis/callableReference/callableReferencesAreSerializable.txt b/compiler/testData/codegen/light-analysis/callableReference/callableReferencesAreSerializable.txt new file mode 100644 index 00000000000..e9bfc087910 --- /dev/null +++ b/compiler/testData/codegen/light-analysis/callableReference/callableReferencesAreSerializable.txt @@ -0,0 +1,12 @@ +@kotlin.Metadata +public final class CallableReferencesAreSerializableKt { + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String +} + +@kotlin.Metadata +public final class Foo { + private final @org.jetbrains.annotations.NotNull field prop: java.lang.String + public method (@org.jetbrains.annotations.NotNull p0: java.lang.String): void + public final @org.jetbrains.annotations.NotNull method getProp(): java.lang.String + public final method method(): void +} diff --git a/compiler/testData/codegen/light-analysis/callableReference/serializability/boundWithNotSerializableReceiver.txt b/compiler/testData/codegen/light-analysis/callableReference/serializability/boundWithNotSerializableReceiver.txt new file mode 100644 index 00000000000..d6758265b53 --- /dev/null +++ b/compiler/testData/codegen/light-analysis/callableReference/serializability/boundWithNotSerializableReceiver.txt @@ -0,0 +1,11 @@ +@kotlin.Metadata +public final class BoundWithNotSerializableReceiverKt { + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String +} + +@kotlin.Metadata +public final class Foo { + private final @org.jetbrains.annotations.NotNull field value: java.lang.String + public method (@org.jetbrains.annotations.NotNull p0: java.lang.String): void + public final @org.jetbrains.annotations.NotNull method getValue(): java.lang.String +} diff --git a/compiler/testData/codegen/light-analysis/callableReference/serializability/boundWithSerializableReceiver.txt b/compiler/testData/codegen/light-analysis/callableReference/serializability/boundWithSerializableReceiver.txt new file mode 100644 index 00000000000..0ea1f477bd6 --- /dev/null +++ b/compiler/testData/codegen/light-analysis/callableReference/serializability/boundWithSerializableReceiver.txt @@ -0,0 +1,17 @@ +@kotlin.Metadata +public final class BoundWithSerializableReceiverKt { + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String +} + +@kotlin.Metadata +public final class Foo { + private final @org.jetbrains.annotations.NotNull field value: java.lang.String + public method (@org.jetbrains.annotations.NotNull p0: java.lang.String): void + public final @org.jetbrains.annotations.NotNull method component1(): java.lang.String + public synthetic static method copy$default(p0: Foo, p1: java.lang.String, p2: int, p3: java.lang.Object): Foo + public final @org.jetbrains.annotations.NotNull method copy(@org.jetbrains.annotations.NotNull p0: java.lang.String): Foo + public method equals(p0: java.lang.Object): boolean + public final @org.jetbrains.annotations.NotNull method getValue(): java.lang.String + public method hashCode(): int + public method toString(): java.lang.String +} diff --git a/compiler/testData/codegen/light-analysis/callableReference/serializability/noReflect.txt b/compiler/testData/codegen/light-analysis/callableReference/serializability/noReflect.txt new file mode 100644 index 00000000000..98909a89a36 --- /dev/null +++ b/compiler/testData/codegen/light-analysis/callableReference/serializability/noReflect.txt @@ -0,0 +1,12 @@ +@kotlin.Metadata +public final class Foo { + private final @org.jetbrains.annotations.NotNull field prop: java.lang.String + public method (@org.jetbrains.annotations.NotNull p0: java.lang.String): void + public final @org.jetbrains.annotations.NotNull method getProp(): java.lang.String + public final method method(): void +} + +@kotlin.Metadata +public final class NoReflectKt { + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String +} diff --git a/compiler/testData/codegen/light-analysis/callableReference/serializability/reflectedIsNotSerialized.txt b/compiler/testData/codegen/light-analysis/callableReference/serializability/reflectedIsNotSerialized.txt new file mode 100644 index 00000000000..354d31aeb71 --- /dev/null +++ b/compiler/testData/codegen/light-analysis/callableReference/serializability/reflectedIsNotSerialized.txt @@ -0,0 +1,5 @@ +@kotlin.Metadata +public final class ReflectedIsNotSerializedKt { + public final static method bar(): void + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String +} diff --git a/compiler/testData/codegen/light-analysis/callableReference/serializability/withReflect.txt b/compiler/testData/codegen/light-analysis/callableReference/serializability/withReflect.txt new file mode 100644 index 00000000000..5890a9f5184 --- /dev/null +++ b/compiler/testData/codegen/light-analysis/callableReference/serializability/withReflect.txt @@ -0,0 +1,12 @@ +@kotlin.Metadata +public final class Foo { + private final @org.jetbrains.annotations.NotNull field prop: java.lang.String + public method (@org.jetbrains.annotations.NotNull p0: java.lang.String): void + public final @org.jetbrains.annotations.NotNull method getProp(): java.lang.String + public final method method(): void +} + +@kotlin.Metadata +public final class WithReflectKt { + public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String +} 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 95e0a4c464e..a45f16e79f1 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 @@ -2242,6 +2242,45 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } } + + @TestMetadata("compiler/testData/codegen/box/callableReference/serializability") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Serializability extends AbstractIrBlackBoxCodegenTest { + public void testAllFilesPresentInSerializability() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/callableReference/serializability"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("boundWithNotSerializableReceiver.kt") + public void testBoundWithNotSerializableReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/boundWithNotSerializableReceiver.kt"); + doTest(fileName); + } + + @TestMetadata("boundWithSerializableReceiver.kt") + public void testBoundWithSerializableReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/boundWithSerializableReceiver.kt"); + doTest(fileName); + } + + @TestMetadata("noReflect.kt") + public void testNoReflect() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/noReflect.kt"); + doTest(fileName); + } + + @TestMetadata("reflectedIsNotSerialized.kt") + public void testReflectedIsNotSerialized() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/reflectedIsNotSerialized.kt"); + doTest(fileName); + } + + @TestMetadata("withReflect.kt") + public void testWithReflect() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/withReflect.kt"); + doTest(fileName); + } + } } @TestMetadata("compiler/testData/codegen/box/casts") diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 3693b948e46..78c5dd2e2be 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -2242,6 +2242,45 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } } + + @TestMetadata("compiler/testData/codegen/box/callableReference/serializability") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Serializability extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInSerializability() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/callableReference/serializability"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("boundWithNotSerializableReceiver.kt") + public void testBoundWithNotSerializableReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/boundWithNotSerializableReceiver.kt"); + doTest(fileName); + } + + @TestMetadata("boundWithSerializableReceiver.kt") + public void testBoundWithSerializableReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/boundWithSerializableReceiver.kt"); + doTest(fileName); + } + + @TestMetadata("noReflect.kt") + public void testNoReflect() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/noReflect.kt"); + doTest(fileName); + } + + @TestMetadata("reflectedIsNotSerialized.kt") + public void testReflectedIsNotSerialized() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/reflectedIsNotSerialized.kt"); + doTest(fileName); + } + + @TestMetadata("withReflect.kt") + public void testWithReflect() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/withReflect.kt"); + doTest(fileName); + } + } } @TestMetadata("compiler/testData/codegen/box/casts") diff --git a/core/runtime.jvm/src/kotlin/jvm/internal/CallableReference.java b/core/runtime.jvm/src/kotlin/jvm/internal/CallableReference.java index d679a7a0239..78f55e7a9f6 100644 --- a/core/runtime.jvm/src/kotlin/jvm/internal/CallableReference.java +++ b/core/runtime.jvm/src/kotlin/jvm/internal/CallableReference.java @@ -22,6 +22,8 @@ import kotlin.reflect.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.io.ObjectStreamException; +import java.io.Serializable; import java.lang.annotation.Annotation; import java.util.List; import java.util.Map; @@ -33,18 +35,27 @@ import java.util.Map; * and stored in the {@link CallableReference#reflected} field. */ @SuppressWarnings({"unchecked", "NullableProblems"}) -public abstract class CallableReference implements KCallable { +public abstract class CallableReference implements KCallable, Serializable { // This field is not volatile intentionally: // 1) It's fine if the value is computed multiple times in different threads; // 2) An uninitialized value cannot be observed in this field from other thread because only already initialized or safely initialized // objects are written to it. The latter is guaranteed because both KFunctionImpl and KPropertyImpl have at least one final field. - private KCallable reflected; + private transient KCallable reflected; @SinceKotlin(version = "1.1") protected final Object receiver; @SinceKotlin(version = "1.1") - public static final Object NO_RECEIVER = new Object(); + public static final Object NO_RECEIVER = NoReceiver.INSTANCE; + + @SinceKotlin(version = "1.2") + private static class NoReceiver implements Serializable { + private static final NoReceiver INSTANCE = new NoReceiver(); + + private Object readResolve() throws ObjectStreamException { + return INSTANCE; + } + } public CallableReference() { this(NO_RECEIVER); 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 9d2e862d972..5c247ae2c0c 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 @@ -2627,6 +2627,75 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { doTest(fileName); } } + + @TestMetadata("compiler/testData/codegen/box/callableReference/serializability") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Serializability extends AbstractJsCodegenBoxTest { + public void testAllFilesPresentInSerializability() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/callableReference/serializability"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true); + } + + @TestMetadata("boundWithNotSerializableReceiver.kt") + public void testBoundWithNotSerializableReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/boundWithNotSerializableReceiver.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("boundWithSerializableReceiver.kt") + public void testBoundWithSerializableReceiver() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/boundWithSerializableReceiver.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("noReflect.kt") + public void testNoReflect() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/noReflect.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("reflectedIsNotSerialized.kt") + public void testReflectedIsNotSerialized() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/reflectedIsNotSerialized.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("withReflect.kt") + public void testWithReflect() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/callableReference/serializability/withReflect.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/casts") diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-runtime.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-runtime.txt index aa914b860a4..07c0e5127b4 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-runtime.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-runtime.txt @@ -369,7 +369,7 @@ public final class kotlin/jvm/internal/ByteSpreadBuilder : kotlin/jvm/internal/P public final fun toArray ()[B } -public abstract class kotlin/jvm/internal/CallableReference : kotlin/reflect/KCallable { +public abstract class kotlin/jvm/internal/CallableReference : java/io/Serializable, kotlin/reflect/KCallable { public static final field NO_RECEIVER Ljava/lang/Object; protected final field receiver Ljava/lang/Object; public fun ()V diff --git a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt index 07f9c1b5483..c8190636fe9 100644 --- a/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt +++ b/libraries/tools/binary-compatibility-validator/reference-public-api/kotlin-stdlib-runtime-merged.txt @@ -2364,7 +2364,7 @@ public final class kotlin/jvm/internal/ByteSpreadBuilder : kotlin/jvm/internal/P public final fun toArray ()[B } -public abstract class kotlin/jvm/internal/CallableReference : kotlin/reflect/KCallable { +public abstract class kotlin/jvm/internal/CallableReference : java/io/Serializable, kotlin/reflect/KCallable { public static final field NO_RECEIVER Ljava/lang/Object; protected final field receiver Ljava/lang/Object; public fun ()V