Skip to content

Commit a8f1cfb

Browse files
committed
Eagerly read attributes
- We need to load the type `scala.deprecated` more lazily in compiling stdlib More work is needed to get the ClassfileParser instances to not be kept in memory longer than needed. Some ideas for further refactoring: - Make `pool` pass through as `DataReader`, and make a facade for them. As both have exactly the same life duration. - Move `MemberCompleter`, `AttributeCompleter` and `InnerClass` out of `ClassfileParser`.
1 parent d92d26a commit a8f1cfb

File tree

2 files changed

+131
-83
lines changed

2 files changed

+131
-83
lines changed

compiler/src/dotty/tools/dotc/core/classfile/ClassfileParser.scala

Lines changed: 122 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,6 @@ object ClassfileParser {
5050
mapOver(tp)
5151
}
5252
}
53-
54-
def withReader[T](classfile: AbstractFile)(op: DataReader ?=> T)(using Context): T =
55-
ctx.base.reusableDataReader.using { reader =>
56-
op(using reader.reset(classfile))
57-
}
5853
}
5954

6055
class ClassfileParser(
@@ -86,11 +81,14 @@ class ClassfileParser(
8681
private def mismatchError(className: SimpleName) =
8782
throw new IOException(s"class file '${classfile.canonicalPath}' has location not matching its contents: contains class $className")
8883

89-
def run()(using Context): Option[Embedded] = try withReader(classfile) { (using in) =>
84+
def run()(using Context): Option[Embedded] = try ctx.base.reusableDataReader.withInstance { reader =>
85+
implicit val reader2 = reader.reset(classfile)
9086
report.debuglog("[class] >> " + classRoot.fullName)
9187
parseHeader()
9288
this.pool = new ConstantPool
93-
parseClass()
89+
val res = parseClass()
90+
this.pool = null
91+
res
9492
}
9593
catch {
9694
case e: RuntimeException =>
@@ -114,7 +112,7 @@ class ClassfileParser(
114112
}
115113

116114
/** Return the class symbol of the given name. */
117-
def classNameToSymbol(name: Name)(using Context, DataReader): Symbol = innerClasses.get(name) match {
115+
def classNameToSymbol(name: Name)(using Context): Symbol = innerClasses.get(name.toString) match {
118116
case Some(entry) => innerClasses.classSymbol(entry)
119117
case None => requiredClass(name)
120118
}
@@ -191,7 +189,7 @@ class ClassfileParser(
191189

192190
setClassInfo(moduleRoot, staticInfo, fromScala2 = false)
193191

194-
classInfo = parseAttributes(classRoot.symbol, classInfo)
192+
classInfo = parseAttributes(classRoot.symbol).complete(classInfo)
195193
if (isAnnotation)
196194
// classInfo must be a TempClassInfoType and not a TempPolyType,
197195
// because Java annotations cannot have type parameters.
@@ -227,32 +225,37 @@ class ClassfileParser(
227225
val sflags =
228226
if (method) Flags.Method | methodTranslation.flags(jflags)
229227
else fieldTranslation.flags(jflags)
230-
val name = pool.getName(in.nextChar)
231-
if (!sflags.isOneOf(Flags.PrivateOrArtifact) || name.name == nme.CONSTRUCTOR) {
228+
val preName = pool.getName(in.nextChar)
229+
if (!sflags.isOneOf(Flags.PrivateOrArtifact) || preName.name == nme.CONSTRUCTOR) {
230+
val sig = pool.getExternalName(in.nextChar).value
231+
val completer = MemberCompleter(preName.name, jflags, sig)
232232
val member = newSymbol(
233-
getOwner(jflags), name.name, sflags, memberCompleter,
233+
getOwner(jflags), preName.name, sflags, completer,
234234
getPrivateWithin(jflags), coord = start)
235+
236+
completer.attrCompleter = parseAttributes(member)
237+
235238
getScope(jflags).enter(member)
239+
240+
}
241+
else {
242+
in.nextChar // info
243+
skipAttributes()
236244
}
237-
// skip rest of member for now
238-
in.nextChar // info
239-
skipAttributes()
240245
}
241246

242-
val memberCompleter: LazyType = new LazyType {
247+
class MemberCompleter(name: SimpleName, jflags: Int, sig: String) extends LazyType {
248+
var attrCompleter: AttributeCompleter = null
243249

244-
def complete(denot: SymDenotation)(using Context): Unit = withReader(classfile) { (using in) =>
245-
in.bp = denot.symbol.coord.toIndex
250+
def complete(denot: SymDenotation)(using Context): Unit = {
246251
val sym = denot.symbol
247-
val jflags = in.nextChar
248252
val isEnum = (jflags & JAVA_ACC_ENUM) != 0
249-
val name = pool.getName(in.nextChar).name
250253
val isConstructor = name eq nme.CONSTRUCTOR
251254

252255
/** Strip leading outer param from constructor and trailing access tag for
253256
* private inner constructors.
254257
*/
255-
def normalizeConstructorParams() = innerClasses.get(currentClassName) match {
258+
def normalizeConstructorParams() = innerClasses.get(currentClassName.toString) match {
256259
case Some(entry) if !isStatic(entry.jflags) =>
257260
val mt @ MethodTpe(paramNames, paramTypes, resultType) = denot.info
258261
var normalizedParamNames = paramNames.tail
@@ -283,9 +286,9 @@ class ClassfileParser(
283286
}
284287

285288
val isVarargs = denot.is(Flags.Method) && (jflags & JAVA_ACC_VARARGS) != 0
286-
denot.info = pool.getType(in.nextChar, isVarargs)
289+
denot.info = sigToType(sig, isVarargs = isVarargs)
287290
if (isConstructor) normalizeConstructorParams()
288-
denot.info = translateTempPoly(parseAttributes(sym, denot.info, isVarargs))
291+
denot.info = translateTempPoly(attrCompleter.complete(denot.info, isVarargs))
289292
if (isConstructor) normalizeConstructorInfo()
290293

291294
if (ctx.explicitNulls) denot.info = JavaNullInterop.nullifyMember(denot.symbol, denot.info, isEnum)
@@ -317,7 +320,22 @@ class ClassfileParser(
317320
case BOOL_TAG => defn.BooleanType
318321
}
319322

320-
private def sigToType(sig: String, owner: Symbol = null, isVarargs: Boolean = false)(using Context, DataReader): Type = {
323+
/** As specified in https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.16.1,
324+
* an annotation argument of type boolean, byte, char or short will
325+
* be represented as a CONSTANT_INTEGER, so we need to convert it to
326+
* produce a correctly-typed tree. We need to do this each time the
327+
* constant is accessed instead of storing the result of the
328+
* conversion in the `values` cache, because the same constant might
329+
* be used for annotation arguments of different type.
330+
*/
331+
def convertTo(ct: Constant, pt: Type)(using Context): Constant = {
332+
if (pt eq defn.BooleanType) && ct.tag == IntTag then
333+
Constant(ct.value != 0)
334+
else
335+
ct.convertTo(pt)
336+
}
337+
338+
private def sigToType(sig: String, owner: Symbol = null, isVarargs: Boolean = false)(using Context): Type = {
321339
var index = 0
322340
val end = sig.length
323341
def accept(ch: Char): Unit = {
@@ -518,7 +536,10 @@ class ClassfileParser(
518536
case STRING_TAG =>
519537
if (skip) None else Some(lit(Constant(pool.getName(index).value)))
520538
case BOOL_TAG | BYTE_TAG | CHAR_TAG | SHORT_TAG =>
521-
if (skip) None else Some(lit(pool.getConstant(index, constantTagToType(tag))))
539+
if (skip) None else {
540+
val constant = convertTo(pool.getConstant(index), constantTagToType(tag))
541+
Some(lit(constant))
542+
}
522543
case INT_TAG | LONG_TAG | FLOAT_TAG | DOUBLE_TAG =>
523544
if (skip) None else Some(lit(pool.getConstant(index)))
524545
case CLASS_TAG =>
@@ -604,8 +625,48 @@ class ClassfileParser(
604625
None // ignore malformed annotations
605626
}
606627

607-
def parseAttributes(sym: Symbol, symtype: Type, isVarargs: Boolean = false)(using ctx: Context, in: DataReader): Type = {
608-
var newType = symtype
628+
/** A completer for attributes
629+
*
630+
* Top-level classes complete attributes eagerly, while members complete lazily.
631+
*
632+
* @note We cannot simply store the bytes of attributes, as the bytes may
633+
* contain references to the constant pool, where the constants are loaded
634+
* lazily.
635+
*/
636+
class AttributeCompleter(sym: Symbol) {
637+
var sig: String = null
638+
var constant: Constant = null
639+
var exceptions: List[NameOrString] = Nil
640+
var annotations: List[Annotation] = Nil
641+
def complete(tp: Type, isVarargs: Boolean = false)(using Context): Type = {
642+
val updatedType =
643+
if sig == null then tp
644+
else {
645+
val newType = sigToType(sig, sym, isVarargs)
646+
if (ctx.debug && ctx.verbose)
647+
println("" + sym + "; signature = " + sig + " type = " + newType)
648+
newType
649+
}
650+
651+
val newType =
652+
if this.constant != null then
653+
val ct = convertTo(this.constant, updatedType)
654+
if ct != null then ConstantType(ct) else updatedType
655+
else updatedType
656+
657+
annotations.foreach(annot => sym.addAnnotation(annot))
658+
659+
exceptions.foreach { ex =>
660+
val cls = getClassSymbol(ex.name)
661+
sym.addAnnotation(ThrowsAnnotation(cls.asClass))
662+
}
663+
664+
cook.apply(newType)
665+
}
666+
}
667+
668+
def parseAttributes(sym: Symbol)(using ctx: Context, in: DataReader): AttributeCompleter = {
669+
val res = new AttributeCompleter(sym)
609670

610671
def parseAttribute(): Unit = {
611672
val attrName = pool.getName(in.nextChar).name.toTypeName
@@ -614,9 +675,7 @@ class ClassfileParser(
614675
attrName match {
615676
case tpnme.SignatureATTR =>
616677
val sig = pool.getExternalName(in.nextChar)
617-
newType = sigToType(sig.value, sym, isVarargs)
618-
if (ctx.debug && ctx.verbose)
619-
println("" + sym + "; signature = " + sig + " type = " + newType)
678+
res.sig = sig.value
620679

621680
case tpnme.SyntheticATTR =>
622681
sym.setFlag(Flags.SyntheticArtifact)
@@ -627,11 +686,13 @@ class ClassfileParser(
627686
case tpnme.DeprecatedATTR =>
628687
val msg = Literal(Constant("see corresponding Javadoc for more information."))
629688
val since = Literal(Constant(""))
630-
sym.addAnnotation(Annotation(defn.DeprecatedAnnot, msg, since))
689+
res.annotations ::= Annotation.deferredSymAndTree(defn.DeprecatedAnnot) {
690+
New(defn.DeprecatedAnnot.typeRef, msg :: since :: Nil)
691+
}
631692

632693
case tpnme.ConstantValueATTR =>
633-
val c = pool.getConstant(in.nextChar, symtype)
634-
if (c ne null) newType = ConstantType(c)
694+
val c = pool.getConstant(in.nextChar)
695+
if (c ne null) res.constant = c
635696
else report.warning(s"Invalid constant in attribute of ${sym.showLocated} while parsing ${classfile}")
636697

637698
case tpnme.AnnotationDefaultATTR =>
@@ -652,12 +713,13 @@ class ClassfileParser(
652713
parseExceptions(attrLen)
653714

654715
case tpnme.CodeATTR =>
655-
if (sym.owner.isAllOf(Flags.JavaInterface)) {
716+
in.skip(attrLen)
717+
// flag test will trigger completion and cycles, thus have to be lazy
718+
if (sym.owner.flagsUNSAFE.isAllOf(Flags.JavaInterface)) {
656719
sym.resetFlag(Flags.Deferred)
657720
sym.owner.resetFlag(Flags.PureInterface)
658-
report.log(s"$sym in ${sym.owner} is a java8+ default method.")
721+
report.log(s"$sym in ${sym.owner} is a java 8+ default method.")
659722
}
660-
in.skip(attrLen)
661723

662724
case _ =>
663725
}
@@ -672,11 +734,12 @@ class ClassfileParser(
672734
val nClasses = in.nextChar
673735
for (n <- 0 until nClasses) {
674736
// FIXME: this performs an equivalent of getExceptionTypes instead of getGenericExceptionTypes (SI-7065)
675-
val cls = pool.getClassSymbol(in.nextChar.toInt)
676-
sym.addAnnotation(ThrowsAnnotation(cls.asClass))
737+
val cls = pool.getClassName(in.nextChar.toInt)
738+
res.exceptions ::= cls
677739
}
678740
}
679741

742+
680743
/** Parse a sequence of annotations and attaches them to the
681744
* current symbol sym, except for the ScalaSignature annotation that it returns, if it is available. */
682745
def parseAnnotations(len: Int): Unit = {
@@ -693,7 +756,7 @@ class ClassfileParser(
693756
for (i <- 0 until in.nextChar)
694757
parseAttribute()
695758

696-
cook.apply(newType)
759+
res
697760
}
698761

699762
/** Annotations in Scala are assumed to get all their arguments as constructor
@@ -730,7 +793,7 @@ class ClassfileParser(
730793

731794
for entry <- innerClasses.valuesIterator do
732795
// create a new class member for immediate inner classes
733-
if entry.outerName == currentClassName then
796+
if entry.outer.name == currentClassName then
734797
val file = ctx.platform.classPath.findClassFile(entry.externalName.toString) getOrElse {
735798
throw new AssertionError(entry.externalName)
736799
}
@@ -911,8 +974,11 @@ class ClassfileParser(
911974
val nameIndex = in.nextChar
912975
val jflags = in.nextChar
913976
if (innerIndex != 0 && outerIndex != 0 && nameIndex != 0) {
914-
val entry = InnerClassEntry(innerIndex, outerIndex, nameIndex, jflags)
915-
innerClasses(pool.getClassName(innerIndex).name) = entry
977+
val inner = pool.getClassName(innerIndex)
978+
val outer = pool.getClassName(outerIndex)
979+
val name = pool.getName(nameIndex)
980+
val entry = InnerClassEntry(inner, outer, name, jflags)
981+
innerClasses(inner.value) = entry
916982
}
917983
}
918984
}
@@ -922,20 +988,20 @@ class ClassfileParser(
922988
}
923989

924990
/** An entry in the InnerClasses attribute of this class file. */
925-
private case class InnerClassEntry(external: Int, outer: Int, name: Int, jflags: Int) {
926-
def externalName(using DataReader): SimpleName = pool.getClassName(external).name
927-
def outerName(using DataReader): SimpleName = pool.getClassName(outer).name
928-
def originalName(using DataReader): SimpleName = pool.getName(name).name
991+
case class InnerClassEntry(external: NameOrString, outer: NameOrString, name: NameOrString, jflags: Int) {
992+
def externalName = external.value
993+
def outerName = outer.value
994+
def originalName = name.name
929995

930-
def show(using DataReader): String =
931-
s"$originalName in $outerName($externalName)"
996+
// The name of the outer class, without its trailing $ if it has one.
997+
def strippedOuter = outer.name.stripModuleClassSuffix
932998
}
933999

934-
private object innerClasses extends util.HashMap[Name, InnerClassEntry] {
1000+
private object innerClasses extends util.HashMap[String, InnerClassEntry] {
9351001
/** Return the Symbol of the top level class enclosing `name`,
9361002
* or 'name's symbol if no entry found for `name`.
9371003
*/
938-
def topLevelClass(name: Name)(using Context, DataReader): Symbol = {
1004+
def topLevelClass(name: String)(using Context): Symbol = {
9391005
val tlName = if (contains(name)) {
9401006
var entry = this(name)
9411007
while (contains(entry.outerName))
@@ -944,13 +1010,13 @@ class ClassfileParser(
9441010
}
9451011
else
9461012
name
947-
classNameToSymbol(tlName)
1013+
classNameToSymbol(tlName.toTypeName)
9481014
}
9491015

9501016
/** Return the class symbol for `entry`. It looks it up in its outer class.
9511017
* This might force outer class symbols.
9521018
*/
953-
def classSymbol(entry: InnerClassEntry)(using Context, DataReader): Symbol = {
1019+
def classSymbol(entry: InnerClassEntry)(using Context): Symbol = {
9541020
def getMember(sym: Symbol, name: Name)(using Context): Symbol =
9551021
if (isStatic(entry.jflags))
9561022
if (sym == classRoot.symbol)
@@ -966,7 +1032,7 @@ class ClassfileParser(
9661032
else
9671033
sym.info.member(name).symbol
9681034

969-
val outerName = entry.outerName.stripModuleClassSuffix
1035+
val outerName = entry.strippedOuter
9701036
val innerName = entry.originalName
9711037
val owner = classNameToSymbol(outerName)
9721038
val result = atPhase(typerPhase)(getMember(owner, innerName.toTypeName))
@@ -1025,7 +1091,7 @@ class ClassfileParser(
10251091
}
10261092
}
10271093

1028-
def getClassSymbol(name: SimpleName)(using Context, DataReader): Symbol =
1094+
def getClassSymbol(name: SimpleName)(using Context): Symbol =
10291095
if (name.endsWith("$") && (name ne nme.nothingRuntimeClass) && (name ne nme.nullRuntimeClass))
10301096
// Null$ and Nothing$ ARE classes
10311097
requiredModule(name.dropRight(1))
@@ -1148,7 +1214,7 @@ class ClassfileParser(
11481214
getClassSymbol(index)
11491215
}
11501216

1151-
def getConstant(index: Int, pt: Type = WildcardType)(using ctx: Context, in: DataReader): Constant = {
1217+
def getConstant(index: Int)(using ctx: Context, in: DataReader): Constant = {
11521218
if (index <= 0 || len <= index) errorBadIndex(index)
11531219
var value = values(index)
11541220
if (value eq null) {
@@ -1172,21 +1238,7 @@ class ClassfileParser(
11721238
values(index) = value
11731239
}
11741240
value match {
1175-
case ct: Constant =>
1176-
if pt ne WildcardType then
1177-
// As specified in https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.7.16.1,
1178-
// an annotation argument of type boolean, byte, char or short will
1179-
// be represented as a CONSTANT_INTEGER, so we need to convert it to
1180-
// produce a correctly-typed tree. We need to do this each time the
1181-
// constant is accessed instead of storing the result of the
1182-
// conversion in the `values` cache, because the same constant might
1183-
// be used for annotation arguments of different type.
1184-
if (pt eq defn.BooleanType) && ct.tag == IntTag then
1185-
Constant(ct.value != 0)
1186-
else
1187-
ct.convertTo(pt)
1188-
else
1189-
ct
1241+
case ct: Constant => ct
11901242
case cls: Symbol => Constant(cls.typeRef)
11911243
case arr: Type => Constant(arr)
11921244
}

0 commit comments

Comments
 (0)