Skip to content

Commit 7bfc675

Browse files
authored
Merge pull request #849 from adpi2/fix-780
Improve perf of Scala2Decoder
2 parents c8318a2 + 592ffa2 commit 7bfc675

File tree

6 files changed

+65
-76
lines changed

6 files changed

+65
-76
lines changed

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/ClassEntryLookUp.scala

Lines changed: 50 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,17 @@ private[internal] case class ClassFile(
3434
private class ClassEntryLookUp(
3535
val entry: ClassEntry,
3636
fqcnToClassFile: Map[String, ClassFile],
37-
sourceUriToSourceFile: Map[SourceFileKey, SourceFile],
38-
sourceUriToClassFiles: Map[SourceFileKey, Seq[ClassFile]],
37+
sourceUriToSourceFile: Map[SanitizedUri, SourceFile],
38+
sourceUriToClassFiles: Map[SanitizedUri, Seq[ClassFile]],
3939
classNameToSourceFile: Map[String, SourceFile],
4040
missingSourceFileClassFiles: Seq[ClassFile],
4141
val orphanClassFiles: Seq[ClassFile],
4242
logger: Logger
4343
) {
44-
private val cachedSourceLines = mutable.Map[SourceLineKey, Seq[ClassFile]]()
44+
private val sourceLineCache = mutable.Map.empty[SourceLine, Seq[ClassFile]]
45+
private val scalaSigCache = mutable.Map.empty[String, Option[ScalaSig]]
4546

46-
def sources: Iterable[SourceFileKey] = sourceUriToSourceFile.keys
47+
def sources: Iterable[SanitizedUri] = sourceUriToSourceFile.keys
4748
def fullyQualifiedNames: Iterable[String] = {
4849
classNameToSourceFile.keys ++
4950
orphanClassFiles.map(_.fullyQualifiedName) ++
@@ -54,75 +55,64 @@ private class ClassEntryLookUp(
5455
fullyQualifiedNames.groupBy[String](NameTransformer.scalaClassName)
5556

5657
def getFullyQualifiedClassName(
57-
sourceKey: SourceFileKey,
58+
sourceUri: SanitizedUri,
5859
lineNumber: Int
5960
): Option[String] = {
60-
val line = SourceLineKey(sourceKey, lineNumber)
61+
val sourceLine = SourceLine(sourceUri, lineNumber)
6162

62-
if (!cachedSourceLines.contains(line)) {
63+
if (!sourceLineCache.contains(sourceLine)) {
6364
// read and cache line numbers from class files
6465
sourceUriToClassFiles
65-
.getOrElse(sourceKey, Nil)
66+
.getOrElse(sourceUri, Nil)
6667
.groupBy(_.classSystem)
6768
.foreach { case (classSystem, classFiles) =>
6869
classSystem
69-
.within((_, root) => loadLineNumbers(root, classFiles, sourceKey))
70+
.within((_, root) => classFiles.foreach(loadLines(root, _, sourceUri)))
7071
.warnFailure(logger, s"Cannot load line numbers in ${classSystem.name}")
7172
}
7273
}
7374

74-
cachedSourceLines
75-
.get(line)
75+
sourceLineCache
76+
.get(sourceLine)
7677
.map { classFiles =>
7778
// The same breakpoint can stop in different classes
7879
// We choose the one with the smallest name
7980
classFiles.map(_.fullyQualifiedName).minBy(_.length)
8081
}
8182
}
8283

83-
private def loadLineNumbers(
84-
root: Path,
85-
classFiles: Seq[ClassFile],
86-
sourceKey: SourceFileKey
87-
): Unit = {
88-
for (classFile <- classFiles) {
89-
val path = root.resolve(classFile.relativePath)
90-
val inputStream = Files.newInputStream(path)
91-
try {
92-
val reader = new ClassReader(inputStream)
93-
94-
val lineNumbers = mutable.Buffer[Int]()
95-
96-
val visitor = new ClassVisitor(Opcodes.ASM9) {
97-
override def visitMethod(
98-
access: Int,
99-
name: String,
100-
desc: String,
101-
signature: String,
102-
exceptions: Array[String]
103-
): MethodVisitor = {
104-
new MethodVisitor(Opcodes.ASM9) {
105-
override def visitLineNumber(line: Int, start: Label): Unit = {
106-
lineNumbers.append(line)
107-
}
108-
}
84+
private def loadLines(root: Path, classFile: ClassFile, sourceUri: SanitizedUri): Unit = {
85+
val lines = mutable.Set[Int]()
86+
val path = root.resolve(classFile.relativePath)
87+
val inputStream = Files.newInputStream(path)
88+
try {
89+
val reader = new ClassReader(inputStream)
90+
val visitor = new ClassVisitor(Opcodes.ASM9) {
91+
override def visitMethod(
92+
access: Int,
93+
name: String,
94+
desc: String,
95+
signature: String,
96+
exceptions: Array[String]
97+
): MethodVisitor =
98+
new MethodVisitor(Opcodes.ASM9) {
99+
override def visitLineNumber(line: Int, start: Label): Unit =
100+
lines += line
109101
}
110-
}
111-
reader.accept(visitor, 0)
112-
113-
for (n <- lineNumbers) {
114-
val line = SourceLineKey(sourceKey, n)
115-
cachedSourceLines.update(
116-
line,
117-
cachedSourceLines.getOrElse(line, Seq.empty) :+ classFile
118-
)
119-
}
120-
} finally inputStream.close()
102+
}
103+
reader.accept(visitor, 0)
104+
} finally inputStream.close()
105+
for (line <- lines) {
106+
val sourceLine = SourceLine(sourceUri, line)
107+
sourceLineCache.update(
108+
sourceLine,
109+
sourceLineCache.getOrElse(sourceLine, Seq.empty) :+ classFile
110+
)
121111
}
122112
}
123113

124114
def getSourceContent(sourceUri: URI): Option[String] =
125-
sourceUriToSourceFile.get(SourceFileKey(sourceUri)).flatMap(readSourceContent(_, logger))
115+
sourceUriToSourceFile.get(SanitizedUri(sourceUri)).flatMap(readSourceContent(_, logger))
126116

127117
def getSourceFileURI(fqcn: String): Option[URI] =
128118
classNameToSourceFile.get(fqcn).map(_.uri)
@@ -131,7 +121,7 @@ private class ClassEntryLookUp(
131121
getSourceFileURI(fqcn).flatMap(getSourceContent)
132122

133123
def getClassFiles(sourceUri: URI): Seq[ClassFile] =
134-
sourceUriToClassFiles.get(SourceFileKey(sourceUri)).getOrElse(Seq.empty)
124+
sourceUriToClassFiles.get(SanitizedUri(sourceUri)).getOrElse(Seq.empty)
135125

136126
def getClassFile(fqcn: String): Option[ClassFile] =
137127
fqcnToClassFile.get(fqcn)
@@ -156,7 +146,7 @@ private class ClassEntryLookUp(
156146
else scalaSigs.headOption
157147
}
158148

159-
fromClass.orElse(fromSource)
149+
scalaSigCache.getOrElseUpdate(fqcn, fromClass.orElse(fromSource))
160150
}
161151
}
162152

@@ -182,21 +172,21 @@ private object ClassEntryLookUp {
182172
classFiles.map(c => (c.fullyQualifiedName, c)).toMap
183173

184174
val sourceFileToRoot = sourceLookUps.flatMap(l => l.sourceFiles.map(f => (f -> l.root))).toMap
185-
val sourceUriToSourceFile = sourceLookUps.flatMap(_.sourceFiles).map(f => (SourceFileKey(f.uri), f)).toMap
175+
val sourceUriToSourceFile = sourceLookUps.flatMap(_.sourceFiles).map(f => (SanitizedUri(f.uri), f)).toMap
186176
val sourceNameToSourceFile = sourceLookUps.flatMap(_.sourceFiles).groupBy(f => f.fileName)
187177

188178
val classNameToSourceFile = mutable.Map[String, SourceFile]()
189-
val sourceUriToClassFiles = mutable.Map[SourceFileKey, Seq[ClassFile]]()
179+
val sourceUriToClassFiles = mutable.Map[SanitizedUri, Seq[ClassFile]]()
190180
val orphanClassFiles = mutable.Buffer[ClassFile]()
191181
val missingSourceFileClassFiles = mutable.Buffer[ClassFile]()
192182

193183
for (classFile <- classFiles) {
194184
def recordSourceFile(sourceFile: SourceFile): Unit = {
195185
classNameToSourceFile.put(classFile.fullyQualifiedName, sourceFile)
196186
sourceUriToClassFiles.update(
197-
SourceFileKey(sourceFile.uri),
187+
SanitizedUri(sourceFile.uri),
198188
sourceUriToClassFiles.getOrElse(
199-
SourceFileKey(sourceFile.uri),
189+
SanitizedUri(sourceFile.uri),
200190
Seq.empty
201191
) :+ classFile
202192
)
@@ -360,12 +350,12 @@ private object ClassEntryLookUp {
360350
/**
361351
* On a case-insensitive system we need to sanitize all URIs to use them as Map keys.
362352
*/
363-
private case class SourceFileKey(sanitizeUri: URI)
353+
private class SanitizedUri(val uri: URI) extends AnyVal
364354

365-
private object SourceFileKey {
355+
private object SanitizedUri {
366356
private val isCaseSensitiveFileSystem = Properties.isWin || Properties.isMac
367357

368-
def apply(uri: URI): SourceFileKey = {
358+
def apply(uri: URI): SanitizedUri = {
369359
val sanitizeUri: URI =
370360
if (isCaseSensitiveFileSystem) {
371361
uri.getScheme match {
@@ -379,8 +369,8 @@ private object SourceFileKey {
379369
case _ => uri
380370
}
381371
} else uri
382-
new SourceFileKey(sanitizeUri)
372+
new SanitizedUri(sanitizeUri)
383373
}
384374
}
385375

386-
private case class SourceLineKey(sourceFile: SourceFileKey, lineNumber: Int)
376+
private case class SourceLine(sourceFile: SanitizedUri, lineNumber: Int)

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/SourceLookUpProvider.scala

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ import scala.collection.parallel.immutable.ParVector
99

1010
private[debugadapter] final class SourceLookUpProvider(
1111
private[internal] var classPathEntries: Seq[ClassEntryLookUp],
12-
private var sourceUriToClassPathEntry: Map[SourceFileKey, ClassEntryLookUp],
12+
private var sourceUriToClassPathEntry: Map[SanitizedUri, ClassEntryLookUp],
1313
private var fqcnToClassPathEntry: Map[String, ClassEntryLookUp],
1414
logger: Logger
1515
) extends ISourceLookUpProvider {
16-
var classesByScalaNameMap: Map[String, Seq[String]] = loadClassesByScalaName
16+
private var classesByScalaNameMap: Map[String, Seq[String]] = loadClassesByScalaName
1717

1818
override def supportsRealtimeBreakpointVerification(): Boolean = true
1919

@@ -24,7 +24,7 @@ private[debugadapter] final class SourceLookUpProvider(
2424
override def getSourceContents(uri: String): String = {
2525
val sourceUri = URI.create(uri)
2626
sourceUriToClassPathEntry
27-
.get(SourceFileKey(sourceUri))
27+
.get(SanitizedUri(sourceUri))
2828
.flatMap(_.getSourceContent(sourceUri))
2929
.orNull
3030
}
@@ -41,7 +41,7 @@ private[debugadapter] final class SourceLookUpProvider(
4141
val resolvedName = uri.getSchemeSpecificPart
4242
lines.map(_ => resolvedName)
4343
case _ =>
44-
val key = SourceFileKey(uri);
44+
val key = SanitizedUri(uri);
4545
sourceUriToClassPathEntry.get(key) match {
4646
case None => lines.map(_ => null)
4747
case Some(entry) =>

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/evaluator/JdiClassLoader.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,6 @@ private[internal] class JdiClassLoader(
9090
case c: CharValue => Safe(value)
9191
case _ => mirrorOf(value.value.toString)
9292
}
93-
_ = getClass
9493
(className, sig) = value.value match {
9594
case _: BooleanValue => ("java.lang.Boolean", "(Ljava/lang/String;)Ljava/lang/Boolean;")
9695
case _: ByteValue => ("java.lang.Byte", "(Ljava/lang/String;)Ljava/lang/Byte;")

modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/JdiExtensions.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ object JdiExtensions {
3131
method.name == "<clinit>"
3232

3333
def isAnonFunction: Boolean =
34-
method.name.matches(".+\\$anonfun\\$\\d+")
34+
method.name.matches(".+\\$anonfun(\\$.+)?\\$\\d+")
3535

3636
def isLiftedMethod: Boolean =
3737
method.name.matches(".+\\$\\d+")
@@ -43,8 +43,8 @@ object JdiExtensions {
4343
method.name.contains("$lzyINIT") || method.name.contains("$lzycompute")
4444

4545
def isLazyGetter: Boolean =
46-
method.argumentTypes.asScala.toSeq match {
47-
case Seq(argType) => lazyTypes.contains(argType.name)
46+
method.argumentTypeNames.asScala.toSeq match {
47+
case Seq(argTypeName) => lazyTypes.contains(argTypeName)
4848
case _ => false
4949
}
5050

modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/ClassEntryLookUpSpec.scala

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class ClassEntryLookUpSpec extends FunSuite {
2323
"example.Main$Hello$InnerHello$1"
2424

2525
val className =
26-
lookUp.getFullyQualifiedClassName(SourceFileKey(expectedSourceFile), 14)
26+
lookUp.getFullyQualifiedClassName(SanitizedUri(expectedSourceFile), 14)
2727
assert(className.contains(expectedClassName))
2828

2929
val sourceFile = lookUp.getSourceFileURI(expectedClassName)
@@ -42,7 +42,7 @@ class ClassEntryLookUpSpec extends FunSuite {
4242
val expectedClassName = "cats.instances.ListInstances$$anon$1"
4343

4444
val className =
45-
lookUp.getFullyQualifiedClassName(SourceFileKey(expectedSourceFile), 28)
45+
lookUp.getFullyQualifiedClassName(SanitizedUri(expectedSourceFile), 28)
4646
assert(className.contains(expectedClassName))
4747

4848
val sourceFile = lookUp.getSourceFileURI(expectedClassName)
@@ -99,8 +99,8 @@ class ClassEntryLookUpSpec extends FunSuite {
9999
assert(sourceFile.contains("scala-debug-adapter"))
100100

101101
val moddedSourceFile = sourceFile.replace("scala-debug-adapter", "SCALA-deBug-Adapter")
102-
val sourceFileKey = SourceFileKey(toUri(moddedSourceFile))
103-
val className = lookUp.getFullyQualifiedClassName(sourceFileKey, 14)
102+
val sourceUri = SanitizedUri(toUri(moddedSourceFile))
103+
val className = lookUp.getFullyQualifiedClassName(sourceUri, 14)
104104

105105
val expectedClassName = if (isFilesystemCaseInsensitive) Some("example.Main$Hello$InnerHello$1") else None
106106
assertEquals(className, expectedClassName)
@@ -116,11 +116,11 @@ class ClassEntryLookUpSpec extends FunSuite {
116116
val moddedSourceJar = sourceJar.replace("cats-core", "Cats-CoRe")
117117
val moddedsourceFile = URI.create(s"jar:${toUri(moddedSourceJar)}!/cats/instances/list.scala")
118118
val expectedClassName = if (isFilesystemCaseInsensitive) Some("cats.instances.ListInstances$$anon$1") else None
119-
val obtainedClassName = lookUp.getFullyQualifiedClassName(SourceFileKey(moddedsourceFile), 28)
119+
val obtainedClassName = lookUp.getFullyQualifiedClassName(SanitizedUri(moddedsourceFile), 28)
120120
assertEquals(obtainedClassName, expectedClassName)
121121

122122
val invalidSourceFile = URI.create(s"jar:file:${toUri(sourceJar)}!/caTs/Instances/list.scala")
123-
val notFound = lookUp.getFullyQualifiedClassName(SourceFileKey(invalidSourceFile), 28)
123+
val notFound = lookUp.getFullyQualifiedClassName(SanitizedUri(invalidSourceFile), 28)
124124
assert(notFound.isEmpty)
125125
}
126126

modules/tests/src/test/scala/ch/epfl/scala/debugadapter/internal/MetalsClassBreakpointSuite.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ class MetalsClassBreakpointSuite extends FunSuite {
171171
val lineNumber = original.linesIterator.toSeq.indexWhere(_.contains(">>")) + 1
172172
val debuggee = TestingDebuggee.mainClass(source, "Main", scalaVersion)
173173
val lookUp = ClassEntryLookUp(debuggee.mainModule, NoopLogger)
174-
val sourceFileKey = SourceFileKey(debuggee.sourceFiles.head.toUri)
175-
val className = lookUp.getFullyQualifiedClassName(sourceFileKey, lineNumber)
174+
val sourceUri = SanitizedUri(debuggee.sourceFiles.head.toUri)
175+
val className = lookUp.getFullyQualifiedClassName(sourceUri, lineNumber)
176176
assert(className.contains(expectedClassName))
177177
}
178178
}

0 commit comments

Comments
 (0)