Skip to content

Erasure#typedSelect: several fixes related to qualifier accessibility #9364

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jul 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import core.Annotations.BodyAnnotation
import typer.{NoChecking, LiftErased}
import typer.Inliner
import typer.ProtoTypes._
import typer.ErrorReporting.errorTree
import core.TypeErasure._
import core.Decorators._
import dotty.tools.dotc.ast.{tpd, untpd}
Expand Down Expand Up @@ -684,24 +685,52 @@ object Erasure {
qual
}

/** Can we safely use `cls` as a qualifier without getting a runtime error on
* the JVM due to its accessibility checks?
*/
def isJvmAccessible(cls: Symbol): Boolean =
// Scala classes are always emitted as public, unless the
// `private` modifier is used, but a non-private class can never
// extend a private class, so such a class will never be a cast target.
!cls.is(Flags.JavaDefined) || {
// We can't rely on `isContainedWith` here because packages are
// not nested from the JVM point of view.
val boundary = cls.accessBoundary(cls.owner)(using preErasureCtx)
(boundary eq defn.RootClass) ||
(ctx.owner.enclosingPackageClass eq boundary)
}

def recur(qual: Tree): Tree = {
val qualIsPrimitive = qual.tpe.widen.isPrimitiveValueType
val symIsPrimitive = sym.owner.isPrimitiveValueClass

def originalQual: Type =
erasure(tree.qualifier.typeOpt.widen.finalResultType)

if (qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType)
recur(box(qual))
else if (!qualIsPrimitive && symIsPrimitive)
recur(unbox(qual, sym.owner.typeRef))
else if (sym.owner eq defn.ArrayClass)
selectArrayMember(qual, erasure(tree.qualifier.typeOpt.widen.finalResultType))
selectArrayMember(qual, originalQual)
else {
val qual1 = adaptIfSuper(qual)
if (qual1.tpe.derivesFrom(sym.owner) || qual1.isInstanceOf[Super])
select(qual1, sym)
else
val castTarget = // Avoid inaccessible cast targets, see i8661
if sym.owner.isAccessibleFrom(qual1.tpe)(using preErasureCtx)
then sym.owner.typeRef
else erasure(tree.qualifier.typeOpt.widen)
if isJvmAccessible(sym.owner)
then
sym.owner.typeRef
else
// If the owner is inaccessible, try going through the qualifier,
// but be careful to not go in an infinite loop in case that doesn't
// work either.
val tp = originalQual
if tp =:= qual1.tpe.widen then
return errorTree(qual1,
ex"Unable to emit reference to ${sym.showLocated}, ${sym.owner} is not accessible in ${ctx.owner.enclosingClass}")
tp
recur(cast(qual1, castTarget))
}
}
Expand Down
5 changes: 5 additions & 0 deletions tests/neg/java-trait-access/A.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package pkg;

class A {
public void foo() {}
}
18 changes: 18 additions & 0 deletions tests/neg/java-trait-access/B.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package pkg {
trait B extends A
class C extends B
}

object Test {
def test1: Unit = {
val c = new pkg.C
c.foo() // OK
val b: pkg.B = c
b.foo() // error: Unable to emit reference to method foo in class A, class A is not accessible in object Test
}

val c2 = new pkg.C
c2.foo() // OK
val b2: pkg.B = c2
b2.foo() // error: Unable to emit reference to method foo in class A, class A is not accessible in object Test
}
13 changes: 13 additions & 0 deletions tests/pos/trait-access.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package foo {
protected[foo] trait A {
def a: Unit = {}
}
class B extends A
}
trait C extends foo.B
object Test {
def test: Unit = {
val c = new C {}
c.a
}
}