From 3e19e9d190f800da551650cdcbbdbb9f7c845f07 Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Tue, 26 Apr 2022 08:31:31 +0200 Subject: [PATCH] Scripting: fix loadDependencies property use with cached scripts #KT-50902 fixed --- .../experimental/jvmhost/test/CachingTest.kt | 73 ++++++++++++++++--- .../experimental/jvmhost/jvmScriptSaving.kt | 4 +- 2 files changed, 65 insertions(+), 12 deletions(-) diff --git a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/CachingTest.kt b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/CachingTest.kt index 761a0d7fa4e..aa693396107 100644 --- a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/CachingTest.kt +++ b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/CachingTest.kt @@ -13,12 +13,14 @@ import org.junit.Assert import org.junit.Ignore import org.junit.Test import java.io.* +import java.net.URLClassLoader import java.security.MessageDigest import kotlin.script.experimental.api.* import kotlin.script.experimental.host.toScriptSource import kotlin.script.experimental.host.with import kotlin.script.experimental.jvm.* import kotlin.script.experimental.jvm.impl.KJvmCompiledScript +import kotlin.script.experimental.jvm.loadDependencies import kotlin.script.experimental.jvm.util.KotlinJars import kotlin.script.experimental.jvm.util.classpathFromClass import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost @@ -130,17 +132,7 @@ class CachingTest : TestCase() { fun ignoredTestLocalDependencyWithJarCacheInvalidation() { withTempDir("scriptingTestDepDir") { depDir -> val standardJars = KotlinJars.kotlinScriptStandardJars - val outJar = File(depDir, "dependency.jar") - val inKt = File(depDir, "Dependency.kt").apply { writeText("class Dependency(val v: Int)") } - val outStream = ByteArrayOutputStream() - val compileExitCode = K2JVMCompiler().exec( - PrintStream(outStream), - "-d", outJar.path, "-no-stdlib", "-cp", standardJars.joinToString(File.pathSeparator), inKt.path - ) - assertTrue( - "Compilation Failed:\n$outStream", - outStream.size() == 0 && compileExitCode == ExitCode.OK && outJar.exists() - ) + val outJar = makeDependenciesJar(depDir, standardJars) withTempDir("scriptingTestJarChacheWithDep") { cacheDir -> val cache = TestCompiledScriptJarsCache(cacheDir) @@ -188,6 +180,65 @@ class CachingTest : TestCase() { } } + @Test + fun testLocalDependencyWithExternalLoadAndCache() { + withTempDir("scriptingTestDepDir") { depDir -> + val standardJars = KotlinJars.kotlinScriptStandardJars + val outJar = makeDependenciesJar(depDir, standardJars) + + withTempDir("scriptingTestJarChacheWithExtLoadedDep") { cacheDir -> + val cache = TestCompiledScriptJarsCache(cacheDir) + Assert.assertTrue(cache.baseDir.listFiles()!!.isEmpty()) + + val hostConfiguration = defaultJvmScriptingHostConfiguration.with { + jvm { + baseClassLoader(URLClassLoader((standardJars + outJar).map { it.toURI().toURL() }.toTypedArray(), null)) + compilationCache(cache) + } + } + val host = BasicJvmScriptingHost(compiler = JvmScriptCompiler(hostConfiguration), evaluator = BasicJvmScriptEvaluator()) + + val scriptCompilationConfiguration = ScriptCompilationConfiguration { + updateClasspath(standardJars + outJar) + this.hostConfiguration.update { hostConfiguration } + } + val scriptEvaluationConfiguration = ScriptEvaluationConfiguration { + jvm { + loadDependencies(false) + } + this.hostConfiguration.update { hostConfiguration } + } + + val script = "Dependency(42).v".toScriptSource() + + // Without the patch that fixes loadDependencies usage in kotlin.script.experimental.jvmhost.KJvmCompiledScriptLazilyLoadedFromClasspath.getClass + // AND with hostConfiguration removed from scriptEvaluationConfiguration (essentially creating a misconfigured evaluator) + // the first evaluation fails because it cannot find the class for dependency, but the second mistakingly succeed, because dependency is taken from the cache + // (see #KT-50902 for details) + val res0 = host.eval(script, scriptCompilationConfiguration, scriptEvaluationConfiguration).valueOrThrow().returnValue + assertEquals(42, (res0 as? ResultValue.Value)?.value) + + val res1 = host.eval(script, scriptCompilationConfiguration, scriptEvaluationConfiguration).valueOrThrow().returnValue + assertEquals(42, (res1 as? ResultValue.Value)?.value) + } + } + } + + private fun makeDependenciesJar(depDir: File, standardJars: List): File { + val outJar = File(depDir, "dependency.jar") + val inKt = File(depDir, "Dependency.kt").apply { writeText("class Dependency(val v: Int)") } + val outStream = ByteArrayOutputStream() + val compileExitCode = K2JVMCompiler().exec( + PrintStream(outStream), + "-d", outJar.path, "-no-stdlib", "-cp", standardJars.joinToString(File.pathSeparator), inKt.path + ) + assertTrue( + "Compilation Failed:\n$outStream", + outStream.size() == 0 && compileExitCode == ExitCode.OK && outJar.exists() + ) + return outJar + } + private fun checkWithCache( cache: ScriptingCacheWithCounters, script: SourceCode, expectedOutput: List, checkDirectEval: Boolean = true, compilationConfiguration: ScriptCompilationConfiguration.Builder.() -> Unit = {}, diff --git a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt index c309faca76b..b1033c66fa9 100644 --- a/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt +++ b/libraries/scripting/jvm-host/src/kotlin/script/experimental/jvmhost/jvmScriptSaving.kt @@ -20,6 +20,7 @@ import kotlin.script.experimental.jvm.JvmDependency import kotlin.script.experimental.jvm.baseClassLoader import kotlin.script.experimental.jvm.impl.* import kotlin.script.experimental.jvm.jvm +import kotlin.script.experimental.jvm.loadDependencies import kotlin.script.experimental.jvm.util.scriptCompilationClasspathFromContextOrNull // TODO: generate execution code (main) @@ -145,8 +146,9 @@ private class KJvmCompiledScriptLazilyLoadedFromClasspath( if (loadedScript == null) { val actualEvaluationConfiguration = scriptEvaluationConfiguration ?: ScriptEvaluationConfiguration() val baseClassLoader = actualEvaluationConfiguration[ScriptEvaluationConfiguration.jvm.baseClassLoader] + val loadDependencies = actualEvaluationConfiguration[ScriptEvaluationConfiguration.jvm.loadDependencies]!! val classLoader = URLClassLoader( - classPath.map { it.toURI().toURL() }.toTypedArray(), + classPath.let { if (loadDependencies) it else it.take(1) }.map { it.toURI().toURL() }.toTypedArray(), baseClassLoader ) loadedScript = createScriptFromClassLoader(scriptClassFQName, classLoader)