Skip to content

Commit 70ce89a

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 53e4cfc commit 70ce89a

File tree

6 files changed

+84
-118
lines changed

6 files changed

+84
-118
lines changed

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

Lines changed: 82 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, paramNames)
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, ListBuffer())
580581
addJavaFlagsAnnotations(sym, jflags)
581582
getScope(jflags) enter sym
582583

@@ -611,47 +612,14 @@ 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+
var paramNames: ListBuffer[Name] = ListBuffer(null)// null means we didn't find any
618+
val lazyInfo = new JavaSignatureTypeCompleter(name, jflags, index = u2, paramNames)
619+
sym.info = lazyInfo
649620
propagatePackageBoundary(jflags, sym)
650-
parseAttributes(sym, info, removedOuterParameter)
621+
parseAttributes(sym, jflags.isVarargs, paramNames)
651622
addJavaFlagsAnnotations(sym, jflags)
652-
if (jflags.isVarargs)
653-
sym modifyInfo arrayToRepeated
654-
655623
getScope(jflags) enter sym
656624
}
657625
}
@@ -835,8 +803,7 @@ abstract class ClassfileParser {
835803
/**
836804
* Only invoked for java classfiles.
837805
*/
838-
def parseAttributes(sym: Symbol, symtype: Type, removedOuterParameter: Boolean = false): Unit = {
839-
var paramNames: ListBuffer[Name] = null // null means we didn't find any
806+
def parseAttributes(sym: Symbol, isVarargs: Boolean = false, paramNames: ListBuffer[Name]): Unit = {
840807
def convertTo(c: Constant, pt: Type): Constant = {
841808
if (pt.typeSymbol == BooleanClass && c.tag == IntTag)
842809
Constant(c.value != 0)
@@ -850,9 +817,12 @@ abstract class ClassfileParser {
850817
attrName match {
851818
case tpnme.SignatureATTR =>
852819
val sig = pool.getExternalName(u2)
853-
val newType = sigToType(sym, sig.value)
854-
sym.setInfo(newType)
855-
820+
if (sym.isClass) {
821+
sym.setInfo(sigToType(sym, sig.value))
822+
} else {
823+
val lazyType = new JavaGenericSignatureTypeCompleter(sig.value, isVarargs, paramNames)
824+
sym.setInfo(lazyType)
825+
}
856826
case tpnme.SyntheticATTR =>
857827
sym.setFlag(SYNTHETIC | ARTIFACT)
858828
in.skip(attrLen)
@@ -868,20 +838,16 @@ abstract class ClassfileParser {
868838

869839
case tpnme.ConstantValueATTR =>
870840
val c = pool.getConstant(u2)
871-
val c1 = convertTo(c, symtype)
841+
val c1 = convertTo(c, sym.initialize.info.resultType)
872842
if (c1 ne null) sym.setInfo(ConstantType(c1))
873-
else devWarning(s"failure to convert $c to $symtype")
843+
else devWarning(s"failure to convert $c to ${sym.initialize.info.resultType}")
874844

875845
case tpnme.MethodParametersATTR =>
876846
def readParamNames(): Unit = {
847+
paramNames.clear()
877848
import scala.tools.asm.Opcodes.ACC_SYNTHETIC
878849
val paramCount = u1
879850
var i = 0
880-
if (removedOuterParameter && i < paramCount) {
881-
in.skip(4)
882-
i += 1
883-
}
884-
paramNames = new ListBuffer()
885851
while (i < paramCount) {
886852
val rawname = pool.getName(u2)
887853
val access = u2
@@ -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,71 @@ 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, paramNames: ListBuffer[Name]) extends LazyType {
1265+
override def complete(sym: symbolTable.Symbol): Unit = {
1266+
var removedOuterParameter = false
1267+
var info = pool.getType(sym, index)
1268+
if (name == nme.CONSTRUCTOR)
1269+
info match {
1270+
case MethodType(params, restpe) =>
1271+
// if this is a non-static inner class, remove the explicit outer parameter
1272+
val paramsNoOuter = innerClasses getEntry currentClass match {
1273+
case Some(entry) if !entry.jflags.isStatic =>
1274+
/* About `clazz.owner.hasPackageFlag` below: scala/bug#5957
1275+
* For every nested java class A$B, there are two symbols in the scala compiler.
1276+
* 1. created by SymbolLoader, because of the existence of the A$B.class file, owner: package
1277+
* 2. created by ClassfileParser of A when reading the inner classes, owner: A
1278+
* If symbol 1 gets completed (e.g. because the compiled source mentions `A$B`, not `A#B`), the
1279+
* ClassfileParser for 1 executes, and clazz.owner is the package.
1280+
*/
1281+
assert(params.head.tpe.typeSymbol == clazz.owner || clazz.owner.hasPackageFlag, params.head.tpe.typeSymbol + ": " + clazz.owner)
1282+
removedOuterParameter = true
1283+
params.tail
1284+
case _ =>
1285+
params
1286+
}
1287+
val newParams = paramsNoOuter match {
1288+
case (init :+ tail) if jflags.isSynthetic =>
1289+
// scala/bug#7455 strip trailing dummy argument ("access constructor tag") from synthetic constructors which
1290+
// are added when an inner class needs to access a private constructor.
1291+
init
1292+
case _ =>
1293+
paramsNoOuter
1294+
}
1295+
1296+
info = MethodType(newParams, clazz.tpe)
1297+
}
1298+
// Note: the info may be overwritten later with a generic signature
1299+
// parsed from SignatureATTR
1300+
sym.setInfo(if (jflags.isVarargs) arrayToRepeated(info) else info)
1301+
addParamNames(sym, removedOuterParameter)
1302+
}
1303+
1304+
private def addParamNames(sym: Symbol, removedOuterParameter: Boolean): Unit =
1305+
if ((paramNames ne null) && paramNames.headOption != Some(null) && sym.hasRawInfo && sym.isMethod) {
1306+
val params = sym.rawInfo.params.drop(if (removedOuterParameter) 1 else 0)
1307+
(paramNames zip params).foreach {
1308+
case (nme.NO_NAME, _) => // param was ACC_SYNTHETIC; ignore
1309+
case (name, param) =>
1310+
param.resetFlag(SYNTHETIC)
1311+
param.name = name
1312+
}
1313+
if (isDeveloper && !sameLength(paramNames.toList, params)) {
1314+
// there's not anything we can do, but it's slightly worrisome
1315+
devWarning(
1316+
sm"""MethodParameters length mismatch while parsing $sym:
1317+
| rawInfo.params: ${sym.rawInfo.params}
1318+
| MethodParameters: ${paramNames.toList}""")
1319+
}
1320+
}
1321+
1322+
}
1323+
final class JavaGenericSignatureTypeCompleter(sig: String, isVarargs: Boolean, paramNames: ListBuffer[Name]) extends LazyType {
1324+
override def complete(sym: symbolTable.Symbol): Unit = {
1325+
var info = sigToType(sym, sig)
1326+
sym.setInfo(if (isVarargs) arrayToRepeated(info) else info)
1327+
}
1328+
}
13161329

13171330
def skipAttributes(): Unit = {
13181331
var attrCount: Int = u2

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)