From 18c04b9f8915917983126c6573f3933ea303ee21 Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 7 Dec 2022 21:45:32 +0100 Subject: [PATCH 1/2] Only include accessible base classes in orDominator By joining union types we could previously uncover inaccessible base classes that could lead to access errors at runtime. We now filter out such classes when computing the orDominator. Fixes #16474 --- compiler/src/dotty/tools/dotc/core/TypeOps.scala | 10 ++++++++-- tests/pos/i16474.scala | 13 +++++++++++++ tests/run/i16474/BaseProvider.java | 6 ++++++ tests/run/i16474/Case1.java | 9 +++++++++ tests/run/i16474/Case1Provider.java | 5 +++++ tests/run/i16474/Case2.java | 8 ++++++++ tests/run/i16474/Case2Provider.java | 5 +++++ tests/run/i16474/Encrypter.java | 4 ++++ tests/run/i16474/test.scala | 14 ++++++++++++++ 9 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 tests/pos/i16474.scala create mode 100644 tests/run/i16474/BaseProvider.java create mode 100644 tests/run/i16474/Case1.java create mode 100644 tests/run/i16474/Case1Provider.java create mode 100644 tests/run/i16474/Case2.java create mode 100644 tests/run/i16474/Case2Provider.java create mode 100644 tests/run/i16474/Encrypter.java create mode 100644 tests/run/i16474/test.scala diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 9363b27b4dde..6d9e20412f4f 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -209,7 +209,7 @@ object TypeOps: /** Approximate union type by intersection of its dominators. * That is, replace a union type Tn | ... | Tn - * by the smallest intersection type of base-class instances of T1,...,Tn. + * by the smallest intersection type of accessible base-class instances of T1,...,Tn. * Example: Given * * trait C[+T] @@ -370,8 +370,14 @@ object TypeOps: } } + def isAccessible(cls: ClassSymbol) = + if cls.isOneOf(AccessFlags) || cls.privateWithin.exists then + cls.isAccessibleFrom(tp.baseType(cls).normalizedPrefix) + else true + // Step 3: Intersect base classes of both sides - val commonBaseClasses = orBaseClasses(tp) + val commonBaseClasses = orBaseClasses(tp).filterConserve(isAccessible) + val doms = dominators(commonBaseClasses, Nil) def baseTp(cls: ClassSymbol): Type = tp.baseType(cls).mapReduceOr(identity)(mergeRefinedOrApplied) diff --git a/tests/pos/i16474.scala b/tests/pos/i16474.scala new file mode 100644 index 000000000000..834b22c7691f --- /dev/null +++ b/tests/pos/i16474.scala @@ -0,0 +1,13 @@ +object o: + + private[o] trait A + trait B + class C extends A, B + class D extends A, B + +def test = + def f[T](x: T): T => T = identity + val g = f(if ??? then o.C() else o.D()) + g(new o.B{}) + + diff --git a/tests/run/i16474/BaseProvider.java b/tests/run/i16474/BaseProvider.java new file mode 100644 index 000000000000..29346bcf7730 --- /dev/null +++ b/tests/run/i16474/BaseProvider.java @@ -0,0 +1,6 @@ +// BaseProvider.java +package repro.impl; + +import repro.*; +abstract class BaseProvider{} + diff --git a/tests/run/i16474/Case1.java b/tests/run/i16474/Case1.java new file mode 100644 index 000000000000..9d05a2abb6d6 --- /dev/null +++ b/tests/run/i16474/Case1.java @@ -0,0 +1,9 @@ +// Case1.java +package repro; + +import repro.impl.*; + +public class Case1 extends Case1Provider implements Encrypter { + public Case1(){} +} + diff --git a/tests/run/i16474/Case1Provider.java b/tests/run/i16474/Case1Provider.java new file mode 100644 index 000000000000..0b6c63d3bb3d --- /dev/null +++ b/tests/run/i16474/Case1Provider.java @@ -0,0 +1,5 @@ +// Case1Provider.java +package repro.impl; + +public abstract class Case1Provider extends BaseProvider {} + diff --git a/tests/run/i16474/Case2.java b/tests/run/i16474/Case2.java new file mode 100644 index 000000000000..37804d3b84eb --- /dev/null +++ b/tests/run/i16474/Case2.java @@ -0,0 +1,8 @@ +// Case2.java +package repro; + +import repro.impl.*; + +public class Case2 extends Case2Provider implements Encrypter { + public Case2(){} +} diff --git a/tests/run/i16474/Case2Provider.java b/tests/run/i16474/Case2Provider.java new file mode 100644 index 000000000000..e860964e59c3 --- /dev/null +++ b/tests/run/i16474/Case2Provider.java @@ -0,0 +1,5 @@ +// Case2Provider.java +package repro.impl; + +public abstract class Case2Provider extends BaseProvider {} + diff --git a/tests/run/i16474/Encrypter.java b/tests/run/i16474/Encrypter.java new file mode 100644 index 000000000000..ec721d27af9d --- /dev/null +++ b/tests/run/i16474/Encrypter.java @@ -0,0 +1,4 @@ +// Encrypter.java +package repro; +public interface Encrypter{} + diff --git a/tests/run/i16474/test.scala b/tests/run/i16474/test.scala new file mode 100644 index 000000000000..2b58807e119b --- /dev/null +++ b/tests/run/i16474/test.scala @@ -0,0 +1,14 @@ +// test.scala +import repro.* +import scala.util.Try + +def get(arg: Any): Try[Encrypter] = Try { + val x: Any = 1 + arg match + case 1 => new Case1() + case 2 => new Case2() + case _ => throw new RuntimeException(s"Unsupported got $arg") +} + +@main def Test = + val result = get(null) From 882bb41923cb7476df1430edbdae9ecd5ef3b74c Mon Sep 17 00:00:00 2001 From: odersky Date: Wed, 7 Dec 2022 23:57:02 +0100 Subject: [PATCH 2/2] Skip test for scalaJS Add a magical comment instead of moving test --- tests/run/i16474/test.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/run/i16474/test.scala b/tests/run/i16474/test.scala index 2b58807e119b..2dc2900dd2a8 100644 --- a/tests/run/i16474/test.scala +++ b/tests/run/i16474/test.scala @@ -1,3 +1,4 @@ +// scalajs: --skip // test.scala import repro.* import scala.util.Try