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 +- 4 files changed, 53 insertions(+), 53 deletions(-) (limited to 'src/compiler') 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) } -- cgit v1.2.3