-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #417 from adpi2/refactor-expression-eval
Refactor JDI wrappers
- Loading branch information
Showing
16 changed files
with
387 additions
and
349 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
8 changes: 0 additions & 8 deletions
8
...es/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/FrameReference.scala
This file was deleted.
Oops, something went wrong.
42 changes: 12 additions & 30 deletions
42
modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiArray.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,21 @@ | ||
package ch.epfl.scala.debugadapter.internal.evaluator | ||
|
||
import com.sun.jdi.{ArrayReference, ThreadReference, Value} | ||
import com.sun.jdi.ArrayReference | ||
import com.sun.jdi.ThreadReference | ||
import com.sun.jdi.Value | ||
|
||
import scala.jdk.CollectionConverters.* | ||
|
||
object JdiArray { | ||
def apply(value: Value, thread: ThreadReference): JdiArray = | ||
new JdiArray(value.asInstanceOf[ArrayReference], thread) | ||
|
||
def apply(arrayType: String, arraySize: Int, classLoader: JdiClassLoader): Safe[JdiArray] = { | ||
val thread = classLoader.thread | ||
for { | ||
classClass <- classLoader.loadClass("java.lang.Class") | ||
intValue <- classLoader.mirrorOf("int") | ||
intClass <- | ||
classClass.invoke("getPrimitiveClass", List(intValue)) | ||
arrayClass <- classLoader.loadClass("java.lang.reflect.Array") | ||
newInstanceValue <- classLoader.mirrorOf("newInstance") | ||
newInstanceMethod <- arrayClass | ||
.invoke("getMethod", List(newInstanceValue, classClass.reference, intClass)) | ||
.map(JdiObject(_, thread)) | ||
arrayTypeClass <- classLoader.loadClass(arrayType) | ||
integerRef <- JdiPrimitive.box(arraySize, classLoader, thread) | ||
array <- newInstanceMethod | ||
.invoke("invoke", List(null, arrayTypeClass.reference, integerRef)) | ||
.map(JdiArray(_, thread)) | ||
} yield array | ||
} | ||
} | ||
|
||
class JdiArray(reference: ArrayReference, thread: ThreadReference) extends JdiObject(reference, thread) { | ||
class JdiArray(arrayRef: ArrayReference, thread: ThreadReference) extends JdiObject(arrayRef, thread) { | ||
def setValue(index: Int, value: Value): Unit = | ||
reference.setValue(index, value) | ||
arrayRef.setValue(index, value) | ||
|
||
def setValues(values: Seq[Value]): Unit = reference.setValues(values.asJava) | ||
def setValues(values: Seq[JdiValue]): Unit = arrayRef.setValues(values.map(_.value).asJava) | ||
|
||
def getValues: Seq[Value] = reference.getValues.asScala.toSeq | ||
def getValues: Seq[JdiValue] = arrayRef.getValues.asScala.toSeq.map(JdiValue(_, thread)) | ||
} | ||
|
||
object JdiArray { | ||
def apply(arrayValue: Value, thread: ThreadReference): JdiArray = | ||
new JdiArray(arrayValue.asInstanceOf[ArrayReference], thread) | ||
} |
76 changes: 76 additions & 0 deletions
76
modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiClass.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package ch.epfl.scala.debugadapter.internal.evaluator | ||
|
||
import com.sun.jdi._ | ||
|
||
import scala.collection.JavaConverters.* | ||
import scala.util.control.NonFatal | ||
|
||
private[internal] class JdiClass( | ||
cls: ClassType, | ||
thread: ThreadReference | ||
) extends JdiObject(cls.classObject, thread) { | ||
|
||
def initialized: Boolean = cls.isInitialized | ||
def className: String = cls.name | ||
override def classLoader: JdiClassLoader = JdiClassLoader(cls.classLoader, thread) | ||
|
||
def newInstance(args: Seq[JdiValue]): Safe[JdiObject] = { | ||
val ctr = cls.methodsByName("<init>").asScala.head | ||
newInstance(ctr, args) | ||
} | ||
|
||
def newInstance(signature: String, args: Seq[JdiValue]): Safe[JdiObject] = { | ||
val ctr = cls.methodsByName("<init>", signature).asScala.head | ||
newInstance(ctr, args) | ||
} | ||
|
||
private def newInstance(ctr: Method, args: Seq[JdiValue]): Safe[JdiObject] = | ||
for { | ||
_ <- prepareMethod(ctr) | ||
instance <- Safe(cls.newInstance(thread, ctr, args.map(_.value).asJava, ObjectReference.INVOKE_SINGLE_THREADED)) | ||
.recoverWith(recoverInvocationException(thread)) | ||
} yield JdiObject(instance, thread) | ||
|
||
// Load the argument types of the method to avoid ClassNotLoadedException | ||
// TODO Should we use this method before all invocations: methods, ctrs, fields | ||
private def prepareMethod(method: Method): Safe[Unit] = { | ||
def loadArgumentsRecursively(): Safe[Unit] = { | ||
try { | ||
method.argumentTypes() | ||
Safe(()) | ||
} catch { | ||
case exception: ClassNotLoadedException => | ||
val className = exception.className.stripSuffix("[]").replace('/', '.') | ||
classLoader.loadClass(className).flatMap(_ => loadArgumentsRecursively()) | ||
case NonFatal(cause) => Safe(throw cause) | ||
} | ||
} | ||
loadArgumentsRecursively() | ||
} | ||
|
||
def getStaticField(fieldName: String): Safe[JdiValue] = | ||
Safe(cls.getValue(cls.fieldByName(fieldName))).map(JdiValue(_, thread)) | ||
|
||
def invokeStatic(methodName: String, args: Seq[JdiValue]): Safe[JdiValue] = { | ||
val method = cls.methodsByName(methodName).asScala.head | ||
invokeStatic(method, args) | ||
} | ||
|
||
def invokeStatic(methodName: String, signature: String, args: Seq[JdiValue]): Safe[JdiValue] = { | ||
val method = cls.methodsByName(methodName, signature).asScala.head | ||
invokeStatic(method, args) | ||
} | ||
|
||
private def invokeStatic(method: Method, args: Seq[JdiValue]): Safe[JdiValue] = | ||
Safe(cls.invokeMethod(thread, method, args.map(_.value).asJava, ObjectReference.INVOKE_SINGLE_THREADED)) | ||
.map(JdiValue(_, thread)) | ||
.recoverWith(recoverInvocationException(thread)) | ||
} | ||
|
||
object JdiClass { | ||
def apply(classType: Type, thread: ThreadReference): JdiClass = | ||
new JdiClass(classType.asInstanceOf[ClassType], thread) | ||
|
||
def apply(classObject: Value, thread: ThreadReference): JdiClass = | ||
JdiClass(classObject.asInstanceOf[ClassObjectReference].reflectedType, thread) | ||
} |
131 changes: 82 additions & 49 deletions
131
...es/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiClassLoader.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,95 @@ | ||
package ch.epfl.scala.debugadapter.internal.evaluator | ||
|
||
import com.sun.jdi._ | ||
import scala.collection.JavaConverters.* | ||
|
||
private[internal] object JdiClassLoader { | ||
def fromFrame(frame: FrameReference): Safe[JdiClassLoader] = Safe { | ||
val scalaLibClassLoader = | ||
for { | ||
scalaLibClass <- frame.thread.virtualMachine.allClasses.asScala | ||
.find(c => c.name.startsWith("scala.runtime")) | ||
classLoader <- Option(scalaLibClass.classLoader) | ||
} yield classLoader | ||
|
||
val classLoader = Option(frame.current().location.method.declaringType.classLoader) | ||
.orElse(scalaLibClassLoader) | ||
.getOrElse(throw new Exception("Cannot find the classloader of the Scala library")) | ||
JdiClassLoader(classLoader, frame.thread) | ||
} | ||
|
||
def apply( | ||
classLoader: ClassLoaderReference, | ||
thread: ThreadReference | ||
): JdiClassLoader = { | ||
val classLoaderType = classLoader.referenceType | ||
val loadClassMethod = method( | ||
"loadClass", | ||
"(Ljava/lang/String;)Ljava/lang/Class;", | ||
classLoaderType | ||
) | ||
new JdiClassLoader(classLoader, loadClassMethod, thread) | ||
} | ||
} | ||
import java.nio.file.Path | ||
|
||
private[internal] case class JdiClassLoader( | ||
classLoaderRef: ClassLoaderReference, | ||
loadClassMethod: Method, | ||
private[internal] class JdiClassLoader( | ||
reference: ClassLoaderReference, | ||
thread: ThreadReference | ||
) { | ||
) extends JdiObject(reference, thread) { | ||
|
||
def loadClass(name: String): Safe[JdiClassObject] = { | ||
private def loadClassClass: Safe[JdiClass] = | ||
for { | ||
nameValue <- mirrorOf(name) | ||
classObject <- invokeMethod( | ||
classLoaderRef, | ||
loadClassMethod, | ||
List(nameValue), | ||
thread | ||
classClassName <- mirrorOf("java.lang.Class") | ||
classClass <- invoke("loadClass", "(Ljava/lang/String;)Ljava/lang/Class;", Seq(classClassName)) | ||
} yield classClass.asClass | ||
|
||
def loadClass(className: String): Safe[JdiClass] = | ||
for { | ||
classNameValue <- mirrorOf(className) | ||
classClass <- loadClassClass | ||
classObject <- classClass.invokeStatic( | ||
"forName", | ||
"(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;", | ||
Seq(classNameValue, mirrorOf(true), this) | ||
) | ||
} yield classObject.asClass | ||
|
||
def mirrorOf(str: String): Safe[JdiString] = | ||
Safe(thread.virtualMachine.mirrorOf(str)).map(new JdiString(_, thread)) | ||
|
||
def mirrorOf(boolean: Boolean): JdiValue = | ||
new JdiValue(thread.virtualMachine.mirrorOf(boolean), thread) | ||
|
||
def mirrorOf(integer: Int): JdiValue = | ||
new JdiValue(thread.virtualMachine.mirrorOf(integer), thread) | ||
|
||
def boxIfPrimitive(value: JdiValue): Safe[JdiValue] = | ||
value.value match { | ||
case value: BooleanValue => box(value.value) | ||
case value: CharValue => box(value.value) | ||
case value: DoubleValue => box(value.value) | ||
case value: FloatValue => box(value.value) | ||
case value: IntegerValue => box(value.value) | ||
case value: LongValue => box(value.value) | ||
case value: ShortValue => box(value.value) | ||
case value => Safe(JdiValue(value, thread)) | ||
} | ||
|
||
def box(value: AnyVal): Safe[JdiObject] = | ||
for { | ||
jdiValue <- mirrorOf(value.toString) | ||
_ = getClass | ||
(className, sig) = value match { | ||
case _: Boolean => ("java.lang.Boolean", "(Ljava/lang/String;)Ljava/lang/Boolean;") | ||
case _: Byte => ("java.lang.Byte", "(Ljava/lang/String;)Ljava/lang/Byte;") | ||
case _: Char => ("java.lang.Character", "(Ljava/lang/String;)Ljava/lang/Character;") | ||
case _: Double => ("java.lang.Double", "(Ljava/lang/String;)Ljava/lang/Double;") | ||
case _: Float => ("java.lang.Float", "(Ljava/lang/String;)Ljava/lang/Float;") | ||
case _: Int => ("java.lang.Integer", "(Ljava/lang/String;)Ljava/lang/Integer;") | ||
case _: Long => ("java.lang.Long", "(Ljava/lang/String;)Ljava/lang/Long;") | ||
case _: Short => ("java.lang.Short", "(Ljava/lang/String;)Ljava/lang/Short;") | ||
} | ||
clazz <- loadClass(className) | ||
objectRef <- clazz.invokeStatic("valueOf", sig, List(jdiValue)) | ||
} yield objectRef.asObject | ||
|
||
def createArray(arrayType: String, values: Seq[JdiValue]): Safe[JdiArray] = | ||
for { | ||
arrayTypeClass <- loadClass(arrayType) | ||
arrayClass <- loadClass("java.lang.reflect.Array") | ||
size = mirrorOf(values.size) | ||
array <- arrayClass | ||
.invokeStatic("newInstance", "(Ljava/lang/Class;I)Ljava/lang/Object;", Seq(arrayTypeClass, size)) | ||
.map(_.asArray) | ||
} yield { | ||
new JdiClassObject( | ||
classObject.asInstanceOf[ClassObjectReference], | ||
this, | ||
thread | ||
) | ||
array.setValues(values) | ||
array | ||
} | ||
} | ||
|
||
def mirrorOf(str: String): Safe[StringReference] = { | ||
Safe(thread.virtualMachine.mirrorOf(str)) | ||
} | ||
def createChildLoader(classPathEntry: Path): Safe[JdiClassLoader] = | ||
for { | ||
classPathValue <- mirrorOf(classPathEntry.toUri.toString) | ||
urlClass <- loadClass("java.net.URL") | ||
url <- urlClass.newInstance("(Ljava/lang/String;)V", List(classPathValue)) | ||
urls <- createArray("java.net.URL", Seq(url)) | ||
classOfUrlClassLoader <- loadClass("java.net.URLClassLoader") | ||
urlClassLoader <- classOfUrlClassLoader.newInstance("([Ljava/net/URL;Ljava/lang/ClassLoader;)V", List(urls, this)) | ||
} yield urlClassLoader.asClassLoader | ||
} | ||
|
||
private[internal] object JdiClassLoader { | ||
def apply(ref: Value, thread: ThreadReference): JdiClassLoader = | ||
new JdiClassLoader(ref.asInstanceOf[ClassLoaderReference], thread) | ||
} |
36 changes: 0 additions & 36 deletions
36
...es/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiClassObject.scala
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.