99b38ccb74
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
126 lines
5.5 KiB
Kotlin
126 lines
5.5 KiB
Kotlin
/*
|
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
|
*/
|
|
|
|
package org.jetbrains.kotlin.reflection
|
|
|
|
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
|
|
import org.jetbrains.kotlin.test.CompilerTestUtil
|
|
import org.jetbrains.kotlin.test.KotlinTestUtils
|
|
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
|
|
import org.jetbrains.kotlin.test.util.KtTestUtil
|
|
import java.io.File
|
|
import java.net.URLClassLoader
|
|
import java.util.concurrent.CountDownLatch
|
|
import java.util.concurrent.TimeUnit
|
|
import java.util.concurrent.atomic.AtomicReference
|
|
import kotlin.concurrent.thread
|
|
import kotlin.reflect.KClass
|
|
|
|
class ReflectionIntegrationTest : KtUsefulTestCase() {
|
|
// This test checks that we use the correct class loader to load .kotlin_builtins resource files.
|
|
// On Android, the class loader used to load the application classes differs from the boot class loader (which loads core JDK classes),
|
|
// so attempting to find kotlin/kotlin.kotlin_builtins in the same class loader that found java.lang.Object can fail.
|
|
// We should always use the class loader that loads stdlib class files to locate .kotlin_builtins resource files
|
|
fun testClassLoaderForBuiltIns() {
|
|
val tmpdir = KotlinTestUtils.tmpDirForTest(this)
|
|
|
|
val root = KtTestUtil.getTestDataPathBase() + "/reflection/classLoaderForBuiltIns"
|
|
KotlinTestUtils.compileJavaFiles(
|
|
listOf(File("$root/Main.java")),
|
|
listOf("-d", tmpdir.absolutePath)
|
|
)
|
|
|
|
val lib = CompilerTestUtil.compileJvmLibrary(File("$root/test.kt"))
|
|
|
|
runJava(
|
|
"-ea",
|
|
"-classpath",
|
|
tmpdir.absolutePath,
|
|
"Main",
|
|
lib.absolutePath,
|
|
ForTestCompileRuntime.runtimeJarForTests().absolutePath,
|
|
ForTestCompileRuntime.reflectJarForTests().absolutePath,
|
|
)
|
|
}
|
|
|
|
// This test checks that simultaneous access to kotlin-reflect from different threads works, in case the URLClassLoader instance is
|
|
// being closed in one of the threads. It creates two threads that for several seconds continuously load kotlin-reflect in a new
|
|
// class loader, call something from it, and close the class loader.
|
|
fun testParallelAccess() {
|
|
val urls = arrayOf(
|
|
ForTestCompileRuntime.reflectJarForTests().toURI().toURL(),
|
|
ForTestCompileRuntime.runtimeJarForTests().toURI().toURL(),
|
|
)
|
|
|
|
val latch = CountDownLatch(1)
|
|
val error = AtomicReference<Throwable?>()
|
|
repeat(2) {
|
|
thread {
|
|
while (latch.count == 1L) {
|
|
try {
|
|
val classLoader = URLClassLoader(urls, null)
|
|
|
|
// Invoke KClass.primaryConstructor on String::class reflectively.
|
|
// Among other things, it leads to reading a resource kotlin/kotlin.kotlin_builtins from the JAR file.
|
|
val getPrimaryConstructor = classLoader.loadClass("kotlin.reflect.full.KClasses")
|
|
.getDeclaredMethod("getPrimaryConstructor", classLoader.loadClass(KClass::class.java.name))
|
|
val createKClass = classLoader.loadClass("kotlin.jvm.internal.Reflection")
|
|
.getDeclaredMethod("getOrCreateKotlinClass", Class::class.java)
|
|
getPrimaryConstructor(null, createKClass(null, String::class.java))
|
|
|
|
// Close URLClassLoader to verify that it won't affect reading the class loader resources from the other thread(s).
|
|
classLoader.close()
|
|
} catch (e: Throwable) {
|
|
error.set(e)
|
|
latch.countDown()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
latch.await(5L, TimeUnit.SECONDS)
|
|
latch.countDown()
|
|
|
|
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)
|
|
}
|
|
}
|