Skip to content

Scala2Unpickler: Don't trust the owner field of tparams #12660

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 1 commit into from
Jun 2, 2021
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
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,27 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
sym.setFlag(Scala2x)
if (!(isRefinementClass(sym) || isUnpickleRoot(sym) || sym.is(Scala2Existential))) {
val owner = sym.owner
if (owner.isClass)
val canEnter =
owner.isClass &&
(!sym.is(TypeParam) ||
owner.infoOrCompleter.match
case completer: ClassUnpickler =>
// Type parameters seen after class initialization are not
// actually type parameters of the current class but of some
// external class because of the bizarre way in which Scala 2
// pickles them (see
// https://github.com/scala/scala/blob/aa31e3e6bb945f5d69740d379ede1cd514904109/src/compiler/scala/tools/nsc/symtab/classfile/Pickler.scala#L181-L197).
// Make sure we don't enter them in the class otherwise the
// compiler will get very confused (testcase in sbt-test/scala2-compat/i12641).
// Note: I don't actually know if these stray type parameters
// can also show up before initialization, if that's the case
// we'll need to study more closely how Scala 2 handles type
// parameter unpickling and try to emulate it.
!completer.isInitialized
case _ =>
true)

if (canEnter)
owner.asClass.enter(sym, symScope(owner))
}
sym
Expand Down Expand Up @@ -625,23 +645,30 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
object localMemberUnpickler extends LocalUnpickler

class ClassUnpickler(infoRef: Int) extends LocalUnpickler with TypeParamsCompleter {
private def readTypeParams()(using Context): List[TypeSymbol] = {
private var myTypeParams: List[TypeSymbol] = null

private def readTypeParams()(using Context): Unit = {
val tag = readByte()
val end = readNat() + readIndex
if (tag == POLYtpe) {
val unusedRestpeRef = readNat()
until(end, () => readSymbolRef()(using ctx)).asInstanceOf[List[TypeSymbol]]
}
else Nil
myTypeParams =
if (tag == POLYtpe) {
val unusedRestpeRef = readNat()
until(end, () => readSymbolRef()(using ctx)).asInstanceOf[List[TypeSymbol]]
} else Nil
}
private def loadTypeParams(using Context) =
private def loadTypeParams()(using Context) =
atReadPos(index(infoRef), () => readTypeParams()(using ctx))

/** Have the type params of this class already been unpickled? */
def isInitialized: Boolean = myTypeParams ne null

/** Force reading type params early, we need them in setClassInfo of subclasses. */
def init()(using Context): List[TypeSymbol] = loadTypeParams
def init()(using Context): List[TypeSymbol] =
if !isInitialized then loadTypeParams()
myTypeParams

override def completerTypeParams(sym: Symbol)(using Context): List[TypeSymbol] =
loadTypeParams
init()
}

def rootClassUnpickler(start: Coord, cls: Symbol, module: Symbol, infoRef: Int): ClassUnpickler =
Expand Down
9 changes: 9 additions & 0 deletions sbt-test/scala2-compat/i12641/app/App.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import cek.Async


object demo {

def test1[F[_]](ev: Async[F]): Unit = ???
def test2[F[_]](ev: Async[F]): Unit = ???

}
13 changes: 13 additions & 0 deletions sbt-test/scala2-compat/i12641/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
val scala3Version = sys.props("plugin.scalaVersion")
val scala2Version = sys.props("plugin.scala2Version")

lazy val lib = project.in(file("lib"))
.settings(
scalaVersion := scala2Version
)

lazy val app = project.in(file("app"))
.dependsOn(lib)
.settings(
scalaVersion := scala3Version
)
29 changes: 29 additions & 0 deletions sbt-test/scala2-compat/i12641/lib/Lib.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package cek

trait Async[F[_]]

object Async {
trait WriterTAsync[F[_], L1]
extends Async[({ type LL[A] = WriterT[F, L1, A] })#LL]
with MonadCancel.WriterTMonadCancel[F, L1] {

override def delegate = super.delegate
}

}

case class WriterT[F[_], L0, V]()

trait MonadError[F[_]]
trait MonadCancel[F[_]]

object MonadCancel {

trait WriterTMonadCancel[F[_], L2]
extends MonadCancel[({ type LL[A] = WriterT[F, L2, A] })#LL] {

def delegate: MonadError[({ type LL[A] = WriterT[F, L2, A] })#LL] =
???

}
}
1 change: 1 addition & 0 deletions sbt-test/scala2-compat/i12641/test
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
> app/compile