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

Fix #491: Differentiate getter and method with same name #494

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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 @@ -49,7 +58,11 @@ abstract class ScalaUnpickler(scalaVersion: ScalaVersion) extends StepFilter {
else if (isLocalClass(method.declaringType)) Some(formatJava(method))
else if (scalaVersion.isScala2 && isNestedClass(method.declaringType)) Some(formatJava(method))
else if (isDefaultValue(method)) 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 @@ -150,10 +163,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 @@ -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")
Original file line number Diff line number Diff line change
Expand Up @@ -41,46 +41,33 @@ class Scala3Unpickler(
else exception.getMessage

def skipMethod(obj: Any): Boolean =
findSymbolImpl(obj) match
case Failure(exception) => throwOrWarn(exception); false
case Success(Some(symbol)) => skip(symbol)
case Success(None) => true
findSymbol(obj).forall(skip)

def formatMethod(obj: Any): Optional[String] =
val method = jdi.Method(obj)
findSymbol(obj).map { t =>
val notMethodType = t.declaredType match {
case _: MethodType => false
case _: PolyType => false
case _ => true
}
val optionalString = if (notMethodType) ": " else ""
s"${formatSymbol(t)}${optionalString}${formatType(t.declaredType)}"
}.asJava
findSymbol(obj) match
case None => Optional.empty
case Some(symbol) =>
val sep = if !symbol.declaredType.isInstanceOf[MethodicType] then ": " else ""
Optional.of(s"${formatSymbol(symbol)}$sep${formatType(symbol.declaredType)}")

private[stacktrace] def findSymbol(obj: Any): Option[TermSymbol] =
findSymbolImpl(obj).get

private def findSymbolImpl(obj: Any): Try[Option[TermSymbol]] =
val method = jdi.Method(obj)
val fqcn = method.declaringType.name
findDeclaringType(fqcn, method.isExtensionMethod) match
case None =>
Failure(Exception(s"Cannot find Scala symbol of $fqcn"))
throw new Exception(s"Cannot find Scala symbol of $fqcn")
case Some(declaringType) =>
val matchingSymbols =
declaringType.declarations
.collect { case sym: TermSymbol if sym.isTerm => sym }
.filter(matchSymbol(method, _))

if matchingSymbols.size > 1 then
val builder = new java.lang.StringBuilder
builder.append(
s"Found ${matchingSymbols.size} matching symbols for $method:"
)
matchingSymbols.foreach(sym => builder.append(s"\n$sym"))
Failure(Exception(builder.toString))
else Success(matchingSymbols.headOption)
val message =
s"Found ${matchingSymbols.size} matching symbols for $method:" +
matchingSymbols.mkString("\n")
throw new Exception(message)
else matchingSymbols.headOption

def formatType(t: Type): String =
t match
Expand Down Expand Up @@ -249,25 +236,21 @@ class Scala3Unpickler(
else encodedScalaName == expectedName

private def matchSignature(method: jdi.Method, symbol: TermSymbol): Boolean =
try
symbol.signedName match
case SignedName(_, sig, _) =>
val javaArgs = method.arguments.headOption.map(_.name) match
case Some("$this") if method.isExtensionMethod => method.arguments.tail
case Some("$outer") if method.isClassInitializer => method.arguments.tail
case _ => method.arguments
matchArguments(sig.paramsSig, javaArgs) &&
method.returnType.forall(returnType => {
val javaRetType =
if (method.isClassInitializer) method.declaringType else returnType
matchType(sig.resSig, javaRetType)
})
case _ =>
true // TODO compare symbol.declaredType
catch
case e: UnsupportedOperationException =>
warn(e.getMessage)
true
symbol.signedName match
case SignedName(_, sig, _) =>
val javaArgs = method.arguments.headOption.map(_.name) match
case Some("$this") if method.isExtensionMethod => method.arguments.tail
case Some("$outer") if method.isClassInitializer => method.arguments.tail
case _ => method.arguments
matchArguments(sig.paramsSig, javaArgs) &&
method.returnType.forall { returnType =>
val javaRetType =
if (method.isClassInitializer) method.declaringType else returnType
matchType(sig.resSig, javaRetType)
}
case _ =>
// TODO compare symbol.declaredType
method.arguments.isEmpty

private def matchArguments(scalaArgs: Seq[ParamSig], javaArgs: Seq[jdi.LocalVariable]): Boolean =
scalaArgs
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -696,14 +696,29 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS
unpickler.assertFormat("example.example$package", "java.lang.String foo()", "example.foo: String")
}

test("i491") {
val source =
"""|package example
|
|class A {
| val m: String = ""
| def m(x: String): String = ""
|}
|""".stripMargin
val debuggee = TestingDebuggee.mainClass(source, "example", scalaVersion)
val unpickler = getUnpickler(debuggee)
unpickler.assertFormat("example.A", "java.lang.String m()", "A.m: String")
unpickler.assertFormat("example.A", "java.lang.String m(java.lang.String x)", "A.m(x: String): String")
}

private def getUnpickler(debuggee: Debuggee): Scala3Unpickler =
val javaRuntimeJars = debuggee.javaRuntime.toSeq.flatMap {
case Java8(_, classJars, _) => classJars
case java9OrAbove: Java9OrAbove =>
java9OrAbove.classSystems.map(_.fileSystem.getPath("/modules", "java.base"))
}
val debuggeeClasspath = debuggee.classPath.toArray ++ javaRuntimeJars
new Scala3Unpickler(debuggeeClasspath, println, false)
new Scala3Unpickler(debuggeeClasspath, println, testMode = true)

extension (unpickler: Scala3Unpickler)
private def assertFind(declaringType: String, javaSig: String)(using munit.Location): Unit =
Expand Down
Loading