Reflection: fix exceptions on concurrent access to some properties
Using `ReflectionProperties.lazy` is incorrect because it allows several threads to observe different resulting values if they're computing it simultaneously (unlike `lazy(PUBLICATION)`, which always returns the value that "won the race"). In the case of property delegates, for example, if we're invoking `isAccessible = true` and then `getDelegate()` concurrently, it might happen that when some thread invokes `getDelegate()`, it gets the underlying Field object which was written by another thread and which has not yet been made accessible, leading to IllegalPropertyDelegateAccessException. #KT-27585 Fixed
This commit is contained in:
committed by
Space Team
parent
e8f95a3376
commit
99b38ccb74
+1
-1
@@ -164,7 +164,7 @@
|
|||||||
/compiler/testData/psi/ "Kotlin Compiler Core"
|
/compiler/testData/psi/ "Kotlin Compiler Core"
|
||||||
/compiler/testData/psiUtil/ "Kotlin Compiler Core"
|
/compiler/testData/psiUtil/ "Kotlin Compiler Core"
|
||||||
/compiler/testData/recursiveProcessor/ "Kotlin Compiler Core"
|
/compiler/testData/recursiveProcessor/ "Kotlin Compiler Core"
|
||||||
/compiler/testData/reflection/classLoaderForBuiltIns/ "Kotlin Libraries"
|
/compiler/testData/reflection/ "Kotlin Libraries"
|
||||||
/compiler/testData/repl/ "Kotlin Compiler Core"
|
/compiler/testData/repl/ "Kotlin Compiler Core"
|
||||||
/compiler/testData/resolve/ "Kotlin Compiler Core"
|
/compiler/testData/resolve/ "Kotlin Compiler Core"
|
||||||
/compiler/testData/resolveAnnotations/ "Kotlin Compiler Core"
|
/compiler/testData/resolveAnnotations/ "Kotlin Compiler Core"
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
import java.util.concurrent.CyclicBarrier
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
import kotlin.reflect.full.*
|
||||||
|
import kotlin.reflect.jvm.*
|
||||||
|
|
||||||
|
const val N_THREADS = 50
|
||||||
|
|
||||||
|
class C {
|
||||||
|
private fun function() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val instance = C()
|
||||||
|
val reference = C::class.functions.single { it.name == "function" }
|
||||||
|
|
||||||
|
val gate = CyclicBarrier(N_THREADS + 1)
|
||||||
|
var fail = AtomicReference<Throwable?>(null)
|
||||||
|
var finished = AtomicInteger(0)
|
||||||
|
for (i in 0 until N_THREADS) {
|
||||||
|
thread {
|
||||||
|
gate.await()
|
||||||
|
reference.isAccessible = true
|
||||||
|
try {
|
||||||
|
reference.javaMethod!!.invoke(instance)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
fail.set(e)
|
||||||
|
}
|
||||||
|
finished.incrementAndGet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gate.await()
|
||||||
|
|
||||||
|
while (finished.get() != N_THREADS) Thread.sleep(25L)
|
||||||
|
fail.get()?.let { throw it }
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import java.util.concurrent.atomic.AtomicReference
|
||||||
|
import java.util.concurrent.CyclicBarrier
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
import kotlin.reflect.jvm.*
|
||||||
|
|
||||||
|
const val N_THREADS = 50
|
||||||
|
|
||||||
|
class Delegate {
|
||||||
|
operator fun getValue(x: Any?, y: Any?): String = "OK"
|
||||||
|
operator fun setValue(x: Any?, y: Any?, z: String) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
var property by Delegate()
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val reference = ::property
|
||||||
|
|
||||||
|
val gate = CyclicBarrier(N_THREADS + 1)
|
||||||
|
var fail = AtomicReference<Throwable?>(null)
|
||||||
|
var finished = AtomicInteger(0)
|
||||||
|
for (i in 0 until N_THREADS) {
|
||||||
|
thread {
|
||||||
|
gate.await()
|
||||||
|
reference.isAccessible = true
|
||||||
|
try {
|
||||||
|
reference.getDelegate()!!
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
fail.set(e)
|
||||||
|
}
|
||||||
|
finished.incrementAndGet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gate.await()
|
||||||
|
|
||||||
|
while (finished.get() != N_THREADS) Thread.sleep(25L)
|
||||||
|
fail.get()?.let { throw it }
|
||||||
|
}
|
||||||
@@ -34,25 +34,15 @@ class ReflectionIntegrationTest : KtUsefulTestCase() {
|
|||||||
|
|
||||||
val lib = CompilerTestUtil.compileJvmLibrary(File("$root/test.kt"))
|
val lib = CompilerTestUtil.compileJvmLibrary(File("$root/test.kt"))
|
||||||
|
|
||||||
val javaHome = System.getProperty("java.home")
|
runJava(
|
||||||
val javaExe = File(javaHome, "bin/java.exe").takeIf(File::exists)
|
|
||||||
?: File(javaHome, "bin/java").takeIf(File::exists)
|
|
||||||
?: error("Can't find 'java' executable in $javaHome")
|
|
||||||
|
|
||||||
val command = arrayOf(
|
|
||||||
javaExe.absolutePath,
|
|
||||||
"-ea",
|
"-ea",
|
||||||
"-classpath",
|
"-classpath",
|
||||||
tmpdir.absolutePath,
|
tmpdir.absolutePath,
|
||||||
"Main",
|
"Main",
|
||||||
lib.absolutePath,
|
lib.absolutePath,
|
||||||
ForTestCompileRuntime.runtimeJarForTests().absolutePath,
|
ForTestCompileRuntime.runtimeJarForTests().absolutePath,
|
||||||
ForTestCompileRuntime.reflectJarForTests().absolutePath
|
ForTestCompileRuntime.reflectJarForTests().absolutePath,
|
||||||
)
|
)
|
||||||
|
|
||||||
val process = ProcessBuilder(*command).inheritIO().start()
|
|
||||||
process.waitFor(1, TimeUnit.MINUTES)
|
|
||||||
assertEquals(0, process.exitValue())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This test checks that simultaneous access to kotlin-reflect from different threads works, in case the URLClassLoader instance is
|
// This test checks that simultaneous access to kotlin-reflect from different threads works, in case the URLClassLoader instance is
|
||||||
@@ -95,4 +85,41 @@ class ReflectionIntegrationTest : KtUsefulTestCase() {
|
|||||||
|
|
||||||
error.get()?.let { throw it }
|
error.get()?.let { throw it }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun testConcurrentAccessToPropertyDelegate() {
|
||||||
|
compileAndRunProgram(KtTestUtil.getTestDataPathBase() + "/reflection/concurrentAccessToPropertyDelegate")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testConcurrentAccessToPrivateFunction() {
|
||||||
|
compileAndRunProgram(KtTestUtil.getTestDataPathBase() + "/reflection/concurrentAccessToPrivateFunction")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun compileAndRunProgram(root: String) {
|
||||||
|
val lib = CompilerTestUtil.compileJvmLibrary(File("$root/test.kt"))
|
||||||
|
|
||||||
|
runJava(
|
||||||
|
"-ea",
|
||||||
|
"-classpath",
|
||||||
|
listOf(
|
||||||
|
ForTestCompileRuntime.runtimeJarForTests().absolutePath,
|
||||||
|
ForTestCompileRuntime.reflectJarForTests().absolutePath,
|
||||||
|
lib.absolutePath,
|
||||||
|
).joinToString(File.pathSeparator),
|
||||||
|
"TestKt",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun runJava(vararg args: String) {
|
||||||
|
val javaHome = System.getProperty("java.home")
|
||||||
|
val javaExe = File(javaHome, "bin/java.exe").takeIf(File::exists)
|
||||||
|
?: File(javaHome, "bin/java").takeIf(File::exists)
|
||||||
|
?: error("Can't find 'java' executable in $javaHome")
|
||||||
|
|
||||||
|
val process = ProcessBuilder(javaExe.absolutePath, *args).start()
|
||||||
|
process.waitFor(1, TimeUnit.MINUTES)
|
||||||
|
val stderr = process.errorStream.reader().readText()
|
||||||
|
val stdout = process.inputStream.reader().readText()
|
||||||
|
val exitCode = process.exitValue()
|
||||||
|
assertEquals("Program exited with exit code $exitCode.\nStdout:\n$stdout\nStderr:\n$stderr", 0, exitCode)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import java.lang.reflect.Constructor
|
|||||||
import java.lang.reflect.Member
|
import java.lang.reflect.Member
|
||||||
import java.lang.reflect.Method
|
import java.lang.reflect.Method
|
||||||
import java.lang.reflect.Modifier
|
import java.lang.reflect.Modifier
|
||||||
|
import kotlin.LazyThreadSafetyMode.PUBLICATION
|
||||||
import kotlin.jvm.internal.CallableReference
|
import kotlin.jvm.internal.CallableReference
|
||||||
import kotlin.jvm.internal.FunctionBase
|
import kotlin.jvm.internal.FunctionBase
|
||||||
import kotlin.reflect.KFunction
|
import kotlin.reflect.KFunction
|
||||||
@@ -58,7 +59,7 @@ internal class KFunctionImpl private constructor(
|
|||||||
|
|
||||||
override val name: String get() = descriptor.name.asString()
|
override val name: String get() = descriptor.name.asString()
|
||||||
|
|
||||||
override val caller: Caller<*> by ReflectProperties.lazy caller@{
|
override val caller: Caller<*> by lazy(PUBLICATION) caller@{
|
||||||
val member: Member? = when (val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)) {
|
val member: Member? = when (val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)) {
|
||||||
is KotlinConstructor -> {
|
is KotlinConstructor -> {
|
||||||
if (isAnnotationConstructor)
|
if (isAnnotationConstructor)
|
||||||
@@ -89,7 +90,7 @@ internal class KFunctionImpl private constructor(
|
|||||||
}.createInlineClassAwareCallerIfNeeded(descriptor)
|
}.createInlineClassAwareCallerIfNeeded(descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val defaultCaller: Caller<*>? by ReflectProperties.lazy defaultCaller@{
|
override val defaultCaller: Caller<*>? by lazy(PUBLICATION) defaultCaller@{
|
||||||
val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)
|
val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)
|
||||||
val member: Member? = when (jvmSignature) {
|
val member: Member? = when (jvmSignature) {
|
||||||
is KotlinFunction -> {
|
is KotlinFunction -> {
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ internal open class KProperty0Impl<out V> : KProperty0<V>, KPropertyImpl<V> {
|
|||||||
container, name, signature, boundReceiver
|
container, name, signature, boundReceiver
|
||||||
)
|
)
|
||||||
|
|
||||||
private val _getter = ReflectProperties.lazy { Getter(this) }
|
private val _getter = lazy(PUBLICATION) { Getter(this) }
|
||||||
|
|
||||||
override val getter: Getter<V> get() = _getter()
|
override val getter: Getter<V> get() = _getter.value
|
||||||
|
|
||||||
override fun get(): V = getter.call()
|
override fun get(): V = getter.call()
|
||||||
|
|
||||||
@@ -52,9 +52,9 @@ internal class KMutableProperty0Impl<V> : KProperty0Impl<V>, KMutableProperty0<V
|
|||||||
container, name, signature, boundReceiver
|
container, name, signature, boundReceiver
|
||||||
)
|
)
|
||||||
|
|
||||||
private val _setter = ReflectProperties.lazy { Setter(this) }
|
private val _setter = lazy(PUBLICATION) { Setter(this) }
|
||||||
|
|
||||||
override val setter: Setter<V> get() = _setter()
|
override val setter: Setter<V> get() = _setter.value
|
||||||
|
|
||||||
override fun set(value: V) = setter.call(value)
|
override fun set(value: V) = setter.call(value)
|
||||||
|
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ internal open class KProperty1Impl<T, out V> : KProperty1<T, V>, KPropertyImpl<V
|
|||||||
|
|
||||||
constructor(container: KDeclarationContainerImpl, descriptor: PropertyDescriptor) : super(container, descriptor)
|
constructor(container: KDeclarationContainerImpl, descriptor: PropertyDescriptor) : super(container, descriptor)
|
||||||
|
|
||||||
private val _getter = ReflectProperties.lazy { Getter(this) }
|
private val _getter = lazy(PUBLICATION) { Getter(this) }
|
||||||
|
|
||||||
override val getter: Getter<T, V> get() = _getter()
|
override val getter: Getter<T, V> get() = _getter.value
|
||||||
|
|
||||||
override fun get(receiver: T): V = getter.call(receiver)
|
override fun get(receiver: T): V = getter.call(receiver)
|
||||||
|
|
||||||
@@ -52,9 +52,9 @@ internal class KMutableProperty1Impl<T, V> : KProperty1Impl<T, V>, KMutablePrope
|
|||||||
|
|
||||||
constructor(container: KDeclarationContainerImpl, descriptor: PropertyDescriptor) : super(container, descriptor)
|
constructor(container: KDeclarationContainerImpl, descriptor: PropertyDescriptor) : super(container, descriptor)
|
||||||
|
|
||||||
private val _setter = ReflectProperties.lazy { Setter(this) }
|
private val _setter = lazy(PUBLICATION) { Setter(this) }
|
||||||
|
|
||||||
override val setter: Setter<T, V> get() = _setter()
|
override val setter: Setter<T, V> get() = _setter.value
|
||||||
|
|
||||||
override fun set(receiver: T, value: V) = setter.call(receiver, value)
|
override fun set(receiver: T, value: V) = setter.call(receiver, value)
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ internal open class KProperty2Impl<D, E, out V> : KProperty2<D, E, V>, KProperty
|
|||||||
|
|
||||||
constructor(container: KDeclarationContainerImpl, descriptor: PropertyDescriptor) : super(container, descriptor)
|
constructor(container: KDeclarationContainerImpl, descriptor: PropertyDescriptor) : super(container, descriptor)
|
||||||
|
|
||||||
private val _getter = ReflectProperties.lazy { Getter(this) }
|
private val _getter = lazy(PUBLICATION) { Getter(this) }
|
||||||
|
|
||||||
override val getter: Getter<D, E, V> get() = _getter()
|
override val getter: Getter<D, E, V> get() = _getter.value
|
||||||
|
|
||||||
override fun get(receiver1: D, receiver2: E): V = getter.call(receiver1, receiver2)
|
override fun get(receiver1: D, receiver2: E): V = getter.call(receiver1, receiver2)
|
||||||
|
|
||||||
@@ -51,9 +51,9 @@ internal class KMutableProperty2Impl<D, E, V> : KProperty2Impl<D, E, V>, KMutabl
|
|||||||
|
|
||||||
constructor(container: KDeclarationContainerImpl, descriptor: PropertyDescriptor) : super(container, descriptor)
|
constructor(container: KDeclarationContainerImpl, descriptor: PropertyDescriptor) : super(container, descriptor)
|
||||||
|
|
||||||
private val _setter = ReflectProperties.lazy { Setter(this) }
|
private val _setter = lazy(PUBLICATION) { Setter(this) }
|
||||||
|
|
||||||
override val setter: Setter<D, E, V> get() = _setter()
|
override val setter: Setter<D, E, V> get() = _setter.value
|
||||||
|
|
||||||
override fun set(receiver1: D, receiver2: E, value: V) = setter.call(receiver1, receiver2, value)
|
override fun set(receiver1: D, receiver2: E, value: V) = setter.call(receiver1, receiver2, value)
|
||||||
|
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.resolve.isUnderlyingPropertyOfInlineClass
|
|||||||
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor
|
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
import java.lang.reflect.*
|
import java.lang.reflect.*
|
||||||
|
import kotlin.LazyThreadSafetyMode.PUBLICATION
|
||||||
import kotlin.jvm.internal.CallableReference
|
import kotlin.jvm.internal.CallableReference
|
||||||
import kotlin.reflect.KFunction
|
import kotlin.reflect.KFunction
|
||||||
import kotlin.reflect.KMutableProperty
|
import kotlin.reflect.KMutableProperty
|
||||||
@@ -48,7 +49,7 @@ internal abstract class KPropertyImpl<out V> private constructor(
|
|||||||
|
|
||||||
override val isBound: Boolean get() = rawBoundReceiver != CallableReference.NO_RECEIVER
|
override val isBound: Boolean get() = rawBoundReceiver != CallableReference.NO_RECEIVER
|
||||||
|
|
||||||
private val _javaField: ReflectProperties.LazyVal<Field?> = ReflectProperties.lazy {
|
private val _javaField = lazy(PUBLICATION) {
|
||||||
when (val jvmSignature = RuntimeTypeMapper.mapPropertySignature(descriptor)) {
|
when (val jvmSignature = RuntimeTypeMapper.mapPropertySignature(descriptor)) {
|
||||||
is KotlinProperty -> {
|
is KotlinProperty -> {
|
||||||
val descriptor = jvmSignature.descriptor
|
val descriptor = jvmSignature.descriptor
|
||||||
@@ -75,7 +76,7 @@ internal abstract class KPropertyImpl<out V> private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val javaField: Field? get() = _javaField()
|
val javaField: Field? get() = _javaField.value
|
||||||
|
|
||||||
protected fun computeDelegateSource(): Member? {
|
protected fun computeDelegateSource(): Member? {
|
||||||
if (!descriptor.isDelegated) return null
|
if (!descriptor.isDelegated) return null
|
||||||
@@ -175,7 +176,7 @@ internal abstract class KPropertyImpl<out V> private constructor(
|
|||||||
property.descriptor.getter ?: DescriptorFactory.createDefaultGetter(property.descriptor, Annotations.EMPTY)
|
property.descriptor.getter ?: DescriptorFactory.createDefaultGetter(property.descriptor, Annotations.EMPTY)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val caller: Caller<*> by ReflectProperties.lazy {
|
override val caller: Caller<*> by lazy(PUBLICATION) {
|
||||||
computeCallerForAccessor(isGetter = true)
|
computeCallerForAccessor(isGetter = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,7 +197,7 @@ internal abstract class KPropertyImpl<out V> private constructor(
|
|||||||
property.descriptor.setter ?: DescriptorFactory.createDefaultSetter(property.descriptor, Annotations.EMPTY, Annotations.EMPTY)
|
property.descriptor.setter ?: DescriptorFactory.createDefaultSetter(property.descriptor, Annotations.EMPTY, Annotations.EMPTY)
|
||||||
}
|
}
|
||||||
|
|
||||||
override val caller: Caller<*> by ReflectProperties.lazy {
|
override val caller: Caller<*> by lazy(PUBLICATION) {
|
||||||
computeCallerForAccessor(isGetter = false)
|
computeCallerForAccessor(isGetter = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user