Skip to content

Commit

Permalink
Fixes IllegalAccessError with Java package protected class
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
eed3si9n committed Aug 10, 2024
1 parent 55ddaec commit e7d479f
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 25 deletions.
53 changes: 28 additions & 25 deletions compiler/src/dotty/tools/dotc/transform/Erasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import ExplicitOuter.*
import core.Mode
import util.Property
import reporting.*
import scala.annotation.tailrec

class Erasure extends Phase with DenotTransformer {

Expand Down Expand Up @@ -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

Expand All @@ -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))
}
Expand Down
21 changes: 21 additions & 0 deletions tests/run/java-package-protected/A.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// filter: unchecked
package a;

/** This is package protected. */
class B<T extends B<T>> {
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<A> { }
10 changes: 10 additions & 0 deletions tests/run/java-package-protected/C.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package b

import a.*

object C:
def m: Int =
val a = new A()
.setConnectTimeout(1)
.setFailedAttempts(1)
0
5 changes: 5 additions & 0 deletions tests/run/java-package-protected/Test.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// scalajs: --skip

object Test extends App:
assert(b.C.m == 0)
end Test

0 comments on commit e7d479f

Please sign in to comment.