diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala | 271 |
1 files changed, 162 insertions, 109 deletions
diff --git a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala index 872d1cc522..d2ee944916 100644 --- a/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala +++ b/src/compiler/scala/tools/nsc/backend/jvm/BTypes.scala @@ -7,12 +7,15 @@ package scala.tools.nsc package backend.jvm import scala.annotation.switch +import scala.collection.concurrent.TrieMap +import scala.reflect.internal.util.Position import scala.tools.asm import asm.Opcodes -import scala.tools.asm.tree.{InnerClassNode, ClassNode} +import scala.tools.asm.tree.{MethodInsnNode, InnerClassNode, ClassNode} import scala.tools.nsc.backend.jvm.BTypes.{InlineInfo, MethodInlineInfo} +import scala.tools.nsc.backend.jvm.BackendReporting._ +import BackendReporting.RightBiasedEither import scala.tools.nsc.backend.jvm.opt._ -import opt.OptimizerReporting._ import scala.collection.convert.decorateAsScala._ /** @@ -41,6 +44,8 @@ abstract class BTypes { val callGraph: CallGraph[this.type] + val backendReporting: BackendReporting + // Allows to define per-run caches here and in the CallGraph component, which don't have a global def recordPerRunCache[T <: collection.generic.Clearable](cache: T): T @@ -50,6 +55,9 @@ abstract class BTypes { // When the inliner is not enabled, there's no point in adding InlineInfos to all ClassBTypes def inlinerEnabled: Boolean + // Settings that define what kind of optimizer warnings are emitted. + def warnSettings: WarnSettings + /** * A map from internal names to ClassBTypes. Every ClassBType is added to this map on its * construction. @@ -61,7 +69,19 @@ abstract class BTypes { * Concurrent because stack map frames are computed when in the class writer, which might run * on multiple classes concurrently. */ - val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(collection.concurrent.TrieMap.empty[InternalName, ClassBType]) + val classBTypeFromInternalName: collection.concurrent.Map[InternalName, ClassBType] = recordPerRunCache(TrieMap.empty) + + /** + * Store the position of every MethodInsnNode during code generation. This allows each callsite + * in the call graph to remember its source position, which is required for inliner warnings. + */ + val callsitePositions: collection.concurrent.Map[MethodInsnNode, Position] = recordPerRunCache(TrieMap.empty) + + /** + * Contains the internal names of all classes that are defined in Java source files of the current + * compilation run (mixed compilation). Used for more detailed error reporting. + */ + val javaDefinedClasses: collection.mutable.Set[InternalName] = recordPerRunCache(collection.mutable.Set.empty) /** * Obtain the BType for a type descriptor or internal name. For class descriptors, the ClassBType @@ -73,29 +93,33 @@ abstract class BTypes { * * This method supports both descriptors and internal names. */ - def bTypeForDescriptorOrInternalNameFromClassfile(desc: String): Option[BType] = (desc(0): @switch) match { - case 'V' => Some(UNIT) - case 'Z' => Some(BOOL) - case 'C' => Some(CHAR) - case 'B' => Some(BYTE) - case 'S' => Some(SHORT) - case 'I' => Some(INT) - case 'F' => Some(FLOAT) - case 'J' => Some(LONG) - case 'D' => Some(DOUBLE) - case '[' => bTypeForDescriptorOrInternalNameFromClassfile(desc.substring(1)) map ArrayBType + def bTypeForDescriptorOrInternalNameFromClassfile(desc: String): BType = (desc(0): @switch) match { + case 'V' => UNIT + case 'Z' => BOOL + case 'C' => CHAR + case 'B' => BYTE + case 'S' => SHORT + case 'I' => INT + case 'F' => FLOAT + case 'J' => LONG + case 'D' => DOUBLE + case '[' => ArrayBType(bTypeForDescriptorOrInternalNameFromClassfile(desc.substring(1))) case 'L' if desc.last == ';' => classBTypeFromParsedClassfile(desc.substring(1, desc.length - 1)) case _ => classBTypeFromParsedClassfile(desc) } /** - * Parse the classfile for `internalName` and construct the [[ClassBType]]. Returns `None` if the - * classfile cannot be found in the `byteCodeRepository`. + * Parse the classfile for `internalName` and construct the [[ClassBType]]. If the classfile cannot + * be found in the `byteCodeRepository`, the `info` of the resulting ClassBType is undefined. */ - def classBTypeFromParsedClassfile(internalName: InternalName): Option[ClassBType] = { - classBTypeFromInternalName.get(internalName) orElse { - byteCodeRepository.classNode(internalName) map classBTypeFromClassNode - } + def classBTypeFromParsedClassfile(internalName: InternalName): ClassBType = { + classBTypeFromInternalName.getOrElse(internalName, { + val res = ClassBType(internalName) + byteCodeRepository.classNode(internalName) match { + case Left(msg) => res.info = Left(NoClassBTypeInfoMissingBytecode(msg)); res + case Right(c) => setClassInfoFromParsedClassfile(c, res) + } + }) } /** @@ -108,26 +132,15 @@ abstract class BTypes { } private def setClassInfoFromParsedClassfile(classNode: ClassNode, classBType: ClassBType): ClassBType = { - def ensureClassBTypeFromParsedClassfile(internalName: InternalName): ClassBType = { - classBTypeFromParsedClassfile(internalName) getOrElse { - // When building a ClassBType from a parsed classfile, we need the ClassBTypes for all - // referenced types. - // TODO: make this more robust with respect to incomplete classpaths. - // Maybe not those parts of the ClassBType that require the missing class are not actually - // queried during the backend, so every part of a ClassBType that requires parsing a - // (potentially missing) classfile should be computed lazily. - assertionError(s"Could not find bytecode for class $internalName") - } - } val superClass = classNode.superName match { case null => assert(classNode.name == ObjectReference.internalName, s"class with missing super type: ${classNode.name}") None case superName => - Some(ensureClassBTypeFromParsedClassfile(superName)) + Some(classBTypeFromParsedClassfile(superName)) } - val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(ensureClassBTypeFromParsedClassfile)(collection.breakOut) + val interfaces: List[ClassBType] = classNode.interfaces.asScala.map(classBTypeFromParsedClassfile)(collection.breakOut) val flags = classNode.access @@ -145,15 +158,13 @@ abstract class BTypes { def nestedInCurrentClass(innerClassNode: InnerClassNode): Boolean = { (innerClassNode.outerName != null && innerClassNode.outerName == classNode.name) || (innerClassNode.outerName == null && { - val classNodeForInnerClass = byteCodeRepository.classNode(innerClassNode.name) getOrElse { - assertionError(s"Could not find bytecode for class ${innerClassNode.name}") - } + val classNodeForInnerClass = byteCodeRepository.classNode(innerClassNode.name).get // TODO: don't get here, but set the info to Left at the end classNodeForInnerClass.outerClass == classNode.name }) } val nestedClasses: List[ClassBType] = classNode.innerClasses.asScala.collect({ - case i if nestedInCurrentClass(i) => ensureClassBTypeFromParsedClassfile(i.name) + case i if nestedInCurrentClass(i) => classBTypeFromParsedClassfile(i.name) })(collection.breakOut) // if classNode is a nested class, it has an innerClass attribute for itself. in this @@ -163,11 +174,11 @@ abstract class BTypes { val enclosingClass = if (innerEntry.outerName != null) { // if classNode is a member class, the outerName is non-null - ensureClassBTypeFromParsedClassfile(innerEntry.outerName) + classBTypeFromParsedClassfile(innerEntry.outerName) } else { // for anonymous or local classes, the outerName is null, but the enclosing class is // stored in the EnclosingMethod attribute (which ASM encodes in classNode.outerClass). - ensureClassBTypeFromParsedClassfile(classNode.outerClass) + classBTypeFromParsedClassfile(classNode.outerClass) } val staticFlag = (innerEntry.access & Opcodes.ACC_STATIC) != 0 NestedInfo(enclosingClass, Option(innerEntry.outerName), Option(innerEntry.innerName), staticFlag) @@ -175,7 +186,7 @@ abstract class BTypes { val inlineInfo = inlineInfoFromClassfile(classNode) - classBType.info = ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo) + classBType.info = Right(ClassInfo(superClass, interfaces, flags, nestedClasses, nestedInfo, inlineInfo)) classBType } @@ -186,12 +197,16 @@ abstract class BTypes { */ def inlineInfoFromClassfile(classNode: ClassNode): InlineInfo = { def fromClassfileAttribute: Option[InlineInfo] = { - // TODO: if this is a scala class and there's no attribute, emit an inliner warning if the InlineInfo is used if (classNode.attrs == null) None else classNode.attrs.asScala.collect({ case a: InlineInfoAttribute => a}).headOption.map(_.inlineInfo) } def fromClassfileWithoutAttribute = { + val warning = { + val isScala = classNode.attrs != null && classNode.attrs.asScala.exists(a => a.`type` == BTypes.ScalaAttributeName || a.`type` == BTypes.ScalaSigAttributeName) + if (isScala) Some(NoInlineInfoAttribute(classNode.name)) + else None + } // when building MethodInlineInfos for the members of a ClassSymbol, we exclude those methods // in scalaPrimitives. This is necessary because some of them have non-erased types, which would // require special handling. Excluding is OK because they are never inlined. @@ -209,7 +224,7 @@ abstract class BTypes { traitImplClassSelfType = None, isEffectivelyFinal = BytecodeUtils.isFinalClass(classNode), methodInfos = methodInfos, - warning = None) + warning) } // The InlineInfo is built from the classfile (not from the symbol) for all classes that are NOT @@ -283,7 +298,7 @@ abstract class BTypes { * promotions (e.g. BYTE to INT). Its operation can be visualized more easily in terms of the * Java bytecode type hierarchy. */ - final def conformsTo(other: BType): Boolean = { + final def conformsTo(other: BType): Either[NoClassBTypeInfo, Boolean] = tryEither(Right({ assert(isRef || isPrimitive, s"conformsTo cannot handle $this") assert(other.isRef || other.isPrimitive, s"conformsTo cannot handle $other") @@ -291,7 +306,7 @@ abstract class BTypes { case ArrayBType(component) => if (other == ObjectReference || other == jlCloneableReference || other == jioSerializableReference) true else other match { - case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent) + case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent).orThrow case _ => false } @@ -300,7 +315,7 @@ abstract class BTypes { if (other.isBoxed) this == other else if (other == ObjectReference) true else other match { - case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) // e.g., java/lang/Double conforms to java/lang/Number + case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow // e.g., java/lang/Double conforms to java/lang/Number case _ => false } } else if (isNullType) { @@ -310,7 +325,7 @@ abstract class BTypes { } else if (isNothingType) { true } else other match { - case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) + case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType).orThrow // case ArrayBType(_) => this.isNullType // documentation only, because `if (isNullType)` above covers this case case _ => // isNothingType || // documentation only, because `if (isNothingType)` above covers this case @@ -325,7 +340,7 @@ abstract class BTypes { assert(isPrimitive && other.isPrimitive, s"Expected primitive types $this - $other") this == other } - } + })) /** * Compute the upper bound of two types. @@ -765,9 +780,22 @@ abstract class BTypes { * A ClassBType represents a class or interface type. The necessary information to build a * ClassBType is extracted from compiler symbols and types, see BTypesFromSymbols. * - * Currently non-final due to SI-9111 + * The `info` field contains either the class information on an error message why the info could + * not be computed. There are two reasons for an erroneous info: + * 1. The ClassBType was built from a class symbol that stems from a java source file, and the + * symbol's type could not be completed successfully (SI-9111) + * 2. The ClassBType should be built from a classfile, but the class could not be found on the + * compilation classpath. + * + * Note that all ClassBTypes required in a non-optimzied run are built during code generation from + * the class symbols referenced by the ASTs, so they have a valid info. Therefore the backend + * often invokes `info.get` (which asserts the info to exist) when reading data from the ClassBType. + * + * The inliner on the other hand uses ClassBTypes that are built from classfiles, which may have + * a missing info. In order not to crash the compiler unnecessarily, the inliner does not force + * infos using `get`, but it reports inliner warnings for missing infos that prevent inlining. */ - /*final*/ case class ClassBType(internalName: InternalName) extends RefBType { + final case class ClassBType(internalName: InternalName) extends RefBType { /** * Write-once variable allows initializing a cyclic graph of infos. This is required for * nested classes. Example: for the definition `class A { class B }` we have @@ -775,14 +803,14 @@ abstract class BTypes { * B.info.nestedInfo.outerClass == A * A.info.nestedClasses contains B */ - private var _info: ClassInfo = null + private var _info: Either[NoClassBTypeInfo, ClassInfo] = null - def info: ClassInfo = { + def info: Either[NoClassBTypeInfo, ClassInfo] = { assert(_info != null, s"ClassBType.info not yet assigned: $this") _info } - def info_=(i: ClassInfo): Unit = { + def info_=(i: Either[NoClassBTypeInfo, ClassInfo]): Unit = { assert(_info == null, s"Cannot set ClassBType.info multiple times: $this") _info = i checkInfoConsistency() @@ -791,27 +819,29 @@ abstract class BTypes { classBTypeFromInternalName(internalName) = this private def checkInfoConsistency(): Unit = { + if (info.isLeft) return + // we assert some properties. however, some of the linked ClassBType (members, superClass, // interfaces) may not yet have an `_info` (initialization of cyclic structures). so we do a - // best-effort verification. - def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || p(c) + // best-effort verification. also we don't report an error if the info is a Left. + def ifInit(c: ClassBType)(p: ClassBType => Boolean): Boolean = c._info == null || c.info.isLeft || p(c) def isJLO(t: ClassBType) = t.internalName == ObjectReference.internalName assert(!ClassBType.isInternalPhantomType(internalName), s"Cannot create ClassBType for phantom type $this") assert( - if (info.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) } - else if (isInterface) isJLO(info.superClass.get) - else !isJLO(this) && ifInit(info.superClass.get)(!_.isInterface), - s"Invalid superClass in $this: ${info.superClass}" + if (info.get.superClass.isEmpty) { isJLO(this) || (isCompilingPrimitive && ClassBType.hasNoSuper(internalName)) } + else if (isInterface.get) isJLO(info.get.superClass.get) + else !isJLO(this) && ifInit(info.get.superClass.get)(!_.isInterface.get), + s"Invalid superClass in $this: ${info.get.superClass}" ) assert( - info.interfaces.forall(c => ifInit(c)(_.isInterface)), - s"Invalid interfaces in $this: ${info.interfaces}" + info.get.interfaces.forall(c => ifInit(c)(_.isInterface.get)), + s"Invalid interfaces in $this: ${info.get.interfaces}" ) - assert(info.nestedClasses.forall(c => ifInit(c)(_.isNestedClass)), info.nestedClasses) + assert(info.get.nestedClasses.forall(c => ifInit(c)(_.isNestedClass.get)), info.get.nestedClasses) } /** @@ -819,12 +849,12 @@ abstract class BTypes { */ def simpleName: String = internalName.split("/").last - def isInterface = (info.flags & asm.Opcodes.ACC_INTERFACE) != 0 + def isInterface: Either[NoClassBTypeInfo, Boolean] = info.map(i => (i.flags & asm.Opcodes.ACC_INTERFACE) != 0) - def superClassesTransitive: List[ClassBType] = info.superClass match { - case None => Nil - case Some(sc) => sc :: sc.superClassesTransitive - } + def superClassesTransitive: Either[NoClassBTypeInfo, List[ClassBType]] = info.flatMap(i => i.superClass match { + case None => Right(Nil) + case Some(sc) => sc.superClassesTransitive.map(sc :: _) + }) /** * The prefix of the internal name until the last '/', or the empty string. @@ -837,15 +867,19 @@ abstract class BTypes { } } - def isPublic = (info.flags & asm.Opcodes.ACC_PUBLIC) != 0 + def isPublic: Either[NoClassBTypeInfo, Boolean] = info.map(i => (i.flags & asm.Opcodes.ACC_PUBLIC) != 0) - def isNestedClass = info.nestedInfo.isDefined + def isNestedClass: Either[NoClassBTypeInfo, Boolean] = info.map(_.nestedInfo.isDefined) - def enclosingNestedClassesChain: List[ClassBType] = - if (isNestedClass) this :: info.nestedInfo.get.enclosingClass.enclosingNestedClassesChain - else Nil + def enclosingNestedClassesChain: Either[NoClassBTypeInfo, List[ClassBType]] = { + isNestedClass.flatMap(isNested => { + // if isNested is true, we know that info.get is defined, and nestedInfo.get is also defined. + if (isNested) info.get.nestedInfo.get.enclosingClass.enclosingNestedClassesChain.map(this :: _) + else Right(Nil) + }) + } - def innerClassAttributeEntry: Option[InnerClassEntry] = info.nestedInfo map { + def innerClassAttributeEntry: Either[NoClassBTypeInfo, Option[InnerClassEntry]] = info.map(i => i.nestedInfo map { case NestedInfo(_, outerName, innerName, isStaticNestedClass) => InnerClassEntry( internalName, @@ -853,30 +887,39 @@ abstract class BTypes { innerName.orNull, GenBCode.mkFlags( // the static flag in the InnerClass table has a special meaning, see InnerClass comment - info.flags & ~Opcodes.ACC_STATIC, + i.flags & ~Opcodes.ACC_STATIC, if (isStaticNestedClass) Opcodes.ACC_STATIC else 0 ) & ClassBType.INNER_CLASSES_FLAGS ) - } - - def inlineInfoAttribute: InlineInfoAttribute = InlineInfoAttribute(info.inlineInfo) + }) - def isSubtypeOf(other: ClassBType): Boolean = { - if (this == other) return true + def inlineInfoAttribute: Either[NoClassBTypeInfo, InlineInfoAttribute] = info.map(i => { + // InlineInfos are serialized for classes being compiled. For those the info was built by + // buildInlineInfoFromClassSymbol, which only adds a warning under SI-9111, which in turn + // only happens for class symbols of java source files. + // we could put this assertion into InlineInfoAttribute, but it is more safe to put it here + // where it affect only GenBCode, and not add any assertion to GenASM in 2.11.6. + assert(i.inlineInfo.warning.isEmpty, i.inlineInfo.warning) + InlineInfoAttribute(i.inlineInfo) + }) - if (isInterface) { - if (other == ObjectReference) return true // interfaces conform to Object - if (!other.isInterface) return false // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false. + def isSubtypeOf(other: ClassBType): Either[NoClassBTypeInfo, Boolean] = try { + if (this == other) return Right(true) + if (isInterface.orThrow) { + if (other == ObjectReference) return Right(true) // interfaces conform to Object + if (!other.isInterface.orThrow) return Right(false) // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false. // else: this and other are both interfaces. continue to (*) } else { - val sc = info.superClass - if (sc.isDefined && sc.get.isSubtypeOf(other)) return true // the superclass of this class conforms to other - if (!other.isInterface) return false // this and other are both classes, and the superclass of this does not conform + val sc = info.orThrow.superClass + if (sc.isDefined && sc.get.isSubtypeOf(other).orThrow) return Right(true) // the superclass of this class conforms to other + if (!other.isInterface.orThrow) return Right(false) // this and other are both classes, and the superclass of this does not conform // else: this is a class, the other is an interface. continue to (*) } // (*) check if some interface of this class conforms to other. - info.interfaces.exists(_.isSubtypeOf(other)) + Right(info.orThrow.interfaces.exists(_.isSubtypeOf(other).orThrow)) + } catch { + case Invalid(noInfo: NoClassBTypeInfo) => Left(noInfo) } /** @@ -886,34 +929,36 @@ abstract class BTypes { * http://comments.gmane.org/gmane.comp.java.vm.languages/2293 * https://issues.scala-lang.org/browse/SI-3872 */ - def jvmWiseLUB(other: ClassBType): ClassBType = { + def jvmWiseLUB(other: ClassBType): Either[NoClassBTypeInfo, ClassBType] = { def isNotNullOrNothing(c: ClassBType) = !c.isNullType && !c.isNothingType assert(isNotNullOrNothing(this) && isNotNullOrNothing(other), s"jvmWiseLub for null or nothing: $this - $other") - val res: ClassBType = (this.isInterface, other.isInterface) match { - case (true, true) => - // exercised by test/files/run/t4761.scala - if (other.isSubtypeOf(this)) this - else if (this.isSubtypeOf(other)) other - else ObjectReference - - case (true, false) => - if (other.isSubtypeOf(this)) this else ObjectReference - - case (false, true) => - if (this.isSubtypeOf(other)) other else ObjectReference + tryEither { + val res: ClassBType = (this.isInterface.orThrow, other.isInterface.orThrow) match { + case (true, true) => + // exercised by test/files/run/t4761.scala + if (other.isSubtypeOf(this).orThrow) this + else if (this.isSubtypeOf(other).orThrow) other + else ObjectReference + + case (true, false) => + if (other.isSubtypeOf(this).orThrow) this else ObjectReference + + case (false, true) => + if (this.isSubtypeOf(other).orThrow) other else ObjectReference + + case _ => + // TODO @lry I don't really understand the reasoning here. + // Both this and other are classes. The code takes (transitively) all superclasses and + // finds the first common one. + // MOST LIKELY the answer can be found here, see the comments and links by Miguel: + // - https://issues.scala-lang.org/browse/SI-3872 + firstCommonSuffix(this :: this.superClassesTransitive.orThrow, other :: other.superClassesTransitive.orThrow) + } - case _ => - // TODO @lry I don't really understand the reasoning here. - // Both this and other are classes. The code takes (transitively) all superclasses and - // finds the first common one. - // MOST LIKELY the answer can be found here, see the comments and links by Miguel: - // - https://issues.scala-lang.org/browse/SI-3872 - firstCommonSuffix(this :: this.superClassesTransitive, other :: other.superClassesTransitive) + assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res") + Right(res) } - - assert(isNotNullOrNothing(res), s"jvmWiseLub computed: $res") - res } private def firstCommonSuffix(as: List[ClassBType], bs: List[ClassBType]): ClassBType = { @@ -1080,11 +1125,15 @@ object BTypes { * @param methodInfos The [[MethodInlineInfo]]s for the methods declared in this class. * The map is indexed by the string s"$name$descriptor" (to * disambiguate overloads). + * + * @param warning Contains an warning message if an error occured when building this + * InlineInfo, for example if some classfile could not be found on + * the classpath. This warning can be reported later by the inliner. */ final case class InlineInfo(traitImplClassSelfType: Option[InternalName], isEffectivelyFinal: Boolean, methodInfos: Map[String, MethodInlineInfo], - warning: Option[String]) + warning: Option[ClassInlineInfoWarning]) val EmptyInlineInfo = InlineInfo(None, false, Map.empty, None) @@ -1102,4 +1151,8 @@ object BTypes { traitMethodWithStaticImplementation: Boolean, annotatedInline: Boolean, annotatedNoInline: Boolean) + + // no static way (without symbol table instance) to get to nme.ScalaATTR / ScalaSignatureATTR + val ScalaAttributeName = "Scala" + val ScalaSigAttributeName = "ScalaSig" }
\ No newline at end of file |