From 56018ac1be000ce683ad4ea418edb6331fe7d4ea Mon Sep 17 00:00:00 2001 From: Guillaume Martres Date: Wed, 11 Jan 2023 15:37:46 +0100 Subject: [PATCH] Disallow constructor params from appearing in parent types for soundness Fixes #16270. --- .../tools/dotc/transform/PostTyper.scala | 8 ++++++ tests/init/neg/early-promote4.scala | 8 +++--- tests/init/neg/early-promote5.scala | 6 ++--- tests/neg/i16270a.scala | 25 +++++++++++++++++++ tests/neg/i16270b.scala | 9 +++++++ tests/neg/i16270c.scala | 3 +++ tests/neg/i3935.scala | 10 ++++++++ tests/{pos => neg}/i5636.scala | 2 +- tests/pos/i3935.scala | 10 -------- 9 files changed, 63 insertions(+), 18 deletions(-) create mode 100644 tests/neg/i16270a.scala create mode 100644 tests/neg/i16270b.scala create mode 100644 tests/neg/i16270c.scala create mode 100644 tests/neg/i3935.scala rename tests/{pos => neg}/i5636.scala (72%) delete mode 100644 tests/pos/i3935.scala diff --git a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala index 75a6bedbed57..5abb32b15d57 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostTyper.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostTyper.scala @@ -390,6 +390,14 @@ class PostTyper extends MacroTransform with IdentityDenotTransformer { thisPhase case impl: Template => for parent <- impl.parents do Checking.checkTraitInheritance(parent.tpe.classSymbol, sym.asClass, parent.srcPos) + // Constructor parameters are in scope when typing a parent. + // While they can safely appear in a parent tree, to preserve + // soundness we need to ensure they don't appear in a parent + // type (#16270). + val illegalRefs = parent.tpe.namedPartsWith(p => p.symbol.is(ParamAccessor) && (p.symbol.owner eq sym)) + if illegalRefs.nonEmpty then + report.error( + em"The type of a class parent cannot refer to constructor parameters, but ${parent.tpe} refers to ${illegalRefs.map(_.name.show).mkString(",")}", parent.srcPos) // Add SourceFile annotation to top-level classes if sym.owner.is(Package) then if ctx.compilationUnit.source.exists && sym != defn.SourceFileAnnot then diff --git a/tests/init/neg/early-promote4.scala b/tests/init/neg/early-promote4.scala index 65f917553974..487a75c5516f 100644 --- a/tests/init/neg/early-promote4.scala +++ b/tests/init/neg/early-promote4.scala @@ -8,13 +8,13 @@ class Outer { trait B { def bar() = assert(a == 5) } -} -class M(val o: Outer) extends A with o.B { - val n: Int = 10 + class M extends A with B { + val n: Int = 10 + } } class Dummy { val m: Int = n + 4 val n: Int = 10 // error -} \ No newline at end of file +} diff --git a/tests/init/neg/early-promote5.scala b/tests/init/neg/early-promote5.scala index 404f6fdb8d70..3f850b623ea3 100644 --- a/tests/init/neg/early-promote5.scala +++ b/tests/init/neg/early-promote5.scala @@ -8,13 +8,13 @@ class Outer { trait B { def bar(x: A) = println(a) } -} -class M(val o: Outer, c: Container) extends A with o.B + class M(c: Container) extends A with B +} class Container { val o = new Outer - val m = new M(o, this) // error + val m = new o.M(this) // error val s = "hello" } diff --git a/tests/neg/i16270a.scala b/tests/neg/i16270a.scala new file mode 100644 index 000000000000..b4a5016aaa08 --- /dev/null +++ b/tests/neg/i16270a.scala @@ -0,0 +1,25 @@ +class Outer { + type Smuggler + var smuggler: Option[Smuggler] = None +} +class Foo[T](var unpack: T) +class Evil(val outer: Outer, extract: outer.type => Unit) extends Foo[outer.type](outer) { // error + def doExtract(): Unit = extract(unpack) +} + +object Test { + def main(args: Array[String]): Unit = { + val outer1 = new Outer { type Smuggler = Int } + outer1.smuggler = Some(5) + val evil1 = new Evil(outer1, _ => ()) + + val outer2 = new Outer { type Smuggler = String } + var extractedOuter2: Option[outer2.type] = None + val evil2 = new Evil(outer2, x => extractedOuter2 = Some(x)) + + evil2.unpack = evil1.unpack + evil2.doExtract() + val smuggled: String = extractedOuter2.get.smuggler.get + println(smuggled) + } +} diff --git a/tests/neg/i16270b.scala b/tests/neg/i16270b.scala new file mode 100644 index 000000000000..d520bf7516e2 --- /dev/null +++ b/tests/neg/i16270b.scala @@ -0,0 +1,9 @@ +class Outer { + class Foo(var unpack: Outer.this.type) + + type Smuggler + var smuggler: Option[Smuggler] = None +} +class Evil(val outer: Outer, extract: outer.type => Unit) extends outer.Foo(outer) { // error + def doExtract(): Unit = extract(unpack) +} diff --git a/tests/neg/i16270c.scala b/tests/neg/i16270c.scala new file mode 100644 index 000000000000..e1d51913c1ce --- /dev/null +++ b/tests/neg/i16270c.scala @@ -0,0 +1,3 @@ +class Foo[T <: Singleton](x: T) +class Outer +class Evil(val outer: Outer) extends Foo(outer) // error (because outer.type appears in the inferred type) diff --git a/tests/neg/i3935.scala b/tests/neg/i3935.scala new file mode 100644 index 000000000000..07515a4c9ff9 --- /dev/null +++ b/tests/neg/i3935.scala @@ -0,0 +1,10 @@ +enum Foo3[T](x: T) { + case Bar[S, T](y: T) extends Foo3[y.type](y) // error +} + +// val foo: Foo3.Bar[Nothing, 3] = Foo3.Bar(3) +// val bar = foo + +// def baz[T](f: Foo3[T]): f.type = f + +// val qux = baz(bar) // existentials are back in Dotty? diff --git a/tests/pos/i5636.scala b/tests/neg/i5636.scala similarity index 72% rename from tests/pos/i5636.scala rename to tests/neg/i5636.scala index 0a38439d718e..9c3b30af801a 100644 --- a/tests/pos/i5636.scala +++ b/tests/neg/i5636.scala @@ -4,6 +4,6 @@ trait Bar[X] { def foo: X = ??? } // same for `class Foo(...)...` -trait Foo(val a: A) extends Bar[a.type] { +trait Foo(val a: A) extends Bar[a.type] { // error val same: a.type = foo } diff --git a/tests/pos/i3935.scala b/tests/pos/i3935.scala deleted file mode 100644 index 4a3af9e3b6ec..000000000000 --- a/tests/pos/i3935.scala +++ /dev/null @@ -1,10 +0,0 @@ -enum Foo3[T](x: T) { - case Bar[S, T](y: T) extends Foo3[y.type](y) -} - -val foo: Foo3.Bar[Nothing, 3] = Foo3.Bar(3) -val bar = foo - -def baz[T](f: Foo3[T]): f.type = f - -val qux = baz(bar) // existentials are back in Dotty?