From 5426071102c623574f80962bac03312b19fd919e Mon Sep 17 00:00:00 2001 From: Ilya Chernikov Date: Wed, 11 Sep 2019 16:07:05 +0200 Subject: [PATCH] Add dependency classloader to the evaluation classloader: allows to use dependencies from classloader in the scenario with evaluation in the isolated environment --- .../test/ResolveFromClassloaderTest.kt | 26 ++++++++++++++--- .../jvm/impl/KJvmCompiledModule.kt | 2 +- .../jvm/impl/KJvmCompiledScript.kt | 28 +++++++++---------- 3 files changed, 36 insertions(+), 20 deletions(-) diff --git a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ResolveFromClassloaderTest.kt b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ResolveFromClassloaderTest.kt index a5dc54e00d4..f0713f2310e 100644 --- a/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ResolveFromClassloaderTest.kt +++ b/libraries/scripting/jvm-host-test/test/kotlin/script/experimental/jvmhost/test/ResolveFromClassloaderTest.kt @@ -8,13 +8,12 @@ package kotlin.script.experimental.jvmhost.test import junit.framework.TestCase import org.junit.Test import java.io.File -import kotlin.script.experimental.api.ResultValue -import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.api.dependencies -import kotlin.script.experimental.api.valueOrThrow +import kotlin.script.experimental.api.* import kotlin.script.experimental.host.toScriptSource import kotlin.script.experimental.jvm.JvmDependency import kotlin.script.experimental.jvm.JvmDependencyFromClassLoader +import kotlin.script.experimental.jvm.baseClassLoader +import kotlin.script.experimental.jvm.jvm import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost class ResolveFromClassloaderTest : TestCase() { @@ -29,6 +28,25 @@ class ResolveFromClassloaderTest : TestCase() { assertEquals(42, res.value) } + @Test + fun testResolveFromClassloaderIsolated() { + val script = "${ShouldBeVisibleFromScript::class.java.name}().x".toScriptSource() + val compilationConfiguration = ScriptCompilationConfiguration { + dependencies(JvmDependencyFromClassLoader { ShouldBeVisibleFromScript::class.java.classLoader }) + } + val evaluationConfiguration = ScriptEvaluationConfiguration { + jvm { + baseClassLoader(null) + } + } + val res = BasicJvmScriptingHost().eval(script, compilationConfiguration, evaluationConfiguration).valueOrThrow().returnValue + when (res) { + is ResultValue.Value -> assertEquals(42, res.value) + is ResultValue.Error -> throw res.error + else -> throw Exception("Unexpected evaluation result: $res") + } + } + @Test fun testResolveFromClassloaderAndClassPath() { val script = """ diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledModule.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledModule.kt index 9d519b88775..cf5ee8bfb96 100644 --- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledModule.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledModule.kt @@ -28,7 +28,7 @@ class KJvmCompiledModuleFromClassLoader(val moduleClassLoader: ClassLoader) : KJ else DualClassLoader(moduleClassLoader, baseClassLoader) } -private class DualClassLoader(fallbackLoader: ClassLoader, parentLoader: ClassLoader?) : +internal class DualClassLoader(fallbackLoader: ClassLoader, parentLoader: ClassLoader?) : ClassLoader(singleClassLoader(fallbackLoader, parentLoader) ?: parentLoader) { private class Wrapper(parent: ClassLoader) : ClassLoader(parent) { diff --git a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledScript.kt b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledScript.kt index 0f450cc5c54..92f0e7bb088 100644 --- a/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledScript.kt +++ b/libraries/scripting/jvm/src/kotlin/script/experimental/jvm/impl/KJvmCompiledScript.kt @@ -141,23 +141,21 @@ fun getConfigurationWithClassloader( private fun CompiledScript<*>.makeClassLoaderFromDependencies(baseClassLoader: ClassLoader?): ClassLoader? { val processedScripts = mutableSetOf>() - fun seq(res: Sequence>, script: CompiledScript<*>): Sequence> { - if (processedScripts.contains(script)) return res - processedScripts.add(script) - return script.otherScripts.asSequence().fold(res + script, ::seq) - } + fun recursiveScriptsSeq(res: Sequence>, script: CompiledScript<*>): Sequence> = + if (processedScripts.add(script)) script.otherScripts.asSequence().fold(res + script, ::recursiveScriptsSeq) + else res - val dependencies = seq(emptySequence(), this).flatMap { script -> + val dependenciesWithConfigurations = recursiveScriptsSeq(emptySequence(), this).flatMap { script -> script.compilationConfiguration[ScriptCompilationConfiguration.dependencies] - ?.asSequence() - ?.flatMap { dep -> - (dep as? JvmDependency)?.classpath?.asSequence()?.map { it.toURI().toURL() } ?: emptySequence() - } - ?: emptySequence() - }.distinct().toList() - // TODO: previous dependencies and classloaders should be taken into account here - return if (dependencies.isEmpty()) baseClassLoader - else URLClassLoader(dependencies.toTypedArray(), baseClassLoader) + ?.asSequence()?.map { script.compilationConfiguration to it } ?: emptySequence() + } + return dependenciesWithConfigurations.fold(baseClassLoader) { parentClassLoader, (compilationConfiguration, scriptDependency) -> + when (scriptDependency) { + is JvmDependency -> URLClassLoader(scriptDependency.classpath.map { it.toURI().toURL() }.toTypedArray(), parentClassLoader) + is JvmDependencyFromClassLoader -> DualClassLoader(scriptDependency.getClassLoader(compilationConfiguration), baseClassLoader) + else -> baseClassLoader + } + } } const val KOTLIN_SCRIPT_METADATA_PATH = "META-INF/kotlin/script"