Skip to content

Commit 805eeab

Browse files
ligeeSpace Team
authored and
Space Team
committed
K1 Scripting: treat inaccessible provided props classes as Any
So if classloaders for, e.g., REPL are not configured properly, making classes of specified provided properties inaccessible, we can still access the values using Any type for them. This allows, e.g., solving tricky problems with properties generated from JSR-223 bindings. Note that this is K1-only functionality, since JSR-223 is not yet supported in K2 #KT-67747 fixed (cherry picked from commit 5d724e6)
1 parent adca60d commit 805eeab

File tree

2 files changed

+66
-1
lines changed

2 files changed

+66
-1
lines changed

libraries/scripting/jsr223-test/test/kotlin/script/experimental/jsr223/test/KotlinJsr223ScriptEngineIT.kt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import java.nio.file.Files.createTempDirectory
2020
import java.nio.file.Files.createTempFile
2121
import javax.script.*
2222
import kotlin.script.experimental.jvmhost.jsr223.KotlinJsr223ScriptEngineImpl
23+
import kotlin.script.templates.standard.ScriptTemplateWithBindings
2324

2425
// duplicating it here to avoid dependency on the implementation - it may interfere with tests
2526
private const val KOTLIN_JSR223_RESOLVE_FROM_CLASSLOADER_PROPERTY = "kotlin.jsr223.experimental.resolve.dependencies.from.context.classloader"
@@ -33,6 +34,11 @@ fun callLambda(x: Int, aFunction: (Int) -> Int): Int = aFunction.invoke(x)
3334
@Suppress("unused") // accessed from the tests below
3435
inline fun inlineCallLambda(x: Int, aFunction: (Int) -> Int): Int = aFunction.invoke(x)
3536

37+
@Suppress("unused", "UNCHECKED_CAST") // accessed from the tests below
38+
fun ScriptTemplateWithBindings.myFunFromBindings(n: Int): Int =
39+
(bindings["myFunFromBindings"] as (Int) -> Int).invoke(n)
40+
41+
3642
class KotlinJsr223ScriptEngineIT {
3743

3844
init {
@@ -343,6 +349,36 @@ obj
343349
Assert.assertEquals(7, res3)
344350
}
345351

352+
@Test
353+
fun testEvalInEvalWithBindingsWithLambda() {
354+
// the problem (KT-67747) is only reproducible with INDY lambdas
355+
withProperty(
356+
"kotlin.script.base.compiler.arguments",
357+
getPropertyValue = { listOfNotNull(it?.takeIf(String::isNotBlank), "-Xlambdas=indy").joinToString(" ") }
358+
) {
359+
val engine = ScriptEngineManager().getEngineByExtension("kts")!!
360+
// code here is somewhat similar to one used in Spring's KotlinScriptTemplateTests
361+
val res1 = engine.eval(
362+
"""
363+
fun f(script: String): Int {
364+
val bindings = javax.script.SimpleBindings()
365+
bindings.put("myFunFromBindings", { i: Int -> i * 7 })
366+
return eval(script, bindings) as Int
367+
}
368+
""".trimIndent()
369+
)
370+
Assert.assertNull(res1)
371+
// Note that direct call to the lambda stored in bindings is not possible, so the additional helper
372+
// [kotlin.script.experimental.jsr223.test.myFunFromBindings] is used (as in Spring)
373+
val script = """
374+
import kotlin.script.experimental.jsr223.test.*
375+
myFunFromBindings(6)
376+
""".trimIndent()
377+
val res2 = (engine as Invocable).invokeFunction("f", script)
378+
Assert.assertEquals(42, res2)
379+
}
380+
}
381+
346382
@Test
347383
fun `kotlin script evaluation should support functional return types`() {
348384
val scriptEngine = ScriptEngineManager().getEngineByExtension("kts")!!
@@ -470,3 +506,20 @@ fun assertThrows(exceptionClass: Class<*>, body: () -> Unit) {
470506
}
471507
}
472508
}
509+
510+
internal fun <T> withProperty(name: String, getPropertyValue: (String?) -> String?, body: () -> T): T {
511+
val prevPropertyVal = System.getProperty(name)
512+
val value = getPropertyValue(prevPropertyVal)
513+
when (value) {
514+
null -> System.clearProperty(name)
515+
else -> System.setProperty(name, value)
516+
}
517+
try {
518+
return body()
519+
} finally {
520+
when (prevPropertyVal) {
521+
null -> System.clearProperty(name)
522+
else -> System.setProperty(name, prevPropertyVal)
523+
}
524+
}
525+
}

plugins/scripting/scripting-compiler-impl/src/org/jetbrains/kotlin/scripting/resolve/LazyScriptDescriptor.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,7 @@ class LazyScriptDescriptor(
221221
}
222222
}
223223

224+
// TODO: we may want to treat getScriptingClass call here the same way as in scriptProvidedProperties
224225
scriptCompilationConfiguration()[ScriptCompilationConfiguration.implicitReceivers]?.mapNotNullTo(res) { receiver ->
225226
findTypeDescriptor(getScriptingClass(receiver), Errors.MISSING_SCRIPT_RECEIVER_CLASS)
226227
}
@@ -262,7 +263,18 @@ class LazyScriptDescriptor(
262263
private val scriptProvidedProperties: () -> List<ScriptProvidedPropertyDescriptor> = resolveSession.storageManager.createLazyValue {
263264
scriptCompilationConfiguration()[ScriptCompilationConfiguration.providedProperties].orEmpty()
264265
.mapNotNull { (name, type) ->
265-
findTypeDescriptor(getScriptingClass(type), Errors.MISSING_SCRIPT_PROVIDED_PROPERTY_CLASS)?.let {
266+
val propertyClass = try {
267+
getScriptingClass(type)
268+
} catch (e: IllegalArgumentException) {
269+
// IAE here means that we're unable to access the class of the property, but we can treat it as Any
270+
null
271+
}
272+
val propertyType =
273+
// If we cannot load the class for the property type, replacing it with Any allows keeping the property avoiding
274+
// possibly risky deleting at this place and also still allows using it from the script with a cast
275+
if (propertyClass == null) builtIns.any
276+
else findTypeDescriptor(propertyClass, Errors.MISSING_SCRIPT_PROVIDED_PROPERTY_CLASS)
277+
propertyType?.let {
266278
name.toValidJvmIdentifier() to
267279
it.defaultType.makeNullableAsSpecified(type.isNullable).replaceArgumentsWithStarProjections()
268280
}

0 commit comments

Comments
 (0)