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

More refactoring in RuntimeValidation #630

Merged
merged 1 commit into from
Jan 22, 2024
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
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
}
}
Loading