diff --git a/compiler/src/dotty/tools/dotc/core/Contexts.scala b/compiler/src/dotty/tools/dotc/core/Contexts.scala index 11c8c135b266..4086003645ed 100644 --- a/compiler/src/dotty/tools/dotc/core/Contexts.scala +++ b/compiler/src/dotty/tools/dotc/core/Contexts.scala @@ -14,7 +14,7 @@ import Uniques._ import ast.Trees._ import ast.untpd import Flags.GivenOrImplicit -import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet} +import util.{NoSource, SimpleIdentityMap, SourceFile, HashSet, ReusableInstance} import typer.{Implicits, ImportInfo, Inliner, SearchHistory, SearchRoot, TypeAssigner, Typer, Nullables} import Nullables.{NotNullInfo, given} import Implicits.ContextualImplicits @@ -26,6 +26,7 @@ import scala.io.Codec import collection.mutable import printing._ import config.{JavaPlatform, SJSPlatform, Platform, ScalaSettings} +import classfile.ReusableDataReader import scala.annotation.internal.sharable @@ -912,6 +913,8 @@ object Contexts { private var charArray = new Array[Char](256) + private[core] val reusableDataReader = ReusableInstance(new ReusableDataReader()) + def sharedCharArray(len: Int): Array[Char] = while len > charArray.length do charArray = new Array[Char](charArray.length * 2) diff --git a/compiler/src/dotty/tools/dotc/core/classfile/AbstractFileReader.scala b/compiler/src/dotty/tools/dotc/core/classfile/AbstractFileReader.scala index 4ade0fa44903..d4f781e22126 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/AbstractFileReader.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/AbstractFileReader.scala @@ -5,6 +5,7 @@ package classfile import java.lang.Float.intBitsToFloat import java.lang.Double.longBitsToDouble +import java.io.{ByteArrayInputStream, DataInputStream} import io.AbstractFile @@ -14,16 +15,22 @@ import io.AbstractFile * @author Philippe Altherr * @version 1.0, 23/03/2004 */ -class AbstractFileReader(val file: AbstractFile) { - - /** the buffer containing the file - */ - val buf: Array[Byte] = file.toByteArray +final class AbstractFileReader(val buf: Array[Byte]) extends DataReader { + def this(file: AbstractFile) = this(file.toByteArray) /** the current input pointer */ var bp: Int = 0 + /** extract a byte at position bp from buf + */ + def getByte(mybp: Int): Byte = + buf(mybp) + + def getBytes(mybp: Int, bytes: Array[Byte]): Unit = { + System.arraycopy(buf, mybp, bytes, 0, bytes.length) + } + /** return byte at offset 'pos' */ @throws(classOf[IndexOutOfBoundsException]) @@ -60,13 +67,13 @@ class AbstractFileReader(val file: AbstractFile) { /** extract a character at position bp from buf */ def getChar(mybp: Int): Char = - (((buf(mybp) & 0xff) << 8) + (buf(mybp + 1) & 0xff)).toChar + (((getByte(mybp) & 0xff) << 8) + (getByte(mybp+1) & 0xff)).toChar /** extract an integer at position bp from buf */ def getInt(mybp: Int): Int = - ((buf(mybp ) & 0xff) << 24) + ((buf(mybp + 1) & 0xff) << 16) + - ((buf(mybp + 2) & 0xff) << 8) + (buf(mybp + 3) & 0xff) + ((getByte(mybp) & 0xff) << 24) + ((getByte(mybp + 1) & 0xff) << 16) + + ((getByte(mybp + 2) & 0xff) << 8) + (getByte(mybp + 3) & 0xff) /** extract a long integer at position bp from buf */ @@ -81,6 +88,9 @@ class AbstractFileReader(val file: AbstractFile) { */ def getDouble(mybp: Int): Double = longBitsToDouble(getLong(mybp)) + def getUTF(mybp: Int, len: Int): String = + new DataInputStream(new ByteArrayInputStream(buf, mybp, len)).readUTF + /** skip next 'n' bytes */ def skip(n: Int): Unit = { bp += n } diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala index 8316c83dad1d..5edfaa29dc9c 100644 --- a/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala +++ b/compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala @@ -62,8 +62,6 @@ class ClassfileParser( import ClassfileConstants._ import ClassfileParser._ - protected val in: AbstractFileReader = new AbstractFileReader(classfile) - protected val staticModule: Symbol = moduleRoot.sourceModule(using ictx) protected val instanceScope: MutableScope = newScope // the scope of all instance definitions @@ -81,13 +79,16 @@ class ClassfileParser( private def currentIsTopLevel(using Context) = classRoot.owner.is(Flags.PackageClass) private def mismatchError(className: SimpleName) = - throw new IOException(s"class file '${in.file.canonicalPath}' has location not matching its contents: contains class $className") + throw new IOException(s"class file '${classfile.canonicalPath}' has location not matching its contents: contains class $className") - def run()(using Context): Option[Embedded] = try { + def run()(using Context): Option[Embedded] = try ctx.base.reusableDataReader.withInstance { reader => + implicit val reader2 = reader.reset(classfile) report.debuglog("[class] >> " + classRoot.fullName) parseHeader() this.pool = new ConstantPool - parseClass() + val res = parseClass() + this.pool = null + res } catch { case e: RuntimeException => @@ -97,34 +98,34 @@ class ClassfileParser( |${Option(e.getMessage).getOrElse("")}""") } - private def parseHeader(): Unit = { + private def parseHeader()(using in: DataReader): 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)}") + throw new IOException(s"class file '${classfile}' 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") + s"class file '${classfile}' 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)(using Context): Symbol = innerClasses.get(name) match { + def classNameToSymbol(name: Name)(using Context): Symbol = innerClasses.get(name.toString) match { case Some(entry) => innerClasses.classSymbol(entry) case None => requiredClass(name) } var sawPrivateConstructor: Boolean = false - def parseClass()(using Context): Option[Embedded] = { + def parseClass()(using ctx: Context, in: DataReader): 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) + currentClassName = pool.getClassName(nameIdx).name if (currentIsTopLevel && currentClassName != classRoot.fullName.toSimpleName && @@ -162,6 +163,7 @@ class ClassfileParser( superType :: ifaces } + val result = unpickleOrParseInnerClasses() if (!result.isDefined) { var classInfo: Type = TempClassInfoType(parseParents, instanceScope, classRoot.symbol) @@ -187,7 +189,7 @@ class ClassfileParser( setClassInfo(moduleRoot, staticInfo, fromScala2 = false) - classInfo = parseAttributes(classRoot.symbol, classInfo) + classInfo = parseAttributes(classRoot.symbol).complete(classInfo) if (isAnnotation) // classInfo must be a TempClassInfoType and not a TempPolyType, // because Java annotations cannot have type parameters. @@ -217,90 +219,91 @@ class ClassfileParser( } } - def parseMember(method: Boolean)(using Context): Unit = { + def parseMember(method: Boolean)(using ctx: Context, in: DataReader): 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.isOneOf(Flags.PrivateOrArtifact) || name == nme.CONSTRUCTOR) { + val preName = pool.getName(in.nextChar) + if (!sflags.isOneOf(Flags.PrivateOrArtifact) || preName.name == nme.CONSTRUCTOR) { + val sig = pool.getExternalName(in.nextChar).value + val completer = MemberCompleter(preName.name, jflags, sig) val member = newSymbol( - getOwner(jflags), name, sflags, memberCompleter, + getOwner(jflags), preName.name, sflags, completer, getPrivateWithin(jflags), coord = start) + + completer.attrCompleter = parseAttributes(member) + getScope(jflags).enter(member) + + } + else { + in.nextChar // info + skipAttributes() } - // skip rest of member for now - in.nextChar // info - skipAttributes() } - val memberCompleter: LazyType = new LazyType { + class MemberCompleter(name: SimpleName, jflags: Int, sig: String) extends LazyType { + var attrCompleter: AttributeCompleter = null def complete(denot: SymDenotation)(using 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 and trailing access tag for - * private inner constructors. - */ - def normalizeConstructorParams() = innerClasses.get(currentClassName) match { - case Some(entry) if !isStatic(entry.jflags) => - val mt @ MethodTpe(paramNames, paramTypes, resultType) = denot.info - var normalizedParamNames = paramNames.tail - var normalizedParamTypes = paramTypes.tail - if ((jflags & JAVA_ACC_SYNTHETIC) != 0) { - // SI-7455 strip trailing dummy argument ("access constructor tag") from synthetic constructors which - // are added when an inner class needs to access a private constructor. - normalizedParamNames = paramNames.dropRight(1) - normalizedParamTypes = paramTypes.dropRight(1) - } - denot.info = mt.derivedLambdaType(normalizedParamNames, normalizedParamTypes, 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 rt = classRoot.typeRef appliedTo (classRoot.typeParams map (_.typeRef)) - - def resultType(tpe: Type): Type = tpe match { - case mt @ MethodType(paramNames) => mt.derivedLambdaType(paramNames, mt.paramInfos, rt) - case pt : PolyType => pt.derivedLambdaType(pt.paramNames, pt.paramInfos, resultType(pt.resType)) + val sym = denot.symbol + val isEnum = (jflags & JAVA_ACC_ENUM) != 0 + val isConstructor = name eq nme.CONSTRUCTOR + + /** Strip leading outer param from constructor and trailing access tag for + * private inner constructors. + */ + def normalizeConstructorParams() = innerClasses.get(currentClassName.toString) match { + case Some(entry) if !isStatic(entry.jflags) => + val mt @ MethodTpe(paramNames, paramTypes, resultType) = denot.info + var normalizedParamNames = paramNames.tail + var normalizedParamTypes = paramTypes.tail + if ((jflags & JAVA_ACC_SYNTHETIC) != 0) { + // SI-7455 strip trailing dummy argument ("access constructor tag") from synthetic constructors which + // are added when an inner class needs to access a private constructor. + normalizedParamNames = paramNames.dropRight(1) + normalizedParamTypes = paramTypes.dropRight(1) } + denot.info = mt.derivedLambdaType(normalizedParamNames, normalizedParamTypes, 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 rt = classRoot.typeRef appliedTo (classRoot.typeParams map (_.typeRef)) - denot.info = resultType(denot.info) - addConstructorTypeParams(denot) + def resultType(tpe: Type): Type = tpe match { + case mt @ MethodType(paramNames) => mt.derivedLambdaType(paramNames, mt.paramInfos, rt) + case pt : PolyType => pt.derivedLambdaType(pt.paramNames, pt.paramInfos, resultType(pt.resType)) } - val isVarargs = denot.is(Flags.Method) && (jflags & JAVA_ACC_VARARGS) != 0 - denot.info = pool.getType(in.nextChar, isVarargs) - if (isConstructor) normalizeConstructorParams() - denot.info = translateTempPoly(parseAttributes(sym, denot.info, isVarargs)) - if (isConstructor) normalizeConstructorInfo() + denot.info = resultType(denot.info) + addConstructorTypeParams(denot) + } + + val isVarargs = denot.is(Flags.Method) && (jflags & JAVA_ACC_VARARGS) != 0 + denot.info = sigToType(sig, isVarargs = isVarargs) + if (isConstructor) normalizeConstructorParams() + denot.info = translateTempPoly(attrCompleter.complete(denot.info, isVarargs)) + if (isConstructor) normalizeConstructorInfo() - if (ctx.explicitNulls) denot.info = JavaNullInterop.nullifyMember(denot.symbol, denot.info, isEnum) + if (ctx.explicitNulls) denot.info = JavaNullInterop.nullifyMember(denot.symbol, denot.info, isEnum) - // seal java enums - if (isEnum) { - val enumClass = sym.owner.linkedClass - if (!enumClass.exists) - report.warning(s"no linked class for java enum $sym in ${sym.owner}. A referencing class file might be missing an InnerClasses entry.") - else { - if (!enumClass.is(Flags.Sealed)) enumClass.setFlag(Flags.AbstractSealed) - enumClass.addAnnotation(Annotation.Child(sym, NoSpan)) - } + // seal java enums + if (isEnum) { + val enumClass = sym.owner.linkedClass + if (!enumClass.exists) + report.warning(s"no linked class for java enum $sym in ${sym.owner}. A referencing class file might be missing an InnerClasses entry.") + else { + if (!enumClass.is(Flags.Sealed)) enumClass.setFlag(Flags.AbstractSealed) + enumClass.addAnnotation(Annotation.Child(sym, NoSpan)) } } - finally - in.bp = oldbp + } } @@ -317,7 +320,22 @@ class ClassfileParser( case BOOL_TAG => defn.BooleanType } - private def sigToType(sig: SimpleName, owner: Symbol = null, isVarargs: Boolean = false)(using Context): Type = { + /** As specified in https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.16.1, + * an annotation argument of type boolean, byte, char or short will + * be represented as a CONSTANT_INTEGER, so we need to convert it to + * produce a correctly-typed tree. We need to do this each time the + * constant is accessed instead of storing the result of the + * conversion in the `values` cache, because the same constant might + * be used for annotation arguments of different type. + */ + def convertTo(ct: Constant, pt: Type)(using Context): Constant = { + if (pt eq defn.BooleanType) && ct.tag == IntTag then + Constant(ct.value != 0) + else + ct.convertTo(pt) + } + + private def sigToType(sig: String, owner: Symbol = null, isVarargs: Boolean = false)(using Context): Type = { var index = 0 val end = sig.length def accept(ch: Char): Unit = { @@ -327,7 +345,7 @@ class ClassfileParser( def subName(isDelimiter: Char => Boolean): SimpleName = { val start = index while (!isDelimiter(sig(index))) { index += 1 } - sig.slice(start, index) + termName(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. @@ -500,7 +518,7 @@ class ClassfileParser( } // sigToType - def parseAnnotArg(skip: Boolean = false)(using Context): Option[untpd.Tree] = { + def parseAnnotArg(skip: Boolean = false)(using ctx: Context, in: DataReader): Option[untpd.Tree] = { // If we encounter an empty array literal, we need the type of the corresponding // parameter to properly type it, but that would require forcing the annotation @@ -516,9 +534,12 @@ class ClassfileParser( tag match { case STRING_TAG => - if (skip) None else Some(lit(Constant(pool.getName(index).toString))) + if (skip) None else Some(lit(Constant(pool.getName(index).value))) case BOOL_TAG | BYTE_TAG | CHAR_TAG | SHORT_TAG => - if (skip) None else Some(lit(pool.getConstant(index, constantTagToType(tag)))) + if (skip) None else { + val constant = convertTo(pool.getConstant(index), constantTagToType(tag)) + Some(lit(constant)) + } case INT_TAG | LONG_TAG | FLOAT_TAG | DOUBLE_TAG => if (skip) None else Some(lit(pool.getConstant(index))) case CLASS_TAG => @@ -530,7 +551,7 @@ class ClassfileParser( None else { val enumModuleClass = enumClassTp.classSymbol.companionModule - Some(Select(ref(enumModuleClass), enumCaseName)) + Some(Select(ref(enumModuleClass), enumCaseName.name)) } case ARRAY_TAG => val arr = new ArrayBuffer[Tree]() @@ -565,14 +586,14 @@ class ClassfileParser( /** Parse and return a single annotation. If it is malformed, * return None. */ - def parseAnnotation(attrNameIndex: Char, skip: Boolean = false)(using Context): Option[ClassfileAnnotation] = try { + def parseAnnotation(attrNameIndex: Char, skip: Boolean = false)(using ctx: Context, in: DataReader): Option[ClassfileAnnotation] = try { val attrType = pool.getType(attrNameIndex) attrType match case tp: TypeRef => // Silently ignore missing annotation classes like javac if tp.denot.infoOrCompleter.isInstanceOf[StubInfo] then if ctx.debug then - report.warning(i"Error while parsing annotations in ${in.file}: annotation class $tp not present on classpath") + report.warning(i"Error while parsing annotations in ${classfile}: annotation class $tp not present on classpath") return None case _ => @@ -582,7 +603,7 @@ class ClassfileParser( for (i <- 0 until nargs) { val name = pool.getName(in.nextChar) parseAnnotArg(skip) match { - case Some(arg) => argbuf += untpd.NamedArg(name, arg) + case Some(arg) => argbuf += untpd.NamedArg(name.name, arg) case None => hasError = !skip } } @@ -598,39 +619,85 @@ class ClassfileParser( // 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. - report.warning("Caught: " + ex + " while parsing annotations in " + in.file) + report.warning("Caught: " + ex + " while parsing annotations in " + classfile) if (ctx.debug) ex.printStackTrace() None // ignore malformed annotations } - def parseAttributes(sym: Symbol, symtype: Type, isVarargs: Boolean = false)(using Context): Type = { - var newType = symtype + /** A completer for attributes + * + * Top-level classes complete attributes eagerly, while members complete lazily. + * + * @note We cannot simply store the bytes of attributes, as the bytes may + * contain references to the constant pool, where the constants are loaded + * lazily. + */ + class AttributeCompleter(sym: Symbol) { + var sig: String = null + var constant: Constant = null + var exceptions: List[NameOrString] = Nil + var annotations: List[Annotation] = Nil + def complete(tp: Type, isVarargs: Boolean = false)(using Context): Type = { + val updatedType = + if sig == null then tp + else { + val newType = sigToType(sig, sym, isVarargs) + if (ctx.debug && ctx.verbose) + println("" + sym + "; signature = " + sig + " type = " + newType) + newType + } + + val newType = + if this.constant != null then + val ct = convertTo(this.constant, updatedType) + if ct != null then ConstantType(ct) else updatedType + else updatedType + + annotations.foreach(annot => sym.addAnnotation(annot)) + + exceptions.foreach { ex => + val cls = getClassSymbol(ex.name) + sym.addAnnotation(ThrowsAnnotation(cls.asClass)) + } + + cook.apply(newType) + } + } + + def parseAttributes(sym: Symbol)(using ctx: Context, in: DataReader): AttributeCompleter = { + val res = new AttributeCompleter(sym) def parseAttribute(): Unit = { - val attrName = pool.getName(in.nextChar).toTypeName + val attrName = pool.getName(in.nextChar).name.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, isVarargs) - if (ctx.debug && ctx.verbose) - println("" + sym + "; signature = " + sig + " type = " + newType) + res.sig = sig.value + 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)) + res.annotations ::= Annotation.deferredSymAndTree(defn.DeprecatedAnnot) { + New(defn.DeprecatedAnnot.typeRef, msg :: since :: Nil) + } + case tpnme.ConstantValueATTR => - val c = pool.getConstant(in.nextChar, symtype) - if (c ne null) newType = ConstantType(c) + val c = pool.getConstant(in.nextChar) + if (c ne null) res.constant = c else report.warning(s"Invalid constant in attribute of ${sym.showLocated} while parsing ${classfile}") + case tpnme.AnnotationDefaultATTR => sym.addAnnotation(Annotation(defn.AnnotationDefaultAnnot, Nil)) + // Java annotations on classes / methods / fields with RetentionPolicy.RUNTIME case tpnme.RuntimeVisibleAnnotationATTR | tpnme.RuntimeInvisibleAnnotationATTR => @@ -646,12 +713,13 @@ class ClassfileParser( parseExceptions(attrLen) case tpnme.CodeATTR => - if (sym.owner.isAllOf(Flags.JavaInterface)) { + in.skip(attrLen) + // flag test will trigger completion and cycles, thus have to be lazy + if (sym.owner.flagsUNSAFE.isAllOf(Flags.JavaInterface)) { sym.resetFlag(Flags.Deferred) sym.owner.resetFlag(Flags.PureInterface) - report.log(s"$sym in ${sym.owner} is a java8+ default method.") + report.log(s"$sym in ${sym.owner} is a java 8+ default method.") } - in.skip(attrLen) case _ => } @@ -666,11 +734,12 @@ class ClassfileParser( 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)) + val cls = pool.getClassName(in.nextChar.toInt) + res.exceptions ::= cls } } + /** 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 = { @@ -687,7 +756,7 @@ class ClassfileParser( for (i <- 0 until in.nextChar) parseAttribute() - cook.apply(newType) + res } /** Annotations in Scala are assumed to get all their arguments as constructor @@ -713,12 +782,7 @@ class ClassfileParser( /** Enter own inner classes in the right scope. It needs the scopes to be set up, * and implicitly current class' superclasses. */ - private def enterOwnInnerClasses()(using Context): Unit = { - def className(name: Name): Name = { - val name1 = name.toSimpleName - name1.drop(name1.lastIndexOf('.') + 1) - } - + private def enterOwnInnerClasses()(using Context, DataReader): Unit = { def enterClassAndModule(entry: InnerClassEntry, file: AbstractFile, jflags: Int) = SymbolLoaders.enterClassAndModule( getOwner(jflags), @@ -729,7 +793,7 @@ class ClassfileParser( for entry <- innerClasses.valuesIterator do // create a new class member for immediate inner classes - if entry.outerName == currentClassName then + if entry.outer.name == currentClassName then val file = ctx.platform.classPath.findClassFile(entry.externalName.toString) getOrElse { throw new AssertionError(entry.externalName) } @@ -744,7 +808,7 @@ class ClassfileParser( * Restores the old `bp`. * @return true iff classfile is from Scala, so no Java info needs to be read. */ - def unpickleOrParseInnerClasses()(using Context): Option[Embedded] = { + def unpickleOrParseInnerClasses()(using ctx: Context, in: DataReader): Option[Embedded] = { val oldbp = in.bp try { skipSuperclasses() @@ -756,7 +820,7 @@ class ClassfileParser( def scan(target: TypeName): Boolean = { in.bp = attrbp var i = 0 - while (i < attrs && pool.getName(in.nextChar).toTypeName != target) { + while (i < attrs && pool.getName(in.nextChar).name.toTypeName != target) { val attrLen = in.nextInt in.skip(attrLen) i += 1 @@ -884,7 +948,7 @@ class ClassfileParser( var j = 0 while (j < nArgs) { val argName = pool.getName(in.nextChar) - if (argName == nme.bytes) { + if (argName.name == nme.bytes) { if (attrClass == defn.ScalaSignatureAnnot) return unpickleScala(parseScalaSigBytes) else if (attrClass == defn.ScalaLongSignatureAnnot) @@ -910,8 +974,11 @@ class ClassfileParser( 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 + val inner = pool.getClassName(innerIndex) + val outer = pool.getClassName(outerIndex) + val name = pool.getName(nameIndex) + val entry = InnerClassEntry(inner, outer, name, jflags) + innerClasses(inner.value) = entry } } } @@ -921,20 +988,20 @@ class ClassfileParser( } /** An entry in the InnerClasses attribute of this class file. */ - case class InnerClassEntry(external: Int, outer: Int, name: Int, jflags: Int) { - def externalName: SimpleName = pool.getClassName(external) - def outerName: SimpleName = pool.getClassName(outer) - def originalName: SimpleName = pool.getName(name) + case class InnerClassEntry(external: NameOrString, outer: NameOrString, name: NameOrString, jflags: Int) { + def externalName = external.value + def outerName = outer.value + def originalName = name.name - override def toString: String = - s"$originalName in $outerName($externalName)" + // The name of the outer class, without its trailing $ if it has one. + def strippedOuter = outer.name.stripModuleClassSuffix } - object innerClasses extends util.HashMap[Name, InnerClassEntry] { + private object innerClasses extends util.HashMap[String, 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)(using Context): Symbol = { + def topLevelClass(name: String)(using Context): Symbol = { val tlName = if (contains(name)) { var entry = this(name) while (contains(entry.outerName)) @@ -943,7 +1010,7 @@ class ClassfileParser( } else name - classNameToSymbol(tlName) + classNameToSymbol(tlName.toTypeName) } /** Return the class symbol for `entry`. It looks it up in its outer class. @@ -965,7 +1032,7 @@ class ClassfileParser( else sym.info.member(name).symbol - val outerName = entry.outerName.stripModuleClassSuffix + val outerName = entry.strippedOuter val innerName = entry.originalName val owner = classNameToSymbol(outerName) val result = atPhase(typerPhase)(getMember(owner, innerName.toTypeName)) @@ -980,21 +1047,21 @@ class ClassfileParser( } } - def skipAttributes(): Unit = { + def skipAttributes()(using in: DataReader): Unit = { val attrCount = in.nextChar for (i <- 0 until attrCount) { in.skip(2); in.skip(in.nextInt) } } - def skipMembers(): Unit = { + def skipMembers()(using in: DataReader): Unit = { val memberCount = in.nextChar for (i <- 0 until memberCount) { in.skip(6); skipAttributes() } } - def skipSuperclasses(): Unit = { + def skipSuperclasses()(using in: DataReader): Unit = { in.skip(2) // superclass val ifaces = in.nextChar in.skip(2 * ifaces) @@ -1016,11 +1083,25 @@ class ClassfileParser( private def isStatic(flags: Int) = (flags & JAVA_ACC_STATIC) != 0 private def hasAnnotation(flags: Int) = (flags & JAVA_ACC_ANNOTATION) != 0 - class ConstantPool { + protected class NameOrString(val value: String) { + private var _name: SimpleName = null + def name: SimpleName = { + if (_name eq null) _name = termName(value) + _name + } + } + + def getClassSymbol(name: SimpleName)(using Context): Symbol = + if (name.endsWith("$") && (name ne nme.nothingRuntimeClass) && (name ne nme.nullRuntimeClass)) + // Null$ and Nothing$ ARE classes + requiredModule(name.dropRight(1)) + else classNameToSymbol(name) + + class ConstantPool(using in: DataReader) { private val len = in.nextChar private val starts = new Array[Int](len) private val values = new Array[AnyRef](len) - private val internalized = new Array[SimpleName](len) + private val internalized = new Array[NameOrString](len) { var i = 1 while (i < starts.length) { @@ -1047,47 +1128,41 @@ class ClassfileParser( } /** Return the name found at given index. */ - def getName(index: Int): SimpleName = { + def getName(index: Int)(using in: DataReader): NameOrString = { if (index <= 0 || len <= index) errorBadIndex(index) values(index) match { - case name: SimpleName => name + case name: NameOrString => name case null => val start = starts(index) - if (in.buf(start).toInt != CONSTANT_UTF8) errorBadTag(start) + if (in.getByte(start).toInt != CONSTANT_UTF8) errorBadTag(start) val len = in.getChar(start + 1).toInt - val name = termName(fromMUTF8(in.buf, start + 1, len + 2)) + val name = new NameOrString(in.getUTF(start + 1, len + 2)) values(index) = name name } } - private def fromMUTF8(bytes: Array[Byte], offset: Int, len: Int): String = - new DataInputStream(new ByteArrayInputStream(bytes, offset, len)).readUTF - /** Return the name found at given index in the constant pool, with '/' replaced by '.'. */ - def getExternalName(index: Int): SimpleName = { + def getExternalName(index: Int)(using in: DataReader): NameOrString = { if (index <= 0 || len <= index) errorBadIndex(index) if (internalized(index) == null) - internalized(index) = getName(index).replace('/', '.') + internalized(index) = new NameOrString(getName(index).value.replace('/', '.')) internalized(index) } - def getClassSymbol(index: Int)(using Context): Symbol = { + def getClassSymbol(index: Int)(using ctx: Context, in: DataReader): 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) + if (in.getByte(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 = requiredModule(name.dropRight(1)) - else c = classNameToSymbol(name) + c = ClassfileParser.this.getClassSymbol(name.name) values(index) = c } c @@ -1096,9 +1171,9 @@ class ClassfileParser( /** 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): SimpleName = { + def getClassName(index: Int)(using in: DataReader): NameOrString = { val start = starts(index) - if (in.buf(start).toInt != CONSTANT_CLASS) errorBadTag(start) + if (in.getByte(start).toInt != CONSTANT_CLASS) errorBadTag(start) getExternalName(in.getChar(start + 1)) } @@ -1106,20 +1181,20 @@ class ClassfileParser( * arrays are considered to be class types, they might * appear as entries in 'newarray' or 'cast' opcodes. */ - def getClassOrArrayType(index: Int)(using Context): Type = { + def getClassOrArrayType(index: Int)(using ctx: Context, in: DataReader): 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) + if (in.getByte(start).toInt != CONSTANT_CLASS) errorBadTag(start) val name = getExternalName(in.getChar(start + 1)) - if (name.firstPart(0) == ARRAY_TAG) { - c = sigToType(name) + if (name.value.charAt(0) == ARRAY_TAG) { + c = sigToType(name.value) values(index) = c } else { - val sym = classNameToSymbol(name) + val sym = classNameToSymbol(name.name) values(index) = sym c = sym.typeRef } @@ -1131,22 +1206,22 @@ class ClassfileParser( c } - def getType(index: Int, isVarargs: Boolean = false)(using Context): Type = - sigToType(getExternalName(index), isVarargs = isVarargs) + def getType(index: Int, isVarargs: Boolean = false)(using Context, DataReader): Type = + sigToType(getExternalName(index).value, isVarargs = isVarargs) - def getSuperClass(index: Int)(using Context): Symbol = { + def getSuperClass(index: Int)(using Context, DataReader): Symbol = { assert(index != 0, "attempt to parse java.lang.Object from classfile") getClassSymbol(index) } - def getConstant(index: Int, pt: Type = WildcardType)(using Context): Constant = { + def getConstant(index: Int)(using ctx: Context, in: DataReader): 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 { + value = (in.getByte(start).toInt: @switch) match { case CONSTANT_STRING => - Constant(getName(in.getChar(start + 1).toInt).toString) + Constant(getName(in.getChar(start + 1).toInt).value) case CONSTANT_INTEGER => Constant(in.getInt(start + 1)) case CONSTANT_FLOAT => @@ -1163,21 +1238,7 @@ class ClassfileParser( values(index) = value } value match { - case ct: Constant => - if pt ne WildcardType then - // As specified in https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.16.1, - // an annotation argument of type boolean, byte, char or short will - // be represented as a CONSTANT_INTEGER, so we need to convert it to - // produce a correctly-typed tree. We need to do this each time the - // constant is accessed instead of storing the result of the - // conversion in the `values` cache, because the same constant might - // be used for annotation arguments of different type. - if (pt eq defn.BooleanType) && ct.tag == IntTag then - Constant(ct.value != 0) - else - ct.convertTo(pt) - else - ct + case ct: Constant => ct case cls: Symbol => Constant(cls.typeRef) case arr: Type => Constant(arr) } @@ -1190,22 +1251,22 @@ class ClassfileParser( arr } - def getBytes(index: Int): Array[Byte] = { + def getBytes(index: Int)(using in: DataReader): 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) + if (in.getByte(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) + in.getBytes(start + 3, bytes) value = getSubArray(bytes) values(index) = value } value } - def getBytes(indices: List[Int]): Array[Byte] = { + def getBytes(indices: List[Int])(using in: DataReader): Array[Byte] = { assert(!indices.isEmpty, indices) var value = values(indices.head).asInstanceOf[Array[Byte]] if (value eq null) { @@ -1213,9 +1274,11 @@ class ClassfileParser( 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) + if (in.getByte(start).toInt != CONSTANT_UTF8) errorBadTag(start) val len = in.getChar(start + 1) - bytesBuffer ++= in.buf.view.slice(start + 3, start + 3 + len) + val buf = new Array[Byte](len) + in.getBytes(start + 3, buf) + bytesBuffer ++= buf } value = getSubArray(bytesBuffer.toArray) values(indices.head) = value @@ -1224,11 +1287,11 @@ class ClassfileParser( } /** Throws an exception signaling a bad constant index. */ - private def errorBadIndex(index: Int) = + private def errorBadIndex(index: Int)(using in: DataReader) = 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) + private def errorBadTag(start: Int)(using in: DataReader) = + throw new RuntimeException("bad constant pool tag " + in.getByte(start) + " at byte " + start) } } diff --git a/compiler/src/dotty/tools/dotc/core/classfile/DataReader.scala b/compiler/src/dotty/tools/dotc/core/classfile/DataReader.scala new file mode 100644 index 000000000000..d96bebf25fb7 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/classfile/DataReader.scala @@ -0,0 +1,61 @@ +package dotty.tools +package dotc +package core +package classfile + +trait DataReader { + + def bp: Int + def bp_=(i: Int): Unit + + def buf: Array[Byte] + + /** read a byte + */ + @throws(classOf[IndexOutOfBoundsException]) + def nextByte: Byte + + /** read some bytes + */ + def nextBytes(len: Int): Array[Byte] + + /** read a character + */ + def nextChar: Char + + /** read an integer + */ + def nextInt: Int + + /** extract a character at position bp from buf + */ + def getChar(mybp: Int): Char + + /** extract an integer at position bp from buf + */ + def getByte(mybp: Int): Byte + + def getBytes(mybp: Int, bytes: Array[Byte]): Unit + + /** extract an integer at position bp from buf + */ + def getInt(mybp: Int): Int + + /** extract a long integer at position bp from buf + */ + def getLong(mybp: Int): Long + + /** extract a float at position bp from buf + */ + def getFloat(mybp: Int): Float + + /** extract a double at position bp from buf + */ + def getDouble(mybp: Int): Double + + def getUTF(mybp: Int, len: Int): String + + /** skip next 'n' bytes + */ + def skip(n: Int): Unit +} diff --git a/compiler/src/dotty/tools/dotc/core/classfile/ReusableDataReader.scala b/compiler/src/dotty/tools/dotc/core/classfile/ReusableDataReader.scala new file mode 100644 index 000000000000..c9a04c392242 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/core/classfile/ReusableDataReader.scala @@ -0,0 +1,148 @@ +package dotty.tools +package dotc +package core +package classfile + +import java.io.{DataInputStream, InputStream} +import java.nio.{BufferUnderflowException, ByteBuffer} + +final class ReusableDataReader() extends DataReader { + private[this] var data = new Array[Byte](32768) + private[this] var bb: ByteBuffer = ByteBuffer.wrap(data) + private[this] var size = 0 + private[this] val reader: DataInputStream = { + val stream = new InputStream { + override def read(): Int = try { + bb.get & 0xff + } catch { + case _: BufferUnderflowException => -1 + } + + override def read(b: Array[Byte], off: Int, len: Int): Int = { + val pos = bb.position() + bb.get(b, off, len) + bb.position() - pos + } + + override def markSupported(): Boolean = false + } + new DataInputStream(stream) + } + + def buf: Array[Byte] = data + + private def nextPositivePowerOfTwo(target: Int): Int = 1 << -Integer.numberOfLeadingZeros(target - 1) + + def reset(file: dotty.tools.io.AbstractFile): this.type = { + this.size = 0 + file.sizeOption match { + case Some(size) => + if (size > data.length) { + data = new Array[Byte](nextPositivePowerOfTwo(size)) + } else { + java.util.Arrays.fill(data, 0.toByte) + } + val input = file.input + try { + var endOfInput = false + while (!endOfInput) { + val remaining = data.length - this.size + if (remaining == 0) endOfInput = true + else { + val read = input.read(data, this.size, remaining) + if (read < 0) endOfInput = true + else this.size += read + } + } + bb = ByteBuffer.wrap(data, 0, size) + } finally { + input.close() + } + case None => + val input = file.input + try { + var endOfInput = false + while (!endOfInput) { + val remaining = data.length - size + if (remaining == 0) { + data = java.util.Arrays.copyOf(data, nextPositivePowerOfTwo(size)) + } + val read = input.read(data, this.size, data.length - this.size) + if (read < 0) endOfInput = true + else this.size += read + } + bb = ByteBuffer.wrap(data, 0, size) + } finally { + input.close() + } + } + this + } + + @throws(classOf[IndexOutOfBoundsException]) + def nextByte: Byte = bb.get + + def nextBytes(len: Int): Array[Byte] = { + val result = new Array[Byte](len) + reader.readFully(result) + result + } + + def nextChar: Char = bb.getChar() + + def nextInt: Int = bb.getInt() + + def getChar(mybp: Int): Char = { + bb.getChar(mybp) + } + + def getInt(mybp: Int): Int = { + bb.getInt(mybp) + } + + def getLong(mybp: Int): Long = { + bb.getLong(mybp) + } + + def getFloat(mybp: Int): Float = { + bb.getFloat(mybp) + } + + def getDouble(mybp: Int): Double = { + bb.getDouble(mybp) + } + + def skip(n: Int): Unit = { + bb.position(bb.position() + n) + } + def bp: Int = bb.position() + def bp_=(i: Int): Unit = { + try { + bb.position(i) + } catch { + case ex: IllegalArgumentException => + throw ex + } + } + + def getByte(mybp: Int): Byte = { + bb.get(mybp) + } + def getBytes(mybp: Int, bytes: Array[Byte]): Unit = { + val saved = bb.position() + bb.position(mybp) + try reader.readFully(bytes) + finally bb.position(saved) + } + def getUTF(mybp: Int, len: Int): String = { + val saved = bb.position() + val savedLimit = bb.limit() + bb.position(mybp) + bb.limit(mybp + len) + try reader.readUTF() + finally { + bb.limit(savedLimit) + bb.position(saved) + } + } +} diff --git a/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala b/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala new file mode 100644 index 000000000000..16bd2decd908 --- /dev/null +++ b/compiler/src/dotty/tools/dotc/util/ReusableInstance.scala @@ -0,0 +1,33 @@ +package dotty.tools.dotc.util + +import scala.collection.mutable.ArrayBuffer +import scala.util.chaining._ + +/** A wrapper for a list of cached instances of a type `T`. + * The wrapper is recursion-reentrant: several instances are kept, so + * at each depth of reentrance we are reusing the instance for that. + * + * An instance is created upon creating this object, and more instances + * are allocated dynamically, on demand, when reentrance occurs. + * + * Not thread safe. + * + * Ported from scala.reflect.internal.util.ReusableInstance + */ +final class ReusableInstance[T <: AnyRef] private (make: => T) { + private[this] val cache = new ArrayBuffer[T](ReusableInstance.InitialSize).tap(_.addOne(make)) + private[this] var taken = 0 + + inline def withInstance[R](action: T => R): R ={ + if (taken == cache.size) + cache += make + taken += 1 + try action(cache(taken-1)) finally taken -= 1 + } +} + +object ReusableInstance { + private final val InitialSize = 4 + + def apply[T <: AnyRef](make: => T): ReusableInstance[T] = new ReusableInstance[T](make) +}