From 6ca8e281723b8fc6e137f9e87946ee2c1739845d Mon Sep 17 00:00:00 2001 From: Lucas Nouguier Date: Tue, 27 Jun 2023 10:30:23 +0200 Subject: [PATCH 1/6] evalute & preevaluate if ONLY :D fix tests expected error --- .../evaluator/RuntimeDefaultEvaluator.scala | 10 + .../evaluator/RuntimeDefaultValidator.scala | 21 +- .../evaluator/RuntimeEvaluation.scala | 2 +- .../evaluator/RuntimeEvaluationHelpers.scala | 12 +- .../RuntimeEvaluatorExtractors.scala | 27 +- .../RuntimePreEvaluationValidator.scala | 22 ++ .../internal/evaluator/RuntimeTree.scala | 57 +++- .../internal/JavaRuntimeEvaluatorTests.scala | 18 +- .../internal/RuntimeEvaluatorTests.scala | 275 +++++++++++------- 9 files changed, 305 insertions(+), 139 deletions(-) diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultEvaluator.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultEvaluator.scala index 59658a066..21edc9106 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultEvaluator.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultEvaluator.scala @@ -21,6 +21,7 @@ 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) } @@ -120,6 +121,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 { diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala index 7163e97c3..80a32b84b 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala @@ -31,12 +31,13 @@ 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 _ => Recoverable("Expression not supported at runtime") } @@ -55,10 +56,10 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val logger: Logger) extends R /* -------------------------------------------------------------------------- */ /* 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) } /* -------------------------------------------------------------------------- */ @@ -252,6 +253,20 @@ 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(_, _), objType) + } yield ifTree + } } object RuntimeDefaultValidator { diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluation.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluation.scala index ea8b9a981..a7602410f 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluation.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluation.scala @@ -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] diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala index 319da7e90..d0894e24c 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala @@ -136,12 +136,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) @@ -374,7 +374,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)) } @@ -387,7 +389,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)) } diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluatorExtractors.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluatorExtractors.scala index f38aeea62..8ab59827b 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluatorExtractors.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluatorExtractors.scala @@ -26,19 +26,20 @@ protected[internal] object RuntimeEvaluatorExtractors { } 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 => 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 { @@ -61,6 +62,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 { diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala index 4b3abed8b..b9efc1bdd 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala @@ -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, @@ -16,6 +19,9 @@ class RuntimePreEvaluationValidator( 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) @@ -41,6 +47,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 { diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala index 2e9459b54..ba542e421 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala @@ -1,7 +1,7 @@ package ch.epfl.scala.debugadapter.internal.evaluator import com.sun.jdi._ -import RuntimeEvaluatorExtractors.{IsAnyVal, Module} +import RuntimeEvaluatorExtractors.{BooleanTree, IsAnyVal, Module} import scala.util.Success /* -------------------------------------------------------------------------- */ @@ -51,7 +51,7 @@ case class LiteralTree private ( object LiteralTree { def apply(value: (Safe[Any], Type)): Validation[LiteralTree] = value._1 match { case Safe(Success(_: String)) | Safe(Success(IsAnyVal(_))) => Valid(new LiteralTree(value._1, value._2)) - case _ => Fatal(s"Unsupported literal type: ${value.getClass}") + case _ => CompilerRecoverable(s"Unsupported literal type: ${value.getClass}") } } @@ -327,3 +327,56 @@ case class PreEvaluatedTree( object PreEvaluatedTree { def apply(value: (Safe[JdiValue], Type)) = new PreEvaluatedTree(value._1, value._2) } + +/* -------------------------------------------------------------------------- */ +/* Flow control trees */ +/* -------------------------------------------------------------------------- */ +case class IfTree private ( + p: RuntimeEvaluableTree, + thenp: RuntimeEvaluableTree, + elsep: RuntimeEvaluableTree, + `type`: Type +) extends RuntimeEvaluableTree { + override def prettyPrint(depth: Int): String = { + val indent = "\t" * (depth + 1) + s"""|IfTree( + |${indent}p= ${p.prettyPrint(depth + 1)}, + |${indent}ifTrue= ${thenp.prettyPrint(depth + 1)}, + |${indent}ifFalse= ${elsep.prettyPrint(depth + 1)} + |${indent}t= ${`type`} + |${indent.dropRight(1)})""".stripMargin + } +} + +object IfTree { + + /** + * Returns the type of the branch that is chosen, if any + * + * @param t1 + * @param t2 + * @return Some(true) if t1 is chosen, Some(false) if t2 is chosen, None if no branch is chosen + */ + def apply( + p: RuntimeEvaluableTree, + ifTrue: RuntimeEvaluableTree, + ifFalse: RuntimeEvaluableTree, + assignableFrom: ( + Type, + Type + ) => Boolean, // ! This is a hack, passing a wrong method would lead to inconsistent trees + objType: => Type + ): Validation[IfTree] = { + val pType = p.`type` + val tType = ifTrue.`type` + val fType = ifFalse.`type` + + p match { + case BooleanTree(_) => + if (assignableFrom(tType, fType)) Valid(IfTree(p, ifTrue, ifFalse, tType)) + else if (assignableFrom(fType, tType)) Valid(IfTree(p, ifTrue, ifFalse, fType)) + else Valid(IfTree(p, ifTrue, ifFalse, objType)) + case _ => CompilerRecoverable("A predicate must be a boolean") + } + } +} diff --git a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/JavaRuntimeEvaluatorTests.scala b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/JavaRuntimeEvaluatorTests.scala index f6f6b0d07..152e946ac 100644 --- a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/JavaRuntimeEvaluatorTests.scala +++ b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/JavaRuntimeEvaluatorTests.scala @@ -247,7 +247,7 @@ class JavaRuntimeEvaluatorTests extends DebugTestSuite { check( Breakpoint(17), DebugStepAssert.inParallel( - Evaluation.failed("coucou", "Accessing static field"), + Evaluation.failed("coucou"), Evaluation.success("lapin", "lapin"), Evaluation.success("love", "love") ) @@ -272,13 +272,13 @@ class JavaRuntimeEvaluatorTests extends DebugTestSuite { Breakpoint(11), DebugStepAssert.inParallel( Evaluation.success("inner.helloInner()", "hello inner 42"), - Evaluation.failed("inner.helloInner", "Accessing static field"), + Evaluation.failed("inner.helloInner"), Evaluation.success("Main.StaticInner.z", 84), Evaluation.success("Foo.StaticFriendFoo.z", 168), - Evaluation.failed("foo.friendFoo(new Foo()).z", "Accessing static field"), + Evaluation.failed("foo.friendFoo(new Foo()).z"), Evaluation.failed("foo.friendFoo(new Foo()).staticMethod()"), Evaluation.success("new Foo().friendFoo(new Foo()).y", 42), - Evaluation.failed("new Foo().friendFoo(new Foo()).greet", "Accessing static field"), + Evaluation.failed("new Foo().friendFoo(new Foo()).greet"), Evaluation.success("Main.StaticInner.StaticDoubleInner.z", 168) ) ) @@ -289,11 +289,11 @@ class JavaRuntimeEvaluatorTests extends DebugTestSuite { check( Breakpoint(13), DebugStepAssert.inParallel( - Evaluation.failed("main.coucou", "Accessing static field"), - Evaluation.failed("hiddenFoo.foofoo", "Accessing static field"), - Evaluation.failed("foo.foofoo", "Accessing static field"), - Evaluation.failed("superfoo.foofoo", "Accessing static field"), - Evaluation.failed("main.staticMethod()", "Accessing static method") + Evaluation.failed("main.coucou"), + Evaluation.failed("hiddenFoo.foofoo"), + Evaluation.failed("foo.foofoo"), + Evaluation.failed("superfoo.foofoo"), + Evaluation.failed("main.staticMethod()") ) ) diff --git a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala index 0cb977ce7..4f0b0d6c8 100644 --- a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala +++ b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala @@ -246,6 +246,117 @@ object RuntimeEvaluatorEnvironments { | def test(i: java.lang.Integer): String = "boxed int" |} |""".stripMargin + + val arraysSource = + """|package example + | + |object Main { + | def main(args: Array[String]): Unit = { + | val arr = Array(1, 2, 3) + | val sh: Short = 2 + | val ch: Char = 2 + | val by: Byte = 2 + | println("ok") + | } + | + | def test(arr: Array[Int]): String = arr.mkString(",") + | + | def test(arr: Array[Test]): String = arr.map(_.i).mkString(",") + | + | case class Test(i: Int) + |} + |""".stripMargin + val collectionSource = + """|package example + | + |object Main { + | def main(args: Array[String]): Unit = { + | val list = List(1, 2, 3) + | val map = Map(1 -> "one", 2 -> "two") + | val set = Set(1, 2, 3) + | val seq = Seq(1, 2, 3) + | val vector = Vector(1, 2, 3) + | println("ok") + | } + |} + |""".stripMargin + + val innerInstantiation = + """|package example + | + |class A { + | class AA { + | class AAA(val x: Int) + | } + | object AA { + | class StaticAAA + | } + |} + | + |object A { + | class StaticAA { + | class AAA + | } + | object StaticAA { + | class StaticAAA + | } + |} + | + |object Main { + | val AStaticAA = new A.StaticAA + | def main(args: Array[String]): Unit = { + | val a = new A + | val aAA = new a.AA + | val aAAaaa1 = new aAA.AAA(42) + | val aAAaaa2 = new aAA.AAA(43) + | println("ok") + | } + |} + |""".stripMargin + + val outerPreEval = + """|package example + | + |class A { + | val x = 42 + | class C + | object C { def life = x } + |} + | + |class B extends A { + | val y = 43 + |} + | + |object B extends A { + | val y = 84 + |} + | + |object Main { + | def main(args: Array[String]): Unit = { + | val b = new B + | val bc = new b.C + | val bC = b.C + | val Bc = new B.C + | val BC = B.C + | println("ok") + | } + |} + |""".stripMargin + val flowControl = + """|package example + | + |object Main { + | def main(args: Array[String]): Unit = { + | val x = 1 + | val t = Test(-1) + | println("ok") + | } + | + | def test(x: Int): String = s"int $x" + | def test(t: Test): String = s"test ${t.i}" + | + | case class Test(i: Int) + |}""".stripMargin } abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends DebugTestSuite { @@ -259,6 +370,16 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb lazy val cls = TestingDebuggee.mainClass(RuntimeEvaluatorEnvironments.cls, "example.Main", scalaVersion) lazy val boxingOverloads = TestingDebuggee.mainClass(RuntimeEvaluatorEnvironments.boxingOverloads, "example.Main", scalaVersion) + lazy val arrays = + TestingDebuggee.mainClass(RuntimeEvaluatorEnvironments.arraysSource, "example.Main", scalaVersion) + lazy val collections = + TestingDebuggee.mainClass(RuntimeEvaluatorEnvironments.collectionSource, "example.Main", scalaVersion) + lazy val inners = + TestingDebuggee.mainClass(RuntimeEvaluatorEnvironments.innerInstantiation, "example.Main", scalaVersion) + lazy val outerPreEval = + TestingDebuggee.mainClass(RuntimeEvaluatorEnvironments.outerPreEval, "example.Main", scalaVersion) + lazy val controlFlow = + TestingDebuggee.mainClass(RuntimeEvaluatorEnvironments.flowControl, "example.Main", scalaVersion) protected override def defaultConfig: DebugConfig = super.defaultConfig.copy(evaluationMode = DebugConfig.RuntimeEvaluationOnly) @@ -398,20 +519,7 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb } test("Should work on arrays") { - val arraysSource = - """|package example - | - |object Main { - | def main(args: Array[String]): Unit = { - | val arr = Array(1, 2, 3) - | val sh: Short = 2 - | val ch: Char = 2 - | val by: Byte = 2 - | println("ok") - | } - |} - |""".stripMargin - implicit val debuggee = TestingDebuggee.mainClass(arraysSource, "example.Main", scalaVersion) + implicit val debuggee = arrays check( Breakpoint(9), Evaluation.success("arr(0)", 1), @@ -421,26 +529,14 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb Evaluation.success("arr(by)", 3), Evaluation.success("arr(new Integer(2))", 3), Evaluation.success("arr(new Character('\u0000'))", 1), - Evaluation.failed("arr(3)") + Evaluation.failed("arr(3)"), + Evaluation.success("test(arr)", "1,2,3"), + Evaluation.failed("test(Array(Test(1), Test(2), Test(3)))") ) } test("Should work on collections") { - val collectionSource = - """|package example - | - |object Main { - | def main(args: Array[String]): Unit = { - | val list = List(1, 2, 3) - | val map = Map(1 -> "one", 2 -> "two") - | val set = Set(1, 2, 3) - | val seq = Seq(1, 2, 3) - | val vector = Vector(1, 2, 3) - | println("ok") - | } - |} - |""".stripMargin - implicit val debuggee = TestingDebuggee.mainClass(collectionSource, "example.Main", scalaVersion) + implicit val debuggee = collections check( Breakpoint(10), Evaluation.success("list(0).toString", "1"), @@ -624,96 +720,49 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb } test("Should instantiate inner classes") { - val source = - """|package example - | - |class A { - | val test = { - | 42 - | println("ok") - | } - | class AA { - | class AAA(val x: Int) - | } - | object AA { - | class StaticAAA - | } - |} - | - |object A { - | class StaticAA { - | class AAA - | } - | object StaticAA { - | class StaticAAA - | } - |} - | - |object Main { - | val AStaticAA = new A.StaticAA - | def main(args: Array[String]): Unit = { - | val a = new A - | a.test - | val aAA = new a.AA - | val aAAaaa1 = new aAA.AAA(42) - | val aAAaaa2 = new aAA.AAA(43) - | println("ok") - | } - |} - |""".stripMargin - implicit val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + implicit val debuggee = inners check( - Breakpoint(6), - Evaluation.success("new AA", ObjectRef("A$AA")), - Breakpoint(33), - Evaluation.success("new a.AA", ObjectRef("A$AA")), - Evaluation.success("new aAA.AAA(42)", ObjectRef("A$AA$AAA")), - Evaluation.success("new a.AA.StaticAAA", ObjectRef("A$AA$StaticAAA")), - Evaluation.success("new A.StaticAA", ObjectRef("A$StaticAA")), - Evaluation.success("new AStaticAA.AAA", ObjectRef("A$StaticAA$AAA")), - Evaluation.success("new this.AStaticAA.AAA", ObjectRef("A$StaticAA$AAA")), - Evaluation.success("new A.StaticAA.StaticAAA", ObjectRef("A$StaticAA$StaticAAA")), - Evaluation.success("aAAaaa1.x", 42), - Evaluation.success("aAAaaa2.x", 43) + Breakpoint(28), + DebugStepAssert.inParallel( + Evaluation.success("new a.AA") { res => res.startsWith("A$AA@") }, + Evaluation.success("new aAA.AAA(42)") { res => res.startsWith("A$AA$AAA@") }, + Evaluation.success("new a.AA.StaticAAA") { res => res.startsWith("A$AA$StaticAAA@") }, + Evaluation.success("new A.StaticAA") { res => res.startsWith("A$StaticAA@") }, + Evaluation.success("new AStaticAA.AAA") { res => res.startsWith("A$StaticAA$AAA@") }, + Evaluation.success("new this.AStaticAA.AAA") { res => res.startsWith("A$StaticAA$AAA@") }, + Evaluation.success("new A.StaticAA.StaticAAA") { res => res.startsWith("A$StaticAA$StaticAAA@") }, + Evaluation.success("aAAaaa1.x", 42), + Evaluation.success("aAAaaa2.x", 43) + ) ) } test("Should pre-evaluate $outer") { - val source = - """|package example - | - |class A { - | val x = 42 - | class C - | object C { def life = x } - |} - | - |class B extends A { - | val y = 43 - |} - | - |object B extends A { - | val y = 84 - |} - | - |object Main { - | def main(args: Array[String]): Unit = { - | val b = new B - | val bc = new b.C - | val bC = b.C - | val Bc = new B.C - | val BC = B.C - | println("ok") - | } - |} - |""".stripMargin - implicit val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + implicit val debuggee = outerPreEval check( Breakpoint(24), - Evaluation.success("bc.y", 43), - Evaluation.success("bC.y", 43), - Evaluation.success("Bc.y", 84), - Evaluation.success("BC.y", 84) + DebugStepAssert.inParallel( + Evaluation.success("bc.y", 43), + Evaluation.success("bC.y", 43), + Evaluation.success("Bc.y", 84), + Evaluation.success("BC.y", 84) + ) + ) + } + + test("Should evaluate if control flows") { + implicit val debuggee = controlFlow + check( + Breakpoint(7), + Evaluation.success("if (true) 1 else 2", 1), + Evaluation.success("if (x == 1) 2 else 1", 2), + Evaluation.success("if (x == 1) \"a string\" else 1", "a string"), + Evaluation.success("test(if(true) 1 else 2)", "int 1"), + Evaluation.success("test(if(false) x else t)", "test -1"), + Evaluation.success("test(if(true) x else t)", "int 1"), + Evaluation.success("(if(true) Test(-1) else x).i", -1), + Evaluation.success("(if(false) x else Test(-1)).i", -1), + Evaluation.failed("test(if(Test(-1).i == -1) Test(-1) else x)") ) } } From 5dfb4e35228749949277b25fd34d12c08f8d9f5e Mon Sep 17 00:00:00 2001 From: Lucas Nouguier Date: Tue, 4 Jul 2023 17:38:24 +0200 Subject: [PATCH 2/6] added common ancestor search --- .../evaluator/RuntimeDefaultValidator.scala | 8 +- .../evaluator/RuntimeEvaluationHelpers.scala | 40 ++++++- .../internal/RuntimeEvaluatorTests.scala | 103 ++++++++++-------- 3 files changed, 105 insertions(+), 46 deletions(-) diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala index 80a32b84b..4316d986d 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala @@ -264,7 +264,13 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val logger: Logger) extends R cond <- validate(tree.cond) thenp <- validate(tree.thenp) elsep <- validate(tree.elsep) - ifTree <- IfTree(cond, thenp, elsep, isAssignableFrom(_, _), objType) + ifTree <- IfTree( + cond, + thenp, + elsep, + isAssignableFrom(_, _), + extractCommonType(thenp.`type`, elsep.`type`).getOrElse(objType) + ) } yield ifTree } } diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala index d0894e24c..1a2d0c851 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala @@ -11,6 +11,7 @@ import scala.meta.{Type => MType} import scala.util.Failure import scala.util.Try import scala.jdk.CollectionConverters.* +import scala.annotation.tailrec private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) { import RuntimeEvaluationHelpers.* @@ -215,7 +216,44 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) { loop(qual) } - def validateType(tpe: MType, thisType: Option[RuntimeEvaluableTree])( + + @tailrec + private def getAncestors( + of: Type, + depth: Int = 0, + acc: List[(ClassType, Int)] = List() + ): List[(ReferenceType, Int)] = { + of match { + case cls: ClassType => + val spr = cls.superclass() + if (spr != null) getAncestors(spr, depth + 1, (cls, depth) :: acc) + else (cls, depth) :: acc + case _ => acc + } + } + + def extractCommonType(tpe1: Type, tpe2: Type): Option[Type] = { + lazy val commonAncestors = for { + ancestors1 <- getAncestors(tpe1) + ancestors2 <- getAncestors(tpe2) + if ancestors1._1.equals(ancestors2._1) + } yield (ancestors1, ancestors2) + + if (tpe1.equals(tpe2)) Some(tpe1) + else if (commonAncestors.isEmpty) None + else + Some { + commonAncestors + .reduce { (acc, elem) => + if (acc._1._2 < elem._1._2 && acc._2._2 < elem._2._2) acc + else elem + } + ._1 + ._1 + } + } + + def validateType(tpe: MType, thisTypeName: Option[String])( termValidation: Term => Validation[RuntimeEvaluableTree] ): Validation[(Option[RuntimeEvaluableTree], ClassTree)] = tpe match { diff --git a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala index 4f0b0d6c8..b9c75b233 100644 --- a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala +++ b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala @@ -344,6 +344,13 @@ object RuntimeEvaluatorEnvironments { |""".stripMargin val flowControl = """|package example + | + |class A { + | val x: String = "a" + |} + | + |class B extends A + |class C extends A | |object Main { | def main(args: Array[String]): Unit = { @@ -354,6 +361,7 @@ object RuntimeEvaluatorEnvironments { | | def test(x: Int): String = s"int $x" | def test(t: Test): String = s"test ${t.i}" + | def isTrue = true | | case class Test(i: Int) |}""".stripMargin @@ -522,16 +530,18 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb implicit val debuggee = arrays check( Breakpoint(9), - Evaluation.success("arr(0)", 1), - Evaluation.success("arr(2)", 3), - Evaluation.success("arr(sh)", 3), - Evaluation.success("arr(ch)", 3), - Evaluation.success("arr(by)", 3), - Evaluation.success("arr(new Integer(2))", 3), - Evaluation.success("arr(new Character('\u0000'))", 1), - Evaluation.failed("arr(3)"), - Evaluation.success("test(arr)", "1,2,3"), - Evaluation.failed("test(Array(Test(1), Test(2), Test(3)))") + DebugStepAssert.inParallel( + Evaluation.success("arr(0)", 1), + Evaluation.success("arr(2)", 3), + Evaluation.success("arr(sh)", 3), + Evaluation.success("arr(ch)", 3), + Evaluation.success("arr(by)", 3), + Evaluation.success("arr(new Integer(2))", 3), + Evaluation.success("arr(new Character('\u0000'))", 1), + Evaluation.failed("arr(3)"), + Evaluation.success("test(arr)", "1,2,3"), + Evaluation.failed("test(Array(Test(1), Test(2), Test(3)))") + ) ) } @@ -539,30 +549,32 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb implicit val debuggee = collections check( Breakpoint(10), - Evaluation.success("list(0).toString", "1"), - Evaluation.success("list(2).toString", "3"), - Evaluation.success("list(new Integer(2)).toString", "3"), - Evaluation.successOrIgnore("list(new Character('\u0000')).toString", "1", true), - Evaluation.failed("list(3)"), - Evaluation.success("map(1)", "one"), - Evaluation.success("map(2)", "two"), - Evaluation.success("map(new Integer(2))", "two"), - Evaluation.successOrIgnore("map(new Character('\u0000'))", "one", true), - Evaluation.failed("map(3)"), - Evaluation.success("set(1)", true), - Evaluation.success("set(2)", true), - Evaluation.success("set(new Integer(2))", true), - Evaluation.successOrIgnore("set(new Character('\u0000')).toStrign", "1", true), - Evaluation.success("set(4)", false), - Evaluation.success("seq(0).toString", "1"), - Evaluation.success("seq(2).toString", "3"), - Evaluation.success("seq(new Integer(2)).toString", "3"), - Evaluation.successOrIgnore("seq(new Character('\u0000')).toString", "1", true), - Evaluation.failed("seq(3)"), - Evaluation.success("vector(0).toString", "1"), - Evaluation.success("vector(2).toString", "3"), - Evaluation.success("vector(new Integer(2)).toString", "3"), - Evaluation.successOrIgnore("vector(new Character('\u0000'))", 1, true) + DebugStepAssert.inParallel( + Evaluation.success("list(0).toString", "1"), + Evaluation.success("list(2).toString", "3"), + Evaluation.success("list(new Integer(2)).toString", "3"), + Evaluation.successOrIgnore("list(new Character('\u0000')).toString", "1", true), + Evaluation.failed("list(3)"), + Evaluation.success("map(1)", "one"), + Evaluation.success("map(2)", "two"), + Evaluation.success("map(new Integer(2))", "two"), + Evaluation.successOrIgnore("map(new Character('\u0000'))", "one", true), + Evaluation.failed("map(3)"), + Evaluation.success("set(1)", true), + Evaluation.success("set(2)", true), + Evaluation.success("set(new Integer(2))", true), + Evaluation.successOrIgnore("set(new Character('\u0000')).toStrign", "1", true), + Evaluation.success("set(4)", false), + Evaluation.success("seq(0).toString", "1"), + Evaluation.success("seq(2).toString", "3"), + Evaluation.success("seq(new Integer(2)).toString", "3"), + Evaluation.successOrIgnore("seq(new Character('\u0000')).toString", "1", true), + Evaluation.failed("seq(3)"), + Evaluation.success("vector(0).toString", "1"), + Evaluation.success("vector(2).toString", "3"), + Evaluation.success("vector(new Integer(2)).toString", "3"), + Evaluation.successOrIgnore("vector(new Character('\u0000'))", 1, true) + ) ) } @@ -753,16 +765,19 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb test("Should evaluate if control flows") { implicit val debuggee = controlFlow check( - Breakpoint(7), - Evaluation.success("if (true) 1 else 2", 1), - Evaluation.success("if (x == 1) 2 else 1", 2), - Evaluation.success("if (x == 1) \"a string\" else 1", "a string"), - Evaluation.success("test(if(true) 1 else 2)", "int 1"), - Evaluation.success("test(if(false) x else t)", "test -1"), - Evaluation.success("test(if(true) x else t)", "int 1"), - Evaluation.success("(if(true) Test(-1) else x).i", -1), - Evaluation.success("(if(false) x else Test(-1)).i", -1), - Evaluation.failed("test(if(Test(-1).i == -1) Test(-1) else x)") + Breakpoint(14), + DebugStepAssert.inParallel( + Evaluation.success("if (true) 1 else 2", 1), + Evaluation.success("if (x == 1) 2 else 1", 2), + Evaluation.success("if (x == 1) \"a string\" else 1", "a string"), + Evaluation.success("test(if(true) 1 else 2)", "int 1"), + Evaluation.success("test(if(false) x else t)", "test -1"), + Evaluation.success("test(if(true) x else t)", "int 1"), + Evaluation.success("(if(true) Test(-1) else x).i", -1), + Evaluation.success("(if(false) x else Test(-1)).i", -1), + Evaluation.failed("test(if(Test(-1).i == -1) Test(-1) else x)"), + Evaluation.success("(if (isTrue) new B else new C).x", "a") + ) ) } } From d4b57df4479f17a93a6a8b1074c94dc4a26ced70 Mon Sep 17 00:00:00 2001 From: Lucas Nouguier Date: Wed, 5 Jul 2023 10:28:27 +0200 Subject: [PATCH 3/6] fixes #483 --- .../evaluator/RuntimeDefaultValidator.scala | 19 +++++++++++++------ .../evaluator/RuntimeEvaluationHelpers.scala | 2 +- .../RuntimePreEvaluationValidator.scala | 3 --- .../internal/RuntimeEvaluatorTests.scala | 3 ++- 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala index 4316d986d..128278204 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala @@ -152,12 +152,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) } } /* -------------------------------------------------------------------------- */ diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala index 1a2d0c851..c11473d02 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala @@ -253,7 +253,7 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) { } } - def validateType(tpe: MType, thisTypeName: Option[String])( + def validateType(tpe: MType, thisType: Option[RuntimeEvaluableTree])( termValidation: Term => Validation[RuntimeEvaluableTree] ): Validation[(Option[RuntimeEvaluableTree], ClassTree)] = tpe match { diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala index b9efc1bdd..57cfa33b4 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimePreEvaluationValidator.scala @@ -16,9 +16,6 @@ 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) diff --git a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala index b9c75b233..81729cc97 100644 --- a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala +++ b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala @@ -776,7 +776,8 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb Evaluation.success("(if(true) Test(-1) else x).i", -1), Evaluation.success("(if(false) x else Test(-1)).i", -1), Evaluation.failed("test(if(Test(-1).i == -1) Test(-1) else x)"), - Evaluation.success("(if (isTrue) new B else new C).x", "a") + Evaluation.success("(if (isTrue) new B else new C).x", "a"), + Evaluation.failed("(if (isTrue) 1 else \"a string\").x") ) ) } From 396bef85fc2c8c4f9abb88249055efcdac085872 Mon Sep 17 00:00:00 2001 From: Lucas Nouguier Date: Wed, 5 Jul 2023 10:46:40 +0200 Subject: [PATCH 4/6] simplified common ancestor search --- .../evaluator/RuntimeDefaultValidator.scala | 2 +- .../evaluator/RuntimeEvaluationHelpers.scala | 52 ++++++------------- 2 files changed, 16 insertions(+), 38 deletions(-) diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala index 128278204..8aaab0ac8 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala @@ -276,7 +276,7 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val logger: Logger) extends R thenp, elsep, isAssignableFrom(_, _), - extractCommonType(thenp.`type`, elsep.`type`).getOrElse(objType) + extractCommonSuperClass(thenp.`type`, elsep.`type`).getOrElse(objType) ) } yield ifTree } diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala index c11473d02..1a1d74b47 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala @@ -1,17 +1,18 @@ 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 scala.annotation.tailrec + +import RuntimeEvaluatorExtractors.* private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) { import RuntimeEvaluationHelpers.* @@ -217,40 +218,17 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) { loop(qual) } - @tailrec - private def getAncestors( - of: Type, - depth: Int = 0, - acc: List[(ClassType, Int)] = List() - ): List[(ReferenceType, Int)] = { - of match { - case cls: ClassType => - val spr = cls.superclass() - if (spr != null) getAncestors(spr, depth + 1, (cls, depth) :: acc) - else (cls, depth) :: acc - case _ => acc - } - } - - def extractCommonType(tpe1: Type, tpe2: Type): Option[Type] = { - lazy val commonAncestors = for { - ancestors1 <- getAncestors(tpe1) - ancestors2 <- getAncestors(tpe2) - if ancestors1._1.equals(ancestors2._1) - } yield (ancestors1, ancestors2) - - if (tpe1.equals(tpe2)) Some(tpe1) - else if (commonAncestors.isEmpty) None - else - Some { - commonAncestors - .reduce { (acc, elem) => - if (acc._1._2 < elem._1._2 && acc._2._2 < elem._2._2) acc - else elem - } - ._1 - ._1 + 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])( From 2f14ff85360021e1ee1273f1ed9c60cfc1bb3498 Mon Sep 17 00:00:00 2001 From: Lucas Nouguier Date: Mon, 3 Jul 2023 16:00:08 +0200 Subject: [PATCH 5/6] fix naming conflicts --- .../evaluator/RuntimeDefaultValidator.scala | 6 ++--- .../evaluator/RuntimeEvaluationHelpers.scala | 8 ++++-- .../RuntimeEvaluatorExtractors.scala | 10 +++++++ .../internal/evaluator/RuntimeTree.scala | 5 ++-- .../internal/RuntimeEvaluatorTests.scala | 27 +++++++++++++++++++ 5 files changed, 48 insertions(+), 8 deletions(-) diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala index 8aaab0ac8..52cc84975 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala @@ -175,7 +175,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], @@ -199,8 +199,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)) } diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala index 1a1d74b47..9cec8f46d 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluationHelpers.scala @@ -120,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")) } @@ -275,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 } diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluatorExtractors.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluatorExtractors.scala index 8ab59827b..2f3f04e69 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluatorExtractors.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluatorExtractors.scala @@ -25,6 +25,16 @@ 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): Boolean = tree match { diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala index ba542e421..5442c7648 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala @@ -138,10 +138,9 @@ case class ArrayElemTree private (array: RuntimeEvaluableTree, index: RuntimeEva } object ArrayElemTree { - def apply(tree: RuntimeTree, funName: String, index: Seq[RuntimeEvaluableTree]): Validation[ArrayElemTree] = { + def apply(tree: RuntimeTree, index: Seq[RuntimeEvaluableTree]): Validation[ArrayElemTree] = { val integerTypes = Seq("java.lang.Integer", "java.lang.Short", "java.lang.Byte", "java.lang.Character") - if (funName != "apply") Recoverable("Not an array accessor") - else if (index.size < 1 || index.size > 1) Recoverable("Array accessor must have one argument") + if (index.size < 1 || index.size > 1) Recoverable("Array accessor must have one argument") else (tree, tree.`type`) match { case (tree: RuntimeEvaluableTree, arr: ArrayType) => diff --git a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala index 81729cc97..b3908b52f 100644 --- a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala +++ b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala @@ -365,6 +365,20 @@ object RuntimeEvaluatorEnvironments { | | case class Test(i: Int) |}""".stripMargin + + val staticAccess = + """|package example + | + |object Main { + | def main(args: Array[String]): Unit = { + | val x = 1 + | val test = Test(-1) + | println("ok") + | } + | def test(x: Int): String = s"int $x" + | def test(t: Test): String = s"test ${t.i}" + | case class Test(i: Int) + |}""".stripMargin } abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends DebugTestSuite { @@ -388,6 +402,8 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb TestingDebuggee.mainClass(RuntimeEvaluatorEnvironments.outerPreEval, "example.Main", scalaVersion) lazy val controlFlow = TestingDebuggee.mainClass(RuntimeEvaluatorEnvironments.flowControl, "example.Main", scalaVersion) + lazy val staticAccess = + TestingDebuggee.mainClass(RuntimeEvaluatorEnvironments.staticAccess, "example.Main", scalaVersion) protected override def defaultConfig: DebugConfig = super.defaultConfig.copy(evaluationMode = DebugConfig.RuntimeEvaluationOnly) @@ -781,6 +797,17 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb ) ) } + + test( + "Should not call the apply method when calling a method with the same name as an instance with an apply method" + ) { + implicit val debuggee = staticAccess + check( + Breakpoint(7), + Evaluation.success("test(-1)", "int -1"), + Evaluation.success("test(test)", "test -1") + ) + } } /* -------------------------------------------------------------------------- */ From c7b6313ac7f6126e08921664f9038badfaa18b0e Mon Sep 17 00:00:00 2001 From: Lucas Nouguier Date: Mon, 3 Jul 2023 16:28:06 +0200 Subject: [PATCH 6/6] evaluate blocks --- .../internal/evaluator/RuntimeDefaultEvaluator.scala | 1 + .../internal/evaluator/RuntimeDefaultValidator.scala | 10 ++++++++++ .../evaluator/RuntimeEvaluatorExtractors.scala | 2 +- .../debugadapter/internal/evaluator/RuntimeTree.scala | 4 ++++ .../debugadapter/internal/RuntimeEvaluatorTests.scala | 10 ++++++++++ 5 files changed, 26 insertions(+), 1 deletion(-) diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultEvaluator.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultEvaluator.scala index 21edc9106..ef995635b 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultEvaluator.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultEvaluator.scala @@ -24,6 +24,7 @@ class RuntimeDefaultEvaluator(val frame: JdiFrame, val logger: Logger) extends R 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)) } /* -------------------------------------------------------------------------- */ diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala index 52cc84975..2ed1ee343 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeDefaultValidator.scala @@ -39,6 +39,7 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val logger: Logger) extends R case select: Term.Select => validateSelect(select) 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") } @@ -53,6 +54,15 @@ 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 */ /* -------------------------------------------------------------------------- */ diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluatorExtractors.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluatorExtractors.scala index 2f3f04e69..85588639b 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluatorExtractors.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeEvaluatorExtractors.scala @@ -44,7 +44,7 @@ protected[internal] object RuntimeEvaluatorExtractors { case OuterModuleTree(module) => unapply(module) case IfTree(p, t, f, _) => unapply(p) || unapply(t) || unapply(f) case _: MethodTree | _: NewInstanceTree => true - case _: LiteralTree | _: LocalVarTree | _: PreEvaluatedTree | _: ThisTree => false + case _: LiteralTree | _: LocalVarTree | _: PreEvaluatedTree | _: ThisTree | UnitTree => false case _: StaticFieldTree | _: ClassTree | _: TopLevelModuleTree => false case _: PrimitiveBinaryOpTree | _: PrimitiveUnaryOpTree | _: ArrayElemTree => false } diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala index 5442c7648..d6525a201 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/RuntimeTree.scala @@ -35,6 +35,10 @@ sealed trait OuterTree extends RuntimeEvaluableTree /* -------------------------------------------------------------------------- */ /* Simple trees */ /* -------------------------------------------------------------------------- */ +case object UnitTree extends RuntimeEvaluableTree { + override def `type`: Type = ??? + override def prettyPrint(depth: Int): String = "UnitTree()" +} case class LiteralTree private ( value: Safe[Any], `type`: Type diff --git a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala index b3908b52f..2f8ae3a46 100644 --- a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala +++ b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/RuntimeEvaluatorTests.scala @@ -55,6 +55,7 @@ object RuntimeEvaluatorEnvironments { | private val lapinou = "lapinou" | def bar(str: String, int: Int): String = str + int | def keepTheRabbit: String = lapinou + | def bar(i: Int) = s"bar ${i}" |} | |object Foo { val foofoo = "foofoo" } @@ -808,6 +809,15 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb Evaluation.success("test(test)", "test -1") ) } + + test("Should evaluate blocks") { + implicit val debuggee = field + check( + Breakpoint(8), + Evaluation.success("{ 1+1; 2+2; lapin}", "lapin"), + Evaluation.success("f1.bar { 1+1; 2+2 }", "bar 4") + ) + } } /* -------------------------------------------------------------------------- */