From e4529ca2e0091ec137c791419ae08c8da8e0aecf Mon Sep 17 00:00:00 2001 From: Lukas Rytz Date: Wed, 30 Mar 2016 16:50:36 +0200 Subject: 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. --- .../tools/nsc/backend/jvm/BCodeBodyBuilder.scala | 11 +- .../scala/tools/nsc/backend/jvm/BCodeHelpers.scala | 60 ++++++--- .../tools/nsc/backend/jvm/BTypesFromSymbols.scala | 33 ++--- .../scala/tools/nsc/transform/Erasure.scala | 2 +- .../scala/reflect/internal/transform/Erasure.scala | 5 +- .../scala/reflect/runtime/JavaMirrors.scala | 1 + test/junit/scala/issues/RunTest.scala | 143 +++++++++++++++++++++ 7 files changed, 200 insertions(+), 55 deletions(-) create mode 100644 test/junit/scala/issues/RunTest.scala diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala index 82aa3c65aa..c17f3c0b5f 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeBodyBuilder.scala @@ -469,13 +469,10 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder { case NullTag => emit(asm.Opcodes.ACONST_NULL) case ClazzTag => - val toPush: BType = { - typeToBType(const.typeValue) match { - case kind: PrimitiveBType => boxedClassOfPrimitive(kind) - case kind => kind - } - } - mnode.visitLdcInsn(toPush.toASMType) + val tp = typeToBType(const.typeValue) + // classOf[Int] is transformed to Integer.TYPE by CleanUp + assert(!tp.isPrimitive, s"expected class type in classOf[T], found primitive type $tp") + mnode.visitLdcInsn(tp.toASMType) case EnumTag => val sym = const.symbolValue diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala index 324fc10eae..03ac006517 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BCodeHelpers.scala @@ -595,17 +595,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { val classSym = if (sym.isJavaDefined && sym.isModuleClass) exitingPickler(sym.linkedClassOfClass) else sym classBTypeFromSymbol(classSym).internalName } - - /** - * The jvm descriptor of a type. - */ - final def descriptor(t: Type): String = typeToBType(t).descriptor - - /** - * The jvm descriptor for a symbol. - */ - final def descriptor(sym: Symbol): String = classBTypeFromSymbol(sym).descriptor - } // end of trait BCInnerClassGen trait BCAnnotGen extends BCInnerClassGen { @@ -614,6 +603,35 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { private lazy val AnnotationRetentionPolicyClassValue = AnnotationRetentionPolicyModule.tpe.member(TermName("CLASS")) private lazy val AnnotationRetentionPolicyRuntimeValue = AnnotationRetentionPolicyModule.tpe.member(TermName("RUNTIME")) + /** + * Annotations are not processed by the compilation pipeline like ordinary trees. Instead, the + * typer extracts them into [[AnnotationInfo]] objects which are attached to the corresponding + * symbol (sym.annotations) or type (as an AnnotatedType, eliminated by erasure). + * + * For Scala annotations this is OK: they are stored in the pickle and ignored by the backend. + * Java annoations on the other hand are additionally emitted to the classfile in Java's format. + * + * This means that [[Type]] instances within an AnnotaionInfo reach the backend non-erased. Examples: + * - @(javax.annotation.Resource @annotation.meta.getter) val x = 0 + * Here, annotationInfo.atp is an AnnotatedType. + * - @SomeAnnotation[T] val x = 0 + * In principle, the annotationInfo.atp is a non-erased type ref. However, this cannot + * actually happen because Java annotations cannot be generic. + * - @javax.annotation.Resource(`type` = classOf[List[_]]) val x = 0 + * The annotationInfo.assocs contains a LiteralAnnotArg(Constant(tp)) where tp is the + * non-erased existential type. + */ + def erasedType(tp: Type): Type = enteringErasure { + // make sure we don't erase value class references to the type that the value class boxes + // this is basically the same logic as in erasure's preTransform, case Literal(classTag). + tp.dealiasWiden match { + case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => erasure.scalaErasure.eraseNormalClassRef(tr) + case tpe => erasure.erasure(tpe.typeSymbol)(tpe) + } + } + + def descriptorForErasedType(tp: Type): String = typeToBType(erasedType(tp)).descriptor + /** Whether an annotation should be emitted as a Java annotation * .initialize: if 'annot' is read from pickle, atp might be uninitialized */ @@ -650,7 +668,6 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { ca(idx) = b.asInstanceOf[Char] idx += 1 } - ca } @@ -713,9 +730,10 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { case StringTag => assert(const.value != null, const) // TODO this invariant isn't documented in `case class Constant` av.visit(name, const.stringValue) // `stringValue` special-cases null, but that execution path isn't exercised for a const with StringTag - case ClazzTag => av.visit(name, typeToBType(const.typeValue).toASMType) + case ClazzTag => + av.visit(name, typeToBType(erasedType(const.typeValue)).toASMType) case EnumTag => - val edesc = descriptor(const.tpe) // the class descriptor of the enumeration class. + val edesc = descriptorForErasedType(const.tpe) // the class descriptor of the enumeration class. val evalue = const.symbolValue.name.toString // value the actual enumeration value. av.visitEnum(name, edesc, evalue) } @@ -740,7 +758,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { case NestedAnnotArg(annInfo) => val AnnotationInfo(typ, args, assocs) = annInfo assert(args.isEmpty, args) - val desc = descriptor(typ) // the class descriptor of the nested annotation class + val desc = descriptorForErasedType(typ) // the class descriptor of the nested annotation class val nestedVisitor = av.visitAnnotation(name, desc) emitAssocs(nestedVisitor, assocs) } @@ -765,7 +783,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = cw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) + val av = cw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -777,7 +795,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = mw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) + val av = mw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -789,7 +807,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { for(annot <- annotations; if shouldEmitAnnotation(annot)) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val av = fw.visitAnnotation(descriptor(typ), isRuntimeVisible(annot)) + val av = fw.visitAnnotation(descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(av, assocs) } } @@ -804,7 +822,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { annot <- annots) { val AnnotationInfo(typ, args, assocs) = annot assert(args.isEmpty, args) - val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptor(typ), isRuntimeVisible(annot)) + val pannVisitor: asm.AnnotationVisitor = jmethod.visitParameterAnnotation(idx, descriptorForErasedType(typ), isRuntimeVisible(annot)) emitAssocs(pannVisitor, assocs) } } @@ -988,7 +1006,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { mirrorMethod.visitCode() - mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, descriptor(module)) + mirrorMethod.visitFieldInsn(asm.Opcodes.GETSTATIC, moduleName, strMODULE_INSTANCE_FIELD, classBTypeFromSymbol(module).descriptor) var index = 0 for(jparamType <- paramJavaTypes) { @@ -1049,7 +1067,7 @@ abstract class BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { def getExceptions(excs: List[AnnotationInfo]): List[String] = { for (ThrownException(tp) <- excs.distinct) yield { - val erased = enteringErasure(erasure.erasure(tp.typeSymbol)(tp)) + val erased = erasedType(tp) internalName(erased.typeSymbol) } } diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala index 85563be428..759b0a615a 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypesFromSymbols.scala @@ -177,7 +177,7 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { /** * When compiling Array.scala, the type parameter T is not erased and shows up in method - * signatures, e.g. `def apply(i: Int): T`. A TyperRef to T is replaced by ObjectReference. + * signatures, e.g. `def apply(i: Int): T`. A TypeRef for T is replaced by ObjectRef. */ def nonClassTypeRefToBType(sym: Symbol): ClassBType = { assert(sym.isType && isCompilingArray, sym) @@ -190,39 +190,24 @@ class BTypesFromSymbols[G <: Global](val global: G) extends BTypes { case TypeRef(_, sym, _) => primitiveOrClassToBType(sym) // Common reference to a type such as scala.Int or java.lang.String case ClassInfoType(_, _, sym) => primitiveOrClassToBType(sym) // We get here, for example, for genLoadModule, which invokes typeToBType(moduleClassSymbol.info) - /* AnnotatedType should (probably) be eliminated by erasure. However we know it happens for - * meta-annotated annotations (@(ann @getter) val x = 0), so we don't emit a warning. - * The type in the AnnotationInfo is an AnnotatedTpe. Tested in jvm/annotations.scala. - */ - case a @ AnnotatedType(_, t) => - debuglog(s"typeKind of annotated type $a") - typeToBType(t) - - /* ExistentialType should (probably) be eliminated by erasure. We know they get here for - * classOf constants: - * class C[T] - * class T { final val k = classOf[C[_]] } - */ - case e @ ExistentialType(_, t) => - debuglog(s"typeKind of existential type $e") - typeToBType(t) - /* The cases below should probably never occur. They are kept for now to avoid introducing * new compiler crashes, but we added a warning. The compiler / library bootstrap and the * test suite don't produce any warning. */ case tp => - currentUnit.warning(tp.typeSymbol.pos, + warning(tp.typeSymbol.pos, s"an unexpected type representation reached the compiler backend while compiling $currentUnit: $tp. " + "If possible, please file a bug on issues.scala-lang.org.") tp match { - 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 - case ThisType(sym) => classBTypeFromSymbol(sym) - case SingleType(_, sym) => primitiveOrClassToBType(sym) - case ConstantType(_) => typeToBType(t.underlying) - case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get) + 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 + case ThisType(sym) => classBTypeFromSymbol(sym) + case SingleType(_, sym) => primitiveOrClassToBType(sym) + case ConstantType(_) => typeToBType(t.underlying) + case RefinedType(parents, _) => parents.map(typeToBType(_).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b).get) + case AnnotatedType(_, t) => typeToBType(t) + case ExistentialType(_, t) => typeToBType(t) } } } diff --git a/src/compiler/scala/tools/nsc/transform/Erasure.scala b/src/compiler/scala/tools/nsc/transform/Erasure.scala index 41f22e5669..ba15b01446 100644 --- a/src/compiler/scala/tools/nsc/transform/Erasure.scala +++ b/src/compiler/scala/tools/nsc/transform/Erasure.scala @@ -1112,7 +1112,7 @@ abstract class Erasure extends AddInterfaces case Literal(ct) if ct.tag == ClazzTag && ct.typeValue.typeSymbol != definitions.UnitClass => - val erased = ct.typeValue match { + val erased = ct.typeValue.dealiasWiden match { case tr @ TypeRef(_, clazz, _) if clazz.isDerivedValueClass => scalaErasure.eraseNormalClassRef(tr) case tpe => specialScalaErasure(tpe) } diff --git a/src/reflect/scala/reflect/internal/transform/Erasure.scala b/src/reflect/scala/reflect/internal/transform/Erasure.scala index 32af6529ca..c069e2c198 100644 --- a/src/reflect/scala/reflect/internal/transform/Erasure.scala +++ b/src/reflect/scala/reflect/internal/transform/Erasure.scala @@ -112,8 +112,9 @@ trait Erasure { protected def eraseDerivedValueClassRef(tref: TypeRef): Type = erasedValueClassArg(tref) def apply(tp: Type): Type = tp match { - case ConstantType(_) => - tp + case ConstantType(ct) => + if (ct.tag == ClazzTag) ConstantType(Constant(apply(ct.typeValue))) + else tp case st: ThisType if st.sym.isPackageClass => tp case st: SubType => diff --git a/src/reflect/scala/reflect/runtime/JavaMirrors.scala b/src/reflect/scala/reflect/runtime/JavaMirrors.scala index 49799136de..bdcfcabdd5 100644 --- a/src/reflect/scala/reflect/runtime/JavaMirrors.scala +++ b/src/reflect/scala/reflect/runtime/JavaMirrors.scala @@ -1161,6 +1161,7 @@ private[scala] trait JavaMirrors extends internal.SymbolTable with api.JavaUnive propagatePackageBoundary(jmeth.javaFlags, meth) copyAnnotations(meth, jmeth) if (jmeth.javaFlags.isVarargs) meth modifyInfo arrayToRepeated + if (jmeth.getDefaultValue != null) meth.addAnnotation(AnnotationDefaultAttr) markAllCompleted(meth) meth } diff --git a/test/junit/scala/issues/RunTest.scala b/test/junit/scala/issues/RunTest.scala new file mode 100644 index 0000000000..2bc8008222 --- /dev/null +++ b/test/junit/scala/issues/RunTest.scala @@ -0,0 +1,143 @@ +package scala.issues + +import org.junit.runner.RunWith +import org.junit.runners.JUnit4 +import org.junit.{AfterClass, BeforeClass, Test} +import org.junit.Assert._ + +import scala.reflect.runtime._ +import scala.tools.reflect.ToolBox +import scala.tools.testing.ClearAfterClass + +object RunTest extends ClearAfterClass.Clearable { + var toolBox = universe.runtimeMirror(getClass.getClassLoader).mkToolBox() + override def clear(): Unit = { toolBox = null } + + // definitions for individual tests + + class VC(val x: Any) extends AnyVal +} + +@RunWith(classOf[JUnit4]) +class RunTest extends ClearAfterClass { + ClearAfterClass.stateToClear = RunTest + + def run[T](code: String): T = { + val tb = RunTest.toolBox + tb.eval(tb.parse(code)).asInstanceOf[T] + } + + @Test + def classOfValueClassAlias(): Unit = { + val code = + """import scala.issues.RunTest.VC + |type aVC = VC + |type aInt = Int + |type aInteger = Integer + |classOf[VC] == classOf[aVC] && + | classOf[aInt] == classOf[Int] && + | classOf[aInteger] == classOf[Integer] && + | classOf[aInt] != classOf[aInteger] + """.stripMargin + assertTrue(run[Boolean](code)) + } + + @Test + def classOfFinalVal(): Unit = { + val code = + """class C { + | final val a1 = classOf[Int] + | final val b1 = classOf[List[_]] + | final val c1 = classOf[List[String]] + | final val d1 = classOf[Array[Int]] + | final val e1 = classOf[Array[List[_]]] + | final val f1 = classOf[Array[_]] + | + | val a2 = classOf[Int] + | val b2 = classOf[List[_]] + | val c2 = classOf[List[String]] + | val d2 = classOf[Array[Int]] + | val e2 = classOf[Array[List[_]]] + | val f2 = classOf[Array[_]] + | + | val listC = Class.forName("scala.collection.immutable.List") + | + | val compare = List( + | (a1, a2, Integer.TYPE), + | (b1, b2, listC), + | (c1, c2, listC), + | (d1, d2, Array(1).getClass), + | (e1, e2, Array(List()).getClass), + | (f1, f2, new Object().getClass)) + |} + |(new C).compare + """.stripMargin + type K = Class[_] + val cs = run[List[(K, K, K)]](code) + for ((x, y, z) <- cs) { + assertEquals(x, y) + assertEquals(x, z) + } + } + + @Test + def t9702(): Unit = { + val code = + """import javax.annotation.Resource + |import scala.issues.RunTest.VC + |class C { + | type aList[K] = List[K] + | type aVC = VC + | type aInt = Int + | type aInteger = Integer + | @Resource(`type` = classOf[List[Int]]) def a = 0 + | @Resource(`type` = classOf[List[_]]) def b = 0 + | @Resource(`type` = classOf[aList[_]]) def c = 0 + | @Resource(`type` = classOf[Int]) def d = 0 + | @Resource(`type` = classOf[aInt]) def e = 0 + | @Resource(`type` = classOf[Integer]) def f = 0 + | @Resource(`type` = classOf[aInteger]) def g = 0 + | @Resource(`type` = classOf[VC]) def h = 0 + | @Resource(`type` = classOf[aVC]) def i = 0 + | @Resource(`type` = classOf[Array[Int]]) def j = 0 + | @Resource(`type` = classOf[Array[List[_]]]) def k = 0 + |} + |val c = classOf[C] + |def typeArg(meth: String) = c.getDeclaredMethod(meth).getDeclaredAnnotation(classOf[Resource]).`type` + |('a' to 'k').toList.map(_.toString).map(typeArg) + """.stripMargin + + val l = Class.forName("scala.collection.immutable.List") + val i = Integer.TYPE + val ig = new Integer(1).getClass + val v = new RunTest.VC(1).getClass + val ai = Array(1).getClass + val al = Array(List()).getClass + + // sanity checks + assertEquals(i, classOf[Int]) + assertNotEquals(i, ig) + + assertEquals(run[List[Class[_]]](code), + List(l, l, l, i, i, ig, ig, v, v, ai, al)) + } + + @Test + def annotationInfoNotErased(): Unit = { + val code = + """import javax.annotation.Resource + |import scala.annotation.meta.getter + |class C { + | type Rg = Resource @getter + | @(Resource @getter)(`type` = classOf[Int]) def a = 0 + | @Rg(`type` = classOf[Int]) def b = 0 + |} + |val c = classOf[C] + |def typeArg(meth: String) = c.getDeclaredMethod(meth).getDeclaredAnnotation(classOf[Resource]).`type` + |List("a", "b") map typeArg + |""".stripMargin + + val i = Integer.TYPE + assertEquals(run[List[Class[_]]](code), List(i, i)) + } +} -- cgit v1.2.3