diff options
Diffstat (limited to 'src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala')
-rw-r--r-- | src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala | 282 |
1 files changed, 157 insertions, 125 deletions
diff --git a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala index 809effe18b..f146419a73 100644 --- a/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala +++ b/src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala @@ -8,16 +8,18 @@ package tools.nsc package symtab package classfile -import java.io.{ File, IOException } +import java.io.{ByteArrayInputStream, DataInputStream, File, IOException} import java.lang.Integer.toHexString -import scala.collection.{ mutable, immutable } -import scala.collection.mutable.{ ListBuffer, ArrayBuffer } + +import scala.collection.{immutable, mutable} +import scala.collection.mutable.{ArrayBuffer, ListBuffer} import scala.annotation.switch -import scala.reflect.internal.{ JavaAccFlags } -import scala.reflect.internal.pickling.{PickleBuffer, ByteCodecs} +import scala.reflect.internal.JavaAccFlags +import scala.reflect.internal.pickling.{ByteCodecs, PickleBuffer} import scala.reflect.io.NoAbstractFile +import scala.tools.nsc.util.ClassPath import scala.tools.nsc.io.AbstractFile -import scala.tools.nsc.util.ClassFileLookup +import scala.util.control.NonFatal /** This abstract class implements a class file parser. * @@ -43,8 +45,8 @@ abstract class ClassfileParser { */ protected def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol - /** The way of the class file lookup used by the compiler. */ - def classFileLookup: ClassFileLookup[AbstractFile] + /** The compiler classpath. */ + def classPath: ClassPath import definitions._ import scala.reflect.internal.ClassfileConstants._ @@ -53,18 +55,18 @@ abstract class ClassfileParser { protected type ThisConstantPool <: ConstantPool protected def newConstantPool: ThisConstantPool - protected var file: AbstractFile = _ // the class file - protected var in: AbstractFileReader = _ // the class file reader - protected var clazz: Symbol = _ // the class symbol containing dynamic members - protected var staticModule: Symbol = _ // the module symbol containing static members - protected var instanceScope: Scope = _ // the scope of all instance definitions - protected var staticScope: Scope = _ // the scope of all static definitions - protected var pool: ThisConstantPool = _ // the classfile's constant pool - protected var isScala: Boolean = _ // does class file describe a scala class? - protected var isScalaAnnot: Boolean = _ // does class file describe a scala class with its pickled info in an annotation? - protected var isScalaRaw: Boolean = _ // this class file is a scala class with no pickled info - protected var busy: Symbol = _ // lock to detect recursive reads - protected var currentClass: Name = _ // JVM name of the current class + protected var file: AbstractFile = _ // the class file + protected var in: AbstractFileReader = _ // the class file reader + protected var clazz: ClassSymbol = _ // the class symbol containing dynamic members + protected var staticModule: ModuleSymbol = _ // the module symbol containing static members + protected var instanceScope: Scope = _ // the scope of all instance definitions + protected var staticScope: Scope = _ // the scope of all static definitions + protected var pool: ThisConstantPool = _ // the classfile's constant pool + protected var isScala: Boolean = _ // does class file describe a scala class? + protected var isScalaAnnot: Boolean = _ // does class file describe a scala class with its pickled info in an annotation? + protected var isScalaRaw: Boolean = _ // this class file is a scala class with no pickled info + protected var busy: Symbol = _ // lock to detect recursive reads + protected var currentClass: Name = _ // JVM name of the current class protected var classTParams = Map[Name,Symbol]() protected var srcfile0 : Option[AbstractFile] = None protected def moduleClass: Symbol = staticModule.moduleClass @@ -132,17 +134,21 @@ abstract class ClassfileParser { finally loaders.parentsLevel -= 1 } - def parse(file: AbstractFile, root: Symbol): Unit = { - debuglog("[class] >> " + root.fullName) - + /** + * `clazz` and `module` are the class and module symbols corresponding to the classfile being + * parsed. Note that the ClassfileLoader unconditionally creates both of these symbols, they may + * may get invalidated later on (.exists). + * + * Note that using `companionModule` / `companionClass` does not always work to navigate between + * those two symbols, namely when they are shadowed by a type / value in the a package object + * (scala-dev#248). + */ + def parse(file: AbstractFile, clazz: ClassSymbol, module: ModuleSymbol): Unit = { this.file = file - pushBusy(root) { + pushBusy(clazz) { this.in = new AbstractFileReader(file) - this.clazz = if (root.isModule) root.companionClass else root - // WARNING! do no use clazz.companionModule to find staticModule. - // In a situation where root can be defined, but its companionClass not, - // this would give incorrect results (see SI-5031 in separate compilation scenario) - this.staticModule = if (root.isModule) root else root.companionModule + this.clazz = clazz + this.staticModule = module this.isScala = false parseHeader() @@ -206,10 +212,14 @@ abstract class ClassfileParser { case name: Name => name case _ => val start = firstExpecting(index, CONSTANT_UTF8) - recordAtIndex(newTermName(in.buf, start + 2, in.getChar(start).toInt), index) + val len = in.getChar(start).toInt + recordAtIndex(TermName(fromMUTF8(in.buf, start, len + 2)), index) } ) + private def fromMUTF8(bytes: Array[Byte], offset: Int, len: Int): String = + new DataInputStream(new ByteArrayInputStream(bytes, offset, len)).readUTF + /** Return the name found at given index in the constant pool, with '/' replaced by '.'. */ def getExternalName(index: Int): Name = { if (index <= 0 || len <= index) @@ -271,7 +281,7 @@ abstract class ClassfileParser { * arrays are considered to be class types, they might * appear as entries in 'newarray' or 'cast' opcodes. */ - def getClassOrArrayType(index: Int): Type = ( + def getClassOrArrayType(index: Int): Type = { if (index <= 0 || len <= index) errorBadIndex(index) else values(index) match { case tp: Type => tp @@ -283,7 +293,7 @@ abstract class ClassfileParser { case _ => recordAtIndex(classNameToSymbol(name), index).tpe_* } } - ) + } def getType(index: Int): Type = getType(null, index) def getType(sym: Symbol, index: Int): Type = sigToType(sym, getExternalName(index)) @@ -356,63 +366,43 @@ abstract class ClassfileParser { abort(s"bad constant pool tag ${in.buf(start)} at byte $start") } - private def loadClassSymbol(name: Name): Symbol = { - val file = classFileLookup findClassFile name.toString getOrElse { - // SI-5593 Scaladoc's current strategy is to visit all packages in search of user code that can be documented - // therefore, it will rummage through the classpath triggering errors whenever it encounters package objects - // that are not in their correct place (see bug for details) - - // TODO More consistency with use of stub symbols in `Unpickler` - // - better owner than `NoSymbol` - // - remove eager warning - val msg = s"Class $name not found - continuing with a stub." - if ((!settings.isScaladoc) && (settings.verbose || settings.developer)) warning(msg) - return NoSymbol.newStubSymbol(name.toTypeName, msg) - } - val completer = new loaders.ClassfileLoader(file) - var owner: Symbol = rootMirror.RootClass - var sym: Symbol = NoSymbol - var ss: Name = null - var start = 0 - var end = name indexOf '.' - - while (end > 0) { - ss = name.subName(start, end) - sym = owner.info.decls lookup ss - if (sym == NoSymbol) { - sym = owner.newPackage(ss.toTermName) setInfo completer - sym.moduleClass setInfo completer - owner.info.decls enter sym - } - owner = sym.moduleClass - start = end + 1 - end = name.indexOf('.', start) - } - ss = name.subName(0, start) - owner.info.decls lookup ss orElse { - sym = owner.newClass(ss.toTypeName) setInfoAndEnter completer - debuglog("loaded "+sym+" from file "+file) - sym - } + def stubClassSymbol(name: Name): Symbol = { + // SI-5593 Scaladoc's current strategy is to visit all packages in search of user code that can be documented + // therefore, it will rummage through the classpath triggering errors whenever it encounters package objects + // that are not in their correct place (see bug for details) + + // TODO More consistency with use of stub symbols in `Unpickler` + // - better owner than `NoSymbol` + // - remove eager warning + val msg = s"Class $name not found - continuing with a stub." + if ((!settings.isScaladoc) && (settings.verbose || settings.developer)) warning(msg) + NoSymbol.newStubSymbol(name.toTypeName, msg) } - /** FIXME - we shouldn't be doing ad hoc lookups in the empty package. - * The method called "getClassByName" should either return the class or not. - */ - private def lookupClass(name: Name) = ( + private def lookupClass(name: Name) = try { if (name containsChar '.') - rootMirror getClassByName name // see tickets #2464, #3756 + rootMirror getClassByName name else + // FIXME - we shouldn't be doing ad hoc lookups in the empty package, getClassByName should return the class definitions.getMember(rootMirror.EmptyPackageClass, name.toTypeName) - ) + } catch { + // The handler + // - prevents crashes with deficient InnerClassAttributes (SI-2464, 0ce0ad5) + // - was referenced in the bugfix commit for SI-3756 (4fb0d53), not sure why + // - covers the case when a type alias in a package object shadows a class symbol, + // getClassByName throws a MissingRequirementError (scala-dev#248) + case _: FatalError => + // getClassByName can throw a MissingRequirementError (which extends FatalError) + // definitions.getMember can throw a FatalError, for example in pos/t5165b + stubClassSymbol(name) + } /** Return the class symbol of the given name. */ def classNameToSymbol(name: Name): Symbol = { if (innerClasses contains name) innerClasses innerSymbol name else - try lookupClass(name) - catch { case _: FatalError => loadClassSymbol(name) } + lookupClass(name) } def parseClass() { @@ -441,13 +431,10 @@ abstract class ClassfileParser { } val isTopLevel = !(currentClass containsChar '$') // Java class name; *don't* try to to use Scala name decoding (SI-7532) - - val c = if (isTopLevel) pool.getClassSymbol(nameIdx) else clazz if (isTopLevel) { - if (c != clazz) { - if ((clazz eq NoSymbol) && (c ne NoSymbol)) clazz = c - else mismatchError(c) - } + val c = pool.getClassSymbol(nameIdx) + // scala-dev#248: when a type alias (in a package object) shadows a class symbol, getClassSymbol returns a stub + if (!c.isInstanceOf[StubSymbol] && c != clazz) mismatchError(c) } addEnclosingTParams(clazz) @@ -542,7 +529,7 @@ abstract class ClassfileParser { devWarning(s"no linked class for java enum $sym in ${sym.owner}. A referencing class file might be missing an InnerClasses entry.") case linked => if (!linked.isSealed) - // Marking the enum class SEALED | ABSTRACT enables exhaustiveness checking. + // Marking the enum class SEALED | ABSTRACT enables exhaustiveness checking. See also JavaParsers. // This is a bit of a hack and requires excluding the ABSTRACT flag in the backend, see method javaClassfileFlags. linked setFlag (SEALED | ABSTRACT) linked addChild sym @@ -566,6 +553,7 @@ abstract class ClassfileParser { val name = readName() val sym = ownerForFlags(jflags).newMethod(name.toTermName, NoPosition, sflags) var info = pool.getType(sym, u2) + var removedOuterParameter = false if (name == nme.CONSTRUCTOR) info match { case MethodType(params, restpe) => @@ -580,6 +568,7 @@ abstract class ClassfileParser { * ClassfileParser for 1 executes, and clazz.owner is the package. */ assert(params.head.tpe.typeSymbol == clazz.owner || clazz.owner.hasPackageFlag, params.head.tpe.typeSymbol + ": " + clazz.owner) + removedOuterParameter = true params.tail case _ => params @@ -599,7 +588,7 @@ abstract class ClassfileParser { // parsed from SignatureATTR sym setInfo info propagatePackageBoundary(jflags, sym) - parseAttributes(sym, info) + parseAttributes(sym, info, removedOuterParameter) if (jflags.isVarargs) sym modifyInfo arrayToRepeated @@ -782,7 +771,7 @@ abstract class ClassfileParser { GenPolyType(ownTypeParams, tpe) } // sigToType - def parseAttributes(sym: Symbol, symtype: Type) { + def parseAttributes(sym: Symbol, symtype: Type, removedOuterParameter: Boolean = false) { def convertTo(c: Constant, pt: Type): Constant = { if (pt.typeSymbol == BooleanClass && c.tag == IntTag) Constant(c.value != 0) @@ -815,6 +804,31 @@ abstract class ClassfileParser { val c1 = convertTo(c, symtype) if (c1 ne null) sym.setInfo(ConstantType(c1)) else devWarning(s"failure to convert $c to $symtype") + case tpnme.MethodParametersATTR => + def readParamNames(): Unit = { + import scala.tools.asm.Opcodes.ACC_SYNTHETIC + val paramCount = u1 + var i = 0 + if (removedOuterParameter && i < paramCount) { + in.skip(4) + i += 1 + } + var remainingParams = sym.paramss.head // Java only has exactly one parameter list + while (i < paramCount) { + val name = pool.getName(u2) + val access = u2 + if (remainingParams.nonEmpty) { + val param = remainingParams.head + remainingParams = remainingParams.tail + if ((access & ACC_SYNTHETIC) != ACC_SYNTHETIC) { // name not synthetic + param.name = name.encode + param.resetFlag(SYNTHETIC) + } + } + i += 1 + } + } + readParamNames() case tpnme.ScalaSignatureATTR => if (!isScalaAnnot) { devWarning(s"symbol ${sym.fullName} has pickled signature in attribute") @@ -830,16 +844,19 @@ abstract class ClassfileParser { // Java annotations on classes / methods / fields with RetentionPolicy.RUNTIME case tpnme.RuntimeAnnotationATTR => if (isScalaAnnot || !isScala) { - val scalaSigAnnot = parseAnnotations(attrLen) - if (isScalaAnnot) - scalaSigAnnot match { - case Some(san: AnnotationInfo) => - val bytes = - san.assocs.find({ _._1 == nme.bytes }).get._2.asInstanceOf[ScalaSigBytes].bytes - unpickler.unpickle(bytes, 0, clazz, staticModule, in.file.name) - case None => - throw new RuntimeException("Scala class file does not contain Scala annotation") - } + // For Scala classfiles we are only interested in the scala signature annotations. Other + // annotations should be skipped (the pickle contains the symbol's annotations). + // Skipping them also prevents some spurious warnings / errors related to SI-7014, + // SI-7551, pos/5165b + val scalaSigAnnot = parseAnnotations(onlyScalaSig = isScalaAnnot) + if (isScalaAnnot) scalaSigAnnot match { + case Some(san: AnnotationInfo) => + val bytes = + san.assocs.find({ _._1 == nme.bytes }).get._2.asInstanceOf[ScalaSigBytes].bytes + unpickler.unpickle(bytes, 0, clazz, staticModule, in.file.name) + case None => + throw new RuntimeException("Scala class file does not contain Scala annotation") + } debuglog("[class] << " + sym.fullName + sym.annotationsString) } else @@ -873,6 +890,24 @@ abstract class ClassfileParser { } } + def skipAnnotArg(): Unit = { + u1 match { + case STRING_TAG | BOOL_TAG | BYTE_TAG | CHAR_TAG | SHORT_TAG | + INT_TAG | LONG_TAG | FLOAT_TAG | DOUBLE_TAG | CLASS_TAG => + in.skip(2) + + case ENUM_TAG => + in.skip(4) + + case ARRAY_TAG => + val num = u2 + for (i <- 0 until num) skipAnnotArg() + + case ANNOTATION_TAG => + parseAnnotation(u2, onlyScalaSig = true) + } + } + def parseAnnotArg: Option[ClassfileAnnotArg] = { val tag = u1 val index = u2 @@ -906,7 +941,7 @@ abstract class ClassfileParser { if (hasError) None else Some(ArrayAnnotArg(arr.toArray)) case ANNOTATION_TAG => - parseAnnotation(index) map (NestedAnnotArg(_)) + parseAnnotation(index, onlyScalaSig = false) map (NestedAnnotArg(_)) } } @@ -933,7 +968,7 @@ abstract class ClassfileParser { /* Parse and return a single annotation. If it is malformed, * return None. */ - def parseAnnotation(attrNameIndex: Int): Option[AnnotationInfo] = try { + def parseAnnotation(attrNameIndex: Int, onlyScalaSig: Boolean): Option[AnnotationInfo] = try { val attrType = pool.getType(attrNameIndex) val nargs = u2 val nvpairs = new ListBuffer[(Name, ClassfileAnnotArg)] @@ -954,18 +989,17 @@ abstract class ClassfileParser { case None => hasError = true } else - parseAnnotArg match { + if (onlyScalaSig) skipAnnotArg() + else parseAnnotArg match { case Some(c) => nvpairs += ((name, c)) case None => hasError = true } } if (hasError) None else Some(AnnotationInfo(attrType, List(), nvpairs.toList)) - } - catch { - case f: FatalError => throw f // don't eat fatal errors, they mean a class was not found - case ex: java.lang.Error => throw ex - case ex: Throwable => + } catch { + case f: FatalError => throw f // don't eat fatal errors, they mean a class was not found + case NonFatal(ex) => // We want to be robust when annotations are unavailable, so the very least // we can do is warn the user about the exception // There was a reference to ticket 1135, but that is outdated: a reference to a class not on @@ -974,7 +1008,6 @@ abstract class ClassfileParser { // and that should never be swallowed silently. warning(s"Caught: $ex while parsing annotations in ${in.file}") if (settings.debug) ex.printStackTrace() - None // ignore malformed annotations } @@ -996,19 +1029,18 @@ abstract class ClassfileParser { /* Parse a sequence of annotations and attaches them to the * current symbol sym, except for the ScalaSignature annotation that it returns, if it is available. */ - def parseAnnotations(len: Int): Option[AnnotationInfo] = { + def parseAnnotations(onlyScalaSig: Boolean): Option[AnnotationInfo] = { val nAttr = u2 var scalaSigAnnot: Option[AnnotationInfo] = None - for (n <- 0 until nAttr) - parseAnnotation(u2) match { - case Some(scalaSig) if (scalaSig.atp == ScalaSignatureAnnotation.tpe) => - scalaSigAnnot = Some(scalaSig) - case Some(scalaSig) if (scalaSig.atp == ScalaLongSignatureAnnotation.tpe) => - scalaSigAnnot = Some(scalaSig) - case Some(annot) => - sym.addAnnotation(annot) - case None => - } + for (n <- 0 until nAttr) parseAnnotation(u2, onlyScalaSig) match { + case Some(scalaSig) if scalaSig.atp == ScalaSignatureAnnotation.tpe => + scalaSigAnnot = Some(scalaSig) + case Some(scalaSig) if scalaSig.atp == ScalaLongSignatureAnnotation.tpe => + scalaSigAnnot = Some(scalaSig) + case Some(annot) => + sym.addAnnotation(annot) + case None => + } scalaSigAnnot } @@ -1025,7 +1057,6 @@ abstract class ClassfileParser { def enterClassAndModule(entry: InnerClassEntry, file: AbstractFile) { def jflags = entry.jflags - val completer = new loaders.ClassfileLoader(file) val name = entry.originalName val sflags = jflags.toScalaFlags val owner = ownerForFlags(jflags) @@ -1039,8 +1070,11 @@ abstract class ClassfileParser { val (innerClass, innerModule) = if (file == NoAbstractFile) { (newStub(name.toTypeName), newStub(name.toTermName)) } else { - val cls = owner.newClass(name.toTypeName, NoPosition, sflags) setInfo completer - val mod = owner.newModule(name.toTermName, NoPosition, sflags) setInfo completer + val cls = owner.newClass(name.toTypeName, NoPosition, sflags) + val mod = owner.newModule(name.toTermName, NoPosition, sflags) + val completer = new loaders.ClassfileLoader(file, cls, mod) + cls setInfo completer + mod setInfo completer mod.moduleClass setInfo loaders.moduleClassLoader List(cls, mod.moduleClass) foreach (_.associatedFile = file) (cls, mod) @@ -1064,7 +1098,7 @@ abstract class ClassfileParser { for (entry <- innerClasses.entries) { // create a new class member for immediate inner classes if (entry.outerName == currentClass) { - val file = classFileLookup.findClassFile(entry.externalName.toString) + val file = classPath.findClassFile(entry.externalName.toString) enterClassAndModule(entry, file.getOrElse(NoAbstractFile)) } } @@ -1083,8 +1117,6 @@ abstract class ClassfileParser { val attrName = readTypeName() val attrLen = u4 attrName match { - case tpnme.SignatureATTR => - in.skip(attrLen) case tpnme.ScalaSignatureATTR => isScala = true val pbuf = new PickleBuffer(in.buf, in.bp, in.bp + attrLen) @@ -1151,10 +1183,10 @@ abstract class ClassfileParser { private def innerSymbol(entry: InnerClassEntry): Symbol = { val name = entry.originalName.toTypeName val enclosing = entry.enclosing - val member = ( + val member = { if (enclosing == clazz) entry.scope lookup name else lookupMemberAtTyperPhaseIfPossible(enclosing, name) - ) + } def newStub = { enclosing .newStubSymbol(name, s"Unable to locate class corresponding to inner class entry for $name in owner ${entry.outerName}") |