Skip to content

Commit 95bd38e

Browse files
committed
Refactor JDI wrappers
1 parent fe99771 commit 95bd38e

16 files changed

+376
-345
lines changed

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

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ import ch.epfl.scala.debugadapter.Logger
1111
import ch.epfl.scala.debugadapter.ManagedEntry
1212
import ch.epfl.scala.debugadapter.UnmanagedEntry
1313
import ch.epfl.scala.debugadapter.internal.evaluator.CompiledExpression
14-
import ch.epfl.scala.debugadapter.internal.evaluator.FrameReference
14+
import ch.epfl.scala.debugadapter.internal.evaluator.JdiFrame
1515
import ch.epfl.scala.debugadapter.internal.evaluator.JdiObject
16+
import ch.epfl.scala.debugadapter.internal.evaluator.JdiValue
1617
import ch.epfl.scala.debugadapter.internal.evaluator.LocalValue
1718
import ch.epfl.scala.debugadapter.internal.evaluator.MessageLogger
1819
import ch.epfl.scala.debugadapter.internal.evaluator.MethodInvocationFailed
@@ -53,7 +54,7 @@ private[internal] class EvaluationProvider(
5354
override def isInEvaluation(thread: ThreadReference) = isEvaluating.get
5455

5556
override def evaluate(expression: String, thread: ThreadReference, depth: Int): CompletableFuture[Value] = {
56-
val frame = FrameReference(thread, depth)
57+
val frame = JdiFrame(thread, depth)
5758
val evaluation = for {
5859
preparedExpression <- prepare(expression, frame)
5960
evaluation <- evaluate(preparedExpression, frame)
@@ -71,7 +72,7 @@ private[internal] class EvaluationProvider(
7172
breakpoint: IEvaluatableBreakpoint,
7273
thread: ThreadReference
7374
): CompletableFuture[Value] = {
74-
val frame = FrameReference(thread, 0)
75+
val frame = JdiFrame(thread, 0)
7576
val location = frame.current().location
7677
val locationCode = (location.method.name, location.codeIndex).hashCode
7778
val expression =
@@ -96,18 +97,20 @@ private[internal] class EvaluationProvider(
9697
thisContext: ObjectReference,
9798
methodName: String,
9899
methodSignature: String,
99-
args: Array[Value],
100+
rawArgs: Array[Value],
100101
thread: ThreadReference,
101102
invokeSuper: Boolean
102103
): CompletableFuture[Value] = {
103-
val obj = new JdiObject(thisContext, thread)
104+
val obj = JdiObject(thisContext, thread)
105+
val args = if (rawArgs == null) Seq.empty else rawArgs.toSeq.map(JdiValue(_, thread))
104106
val invocation = evaluationBlock {
105107
obj
106-
.invoke(methodName, methodSignature, if (args == null) List() else args.toList)
108+
.invoke(methodName, methodSignature, args)
107109
.recover {
108110
// if invocation throws an exception, we return that exception as the result
109111
case MethodInvocationFailed(msg, exception) => exception
110112
}
113+
.map(_.value)
111114
}
112115
completeFuture(invocation.getResult, thread)
113116
}
@@ -132,7 +135,7 @@ private[internal] class EvaluationProvider(
132135
case _ => s"Unsupported evaluation in ${entry.name}"
133136
}
134137

135-
private def prepareLogMessage(message: String, frame: FrameReference): Try[PreparedExpression] = {
138+
private def prepareLogMessage(message: String, frame: JdiFrame): Try[PreparedExpression] = {
136139
if (!message.contains("$")) {
137140
Success(PlainLogMessage(message))
138141
} else {
@@ -142,7 +145,7 @@ private[internal] class EvaluationProvider(
142145
}
143146
}
144147

145-
private def prepare(expression: String, frame: FrameReference): Try[PreparedExpression] = {
148+
private def prepare(expression: String, frame: JdiFrame): Try[PreparedExpression] = {
146149
lazy val simpleExpression = simpleEvaluator.prepare(expression, frame)
147150
if (mode.allowSimpleEvaluation && simpleExpression.isDefined) {
148151
Success(simpleExpression.get)
@@ -160,7 +163,7 @@ private[internal] class EvaluationProvider(
160163
}
161164
}
162165

163-
private def evaluate(expression: PreparedExpression, frame: FrameReference): Try[Value] = {
166+
private def evaluate(expression: PreparedExpression, frame: JdiFrame): Try[Value] = {
164167
expression match {
165168
case logMessage: PlainLogMessage => messageLogger.log(logMessage, frame)
166169
case localValue: LocalValue => simpleEvaluator.evaluate(localValue, frame)

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

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,21 @@
11
package ch.epfl.scala.debugadapter.internal.evaluator
22

3-
import com.sun.jdi.{ArrayReference, ThreadReference, Value}
3+
import com.sun.jdi.ArrayReference
4+
import com.sun.jdi.ThreadReference
5+
import com.sun.jdi.Value
46

57
import scala.jdk.CollectionConverters.*
68

7-
object 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] = {
12-
val thread = classLoader.thread
13-
for {
14-
classClass <- classLoader.loadClass("java.lang.Class")
15-
intValue <- classLoader.mirrorOf("int")
16-
intClass <-
17-
classClass.invoke("getPrimitiveClass", List(intValue))
18-
arrayClass <- classLoader.loadClass("java.lang.reflect.Array")
19-
newInstanceValue <- classLoader.mirrorOf("newInstance")
20-
newInstanceMethod <- arrayClass
21-
.invoke("getMethod", List(newInstanceValue, classClass.reference, intClass))
22-
.map(JdiObject(_, thread))
23-
arrayTypeClass <- classLoader.loadClass(arrayType)
24-
integerRef <- JdiPrimitive.box(arraySize, classLoader, thread)
25-
array <- newInstanceMethod
26-
.invoke("invoke", List(null, arrayTypeClass.reference, integerRef))
27-
.map(JdiArray(_, thread))
28-
} yield array
29-
}
30-
}
31-
329
class JdiArray(reference: ArrayReference, thread: ThreadReference) extends JdiObject(reference, thread) {
3310
def setValue(index: Int, value: Value): Unit =
3411
reference.setValue(index, value)
3512

36-
def setValues(values: Seq[Value]): Unit = reference.setValues(values.asJava)
13+
def setValues(values: Seq[JdiValue]): Unit = reference.setValues(values.map(_.value).asJava)
3714

38-
def getValues: Seq[Value] = reference.getValues.asScala.toSeq
15+
def getValues: Seq[JdiValue] = reference.getValues.asScala.toSeq.map(JdiValue(_, thread))
16+
}
17+
18+
object JdiArray {
19+
def apply(value: Value, thread: ThreadReference): JdiArray =
20+
new JdiArray(value.asInstanceOf[ArrayReference], thread)
3921
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package ch.epfl.scala.debugadapter.internal.evaluator
2+
3+
import com.sun.jdi._
4+
5+
import scala.collection.JavaConverters.*
6+
import scala.util.control.NonFatal
7+
8+
private[internal] class JdiClass(
9+
cls: ClassType,
10+
thread: ThreadReference
11+
) extends JdiObject(cls.classObject, thread) {
12+
13+
def initialized: Boolean = cls.isInitialized
14+
def className: String = cls.name
15+
override def classLoader: JdiClassLoader = JdiClassLoader(cls.classLoader, thread)
16+
17+
def newInstance(args: Seq[JdiValue]): Safe[JdiObject] = {
18+
val ctr = cls.methodsByName("<init>").asScala.head
19+
newInstance(ctr, args)
20+
}
21+
22+
def newInstance(signature: String, args: Seq[JdiValue]): Safe[JdiObject] = {
23+
val ctr = cls.methodsByName("<init>", signature).asScala.head
24+
newInstance(ctr, args)
25+
}
26+
27+
private def newInstance(ctr: Method, args: Seq[JdiValue]): Safe[JdiObject] =
28+
for {
29+
_ <- prepareMethod(ctr)
30+
instance <- Safe(cls.newInstance(thread, ctr, args.map(_.value).asJava, ObjectReference.INVOKE_SINGLE_THREADED))
31+
.recoverWith(recoverInvocationException(thread))
32+
} yield JdiObject(instance, thread)
33+
34+
// Load the argument types of the method to avoid ClassNotLoadedException
35+
// TODO Should we use this method before all invocations: methods, ctrs, fields
36+
private def prepareMethod(method: Method): Safe[Unit] = {
37+
def loadArgumentsRecursively(): Safe[Unit] = {
38+
try {
39+
method.argumentTypes()
40+
Safe(())
41+
} catch {
42+
case exception: ClassNotLoadedException =>
43+
val className = exception.className.stripSuffix("[]").replace('/', '.')
44+
classLoader.loadClass(className).flatMap(_ => loadArgumentsRecursively())
45+
case NonFatal(cause) => Safe(throw cause)
46+
}
47+
}
48+
loadArgumentsRecursively()
49+
}
50+
51+
def getStaticField(fieldName: String): Safe[JdiValue] =
52+
Safe(cls.getValue(cls.fieldByName(fieldName))).map(JdiValue(_, thread))
53+
54+
def invokeStatic(methodName: String, args: Seq[JdiValue]): Safe[JdiValue] = {
55+
cls.methodsByName(methodName).forEach(m => println(m.signature))
56+
val method = cls.methodsByName(methodName).asScala.head
57+
invokeStatic(method, args)
58+
}
59+
60+
def invokeStatic(methodName: String, signature: String, args: Seq[JdiValue]): Safe[JdiValue] = {
61+
val method = cls.methodsByName(methodName, signature).asScala.head
62+
invokeStatic(method, args)
63+
}
64+
65+
protected def invokeStatic(method: Method, args: Seq[JdiValue]): Safe[JdiValue] =
66+
Safe(cls.invokeMethod(thread, method, args.map(_.value).asJava, ObjectReference.INVOKE_SINGLE_THREADED))
67+
.map(JdiValue(_, thread))
68+
.recoverWith(recoverInvocationException(thread))
69+
}
70+
71+
object JdiClass {
72+
def apply(classType: ReferenceType, thread: ThreadReference): JdiClass =
73+
new JdiClass(classType.asInstanceOf[ClassType], thread)
74+
75+
def apply(classObject: ClassObjectReference, thread: ThreadReference): JdiClass =
76+
new JdiClass(classObject.reflectedType.asInstanceOf[ClassType], thread)
77+
}
Lines changed: 82 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,95 @@
11
package ch.epfl.scala.debugadapter.internal.evaluator
22

33
import com.sun.jdi._
4-
import scala.collection.JavaConverters.*
54

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-
}
20-
21-
def apply(
22-
classLoader: ClassLoaderReference,
23-
thread: ThreadReference
24-
): JdiClassLoader = {
25-
val classLoaderType = classLoader.referenceType
26-
val loadClassMethod = method(
27-
"loadClass",
28-
"(Ljava/lang/String;)Ljava/lang/Class;",
29-
classLoaderType
30-
)
31-
new JdiClassLoader(classLoader, loadClassMethod, thread)
32-
}
33-
}
5+
import java.nio.file.Path
346

35-
private[internal] case class JdiClassLoader(
36-
classLoaderRef: ClassLoaderReference,
37-
loadClassMethod: Method,
7+
private[internal] class JdiClassLoader(
8+
reference: ClassLoaderReference,
389
thread: ThreadReference
39-
) {
10+
) extends JdiObject(reference, thread) {
4011

41-
def loadClass(name: String): Safe[JdiClassObject] = {
12+
private def loadClassClass: Safe[JdiClass] =
4213
for {
43-
nameValue <- mirrorOf(name)
44-
classObject <- invokeMethod(
45-
classLoaderRef,
46-
loadClassMethod,
47-
List(nameValue),
48-
thread
14+
classClassName <- mirrorOf("java.lang.Class")
15+
classClass <- invoke("loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", Seq(classClassName))
16+
} yield classClass.asClass
17+
18+
def loadClass(className: String): Safe[JdiClass] =
19+
for {
20+
classNameValue <- mirrorOf(className)
21+
classClass <- loadClassClass
22+
classObject <- classClass.invokeStatic(
23+
"forName",
24+
"(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;",
25+
Seq(classNameValue, mirrorOf(true), this)
4926
)
27+
} yield classObject.asClass
28+
29+
def mirrorOf(str: String): Safe[JdiString] =
30+
Safe(thread.virtualMachine.mirrorOf(str)).map(new JdiString(_, thread))
31+
32+
def mirrorOf(boolean: Boolean): JdiValue =
33+
new JdiValue(thread.virtualMachine.mirrorOf(boolean), thread)
34+
35+
def mirrorOf(integer: Int): JdiValue =
36+
new JdiValue(thread.virtualMachine.mirrorOf(integer), thread)
37+
38+
def boxIfPrimitive(value: JdiValue): Safe[JdiValue] =
39+
value.value match {
40+
case value: BooleanValue => box(value.value)
41+
case value: CharValue => box(value.value)
42+
case value: DoubleValue => box(value.value)
43+
case value: FloatValue => box(value.value)
44+
case value: IntegerValue => box(value.value)
45+
case value: LongValue => box(value.value)
46+
case value: ShortValue => box(value.value)
47+
case value => Safe(JdiValue(value, thread))
48+
}
49+
50+
def box(value: AnyVal): Safe[JdiObject] =
51+
for {
52+
jdiValue <- mirrorOf(value.toString)
53+
_ = getClass
54+
(className, sig) = value match {
55+
case _: Boolean => ("java.lang.Boolean", "(Ljava/lang/String;)Ljava/lang/Boolean;")
56+
case _: Byte => ("java.lang.Byte", "(Ljava/lang/String;)Ljava/lang/Byte;")
57+
case _: Char => ("java.lang.Character", "(Ljava/lang/String;)Ljava/lang/Character;")
58+
case _: Double => ("java.lang.Double", "(Ljava/lang/String;)Ljava/lang/Double;")
59+
case _: Float => ("java.lang.Float", "(Ljava/lang/String;)Ljava/lang/Float;")
60+
case _: Int => ("java.lang.Integer", "(Ljava/lang/String;)Ljava/lang/Integer;")
61+
case _: Long => ("java.lang.Long", "(Ljava/lang/String;)Ljava/lang/Long;")
62+
case _: Short => ("java.lang.Short", "(Ljava/lang/String;)Ljava/lang/Short;")
63+
}
64+
clazz <- loadClass(className)
65+
objectRef <- clazz.invokeStatic("valueOf", sig, List(jdiValue))
66+
} yield objectRef.asObject
67+
68+
def createArray(arrayType: String, values: Seq[JdiValue]): Safe[JdiArray] =
69+
for {
70+
arrayTypeClass <- loadClass(arrayType)
71+
arrayClass <- loadClass("java.lang.reflect.Array")
72+
size = mirrorOf(values.size)
73+
array <- arrayClass
74+
.invokeStatic("newInstance", "(Ljava/lang/Class;I)Ljava/lang/Object;", Seq(arrayTypeClass, size))
75+
.map(_.asArray)
5076
} yield {
51-
new JdiClassObject(
52-
classObject.asInstanceOf[ClassObjectReference],
53-
this,
54-
thread
55-
)
77+
array.setValues(values)
78+
array
5679
}
57-
}
5880

59-
def mirrorOf(str: String): Safe[StringReference] = {
60-
Safe(thread.virtualMachine.mirrorOf(str))
61-
}
81+
def createChildLoader(classPathEntry: Path): Safe[JdiClassLoader] =
82+
for {
83+
classPathValue <- mirrorOf(classPathEntry.toUri.toString)
84+
urlClass <- loadClass("java.net.URL")
85+
url <- urlClass.newInstance("(Ljava/lang/String;)V", List(classPathValue))
86+
urls <- createArray("java.net.URL", Seq(url))
87+
classOfUrlClassLoader <- loadClass("java.net.URLClassLoader")
88+
urlClassLoader <- classOfUrlClassLoader.newInstance("([Ljava/net/URL;Ljava/lang/ClassLoader;)V", List(urls, this))
89+
} yield urlClassLoader.asClassLoader
90+
}
91+
92+
private[internal] object JdiClassLoader {
93+
def apply(ref: ClassLoaderReference, thread: ThreadReference): JdiClassLoader =
94+
new JdiClassLoader(ref, thread)
6295
}

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

Lines changed: 0 additions & 36 deletions
This file was deleted.

0 commit comments

Comments
 (0)