Skip to content

Commit

Permalink
Merge pull request #599 from adpi2/better-stack-trace
Browse files Browse the repository at this point in the history
Better stack trace
  • Loading branch information
adpi2 authored Jan 4, 2024
2 parents de2e22b + 627ccc7 commit 20ff51a
Show file tree
Hide file tree
Showing 71 changed files with 44,616 additions and 2,267 deletions.
54 changes: 28 additions & 26 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ inThisBuild(
if (isRelease) dynVer
else "3.2.0-SNAPSHOT" // only for local publishing
},
resolvers += Resolver.mavenLocal
resolvers += Resolver.mavenLocal,
compile / javacOptions ++= Seq("-source", "1.8", "-target", "1.8")
)
)

Expand Down Expand Up @@ -47,7 +48,6 @@ lazy val javaDebug = project
name := "com-microsoft-java-debug-core",
crossPaths := false,
autoScalaLibrary := false,
compile / javacOptions ++= Seq("-source", "1.8", "-target", "1.8"),
libraryDependencies ++= Seq(
"org.apache.commons" % "commons-lang3" % "3.14.0",
"com.google.code.gson" % "gson" % "2.10.1",
Expand Down Expand Up @@ -75,7 +75,7 @@ lazy val core = projectMatrix
.enablePlugins(SbtJdiTools, BuildInfoPlugin)
.settings(
name := "scala-debug-adapter",
scalacOptionsSetting,
scalacOptionsSettings,
libraryDependencies ++= List(
Dependencies.scalaReflect(scalaVersion.value),
Dependencies.asm,
Expand Down Expand Up @@ -114,24 +114,11 @@ lazy val tests = projectMatrix
Dependencies.coursier.cross(CrossVersion.for3Use2_13),
Dependencies.coursierJvm.cross(CrossVersion.for3Use2_13)
),
scalacOptionsSetting,
scalacOptionsSettings,
PgpKeys.publishSigned := {},
publish := {},
// Test / javaOptions += "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=1044",
Test / fork := true,
Test / baseDirectory := (ThisBuild / baseDirectory).value / "modules" / "tests",
// do not use sbt logger, otherwise the output of a test only appears at the end of the suite
Test / testOptions += Tests.Argument(TestFrameworks.MUnit, "+l"),
Test / testOptions := (Test / testOptions)
.dependsOn(
// break cyclic reference
LocalProject("expressionCompiler2_12") / publishLocal,
LocalProject("expressionCompiler2_13") / publishLocal,
LocalProject("expressionCompiler3_0") / publishLocal,
LocalProject("expressionCompiler3") / publishLocal,
LocalProject("unpickler3") / publishLocal
)
.value
testOptionsSettings
)
.dependsOn(core)

Expand All @@ -141,7 +128,7 @@ lazy val sbtPlugin = project
.settings(
name := "sbt-debug-adapter",
scalaVersion := Dependencies.scala212,
scalacOptionsSetting,
scalacOptionsSettings,
sbtVersion := "1.4.9",
scriptedSbt := "1.5.5",
Compile / generateContrabands / contrabandFormatsForType := ContrabandConfig.getFormats,
Expand Down Expand Up @@ -197,7 +184,7 @@ lazy val expressionCompiler = projectMatrix
},
Compile / doc / sources := Seq.empty,
libraryDependencies += Dependencies.scalaCompiler(scalaVersion.value),
scalacOptionsSetting
scalacOptionsSettings
)

lazy val unpickler3: Project = project
Expand All @@ -209,16 +196,31 @@ lazy val unpickler3: Project = project
scalaVersion := Dependencies.scala31Plus,
Compile / doc / sources := Seq.empty,
libraryDependencies ++= Seq(
"ch.epfl.scala" %% "tasty-query" % "0.10.0",
"org.scala-lang" %% "tasty-core" % scalaVersion.value,
"ch.epfl.scala" %% "tasty-query" % "1.2.0",
Dependencies.asm,
Dependencies.asmUtil,
Dependencies.munit % Test
),
Test / fork := true,
// do not use sbt logger, otherwise the output of a test only appears at the end of the suite
Test / testOptions += Tests.Argument(TestFrameworks.MUnit, "+l")
testOptionsSettings
)

lazy val scalacOptionsSetting = Def.settings(
lazy val testOptionsSettings = Def.settings(
Test / fork := true,
// do not use sbt logger, otherwise the output of a test only appears at the end of the suite
Test / testOptions += Tests.Argument(TestFrameworks.MUnit, "+l"),
Test / testOptions := (Test / testOptions)
.dependsOn(
// break cyclic reference
LocalProject("expressionCompiler2_12") / publishLocal,
LocalProject("expressionCompiler2_13") / publishLocal,
LocalProject("expressionCompiler3_0") / publishLocal,
LocalProject("expressionCompiler3") / publishLocal,
LocalProject("unpickler3") / publishLocal
)
.value
)

lazy val scalacOptionsSettings = Def.settings(
scalacOptions ++= onScalaVersion(
scala212 = Seq("-Xsource:3", "-Ywarn-unused-import", "-deprecation"),
scala213 = Seq("-Xsource:3", "-Wunused:imports", "-deprecation"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import java.net.URI
import ch.epfl.scala.debugadapter.Logger
import ch.epfl.scala.debugadapter.internal.ScalaExtension.*
import scala.util.control.NonFatal
import scala.util.Properties

private case class SourceFile(
entry: SourceEntry,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val sourceLookUp: SourceLookU
expression.parse[Stat] match {
case err: Parsed.Error => Fatal(err.details)
case Parsed.Success(tree) => Valid(tree)
case _: Parsed.Success[?] =>
Fatal(new Exception("Parsed expression is not a statement"))
}

def validate(expression: String): Validation[RuntimeEvaluableTree] =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
package ch.epfl.scala.debugadapter.internal.stacktrace

import java.lang.reflect.Method
import ch.epfl.scala.debugadapter.Logger
import com.sun.jdi
import ch.epfl.scala.debugadapter.Debuggee
import java.util.function.Consumer
import java.lang.reflect.InvocationTargetException
import ch.epfl.scala.debugadapter.Java8
import ch.epfl.scala.debugadapter.Java9OrAbove
import scala.util.Try
import ch.epfl.scala.debugadapter.Logger
import com.sun.jdi

import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Method
import java.nio.file.Files
import java.nio.file.Path
import java.util.Optional
import java.util.function.Consumer
import scala.jdk.CollectionConverters._
import scala.jdk.OptionConverters._
import scala.util.Try

class Scala3UnpicklerBridge(
debuggee: Debuggee,
Expand Down Expand Up @@ -45,13 +48,13 @@ class Scala3UnpicklerBridge(

object Scala3UnpicklerBridge {
def load(debuggee: Debuggee, unpicklerClass: Class[?], logger: Logger, testMode: Boolean) = {
// TASTy Query needs the javaRuntimeJars
val javaRuntimeJars = debuggee.javaRuntime.toSeq.flatMap {
case Java8(_, classJars, _) => classJars
case java9OrAbove: Java9OrAbove =>
java9OrAbove.classSystems.map(_.fileSystem.getPath("/modules", "java.base"))
java9OrAbove.classSystems.flatMap { javaFs =>
Files.list(javaFs.fileSystem.getPath("/modules")).iterator.asScala.toSeq
}
}

val debuggeeClasspath = debuggee.classPath.toArray ++ javaRuntimeJars
val warnLogger: Consumer[String] = msg => logger.warn(msg)
val ctr = unpicklerClass.getConstructor(classOf[Array[Path]], classOf[Consumer[String]], classOf[Boolean])
Expand All @@ -66,7 +69,7 @@ object Scala3UnpicklerBridge {
testMode: Boolean
): Try[Scala3UnpicklerBridge] = {
Try {
val className = "ch.epfl.scala.debugadapter.internal.stacktrace.Scala3Unpickler"
val className = "ch.epfl.scala.debugadapter.internal.stacktrace.Scala3UnpicklerBridge"
val cls = classLoader.loadClass(className)

val bridge = load(debuggee, cls, logger, testMode)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ch.epfl.scala.debugadapter.testfmk

import scala.concurrent.duration.*
import scala.util.Properties

trait CommonFunSuite extends munit.FunSuite {
def isJava8: Boolean = Properties.javaVersion.startsWith("1.8")

def isDebug: Boolean = DebugUtils.isDebug

override def munitTimeout: Duration =
if (isDebug) 8.hours else super.munitTimeout
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,24 @@ object DebugStepAssert {
def inParallel(steps: SingleStepAssert[Either[String, String]]*): ParallelStepsAsserts[Either[String, String]] =
ParallelStepsAsserts(steps)

def assertOnFrame(expectedSource: Path, expectedLine: Int)(frames: Seq[StackFrame])(implicit
def assertOnFrame(expectedSource: Path, expectedLine: Int)(frames: Array[StackFrame])(implicit
location: Location
): Unit = {
assert(frames != null)
assertEquals(frames.head.source.path, expectedSource.toString)
assertEquals(frames.head.line, expectedLine)
}

def assertOnFrame(expectedName: String)(frames: Seq[StackFrame])(implicit loc: Location): Unit =
def assertOnFrame(expectedName: String)(frames: Array[StackFrame])(implicit loc: Location): Unit = {
assert(frames != null)
assertEquals(frames.head.name, expectedName)
}

def assertOnFrame(expected: Seq[String])(stackTrace: Seq[StackFrame])(implicit
def assertOnFrame(expected: Seq[String])(frames: Array[StackFrame])(implicit
location: Location
): Unit = {
val obtained = stackTrace.map(frame => frame.name)
assert(frames != null)
val obtained = frames.map(frame => frame.name).toSeq
assertEquals(obtained, expected)
}
}
Expand All @@ -39,21 +43,21 @@ final case class ParallelStepsAsserts[T](steps: Seq[SingleStepAssert[T]]) extend

sealed trait DebugStep[+T]

final case class Breakpoint(sourceFile: Path, line: Int, condition: Option[String]) extends DebugStep[Seq[StackFrame]]
final case class Breakpoint(sourceFile: Path, line: Int, condition: Option[String]) extends DebugStep[Array[StackFrame]]
object Breakpoint {
def apply(line: Int)(implicit ctx: TestingContext, location: Location): SingleStepAssert[Seq[StackFrame]] = {
def apply(line: Int)(implicit ctx: TestingContext, location: Location): SingleStepAssert[Array[StackFrame]] = {
val breakpoint = Breakpoint(ctx.mainSource, line, None)
SingleStepAssert(breakpoint, assertOnFrame(ctx.mainSource, line))
}

def apply(sourceFile: Path, line: Int): SingleStepAssert[Seq[StackFrame]] = {
def apply(sourceFile: Path, line: Int): SingleStepAssert[Array[StackFrame]] = {
val breakpoint = Breakpoint(sourceFile, line, None)
SingleStepAssert(breakpoint, assertOnFrame(sourceFile, line))
}
def apply(line: Int, expectedStackTrace: Seq[String])(implicit
ctx: TestingContext,
location: Location
): SingleStepAssert[Seq[StackFrame]] = {
): SingleStepAssert[Array[StackFrame]] = {
val breakpoint = Breakpoint(ctx.mainSource, line, None)
SingleStepAssert(
breakpoint,
Expand All @@ -63,7 +67,7 @@ object Breakpoint {
)
}

def apply(line: Int, condition: String)(implicit ctx: TestingContext): SingleStepAssert[Seq[StackFrame]] = {
def apply(line: Int, condition: String)(implicit ctx: TestingContext): SingleStepAssert[Array[StackFrame]] = {
val breakpoint = Breakpoint(ctx.mainSource, line, Some(condition))
SingleStepAssert(breakpoint, assertOnFrame(ctx.mainSource, line))
}
Expand All @@ -80,27 +84,27 @@ object Logpoint {
}
}

object StepIn extends DebugStep[Seq[StackFrame]] {
def line(line: Int)(implicit ctx: TestingContext, location: Location): SingleStepAssert[Seq[StackFrame]] =
object StepIn extends DebugStep[Array[StackFrame]] {
def line(line: Int)(implicit ctx: TestingContext, location: Location): SingleStepAssert[Array[StackFrame]] =
SingleStepAssert(StepIn, assertOnFrame(ctx.mainSource, line))

def method(methodName: String)(implicit loc: Location): SingleStepAssert[Seq[StackFrame]] =
def method(methodName: String)(implicit loc: Location): SingleStepAssert[Array[StackFrame]] =
SingleStepAssert(StepIn, assertOnFrame(methodName))
}

object StepOut extends DebugStep[Seq[StackFrame]] {
def line(line: Int)(implicit ctx: TestingContext): SingleStepAssert[Seq[StackFrame]] =
object StepOut extends DebugStep[Array[StackFrame]] {
def line(line: Int)(implicit ctx: TestingContext): SingleStepAssert[Array[StackFrame]] =
SingleStepAssert(StepOut, assertOnFrame(ctx.mainSource, line))

def method(methodName: String): SingleStepAssert[Seq[StackFrame]] =
def method(methodName: String): SingleStepAssert[Array[StackFrame]] =
SingleStepAssert(StepOut, assertOnFrame(methodName))
}

object StepOver extends DebugStep[Seq[StackFrame]] {
def line(line: Int)(implicit ctx: TestingContext): SingleStepAssert[Seq[StackFrame]] =
object StepOver extends DebugStep[Array[StackFrame]] {
def line(line: Int)(implicit ctx: TestingContext): SingleStepAssert[Array[StackFrame]] =
SingleStepAssert(StepOver, assertOnFrame(ctx.mainSource, line))

def method(methodName: String): SingleStepAssert[Seq[StackFrame]] =
def method(methodName: String): SingleStepAssert[Array[StackFrame]] =
SingleStepAssert(StepOver, assertOnFrame(methodName))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,12 @@ trait DebugTest {
assertion(values.getOrElse(throw new NoSuchElementException(variable.name)))
}

def assertStop(assertion: List[StackFrame] => Unit): DebugCheckState = {
def assertStop(assertion: Array[StackFrame] => Unit): DebugCheckState = {
val stopped = state.client.stopped()
val threadId = stopped.threadId
val stackTrace = state.client.stackTrace(threadId)

assertion(stackTrace.stackFrames.toList)
assertion(stackTrace.stackFrames)
state.copy(threadId = threadId, topFrame = stackTrace.stackFrames.head, paused = true)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ch.epfl.scala.debugadapter.testfmk

import java.lang.management.ManagementFactory
import scala.jdk.CollectionConverters.*

object DebugUtils {
def isDebug: Boolean = {
val mxBean = ManagementFactory.getRuntimeMXBean
mxBean.getInputArguments.asScala.exists(_.contains("jdwp"))
}
}
Loading

0 comments on commit 20ff51a

Please sign in to comment.