Skip to content
Closed
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
32 changes: 30 additions & 2 deletions backend/src/main/scala/bloop/Compiler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import java.util.concurrent.Executor

import scala.collection.mutable
import scala.concurrent.Promise
import scala.tools.nsc.FatalError
import scala.util.Try

import bloop.Compiler.Result.Failed
Expand All @@ -21,6 +22,7 @@ import bloop.io.{Paths => BloopPaths}
import bloop.logging.DebugFilter
import bloop.logging.Logger
import bloop.logging.ObservedLogger
import bloop.reporter.ObservedReporter
import bloop.reporter.ProblemPerPhase
import bloop.reporter.Reporter
import bloop.reporter.ZincReporter
Expand All @@ -31,6 +33,7 @@ import bloop.util.AnalysisUtils
import bloop.util.BestEffortUtils
import bloop.util.BestEffortUtils.BestEffortProducts
import bloop.util.CacheHashCode
import bloop.util.HashedSource
import bloop.util.JavaRuntime
import bloop.util.UUIDUtil

Expand All @@ -47,7 +50,6 @@ import sbt.util.InterfaceUtil
import xsbti.T2
import xsbti.VirtualFileRef
import xsbti.compile._
import bloop.util.HashedSource

case class CompileInputs(
scalaInstance: ScalaInstance,
Expand Down Expand Up @@ -783,7 +785,33 @@ object Compiler {
val backgroundTasks =
toBackgroundTasks(backgroundTasksForFailedCompilation.toList)
val failedProblems = findFailedProblems(reporter, None)
Result.Failed(failedProblems, Some(t), elapsed, backgroundTasks, None)
val errorFromThrowable = t match {
case _: AssertionError | _: FatalError | _: IllegalArgumentException =>
bloop.reporter.Problem.fromError(t) match {
case Some(problem) =>
reporter match {
case observedReporter: ObservedReporter =>
observedReporter.log(problem)
case _ =>
}
List(
ProblemPerPhase(
problem,
phase = None
)
)

case None => Nil
}
case _ => Nil
}
Result.Failed(
failedProblems ++ errorFromThrowable,
Some(t),
elapsed,
backgroundTasks,
None
)
}
}
}
Expand Down
65 changes: 65 additions & 0 deletions frontend/src/test/scala/bloop/bsp/BspCompileSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1422,5 +1422,70 @@ class BspCompileSpec(
runTest(2000)
}
}
test("crash-compiler-diagnostics") {
TestUtil.withinWorkspace { workspace =>
// Macro project - must be compiled first
val macroProject = TestProject(
workspace,
"macros",
List(
"""/main/scala/FooMacro.scala
|package macros
|
|import scala.language.experimental.macros
|import scala.reflect.macros.blackbox
|
|object FooMacro {
| def crashNow: Int = macro crashNowImpl
|
| def crashNowImpl(c: blackbox.Context): c.Expr[Int] = {
| import c.universe._
| val badSymbol = c.internal.newTermSymbol(NoSymbol, TermName("badSymbol"))
| val badTree = Ident(badSymbol)
| c.Expr[Int](badTree)
| }
|}
|
|
""".stripMargin
)
)
// Fast file that will be modified to break compilation if stale bytecode is used
val downstreamFile =
"""/main/scala/a/FastCompilation.scala
|package a
|import macros.FooMacro
|
|case class User(name: Int, age: Int)
|
|object Bar{
| def baz2 = FooMacro.crashNow
|
|}
|
""".stripMargin

val logger = new RecordingLogger(ansiCodesSupported = false)
val testProject = TestProject(
workspace,
"a",
List(downstreamFile),
List(macroProject),
scalaVersion = Some("2.13.16")
)
val projects = List(macroProject, testProject)
loadBspState(workspace, projects, logger) { state =>
val macroCompiled = state.compile(macroProject)
assert(macroCompiled.status == ExitStatus.Ok)
val downstreamCompiled = state.compile(testProject)
assert(downstreamCompiled.status == ExitStatus.CompilationError)
val lastDiagnostics = downstreamCompiled.lastDiagnostics(testProject)
assertContains(lastDiagnostics, "Range(Position(6,0),Position(6,0))")
assertContains(lastDiagnostics, "badSymbol")
assertContains(lastDiagnostics, "tree position: line 7 of ")
assertContains(lastDiagnostics, "a/src/main/scala/a/FastCompilation.scala")
}
}
}

}
42 changes: 42 additions & 0 deletions shared/src/main/scala/bloop/reporter/Problem.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ package bloop.reporter
import java.util.Optional

import xsbti.Severity
import java.util.ArrayList
import java.io.File
import java.nio.file.Paths

/** Describes a problem (error, warning, message, etc.) given to the reporter. */
final case class Problem private (
Expand Down Expand Up @@ -36,6 +39,45 @@ object Problem {
)
}

def fromError(error: Throwable): Option[Problem] = {
val assertionErrorRegex = """tree position: line (\d+) of (.+\.scala)""".r
assertionErrorRegex.findFirstMatchIn(error.getMessage()) match {
case Some(m) =>
val line = m.group(1).toInt
val file = Paths.get(m.group(2)).toFile()
Some(
Problem(
-1,
xsbti.Severity.Error,
error.getMessage(),
BloopPosition(file, Optional.of(line)),
"assertion error",
Optional.empty(),
new ArrayList[xsbti.DiagnosticRelatedInformation](),
new ArrayList[xsbti.Action]()
)
)
case None => // No match
None
}
}

case class BloopPosition(file: File, line: Optional[Integer]) extends xsbti.Position {

override def lineContent(): String = ""

override def offset(): Optional[Integer] = Optional.empty()

override def pointer(): Optional[Integer] = Optional.empty()

override def pointerSpace(): Optional[String] = Optional.empty()

override def sourcePath(): Optional[String] = Optional.empty()

override def sourceFile(): Optional[File] = Optional.of(file)

}

case class DiagnosticsCount(errors: Long, warnings: Long) {
override def toString: String = s"$errors errors, $warnings warnings"
}
Expand Down
Loading