Skip to content

Forward binary compatibility bug in 2.13.17 pickling #13183

@szeiger

Description

@szeiger

scala/scala#10976 changed the pickle format in 2.13.17. A macro-related bug resulting from this change was identified in scala/scala-dev#893 (scala/scala-dev#893 (comment)) and deemed acceptable (scala/scala-dev#893 (comment)), but the problem is not limited to macros. It also affects forward compatibility of runtime reflection. This problem does not show up in partest or in the community build where all artifacts are produced by the same compiler version.

Reproduction:

TriggerClass.scala is lifted directly from test/files/run/sd884b/A.scala:

package picklerepro

object T { def i = 0 }

class ann(val x: Int = 1, val y: Int = T.i) extends scala.annotation.StaticAnnotation
class kon(val x: Int = 1, val y: Int = 2) extends scala.annotation.ConstantAnnotation

class TriggerClass {
  @ann(x = 11) def m1: Int = 1
  @ann(y = 22) def m2: Int = 1

  @kon(x = 11) def k1: Int = 1
  @kon(y = 22) def k2: Int = 1
}

ReflectionRunner.scala is the actual test:

package picklerepro

import scala.reflect.runtime.universe._

object ReflectionRunner {
  def main(args: Array[String]): Unit = {
    val className = "picklerepro.TriggerClass"
    println(s"scala.util.Properties.versionNumberString = ${scala.util.Properties.versionNumberString}")
    println(s"Loading class: $className")

    val mirror = runtimeMirror(getClass.getClassLoader)
    val javaCls = Class.forName(className)
    val sym = mirror.classSymbol(javaCls)
    println(s"Got symbol (lazy): $sym")
    println("Forcing .info (this triggers the UnPickler) ...")
    val info = sym.info
    println(s"Info loaded: $info")
    println("Walking members and forcing their annotations ...")
    val members = info.members.toList
      .filter(s => s.name.toString.startsWith("m") || s.name.toString.startsWith("k"))
      .sortBy(_.name.toString)
    for (m <- members) {
      val a = m.annotations.headOption.getOrElse(throw new RuntimeException(s"no annotation on $m"))
      println(s"  $m -> $a")
    }
    println("OK")
  }
}

To trigger the bug, compile TriggerClass.scala with 2.13.18 and then run ReflectionRunner on 2.13.16:

#!/usr/bin/env bash

SCALA_2_13_16=${SCALA_2_13_16:-$HOME/scala-2.13.16}
SCALA_2_13_18=${SCALA_2_13_18:-$HOME/scala-2.13.18}

here=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
work=$(mktemp -d)
trap 'rm -rf "$work"' EXIT

trigger_classes="$work/trigger"
trigger_jar="$work/trigger.jar"
runner_classes="$work/runner"
mkdir -p "$trigger_classes" "$runner_classes"

"$SCALA_2_13_18/bin/scalac" -d "$trigger_classes" "$here/TriggerClass.scala"
( cd "$trigger_classes" && jar cf "$trigger_jar" . )

"$SCALA_2_13_16/bin/scalac" -d "$runner_classes" "$here/ReflectionRunner.scala"

cp="$SCALA_2_13_16/lib/scala-library.jar:$SCALA_2_13_16/lib/scala-reflect.jar:$runner_classes:$trigger_jar"

java -cp "$cp" picklerepro.ReflectionRunner

It fails with

Exception in thread "main" java.lang.RuntimeException: error reading Scala signature of picklerepro.TriggerClass: unsafe symbol defaultArg (child of package meta) in runtime reflection universe
	at scala.reflect.internal.pickling.UnPickler.unpickle(UnPickler.scala:48)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions