Skip to content
Draft
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
13 changes: 13 additions & 0 deletions NOTES_ON_FUEL_TEMP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
max fuel of 100 is enough for all pos test cases except:
* tests/pos/9890.scala
* tests/pos/i18175.scala (and only because we run tests with -Ycheck:all, it compiles just fine without any flags with 100 fuel)
for these, 200 seems fine, I didn't test with a lower fuel.

idea: instead of the current "array of structure", we could have a "structure of array" where we preallocate arrays for the components of RecursiveOperation in ctx (of size maxFuel)
or even just 1 big array at least?

define what we accept as a compiler bug

- later:
- remove that index out of bounds exception catch?
- more generally, when do we use exceptions for control flow?
16 changes: 7 additions & 9 deletions compiler/src/dotty/tools/backend/jvm/BCodeHelpers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -662,15 +662,13 @@ trait BCodeHelpers(val backendUtils: BackendUtils)(using ctx: Context) extends B
import scala.tools.asm.util.CheckClassAdapter
def wrap(body: => Unit): Unit = {
try body
catch {
case ex: Throwable =>
report.error(
em"""|compiler bug: created invalid generic signature for $sym in ${sym.denot.owner.showFullName}
|signature: $sig
|if this is reproducible, please report bug at https://github.com/scala/scala3/issues
""", sym.sourcePos)
throw ex
}
catch case ex: Exception =>
report.error(
em"""|compiler bug: created invalid generic signature for $sym in ${sym.denot.owner.showFullName}
|signature: $sig
|if this is reproducible, please report bug at https://github.com/scala/scala3/issues
""", sym.sourcePos)
throw ex
}

wrap {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ class ClassfileWriters(frontendAccess: PostProcessorFrontendAccess)(using ctx: C
catch {
case ex: ClosedByInterruptException =>
try Files.deleteIfExists(path) // don't leave a empty of half-written classfile around after an interrupt
catch { case _: Throwable => () }
catch { case _: java.io.IOException => () }
throw ex
}
os.close()
Expand Down
5 changes: 1 addition & 4 deletions compiler/src/dotty/tools/backend/jvm/CodeGen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,7 @@ class CodeGen(val backendUtils: BackendUtils, val primitives: ScalaPrimitives, v
registerGeneratedClass(mainClassNode, isArtifact = false)
registerGeneratedClass(mirrorClassNode, isArtifact = true)
catch
case ex: InterruptedException => throw ex
case ex: CompilationUnit.SuspendException => throw ex
case ex: Throwable =>
if !ex.isInstanceOf[TypeError] then ex.printStackTrace()
case ex: TypeError =>
report.error(s"Error while emitting ${unit.source}\n${ex.getMessage}", cd.sourcePos)


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ import dotty.tools.dotc.core.Contexts.*
import dotty.tools.io.AbstractFile
import dotty.tools.dotc.profile.ThreadPoolFactory

import scala.util.control.NonFatal
import dotty.tools.dotc.core.Phases
import dotty.tools.dotc.core.Decorators.em
import dotty.tools.dotc.core.Types.IdentityTypeMap.mapCtx
import dotty.tools.dotc.report

import scala.compiletime.uninitialized
Expand Down Expand Up @@ -155,10 +153,10 @@ private[jvm] object GeneratedClassHandler {
unitInPostProcess.task.value.get.get
catch
case _: ClosedByInterruptException => throw new InterruptedException()
case NonFatal(t) =>
t.printStackTrace()
case e: Exception =>
e.printStackTrace()
given Context = ctx
report.error(em"unable to write ${unitInPostProcess.sourceFile} $t")
report.error(em"unable to write ${unitInPostProcess.sourceFile} $e")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import scala.tools.asm.{ClassReader, Type, Handle }
import scala.tools.asm.tree.*

import scala.collection.mutable
import scala.util.control.{NoStackTrace, NonFatal}
import scala.util.control.NoStackTrace
import scala.annotation.*
import scala.jdk.CollectionConverters.*
import BTypes.InternalName
Expand Down Expand Up @@ -47,7 +47,7 @@ abstract class GenericSignatureVisitor(nestedOnly: Boolean) {

@inline def safely(f: => Unit): Unit = try f catch {
case Aborted =>
case NonFatal(e) => raiseError(s"Exception thrown during signature parsing", sig, Some(e))
case e: Exception => raiseError(s"Exception thrown during signature parsing", sig, Some(e))
}

private def current = {
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/backend/jvm/PostProcessor.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class PostProcessor(val frontendAccess: PostProcessorFrontendAccess,
case e: java.lang.RuntimeException if e.getMessage != null && e.getMessage.contains("too large!") =>
report.error(em"Could not write class $internalName because it exceeds JVM code size limits. ${e.getMessage}")
null
case ex: Throwable =>
case ex: Exception =>
if frontendAccess.compilerSettings.debug then ex.printStackTrace()
report.error(em"Error while emitting $internalName\n${ex.getMessage}")
null
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package dotty.tools.debug

import java.nio.file.Path
import scala.util.control.NonFatal
import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.Driver

Expand Down Expand Up @@ -29,6 +28,6 @@ class ExpressionCompilerBridge:
driver.process(args, reporter)
!reporter.hasErrors
catch
case NonFatal(cause) =>
case cause: Exception =>
cause.printStackTrace()
throw cause
18 changes: 10 additions & 8 deletions compiler/src/dotty/tools/dotc/Driver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import reporting.*
import core.Decorators.*
import util.chaining.*

import scala.util.control.NonFatal
import fromtasty.{TASTYCompiler, TastyFileUtil}

/** Run the Dotty compiler.
Expand Down Expand Up @@ -39,13 +38,13 @@ class Driver {
catch
case ex: FatalError =>
report.error(ex.getMessage) // signals that we should fail compilation.
case ex: Throwable if ctx.usedBestEffortTasty =>
case ex: Exception if ctx.usedBestEffortTasty =>
report.bestEffortError(ex, "Some best-effort tasty files were not able to be read.")
throw ex
case ex: TypeError if !runOrNull.enrichedErrorMessage =>
println(runOrNull.enrichErrorMessage(s"${ex.toMessage} while compiling ${files.map(_.path).mkString(", ")}"))
throw ex
case ex: Throwable if !runOrNull.enrichedErrorMessage =>
case ex: Exception if !runOrNull.enrichedErrorMessage =>
println(runOrNull.enrichErrorMessage(s"Exception while compiling ${files.map(_.path).mkString(", ")}"))
throw ex
ctx.reporter
Expand Down Expand Up @@ -177,7 +176,14 @@ class Driver {
compileCtx.setReporter(reporter)
if (callback != null)
compileCtx.setCompilerCallback(callback)
process(args, compileCtx)
try
process(args, compileCtx)
catch {
case so: StackOverflowError =>
report.error("oopsie")(using compileCtx)
so.printStackTrace()
compileCtx.reporter
}
}

/** Entry point to the compiler with no optional arguments.
Expand Down Expand Up @@ -215,10 +221,6 @@ class Driver {
}

def main(args: Array[String]): Unit = {
// Preload scala.util.control.NonFatal. Otherwise, when trying to catch a StackOverflowError,
// we may try to load it but fail with another StackOverflowError and lose the original exception,
// see <https://groups.google.com/forum/#!topic/scala-user/kte6nak-zPM>.
val _ = NonFatal
sys.exit(if (process(args).hasErrors) 1 else 0)
}
}
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/Run.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import java.io.{BufferedWriter, OutputStreamWriter}
import java.nio.charset.StandardCharsets

import scala.collection.mutable, mutable.ListBuffer
import scala.util.control.NonFatal
import scala.io.Codec

import Run.Progress
Expand Down Expand Up @@ -323,7 +322,7 @@ extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo {

def compile(files: List[AbstractFile]): Unit =
try compileSources(files.map(runContext.getSource(_)))
catch case NonFatal(ex) if !this.enrichedErrorMessage =>
catch case ex: Exception 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import PlainFile.toPlainFile

import scala.jdk.CollectionConverters.*
import scala.collection.immutable.ArraySeq
import scala.util.control.NonFatal

/**
* A trait allowing to look for classpath entries in directories. It provides common logic for
Expand Down Expand Up @@ -134,7 +133,7 @@ object JrtClassPath {
val ctSym = Paths.get(Properties.javaHome).resolve("lib").resolve("ct.sym")
if (Files.notExists(ctSym)) None
else Some(new CtSymClassPath(ctSym, v.toInt))
catch case NonFatal(_) => None
catch case _: Exception => None
case _ =>
try Some(new JrtClassPath(FileSystems.getFileSystem(URI.create("jrt:/"))))
catch case _: ProviderNotFoundException | _: FileSystemNotFoundException => None
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/config/ScalaSettings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,8 @@ private sealed trait XSettings:
val XjarCompressionLevel: Setting[Int] = IntChoiceSetting(AdvancedSetting, "Xjar-compression-level", "compression level to use when writing jar files", Deflater.DEFAULT_COMPRESSION to Deflater.BEST_COMPRESSION, Deflater.DEFAULT_COMPRESSION)
val XkindProjector: Setting[String] = ChoiceSetting(AdvancedSetting, "Xkind-projector", "[underscores, enable, disable]", "Allow `*` as type lambda placeholder to be compatible with kind projector. When invoked as -Xkind-projector:underscores will repurpose `_` to be a type parameter placeholder, this will disable usage of underscore as a wildcard.", List("disable", "", "underscores"), "disable", legacyArgs = true)

val XmaxFuel: Setting[Int] = IntSetting(AdvancedSetting, "XmaxFuel", "Max fuel for recursive operations before abandoning.", 400)

/** Documentation related settings */
val XdropComments: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xdrop-docs", "Drop documentation when scanning source files.", aliases = List("-Xdrop-comments"))
val XcookComments: Setting[Boolean] = BooleanSetting(AdvancedSetting, "Xcook-docs", "Cook the documentation (type check `@usecase`, etc.)", aliases = List("-Xcook-comments"))
Expand Down
19 changes: 19 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Contexts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,19 @@ object Contexts {
final def withUncommittedTyperState: Context =
withTyperState(typerState.uncommittedAncestor)

/** Ensures recursive operations obey the fuel limit, and throws user-friendly errors when they do not. */
inline final def handleRecursive[T](name: String, details: => String, weight: Int = 1)(inline block: T): T =
val op = RecursiveOperation(name, details, weight)
if base.recursiveDepth >= settings.XmaxFuel.value then
throw new RecursionOverflow(op :: base.recursiveOperations)
base.recursiveOperations = op :: base.recursiveOperations
base.recursiveDepth += 1
try
block
finally
base.recursiveDepth -= 1
base.recursiveOperations = base.recursiveOperations.tail

final def withProperty[T](key: Key[T], value: Option[T]): Context =
if (property(key) == value) this
else value match {
Expand Down Expand Up @@ -611,6 +624,9 @@ object Contexts {
private var _source: SourceFile = uninitialized
final def source: SourceFile = _source

private var _recursiveOperations: List[RecursiveOperation] = uninitialized
final def recursiveOperations: List[RecursiveOperation] = _recursiveOperations

private var _moreProperties: Map[Key[Any], Any] = uninitialized
final def moreProperties: Map[Key[Any], Any] = _moreProperties

Expand Down Expand Up @@ -1021,6 +1037,9 @@ object Contexts {
*/
private[dotc] var coverage: Coverage | Null = null

private[dotc] var recursiveDepth: Int = 0
private[dotc] var recursiveOperations: List[RecursiveOperation] = Nil

// Types state
/** A table for hash consing unique types */
private[core] val uniques: Uniques = Uniques()
Expand Down
3 changes: 1 addition & 2 deletions compiler/src/dotty/tools/dotc/core/Decorators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package core

import scala.annotation.tailrec
import scala.collection.mutable.ListBuffer
import scala.util.control.NonFatal

import Contexts.*, Names.*, Phases.*, Symbols.*
import printing.{ Printer, Showable }, printing.Formatting.*, printing.Texts.*
Expand Down Expand Up @@ -286,7 +285,7 @@ object Decorators {
try x.show
catch
case ex: CyclicReference => "... (caught cyclic reference) ..."
case NonFatal(ex)
case ex: Exception
if !ctx.settings.YshowPrintErrors.value =>
s"... (cannot display due to ${ex.className} ${ex.getMessage}) ..."
case _ => String.valueOf(x)
Expand Down
25 changes: 12 additions & 13 deletions compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala
Original file line number Diff line number Diff line change
Expand Up @@ -346,20 +346,19 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
if newSet.isEmpty then deps.remove(referenced)
else deps.updated(referenced, newSet)

def traverse(t: Type) = try
def traverse(t: Type) = ctx.handleRecursive("adjust", t.show):
t match
case param: TypeParamRef =>
if hasBounds(param) then
if variance >= 0 then coDeps = update(coDeps, param)
if variance <= 0 then contraDeps = update(contraDeps, param)
else
traverse(entry(param))
case tp: LazyRef =>
if !seen.contains(tp) then
seen += tp
traverse(tp.ref)
case _ => traverseChildren(t)
catch case ex: Throwable => handleRecursive("adjust", t.show, ex)
case param: TypeParamRef =>
if hasBounds(param) then
if variance >= 0 then coDeps = update(coDeps, param)
if variance <= 0 then contraDeps = update(contraDeps, param)
else
traverse(entry(param))
case tp: LazyRef =>
if !seen.contains(tp) then
seen += tp
traverse(tp.ref)
case _ => traverseChildren(t)
end Adjuster

/** Adjust dependencies to account for the delta of previous entry `prevEntry`
Expand Down
5 changes: 2 additions & 3 deletions compiler/src/dotty/tools/dotc/core/Phases.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import cc.CheckCaptures
import typer.ImportInfo.withRootImports
import ast.{tpd, untpd}
import scala.annotation.internal.sharable
import scala.util.control.NonFatal
import scala.compiletime.uninitialized

object Phases {
Expand Down Expand Up @@ -413,7 +412,7 @@ object Phases {
catch
case _: CompilationUnit.SuspendException => // this unit will be run again in `Run#compileSuspendedUnits`
unitCtx.typerState.resetTo(previousTyperState)
case ex: Throwable if !ctx.run.enrichedErrorMessage =>
case ex: Exception if !ctx.run.enrichedErrorMessage =>
println(ctx.run.enrichErrorMessage(s"unhandled exception while running $phaseName on $unit"))
throw ex
finally ctx.run.advanceUnit()
Expand Down Expand Up @@ -537,7 +536,7 @@ object Phases {
ctx.run.enterUnit(ctx.compilationUnit)
&& {
try {body; true}
catch case NonFatal(ex) if !ctx.run.enrichedErrorMessage =>
catch case ex: Exception if !ctx.run.enrichedErrorMessage =>
report.echo(ctx.run.enrichErrorMessage(s"exception occurred while $doing ${ctx.compilationUnit}"))
throw ex
finally ctx.run.advanceUnit()
Expand Down
48 changes: 48 additions & 0 deletions compiler/src/dotty/tools/dotc/core/RecursiveOperation.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package dotty.tools
package dotc
package core

import dotty.tools.dotc.core.Contexts.Context
import dotty.tools.dotc.core.Decorators.*
import dotty.tools.dotc.reporting.Message

/* TODO:
- Catch StackOverflowError properly in main
*/

/**
* Operation that may cause unbounded recursion depending on user input.
* @param title the operation title
* @param details the lazily-initialized operation details
* @param weight the operation weight, used to prioritize some operations when displaying error messages
*/
class RecursiveOperation(val title: String, details: => String, val weight: Int):
def explanation: String = s"$title $details"

/**
* Thrown when recursing too deep, as an alternative to triggering a stack overflow.
*
* @param ops the recursive operations, most recent first, i.e., in reverse order
*/
class RecursionOverflow(val ops: List[RecursiveOperation])(using Context) extends TypeError:
override def fillInStackTrace(): Throwable =
this

private def opsString(rs: List[RecursiveOperation]): String = {
val maxShown = 20
if (rs.lengthCompare(maxShown) > 0)
i"""${opsString(rs.take(maxShown / 2))}
| ...
|${opsString(rs.takeRight(maxShown / 2))}"""
else
rs.map(_.explanation).mkString("\n ", "\n| ", "")
}

override def toMessage(using Context): Message =
val mostCommon = ops.groupBy(_.title).toList.maxBy(_._2.map(_.weight).sum)._2
em"""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.
|For the unprocessed stack trace, compile with -Xno-enrich-error-messages.
|A recurring operation is (inner to outer):
|${opsString(mostCommon).stripMargin}"""
Loading
Loading