Skip to content

Commit e4529ca

Browse files
committed
SI-9702 Fix backend crash with classOf[T] annotation argument
This commit fixes various issues with classOf literals and Java annotations. - Ensure that a Type within a ConstantType (i.e., a classOf literal) is erased, so `classOf[List[Int]]` becomes `classOf[List]`. - Ensure that no non-erased types are passed to `typeToBType` in the backend. This happens for Java annotations: the annotation type and `classOf` annotation arguments are not erased, the annotationInfos of a symbol are not touched in the compiler pipeline. - If T is an alias to a value class, ensure that `classOf[T]` erases to the value class by calling `dealiasWiden` in erasure.
1 parent 952da60 commit e4529ca

File tree

7 files changed

+200
-55
lines changed

7 files changed

+200
-55
lines changed

src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -469,13 +469,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
469469
case NullTag => emit(asm.Opcodes.ACONST_NULL)
470470

471471
case ClazzTag =>
472-
val toPush: BType = {
473-
typeToBType(const.typeValue) match {
474-
case kind: PrimitiveBType => boxedClassOfPrimitive(kind)
475-
case kind => kind
476-
}
477-
}
478-
mnode.visitLdcInsn(toPush.toASMType)
472+
val tp = typeToBType(const.typeValue)
473+
// classOf[Int] is transformed to Integer.TYPE by CleanUp
474+
assert(!tp.isPrimitive, s"expected class type in classOf[T], found primitive type $tp")
475+
mnode.visitLdcInsn(tp.toASMType)
479476

480477
case EnumTag =>
481478
val sym = const.symbolValue

src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -595,17 +595,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
595595
val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym
596596
classBTypeFromSymbol(classSym).internalName
597597
}
598-
599-
/**
600-
* The jvm descriptor of a type.
601-
*/
602-
final def descriptor(t: Type): String = typeToBType(t).descriptor
603-
604-
/**
605-
* The jvm descriptor for a symbol.
606-
*/
607-
final def descriptor(sym: Symbol): String = classBTypeFromSymbol(sym).descriptor
608-
609598
} // end of trait BCInnerClassGen
610599

611600
trait BCAnnotGen extends BCInnerClassGen {
@@ -614,6 +603,35 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
614603
private lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS"))
615604
private lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME"))
616605

606+
/**
607+
* Annotations are not processed by the compilation pipeline like ordinary trees. Instead, the
608+
* typer extracts them into [[AnnotationInfo]] objects which are attached to the corresponding
609+
* symbol (sym.annotations) or type (as an AnnotatedType, eliminated by erasure).
610+
*
611+
* For Scala annotations this is OK: they are stored in the pickle and ignored by the backend.
612+
* Java annoations on the other hand are additionally emitted to the classfile in Java's format.
613+
*
614+
* This means that [[Type]] instances within an AnnotaionInfo reach the backend non-erased. Examples:
615+
* - @(javax.annotation.Resource @annotation.meta.getter) val x = 0
616+
* Here, annotationInfo.atp is an AnnotatedType.
617+
* - @SomeAnnotation[T] val x = 0
618+
* In principle, the annotationInfo.atp is a non-erased type ref. However, this cannot
619+
* actually happen because Java annotations cannot be generic.
620+
* - @javax.annotation.Resource(`type` = classOf[List[_]]) val x = 0
621+
* The annotationInfo.assocs contains a LiteralAnnotArg(Constant(tp)) where tp is the
622+
* non-erased existential type.
623+
*/
624+
def erasedType(tp: Type): Type = enteringErasure {
625+
// make sure we don't erase value class references to the type that the value class boxes
626+
// this is basically the same logic as in erasure's preTransform, case Literal(classTag).
627+
tp.dealiasWiden match {
628+
case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => erasure.scalaErasure.eraseNormalClassRef(tr)
629+
case tpe => erasure.erasure(tpe.typeSymbol)(tpe)
630+
}
631+
}
632+
633+
def descriptorForErasedType(tp: Type): String = typeToBType(erasedType(tp)).descriptor
634+
617635
/** Whether an annotation should be emitted as a Java annotation
618636
* .initialize: if 'annot' is read from pickle, atp might be uninitialized
619637
*/
@@ -650,7 +668,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
650668
ca(idx) = b.asInstanceOf[Char]
651669
idx += 1
652670
}
653-
654671
ca
655672
}
656673

@@ -713,9 +730,10 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
713730
case StringTag =>
714731
assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant`
715732
av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag
716-
case ClazzTag => av.visit(name, typeToBType(const.typeValue).toASMType)
733+
case ClazzTag =>
734+
av.visit(name, typeToBType(erasedType(const.typeValue)).toASMType)
717735
case EnumTag =>
718-
val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class.
736+
val edesc = descriptorForErasedType(const.tpe) // the class descriptor of the enumeration class.
719737
val evalue = const.symbolValue.name.toString // value the actual enumeration value.
720738
av.visitEnum(name, edesc, evalue)
721739
}
@@ -740,7 +758,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
740758
case NestedAnnotArg(annInfo) =>
741759
val AnnotationInfo(typ, args, assocs) = annInfo
742760
assert(args.isEmpty, args)
743-
val desc = descriptor(typ) // the class descriptor of the nested annotation class
761+
val desc = descriptorForErasedType(typ) // the class descriptor of the nested annotation class
744762
val nestedVisitor = av.visitAnnotation(name, desc)
745763
emitAssocs(nestedVisitor, assocs)
746764
}
@@ -765,7 +783,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
765783
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
766784
val AnnotationInfo(typ, args, assocs) = annot
767785
assert(args.isEmpty, args)
768-
val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
786+
val av = cw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
769787
emitAssocs(av, assocs)
770788
}
771789
}
@@ -777,7 +795,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
777795
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
778796
val AnnotationInfo(typ, args, assocs) = annot
779797
assert(args.isEmpty, args)
780-
val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
798+
val av = mw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
781799
emitAssocs(av, assocs)
782800
}
783801
}
@@ -789,7 +807,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
789807
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
790808
val AnnotationInfo(typ, args, assocs) = annot
791809
assert(args.isEmpty, args)
792-
val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
810+
val av = fw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
793811
emitAssocs(av, assocs)
794812
}
795813
}
@@ -804,7 +822,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
804822
annot <- annots) {
805823
val AnnotationInfo(typ, args, assocs) = annot
806824
assert(args.isEmpty, args)
807-
val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot))
825+
val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptorForErasedType(typ), isRuntimeVisible(annot))
808826
emitAssocs(pannVisitor, assocs)
809827
}
810828
}
@@ -988,7 +1006,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
9881006

9891007
mirrorMethod.visitCode()
9901008

991-
mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module))
1009+
mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, classBTypeFromSymbol(module).descriptor)
9921010

9931011
var index = 0
9941012
for(jparamType <- paramJavaTypes) {
@@ -1049,7 +1067,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
10491067
def getExceptions(excs: List[AnnotationInfo]): List[String] = {
10501068
for (ThrownException(tp) <- excs.distinct)
10511069
yield {
1052-
val erased = enteringErasure(erasure.erasure(tp.typeSymbol)(tp))
1070+
val erased = erasedType(tp)
10531071
internalName(erased.typeSymbol)
10541072
}
10551073
}

src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
177177

178178
/**
179179
* When compiling Array.scala, the type parameter T is not erased and shows up in method
180-
* signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference.
180+
* signatures, e.g. `def apply(i: Int): T`. A TypeRef for T is replaced by ObjectRef.
181181
*/
182182
def nonClassTypeRefToBType(sym: Symbol): ClassBType = {
183183
assert(sym.isType && isCompilingArray, sym)
@@ -190,39 +190,24 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes {
190190
case TypeRef(_, sym, _) => primitiveOrClassToBType(sym) // Common reference to a type such as scala.Int or java.lang.String
191191
case ClassInfoType(_, _, sym) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes typeToBType(moduleClassSymbol.info)
192192

193-
/* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for
194-
* meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning.
195-
* The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala.
196-
*/
197-
case a @ AnnotatedType(_, t) =>
198-
debuglog(s"typeKind of annotated type $a")
199-
typeToBType(t)
200-
201-
/* ExistentialType should (probably) be eliminated by erasure. We know they get here for
202-
* classOf constants:
203-
* class C[T]
204-
* class T { final val k = classOf[C[_]] }
205-
*/
206-
case e @ ExistentialType(_, t) =>
207-
debuglog(s"typeKind of existential type $e")
208-
typeToBType(t)
209-
210193
/* The cases below should probably never occur. They are kept for now to avoid introducing
211194
* new compiler crashes, but we added a warning. The compiler / library bootstrap and the
212195
* test suite don't produce any warning.
213196
*/
214197

215198
case tp =>
216-
currentUnit.warning(tp.typeSymbol.pos,
199+
warning(tp.typeSymbol.pos,
217200
s"an unexpected type representation reached the compiler backend while compiling $currentUnit: $tp. " +
218201
"If possible, please file a bug on issues.scala-lang.org.")
219202

220203
tp match {
221-
case ThisType(ArrayClass) => ObjectRef // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test
222-
case ThisType(sym) => classBTypeFromSymbol(sym)
223-
case SingleType(_, sym) => primitiveOrClassToBType(sym)
224-
case ConstantType(_) => typeToBType(t.underlying)
225-
case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get)
204+
case ThisType(ArrayClass) => ObjectRef // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test
205+
case ThisType(sym) => classBTypeFromSymbol(sym)
206+
case SingleType(_, sym) => primitiveOrClassToBType(sym)
207+
case ConstantType(_) => typeToBType(t.underlying)
208+
case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get)
209+
case AnnotatedType(_, t) => typeToBType(t)
210+
case ExistentialType(_, t) => typeToBType(t)
226211
}
227212
}
228213
}

src/compiler/scala/tools/nsc/transform/Erasure.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1112,7 +1112,7 @@ abstract class Erasure extends AddInterfaces
11121112

11131113
case Literal(ct) if ct.tag == ClazzTag
11141114
&& ct.typeValue.typeSymbol != definitions.UnitClass =>
1115-
val erased = ct.typeValue match {
1115+
val erased = ct.typeValue.dealiasWiden match {
11161116
case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => scalaErasure.eraseNormalClassRef(tr)
11171117
case tpe => specialScalaErasure(tpe)
11181118
}

src/reflect/scala/reflect/internal/transform/Erasure.scala

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,9 @@ trait Erasure {
112112
protected def eraseDerivedValueClassRef(tref: TypeRef): Type = erasedValueClassArg(tref)
113113

114114
def apply(tp: Type): Type = tp match {
115-
case ConstantType(_) =>
116-
tp
115+
case ConstantType(ct) =>
116+
if (ct.tag == ClazzTag) ConstantType(Constant(apply(ct.typeValue)))
117+
else tp
117118
case st: ThisType if st.sym.isPackageClass =>
118119
tp
119120
case st: SubType =>

src/reflect/scala/reflect/runtime/JavaMirrors.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1161,6 +1161,7 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive
11611161
propagatePackageBoundary(jmeth.javaFlags, meth)
11621162
copyAnnotations(meth, jmeth)
11631163
if (jmeth.javaFlags.isVarargs) meth modifyInfo arrayToRepeated
1164+
if (jmeth.getDefaultValue != null) meth.addAnnotation(AnnotationDefaultAttr)
11641165
markAllCompleted(meth)
11651166
meth
11661167
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package scala.issues
2+
3+
import org.junit.runner.RunWith
4+
import org.junit.runners.JUnit4
5+
import org.junit.{AfterClass, BeforeClass, Test}
6+
import org.junit.Assert._
7+
8+
import scala.reflect.runtime._
9+
import scala.tools.reflect.ToolBox
10+
import scala.tools.testing.ClearAfterClass
11+
12+
object RunTest extends ClearAfterClass.Clearable {
13+
var toolBox = universe.runtimeMirror(getClass.getClassLoader).mkToolBox()
14+
override def clear(): Unit = { toolBox = null }
15+
16+
// definitions for individual tests
17+
18+
class VC(val x: Any) extends AnyVal
19+
}
20+
21+
@RunWith(classOf[JUnit4])
22+
class RunTest extends ClearAfterClass {
23+
ClearAfterClass.stateToClear = RunTest
24+
25+
def run[T](code: String): T = {
26+
val tb = RunTest.toolBox
27+
tb.eval(tb.parse(code)).asInstanceOf[T]
28+
}
29+
30+
@Test
31+
def classOfValueClassAlias(): Unit = {
32+
val code =
33+
"""import scala.issues.RunTest.VC
34+
|type aVC = VC
35+
|type aInt = Int
36+
|type aInteger = Integer
37+
|classOf[VC] == classOf[aVC] &&
38+
| classOf[aInt] == classOf[Int] &&
39+
| classOf[aInteger] == classOf[Integer] &&
40+
| classOf[aInt] != classOf[aInteger]
41+
""".stripMargin
42+
assertTrue(run[Boolean](code))
43+
}
44+
45+
@Test
46+
def classOfFinalVal(): Unit = {
47+
val code =
48+
"""class C {
49+
| final val a1 = classOf[Int]
50+
| final val b1 = classOf[List[_]]
51+
| final val c1 = classOf[List[String]]
52+
| final val d1 = classOf[Array[Int]]
53+
| final val e1 = classOf[Array[List[_]]]
54+
| final val f1 = classOf[Array[_]]
55+
|
56+
| val a2 = classOf[Int]
57+
| val b2 = classOf[List[_]]
58+
| val c2 = classOf[List[String]]
59+
| val d2 = classOf[Array[Int]]
60+
| val e2 = classOf[Array[List[_]]]
61+
| val f2 = classOf[Array[_]]
62+
|
63+
| val listC = Class.forName("scala.collection.immutable.List")
64+
|
65+
| val compare = List(
66+
| (a1, a2, Integer.TYPE),
67+
| (b1, b2, listC),
68+
| (c1, c2, listC),
69+
| (d1, d2, Array(1).getClass),
70+
| (e1, e2, Array(List()).getClass),
71+
| (f1, f2, new Object().getClass))
72+
|}
73+
|(new C).compare
74+
""".stripMargin
75+
type K = Class[_]
76+
val cs = run[List[(K, K, K)]](code)
77+
for ((x, y, z) <- cs) {
78+
assertEquals(x, y)
79+
assertEquals(x, z)
80+
}
81+
}
82+
83+
@Test
84+
def t9702(): Unit = {
85+
val code =
86+
"""import javax.annotation.Resource
87+
|import scala.issues.RunTest.VC
88+
|class C {
89+
| type aList[K] = List[K]
90+
| type aVC = VC
91+
| type aInt = Int
92+
| type aInteger = Integer
93+
| @Resource(`type` = classOf[List[Int]]) def a = 0
94+
| @Resource(`type` = classOf[List[_]]) def b = 0
95+
| @Resource(`type` = classOf[aList[_]]) def c = 0
96+
| @Resource(`type` = classOf[Int]) def d = 0
97+
| @Resource(`type` = classOf[aInt]) def e = 0
98+
| @Resource(`type` = classOf[Integer]) def f = 0
99+
| @Resource(`type` = classOf[aInteger]) def g = 0
100+
| @Resource(`type` = classOf[VC]) def h = 0
101+
| @Resource(`type` = classOf[aVC]) def i = 0
102+
| @Resource(`type` = classOf[Array[Int]]) def j = 0
103+
| @Resource(`type` = classOf[Array[List[_]]]) def k = 0
104+
|}
105+
|val c = classOf[C]
106+
|def typeArg(meth: String) = c.getDeclaredMethod(meth).getDeclaredAnnotation(classOf[Resource]).`type`
107+
|('a' to 'k').toList.map(_.toString).map(typeArg)
108+
""".stripMargin
109+
110+
val l = Class.forName("scala.collection.immutable.List")
111+
val i = Integer.TYPE
112+
val ig = new Integer(1).getClass
113+
val v = new RunTest.VC(1).getClass
114+
val ai = Array(1).getClass
115+
val al = Array(List()).getClass
116+
117+
// sanity checks
118+
assertEquals(i, classOf[Int])
119+
assertNotEquals(i, ig)
120+
121+
assertEquals(run[List[Class[_]]](code),
122+
List(l, l, l, i, i, ig, ig, v, v, ai, al))
123+
}
124+
125+
@Test
126+
def annotationInfoNotErased(): Unit = {
127+
val code =
128+
"""import javax.annotation.Resource
129+
|import scala.annotation.meta.getter
130+
|class C {
131+
| type Rg = Resource @getter
132+
| @(Resource @getter)(`type` = classOf[Int]) def a = 0
133+
| @Rg(`type` = classOf[Int]) def b = 0
134+
|}
135+
|val c = classOf[C]
136+
|def typeArg(meth: String) = c.getDeclaredMethod(meth).getDeclaredAnnotation(classOf[Resource]).`type`
137+
|List("a", "b") map typeArg
138+
|""".stripMargin
139+
140+
val i = Integer.TYPE
141+
assertEquals(run[List[Class[_]]](code), List(i, i))
142+
}
143+
}

0 commit comments

Comments
 (0)