From e7d479f80a29c6ae21d0755047e563963b87ac82 Mon Sep 17 00:00:00 2001 From: Eugene Yokota Date: Sat, 10 Aug 2024 12:00:15 -0400 Subject: [PATCH] Fixes IllegalAccessError with Java package protected class This is a backport of Scala 2.x scala/scala 6023 Fixes 13841 Fixes 13897 **Problem** When compiling `builder.call1().call2()` where both are Java-defined package-protected class through a public subsclass, Scala 3 does not properly cast the receiver to the public class, and results in an IllegalAccessError. **Solution** This backports the casting fix from the Scala 2.x compiler. --- .../dotty/tools/dotc/transform/Erasure.scala | 53 ++++++++++--------- tests/run/java-package-protected/A.java | 21 ++++++++ tests/run/java-package-protected/C.scala | 10 ++++ tests/run/java-package-protected/Test.scala | 5 ++ 4 files changed, 64 insertions(+), 25 deletions(-) create mode 100644 tests/run/java-package-protected/A.java create mode 100644 tests/run/java-package-protected/C.scala create mode 100644 tests/run/java-package-protected/Test.scala diff --git a/compiler/src/dotty/tools/dotc/transform/Erasure.scala b/compiler/src/dotty/tools/dotc/transform/Erasure.scala index be00d952566c..7414ca7e69c6 100644 --- a/compiler/src/dotty/tools/dotc/transform/Erasure.scala +++ b/compiler/src/dotty/tools/dotc/transform/Erasure.scala @@ -36,6 +36,7 @@ import ExplicitOuter.* import core.Mode import util.Property import reporting.* +import scala.annotation.tailrec class Erasure extends Phase with DenotTransformer { @@ -764,7 +765,8 @@ object Erasure { (ctx.owner.enclosingPackageClass eq boundary) } - def recur(qual: Tree): Tree = { + @tailrec + def recur(qual: Tree): Tree = val qualIsPrimitive = qual.tpe.widen.isPrimitiveValueType val symIsPrimitive = sym.owner.isPrimitiveValueClass @@ -773,33 +775,34 @@ object Erasure { inContext(preErasureCtx): tree.qualifier.typeOpt.widen.finalResultType) - if (qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType) + if qualIsPrimitive && !symIsPrimitive || qual.tpe.widenDealias.isErasedValueType then recur(box(qual)) - else if (!qualIsPrimitive && symIsPrimitive) + else if !qualIsPrimitive && symIsPrimitive then recur(unbox(qual, sym.owner.typeRef)) - else if (sym.owner eq defn.ArrayClass) + else if sym.owner eq defn.ArrayClass then 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 isJvmAccessible(sym.owner) && sym.owner.isType - 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, - em"Unable to emit reference to ${sym.showLocated}, ${sym.owner} is not accessible in ${ctx.owner.enclosingClass}") - tp - recur(cast(qual1, castTarget)) - } - } + else + adaptIfSuper(qual) match + case qual1: Super => + select(qual1, sym) + case qual1 if !isJvmAccessible(qual1.tpe.typeSymbol) + || !qual1.tpe.derivesFrom(sym.owner) => + val castTarget = // Avoid inaccessible cast targets, see i8661 + if isJvmAccessible(sym.owner) && sym.owner.isType 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, + em"Unable to emit reference to ${sym.showLocated}, ${sym.owner} is not accessible in ${ctx.owner.enclosingClass}") + tp + recur(cast(qual1, castTarget)) + case qual1 => + select(qual1, sym) + end recur checkNotErased(recur(qual1)) } diff --git a/tests/run/java-package-protected/A.java b/tests/run/java-package-protected/A.java new file mode 100644 index 000000000000..66457741f986 --- /dev/null +++ b/tests/run/java-package-protected/A.java @@ -0,0 +1,21 @@ +// filter: unchecked +package a; + +/** This is package protected. */ +class B> { + private int connectTimeout = 10000; + private int failedAttempts = 3; + + public T setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + return (T) this; + } + + public T setFailedAttempts(int failedAttempts) { + this.failedAttempts = failedAttempts; + return (T) this; + } +} + +/** This is public. */ +public class A extends B { } diff --git a/tests/run/java-package-protected/C.scala b/tests/run/java-package-protected/C.scala new file mode 100644 index 000000000000..100b5819ccde --- /dev/null +++ b/tests/run/java-package-protected/C.scala @@ -0,0 +1,10 @@ +package b + +import a.* + +object C: + def m: Int = + val a = new A() + .setConnectTimeout(1) + .setFailedAttempts(1) + 0 diff --git a/tests/run/java-package-protected/Test.scala b/tests/run/java-package-protected/Test.scala new file mode 100644 index 000000000000..a7036a36d4ae --- /dev/null +++ b/tests/run/java-package-protected/Test.scala @@ -0,0 +1,5 @@ +// scalajs: --skip + +object Test extends App: + assert(b.C.m == 0) +end Test