Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Evaluate blocks #477

Merged
merged 6 commits into from
Jul 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ class RuntimeDefaultEvaluator(val frame: JdiFrame, val logger: Logger) extends R
case instance: NewInstanceTree => instantiate(instance)
case method: InstanceMethodTree => invoke(method)
case array: ArrayElemTree => evaluateArrayElement(array)
case branching: IfTree => evaluateIf(branching)
case staticMethod: StaticMethodTree => invokeStatic(staticMethod)
case outer: OuterTree => evaluateOuter(outer)
case UnitTree => Safe(JdiValue(frame.thread.virtualMachine.mirrorOfVoid, frame.thread))
}

/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -120,6 +122,15 @@ class RuntimeDefaultEvaluator(val frame: JdiFrame, val logger: Logger) extends R
array <- eval(tree.array)
index <- eval(tree.index).flatMap(_.unboxIfPrimitive).flatMap(_.toInt)
} yield array.asArray.getValue(index)

/* -------------------------------------------------------------------------- */
/* If tree evaluation */
/* -------------------------------------------------------------------------- */
def evaluateIf(tree: IfTree): Safe[JdiValue] =
for {
predicate <- eval(tree.p).flatMap(_.unboxIfPrimitive).flatMap(_.toBoolean)
value <- if (predicate) eval(tree.thenp) else eval(tree.elsep)
} yield value
}

object RuntimeDefaultEvaluator {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,15 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val logger: Logger) extends R

def validate(expression: Stat): Validation[RuntimeEvaluableTree] =
expression match {
case lit: Lit => validateLiteral(lit)
case value: Term.Name => validateName(value.value, thisTree)
case _: Term.This => thisTree
case sup: Term.Super => Recoverable("Super not (yet) supported at runtime")
case _: Term.Apply | _: Term.ApplyInfix | _: Term.ApplyUnary => validateMethod(extractCall(expression))
case select: Term.Select => validateSelect(select)
case lit: Lit => validateLiteral(lit)
case branch: Term.If => validateIf(branch)
case instance: Term.New => validateNew(instance)
case block: Term.Block => validateBlock(block)
case _ => Recoverable("Expression not supported at runtime")
}

Expand All @@ -52,13 +54,22 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val logger: Logger) extends R
case _ => validate(expression)
}

/* -------------------------------------------------------------------------- */
/* Block validation */
/* -------------------------------------------------------------------------- */
def validateBlock(block: Term.Block): Validation[RuntimeEvaluableTree] =
block.stats.foldLeft(Valid(UnitTree): Validation[RuntimeEvaluableTree]) {
case (Valid(_), stat) => validate(stat)
case (err: Invalid, _) => err
}

/* -------------------------------------------------------------------------- */
/* Literal validation */
/* -------------------------------------------------------------------------- */
def validateLiteral(lit: Lit): Validation[LiteralTree] =
def validateLiteral(lit: Lit): Validation[RuntimeEvaluableTree] =
frame.classLoader().map(loader => LiteralTree(fromLitToValue(lit, loader))).extract match {
case Success(value) => value
case Failure(e) => Fatal(e)
case Failure(e) => CompilerRecoverable(e)
}

/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -151,12 +162,19 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val logger: Logger) extends R
if (methodFirst) zeroArg.orElse(field)
else field.orElse(zeroArg)

of.flatMap { of =>
member
.orElse(validateModule(name, Some(of)))
.orElse(findOuter(of).flatMap(o => validateName(value, Valid(o), methodFirst)))
}.orElse(localVarTreeByName(name))
.orElse(validateModule(name, None))
of
.flatMap { of =>
member
.orElse(validateModule(name, Some(of)))
.orElse(findOuter(of).flatMap(o => validateName(value, Valid(o), methodFirst)))
}
.orElse {
of match {
case Valid(_: ThisTree) | _: Recoverable => localVarTreeByName(name)
case _ => Recoverable(s"${name} is not a local variable")
}
}
.orElse { validateModule(name, None) }
}

/* -------------------------------------------------------------------------- */
Expand All @@ -167,7 +185,7 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val logger: Logger) extends R
args: Seq[RuntimeEvaluableTree]
): Validation[RuntimeEvaluableTree] =
methodTreeByNameAndArgs(on, "apply", args)
.orElse { ArrayElemTree(on, "apply", args) }
.orElse { ArrayElemTree(on, args) }

def validateIndirectApply(
on: Validation[RuntimeTree],
Expand All @@ -191,8 +209,8 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val logger: Logger) extends R
name: String,
args: Seq[RuntimeEvaluableTree]
): Validation[RuntimeEvaluableTree] =
validateIndirectApply(Valid(tree), name, args)
.orElse { methodTreeByNameAndArgs(tree, name, args) }
methodTreeByNameAndArgs(tree, name, args)
.orElse { validateIndirectApply(Valid(tree), name, args) }
.orElse { validateApply(tree, args) }
.orElse { findOuter(tree).flatMap(findMethod(_, name, args)) }

Expand Down Expand Up @@ -252,6 +270,26 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val logger: Logger) extends R
outerTree <- OuterTree(tree, outer)
} yield outerTree
}

/* -------------------------------------------------------------------------- */
/* Flow control validation */
/* -------------------------------------------------------------------------- */

def validateIf(tree: Term.If): Validation[RuntimeEvaluableTree] = {
lazy val objType = loadClass("java.lang.Object").extract.get.cls
for {
cond <- validate(tree.cond)
thenp <- validate(tree.thenp)
elsep <- validate(tree.elsep)
ifTree <- IfTree(
cond,
thenp,
elsep,
isAssignableFrom(_, _),
extractCommonSuperClass(thenp.`type`, elsep.`type`).getOrElse(objType)
)
} yield ifTree
}
}

object RuntimeDefaultValidator {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ trait RuntimeValidator {
*/
protected def validateWithClass(expression: Stat): Validation[RuntimeTree]

def validateLiteral(lit: Lit): Validation[LiteralTree]
def validateLiteral(lit: Lit): Validation[RuntimeEvaluableTree]

def localVarTreeByName(name: String): Validation[RuntimeEvaluableTree]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
package ch.epfl.scala.debugadapter.internal.evaluator

import com.sun.jdi._
import scala.meta.trees.*

import scala.meta.Lit
import scala.util.Success
import RuntimeEvaluatorExtractors.*
import scala.meta.Stat
import scala.meta.Term
import scala.meta.trees.*
import scala.meta.{Type => MType}
import scala.util.Failure
import scala.util.Success
import scala.util.Try
import scala.jdk.CollectionConverters.*

import RuntimeEvaluatorExtractors.*

private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) {
import RuntimeEvaluationHelpers.*
def fromLitToValue(literal: Lit, classLoader: JdiClassLoader): (Safe[Any], Type) = {
Expand Down Expand Up @@ -118,7 +120,11 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) {
encode: Boolean = true
): Validation[MethodTree] = tree match {
case ReferenceTree(ref) =>
methodsByNameAndArgs(ref, funName, args.map(_.`type`), encode).flatMap(toStaticIfNeeded(_, args, tree))
methodsByNameAndArgs(ref, funName, args.map(_.`type`), encode).flatMap {
case ModuleCall() =>
Recoverable("Accessing a module from its instanciation method is not allowed at console-level")
case mt => toStaticIfNeeded(mt, args, tree)
}
case _ => Recoverable(new IllegalArgumentException(s"Cannot find method $funName on $tree"))
}

Expand All @@ -136,12 +142,12 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) {
}

(got, expected) match {
case (g: ArrayType, at: ArrayType) => isAssignableFrom(g.componentType, at.componentType)
case (g: ArrayType, at: ArrayType) => g.componentType().equals(at.componentType()) // TODO: check this
case (g: PrimitiveType, pt: PrimitiveType) => got.equals(pt)
case (g: ReferenceType, ref: ReferenceType) => referenceTypesMatch(g, ref)
case (_: VoidType, _: VoidType) => true

case (g: ClassType, pt: PrimitiveType) =>
case (g: ReferenceType, pt: PrimitiveType) =>
isAssignableFrom(g, frame.getPrimitiveBoxedClass(pt))
case (g: PrimitiveType, ct: ReferenceType) =>
isAssignableFrom(frame.getPrimitiveBoxedClass(g), ct)
Expand Down Expand Up @@ -215,6 +221,20 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) {

loop(qual)
}

def extractCommonSuperClass(tpe1: Type, tpe2: Type): Option[Type] = {
def getSuperClasses(of: Type): Array[ClassType] =
of match {
case cls: ClassType =>
Iterator.iterate(cls)(cls => cls.superclass()).takeWhile(_ != null).toArray
case _ => Array()
}

val superClasses1 = getSuperClasses(tpe1)
val superClasses2 = getSuperClasses(tpe2)
superClasses1.find(superClasses2.contains)
}

def validateType(tpe: MType, thisType: Option[RuntimeEvaluableTree])(
termValidation: Term => Validation[RuntimeEvaluableTree]
): Validation[(Option[RuntimeEvaluableTree], ClassTree)] =
Expand Down Expand Up @@ -259,7 +279,7 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) {

def searchAllClassesFor(name: String, in: Option[String]): Validation[ClassTree] = {
def fullName = in match {
case Some(value) if value == name => name // name duplication when implicit apply call
case Some(value) if value == name => name // name duplication when indirect apply call
case Some(value) => concatenateInnerTypes(value, name)
case None => name
}
Expand Down Expand Up @@ -374,7 +394,9 @@ private[evaluator] object RuntimeEvaluationHelpers {
case (_, Module(mod)) => Valid(InstanceFieldTree(field, mod))
case (_, eval: RuntimeEvaluableTree) =>
if (field.isStatic())
Fatal(s"Accessing static field $field from instance ${eval.`type`} can lead to unexpected behavior")
CompilerRecoverable(
s"Accessing static field $field from instance ${eval.`type`} can lead to unexpected behavior"
)
else Valid(InstanceFieldTree(field, eval))
}

Expand All @@ -387,7 +409,9 @@ private[evaluator] object RuntimeEvaluationHelpers {
case Module(mod) => Valid(InstanceMethodTree(method, args, mod))
case eval: RuntimeEvaluableTree =>
if (method.isStatic())
Fatal(s"Accessing static method $method from instance ${eval.`type`} can lead to unexpected behavior")
CompilerRecoverable(
s"Accessing static method $method from instance ${eval.`type`} can lead to unexpected behavior"
)
else Valid(InstanceMethodTree(method, args, eval))
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,31 @@ protected[internal] object RuntimeEvaluatorExtractors {
unapply(tree.`type`).map(_ => tree.asInstanceOf[RuntimeEvaluableTree])
}

object ModuleCall {
def unapply(m: Method): Boolean = {
val rt = m.returnTypeName
val noArgs = m.argumentTypeNames.size == 0
val isSingleton = rt.endsWith("$")
val isSingletonInstantiation = rt.stripSuffix("$").endsWith(m.name)
noArgs && isSingleton && isSingletonInstantiation
}
}

object MethodCall {
def unapply(tree: RuntimeTree): Option[RuntimeTree] =
def unapply(tree: RuntimeTree): Boolean =
tree match {
case mt: NestedModuleTree => unapply(mt.of)
case ft: InstanceFieldTree => unapply(ft.qual)
case oct: OuterClassTree => unapply(oct.inner)
case OuterModuleTree(module) => unapply(module)
case _: MethodTree | _: NewInstanceTree => Some(tree)
case _: LiteralTree | _: LocalVarTree | _: PreEvaluatedTree | _: ThisTree => None
case _: StaticFieldTree | _: ClassTree | _: TopLevelModuleTree => None
case _: PrimitiveBinaryOpTree | _: PrimitiveUnaryOpTree | _: ArrayElemTree => None
case IfTree(p, t, f, _) => unapply(p) || unapply(t) || unapply(f)
case _: MethodTree | _: NewInstanceTree => true
case _: LiteralTree | _: LocalVarTree | _: PreEvaluatedTree | _: ThisTree | UnitTree => false
case _: StaticFieldTree | _: ClassTree | _: TopLevelModuleTree => false
case _: PrimitiveBinaryOpTree | _: PrimitiveUnaryOpTree | _: ArrayElemTree => false
}
def unapply(tree: Validation[RuntimeTree]): Option[RuntimeTree] =
tree.toOption.filter { unapply(_).isDefined }
def unapply(tree: Validation[RuntimeTree]): Validation[RuntimeTree] =
tree.filter(unapply)
}

object ReferenceTree {
Expand All @@ -61,6 +72,18 @@ protected[internal] object RuntimeEvaluatorExtractors {
}
}

object BooleanTree {
def unapply(p: Validation[RuntimeEvaluableTree]): Validation[RuntimeEvaluableTree] =
p.flatMap(unapply)

def unapply(p: RuntimeEvaluableTree): Validation[RuntimeEvaluableTree] = p.`type` match {
case bt: BooleanType => Valid(p)
case rt: ReferenceType if rt.name() == "java.lang.Boolean" =>
Valid(p)
case _ => CompilerRecoverable(s"The predicate must be a boolean expression, found ${p.`type`}")
}
}

object PrimitiveTest {
object IsIntegral {
def unapply(x: Type): Boolean = x match {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package ch.epfl.scala.debugadapter.internal.evaluator

import ch.epfl.scala.debugadapter.Logger
import scala.util.Success
import scala.meta.Term
import scala.meta.Lit

class RuntimePreEvaluationValidator(
override val frame: JdiFrame,
Expand All @@ -13,8 +16,8 @@ class RuntimePreEvaluationValidator(
Validation.fromTry(tpe).map(PreEvaluatedTree(value, _))
}

override lazy val thisTree: Validation[PreEvaluatedTree] =
ThisTree(frame.thisObject).flatMap(preEvaluate)
override def validateLiteral(lit: Lit): Validation[RuntimeEvaluableTree] =
super.validateLiteral(lit).flatMap(preEvaluate)

override def localVarTreeByName(name: String): Validation[PreEvaluatedTree] =
super.localVarTreeByName(name).flatMap(preEvaluate)
Expand All @@ -41,6 +44,22 @@ class RuntimePreEvaluationValidator(
preEvaluate(tree)
case tree => Valid(tree)
}

override def validateIf(tree: Term.If): Validation[RuntimeEvaluableTree] =
super.validateIf(tree).transform {
case tree @ Valid(IfTree(p: PreEvaluatedTree, thenp, elsep, _)) =>
val predicate = for {
pValue <- p.value
unboxed <- pValue.unboxIfPrimitive
bool <- unboxed.toBoolean
} yield bool
predicate.extract match {
case Success(true) => Valid(thenp)
case Success(false) => Valid(elsep)
case _ => tree
}
case tree => tree
}
}

object RuntimePreEvaluationValidator {
Expand Down
Loading
Loading