1.2.1-SNAPSHOT: improved visibility check for inheritance
This commit is contained in:
1 parent
75723adbbc
commit
7bc17037f9
14 files changed
+114
-53
No files matched your search
@@ -54,14 +54,14 @@ class ClosureScope(val callScope: Scope, val closureScope: Scope) :
|
||||
for (cls in effectiveClass.mro) {
|
||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||
if (rec != null && !rec.isAbstract) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx)) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx, name)) {
|
||||
return rec.copy(receiver = receiver)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally, root object fallback
|
||||
Obj.rootObjectType.members[name]?.let { rec ->
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) {
|
||||
return rec.copy(receiver = receiver)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ open class Scope(
|
||||
}
|
||||
}
|
||||
s.objects[name]?.let { rec ->
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) return rec
|
||||
}
|
||||
caller?.let { ctx ->
|
||||
s.localBindings[ctx.mangledName(name)]?.let { rec ->
|
||||
@@ -120,11 +120,11 @@ open class Scope(
|
||||
}
|
||||
}
|
||||
s.localBindings[name]?.let { rec ->
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) return rec
|
||||
}
|
||||
s.getSlotIndexOf(name)?.let { idx ->
|
||||
val rec = s.getSlotRecord(idx)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller)) return rec
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, caller, name)) return rec
|
||||
}
|
||||
return null
|
||||
}
|
||||
@@ -163,7 +163,7 @@ open class Scope(
|
||||
this.extensions[cls]?.get(name)?.let { return it }
|
||||
}
|
||||
return thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) {
|
||||
if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property || rec.isAbstract) null
|
||||
else rec
|
||||
} else null
|
||||
@@ -186,7 +186,7 @@ open class Scope(
|
||||
s.extensions[cls]?.get(name)?.let { return it }
|
||||
}
|
||||
s.thisObj.objClass.getInstanceMemberOrNull(name)?.let { rec ->
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, caller)) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, caller, name)) {
|
||||
if (rec.type == ObjRecord.Type.Field || rec.type == ObjRecord.Type.Property || rec.isAbstract) {
|
||||
// ignore fields, properties and abstracts here, they will be handled by the caller via readField
|
||||
} else return rec
|
||||
@@ -358,14 +358,14 @@ open class Scope(
|
||||
for (cls in effectiveClass.mro) {
|
||||
val rec = cls.members[name] ?: cls.classScope?.objects?.get(name)
|
||||
if (rec != null && !rec.isAbstract) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx)) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass ?: cls, currentClassCtx, name)) {
|
||||
return rec.copy(receiver = receiver)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Finally, root object fallback
|
||||
Obj.rootObjectType.members[name]?.let { rec ->
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx)) {
|
||||
if (canAccessMember(rec.visibility, rec.declaringClass, currentClassCtx, name)) {
|
||||
return rec.copy(receiver = receiver)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2025 Sergey S. Chernov real.sergeych@gmail.com
|
||||
* Copyright 2026 Sergey S. Chernov real.sergeych@gmail.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -25,7 +25,12 @@ enum class Visibility {
|
||||
}
|
||||
|
||||
/** MI-aware visibility check: whether [caller] can access a member declared in [decl] with [visibility]. */
|
||||
fun canAccessMember(visibility: Visibility, decl: net.sergeych.lyng.obj.ObjClass?, caller: net.sergeych.lyng.obj.ObjClass?): Boolean {
|
||||
fun canAccessMember(
|
||||
visibility: Visibility,
|
||||
decl: net.sergeych.lyng.obj.ObjClass?,
|
||||
caller: net.sergeych.lyng.obj.ObjClass?,
|
||||
name: String? = null
|
||||
): Boolean {
|
||||
val res = when (visibility) {
|
||||
Visibility.Public -> true
|
||||
Visibility.Private -> (decl != null && caller === decl)
|
||||
@@ -33,7 +38,14 @@ fun canAccessMember(visibility: Visibility, decl: net.sergeych.lyng.obj.ObjClass
|
||||
decl == null -> false
|
||||
caller == null -> false
|
||||
caller === decl -> true
|
||||
else -> (caller.allParentsSet.contains(decl))
|
||||
caller.allParentsSet.contains(decl) -> true
|
||||
name != null && decl.allParentsSet.contains(caller) -> {
|
||||
// Ancestor can access protected member of its descendant if it also has this member
|
||||
// (i.e. it's an override of something the ancestor knows about)
|
||||
val existing = caller.getInstanceMemberOrNull(name, includeAbstract = true)
|
||||
existing != null && (existing.visibility == Visibility.Protected || existing.visibility == Visibility.Public)
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
return res
|
||||
|
||||
@@ -113,7 +113,7 @@ open class Obj {
|
||||
if (rec != null && !rec.isAbstract) {
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
@@ -140,7 +140,7 @@ open class Obj {
|
||||
cls.members[name]?.let { rec ->
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
|
||||
if (rec.type == ObjRecord.Type.Property) {
|
||||
@@ -482,7 +482,7 @@ open class Obj {
|
||||
cls.members[name]?.let { rec ->
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${name}: not visible (declared in ${decl.className}, caller ${caller?.className ?: "?"})"))
|
||||
val resolved = resolveRecord(scope, rec, name, decl)
|
||||
if (resolved.type == ObjRecord.Type.Fun && resolved.value is Statement)
|
||||
@@ -526,7 +526,7 @@ open class Obj {
|
||||
}
|
||||
val caller = scope.currentClassCtx
|
||||
// Check visibility for non-property members here if they weren't checked before
|
||||
if (!canAccessMember(obj.visibility, decl, caller))
|
||||
if (!canAccessMember(obj.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't access field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})"))
|
||||
return obj
|
||||
}
|
||||
@@ -574,7 +574,7 @@ open class Obj {
|
||||
|
||||
val decl = field.declaringClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(field.effectiveWriteVisibility, decl, caller))
|
||||
if (!canAccessMember(field.effectiveWriteVisibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't assign field ${name}: not visible (declared in ${decl?.className ?: "?"}, caller ${caller?.className ?: "?"})"))
|
||||
if (field.type == ObjRecord.Type.Delegated) {
|
||||
val del = field.delegate ?: scope.raiseError("Internal error: delegated property $name has no delegate")
|
||||
|
||||
@@ -461,7 +461,7 @@ open class ObjClass(
|
||||
if (existing != null && existing.declaringClass != this) {
|
||||
// If the existing member is private in the ancestor, it's not visible for overriding.
|
||||
// It should be treated as a new member in this class.
|
||||
if (!existing.visibility.isPublic && !canAccessMember(existing.visibility, existing.declaringClass, this)) {
|
||||
if (!existing.visibility.isPublic && !canAccessMember(existing.visibility, existing.declaringClass, this, name)) {
|
||||
// It's effectively not there for us, so actualOverride remains false
|
||||
} else {
|
||||
actualOverride = true
|
||||
|
||||
@@ -68,7 +68,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (cls.className == "Obj") break
|
||||
val mangled = cls.mangledName(name)
|
||||
instanceScope.objects[mangled]?.let { rec ->
|
||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, cls, caller)) {
|
||||
if (canAccessMember(rec.visibility, cls, caller, name)) {
|
||||
return resolveRecord(scope, rec, name, cls)
|
||||
}
|
||||
}
|
||||
@@ -77,8 +77,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
// 2. Unmangled storage
|
||||
instanceScope.objects[name]?.let { rec ->
|
||||
val decl = rec.declaringClass
|
||||
// Unmangled access is only allowed if it's public OR we are inside the same instance's method
|
||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.visibility, decl, caller)) {
|
||||
if (canAccessMember(rec.visibility, decl, caller, name)) {
|
||||
return resolveRecord(scope, rec, name, decl)
|
||||
}
|
||||
}
|
||||
@@ -174,7 +173,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
if (cls.className == "Obj") break
|
||||
val mangled = cls.mangledName(name)
|
||||
instanceScope.objects[mangled]?.let { rec ->
|
||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.effectiveWriteVisibility, cls, caller)) {
|
||||
if (canAccessMember(rec.effectiveWriteVisibility, cls, caller, name)) {
|
||||
updateRecord(scope, rec, name, newValue, cls)
|
||||
return
|
||||
}
|
||||
@@ -184,7 +183,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
// 2. Unmangled storage
|
||||
instanceScope.objects[name]?.let { rec ->
|
||||
val decl = rec.declaringClass
|
||||
if ((scope.thisObj === this && caller != null) || canAccessMember(rec.effectiveWriteVisibility, decl, caller)) {
|
||||
if (canAccessMember(rec.effectiveWriteVisibility, decl, caller, name)) {
|
||||
updateRecord(scope, rec, name, newValue, decl)
|
||||
return
|
||||
}
|
||||
@@ -282,7 +281,7 @@ class ObjInstance(override val objClass: ObjClass) : Obj() {
|
||||
}
|
||||
val decl = rec.declaringClass ?: cls
|
||||
val effectiveCaller = caller ?: if (scope.thisObj === this) objClass else null
|
||||
if (!canAccessMember(rec.visibility, decl, effectiveCaller))
|
||||
if (!canAccessMember(rec.visibility, decl, effectiveCaller, name))
|
||||
scope.raiseError(
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
@@ -433,7 +432,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
// Visibility: declaring class is the qualified ancestor for mangled storage
|
||||
val decl = rec.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})"))
|
||||
return instance.resolveRecord(scope, rec, name, decl)
|
||||
}
|
||||
@@ -442,7 +441,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
instance.instanceScope[name]?.let { rec ->
|
||||
val decl = rec.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
@@ -456,7 +455,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
val r = memberFromAncestor(name) ?: scope.raiseError("no such field: $name")
|
||||
val decl = r.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(r.visibility, decl, caller))
|
||||
if (!canAccessMember(r.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't access field $name (declared in ${decl.className})"))
|
||||
|
||||
return instance.resolveRecord(scope, r, name, decl)
|
||||
@@ -468,7 +467,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
instance.instanceScope.objects[mangled]?.let { f ->
|
||||
val decl = f.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller))
|
||||
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller, name))
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
"can't assign to field $name (declared in ${decl.className})"
|
||||
@@ -482,7 +481,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
instance.instanceScope[name]?.let { f ->
|
||||
val decl = f.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller))
|
||||
if (!canAccessMember(f.effectiveWriteVisibility, decl, caller, name))
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
"can't assign to field $name (declared in ${decl?.className ?: "?"})"
|
||||
@@ -495,7 +494,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
val r = memberFromAncestor(name) ?: scope.raiseError("no such field: $name")
|
||||
val decl = r.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(r.effectiveWriteVisibility, decl, caller))
|
||||
if (!canAccessMember(r.effectiveWriteVisibility, decl, caller, name))
|
||||
ObjIllegalAccessException(scope, "can't assign to field $name (declared in ${decl.className})").raise()
|
||||
if (!r.isMutable) scope.raiseError("can't assign to read-only field: $name")
|
||||
if (r.value.assign(scope, newValue) == null) r.value = newValue
|
||||
@@ -511,7 +510,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
memberFromAncestor(name)?.let { rec ->
|
||||
val decl = rec.declaringClass ?: startClass
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(ObjIllegalAccessException(scope, "can't invoke method $name (declared in ${decl.className})"))
|
||||
val saved = instance.instanceScope.currentClassCtx
|
||||
instance.instanceScope.currentClassCtx = decl
|
||||
@@ -526,7 +525,7 @@ class ObjQualifiedView(val instance: ObjInstance, private val startClass: ObjCla
|
||||
instance.instanceScope[name]?.let { rec ->
|
||||
val decl = rec.declaringClass ?: instance.objClass.findDeclaringClassOf(name)
|
||||
val caller = scope.currentClassCtx
|
||||
if (!canAccessMember(rec.visibility, decl, caller))
|
||||
if (!canAccessMember(rec.visibility, decl, caller, name))
|
||||
scope.raiseError(
|
||||
ObjIllegalAccessException(
|
||||
scope,
|
||||
|
||||
@@ -1363,7 +1363,7 @@ class MethodCallRef(
|
||||
val decl = hierarchyMember.declaringClass ?: base.objClass
|
||||
mKey1 = key; mVer1 = ver; mInvoker1 = { obj, sc, a ->
|
||||
val inst = obj as ObjInstance
|
||||
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx))
|
||||
if (!visibility.isPublic && !canAccessMember(visibility, decl, sc.currentClassCtx, name))
|
||||
sc.raiseError(ObjIllegalAccessException(sc, "can't invoke non-public method $name"))
|
||||
callable.invoke(inst.instanceScope, inst, a)
|
||||
}
|
||||
@@ -1447,7 +1447,7 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
val slot = if (hit) cachedSlot else resolveSlot(scope)
|
||||
if (slot >= 0) {
|
||||
val rec = scope.getSlotRecord(slot)
|
||||
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
if (rec.declaringClass != null && !canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
// Not visible via slot, fallback to other lookups
|
||||
} else {
|
||||
if (PerfFlags.PIC_DEBUG_COUNTERS) {
|
||||
@@ -1499,7 +1499,7 @@ class LocalVarRef(val name: String, private val atPos: Pos) : ObjRef {
|
||||
val slot = if (cachedFrameId == scope.frameId && cachedSlot >= 0 && cachedSlot < scope.slotCount()) cachedSlot else resolveSlot(scope)
|
||||
if (slot >= 0) {
|
||||
val rec = scope.getSlotRecord(slot)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
scope.assign(rec, name, newValue)
|
||||
return
|
||||
}
|
||||
@@ -1608,7 +1608,7 @@ class FastLocalVarRef(
|
||||
val actualOwner = cachedOwnerScope
|
||||
if (slot >= 0 && actualOwner != null) {
|
||||
val rec = actualOwner.getSlotRecord(slot)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
if (PerfFlags.PIC_DEBUG_COUNTERS) {
|
||||
if (ownerValid) PerfStats.fastLocalHit++ else PerfStats.fastLocalMiss++
|
||||
}
|
||||
@@ -1652,7 +1652,7 @@ class FastLocalVarRef(
|
||||
val actualOwner = cachedOwnerScope
|
||||
if (slot >= 0 && actualOwner != null) {
|
||||
val rec = actualOwner.getSlotRecord(slot)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
return scope.resolve(rec, name)
|
||||
}
|
||||
}
|
||||
@@ -1698,7 +1698,7 @@ class FastLocalVarRef(
|
||||
val actualOwner = cachedOwnerScope
|
||||
if (slot >= 0 && actualOwner != null) {
|
||||
val rec = actualOwner.getSlotRecord(slot)
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx)) {
|
||||
if (rec.declaringClass == null || canAccessMember(rec.visibility, rec.declaringClass, scope.currentClassCtx, name)) {
|
||||
scope.assign(rec, name, newValue)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -776,4 +776,38 @@ class OOTest {
|
||||
|
||||
assertEquals("success", result.toString(), "Parameter 'id' should shadow method 'id' in block")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testOverrideVisibilityRules1() = runTest {
|
||||
eval("""
|
||||
interface Base {
|
||||
abstract protected fun foo()
|
||||
|
||||
fun bar() {
|
||||
// it must see foo() as it is protected and
|
||||
// is declared here (even as abstract):
|
||||
foo()
|
||||
}
|
||||
}
|
||||
class Derived : Base {
|
||||
protected val suffix = "!"
|
||||
|
||||
private fun fooPrivateImpl() = "bar"
|
||||
|
||||
override protected fun foo() {
|
||||
// it should access own private and all protected memberes here:
|
||||
fooPrivateImpl() + suffix
|
||||
}
|
||||
}
|
||||
class Derived2: Base {
|
||||
private var value = 42
|
||||
|
||||
override protected fun foo() {
|
||||
if( value < 10 ) 10 else value
|
||||
}
|
||||
}
|
||||
assertEquals("bar!", Derived().bar())
|
||||
assertEquals(42, Derived2().bar())
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user