Skip to content

Commit

Permalink
Merge pull request #417 from adpi2/refactor-expression-eval
Browse files Browse the repository at this point in the history
Refactor JDI wrappers
  • Loading branch information
adpi2 committed May 3, 2023
2 parents 674dde7 + 8d0ca5c commit c689a95
Show file tree
Hide file tree
Showing 16 changed files with 387 additions and 349 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import ch.epfl.scala.debugadapter.Logger
import ch.epfl.scala.debugadapter.ManagedEntry
import ch.epfl.scala.debugadapter.UnmanagedEntry
import ch.epfl.scala.debugadapter.internal.evaluator.CompiledExpression
import ch.epfl.scala.debugadapter.internal.evaluator.FrameReference
import ch.epfl.scala.debugadapter.internal.evaluator.JdiFrame
import ch.epfl.scala.debugadapter.internal.evaluator.JdiObject
import ch.epfl.scala.debugadapter.internal.evaluator.JdiValue
import ch.epfl.scala.debugadapter.internal.evaluator.LocalValue
import ch.epfl.scala.debugadapter.internal.evaluator.MessageLogger
import ch.epfl.scala.debugadapter.internal.evaluator.MethodInvocationFailed
Expand Down Expand Up @@ -53,7 +54,7 @@ private[internal] class EvaluationProvider(
override def isInEvaluation(thread: ThreadReference) = isEvaluating.get

override def evaluate(expression: String, thread: ThreadReference, depth: Int): CompletableFuture[Value] = {
val frame = FrameReference(thread, depth)
val frame = JdiFrame(thread, depth)
val evaluation = for {
preparedExpression <- prepare(expression, frame)
evaluation <- evaluate(preparedExpression, frame)
Expand All @@ -71,7 +72,7 @@ private[internal] class EvaluationProvider(
breakpoint: IEvaluatableBreakpoint,
thread: ThreadReference
): CompletableFuture[Value] = {
val frame = FrameReference(thread, 0)
val frame = JdiFrame(thread, 0)
val location = frame.current().location
val locationCode = (location.method.name, location.codeIndex).hashCode
val expression =
Expand Down Expand Up @@ -100,14 +101,16 @@ private[internal] class EvaluationProvider(
thread: ThreadReference,
invokeSuper: Boolean
): CompletableFuture[Value] = {
val obj = new JdiObject(thisContext, thread)
val obj = JdiObject(thisContext, thread)
val wrappedArgs = if (args == null) Seq.empty else args.toSeq.map(JdiValue(_, thread))
val invocation = evaluationBlock {
obj
.invoke(methodName, methodSignature, if (args == null) List() else args.toList)
.invoke(methodName, methodSignature, wrappedArgs)
.recover {
// if invocation throws an exception, we return that exception as the result
case MethodInvocationFailed(msg, exception) => exception
}
.map(_.value)
}
completeFuture(invocation.getResult, thread)
}
Expand All @@ -132,7 +135,7 @@ private[internal] class EvaluationProvider(
case _ => s"Unsupported evaluation in ${entry.name}"
}

private def prepareLogMessage(message: String, frame: FrameReference): Try[PreparedExpression] = {
private def prepareLogMessage(message: String, frame: JdiFrame): Try[PreparedExpression] = {
if (!message.contains("$")) {
Success(PlainLogMessage(message))
} else {
Expand All @@ -142,7 +145,7 @@ private[internal] class EvaluationProvider(
}
}

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

private def evaluate(expression: PreparedExpression, frame: FrameReference): Try[Value] = {
private def evaluate(expression: PreparedExpression, frame: JdiFrame): Try[Value] = {
expression match {
case logMessage: PlainLogMessage => messageLogger.log(logMessage, frame)
case localValue: LocalValue => simpleEvaluator.evaluate(localValue, frame)
Expand Down

This file was deleted.

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)
}
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)
}
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)
}

This file was deleted.

0 comments on commit c689a95

Please sign in to comment.