Skip to content

Commit

Permalink
merge changes
Browse files Browse the repository at this point in the history
  • Loading branch information
aymanelamyaghri committed Jul 11, 2023
2 parents c326a30 + cb35a4d commit 563bfe5
Show file tree
Hide file tree
Showing 15 changed files with 143 additions and 128 deletions.
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,16 @@ jobs:
- uses: coursier/setup-action@v1.3.3
with:
apps: sbt
- run: sbt +compile
- run: sbt +Test/compile
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
jvm: ['adopt:1.8.0-292', 'adopt:1.11.0.11', 'temurin:1.17.0.3']
jvm: ['adoptium:1.8.0-372', 'adoptium:1.11.0.19', 'adoptium:1.17.0.7']
include:
- os: ubuntu-latest
jvm: 'adoptium:1.20.0.1'
name: Test on ${{ matrix.os }} -- ${{ matrix.jvm }}
runs-on: ${{ matrix.os }}
steps:
Expand All @@ -44,3 +47,4 @@ jobs:
- name: Scripted sbt tests
run: sbt sbtPlugin/scripted
shell: bash
if: ${{ matrix.jvm != 'adoptium:1.20.0.1' }}
2 changes: 1 addition & 1 deletion .scalafmt.conf
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
version = "3.7.7"
version = "3.7.8"
project.git = true
align.preset = none
align.stripMargin = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,17 +246,11 @@ class RuntimeDefaultValidator(val frame: JdiFrame, val logger: Logger) extends R
def validateNew(newValue: Term.New): Validation[RuntimeEvaluableTree] = {
val tpe = newValue.init.tpe
val argClauses = newValue.init.argClauses

for {
args <- argClauses.flatMap(_.map(validate(_))).traverse
outerFqcn = thisTree.toOption
cls <- validateType(tpe, outerFqcn)(validate)
qualInjectedArgs = cls._1 match {
case Some(q) => q +: args
case None => args
}
method <- methodTreeByNameAndArgs(cls._2, "<init>", qualInjectedArgs, encode = false)
newInstance <- NewInstanceTree(method)
(outer, cls) <- validateType(tpe, thisTree.toOption)(validate)
allArgs = outer.filter(_ => needsOuter(cls.`type`)).toSeq ++ args
newInstance <- newInstanceTreeByArgs(cls.`type`, allArgs)
} yield newInstance
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,10 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) {
*/
private def methodsByNameAndArgs(
ref: ReferenceType,
funName: String,
args: Seq[Type],
encode: Boolean = true
encodedName: String,
args: Seq[Type]
): Validation[Method] = {
val candidates: List[Method] = ref.methodsByName {
if (encode) NameTransformer.encode(funName) else funName
}.asScalaList
val candidates: List[Method] = ref.methodsByName(encodedName).asScalaList

val unboxedCandidates = candidates.filter { argsMatch(_, args, boxing = false) }

Expand All @@ -109,25 +106,41 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) {
}

finalCandidates
.toValidation(s"Cannot find a proper method $funName with args types $args on $ref")
.toValidation(s"Cannot find a proper method $encodedName with args types $args on $ref")
.map { loadClassOnNeed }
}

def methodTreeByNameAndArgs(
tree: RuntimeTree,
funName: String,
args: Seq[RuntimeEvaluableTree],
encode: Boolean = true
args: Seq[RuntimeEvaluableTree]
): Validation[MethodTree] = tree match {
case ReferenceTree(ref) =>
methodsByNameAndArgs(ref, funName, args.map(_.`type`), encode).flatMap {
methodsByNameAndArgs(ref, NameTransformer.encode(funName), args.map(_.`type`)).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"))
}

def needsOuter(cls: ClassType): Boolean =
cls
.methodsByName("<init>")
.asScala
.filter(init => init.declaringType.name == cls.name)
.forall { init =>
init.argumentTypeNames.asScala.headOption
.exists { argType =>
val suffix = argType.stripSuffix("$") + "$"
cls.name.startsWith(suffix) && cls.name.size > suffix.size
}
}

def newInstanceTreeByArgs(cls: ClassType, args: Seq[RuntimeEvaluableTree]): Validation[NewInstanceTree] =
methodsByNameAndArgs(cls, "<init>", args.map(_.`type`))
.map(m => NewInstanceTree(StaticMethodTree(m, args, cls)))

/* -------------------------------------------------------------------------- */
/* Type checker */
/* -------------------------------------------------------------------------- */
Expand Down Expand Up @@ -235,25 +248,21 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) {
superClasses1.find(superClasses2.contains)
}

def validateType(tpe: MType, thisType: Option[RuntimeEvaluableTree])(
def validateType(tpe: MType, thisTree: Option[RuntimeEvaluableTree])(
termValidation: Term => Validation[RuntimeEvaluableTree]
): Validation[(Option[RuntimeEvaluableTree], ClassTree)] =
tpe match {
case MType.Name(name) =>
searchAllClassesFor(name, thisType.map(_.`type`.name)).map { cls =>
outerLookup(cls.`type`) match {
case Valid(_) => (thisType, cls)
case _: Invalid => (None, cls)
}
}
// won't work if the class is defined in one of the outer of this
searchAllClassesFor(name, thisTree.map(_.`type`.name)).map(cls => (thisTree, cls))
case MType.Select(qual, name) =>
val cls = for {
qual <- termValidation(qual)
tpe <- resolveInnerType(qual.`type`, name.value)
} yield
if (tpe.isStatic()) (None, ClassTree(tpe))
else (Some(qual), ClassTree(tpe))
cls.orElse(searchAllClassesFor(qual.toString + "." + name.value, thisType.map(_.`type`.name)).map((None, _)))
cls.orElse(searchAllClassesFor(qual.toString + "." + name.value, thisTree.map(_.`type`.name)).map((None, _)))
case _ => Recoverable("Type not supported at runtime")
}

Expand All @@ -262,20 +271,13 @@ private[evaluator] class RuntimeEvaluationHelpers(frame: JdiFrame) {
Validation(ref.fieldByName("$outer"))
.map(_.`type`())
.orElse {
// outer static object
removeLastInnerTypeFromFQCN(ref.name())
.map(name => loadClass(name + "$").extract) match {
case Some(Success(Module(mod))) => Valid(mod)
case _ => Recoverable(s"Cannot find $$outer for $ref")
}
}
.orElse { // For Java compatibility
ref
.fields()
.asScala
.collect { case f if f.name().startsWith("this$") => f.`type`() }
.toSeq
.toValidation("Cannot find $$this$$xx for $ref")
}

def searchAllClassesFor(name: String, in: Option[String]): Validation[ClassTree] = {
def fullName = in match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,13 +224,6 @@ case class NewInstanceTree(init: StaticMethodTree) extends RuntimeEvaluableTree
}
}

object NewInstanceTree {
def apply(init: MethodTree): Validation[NewInstanceTree] = init match {
case init: StaticMethodTree => Valid(new NewInstanceTree(init))
case _ => Recoverable("New instance must be initialized with a static method")
}
}

case class OuterClassTree(
inner: RuntimeEvaluableTree,
`type`: ClassType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ sealed abstract class Validation[+A] {

def filter(p: A => Boolean, runtimeFatal: Boolean = false): Validation[A]
def filterNot(p: A => Boolean, runtimeFatal: Boolean = false): Validation[A] = filter(!p(_), runtimeFatal)
def withFilter(p: A => Boolean): Validation[A] = filter(p)

def map[B](f: A => B): Validation[B]
def flatMap[B](f: A => Validation[B]): Validation[B]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ class Scala2Unpickler(
scalaVersion: ScalaVersion,
logger: Logger,
testMode: Boolean
) extends ScalaUnpickler(scalaVersion) {
) extends ScalaUnpickler(scalaVersion, testMode) {
override protected def skipScala(method: jdi.Method): Boolean = {
if (isLazyInitializer(method)) {
skipLazyInitializer(method)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ import ch.epfl.scala.debugadapter.Java8
import ch.epfl.scala.debugadapter.Java9OrAbove
import scala.util.Try
import java.nio.file.Path
import scala.util.Success
import scala.util.Failure
import java.util.Optional
import ch.epfl.scala.debugadapter.ScalaVersion
import scala.jdk.OptionConverters._
Expand All @@ -20,8 +18,9 @@ class Scala3UnpicklerBridge(
scalaVersion: ScalaVersion,
bridge: Any,
skipMethod: Method,
formatMethod: Method
) extends ScalaUnpickler(scalaVersion) {
formatMethod: Method,
testMode: Boolean
) extends ScalaUnpickler(scalaVersion, testMode) {
override protected def skipScala(method: jdi.Method): Boolean = {
try skipMethod.invoke(bridge, method).asInstanceOf[Boolean]
catch {
Expand All @@ -47,7 +46,7 @@ object Scala3UnpicklerBridge {
logger: Logger,
testMode: Boolean
): Try[Scala3UnpicklerBridge] = {
try {
Try {
val className = "ch.epfl.scala.debugadapter.internal.stacktrace.Scala3Unpickler"
val cls = classLoader.loadClass(className)
val ctr = cls.getConstructor(classOf[Array[Path]], classOf[Consumer[String]], classOf[Boolean])
Expand All @@ -68,9 +67,7 @@ object Scala3UnpicklerBridge {
val skipMethod = cls.getMethods.find(m => m.getName == "skipMethod").get
val formatMethod = cls.getMethods.find(m => m.getName == "formatMethod").get

Success(new Scala3UnpicklerBridge(debuggee.scalaVersion, bridge, skipMethod, formatMethod))
} catch {
case cause: Throwable => Failure(cause)
new Scala3UnpicklerBridge(debuggee.scalaVersion, bridge, skipMethod, formatMethod, testMode)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,16 @@ import com.sun.jdi.Method
import com.sun.jdi.ReferenceType

import scala.jdk.CollectionConverters.*
import scala.util.control.NonFatal

abstract class ScalaUnpickler(scalaVersion: ScalaVersion) extends StepFilter {
abstract class ScalaUnpickler(scalaVersion: ScalaVersion, testMode: Boolean) extends StepFilter {
protected def skipScala(method: Method): Boolean
protected def formatScala(method: Method): Option[String] = Some(formatJava(method))

private def throwOrWarn(exception: Throwable): Unit =
if (testMode) throw exception
else exception.getMessage

override def shouldSkipOver(method: Method): Boolean = {
if (method.isBridge) true
else if (isDynamicClass(method.declaringType)) true
Expand All @@ -31,7 +36,11 @@ abstract class ScalaUnpickler(scalaVersion: ScalaVersion) extends StepFilter {
else if (scalaVersion.isScala2 && isNestedClass(method.declaringType)) false
else if (isDefaultValue(method)) false
else if (isTraitInitializer(method)) skipTraitInitializer(method)
else skipScala(method)
else
try skipScala(method)
catch {
case NonFatal(e) => throwOrWarn(e); false
}
}

def format(method: Method): Option[String] = {
Expand All @@ -48,7 +57,11 @@ abstract class ScalaUnpickler(scalaVersion: ScalaVersion) extends StepFilter {
// TODO in Scala 3 we should be able to find the symbol of a local class using TASTy Query
else if (isLocalClass(method.declaringType)) Some(formatJava(method))
else if (scalaVersion.isScala2 && isNestedClass(method.declaringType)) Some(formatJava(method))
else formatScala(method)
else
try formatScala(method)
catch {
case NonFatal(e) => throwOrWarn(e); Some(formatJava(method))
}
}

private def formatJava(method: Method): String = {
Expand Down Expand Up @@ -149,10 +162,11 @@ object ScalaUnpickler {
.tryLoad(debuggee, classLoader, logger, testMode)
.warnFailure(logger, s"Cannot load step filter for Scala ${debuggee.scalaVersion}")
}
.getOrElse(fallback(debuggee.scalaVersion))
.getOrElse(fallback(debuggee.scalaVersion, testMode))
}

private def fallback(scalaVersion: ScalaVersion): ScalaUnpickler = new ScalaUnpickler(scalaVersion) {
override protected def skipScala(method: Method): Boolean = false
}
private def fallback(scalaVersion: ScalaVersion, testMode: Boolean): ScalaUnpickler =
new ScalaUnpickler(scalaVersion, testMode) {
override protected def skipScala(method: Method): Boolean = false
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ final case class FakeJdiMethod(

override def compareTo(o: Method): Int = ???

override def returnTypeName(): String = ???
override def returnTypeName(): String = returnType.name

override def argumentTypeNames(): ju.List[String] = ???

override def argumentTypes(): ju.List[Type] = ???
override def argumentTypes(): ju.List[Type] = arguments.asScala.map(_.`type`).asJava

override def isAbstract(): Boolean = ???

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb
}

test("Should work on arrays") {
implicit val debuggee = arrays
implicit val debuggee: TestingDebuggee = arrays
check(
Breakpoint(9),
DebugStepAssert.inParallel(
Expand All @@ -563,7 +563,7 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb
}

test("Should work on collections") {
implicit val debuggee = collections
implicit val debuggee: TestingDebuggee = collections
check(
Breakpoint(10),
DebugStepAssert.inParallel(
Expand Down Expand Up @@ -749,7 +749,7 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb
}

test("Should instantiate inner classes") {
implicit val debuggee = inners
implicit val debuggee: TestingDebuggee = inners
check(
Breakpoint(28),
DebugStepAssert.inParallel(
Expand All @@ -767,7 +767,7 @@ abstract class RuntimeEvaluatorTests(val scalaVersion: ScalaVersion) extends Deb
}

test("Should pre-evaluate $outer") {
implicit val debuggee = outerPreEval
implicit val debuggee: TestingDebuggee = outerPreEval
check(
Breakpoint(24),
DebugStepAssert.inParallel(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,22 +10,23 @@ class Method(val obj: Any) extends JavaReflection(obj, "com.sun.jdi.Method"):
ReferenceType(invokeMethod("declaringType"))

def arguments: Seq[LocalVariable] =
invokeMethod[java.util.List[Object]]("arguments").asScala.toSeq
.map(LocalVariable.apply(_))
invokeMethod[java.util.List[Object]]("arguments").asScala.toSeq.map(LocalVariable.apply(_))

def argumentTypes: Seq[Type] =
invokeMethod[java.util.List[Object]]("argumentTypes").asScala.toSeq.map(Type.apply(_))

def returnType: Option[Type] =
try Some(Type(invokeMethod("returnType")))
catch
case e: InvocationTargetException if e.getCause.getClass.getName == "com.sun.jdi.ClassNotLoadedException" =>
None

def isExtensionMethod: Boolean =
name.endsWith("$extension")
def returnTypeName: String = invokeMethod("returnTypeName")

def isExtensionMethod: Boolean = name.endsWith("$extension")

def isTraitInitializer: Boolean =
name == "$init$"
def isTraitInitializer: Boolean = name == "$init$"

def isClassInitializer: Boolean =
name == "<init>"
def isClassInitializer: Boolean = name == "<init>"

override def toString: String = invokeMethod("toString")
Loading

0 comments on commit 563bfe5

Please sign in to comment.