From 97ec2cbcc431cd43cb5e606b2d96a93c322377ee Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Thu, 5 May 2022 14:54:09 +0200 Subject: [PATCH 1/2] Clean up CoreBTypes, consistent names and remove unused entries --- .../tools/backend/jvm/BCodeBodyBuilder.scala | 50 ++++---- .../tools/backend/jvm/BCodeHelpers.scala | 10 +- .../tools/backend/jvm/BCodeIdiomatic.scala | 2 +- .../tools/backend/jvm/BCodeSkelBuilder.scala | 2 +- .../tools/backend/jvm/BCodeSyncAndTry.scala | 8 +- .../src/dotty/tools/backend/jvm/BTypes.scala | 18 +-- .../dotty/tools/backend/jvm/CoreBTypes.scala | 118 +++++++----------- .../dotty/tools/backend/jvm/GenBCode.scala | 2 +- 8 files changed, 88 insertions(+), 122 deletions(-) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index 7bcc8fcf6b09..407b875fc1f1 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -431,7 +431,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { if (value.tag != UnitTag) (value.tag, expectedType) match { case (IntTag, LONG ) => bc.lconst(value.longValue); generatedType = LONG case (FloatTag, DOUBLE) => bc.dconst(value.doubleValue); generatedType = DOUBLE - case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = RT_NULL + case (NullTag, _ ) => bc.emit(asm.Opcodes.ACONST_NULL); generatedType = srNullRef case _ => genConstant(value); generatedType = tpeTK(tree) } @@ -490,7 +490,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { val thrownType = expectedType // `throw null` is valid although scala.Null (as defined in src/libray-aux) isn't a subtype of Throwable. // Similarly for scala.Nothing (again, as defined in src/libray-aux). - assert(thrownType.isNullType || thrownType.isNothingType || thrownType.asClassBType.isSubtypeOf(ThrowableReference)) + assert(thrownType.isNullType || thrownType.isNothingType || thrownType.asClassBType.isSubtypeOf(jlThrowableRef)) emit(asm.Opcodes.ATHROW) end genAdaptAndSendToDest @@ -674,8 +674,8 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { else if (l.isPrimitive) { bc drop l if (cast) { - mnode.visitTypeInsn(asm.Opcodes.NEW, classCastExceptionReference.internalName) - bc dup ObjectReference + mnode.visitTypeInsn(asm.Opcodes.NEW, jlClassCastExceptionRef.internalName) + bc dup ObjectRef emit(asm.Opcodes.ATHROW) } else { bc boolconst false @@ -777,7 +777,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { val nativeKind = tpeTK(expr) genLoad(expr, nativeKind) val MethodNameAndType(mname, methodType) = asmBoxTo(nativeKind) - bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, itf = false) + bc.invokestatic(srBoxesRuntimeRef.internalName, mname, methodType.descriptor, itf = false) generatedType = boxResultType(fun.symbol) // was toTypeKind(fun.symbol.tpe.resultType) case Apply(fun, List(expr)) if Erasure.Boxing.isUnbox(fun.symbol) && fun.symbol.denot.owner != defn.UnitModuleClass => @@ -785,7 +785,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { val boxType = unboxResultType(fun.symbol) // was toTypeKind(fun.symbol.owner.linkedClassOfClass.tpe) generatedType = boxType val MethodNameAndType(mname, methodType) = asmUnboxTo(boxType) - bc.invokestatic(BoxesRunTime.internalName, mname, methodType.descriptor, itf = false) + bc.invokestatic(srBoxesRuntimeRef.internalName, mname, methodType.descriptor, itf = false) case app @ Apply(fun, args) => val sym = fun.symbol @@ -1237,7 +1237,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { liftStringConcat(tree) match { // Optimization for expressions of the form "" + x case List(Literal(Constant("")), arg) => - genLoad(arg, ObjectReference) + genLoad(arg, ObjectRef) genCallMethod(defn.String_valueOf_Object, InvokeStyle.Static) case concatenations => @@ -1409,7 +1409,7 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { /* Generate the scala ## method. */ def genScalaHash(tree: Tree): BType = { - genLoad(tree, ObjectReference) + genLoad(tree, ObjectRef) genCallMethod(NoSymbol, InvokeStyle.Static) // used to dispatch ## on primitives to ScalaRuntime.hash. Should be implemented by a miniphase } @@ -1508,8 +1508,8 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { val nonNullSide = if (ScalaPrimitivesOps.isReferenceEqualityOp(code)) ifOneIsNull(l, r) else null if (nonNullSide != null) { // special-case reference (in)equality test for null (null eq x, x eq null) - genLoad(nonNullSide, ObjectReference) - genCZJUMP(success, failure, op, ObjectReference, targetIfNoJump) + genLoad(nonNullSide, ObjectRef) + genCZJUMP(success, failure, op, ObjectRef, targetIfNoJump) } else { val tk = tpeTK(l).maxType(tpeTK(r)) genLoad(l, tk) @@ -1627,42 +1627,42 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { } else defn.BoxesRunTimeModule_externalEquals } - genLoad(l, ObjectReference) - genLoad(r, ObjectReference) + genLoad(l, ObjectRef) + genLoad(r, ObjectRef) genCallMethod(equalsMethod, InvokeStyle.Static) genCZJUMP(success, failure, Primitives.NE, BOOL, targetIfNoJump) } else { if (isNull(l)) { // null == expr -> expr eq null - genLoad(r, ObjectReference) - genCZJUMP(success, failure, Primitives.EQ, ObjectReference, targetIfNoJump) + genLoad(r, ObjectRef) + genCZJUMP(success, failure, Primitives.EQ, ObjectRef, targetIfNoJump) } else if (isNull(r)) { // expr == null -> expr eq null - genLoad(l, ObjectReference) - genCZJUMP(success, failure, Primitives.EQ, ObjectReference, targetIfNoJump) + genLoad(l, ObjectRef) + genCZJUMP(success, failure, Primitives.EQ, ObjectRef, targetIfNoJump) } else if (isNonNullExpr(l)) { // SI-7852 Avoid null check if L is statically non-null. - genLoad(l, ObjectReference) - genLoad(r, ObjectReference) + genLoad(l, ObjectRef) + genLoad(r, ObjectRef) genCallMethod(defn.Any_equals, InvokeStyle.Virtual) genCZJUMP(success, failure, Primitives.NE, BOOL, targetIfNoJump) } else { // l == r -> if (l eq null) r eq null else l.equals(r) - val eqEqTempLocal = locals.makeLocal(ObjectReference, nme.EQEQ_LOCAL_VAR.mangledString, defn.ObjectType, r.span) + val eqEqTempLocal = locals.makeLocal(ObjectRef, nme.EQEQ_LOCAL_VAR.mangledString, defn.ObjectType, r.span) val lNull = new asm.Label val lNonNull = new asm.Label - genLoad(l, ObjectReference) - genLoad(r, ObjectReference) + genLoad(l, ObjectRef) + genLoad(r, ObjectRef) locals.store(eqEqTempLocal) - bc dup ObjectReference - genCZJUMP(lNull, lNonNull, Primitives.EQ, ObjectReference, targetIfNoJump = lNull) + bc dup ObjectRef + genCZJUMP(lNull, lNonNull, Primitives.EQ, ObjectRef, targetIfNoJump = lNull) markProgramPoint(lNull) - bc drop ObjectReference + bc drop ObjectRef locals.load(eqEqTempLocal) - genCZJUMP(success, failure, Primitives.EQ, ObjectReference, targetIfNoJump = lNonNull) + genCZJUMP(success, failure, Primitives.EQ, ObjectRef, targetIfNoJump = lNonNull) markProgramPoint(lNonNull) locals.load(eqEqTempLocal) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala index f254dea184ed..d9e6ac492342 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala @@ -267,8 +267,8 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = { assertClassNotArrayNotPrimitive(sym) - if (sym == defn.NothingClass) RT_NOTHING - else if (sym == defn.NullClass) RT_NULL + if (sym == defn.NothingClass) srNothingRef + else if (sym == defn.NullClass) srNullRef else { val r = classBTypeFromSymbol(sym) if (r.isNestedClass) innerClassBufferASM += r @@ -703,7 +703,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { flags, mirrorName, null /* no java-generic-signature */, - ObjectReference.internalName, + ObjectRef.internalName, EMPTY_STRING_ARRAY ) @@ -828,7 +828,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { */ def nonClassTypeRefToBType(sym: Symbol): ClassBType = { assert(sym.isType && compilingArray, sym) - ObjectReference.asInstanceOf[ct.bTypes.ClassBType] + ObjectRef.asInstanceOf[ct.bTypes.ClassBType] } tp.widenDealias match { @@ -861,7 +861,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { "If possible, please file a bug on https://github.com/lampepfl/dotty/issues") tp match { - case tp: ThisType if tp.cls == defn.ArrayClass => ObjectReference.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 + 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 case tp: ThisType => storage.getClassBTypeAndRegisterInnerClass(tp.cls) // case t: SingletonType => primitiveOrClassToBType(t.classSymbol) case t: SingletonType => typeToTypeKind(t.underlying)(ct)(storage) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala index b63a080dcead..b96a5949fe88 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala @@ -254,7 +254,7 @@ trait BCodeIdiomatic { case ct: ClassBType if ct.isSubtypeOf(jlCharSequenceRef) => jlCharSequenceRef // Don't match for `ArrayBType(CHAR)`, even though StringBuilder has such an overload: // `"a" + Array('b')` should NOT be "ab", but "a[C@...". - case _: RefBType => ObjectReference + case _: RefBType => ObjectRef // jlStringBuilder does not have overloads for byte and short, but we can just use the int version case BYTE | SHORT => INT case pt: PrimitiveBType => pt diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index 829b156be428..a7f517f03567 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -255,7 +255,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { private def initJClass(jclass: asm.ClassVisitor): Unit = { val ps = claszSymbol.info.parents - val superClass: String = if (ps.isEmpty) ObjectReference.internalName else internalName(ps.head.typeSymbol) + val superClass: String = if (ps.isEmpty) ObjectRef.internalName else internalName(ps.head.typeSymbol) val interfaceNames0 = classBTypeFromSymbol(claszSymbol).info.interfaces map { case classBType => if (classBType.isNestedClass) { innerClassBufferASM += classBType } diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala index 5bcc4e0efead..b5ed27511e7e 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSyncAndTry.scala @@ -30,7 +30,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { def genSynchronized(tree: Apply, expectedType: BType): BType = (tree: @unchecked) match { case Apply(TypeApply(fun, _), args) => - val monitor = locals.makeLocal(ObjectReference, "monitor", defn.ObjectType, tree.span) + val monitor = locals.makeLocal(ObjectRef, "monitor", defn.ObjectType, tree.span) val monCleanup = new asm.Label // if the synchronized block returns a result, store it in a local variable. @@ -40,7 +40,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { /* ------ (1) pushing and entering the monitor, also keeping a reference to it in a local var. ------ */ genLoadQualifier(fun) - bc dup ObjectReference + bc dup ObjectRef locals.store(monitor) emit(asm.Opcodes.MONITORENTER) @@ -185,7 +185,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { for (CaseDef(pat, _, caseBody) <- catches) yield { pat match { case Typed(Ident(nme.WILDCARD), tpt) => NamelessEH(tpeTK(tpt).asClassBType, caseBody) - case Ident(nme.WILDCARD) => NamelessEH(ThrowableReference, caseBody) + case Ident(nme.WILDCARD) => NamelessEH(jlThrowableRef, caseBody) case Bind(_, _) => BoundEH (pat.symbol, caseBody) } } @@ -324,7 +324,7 @@ trait BCodeSyncAndTry extends BCodeBodyBuilder { nopIfNeeded(startTryBody) val finalHandler = currProgramPoint() // version of the finally-clause reached via unhandled exception. protect(startTryBody, finalHandler, finalHandler, null) - val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(ThrowableReference, "exc", defn.ThrowableType, finalizer.span)) + val Local(eTK, _, eIdx, _) = locals(locals.makeLocal(jlThrowableRef, "exc", defn.ThrowableType, finalizer.span)) bc.store(eIdx, eTK) emitFinalizer(finalizer, null, isDuplicate = true) bc.load(eIdx, eTK) diff --git a/compiler/src/dotty/tools/backend/jvm/BTypes.scala b/compiler/src/dotty/tools/backend/jvm/BTypes.scala index da3bead08835..57bd343b6658 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypes.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypes.scala @@ -89,8 +89,8 @@ abstract class BTypes { final def isNonVoidPrimitiveType = isPrimitive && this != UNIT - final def isNullType = this == RT_NULL - final def isNothingType = this == RT_NOTHING + final def isNullType = this == srNullRef + final def isNothingType = this == srNothingRef final def isBoxed = this.isClass && boxedClasses(this.asClassBType) @@ -113,7 +113,7 @@ abstract class BTypes { this match { case ArrayBType(component) => - if (other == ObjectReference || other == jlCloneableReference || other == jioSerializableReference) true + if (other == ObjectRef || other == jlCloneableRef || other == jiSerializableRef) true else other match { case ArrayBType(otherComponoent) => component.conformsTo(otherComponoent) case _ => false @@ -122,7 +122,7 @@ abstract class BTypes { case classType: ClassBType => if (isBoxed) { if (other.isBoxed) this == other - else if (other == ObjectReference) true + else if (other == ObjectRef) true else other match { case otherClassType: ClassBType => classType.isSubtypeOf(otherClassType) // e.g., java/lang/Double conforms to java/lang/Number case _ => false @@ -165,7 +165,7 @@ abstract class BTypes { assert(other.isRef, s"Cannot compute maxType: $this, $other") // Approximate `lub`. The common type of two references is always ObjectReference. - ObjectReference + ObjectRef } /** @@ -668,7 +668,7 @@ abstract class BTypes { if (this == other) return true if (isInterface) { - if (other == ObjectReference) return true // interfaces conform to Object + if (other == ObjectRef) return true // interfaces conform to Object if (!other.isInterface) return false // this is an interface, the other is some class other than object. interfaces cannot extend classes, so the result is false. // else: this and other are both interfaces. continue to (*) } else { @@ -698,13 +698,13 @@ abstract class BTypes { // exercised by test/files/run/t4761.scala if (other.isSubtypeOf(this)) this else if (this.isSubtypeOf(other)) other - else ObjectReference + else ObjectRef case (true, false) => - if (other.isSubtypeOf(this)) this else ObjectReference + if (other.isSubtypeOf(this)) this else ObjectRef case (false, true) => - if (this.isSubtypeOf(other)) other else ObjectReference + if (this.isSubtypeOf(other)) other else ObjectRef case _ => // TODO @lry I don't really understand the reasoning here. diff --git a/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala b/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala index 88a2bf8faa35..076250196b46 100644 --- a/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala +++ b/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala @@ -54,15 +54,15 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface]](val bTyp defn.DoubleClass -> DOUBLE ) - lazy val BOXED_UNIT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Void]) - lazy val BOXED_BOOLEAN : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Boolean]) - lazy val BOXED_BYTE : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Byte]) - lazy val BOXED_SHORT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Short]) - lazy val BOXED_CHAR : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Character]) - lazy val BOXED_INT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Integer]) - lazy val BOXED_LONG : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Long]) - lazy val BOXED_FLOAT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Float]) - lazy val BOXED_DOUBLE : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Double]) + private lazy val BOXED_UNIT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Void]) + private lazy val BOXED_BOOLEAN : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Boolean]) + private lazy val BOXED_BYTE : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Byte]) + private lazy val BOXED_SHORT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Short]) + private lazy val BOXED_CHAR : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Character]) + private lazy val BOXED_INT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Integer]) + private lazy val BOXED_LONG : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Long]) + private lazy val BOXED_FLOAT : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Float]) + private lazy val BOXED_DOUBLE : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Double]) /** * Map from primitive types to their boxed class type. Useful when pushing class literals onto the @@ -105,42 +105,30 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface]](val bTyp } /* - * RT_NOTHING and RT_NULL exist at run-time only. They are the bytecode-level manifestation (in - * method signatures only) of what shows up as NothingClass resp. NullClass in Scala ASTs. + * srNothingRef and srNullRef exist at run-time only. They are the bytecode-level manifestation (in + * method signatures only) of what shows up as NothingClass (scala.Nothing) resp. NullClass (scala.Null) in Scala ASTs. * - * Therefore, when RT_NOTHING or RT_NULL are to be emitted, a mapping is needed: the internal + * Therefore, when srNothingRef or srNullRef are to be emitted, a mapping is needed: the internal * names of NothingClass and NullClass can't be emitted as-is. * TODO @lry Once there's a 2.11.3 starr, use the commented argument list. The current starr crashes on the type literal `scala.runtime.Nothing$` */ - lazy val RT_NOTHING : ClassBType = classBTypeFromSymbol(requiredClass("scala.runtime.Nothing$")) // (requiredClass[scala.runtime.Nothing$]) - lazy val RT_NULL : ClassBType = classBTypeFromSymbol(requiredClass("scala.runtime.Null$")) // (requiredClass[scala.runtime.Null$]) - - lazy val ObjectReference : ClassBType = classBTypeFromSymbol(defn.ObjectClass) - lazy val objArrayReference : ArrayBType = ArrayBType(ObjectReference) + lazy val srNothingRef : ClassBType = classBTypeFromSymbol(requiredClass("scala.runtime.Nothing$")) // (requiredClass[scala.runtime.Nothing$]) + lazy val srNullRef : ClassBType = classBTypeFromSymbol(requiredClass("scala.runtime.Null$")) // (requiredClass[scala.runtime.Null$]) + lazy val ObjectRef : ClassBType = classBTypeFromSymbol(defn.ObjectClass) lazy val StringRef : ClassBType = classBTypeFromSymbol(defn.StringClass) lazy val jlStringBuilderRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.StringBuilder]) lazy val jlStringBufferRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.StringBuffer]) lazy val jlCharSequenceRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.CharSequence]) lazy val jlClassRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.Class[_]]) - lazy val ThrowableReference : ClassBType = classBTypeFromSymbol(defn.ThrowableClass) - lazy val jlCloneableReference : ClassBType = classBTypeFromSymbol(defn.JavaCloneableClass) // java/lang/Cloneable - lazy val jlNPEReference : ClassBType = classBTypeFromSymbol(defn.NullPointerExceptionClass) // java/lang/NullPointerException - lazy val jioSerializableReference : ClassBType = classBTypeFromSymbol(requiredClass[java.io.Serializable]) // java/io/Serializable - lazy val scalaSerializableReference : ClassBType = classBTypeFromSymbol(requiredClass[scala.Serializable]) // scala/Serializable - lazy val classCastExceptionReference : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.ClassCastException]) // java/lang/ClassCastException + lazy val jlThrowableRef : ClassBType = classBTypeFromSymbol(defn.ThrowableClass) + lazy val jlCloneableRef : ClassBType = classBTypeFromSymbol(defn.JavaCloneableClass) // java/lang/Cloneable + lazy val jioSerializableRef : ClassBType = classBTypeFromSymbol(requiredClass[java.io.Serializable]) // java/io/Serializable + lazy val jlClassCastExceptionRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.ClassCastException]) // java/lang/ClassCastException lazy val jlIllegalArgExceptionRef: ClassBType = classBTypeFromSymbol(requiredClass[java.lang.IllegalArgumentException]) lazy val jliSerializedLambdaRef: ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.SerializedLambda]) - lazy val srBooleanRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BooleanRef]) - lazy val srByteRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.ByteRef]) - lazy val srCharRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.CharRef]) - lazy val srIntRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.IntRef]) - lazy val srLongRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LongRef]) - lazy val srFloatRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.FloatRef]) - lazy val srDoubleRef : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.DoubleRef]) - - lazy val BoxesRunTime: ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime]) + lazy val srBoxesRunTimeRef: ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime]) /** * Methods in scala.runtime.BoxesRuntime @@ -157,14 +145,14 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface]](val bTyp ) lazy val asmUnboxTo: Map[BType, MethodNameAndType] = Map( - BOOL -> MethodNameAndType("unboxToBoolean", MethodBType(List(ObjectReference), BOOL)), - BYTE -> MethodNameAndType("unboxToByte", MethodBType(List(ObjectReference), BYTE)), - CHAR -> MethodNameAndType("unboxToChar", MethodBType(List(ObjectReference), CHAR)), - SHORT -> MethodNameAndType("unboxToShort", MethodBType(List(ObjectReference), SHORT)), - INT -> MethodNameAndType("unboxToInt", MethodBType(List(ObjectReference), INT)), - LONG -> MethodNameAndType("unboxToLong", MethodBType(List(ObjectReference), LONG)), - FLOAT -> MethodNameAndType("unboxToFloat", MethodBType(List(ObjectReference), FLOAT)), - DOUBLE -> MethodNameAndType("unboxToDouble", MethodBType(List(ObjectReference), DOUBLE)) + BOOL -> MethodNameAndType("unboxToBoolean", MethodBType(List(ObjectRef), BOOL)), + BYTE -> MethodNameAndType("unboxToByte", MethodBType(List(ObjectRef), BYTE)), + CHAR -> MethodNameAndType("unboxToChar", MethodBType(List(ObjectRef), CHAR)), + SHORT -> MethodNameAndType("unboxToShort", MethodBType(List(ObjectRef), SHORT)), + INT -> MethodNameAndType("unboxToInt", MethodBType(List(ObjectRef), INT)), + LONG -> MethodNameAndType("unboxToLong", MethodBType(List(ObjectRef), LONG)), + FLOAT -> MethodNameAndType("unboxToFloat", MethodBType(List(ObjectRef), FLOAT)), + DOUBLE -> MethodNameAndType("unboxToDouble", MethodBType(List(ObjectRef), DOUBLE)) ) lazy val typeOfArrayOp: Map[Int, BType] = { @@ -178,7 +166,7 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface]](val bTyp (List(LARRAY_LENGTH, LARRAY_GET, LARRAY_SET) map (_ -> LONG)) ++ (List(FARRAY_LENGTH, FARRAY_GET, FARRAY_SET) map (_ -> FLOAT)) ++ (List(DARRAY_LENGTH, DARRAY_GET, DARRAY_SET) map (_ -> DOUBLE)) ++ - (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectReference)) : _* + (List(OARRAY_LENGTH, OARRAY_GET, OARRAY_SET) map (_ -> ObjectRef)) : _* ) } } @@ -197,12 +185,12 @@ trait CoreBTypesProxyGlobalIndependent[BTS <: BTypes] { def boxedClasses: Set[ClassBType] - def RT_NOTHING : ClassBType - def RT_NULL : ClassBType + def srNothingRef : ClassBType + def srNullRef : ClassBType - def ObjectReference : ClassBType - def jlCloneableReference : ClassBType - def jioSerializableReference : ClassBType + def ObjectRef : ClassBType + def jlCloneableRef : ClassBType + def jiSerializableRef : ClassBType } /** @@ -218,16 +206,6 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface def primitiveTypeMap: Map[Symbol, PrimitiveBType] = _coreBTypes.primitiveTypeMap - def BOXED_UNIT : ClassBType = _coreBTypes.BOXED_UNIT - def BOXED_BOOLEAN : ClassBType = _coreBTypes.BOXED_BOOLEAN - def BOXED_BYTE : ClassBType = _coreBTypes.BOXED_BYTE - def BOXED_SHORT : ClassBType = _coreBTypes.BOXED_SHORT - def BOXED_CHAR : ClassBType = _coreBTypes.BOXED_CHAR - def BOXED_INT : ClassBType = _coreBTypes.BOXED_INT - def BOXED_LONG : ClassBType = _coreBTypes.BOXED_LONG - def BOXED_FLOAT : ClassBType = _coreBTypes.BOXED_FLOAT - def BOXED_DOUBLE : ClassBType = _coreBTypes.BOXED_DOUBLE - def boxedClasses: Set[ClassBType] = _coreBTypes.boxedClasses def boxedClassOfPrimitive: Map[PrimitiveBType, ClassBType] = _coreBTypes.boxedClassOfPrimitive @@ -236,35 +214,23 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface def unboxResultType: Map[Symbol, PrimitiveBType] = _coreBTypes.unboxResultType - def RT_NOTHING : ClassBType = _coreBTypes.RT_NOTHING - def RT_NULL : ClassBType = _coreBTypes.RT_NULL - - def ObjectReference : ClassBType = _coreBTypes.ObjectReference - def objArrayReference : ArrayBType = _coreBTypes.objArrayReference + def srNothingRef : ClassBType = _coreBTypes.srNothingRef + def srNullRef : ClassBType = _coreBTypes.srNullRef + def ObjectRef : ClassBType = _coreBTypes.ObjectRef def StringRef : ClassBType = _coreBTypes.StringRef def jlStringBuilderRef : ClassBType = _coreBTypes.jlStringBuilderRef def jlStringBufferRef : ClassBType = _coreBTypes.jlStringBufferRef def jlCharSequenceRef : ClassBType = _coreBTypes.jlCharSequenceRef def jlClassRef : ClassBType = _coreBTypes.jlClassRef - def ThrowableReference : ClassBType = _coreBTypes.ThrowableReference - def jlCloneableReference : ClassBType = _coreBTypes.jlCloneableReference - def jlNPEReference : ClassBType = _coreBTypes.jlNPEReference - def jioSerializableReference : ClassBType = _coreBTypes.jioSerializableReference - def scalaSerializableReference : ClassBType = _coreBTypes.scalaSerializableReference - def classCastExceptionReference : ClassBType = _coreBTypes.classCastExceptionReference + def jlThrowableRef : ClassBType = _coreBTypes.jlThrowableRef + def jlCloneableRef : ClassBType = _coreBTypes.jlCloneableRef + def jiSerializableRef : ClassBType = _coreBTypes.jioSerializableRef + def jlClassCastExceptionRef : ClassBType = _coreBTypes.jlClassCastExceptionRef def jlIllegalArgExceptionRef : ClassBType = _coreBTypes.jlIllegalArgExceptionRef def jliSerializedLambdaRef : ClassBType = _coreBTypes.jliSerializedLambdaRef - def srBooleanRef : ClassBType = _coreBTypes.srBooleanRef - def srByteRef : ClassBType = _coreBTypes.srByteRef - def srCharRef : ClassBType = _coreBTypes.srCharRef - def srIntRef : ClassBType = _coreBTypes.srIntRef - def srLongRef : ClassBType = _coreBTypes.srLongRef - def srFloatRef : ClassBType = _coreBTypes.srFloatRef - def srDoubleRef : ClassBType = _coreBTypes.srDoubleRef - - def BoxesRunTime: ClassBType = _coreBTypes.BoxesRunTime + def srBoxesRuntimeRef: ClassBType = _coreBTypes.srBoxesRunTimeRef def asmBoxTo : Map[BType, MethodNameAndType] = _coreBTypes.asmBoxTo def asmUnboxTo: Map[BType, MethodNameAndType] = _coreBTypes.asmUnboxTo diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index b42b0d6e3dc8..a4c3edd7a905 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -422,7 +422,7 @@ class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrim // stack map frames and invokes the `getCommonSuperClass` method. This method expects all // ClassBTypes mentioned in the source code to exist in the map. - val serlamObjDesc = MethodBType(jliSerializedLambdaRef :: Nil, ObjectReference).descriptor + val serlamObjDesc = MethodBType(jliSerializedLambdaRef :: Nil, ObjectRef).descriptor val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", serlamObjDesc, null, null) def emitLambdaDeserializeIndy(targetMethods: Seq[Handle]): Unit = { From 0aa184da1b27601bec909fa3614e358d73597017 Mon Sep 17 00:00:00 2001 From: Wojciech Mazur Date: Tue, 10 May 2022 12:11:22 +0200 Subject: [PATCH 2/2] 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). --- .../tools/backend/jvm/BCodeBodyBuilder.scala | 27 +- .../tools/backend/jvm/BCodeHelpers.scala | 78 ++--- .../tools/backend/jvm/BCodeIdiomatic.scala | 12 +- .../tools/backend/jvm/BCodeSkelBuilder.scala | 17 +- .../tools/backend/jvm/BTypesFromSymbols.scala | 22 +- .../dotty/tools/backend/jvm/CoreBTypes.scala | 61 +++- .../dotty/tools/backend/jvm/GenBCode.scala | 37 +- .../backend/jvm/GenericSignatureVisitor.scala | 326 ++++++++++++++++++ 8 files changed, 468 insertions(+), 112 deletions(-) create mode 100644 compiler/src/dotty/tools/backend/jvm/GenericSignatureVisitor.scala diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala index 407b875fc1f1..e2a18386224e 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeBodyBuilder.scala @@ -39,7 +39,6 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { import DottyBackendInterface.symExtensions import bTypes._ import coreBTypes._ - import BCodeBodyBuilder._ protected val primitives: DottyPrimitives @@ -1753,9 +1752,9 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { val metafactory = if (flags != 0) - lambdaMetaFactoryAltMetafactoryHandle // altMetafactory required to be able to pass the flags and additional arguments if needed + jliLambdaMetaFactoryAltMetafactoryHandle // altMetafactory required to be able to pass the flags and additional arguments if needed else - lambdaMetaFactoryMetafactoryHandle + jliLambdaMetaFactoryMetafactoryHandle bc.jmethod.visitInvokeDynamicInsn(methodName, desc, metafactory, bsmArgs: _*) @@ -1772,27 +1771,5 @@ trait BCodeBodyBuilder extends BCodeSkelBuilder { private def isEmittedInterface(sym: Symbol): Boolean = sym.isInterface || sym.is(JavaDefined) && (toDenot(sym).isAnnotation || sym.is(ModuleClass) && (sym.companionClass.is(PureInterface)) || sym.companionClass.is(Trait)) -} -object BCodeBodyBuilder { - val lambdaMetaFactoryMetafactoryHandle = new Handle( - Opcodes.H_INVOKESTATIC, - "java/lang/invoke/LambdaMetafactory", - "metafactory", - "(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;", - /* itf = */ false) - - val lambdaMetaFactoryAltMetafactoryHandle = new Handle( - Opcodes.H_INVOKESTATIC, - "java/lang/invoke/LambdaMetafactory", - "altMetafactory", - "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;", - /* itf = */ false) - - val lambdaDeserializeBootstrapHandle = new Handle( - Opcodes.H_INVOKESTATIC, - "scala/runtime/LambdaDeserialize", - "bootstrap", - "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite;", - /* itf = */ false) } diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala index d9e6ac492342..ffefa254a4c3 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala @@ -132,24 +132,24 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } /* - * Populates the InnerClasses JVM attribute with `refedInnerClasses`. - * In addition to inner classes mentioned somewhere in `jclass` (where `jclass` is a class file being emitted) - * `refedInnerClasses` should contain those inner classes defined as direct member classes of `jclass` - * but otherwise not mentioned in `jclass`. + * Populates the InnerClasses JVM attribute with `refedInnerClasses`. See also the doc on inner + * classes in BTypes.scala. * - * `refedInnerClasses` may contain duplicates, - * need not contain the enclosing inner classes of each inner class it lists (those are looked up for consistency). + * `refedInnerClasses` may contain duplicates, need not contain the enclosing inner classes of + * each inner class it lists (those are looked up and included). * - * This method serializes in the InnerClasses JVM attribute in an appropriate order, + * This method serializes in the InnerClasses JVM attribute in an appropriate order, * not necessarily that given by `refedInnerClasses`. * * can-multi-thread */ - final def addInnerClassesASM(jclass: asm.ClassVisitor, refedInnerClasses: List[ClassBType]): Unit = { - val allNestedClasses = refedInnerClasses.flatMap(_.enclosingNestedClassesChain).distinct - + final def addInnerClasses(jclass: asm.ClassVisitor, declaredInnerClasses: List[ClassBType], refedInnerClasses: List[ClassBType]): Unit = { // sorting ensures nested classes are listed after their enclosing class thus satisfying the Eclipse Java compiler - for (nestedClass <- allNestedClasses.sortBy(_.internalName.toString)) { + val allNestedClasses = new mutable.TreeSet[ClassBType]()(Ordering.by(_.internalName)) + allNestedClasses ++= declaredInnerClasses + refedInnerClasses.foreach(allNestedClasses ++= _.enclosingNestedClassesChain) + for nestedClass <- allNestedClasses + do { // Extract the innerClassEntry - we know it exists, enclosingNestedClassesChain only returns nested classes. val Some(e) = nestedClass.innerClassAttributeEntry jclass.visitInnerClass(e.name, e.outerName, e.innerName, e.flags) @@ -218,26 +218,16 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { final val emitLines = debugLevel >= 2 final val emitVars = debugLevel >= 3 - /* - * Contains class-symbols that: - * (a) are known to denote inner classes - * (b) are mentioned somewhere in the class being generated. - * - * In other words, the lifetime of `innerClassBufferASM` is associated to "the class being generated". - */ - final val innerClassBufferASM = mutable.Set.empty[ClassBType] - /** - * The class internal name for a given class symbol. If the symbol describes a nested class, the - * ClassBType is added to the innerClassBufferASM. + * The class internal name for a given class symbol. */ final def internalName(sym: Symbol): String = { // For each java class, the scala compiler creates a class and a module (thus a module class). - // If the `sym` is a java module class, we use the java class instead. This ensures that we - // register the class (instead of the module class) in innerClassBufferASM. + // If the `sym` is a java module class, we use the java class instead. This ensures that the + // ClassBType is created from the main class (instead of the module class). // The two symbols have the same name, so the resulting internalName is the same. val classSym = if (sym.is(JavaDefined) && sym.is(ModuleClass)) sym.linkedClass else sym - getClassBTypeAndRegisterInnerClass(classSym).internalName + getClassBType(classSym).internalName } private def assertClassNotArray(sym: Symbol): Unit = { @@ -251,8 +241,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } /** - * The ClassBType for a class symbol. If the class is nested, the ClassBType is added to the - * innerClassBufferASM. + * The ClassBType for a class symbol. * * The class symbol scala.Nothing is mapped to the class scala.runtime.Nothing$. Similarly, * 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 { * the class descriptor of the receiver (the implementation class) is obtained by creating the * ClassBType. */ - final def getClassBTypeAndRegisterInnerClass(sym: Symbol): ClassBType = { + final def getClassBType(sym: Symbol): ClassBType = { assertClassNotArrayNotPrimitive(sym) if (sym == defn.NothingClass) srNothingRef else if (sym == defn.NullClass) srNullRef - else { - val r = classBTypeFromSymbol(sym) - if (r.isNestedClass) innerClassBufferASM += r - r - } + else classBTypeFromSymbol(sym) } /* @@ -288,16 +273,14 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { } /** - * The jvm descriptor of a type. If `t` references a nested class, its ClassBType is added to - * the innerClassBufferASM. + * The jvm descriptor of a type. */ final def typeDescriptor(t: Type): String = { toTypeKind(t).descriptor } /** - * The jvm descriptor for a symbol. If `sym` represents a nested class, its ClassBType is added - * to the innerClassBufferASM. + * The jvm descriptor for a symbol. */ - final def symDescriptor(sym: Symbol): String = { getClassBTypeAndRegisterInnerClass(sym).descriptor } + final def symDescriptor(sym: Symbol): String = getClassBType(sym).descriptor final def toTypeKind(tp: Type): BType = typeToTypeKind(tp)(BCodeHelpers.this)(this) @@ -691,16 +674,15 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { def genMirrorClass(moduleClass: Symbol, cunit: CompilationUnit): asm.tree.ClassNode = { assert(moduleClass.is(ModuleClass)) assert(moduleClass.companionClass == NoSymbol, moduleClass) - innerClassBufferASM.clear() this.cunit = cunit + val bType = mirrorClassBTypeFromSymbol(moduleClass) val moduleName = internalName(moduleClass) // + "$" - val mirrorName = moduleName.substring(0, moduleName.length() - 1) + val mirrorName = bType.internalName - val flags = (asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL) val mirrorClass = new asm.tree.ClassNode mirrorClass.visit( classfileVersion, - flags, + bType.info.flags, mirrorName, null /* no java-generic-signature */, ObjectRef.internalName, @@ -717,10 +699,6 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { emitAnnotations(mirrorClass, moduleClass.annotations ++ ssa) addForwarders(mirrorClass, mirrorName, moduleClass) - - innerClassBufferASM ++= classBTypeFromSymbol(moduleClass).info.memberClasses - addInnerClassesASM(mirrorClass, innerClassBufferASM.toList) - mirrorClass.visitEnd() moduleClass.name // this side-effect is necessary, really. @@ -754,8 +732,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { * must-single-thread */ def legacyAddCreatorCode(clinit: asm.MethodVisitor, cnode: asm.tree.ClassNode, thisName: String): Unit = { - // this tracks the inner class in innerClassBufferASM, if needed. - val androidCreatorType = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass) + val androidCreatorType = getClassBType(AndroidCreatorClass) val tdesc_creator = androidCreatorType.descriptor cnode.visitField( @@ -818,8 +795,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { def primitiveOrClassToBType(sym: Symbol): BType = { assert(sym.isClass, sym) assert(sym != defn.ArrayClass || compilingArray, sym) - primitiveTypeMap.getOrElse(sym, - storage.getClassBTypeAndRegisterInnerClass(sym)).asInstanceOf[BType] + primitiveTypeMap.getOrElse(sym, storage.getClassBType(sym)).asInstanceOf[BType] } /** @@ -862,7 +838,7 @@ trait BCodeHelpers extends BCodeIdiomatic with BytecodeWriters { tp match { 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 - case tp: ThisType => storage.getClassBTypeAndRegisterInnerClass(tp.cls) + case tp: ThisType => storage.getClassBType(tp.cls) // case t: SingletonType => primitiveOrClassToBType(t.classSymbol) case t: SingletonType => typeToTypeKind(t.underlying)(ct)(storage) case t: RefinedType => typeToTypeKind(t.parent)(ct)(storage) //parents.map(_.toTypeKind(ct)(storage).asClassBType).reduceLeft((a, b) => a.jvmWiseLUB(b)) diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala index b96a5949fe88..02268c2919ba 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeIdiomatic.scala @@ -268,8 +268,10 @@ trait BCodeIdiomatic { * can-multi-thread */ final def genStringBuilderEnd: Unit = { - invokevirtual(JavaStringBuilderClassName, "toString", "()Ljava/lang/String;") + invokevirtual(JavaStringBuilderClassName, "toString", genStringBuilderEndDesc) } + // Use ClassBType refs instead of plain string literal to make sure that needed ClassBTypes are initialized and reachable + private lazy val genStringBuilderEndDesc = MethodBType(Nil, StringRef).descriptor /* Concatenate top N arguments on the stack with `StringConcatFactory#makeConcatWithConstants` * (only works for JDK 9+) @@ -284,13 +286,7 @@ trait BCodeIdiomatic { jmethod.visitInvokeDynamicInsn( "makeConcatWithConstants", asm.Type.getMethodDescriptor(StringRef.toASMType, argTypes:_*), - new asm.Handle( - asm.Opcodes.H_INVOKESTATIC, - "java/lang/invoke/StringConcatFactory", - "makeConcatWithConstants", - "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/invoke/CallSite;", - false - ), + coreBTypes.jliStringConcatFactoryMakeConcatWithConstantsHandle, (recipe +: constants):_* ) } diff --git a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala index a7f517f03567..c79a5543e99a 100644 --- a/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala +++ b/compiler/src/dotty/tools/backend/jvm/BCodeSkelBuilder.scala @@ -112,7 +112,6 @@ trait BCodeSkelBuilder extends BCodeHelpers { def genPlainClass(cd0: TypeDef) = cd0 match { case TypeDef(_, impl: Template) => assert(cnode == null, "GenBCode detected nested methods.") - innerClassBufferASM.clear() claszSymbol = cd0.symbol isCZParcelable = isAndroidParcelableClass(claszSymbol) @@ -231,15 +230,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { if (optSerial.isDefined) { addSerialVUID(optSerial.get, cnode)} addClassFields() - - innerClassBufferASM ++= classBTypeFromSymbol(claszSymbol).info.memberClasses - - val companion = claszSymbol.companionClass - if companion.isTopLevelModuleClass then - innerClassBufferASM ++= classBTypeFromSymbol(companion).info.memberClasses - gen(cd.rhs) - addInnerClassesASM(cnode, innerClassBufferASM.toList) if (AsmUtils.traceClassEnabled && cnode.name.contains(AsmUtils.traceClassPattern)) AsmUtils.traceClass(cnode) @@ -256,11 +247,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { val ps = claszSymbol.info.parents val superClass: String = if (ps.isEmpty) ObjectRef.internalName else internalName(ps.head.typeSymbol) - val interfaceNames0 = classBTypeFromSymbol(claszSymbol).info.interfaces map { - case classBType => - if (classBType.isNestedClass) { innerClassBufferASM += classBType } - classBType.internalName - } + val interfaceNames0 = classBTypeFromSymbol(claszSymbol).info.interfaces.map(_.internalName) /* To avoid deadlocks when combining objects, lambdas and multi-threading, * lambdas in objects are compiled to instance methods of the module class * instead of static methods (see tests/run/deadlock.scala and @@ -881,7 +868,7 @@ trait BCodeSkelBuilder extends BCodeHelpers { // android creator code if (isCZParcelable) { // add a static field ("CREATOR") to this class to cache android.os.Parcelable$Creator - val andrFieldDescr = getClassBTypeAndRegisterInnerClass(AndroidCreatorClass).descriptor + val andrFieldDescr = classBTypeFromSymbol(AndroidCreatorClass).descriptor cnode.visitField( asm.Opcodes.ACC_STATIC | asm.Opcodes.ACC_FINAL, "CREATOR", diff --git a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala index 58e6265376fd..bf275dfa7a62 100644 --- a/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala +++ b/compiler/src/dotty/tools/backend/jvm/BTypesFromSymbols.scala @@ -13,6 +13,7 @@ import dotty.tools.dotc.core.Phases._ import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.core.Phases.Phase import dotty.tools.dotc.transform.SymUtils._ +import dotty.tools.dotc.core.StdNames /** * This class mainly contains the method classBTypeFromSymbol, which extracts the necessary @@ -91,6 +92,20 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes { }) } + final def mirrorClassBTypeFromSymbol(moduleClassSym: Symbol): ClassBType = { + assert(moduleClassSym.isTopLevelModuleClass, s"not a top-level module class: $moduleClassSym") + val internalName = moduleClassSym.javaBinaryName.stripSuffix(StdNames.str.MODULE_SUFFIX) + val bType = ClassBType(internalName) + bType.info = ClassInfo( + superClass = Some(ObjectRef), + interfaces = Nil, + flags = asm.Opcodes.ACC_SUPER | asm.Opcodes.ACC_PUBLIC | asm.Opcodes.ACC_FINAL, + memberClasses = getMemberClasses(moduleClassSym).map(classBTypeFromSymbol), + nestedInfo = None + ) + bType + } + private def setClassInfo(classSym: Symbol, classBType: ClassBType): ClassBType = { val superClassSym: Symbol = { val t = classSym.asClass.superClass @@ -138,13 +153,6 @@ class BTypesFromSymbols[I <: DottyBackendInterface](val int: I) extends BTypes { /* The InnerClass table of a class C must contain all nested classes of C, even if they are only * declared but not otherwise referenced in C (from the bytecode or a method / field signature). * We collect them here. - * - * Nested classes that are also referenced in C will be added to the innerClassBufferASM during - * code generation, but those duplicates will be eliminated when emitting the InnerClass - * attribute. - * - * Why doe we need to collect classes into innerClassBufferASM at all? To collect references to - * nested classes, but NOT nested in C, that are used within C. */ val nestedClassSymbols = { // The lambdalift phase lifts all nested classes to the enclosing class, so if we collect diff --git a/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala b/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala index 076250196b46..e94bda16fbb8 100644 --- a/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala +++ b/compiler/src/dotty/tools/backend/jvm/CoreBTypes.scala @@ -5,6 +5,8 @@ package jvm import dotty.tools.dotc.core.Symbols._ import dotty.tools.dotc.transform.Erasure +import scala.tools.asm.{Handle, Opcodes} +import dotty.tools.dotc.core.StdNames /** * Core BTypes and some other definitions. The initialization of these definitions requies access @@ -125,11 +127,59 @@ class CoreBTypes[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface]](val bTyp lazy val jlCloneableRef : ClassBType = classBTypeFromSymbol(defn.JavaCloneableClass) // java/lang/Cloneable lazy val jioSerializableRef : ClassBType = classBTypeFromSymbol(requiredClass[java.io.Serializable]) // java/io/Serializable lazy val jlClassCastExceptionRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.ClassCastException]) // java/lang/ClassCastException - lazy val jlIllegalArgExceptionRef: ClassBType = classBTypeFromSymbol(requiredClass[java.lang.IllegalArgumentException]) - lazy val jliSerializedLambdaRef: ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.SerializedLambda]) - + lazy val jlIllegalArgExceptionRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.IllegalArgumentException]) + lazy val jliSerializedLambdaRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.SerializedLambda]) + lazy val srBoxesRunTimeRef: ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.BoxesRunTime]) + private lazy val jliCallSiteRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.CallSite]) + private lazy val jliLambdaMetafactoryRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.LambdaMetafactory]) + private lazy val jliMethodHandleRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodHandle]) + private lazy val jliMethodHandlesLookupRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodHandles.Lookup]) + private lazy val jliMethodTypeRef : ClassBType = classBTypeFromSymbol(requiredClass[java.lang.invoke.MethodType]) + private lazy val jliStringConcatFactoryRef : ClassBType = classBTypeFromSymbol(requiredClass("java.lang.invoke.StringConcatFactory")) // since JDK 9 + private lazy val srLambdaDeserialize : ClassBType = classBTypeFromSymbol(requiredClass[scala.runtime.LambdaDeserialize]) + + lazy val jliLambdaMetaFactoryMetafactoryHandle: Handle = new Handle( + Opcodes.H_INVOKESTATIC, + jliLambdaMetafactoryRef.internalName, + "metafactory", + MethodBType( + List(jliMethodHandlesLookupRef, StringRef, jliMethodTypeRef, jliMethodTypeRef, jliMethodHandleRef, jliMethodTypeRef), + jliCallSiteRef + ).descriptor, + /* itf = */ false) + + lazy val jliLambdaMetaFactoryAltMetafactoryHandle: Handle = new Handle( + Opcodes.H_INVOKESTATIC, + jliLambdaMetafactoryRef.internalName, + "altMetafactory", + MethodBType( + List(jliMethodHandlesLookupRef, StringRef, jliMethodTypeRef, ArrayBType(ObjectRef)), + jliCallSiteRef + ).descriptor, + /* itf = */ false) + + lazy val jliLambdaDeserializeBootstrapHandle: Handle = new Handle( + Opcodes.H_INVOKESTATIC, + srLambdaDeserialize.internalName, + "bootstrap", + MethodBType( + List(jliMethodHandlesLookupRef, StringRef, jliMethodTypeRef, ArrayBType(jliMethodHandleRef)), + jliCallSiteRef + ).descriptor, + /* itf = */ false) + + lazy val jliStringConcatFactoryMakeConcatWithConstantsHandle = new Handle( + Opcodes.H_INVOKESTATIC, + jliStringConcatFactoryRef.internalName, + "makeConcatWithConstants", + MethodBType( + List(jliMethodHandlesLookupRef, StringRef, jliMethodTypeRef, StringRef, ArrayBType(ObjectRef)), + jliCallSiteRef + ).descriptor, + /* itf = */ false) + /** * Methods in scala.runtime.BoxesRuntime */ @@ -232,6 +282,11 @@ final class CoreBTypesProxy[BTFS <: BTypesFromSymbols[_ <: DottyBackendInterface def srBoxesRuntimeRef: ClassBType = _coreBTypes.srBoxesRunTimeRef + def jliLambdaMetaFactoryMetafactoryHandle : Handle = _coreBTypes.jliLambdaMetaFactoryMetafactoryHandle + def jliLambdaMetaFactoryAltMetafactoryHandle : Handle = _coreBTypes.jliLambdaMetaFactoryAltMetafactoryHandle + def jliLambdaDeserializeBootstrapHandle : Handle = _coreBTypes.jliLambdaDeserializeBootstrapHandle + def jliStringConcatFactoryMakeConcatWithConstantsHandle: Handle = _coreBTypes.jliStringConcatFactoryMakeConcatWithConstantsHandle + def asmBoxTo : Map[BType, MethodNameAndType] = _coreBTypes.asmBoxTo def asmUnboxTo: Map[BType, MethodNameAndType] = _coreBTypes.asmUnboxTo diff --git a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala index a4c3edd7a905..6430575f304e 100644 --- a/compiler/src/dotty/tools/backend/jvm/GenBCode.scala +++ b/compiler/src/dotty/tools/backend/jvm/GenBCode.scala @@ -356,6 +356,8 @@ class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrim * - converting the plain ClassNode to byte array and placing it on queue-3 */ class Worker2 { + import bTypes.ClassBType + import bTypes.coreBTypes.jliLambdaMetaFactoryAltMetafactoryHandle // lazy val localOpt = new LocalOpt(new Settings()) private def localOptimizations(classNode: ClassNode): Unit = { @@ -372,7 +374,7 @@ class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrim val insn = iter.next() insn match { case indy: InvokeDynamicInsnNode - if indy.bsm == BCodeBodyBuilder.lambdaMetaFactoryAltMetafactoryHandle => + if indy.bsm == jliLambdaMetaFactoryAltMetafactoryHandle => import java.lang.invoke.LambdaMetafactory.FLAG_SERIALIZABLE val metafactoryFlags = indy.bsmArgs(3).asInstanceOf[Integer].toInt val isSerializable = (metafactoryFlags & FLAG_SERIALIZABLE) != 0 @@ -410,7 +412,6 @@ class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrim */ private def addLambdaDeserialize(classNode: ClassNode, implMethodsArray: Array[Handle]): Unit = { import asm.Opcodes._ - import BCodeBodyBuilder._ import bTypes._ import coreBTypes._ @@ -427,7 +428,7 @@ class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrim val mv = cw.visitMethod(ACC_PRIVATE + ACC_STATIC + ACC_SYNTHETIC, "$deserializeLambda$", serlamObjDesc, null, null) def emitLambdaDeserializeIndy(targetMethods: Seq[Handle]): Unit = { mv.visitVarInsn(ALOAD, 0) - mv.visitInvokeDynamicInsn("lambdaDeserialize", serlamObjDesc, lambdaDeserializeBootstrapHandle, targetMethods: _*) + mv.visitInvokeDynamicInsn("lambdaDeserialize", serlamObjDesc, jliLambdaDeserializeBootstrapHandle, targetMethods: _*) } val targetMethodGroupLimit = 255 - 1 - 3 // JVM limit. See See MAX_MH_ARITY in CallSite.java @@ -452,6 +453,34 @@ class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrim mv.visitInsn(ARETURN) } + private def setInnerClasses(classNode: ClassNode): Unit = if (classNode != null) { + classNode.innerClasses.clear() + val (declared, referred) = collectNestedClasses(classNode) + addInnerClasses(classNode, declared, referred) + } + + /** + * Visit the class node and collect all referenced nested classes. + */ + private def collectNestedClasses(classNode: ClassNode): (List[ClassBType], List[ClassBType]) = { + // type InternalName = String + val c = new NestedClassesCollector[ClassBType](nestedOnly = true) { + def declaredNestedClasses(internalName: InternalName): List[ClassBType] = + bTypes.classBTypeFromInternalName(internalName).info.memberClasses + + def getClassIfNested(internalName: InternalName): Option[ClassBType] = { + val c = bTypes.classBTypeFromInternalName(internalName) + Option.when(c.isNestedClass)(c) + } + + def raiseError(msg: String, sig: String, e: Option[Throwable]): Unit = { + // don't crash on invalid generic signatures + } + } + c.visit(classNode) + (c.declaredInnerClasses.toList, c.referredInnerClasses.toList) + } + def run(): Unit = { while (true) { val item = q2.poll @@ -466,6 +495,8 @@ class GenBCodePipeline(val int: DottyBackendInterface, val primitives: DottyPrim val serializableLambdas = collectSerializableLambdas(plainNode) if (serializableLambdas.nonEmpty) addLambdaDeserialize(plainNode, serializableLambdas) + setInnerClasses(plainNode) + setInnerClasses(item.mirror.classNode) addToQ3(item) } catch { case ex: InterruptedException => diff --git a/compiler/src/dotty/tools/backend/jvm/GenericSignatureVisitor.scala b/compiler/src/dotty/tools/backend/jvm/GenericSignatureVisitor.scala new file mode 100644 index 000000000000..e9e532933290 --- /dev/null +++ b/compiler/src/dotty/tools/backend/jvm/GenericSignatureVisitor.scala @@ -0,0 +1,326 @@ +package dotty.tools.backend.jvm + +import scala.language.unsafeNulls + +import scala.tools.asm.{ClassReader, Type, Handle } +import scala.tools.asm.tree._ + +import scala.collection.mutable +import scala.util.control.{NoStackTrace, NonFatal} +import scala.annotation._ +import scala.jdk.CollectionConverters._ + +// Backported from scala/scala, commit sha: 724be0e9425b9ad07c244d25efdad695d75abbcf +// https://github.com/scala/scala/blob/724be0e9425b9ad07c244d25efdad695d75abbcf/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala#L928 +abstract class GenericSignatureVisitor(nestedOnly: Boolean) { + // For performance (`Char => Boolean` is not specialized) + private trait CharBooleanFunction { def apply(c: Char): Boolean } + + final def visitInternalName(internalName: String): Unit = visitInternalName(internalName, 0, if (internalName eq null) 0 else internalName.length) + def visitInternalName(internalName: String, offset: Int, length: Int): Unit + + def raiseError(msg: String, sig: String, e: Option[Throwable] = None): Unit + + def visitClassSignature(sig: String): Unit = if (sig != null) { + val p = new Parser(sig, nestedOnly) + p.safely { p.classSignature() } + } + + def visitMethodSignature(sig: String): Unit = if (sig != null) { + val p = new Parser(sig, nestedOnly) + p.safely { p.methodSignature() } + } + + def visitFieldSignature(sig: String): Unit = if (sig != null) { + val p = new Parser(sig, nestedOnly) + p.safely { p.fieldSignature() } + } + + private final class Parser(sig: String, nestedOnly: Boolean) { + + private var index = 0 + private val end = sig.length + + private val Aborted: Throwable = new NoStackTrace { } + private def abort(): Nothing = throw Aborted + + @inline def safely(f: => Unit): Unit = try f catch { + case Aborted => + case NonFatal(e) => raiseError(s"Exception thrown during signature parsing", sig, Some(e)) + } + + private def current = { + if (index >= end) { + raiseError(s"Out of bounds, $index >= $end", sig) + abort() // Don't continue, even if `notifyInvalidSignature` returns + } + sig.charAt(index) + } + + private def accept(c: Char): Unit = { + if (current != c) { + raiseError(s"Expected $c at $index, found $current", sig) + abort() + } + index += 1 + } + + private def skip(): Unit = { index += 1 } + private def getCurrentAndSkip(): Char = { val c = current; skip(); c } + + private def skipUntil(isDelimiter: CharBooleanFunction): Unit = { + while (!isDelimiter(current)) { index += 1 } + } + private def skipUntilDelimiter(delimiter: Char): Unit = { + sig.indexOf(delimiter, index) match { + case -1 => + raiseError(s"Out of bounds", sig) + abort() // Don't continue, even if `notifyInvalidSignature` returns + case i => + index = i + } + } + + private def appendUntil(builder: java.lang.StringBuilder, isDelimiter: CharBooleanFunction): Unit = { + val start = index + skipUntil(isDelimiter) + builder.append(sig, start, index) + } + + def isBaseType(c: Char): Boolean = c match { + case 'B' | 'C' | 'D' | 'F' | 'I' | 'J' | 'S' | 'Z' => true + case _ => false + } + + private val isClassNameEnd: CharBooleanFunction = (c: Char) => c == '<' || c == '.' || c == ';' + + private def typeArguments(): Unit = if (current == '<') { + skip() + while (current != '>') current match { + case '*' | '+' | '-' => + skip() + case _ => + referenceTypeSignature() + } + accept('>') + } + + @tailrec private def referenceTypeSignature(): Unit = getCurrentAndSkip() match { + case 'L' => + var names: java.lang.StringBuilder = null + + val start = index + var seenDollar = false + while (!isClassNameEnd(current)) { + seenDollar ||= current == '$' + index += 1 + } + if ((current == '.' || seenDollar) || !nestedOnly) { + // OPT: avoid allocations when only a top-level class is encountered + names = new java.lang.StringBuilder(32) + names.append(sig, start, index) + visitInternalName(names.toString) + } + typeArguments() + + while (current == '.') { + skip() + names.append('$') + appendUntil(names, isClassNameEnd) + visitInternalName(names.toString) + typeArguments() + } + accept(';') + + case 'T' => + skipUntilDelimiter(';') + skip() + + case '[' => + if (isBaseType(current)) skip() + else referenceTypeSignature() + } + + private def typeParameters(): Unit = if (current == '<') { + skip() + while (current != '>') { + skipUntilDelimiter(':'); skip() + val c = current + // The ClassBound can be missing, but only if there's an InterfaceBound after. + // This is an assumption that's not in the spec, see https://stackoverflow.com/q/44284928 + if (c != ':' && c != '>') { referenceTypeSignature() } + while (current == ':') { skip(); referenceTypeSignature() } + } + accept('>') + } + + def classSignature(): Unit = { + typeParameters() + while (index < end) referenceTypeSignature() + } + + def methodSignature(): Unit = { + typeParameters() + + accept('(') + while (current != ')') { + if (isBaseType(current)) skip() + else referenceTypeSignature() + } + accept(')') + + if (current == 'V' || isBaseType(current)) skip() + else referenceTypeSignature() + + while (index < end) { + accept('^') + referenceTypeSignature() + } + } + + def fieldSignature(): Unit = if (sig != null) safely { + referenceTypeSignature() + } + } +} + +// Backported from scala/scala, commit sha: 724be0e9425b9ad07c244d25efdad695d75abbcf +// https://github.com/scala/scala/blob/724be0e9425b9ad07c244d25efdad695d75abbcf/src/compiler/scala/tools/nsc/backend/jvm/analysis/BackendUtils.scala#L790 +abstract class NestedClassesCollector[T](nestedOnly: Boolean) extends GenericSignatureVisitor(nestedOnly) { + type InternalName = String + + def declaredNestedClasses(internalName: InternalName): List[T] + def getClassIfNested(internalName: InternalName): Option[T] + + val declaredInnerClasses = mutable.Set.empty[T] + val referredInnerClasses = mutable.Set.empty[T] + + def innerClasses: collection.Set[T] = declaredInnerClasses ++ referredInnerClasses + def clear(): Unit = { + declaredInnerClasses.clear() + referredInnerClasses.clear() + } + + def visit(classNode: ClassNode): Unit = { + visitInternalName(classNode.name) + declaredInnerClasses ++= declaredNestedClasses(classNode.name) + + visitInternalName(classNode.superName) + classNode.interfaces.asScala foreach visitInternalName + visitInternalName(classNode.outerClass) + + visitAnnotations(classNode.visibleAnnotations) + visitAnnotations(classNode.visibleTypeAnnotations) + visitAnnotations(classNode.invisibleAnnotations) + visitAnnotations(classNode.invisibleTypeAnnotations) + + visitClassSignature(classNode.signature) + + for (f <- classNode.fields.asScala) { + visitDescriptor(f.desc) + visitAnnotations(f.visibleAnnotations) + visitAnnotations(f.visibleTypeAnnotations) + visitAnnotations(f.invisibleAnnotations) + visitAnnotations(f.invisibleTypeAnnotations) + visitFieldSignature(f.signature) + } + + for (m <- classNode.methods.asScala) { + visitDescriptor(m.desc) + + visitAnnotations(m.visibleAnnotations) + visitAnnotations(m.visibleTypeAnnotations) + visitAnnotations(m.invisibleAnnotations) + visitAnnotations(m.invisibleTypeAnnotations) + visitAnnotationss(m.visibleParameterAnnotations) + visitAnnotationss(m.invisibleParameterAnnotations) + visitAnnotations(m.visibleLocalVariableAnnotations) + visitAnnotations(m.invisibleLocalVariableAnnotations) + + m.exceptions.asScala foreach visitInternalName + for (tcb <- m.tryCatchBlocks.asScala) visitInternalName(tcb.`type`) + + val iter = m.instructions.iterator + while (iter.hasNext) iter.next() match { + case ti: TypeInsnNode => visitInternalNameOrArrayReference(ti.desc) + case fi: FieldInsnNode => visitInternalNameOrArrayReference(fi.owner); visitDescriptor(fi.desc) + case mi: MethodInsnNode => visitInternalNameOrArrayReference(mi.owner); visitDescriptor(mi.desc) + case id: InvokeDynamicInsnNode => visitDescriptor(id.desc); visitHandle(id.bsm); id.bsmArgs foreach visitConstant + case ci: LdcInsnNode => visitConstant(ci.cst) + case ma: MultiANewArrayInsnNode => visitDescriptor(ma.desc) + case _ => + } + + visitMethodSignature(m.signature) + } + } + + private def containsChar(s: String, offset: Int, length: Int, char: Char): Boolean = { + val ix = s.indexOf(char, offset) + !(ix == -1 || ix >= offset + length) + } + + def visitInternalName(internalName: String, offset: Int, length: Int): Unit = if (internalName != null && containsChar(internalName, offset, length, '$')) { + for (c <- getClassIfNested(internalName.substring(offset, length))) + if (!declaredInnerClasses.contains(c)) + referredInnerClasses += c + } + + // either an internal/Name or [[Linternal/Name; -- there are certain references in classfiles + // that are either an internal name (without the surrounding `L;`) or an array descriptor + // `[Linternal/Name;`. + def visitInternalNameOrArrayReference(ref: String): Unit = if (ref != null) { + val bracket = ref.lastIndexOf('[') + if (bracket == -1) visitInternalName(ref) + else if (ref.charAt(bracket + 1) == 'L') visitInternalName(ref, bracket + 2, ref.length - 1) + } + + // we are only interested in the class references in the descriptor, so we can skip over + // primitives and the brackets of array descriptors + def visitDescriptor(desc: String): Unit = (desc.charAt(0): @switch) match { + case '(' => + var i = 1 + while (i < desc.length) { + if (desc.charAt(i) == 'L') { + val start = i + 1 // skip the L + var seenDollar = false + while ({val ch = desc.charAt(i); seenDollar ||= (ch == '$'); ch != ';'}) i += 1 + if (seenDollar) + visitInternalName(desc, start, i) + } + // skips over '[', ')', primitives + i += 1 + } + + case 'L' => + visitInternalName(desc, 1, desc.length - 1) + + case '[' => + visitInternalNameOrArrayReference(desc) + + case _ => // skip over primitive types + } + + def visitConstant(const: AnyRef): Unit = const match { + case t: Type => visitDescriptor(t.getDescriptor) + case _ => + } + + // in principle we could references to annotation types, as they only end up as strings in the + // constant pool, not as class references. however, the java compiler still includes nested + // annotation classes in the innerClass table, so we do the same. explained in detail in the + // large comment in class BTypes. + def visitAnnotation(annot: AnnotationNode): Unit = { + visitDescriptor(annot.desc) + if (annot.values != null) annot.values.asScala foreach visitConstant + } + + def visitAnnotations(annots: java.util.List[_ <: AnnotationNode]) = if (annots != null) annots.asScala foreach visitAnnotation + def visitAnnotationss(annotss: Array[java.util.List[AnnotationNode]]) = if (annotss != null) annotss foreach visitAnnotations + + def visitHandle(handle: Handle): Unit = { + visitInternalNameOrArrayReference(handle.getOwner) + visitDescriptor(handle.getDesc) + } +} +