Debugger: navigate to call site of inline function

#KT-11438 Fixed
This commit is contained in:
Natalia Ukhorskaya
2016-03-18 19:03:01 +03:00
committed by Michael Bogdanov
parent bdd24cfa7d
commit 02c5e005fe
2 changed files with 160 additions and 9 deletions
@@ -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) }