Skip to content
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

Enrich and finesse compiler crash reporting #17031

Merged
merged 3 commits into from
Mar 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions compiler/src/dotty/tools/dotc/Driver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,20 @@ class Driver {

protected def doCompile(compiler: Compiler, files: List[AbstractFile])(using Context): Reporter =
if files.nonEmpty then
var runOrNull = ctx.run
try
val run = compiler.newRun
runOrNull = run
run.compile(files)
finish(compiler, run)
catch
case ex: FatalError =>
report.error(ex.getMessage.nn) // signals that we should fail compilation.
case ex: TypeError =>
println(s"${ex.toMessage} while compiling ${files.map(_.path).mkString(", ")}")
case ex: TypeError if !runOrNull.enrichedErrorMessage =>
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
println(runOrNull.enrichErrorMessage(s"${ex.toMessage} while compiling ${files.map(_.path).mkString(", ")}"))
throw ex
case ex: Throwable =>
println(s"$ex while compiling ${files.map(_.path).mkString(", ")}")
case ex: Throwable if !runOrNull.enrichedErrorMessage =>
println(runOrNull.enrichErrorMessage(s"Exception while compiling ${files.map(_.path).mkString(", ")}"))
throw ex
ctx.reporter

Expand Down
28 changes: 20 additions & 8 deletions compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -173,15 +173,14 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
*/
var ccImportEncountered = false

private var myEnrichedErrorMessage = false

def compile(files: List[AbstractFile]): Unit =
try
val sources = files.map(runContext.getSource(_))
compileSources(sources)
catch
case NonFatal(ex) =>
if units.nonEmpty then report.echo(i"exception occurred while compiling $units%, %")
else report.echo(s"exception occurred while compiling ${files.map(_.name).mkString(", ")}")
throw ex
try compileSources(files.map(runContext.getSource(_)))
catch case NonFatal(ex) if !this.enrichedErrorMessage =>
val files1 = if units.isEmpty then files else units.map(_.source.file)
report.echo(this.enrichErrorMessage(s"exception occurred while compiling ${files1.map(_.path)}"))
throw ex

/** TODO: There's a fundamental design problem here: We assemble phases using `fusePhases`
* when we first build the compiler. But we modify them with -Yskip, -Ystop
Expand Down Expand Up @@ -398,3 +397,16 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
given runContext[Dummy_so_its_a_def]: Context = myCtx.nn
assert(runContext.runId <= Periods.MaxPossibleRunId)
}

object Run {
extension (run: Run | Null)
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
def enrichedErrorMessage: Boolean = if run == null then false else run.myEnrichedErrorMessage
def enrichErrorMessage(errorMessage: String)(using Context): String =
if run == null then
report.enrichErrorMessage(errorMessage)
else if !run.enrichedErrorMessage then
run.myEnrichedErrorMessage = true
report.enrichErrorMessage(errorMessage)
else
errorMessage
}
5 changes: 1 addition & 4 deletions compiler/src/dotty/tools/dotc/core/Decorators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,7 @@ object Decorators {
case ex: CyclicReference => "... (caught cyclic reference) ..."
case NonFatal(ex)
if !ctx.mode.is(Mode.PrintShowExceptions) && !ctx.settings.YshowPrintErrors.value =>
val msg = ex match
case te: TypeError => te.toMessage.message
case _ => ex.getMessage
s"[cannot display due to $msg, raw string = $x]"
s"... (cannot display due to ${ex.className} ${ex.getMessage}) ..."
case _ => String.valueOf(x).nn

/** Returns the simple class name of `x`. */
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -322,8 +322,8 @@ object Phases {
units.map { unit =>
val unitCtx = ctx.fresh.setPhase(this.start).setCompilationUnit(unit).withRootImports
try run(using unitCtx)
catch case ex: Throwable =>
println(s"$ex while running $phaseName on $unit")
catch case ex: Throwable if !ctx.run.enrichedErrorMessage =>
println(ctx.run.enrichErrorMessage(s"unhandled exception while running $phaseName on $unit"))
throw ex
unitCtx.compilationUnit
}
Expand Down
63 changes: 61 additions & 2 deletions compiler/src/dotty/tools/dotc/report.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ import reporting._
import Diagnostic._
import util.{SourcePosition, NoSourcePosition, SrcPos}
import core._
import Contexts._, Symbols._, Decorators._
import Contexts._, Flags.*, Symbols._, Decorators._
import config.SourceVersion
import ast._
import config.Feature.sourceVersion
import java.lang.System.currentTimeMillis


object report:

/** For sending messages that are printed only if -verbose is set */
Expand Down Expand Up @@ -129,4 +128,64 @@ object report:
case Nil => pos
recur(pos.sourcePos, tpd.enclosingInlineds)

private object messageRendering extends MessageRendering

// Should only be called from Run#enrichErrorMessage.
def enrichErrorMessage(errorMessage: String)(using Context): String = try {
def formatExplain(pairs: List[(String, Any)]) = pairs.map((k, v) => f"$k%20s: $v").mkString("\n")

val settings = ctx.settings.userSetSettings(ctx.settingsState).sortBy(_.name)
val tree = ctx.tree
val sym = tree.symbol
val pos = tree.sourcePos
val path = pos.source.path
val site = ctx.outersIterator.map(_.owner).filter(sym => !sym.exists || sym.isClass || sym.is(Method)).next()

import untpd.*
extension (tree: Tree) def summaryString: String = tree match
case Literal(const) => s"Literal($const)"
case Ident(name) => s"Ident(${name.decode})"
case Select(qual, name) => s"Select(${qual.summaryString}, ${name.decode})"
case tree: NameTree => (if tree.isType then "type " else "") + tree.name.decode
case tree => s"${tree.className}${if tree.symbol.exists then s"(${tree.symbol})" else ""}"

val info1 = formatExplain(List(
"while compiling" -> ctx.compilationUnit,
"during phase" -> ctx.phase.prevMega,
"mode" -> ctx.mode,
"library version" -> scala.util.Properties.versionString,
"compiler version" -> dotty.tools.dotc.config.Properties.versionString,
"settings" -> settings.map(s => if s.value == "" then s"${s.name} \"\"" else s"${s.name} ${s.value}").mkString(" "),
))
val symbolInfos = if sym eq NoSymbol then List("symbol" -> sym) else List(
"symbol" -> sym.showLocated,
"symbol definition" -> s"${sym.showDcl} (a ${sym.className})",
"symbol package" -> sym.enclosingPackageClass.fullName,
"symbol owners" -> sym.showExtendedLocation,
)
val info2 = formatExplain(List(
"tree" -> tree.summaryString,
"tree position" -> (if pos.exists then s"$path:${pos.line + 1}:${pos.column}" else s"$path:<unknown>"),
"tree type" -> tree.typeOpt.show,
) ::: symbolInfos ::: List(
"call site" -> s"${site.showLocated} in ${site.enclosingPackageClass}"
))
val context_s = try
s""" == Source file context for tree position ==
|
|${messageRendering.messageAndPos(Diagnostic.Error("", pos))}""".stripMargin
catch case _: Exception => "<Cannot read source file>"
s"""
| $errorMessage
|
| An unhandled exception was thrown in the compiler.
| Please file a crash report here:
| https://github.com/lampepfl/dotty/issues/new/choose
|
|$info1
|
|$info2
|
|$context_s""".stripMargin
} catch case _: Throwable => errorMessage // don't introduce new errors trying to report errors, so swallow exceptions
end report
10 changes: 4 additions & 6 deletions compiler/src/dotty/tools/dotc/typer/ReTyper.scala
Original file line number Diff line number Diff line change
Expand Up @@ -124,12 +124,10 @@ class ReTyper(nestingLevel: Int = 0) extends Typer(nestingLevel) with ReChecking

override def typedUnadapted(tree: untpd.Tree, pt: Type, locked: TypeVars)(using Context): Tree =
try super.typedUnadapted(tree, pt, locked)
catch {
case NonFatal(ex) =>
if ctx.phase != Phases.typerPhase && ctx.phase != Phases.inliningPhase then
println(i"exception while typing $tree of class ${tree.getClass} # ${tree.uniqueId}")
throw ex
}
catch case NonFatal(ex) if ctx.phase != Phases.typerPhase && ctx.phase != Phases.inliningPhase && !ctx.run.enrichedErrorMessage =>
val treeStr = tree.show(using ctx.withPhase(ctx.phase.prevMega))
println(ctx.run.enrichErrorMessage(s"exception while retyping $treeStr of class ${tree.className} # ${tree.uniqueId}"))
throw ex

override def inlineExpansion(mdef: DefDef)(using Context): List[Tree] = mdef :: Nil

Expand Down
21 changes: 21 additions & 0 deletions compiler/test/dotty/tools/dotc/core/ShowDecoratorTest.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package dotty.tools
package dotc
package core

import Contexts.*, Decorators.*, Denotations.*, SymDenotations.*, Symbols.*, Types.*
import printing.Formatting.Show

import org.junit.Test
import org.junit.Assert.*

class ShowDecoratorTest extends DottyTest:
import ShowDecoratorTest.*

@Test def t1 = assertEquals("... (cannot display due to FooException boom) ...", Foo().tryToShow)
end ShowDecoratorTest

object ShowDecoratorTest:
import printing.*, Texts.*
class FooException extends Exception("boom")
case class Foo() extends Showable:
def toText(printer: Printer): Text = throw new FooException