package dotty.tools package dotc package core package classfile import Contexts._, Symbols._, Types._, Names._, StdNames._, NameOps._, Scopes._, Decorators._ import SymDenotations._, unpickleScala2.Scala2Unpickler._, Constants._, Annotations._, util.Positions._ import NameKinds.{ModuleClassName, DefaultGetterName} import ast.tpd._ import java.io.{ File, IOException } import java.lang.Integer.toHexString import scala.collection.{ mutable, immutable } import scala.collection.mutable.{ ListBuffer, ArrayBuffer } import scala.annotation.switch import typer.Checking.checkNonCyclic import io.AbstractFile import scala.util.control.NonFatal object ClassfileParser { /** Marker trait for unpicklers that can be embedded in classfiles. */ trait Embedded } class ClassfileParser( classfile: AbstractFile, classRoot: ClassDenotation, moduleRoot: ClassDenotation)(ictx: Context) { //println(s"parsing ${classRoot.name.debugString} ${moduleRoot.name.debugString}") import ClassfileConstants._ import ClassfileParser._ protected val in = new AbstractFileReader(classfile) protected val staticModule: Symbol = moduleRoot.sourceModule(ictx) protected val instanceScope: MutableScope = newScope // the scope of all instance definitions protected val staticScope: MutableScope = newScope // the scope of all static definitions protected var pool: ConstantPool = _ // the classfile's constant pool protected var currentClassName: SimpleTermName = _ // JVM name of the current class protected var classTParams = Map[Name,Symbol]() classRoot.info = (new NoCompleter).withDecls(instanceScope) moduleRoot.info = (new NoCompleter).withDecls(staticScope).withSourceModule(_ => staticModule) private def currentIsTopLevel(implicit ctx: Context) = classRoot.owner is Flags.PackageClass private def mismatchError(className: SimpleTermName) = throw new IOException(s"class file '${in.file}' has location not matching its contents: contains class $className") def run()(implicit ctx: Context): Option[Embedded] = try { ctx.debuglog("[class] >> " + classRoot.fullName) parseHeader this.pool = new ConstantPool parseClass() } catch { case e: RuntimeException => if (ctx.debug) e.printStackTrace() throw new IOException( i"""class file $classfile is broken, reading aborted with ${e.getClass} |${Option(e.getMessage).getOrElse("")}""") } private def parseHeader(): Unit = { val magic = in.nextInt if (magic != JAVA_MAGIC) throw new IOException(s"class file '${in.file}' has wrong magic number 0x${toHexString(magic)}, should be 0x${toHexString(JAVA_MAGIC)}") val minorVersion = in.nextChar.toInt val majorVersion = in.nextChar.toInt if ((majorVersion < JAVA_MAJOR_VERSION) || ((majorVersion == JAVA_MAJOR_VERSION) && (minorVersion < JAVA_MINOR_VERSION))) throw new IOException( s"class file '${in.file}' has unknown version $majorVersion.$minorVersion, should be at least $JAVA_MAJOR_VERSION.$JAVA_MINOR_VERSION") } /** Return the class symbol of the given name. */ def classNameToSymbol(name: Name)(implicit ctx: Context): Symbol = innerClasses.get(name) match { case Some(entry) => innerClasses.classSymbol(entry.externalName) case None => ctx.requiredClass(name) } var sawPrivateConstructor = false def parseClass()(implicit ctx: Context): Option[Embedded] = { val jflags = in.nextChar val isAnnotation = hasAnnotation(jflags) val sflags = classTranslation.flags(jflags) val isEnum = (jflags & JAVA_ACC_ENUM) != 0 val nameIdx = in.nextChar currentClassName = pool.getClassName(nameIdx) if (currentIsTopLevel && currentClassName != classRoot.fullName.toSimpleName) mismatchError(currentClassName) addEnclosingTParams() /** Parse parents for Java classes. For Scala, return AnyRef, since the real type will be unpickled. * Updates the read pointer of 'in'. */ def parseParents: List[Type] = { val superType = if (isAnnotation) { in.nextChar; defn.AnnotationType } else pool.getSuperClass(in.nextChar).typeRef val ifaceCount = in.nextChar var ifaces = for (i <- (0 until ifaceCount).toList) yield pool.getSuperClass(in.nextChar).typeRef // Dotty deviation: was // var ifaces = for (i <- List.range(0 until ifaceCount)) ... // This does not typecheck because the type parameter of List is now lower-bounded by Int | Char. // Consequently, no best implicit for the "Integral" evidence parameter of "range" // is found. If we treat constant subtyping specially, we might be able // to do something there. But in any case, the until should be more efficient. if (isAnnotation) ifaces = defn.ClassfileAnnotationType :: ifaces superType :: ifaces } val result = unpickleOrParseInnerClasses() if (!result.isDefined) { var classInfo: Type = TempClassInfoType(parseParents, instanceScope, classRoot.symbol) // might be reassigned by later parseAttributes val staticInfo = TempClassInfoType(List(), staticScope, moduleRoot.symbol) enterOwnInnerClasses classRoot.setFlag(sflags) moduleRoot.setFlag(Flags.JavaDefined | Flags.ModuleClassCreationFlags) setPrivateWithin(classRoot, jflags) setPrivateWithin(moduleRoot, jflags) setPrivateWithin(moduleRoot.sourceModule, jflags) for (i <- 0 until in.nextChar) parseMember(method = false) for (i <- 0 until in.nextChar) parseMember(method = true) classInfo = parseAttributes(classRoot.symbol, classInfo) if (isAnnotation) addAnnotationConstructor(classInfo) val companionClassMethod = ctx.synthesizeCompanionMethod(nme.COMPANION_CLASS_METHOD, classRoot, moduleRoot) if (companionClassMethod.exists) companionClassMethod.entered val companionModuleMethod = ctx.synthesizeCompanionMethod(nme.COMPANION_MODULE_METHOD, moduleRoot, classRoot) if (companionModuleMethod.exists) companionModuleMethod.entered setClassInfo(classRoot, classInfo) setClassInfo(moduleRoot, staticInfo) } // eager load java enum definitions for exhaustivity check of pattern match if (isEnum) { instanceScope.toList.map(_.ensureCompleted()) staticScope.toList.map(_.ensureCompleted()) classRoot.setFlag(Flags.Enum) moduleRoot.setFlag(Flags.Enum) } result } /** Add type parameters of enclosing classes */ def addEnclosingTParams()(implicit ctx: Context): Unit = { var sym = classRoot.owner while (sym.isClass && !(sym is Flags.ModuleClass)) { for (tparam <- sym.typeParams) { classTParams = classTParams.updated(tparam.name.unexpandedName, tparam) } sym = sym.owner } } def parseMember(method: Boolean)(implicit ctx: Context): Unit = { val start = indexCoord(in.bp) val jflags = in.nextChar val sflags = if (method) Flags.Method | methodTranslation.flags(jflags) else fieldTranslation.flags(jflags) val name = pool.getName(in.nextChar) if (!(sflags is Flags.Private) || name == nme.CONSTRUCTOR || ctx.settings.optimise.value) { val member = ctx.newSymbol( getOwner(jflags), name, sflags, memberCompleter, coord = start) getScope(jflags).enter(member) } // skip rest of member for now in.nextChar // info skipAttributes } val memberCompleter = new LazyType { def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { val oldbp = in.bp try { in.bp = denot.symbol.coord.toIndex val sym = denot.symbol val jflags = in.nextChar val isEnum = (jflags & JAVA_ACC_ENUM) != 0 val name = pool.getName(in.nextChar) val isConstructor = name eq nme.CONSTRUCTOR /** Strip leading outer param from constructor. * Todo: Also strip trailing access tag for private inner constructors? */ def stripOuterParamFromConstructor() = innerClasses.get(currentClassName) match { case Some(entry) if !isStatic(entry.jflags) => val mt @ MethodTpe(paramNames, paramTypes, resultType) = denot.info denot.info = mt.derivedLambdaType(paramNames.tail, paramTypes.tail, resultType) case _ => } /** Make return type of constructor be the enclosing class type, * and make constructor type polymorphic in the type parameters of the class */ def normalizeConstructorInfo() = { val mt @ MethodType(paramNames) = denot.info val rt = classRoot.typeRef appliedTo (classRoot.typeParams map (_.typeRef)) denot.info = mt.derivedLambdaType(paramNames, mt.paramInfos, rt) addConstructorTypeParams(denot) } denot.info = pool.getType(in.nextChar) if (isEnum) denot.info = ConstantType(Constant(sym)) if (isConstructor) stripOuterParamFromConstructor() setPrivateWithin(denot, jflags) denot.info = translateTempPoly(parseAttributes(sym, denot.info)) if (isConstructor) normalizeConstructorInfo() if ((denot is Flags.Method) && (jflags & JAVA_ACC_VARARGS) != 0) denot.info = arrayToRepeated(denot.info) // seal java enums if (isEnum) { val enumClass = sym.owner.linkedClass if (!(enumClass is Flags.Sealed)) enumClass.setFlag(Flags.AbstractSealed) enumClass.addAnnotation(Annotation.makeChild(sym)) } } finally { in.bp = oldbp } } } /** Map direct references to Object to references to Any */ final def objToAny(tp: Type)(implicit ctx: Context) = if (tp.isDirectRef(defn.ObjectClass) && !ctx.phase.erasedTypes) defn.AnyType else tp private def sigToType(sig: SimpleTermName, owner: Symbol = null)(implicit ctx: Context): Type = { var index = 0 val end = sig.length def accept(ch: Char): Unit = { assert(sig(index) == ch, (sig(index), ch)) index += 1 } def subName(isDelimiter: Char => Boolean): SimpleTermName = { val start = index while (!isDelimiter(sig(index))) { index += 1 } sig.slice(start, index) } // Warning: sigToType contains nested completers which might be forced in a later run! // So local methods need their own ctx parameters. def sig2type(tparams: immutable.Map[Name, Symbol], skiptvs: Boolean)(implicit ctx: Context): Type = { val tag = sig(index); index += 1 (tag: @switch) match { case BYTE_TAG => defn.ByteType case CHAR_TAG => defn.CharType case DOUBLE_TAG => defn.DoubleType case FLOAT_TAG => defn.FloatType case INT_TAG => defn.IntType case LONG_TAG => defn.LongType case SHORT_TAG => defn.ShortType case VOID_TAG => defn.UnitType case BOOL_TAG => defn.BooleanType case 'L' => def processInner(tp: Type): Type = tp match { case tp: TypeRef if !(tp.symbol.owner is Flags.ModuleClass) => TypeRef(processInner(tp.prefix.widen), tp.name) case _ => tp } def processClassType(tp: Type): Type = tp match { case tp: TypeRef => if (sig(index) == '<') { accept('<') var tp1: Type = tp var formals = tp.typeParamSymbols while (sig(index) != '>') { sig(index) match { case variance @ ('+' | '-' | '*') => index += 1 val bounds = variance match { case '+' => objToAny(TypeBounds.upper(sig2type(tparams, skiptvs))) case '-' => val tp = sig2type(tparams, skiptvs) // sig2type seems to return AnyClass regardless of the situation: // we don't want Any as a LOWER bound. if (tp.isDirectRef(defn.AnyClass)) TypeBounds.empty else TypeBounds.lower(tp) case '*' => TypeBounds.empty } tp1 = RefinedType(tp1, formals.head.name, bounds) case _ => tp1 = RefinedType(tp1, formals.head.name, TypeAlias(sig2type(tparams, skiptvs))) } formals = formals.tail } accept('>') tp1 } else tp case tp => assert(sig(index) != '<', tp) tp } val classSym = classNameToSymbol(subName(c => c == ';' || c == '<')) var tpe = processClassType(processInner(classSym.typeRef)) while (sig(index) == '.') { accept('.') val name = subName(c => c == ';' || c == '<' || c == '.').toTypeName val clazz = tpe.member(name).symbol tpe = processClassType(processInner(clazz.typeRef)) } accept(';') tpe case ARRAY_TAG => while ('0' <= sig(index) && sig(index) <= '9') index += 1 var elemtp = sig2type(tparams, skiptvs) // make unbounded Array[T] where T is a type variable into Ar ray[T with Object] // (this is necessary because such arrays have a representation which is incompatible // with arrays of primitive types. // NOTE that the comparison to Object only works for abstract types bounded by classes that are strict subclasses of Object // if the bound is exactly Object, it will have been converted to Any, and the comparison will fail // see also RestrictJavaArraysMap (when compiling java sources directly) if (elemtp.typeSymbol.isAbstractType && !(elemtp.derivesFrom(defn.ObjectClass))) { elemtp = AndType(elemtp, defn.ObjectType) } defn.ArrayOf(elemtp) case '(' => // we need a method symbol. given in line 486 by calling getType(methodSym, ..) val paramtypes = new ListBuffer[Type]() var paramnames = new ListBuffer[TermName]() while (sig(index) != ')') { paramnames += nme.syntheticParamName(paramtypes.length) paramtypes += objToAny(sig2type(tparams, skiptvs)) } index += 1 val restype = sig2type(tparams, skiptvs) JavaMethodType(paramnames.toList, paramtypes.toList, restype) case 'T' => val n = subName(';'.==).toTypeName index += 1 //assert(tparams contains n, s"classTparams = $classTParams, tparams = $tparams, key = $n") if (skiptvs) defn.AnyType else tparams(n).typeRef } } // sig2type(tparams, skiptvs) def sig2typeBounds(tparams: immutable.Map[Name, Symbol], skiptvs: Boolean)(implicit ctx: Context): Type = { val ts = new ListBuffer[Type] while (sig(index) == ':') { index += 1 if (sig(index) != ':') // guard against empty class bound ts += objToAny(sig2type(tparams, skiptvs)) } TypeBounds.upper(((NoType: Type) /: ts)(_ & _) orElse defn.AnyType) } var tparams = classTParams def typeParamCompleter(start: Int) = new LazyType { def complete(denot: SymDenotation)(implicit ctx: Context): Unit = { val savedIndex = index try { index = start denot.info = checkNonCyclic( // we need the checkNonCyclic call to insert LazyRefs for F-bounded cycles denot.symbol, sig2typeBounds(tparams, skiptvs = false), reportErrors = false) } finally { index = savedIndex } } } val newTParams = new ListBuffer[Symbol]() if (sig(index) == '<') { assert(owner != null) index += 1 val start = index while (sig(index) != '>') { val tpname = subName(':'.==).toTypeName val expname = if (owner.isClass) tpname.expandedName(owner) else tpname val s = ctx.newSymbol( owner, expname, owner.typeParamCreationFlags, typeParamCompleter(index), coord = indexCoord(index)) if (owner.isClass) owner.asClass.enter(s) tparams = tparams + (tpname -> s) sig2typeBounds(tparams, skiptvs = true) newTParams += s } index += 1 } val ownTypeParams = newTParams.toList.asInstanceOf[List[TypeSymbol]] val tpe = if ((owner == null) || !owner.isClass) sig2type(tparams, skiptvs = false) else { classTParams = tparams val parents = new ListBuffer[Type]() while (index < end) { parents += sig2type(tparams, skiptvs = false) // here the variance doesnt'matter } TempClassInfoType(parents.toList, instanceScope, owner) } if (ownTypeParams.isEmpty) tpe else TempPolyType(ownTypeParams, tpe) } // sigToType def parseAnnotArg(skip: Boolean = false)(implicit ctx: Context): Option[Tree] = { val tag = in.nextByte.toChar val index = in.nextChar tag match { case STRING_TAG => if (skip) None else Some(Literal(Constant(pool.getName(index).toString))) case BOOL_TAG | BYTE_TAG | CHAR_TAG | SHORT_TAG | INT_TAG | LONG_TAG | FLOAT_TAG | DOUBLE_TAG => if (skip) None else Some(Literal(pool.getConstant(index))) case CLASS_TAG => if (skip) None else Some(Literal(Constant(pool.getType(index)))) case ENUM_TAG => val t = pool.getType(index) val n = pool.getName(in.nextChar) val module = t.typeSymbol.companionModule val s = module.info.decls.lookup(n) if (skip) { None } else if (s != NoSymbol) { Some(Literal(Constant(s))) } else { ctx.warning(s"""While parsing annotations in ${in.file}, could not find $n in enum $module.\nThis is likely due to an implementation restriction: an annotation argument cannot refer to a member of the annotated class (SI-7014).""") None } case ARRAY_TAG => val arr = new ArrayBuffer[Tree]() var hasError = false for (i <- 0 until index) parseAnnotArg(skip) match { case Some(c) => arr += c case None => hasError = true } if (hasError) None else if (skip) None else { val elems = arr.toList val elemType = if (elems.isEmpty) defn.ObjectType else ctx.typeComparer.lub(elems.tpes).widen Some(JavaSeqLiteral(elems, TypeTree(elemType))) } case ANNOTATION_TAG => parseAnnotation(index, skip) map (_.tree) } } /** Parse and return a single annotation. If it is malformed, * return None. */ def parseAnnotation(attrNameIndex: Char, skip: Boolean = false)(implicit ctx: Context): Option[Annotation] = try { val attrType = pool.getType(attrNameIndex) val nargs = in.nextChar val argbuf = new ListBuffer[Tree] var hasError = false for (i <- 0 until nargs) { val name = pool.getName(in.nextChar) parseAnnotArg(skip) match { case Some(arg) => argbuf += NamedArg(name, arg) case None => hasError = !skip } } if (hasError || skip) None else Some(Annotation.deferredResolve(attrType, argbuf.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. ctx.warning("Caught: " + ex + " while parsing annotations in " + in.file) if (ctx.debug) ex.printStackTrace() None // ignore malformed annotations } def parseAttributes(sym: Symbol, symtype: Type)(implicit ctx: Context): Type = { def convertTo(c: Constant, pt: Type): Constant = { if (pt == defn.BooleanType && c.tag == IntTag) Constant(c.value != 0) else c convertTo pt } var newType = symtype def parseAttribute(): Unit = { val attrName = pool.getName(in.nextChar).toTypeName val attrLen = in.nextInt val end = in.bp + attrLen attrName match { case tpnme.SignatureATTR => val sig = pool.getExternalName(in.nextChar) newType = sigToType(sig, sym) if (ctx.debug && ctx.verbose) println("" + sym + "; signature = " + sig + " type = " + newType) case tpnme.SyntheticATTR => sym.setFlag(Flags.SyntheticArtifact) case tpnme.BridgeATTR => sym.setFlag(Flags.Bridge) case tpnme.DeprecatedATTR => val msg = Literal(Constant("see corresponding Javadoc for more information.")) val since = Literal(Constant("")) sym.addAnnotation(Annotation(defn.DeprecatedAnnot, msg, since)) case tpnme.ConstantValueATTR => val c = pool.getConstant(in.nextChar) val c1 = convertTo(c, symtype) if (c1 ne null) newType = ConstantType(c1) else println("failure to convert " + c + " to " + symtype); //debug case tpnme.AnnotationDefaultATTR => sym.addAnnotation(Annotation(defn.AnnotationDefaultAnnot, Nil)) // Java annotations on classes / methods / fields with RetentionPolicy.RUNTIME case tpnme.RuntimeAnnotationATTR => parseAnnotations(attrLen) // 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) case tpnme.CodeATTR => if (sym.owner is Flags.JavaTrait) { sym.resetFlag(Flags.Deferred) sym.owner.resetFlag(Flags.PureInterface) ctx.log(s"$sym in ${sym.owner} is a java8+ default method.") } in.skip(attrLen) case _ => } in.bp = end } /** * Parse the "Exceptions" attribute which denotes the exceptions * thrown by a method. */ def parseExceptions(len: Int): Unit = { val nClasses = in.nextChar for (n <- 0 until nClasses) { // FIXME: this performs an equivalent of getExceptionTypes instead of getGenericExceptionTypes (SI-7065) val cls = pool.getClassSymbol(in.nextChar.toInt) sym.addAnnotation(ThrowsAnnotation(cls.asClass)) } } /** 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): Unit = { val nAttr = in.nextChar for (n <- 0 until nAttr) parseAnnotation(in.nextChar) match { case Some(annot) => sym.addAnnotation(annot) case None => } } // begin parseAttributes for (i <- 0 until in.nextChar) { parseAttribute() } newType } /** Add synthetic constructor(s) and potentially also default getters which * reflects the fields of the annotation with given `classInfo`. * Annotations in Scala are assumed to get all their arguments as constructor * parameters. For Java annotations we need to fake it by making up the constructor. * Note that default getters have type Nothing. That's OK because we need * them only to signal that the corresponding parameter is optional. */ def addAnnotationConstructor(classInfo: Type, tparams: List[TypeSymbol] = Nil)(implicit ctx: Context): Unit = { def addDefaultGetter(attr: Symbol, n: Int) = ctx.newSymbol( owner = moduleRoot.symbol, name = DefaultGetterName(nme.CONSTRUCTOR, n), flags = attr.flags & Flags.AccessFlags, info = defn.NothingType).entered classInfo match { case classInfo @ TempPolyType(tparams, restpe) if tparams.isEmpty => addAnnotationConstructor(restpe, tparams) case classInfo: TempClassInfoType => val attrs = classInfo.decls.toList.filter(_.isTerm) val targs = tparams.map(_.typeRef) val paramNames = attrs.map(_.name.asTermName) val paramTypes = attrs.map(_.info.resultType) def addConstr(ptypes: List[Type]) = { val mtype = MethodType(paramNames, ptypes, classRoot.typeRef.appliedTo(targs)) val constrType = if (tparams.isEmpty) mtype else TempPolyType(tparams, mtype) val constr = ctx.newSymbol( owner = classRoot.symbol, name = nme.CONSTRUCTOR, flags = Flags.Synthetic, info = constrType ).entered for ((attr, i) <- attrs.zipWithIndex) if (attr.hasAnnotation(defn.AnnotationDefaultAnnot)) { constr.setFlag(Flags.HasDefaultParams) addDefaultGetter(attr, i) } } addConstr(paramTypes) // The code below added an extra constructor to annotations where the // last parameter of the constructor is an Array[X] for some X, the // array was replaced by a vararg argument. Unfortunately this breaks // inference when doing: // @Annot(Array()) // The constructor is overloaded so the expected type of `Array()` is // WildcardType, and the type parameter of the Array apply method gets // instantiated to `Nothing` instead of `X`. // I'm leaving this commented out in case we improve inference to make this work. // Note that if this is reenabled then JavaParser will also need to be modified // to add the extra constructor (this was not implemented before). /* if (paramTypes.nonEmpty) paramTypes.last match { case defn.ArrayOf(elemtp) => addConstr(paramTypes.init :+ defn.RepeatedParamType.appliedTo(elemtp)) case _ => } */ } } /** Enter own inner classes in the right scope. It needs the scopes to be set up, * and implicitly current class' superclasses. */ private def enterOwnInnerClasses()(implicit ctx: Context): Unit = { def className(name: Name): Name = { val name1 = name.toSimpleName name1.drop(name1.lastIndexOf('.') + 1) } def enterClassAndModule(entry: InnerClassEntry, file: AbstractFile, jflags: Int) = { ctx.base.loaders.enterClassAndModule( getOwner(jflags), entry.originalName, new ClassfileLoader(file), classTranslation.flags(jflags), getScope(jflags)) } for (entry <- innerClasses.values) { // create a new class member for immediate inner classes if (entry.outerName == currentClassName) { val file = ctx.platform.classPath.findBinaryFile(entry.externalName.toString) getOrElse { throw new AssertionError(entry.externalName) } enterClassAndModule(entry, file, entry.jflags) } } } /** Parse inner classes. Expects `in.bp` to point to the superclass entry. * Restores the old `bp`. * @return true iff classfile is from Scala, so no Java info needs to be read. */ def unpickleOrParseInnerClasses()(implicit ctx: Context): Option[Embedded] = { val oldbp = in.bp try { skipSuperclasses() skipMembers() // fields skipMembers() // methods val attrs = in.nextChar val attrbp = in.bp def scan(target: TypeName): Boolean = { in.bp = attrbp var i = 0 while (i < attrs && pool.getName(in.nextChar).toTypeName != target) { val attrLen = in.nextInt in.skip(attrLen) i += 1 } i < attrs } def unpickleScala(bytes: Array[Byte]): Some[Embedded] = { val unpickler = new unpickleScala2.Scala2Unpickler(bytes, classRoot, moduleRoot)(ctx) unpickler.run()(ctx.addMode(Mode.Scala2Unpickling)) Some(unpickler) } def unpickleTASTY(bytes: Array[Byte]): Some[Embedded] = { val unpickler = new tasty.DottyUnpickler(bytes) unpickler.enter(roots = Set(classRoot, moduleRoot, moduleRoot.sourceModule)) Some(unpickler) } def parseScalaSigBytes: Array[Byte] = { val tag = in.nextByte.toChar assert(tag == STRING_TAG, tag) pool getBytes in.nextChar } def parseScalaLongSigBytes: Array[Byte] = { val tag = in.nextByte.toChar assert(tag == ARRAY_TAG, tag) val stringCount = in.nextChar val entries = for (i <- 0 until stringCount) yield { val stag = in.nextByte.toChar assert(stag == STRING_TAG, stag) in.nextChar.toInt } pool.getBytes(entries.toList) } if (scan(tpnme.TASTYATTR)) { val attrLen = in.nextInt return unpickleTASTY(in.nextBytes(attrLen)) } if (scan(tpnme.RuntimeAnnotationATTR)) { val attrLen = in.nextInt val nAnnots = in.nextChar var i = 0 while (i < nAnnots) { val attrClass = pool.getType(in.nextChar).typeSymbol val nArgs = in.nextChar var j = 0 while (j < nArgs) { val argName = pool.getName(in.nextChar) if (argName == nme.bytes) if (attrClass == defn.ScalaSignatureAnnot) return unpickleScala(parseScalaSigBytes) else if (attrClass == defn.ScalaLongSignatureAnnot) return unpickleScala(parseScalaLongSigBytes) else if (attrClass == defn.TASTYSignatureAnnot) return unpickleTASTY(parseScalaSigBytes) else if (attrClass == defn.TASTYLongSignatureAnnot) return unpickleTASTY(parseScalaLongSigBytes) parseAnnotArg(skip = true) j += 1 } i += 1 } } if (scan(tpnme.InnerClassesATTR)) { val attrLen = in.nextInt val entries = in.nextChar.toInt for (i <- 0 until entries) { val innerIndex = in.nextChar val outerIndex = in.nextChar val nameIndex = in.nextChar val jflags = in.nextChar if (innerIndex != 0 && outerIndex != 0 && nameIndex != 0) { val entry = InnerClassEntry(innerIndex, outerIndex, nameIndex, jflags) innerClasses(pool.getClassName(innerIndex)) = entry } } } None } finally in.bp = oldbp } /** An entry in the InnerClasses attribute of this class file. */ case class InnerClassEntry(external: Int, outer: Int, name: Int, jflags: Int) { def externalName = pool.getClassName(external) def outerName = pool.getClassName(outer) def originalName = pool.getName(name) override def toString = originalName + " in " + outerName + "(" + externalName + ")" } object innerClasses extends scala.collection.mutable.HashMap[Name, InnerClassEntry] { /** Return the Symbol of the top level class enclosing `name`, * or 'name's symbol if no entry found for `name`. */ def topLevelClass(name: Name)(implicit ctx: Context): Symbol = { val tlName = if (isDefinedAt(name)) { var entry = this(name) while (isDefinedAt(entry.outerName)) entry = this(entry.outerName) entry.outerName } else name classNameToSymbol(tlName) } /** Return the class symbol for `externalName`. 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 `defn`. */ def classSymbol(externalName: Name)(implicit ctx: Context): Symbol = { /** Return the symbol of `innerName`, having the given `externalName`. */ def innerSymbol(externalName: Name, innerName: Name, static: Boolean): Symbol = { def getMember(sym: Symbol, name: Name)(implicit ctx: Context): Symbol = if (static) if (sym == classRoot.symbol) staticScope.lookup(name) else sym.companionModule.info.member(name).symbol else if (sym == classRoot.symbol) instanceScope.lookup(name) else sym.info.member(name).symbol innerClasses.get(externalName) match { case Some(entry) => val outerName = entry.outerName.stripModuleClassSuffix val owner = classSymbol(outerName) val result = ctx.atPhaseNotLaterThan(ctx.typerPhase) { implicit ctx => getMember(owner, innerName.toTypeName) } assert(result ne NoSymbol, i"""failure to resolve inner class: |externalName = $externalName, |outerName = $outerName, |innerName = $innerName |owner.fullName = ${owner.showFullName} |while parsing ${classfile}""") result case None => classNameToSymbol(externalName) } } get(externalName) match { case Some(entry) => innerSymbol(entry.externalName, entry.originalName, isStatic(entry.jflags)) case None => classNameToSymbol(externalName) } } } def skipAttributes(): Unit = { val attrCount = in.nextChar for (i <- 0 until attrCount) { in.skip(2); in.skip(in.nextInt) } } def skipMembers(): Unit = { val memberCount = in.nextChar for (i <- 0 until memberCount) { in.skip(6); skipAttributes() } } def skipSuperclasses(): Unit = { in.skip(2) // superclass val ifaces = in.nextChar in.skip(2 * ifaces) } protected def getOwner(flags: Int): Symbol = if (isStatic(flags)) moduleRoot.symbol else classRoot.symbol protected def getScope(flags: Int): MutableScope = if (isStatic(flags)) staticScope else instanceScope private def setPrivateWithin(denot: SymDenotation, jflags: Int)(implicit ctx: Context): Unit = { if ((jflags & (JAVA_ACC_PRIVATE | JAVA_ACC_PUBLIC)) == 0) denot.privateWithin = denot.enclosingPackageClass } private def isPrivate(flags: Int) = (flags & JAVA_ACC_PRIVATE) != 0 private def isStatic(flags: Int) = (flags & JAVA_ACC_STATIC) != 0 private def hasAnnotation(flags: Int) = (flags & JAVA_ACC_ANNOTATION) != 0 class ConstantPool { private val len = in.nextChar private val starts = new Array[Int](len) private val values = new Array[AnyRef](len) private val internalized = new Array[SimpleTermName](len) { var i = 1 while (i < starts.length) { starts(i) = in.bp i += 1 (in.nextByte.toInt: @switch) match { case CONSTANT_UTF8 | CONSTANT_UNICODE => in.skip(in.nextChar) case CONSTANT_CLASS | CONSTANT_STRING | CONSTANT_METHODTYPE => in.skip(2) case CONSTANT_METHODHANDLE => in.skip(3) case CONSTANT_FIELDREF | CONSTANT_METHODREF | CONSTANT_INTFMETHODREF | CONSTANT_NAMEANDTYPE | CONSTANT_INTEGER | CONSTANT_FLOAT | CONSTANT_INVOKEDYNAMIC => in.skip(4) case CONSTANT_LONG | CONSTANT_DOUBLE => in.skip(8) i += 1 case _ => errorBadTag(in.bp - 1) } } } /** Return the name found at given index. */ def getName(index: Int): SimpleTermName = { if (index <= 0 || len <= index) errorBadIndex(index) values(index) match { case name: SimpleTermName => name case null => val start = starts(index) if (in.buf(start).toInt != CONSTANT_UTF8) errorBadTag(start) val name = termName(in.buf, start + 3, in.getChar(start + 1)) values(index) = name name } } /** Return the name found at given index in the constant pool, with '/' replaced by '.'. */ def getExternalName(index: Int): SimpleTermName = { if (index <= 0 || len <= index) errorBadIndex(index) if (internalized(index) == null) internalized(index) = getName(index).replace('/', '.') internalized(index) } def getClassSymbol(index: Int)(implicit ctx: Context): Symbol = { if (index <= 0 || len <= index) errorBadIndex(index) var c = values(index).asInstanceOf[Symbol] if (c eq null) { val start = starts(index) if (in.buf(start).toInt != CONSTANT_CLASS) errorBadTag(start) val name = getExternalName(in.getChar(start + 1)) if (name.endsWith("$") && (name ne nme.nothingRuntimeClass) && (name ne nme.nullRuntimeClass)) // Null$ and Nothing$ ARE classes c = ctx.requiredModule(name.dropRight(1)) else c = classNameToSymbol(name) values(index) = c } c } /** 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): SimpleTermName = { val start = starts(index) if (in.buf(start).toInt != CONSTANT_CLASS) errorBadTag(start) getExternalName(in.getChar(start + 1)) } /** Return a name and a type at the given index. */ private def getNameAndType(index: Int, ownerTpe: Type)(implicit ctx: Context): (Name, Type) = { if (index <= 0 || len <= index) errorBadIndex(index) var p = values(index).asInstanceOf[(Name, Type)] if (p eq null) { val start = starts(index) if (in.buf(start).toInt != CONSTANT_NAMEANDTYPE) errorBadTag(start) val name = getName(in.getChar(start + 1).toInt) var tpe = getType(in.getChar(start + 3).toInt) // fix the return type, which is blindly set to the class currently parsed if (name == nme.CONSTRUCTOR) tpe match { case tp: MethodType => tp.derivedLambdaType(tp.paramNames, tp.paramInfos, ownerTpe) } p = (name, tpe) values(index) = p } p } /** 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)(implicit ctx: Context): Type = { if (index <= 0 || len <= index) errorBadIndex(index) val value = values(index) var c: Type = null if (value eq null) { val start = starts(index) if (in.buf(start).toInt != CONSTANT_CLASS) errorBadTag(start) val name = getExternalName(in.getChar(start + 1)) if (name.firstPart(0) == ARRAY_TAG) { c = sigToType(name) values(index) = c } else { val sym = classNameToSymbol(name) values(index) = sym c = sym.typeRef } } else c = value match { case tp: Type => tp case cls: Symbol => cls.typeRef } c } def getType(index: Int)(implicit ctx: Context): Type = sigToType(getExternalName(index)) def getSuperClass(index: Int)(implicit ctx: Context): Symbol = { assert(index != 0, "attempt to parse java.lang.Object from classfile") getClassSymbol(index) } def getConstant(index: Int)(implicit ctx: Context): Constant = { if (index <= 0 || len <= index) errorBadIndex(index) var value = values(index) if (value eq null) { val start = starts(index) value = (in.buf(start).toInt: @switch) match { case CONSTANT_STRING => Constant(getName(in.getChar(start + 1).toInt).toString) case CONSTANT_INTEGER => Constant(in.getInt(start + 1)) case CONSTANT_FLOAT => Constant(in.getFloat(start + 1)) case CONSTANT_LONG => Constant(in.getLong(start + 1)) case CONSTANT_DOUBLE => Constant(in.getDouble(start + 1)) case CONSTANT_CLASS => getClassOrArrayType(index).typeSymbol case _ => errorBadTag(start) } values(index) = value } value match { case ct: Constant => ct case cls: Symbol => Constant(cls.typeRef) case arr: Type => Constant(arr) } } 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 } def getBytes(index: Int): Array[Byte] = { if (index <= 0 || len <= index) errorBadIndex(index) var value = values(index).asInstanceOf[Array[Byte]] if (value eq null) { val start = starts(index) if (in.buf(start).toInt != CONSTANT_UTF8) errorBadTag(start) val len = in.getChar(start + 1) val bytes = new Array[Byte](len) System.arraycopy(in.buf, start + 3, bytes, 0, len) value = getSubArray(bytes) values(index) = value } value } def getBytes(indices: List[Int]): Array[Byte] = { assert(!indices.isEmpty, indices) var value = values(indices.head).asInstanceOf[Array[Byte]] if (value eq null) { val bytesBuffer = ArrayBuffer.empty[Byte] for (index <- indices) { if (index <= 0 || ConstantPool.this.len <= index) errorBadIndex(index) val start = starts(index) if (in.buf(start).toInt != CONSTANT_UTF8) errorBadTag(start) val len = in.getChar(start + 1) bytesBuffer ++= in.buf.view(start + 3, start + 3 + len) } value = getSubArray(bytesBuffer.toArray) values(indices.head) = value } value } /** Throws an exception signaling a bad constant index. */ private def errorBadIndex(index: Int) = throw new RuntimeException("bad constant pool index: " + index + " at pos: " + in.bp) /** Throws an exception signaling a bad tag at given address. */ private def errorBadTag(start: Int) = throw new RuntimeException("bad constant pool tag " + in.buf(start) + " at byte " + start) } }