-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Fix #4368: Avoid stackoverflows for some cyclic definitions #4385
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
4be1409
Detect and flag deep findMember recursions
odersky 961cae9
Add test for cyclic references involving members of inherited and int…
odersky 8a4f24e
Add test
odersky 04bec4b
Detect more cyclic references
odersky 8240d44
Rearrange TypeError exceptions
odersky e6af770
Further refactoring of stackoverflow handling
odersky 01b5ecf
Fix CyclicError reporting
odersky 25c6abb
Fix context for cyclic error message
odersky e9ef0a7
Fix^2 context in CyclicReference toMessage
odersky 0daaab0
Fix error annotation in test
odersky 03b9eb3
MergeError refactorings
odersky e989cad
Add `append` method to Message
odersky c05e353
Also catch TypeErrors in all transforms
odersky a84b75f
rearrange try-catch in MegaPhase
odersky 5ade7fe
Preload handleRecursive to prevent nested stackoverflows
Blaisorblade ecbd692
Ensure `-YshowSuppressedErrors` also affects `UniqueMessagePositions`
Blaisorblade 5ef9263
Fix typo in modified (commented-out) code
Blaisorblade 34c4107
Add a flag to disable handleRecursive
Blaisorblade 7814a44
Typer: Refine position when catching top-level TypeError
Blaisorblade 640e0a8
Address review feedback
Blaisorblade 7fea207
Negate description of negated option
Blaisorblade File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
package dotty.tools | ||
package dotc | ||
package core | ||
|
||
import util.common._ | ||
import Types._ | ||
import Symbols._ | ||
import Flags._ | ||
import Names._ | ||
import Contexts._ | ||
import SymDenotations._ | ||
import Denotations._ | ||
import Decorators._ | ||
import reporting.diagnostic.Message | ||
import reporting.diagnostic.messages._ | ||
import ast.untpd | ||
import config.Printers.cyclicErrors | ||
|
||
class TypeError(msg: String) extends Exception(msg) { | ||
def this() = this("") | ||
def toMessage(implicit ctx: Context): Message = getMessage | ||
} | ||
|
||
class MalformedType(pre: Type, denot: Denotation, absMembers: Set[Name]) extends TypeError { | ||
override def toMessage(implicit ctx: Context): Message = | ||
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(", ")}" | ||
} | ||
|
||
class MissingType(pre: Type, name: Name) extends TypeError { | ||
private def otherReason(pre: Type)(implicit ctx: Context): String = pre match { | ||
case pre: ThisType if pre.cls.givenSelfType.exists => | ||
i"\nor the self type of $pre might not contain all transitive dependencies" | ||
case _ => "" | ||
} | ||
|
||
override def toMessage(implicit ctx: Context): Message = { | ||
if (ctx.debug) printStackTrace() | ||
i"""cannot resolve reference to type $pre.$name | ||
|the classfile defining the type might be missing from the classpath${otherReason(pre)}""" | ||
} | ||
} | ||
|
||
class RecursionOverflow(val op: String, details: => String, previous: Throwable, val weight: Int) extends TypeError { | ||
|
||
def explanation = s"$op $details" | ||
|
||
private def recursions: List[RecursionOverflow] = { | ||
val nested = previous match { | ||
case previous: RecursionOverflow => previous.recursions | ||
case _ => Nil | ||
} | ||
this :: nested | ||
} | ||
|
||
def opsString(rs: List[RecursionOverflow])(implicit ctx: Context): String = { | ||
val maxShown = 20 | ||
if (rs.lengthCompare(maxShown) > 0) | ||
i"""${opsString(rs.take(maxShown / 2))} | ||
| ... | ||
|${opsString(rs.takeRight(maxShown / 2))}""" | ||
else | ||
(rs.map(_.explanation): List[String]).mkString("\n ", "\n| ", "") | ||
} | ||
|
||
override def toMessage(implicit ctx: Context): Message = { | ||
val mostCommon = recursions.groupBy(_.op).toList.maxBy(_._2.map(_.weight).sum)._2.reverse | ||
s"""Recursion limit exceeded. | ||
|Maybe there is an illegal cyclic reference? | ||
|If that's not the case, you could also try to increase the stacksize using the -Xss JVM option. | ||
|A recurring operation is (inner to outer): | ||
|${opsString(mostCommon)}""".stripMargin | ||
} | ||
|
||
override def fillInStackTrace(): Throwable = this | ||
override def getStackTrace() = previous.getStackTrace() | ||
} | ||
|
||
/** Post-process exceptions that might result from StackOverflow to add | ||
* tracing information while unwalking the stack. | ||
*/ | ||
// Beware: Since this object is only used when handling a StackOverflow, this code | ||
// cannot consume significant amounts of stack. | ||
object handleRecursive { | ||
def apply(op: String, details: => String, exc: Throwable, weight: Int = 1)(implicit ctx: Context): Nothing = { | ||
if (ctx.settings.YnoDecodeStacktraces.value) { | ||
throw exc | ||
} else { | ||
exc match { | ||
case _: RecursionOverflow => | ||
throw new RecursionOverflow(op, details, exc, weight) | ||
case _ => | ||
var e = exc | ||
while (e != null && !e.isInstanceOf[StackOverflowError]) e = e.getCause | ||
if (e != null) throw new RecursionOverflow(op, details, e, weight) | ||
else throw exc | ||
} | ||
} | ||
} | ||
} | ||
|
||
class CyclicReference private (val denot: SymDenotation) extends TypeError { | ||
|
||
override def toMessage(implicit ctx: Context) = { | ||
|
||
def errorMsg(cx: Context): Message = | ||
if (cx.mode is Mode.InferringReturnType) { | ||
cx.tree match { | ||
case tree: untpd.DefDef if !tree.tpt.typeOpt.exists => | ||
OverloadedOrRecursiveMethodNeedsResultType(tree.name) | ||
case tree: untpd.ValDef if !tree.tpt.typeOpt.exists => | ||
RecursiveValueNeedsResultType(tree.name) | ||
case _ => | ||
errorMsg(cx.outer) | ||
} | ||
} | ||
else CyclicReferenceInvolving(denot) | ||
|
||
val cycleSym = denot.symbol | ||
if (cycleSym.is(Implicit, butNot = Method) && cycleSym.owner.isTerm) | ||
CyclicReferenceInvolvingImplicit(cycleSym) | ||
else | ||
errorMsg(ctx) | ||
} | ||
} | ||
|
||
object CyclicReference { | ||
def apply(denot: SymDenotation)(implicit ctx: Context): CyclicReference = { | ||
val ex = new CyclicReference(denot) | ||
if (!(ctx.mode is Mode.CheckCyclic)) { | ||
cyclicErrors.println(ex.getMessage) | ||
for (elem <- ex.getStackTrace take 200) | ||
cyclicErrors.println(elem.toString) | ||
} | ||
ex | ||
} | ||
} | ||
|
||
class MergeError(val sym1: Symbol, val sym2: Symbol, val tp1: Type, val tp2: Type, prefix: Type) extends TypeError { | ||
|
||
private def showSymbol(sym: Symbol)(implicit ctx: Context): String = | ||
if (sym.exists) sym.showLocated else "[unknown]" | ||
|
||
private def showType(tp: Type)(implicit ctx: Context) = tp match { | ||
case ClassInfo(_, cls, _, _, _) => cls.showLocated | ||
case _ => tp.show | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does this refactoring intentionally drop the case for type bounds here (search and compare with the removed |
||
} | ||
|
||
protected def addendum(implicit ctx: Context) = | ||
if (prefix `eq` NoPrefix) "" else i"\nas members of type $prefix" | ||
|
||
override def toMessage(implicit ctx: Context): Message = { | ||
if (ctx.debug) printStackTrace() | ||
i"""cannot merge | ||
| ${showSymbol(sym1)} of type ${showType(tp1)} and | ||
| ${showSymbol(sym2)} of type ${showType(tp2)}$addendum | ||
""" | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment is misleading. How I understand it: "if you pass the flag, you won't see original stack traces". I guess it is the opposite
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh right, fixing it