Skip to content

Commit

Permalink
Merge pull request #630 from adpi2/runtime-validation-refacto
Browse files Browse the repository at this point in the history
More refactoring in RuntimeValidation
  • Loading branch information
adpi2 authored Jan 22, 2024
2 parents 8cbf398 + 65cd02a commit 0760f6f
Show file tree
Hide file tree
Showing 5 changed files with 56 additions and 98 deletions.
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ inThisBuild(
developers := Developers.list,
version ~= { dynVer =>
if (isRelease) dynVer
else "3.2.0-SNAPSHOT" // only for local publishing
else "4.0.0-SNAPSHOT" // only for local publishing
},
resolvers += Resolver.mavenLocal,
compile / javacOptions ++= Seq("-source", "1.8", "-target", "1.8")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,6 @@ object RuntimeEvaluationTree {
override def prettyPrint(depth: Int): String = s"This(${`type`})"
}

object This {
def apply(ths: Option[JdiObject])(implicit logger: Logger): Validation[This] =
Validation.fromOption(ths).map(ths => This(ths.reference.referenceType()))
}

case class StaticModule(
`type`: jdi.ReferenceType
) extends RuntimeEvaluationTree {
Expand Down Expand Up @@ -248,7 +243,7 @@ object RuntimeEvaluationTree {
def apply(lhs: RuntimeEvaluationTree, rhs: RuntimeEvaluationTree, tpe: jdi.Type): Validation[Assign] =
lhs match {
case lhs: Assignable => Valid(Assign(lhs, rhs, tpe))
case _ => CompilerRecoverable("Left hand side of an assignment must be assignable")
case _ => Recoverable("Left hand side of an assignment must be assignable")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ import scala.meta.trees.*
import scala.meta.{Type => _, *}
import scala.util.Failure
import scala.util.Success

import scala.collection.mutable.Buffer
import scala.util.Try

private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: SourceLookUpProvider, preEvaluation: Boolean)(
Expand Down Expand Up @@ -56,7 +54,10 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source
}

private lazy val thisTree: Validation[RuntimeEvaluationTree] =
Validation.fromOption(frame.thisObject.map(ths => This(ths.reference.referenceType())))
Validation.fromOption(
frame.thisObject.map(ths => This(ths.reference.referenceType())),
"`this` is unavailable in a static context"
)

private lazy val declaringType: Validation[jdi.ReferenceType] =
Validation(frame.current().location().declaringType())
Expand Down Expand Up @@ -120,37 +121,18 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source
private def validateLiteral(lit: Lit): Validation[RuntimeEvaluationTree] =
classLoader.map { loader =>
val value = loader.mirrorOfLiteral(lit.value)
val tpe = loader
.mirrorOfLiteral(lit.value)
.map(_.value.`type`)
.extract
.get
val tpe = loader.mirrorOfLiteral(lit.value).map(_.value.`type`).extract.get
Value(value, tpe)
}

private def findVariable(name: String, preevaluate: Boolean = preEvaluation): Validation[RuntimeEvaluationTree] = {
val encodedName = NameTransformer.encode(name)
Validation
.fromOption(frame.variableByName(encodedName))
.filter(_.`type`.name != "scala.Function0")
.map(v => LocalVar(encodedName, v.`type`))
.flatMap(v => if (preevaluate) preEvaluate(v) else Valid(v))
}

private def findField(name: String, ref: jdi.ReferenceType): Option[jdi.Field] = {
val encodedName = NameTransformer.encode(name)
Option(ref.fieldByName(encodedName))
.orElse(ref.visibleFields.asScala.find(_.name.endsWith("$" + encodedName)))
}

private def findField(
qualifier: RuntimeEvaluationTree,
name: String,
preevaluate: Boolean = preEvaluation
): Validation[RuntimeEvaluationTree] = {
for {
qualifierTpe <- asReference(qualifier.`type`)
field <- Validation.fromOption(findField(name, qualifierTpe))
field <- findField(name, qualifierTpe)
_ = loadClassOnNeed(field)
fieldTree <- asInstanceField(field, qualifier, preevaluate)
} yield fieldTree
Expand All @@ -162,12 +144,28 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source
preevaluate: Boolean = preEvaluation
): Validation[RuntimeEvaluationTree] = {
for {
field <- Validation.fromOption(findField(name, qualifier))
field <- findField(name, qualifier)
_ = loadClassOnNeed(field)
fieldTree <- asStaticField(field, preevaluate = preevaluate)
} yield fieldTree
}

private def findVariable(name: String, preevaluate: Boolean = preEvaluation): Validation[RuntimeEvaluationTree] = {
val encodedName = NameTransformer.encode(name)
Validation
.fromOption(frame.variableByName(encodedName), s"$name is not a local variable")
.filter(_.`type`.name != "scala.Function0")
.map(v => LocalVar(encodedName, v.`type`))
.flatMap(v => if (preevaluate) preEvaluate(v) else Valid(v))
}

private def findField(name: String, ref: jdi.ReferenceType): Validation[jdi.Field] = {
val encodedName = NameTransformer.encode(name)
def fieldOpt = Option(ref.fieldByName(encodedName))
.orElse(ref.visibleFields.asScala.find(_.name.endsWith("$" + encodedName)))
Validation.fromOption(fieldOpt, s"$name is not a field in ${ref.name}")
}

private def findClass(name: String): Validation[jdi.ReferenceType] =
thisTree
.flatMap(findClassMember(name, _))
Expand Down Expand Up @@ -219,7 +217,7 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source
val qualifierTypeName = qualifier.`type`.name
findClassMember(moduleClassName, qualifier).flatMap { tpe =>
if (inCompanion(Some(qualifierTypeName), moduleClassName))
CompilerRecoverable(s"Cannot access module $name from $qualifierTypeName")
Recoverable(s"Cannot access module $name from $qualifierTypeName")
else asModule(tpe, qualifier)
}
}
Expand Down Expand Up @@ -300,16 +298,18 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source
}

// ! May not be correct when dealing with an object inside a class
private def findOuter(qualifier: RuntimeEvaluationTree): Validation[RuntimeEvaluationTree] =
private def findOuter(qualifier: RuntimeEvaluationTree): Validation[RuntimeEvaluationTree] = {
val qualifierTypeName = qualifier.`type`.name
asReference(qualifier.`type`)
.flatMap(tpe => Validation(tpe.fieldByName("$outer")))
.flatMap(asInstanceField(_, qualifier, preevaluate = true))
.orElse {
Validation
.fromOption(removeLastInnerTypeFromFQCN(qualifier.`type`.name))
.fromOption(removeLastInnerTypeFromFQCN(qualifierTypeName), s"$qualifierTypeName is not an inner class")
.flatMap(name => loadClass(name + "$"))
.flatMap(asStaticModule)
}
}

private def validateIf(tree: Term.If): Validation[RuntimeEvaluationTree] =
for {
Expand All @@ -331,9 +331,9 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source
val tpe =
if (isAssignableFrom(tType, fType)) Valid(tType)
else if (isAssignableFrom(fType, tType)) Valid(fType)
else Validation.fromOption(getCommonSuperClass(tType, fType)).orElse(loadClass("java.lang.Object"))
else getCommonSuperClass(tType, fType).orElse(loadClass("java.lang.Object"))
tpe.flatMap(tpe => preEvaluate(If(cond, ifTrue, ifFalse, tpe)))
} else CompilerRecoverable("A predicate must be a boolean")
} else Recoverable("A predicate must be a boolean")
}

private def isBoolean(tpe: jdi.Type): Boolean =
Expand All @@ -357,7 +357,7 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source
findVariable(name, false)
.orElse(thisTree.flatMap(findField(_, name, false)))
.orElse(declaringType.flatMap(findStaticField(_, name, false)))
case _ => CompilerRecoverable("Unsupported assignment")
case _ => Recoverable("Unsupported assignment")
}

for {
Expand All @@ -377,12 +377,7 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source
}

private def fromLitToValue(literal: Lit, classLoader: JdiClassLoader): (Safe[Any], jdi.Type) = {
val tpe = classLoader
.mirrorOfLiteral(literal.value)
.map(_.value.`type`)
.getResult
.get

val tpe = classLoader.mirrorOfLiteral(literal.value).map(_.value.`type`).getResult.get
(Safe(literal.value), tpe)
}

Expand Down Expand Up @@ -537,8 +532,8 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source

private def isAssignableFrom(got: jdi.Type, expected: jdi.Type): Boolean = {
def referenceTypesMatch(got: jdi.ReferenceType, expected: jdi.ReferenceType) = {
val assignableFrom = expected.classObject().referenceType().methodsByName("isAssignableFrom").get(0)
val params = Seq(got.classObject()).asJava
val assignableFrom = expected.classObject.referenceType.methodsByName("isAssignableFrom").get(0)
val params = Seq(got.classObject).asJava
expected.classObject
.invokeMethod(frame.thread, assignableFrom, params, jdi.ObjectReference.INVOKE_SINGLE_THREADED)
.asInstanceOf[jdi.BooleanValue]
Expand Down Expand Up @@ -624,7 +619,7 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source
loop(qualifier)
}

private def getCommonSuperClass(tpe1: jdi.Type, tpe2: jdi.Type): Option[jdi.Type] = {
private def getCommonSuperClass(tpe1: jdi.Type, tpe2: jdi.Type): Validation[jdi.Type] = {
def getSuperClasses(of: jdi.Type): Array[jdi.ClassType] =
of match {
case cls: jdi.ClassType =>
Expand All @@ -634,7 +629,10 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source

val superClasses1 = getSuperClasses(tpe1)
val superClasses2 = getSuperClasses(tpe2)
superClasses1.find(superClasses2.contains)
Validation.fromOption(
superClasses1.find(superClasses2.contains),
s"${tpe1.name} and ${tpe2.name} do not have any common super class"
)
}

private def validateType(
Expand Down Expand Up @@ -738,7 +736,7 @@ private[evaluator] class RuntimeValidation(frame: JdiFrame, sourceLookUp: Source
iter.size match {
case 1 => Valid(iter.head)
case 0 => Recoverable(message)
case _ => CompilerRecoverable(s"$message: multiple values found")
case _ => Recoverable(s"$message: multiple values found")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ package ch.epfl.scala.debugadapter.internal.evaluator

import java.util.NoSuchElementException

import com.sun.jdi.VMDisconnectedException
import com.sun.jdi.ObjectCollectedException
import com.sun.jdi.InvalidStackFrameException
import com.sun.jdi.AbsentInformationException
import com.sun.jdi.InvocationException
import com.sun.jdi.VMOutOfMemoryException
import com.sun.jdi.*
import ch.epfl.scala.debugadapter.Logger

sealed abstract class Validation[+A] {
Expand Down Expand Up @@ -45,7 +40,8 @@ final case class Valid[+A](value: A) extends Validation[A]() {
override def toOption: Option[A] = Some(value)
}

sealed abstract class Invalid(val exception: Exception) extends Validation[Nothing]() {
sealed trait Invalid extends Validation[Nothing] {
def exception: Throwable
override val isValid: Boolean = false
override def map[B](f: Nothing => B)(implicit logger: Logger): Validation[B] = this
override def flatMap[B](f: Nothing => Validation[B])(implicit logger: Logger): Validation[B] = this
Expand All @@ -57,26 +53,27 @@ sealed abstract class Invalid(val exception: Exception) extends Validation[Nothi
override def toOption: Option[Nothing] = None
}

final case class Recoverable(override val exception: Exception) extends Invalid(exception) {
override def orElse[B >: Nothing](f: => Validation[B]): Validation[B] = f
final case class Recoverable(exception: Exception) extends Invalid {
override def orElse[B >: Nothing](f: => Validation[B]): Validation[B] = f match {
case valid: Valid[B] => valid
case _: Recoverable => this
case fatal: Fatal => fatal
}
}

sealed abstract class Unrecoverable(override val exception: Exception) extends Invalid(exception) {
final case class Fatal(exception: Throwable) extends Invalid {
override def orElse[B >: Nothing](f: => Validation[B]): Validation[B] = this
override def transform[B](f: Validation[Nothing] => Validation[B]): Validation[B] = this
}

final case class Fatal(override val exception: Exception) extends Unrecoverable(exception)
final case class CompilerRecoverable(override val exception: Exception) extends Unrecoverable(exception)

object Validation {
private def handler(t: Throwable)(implicit logger: Logger): Invalid = t match {
case e @ (_: VMDisconnectedException | _: ObjectCollectedException) => Fatal(e)
case e @ (_: InvalidStackFrameException | _: AbsentInformationException) => Fatal(e)
case e @ (_: InvocationException | _: VMOutOfMemoryException) => Fatal(e)
case e: Exception =>
logger.warn(s"Unexpected error while validating: $e")
CompilerRecoverable(e)
Recoverable(e)
}

def apply[A](input: => A)(implicit logger: Logger): Validation[A] = {
Expand All @@ -89,10 +86,10 @@ object Validation {
}
}

def fromOption[A](value: => Option[A]): Validation[A] = {
def fromOption[A](value: => Option[A], message: String): Validation[A] = {
value match {
case Some(value) => Valid(value)
case None => Recoverable("Empty option")
case None => Recoverable(new NoSuchElementException(message))
}
}

Expand All @@ -106,23 +103,9 @@ object Validation {
}

object Invalid {
def unapply(invalid: Invalid): Option[Exception] = Some(invalid.exception)
def unapply(invalid: Invalid): Option[Throwable] = Some(invalid.exception)
}

object Recoverable {
def apply(s: String) = new Recoverable(new NoSuchElementException(s))
}

object Fatal {
def apply(t: Throwable): Unrecoverable = new Fatal(new Exception(t))
def apply(str: String): Unrecoverable = new Fatal(new Exception(str))
}

object CompilerRecoverable {
def apply(t: Throwable): Unrecoverable = CompilerRecoverable(
new Exception(s"Can't validate at runtime, recovering with compiler: $t")
)
def apply(str: String): Unrecoverable = CompilerRecoverable(
new Exception(s"Can't validate at runtime, recovering with compiler: $str")
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import scala.util.Failure
import scala.util.Success
import ch.epfl.scala.debugadapter.Logger

import scala.jdk.CollectionConverters.*

package object evaluator {
implicit class SafeSeq[A](seq: Seq[Safe[A]]) {
def traverse: Safe[Seq[A]] = {
Expand Down Expand Up @@ -36,20 +34,4 @@ package object evaluator {
}
}
}

implicit class SeqExtensions[A](seq: Seq[A]) {
def toValidation(message: String): Validation[A] =
seq.size match {
case 1 => Valid(seq.head)
case 0 => Recoverable(message)
case _ => CompilerRecoverable(s"$message: multiple values found")
}

def asJavaList = seq.asJava
}

implicit class JavaListToScala[A](list: java.util.List[A]) {
def asScalaList: List[A] = list.asScala.toList
def asScalaSeq: Seq[A] = list.asScala.toSeq
}
}

0 comments on commit 0760f6f

Please sign in to comment.