Prevent cross-script module slot aliasing

This commit is contained in:
Sergey Chernov 2026-04-21 17:34:00 +03:00
parent ee634c8dff
commit c80900c503
3 changed files with 58 additions and 9 deletions

View File

@ -108,11 +108,7 @@ class Script(
seedImportBindings(scope, seedScope) seedImportBindings(scope, seedScope)
} }
if (moduleSlotPlan.isNotEmpty()) { if (moduleSlotPlan.isNotEmpty()) {
scope.applySlotPlan(moduleSlotPlan) installModuleSlotPlan(scope)
for (name in moduleSlotPlan.keys) {
val record = scope.objects[name] ?: scope.localBindings[name] ?: continue
scope.updateSlotFor(name, record)
}
} }
} }
@ -120,12 +116,26 @@ class Script(
if (importBindings.isEmpty() && importedModules.isEmpty()) return if (importBindings.isEmpty() && importedModules.isEmpty()) return
seedImportBindings(scope, seedScope) seedImportBindings(scope, seedScope)
if (moduleSlotPlan.isNotEmpty()) { if (moduleSlotPlan.isNotEmpty()) {
scope.applySlotPlan(moduleSlotPlan) installModuleSlotPlan(scope)
for (name in moduleSlotPlan.keys) { }
val record = scope.objects[name] ?: scope.localBindings[name] ?: continue }
scope.updateSlotFor(name, record)
private fun installModuleSlotPlan(scope: Scope) {
for ((name, index) in moduleSlotPlan) {
if (scope.getSlotIndexOf(name) != null) continue
if (scope.hasSlotPlanConflict(mapOf(name to index))) {
val record = scope.objects[name]
?: scope.localBindings[name]
?: ObjRecord(ObjUnset, isMutable = true)
scope.allocateSlotFor(name, record)
} else {
scope.applySlotPlan(mapOf(name to index))
} }
} }
for (name in moduleSlotPlan.keys) {
val record = scope.objects[name] ?: scope.localBindings[name] ?: continue
scope.updateSlotFor(name, record)
}
} }
private suspend fun seedModuleLocals( private suspend fun seedModuleLocals(

View File

@ -5654,6 +5654,14 @@ class CmdFrame(
if (index < target.slotCount) return index if (index < target.slotCount) return index
return index return index
} }
if (target.hasSlotPlanConflict(mapOf(name to index))) {
val record = target.getLocalRecordDirect(name)
?: target.localBindings[name]
?: target.parent?.get(name)
?: target.get(name)
?: ObjRecord(ObjUnset, isMutable = true)
return target.allocateSlotFor(name, record)
}
target.applySlotPlan(mapOf(name to index)) target.applySlotPlan(mapOf(name to index))
val existing = target.getLocalRecordDirect(name) ?: target.localBindings[name] val existing = target.getLocalRecordDirect(name) ?: target.localBindings[name]
if (existing != null) { if (existing != null) {

View File

@ -150,6 +150,37 @@ class CompilerVmReviewRegressionTest {
assertEquals(42, callable.call(scope, ObjInt.of(40)).toInt()) assertEquals(42, callable.call(scope, ObjInt.of(40)).toInt())
} }
@Test
fun preparedLambdaKeepsImmutableModuleCaptureAcrossOtherScriptsInSameScope() = runTest {
val unaryLambda = Compiler.compile(
Source(
"<cross-script-capture-unary>",
"""
val delta = 2
{ x -> x + delta }
""".trimIndent()
),
Script.defaultImportManager
)
val unrelatedScript = Compiler.compile(
Source(
"<cross-script-capture-unrelated>",
"""
val base = 7
base
""".trimIndent()
),
Script.defaultImportManager
)
val scope = Script.newScope()
val callable = unaryLambda.execute(scope) as Statement
unrelatedScript.execute(scope)
assertEquals(42, callable.call(scope, ObjInt.of(40)).toInt())
assertEquals(42, scope.asFacade().call(callable, Arguments(ObjInt.of(40))).toInt())
}
@Test @Test
fun subjectlessWhenReportsScriptError() = runTest { fun subjectlessWhenReportsScriptError() = runTest {
val ex = assertFailsWith<ScriptError> { val ex = assertFailsWith<ScriptError> {