Debugger: navigate to call site of inline function
#KT-11438 Fixed
This commit is contained in:
committed by
Michael Bogdanov
parent
bdd24cfa7d
commit
02c5e005fe
@@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2016 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.jetbrains.kotlin.idea.filters
|
||||||
|
|
||||||
|
import com.intellij.execution.filters.FileHyperlinkInfo
|
||||||
|
import com.intellij.execution.filters.HyperlinkInfoBase
|
||||||
|
import com.intellij.execution.filters.OpenFileHyperlinkInfo
|
||||||
|
import com.intellij.openapi.fileEditor.OpenFileDescriptor
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
|
import com.intellij.ui.SimpleColoredComponent
|
||||||
|
import com.intellij.ui.awt.RelativePoint
|
||||||
|
import com.intellij.ui.components.JBList
|
||||||
|
import java.awt.Component
|
||||||
|
import javax.swing.JList
|
||||||
|
import javax.swing.ListCellRenderer
|
||||||
|
|
||||||
|
class InlineFunctionHyperLinkInfo(
|
||||||
|
private val project: Project,
|
||||||
|
private val inlineInfo: List<InlineInfo>
|
||||||
|
) : HyperlinkInfoBase(), FileHyperlinkInfo {
|
||||||
|
|
||||||
|
override fun navigate(project: Project, hyperlinkLocationPoint: RelativePoint?) {
|
||||||
|
if (inlineInfo.isEmpty()) return
|
||||||
|
|
||||||
|
if (inlineInfo.size == 1) {
|
||||||
|
OpenFileHyperlinkInfo(project, inlineInfo.first().file, inlineInfo.first().line).navigate(project)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val list = JBList(inlineInfo)
|
||||||
|
list.cellRenderer = InlineInfoCellRenderer()
|
||||||
|
val popup = JBPopupFactory.getInstance().createListPopupBuilder(list)
|
||||||
|
.setTitle("Navigate to")
|
||||||
|
.setItemChoosenCallback {
|
||||||
|
val fileInfo = list.selectedValue as InlineInfo
|
||||||
|
OpenFileHyperlinkInfo(project, fileInfo.file, fileInfo.line).navigate(project)
|
||||||
|
}
|
||||||
|
.createPopup()
|
||||||
|
|
||||||
|
if (hyperlinkLocationPoint != null) {
|
||||||
|
popup.show(hyperlinkLocationPoint)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
popup.showInFocusCenter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDescriptor(): OpenFileDescriptor? {
|
||||||
|
val file = inlineInfo.firstOrNull()
|
||||||
|
return file?.let { OpenFileDescriptor(project, file.file, file.line, 0) }
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class InlineInfo(val prefix: String, val file: VirtualFile, val line: Int) {
|
||||||
|
class CallSiteInfo(file: VirtualFile, line: Int): InlineInfo("inline function call site", file, line)
|
||||||
|
class InlineFunctionBodyInfo(file: VirtualFile, line: Int): InlineInfo("inline function body", file, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InlineInfoCellRenderer : SimpleColoredComponent(), ListCellRenderer<InlineInfo> {
|
||||||
|
init {
|
||||||
|
isOpaque = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getListCellRendererComponent(
|
||||||
|
list: JList<out InlineInfo>?,
|
||||||
|
value: InlineInfo?,
|
||||||
|
index: Int,
|
||||||
|
isSelected: Boolean,
|
||||||
|
cellHasFocus: Boolean
|
||||||
|
): Component {
|
||||||
|
|
||||||
|
clear()
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
append(value.prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
background = list?.selectionBackground
|
||||||
|
foreground = list?.selectionForeground
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
background = list?.background
|
||||||
|
foreground = list?.foreground
|
||||||
|
}
|
||||||
|
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,7 @@ import com.intellij.openapi.vfs.VirtualFile
|
|||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import org.jetbrains.kotlin.codegen.inline.FileMapping
|
import org.jetbrains.kotlin.codegen.inline.FileMapping
|
||||||
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil
|
import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil
|
||||||
|
import org.jetbrains.kotlin.codegen.inline.SMAP
|
||||||
import org.jetbrains.kotlin.codegen.inline.SMAPParser
|
import org.jetbrains.kotlin.codegen.inline.SMAPParser
|
||||||
import org.jetbrains.kotlin.idea.refactoring.getLineCount
|
import org.jetbrains.kotlin.idea.refactoring.getLineCount
|
||||||
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
|
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
|
||||||
@@ -36,6 +37,7 @@ import org.jetbrains.kotlin.load.kotlin.JvmVirtualFileFinder
|
|||||||
import org.jetbrains.kotlin.name.ClassId
|
import org.jetbrains.kotlin.name.ClassId
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.name.tail
|
import org.jetbrains.kotlin.name.tail
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.check
|
import org.jetbrains.kotlin.utils.addToStdlib.check
|
||||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||||
@@ -67,11 +69,15 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
|
|||||||
|
|
||||||
val virtualFile = file.virtualFile ?: return null
|
val virtualFile = file.virtualFile ?: return null
|
||||||
|
|
||||||
return virtualFileForInlineCall(jvmClassName, virtualFile, lineNumber + 1, project) ?:
|
val hyperlinkInfoForInline = getHyperlinkInfoIfInline(jvmClassName, virtualFile, lineNumber + 1, project)
|
||||||
OpenFileHyperlinkInfo(project, virtualFile, lineNumber)
|
if (hyperlinkInfoForInline != null) {
|
||||||
|
return hyperlinkInfoForInline
|
||||||
|
}
|
||||||
|
|
||||||
|
return OpenFileHyperlinkInfo(project, virtualFile, lineNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun virtualFileForInlineCall(jvmName: JvmClassName, file: VirtualFile, lineNumber: Int, project: Project): OpenFileHyperlinkInfo? {
|
private fun getHyperlinkInfoIfInline(jvmName: JvmClassName, file: VirtualFile, lineNumber: Int, project: Project): InlineFunctionHyperLinkInfo? {
|
||||||
val fqNameWithInners = jvmName.fqNameForClassNameWithoutDollars.tail(jvmName.packageFqName)
|
val fqNameWithInners = jvmName.fqNameForClassNameWithoutDollars.tail(jvmName.packageFqName)
|
||||||
|
|
||||||
if (ProjectRootsUtil.isLibrarySourceFile(project, file)) {
|
if (ProjectRootsUtil.isLibrarySourceFile(project, file)) {
|
||||||
@@ -111,10 +117,30 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readDebugInfoForInlineFun(bytes: ByteArray, line: Int, project: Project): OpenFileHyperlinkInfo? {
|
private fun readDebugInfoForInlineFun(bytes: ByteArray, line: Int, project: Project): InlineFunctionHyperLinkInfo? {
|
||||||
val debugInfo = readDebugInfo(bytes) ?: return null
|
val smapData = readDebugInfo(bytes) ?: return null
|
||||||
|
|
||||||
val mappings = SMAPParser.parse(debugInfo)
|
val inlineInfo = arrayListOf<InlineFunctionHyperLinkInfo.InlineInfo>()
|
||||||
|
|
||||||
|
val (inlineFunctionBodyFile, inlineFunctionBodyLine) = parseStrata(smapData.kotlin1, line, project) ?: return null
|
||||||
|
inlineInfo.add(InlineFunctionHyperLinkInfo.InlineInfo.InlineFunctionBodyInfo(
|
||||||
|
inlineFunctionBodyFile.virtualFile,
|
||||||
|
inlineFunctionBodyLine))
|
||||||
|
|
||||||
|
val kotlin2 = parseStrata(smapData.kotlin2, line, project)
|
||||||
|
if (kotlin2 != null) {
|
||||||
|
inlineInfo.add(InlineFunctionHyperLinkInfo.InlineInfo.CallSiteInfo(
|
||||||
|
kotlin2.first.virtualFile,
|
||||||
|
kotlin2.second))
|
||||||
|
}
|
||||||
|
|
||||||
|
return InlineFunctionHyperLinkInfo(project, inlineInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseStrata(strata: String?, line: Int, project: Project): Pair<KtFile, Int>? {
|
||||||
|
if (strata == null) return null
|
||||||
|
|
||||||
|
val mappings = SMAPParser.parse(strata)
|
||||||
|
|
||||||
val mappingInfo = mappings.fileMappings.firstOrNull {
|
val mappingInfo = mappings.fileMappings.firstOrNull {
|
||||||
it.getIntervalIfContains(line) != null
|
it.getIntervalIfContains(line) != null
|
||||||
@@ -122,10 +148,11 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
|
|||||||
|
|
||||||
val newJvmName = JvmClassName.byInternalName(mappingInfo.path)
|
val newJvmName = JvmClassName.byInternalName(mappingInfo.path)
|
||||||
val newSourceFile = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, newJvmName, mappingInfo.name) ?: return null
|
val newSourceFile = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, newJvmName, mappingInfo.name) ?: return null
|
||||||
return OpenFileHyperlinkInfo(project, newSourceFile.virtualFile, mappingInfo.getIntervalIfContains(line)!!.map(line) - 1)
|
|
||||||
|
return newSourceFile to mappingInfo.getIntervalIfContains(line)!!.mapDestToSource(line) - 1
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun readDebugInfo(bytes: ByteArray): String? {
|
private fun readDebugInfo(bytes: ByteArray): SmapData? {
|
||||||
val cr = ClassReader(bytes);
|
val cr = ClassReader(bytes);
|
||||||
var debugInfo: String? = null
|
var debugInfo: String? = null
|
||||||
cr.accept(object : ClassVisitor(InlineCodegenUtil.API) {
|
cr.accept(object : ClassVisitor(InlineCodegenUtil.API) {
|
||||||
@@ -133,7 +160,27 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
|
|||||||
debugInfo = debug
|
debugInfo = debug
|
||||||
}
|
}
|
||||||
}, ClassReader.SKIP_FRAMES and ClassReader.SKIP_CODE)
|
}, ClassReader.SKIP_FRAMES and ClassReader.SKIP_CODE)
|
||||||
return debugInfo
|
return debugInfo?.let { SmapData(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SmapData(debugInfo: String) {
|
||||||
|
var kotlin1: String? = null
|
||||||
|
private set
|
||||||
|
var kotlin2: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
val intervals = debugInfo.split(SMAP.END).filter { it.isNotBlank() }
|
||||||
|
when(intervals.count()) {
|
||||||
|
1 -> {
|
||||||
|
kotlin1 = intervals[0] + SMAP.END
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
kotlin1 = intervals[0] + SMAP.END
|
||||||
|
kotlin2 = intervals[1] + SMAP.END
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun FileMapping.getIntervalIfContains(destLine: Int) = lineMappings.firstOrNull { it.contains(destLine) }
|
private fun FileMapping.getIntervalIfContains(destLine: Int) = lineMappings.firstOrNull { it.contains(destLine) }
|
||||||
|
|||||||
Reference in New Issue
Block a user