Skip to content

Commit 96a751b

Browse files
committed
Avoid cycles and some needless class reading in Java classfile parsing
Manually tested with: ``` $ scalac -cp $(coursier fetch -q -p com.datastax.cassandra:dse-driver:1.0.0) test.scala test.scala:2: error: illegal cyclic reference involving class Cluster new com.datastax.driver.dse.DseCluster.Builder() ^ one error found $ /code/scala/build/quick/bin/scalac -cp $(coursier fetch -q -p com.datastax.cassandra:dse-driver:1.0.0) test.scala $ cat test.scala class Test { new com.datastax.driver.dse.DseCluster.Builder() } ``` ``` class Test { new com.datastax.driver.dse.DseCluster.Builder() } ``` I was also able to compile `pos/cycle{-jsoup,}` without the experimental `-Ybreak-cycles`. I've removed the entire `-Ybreak-cycles` option and supporting code in favour of the approach in this patch. What's changed? I've used lazy types for methods infos, which is analagous to what we do in `Unpickler` for scala originated types. Fixes scala/bug#3809
1 parent 8a9ab2d commit 96a751b

File tree

6 files changed

+92
-118
lines changed

6 files changed

+92
-118
lines changed

src/compiler/scala/tools/nsc/symtab/classfile/ClassfileParser.scala

Lines changed: 90 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -519,7 +519,8 @@ abstract class ClassfileParser {
519519
skipMembers() // methods
520520

521521
// attributes now depend on having infos set already
522-
parseAttributes(clazz, classInfo)
522+
val paramNames = ListBuffer[Name](null)
523+
parseAttributes(clazz, isVarargs = false)
523524

524525
def queueLoad(): Unit = {
525526
in.bp = fieldsStartBp
@@ -576,7 +577,7 @@ abstract class ClassfileParser {
576577
else info
577578
}
578579
propagatePackageBoundary(jflags, sym)
579-
parseAttributes(sym, info)
580+
parseAttributes(sym, isVarargs = false)
580581
addJavaFlagsAnnotations(sym, jflags)
581582
getScope(jflags) enter sym
582583

@@ -611,47 +612,13 @@ abstract class ClassfileParser {
611612
} else {
612613
val name = readName()
613614
val sym = ownerForFlags(jflags).newMethod(name.toTermName, NoPosition, sflags)
614-
var info = pool.getType(sym, u2)
615-
var removedOuterParameter = false
616-
if (name == nme.CONSTRUCTOR)
617-
info match {
618-
case MethodType(params, restpe) =>
619-
// if this is a non-static inner class, remove the explicit outer parameter
620-
val paramsNoOuter = innerClasses getEntry currentClass match {
621-
case Some(entry) if !entry.jflags.isStatic =>
622-
/* About `clazz.owner.hasPackageFlag` below: scala/bug#5957
623-
* For every nested java class A$B, there are two symbols in the scala compiler.
624-
* 1. created by SymbolLoader, because of the existence of the A$B.class file, owner: package
625-
* 2. created by ClassfileParser of A when reading the inner classes, owner: A
626-
* If symbol 1 gets completed (e.g. because the compiled source mentions `A$B`, not `A#B`), the
627-
* ClassfileParser for 1 executes, and clazz.owner is the package.
628-
*/
629-
assert(params.head.tpe.typeSymbol == clazz.owner || clazz.owner.hasPackageFlag, params.head.tpe.typeSymbol + ": " + clazz.owner)
630-
removedOuterParameter = true
631-
params.tail
632-
case _ =>
633-
params
634-
}
635-
val newParams = paramsNoOuter match {
636-
case (init :+ tail) if jflags.isSynthetic =>
637-
// scala/bug#7455 strip trailing dummy argument ("access constructor tag") from synthetic constructors which
638-
// are added when an inner class needs to access a private constructor.
639-
init
640-
case _ =>
641-
paramsNoOuter
642-
}
643-
644-
info = MethodType(newParams, clazz.tpe)
645-
}
646615
// Note: the info may be overwritten later with a generic signature
647616
// parsed from SignatureATTR
648-
sym setInfo info
617+
val lazyInfo = new JavaSignatureTypeCompleter(name, jflags, index = u2)
618+
sym.info = lazyInfo
649619
propagatePackageBoundary(jflags, sym)
650-
parseAttributes(sym, info, removedOuterParameter)
620+
parseAttributes(sym, jflags.isVarargs)
651621
addJavaFlagsAnnotations(sym, jflags)
652-
if (jflags.isVarargs)
653-
sym modifyInfo arrayToRepeated
654-
655622
getScope(jflags) enter sym
656623
}
657624
}
@@ -835,8 +802,7 @@ abstract class ClassfileParser {
835802
/**
836803
* Only invoked for java classfiles.
837804
*/
838-
def parseAttributes(sym: Symbol, symtype: Type, removedOuterParameter: Boolean = false): Unit = {
839-
var paramNames: ListBuffer[Name] = null // null means we didn't find any
805+
def parseAttributes(sym: symbolTable.Symbol, isVarargs: Boolean = false): Unit = {
840806
def convertTo(c: Constant, pt: Type): Constant = {
841807
if (pt.typeSymbol == BooleanClass && c.tag == IntTag)
842808
Constant(c.value != 0)
@@ -850,9 +816,12 @@ abstract class ClassfileParser {
850816
attrName match {
851817
case tpnme.SignatureATTR =>
852818
val sig = pool.getExternalName(u2)
853-
val newType = sigToType(sym, sig.value)
854-
sym.setInfo(newType)
855-
819+
if (sym.isClass) {
820+
sym.setInfo(sigToType(sym, sig.value))
821+
} else {
822+
val lazyType = new JavaGenericSignatureTypeCompleter(sig.value, isVarargs)
823+
sym.setInfo(lazyType)
824+
}
856825
case tpnme.SyntheticATTR =>
857826
sym.setFlag(SYNTHETIC | ARTIFACT)
858827
in.skip(attrLen)
@@ -868,20 +837,16 @@ abstract class ClassfileParser {
868837

869838
case tpnme.ConstantValueATTR =>
870839
val c = pool.getConstant(u2)
871-
val c1 = convertTo(c, symtype)
840+
val c1 = convertTo(c, sym.initialize.info.resultType)
872841
if (c1 ne null) sym.setInfo(ConstantType(c1))
873-
else devWarning(s"failure to convert $c to $symtype")
842+
else devWarning(s"failure to convert $c to ${sym.initialize.info.resultType}")
874843

875844
case tpnme.MethodParametersATTR =>
876845
def readParamNames(): Unit = {
877846
import scala.tools.asm.Opcodes.ACC_SYNTHETIC
847+
val paramNames = ListBuffer[Name]()
878848
val paramCount = u1
879849
var i = 0
880-
if (removedOuterParameter && i < paramCount) {
881-
in.skip(4)
882-
i += 1
883-
}
884-
paramNames = new ListBuffer()
885850
while (i < paramCount) {
886851
val rawname = pool.getName(u2)
887852
val access = u2
@@ -893,6 +858,7 @@ abstract class ClassfileParser {
893858
paramNames += name
894859
i += 1
895860
}
861+
sym.updateAttachment(new ParamNames(paramNames.toList))
896862
}
897863
readParamNames()
898864

@@ -963,26 +929,8 @@ abstract class ClassfileParser {
963929
sym.addThrowsAnnotation(cls)
964930
}
965931
}
966-
def addParamNames(): Unit =
967-
if ((paramNames ne null) && sym.hasRawInfo && sym.isMethod) {
968-
val params = sym.rawInfo.params
969-
(paramNames zip params).foreach {
970-
case (nme.NO_NAME, _) => // param was ACC_SYNTHETIC; ignore
971-
case (name, param) =>
972-
param.resetFlag(SYNTHETIC)
973-
param.name = name
974-
}
975-
if (isDeveloper && !sameLength(paramNames.toList, params)) {
976-
// there's not anything we can do, but it's slightly worrisome
977-
devWarning(
978-
sm"""MethodParameters length mismatch while parsing $sym:
979-
| rawInfo.params: ${sym.rawInfo.params}
980-
| MethodParameters: ${paramNames.toList}""")
981-
}
982-
}
983932
// begin parseAttributes
984933
for (i <- 0 until u2) parseAttribute()
985-
addParamNames()
986934
}
987935

988936

@@ -1313,6 +1261,76 @@ abstract class ClassfileParser {
13131261
sym setInfo createFromClonedSymbols(alias.initialize.typeParams, alias.tpe)(typeFun)
13141262
}
13151263
}
1264+
final class JavaSignatureTypeCompleter(name: Name, jflags: JavaAccFlags, index: Int) extends LazyType {
1265+
override def complete(sym: symbolTable.Symbol): Unit = {
1266+
var info = pool.getType(sym, index)
1267+
if (name == nme.CONSTRUCTOR)
1268+
info match {
1269+
case MethodType(params, restpe) =>
1270+
// if this is a non-static inner class, remove the explicit outer parameter
1271+
val paramsNoOuter = innerClasses getEntry currentClass match {
1272+
case Some(entry) if !entry.jflags.isStatic =>
1273+
/* About `clazz.owner.hasPackageFlag` below: scala/bug#5957
1274+
* For every nested java class A$B, there are two symbols in the scala compiler.
1275+
* 1. created by SymbolLoader, because of the existence of the A$B.class file, owner: package
1276+
* 2. created by ClassfileParser of A when reading the inner classes, owner: A
1277+
* If symbol 1 gets completed (e.g. because the compiled source mentions `A$B`, not `A#B`), the
1278+
* ClassfileParser for 1 executes, and clazz.owner is the package.
1279+
*/
1280+
assert(params.head.tpe.typeSymbol == clazz.owner || clazz.owner.hasPackageFlag, params.head.tpe.typeSymbol + ": " + clazz.owner)
1281+
params.tail
1282+
case _ =>
1283+
params
1284+
}
1285+
val newParams = paramsNoOuter match {
1286+
case (init :+ tail) if jflags.isSynthetic =>
1287+
// scala/bug#7455 strip trailing dummy argument ("access constructor tag") from synthetic constructors which
1288+
// are added when an inner class needs to access a private constructor.
1289+
init
1290+
case _ =>
1291+
paramsNoOuter
1292+
}
1293+
1294+
info = MethodType(newParams, clazz.tpe)
1295+
}
1296+
// Note: the info may be overwritten later with a generic signature
1297+
// parsed from SignatureATTR
1298+
sym.setInfo(if (jflags.isVarargs) arrayToRepeated(info) else info)
1299+
addParamNames(sym)
1300+
}
1301+
}
1302+
1303+
private def addParamNames(sym: Symbol): Unit = {
1304+
sym.getAndRemoveAttachment[ParamNames] match {
1305+
case Some(ParamNames(paramNames)) =>
1306+
if (sym.hasRawInfo && sym.isMethod) {
1307+
val params = sym.rawInfo.params
1308+
val paramNames1 = paramNames.takeRight(params.length)
1309+
(paramNames1 zip params).foreach {
1310+
case (nme.NO_NAME, _) => // param was ACC_SYNTHETIC; ignore
1311+
case (name, param) =>
1312+
param.resetFlag(SYNTHETIC)
1313+
param.name = name
1314+
}
1315+
if (isDeveloper && !sameLength(paramNames.toList, params)) {
1316+
// there's not anything we can do, but it's slightly worrisome
1317+
devWarning(
1318+
sm"""MethodParameters length mismatch while parsing $sym:
1319+
| rawInfo.params: ${sym.rawInfo.params}
1320+
| MethodParameters: ${paramNames.toList}""")
1321+
}
1322+
}
1323+
case _ =>
1324+
}
1325+
}
1326+
1327+
final class JavaGenericSignatureTypeCompleter(sig: String, isVarargs: Boolean) extends LazyType {
1328+
override def complete(sym: symbolTable.Symbol): Unit = {
1329+
var info = sigToType(sym, sig)
1330+
sym.setInfo(if (isVarargs) arrayToRepeated(info) else info)
1331+
addParamNames(sym)
1332+
}
1333+
}
13161334

13171335
def skipAttributes(): Unit = {
13181336
var attrCount: Int = u2
@@ -1340,4 +1358,7 @@ abstract class ClassfileParser {
13401358

13411359
protected def getScope(flags: JavaAccFlags): Scope =
13421360
if (flags.isStatic) staticScope else instanceScope
1361+
1362+
private case class ParamNames(names: Seq[Name])
13431363
}
1364+

src/reflect/scala/reflect/internal/Types.scala

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1554,53 +1554,9 @@ trait Types
15541554
throw new TypeError("illegal cyclic inheritance involving " + tpe.typeSymbol)
15551555
}
15561556

1557-
object baseClassesCycleMonitor {
1558-
private var open: List[Symbol] = Nil
1559-
@inline private def cycleLog(msg: => String) {
1560-
if (settings.debug)
1561-
Console.err.println(msg)
1562-
}
1563-
def size = open.size
1564-
def push(clazz: Symbol) {
1565-
cycleLog("+ " + (" " * size) + clazz.fullNameString)
1566-
open ::= clazz
1567-
}
1568-
def pop(clazz: Symbol) {
1569-
assert(open.head eq clazz, (clazz, open))
1570-
open = open.tail
1571-
}
1572-
def isOpen(clazz: Symbol) = open contains clazz
1573-
}
1574-
15751557
protected def defineBaseClassesOfCompoundType(tpe: CompoundType) {
1576-
def define() = defineBaseClassesOfCompoundType(tpe, force = false)
1577-
if (!breakCycles || isPastTyper) define()
1578-
else tpe match {
1579-
// non-empty parents helpfully excludes all package classes
1580-
case tpe @ ClassInfoType(_ :: _, _, clazz) if !clazz.isAnonOrRefinementClass =>
1581-
// Cycle: force update
1582-
if (baseClassesCycleMonitor isOpen clazz)
1583-
defineBaseClassesOfCompoundType(tpe, force = true)
1584-
else {
1585-
baseClassesCycleMonitor push clazz
1586-
try define()
1587-
finally baseClassesCycleMonitor pop clazz
1588-
}
1589-
case _ =>
1590-
define()
1591-
}
1592-
}
1593-
private def defineBaseClassesOfCompoundType(tpe: CompoundType, force: Boolean) {
15941558
val period = tpe.baseClassesPeriod
1595-
if (period == currentPeriod) {
1596-
if (force && breakCycles) {
1597-
def what = tpe.typeSymbol + " in " + tpe.typeSymbol.owner.fullNameString
1598-
val bcs = computeBaseClasses(tpe)
1599-
tpe.baseClassesCache = bcs
1600-
warning(s"Breaking cycle in base class computation of $what ($bcs)")
1601-
}
1602-
}
1603-
else {
1559+
if (period != currentPeriod) {
16041560
tpe.baseClassesPeriod = currentPeriod
16051561
if (!isValidForBaseClasses(period)) {
16061562
val start = if (StatisticsStatics.areSomeColdStatsEnabled) statistics.pushTimer(typeOpsStack, baseClassesNanos) else null

src/reflect/scala/reflect/runtime/JavaUniverseForce.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@ trait JavaUniverseForce { self: runtime.JavaUniverse =>
150150
this.SuperType
151151
this.TypeBounds
152152
this.CompoundType
153-
this.baseClassesCycleMonitor
154153
this.RefinedType
155154
this.ClassInfoType
156155
this.ConstantType

test/files/pos/cycle-jsoup.flags

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/files/pos/cycle.flags

Lines changed: 0 additions & 1 deletion
This file was deleted.

test/files/run/t7455/Test.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ object Test extends DirectTest {
2323
clazz = compiler.rootMirror.staticClass(name)
2424
constr <- clazz.info.member(termNames.CONSTRUCTOR).alternatives
2525
} {
26-
println(constr.defString)
2726
fullyInitializeSymbol(constr)
27+
println(constr.defString)
2828
}
2929
}
3030
}

0 commit comments

Comments
 (0)