@@ -20,24 +20,53 @@ import com.intellij.debugger.SourcePosition
20
20
import com.intellij.debugger.engine.DebugProcess
21
21
import com.intellij.debugger.jdi.VirtualMachineProxyImpl
22
22
import com.intellij.openapi.application.ApplicationManager
23
+ import com.intellij.openapi.compiler.CompilerPaths
23
24
import com.intellij.openapi.project.Project
25
+ import com.intellij.openapi.roots.ProjectFileIndex
24
26
import com.intellij.openapi.vfs.VirtualFile
25
27
import com.intellij.psi.search.GlobalSearchScope
28
+ import com.intellij.util.containers.ConcurrentWeakFactoryMap
29
+ import com.intellij.util.containers.ContainerUtil
26
30
import com.sun.jdi.Location
27
31
import com.sun.jdi.ReferenceType
28
32
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil
33
+ import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
34
+ import org.jetbrains.kotlin.idea.refactoring.getLineCount
29
35
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
36
+ import org.jetbrains.kotlin.idea.refactoring.toPsiFile
37
+ import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
30
38
import org.jetbrains.kotlin.idea.util.application.runReadAction
31
39
import org.jetbrains.kotlin.lexer.KtTokens
40
+ import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder
41
+ import org.jetbrains.kotlin.name.ClassId
32
42
import org.jetbrains.kotlin.name.FqName
43
+ import org.jetbrains.kotlin.name.Name
44
+ import org.jetbrains.kotlin.name.tail
33
45
import org.jetbrains.kotlin.psi.KtFile
34
46
import org.jetbrains.kotlin.psi.KtFunction
35
47
import org.jetbrains.kotlin.psi.psiUtil.parents
36
48
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
49
+ import org.jetbrains.kotlin.utils.addToStdlib.check
37
50
import org.jetbrains.kotlin.utils.getOrPutNullable
38
51
import org.jetbrains.org.objectweb.asm.*
52
+ import java.io.File
39
53
import java.util.*
40
54
55
+ fun isInlineFunctionLineNumber (file : VirtualFile , lineNumber : Int , project : Project ): Boolean {
56
+ if (ProjectRootsUtil .isProjectSourceFile(project, file)) {
57
+ val linesInFile = file.toPsiFile(project)?.getLineCount() ? : return false
58
+ return lineNumber > linesInFile
59
+ }
60
+
61
+ return true
62
+ }
63
+
64
+ fun readDebugBytecodeInfo (project : Project ,
65
+ jvmName : JvmClassName ,
66
+ file : VirtualFile ): BytecodeDebugInfo ? {
67
+ return KotlinDebuggerCaches .getOrReadDebugInfoFromBytecode(project, jvmName, file)
68
+ }
69
+
41
70
fun noStrataLineNumber (location : Location , isDexDebug : Boolean , project : Project , preferInlined : Boolean = false): Int {
42
71
if (isDexDebug) {
43
72
if (! preferInlined) {
@@ -73,13 +102,100 @@ fun getLastLineNumberForLocation(location: Location, project: Project, searchSco
73
102
return lineMapping.values.firstOrNull { it.contains(lineNumber) }?.last()
74
103
}
75
104
76
- fun readLineNumberTableMapping (bytes : ByteArray ): Map <BytecodeMethodKey , Map <String , Set <Int >>> {
105
+ class WeakBytecodeDebugInfoStorage : ConcurrentWeakFactoryMap <BinaryCacheKey , BytecodeDebugInfo ?>() {
106
+ override fun create (key : BinaryCacheKey ): BytecodeDebugInfo ? {
107
+ val bytes = readClassFileImpl(key.project, key.jvmName, key.file) ? : return null
108
+
109
+ val smapData = readDebugInfo(bytes)
110
+ val lineNumberMapping = readLineNumberTableMapping(bytes)
111
+
112
+ return BytecodeDebugInfo (smapData, lineNumberMapping)
113
+ }
114
+ override fun createMap (): Map <BinaryCacheKey , BytecodeDebugInfo ?> {
115
+ return ContainerUtil .createConcurrentWeakKeyWeakValueMap()
116
+ }
117
+ }
118
+
119
+ class BytecodeDebugInfo (val smapData : SmapData ? , val lineTableMapping : Map <BytecodeMethodKey , Map <String , Set <Int >>>)
120
+
121
+ data class BytecodeMethodKey (val methodName : String , val signature : String )
122
+
123
+ data class BinaryCacheKey (val project : Project , val jvmName : JvmClassName , val file : VirtualFile )
124
+
125
+ private fun readClassFileImpl (project : Project ,
126
+ jvmName : JvmClassName ,
127
+ file : VirtualFile ): ByteArray? {
128
+ val fqNameWithInners = jvmName.fqNameForClassNameWithoutDollars.tail(jvmName.packageFqName)
129
+
130
+ fun readFromLibrary (): ByteArray? {
131
+ if (! ProjectRootsUtil .isLibrarySourceFile(project, file)) return null
132
+
133
+ val classId = ClassId (jvmName.packageFqName, Name .identifier(fqNameWithInners.asString()))
134
+
135
+ val fileFinder = JvmVirtualFileFinder .SERVICE .getInstance(project)
136
+ val classFile = fileFinder.findVirtualFileWithHeader(classId) ? : return null
137
+ return classFile.contentsToByteArray()
138
+ }
139
+
140
+ fun readFromOutput (isForTestClasses : Boolean ): ByteArray? {
141
+ if (! ProjectRootsUtil .isProjectSourceFile(project, file)) return null
142
+
143
+ val module = ProjectFileIndex .SERVICE .getInstance(project).getModuleForFile(file)
144
+ val outputDir = CompilerPaths .getModuleOutputDirectory(module, /* forTests = */ isForTestClasses) ? : return null
145
+
146
+ val className = fqNameWithInners.asString().replace(' .' , ' $' )
147
+ var classByDirectory = findClassFileByPath(jvmName.packageFqName.asString(), className, outputDir)
148
+
149
+ if (classByDirectory == null ) {
150
+ if (! isForTestClasses) {
151
+ return null
152
+ }
153
+
154
+ val outputModeDirName = outputDir.name
155
+ val androidTestOutputDir = outputDir.parent?.parent?.findChild(" androidTest" )?.findChild(outputModeDirName) ? : return null
156
+
157
+ classByDirectory = findClassFileByPath(jvmName.packageFqName.asString(), className, androidTestOutputDir) ? : return null
158
+ }
159
+
160
+ return classByDirectory.readBytes()
161
+ }
162
+
163
+ fun readFromSourceOutput (): ByteArray? = readFromOutput(false )
164
+
165
+ fun readFromTestOutput (): ByteArray? = readFromOutput(true )
166
+
167
+ return readFromLibrary() ? :
168
+ readFromSourceOutput() ? :
169
+ readFromTestOutput()
170
+ }
171
+
172
+ private fun findClassFileByPath (packageName : String , className : String , outputDir : VirtualFile ): File ? {
173
+ val outDirFile = File (outputDir.path).check(File ::exists) ? : return null
174
+
175
+ val parentDirectory = File (outDirFile, packageName.replace(" ." , File .separator))
176
+ if (! parentDirectory.exists()) return null
177
+
178
+ if (ApplicationManager .getApplication().isUnitTestMode) {
179
+ val beforeDexFileClassFile = File (parentDirectory, className + " .class.before_dex" )
180
+ if (beforeDexFileClassFile.exists()) {
181
+ return beforeDexFileClassFile
182
+ }
183
+ }
184
+
185
+ val classFile = File (parentDirectory, className + " .class" )
186
+ if (classFile.exists()) {
187
+ return classFile
188
+ }
189
+
190
+ return null
191
+ }
192
+
193
+ private fun readLineNumberTableMapping (bytes : ByteArray ): Map <BytecodeMethodKey , Map <String , Set <Int >>> {
77
194
val lineNumberMapping = HashMap <BytecodeMethodKey , Map <String , Set <Int >>>()
78
195
79
196
ClassReader (bytes).accept(object : ClassVisitor (InlineCodegenUtil .API ) {
80
197
override fun visitMethod (access : Int , name : String? , desc : String? , signature : String? , exceptions : Array <out String >? ): MethodVisitor ? {
81
198
if (name == null || desc == null ) {
82
- // TODO: check constructors
83
199
return null
84
200
}
85
201
@@ -113,7 +229,7 @@ internal fun getOriginalPositionOfInlinedLine(location: Location, project: Proje
113
229
return mapStacktraceLineToSource(smapData, lineNumber, project, SourceLineKind .EXECUTED_LINE , searchScope)
114
230
}
115
231
116
- internal fun findAndReadClassFile (
232
+ private fun findAndReadClassFile (
117
233
fqName : FqName , fileName : String , project : Project , searchScope : GlobalSearchScope ,
118
234
fileFilter : (VirtualFile ) -> Boolean ): BytecodeDebugInfo ? {
119
235
val internalName = fqName.asString().replace(' .' , ' /' )
@@ -124,7 +240,7 @@ internal fun findAndReadClassFile(
124
240
val virtualFile = file.virtualFile ? : return null
125
241
if (! fileFilter(virtualFile)) return null
126
242
127
- return readClassFile (project, jvmClassName, virtualFile)
243
+ return readDebugBytecodeInfo (project, jvmClassName, virtualFile)
128
244
}
129
245
130
246
internal fun getLocationsOfInlinedLine (type : ReferenceType , position : SourcePosition , sourceSearchScope : GlobalSearchScope ): List <Location > {
@@ -156,7 +272,7 @@ private fun inlinedLinesNumbers(
156
272
157
273
val virtualFile = file.virtualFile ? : return listOf ()
158
274
159
- val debugInfo = readClassFile (project, jvmClassName, virtualFile) ? : return listOf ()
275
+ val debugInfo = readDebugBytecodeInfo (project, jvmClassName, virtualFile) ? : return listOf ()
160
276
val smapData = debugInfo.smapData ? : return listOf ()
161
277
162
278
val smap = smapData.kotlinStrata ? : return listOf ()
0 commit comments