All Downloads are FREE. Search and download functionalities are using the official Maven repository.

scala.tools.nsc.symtab.classfile.ClassfileParser.scala Maven / Gradle / Ivy

The newest version!
/*
 * Scala (https://www.scala-lang.org)
 *
 * Copyright EPFL and Lightbend, Inc.
 *
 * Licensed under Apache License 2.0
 * (http://www.apache.org/licenses/LICENSE-2.0).
 *
 * See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 */

package scala
package tools.nsc
package symtab
package classfile

import java.io.{ByteArrayOutputStream, IOException}
import java.lang.Integer.toHexString
import java.net.URLClassLoader
import java.util.UUID

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.ByteCodecs
import scala.reflect.internal.util.ReusableInstance
import scala.tools.nsc.Reporting.WarningCategory
import scala.reflect.io.{NoAbstractFile, PlainFile, ZipArchive}
import scala.tools.nsc.util.ClassPath
import scala.tools.nsc.io.AbstractFile
import scala.tools.tasty.{TastyHeaderUnpickler, TastyReader}
import scala.tools.nsc.tasty.{TastyUniverse, TastyUnpickler}
import scala.util.control.NonFatal

/** This abstract class implements a class file parser.
 *
 *  @author Martin Odersky
 */
abstract class ClassfileParser(reader: ReusableInstance[ReusableDataReader]) {
  val symbolTable: SymbolTable {
    def settings: Settings
  }
  val loaders: SymbolLoaders {
    val symbolTable: ClassfileParser.this.symbolTable.type
  }

  import symbolTable._
  /**
   * If typer phase is defined then perform member lookup of a symbol
   * `sym` at typer phase. This method results from refactoring. The
   * original author of the logic that uses typer phase didn't explain
   * why we need to force infos at that phase specifically. It only mentioned
   * that ClassfileParse can be called late (e.g. at flatten phase) and
   * we make to make sure we handle such situation properly.
   */
  protected def lookupMemberAtTyperPhaseIfPossible(sym: Symbol, name: Name): Symbol

  /** The compiler classpath. */
  def classPath: ClassPath

  import definitions._
  import scala.reflect.internal.ClassfileConstants._
  import Flags._

  protected var file: AbstractFile = _         // the class file
  protected var in: DataReader = _             // 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: ConstantPool = _         // the classfile's constant pool
  protected var isScala: Boolean = _           // does class file describe a scala class?
  protected var isTASTY: Boolean = _           // is this class accompanied by a TASTY file?
  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: String = _       // JVM name of the current class
  protected var classTParams = Map[Name,Symbol]()
  protected var srcfile0 : Option[AbstractFile] = None
  protected def moduleClass: Symbol = staticModule.moduleClass
  protected val TASTYUUIDLength: Int = 16
  private var sawPrivateConstructor = false

  private def ownerForFlags(jflags: JavaAccFlags) = if (jflags.isStatic) moduleClass else clazz

  def srcfile = srcfile0

  // u1, u2, and u4 are what these data types are called in the JVM spec.
  // They are an unsigned byte, unsigned char, and unsigned int respectively.
  // We bitmask u1 into an Int to make sure it's 0-255 (and u1 isn't used
  // for much beyond tags) but leave u2 alone as it's already unsigned.
  protected final def u1(): Int = in.nextByte & 0xFF
  protected final def u2(): Int = in.nextChar.toInt
  protected final def u4(): Int = in.nextInt

  protected final def s1(): Int = in.nextByte.toInt // sign-extend the byte to int
  protected final def s2(): Int = (in.nextByte.toInt << 8) | u1 // sign-extend and shift the first byte, or with the unsigned second byte

  private def readInnerClassFlags() = readClassFlags()
  private def readClassFlags()      = JavaAccFlags classFlags u2
  private def readMethodFlags()     = JavaAccFlags methodFlags u2
  private def readFieldFlags()      = JavaAccFlags fieldFlags u2
  private def readTypeName()        = readName().toTypeName
  private def readName()            = pool.getName(u2).name
  @annotation.unused
  private def readType()            = pool getType u2

  private object unpickler extends scala.reflect.internal.pickling.UnPickler {
    val symbolTable: ClassfileParser.this.symbolTable.type = ClassfileParser.this.symbolTable
  }

  object TastyUniverse extends TastyUniverse {
    type SymbolTable = ClassfileParser.this.symbolTable.type
    val symbolTable: SymbolTable = ClassfileParser.this.symbolTable
  }

  private def handleMissing(e: MissingRequirementError) = {
    if (settings.debug) e.printStackTrace
    throw new IOException(s"Missing dependency '${e.req}', required by $file")
  }
  private def handleError(e: Exception) = {
    if (settings.debug) e.printStackTrace()
    throw new IOException(s"class file '$file' is broken\n(${e.getClass}/${e.getMessage})")
  }
  private def mismatchError(c: Symbol) = {
    throw new IOException(s"class file '$file' has location not matching its contents: contains $c")
  }

  private def parseErrorHandler[T]: PartialFunction[Throwable, T] = {
    case e: MissingRequirementError => handleMissing(e)
    case e: RuntimeException        => handleError(e)
  }
  @inline private def pushBusy[T](sym: Symbol)(body: => T): T = {
    if (busy eq sym)
      throw new IOException(s"unsatisfiable cyclic dependency in '$sym'")
    else if ((busy ne null) && (busy ne NoSymbol))
      throw new IOException(s"illegal class file dependency between '$sym' and '$busy'")

    busy = sym
    try body
    catch parseErrorHandler
    finally busy = NoSymbol
  }

  /**
   * `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(clazz) {
      reader.using { reader =>
        this.in           = reader.reset(file)
        this.clazz        = clazz
        this.staticModule = module
        this.isScala      = false

        val magic = in.getInt(in.bp)
        if (magic != JAVA_MAGIC && file.name.endsWith(".sig")) {
          currentClass = clazz.javaClassName
          isScala = true
          unpickler.unpickle(in.buf.take(file.sizeOption.get), 0, clazz, staticModule, file.name)
        } else {
          parseHeader()
          this.pool = new ConstantPool
          parseClass()
          pool = null
        }
      }
      in = null
    }
  }

  private def parseHeader(): Unit = {
    val magic = u4
    if (magic != JAVA_MAGIC)
      abort(s"class file ${file} has wrong magic number 0x${toHexString(magic)}")

    val minor, major = u2
    if (major < JAVA_MAJOR_VERSION || major == JAVA_MAJOR_VERSION && minor < JAVA_MINOR_VERSION)
      abort(s"class file ${file} has unknown version $major.$minor, should be at least $JAVA_MAJOR_VERSION.$JAVA_MINOR_VERSION")
  }

  protected class NameOrString(val value: String) {
    private var _name: Name = null
    def name: Name = {
      if (_name eq null) _name = TermName(value)
      _name
    }
  }

  def getClassSymbol(name: String): Symbol =
    name match {
      case name if name.endsWith(nme.MODULE_SUFFIX_STRING) => rootMirror.getModuleByName(name.stripSuffix(nme.MODULE_SUFFIX_STRING))
      case name                                            => classNameToSymbol(name)
    }

  /**
   * Constructor of this class should not be called directly, use `newConstantPool` instead.
   */
  protected class ConstantPool {
    protected val len          = u2
    protected val starts       = new Array[Int](len)
    protected val values       = new Array[AnyRef](len)
    protected val internalized = new Array[NameOrString](len)

    val initBp = in.bp

    { var i = 1
      while (i < starts.length) {
        starts(i) = in.bp
        i += 1
        (u1: @switch) match {
          case CONSTANT_UTF8 | CONSTANT_UNICODE                                => in skip u2
          case CONSTANT_CLASS | CONSTANT_STRING | CONSTANT_METHODTYPE          => in skip 2
          case CONSTANT_MODULE | CONSTANT_PACKAGE                              => in skip 2
          case CONSTANT_METHODHANDLE                                           => in skip 3
          case CONSTANT_FIELDREF | CONSTANT_METHODREF | CONSTANT_INTFMETHODREF => in skip 4
          case CONSTANT_NAMEANDTYPE | CONSTANT_INTEGER | CONSTANT_FLOAT        => in skip 4
          case CONSTANT_INVOKEDYNAMIC                                          => in skip 4
          case CONSTANT_LONG | CONSTANT_DOUBLE                                 => in skip 8 ; i += 1
          case _                                                               => errorBadTag(in.bp - 1)
        }
      }
    }
    val endBp = in.bp
    def recordAtIndex[T <: AnyRef](value: T, idx: Int): T = {
      values(idx) = value
      value
    }

    def firstExpecting(index: Int, expected: Int): Int = {
      val start = starts(index)
      val first = in.getByte(start).toInt
      if (first == expected) start + 1
      else this errorBadTag start
    }

    /** Return the name found at given index. */
    def getName(index: Int): NameOrString = (
      if (index <= 0 || len <= index) errorBadIndex(index)
      else values(index) match {
        case name: NameOrString => name
        case _          =>
          val start = firstExpecting(index, CONSTANT_UTF8)
          val len   = in.getChar(start).toInt
          recordAtIndex(new NameOrString(in.getUTF(start, len + 2)), index)
      }
    )

    /** Return the name found at given index in the constant pool, with '/' replaced by '.'. */
    def getExternalName(index: Int): NameOrString = {
      if (index <= 0 || len <= index)
        errorBadIndex(index)

      if (internalized(index) == null)
        internalized(index) = new NameOrString(getName(index).value.replace('/', '.'))

      internalized(index)
    }

    def getClassSymbol(index: Int): Symbol = {
      if (index <= 0 || len <= index) errorBadIndex(index)
      values(index) match {
        case sym: Symbol => sym
        case _           =>
          val result = ClassfileParser.this.getClassSymbol(getClassName(index).value)
          recordAtIndex(result, index)
      }
    }

    /** Return the external name of the class info structure found at 'index'.
     *  Use 'getClassSymbol' if the class is sure to be a top-level class.
     */
    def getClassName(index: Int): NameOrString = {
      val start = firstExpecting(index, CONSTANT_CLASS)
      getExternalName((in.getChar(start)).toInt)
    }

    /** Return a name and a type at the given index. If the type is a method
     *  type, a dummy symbol is created in `ownerTpe`, which is used as the
     *  owner of its value parameters. This might lead to inconsistencies,
     *  if a symbol of the given name already exists, and has a different
     *  type.
     */
    protected def getNameAndType(index: Int, ownerTpe: Type): (Name, Type) = {
      if (index <= 0 || len <= index) errorBadIndex(index)
      values(index) match {
        case p: ((Name @unchecked, Type @unchecked)) => p
        case _                                       =>
          val start = firstExpecting(index, CONSTANT_NAMEANDTYPE)
          val name = getName(in.getChar(start).toInt)
          // create a dummy symbol for method types
          val dummy = ownerTpe.typeSymbol.newMethod(name.name.toTermName, ownerTpe.typeSymbol.pos)
          val tpe   = getType(dummy, in.getChar(start + 2).toInt)
          // fix the return type, which is blindly set to the class currently parsed
          val restpe = tpe match {
            case MethodType(formals, _) if name.name == nme.CONSTRUCTOR => MethodType(formals, ownerTpe)
            case _                                                      => tpe
          }
          ((name.name, restpe))
      }
    }

    /** Return the type of a class constant entry. Since
     *  arrays are considered to be class types, they might
     *  appear as entries in 'newarray' or 'cast' opcodes.
     */
    def getClassOrArrayType(index: Int): Type = {
      if (index <= 0 || len <= index) errorBadIndex(index)
      else values(index) match {
        case tp: Type    => tp
        case cls: Symbol => cls.tpe_*
        case _           =>
          val name = getClassName(index)
          name.value.charAt(0) match {
            case ARRAY_TAG => recordAtIndex(sigToType(null, name.value), index)
            case _         => recordAtIndex(classNameToSymbol(name.value), index).tpe_*
          }
      }
    }

    def getType(index: Int): Type              = getType(null, index)
    def getType(sym: Symbol, index: Int): Type = sigToType(sym, getExternalName(index).value)
    def getSuperClassName(index: Int): NameOrString = if (index == 0) null else getClassName(index) // the only classfile that is allowed to have `0` in the super_class is java/lang/Object (see jvm spec)

    private def createConstant(index: Int): Constant = {
      val start = starts(index)
      Constant((in.getByte(start).toInt: @switch) match {
        case CONSTANT_STRING  => getName(in.getChar(start + 1).toInt).value
        case CONSTANT_INTEGER => in.getInt(start + 1)
        case CONSTANT_FLOAT   => in.getFloat(start + 1)
        case CONSTANT_LONG    => in.getLong(start + 1)
        case CONSTANT_DOUBLE  => in.getDouble(start + 1)
        case CONSTANT_CLASS   => getClassOrArrayType(index).typeSymbol.tpe_* // !!! Is this necessary or desirable?
        case _                => errorBadTag(start)
      })
    }
    def getConstant(index: Char): Constant = getConstant(index.toInt)
    def getConstant(index: Int): Constant = (
      if (index <= 0 || len <= index) errorBadIndex(index)
      else values(index) match {
        case  const: Constant => const
        case sym: Symbol      => Constant(sym.tpe_*)
        case tpe: Type        => Constant(tpe)
        case _                => recordAtIndex(createConstant(index), index)
      }
    )

    private def getSubArray(bytes: Array[Byte]): Array[Byte] = {
      val decodedLength = ByteCodecs.decode(bytes)
      val arr           = new Array[Byte](decodedLength)
      System.arraycopy(bytes, 0, arr, 0, decodedLength)
      arr
    }

    /**
     * Get an array of bytes stored in the classfile as a string. The data is encoded in the format
     * described in object [[scala.reflect.internal.pickling.ByteCodecs]]. Used for the ScalaSignature annotation argument.
     */
    def getBytes(index: Int): Array[Byte] = {
      if (index <= 0 || len <= index) errorBadIndex(index)
      else values(index) match {
        case xs: Array[Byte] => xs
        case _ =>
          val start = firstExpecting(index, CONSTANT_UTF8)
          val len = (in getChar start).toInt
          val bytes = new Array[Byte](len)
          in.getBytes(start + 2, bytes)
          recordAtIndex(getSubArray(bytes), index)
      }
    }

    /**
     * Get an array of bytes stored in the classfile as an array of strings. The data is encoded in
     * the format described in object [[scala.reflect.internal.pickling.ByteCodecs]]. Used for the ScalaLongSignature annotation
     * argument.
     */
    def getBytes(indices: List[Int]): Array[Byte] = {
      val head = indices.head
      values(head) match {
        case xs: Array[Byte] => xs
        case _               =>
          val arr: Array[Byte] = indices.toArray flatMap { index =>
            if (index <= 0 || ConstantPool.this.len <= index) errorBadIndex(index)
            val start = firstExpecting(index, CONSTANT_UTF8)
            val len   = (in getChar start).toInt
            val s     = start + 2
            val result = new Array[Byte](len)
            in.getBytes(s, result)
            result
          }
          recordAtIndex(getSubArray(arr), head)
      }
    }

    /** Throws an exception signaling a bad constant index. */
    protected def errorBadIndex(index: Int) =
      abort(s"bad constant pool index: $index at pos: ${in.bp}")

    /** Throws an exception signaling a bad tag at given address. */
    protected def errorBadTag(start: Int) =
      abort(s"bad constant pool tag ${in.getByte(start)} at byte $start")
  }

  def stubClassSymbol(name: Name): Symbol = {
    // scala/bug#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)) loaders.warning(NoPosition, msg, WarningCategory.OtherDebug, clazz.fullNameString)
    NoSymbol.newStubSymbol(name.toTypeName, msg)
  }

  private def lookupClass(name: String) = try {
    def lookupTopLevel = {
      if (name contains '.')
        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, newTypeName(name))
    }

    // For inner classes we usually don't get here: `classNameToSymbol` already returns the symbol
    // of the inner class based on the InnerClass table. However, if the classfile is missing the
    // InnerClass entry for `name`, it might still be that there exists an inner symbol (because
    // some other classfile _does_ have an InnerClass entry for `name`). In this case, we want to
    // return the actual inner symbol (C.D, with owner C), not the top-level symbol C$D. This is
    // what the logic below is for (see PR #5822 / scala/bug#9937).
    val split = if (isScalaRaw) -1 else name.lastIndexOf('$')
    if (split > 0 && split < name.length) {
      val outerName = name.substring(0, split)
      val innerName = name.substring(split + 1, name.length)
      val outerSym = classNameToSymbol(outerName)

      // If the outer class C cannot be found, look for a top-level class C$D
      if (outerSym.isInstanceOf[StubSymbol]) lookupTopLevel
      else {
        val innerNameAsName = newTypeName(innerName)

        // We have a java-defined class name C$D and look for a member D of C. But we don't know if
        // D is declared static or not, so we have to search both in class C and its companion.
        val r = if (outerSym == clazz)
          staticScope.lookup(innerNameAsName) orElse
            instanceScope.lookup(innerNameAsName)
        else
          lookupMemberAtTyperPhaseIfPossible(outerSym, innerNameAsName) orElse
            lookupMemberAtTyperPhaseIfPossible(outerSym.companionModule, innerNameAsName)
        r orElse lookupTopLevel
      }
    } else
      lookupTopLevel
  } catch {
    // The handler
    //   - prevents crashes with deficient InnerClassAttributes (scala/bug#2464, 0ce0ad5)
    //   - was referenced in the bugfix commit for scala/bug#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 ex: FatalError =>
      // getClassByName can throw a MissingRequirementError (which extends FatalError)
      // definitions.getMember can throw a FatalError, for example in pos/t5165b
      if (settings.debug)
        ex.printStackTrace()
      stubClassSymbol(newTypeName(name))
  }

  /** Return the class symbol of the given name. */
  def classNameToSymbol(name: String): Symbol = {
    if (innerClasses contains name)
      innerClasses innerSymbol name
    else
      lookupClass(name)
  }

  def parseClass(): Unit = {
    unpickleOrParseInnerClasses()

    val jflags = readClassFlags()
    val classNameIndex = u2
    currentClass = pool.getClassName(classNameIndex).value

    // Ensure that (top-level) classfiles are in the correct directory
    val isTopLevel = !(currentClass contains '$') // Java class name; *don't* try to to use Scala name decoding (scala/bug#7532)
    if (isTopLevel) {
      val c = pool.getClassSymbol(classNameIndex)
      // scala-dev#248: when a type alias (in a package object) shadows a class symbol, getClassSymbol returns a stub
      // TODO: this also prevents the error when it would be useful (`mv a/C.class .`)
      if (!c.isInstanceOf[StubSymbol] && c != clazz) mismatchError(c)
    }

    if (isScala || isTASTY) {
      () // We're done
    } else if (isScalaRaw) {
      val decls = clazz.enclosingPackage.info.decls
      for (c <- List(clazz, staticModule, staticModule.moduleClass)) {
        c.setInfo(NoType)
        decls.unlink(c)
      }
    } else {
      val sflags = jflags.toScalaFlags // includes JAVA

      addEnclosingTParams(clazz)

      // Create scopes before calling `enterOwnInnerClasses`
      instanceScope = newScope
      staticScope = newScope
      val staticInfo = ClassInfoType(List(), staticScope, moduleClass)

      val parentIndex = u2
      val parentName = if (parentIndex == 0) null else pool.getClassName(parentIndex)
      val ifaceCount = u2
      val ifaces = for (i <- List.range(0, ifaceCount)) yield pool.getSuperClassName(u2)
      val completer = new ClassTypeCompleter(clazz.name, jflags, parentName, ifaces)

      enterOwnInnerClasses()

      clazz setInfo completer
      clazz setFlag sflags
      moduleClass setInfo staticInfo
      moduleClass setFlag JAVA
      staticModule setInfo moduleClass.tpe
      staticModule setFlag JAVA

      propagatePackageBoundary(jflags, clazz, staticModule, moduleClass)

      val fieldsStartBp = in.bp
      skipMembers() // fields
      skipMembers() // methods

      parseAttributes(clazz, completer)

      in.bp = fieldsStartBp
      0 until u2 foreach (_ => parseField())
      sawPrivateConstructor = false
      0 until u2 foreach (_ => parseMethod())
      val needsConstructor = (
           !sawPrivateConstructor
        && !(instanceScope containsName nme.CONSTRUCTOR)
        && ((sflags & INTERFACE) == 0 || (sflags | JAVA_ANNOTATION) != 0)
      )
      if (needsConstructor)
        instanceScope enter clazz.newClassConstructor(NoPosition)

      // we could avoid this if we eagerly created class type param symbols here to expose through the
      // ClassTypeCompleter to satisfy the calls to rawInfo.typeParams from Symbol.typeParams. That would
      // require a refactor of `sigToType`.
      //
      // We would also need to make sure that clazzTParams is populated before member type completers called sig2type.
      clazz.initialize
    }
  }

  /** Add type parameters of enclosing classes */
  def addEnclosingTParams(clazz: Symbol): Unit = {
    var sym = clazz.owner
    while (sym.isClass && !sym.isModuleClass) {
      for (t <- sym.tpe.typeArgs)
        classTParams = classTParams + (t.typeSymbol.name -> t.typeSymbol)

      sym = sym.owner
    }
  }

  def parseField(): Unit = {
    val jflags = readFieldFlags()
    val sflags = jflags.toScalaFlags

    if ((sflags & PRIVATE) != 0L) {
      in.skip(4); skipAttributes()
    } else {
      val name    = readName()
      val lazyInfo = new MemberTypeCompleter(name, jflags, pool.getExternalName(u2).value)
      val sym     = ownerForFlags(jflags).newValue(name.toTermName, NoPosition, sflags)

      // Note: the info may be overwritten later with a generic signature
      // parsed from SignatureATTR
      sym setInfo {
        if (jflags.isEnum) ConstantType(Constant(sym))
        else lazyInfo
      }
      propagatePackageBoundary(jflags, sym)
      parseAttributes(sym, lazyInfo)
      addJavaFlagsAnnotations(sym, jflags)
      getScope(jflags) enter sym

      // sealed java enums
      if (jflags.isEnum) {
        val enumClass = sym.owner.linkedClassOfClass
        enumClass match {
          case NoSymbol =>
            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. 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
        }
      }
    }
  }

  def parseMethod(): Unit = {
    val jflags = readMethodFlags()
    val sflags = jflags.toScalaFlags
    if (jflags.isPrivate) {
      val isConstructor = pool.getName(u2).value == "" // opt avoid interning a Name for private methods we're about to discard
      if (isConstructor)
        sawPrivateConstructor = true
      in.skip(2); skipAttributes()
    } else {
      if ((sflags & PRIVATE) != 0L) {
        in.skip(4); skipAttributes()
      } else {
        val name = readName()
        val sym = ownerForFlags(jflags).newMethod(name.toTermName, NoPosition, sflags)
        // Note: the info may be overwritten later with a generic signature
        // parsed from SignatureATTR
        val lazyInfo = new MemberTypeCompleter(name, jflags, pool.getExternalName(u2).value)
        sym.info = lazyInfo
        propagatePackageBoundary(jflags, sym)
        parseAttributes(sym, lazyInfo)
        addJavaFlagsAnnotations(sym, jflags)
        getScope(jflags) enter sym
      }
    }
  }

  private def sigToType(sym: Symbol, sig: String): Type = {
    val sigChars = sig.toCharArray
    var index = 0
    val end = sig.length
    def accept(ch: Char): Unit = {
      assert(sig.charAt(index) == ch, (sig.charAt(index), ch))
      index += 1
    }
    def subName(isDelimiter: Char => Boolean): String = {
      val start = index
      while (!isDelimiter(sig.charAt(index))) { index += 1 }
      new String(sigChars, start, index - start)
    }
    def sig2type(tparams: immutable.Map[Name,Symbol], skiptvs: Boolean): Type = {
      val tag = sig.charAt(index); index += 1
      tag match {
        case BYTE_TAG   => ByteTpe
        case CHAR_TAG   => CharTpe
        case DOUBLE_TAG => DoubleTpe
        case FLOAT_TAG  => FloatTpe
        case INT_TAG    => IntTpe
        case LONG_TAG   => LongTpe
        case SHORT_TAG  => ShortTpe
        case VOID_TAG   => UnitTpe
        case BOOL_TAG   => BooleanTpe
        case 'L' =>
          def processInner(tp: Type): Type = tp match {
            case TypeRef(pre, sym, args) if (!sym.isStatic) =>
              typeRef(processInner(pre.widen), sym, args)
            case _ =>
              tp
          }
          def processClassType(tp: Type): Type = tp match {
            case TypeRef(pre, classSym, args) =>
              val existentials = new ListBuffer[Symbol]()
              if (sig.charAt(index) == '<') {
                accept('<')
                val xs = new ListBuffer[Type]()
                var i = 0
                while (sig.charAt(index) != '>') {
                  sig.charAt(index) match {
                    case variance @ ('+' | '-' | '*') =>
                      index += 1
                      val bounds = variance match {
                        case '+' => TypeBounds.upper(sig2type(tparams, skiptvs))
                        case '-' =>
                          val tp = sig2type(tparams, skiptvs)
                          // Interpret `sig2type` returning `Any` as "no bounds";
                          // morally equivalent to TypeBounds.empty, but we're representing Java code, so use ObjectTpeJava for AnyTpe.
                          if (tp.typeSymbol == AnyClass) TypeBounds.upper(definitions.ObjectTpeJava)
                          else TypeBounds(tp, definitions.ObjectTpeJava)
                        case '*' => TypeBounds.upper(definitions.ObjectTpeJava)
                      }
                      val newtparam = sym.newExistential(newTypeName("?"+i), sym.pos) setInfo bounds
                      existentials += newtparam
                      xs += newtparam.tpeHK
                      i += 1
                    case _ =>
                      xs += sig2type(tparams, skiptvs)
                  }
                }
                accept('>')
                assert(xs.length > 0, tp)
                debuglogResult("new existential")(newExistentialType(existentials.toList, typeRef(pre, classSym, xs.toList)))
              }
              // isMonomorphicType is false if the info is incomplete, as it usually is here
              // so have to check unsafeTypeParams.isEmpty before worrying about raw type case below,
              // or we'll create a boatload of needless existentials.
              else if (classSym.isMonomorphicType || classSym.unsafeTypeParams.isEmpty) tp
              else debuglogResult(s"raw type from $classSym") {
                // raw type - existentially quantify all type parameters
                classExistentialType(pre, classSym)
              }
            case tp =>
              assert(sig.charAt(index) != '<', s"sig=$sig, index=$index, tp=$tp")
              tp
          }

          val classSym = classNameToSymbol(subName(c => c == ';' || c == '<'))
          assert(!classSym.isOverloaded, classSym.alternatives)
          val classTpe = if (classSym eq ObjectClass) ObjectTpeJava else classSym.tpe_*
          var tpe = processClassType(processInner(classTpe))
          while (sig.charAt(index) == '.') {
            accept('.')
            val name = newTypeName(subName(c => c == ';' || c == '<' || c == '.'))
            val member = if (tpe.typeSymbol == clazz) instanceScope.lookup(name) else tpe.member(name)
            val dummyArgs = Nil // the actual arguments are added in processClassType
            val inner = typeRef(pre = tpe, sym = member, args = dummyArgs)
            tpe = processClassType(inner)
          }
          accept(';')
          tpe
        case ARRAY_TAG =>
          while ('0' <= sig.charAt(index) && sig.charAt(index) <= '9') index += 1
          var elemtp = sig2type(tparams, skiptvs)
          // make unbounded Array[T] where T is a type variable into Array[T with Object]
          // (this is necessary because such arrays have a representation which is incompatible
          // with arrays of primitive types.
          // see also RestrictJavaArraysMap (when compiling java sources directly)
          if (elemtp.typeSymbol.isAbstractType && elemtp.upperBound =:= ObjectTpe) {
            elemtp = intersectionType(List(elemtp, ObjectTpe))
          }

          arrayType(elemtp)
        case '(' =>
          // we need a method symbol. given in line 486 by calling getType(methodSym, ..)
          assert(sym ne null, sig)
          val paramtypes = new ListBuffer[Type]()
          while (sig.charAt(index) != ')') {
            paramtypes += sig2type(tparams, skiptvs)
          }
          index += 1
          val restype = if (sym != null && sym.isClassConstructor) {
            accept('V')
            clazz.tpe_*
          } else
            sig2type(tparams, skiptvs)
          MethodType(sym.newSyntheticValueParams(paramtypes.toList), restype)
        case 'T' =>
          val n = newTypeName(subName(';'.==))
          index += 1
          if (skiptvs) AnyTpe
          else tparams(n).typeConstructor
      }
    } // sig2type(tparams, skiptvs)

    def sig2typeBounds(tparams: immutable.Map[Name, Symbol], skiptvs: Boolean): Type = {
      val ts = new ListBuffer[Type]
      while (sig.charAt(index) == ':') {
        index += 1
        if (sig.charAt(index) != ':') // guard against empty class bound
          ts += sig2type(tparams, skiptvs)
      }
      TypeBounds.upper(intersectionType(ts.toList, sym))
    }

    var tparams = classTParams
    val newTParams = new ListBuffer[Symbol]()
    if (sig.charAt(index) == '<') {
      assert(sym != null, sig)
      index += 1
      val start = index
      while (sig.charAt(index) != '>') {
        val tpname = newTypeName(subName(':'.==))
        val s = sym.newTypeParameter(tpname)
        tparams = tparams + (tpname -> s)
        sig2typeBounds(tparams, skiptvs = true)
        newTParams += s
      }
      index = start
      while (sig.charAt(index) != '>') {
        val tpname = newTypeName(subName(':'.==))
        val s = tparams(tpname)
        s.setInfo(sig2typeBounds(tparams, skiptvs = false))
      }
      accept('>')
    }
    val ownTypeParams = newTParams.toList
    if (!ownTypeParams.isEmpty)
      sym.setInfo(new TypeParamsType(ownTypeParams))
    val tpe =
      if ((sym eq null) || !sym.isClass)
        sig2type(tparams, skiptvs = false)
      else {
        classTParams = tparams
        val parents = new ListBuffer[Type]()
        while (index < end) {
          val parent = sig2type(tparams, skiptvs = false) // here the variance doesn't matter
          parents += (if (parent == ObjectTpeJava) ObjectTpe else parent)
        }
        ClassInfoType(parents.toList, instanceScope, sym)
      }
    GenPolyType(ownTypeParams, tpe)
  } // sigToType

  /**
   * Only invoked for java classfiles.
   */
  private def parseAttributes(sym: symbolTable.Symbol, completer: JavaTypeCompleter): Unit = {
    def parseAttribute(): Unit = {
      val attrName = readTypeName()
      val attrLen  = u4
      attrName match {
        case tpnme.SignatureATTR =>
          val sigIndex = u2
          val sig = pool.getExternalName(sigIndex)
          assert(sym.rawInfo == completer, sym)
          completer.sig = sig.value
        case tpnme.SyntheticATTR =>
          sym.setFlag(SYNTHETIC | ARTIFACT)
          in.skip(attrLen)

        case tpnme.BridgeATTR =>
          sym.setFlag(BRIDGE | ARTIFACT)
          in.skip(attrLen)

        case tpnme.DeprecatedATTR =>
          in.skip(attrLen)
          if (sym == clazz)
            staticModule.addAnnotation(JavaDeprecatedAttr)

        case tpnme.ConstantValueATTR =>
          completer.constant = pool.getConstant(u2)

        case tpnme.MethodParametersATTR =>
          def readParamNames(): Unit = {
            val paramCount = u1
            val paramNames = new Array[NameOrString](paramCount)
            val paramNameAccess = new Array[Int](paramCount)
            var i = 0
            while (i < paramCount) {
              paramNames(i) = pool.getExternalName(u2)
              paramNameAccess(i) = u2
              i += 1
            }
            completer.paramNames = new ParamNames(paramNames, paramNameAccess)
          }
          readParamNames()

        case tpnme.AnnotationDefaultATTR => // Methods of java annotation classes that have a default
          sym.addAnnotation(AnnotationDefaultAttr)
          in.skip(attrLen)

        case tpnme.RuntimeAnnotationATTR =>
            val numAnnots = u2
          val annots = new ListBuffer[AnnotationInfo]
            for (n <- 0 until numAnnots; annot <- parseAnnotation(u2))
            annots += annot
          /* `sym.withAnnotations(annots)`, like `sym.addAnnotation(annot)`, prepends,
           * so if we parsed in classfile order we would wind up with the annotations
           * in reverse order in `sym.annotations`. Instead we just read them out the
           * other way around, for now. TODO: sym.addAnnotation add to the end?
           */
          sym.setAnnotations(sym.annotations ::: annots.toList)

        // TODO 1: parse runtime visible annotations on parameters
        // case tpnme.RuntimeParamAnnotationATTR

        // TODO 2: also parse RuntimeInvisibleAnnotation / RuntimeInvisibleParamAnnotation,
        // i.e. java annotations with RetentionPolicy.CLASS?

        case tpnme.ExceptionsATTR =>
          parseExceptions(attrLen, completer)

        case tpnme.SourceFileATTR =>
          /*
          if (forInteractive) {
            // opt: disable this code in the batch compiler for performance reasons.
            // it appears to be looking for the .java source file mentioned in this attribute
            // in the output directories of scalac.
            //
            // References:
            // https://issues.scala-lang.org/browse/SI-2689
            // https://github.com/scala/scala/commit/7315339782f6e19ddd6199768352a91ef66eb27d
            // https://github.com/scala-ide/scala-ide/commit/786ea5d4dc44065379a05eb3ac65d37f8948c05d
            //
            // TODO: Does Scala-IDE actually intermingle source and classfiles in a way that this could ever find something?
            //       If they want to use this, they'll need to enable the new setting -Ypresentation-locate-source-file.
            val srcfileLeaf = readName().toString.trim
            val srcpath = sym.enclosingPackage match {
              case NoSymbol => srcfileLeaf
              case rootMirror.EmptyPackage => srcfileLeaf
              case pkg => pkg.fullName(File.separatorChar)+File.separator+srcfileLeaf
            }
            srcfile0 = settings.outputDirs.srcFilesFor(file, srcpath).find(_.exists)
          } else in.skip(attrLen)
          */
          in.skip(attrLen)

        case tpnme.CodeATTR =>
          if (sym.owner.isInterface) {
            sym setFlag JAVA_DEFAULTMETHOD
            log(s"$sym in ${sym.owner} is a java8+ default method.")
          }
          in.skip(attrLen)

        case _ =>
          in.skip(attrLen)
      }
    }

    /*
     * Parse the "Exceptions" attribute which denotes the exceptions
     * thrown by a method.
     */
    def parseExceptions(len: Int, completer: JavaTypeCompleter): Unit = {
      val nClasses = u2
      for (n <- 0 until nClasses) {
        // FIXME: this performs an equivalent of getExceptionTypes instead of getGenericExceptionTypes (scala/bug#7065)
        val cls = pool.getClassName(u2)
        completer.exceptions ::= cls
      }
    }
    // begin parseAttributes
    for (i <- 0 until u2) parseAttribute()
  }


  def parseAnnotArg(): Option[ClassfileAnnotArg] = {
    val tag = u1
    val index = u2
    tag match {
      case STRING_TAG =>
        Some(LiteralAnnotArg(Constant(pool.getName(index).value)))
      case BOOL_TAG | BYTE_TAG | CHAR_TAG | SHORT_TAG | INT_TAG |
           LONG_TAG | FLOAT_TAG | DOUBLE_TAG =>
        Some(LiteralAnnotArg(pool.getConstant(index)))
      case CLASS_TAG  =>
        Some(LiteralAnnotArg(Constant(pool.getType(index))))
      case ENUM_TAG   =>
        val t = pool.getType(index)
        val n = readName()
        val module = t.typeSymbol.companionModule
        val s = module.info.decls.lookup(n)
        if (s != NoSymbol) Some(LiteralAnnotArg(Constant(s)))
        else {
          loaders.warning(
            NoPosition,
            sm"""While parsing annotations in ${file}, could not find $n in enum ${module.nameString}.
                |This is likely due to an implementation restriction: an annotation argument cannot refer to a member of the annotated class (scala/bug#7014).""",
            WarningCategory.Other,
            clazz.fullNameString)
          None
        }

      case ARRAY_TAG  =>
        val arr = new ArrayBuffer[ClassfileAnnotArg]()
        var hasError = false
        for (i <- 0 until index)
          parseAnnotArg() match {
            case Some(c) => arr += c
            case None => hasError = true
          }
        if (hasError) None
        else Some(ArrayAnnotArg(arr.toArray))
      case ANNOTATION_TAG =>
        parseAnnotation(index) map (NestedAnnotArg(_))
    }
  }


  // TODO scala/bug#9296 duplicated code, refactor
  /**
   * Parse and return a single annotation.  If it is malformed, return None.
   */
  def parseAnnotation(attrNameIndex: Int): Option[AnnotationInfo] = try {
    val attrType = pool.getType(attrNameIndex)
    val nargs = u2
    val nvpairs = new ListBuffer[(Name, ClassfileAnnotArg)]
    var hasError = false
    for (i <- 0 until nargs) {
      val name = readName()
      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 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
      // the classpath would *not* end up here. A class not found is signaled
      // with a `FatalError` exception, handled above. Here you'd end up after a NPE (for example),
      // and that should never be swallowed silently.
      loaders.warning(NoPosition, s"Caught: $ex while parsing annotations in ${file}", WarningCategory.Other, clazz.fullNameString)
      if (settings.debug) ex.printStackTrace()
      None // ignore malformed annotations
  }

  /** Apply `@native`/`@transient`/`@volatile` annotations to `sym`,
    * if the corresponding flag is set in `flags`.
    */
  def addJavaFlagsAnnotations(sym: Symbol, flags: JavaAccFlags): Unit =
    flags.toScalaAnnotations(symbolTable) foreach (ann => sym.addAnnotation(ann))

  /** Enter own inner classes in the right scope. It needs the scopes to be set up,
   *  and implicitly current class' superclasses.
   */
  private def enterOwnInnerClasses(): Unit = {
    def className(name: String): String =
      name.substring(name.lastIndexOf('.') + 1, name.length)

    def enterClassAndModule(entry: InnerClassEntry, file: AbstractFile): Unit = {
      def jflags      = entry.jflags
      val name        = entry.originalName
      val sflags      = jflags.toScalaFlags
      val owner       = ownerForFlags(jflags)
      val scope       = getScope(jflags)
      def newStub(name: Name) = {
        val stub = owner.newStubSymbol(name, s"Class file for ${entry.externalName} not found")
        stub.setPos(owner.pos)
        stub.setFlag(JAVA)
      }

      val (innerClass, innerModule) = if (file == NoAbstractFile) {
        (newStub(name.toTypeName), newStub(name.toTermName))
      } else {
        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
        cls.associatedFile = file
        mod.moduleClass.associatedFile = file

        /**
          * need to set privateWithin here because the classfile of a nested protected class is public in bytecode,
          * so propagatePackageBoundary will not set it when the symbols are completed
          */
        if (jflags.isProtected) {
          cls.privateWithin = cls.enclosingPackage
          mod.privateWithin = cls.enclosingPackage
        }

        (cls, mod)
      }

      scope enter innerClass
      scope enter innerModule

      val decls = innerClass.enclosingPackage.info.decls
      def unlinkIfPresent(name: Name) = {
        val e = decls lookupEntry name
        if (e ne null)
          decls unlink e
      }

      val cName = newTermName(className(entry.externalName))
      unlinkIfPresent(cName)
      unlinkIfPresent(cName.toTypeName)
    }

    for (entry <- innerClasses.entries) {
      // create a new class member for immediate inner classes
      if (entry.outerName == currentClass) {
        val file = classPath.findClassFile(entry.externalName.toString)
        enterClassAndModule(entry, file.getOrElse(NoAbstractFile))
      }
    }
  }

  /**
   * Either
   *   - set `isScala` and invoke the unpickler, or
   *   - set `isScalaRaw`, or
   *   - parse inner classes (for Java classfiles)
   *
   * Expects `in.bp` to point to the `access_flags` entry, restores the old `bp`.
   */
  def unpickleOrParseInnerClasses(): Unit = {
    val oldbp = in.bp
    in.skip(4) // access_flags, this_class
    skipSuperclasses()
    skipMembers() // fields
    skipMembers() // methods

    var innersStart = -1
    var runtimeAnnotStart = -1
    var TASTYAttrStart = -1
    var TASTYAttrLen = -1

    val numAttrs = u2()
    var i = 0
    while (i < numAttrs) {
      val attrName = readTypeName()
      val attrLen = u4()
      attrName match {
        case tpnme.ScalaSignatureATTR =>
          isScala = true
          if (runtimeAnnotStart != -1) i = numAttrs
        case tpnme.ScalaATTR =>
          isScalaRaw = true
          i = numAttrs
        case tpnme.TASTYATTR =>
          isTASTY = true
          TASTYAttrLen = attrLen
          TASTYAttrStart = in.bp
          i = numAttrs
        case tpnme.InnerClassesATTR =>
          innersStart = in.bp
        case tpnme.RuntimeAnnotationATTR =>
          runtimeAnnotStart = in.bp
          if (isScala) i = numAttrs
        case _ =>
      }
      in.skip(attrLen)
      i += 1
    }

    // To understand the situation, it's helpful to know that:
    // - Scalac emits the `ScalaSignature` attribute for classfiles with pickled information
    // and the `Scala` attribute for everything else.
    // - Dotty emits the `TASTY` attribute for classfiles with pickled information
    // and the `Scala` attribute for _every_ classfile.
    isScalaRaw &= !isTASTY

    if (isScala) {
      def parseScalaSigBytes(): Array[Byte] = {
        val tag = u1()
        assert(tag == STRING_TAG, tag)
        pool.getBytes(u2())
      }

      def parseScalaLongSigBytes(): Array[Byte] = {
        val tag = u1()
        assert(tag == ARRAY_TAG, tag)
        val stringCount = u2()
        val entries =
          for (_ <- 0 until stringCount) yield {
            val stag = u1()
            assert(stag == STRING_TAG, stag)
            u2()
          }
        pool.getBytes(entries.toList)
      }

      def checkScalaSigAnnotArg() = {
        val numArgs = u2
        assert(numArgs == 1, s"ScalaSignature has $numArgs arguments")
        val name = readName()
        assert(name == nme.bytes, s"ScalaSignature argument has name $name")
      }

      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 =>
          in.skip(2) // type
          skipAnnotArgs()
      }

      def skipAnnotArgs() = {
        val numArgs = u2
        for (i <- 0 until numArgs) {
          in.skip(2)
          skipAnnotArg()
        }
      }

      val SigTpe = ScalaSignatureAnnotation.tpe
      val LongSigTpe = ScalaLongSignatureAnnotation.tpe

      assert(runtimeAnnotStart != -1, s"No RuntimeVisibleAnnotations in classfile with ScalaSignature attribute: $clazz")
      in.bp = runtimeAnnotStart
      val numAnnots = u2()
      var i = 0
      var bytes: Array[Byte] = null
      while (i < numAnnots && bytes == null) {
        pool.getType(u2) match {
          case SigTpe =>
            checkScalaSigAnnotArg()
            bytes = parseScalaSigBytes()
          case LongSigTpe =>
            checkScalaSigAnnotArg()
            bytes = parseScalaLongSigBytes()
          case t =>
            skipAnnotArgs()
        }
        i += 1
      }

      AnyRefClass // Force scala.AnyRef, otherwise we get "error: Symbol AnyRef is missing from the classpath"
      assert(bytes != null, s"No Scala(Long)Signature annotation in classfile with ScalaSignature attribute: $clazz")
      unpickler.unpickle(bytes, 0, clazz, staticModule, file.name)
    } else if (isTASTY) {

      def parseTASTYFile(): Array[Byte] = file.underlyingSource match { // TODO: simplify when #3552 is fixed
        case None =>
          reporter.error(NoPosition, "Could not load TASTY from .tasty for virtual file " + file)
          Array.empty
        case Some(jar: ZipArchive) => // We are in a jar
          val cl = new URLClassLoader(Array(jar.toURL), /*parent =*/ null)
          val path = file.path.stripSuffix(".class") + ".tasty"
          val stream = cl.getResourceAsStream(path)
          if (stream != null) {
            val tastyOutStream = new ByteArrayOutputStream()
            val buffer = new Array[Byte](1024)
            var read = stream.read(buffer, 0, buffer.length)
            while (read != -1) {
              tastyOutStream.write(buffer, 0, read)
              read = stream.read(buffer, 0, buffer.length)
            }
            tastyOutStream.flush()
            tastyOutStream.toByteArray
          } else {
            reporter.error(NoPosition, s"Could not find $path in $jar")
            Array.empty
          }
        case _ =>
          val plainFile = new PlainFile(io.File(file.path).changeExtension("tasty"))
          if (plainFile.exists) plainFile.toByteArray
          else {
            reporter.error(NoPosition, "Could not find " + plainFile)
            Array.empty
          }
      }

      def parseTASTYBytes(): Array[Byte] = {
        assert(TASTYAttrLen == TASTYUUIDLength, "TASTY Attribute is not a UUID")
        assert(TASTYAttrStart != -1, "no TASTY Annotation position")
        in.bp = TASTYAttrStart
        val TASTY = in.nextBytes(TASTYUUIDLength)
        val TASTYBytes = parseTASTYFile()
        if (TASTYBytes.isEmpty) {
          reporter.error(NoPosition, s"No Tasty file found for classfile $file with TASTY Attribute")
        }
        val reader = new TastyReader(TASTY, 0, TASTYUUIDLength)
        val expectedUUID = new UUID(reader.readUncompressedLong(), reader.readUncompressedLong())
        val tastyUUID = new TastyHeaderUnpickler(TASTYBytes).readHeader()
        if (expectedUUID != tastyUUID) {
          reporter.error(NoPosition, s"Tasty UUID ($tastyUUID) file did not correspond the tasty UUID ($expectedUUID) declared in the classfile $file.")
        }
        TASTYBytes
      }

      AnyRefClass // Force scala.AnyRef, otherwise we get "error: Symbol AnyRef is missing from the classpath"
      val bytes = parseTASTYBytes()
      TastyUnpickler.unpickle(TastyUniverse)(bytes, clazz, staticModule, file.path.stripSuffix(".class") + ".tasty")
    } else if (!isScalaRaw && innersStart != -1) {
      in.bp = innersStart
      val entries = u2()
      for (_ <- 0 until entries) {
        val innerIndex, outerIndex, nameIndex = u2()
        val jflags = readInnerClassFlags()
        if (innerIndex != 0 && outerIndex != 0 && nameIndex != 0)
          innerClasses add InnerClassEntry(pool.getClassName(innerIndex), pool.getClassName(outerIndex), pool.getName(nameIndex), jflags)
      }
    }
    in.bp = oldbp
  }

  /** An entry in the InnerClasses attribute of this class file. */
  case class InnerClassEntry(external: NameOrString, outer: NameOrString, name: NameOrString, jflags: JavaAccFlags) {
    def externalName = external.value
    def outerName    = outer.value
    def originalName = name.name
    def isModule     = originalName.isTermName
    def scope        = if (jflags.isStatic) staticScope else instanceScope
    def enclosing    = if (jflags.isStatic) enclModule else enclClass

    // The name of the outer class, without its trailing $ if it has one.
    private def strippedOuter = outerName.stripSuffix(nme.MODULE_SUFFIX_STRING)
    private def isInner       = innerClasses contains strippedOuter
    private def enclClass     = if (isInner) innerClasses innerSymbol strippedOuter else classNameToSymbol(strippedOuter)
    private def enclModule    = enclClass.companionModule
  }

  /** Return the class symbol for the given name. It looks it up in its outer class.
   *  Forces all outer class symbols to be completed.
   *
   *  If the given name is not an inner class, it returns the symbol found in `definitions`.
   */
  object innerClasses {
    private val inners = mutable.HashMap[String, InnerClassEntry]()

    def contains(name: String) = inners contains name
    def getEntry(name: String) = inners get name
    def entries              = inners.values

    def add(entry: InnerClassEntry): Unit = {
      devWarningIf(inners contains entry.externalName) {
        val existing = inners(entry.externalName)
        s"Overwriting inner class entry! Was $existing, now $entry"
      }
      inners(entry.externalName) = entry
    }
    def innerSymbol(externalName: String): Symbol = this getEntry externalName match {
      case Some(entry) => innerSymbol(entry)
      case _           => NoSymbol
    }

    private def innerSymbol(entry: InnerClassEntry): Symbol = {
      val name      = entry.originalName.toTypeName
      val enclosing = entry.enclosing
      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}")
          .setPos(enclosing.pos)
      }
      member.orElse(newStub)
    }
  }

  class TypeParamsType(override val typeParams: List[Symbol]) extends LazyType with FlagAgnosticCompleter {
    override def complete(sym: Symbol): Unit = { throw new AssertionError("cyclic type dereferencing") }
  }
  class LazyAliasType(alias: Symbol) extends LazyType with FlagAgnosticCompleter {
    override def complete(sym: Symbol): Unit = {
      sym setInfo createFromClonedSymbols(alias.initialize.typeParams, alias.tpe)(typeFun)
    }
  }
  private class ParamNames(val names: Array[NameOrString], val access: Array[Int]) {
    assert(names.length == access.length, "Require as many names as access")
    def length = names.length
  }
  private abstract class JavaTypeCompleter extends LazyType {
    var constant: Constant = _
    var sig: String = _
    var paramNames: ParamNames = _
    var exceptions: List[NameOrString] = Nil
  }
  private final class ClassTypeCompleter(name: Name, jflags: JavaAccFlags, parent: NameOrString, ifaces: List[NameOrString]) extends JavaTypeCompleter {
    override def complete(sym: symbolTable.Symbol): Unit = {
      val info = if (sig != null) sigToType(sym, sig) else {
        val superTpe = if (parent == null) definitions.AnyClass.tpe_* else getClassSymbol(parent.value).tpe_*
        val superTpe1 = if (superTpe == ObjectTpeJava) ObjectTpe else superTpe
        val ifacesTypes = ifaces.filterNot(_ eq null).map(x => getClassSymbol(x.value).tpe_*)
        ClassInfoType(superTpe1 :: ifacesTypes, instanceScope, clazz)
      }
      sym.setInfo(info)
    }
  }

  private final class MemberTypeCompleter(name: Name, jflags: JavaAccFlags, descriptor: String) extends JavaTypeCompleter {
    override def isJavaVarargsMethod: Boolean = jflags.isVarargs
    override def javaThrownExceptions: List[Symbol] = exceptions.map(e => classNameToSymbol(e.value))
    override def complete(sym: symbolTable.Symbol): Unit = {
      def descriptorInfo = sigToType(sym, descriptor)
      val hasOuterParam = (name == nme.CONSTRUCTOR) && (descriptorInfo match {
        case MethodType(params, restpe) =>
          // if this is a non-static inner class, remove the explicit outer parameter
          innerClasses getEntry currentClass match {
            case Some(entry) if !entry.jflags.isStatic =>
              /* About `clazz.owner.hasPackageFlag` below: scala/bug#5957
               * For every nested java class A$B, there are two symbols in the scala compiler.
               *  1. created by SymbolLoader, because of the existence of the A$B.class file, owner: package
               *  2. created by ClassfileParser of A when reading the inner classes, owner: A
               * If symbol 1 gets completed (e.g. because the compiled source mentions `A$B`, not `A#B`), the
               * 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)
              true
            case _ =>
              false
          }
        case _ => false
      })

      val info = if (sig != null) {
        sigToType(sym, sig)
      } else if (name == nme.CONSTRUCTOR) {
        descriptorInfo match {
          case MethodType(params, restpe) =>
            val paramsNoOuter = if (hasOuterParam) params.tail else params
            val newParams = paramsNoOuter match {
              case (init :+ tail) if jflags.isSynthetic =>
                // scala/bug#7455 strip trailing dummy argument ("access constructor tag") from synthetic constructors which
                // are added when an inner class needs to access a private constructor.
                init
              case _ =>
                paramsNoOuter
            }
            MethodType(newParams, clazz.tpe)
          case info => info
        }
      } else {
        descriptorInfo
      }
      if (constant != null) {
        val c1 = convertTo(constant, info.resultType)
        if (c1 ne null) sym.setInfo(ConstantType(c1))
        else {
          devWarning(s"failure to convert $constant to ${info.resultType}")
          sym.setInfo(info)
        }
      } else {
        sym.setInfo(if (sym.isMethod && jflags.isVarargs) arrayToRepeated(info) else info)
      }

      for (e <- exceptions) {
        // we call initialize due to the fact that we call Symbol.isMonomorphicType in addThrowsAnnotation
        // and that method requires Symbol to be forced to give the right answers, see scala/bug#7107 for details
        val cls = getClassSymbol(e.value)
        sym withAnnotation AnnotationInfo.lazily {
          val throwableTpe = cls.tpe_*
          AnnotationInfo(appliedType(ThrowsClass, throwableTpe), List(Literal(Constant(throwableTpe))), Nil)
        }
      }

      // Note: the info may be overwritten later with a generic signature
      // parsed from SignatureATTR
      if (paramNames != null) {
        import scala.tools.asm.Opcodes.ACC_SYNTHETIC

        if (sym.hasRawInfo && sym.isMethod) {
          val paramNamesNoOuter = (if (hasOuterParam) 1 else 0) to paramNames.length
          val params = sym.rawInfo.params
          foreach2(paramNamesNoOuter.toList, params) {
            case (i, param) =>
              val isSynthetic = (paramNames.access(i) & ACC_SYNTHETIC) != 0
              if (!isSynthetic) {
                param.name = paramNames.names(i).name.toTermName.encode
                param.resetFlag(SYNTHETIC)
              }
          }
          // there's not anything we can do, but it's slightly worrisome
          devWarningIf(!sameLength(paramNamesNoOuter.toList, params)) {
            sm"""MethodParameters length mismatch while parsing $sym:
                |  rawInfo.params: ${sym.rawInfo.params}"""
          }
        }
      }
    }
    private def convertTo(c: Constant, pt: Type): Constant = {
      if (pt.typeSymbol == BooleanClass && c.tag == IntTag)
        Constant(c.value != 0)
      else
        c convertTo pt
    }
  }

  def skipAttributes(): Unit = {
    var attrCount: Int = u2
    while (attrCount > 0) {
      in skip 2
      in skip u4
      attrCount -= 1
    }
  }

  def skipMembers(): Unit = {
    var memberCount: Int = u2
    while (memberCount > 0) {
      in skip 6
      skipAttributes()
      memberCount -= 1
    }
  }

  def skipSuperclasses(): Unit = {
    in.skip(2) // superclass
    val ifaces = u2
    in.skip(2 * ifaces)
  }

  protected def getScope(flags: JavaAccFlags): Scope =
    if (flags.isStatic) staticScope else instanceScope
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy