Skip to content

Commit c21d07f

Browse files
authored
Merge pull request #675 from adpi2/fix-672
fix #672: avoid GC on conditional breakpoint values
2 parents a7b7fde + aa9c003 commit c21d07f

File tree

8 files changed

+84
-94
lines changed

8 files changed

+84
-94
lines changed

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ import ch.epfl.scala.debugadapter.internal.evaluator.*
1111
import com.microsoft.java.debug.core.IEvaluatableBreakpoint
1212
import com.microsoft.java.debug.core.adapter.IDebugAdapterContext
1313
import com.microsoft.java.debug.core.adapter.IEvaluationProvider
14+
import com.sun.jdi
1415
import com.sun.jdi.ObjectReference
1516
import com.sun.jdi.ThreadReference
16-
import com.sun.jdi.Value
1717

1818
import java.util.concurrent.CompletableFuture
1919
import java.util.concurrent.atomic.AtomicBoolean
@@ -40,7 +40,7 @@ private[internal] class EvaluationProvider(
4040

4141
override def isInEvaluation(thread: ThreadReference) = isEvaluating.get
4242

43-
override def evaluate(expression: String, thread: ThreadReference, depth: Int): CompletableFuture[Value] = {
43+
override def evaluate(expression: String, thread: ThreadReference, depth: Int): CompletableFuture[jdi.Value] = {
4444
val frame = JdiFrame(thread, depth)
4545
val evaluation = for {
4646
preparedExpression <- prepare(expression, frame, preEvaluation = true)
@@ -53,12 +53,12 @@ private[internal] class EvaluationProvider(
5353
expression: String,
5454
thisContext: ObjectReference,
5555
thread: ThreadReference
56-
): CompletableFuture[Value] = ???
56+
): CompletableFuture[jdi.Value] = ???
5757

5858
override def evaluateForBreakpoint(
5959
breakpoint: IEvaluatableBreakpoint,
6060
thread: ThreadReference
61-
): CompletableFuture[Value] = {
61+
): CompletableFuture[jdi.Value] = {
6262
val frame = JdiFrame(thread, 0)
6363
val location = frame.current().location
6464
val locationCode = (location.method.name, location.codeIndex).hashCode
@@ -82,10 +82,10 @@ private[internal] class EvaluationProvider(
8282
thisContext: ObjectReference,
8383
methodName: String,
8484
methodSignature: String,
85-
args: Array[Value],
85+
args: Array[jdi.Value],
8686
thread: ThreadReference,
8787
invokeSuper: Boolean
88-
): CompletableFuture[Value] = {
88+
): CompletableFuture[jdi.Value] = {
8989
val obj = JdiObject(thisContext, thread)
9090
val wrappedArgs = if (args == null) Seq.empty else args.toSeq.map(JdiValue(_, thread))
9191
val invocation = evaluationBlock {
@@ -162,13 +162,13 @@ private[internal] class EvaluationProvider(
162162
case If(p, t, f, _) => containsMethodCall(p) || containsMethodCall(t) || containsMethodCall(f)
163163
case Assign(lhs, rhs, _) => containsMethodCall(lhs) || containsMethodCall(rhs)
164164
case _: CallMethod | _: NewInstance => true
165-
case _: LocalVar | _: RuntimeEvaluationTree.Value | _: This => false
165+
case _: LocalVar | _: Value | _: This | _: Literal => false
166166
case _: StaticField | _: StaticModule => false
167167
case _: CallBinaryOp | _: CallUnaryOp | _: ArrayElem => false
168168
}
169169
}
170170

171-
private def evaluate(expression: PreparedExpression, frame: JdiFrame): Try[Value] = evaluationBlock {
171+
private def evaluate(expression: PreparedExpression, frame: JdiFrame): Try[jdi.Value] = evaluationBlock {
172172
val result = expression match {
173173
case logMessage: PlainLogMessage => MessageLogger.log(logMessage, frame)
174174
case expr: RuntimeExpression => runtimeEvaluator.evaluate(expr, frame)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class RuntimeEvaluation(frame: JdiFrame, logger: Logger) {
1111
private def eval(stat: RuntimeEvaluationTree): Safe[JdiValue] =
1212
stat match {
1313
case Value(value, _) => value
14+
case Literal(value, _) => frame.classLoader().flatMap(_.mirrorOfLiteral(value))
1415
case LocalVar(varName, _) => Safe.successful(frame.variableByName(varName).map(frame.variableValue).get)
1516
case primitive: CallBinaryOp => invokePrimitive(primitive)
1617
case primitive: CallUnaryOp => invokePrimitive(primitive)

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

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ object RuntimeEvaluationTree {
2424

2525
case class LocalVar(name: String, `type`: jdi.Type) extends Assignable {
2626
override def prettyPrint(depth: Int): String = {
27-
val indent = "\t" * (depth + 1)
27+
val indent = " " * (depth + 1)
2828
s"""|LocalVar(
2929
|${indent}name= $name,
3030
|${indent}type= ${`type`}
@@ -35,7 +35,7 @@ object RuntimeEvaluationTree {
3535
case class InstanceField(field: jdi.Field, qualifier: RuntimeEvaluationTree) extends Field {
3636
override lazy val `type` = field.`type`()
3737
override def prettyPrint(depth: Int): String = {
38-
val indent = "\t" * (depth + 1)
38+
val indent = " " * (depth + 1)
3939
s"""|InstanceField(
4040
|${indent}field = $field,
4141
|${indent}qualifier = ${qualifier.prettyPrint(depth + 1)}
@@ -45,7 +45,7 @@ object RuntimeEvaluationTree {
4545
case class StaticField(field: jdi.Field) extends Field {
4646
override lazy val `type` = field.`type`()
4747
override def prettyPrint(depth: Int): String = {
48-
val indent = "\t" * (depth + 1)
48+
val indent = " " * (depth + 1)
4949
s"""|StaticField(
5050
|${indent}field = $field
5151
|${indent.dropRight(1)})""".stripMargin
@@ -59,7 +59,7 @@ object RuntimeEvaluationTree {
5959
) extends CallMethod {
6060
override lazy val `type` = method.returnType()
6161
override def prettyPrint(depth: Int): String = {
62-
val indent = "\t" * (depth + 1)
62+
val indent = " " * (depth + 1)
6363
s"""|InstanceMethod(
6464
|${indent}method = $method -> ${method.returnType()},
6565
|${indent}args = ${args.map(_.prettyPrint(depth + 1)).mkString(",\n" + indent)},
@@ -74,7 +74,7 @@ object RuntimeEvaluationTree {
7474
) extends CallMethod {
7575
override lazy val `type` = method.returnType()
7676
override def prettyPrint(depth: Int): String = {
77-
val indent = "\t" * (depth + 1)
77+
val indent = " " * (depth + 1)
7878
s"""|CallStaticMethod(
7979
|${indent}m= $method,
8080
|${indent}args= ${args.map(_.prettyPrint(depth + 1)).mkString(",\n" + indent)},
@@ -90,7 +90,7 @@ object RuntimeEvaluationTree {
9090
) extends RuntimeEvaluationTree {
9191
override lazy val `type` = op.typeCheck(lhs.`type`, rhs.`type`)
9292
override def prettyPrint(depth: Int): String = {
93-
val indent = "\t" * (depth + 1)
93+
val indent = " " * (depth + 1)
9494
s"""|CallBinaryOp(
9595
|${indent}lhs = ${lhs.prettyPrint(depth + 1)},
9696
|${indent}rhs = ${rhs.prettyPrint(depth + 1)},
@@ -102,7 +102,7 @@ object RuntimeEvaluationTree {
102102
case class ArrayElem(array: RuntimeEvaluationTree, index: RuntimeEvaluationTree, `type`: jdi.Type)
103103
extends RuntimeEvaluationTree {
104104
override def prettyPrint(depth: Int): String = {
105-
val indent = "\t" * (depth + 1)
105+
val indent = " " * (depth + 1)
106106
s"""|ArrayElem(
107107
|${indent}array = $array,
108108
|${indent}index = $index
@@ -116,7 +116,7 @@ object RuntimeEvaluationTree {
116116
) extends RuntimeEvaluationTree {
117117
override lazy val `type` = op.typeCheck(rhs.`type`)
118118
override def prettyPrint(depth: Int): String = {
119-
val indent = "\t" * (depth + 1)
119+
val indent = " " * (depth + 1)
120120
s"""|UnaryOpCall(
121121
|${indent}rhs= ${rhs.prettyPrint(depth + 1)},
122122
|${indent}op= $op
@@ -127,7 +127,7 @@ object RuntimeEvaluationTree {
127127
case class NewInstance(init: CallStaticMethod) extends RuntimeEvaluationTree {
128128
override lazy val `type`: jdi.ReferenceType = init.method.declaringType() // .asInstanceOf[jdi.ClassType]
129129
override def prettyPrint(depth: Int): String = {
130-
val indent = "\t" * (depth + 1)
130+
val indent = " " * (depth + 1)
131131
s"""|NewInstance(
132132
|${indent}init= ${init.prettyPrint(depth + 1)}
133133
|${indent.dropRight(1)})""".stripMargin
@@ -140,7 +140,7 @@ object RuntimeEvaluationTree {
140140

141141
case class StaticModule(`type`: jdi.ClassType) extends RuntimeEvaluationTree {
142142
override def prettyPrint(depth: Int): String = {
143-
val indent = "\t" * (depth + 1)
143+
val indent = " " * (depth + 1)
144144
s"""|StaticModule(
145145
|${indent}mod= ${`type`}
146146
|${indent.dropRight(1)})""".stripMargin
@@ -149,34 +149,42 @@ object RuntimeEvaluationTree {
149149

150150
case class NestedModule(`type`: jdi.ReferenceType, init: CallInstanceMethod) extends RuntimeEvaluationTree {
151151
override def prettyPrint(depth: Int): String = {
152-
val indent = "\t" * (depth + 1)
152+
val indent = " " * (depth + 1)
153153
s"""|NestedModule(
154154
|${indent}type = ${`type`}
155155
|${indent}init = ${init.prettyPrint(depth + 1)}
156156
|${indent.dropRight(1)})""".stripMargin
157157
}
158158
}
159159

160-
case class Value(
161-
value: Safe[JdiValue],
162-
`type`: jdi.Type
163-
) extends RuntimeEvaluationTree {
160+
case class Value(value: Safe[JdiValue], `type`: jdi.Type) extends RuntimeEvaluationTree {
164161
override def prettyPrint(depth: Int): String = {
165-
val indent = "\t" * (depth + 1)
162+
val indent = " " * (depth + 1)
166163
s"""|Value(
167164
|${indent}v= $value,
168165
|${indent}t= ${`type`}
169166
|${indent.dropRight(1)})""".stripMargin
170167
}
171168
}
169+
170+
case class Literal(value: Any, `type`: jdi.Type) extends RuntimeEvaluationTree {
171+
override def prettyPrint(depth: Int): String = {
172+
val indent = " " * (depth + 1)
173+
s"""|Literal(
174+
|${indent}v= $value,
175+
|${indent}t= ${`type`}
176+
|${indent.dropRight(1)})""".stripMargin
177+
}
178+
}
179+
172180
case class If(
173181
p: RuntimeEvaluationTree,
174182
thenp: RuntimeEvaluationTree,
175183
elsep: RuntimeEvaluationTree,
176184
`type`: jdi.Type
177185
) extends RuntimeEvaluationTree {
178186
override def prettyPrint(depth: Int): String = {
179-
val indent = "\t" * (depth + 1)
187+
val indent = " " * (depth + 1)
180188
s"""|If(
181189
|${indent}p= ${p.prettyPrint(depth + 1)},
182190
|${indent}ifTrue= ${thenp.prettyPrint(depth + 1)},
@@ -192,7 +200,7 @@ object RuntimeEvaluationTree {
192200
`type`: jdi.Type
193201
) extends RuntimeEvaluationTree {
194202
override def prettyPrint(depth: Int): String = {
195-
val indent = "\t" * (depth + 1)
203+
val indent = " " * (depth + 1)
196204
s"""|Assign(
197205
|${indent}lhs= ${lhs.prettyPrint(depth + 1)},
198206
|${indent}rhs= ${rhs.prettyPrint(depth + 1)},

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

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source
113113
case NestedModule(_, CallInstanceMethod(_, _, _: Value)) => eval
114114
case _: StaticModule => eval
115115
case _: This => eval
116+
case _: Literal => eval
116117
case _ => tree
117118
}
118119
} else tree
@@ -128,9 +129,10 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source
128129

129130
private def validateLiteral(lit: Lit): Validation[RuntimeEvaluationTree] =
130131
classLoader.map { loader =>
131-
val value = loader.mirrorOfLiteral(lit.value)
132-
val tpe = if (lit.value == null) null else value.map(_.value.`type`).extract.get
133-
Value(value, tpe)
132+
val tpe =
133+
if (lit.value == null) null
134+
else loader.mirrorOfLiteral(lit.value).map(_.value.`type`).getResult.get
135+
preEvaluate(Literal(lit.value, tpe))
134136
}
135137

136138
private def findField(
@@ -412,11 +414,6 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source
412414
case _ => Recoverable(s"$tpe is not a reference type")
413415
}
414416

415-
private def fromLitToValue(literal: Lit, classLoader: JdiClassLoader): (Safe[Any], jdi.Type) = {
416-
val tpe = classLoader.mirrorOfLiteral(literal.value).map(_.value.`type`).getResult.get
417-
(Safe(literal.value), tpe)
418-
}
419-
420417
private def moreSpecificThan(m1: jdi.Method, m2: jdi.Method): Boolean = {
421418
m1.argumentTypes()
422419
.asScala

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,8 @@ class Safe[+A] private (
7171
}
7272

7373
object Safe {
74-
def apply[A](f: => A): Safe[A] = {
75-
val result = Try(f)
76-
apply(result)
77-
}
74+
def apply[A](f: => A): Safe[A] =
75+
apply(Try(f))
7876

7977
def apply[A](a: Try[A]): Safe[A] = {
8078
a match {

modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/TestingDebugClient.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ class TestingDebugClient(socket: Socket, logger: Logger)(implicit
192192
)
193193
}
194194

195-
def redefineClasses(timeout: Duration = defaultTimeout(1.second)): Either[String, Array[String]] = {
195+
def redefineClasses(timeout: Duration = defaultTimeout(2.seconds)): Either[String, Array[String]] = {
196196
val args = new RedefineClassesArguments()
197197
val request = createRequest(Command.REDEFINECLASSES, args)
198198
val response = Await.result(sendRequest(request), timeout)

modules/tests/src/test/scala/ch/epfl/scala/debugadapter/SourceBreakpointTests.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class Scala3SourceBreakpointTests extends SourceBreakpointTests(ScalaVersion.`3.
5050
}
5151

5252
abstract class SourceBreakpointTests(val scalaVersion: ScalaVersion) extends DebugTestSuite {
53-
test("evaluate simple breakpoint") {
53+
test("simple conditional breakpoint") {
5454
val source =
5555
"""|package example
5656
|object Main {
@@ -64,7 +64,7 @@ abstract class SourceBreakpointTests(val scalaVersion: ScalaVersion) extends Deb
6464
check(Breakpoint(5, "x == 42"), Evaluation.success("x", 42))
6565
}
6666

67-
test("evaluate breakpoint in lambda") {
67+
test("conditional breakpoint in lambda") {
6868
val source =
6969
"""|package example
7070
|object Main {

0 commit comments

Comments
 (0)