Skip to content

Commit bda8508

Browse files
committed
Simplify and correctify calculation of the InnerClass attribute
The InnerClass attribute needs to contain an entry for every nested class that is defined or referenced in a class. Details are in a doc comment in BTypes.scala. Instead of collecting ClassBTypes of nested classes into a hash map during code generation, traverse the class before writing it out to disk. The previous approach was incorrect as soon as the generated bytecode was modified by the optimzier (DCE, inlining).
1 parent 97ec2cb commit bda8508

8 files changed

+466
-112
lines changed

compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala

+2-25
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
3939
import DottyBackendInterface.symExtensions
4040
import bTypes._
4141
import coreBTypes._
42-
import BCodeBodyBuilder._
4342

4443
protected val primitives: DottyPrimitives
4544

@@ -1753,9 +1752,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
17531752

17541753
val metafactory =
17551754
if (flags != 0)
1756-
lambdaMetaFactoryAltMetafactoryHandle // altMetafactory required to be able to pass the flags and additional arguments if needed
1755+
jliLambdaMetaFactoryAltMetafactoryHandle // altMetafactory required to be able to pass the flags and additional arguments if needed
17571756
else
1758-
lambdaMetaFactoryMetafactoryHandle
1757+
jliLambdaMetaFactoryMetafactoryHandle
17591758

17601759
bc.jmethod.visitInvokeDynamicInsn(methodName, desc, metafactory, bsmArgs: _*)
17611760

@@ -1772,27 +1771,5 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder {
17721771
private def isEmittedInterface(sym: Symbol): Boolean = sym.isInterface ||
17731772
sym.is(JavaDefined) && (toDenot(sym).isAnnotation || sym.is(ModuleClass) && (sym.companionClass.is(PureInterface)) || sym.companionClass.is(Trait))
17741773

1775-
}
17761774

1777-
object BCodeBodyBuilder {
1778-
val lambdaMetaFactoryMetafactoryHandle = new Handle(
1779-
Opcodes.H_INVOKESTATIC,
1780-
"java/lang/invoke/LambdaMetafactory",
1781-
"metafactory",
1782-
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
1783-
/* itf = */ false)
1784-
1785-
val lambdaMetaFactoryAltMetafactoryHandle = new Handle(
1786-
Opcodes.H_INVOKESTATIC,
1787-
"java/lang/invoke/LambdaMetafactory",
1788-
"altMetafactory",
1789-
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;",
1790-
/* itf = */ false)
1791-
1792-
val lambdaDeserializeBootstrapHandle = new Handle(
1793-
Opcodes.H_INVOKESTATIC,
1794-
"scala/runtime/LambdaDeserialize",
1795-
"bootstrap",
1796-
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite;",
1797-
/* itf = */ false)
17981775
}

compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala

+27-51
Original file line numberDiff line numberDiff line change
@@ -132,24 +132,24 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
132132
}
133133

134134
/*
135-
* Populates the InnerClasses JVM attribute with `refedInnerClasses`.
136-
* In addition to inner classes mentioned somewhere in `jclass` (where `jclass` is a class file being emitted)
137-
* `refedInnerClasses` should contain those inner classes defined as direct member classes of `jclass`
138-
* but otherwise not mentioned in `jclass`.
135+
* Populates the InnerClasses JVM attribute with `refedInnerClasses`. See also the doc on inner
136+
* classes in BTypes.scala.
139137
*
140-
* `refedInnerClasses` may contain duplicates,
141-
* need not contain the enclosing inner classes of each inner class it lists (those are looked up for consistency).
138+
* `refedInnerClasses` may contain duplicates, need not contain the enclosing inner classes of
139+
* each inner class it lists (those are looked up and included).
142140
*
143-
* This method serializes in the InnerClasses JVM attribute in an appropriate order,
141+
* This method serializes in the InnerClasses JVM attribute in an appropriate order,
144142
* not necessarily that given by `refedInnerClasses`.
145143
*
146144
* can-multi-thread
147145
*/
148-
final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]): Unit = {
149-
val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain).distinct
150-
146+
final def addInnerClasses(jclass: asm.ClassVisitor, declaredInnerClasses: List[ClassBType], refedInnerClasses: List[ClassBType]): Unit = {
151147
// sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler
152-
for (nestedClass <- allNestedClasses.sortBy(_.internalName.toString)) {
148+
val allNestedClasses = new mutable.TreeSet[ClassBType]()(Ordering.by(_.internalName))
149+
allNestedClasses ++= declaredInnerClasses
150+
refedInnerClasses.foreach(allNestedClasses ++= _.enclosingNestedClassesChain)
151+
for nestedClass <- allNestedClasses
152+
do {
153153
// Extract the innerClassEntry - we know it exists, enclosingNestedClassesChain only returns nested classes.
154154
val Some(e) = nestedClass.innerClassAttributeEntry
155155
jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.flags)
@@ -218,26 +218,16 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
218218
final val emitLines = debugLevel >= 2
219219
final val emitVars = debugLevel >= 3
220220

221-
/*
222-
* Contains class-symbols that:
223-
* (a) are known to denote inner classes
224-
* (b) are mentioned somewhere in the class being generated.
225-
*
226-
* In other words, the lifetime of `innerClassBufferASM` is associated to "the class being generated".
227-
*/
228-
final val innerClassBufferASM = mutable.Set.empty[ClassBType]
229-
230221
/**
231-
* The class internal name for a given class symbol. If the symbol describes a nested class, the
232-
* ClassBType is added to the innerClassBufferASM.
222+
* The class internal name for a given class symbol.
233223
*/
234224
final def internalName(sym: Symbol): String = {
235225
// For each java class, the scala compiler creates a class and a module (thus a module class).
236-
// If the `sym` is a java module class, we use the java class instead. This ensures that we
237-
// register the class (instead of the module class) in innerClassBufferASM.
226+
// If the `sym` is a java module class, we use the java class instead. This ensures that the
227+
// ClassBType is created from the main class (instead of the module class).
238228
// The two symbols have the same name, so the resulting internalName is the same.
239229
val classSym = if (sym.is(JavaDefined) && sym.is(ModuleClass)) sym.linkedClass else sym
240-
getClassBTypeAndRegisterInnerClass(classSym).internalName
230+
getClassBType(classSym).internalName
241231
}
242232

243233
private def assertClassNotArray(sym: Symbol): Unit = {
@@ -251,8 +241,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
251241
}
252242

253243
/**
254-
* The ClassBType for a class symbol. If the class is nested, the ClassBType is added to the
255-
* innerClassBufferASM.
244+
* The ClassBType for a class symbol.
256245
*
257246
* The class symbol scala.Nothing is mapped to the class scala.runtime.Nothing$. Similarly,
258247
* scala.Null is mapped to scala.runtime.Null$. This is because there exist no class files
@@ -264,16 +253,12 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
264253
* the class descriptor of the receiver (the implementation class) is obtained by creating the
265254
* ClassBType.
266255
*/
267-
final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = {
256+
final def getClassBType(sym: Symbol): ClassBType = {
268257
assertClassNotArrayNotPrimitive(sym)
269258

270259
if (sym == defn.NothingClass) srNothingRef
271260
else if (sym == defn.NullClass) srNullRef
272-
else {
273-
val r = classBTypeFromSymbol(sym)
274-
if (r.isNestedClass) innerClassBufferASM += r
275-
r
276-
}
261+
else classBTypeFromSymbol(sym)
277262
}
278263

279264
/*
@@ -288,16 +273,14 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
288273
}
289274

290275
/**
291-
* The jvm descriptor of a type. If `t` references a nested class, its ClassBType is added to
292-
* the innerClassBufferASM.
276+
* The jvm descriptor of a type.
293277
*/
294278
final def typeDescriptor(t: Type): String = { toTypeKind(t).descriptor }
295279

296280
/**
297-
* The jvm descriptor for a symbol. If `sym` represents a nested class, its ClassBType is added
298-
* to the innerClassBufferASM.
281+
* The jvm descriptor for a symbol.
299282
*/
300-
final def symDescriptor(sym: Symbol): String = { getClassBTypeAndRegisterInnerClass(sym).descriptor }
283+
final def symDescriptor(sym: Symbol): String = getClassBType(sym).descriptor
301284

302285
final def toTypeKind(tp: Type): BType = typeToTypeKind(tp)(BCodeHelpers.this)(this)
303286

@@ -691,16 +674,15 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
691674
def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = {
692675
assert(moduleClass.is(ModuleClass))
693676
assert(moduleClass.companionClass == NoSymbol, moduleClass)
694-
innerClassBufferASM.clear()
695677
this.cunit = cunit
678+
val bType = mirrorClassBTypeFromSymbol(moduleClass)
696679
val moduleName = internalName(moduleClass) // + "$"
697-
val mirrorName = moduleName.substring(0, moduleName.length() - 1)
680+
val mirrorName = bType.internalName
698681

699-
val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL)
700682
val mirrorClass = new asm.tree.ClassNode
701683
mirrorClass.visit(
702684
classfileVersion,
703-
flags,
685+
bType.info.flags,
704686
mirrorName,
705687
null /* no java-generic-signature */,
706688
ObjectRef.internalName,
@@ -717,10 +699,6 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
717699
emitAnnotations(mirrorClass, moduleClass.annotations ++ ssa)
718700

719701
addForwarders(mirrorClass, mirrorName, moduleClass)
720-
721-
innerClassBufferASM ++= classBTypeFromSymbol(moduleClass).info.memberClasses
722-
addInnerClassesASM(mirrorClass, innerClassBufferASM.toList)
723-
724702
mirrorClass.visitEnd()
725703

726704
moduleClass.name // this side-effect is necessary, really.
@@ -754,8 +732,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
754732
* must-single-thread
755733
*/
756734
def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String): Unit = {
757-
// this tracks the inner class in innerClassBufferASM, if needed.
758-
val androidCreatorType = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass)
735+
val androidCreatorType = getClassBType(AndroidCreatorClass)
759736
val tdesc_creator = androidCreatorType.descriptor
760737

761738
cnode.visitField(
@@ -818,8 +795,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
818795
def primitiveOrClassToBType(sym: Symbol): BType = {
819796
assert(sym.isClass, sym)
820797
assert(sym != defn.ArrayClass || compilingArray, sym)
821-
primitiveTypeMap.getOrElse(sym,
822-
storage.getClassBTypeAndRegisterInnerClass(sym)).asInstanceOf[BType]
798+
primitiveTypeMap.getOrElse(sym, storage.getClassBType(sym)).asInstanceOf[BType]
823799
}
824800

825801
/**
@@ -862,7 +838,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters {
862838

863839
tp match {
864840
case tp: ThisType if tp.cls == defn.ArrayClass => ObjectRef.asInstanceOf[ct.bTypes.ClassBType] // was introduced in 9b17332f11 to fix SI-999, but this code is not reached in its test, or any other test
865-
case tp: ThisType => storage.getClassBTypeAndRegisterInnerClass(tp.cls)
841+
case tp: ThisType => storage.getClassBType(tp.cls)
866842
// case t: SingletonType => primitiveOrClassToBType(t.classSymbol)
867843
case t: SingletonType => typeToTypeKind(t.underlying)(ct)(storage)
868844
case t: RefinedType => typeToTypeKind(t.parent)(ct)(storage) //parents.map(_.toTypeKind(ct)(storage).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b))

compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala

+4-8
Original file line numberDiff line numberDiff line change
@@ -268,8 +268,10 @@ trait BCodeIdiomatic {
268268
* can-multi-thread
269269
*/
270270
final def genStringBuilderEnd: Unit = {
271-
invokevirtual(JavaStringBuilderClassName, "toString", "()Ljava/lang/String;")
271+
invokevirtual(JavaStringBuilderClassName, "toString", genStringBuilderEndDesc)
272272
}
273+
// Use ClassBType refs instead of plain string literal to make sure that needed ClassBTypes are initialized and reachable
274+
private lazy val genStringBuilderEndDesc = MethodBType(Nil, StringRef).descriptor
273275

274276
/* Concatenate top N arguments on the stack with `StringConcatFactory#makeConcatWithConstants`
275277
* (only works for JDK 9+)
@@ -284,13 +286,7 @@ trait BCodeIdiomatic {
284286
jmethod.visitInvokeDynamicInsn(
285287
"makeConcatWithConstants",
286288
asm.Type.getMethodDescriptor(StringRef.toASMType, argTypes:_*),
287-
new asm.Handle(
288-
asm.Opcodes.H_INVOKESTATIC,
289-
"java/lang/invoke/StringConcatFactory",
290-
"makeConcatWithConstants",
291-
"(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;",
292-
false
293-
),
289+
coreBTypes.jliStringConcatFactoryMakeConcatWithConstantsHandle,
294290
(recipe +: constants):_*
295291
)
296292
}

compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala

+2-15
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ trait BCodeSkelBuilder extends BCodeHelpers {
112112
def genPlainClass(cd0: TypeDef) = cd0 match {
113113
case TypeDef(_, impl: Template) =>
114114
assert(cnode == null, "GenBCode detected nested methods.")
115-
innerClassBufferASM.clear()
116115

117116
claszSymbol = cd0.symbol
118117
isCZParcelable = isAndroidParcelableClass(claszSymbol)
@@ -231,15 +230,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
231230
if (optSerial.isDefined) { addSerialVUID(optSerial.get, cnode)}
232231

233232
addClassFields()
234-
235-
innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.memberClasses
236-
237-
val companion = claszSymbol.companionClass
238-
if companion.isTopLevelModuleClass then
239-
innerClassBufferASM ++= classBTypeFromSymbol(companion).info.memberClasses
240-
241233
gen(cd.rhs)
242-
addInnerClassesASM(cnode, innerClassBufferASM.toList)
243234

244235
if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern))
245236
AsmUtils.traceClass(cnode)
@@ -256,11 +247,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
256247

257248
val ps = claszSymbol.info.parents
258249
val superClass: String = if (ps.isEmpty) ObjectRef.internalName else internalName(ps.head.typeSymbol)
259-
val interfaceNames0 = classBTypeFromSymbol(claszSymbol).info.interfaces map {
260-
case classBType =>
261-
if (classBType.isNestedClass) { innerClassBufferASM += classBType }
262-
classBType.internalName
263-
}
250+
val interfaceNames0 = classBTypeFromSymbol(claszSymbol).info.interfaces.map(_.internalName)
264251
/* To avoid deadlocks when combining objects, lambdas and multi-threading,
265252
* lambdas in objects are compiled to instance methods of the module class
266253
* instead of static methods (see tests/run/deadlock.scala and
@@ -881,7 +868,7 @@ trait BCodeSkelBuilder extends BCodeHelpers {
881868
// android creator code
882869
if (isCZParcelable) {
883870
// add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator
884-
val andrFieldDescr = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass).descriptor
871+
val andrFieldDescr = classBTypeFromSymbol(AndroidCreatorClass).descriptor
885872
cnode.visitField(
886873
asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL,
887874
"CREATOR",

compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala

+15-7
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import dotty.tools.dotc.core.Phases._
1313
import dotty.tools.dotc.core.Symbols._
1414
import dotty.tools.dotc.core.Phases.Phase
1515
import dotty.tools.dotc.transform.SymUtils._
16+
import dotty.tools.dotc.core.StdNames
1617

1718
/**
1819
* This class mainly contains the method classBTypeFromSymbol, which extracts the necessary
@@ -91,6 +92,20 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes {
9192
})
9293
}
9394

95+
final def mirrorClassBTypeFromSymbol(moduleClassSym: Symbol): ClassBType = {
96+
assert(moduleClassSym.isTopLevelModuleClass, s"not a top-level module class: $moduleClassSym")
97+
val internalName = moduleClassSym.javaBinaryName.stripSuffix(StdNames.str.MODULE_SUFFIX)
98+
val bType = ClassBType(internalName)
99+
bType.info = ClassInfo(
100+
superClass = Some(ObjectRef),
101+
interfaces = Nil,
102+
flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL,
103+
memberClasses = getMemberClasses(moduleClassSym).map(classBTypeFromSymbol),
104+
nestedInfo = None
105+
)
106+
bType
107+
}
108+
94109
private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = {
95110
val superClassSym: Symbol = {
96111
val t = classSym.asClass.superClass
@@ -138,13 +153,6 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes {
138153
/* The InnerClass table of a class C must contain all nested classes of C, even if they are only
139154
* declared but not otherwise referenced in C (from the bytecode or a method / field signature).
140155
* We collect them here.
141-
*
142-
* Nested classes that are also referenced in C will be added to the innerClassBufferASM during
143-
* code generation, but those duplicates will be eliminated when emitting the InnerClass
144-
* attribute.
145-
*
146-
* Why doe we need to collect classes into innerClassBufferASM at all? To collect references to
147-
* nested classes, but NOT nested in C, that are used within C.
148156
*/
149157
val nestedClassSymbols = {
150158
// The lambdalift phase lifts all nested classes to the enclosing class, so if we collect

0 commit comments

Comments
 (0)