Skip to content

Commit 1c955a6

Browse files
committed
Cache debug information from bytecode in Android debug
1 parent 78c76a2 commit 1c955a6

File tree

4 files changed

+94
-39
lines changed

4 files changed

+94
-39
lines changed

idea/src/org/jetbrains/kotlin/idea/debugger/NoStrataPositionManagerHelper.kt

Lines changed: 34 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,6 @@ import org.jetbrains.kotlin.utils.getOrPutNullable
3838
import org.jetbrains.org.objectweb.asm.*
3939
import java.util.*
4040

41-
// TODO: Don't read same bytecode file again and again
42-
// TODO: Build line mapping for the whole file
43-
// TODO: Quick caching for the same location
44-
4541
fun noStrataLineNumber(location: Location, isDexDebug: Boolean, project: Project, preferInlined: Boolean = false): Int {
4642
if (isDexDebug) {
4743
if (!preferInlined) {
@@ -68,33 +64,40 @@ fun getLastLineNumberForLocation(location: Location, project: Project, searchSco
6864
val fileName = location.sourceName()
6965

7066
val method = location.method() ?: return null
67+
val name = method.name() ?: return null
68+
val signature = method.signature() ?: return null
7169

72-
val bytes = findAndReadClassFile(fqName, fileName, project, searchScope, { isInlineFunctionLineNumber(it, lineNumber, project) }) ?: return null
70+
val debugInfo = findAndReadClassFile(fqName, fileName, project, searchScope, { isInlineFunctionLineNumber(it, lineNumber, project) }) ?: return null
7371

74-
fun readLineNumberTableMapping(bytes: ByteArray): Map<String, Set<Int>> {
75-
val labelsToAllStrings = HashMap<String, MutableSet<Int>>()
72+
val lineMapping = debugInfo.lineTableMapping[BytecodeMethodKey(name, signature)] ?: return null
73+
return lineMapping.values.firstOrNull { it.contains(lineNumber) }?.last()
74+
}
7675

77-
ClassReader(bytes).accept(object : ClassVisitor(InlineCodegenUtil.API) {
78-
override fun visitMethod(access: Int, name: String?, desc: String?, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
79-
if (!(name == method.name() && desc == method.signature())) {
80-
return null
81-
}
76+
fun readLineNumberTableMapping(bytes: ByteArray): Map<BytecodeMethodKey, Map<String, Set<Int>>> {
77+
val lineNumberMapping = HashMap<BytecodeMethodKey, Map<String, Set<Int>>>()
78+
79+
ClassReader(bytes).accept(object : ClassVisitor(InlineCodegenUtil.API) {
80+
override fun visitMethod(access: Int, name: String?, desc: String?, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
81+
if (name == null || desc == null) {
82+
// TODO: check constructors
83+
return null
84+
}
8285

83-
return object : MethodVisitor(Opcodes.ASM5, null) {
84-
override fun visitLineNumber(line: Int, start: Label?) {
85-
if (start != null) {
86-
labelsToAllStrings.getOrPutNullable(start.toString(), { LinkedHashSet<Int>() }).add(line)
87-
}
86+
val methodKey = BytecodeMethodKey(name, desc)
87+
val methodLinesMapping = HashMap<String, MutableSet<Int>>()
88+
lineNumberMapping[methodKey] = methodLinesMapping
89+
90+
return object : MethodVisitor(Opcodes.ASM5, null) {
91+
override fun visitLineNumber(line: Int, start: Label?) {
92+
if (start != null) {
93+
methodLinesMapping.getOrPutNullable(start.toString(), { LinkedHashSet<Int>() }).add(line)
8894
}
8995
}
9096
}
91-
}, ClassReader.SKIP_FRAMES and ClassReader.SKIP_CODE)
92-
93-
return labelsToAllStrings
94-
}
97+
}
98+
}, ClassReader.SKIP_FRAMES and ClassReader.SKIP_CODE)
9599

96-
val lineMapping = readLineNumberTableMapping(bytes)
97-
return lineMapping.values.firstOrNull { it.contains(lineNumber) }?.last()
100+
return lineNumberMapping
98101
}
99102

100103
internal fun getOriginalPositionOfInlinedLine(location: Location, project: Project): Pair<KtFile, Int>? {
@@ -103,14 +106,16 @@ internal fun getOriginalPositionOfInlinedLine(location: Location, project: Proje
103106
val fileName = location.sourceName()
104107
val searchScope = GlobalSearchScope.allScope(project)
105108

106-
val bytes = findAndReadClassFile(fqName, fileName, project, searchScope, { isInlineFunctionLineNumber(it, lineNumber, project) }) ?: return null
107-
val smapData = readDebugInfo(bytes) ?: return null
109+
val debugInfo = findAndReadClassFile(fqName, fileName, project, searchScope, { isInlineFunctionLineNumber(it, lineNumber, project) }) ?:
110+
return null
111+
val smapData = debugInfo.smapData ?: return null
112+
108113
return mapStacktraceLineToSource(smapData, lineNumber, project, SourceLineKind.EXECUTED_LINE, searchScope)
109114
}
110115

111116
internal fun findAndReadClassFile(
112117
fqName: FqName, fileName: String, project: Project, searchScope: GlobalSearchScope,
113-
fileFilter: (VirtualFile) -> Boolean): ByteArray? {
118+
fileFilter: (VirtualFile) -> Boolean): BytecodeDebugInfo? {
114119
val internalName = fqName.asString().replace('.', '/')
115120
val jvmClassName = JvmClassName.byInternalName(internalName)
116121

@@ -151,8 +156,8 @@ private fun inlinedLinesNumbers(
151156

152157
val virtualFile = file.virtualFile ?: return listOf()
153158

154-
val bytes = readClassFile(project, jvmClassName, virtualFile) ?: return listOf()
155-
val smapData = readDebugInfo(bytes) ?: return listOf()
159+
val debugInfo = readClassFile(project, jvmClassName, virtualFile) ?: return listOf()
160+
val smapData = debugInfo.smapData ?: return listOf()
156161

157162
val smap = smapData.kotlinStrata ?: return listOf()
158163

@@ -171,5 +176,5 @@ private fun inlinedLinesNumbers(
171176
@Volatile var emulateDexDebugInTests: Boolean = false
172177

173178
fun DebugProcess.isDexDebug() =
174-
(emulateDexDebugInTests && ApplicationManager.getApplication ().isUnitTestMode) ||
179+
(emulateDexDebugInTests && ApplicationManager.getApplication().isUnitTestMode) ||
175180
(this.virtualMachineProxy as? VirtualMachineProxyImpl)?.virtualMachine?.name() == "Dalvik" // TODO: check other machine names

idea/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinDebuggerCaches.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
2121
import com.intellij.openapi.components.ServiceManager
2222
import com.intellij.openapi.project.Project
2323
import com.intellij.openapi.roots.libraries.LibraryUtil
24+
import com.intellij.openapi.vfs.VirtualFile
2425
import com.intellij.psi.PsiElement
2526
import com.intellij.psi.util.CachedValueProvider
2627
import com.intellij.psi.util.CachedValuesManager
@@ -38,11 +39,15 @@ import org.jetbrains.kotlin.config.CompilerConfiguration
3839
import org.jetbrains.kotlin.descriptors.ClassDescriptor
3940
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
4041
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
42+
import org.jetbrains.kotlin.idea.debugger.BinaryCacheKey
43+
import org.jetbrains.kotlin.idea.debugger.BytecodeDebugInfo
44+
import org.jetbrains.kotlin.idea.debugger.WeakConcurrentBinaryStorage
4145
import org.jetbrains.kotlin.idea.util.application.runReadAction
4246
import org.jetbrains.kotlin.psi.KtCodeFragment
4347
import org.jetbrains.kotlin.psi.KtElement
4448
import org.jetbrains.kotlin.psi.KtFile
4549
import org.jetbrains.kotlin.resolve.DescriptorUtils
50+
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
4651
import org.jetbrains.kotlin.types.KotlinType
4752
import java.util.*
4853
import java.util.concurrent.ConcurrentHashMap
@@ -69,6 +74,13 @@ class KotlinDebuggerCaches(project: Project) {
6974
PsiModificationTracker.MODIFICATION_COUNT)
7075
}, false)
7176

77+
private val binaryCache = CachedValuesManager.getManager(project).createCachedValue(
78+
{
79+
CachedValueProvider.Result<WeakConcurrentBinaryStorage>(
80+
WeakConcurrentBinaryStorage(),
81+
PsiModificationTracker.MODIFICATION_COUNT)
82+
}, false)
83+
7284
companion object {
7385
private val LOG = Logger.getLogger(KotlinDebuggerCaches::class.java)!!
7486

@@ -154,6 +166,14 @@ class KotlinDebuggerCaches(project: Project) {
154166
}
155167
}
156168

169+
fun readFileContent(
170+
project: Project,
171+
jvmName: JvmClassName,
172+
file: VirtualFile): BytecodeDebugInfo? {
173+
val cache = getInstance(project)
174+
return cache.binaryCache.value[BinaryCacheKey(project, jvmName, file)]
175+
}
176+
157177
private fun getElementToCreateTypeMapperForLibraryFile(element: PsiElement?) =
158178
runReadAction { element as? KtElement ?: PsiTreeUtil.getParentOfType(element, KtElement::class.java)!! }
159179

idea/src/org/jetbrains/kotlin/idea/debugger/smapUtil.kt

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,13 @@ import com.intellij.openapi.project.Project
2222
import com.intellij.openapi.roots.ProjectFileIndex
2323
import com.intellij.openapi.vfs.VirtualFile
2424
import com.intellij.psi.search.GlobalSearchScope
25+
import com.intellij.util.containers.ConcurrentWeakFactoryMap
26+
import com.intellij.util.containers.ContainerUtil
2527
import org.jetbrains.kotlin.codegen.inline.FileMapping
2628
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil
2729
import org.jetbrains.kotlin.codegen.inline.SMAP
2830
import org.jetbrains.kotlin.codegen.inline.SMAPParser
31+
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
2932
import org.jetbrains.kotlin.idea.refactoring.getLineCount
3033
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
3134
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
@@ -49,12 +52,38 @@ fun isInlineFunctionLineNumber(file: VirtualFile, lineNumber: Int, project: Proj
4952
return true
5053
}
5154

55+
class WeakConcurrentBinaryStorage : ConcurrentWeakFactoryMap<BinaryCacheKey, BytecodeDebugInfo?>() {
56+
override fun create(key: BinaryCacheKey): BytecodeDebugInfo? {
57+
val bytes = readClassFileImpl(key.project, key.jvmName, key.file) ?: return null
58+
59+
val smapData = readDebugInfo(bytes)
60+
val lineNumberMapping = readLineNumberTableMapping(bytes)
61+
62+
return BytecodeDebugInfo(smapData, lineNumberMapping)
63+
}
64+
65+
override fun createMap(): Map<BinaryCacheKey, BytecodeDebugInfo?> {
66+
return ContainerUtil.createConcurrentWeakKeyWeakValueMap()
67+
}
68+
}
69+
5270
fun readClassFile(project: Project,
5371
jvmName: JvmClassName,
54-
file: VirtualFile): ByteArray? {
72+
file: VirtualFile): BytecodeDebugInfo? {
73+
return KotlinDebuggerCaches.readFileContent(project, jvmName, file)
74+
}
75+
76+
class BytecodeDebugInfo(val smapData: SmapData?, val lineTableMapping: Map<BytecodeMethodKey, Map<String, Set<Int>>>)
77+
78+
data class BytecodeMethodKey(val methodName: String, val signature: String)
79+
data class BinaryCacheKey(val project: Project, val jvmName: JvmClassName, val file: VirtualFile)
80+
81+
private fun readClassFileImpl(project: Project,
82+
jvmName: JvmClassName,
83+
file: VirtualFile): ByteArray? {
5584
val fqNameWithInners = jvmName.fqNameForClassNameWithoutDollars.tail(jvmName.packageFqName)
5685

57-
fun readFromLibrary() : ByteArray? {
86+
fun readFromLibrary(): ByteArray? {
5887
if (!ProjectRootsUtil.isLibrarySourceFile(project, file)) return null
5988

6089
val classId = ClassId(jvmName.packageFqName, Name.identifier(fqNameWithInners.asString()))
@@ -84,12 +113,13 @@ fun readClassFile(project: Project,
84113
classByDirectory = findClassFileByPath(jvmName.packageFqName.asString(), className, androidTestOutputDir) ?: return null
85114
}
86115

116+
println("Read file: " + classByDirectory)
87117
return classByDirectory.readBytes()
88118
}
89119

90-
fun readFromSourceOutput() : ByteArray? = readFromOutput(false)
120+
fun readFromSourceOutput(): ByteArray? = readFromOutput(false)
91121

92-
fun readFromTestOutput() : ByteArray? = readFromOutput(true)
122+
fun readFromTestOutput(): ByteArray? = readFromOutput(true)
93123

94124
return readFromLibrary() ?:
95125
readFromSourceOutput() ?:
@@ -128,9 +158,9 @@ fun mapStacktraceLineToSource(smapData: SmapData,
128158
lineKind: SourceLineKind,
129159
searchScope: GlobalSearchScope): Pair<KtFile, Int>? {
130160
val smap = when (lineKind) {
131-
SourceLineKind.CALL_LINE -> smapData.kotlinDebugStrata
132-
SourceLineKind.EXECUTED_LINE -> smapData.kotlinStrata
133-
} ?: return null
161+
SourceLineKind.CALL_LINE -> smapData.kotlinDebugStrata
162+
SourceLineKind.EXECUTED_LINE -> smapData.kotlinStrata
163+
} ?: return null
134164

135165
val mappingInfo = smap.fileMappings.firstOrNull {
136166
it.getIntervalIfContains(line) != null
@@ -166,7 +196,7 @@ class SmapData(debugInfo: String) {
166196

167197
init {
168198
val intervals = debugInfo.split(SMAP.END).filter(String::isNotBlank)
169-
when(intervals.count()) {
199+
when (intervals.count()) {
170200
1 -> {
171201
kotlinStrata = SMAPParser.parse(intervals[0] + SMAP.END)
172202
kotlinDebugStrata = null

idea/src/org/jetbrains/kotlin/idea/filters/KotlinExceptionFilter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
7575
private fun createHyperlinks(jvmName: JvmClassName, file: VirtualFile, line: Int, project: Project): InlineFunctionHyperLinkInfo? {
7676
if (!isInlineFunctionLineNumber(file, line, project)) return null
7777

78-
val bytes = readClassFile(project, jvmName, file) ?: return null
79-
val smapData = readDebugInfo(bytes) ?: return null
78+
val debugInfo = readClassFile(project, jvmName, file) ?: return null
79+
val smapData = debugInfo.smapData ?: return null
8080

8181
val inlineInfos = arrayListOf<InlineFunctionHyperLinkInfo.InlineInfo>()
8282

0 commit comments

Comments
 (0)