Skip to content

Commit 1399889

Browse files
authored
Merge pull request #4385 from dotty-staging/fix-#4368
Fix #4368: Avoid stackoverflows for some cyclic definitions
2 parents 59bc9c1 + 7fea207 commit 1399889

27 files changed

+595
-197
lines changed

compiler/src/dotty/tools/dotc/config/Config.scala

-5
Original file line numberDiff line numberDiff line change
@@ -184,11 +184,6 @@ object Config {
184184
*/
185185
final val LogPendingFindMemberThreshold = 9
186186

187-
/** Maximal number of outstanding recursive calls to findMember before backing out
188-
* when findMemberLimit is set.
189-
*/
190-
final val PendingFindMemberLimit = LogPendingFindMemberThreshold * 4
191-
192187
/** When in IDE, turn StaleSymbol errors into warnings instead of crashing */
193188
final val ignoreStaleInIDE = true
194189
}

compiler/src/dotty/tools/dotc/config/ScalaSettings.scala

+2-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ class ScalaSettings extends Settings.SettingGroup {
6363
val Xhelp = BooleanSetting("-X", "Print a synopsis of advanced options.")
6464
val XnoForwarders = BooleanSetting("-Xno-forwarders", "Do not generate static forwarders in mirror classes.")
6565
val XminImplicitSearchDepth = IntSetting("-Xmin-implicit-search-depth", "Set number of levels of implicit searches undertaken before checking for divergence.", 5)
66-
val xmaxInlines = IntSetting("-Xmax-inlines", "Maximal number of successive inlines", 32)
66+
val XmaxInlines = IntSetting("-Xmax-inlines", "Maximal number of successive inlines", 32)
6767
val XmaxClassfileName = IntSetting("-Xmax-classfile-name", "Maximum filename length for generated classes", 255, 72 to 255)
6868
val Xmigration = VersionSetting("-Xmigration", "Warn about constructs whose behavior may have changed since version.")
6969
val Xprint = PhasesSetting("-Xprint", "Print out program after")
@@ -146,6 +146,7 @@ class ScalaSettings extends Settings.SettingGroup {
146146
val Xlink = BooleanSetting("-Xlink", "Recompile library code with the application.")
147147
val YoptPhases = PhasesSetting("-Yopt-phases", "Restrict the optimisation phases to execute under -optimise.")
148148
val YoptFuel = IntSetting("-Yopt-fuel", "Maximum number of optimisations performed under -optimise.", -1)
149+
val YnoDecodeStacktraces = BooleanSetting("-Yno-decode-stacktraces", "Show raw StackOverflow stacktraces, instead of decoding them into triggering operations.")
149150

150151
/** Dottydoc specific settings */
151152
val siteRoot = StringSetting(

compiler/src/dotty/tools/dotc/core/Denotations.scala

+12-32
Original file line numberDiff line numberDiff line change
@@ -332,21 +332,8 @@ object Denotations {
332332
}
333333

334334
/** Handle merge conflict by throwing a `MergeError` exception */
335-
private def mergeConflict(tp1: Type, tp2: Type, that: Denotation)(implicit ctx: Context): Type = {
336-
def showSymbol(sym: Symbol): String = if (sym.exists) sym.showLocated else "[unknown]"
337-
def showType(tp: Type) = tp match {
338-
case ClassInfo(_, cls, _, _, _) => cls.showLocated
339-
case bounds: TypeBounds => i"type bounds $bounds"
340-
case _ => tp.show
341-
}
342-
val msg =
343-
s"""cannot merge
344-
| ${showSymbol(this.symbol)} of type ${showType(tp1)} and
345-
| ${showSymbol(that.symbol)} of type ${showType(tp2)}
346-
"""
347-
if (true) throw new MergeError(msg, tp1, tp2)
348-
else throw new Error(msg) // flip condition for debugging
349-
}
335+
private def mergeConflict(tp1: Type, tp2: Type, that: Denotation)(implicit ctx: Context): Type =
336+
throw new MergeError(this.symbol, that.symbol, tp1, tp2, NoPrefix)
350337

351338
/** Merge parameter names of lambda types. If names in corresponding positions match, keep them,
352339
* otherwise generate new synthetic names.
@@ -537,8 +524,7 @@ object Denotations {
537524
else if (pre.widen.classSymbol.is(Scala2x) || ctx.scala2Mode)
538525
info1 // follow Scala2 linearization -
539526
// compare with way merge is performed in SymDenotation#computeMembersNamed
540-
else
541-
throw new MergeError(s"${ex.getMessage} as members of type ${pre.show}", ex.tp1, ex.tp2)
527+
else throw new MergeError(ex.sym1, ex.sym2, ex.tp1, ex.tp2, pre)
542528
}
543529
new JointRefDenotation(sym, jointInfo, denot1.validFor & denot2.validFor)
544530
}
@@ -1136,21 +1122,15 @@ object Denotations {
11361122
def doubleDefError(denot1: Denotation, denot2: Denotation, pre: Type = NoPrefix)(implicit ctx: Context): Nothing = {
11371123
val sym1 = denot1.symbol
11381124
val sym2 = denot2.symbol
1139-
def fromWhere = if (pre == NoPrefix) "" else i"\nwhen seen as members of $pre"
1140-
val msg =
1141-
if (denot1.isTerm)
1142-
i"""cannot merge
1143-
| $sym1: ${sym1.info} and
1144-
| $sym2: ${sym2.info};
1145-
|they are both defined in ${sym1.owner} but have matching signatures
1146-
| ${denot1.info} and
1147-
| ${denot2.info}$fromWhere"""
1148-
else
1149-
i"""cannot merge
1150-
| $sym1 ${denot1.info}
1151-
| $sym2 ${denot2.info}
1152-
|they are conflicting definitions$fromWhere"""
1153-
throw new MergeError(msg, denot2.info, denot2.info)
1125+
if (denot1.isTerm)
1126+
throw new MergeError(sym1, sym2, sym1.info, sym2.info, pre) {
1127+
override def addendum(implicit ctx: Context) =
1128+
i"""
1129+
|they are both defined in ${sym1.owner} but have matching signatures
1130+
| ${denot1.info} and
1131+
| ${denot2.info}${super.addendum}"""
1132+
}
1133+
else throw new MergeError(sym1, sym2, denot1.info, denot2.info, pre)
11541134
}
11551135

11561136
// --- Overloaded denotations and predenotations -------------------------------------------------

compiler/src/dotty/tools/dotc/core/TypeApplications.scala

+4-1
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ class TypeApplications(val self: Type) extends AnyVal {
168168
* any type parameter that is-rebound by the refinement.
169169
*/
170170
final def typeParams(implicit ctx: Context): List[TypeParamInfo] = /*>|>*/ track("typeParams") /*<|<*/ {
171-
self match {
171+
try self match {
172172
case self: TypeRef =>
173173
val tsym = self.symbol
174174
if (tsym.isClass) tsym.typeParams
@@ -193,6 +193,9 @@ class TypeApplications(val self: Type) extends AnyVal {
193193
case _ =>
194194
Nil
195195
}
196+
catch {
197+
case ex: Throwable => handleRecursive("type parameters of", self.show, ex)
198+
}
196199
}
197200

198201
/** If `self` is a higher-kinded type, its type parameters, otherwise Nil */

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

+7-2
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,11 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
121121
protected def isSubType(tp1: Type, tp2: Type, a: ApproxState): Boolean = {
122122
val saved = approx
123123
this.approx = a
124-
try recur(tp1, tp2) finally this.approx = saved
124+
try recur(tp1, tp2)
125+
catch {
126+
case ex: Throwable => handleRecursive("subtype", i"$tp1 <:< $tp2", ex, weight = 2)
127+
}
128+
finally this.approx = saved
125129
}
126130

127131
protected def isSubType(tp1: Type, tp2: Type): Boolean = isSubType(tp1, tp2, NoApprox)
@@ -161,7 +165,8 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
161165
try {
162166
pendingSubTypes += p
163167
firstTry
164-
} finally {
168+
}
169+
finally {
165170
pendingSubTypes -= p
166171
}
167172
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package dotty.tools
2+
package dotc
3+
package core
4+
5+
import util.common._
6+
import Types._
7+
import Symbols._
8+
import Flags._
9+
import Names._
10+
import Contexts._
11+
import SymDenotations._
12+
import Denotations._
13+
import Decorators._
14+
import reporting.diagnostic.Message
15+
import reporting.diagnostic.messages._
16+
import ast.untpd
17+
import config.Printers.cyclicErrors
18+
19+
class TypeError(msg: String) extends Exception(msg) {
20+
def this() = this("")
21+
def toMessage(implicit ctx: Context): Message = getMessage
22+
}
23+
24+
class MalformedType(pre: Type, denot: Denotation, absMembers: Set[Name]) extends TypeError {
25+
override def toMessage(implicit ctx: Context): Message =
26+
i"malformed type: $pre is not a legal prefix for $denot because it contains abstract type member${if (absMembers.size == 1) "" else "s"} ${absMembers.mkString(", ")}"
27+
}
28+
29+
class MissingType(pre: Type, name: Name) extends TypeError {
30+
private def otherReason(pre: Type)(implicit ctx: Context): String = pre match {
31+
case pre: ThisType if pre.cls.givenSelfType.exists =>
32+
i"\nor the self type of $pre might not contain all transitive dependencies"
33+
case _ => ""
34+
}
35+
36+
override def toMessage(implicit ctx: Context): Message = {
37+
if (ctx.debug) printStackTrace()
38+
i"""cannot resolve reference to type $pre.$name
39+
|the classfile defining the type might be missing from the classpath${otherReason(pre)}"""
40+
}
41+
}
42+
43+
class RecursionOverflow(val op: String, details: => String, previous: Throwable, val weight: Int) extends TypeError {
44+
45+
def explanation = s"$op $details"
46+
47+
private def recursions: List[RecursionOverflow] = {
48+
val nested = previous match {
49+
case previous: RecursionOverflow => previous.recursions
50+
case _ => Nil
51+
}
52+
this :: nested
53+
}
54+
55+
def opsString(rs: List[RecursionOverflow])(implicit ctx: Context): String = {
56+
val maxShown = 20
57+
if (rs.lengthCompare(maxShown) > 0)
58+
i"""${opsString(rs.take(maxShown / 2))}
59+
| ...
60+
|${opsString(rs.takeRight(maxShown / 2))}"""
61+
else
62+
(rs.map(_.explanation): List[String]).mkString("\n ", "\n| ", "")
63+
}
64+
65+
override def toMessage(implicit ctx: Context): Message = {
66+
val mostCommon = recursions.groupBy(_.op).toList.maxBy(_._2.map(_.weight).sum)._2.reverse
67+
s"""Recursion limit exceeded.
68+
|Maybe there is an illegal cyclic reference?
69+
|If that's not the case, you could also try to increase the stacksize using the -Xss JVM option.
70+
|A recurring operation is (inner to outer):
71+
|${opsString(mostCommon)}""".stripMargin
72+
}
73+
74+
override def fillInStackTrace(): Throwable = this
75+
override def getStackTrace() = previous.getStackTrace()
76+
}
77+
78+
/** Post-process exceptions that might result from StackOverflow to add
79+
* tracing information while unwalking the stack.
80+
*/
81+
// Beware: Since this object is only used when handling a StackOverflow, this code
82+
// cannot consume significant amounts of stack.
83+
object handleRecursive {
84+
def apply(op: String, details: => String, exc: Throwable, weight: Int = 1)(implicit ctx: Context): Nothing = {
85+
if (ctx.settings.YnoDecodeStacktraces.value) {
86+
throw exc
87+
} else {
88+
exc match {
89+
case _: RecursionOverflow =>
90+
throw new RecursionOverflow(op, details, exc, weight)
91+
case _ =>
92+
var e = exc
93+
while (e != null && !e.isInstanceOf[StackOverflowError]) e = e.getCause
94+
if (e != null) throw new RecursionOverflow(op, details, e, weight)
95+
else throw exc
96+
}
97+
}
98+
}
99+
}
100+
101+
class CyclicReference private (val denot: SymDenotation) extends TypeError {
102+
103+
override def toMessage(implicit ctx: Context) = {
104+
105+
def errorMsg(cx: Context): Message =
106+
if (cx.mode is Mode.InferringReturnType) {
107+
cx.tree match {
108+
case tree: untpd.DefDef if !tree.tpt.typeOpt.exists =>
109+
OverloadedOrRecursiveMethodNeedsResultType(tree.name)
110+
case tree: untpd.ValDef if !tree.tpt.typeOpt.exists =>
111+
RecursiveValueNeedsResultType(tree.name)
112+
case _ =>
113+
errorMsg(cx.outer)
114+
}
115+
}
116+
else CyclicReferenceInvolving(denot)
117+
118+
val cycleSym = denot.symbol
119+
if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm)
120+
CyclicReferenceInvolvingImplicit(cycleSym)
121+
else
122+
errorMsg(ctx)
123+
}
124+
}
125+
126+
object CyclicReference {
127+
def apply(denot: SymDenotation)(implicit ctx: Context): CyclicReference = {
128+
val ex = new CyclicReference(denot)
129+
if (!(ctx.mode is Mode.CheckCyclic)) {
130+
cyclicErrors.println(ex.getMessage)
131+
for (elem <- ex.getStackTrace take 200)
132+
cyclicErrors.println(elem.toString)
133+
}
134+
ex
135+
}
136+
}
137+
138+
class MergeError(val sym1: Symbol, val sym2: Symbol, val tp1: Type, val tp2: Type, prefix: Type) extends TypeError {
139+
140+
private def showSymbol(sym: Symbol)(implicit ctx: Context): String =
141+
if (sym.exists) sym.showLocated else "[unknown]"
142+
143+
private def showType(tp: Type)(implicit ctx: Context) = tp match {
144+
case ClassInfo(_, cls, _, _, _) => cls.showLocated
145+
case _ => tp.show
146+
}
147+
148+
protected def addendum(implicit ctx: Context) =
149+
if (prefix `eq` NoPrefix) "" else i"\nas members of type $prefix"
150+
151+
override def toMessage(implicit ctx: Context): Message = {
152+
if (ctx.debug) printStackTrace()
153+
i"""cannot merge
154+
| ${showSymbol(sym1)} of type ${showType(tp1)} and
155+
| ${showSymbol(sym2)} of type ${showType(tp2)}$addendum
156+
"""
157+
}
158+
}

compiler/src/dotty/tools/dotc/core/TypeOps.scala

-6
Original file line numberDiff line numberDiff line change
@@ -326,10 +326,4 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
326326

327327
object TypeOps {
328328
@sharable var track = false // !!!DEBUG
329-
330-
/** When a property with this key is set in a context, it limits the number
331-
* of recursive member searches. If the limit is reached, findMember returns
332-
* NoDenotation.
333-
*/
334-
val findMemberLimit = new Property.Key[Unit]
335329
}

0 commit comments

Comments
 (0)