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 org.jetbrains.kotlin.codegen.inline.FileMapping
|
||||
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.idea.refactoring.getLineCount
|
||||
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.Name
|
||||
import org.jetbrains.kotlin.name.tail
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.check
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
@@ -67,11 +69,15 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
|
||||
|
||||
val virtualFile = file.virtualFile ?: return null
|
||||
|
||||
return virtualFileForInlineCall(jvmClassName, virtualFile, lineNumber + 1, project) ?:
|
||||
OpenFileHyperlinkInfo(project, virtualFile, lineNumber)
|
||||
val hyperlinkInfoForInline = getHyperlinkInfoIfInline(jvmClassName, virtualFile, lineNumber + 1, project)
|
||||
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)
|
||||
|
||||
if (ProjectRootsUtil.isLibrarySourceFile(project, file)) {
|
||||
@@ -111,10 +117,30 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
|
||||
return null
|
||||
}
|
||||
|
||||
private fun readDebugInfoForInlineFun(bytes: ByteArray, line: Int, project: Project): OpenFileHyperlinkInfo? {
|
||||
val debugInfo = readDebugInfo(bytes) ?: return null
|
||||
private fun readDebugInfoForInlineFun(bytes: ByteArray, line: Int, project: Project): InlineFunctionHyperLinkInfo? {
|
||||
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 {
|
||||
it.getIntervalIfContains(line) != null
|
||||
@@ -122,10 +148,11 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
|
||||
|
||||
val newJvmName = JvmClassName.byInternalName(mappingInfo.path)
|
||||
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);
|
||||
var debugInfo: String? = null
|
||||
cr.accept(object : ClassVisitor(InlineCodegenUtil.API) {
|
||||
@@ -133,7 +160,27 @@ class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter
|
||||
debugInfo = debug
|
||||
}
|
||||
}, 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) }
|
||||
|
||||
Reference in New Issue
Block a user