Skip to content

Commit

Permalink
feat: outer fields/methods evaluation
Browse files Browse the repository at this point in the history
  • Loading branch information
iusildra committed May 19, 2023
1 parent 09fd135 commit 3891fb5
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,13 @@ case class RuntimeEvaluator(
case primitive: PrimitiveUnaryOpTree => invokePrimitive(primitive)
case module: ModuleTree => evaluateModule(module, module.of.map(evaluate))
case literal: LiteralTree => evaluateLiteral(literal)
case ThisTree(obj) => Safe.successful(JdiValue(obj.instances(1).get(0), frame.thread))
case ThisTree(obj) => Safe(JdiValue(obj.instances(1).get(0), frame.thread))
case field: InstanceFieldTree => evaluateField(field)
case staticField: StaticFieldTree => evaluateStaticField(staticField)
case instance: NewInstanceTree => instantiate(instance)
case method: InstanceMethodTree => invoke(method)
case staticMethod: StaticMethodTree => invokeStatic(staticMethod)
case outer: OuterTree => evaluateOuter(outer)
}

/* -------------------------------------------------------------------------- */
Expand All @@ -72,6 +73,20 @@ case class RuntimeEvaluator(
result <- loader.mirrorOfLiteral(value)
} yield result

/* -------------------------------------------------------------------------- */
/* Outer evaluation */
/* -------------------------------------------------------------------------- */
private def evaluateOuter(tree: OuterTree): Safe[JdiValue] =
evaluate(tree.inner).flatMap { innerValue =>
tree match {
case OuterModuleTree(_, module) =>
evaluateModule(module, Some(Safe(innerValue)))
case _: OuterClassTree =>
Safe(innerValue.asObject.getField("$outer"))
}

}

/* -------------------------------------------------------------------------- */
/* Field evaluation */
/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -235,9 +250,9 @@ case class RuntimeEvaluator(
for {
field <- Validation(ref.fieldByName(name))
_ = loadClassOnNeed(field, frame)
finalField <- field match {
finalField <- field.`type` match {
case Module(module) => ModuleTree(module, of.toOption)
case field => Valid(toStaticIfNeeded(field, of.get))
case _ => Valid(toStaticIfNeeded(field, of.get))
}
} yield finalField
}
Expand Down Expand Up @@ -271,9 +286,7 @@ case class RuntimeEvaluator(
private def validateModule(name: String, of: Option[RuntimeTree]): Validation[ModuleTree] = {
val moduleName = if (name.endsWith("$")) name else name + "$"
searchAllClassesFor(moduleName, of.map(_.`type`.name()), frame).flatMap { cls =>
val isInClass = frame
.classLoader()
.flatMap(_.loadClass(removeLastInnerTypeFromFQCN(cls.name())))
val isInClass = loadClass(removeLastInnerTypeFromFQCN(cls.name()), frame)
.withFilterNot { _.cls.methodsByName(moduleName.stripSuffix("$")).isEmpty() }

// TODO: understand why I can't merge n°1 and n°3
Expand Down Expand Up @@ -304,9 +317,6 @@ case class RuntimeEvaluator(
}
}

private def validateModuleOrClass(name: String, of: Option[RuntimeTree]): Validation[TypeTree] =
validateModule(name, of).orElse(validateClass(name, of))

private def validateName(
value: Term.Name,
of: Validation[RuntimeTree]
Expand All @@ -315,7 +325,12 @@ case class RuntimeEvaluator(
varTreeByName(name)
.orElse(fieldTreeByName(of, name))
.orElse(zeroArgMethodTreeByName(of, name))
.recoverWith(validateModule(name, thisTree.toOption))
.recoverWith(validateModule(name, of.toOption))
.recoverWith {
of
.flatMap(findOuter(_, frame))
.flatMap(outer => validateName(value, Valid(outer)))
}
}

/* -------------------------------------------------------------------------- */
Expand All @@ -327,7 +342,7 @@ case class RuntimeEvaluator(
args: Seq[RuntimeEvaluationTree]
): Validation[MethodTree] =
for {
module <- validateModuleOrClass(moduleName, Some(on))
module <- validateModule(moduleName, Some(on)).orElse(validateClass(moduleName, Some(on)))
applyCall <- methodsByNameAndArgs(module.`type`, "apply", args.map(_.`type`), frame)
} yield toStaticIfNeeded(applyCall, args, module)

Expand Down Expand Up @@ -356,15 +371,6 @@ case class RuntimeEvaluator(
} yield apply
}

// private def findInOuter(
// ref: ReferenceType,
// name: String,
// args: Seq[RuntimeValidationTree]
// ): Validation[MethodTree] =
// Option(ref.fieldByName("$outer"))
// }

// TODO: should look into upper classes as well
/**
* Returns a [[MethodTree]] representing the method called.
*
Expand All @@ -388,6 +394,7 @@ case class RuntimeEvaluator(
.map(toStaticIfNeeded(_, args, tree))
.orElse(validateApplyCall(name, tree, args))
.orElse(validateImplicitApplyCall(ref, tree, name, args))
.orElse(findOuter(tree, frame).flatMap(findMethod(_, name, args)))
case t => illegalAccess(t, "ReferenceType")
}

Expand Down Expand Up @@ -572,14 +579,37 @@ private object Helpers {
new ClassCastException(s"Cannot cast $x to $typeName")
}

/* -------------------------------------------------------------------------- */
/* Looking for $outer */
/* -------------------------------------------------------------------------- */
def findOuter(tree: RuntimeTree, frame: JdiFrame): Validation[OuterTree] = {
def outerLookup(ref: ReferenceType) = Validation(ref.fieldByName("$outer")).map(_.`type`()).orElse {
loadClass(removeLastInnerTypeFromFQCN(ref.name()) + "$", frame) match {
case Safe(Success(Module(mod: ClassType))) => Valid(mod)
case _ => Recoverable(s"Cannot find $$outer for $ref")
}
}

for {
ref <- ifReference(tree)
outer <- outerLookup(ref)
outerTree <- OuterTree(tree, outer)
} yield outerTree
}

/* -------------------------------------------------------------------------- */
/* Useful patterns */
/* -------------------------------------------------------------------------- */
/* Extract reference if there is */
def ifReference(tree: Validation[RuntimeTree]): Validation[ReferenceType] =
tree match {
case ReferenceTree(ref) => Valid(ref)
case Invalid(e) => Unrecoverable(s"Invalid reference: $e")
case _ => ifReference(tree.get)
}

def ifReference(tree: RuntimeTree): Validation[ReferenceType] =
tree match {
case ReferenceTree(ref) => Valid(ref)
case t => illegalAccess(t, "ReferenceType")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,12 @@ protected[internal] object RuntimeEvaluatorExtractors {
case _ => None
}

def unapply(field: Field): Option[ClassType] = unapply(field.`type`)
def unapply(cls: JdiClass): Option[ClassType] = unapply(cls.cls)

def unapply(tree: RuntimeTree): Option[ClassType] =
tree match {
case mt: ModuleTree => Some(mt.`type`)
case _: LiteralTree | _: LocalVarTree | _: NewInstanceTree | _: ClassTree | _: PrimitiveBinaryOpTree |
_: PrimitiveUnaryOpTree =>
None
case _: FieldTree | _: ThisTree | _: MethodTree => unapply(tree.`type`)
def unapply(tree: RuntimeTree): Option[ModuleTree] =
tree.`type` match {
case Module(cls) => Some(ModuleTree(cls, None))
case _ => None
}
}

Expand All @@ -54,6 +51,7 @@ protected[internal] object RuntimeEvaluatorExtractors {
case pbt: PrimitiveBinaryOpTree => Some(pbt)
case put: PrimitiveUnaryOpTree => Some(put)
case nit: NewInstanceTree => Some(nit)
case outer: OuterTree => Some(outer) // TODO: check
}

def unapply(tree: Option[RuntimeTree]): Option[RuntimeEvaluationTree] =
Expand All @@ -62,17 +60,18 @@ protected[internal] object RuntimeEvaluatorExtractors {
}

object MethodCall {
def unapply(tree: Validation[RuntimeTree]): Option[RuntimeTree] =
tree.toOption.filter {
_ match {
case mt: ModuleTree => mt.of.map(t => unapply(Valid(t))).isDefined
case ft: InstanceFieldTree => unapply(Valid(ft.qual)).isDefined
case _: MethodTree | _: NewInstanceTree => true
case _: LiteralTree | _: LocalVarTree | _: ThisTree | _: StaticFieldTree | _: ClassTree |
_: PrimitiveBinaryOpTree | _: PrimitiveUnaryOpTree =>
false
}
def unapply(tree: RuntimeTree): Option[RuntimeTree] =
tree match {
case mt: ModuleTree => mt.of.flatMap(t => unapply(t))
case ft: InstanceFieldTree => unapply(ft.qual)
case ot: OuterTree => unapply(ot.inner)
case _: MethodTree | _: NewInstanceTree => Some(tree)
case _: LiteralTree | _: LocalVarTree | _: ThisTree | _: StaticFieldTree | _: ClassTree |
_: PrimitiveBinaryOpTree | _: PrimitiveUnaryOpTree =>
None
}
def unapply(tree: Validation[RuntimeTree]): Option[RuntimeTree] =
tree.toOption.filter { unapply(_).isDefined }
}

object ReferenceTree {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ sealed trait RuntimeBinaryOp {
def typeCheck(lhs: Type, rhs: Type): Type
}

//TODO: supports + - ~
sealed trait RuntimeUnaryOp {
def evaluate(rhs: JdiValue, loader: JdiClassLoader): Safe[JdiValue]
def typeCheck(lhs: Type): Type
Expand Down Expand Up @@ -82,20 +81,12 @@ object RuntimeBinaryOp {
case (_, _, "==") => Valid(Eq)
case (_, _, "!=") => Valid(Neq)
case (NotPrimitive(), _, _) | (_, NotPrimitive(), _) =>
println("\u001b[31mnot primitive\u001b[0m")
Recoverable("Primitive operations don't support reference types")
case (left, right, "&&") =>
(left, right) match {
case (IsBoolean(), IsBoolean()) => Valid(And)
case _ => Recoverable("Boolean operations don't support non-boolean types")
}
case (left, right, "||") =>
(left, right) match {
case (IsBoolean(), IsBoolean()) => Valid(Or)
case _ => Recoverable("Boolean operations don't support non-boolean types")
}
case (IsBoolean(), IsBoolean(), "&&") => Valid(And)
case (IsBoolean(), IsBoolean(), "||") => Valid(Or)
case (_, _, "&&") | (_, _, "||") =>
Recoverable("Boolean operations don't support numeric types")
case (NotNumeric(), _, _) | (_, NotNumeric(), _) =>
println("\u001b[31mnot numeric\u001b[0m")
Recoverable("Numeric operations don't support boolean types")
case (_, _, "+") => Valid(Plus)
case (_, _, "-") => Valid(Minus)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package ch.epfl.scala.debugadapter.internal.evaluator

import com.sun.jdi._
import RuntimeEvaluatorExtractors.IsAnyVal
import RuntimeEvaluatorExtractors.{IsAnyVal, Module}
import scala.util.Success

/* -------------------------------------------------------------------------- */
Expand All @@ -15,10 +15,6 @@ sealed trait RuntimeTree {
sealed trait RuntimeValidationTree extends RuntimeTree
sealed trait RuntimeEvaluationTree extends RuntimeTree

sealed trait TypeTree extends RuntimeTree {
override def `type`: ClassType
}

sealed trait MethodTree extends RuntimeEvaluationTree {
def method: Method
}
Expand All @@ -27,6 +23,10 @@ sealed trait FieldTree extends RuntimeEvaluationTree {
def field: Field
}

sealed trait OuterTree extends RuntimeEvaluationTree {
def inner: RuntimeEvaluationTree
}

/* -------------------------------------------------------------------------- */
/* Simple trees */
/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -171,6 +171,39 @@ case class NewInstanceTree(method: Method, args: Seq[RuntimeEvaluationTree]) ext
}
}

case class OuterClassTree(
inner: RuntimeEvaluationTree,
`type`: ClassType
) extends OuterTree {
override def prettyPrint(depth: Int): String = {
val indent = "\t" * (depth + 1)
s"""|OuterClassTree(
|${indent}of= ${inner.prettyPrint(depth + 1)}
|${indent.dropRight(1)})""".stripMargin
}
}

case class OuterModuleTree(
inner: RuntimeEvaluationTree,
module: ModuleTree
) extends OuterTree {
override def `type`: ClassType = module.`type`
override def prettyPrint(depth: Int): String = {
val indent = "\t" * (depth + 1)
s"""|OuterModuleTree(
|${indent}of= ${inner.prettyPrint(depth + 1)}
|${indent.dropRight(1)})""".stripMargin
}
}

object OuterTree {
def apply(of: RuntimeTree, tpe: Type): Validation[OuterTree] = (of, tpe) match {
case (tree: RuntimeEvaluationTree, Module(module)) => Valid(new OuterModuleTree(tree, ModuleTree(module, None)))
case (tree: RuntimeEvaluationTree, ct: ClassType) => Valid(new OuterClassTree(tree, ct))
case _ => Recoverable("No valid outer can be found")
}
}

case class ThisTree(
`type`: ReferenceType
) extends RuntimeEvaluationTree {
Expand All @@ -180,8 +213,7 @@ case class ThisTree(
case class ModuleTree(
`type`: ClassType,
of: Option[RuntimeEvaluationTree]
) extends RuntimeEvaluationTree
with TypeTree {
) extends RuntimeEvaluationTree {
override def prettyPrint(depth: Int): String = {
val indent = "\t" * (depth + 1)
s"""|ModuleTree(
Expand All @@ -206,8 +238,7 @@ object ModuleTree {

case class ClassTree(
`type`: ClassType
) extends RuntimeValidationTree
with TypeTree {
) extends RuntimeValidationTree {
override def prettyPrint(depth: Int): String = {
val indent = "\t" * (depth + 1)
s"""|ClassTree(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ object Safe {
}
}

def unapply(safe: Safe[_]): Option[Try[_]] = Some(safe.result)
def unapply[A](safe: Safe[A]): Option[Try[A]] = Some(safe.result)

def join[A, B](safeA: Safe[A], safeB: Safe[B]): Safe[(A, B)] = {
safeA.flatMap(a => safeB.map(b => (a, b)))
Expand Down
Loading

0 comments on commit 3891fb5

Please sign in to comment.