Skip to content

Commit 05c0b52

Browse files
committed
Fix protected constructor access checks in secondary constructors and super call arguments
Refine isConstructorAccessOK to check isPrimaryConstructor instead of isConstructor. This rejects `new Parent(x)` in secondary constructors, which was incorrectly allowed because ctx.owner.isConstructor was true for both primary (super call) and secondary constructors. Add a post-hoc check in checkParentCall that walks super call arguments and rejects protected constructor accesses within them. This handles cases like `extends B(new A(1))` where A has a protected constructor and is a direct or transitive parent. The check leverages the fact that ctx.owner is the class (not the constructor) in checkParentCall, so isConstructorAccessOK is naturally false.
1 parent 230925d commit 05c0b52

File tree

3 files changed

+25
-1
lines changed

3 files changed

+25
-1
lines changed

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,7 @@ object SymDenotations {
947947
if !cls.exists then
948948
pre.termSymbol.isPackageObject && accessWithin(pre.termSymbol.owner)
949949
else {
950-
val isConstructorAccessOK = isConstructor && ctx.owner.isConstructor
950+
val isConstructorAccessOK = isConstructor && ctx.owner.isPrimaryConstructor
951951
// allow accesses to types from arbitrary subclasses fixes #4737
952952
// don't perform this check for static members
953953
isType || pre.derivesFrom(cls) || isConstructorAccessOK || owner.is(ModuleClass)

compiler/src/dotty/tools/dotc/typer/Checking.scala

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,6 +1415,29 @@ trait Checking {
14151415
case Apply(fn, _) => checkLegalConstructorCall(fn, call, "")
14161416
case _ =>
14171417
}
1418+
1419+
// Check that super call arguments don't contain `new` expressions
1420+
// accessing protected constructors. The super call itself is allowed
1421+
// (via isConstructorAccessOK in isProtectedAccessOK), but nested `new`
1422+
// expressions in its arguments should not benefit from that exception.
1423+
// Since ctx.owner here is the class (not the constructor),
1424+
// isConstructorAccessOK is automatically false, giving the correct check.
1425+
def collectArgs(tree: Tree): List[Tree] = tree match
1426+
case Apply(fn, args) => args ++ collectArgs(fn)
1427+
case TypeApply(fn, _) => collectArgs(fn)
1428+
case _ => Nil
1429+
for arg <- collectArgs(call) do
1430+
arg.foreachSubTree:
1431+
case sel @ tpd.Select(nw: tpd.New, nme.CONSTRUCTOR) =>
1432+
val constrSym = sel.symbol
1433+
if constrSym.is(Protected)
1434+
&& !constrSym.isAccessibleFrom(nw.tpe, superAccess = false)
1435+
then
1436+
report.error(
1437+
em"""${constrSym} cannot be accessed as a member of ${nw.tpe} from ${caller}.
1438+
| protected ${constrSym} can only be accessed from ${caller} or one of its subclasses.""",
1439+
nw.srcPos)
1440+
case _ =>
14181441
}
14191442

14201443
/** Check that `tpt` does not define a higher-kinded type */

tests/neg/i25442/test.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ class I protected (x: Int) {
55
class M protected () extends I(42) {
66
def t1 = new M() // ok
77
def t2 = new I(42) // error
8+
def this(x: Int) = { this(); new I(x) } // error
89
}

0 commit comments

Comments
 (0)