Skip to content

Commit 96f230a

Browse files
committed
Merge pull request scala#5059 from lrytz/t9702
SI-9702 Fix backend crash with classOf[T] annotation argument
2 parents 5654ebd + e4529ca commit 96f230a

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
@@ -597,17 +597,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
597597
val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym
598598
classBTypeFromSymbol(classSym).internalName
599599
}
600-
601-
/**
602-
* The jvm descriptor of a type.
603-
*/
604-
final def descriptor(t: Type): String = typeToBType(t).descriptor
605-
606-
/**
607-
* The jvm descriptor for a symbol.
608-
*/
609-
final def descriptor(sym: Symbol): String = classBTypeFromSymbol(sym).descriptor
610-
611600
} // end of trait BCInnerClassGen
612601

613602
trait BCAnnotGen extends BCInnerClassGen {
@@ -616,6 +605,35 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
616605
private lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS"))
617606
private lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME"))
618607

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

@@ -715,9 +732,10 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
715732
case StringTag =>
716733
assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant`
717734
av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag
718-
case ClazzTag => av.visit(name, typeToBType(const.typeValue).toASMType)
735+
case ClazzTag =>
736+
av.visit(name, typeToBType(erasedType(const.typeValue)).toASMType)
719737
case EnumTag =>
720-
val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class.
738+
val edesc = descriptorForErasedType(const.tpe) // the class descriptor of the enumeration class.
721739
val evalue = const.symbolValue.name.toString // value the actual enumeration value.
722740
av.visitEnum(name, edesc, evalue)
723741
}
@@ -742,7 +760,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
742760
case NestedAnnotArg(annInfo) =>
743761
val AnnotationInfo(typ, args, assocs) = annInfo
744762
assert(args.isEmpty, args)
745-
val desc = descriptor(typ) // the class descriptor of the nested annotation class
763+
val desc = descriptorForErasedType(typ) // the class descriptor of the nested annotation class
746764
val nestedVisitor = av.visitAnnotation(name, desc)
747765
emitAssocs(nestedVisitor, assocs)
748766
}
@@ -767,7 +785,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
767785
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
768786
val AnnotationInfo(typ, args, assocs) = annot
769787
assert(args.isEmpty, args)
770-
val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
788+
val av = cw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
771789
emitAssocs(av, assocs)
772790
}
773791
}
@@ -779,7 +797,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
779797
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
780798
val AnnotationInfo(typ, args, assocs) = annot
781799
assert(args.isEmpty, args)
782-
val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
800+
val av = mw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
783801
emitAssocs(av, assocs)
784802
}
785803
}
@@ -791,7 +809,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
791809
for(annot <- annotations; if shouldEmitAnnotation(annot)) {
792810
val AnnotationInfo(typ, args, assocs) = annot
793811
assert(args.isEmpty, args)
794-
val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot))
812+
val av = fw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot))
795813
emitAssocs(av, assocs)
796814
}
797815
}
@@ -806,7 +824,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
806824
annot <- annots) {
807825
val AnnotationInfo(typ, args, assocs) = annot
808826
assert(args.isEmpty, args)
809-
val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot))
827+
val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptorForErasedType(typ), isRuntimeVisible(annot))
810828
emitAssocs(pannVisitor, assocs)
811829
}
812830
}
@@ -990,7 +1008,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
9901008

9911009
mirrorMethod.visitCode()
9921010

993-
mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module))
1011+
mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, classBTypeFromSymbol(module).descriptor)
9941012

9951013
var index = 0
9961014
for(jparamType <- paramJavaTypes) {
@@ -1051,7 +1069,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
10511069
def getExceptions(excs: List[AnnotationInfo]): List[String] = {
10521070
for (ThrownException(tp) <- excs.distinct)
10531071
yield {
1054-
val erased = enteringErasure(erasure.erasure(tp.typeSymbol)(tp))
1072+
val erased = erasedType(tp)
10551073
internalName(erased.typeSymbol)
10561074
}
10571075
}

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
@@ -1118,7 +1118,7 @@ abstract class Erasure extends AddInterfaces
11181118

11191119
case Literal(ct) if ct.tag == ClazzTag
11201120
&& ct.typeValue.typeSymbol != definitions.UnitClass =>
1121-
val erased = ct.typeValue match {
1121+
val erased = ct.typeValue.dealiasWiden match {
11221122
case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => scalaErasure.eraseNormalClassRef(tr)
11231123
case tpe => specialScalaErasure(tpe)
11241124
}

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)