Skip to content

Commit 21ca7b0

Browse files
authored
Merge pull request #349 from bryljaku/logpoint-opt
Dont evaluate expression if there's no interpolation
2 parents cb1f64a + 0e2573e commit 21ca7b0

File tree

17 files changed

+142
-81
lines changed

17 files changed

+142
-81
lines changed

modules/core/src/main/scala/ch/epfl/scala/debugadapter/DebugConfig.scala

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@ object DebugConfig {
1818
)
1919

2020
sealed trait EvaluationMode {
21-
def canUseCompiler: Boolean = true
22-
def canBypassCompiler: Boolean = true
21+
def allowScalaEvaluation: Boolean = false
22+
def allowSimpleEvaluation: Boolean = false
2323
}
2424

25-
case object ExpressionCompilerOnly extends EvaluationMode {
26-
override def canBypassCompiler: Boolean = false
25+
case object ScalaEvaluationOnly extends EvaluationMode {
26+
override def allowScalaEvaluation: Boolean = true
2727
}
28-
case object AlwaysBypassCompiler extends EvaluationMode {
29-
override def canUseCompiler: Boolean = false
28+
case object SimpleEvaluationOnly extends EvaluationMode {
29+
override def allowSimpleEvaluation: Boolean = true
30+
}
31+
case object NoEvaluation extends EvaluationMode
32+
case object MixedEvaluation extends EvaluationMode {
33+
override def allowScalaEvaluation: Boolean = true
34+
override def allowSimpleEvaluation: Boolean = true
3035
}
31-
case object MixedEvaluation extends EvaluationMode
3236
}

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/EvaluationProvider.scala

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ import ch.epfl.scala.debugadapter.internal.evaluator.CompiledExpression
1010
import ch.epfl.scala.debugadapter.internal.evaluator.FrameReference
1111
import ch.epfl.scala.debugadapter.internal.evaluator.JdiObject
1212
import ch.epfl.scala.debugadapter.internal.evaluator.LocalValue
13+
import ch.epfl.scala.debugadapter.internal.evaluator.MessageLogger
1314
import ch.epfl.scala.debugadapter.internal.evaluator.MethodInvocationFailed
15+
import ch.epfl.scala.debugadapter.internal.evaluator.PlainLogMessage
1416
import ch.epfl.scala.debugadapter.internal.evaluator.PreparedExpression
1517
import ch.epfl.scala.debugadapter.internal.evaluator.ScalaEvaluator
1618
import ch.epfl.scala.debugadapter.internal.evaluator.SimpleEvaluator
@@ -32,6 +34,7 @@ import ScalaExtension.*
3234
private[internal] class EvaluationProvider(
3335
sourceLookUp: SourceLookUpProvider,
3436
simpleEvaluator: SimpleEvaluator,
37+
messageLogger: MessageLogger,
3538
scalaEvaluators: Map[ClassEntry, ScalaEvaluator],
3639
mode: DebugConfig.EvaluationMode,
3740
logger: Logger
@@ -69,13 +72,11 @@ private[internal] class EvaluationProvider(
6972
val locationCode = (location.method.name, location.codeIndex).hashCode
7073
val expression =
7174
if (breakpoint.getCompiledExpression(locationCode) != null) {
72-
breakpoint.getCompiledExpression(locationCode).asInstanceOf[Try[CompiledExpression]]
75+
breakpoint.getCompiledExpression(locationCode).asInstanceOf[Try[PreparedExpression]]
7376
} else if (breakpoint.containsConditionalExpression) {
7477
prepare(breakpoint.getCondition, frame)
7578
} else if (breakpoint.containsLogpointExpression) {
76-
val tripleQuote = "\"\"\""
77-
val expression = s"""println(s$tripleQuote${breakpoint.getLogMessage}$tripleQuote)"""
78-
prepare(expression, frame)
79+
prepareLogMessage(breakpoint.getLogMessage, frame)
7980
} else {
8081
Failure(new Exception("Missing expression"))
8182
}
@@ -113,11 +114,21 @@ private[internal] class EvaluationProvider(
113114
evaluator <- scalaEvaluators.get(entry).toTry(s"Missing expression compiler for entry ${entry.name}")
114115
} yield evaluator
115116

117+
private def prepareLogMessage(message: String, frame: FrameReference): Try[PreparedExpression] = {
118+
if (!message.contains("$")) {
119+
Success(PlainLogMessage(message))
120+
} else {
121+
val tripleQuote = "\"\"\""
122+
val expression = s"""println(s$tripleQuote$message$tripleQuote)"""
123+
prepare(expression, frame)
124+
}
125+
}
126+
116127
private def prepare(expression: String, frame: FrameReference): Try[PreparedExpression] = {
117128
lazy val simpleExpression = simpleEvaluator.prepare(expression, frame)
118-
if (mode.canBypassCompiler && simpleExpression.isDefined) {
129+
if (mode.allowSimpleEvaluation && simpleExpression.isDefined) {
119130
Success(simpleExpression.get)
120-
} else if (mode.canUseCompiler) {
131+
} else if (mode.allowScalaEvaluation) {
121132
val fqcn = frame.current().location.declaringType.name
122133
for {
123134
evaluator <- getScalaEvaluator(fqcn)
@@ -133,6 +144,7 @@ private[internal] class EvaluationProvider(
133144

134145
private def evaluate(expression: PreparedExpression, frame: FrameReference): Try[Value] = {
135146
expression match {
147+
case logMessage: PlainLogMessage => messageLogger.log(logMessage, frame)
136148
case localValue: LocalValue => simpleEvaluator.evaluate(localValue, frame)
137149
case expression: CompiledExpression =>
138150
val fqcn = frame.current().location.declaringType.name
@@ -174,6 +186,14 @@ private[internal] object EvaluationProvider {
174186
val scalaEvaluators = debugTools.expressionCompilers.view.map { case (entry, compiler) =>
175187
(entry, new ScalaEvaluator(entry, compiler, logger, config.testMode))
176188
}.toMap
177-
new EvaluationProvider(sourceLookUp, simpleEvaluator, scalaEvaluators, config.evaluationMode, logger)
189+
val messageLogger = new MessageLogger()
190+
new EvaluationProvider(
191+
sourceLookUp,
192+
simpleEvaluator,
193+
messageLogger,
194+
scalaEvaluators,
195+
config.evaluationMode,
196+
logger
197+
)
178198
}
179199
}

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiArray.scala

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package ch.epfl.scala.debugadapter.internal.evaluator
22

3-
import com.sun.jdi.{ArrayReference, ObjectReference, ThreadReference, Value}
3+
import com.sun.jdi.{ArrayReference, ThreadReference, Value}
44

55
import scala.jdk.CollectionConverters.*
66

77
object JdiArray {
8-
def apply(
9-
arrayType: String,
10-
arraySize: Int,
11-
classLoader: JdiClassLoader
12-
): Safe[JdiArray] = {
8+
def apply(value: Value, thread: ThreadReference): JdiArray =
9+
new JdiArray(value.asInstanceOf[ArrayReference], thread)
10+
11+
def apply(arrayType: String, arraySize: Int, classLoader: JdiClassLoader): Safe[JdiArray] = {
1312
val thread = classLoader.thread
1413
for {
1514
classClass <- classLoader.loadClass("java.lang.Class")
@@ -18,23 +17,14 @@ object JdiArray {
1817
classClass.invoke("getPrimitiveClass", List(intValue))
1918
arrayClass <- classLoader.loadClass("java.lang.reflect.Array")
2019
newInstanceValue <- classLoader.mirrorOf("newInstance")
21-
newInstanceMethod <-
22-
arrayClass
23-
.invoke(
24-
"getMethod",
25-
List(newInstanceValue, classClass.reference, intClass)
26-
)
27-
.map(_.asInstanceOf[ObjectReference])
28-
.map(new JdiObject(_, thread))
20+
newInstanceMethod <- arrayClass
21+
.invoke("getMethod", List(newInstanceValue, classClass.reference, intClass))
22+
.map(JdiObject(_, thread))
2923
arrayTypeClass <- classLoader.loadClass(arrayType)
3024
integerRef <- JdiPrimitive.box(arraySize, classLoader, thread)
3125
array <- newInstanceMethod
32-
.invoke(
33-
"invoke",
34-
List(null, arrayTypeClass.reference, integerRef)
35-
)
36-
.map(_.asInstanceOf[ArrayReference])
37-
.map(new JdiArray(_, thread))
26+
.invoke("invoke", List(null, arrayTypeClass.reference, integerRef))
27+
.map(JdiArray(_, thread))
3828
} yield array
3929
}
4030
}

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiClassLoader.scala

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
package ch.epfl.scala.debugadapter.internal.evaluator
22

33
import com.sun.jdi._
4+
import scala.collection.JavaConverters.*
5+
6+
private[internal] object JdiClassLoader {
7+
def fromFrame(frame: FrameReference): Safe[JdiClassLoader] = Safe {
8+
val scalaLibClassLoader =
9+
for {
10+
scalaLibClass <- frame.thread.virtualMachine.allClasses.asScala
11+
.find(c => c.name.startsWith("scala.runtime"))
12+
classLoader <- Option(scalaLibClass.classLoader)
13+
} yield classLoader
14+
15+
val classLoader = Option(frame.current().location.method.declaringType.classLoader)
16+
.orElse(scalaLibClassLoader)
17+
.getOrElse(throw new Exception("Cannot find the classloader of the Scala library"))
18+
JdiClassLoader(classLoader, frame.thread)
19+
}
420

5-
private[evaluator] object JdiClassLoader {
621
def apply(
722
classLoader: ClassLoaderReference,
823
thread: ThreadReference
@@ -17,11 +32,12 @@ private[evaluator] object JdiClassLoader {
1732
}
1833
}
1934

20-
private[evaluator] case class JdiClassLoader(
35+
private[internal] case class JdiClassLoader(
2136
classLoaderRef: ClassLoaderReference,
2237
loadClassMethod: Method,
2338
thread: ThreadReference
2439
) {
40+
2541
def loadClass(name: String): Safe[JdiClassObject] = {
2642
for {
2743
nameValue <- mirrorOf(name)

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiClassObject.scala

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,10 @@ class JdiClassObject(
1414
objects <- JdiArray("java.lang.Object", args.size, classLoader)
1515
_ = objects.setValues(args)
1616
constructor <- invoke("getConstructor", parameterTypes)
17-
.map(_.asInstanceOf[ObjectReference])
18-
.map(new JdiObject(_, thread))
17+
.map(JdiObject(_, thread))
1918
jdiObject <- constructor
2019
.invoke("newInstance", List(objects.reference))
21-
.map(_.asInstanceOf[ObjectReference])
22-
.map(new JdiObject(_, thread))
20+
.map(JdiObject(_, thread))
2321
} yield jdiObject
2422
}
2523

@@ -30,12 +28,8 @@ class JdiClassObject(
3028
val parameterTypes = args.map(_.referenceType.classObject())
3129
for {
3230
methodNameValue <- classLoader.mirrorOf(methodName)
33-
method <- invoke(
34-
"getMethod",
35-
methodNameValue :: parameterTypes
36-
)
37-
.map(_.asInstanceOf[ObjectReference])
38-
.map(new JdiObject(_, thread))
31+
method <- invoke("getMethod", methodNameValue :: parameterTypes)
32+
.map(JdiObject(_, thread))
3933
result <- method.invoke("invoke", List(null) ++ args)
4034
} yield result
4135
}

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiObject.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,8 @@ private[internal] class JdiObject(
2121
invokeMethod(reference, m, args, thread)
2222
}
2323
}
24+
25+
private[internal] object JdiObject {
26+
def apply(value: Value, thread: ThreadReference): JdiObject =
27+
new JdiObject(value.asInstanceOf[ObjectReference], thread)
28+
}

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiPrimitive.scala

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@ object JdiPrimitive {
2020
case _: Long => classLoader.loadClass("java.lang.Long")
2121
case _: Short => classLoader.loadClass("java.lang.Short")
2222
}
23-
objectRef <- clazz
24-
.invokeStatic("valueOf", List(jdiValue))
25-
.map(_.asInstanceOf[ObjectReference])
26-
} yield objectRef
23+
objectRef <- clazz.invokeStatic("valueOf", List(jdiValue))
24+
} yield objectRef.asInstanceOf[ObjectReference]
2725
}
2826
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package ch.epfl.scala.debugadapter.internal.evaluator
2+
3+
import com.sun.jdi.*
4+
import scala.util.Try
5+
6+
private[internal] class MessageLogger() {
7+
def log(logMessage: PlainLogMessage, frame: FrameReference): Try[Value] = {
8+
val result = for {
9+
classLoader <- JdiClassLoader.fromFrame(frame)
10+
vm = frame.thread.virtualMachine()
11+
arg <- Safe(vm.mirrorOf(logMessage.message))
12+
predefClass <- classLoader.loadClass("scala.Predef$")
13+
moduleField <- predefClass
14+
.invoke("getDeclaredField", List(vm.mirrorOf("MODULE$")))
15+
.map(JdiObject(_, frame.thread))
16+
predef <- moduleField.invoke("get", List(null)).map(JdiObject(_, frame.thread))
17+
res <- predef.invoke("println", "(Ljava/lang/Object;)V", List(arg))
18+
} yield res
19+
result.getResult
20+
}
21+
}

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/PreparedExpression.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ import java.nio.file.Path
55
sealed trait PreparedExpression
66
final case class CompiledExpression(classDir: Path, className: String) extends PreparedExpression
77
final case class LocalValue(name: String) extends PreparedExpression
8+
final case class PlainLogMessage(message: String) extends PreparedExpression

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/ScalaEvaluator.scala

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ private[internal] class ScalaEvaluator(
3838
Files.write(sourceFile, sourceContent.getBytes(StandardCharsets.UTF_8))
3939

4040
val expressionFqcn = if (packageName.isEmpty) expressionClassName else s"$packageName.$expressionClassName"
41-
val classLoader = findClassLoader(frame)
4241
val compiledExpression =
4342
for {
43+
classLoader <- JdiClassLoader.fromFrame(frame)
4444
(names, values) <- extractValuesAndNames(frame, classLoader)
4545
localNames = names.map(_.value()).toSet
4646
_ <- Safe(
@@ -51,8 +51,8 @@ private[internal] class ScalaEvaluator(
5151
}
5252

5353
private def evaluate(classDir: Path, className: String, frame: FrameReference): Try[Value] = {
54-
val classLoader = findClassLoader(frame)
5554
val evaluatedValue = for {
55+
classLoader <- JdiClassLoader.fromFrame(frame)
5656
(names, values) <- extractValuesAndNames(frame, classLoader)
5757
namesArray <- JdiArray("java.lang.String", names.size, classLoader)
5858
valuesArray <- JdiArray("java.lang.Object", values.size, classLoader)
@@ -67,20 +67,6 @@ private[internal] class ScalaEvaluator(
6767
evaluatedValue.getResult
6868
}
6969

70-
private def findClassLoader(frame: FrameReference): JdiClassLoader = {
71-
val scalaLibClassLoader =
72-
for {
73-
scalaLibClass <- frame.thread.virtualMachine.allClasses.asScala
74-
.find(c => c.name.startsWith("scala.runtime"))
75-
classLoader <- Option(scalaLibClass.classLoader)
76-
} yield classLoader
77-
78-
val classLoader = Option(frame.current().location.method.declaringType.classLoader)
79-
.orElse(scalaLibClassLoader)
80-
.getOrElse(throw new Exception("Cannot find the classloader of the Scala library"))
81-
JdiClassLoader(classLoader, frame.thread)
82-
}
83-
8470
private def evaluateExpression(expressionInstance: JdiObject): Safe[Value] = {
8571
expressionInstance
8672
.invoke("evaluate", List())

0 commit comments

Comments
 (0)