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:
Alexander Udalov
2023-01-24 16:34:00 +01:00
committed by Space Team
parent e8f95a3376
commit 99b38ccb74
9 changed files with 137 additions and 31 deletions
+1 -1
View File
@@ -164,7 +164,7 @@
/compiler/testData/psi/ "Kotlin Compiler Core"
/compiler/testData/psiUtil/ "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/resolve/ "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 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 command = arrayOf(
javaExe.absolutePath,
runJava(
"-ea",
"-classpath",
tmpdir.absolutePath,
"Main",
lib.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
@@ -95,4 +85,41 @@ class ReflectionIntegrationTest : KtUsefulTestCase() {
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.Method
import java.lang.reflect.Modifier
import kotlin.LazyThreadSafetyMode.PUBLICATION
import kotlin.jvm.internal.CallableReference
import kotlin.jvm.internal.FunctionBase
import kotlin.reflect.KFunction
@@ -58,7 +59,7 @@ internal class KFunctionImpl private constructor(
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)) {
is KotlinConstructor -> {
if (isAnnotationConstructor)
@@ -89,7 +90,7 @@ internal class KFunctionImpl private constructor(
}.createInlineClassAwareCallerIfNeeded(descriptor)
}
override val defaultCaller: Caller<*>? by ReflectProperties.lazy defaultCaller@{
override val defaultCaller: Caller<*>? by lazy(PUBLICATION) defaultCaller@{
val jvmSignature = RuntimeTypeMapper.mapSignature(descriptor)
val member: Member? = when (jvmSignature) {
is KotlinFunction -> {
@@ -28,9 +28,9 @@ internal open class KProperty0Impl<out V> : KProperty0<V>, KPropertyImpl<V> {
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()
@@ -52,9 +52,9 @@ internal class KMutableProperty0Impl<V> : KProperty0Impl<V>, KMutableProperty0<V
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)
@@ -28,9 +28,9 @@ internal open class KProperty1Impl<T, out V> : KProperty1<T, V>, KPropertyImpl<V
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)
@@ -52,9 +52,9 @@ internal class KMutableProperty1Impl<T, V> : KProperty1Impl<T, V>, KMutablePrope
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)
@@ -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)
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)
@@ -51,9 +51,9 @@ internal class KMutableProperty2Impl<D, E, V> : KProperty2Impl<D, E, V>, KMutabl
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)
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.resolve.isUnderlyingPropertyOfInlineClass
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedPropertyDescriptor
import org.jetbrains.kotlin.types.TypeUtils
import java.lang.reflect.*
import kotlin.LazyThreadSafetyMode.PUBLICATION
import kotlin.jvm.internal.CallableReference
import kotlin.reflect.KFunction
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
private val _javaField: ReflectProperties.LazyVal<Field?> = ReflectProperties.lazy {
private val _javaField = lazy(PUBLICATION) {
when (val jvmSignature = RuntimeTypeMapper.mapPropertySignature(descriptor)) {
is KotlinProperty -> {
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? {
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)
}
override val caller: Caller<*> by ReflectProperties.lazy {
override val caller: Caller<*> by lazy(PUBLICATION) {
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)
}
override val caller: Caller<*> by ReflectProperties.lazy {
override val caller: Caller<*> by lazy(PUBLICATION) {
computeCallerForAccessor(isGetter = false)
}