Skip to content

Commit

Permalink
Merge pull request #533 from scalacenter/stats
Browse files Browse the repository at this point in the history
Add stats for Scala3Unpickler and fix find local enum
  • Loading branch information
adpi2 committed Jul 28, 2023
2 parents ec02508 + 1144316 commit 44a5f64
Show file tree
Hide file tree
Showing 6 changed files with 158 additions and 7 deletions.
2 changes: 1 addition & 1 deletion modules/java-debug
Submodule java-debug updated 0 files
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@ package ch.epfl.scala.debugadapter.internal.javareflect

import ch.epfl.scala.debugadapter.internal.binary
import scala.util.matching.Regex
import scala.jdk.CollectionConverters.*

class JavaReflectClass(cls: Class[?]) extends binary.ClassType:

override def name: String = cls.getTypeName

override def superclass = Option(cls.getSuperclass).map(JavaReflectClass(_))

override def interfaces = cls.getInterfaces.toList.map(JavaReflectClass(_))

override def toString: String = cls.toString

override def isInterface: Boolean = cls.isInterface()

def declaredMethods: Seq[binary.Method] =
cls.getDeclaredMethods.map(JavaReflectMethod(_)) ++
cls.getDeclaredConstructors.map(JavaReflectConstructor(_))
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@ package ch.epfl.scala.debugadapter.internal.stacktrace
import ch.epfl.scala.debugadapter.internal.binary

object LocalClass:
def unapply(cls: binary.ClassType): Option[(String, String, Option[String])] =
val decodedClassName = NameTransformer.decode(cls.name.split('.').last)
unapply(decodedClassName)

def unapply(decodedClassName: String): Option[(String, String, Option[String])] =
"(.+)\\$([^$]+)\\$\\d+(\\$.*)?".r
.unapplySeq(NameTransformer.decode(decodedClassName))
.filter(xs => xs(1) != "anon")
.map(xs => (xs(0), xs(1), Option(xs(2)).map(_.stripPrefix("$")).filter(_.nonEmpty)))

object LazyInit:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,9 @@ class Scala3Unpickler(
val superClassAndInterfaces = (cls.superclass.toSeq ++ cls.interfaces).map(findClass(_)).toSet

def matchesParents(classSymbol: ClassSymbol): Boolean =
val symbolParents =
if !cls.isInterface then classSymbol.parentClasses
else classSymbol.parentClasses.filter(_.isTrait)
superClassAndInterfaces == symbolParents.toSet
if classSymbol.isEnum then superClassAndInterfaces == classSymbol.parentClasses.toSet + ctx.defn.ProductClass
else if cls.isInterface then superClassAndInterfaces == classSymbol.parentClasses.filter(_.isTrait).toSet
else superClassAndInterfaces == classSymbol.parentClasses.toSet

collectLocalSymbols(owner) { case cls: ClassSymbol if cls.matchName(name) && matchesParents(cls) => cls }

Expand Down Expand Up @@ -399,8 +398,11 @@ class Scala3Unpickler(
extension [T <: Symbol](symbols: Seq[T])
def singleOrThrow(binaryName: String): T =
singleOptOrThrow(binaryName)
.getOrElse(throw new Exception(s"Cannot find Scala symbol of $binaryName"))
.getOrElse(throw new NotFoundException(s"Cannot find Scala symbol of $binaryName"))

def singleOptOrThrow(binaryName: String): Option[T] =
if symbols.size > 1 then throw new Exception(s"Found ${symbols.size} matching symbols for $binaryName")
if symbols.size > 1 then throw new AmbiguousException(s"Found ${symbols.size} matching symbols for $binaryName")
else symbols.headOption

case class AmbiguousException(m: String) extends Exception
case class NotFoundException(m: String) extends Exception
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package ch.epfl.scala.debugadapter.internal.stacktrace

import ch.epfl.scala.debugadapter.*
import ch.epfl.scala.debugadapter.internal.IO
import ch.epfl.scala.debugadapter.internal.binary.*
import ch.epfl.scala.debugadapter.internal.javareflect.*
import ch.epfl.scala.debugadapter.internal.stacktrace.LocalClass
import ch.epfl.scala.debugadapter.testfmk.TestingResolver
import org.objectweb.asm.ClassReader
import tastyquery.Symbols.*

import java.net.URLClassLoader
import java.nio.file.Files
import java.nio.file.Path
import scala.collection.mutable
import scala.jdk.CollectionConverters.*
import scala.util.Properties

object Scala3UnpicklerStats:
private val javaRuntime = JavaRuntime(Properties.jdkHome).get
private val javaRuntimeJars = javaRuntime match
case Java8(_, classJars, _) => classJars
case java9OrAbove: Java9OrAbove =>
java9OrAbove.classSystems.map(_.fileSystem.getPath("/modules", "java.base"))

def main(args: Array[String]): Unit =

val localClassCounter = new Counter()
val localMethodCounter = new Counter()

val jars = TestingResolver.fetch("org.scala-lang", "scala3-compiler_3", "3.3.0")
val unpickler = new Scala3Unpickler(jars.map(_.absolutePath).toArray ++ javaRuntimeJars, println, testMode = true)

for
cls <- loadClasses(jars, "scala3-compiler_3-3.3.0")
clsSym <- cls match
case LocalClass(_, _, _) => processClass(unpickler, cls, localClassCounter)
case _ => None
// case AnonClass(_, _, _) => process(cls, anonClassCounter)
// case InnerClass(_, _) => process(cls, innerClassCounter)
// case _ => process(cls, topLevelClassCounter)
method <- cls.declaredMethods
methSym <- method match
case LocalMethod(_, _) => processMethod(unpickler, method, localMethodCounter)
case _ => None

// case LocalLazyInit(_, _, _) => process(method, localClassCounter)
do ()
localClassCounter.printStatus("Local classes")
localMethodCounter.printStatus("Local methods")

def loadClasses(jars: Seq[Library], jarName: String) =
val jar = jars.find(_.name == jarName).get
val classLoader = new URLClassLoader(jars.map(_.absolutePath.toUri.toURL).toArray)
val classes = IO
.withinJarFile(jar.absolutePath) { fs =>
val root = fs.getPath("/")
val sourceMatcher = fs.getPathMatcher("glob:**.class")
Files
.walk(root: Path)
.filter(sourceMatcher.matches)
.iterator
.asScala
.map { classFile =>
val inputStream = Files.newInputStream(classFile)
val reader = new ClassReader(inputStream)
reader.getClassName.replace('/', '.')
}
.toSeq
}
.get
.map(name => JavaReflectClass(classLoader.loadClass(name)))
println(s"classNames: ${classes.size}")
classes

def processClass(unpickler: Scala3Unpickler, cls: ClassType, counter: Counter): Option[ClassSymbol] =
try
val sym = unpickler.findClass(cls)
counter.addSuccess(cls.name)
Some(sym)
catch
case AmbiguousException(e) =>
counter.addAmbiguous(cls.name)
None
case NotFoundException(e) =>
println(cls.name)
counter.addNotFound(cls.name)
None

def processMethod(unpickler: Scala3Unpickler, mthd: Method, counter: Counter): Option[TermSymbol] =
try
val sym = unpickler.findSymbol(mthd)
counter.addSuccess(mthd.name)
sym
catch
case AmbiguousException(e) =>
counter.addAmbiguous(mthd.name)
None
case NotFoundException(e) =>
println(mthd.name)
counter.addNotFound(mthd.name)
None

class Counter:
val success: mutable.Set[String] = mutable.Set.empty[String]
var notFound: mutable.Set[String] = mutable.Set.empty[String]
var ambiguous: mutable.Set[String] = mutable.Set.empty[String]

def addSuccess(cls: String) = success.add(cls)

def addNotFound(cls: String) = notFound.add(cls)

def addAmbiguous(cls: String) = ambiguous.add(cls)

def printStatus(m: String) =
println(s"Status $m:")
println(s" - total is ${ambiguous.size + notFound.size + success.size}")
println(s" - success is ${success.size}")
println(s" - ambiguous is ${ambiguous.size}")
println(s" - notFound is ${notFound.size}")
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,20 @@ abstract class Scala3UnpicklerTests(val scalaVersion: ScalaVersion) extends FunS
debuggee.assertFormat("example.Main$", "example.Foo foo()", "Main.foo: Foo[[X] =>> Either[X, Int]]")
}

test("local enum") {
val source =
"""|package example
|object Main :
| def m =
| enum A:
| case B
| ()
|""".stripMargin

val debuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion)
debuggee.assertFormat("example.Main$A$1", "Main.m.A")
}

test("package object") {
val source =
"""|package object example {
Expand Down

0 comments on commit 44a5f64

Please sign in to comment.