Move out JVM debugger functionality
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
plugins {
|
||||
kotlin("jvm")
|
||||
id("jps-compatible")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile(project(":compiler:backend"))
|
||||
compile(project(":compiler:backend-common"))
|
||||
compile(project(":compiler:light-classes"))
|
||||
compile(project(":idea:idea-core"))
|
||||
compile(project(":idea:ide-common"))
|
||||
compile(project(":idea:jvm-debugger:jvm-debugger-util"))
|
||||
compile(files("${System.getProperty("java.home")}/../lib/tools.jar"))
|
||||
|
||||
compileOnly(intellijPluginDep("stream-debugger"))
|
||||
|
||||
testCompile(project(":kotlin-test:kotlin-test-junit"))
|
||||
testCompile(commonDep("junit:junit"))
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
"test" { none() }
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.debugger.engine.DebuggerUtils
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.ObjectCollectedException
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClass
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClassOrNull
|
||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.getOrComputeClassNames
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.Cached
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.EMPTY
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.NonCached
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isObjectLiteral
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import java.util.*
|
||||
|
||||
class DebuggerClassNameProvider(
|
||||
private val debugProcess: DebugProcess,
|
||||
val findInlineUseSites: Boolean = true,
|
||||
val alwaysReturnLambdaParentClass: Boolean = true
|
||||
) {
|
||||
companion object {
|
||||
private val CLASS_ELEMENT_TYPES = arrayOf<Class<out PsiElement>>(
|
||||
KtFile::class.java,
|
||||
KtClassOrObject::class.java,
|
||||
KtProperty::class.java,
|
||||
KtNamedFunction::class.java,
|
||||
KtFunctionLiteral::class.java,
|
||||
KtAnonymousInitializer::class.java
|
||||
)
|
||||
|
||||
internal fun getRelevantElement(element: PsiElement?): PsiElement? {
|
||||
if (element == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (elementType in CLASS_ELEMENT_TYPES) {
|
||||
if (elementType.isInstance(element)) {
|
||||
return element
|
||||
}
|
||||
}
|
||||
|
||||
// Do not copy the array (*elementTypes) if the element is one we look for
|
||||
return runReadAction { PsiTreeUtil.getNonStrictParentOfType(element, *CLASS_ELEMENT_TYPES) }
|
||||
}
|
||||
}
|
||||
|
||||
private val inlineUsagesSearcher = InlineCallableUsagesSearcher(debugProcess)
|
||||
|
||||
/**
|
||||
* Returns classes in which the given line number *is* present.
|
||||
*/
|
||||
fun getClassesForPosition(position: SourcePosition): List<ReferenceType> = with(debugProcess) {
|
||||
val lineNumber = runReadAction { position.line }
|
||||
|
||||
return doGetClassesForPosition(position)
|
||||
.flatMap { className -> virtualMachineProxy.classesByName(className) }
|
||||
.flatMap { referenceType -> findTargetClasses(referenceType, lineNumber) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns classes names in JDI format (my.app.App$Nested) in which the given line number *may be* present.
|
||||
*/
|
||||
fun getOuterClassNamesForPosition(position: SourcePosition): List<String> {
|
||||
return doGetClassesForPosition(position).toList()
|
||||
}
|
||||
|
||||
private fun doGetClassesForPosition(position: SourcePosition): Set<String> {
|
||||
val relevantElement = runReadAction {
|
||||
position.elementAt?.let { getRelevantElement(it) }
|
||||
}
|
||||
|
||||
val result = getOrComputeClassNames(relevantElement) { element ->
|
||||
getOuterClassNamesForElement(element)
|
||||
}.toMutableSet()
|
||||
|
||||
for (lambda in position.readAction(::getLambdasAtLineIfAny)) {
|
||||
result += getOrComputeClassNames(lambda) { element ->
|
||||
getOuterClassNamesForElement(element)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
@Suppress("NON_TAIL_RECURSIVE_CALL")
|
||||
internal tailrec fun getOuterClassNamesForElement(element: PsiElement?): ComputedClassNames {
|
||||
if (element == null) return EMPTY
|
||||
|
||||
return when (element) {
|
||||
is KtScript -> {
|
||||
getClassType(element)?.let { return Cached(it) }
|
||||
return EMPTY
|
||||
}
|
||||
is KtFile -> {
|
||||
val fileClassName = runReadAction { JvmFileClassUtil.getFileClassInternalName(element) }.toJdiName()
|
||||
Cached(fileClassName)
|
||||
}
|
||||
is KtClassOrObject -> {
|
||||
val enclosingElementForLocal = runReadAction { KtPsiUtil.getEnclosingElementForLocalDeclaration(element) }
|
||||
when {
|
||||
enclosingElementForLocal != null ->
|
||||
// A local class
|
||||
getOuterClassNamesForElement(enclosingElementForLocal)
|
||||
runReadAction { element.isObjectLiteral() } ->
|
||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
else ->
|
||||
// Guaranteed to be non-local class or object
|
||||
element.readAction { _ ->
|
||||
if (element is KtClass && runReadAction { element.isInterface() }) {
|
||||
val name = getNameForNonLocalClass(element)
|
||||
|
||||
if (name != null)
|
||||
Cached(listOf(name, name + JvmAbi.DEFAULT_IMPLS_SUFFIX))
|
||||
else
|
||||
EMPTY
|
||||
} else {
|
||||
getNameForNonLocalClass(element)?.let { Cached(it) } ?: EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is KtProperty -> {
|
||||
val nonInlineClasses = if (runReadAction { element.isTopLevel }) {
|
||||
// Top level property
|
||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
} else {
|
||||
val enclosingElementForLocal = runReadAction { KtPsiUtil.getEnclosingElementForLocalDeclaration(element) }
|
||||
if (enclosingElementForLocal != null) {
|
||||
// Local class
|
||||
getOuterClassNamesForElement(enclosingElementForLocal)
|
||||
} else {
|
||||
val containingClassOrFile = runReadAction {
|
||||
PsiTreeUtil.getParentOfType(element, KtFile::class.java, KtClassOrObject::class.java)
|
||||
}
|
||||
|
||||
if (containingClassOrFile is KtObjectDeclaration && containingClassOrFile.isCompanionInReadAction) {
|
||||
// Properties from the companion object can be placed in the companion object's containing class
|
||||
(getOuterClassNamesForElement(containingClassOrFile.relevantParentInReadAction) +
|
||||
getOuterClassNamesForElement(containingClassOrFile)).distinct()
|
||||
} else if (containingClassOrFile != null) {
|
||||
getOuterClassNamesForElement(containingClassOrFile)
|
||||
} else {
|
||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (findInlineUseSites && (
|
||||
element.isInlineInReadAction ||
|
||||
runReadAction { element.accessors.any { it.hasModifier(KtTokens.INLINE_KEYWORD) } })
|
||||
) {
|
||||
nonInlineClasses + inlineUsagesSearcher.findInlinedCalls(element) { this.getOuterClassNamesForElement(it) }
|
||||
} else {
|
||||
return NonCached(nonInlineClasses.classNames)
|
||||
}
|
||||
}
|
||||
is KtNamedFunction -> {
|
||||
val classNamesOfContainingDeclaration = getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
|
||||
var nonInlineClasses: ComputedClassNames = classNamesOfContainingDeclaration
|
||||
|
||||
if (runReadAction { element.name == null || element.isLocal }) {
|
||||
val nameOfAnonymousClass = runReadAction { getClassType(element) }
|
||||
if (nameOfAnonymousClass != null) {
|
||||
nonInlineClasses += Cached(nameOfAnonymousClass)
|
||||
}
|
||||
}
|
||||
|
||||
if (!findInlineUseSites || !element.isInlineInReadAction) {
|
||||
return NonCached(nonInlineClasses.classNames)
|
||||
}
|
||||
|
||||
val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls(element) { this.getOuterClassNamesForElement(it) }
|
||||
|
||||
nonInlineClasses + inlineCallSiteClasses
|
||||
}
|
||||
is KtAnonymousInitializer -> {
|
||||
val initializerOwner = runReadAction { element.containingDeclaration }
|
||||
|
||||
if (initializerOwner is KtObjectDeclaration && initializerOwner.isCompanionInReadAction) {
|
||||
return getOuterClassNamesForElement(runReadAction { initializerOwner.containingClassOrObject })
|
||||
}
|
||||
|
||||
getOuterClassNamesForElement(initializerOwner)
|
||||
}
|
||||
is KtFunctionLiteral -> {
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||
|
||||
val nonInlinedLambdaClassName = runReadAction {
|
||||
asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName.toJdiName()
|
||||
}
|
||||
|
||||
if (!alwaysReturnLambdaParentClass && !InlineUtil.isInlinedArgument(element, typeMapper.bindingContext, true)) {
|
||||
return Cached(nonInlinedLambdaClassName)
|
||||
}
|
||||
|
||||
Cached(nonInlinedLambdaClassName) + getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
else -> getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
}
|
||||
|
||||
// Should be called in a read action
|
||||
private fun getClassType(element: KtElement): String? {
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||
asmTypeForAnonymousClassOrNull(typeMapper.bindingContext, element)?.let { return it.className }
|
||||
|
||||
val descriptor = typeMapper.bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, element]
|
||||
if (descriptor is ScriptDescriptor) {
|
||||
return typeMapper.mapClass(descriptor).className
|
||||
}
|
||||
|
||||
if (descriptor != null) {
|
||||
val containingDeclaration = descriptor.containingDeclaration
|
||||
if (containingDeclaration is ScriptDescriptor) {
|
||||
return typeMapper.mapClass(containingDeclaration).className
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getNameForNonLocalClass(nonLocalClassOrObject: KtClassOrObject): String? {
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(nonLocalClassOrObject)
|
||||
val descriptor = typeMapper.bindingContext[BindingContext.CLASS, nonLocalClassOrObject] ?: return null
|
||||
|
||||
val type = typeMapper.mapClass(descriptor)
|
||||
if (type.sort != Type.OBJECT) {
|
||||
return null
|
||||
}
|
||||
|
||||
return type.className
|
||||
}
|
||||
|
||||
private val KtDeclaration.isInlineInReadAction: Boolean
|
||||
get() = runReadAction { hasModifier(KtTokens.INLINE_KEYWORD) }
|
||||
|
||||
private val KtObjectDeclaration.isCompanionInReadAction: Boolean
|
||||
get() = runReadAction { isCompanion() }
|
||||
|
||||
private val PsiElement.relevantParentInReadAction
|
||||
get() = runReadAction { getRelevantElement(this.parent) }
|
||||
}
|
||||
|
||||
private fun String.toJdiName() = replace('/', '.')
|
||||
|
||||
private fun DebugProcess.findTargetClasses(outerClass: ReferenceType, lineAt: Int): List<ReferenceType> {
|
||||
val vmProxy = virtualMachineProxy
|
||||
|
||||
try {
|
||||
if (!outerClass.isPrepared) {
|
||||
return emptyList()
|
||||
}
|
||||
} catch (e: ObjectCollectedException) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val targetClasses = ArrayList<ReferenceType>(1)
|
||||
|
||||
try {
|
||||
for (location in outerClass.allLineLocations()) {
|
||||
val locationLine = location.lineNumber() - 1
|
||||
if (locationLine < 0) {
|
||||
// such locations are not correspond to real lines in code
|
||||
continue
|
||||
}
|
||||
|
||||
if (lineAt == locationLine) {
|
||||
val method = location.method()
|
||||
if (method == null || DebuggerUtils.isSynthetic(method) || method.isBridge) {
|
||||
// skip synthetic methods
|
||||
continue
|
||||
}
|
||||
|
||||
targetClasses += outerClass
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The same line number may appear in different classes so we have to scan nested classes as well.
|
||||
// For example, in the next example line 3 appears in both Foo and Foo$Companion.
|
||||
|
||||
/* class Foo {
|
||||
companion object {
|
||||
val a = Foo() /* line 3 */
|
||||
}
|
||||
} */
|
||||
|
||||
val nestedTypes = vmProxy.nestedTypes(outerClass)
|
||||
for (nested in nestedTypes) {
|
||||
targetClasses += findTargetClasses(nested, lineAt)
|
||||
}
|
||||
} catch (_: AbsentInformationException) {
|
||||
}
|
||||
|
||||
return targetClasses
|
||||
}
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
|
||||
abstract class DelegateSourcePosition(private var delegate: SourcePosition) : SourcePosition() {
|
||||
override fun getFile(): PsiFile = delegate.file
|
||||
override fun getElementAt(): PsiElement? = delegate.elementAt
|
||||
override fun getLine(): Int = delegate.line
|
||||
override fun getOffset(): Int = delegate.offset
|
||||
|
||||
override fun openEditor(requestFocus: Boolean): Editor = delegate.openEditor(requestFocus)
|
||||
|
||||
override fun canNavigate() = delegate.canNavigate()
|
||||
override fun canNavigateToSource() = delegate.canNavigateToSource()
|
||||
|
||||
override fun navigate(requestFocus: Boolean) {
|
||||
delegate.navigate(requestFocus)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = delegate.hashCode()
|
||||
override fun equals(other: Any?) = delegate == other
|
||||
|
||||
override fun toString() = "DSP($delegate)"
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.DiagnosticSuppressor
|
||||
|
||||
class DiagnosticSuppressorForDebugger : DiagnosticSuppressor {
|
||||
override fun isSuppressed(diagnostic: Diagnostic): Boolean {
|
||||
val element = diagnostic.psiElement
|
||||
val containingFile = element.containingFile
|
||||
|
||||
if (containingFile is KtCodeFragment) {
|
||||
val diagnosticFactory = diagnostic.factory
|
||||
return diagnosticFactory == Errors.UNSAFE_CALL
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
+112
@@ -0,0 +1,112 @@
|
||||
/*
|
||||
* 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.debugger
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.ui.MessageType
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.search.searches.ReferencesSearch
|
||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.DebuggerClassNameProvider.Companion.getRelevantElement
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isInsideOf
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
|
||||
class InlineCallableUsagesSearcher(private val myDebugProcess: DebugProcess) {
|
||||
fun findInlinedCalls(
|
||||
declaration: KtDeclaration,
|
||||
bindingContext: BindingContext = KotlinDebuggerCaches.getOrCreateTypeMapper(declaration).bindingContext,
|
||||
transformer: (PsiElement) -> ComputedClassNames
|
||||
): ComputedClassNames {
|
||||
if (!checkIfInline(declaration, bindingContext)) {
|
||||
return ComputedClassNames.EMPTY
|
||||
}
|
||||
else {
|
||||
val searchResult = hashSetOf<PsiElement>()
|
||||
val declarationName = runReadAction { declaration.name }
|
||||
|
||||
val task = Runnable {
|
||||
ReferencesSearch.search(declaration, getScopeForInlineDeclarationUsages(declaration)).forEach {
|
||||
if (!runReadAction { it.isImportUsage() }) {
|
||||
val usage = (it.element as? KtElement)?.let(::getRelevantElement)
|
||||
if (usage != null && !runReadAction { declaration.isAncestor(usage) }) {
|
||||
searchResult.add(usage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isSuccess = true
|
||||
val applicationEx = ApplicationManagerEx.getApplicationEx()
|
||||
if (applicationEx.isDispatchThread) {
|
||||
isSuccess = ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
task,
|
||||
"Compute class names for declaration $declarationName",
|
||||
true,
|
||||
myDebugProcess.project)
|
||||
}
|
||||
else {
|
||||
ProgressManager.getInstance().runProcess(task, EmptyProgressIndicator())
|
||||
}
|
||||
|
||||
if (!isSuccess) {
|
||||
XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(
|
||||
"Debugger can skip some executions of $declarationName because the computation of class names was interrupted",
|
||||
MessageType.WARNING
|
||||
).notify(myDebugProcess.project)
|
||||
}
|
||||
|
||||
val results = searchResult.map { transformer(it) }
|
||||
return ComputedClassNames(results.flatMap { it.classNames }, shouldBeCached = results.all { it.shouldBeCached })
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIfInline(declaration: KtDeclaration, bindingContext: BindingContext): Boolean {
|
||||
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration) ?: return false
|
||||
return when (descriptor) {
|
||||
is FunctionDescriptor -> InlineUtil.isInline(descriptor)
|
||||
is PropertyDescriptor -> InlineUtil.hasInlineAccessors(descriptor)
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun getScopeForInlineDeclarationUsages(inlineDeclaration: KtDeclaration): GlobalSearchScope {
|
||||
val virtualFile = runReadAction { inlineDeclaration.containingFile.virtualFile }
|
||||
return if (virtualFile != null && ProjectRootsUtil.isLibraryFile(myDebugProcess.project, virtualFile)) {
|
||||
myDebugProcess.searchScope.uniteWith(
|
||||
KotlinSourceFilterScope.librarySources(GlobalSearchScope.allScope(myDebugProcess.project), myDebugProcess.project))
|
||||
}
|
||||
else {
|
||||
myDebugProcess.searchScope
|
||||
}
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.debugger
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.ide.util.ModuleRendererFactory
|
||||
import com.intellij.openapi.fileEditor.FileEditor
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.ui.EditorNotificationPanel
|
||||
import com.intellij.ui.EditorNotifications
|
||||
import com.intellij.ui.components.JBList
|
||||
import com.intellij.xdebugger.XDebuggerManager
|
||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||
import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil.findFilesWithExactPackage
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class KotlinAlternativeSourceNotificationProvider(private val myProject: Project) : EditorNotifications.Provider<EditorNotificationPanel>() {
|
||||
override fun getKey(): Key<EditorNotificationPanel> {
|
||||
return KEY
|
||||
}
|
||||
|
||||
override fun createNotificationPanel(file: VirtualFile, fileEditor: FileEditor): EditorNotificationPanel? {
|
||||
if (!DebuggerSettings.getInstance().SHOW_ALTERNATIVE_SOURCE) {
|
||||
return null
|
||||
}
|
||||
|
||||
val session = XDebuggerManager.getInstance(myProject).currentSession
|
||||
if (session == null) {
|
||||
FILE_PROCESSED_KEY.set(file, null)
|
||||
return null
|
||||
}
|
||||
|
||||
val position = session.currentPosition
|
||||
if (file != position?.file) {
|
||||
FILE_PROCESSED_KEY.set(file, null)
|
||||
return null
|
||||
}
|
||||
|
||||
if (DumbService.getInstance(myProject).isDumb) return null
|
||||
|
||||
val ktFile = PsiManager.getInstance(myProject).findFile(file) as? KtFile ?: return null
|
||||
|
||||
val packageFqName = ktFile.packageFqName
|
||||
val fileName = ktFile.name
|
||||
|
||||
val alternativeKtFiles = findFilesWithExactPackage(packageFqName, GlobalSearchScope.allScope(myProject), myProject).filterTo(HashSet()) {
|
||||
it.name == fileName
|
||||
}
|
||||
|
||||
FILE_PROCESSED_KEY.set(file, true)
|
||||
|
||||
if (alternativeKtFiles.size <= 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
val currentFirstAlternatives: Collection<KtFile> = listOf(ktFile) + alternativeKtFiles.filter { it != ktFile }
|
||||
|
||||
val frame = session.currentStackFrame
|
||||
val locationDeclName: String? = when (frame) {
|
||||
is JavaStackFrame -> {
|
||||
val location = frame.descriptor.location
|
||||
location?.declaringType()?.name()
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
return AlternativeSourceNotificationPanel(currentFirstAlternatives, myProject, file, locationDeclName)
|
||||
}
|
||||
|
||||
private class AlternativeSourceNotificationPanel(
|
||||
alternatives: Collection<KtFile>,
|
||||
project: Project,
|
||||
file: VirtualFile,
|
||||
locationDeclName: String?
|
||||
) : EditorNotificationPanel() {
|
||||
private class ComboBoxFileElement(val ktFile: KtFile) {
|
||||
private val label: String by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val factory = ModuleRendererFactory.findInstance(ktFile)
|
||||
val moduleRenderer = factory.moduleRenderer
|
||||
moduleRenderer.getListCellRendererComponent(ourDummyList, ktFile, 1, false, false)
|
||||
moduleRenderer.text ?: ""
|
||||
}
|
||||
|
||||
override fun toString(): String = label
|
||||
|
||||
companion object {
|
||||
private val ourDummyList = JBList<KtFile>()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
setText("Alternative source available for file ${file.name}")
|
||||
|
||||
val items = alternatives.map { ComboBoxFileElement(it) }
|
||||
myLinksPanel.add(
|
||||
ComboBox<ComboBoxFileElement>(items.toTypedArray()).apply {
|
||||
addActionListener {
|
||||
val context = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val session = context.debuggerSession
|
||||
val ktFile = (selectedItem as ComboBoxFileElement).ktFile
|
||||
val vFile = ktFile.containingFile.virtualFile
|
||||
|
||||
when {
|
||||
session != null && vFile != null ->
|
||||
session.process.managerThread.schedule(object : DebuggerCommandImpl() {
|
||||
override fun action() {
|
||||
if (!StringUtil.isEmpty(locationDeclName)) {
|
||||
DebuggerUtilsEx.setAlternativeSourceUrl(locationDeclName, vFile.url, project)
|
||||
}
|
||||
|
||||
DebuggerUIUtil.invokeLater {
|
||||
FileEditorManager.getInstance(project).closeFile(file)
|
||||
session.refresh(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
else -> {
|
||||
FileEditorManager.getInstance(project).closeFile(file)
|
||||
ktFile.navigate(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
createActionLabel("Disable") {
|
||||
DebuggerSettings.getInstance().SHOW_ALTERNATIVE_SOURCE = false
|
||||
FILE_PROCESSED_KEY.set(file, null)
|
||||
val fileEditorManager = FileEditorManager.getInstance(project)
|
||||
val editor = fileEditorManager.getSelectedEditor(file)
|
||||
if (editor != null) {
|
||||
fileEditorManager.removeTopComponent(editor, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KEY = Key.create<EditorNotificationPanel>("KotlinAlternativeSource")
|
||||
|
||||
// FIXME: Share AlternativeSourceNotificationProvider.FILE_PROCESSED_KEY
|
||||
@Suppress("UNCHECKED_CAST", "DEPRECATION")
|
||||
private val FILE_PROCESSED_KEY = Key.findKeyByName("AlternativeSourceCheckDone") as Key<Boolean>
|
||||
}
|
||||
}
|
||||
+190
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.DebuggerContext
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.JavaValue
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.jdi.GeneratedLocation
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
||||
import com.intellij.xdebugger.frame.XNamedValue
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
|
||||
class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceProviderBase {
|
||||
private companion object {
|
||||
const val DEBUG_METADATA_KT = "kotlin.coroutines.jvm.internal.DebugMetadataKt"
|
||||
|
||||
tailrec fun findBaseContinuationSuperSupertype(type: ClassType): ClassType? {
|
||||
if (type.name() == "kotlin.coroutines.jvm.internal.BaseContinuationImpl") {
|
||||
return type
|
||||
}
|
||||
|
||||
return findBaseContinuationSuperSupertype(type.superclass() ?: return null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
|
||||
return getAsyncStackTrace(stackFrame.stackFrameProxy, suspendContext)
|
||||
}
|
||||
|
||||
fun getAsyncStackTrace(frameProxy: StackFrameProxyImpl, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
|
||||
val location = frameProxy.location()
|
||||
if (!location.isInKotlinSources()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val method = location.safeMethod() ?: return null
|
||||
val threadReference = frameProxy.threadProxy().threadReference
|
||||
|
||||
if (threadReference == null || !threadReference.isSuspended || !suspendContext.debugProcess.canRunEvaluation) {
|
||||
return null
|
||||
}
|
||||
|
||||
val evaluationContext = EvaluationContextImpl(suspendContext, frameProxy)
|
||||
val context = ExecutionContext(evaluationContext, frameProxy)
|
||||
|
||||
// DebugMetadataKt not found, probably old kotlin-stdlib version
|
||||
val debugMetadataKtType = context.findClassSafe(DEBUG_METADATA_KT) ?: return null
|
||||
|
||||
val asyncContext = AsyncStackTraceContext(context, method, debugMetadataKtType)
|
||||
return asyncContext.getAsyncStackTraceForSuspendLambda() ?: asyncContext.getAsyncStackTraceForSuspendFunction()
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendLambda(): List<StackFrameItem>? {
|
||||
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
|
||||
return null
|
||||
}
|
||||
|
||||
val thisObject = context.frameProxy.thisObject() ?: return null
|
||||
val thisType = thisObject.referenceType()
|
||||
|
||||
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
|
||||
return null
|
||||
}
|
||||
|
||||
return collectFrames(thisObject)
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendFunction(): List<StackFrameItem>? {
|
||||
if ("Lkotlin/coroutines/Continuation;)" !in method.signature()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val frameProxy = context.frameProxy
|
||||
val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
|
||||
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
|
||||
|
||||
return collectFrames(continuation)
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.collectFrames(continuation: ObjectReference): List<StackFrameItem>? {
|
||||
val frames = mutableListOf<StackFrameItem>()
|
||||
collectFramesRecursively(continuation, frames)
|
||||
return frames
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.collectFramesRecursively(continuation: ObjectReference, consumer: MutableList<StackFrameItem>) {
|
||||
val continuationType = continuation.referenceType() as? ClassType ?: return
|
||||
val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
|
||||
|
||||
val location = getLocation(continuation)
|
||||
val spilledVariables = getSpilledVariables(continuation) ?: emptyList()
|
||||
|
||||
if (location != null) {
|
||||
consumer += StackFrameItem(location, spilledVariables)
|
||||
}
|
||||
|
||||
val completionField = baseContinuationSupertype.fieldByName("completion") ?: return
|
||||
val completion = continuation.getValue(completionField) as? ObjectReference ?: return
|
||||
collectFramesRecursively(completion, consumer)
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getLocation(continuation: ObjectReference): Location? {
|
||||
val getStackTraceElementMethod = debugMetadataKtType.methodsByName(
|
||||
"getStackTraceElement",
|
||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)Ljava/lang/StackTraceElement;"
|
||||
).firstOrNull() ?: return null
|
||||
|
||||
val args = listOf(continuation)
|
||||
|
||||
val stackTraceElement = context.invokeMethod(debugMetadataKtType, getStackTraceElementMethod, args) as? ObjectReference
|
||||
?: return null
|
||||
|
||||
val stackTraceElementType = stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name }
|
||||
?: return null
|
||||
|
||||
fun getValue(name: String, desc: String): Value? {
|
||||
val method = stackTraceElementType.methodsByName(name, desc).single()
|
||||
return context.invokeMethod(stackTraceElement, method, emptyList())
|
||||
}
|
||||
|
||||
val className = (getValue("getClassName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||
val methodName = (getValue("getMethodName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||
val lineNumber = (getValue("getLineNumber", "()I") as? IntegerValue)?.value()?.takeIf { it >= 0 } ?: return null
|
||||
|
||||
val locationClass = context.findClassSafe(className) ?: return null
|
||||
return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
|
||||
val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName(
|
||||
"getSpilledVariableFieldMapping",
|
||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;"
|
||||
).firstOrNull() ?: return null
|
||||
|
||||
val args = listOf(continuation)
|
||||
|
||||
val rawSpilledVariables = context.invokeMethod(debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference
|
||||
?: return null
|
||||
|
||||
val length = rawSpilledVariables.length() / 2
|
||||
val spilledVariables = ArrayList<XNamedValue>(length)
|
||||
|
||||
for (index in 0 until length) {
|
||||
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: continue
|
||||
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: continue
|
||||
val field = continuation.referenceType().fieldByName(fieldName) ?: continue
|
||||
|
||||
val valueDescriptor = object : ValueDescriptorImpl(context.project) {
|
||||
override fun calcValueName() = variableName
|
||||
override fun calcValue(evaluationContext: EvaluationContextImpl?) = continuation.getValue(field)
|
||||
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
||||
throw EvaluateException("Spilled variable evaluation is not supported")
|
||||
}
|
||||
|
||||
spilledVariables += JavaValue.create(
|
||||
null,
|
||||
valueDescriptor,
|
||||
context.evaluationContext,
|
||||
context.debugProcess.xdebugProcess!!.nodeManager,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
return spilledVariables
|
||||
}
|
||||
|
||||
private fun ExecutionContext.findClassSafe(className: String): ClassType? {
|
||||
return try {
|
||||
findClass(className) as? ClassType
|
||||
} catch (e: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private class AsyncStackTraceContext(
|
||||
val context: ExecutionContext,
|
||||
val method: Method,
|
||||
val debugMetadataKtType: ClassType
|
||||
)
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.engine.AsyncStackTraceProvider
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
|
||||
interface KotlinCoroutinesAsyncStackTraceProviderBase : AsyncStackTraceProvider {
|
||||
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>?
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
|
||||
interface KotlinCoroutinesAsyncStackTraceProviderBase {
|
||||
fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>?
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2000-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
|
||||
import com.intellij.openapi.components.State
|
||||
import com.intellij.openapi.components.Storage
|
||||
import com.intellij.openapi.options.Configurable
|
||||
import com.intellij.openapi.options.SimpleConfigurable
|
||||
import com.intellij.openapi.util.Getter
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil
|
||||
import com.intellij.xdebugger.XDebuggerUtil
|
||||
import com.intellij.xdebugger.settings.DebuggerSettingsCategory
|
||||
import com.intellij.xdebugger.settings.XDebuggerSettings
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingConfigurableUi
|
||||
|
||||
@State(name = "KotlinDebuggerSettings", storages = arrayOf(Storage("kotlin_debug.xml")))
|
||||
class KotlinDebuggerSettings : XDebuggerSettings<KotlinDebuggerSettings>("kotlin_debugger"), Getter<KotlinDebuggerSettings> {
|
||||
var DEBUG_RENDER_DELEGATED_PROPERTIES: Boolean = true
|
||||
var DEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES: Boolean = true
|
||||
var DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED: Boolean = false
|
||||
|
||||
companion object {
|
||||
fun getInstance(): KotlinDebuggerSettings {
|
||||
return XDebuggerUtil.getInstance()?.getDebuggerSettings(KotlinDebuggerSettings::class.java)!!
|
||||
}
|
||||
}
|
||||
|
||||
override fun createConfigurables(category: DebuggerSettingsCategory): Collection<Configurable?> {
|
||||
return when (category) {
|
||||
DebuggerSettingsCategory.STEPPING ->
|
||||
listOf(SimpleConfigurable.create(
|
||||
"reference.idesettings.debugger.kotlin.stepping",
|
||||
"Kotlin",
|
||||
KotlinSteppingConfigurableUi::class.java,
|
||||
this))
|
||||
DebuggerSettingsCategory.DATA_VIEWS ->
|
||||
listOf(SimpleConfigurable.create(
|
||||
"reference.idesettings.debugger.kotlin.data.view",
|
||||
"Kotlin",
|
||||
KotlinDelegatedPropertyRendererConfigurableUi::class.java,
|
||||
this))
|
||||
else -> listOf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getState() = this
|
||||
override fun get() = this
|
||||
|
||||
override fun loadState(state: KotlinDebuggerSettings) {
|
||||
XmlSerializerUtil.copyBean<KotlinDebuggerSettings>(state, this)
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.debugger.KotlinDelegatedPropertyRendererConfigurableUi">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="99d36" class="javax.swing.JCheckBox" binding="renderDelegatedProperties">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<selected value="false"/>
|
||||
<text resource-bundle="org/jetbrains/kotlin/idea/KotlinBundle" key="debugger.data.view.delegated.properties"/>
|
||||
</properties>
|
||||
</component>
|
||||
<vspacer id="c37da">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger;
|
||||
|
||||
|
||||
import com.intellij.openapi.options.ConfigurableUi;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class KotlinDelegatedPropertyRendererConfigurableUi implements ConfigurableUi<KotlinDebuggerSettings> {
|
||||
private JCheckBox renderDelegatedProperties;
|
||||
private JPanel myPanel;
|
||||
|
||||
@Override
|
||||
public void reset(@NotNull KotlinDebuggerSettings settings) {
|
||||
boolean flag = settings.getDEBUG_RENDER_DELEGATED_PROPERTIES();
|
||||
renderDelegatedProperties.setSelected(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified(@NotNull KotlinDebuggerSettings settings) {
|
||||
return settings.getDEBUG_RENDER_DELEGATED_PROPERTIES() != renderDelegatedProperties.isSelected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(@NotNull KotlinDebuggerSettings settings) {
|
||||
settings.setDEBUG_RENDER_DELEGATED_PROPERTIES(renderDelegatedProperties.isSelected());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return myPanel;
|
||||
}
|
||||
}
|
||||
+196
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.FrameExtraVariablesProvider
|
||||
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImports
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImportsImpl
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.util.text.CharArrayUtil
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineEndOffset
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class KotlinFrameExtraVariablesProvider : FrameExtraVariablesProvider {
|
||||
override fun isAvailable(sourcePosition: SourcePosition, evalContext: EvaluationContext): Boolean {
|
||||
if (runReadAction { sourcePosition.line } < 0) return false
|
||||
return sourcePosition.file.fileType == KotlinFileType.INSTANCE && DebuggerSettings.getInstance().AUTO_VARIABLES_MODE
|
||||
}
|
||||
|
||||
override fun collectVariables(
|
||||
sourcePosition: SourcePosition, evalContext: EvaluationContext, alreadyCollected: MutableSet<String>): Set<TextWithImports> {
|
||||
return runReadAction { findAdditionalExpressions(sourcePosition) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun findAdditionalExpressions(position: SourcePosition): Set<TextWithImports> {
|
||||
val line = position.line
|
||||
val file = position.file
|
||||
|
||||
val vFile = file.virtualFile
|
||||
val doc = if (vFile != null) FileDocumentManager.getInstance().getDocument(vFile) else null
|
||||
if (doc == null || doc.lineCount == 0 || line > (doc.lineCount - 1)) {
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
val offset = file.getLineStartOffset(line)?.takeIf { it > 0 } ?: return emptySet()
|
||||
|
||||
val elem = file.findElementAt(offset) ?: return emptySet()
|
||||
val containingElement = getContainingElement(elem) ?: elem
|
||||
|
||||
val limit = getLineRangeForElement(containingElement, doc)
|
||||
|
||||
var startLine = max(limit.startOffset, line)
|
||||
while (startLine - 1 > limit.startOffset && shouldSkipLine(file, doc, startLine - 1)) {
|
||||
startLine--
|
||||
}
|
||||
|
||||
var endLine = min(limit.endOffset, line)
|
||||
while (endLine + 1 < limit.endOffset && shouldSkipLine(file, doc, endLine + 1)) {
|
||||
endLine++
|
||||
}
|
||||
|
||||
val startOffset = file.getLineStartOffset(startLine) ?: return emptySet()
|
||||
val endOffset = file.getLineEndOffset(endLine) ?: return emptySet()
|
||||
|
||||
if (startOffset >= endOffset) return emptySet()
|
||||
|
||||
val lineRange = TextRange(startOffset, endOffset)
|
||||
if (lineRange.isEmpty) return emptySet()
|
||||
|
||||
val expressions = LinkedHashSet<TextWithImports>()
|
||||
|
||||
val variablesCollector = VariablesCollector(lineRange, expressions)
|
||||
containingElement.accept(variablesCollector)
|
||||
|
||||
return expressions
|
||||
}
|
||||
|
||||
private fun getContainingElement(element: PsiElement): KtElement? {
|
||||
val contElement = PsiTreeUtil.getParentOfType(element, KtDeclaration::class.java) ?: PsiTreeUtil.getParentOfType(element, KtElement::class.java)
|
||||
if (contElement is KtProperty && contElement.isLocal) {
|
||||
val parent = contElement.parent
|
||||
return getContainingElement(parent)
|
||||
}
|
||||
|
||||
if (contElement is KtDeclarationWithBody) {
|
||||
return contElement.bodyExpression
|
||||
}
|
||||
return contElement
|
||||
}
|
||||
|
||||
private fun getLineRangeForElement(containingElement: PsiElement, doc: Document): TextRange {
|
||||
val elemRange = containingElement.textRange
|
||||
return TextRange(doc.getLineNumber(elemRange.startOffset), doc.getLineNumber(elemRange.endOffset))
|
||||
}
|
||||
|
||||
private fun shouldSkipLine(file: PsiFile, doc: Document, line: Int): Boolean {
|
||||
val start = CharArrayUtil.shiftForward(doc.charsSequence, doc.getLineStartOffset(line), " \n\t")
|
||||
val end = doc.getLineEndOffset(line)
|
||||
if (start >= end) {
|
||||
return true
|
||||
}
|
||||
|
||||
val elemAtOffset = file.findElementAt(start)
|
||||
val topmostElementAtOffset = CodeInsightUtils.getTopmostElementAtOffset(elemAtOffset!!, start)
|
||||
return topmostElementAtOffset !is KtDeclaration
|
||||
}
|
||||
|
||||
private class VariablesCollector(
|
||||
private val myLineRange: TextRange,
|
||||
private val myExpressions: MutableSet<TextWithImports>
|
||||
) : KtTreeVisitorVoid() {
|
||||
|
||||
override fun visitKtElement(element: KtElement) {
|
||||
if (element.isInRange()) {
|
||||
super.visitKtElement(element)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
|
||||
if (expression.isInRange()) {
|
||||
val selector = expression.selectorExpression
|
||||
if (selector is KtReferenceExpression) {
|
||||
if (isRefToProperty(selector)) {
|
||||
myExpressions.add(expression.createText())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
super.visitQualifiedExpression(expression)
|
||||
}
|
||||
|
||||
private fun isRefToProperty(expression: KtReferenceExpression): Boolean {
|
||||
// NB: analyze() cannot be called here, because DELEGATED_PROPERTY_RESOLVED_CALL will be always null
|
||||
// Looks like a bug
|
||||
@Suppress("DEPRECATION")
|
||||
val context = expression.analyzeWithAllCompilerChecks().bindingContext
|
||||
val descriptor = context[BindingContext.REFERENCE_TARGET, expression]
|
||||
if (descriptor is PropertyDescriptor) {
|
||||
val getter = descriptor.getter
|
||||
return (getter == null || context[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getter] == null) &&
|
||||
descriptor.compileTimeInitializer == null
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visitReferenceExpression(expression: KtReferenceExpression) {
|
||||
if (expression.isInRange()) {
|
||||
if (isRefToProperty(expression)) {
|
||||
myExpressions.add(expression.createText())
|
||||
}
|
||||
}
|
||||
super.visitReferenceExpression(expression)
|
||||
}
|
||||
|
||||
private fun KtElement.isInRange(): Boolean = myLineRange.intersects(this.textRange)
|
||||
private fun KtElement.createText(): TextWithImports = TextWithImportsImpl(CodeFragmentKind.EXPRESSION, this.text)
|
||||
|
||||
override fun visitClass(klass: KtClass) {
|
||||
// Do not show expressions used in local classes
|
||||
}
|
||||
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
// Do not show expressions used in local functions
|
||||
}
|
||||
|
||||
override fun visitObjectLiteralExpression(expression: KtObjectLiteralExpression) {
|
||||
// Do not show expressions used in anonymous objects
|
||||
}
|
||||
|
||||
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
|
||||
// Do not show expressions used in lambdas
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger;
|
||||
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpointType;
|
||||
import com.jetbrains.javascript.debugger.JavaScriptDebugAware;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType;
|
||||
|
||||
public class KotlinJavaScriptDebugAware extends JavaScriptDebugAware {
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends XLineBreakpointType<?>> getBreakpointTypeClass() {
|
||||
return KotlinLineBreakpointType.class;
|
||||
}
|
||||
}
|
||||
+358
@@ -0,0 +1,358 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger
|
||||
|
||||
import com.intellij.debugger.MultiRequestPositionManager
|
||||
import com.intellij.debugger.NoDataException
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.PositionManagerEx
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.requests.ClassPrepareRequestor
|
||||
import com.intellij.openapi.fileTypes.FileType
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.util.Computable
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.impl.compiled.ClsFileImpl
|
||||
import com.intellij.psi.search.DelegatingGlobalSearchScope
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.ThreeState
|
||||
import com.intellij.xdebugger.frame.XStackFrame
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.request.ClassPrepareRequest
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||
import org.jetbrains.kotlin.idea.core.KotlinFileTypeFactory
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineCount
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import com.intellij.debugger.engine.DebuggerUtils as JDebuggerUtils
|
||||
|
||||
class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiRequestPositionManager, PositionManagerEx() {
|
||||
private val allKotlinFilesScope =
|
||||
object : DelegatingGlobalSearchScope(
|
||||
KotlinSourceFilterScope.projectAndLibrariesSources(GlobalSearchScope.allScope(myDebugProcess.project), myDebugProcess.project)
|
||||
) {
|
||||
private val projectIndex = ProjectRootManager.getInstance(myDebugProcess.project).fileIndex
|
||||
private val scopeComparator =
|
||||
Comparator.comparing(projectIndex::isInSourceContent)
|
||||
.thenComparing(projectIndex::isInLibrarySource)
|
||||
.thenComparing { file1, file2 -> super.compare(file1, file2) }
|
||||
|
||||
override fun compare(file1: VirtualFile, file2: VirtualFile): Int {
|
||||
return scopeComparator.compare(file1, file2)
|
||||
}
|
||||
}
|
||||
|
||||
private val sourceSearchScopes: List<GlobalSearchScope> = listOf(
|
||||
myDebugProcess.searchScope,
|
||||
allKotlinFilesScope
|
||||
)
|
||||
|
||||
override fun getAcceptedFileTypes(): Set<FileType> = KotlinFileTypeFactory.KOTLIN_FILE_TYPES_SET
|
||||
|
||||
override fun evaluateCondition(context: EvaluationContext, frame: StackFrameProxyImpl, location: Location, expression: String): ThreeState? {
|
||||
return ThreeState.UNSURE
|
||||
}
|
||||
|
||||
override fun createStackFrame(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl, location: Location): XStackFrame? {
|
||||
if (location.isInKotlinSources()) {
|
||||
return KotlinStackFrame(frame)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getSourcePosition(location: Location?): SourcePosition? {
|
||||
if (location == null) throw NoDataException.INSTANCE
|
||||
|
||||
val fileName = location.safeSourceName() ?: throw NoDataException.INSTANCE
|
||||
val lineNumber = location.safeLineNumber()
|
||||
if (lineNumber < 0) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
if (!DebuggerUtils.isKotlinSourceFile(fileName)) throw NoDataException.INSTANCE
|
||||
|
||||
val psiFile = getPsiFileByLocation(location)?.let {
|
||||
replaceWithAlternativeSource(it, location)
|
||||
}
|
||||
|
||||
if (psiFile == null) {
|
||||
val isKotlinStrataAvailable = location.declaringType().containsKotlinStrata()
|
||||
if (isKotlinStrataAvailable) {
|
||||
try {
|
||||
val javaSourceFileName = location.sourceName("Java")
|
||||
val javaClassName = JvmClassName.byInternalName(defaultInternalName(location))
|
||||
val project = myDebugProcess.project
|
||||
|
||||
val defaultPsiFile = DebuggerUtils.findSourceFileForClass(
|
||||
project, sourceSearchScopes, javaClassName, javaSourceFileName, location)
|
||||
|
||||
if (defaultPsiFile != null) {
|
||||
return SourcePosition.createFromLine(defaultPsiFile, 0)
|
||||
}
|
||||
}
|
||||
catch (e: AbsentInformationException) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
if (psiFile !is KtFile) throw NoDataException.INSTANCE
|
||||
|
||||
val sourceLineNumber = location.safeSourceLineNumber()
|
||||
if (sourceLineNumber < 0) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
val lambdaOrFunIfInside = getLambdaOrFunIfInside(location, psiFile, sourceLineNumber)
|
||||
if (lambdaOrFunIfInside != null) {
|
||||
return SourcePosition.createFromElement(lambdaOrFunIfInside.bodyExpression!!)
|
||||
}
|
||||
|
||||
val elementInDeclaration = getElementForDeclarationLine(location, psiFile, sourceLineNumber)
|
||||
if (elementInDeclaration != null) {
|
||||
return SourcePosition.createFromElement(elementInDeclaration)
|
||||
}
|
||||
|
||||
if (sourceLineNumber > psiFile.getLineCount() && myDebugProcess.isDexDebug()) {
|
||||
val (line, ktFile) = ktLocationInfo(location, true, myDebugProcess.project, false, psiFile)
|
||||
return SourcePosition.createFromLine(ktFile ?: psiFile, line - 1)
|
||||
}
|
||||
|
||||
val sameLineLocations = location.safeMethod()?.safeAllLineLocations()?.filter {
|
||||
it.safeLineNumber() == lineNumber && it.safeSourceName() == fileName
|
||||
}
|
||||
|
||||
if (sameLineLocations != null) {
|
||||
// There're several locations for same source line. If same source position would be created for all of them,
|
||||
// breakpoints at this line will stop on every location.
|
||||
// Each location is probably some code in arguments between inlined invocations (otherwise same line locations would
|
||||
// have been merged into one), but it's impossible to correctly map locations to actual source expressions now.
|
||||
val locationIndex = sameLineLocations.indexOf(location)
|
||||
if (locationIndex > 0) {
|
||||
/*
|
||||
`finally {}` block code is placed in the class file twice.
|
||||
Unless the debugger metadata is available, we can't figure out if we are inside `finally {}`, so we have to check it using PSI.
|
||||
This is conceptually wrong and won't work in some cases, but it's still better than nothing.
|
||||
*/
|
||||
val elementAt = psiFile.getLineStartOffset(lineNumber)?.let { psiFile.findElementAt(it) }
|
||||
val isInsideDuplicatedFinally = elementAt != null && elementAt.getStrictParentOfType<KtFinallySection>() != null
|
||||
if (!isInsideDuplicatedFinally) {
|
||||
return KotlinReentrantSourcePosition(SourcePosition.createFromLine(psiFile, sourceLineNumber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SourcePosition.createFromLine(psiFile, sourceLineNumber)
|
||||
}
|
||||
|
||||
class KotlinReentrantSourcePosition(delegate: SourcePosition) : DelegateSourcePosition(delegate)
|
||||
|
||||
private fun replaceWithAlternativeSource(psiFile: PsiFile, location: Location): PsiFile {
|
||||
fun findAlternativeSource(): PsiFile? {
|
||||
val qName = location.declaringType().name()
|
||||
val alternativeFileUrl = DebuggerUtilsEx.getAlternativeSourceUrl(qName, myDebugProcess.project) ?: return null
|
||||
val alternativePsiFile = VirtualFileManager.getInstance().findFileByUrl(alternativeFileUrl) ?: return null
|
||||
return psiFile.manager.findFile(alternativePsiFile)
|
||||
}
|
||||
|
||||
return findAlternativeSource() ?: psiFile
|
||||
}
|
||||
|
||||
// Returns a property or a constructor if debugger stops at class declaration
|
||||
private fun getElementForDeclarationLine(location: Location, file: KtFile, lineNumber: Int): KtElement? {
|
||||
val lineStartOffset = file.getLineStartOffset(lineNumber) ?: return null
|
||||
val elementAt = file.findElementAt(lineStartOffset)
|
||||
val contextElement = getContextElement(elementAt)
|
||||
|
||||
if (contextElement !is KtClass) return null
|
||||
|
||||
val methodName = location.method().name()
|
||||
return when {
|
||||
JvmAbi.isGetterName(methodName) -> {
|
||||
val parameterForGetter = contextElement.primaryConstructor?.valueParameters?.firstOrNull {
|
||||
it.hasValOrVar() && it.name != null && JvmAbi.getterName(it.name!!) == methodName
|
||||
} ?: return null
|
||||
parameterForGetter
|
||||
}
|
||||
methodName == "<init>" -> contextElement.primaryConstructor
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLambdaOrFunIfInside(location: Location, file: KtFile, lineNumber: Int): KtFunction? {
|
||||
val currentLocationFqName = location.declaringType().name() ?: return null
|
||||
|
||||
val start = CodeInsightUtils.getStartLineOffset(file, lineNumber)
|
||||
val end = CodeInsightUtils.getEndLineOffset(file, lineNumber)
|
||||
if (start == null || end == null) return null
|
||||
|
||||
val literalsOrFunctions = getLambdasAtLineIfAny(file, lineNumber)
|
||||
if (literalsOrFunctions.isEmpty()) return null
|
||||
|
||||
val elementAt = file.findElementAt(start) ?: return null
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(elementAt)
|
||||
|
||||
val currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(FqName(currentLocationFqName))
|
||||
.internalName.replace('/', '.')
|
||||
|
||||
for (literal in literalsOrFunctions) {
|
||||
if (InlineUtil.isInlinedArgument(literal, typeMapper.bindingContext, true)) {
|
||||
if (isInsideInlineArgument(literal, location, myDebugProcess as DebugProcessImpl, typeMapper.bindingContext)) {
|
||||
return literal
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
val internalClassNames = DebuggerClassNameProvider(myDebugProcess, alwaysReturnLambdaParentClass = false)
|
||||
.getOuterClassNamesForElement(literal.firstChild)
|
||||
.classNames
|
||||
|
||||
if (internalClassNames.any { it == currentLocationClassName }) {
|
||||
return literal
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getPsiFileByLocation(location: Location): PsiFile? {
|
||||
val sourceName = location.safeSourceName() ?: return null
|
||||
|
||||
val referenceInternalName = try {
|
||||
if (location.declaringType().containsKotlinStrata()) {
|
||||
//replace is required for windows
|
||||
location.sourcePath().replace('\\', '/')
|
||||
}
|
||||
else {
|
||||
defaultInternalName(location)
|
||||
}
|
||||
}
|
||||
catch (e: AbsentInformationException) {
|
||||
defaultInternalName(location)
|
||||
}
|
||||
|
||||
val className = JvmClassName.byInternalName(referenceInternalName)
|
||||
|
||||
val project = myDebugProcess.project
|
||||
|
||||
return DebuggerUtils.findSourceFileForClass(project, sourceSearchScopes, className, sourceName, location)
|
||||
}
|
||||
|
||||
private fun defaultInternalName(location: Location): String {
|
||||
//no stratum or source path => use default one
|
||||
val referenceFqName = location.declaringType().name()
|
||||
// JDI names are of form "package.Class$InnerClass"
|
||||
return referenceFqName.replace('.', '/')
|
||||
}
|
||||
|
||||
override fun getAllClasses(sourcePosition: SourcePosition): List<ReferenceType> {
|
||||
val psiFile = sourcePosition.file
|
||||
if (psiFile is KtFile) {
|
||||
if (!ProjectRootsUtil.isInProjectOrLibSource(psiFile)) return emptyList()
|
||||
return DebuggerClassNameProvider(myDebugProcess).getClassesForPosition(sourcePosition)
|
||||
}
|
||||
|
||||
if (psiFile is ClsFileImpl) {
|
||||
val decompiledPsiFile = psiFile.readAction { it.decompiledPsiFile }
|
||||
if (decompiledPsiFile is KtClsFile && runReadAction { sourcePosition.line } == -1) {
|
||||
val className = JvmFileClassUtil.getFileClassInternalName(decompiledPsiFile)
|
||||
return myDebugProcess.virtualMachineProxy.classesByName(className)
|
||||
}
|
||||
}
|
||||
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
fun originalClassNamesForPosition(position: SourcePosition): List<String> {
|
||||
return DebuggerClassNameProvider(myDebugProcess, findInlineUseSites = false).getOuterClassNamesForPosition(position)
|
||||
}
|
||||
|
||||
override fun locationsOfLine(type: ReferenceType, position: SourcePosition): List<Location> {
|
||||
if (position.file !is KtFile) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
try {
|
||||
if (myDebugProcess.isDexDebug()) {
|
||||
val inlineLocations = runReadAction { getLocationsOfInlinedLine(type, position, myDebugProcess.searchScope) }
|
||||
if (!inlineLocations.isEmpty()) {
|
||||
return inlineLocations
|
||||
}
|
||||
}
|
||||
|
||||
val line = position.line + 1
|
||||
|
||||
val locations = type.locationsOfLine(KOTLIN_STRATA_NAME, null, line)
|
||||
if (locations == null || locations.isEmpty()) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
return locations.filter { it.sourceName(KOTLIN_STRATA_NAME) == position.file.name }
|
||||
}
|
||||
catch (e: AbsentInformationException) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Since Idea 14.0.3 use createPrepareRequests fun")
|
||||
override fun createPrepareRequest(classPrepareRequestor: ClassPrepareRequestor, sourcePosition: SourcePosition): ClassPrepareRequest? {
|
||||
return createPrepareRequests(classPrepareRequestor, sourcePosition).firstOrNull()
|
||||
}
|
||||
|
||||
override fun createPrepareRequests(requestor: ClassPrepareRequestor, position: SourcePosition): List<ClassPrepareRequest> {
|
||||
if (position.file !is KtFile) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
return DumbService.getInstance(myDebugProcess.project).runReadActionInSmartMode(Computable {
|
||||
val classNames = DebuggerClassNameProvider(myDebugProcess).getOuterClassNamesForPosition(position)
|
||||
classNames.flatMap { name ->
|
||||
listOfNotNull(
|
||||
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, name),
|
||||
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, "$name$*")
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <U, V> U.readAction(crossinline f: (U) -> V): V {
|
||||
return runReadAction { f(this) }
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger;
|
||||
|
||||
import com.intellij.debugger.PositionManager;
|
||||
import com.intellij.debugger.PositionManagerFactory;
|
||||
import com.intellij.debugger.engine.DebugProcess;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class KotlinPositionManagerFactory extends PositionManagerFactory {
|
||||
@Override
|
||||
public PositionManager createPositionManager(@NotNull DebugProcess process) {
|
||||
return new KotlinPositionManager(process);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.SourcePositionHighlighter
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.kotlin.psi.KtFunctionLiteral
|
||||
|
||||
class KotlinSourcePositionHighlighter: SourcePositionHighlighter() {
|
||||
override fun getHighlightRange(sourcePosition: SourcePosition?): TextRange? {
|
||||
val lambda = sourcePosition?.elementAt?.parent
|
||||
if (lambda is KtFunctionLiteral) {
|
||||
return lambda.textRange
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.SourcePositionProvider
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextUtil
|
||||
import com.intellij.debugger.impl.PositionUtil
|
||||
import com.intellij.debugger.ui.tree.FieldDescriptor
|
||||
import com.intellij.debugger.ui.tree.LocalVariableDescriptor
|
||||
import com.intellij.debugger.ui.tree.NodeDescriptor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.ClassNotPreparedException
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContextUtils
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
|
||||
class KotlinSourcePositionProvider : SourcePositionProvider() {
|
||||
override fun computeSourcePosition(descriptor: NodeDescriptor, project: Project, context: DebuggerContextImpl, nearest: Boolean): SourcePosition? {
|
||||
if (context.frameProxy == null) return null
|
||||
|
||||
if (descriptor is FieldDescriptor) {
|
||||
return computeSourcePosition(descriptor, project, context, nearest)
|
||||
}
|
||||
|
||||
if (descriptor is LocalVariableDescriptor) {
|
||||
return computeSourcePosition(descriptor, project, context, nearest)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun computeSourcePosition(
|
||||
descriptor: LocalVariableDescriptor,
|
||||
project: Project,
|
||||
context: DebuggerContextImpl,
|
||||
nearest: Boolean
|
||||
): SourcePosition? {
|
||||
val place = PositionUtil.getContextElement(context) ?: return null
|
||||
if (place.containingFile !is KtFile) return null
|
||||
|
||||
val contextElement = getContextElement(place) ?: return null
|
||||
|
||||
val codeFragment = KtPsiFactory(project).createExpressionCodeFragment(descriptor.name, contextElement)
|
||||
val expression = codeFragment.getContentElement()
|
||||
if (expression is KtSimpleNameExpression) {
|
||||
val bindingContext = expression.analyze(BodyResolveMode.PARTIAL)
|
||||
val declarationDescriptor = BindingContextUtils.extractVariableDescriptorFromReference(bindingContext, expression)
|
||||
val sourceElement = declarationDescriptor?.source
|
||||
if (sourceElement is KotlinSourceElement) {
|
||||
val element = sourceElement.getPsi() ?: return null
|
||||
if (nearest) {
|
||||
return DebuggerContextUtil.findNearest(context, element, element.containingFile)
|
||||
}
|
||||
return SourcePosition.createFromOffset(element.containingFile, element.textOffset)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun computeSourcePosition(descriptor: FieldDescriptor, project: Project, context: DebuggerContextImpl, nearest: Boolean): SourcePosition? {
|
||||
val fieldName = descriptor.field.name()
|
||||
|
||||
if (fieldName == AsmUtil.CAPTURED_THIS_FIELD
|
||||
|| fieldName == AsmUtil.CAPTURED_RECEIVER_FIELD
|
||||
|| fieldName.startsWith(AsmUtil.LABELED_THIS_FIELD)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
val type = descriptor.field.declaringType()
|
||||
val myClass = findClassByType(project, type, context)?.navigationElement as? KtClassOrObject ?: return null
|
||||
|
||||
val field = myClass.declarations.firstOrNull { fieldName == it.name } ?: return null
|
||||
|
||||
if (nearest) {
|
||||
return DebuggerContextUtil.findNearest(context, field, myClass.containingFile)
|
||||
}
|
||||
return SourcePosition.createFromOffset(field.containingFile, field.textOffset)
|
||||
}
|
||||
|
||||
private fun findClassByType(project: Project, type: ReferenceType, context: DebuggerContextImpl): PsiElement? {
|
||||
val session = context.debuggerSession
|
||||
val scope = session?.searchScope ?: GlobalSearchScope.allScope(project)
|
||||
val className = JvmClassName.byInternalName(type.name()).fqNameForClassNameWithoutDollars.asString()
|
||||
|
||||
val myClass = JavaPsiFacade.getInstance(project).findClass(className, scope)
|
||||
if (myClass != null) return myClass
|
||||
|
||||
val position = getLastSourcePosition(type, context)
|
||||
if (position != null) {
|
||||
val element = position.elementAt
|
||||
if (element != null) {
|
||||
return element.getStrictParentOfType<KtClassOrObject>()
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getLastSourcePosition(type: ReferenceType, context: DebuggerContextImpl): SourcePosition? {
|
||||
val debugProcess = context.debugProcess
|
||||
if (debugProcess != null) {
|
||||
try {
|
||||
val locations = type.allLineLocations()
|
||||
if (!locations.isEmpty()) {
|
||||
val lastLocation = locations.get(locations.size - 1)
|
||||
return debugProcess.positionManager.getSourcePosition(lastLocation)
|
||||
}
|
||||
}
|
||||
catch (ignored: AbsentInformationException) {
|
||||
}
|
||||
catch (ignored: ClassNotPreparedException) {
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
+416
@@ -0,0 +1,416 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger
|
||||
|
||||
import com.intellij.debugger.DebuggerContext
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.JavaValue
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.impl.descriptors.data.DescriptorData
|
||||
import com.intellij.debugger.impl.descriptors.data.DisplayKey
|
||||
import com.intellij.debugger.impl.descriptors.data.SimpleDisplayKey
|
||||
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
||||
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
||||
import com.intellij.debugger.ui.impl.watch.ThisDescriptorImpl
|
||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiExpression
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import com.intellij.xdebugger.frame.XValue
|
||||
import com.intellij.xdebugger.frame.XValueChildrenList
|
||||
import com.sun.jdi.ObjectReference
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.Type
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil.THIS
|
||||
import org.jetbrains.kotlin.codegen.DESTRUCTURED_LAMBDA_ARGUMENT_VARIABLE_PREFIX
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||
import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
|
||||
import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
import org.jetbrains.kotlin.utils.getSafe
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.*
|
||||
import kotlin.coroutines.Continuation
|
||||
|
||||
class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDescriptorImpl(frame, MethodsTracker()), true) {
|
||||
private companion object {
|
||||
private val LOG = Logger.getInstance(this::class.java)
|
||||
}
|
||||
|
||||
private val kotlinVariableViewService = ToggleKotlinVariablesState.getService()
|
||||
|
||||
override fun superBuildVariables(evaluationContext: EvaluationContextImpl, children: XValueChildrenList) {
|
||||
if (!kotlinVariableViewService.kotlinVariableView) {
|
||||
return super.superBuildVariables(evaluationContext, children)
|
||||
}
|
||||
|
||||
val nodeManager = evaluationContext.debugProcess.xdebugProcess?.nodeManager
|
||||
|
||||
fun addItem(variable: LocalVariableProxyImpl) {
|
||||
if (nodeManager == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val variableDescriptor = nodeManager.getLocalVariableDescriptor(null, variable)
|
||||
children.add(JavaValue.create(null, variableDescriptor, evaluationContext, nodeManager, false))
|
||||
}
|
||||
|
||||
val (thisReferences, otherVariables) = visibleVariables
|
||||
.partition { it.name() == THIS || it is ThisLocalVariable }
|
||||
|
||||
if (!removeSyntheticThisObject(evaluationContext, children, thisReferences) && thisReferences.isNotEmpty()) {
|
||||
val thisLabels = thisReferences.asSequence()
|
||||
.filterIsInstance<ThisLocalVariable>()
|
||||
.mapNotNullTo(hashSetOf()) { it.label }
|
||||
|
||||
remapThisObjectForOuterThis(evaluationContext, children, thisLabels)
|
||||
}
|
||||
|
||||
thisReferences.forEach(::addItem)
|
||||
otherVariables.forEach(::addItem)
|
||||
}
|
||||
|
||||
private fun removeSyntheticThisObject(
|
||||
evaluationContext: EvaluationContextImpl,
|
||||
children: XValueChildrenList,
|
||||
thisReferences: List<LocalVariableProxyImpl>
|
||||
): Boolean {
|
||||
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return false
|
||||
|
||||
if (thisObject.type().isSubtype(CONTINUATION_TYPE)) {
|
||||
ExistingInstanceThis.find(children)?.remove()
|
||||
return true
|
||||
}
|
||||
|
||||
val thisObjectType = thisObject.type()
|
||||
if (thisObjectType.isSubtype(Function::class.java.name) && '$' in thisObjectType.signature()) {
|
||||
val existingThis = ExistingInstanceThis.find(children)
|
||||
if (existingThis != null) {
|
||||
existingThis.remove()
|
||||
val javaValue = existingThis.value as? JavaValue
|
||||
if (javaValue != null) {
|
||||
attachCapturedThisFromLambda(evaluationContext, children, javaValue, thisReferences)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return removeCallSiteThisInInlineFunction(evaluationContext, children)
|
||||
}
|
||||
|
||||
private fun removeCallSiteThisInInlineFunction(evaluationContext: EvaluationContextImpl, children: XValueChildrenList): Boolean {
|
||||
val frameProxy = evaluationContext.frameProxy
|
||||
|
||||
val variables = frameProxy?.safeVisibleVariables() ?: return false
|
||||
val inlineDepth = getInlineDepth(variables)
|
||||
val declarationSiteThis = variables.firstOrNull { v ->
|
||||
val name = v.name()
|
||||
name.endsWith(INLINE_FUN_VAR_SUFFIX) && name.dropInlineSuffix() == AsmUtil.INLINE_DECLARATION_SITE_THIS
|
||||
}
|
||||
|
||||
if (inlineDepth > 0 && declarationSiteThis != null) {
|
||||
ExistingInstanceThis.find(children)?.remove()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun attachCapturedThisFromLambda(
|
||||
evaluationContext: EvaluationContextImpl,
|
||||
children: XValueChildrenList,
|
||||
javaValue: JavaValue,
|
||||
thisReferences: List<LocalVariableProxyImpl>
|
||||
) {
|
||||
try {
|
||||
val value = javaValue.descriptor.calcValue(evaluationContext) as? ObjectReference ?: return
|
||||
val thisField = value.referenceType().fieldByName(AsmUtil.CAPTURED_THIS_FIELD) ?: return
|
||||
val thisValue = value.getValue(thisField) as? ObjectReference ?: return
|
||||
val thisType = thisValue.referenceType()
|
||||
val unsafeLabel = generateThisLabelUnsafe(thisType) ?: return
|
||||
val label = checkLabel(unsafeLabel)
|
||||
|
||||
if (label != null) {
|
||||
val thisName = getThisName(label)
|
||||
|
||||
if (thisReferences.any { it.name() == thisName }) {
|
||||
// Avoid label duplication
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val thisName = when {
|
||||
thisReferences.isEmpty() -> THIS
|
||||
label != null -> getThisName(label)
|
||||
else -> "$THIS (anonymous fun)"
|
||||
}
|
||||
|
||||
val nodeManager = evaluationContext.debugProcess.xdebugProcess?.nodeManager ?: return
|
||||
val thisDescriptor = nodeManager.getDescriptor(this.descriptor, LabeledThisData(thisName, thisValue))
|
||||
children.add(JavaValue.create(null, thisDescriptor, evaluationContext, nodeManager, false))
|
||||
} catch (e: EvaluateException) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private fun remapThisObjectForOuterThis(
|
||||
evaluationContext: EvaluationContextImpl,
|
||||
children: XValueChildrenList,
|
||||
existingThisLabels: Set<String>
|
||||
) {
|
||||
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return
|
||||
val variable = ExistingInstanceThis.find(children) ?: return
|
||||
|
||||
val thisLabel = generateThisLabel(thisObject.referenceType())?.takeIf { it !in existingThisLabels }
|
||||
if (thisLabel == null) {
|
||||
variable.remove()
|
||||
return
|
||||
}
|
||||
|
||||
// add additional checks?
|
||||
variable.remapName(getThisName(thisLabel))
|
||||
}
|
||||
|
||||
// Very Dirty Work-around.
|
||||
// Hopefully, there will be an API for that in 2019.1.
|
||||
private class ExistingInstanceThis(
|
||||
private val children: XValueChildrenList,
|
||||
private val index: Int,
|
||||
val value: XValue,
|
||||
private val size: Int
|
||||
) {
|
||||
companion object {
|
||||
private const val THIS_NAME = "this"
|
||||
|
||||
fun find(children: XValueChildrenList): ExistingInstanceThis? {
|
||||
val size = children.size()
|
||||
for (i in 0 until size) {
|
||||
if (children.getName(i) == THIS_NAME) {
|
||||
val valueDescriptor = (children.getValue(i) as? JavaValue)?.descriptor
|
||||
@Suppress("FoldInitializerAndIfToElvis")
|
||||
if (valueDescriptor !is ThisDescriptorImpl) {
|
||||
return null
|
||||
}
|
||||
|
||||
return ExistingInstanceThis(children, i, children.getValue(i), size)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun remapName(newName: String) {
|
||||
val (names, _) = getLists() ?: return
|
||||
names[index] = newName
|
||||
}
|
||||
|
||||
fun remove() {
|
||||
val (names, values) = getLists() ?: return
|
||||
names.removeAt(index)
|
||||
values.removeAt(index)
|
||||
}
|
||||
|
||||
private fun getLists(): Lists? {
|
||||
if (children.size() != size) {
|
||||
throw IllegalStateException("Children list was modified")
|
||||
}
|
||||
|
||||
var namesList: MutableList<Any?>? = null
|
||||
var valuesList: MutableList<Any?>? = null
|
||||
|
||||
for (field in XValueChildrenList::class.java.declaredFields) {
|
||||
val mods = field.modifiers
|
||||
if (Modifier.isPrivate(mods) && Modifier.isFinal(mods) && !Modifier.isStatic(mods) && field.type == List::class.java) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val list = (field.getSafe(children) as? MutableList<Any?>)?.takeIf { it.size == size } ?: continue
|
||||
|
||||
if (list[index] == THIS_NAME) {
|
||||
namesList = list
|
||||
} else if (list[index] === value) {
|
||||
valuesList = list
|
||||
}
|
||||
}
|
||||
|
||||
if (namesList != null && valuesList != null) {
|
||||
return Lists(namesList, valuesList)
|
||||
}
|
||||
}
|
||||
|
||||
LOG.error(
|
||||
"Can't find name/value lists, existing fields: "
|
||||
+ Arrays.toString(XValueChildrenList::class.java.declaredFields)
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private data class Lists(val names: MutableList<Any?>, val values: MutableList<Any?>)
|
||||
}
|
||||
|
||||
override fun getVisibleVariables(): List<LocalVariableProxyImpl> {
|
||||
val allVisibleVariables = super.getStackFrameProxy().safeVisibleVariables()
|
||||
|
||||
if (!kotlinVariableViewService.kotlinVariableView) {
|
||||
return allVisibleVariables.map { variable ->
|
||||
if (isFakeLocalVariableForInline(variable.name())) variable.wrapSyntheticInlineVariable() else variable
|
||||
}
|
||||
}
|
||||
|
||||
val inlineDepth = getInlineDepth(allVisibleVariables)
|
||||
|
||||
val (thisVariables, otherVariables) = allVisibleVariables.asSequence()
|
||||
.filter { !isHidden(it, inlineDepth) }
|
||||
.partition {
|
||||
it.name() == THIS
|
||||
|| it.name() == AsmUtil.THIS_IN_DEFAULT_IMPLS
|
||||
|| it.name().startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|
||||
|| (INLINED_THIS_REGEX.matches(it.name()))
|
||||
}
|
||||
|
||||
val (mainThis, otherThis) = thisVariables
|
||||
.sortedByDescending { it.variable }
|
||||
.let { it.firstOrNull() to it.drop(1) }
|
||||
|
||||
val remappedMainThis = mainThis?.clone(THIS, null)
|
||||
val remappedOther = (otherThis + otherVariables).map { it.remapVariableNameIfNeeded() }
|
||||
return (listOfNotNull(remappedMainThis) + remappedOther).sortedBy { it.variable }
|
||||
}
|
||||
|
||||
private fun isHidden(variable: LocalVariableProxyImpl, inlineDepth: Int): Boolean {
|
||||
val name = variable.name()
|
||||
return isFakeLocalVariableForInline(name)
|
||||
|| name.startsWith(DESTRUCTURED_LAMBDA_ARGUMENT_VARIABLE_PREFIX)
|
||||
|| name.startsWith(AsmUtil.LOCAL_FUNCTION_VARIABLE_PREFIX)
|
||||
|| getInlineDepth(variable.name()) != inlineDepth
|
||||
|| name == CONTINUATION_VARIABLE_NAME
|
||||
}
|
||||
|
||||
private fun LocalVariableProxyImpl.remapVariableNameIfNeeded(): LocalVariableProxyImpl {
|
||||
val name = this.name().dropInlineSuffix()
|
||||
|
||||
@Suppress("ConvertToStringTemplate")
|
||||
return when {
|
||||
isLabeledThisReference() -> {
|
||||
val label = name.drop(AsmUtil.LABELED_THIS_PARAMETER.length)
|
||||
clone(getThisName(label), label)
|
||||
}
|
||||
name == AsmUtil.THIS_IN_DEFAULT_IMPLS -> clone(THIS + " (outer)", null)
|
||||
name == AsmUtil.RECEIVER_PARAMETER_NAME -> clone(THIS + " (receiver)", null)
|
||||
INLINED_THIS_REGEX.matches(name) -> {
|
||||
val label = generateThisLabel(frame.getValue(this)?.type())
|
||||
if (label != null) {
|
||||
clone(getThisName(label), label)
|
||||
} else {
|
||||
this@remapVariableNameIfNeeded
|
||||
}
|
||||
}
|
||||
name != this.name() -> {
|
||||
object : LocalVariableProxyImpl(frame, variable) {
|
||||
override fun name() = name
|
||||
}
|
||||
}
|
||||
else -> this@remapVariableNameIfNeeded
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateThisLabel(type: Type?): String? {
|
||||
return checkLabel(generateThisLabelUnsafe(type) ?: return null)
|
||||
}
|
||||
|
||||
private fun generateThisLabelUnsafe(type: Type?): String? {
|
||||
val referenceType = type as? ReferenceType ?: return null
|
||||
return referenceType.name().substringAfterLast('.').substringAfterLast('$')
|
||||
}
|
||||
|
||||
private fun checkLabel(label: String): String? {
|
||||
if (label.isEmpty() || label.all { it.isDigit() }) {
|
||||
return null
|
||||
}
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
private fun String.dropInlineSuffix(): String {
|
||||
val depth = getInlineDepth(this)
|
||||
if (depth == 0) {
|
||||
return this
|
||||
}
|
||||
|
||||
return dropLast(depth * INLINE_FUN_VAR_SUFFIX.length)
|
||||
}
|
||||
|
||||
private fun LocalVariableProxyImpl.clone(name: String, label: String?): LocalVariableProxyImpl {
|
||||
return object : LocalVariableProxyImpl(frame, variable), ThisLocalVariable {
|
||||
override fun name() = name
|
||||
override val label = label
|
||||
}
|
||||
}
|
||||
|
||||
private fun LocalVariableProxyImpl.isLabeledThisReference(): Boolean {
|
||||
@Suppress("ConvertToStringTemplate")
|
||||
return name().startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|
||||
}
|
||||
}
|
||||
|
||||
private interface ThisLocalVariable {
|
||||
val label: String?
|
||||
}
|
||||
|
||||
private fun LocalVariableProxyImpl.wrapSyntheticInlineVariable(): LocalVariableProxyImpl {
|
||||
val proxyWrapper = object : StackFrameProxyImpl(frame.threadProxy(), frame.stackFrame, frame.indexFromBottom) {
|
||||
override fun getValue(localVariable: LocalVariableProxyImpl): Value {
|
||||
return frame.virtualMachine.mirrorOfVoid()
|
||||
}
|
||||
}
|
||||
return LocalVariableProxyImpl(proxyWrapper, variable)
|
||||
}
|
||||
|
||||
private fun getThisName(label: String): String {
|
||||
return "$THIS (@$label)"
|
||||
}
|
||||
|
||||
private class LabeledThisData(val name: String, val value: ObjectReference) : DescriptorData<ValueDescriptorImpl>() {
|
||||
override fun createDescriptorImpl(project: Project): ValueDescriptorImpl {
|
||||
return object : ValueDescriptorImpl(project, value) {
|
||||
override fun getName() = this@LabeledThisData.name
|
||||
override fun calcValue(evaluationContext: EvaluationContextImpl?) = value
|
||||
override fun canSetValue() = false
|
||||
|
||||
override fun getDescriptorEvaluation(context: DebuggerContext?): PsiExpression {
|
||||
// TODO change to labeled this
|
||||
val elementFactory = JavaPsiFacade.getElementFactory(myProject)
|
||||
try {
|
||||
return elementFactory.createExpressionFromText("this", null)
|
||||
} catch (e: IncorrectOperationException) {
|
||||
throw EvaluateException(e.message, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDisplayKey(): DisplayKey<ValueDescriptorImpl> = SimpleDisplayKey(this)
|
||||
override fun equals(other: Any?) = other is LabeledThisData && other.name == name
|
||||
override fun hashCode() = name.hashCode()
|
||||
}
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.ToggleAction
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.xdebugger.XDebugSession
|
||||
import com.intellij.xdebugger.impl.XDebuggerUtilImpl
|
||||
import org.jetbrains.kotlin.idea.core.KotlinFileTypeFactory
|
||||
|
||||
class ToggleKotlinVariablesState {
|
||||
companion object {
|
||||
private const val KOTLIN_VARIABLE_VIEW = "debugger.kotlin.variable.view"
|
||||
|
||||
fun getService(): ToggleKotlinVariablesState {
|
||||
return ServiceManager.getService(ToggleKotlinVariablesState::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
var kotlinVariableView = PropertiesComponent.getInstance().getBoolean(KOTLIN_VARIABLE_VIEW, true)
|
||||
set(newValue) {
|
||||
field = newValue
|
||||
PropertiesComponent.getInstance().setValue(KOTLIN_VARIABLE_VIEW, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleKotlinVariablesView : ToggleAction() {
|
||||
private val kotlinVariableViewService = ToggleKotlinVariablesState.getService()
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
super.update(e)
|
||||
val session = XDebugSession.DATA_KEY.getData(e.dataContext)
|
||||
e.presentation.isEnabledAndVisible = session != null && session.isInKotlinFile()
|
||||
}
|
||||
|
||||
private fun XDebugSession.isInKotlinFile(): Boolean {
|
||||
val fileExtension = currentPosition?.file?.extension ?: return false
|
||||
return fileExtension in KotlinFileTypeFactory.KOTLIN_EXTENSIONS
|
||||
}
|
||||
|
||||
override fun isSelected(e: AnActionEvent) = kotlinVariableViewService.kotlinVariableView
|
||||
|
||||
override fun setSelected(e: AnActionEvent, state: Boolean) {
|
||||
kotlinVariableViewService.kotlinVariableView = state
|
||||
XDebuggerUtilImpl.rebuildAllSessionsViews(e.project)
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinBreakpointFiltersPanel">
|
||||
<grid id="27dc6" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<grid id="5736" binding="myConditionsPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="7">
|
||||
<margin top="2" left="2" bottom="5" right="5"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<clientProperties>
|
||||
<BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithoutIndent"/>
|
||||
</clientProperties>
|
||||
<border type="etched" title-resource-bundle="messages/DebuggerBundle" title-key="label.breakpoint.properties.panel.group.conditions"/>
|
||||
<children>
|
||||
<grid id="8e867" binding="myInstanceFiltersPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="17e11" class="javax.swing.JCheckBox" binding="myInstanceFiltersCheckBox">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<margin top="2" left="2" bottom="0" right="2"/>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="breakpoint.properties.panel.option.instance.filters"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="5231f" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<hspacer id="eeee7">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="15" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
<xy id="28068" binding="myInstanceFiltersFieldPanel" layout-manager="XYLayout" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</xy>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="25884" binding="myClassFiltersPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="729d5" class="javax.swing.JCheckBox" binding="myClassFiltersCheckBox">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<margin top="2" left="2" bottom="0" right="2"/>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="breakpoint.properties.panel.option.class.filters"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="9bef6" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<xy id="e3d10" binding="myClassFiltersFieldPanel" layout-manager="XYLayout" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</xy>
|
||||
<hspacer id="ec2a">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="15" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="275ca" binding="myPassCountPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="7" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="27bbc" class="javax.swing.JCheckBox" binding="myPassCountCheckbox">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<margin top="2" left="2" bottom="0" right="2"/>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="breakpoint.properties.panel.option.pass.count"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="71095" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="33fef" class="javax.swing.JTextField" binding="myPassCountField">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="15" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties>
|
||||
<enabled value="false"/>
|
||||
<horizontalAlignment value="10"/>
|
||||
</properties>
|
||||
</component>
|
||||
<hspacer id="28e6f">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="15" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<vspacer id="d1c29">
|
||||
<constraints>
|
||||
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+403
@@ -0,0 +1,403 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.breakpoints;
|
||||
|
||||
import com.intellij.debugger.InstanceFilter;
|
||||
import com.intellij.debugger.ui.breakpoints.EditClassFiltersDialog;
|
||||
import com.intellij.debugger.ui.breakpoints.EditInstanceFiltersDialog;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.ui.FieldPanel;
|
||||
import com.intellij.ui.MultiLineTooltipUI;
|
||||
import com.intellij.ui.classFilter.ClassFilter;
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint;
|
||||
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel;
|
||||
import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase;
|
||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class KotlinBreakpointFiltersPanel<T extends KotlinPropertyBreakpointProperties, B extends XBreakpoint<T>> extends XBreakpointCustomPropertiesPanel<B> {
|
||||
private JPanel myConditionsPanel;
|
||||
private JPanel myInstanceFiltersPanel;
|
||||
private JCheckBox myInstanceFiltersCheckBox;
|
||||
private JPanel myInstanceFiltersFieldPanel;
|
||||
private JPanel myClassFiltersPanel;
|
||||
private JCheckBox myClassFiltersCheckBox;
|
||||
private JPanel myClassFiltersFieldPanel;
|
||||
private JPanel myPassCountPanel;
|
||||
private JCheckBox myPassCountCheckbox;
|
||||
private JTextField myPassCountField;
|
||||
|
||||
private final FieldPanel myInstanceFiltersField;
|
||||
private final FieldPanel myClassFiltersField;
|
||||
|
||||
private ClassFilter[] myClassFilters = ClassFilter.EMPTY_ARRAY;
|
||||
private ClassFilter[] myClassExclusionFilters = ClassFilter.EMPTY_ARRAY;
|
||||
private InstanceFilter[] myInstanceFilters = InstanceFilter.EMPTY_ARRAY;
|
||||
protected final Project myProject;
|
||||
|
||||
private PsiClass myBreakpointPsiClass;
|
||||
|
||||
public KotlinBreakpointFiltersPanel(Project project) {
|
||||
myProject = project;
|
||||
myInstanceFiltersField = new FieldPanel(new MyTextField(), "", null,
|
||||
new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
reloadInstanceFilters();
|
||||
EditInstanceFiltersDialog _dialog = new EditInstanceFiltersDialog(myProject);
|
||||
_dialog.setFilters(myInstanceFilters);
|
||||
_dialog.show();
|
||||
if (_dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
|
||||
myInstanceFilters = _dialog.getFilters();
|
||||
updateInstanceFilterEditor(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
myClassFiltersField = new FieldPanel(new MyTextField(), "", null,
|
||||
new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
reloadClassFilters();
|
||||
|
||||
com.intellij.ide.util.ClassFilter classFilter = createClassConditionFilter();
|
||||
|
||||
EditClassFiltersDialog _dialog = new EditClassFiltersDialog(myProject, classFilter);
|
||||
_dialog.setFilters(myClassFilters, myClassExclusionFilters);
|
||||
_dialog.show();
|
||||
if (_dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
|
||||
myClassFilters = _dialog.getFilters();
|
||||
myClassExclusionFilters = _dialog.getExclusionFilters();
|
||||
updateClassFilterEditor(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
ActionListener updateListener = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
updateCheckboxes();
|
||||
}
|
||||
};
|
||||
|
||||
myPassCountCheckbox.addActionListener(updateListener);
|
||||
myInstanceFiltersCheckBox.addActionListener(updateListener);
|
||||
myClassFiltersCheckBox.addActionListener(updateListener);
|
||||
|
||||
ToolTipManager.sharedInstance().registerComponent(myClassFiltersField.getTextField());
|
||||
ToolTipManager.sharedInstance().registerComponent(myInstanceFiltersField.getTextField());
|
||||
|
||||
insert(myInstanceFiltersFieldPanel, myInstanceFiltersField);
|
||||
insert(myClassFiltersFieldPanel, myClassFiltersField);
|
||||
|
||||
DebuggerUIUtil.focusEditorOnCheck(myPassCountCheckbox, myPassCountField);
|
||||
DebuggerUIUtil.focusEditorOnCheck(myInstanceFiltersCheckBox, myInstanceFiltersField.getTextField());
|
||||
DebuggerUIUtil.focusEditorOnCheck(myClassFiltersCheckBox, myClassFiltersField.getTextField());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return myConditionsPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisibleOnPopup(@NotNull B breakpoint) {
|
||||
JavaBreakpointProperties properties = breakpoint.getProperties();
|
||||
if (properties != null) {
|
||||
return properties.isCOUNT_FILTER_ENABLED() || properties.isCLASS_FILTERS_ENABLED() || properties.isINSTANCE_FILTERS_ENABLED();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveTo(@NotNull B breakpoint) {
|
||||
JavaBreakpointProperties properties = breakpoint.getProperties();
|
||||
if (properties == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean changed = false;
|
||||
try {
|
||||
String text = myPassCountField.getText().trim();
|
||||
int filter = !text.isEmpty() ? Integer.parseInt(text) : 0;
|
||||
if (filter < 0) filter = 0;
|
||||
changed = properties.setCOUNT_FILTER(filter);
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
}
|
||||
|
||||
changed = properties.setCOUNT_FILTER_ENABLED(properties.getCOUNT_FILTER() > 0 && myPassCountCheckbox.isSelected()) || changed;
|
||||
reloadInstanceFilters();
|
||||
reloadClassFilters();
|
||||
updateInstanceFilterEditor(true);
|
||||
updateClassFilterEditor(true);
|
||||
|
||||
changed = properties.setINSTANCE_FILTERS_ENABLED(myInstanceFiltersField.getText().length() > 0 && myInstanceFiltersCheckBox.isSelected()) || changed;
|
||||
changed = properties.setCLASS_FILTERS_ENABLED(myClassFiltersField.getText().length() > 0 && myClassFiltersCheckBox.isSelected()) || changed;
|
||||
changed = properties.setClassFilters(myClassFilters) || changed;
|
||||
changed = properties.setClassExclusionFilters(myClassExclusionFilters) || changed;
|
||||
changed = properties.setInstanceFilters(myInstanceFilters) || changed;
|
||||
if (changed) {
|
||||
((XBreakpointBase)breakpoint).fireBreakpointChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private static void insert(JPanel panel, JComponent component) {
|
||||
panel.setLayout(new BorderLayout());
|
||||
panel.add(component, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFrom(@NotNull B breakpoint) {
|
||||
JavaBreakpointProperties properties = breakpoint.getProperties();
|
||||
if (properties != null) {
|
||||
if (properties.getCOUNT_FILTER() > 0) {
|
||||
myPassCountField.setText(Integer.toString(properties.getCOUNT_FILTER()));
|
||||
}
|
||||
else {
|
||||
myPassCountField.setText("");
|
||||
}
|
||||
|
||||
myPassCountCheckbox.setSelected(properties.isCOUNT_FILTER_ENABLED());
|
||||
|
||||
myInstanceFiltersCheckBox.setSelected(properties.isINSTANCE_FILTERS_ENABLED());
|
||||
myInstanceFiltersField.setEnabled(properties.isINSTANCE_FILTERS_ENABLED());
|
||||
myInstanceFiltersField.getTextField().setEditable(properties.isINSTANCE_FILTERS_ENABLED());
|
||||
myInstanceFilters = properties.getInstanceFilters();
|
||||
updateInstanceFilterEditor(true);
|
||||
|
||||
myClassFiltersCheckBox.setSelected(properties.isCLASS_FILTERS_ENABLED());
|
||||
myClassFiltersField.setEnabled(properties.isCLASS_FILTERS_ENABLED());
|
||||
myClassFiltersField.getTextField().setEditable(properties.isCLASS_FILTERS_ENABLED());
|
||||
myClassFilters = properties.getClassFilters();
|
||||
myClassExclusionFilters = properties.getClassExclusionFilters();
|
||||
updateClassFilterEditor(true);
|
||||
|
||||
XSourcePosition position = breakpoint.getSourcePosition();
|
||||
// TODO: need to calculate psi class
|
||||
//myBreakpointPsiClass = breakpoint.getPsiClass();
|
||||
}
|
||||
updateCheckboxes();
|
||||
}
|
||||
|
||||
private void updateInstanceFilterEditor(boolean updateText) {
|
||||
List<String> filters = new ArrayList<String>();
|
||||
for (InstanceFilter instanceFilter : myInstanceFilters) {
|
||||
if (instanceFilter.isEnabled()) {
|
||||
filters.add(Long.toString(instanceFilter.getId()));
|
||||
}
|
||||
}
|
||||
if (updateText) {
|
||||
myInstanceFiltersField.setText(StringUtil.join(filters, " "));
|
||||
}
|
||||
|
||||
String tipText = concatWithEx(filters, " ", (int)Math.sqrt(myInstanceFilters.length) + 1, "\n");
|
||||
myInstanceFiltersField.getTextField().setToolTipText(tipText);
|
||||
}
|
||||
|
||||
private class MyTextField extends JTextField {
|
||||
public MyTextField() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTipText(MouseEvent event) {
|
||||
reloadClassFilters();
|
||||
updateClassFilterEditor(false);
|
||||
reloadInstanceFilters();
|
||||
updateInstanceFilterEditor(false);
|
||||
String toolTipText = super.getToolTipText(event);
|
||||
return getToolTipText().length() == 0 ? null : toolTipText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JToolTip createToolTip() {
|
||||
JToolTip toolTip = new JToolTip(){{
|
||||
setUI(new MultiLineTooltipUI());
|
||||
}};
|
||||
toolTip.setComponent(this);
|
||||
return toolTip;
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadClassFilters() {
|
||||
String filtersText = myClassFiltersField.getText();
|
||||
|
||||
ArrayList<ClassFilter> classFilters = new ArrayList<ClassFilter>();
|
||||
ArrayList<ClassFilter> exclusionFilters = new ArrayList<ClassFilter>();
|
||||
int startFilter = -1;
|
||||
for(int i = 0; i <= filtersText.length(); i++) {
|
||||
if(i < filtersText.length() && !Character.isWhitespace(filtersText.charAt(i))){
|
||||
if(startFilter == -1) {
|
||||
startFilter = i;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(startFilter >=0) {
|
||||
if(filtersText.charAt(startFilter) == '-') {
|
||||
exclusionFilters.add(new ClassFilter(filtersText.substring(startFilter + 1, i)));
|
||||
}
|
||||
else {
|
||||
classFilters.add(new ClassFilter(filtersText.substring(startFilter, i)));
|
||||
}
|
||||
startFilter = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ClassFilter classFilter : myClassFilters) {
|
||||
if (!classFilter.isEnabled()) {
|
||||
classFilters.add(classFilter);
|
||||
}
|
||||
}
|
||||
for (ClassFilter classFilter : myClassExclusionFilters) {
|
||||
if (!classFilter.isEnabled()) {
|
||||
exclusionFilters.add(classFilter);
|
||||
}
|
||||
}
|
||||
myClassFilters = classFilters .toArray(new ClassFilter[classFilters .size()]);
|
||||
myClassExclusionFilters = exclusionFilters.toArray(new ClassFilter[exclusionFilters.size()]);
|
||||
}
|
||||
|
||||
private void reloadInstanceFilters() {
|
||||
String filtersText = myInstanceFiltersField.getText();
|
||||
|
||||
ArrayList<InstanceFilter> idxs = new ArrayList<InstanceFilter>();
|
||||
int startNumber = -1;
|
||||
for(int i = 0; i <= filtersText.length(); i++) {
|
||||
if(i < filtersText.length() && Character.isDigit(filtersText.charAt(i))) {
|
||||
if(startNumber == -1) {
|
||||
startNumber = i;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(startNumber >=0) {
|
||||
idxs.add(InstanceFilter.create(filtersText.substring(startNumber, i)));
|
||||
startNumber = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (InstanceFilter instanceFilter : myInstanceFilters) {
|
||||
if (!instanceFilter.isEnabled()) {
|
||||
idxs.add(instanceFilter);
|
||||
}
|
||||
}
|
||||
myInstanceFilters = idxs.toArray(new InstanceFilter[idxs.size()]);
|
||||
}
|
||||
|
||||
private void updateClassFilterEditor(boolean updateText) {
|
||||
List<String> filters = new ArrayList<String>();
|
||||
for (ClassFilter classFilter : myClassFilters) {
|
||||
if (classFilter.isEnabled()) {
|
||||
filters.add(classFilter.getPattern());
|
||||
}
|
||||
}
|
||||
List<String> excludeFilters = new ArrayList<String>();
|
||||
for (ClassFilter classFilter : myClassExclusionFilters) {
|
||||
if (classFilter.isEnabled()) {
|
||||
excludeFilters.add("-" + classFilter.getPattern());
|
||||
}
|
||||
}
|
||||
if (updateText) {
|
||||
String editorText = StringUtil.join(filters, " ");
|
||||
if(!filters.isEmpty()) {
|
||||
editorText += " ";
|
||||
}
|
||||
editorText += StringUtil.join(excludeFilters, " ");
|
||||
myClassFiltersField.setText(editorText);
|
||||
}
|
||||
|
||||
int width = (int)Math.sqrt(myClassExclusionFilters.length + myClassFilters.length) + 1;
|
||||
String tipText = concatWithEx(filters, " ", width, "\n");
|
||||
if(!filters.isEmpty()) {
|
||||
tipText += "\n";
|
||||
}
|
||||
tipText += concatWithEx(excludeFilters, " ", width, "\n");
|
||||
myClassFiltersField.getTextField().setToolTipText(tipText);
|
||||
}
|
||||
|
||||
private static String concatWithEx(List<String> s, String concator, int N, String NthConcator) {
|
||||
String result = "";
|
||||
int i = 1;
|
||||
for (Iterator iterator = s.iterator(); iterator.hasNext(); i++) {
|
||||
String str = (String) iterator.next();
|
||||
result += str;
|
||||
if(iterator.hasNext()){
|
||||
if(i % N == 0){
|
||||
result += NthConcator;
|
||||
}
|
||||
else {
|
||||
result += concator;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected com.intellij.ide.util.ClassFilter createClassConditionFilter() {
|
||||
com.intellij.ide.util.ClassFilter classFilter;
|
||||
if(myBreakpointPsiClass != null) {
|
||||
classFilter = new com.intellij.ide.util.ClassFilter() {
|
||||
@Override
|
||||
public boolean isAccepted(PsiClass aClass) {
|
||||
return myBreakpointPsiClass == aClass || aClass.isInheritor(myBreakpointPsiClass, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
classFilter = null;
|
||||
}
|
||||
return classFilter;
|
||||
}
|
||||
|
||||
protected void updateCheckboxes() {
|
||||
boolean passCountApplicable = true;
|
||||
if (myInstanceFiltersCheckBox.isSelected() || myClassFiltersCheckBox.isSelected()) {
|
||||
passCountApplicable = false;
|
||||
}
|
||||
myPassCountCheckbox.setEnabled(passCountApplicable);
|
||||
|
||||
boolean passCountSelected = myPassCountCheckbox.isSelected();
|
||||
myInstanceFiltersCheckBox.setEnabled(!passCountSelected);
|
||||
myClassFiltersCheckBox.setEnabled(!passCountSelected);
|
||||
|
||||
myPassCountField.setEditable(myPassCountCheckbox.isSelected());
|
||||
myPassCountField.setEnabled (myPassCountCheckbox.isSelected());
|
||||
|
||||
myInstanceFiltersField.setEnabled(myInstanceFiltersCheckBox.isSelected());
|
||||
myInstanceFiltersField.getTextField().setEditable(myInstanceFiltersCheckBox.isSelected());
|
||||
|
||||
myClassFiltersField.setEnabled(myClassFiltersCheckBox.isSelected());
|
||||
myClassFiltersField.getTextField().setEditable(myClassFiltersCheckBox.isSelected());
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.JavaBreakpointHandler
|
||||
import com.intellij.debugger.engine.JavaBreakpointHandlerFactory
|
||||
|
||||
class KotlinFieldBreakpointHandlerFactory : JavaBreakpointHandlerFactory {
|
||||
override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? {
|
||||
return KotlinFieldBreakpointHandler(process)
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinLineBreakpointHandlerFactory: JavaBreakpointHandlerFactory {
|
||||
override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? {
|
||||
return KotlinLineBreakpointHandler(process)
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinFieldBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinFieldBreakpointType::class.java, process)
|
||||
class KotlinLineBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinLineBreakpointType::class.java, process)
|
||||
+397
@@ -0,0 +1,397 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.impl.PositionUtil
|
||||
import com.intellij.debugger.requests.Requestor
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointCategory
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointWithHighlighter
|
||||
import com.intellij.debugger.ui.breakpoints.FieldBreakpoint
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.Method
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.event.*
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import com.sun.jdi.request.MethodEntryRequest
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.debugger.safeAllLineLocations
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import javax.swing.Icon
|
||||
|
||||
class KotlinFieldBreakpoint(
|
||||
project: Project,
|
||||
breakpoint: XBreakpoint<KotlinPropertyBreakpointProperties>
|
||||
): BreakpointWithHighlighter<KotlinPropertyBreakpointProperties>(project, breakpoint) {
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpoint")
|
||||
private val CATEGORY: Key<FieldBreakpoint> = BreakpointCategory.lookup<FieldBreakpoint>("field_breakpoints")
|
||||
}
|
||||
|
||||
private enum class BreakpointType {
|
||||
FIELD,
|
||||
METHOD
|
||||
}
|
||||
|
||||
private var breakpointType: BreakpointType = BreakpointType.FIELD
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (!BreakpointWithHighlighter.isPositionValid(xBreakpoint.sourcePosition)) return false
|
||||
|
||||
return runReadAction {
|
||||
val field = getField()
|
||||
field != null && field.isValid
|
||||
}
|
||||
}
|
||||
|
||||
fun getField(): KtCallableDeclaration? {
|
||||
val sourcePosition = sourcePosition
|
||||
return getProperty(sourcePosition)
|
||||
}
|
||||
|
||||
private fun getProperty(sourcePosition: SourcePosition?): KtCallableDeclaration? {
|
||||
val property: KtProperty? = PositionUtil.getPsiElementAt(project, KtProperty::class.java, sourcePosition)
|
||||
if (property != null) {
|
||||
return property
|
||||
}
|
||||
val parameter: KtParameter? = PositionUtil.getPsiElementAt(project, KtParameter::class.java, sourcePosition)
|
||||
if (parameter != null) {
|
||||
return parameter
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun reload() {
|
||||
super.reload()
|
||||
|
||||
val property = getProperty(sourcePosition) ?: return
|
||||
val propertyName = property.name ?: return
|
||||
setFieldName(propertyName)
|
||||
|
||||
if (property is KtProperty && property.isTopLevel) {
|
||||
properties.myClassName = JvmFileClassUtil.getFileClassInfoNoResolve(property.getContainingKtFile()).fileClassFqName.asString()
|
||||
} else {
|
||||
val ktClass: KtClassOrObject? = PsiTreeUtil.getParentOfType(property, KtClassOrObject::class.java)
|
||||
if (ktClass is KtClassOrObject) {
|
||||
val fqName = ktClass.fqName
|
||||
if (fqName != null) {
|
||||
properties.myClassName = fqName.asString()
|
||||
}
|
||||
}
|
||||
}
|
||||
isInstanceFiltersEnabled = false
|
||||
}
|
||||
|
||||
override fun createRequestForPreparedClass(debugProcess: DebugProcessImpl?, refType: ReferenceType?) {
|
||||
if (debugProcess == null || refType == null) return
|
||||
|
||||
val property = getProperty(sourcePosition) ?: return
|
||||
|
||||
breakpointType = (computeBreakpointType(property) ?: return)
|
||||
|
||||
val vm = debugProcess.virtualMachineProxy
|
||||
try {
|
||||
if (properties.WATCH_INITIALIZATION) {
|
||||
val sourcePosition = sourcePosition
|
||||
if (sourcePosition != null) {
|
||||
debugProcess.positionManager
|
||||
.locationsOfLine(refType, sourcePosition)
|
||||
.filter { it.method().isConstructor || it.method().isStaticInitializer }
|
||||
.forEach {
|
||||
val request = debugProcess.requestsManager.createBreakpointRequest(this, it)
|
||||
debugProcess.requestsManager.enableRequest(request)
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Breakpoint request added")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (breakpointType) {
|
||||
BreakpointType.FIELD -> {
|
||||
val field = refType.fieldByName(getFieldName())
|
||||
if (field != null) {
|
||||
val manager = debugProcess.requestsManager
|
||||
if (properties.WATCH_MODIFICATION && vm.canWatchFieldModification()) {
|
||||
val request = manager.createModificationWatchpointRequest(this, field)
|
||||
debugProcess.requestsManager.enableRequest(request)
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Modification request added")
|
||||
}
|
||||
}
|
||||
if (properties.WATCH_ACCESS && vm.canWatchFieldAccess()) {
|
||||
val request = manager.createAccessWatchpointRequest(this, field)
|
||||
debugProcess.requestsManager.enableRequest(request)
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Field access request added (field = ${field.name()}; refType = ${refType.name()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BreakpointType.METHOD -> {
|
||||
val fieldName = getFieldName()
|
||||
|
||||
if (properties.WATCH_ACCESS) {
|
||||
val getter = refType.methodsByName(JvmAbi.getterName(fieldName)).firstOrNull()
|
||||
if (getter != null) {
|
||||
createMethodBreakpoint(debugProcess, refType, getter)
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.WATCH_MODIFICATION) {
|
||||
val setter = refType.methodsByName(JvmAbi.setterName(fieldName)).firstOrNull()
|
||||
if (setter != null) {
|
||||
createMethodBreakpoint(debugProcess, refType, setter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ex: Exception) {
|
||||
LOG.debug(ex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeBreakpointType(property: KtCallableDeclaration): BreakpointType? {
|
||||
return runReadAction {
|
||||
val bindingContext = property.analyze()
|
||||
var descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, property)
|
||||
if (descriptor is ValueParameterDescriptor) {
|
||||
descriptor = bindingContext.get(BindingContext.VALUE_PARAMETER_AS_PROPERTY, descriptor)
|
||||
}
|
||||
|
||||
if (descriptor is PropertyDescriptor) {
|
||||
if (bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, descriptor)!!) {
|
||||
BreakpointType.FIELD
|
||||
}
|
||||
else {
|
||||
BreakpointType.METHOD
|
||||
}
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMethodBreakpoint(debugProcess: DebugProcessImpl, refType: ReferenceType, accessor: Method) {
|
||||
val manager = debugProcess.requestsManager
|
||||
val line = accessor.safeAllLineLocations().firstOrNull()
|
||||
if (line != null) {
|
||||
val request = manager.createBreakpointRequest(this, line)
|
||||
debugProcess.requestsManager.enableRequest(request)
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Breakpoint request added")
|
||||
}
|
||||
}
|
||||
else {
|
||||
var entryRequest: MethodEntryRequest? = findRequest(debugProcess, MethodEntryRequest::class.java, this)
|
||||
if (entryRequest == null) {
|
||||
entryRequest = manager.createMethodEntryRequest(this)!!
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Method entry request added (method = ${accessor.name()}; refType = ${refType.name()})")
|
||||
}
|
||||
}
|
||||
else {
|
||||
entryRequest.disable()
|
||||
}
|
||||
entryRequest.addClassFilter(refType)
|
||||
manager.enableRequest(entryRequest)
|
||||
}
|
||||
}
|
||||
|
||||
inline private fun <reified T : EventRequest> findRequest(debugProcess: DebugProcessImpl, requestClass: Class<T>, requestor: Requestor): T? {
|
||||
val requests = debugProcess.requestsManager.findRequests(requestor)
|
||||
for (eventRequest in requests) {
|
||||
if (eventRequest::class.java == requestClass) {
|
||||
return eventRequest as T
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun evaluateCondition(context: EvaluationContextImpl, event: LocatableEvent): Boolean {
|
||||
if (breakpointType == BreakpointType.METHOD && !matchesEvent(event)) {
|
||||
return false
|
||||
}
|
||||
return super.evaluateCondition(context, event)
|
||||
}
|
||||
|
||||
fun matchesEvent(event: LocatableEvent): Boolean {
|
||||
val method = event.location()?.method()
|
||||
// TODO check property type
|
||||
return method != null && method.name() in getMethodsName()
|
||||
}
|
||||
|
||||
private fun getMethodsName(): List<String> {
|
||||
val fieldName = getFieldName()
|
||||
return listOf(JvmAbi.getterName(fieldName), JvmAbi.setterName(fieldName))
|
||||
}
|
||||
|
||||
override fun getEventMessage(event: LocatableEvent): String {
|
||||
val location = event.location()!!
|
||||
val locationQName = location.declaringType().name() + "." + location.method().name()
|
||||
val locationFileName = try {
|
||||
location.sourceName()
|
||||
}
|
||||
catch (e: AbsentInformationException) {
|
||||
fileName
|
||||
}
|
||||
catch (e: InternalError) {
|
||||
fileName
|
||||
}
|
||||
|
||||
val locationLine = location.lineNumber()
|
||||
when (event) {
|
||||
is ModificationWatchpointEvent-> {
|
||||
val field = event.field()
|
||||
return DebuggerBundle.message(
|
||||
"status.static.field.watchpoint.reached.access",
|
||||
field.declaringType().name(),
|
||||
field.name(),
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
is AccessWatchpointEvent -> {
|
||||
val field = event.field()
|
||||
return DebuggerBundle.message(
|
||||
"status.static.field.watchpoint.reached.access",
|
||||
field.declaringType().name(),
|
||||
field.name(),
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
is MethodEntryEvent -> {
|
||||
val method = event.method()
|
||||
return DebuggerBundle.message(
|
||||
"status.method.entry.breakpoint.reached",
|
||||
method.declaringType().name() + "." + method.name() + "()",
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
is MethodExitEvent -> {
|
||||
val method = event.method()
|
||||
return DebuggerBundle.message(
|
||||
"status.method.exit.breakpoint.reached",
|
||||
method.declaringType().name() + "." + method.name() + "()",
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
}
|
||||
return DebuggerBundle.message(
|
||||
"status.line.breakpoint.reached",
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
|
||||
fun setFieldName(fieldName: String) {
|
||||
properties.myFieldName = fieldName
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun setWatchAccess(value: Boolean) {
|
||||
properties.WATCH_ACCESS = value
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun setWatchModification(value: Boolean) {
|
||||
properties.WATCH_MODIFICATION = value
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun setWatchInitialization(value: Boolean) {
|
||||
properties.WATCH_INITIALIZATION = value
|
||||
}
|
||||
|
||||
override fun getDisabledIcon(isMuted: Boolean): Icon {
|
||||
val master = DebuggerManagerEx.getInstanceEx(myProject).breakpointManager.findMasterBreakpoint(this)
|
||||
return when {
|
||||
isMuted && master == null -> AllIcons.Debugger.Db_muted_disabled_field_breakpoint
|
||||
isMuted && master != null -> AllIcons.Debugger.Db_muted_dep_field_breakpoint
|
||||
master != null -> AllIcons.Debugger.Db_dep_field_breakpoint
|
||||
else -> AllIcons.Debugger.Db_disabled_field_breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSetIcon(isMuted: Boolean): Icon {
|
||||
return when {
|
||||
isMuted -> AllIcons.Debugger.Db_muted_field_breakpoint
|
||||
else -> AllIcons.Debugger.Db_field_breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
// BUNCH: 182
|
||||
override fun getInvalidIcon(isMuted: Boolean): Icon {
|
||||
return AllIcons.Debugger.Db_invalid_breakpoint
|
||||
}
|
||||
|
||||
override fun getVerifiedIcon(isMuted: Boolean): Icon {
|
||||
return when {
|
||||
isMuted -> AllIcons.Debugger.Db_muted_field_breakpoint
|
||||
else -> AllIcons.Debugger.Db_verified_field_breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
override fun getVerifiedWarningsIcon(isMuted: Boolean): Icon = AllIcons.Debugger.Db_exception_breakpoint
|
||||
|
||||
override fun getCategory() = CATEGORY
|
||||
|
||||
override fun getDisplayName(): String? {
|
||||
if (!isValid) {
|
||||
return DebuggerBundle.message("status.breakpoint.invalid")
|
||||
}
|
||||
val className = className
|
||||
return if (className != null && !className.isEmpty()) className + "." + getFieldName() else getFieldName()
|
||||
}
|
||||
|
||||
private fun getFieldName(): String {
|
||||
val declaration = getField()
|
||||
return runReadAction { declaration?.name } ?: "unknown"
|
||||
}
|
||||
|
||||
override fun getEvaluationElement(): PsiElement? {
|
||||
return getField()
|
||||
}
|
||||
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.ui.IdeBorderFactory
|
||||
import com.intellij.util.ui.DialogUtil
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel
|
||||
import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import java.awt.BorderLayout
|
||||
import javax.swing.Box
|
||||
import javax.swing.JCheckBox
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class KotlinFieldBreakpointPropertiesPanel: XBreakpointCustomPropertiesPanel<XLineBreakpoint<KotlinPropertyBreakpointProperties>>() {
|
||||
private var myWatchInitializationCheckBox: JCheckBox by Delegates.notNull()
|
||||
private var myWatchAccessCheckBox: JCheckBox by Delegates.notNull()
|
||||
private var myWatchModificationCheckBox: JCheckBox by Delegates.notNull()
|
||||
|
||||
override fun getComponent(): JComponent {
|
||||
myWatchInitializationCheckBox = JCheckBox(KotlinBundle.message("debugger.field.watchpoints.properties.panel.field.initialization.label"))
|
||||
myWatchAccessCheckBox = JCheckBox(KotlinBundle.message("debugger.field.watchpoints.properties.panel.field.access.label"))
|
||||
myWatchModificationCheckBox = JCheckBox(KotlinBundle.message("debugger.field.watchpoints.properties.panel.field.modification.label"))
|
||||
|
||||
DialogUtil.registerMnemonic(myWatchInitializationCheckBox)
|
||||
DialogUtil.registerMnemonic(myWatchAccessCheckBox)
|
||||
DialogUtil.registerMnemonic(myWatchModificationCheckBox)
|
||||
|
||||
fun Box.addNewPanelForCheckBox(checkBox: JCheckBox) {
|
||||
val panel = JPanel(BorderLayout())
|
||||
panel.add(checkBox, BorderLayout.NORTH)
|
||||
this.add(panel)
|
||||
}
|
||||
|
||||
val watchBox = Box.createVerticalBox()
|
||||
watchBox.addNewPanelForCheckBox(myWatchInitializationCheckBox)
|
||||
watchBox.addNewPanelForCheckBox(myWatchAccessCheckBox)
|
||||
watchBox.addNewPanelForCheckBox(myWatchModificationCheckBox)
|
||||
|
||||
val mainPanel = JPanel(BorderLayout())
|
||||
val innerPanel = JPanel(BorderLayout())
|
||||
innerPanel.add(watchBox, BorderLayout.CENTER)
|
||||
innerPanel.add(Box.createHorizontalStrut(3), BorderLayout.WEST)
|
||||
innerPanel.add(Box.createHorizontalStrut(3), BorderLayout.EAST)
|
||||
mainPanel.add(innerPanel, BorderLayout.NORTH)
|
||||
mainPanel.border = IdeBorderFactory.createTitledBorder(DebuggerBundle.message("label.group.watch.events"), true)
|
||||
return mainPanel
|
||||
}
|
||||
|
||||
override fun loadFrom(breakpoint: XLineBreakpoint<KotlinPropertyBreakpointProperties>) {
|
||||
myWatchInitializationCheckBox.isSelected = breakpoint.properties.WATCH_INITIALIZATION
|
||||
myWatchAccessCheckBox.isSelected = breakpoint.properties.WATCH_ACCESS
|
||||
myWatchModificationCheckBox.isSelected = breakpoint.properties.WATCH_MODIFICATION
|
||||
}
|
||||
|
||||
override fun saveTo(breakpoint: XLineBreakpoint<KotlinPropertyBreakpointProperties>) {
|
||||
var changed = breakpoint.properties.WATCH_ACCESS != myWatchAccessCheckBox.isSelected
|
||||
breakpoint.properties.WATCH_ACCESS = myWatchAccessCheckBox.isSelected
|
||||
|
||||
changed = breakpoint.properties.WATCH_MODIFICATION != myWatchModificationCheckBox.isSelected || changed
|
||||
breakpoint.properties.WATCH_MODIFICATION = myWatchModificationCheckBox.isSelected
|
||||
|
||||
changed = breakpoint.properties.WATCH_INITIALIZATION != myWatchInitializationCheckBox.isSelected || changed
|
||||
breakpoint.properties.WATCH_INITIALIZATION = myWatchInitializationCheckBox.isSelected
|
||||
|
||||
if (changed) {
|
||||
(breakpoint as XBreakpointBase<*, *, *>).fireBreakpointChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* 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.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.ui.breakpoints.Breakpoint
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointManager
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointWithHighlighter
|
||||
import com.intellij.debugger.ui.breakpoints.JavaBreakpointType
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.xdebugger.XDebuggerManager
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpointType
|
||||
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.dialog.AddFieldBreakpointDialog
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.psi.KtDeclarationContainer
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import javax.swing.JComponent
|
||||
|
||||
class KotlinFieldBreakpointType : JavaBreakpointType<KotlinPropertyBreakpointProperties>, XLineBreakpointType<KotlinPropertyBreakpointProperties>(
|
||||
"kotlin-field", KotlinBundle.message("debugger.field.watchpoints.tab.title")
|
||||
) {
|
||||
override fun createJavaBreakpoint(project: Project, breakpoint: XBreakpoint<KotlinPropertyBreakpointProperties>): Breakpoint<KotlinPropertyBreakpointProperties> {
|
||||
return KotlinFieldBreakpoint(project, breakpoint)
|
||||
}
|
||||
|
||||
override fun canPutAt(file: VirtualFile, line: Int, project: Project): Boolean {
|
||||
return canPutAt(file, line, project, this::class.java)
|
||||
}
|
||||
|
||||
override fun getPriority() = 120
|
||||
|
||||
override fun createBreakpointProperties(file: VirtualFile, line: Int): KotlinPropertyBreakpointProperties? {
|
||||
return KotlinPropertyBreakpointProperties()
|
||||
}
|
||||
|
||||
override fun addBreakpoint(project: Project, parentComponent: JComponent?): XLineBreakpoint<KotlinPropertyBreakpointProperties>? {
|
||||
var result: XLineBreakpoint<KotlinPropertyBreakpointProperties>? = null
|
||||
|
||||
val dialog = object : AddFieldBreakpointDialog(project) {
|
||||
override fun validateData(): Boolean {
|
||||
val className = className
|
||||
if (className.isEmpty()) {
|
||||
reportError(project, DebuggerBundle.message("error.field.breakpoint.class.name.not.specified"))
|
||||
return false
|
||||
}
|
||||
|
||||
val psiClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project))
|
||||
if (psiClass !is KtLightClass) {
|
||||
reportError(project, "Couldn't find '$className' class")
|
||||
return false
|
||||
}
|
||||
|
||||
val fieldName = fieldName
|
||||
if (fieldName.isEmpty()) {
|
||||
reportError(project, DebuggerBundle.message("error.field.breakpoint.field.name.not.specified"))
|
||||
return false
|
||||
}
|
||||
|
||||
result = when (psiClass) {
|
||||
is KtLightClassForFacade -> {
|
||||
psiClass.files.asSequence().mapNotNull { createBreakpointIfPropertyExists(it, it, className, fieldName) }.firstOrNull()
|
||||
}
|
||||
is KtLightClassForSourceDeclaration -> {
|
||||
val jetClass = psiClass.kotlinOrigin
|
||||
createBreakpointIfPropertyExists(jetClass, jetClass.containingKtFile, className, fieldName)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
reportError(project, DebuggerBundle.message("error.field.breakpoint.field.not.found", className, fieldName, fieldName))
|
||||
}
|
||||
|
||||
return result != null
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
return result
|
||||
}
|
||||
|
||||
private fun createBreakpointIfPropertyExists(
|
||||
declaration: KtDeclarationContainer,
|
||||
file: KtFile,
|
||||
className: String,
|
||||
fieldName: String
|
||||
): XLineBreakpoint<KotlinPropertyBreakpointProperties>? {
|
||||
val project = file.project
|
||||
val property = declaration.declarations.firstOrNull { it is KtProperty && it.name == fieldName } ?: return null
|
||||
|
||||
val document = PsiDocumentManager.getInstance(project).getDocument(file) ?: return null
|
||||
val line = document.getLineNumber(property.textOffset)
|
||||
return runWriteAction {
|
||||
XDebuggerManager.getInstance(project).breakpointManager.addLineBreakpoint(
|
||||
this,
|
||||
file.virtualFile.url,
|
||||
line,
|
||||
KotlinPropertyBreakpointProperties(fieldName, className)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reportError(project: Project, message: String) {
|
||||
Messages.showMessageDialog(project, message, DebuggerBundle.message("add.field.breakpoint.dialog.title"), Messages.getErrorIcon())
|
||||
}
|
||||
|
||||
override fun isAddBreakpointButtonVisible() = true
|
||||
|
||||
override fun getMutedEnabledIcon() = AllIcons.Debugger.Db_muted_field_breakpoint
|
||||
|
||||
override fun getDisabledIcon() = AllIcons.Debugger.Db_disabled_field_breakpoint
|
||||
|
||||
override fun getEnabledIcon() = AllIcons.Debugger.Db_field_breakpoint
|
||||
|
||||
override fun getMutedDisabledIcon() = AllIcons.Debugger.Db_muted_disabled_field_breakpoint
|
||||
|
||||
override fun canBeHitInOtherPlaces() = true
|
||||
|
||||
override fun getShortText(breakpoint: XLineBreakpoint<KotlinPropertyBreakpointProperties>): String? {
|
||||
val properties = breakpoint.properties
|
||||
val className = properties.myClassName
|
||||
return if (!className.isEmpty()) className + "." + properties.myFieldName else properties.myFieldName
|
||||
}
|
||||
|
||||
override fun createProperties(): KotlinPropertyBreakpointProperties? {
|
||||
return KotlinPropertyBreakpointProperties()
|
||||
}
|
||||
|
||||
override fun createCustomPropertiesPanel(): XBreakpointCustomPropertiesPanel<XLineBreakpoint<KotlinPropertyBreakpointProperties>>? {
|
||||
return KotlinFieldBreakpointPropertiesPanel()
|
||||
}
|
||||
|
||||
override fun getDisplayText(breakpoint: XLineBreakpoint<KotlinPropertyBreakpointProperties>): String? {
|
||||
val kotlinBreakpoint = BreakpointManager.getJavaBreakpoint(breakpoint) as? BreakpointWithHighlighter
|
||||
return if (kotlinBreakpoint != null) {
|
||||
kotlinBreakpoint.description
|
||||
}
|
||||
else {
|
||||
super.getDisplayText(breakpoint)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEditorsProvider() = null
|
||||
|
||||
override fun createCustomRightPropertiesPanel(project: Project): XBreakpointCustomPropertiesPanel<XLineBreakpoint<KotlinPropertyBreakpointProperties>>? {
|
||||
return KotlinBreakpointFiltersPanel(project)
|
||||
}
|
||||
|
||||
override fun isSuspendThreadSupported() = true
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.ui.breakpoints.LineBreakpoint
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.xdebugger.XSourcePosition
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpointProperties
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
||||
|
||||
class KotlinLineBreakpoint(
|
||||
project: Project?,
|
||||
xBreakpoint: XBreakpoint<out XBreakpointProperties<*>>?
|
||||
) : LineBreakpoint<JavaLineBreakpointProperties>(project, xBreakpoint) {
|
||||
override fun processClassPrepare(debugProcess: DebugProcess?, classType: ReferenceType?) {
|
||||
val sourcePosition = xBreakpoint?.sourcePosition
|
||||
|
||||
if (classType != null && sourcePosition != null) {
|
||||
if (!hasTargetLine(classType, sourcePosition)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
super.processClassPrepare(debugProcess, classType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if `classType` definitely does not contain a location for a given `sourcePosition`.
|
||||
*/
|
||||
private fun hasTargetLine(classType: ReferenceType, sourcePosition: XSourcePosition): Boolean {
|
||||
val allLineLocations = DebuggerUtilsEx.allLineLocations(classType) ?: return true
|
||||
|
||||
if (classType.virtualMachine().isDexDebug()) {
|
||||
return true
|
||||
}
|
||||
|
||||
val fileName = sourcePosition.file.name
|
||||
val lineNumber = sourcePosition.line + 1
|
||||
|
||||
for (location in allLineLocations) {
|
||||
try {
|
||||
val kotlinFileName = location.sourceName(KOTLIN_STRATA_NAME)
|
||||
val kotlinLineNumber = location.lineNumber(KOTLIN_STRATA_NAME)
|
||||
if (kotlinFileName == fileName && kotlinLineNumber == lineNumber) {
|
||||
return true
|
||||
}
|
||||
} catch (e: AbsentInformationException) {
|
||||
if (location.sourceName() == fileName && location.lineNumber() == lineNumber) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
+152
@@ -0,0 +1,152 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.breakpoints;
|
||||
|
||||
import com.intellij.debugger.SourcePosition;
|
||||
import com.intellij.debugger.ui.breakpoints.Breakpoint;
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointManager;
|
||||
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType;
|
||||
import com.intellij.debugger.ui.breakpoints.LineBreakpoint;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint;
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties;
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinPositionManager;
|
||||
import org.jetbrains.kotlin.psi.KtClassInitializer;
|
||||
import org.jetbrains.kotlin.psi.KtFunction;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class KotlinLineBreakpointType extends JavaLineBreakpointType {
|
||||
public KotlinLineBreakpointType() {
|
||||
super("kotlin-line", "Kotlin Line Breakpoints");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Breakpoint<JavaLineBreakpointProperties> createJavaBreakpoint(
|
||||
Project project, XBreakpoint<JavaLineBreakpointProperties> breakpoint
|
||||
) {
|
||||
return new KotlinLineBreakpoint(project, breakpoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesPosition(@NotNull LineBreakpoint<?> breakpoint, @NotNull SourcePosition position) {
|
||||
JavaBreakpointProperties properties = getProperties(breakpoint);
|
||||
if (properties == null || properties instanceof JavaLineBreakpointProperties) {
|
||||
if (position instanceof KotlinPositionManager.KotlinReentrantSourcePosition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (properties != null && ((JavaLineBreakpointProperties) properties).getLambdaOrdinal() == null) return true;
|
||||
|
||||
PsiElement containingMethod = getContainingMethod(breakpoint);
|
||||
if (containingMethod == null) return false;
|
||||
return inTheMethod(position, containingMethod);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PsiElement getContainingMethod(@NotNull LineBreakpoint<?> breakpoint) {
|
||||
SourcePosition position = breakpoint.getSourcePosition();
|
||||
if (position == null) return null;
|
||||
|
||||
JavaBreakpointProperties properties = getProperties(breakpoint);
|
||||
if (properties instanceof JavaLineBreakpointProperties) {
|
||||
Integer ordinal = ((JavaLineBreakpointProperties) properties).getLambdaOrdinal();
|
||||
PsiElement lambda = getLambdaByOrdinal(position, ordinal);
|
||||
if (lambda != null) return lambda;
|
||||
}
|
||||
|
||||
return getContainingMethod(position.getElementAt());
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private static JavaBreakpointProperties getProperties(@NotNull LineBreakpoint<?> breakpoint) {
|
||||
XBreakpoint<?> xBreakpoint = breakpoint.getXBreakpoint();
|
||||
return xBreakpoint != null ? (JavaBreakpointProperties) xBreakpoint.getProperties() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static KtFunction getLambdaByOrdinal(SourcePosition position, Integer ordinal) {
|
||||
if (ordinal != null && ordinal >= 0) {
|
||||
List<KtFunction> lambdas = BreakpointTypeUtilsKt.getLambdasAtLineIfAny(position);
|
||||
if (lambdas.size() > ordinal) {
|
||||
return lambdas.get(ordinal);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement getContainingMethod(@Nullable PsiElement elem) {
|
||||
//noinspection unchecked
|
||||
return PsiTreeUtil.getParentOfType(elem, KtFunction.class, KtClassInitializer.class);
|
||||
}
|
||||
|
||||
public static boolean inTheMethod(@NotNull SourcePosition pos, @NotNull PsiElement method) {
|
||||
PsiElement elem = pos.getElementAt();
|
||||
if (elem == null) return false;
|
||||
return Comparing.equal(getContainingMethod(elem), method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPutAt(@NotNull VirtualFile file, int line, @NotNull Project project) {
|
||||
return BreakpointTypeUtilsKt.canPutAt(file, line, project, getClass());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<JavaBreakpointVariant> computeVariants(@NotNull Project project, @NotNull XSourcePosition position) {
|
||||
return BreakpointTypeUtilsKt.computeVariants(project, position, this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TextRange getHighlightRange(XLineBreakpoint<JavaLineBreakpointProperties> breakpoint) {
|
||||
JavaLineBreakpointProperties properties = breakpoint.getProperties();
|
||||
if (properties != null) {
|
||||
Integer ordinal = properties.getLambdaOrdinal();
|
||||
if (ordinal != null) {
|
||||
Breakpoint javaBreakpoint = BreakpointManager.getJavaBreakpoint(breakpoint);
|
||||
if (javaBreakpoint instanceof LineBreakpoint) {
|
||||
SourcePosition position = ((LineBreakpoint) javaBreakpoint).getSourcePosition();
|
||||
if (position != null) {
|
||||
KtFunction lambda = getLambdaByOrdinal(position, ordinal);
|
||||
if (lambda != null) {
|
||||
return lambda.getTextRange();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.breakpoints
|
||||
|
||||
import com.intellij.util.xmlb.annotations.Attribute
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties
|
||||
|
||||
class KotlinPropertyBreakpointProperties(
|
||||
@Attribute var myFieldName: String = "",
|
||||
@Attribute var myClassName: String = ""
|
||||
): JavaBreakpointProperties<KotlinPropertyBreakpointProperties>() {
|
||||
var WATCH_MODIFICATION: Boolean = true
|
||||
var WATCH_ACCESS: Boolean = false
|
||||
var WATCH_INITIALIZATION: Boolean = false
|
||||
|
||||
override fun getState() = this
|
||||
|
||||
override fun loadState(state: KotlinPropertyBreakpointProperties) {
|
||||
super.loadState(state)
|
||||
|
||||
WATCH_MODIFICATION = state.WATCH_MODIFICATION
|
||||
WATCH_ACCESS = state.WATCH_ACCESS
|
||||
WATCH_INITIALIZATION = state.WATCH_INITIALIZATION
|
||||
myFieldName = state.myFieldName
|
||||
myClassName = state.myClassName
|
||||
}
|
||||
}
|
||||
+150
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.xdebugger.XDebuggerUtil
|
||||
import com.intellij.xdebugger.XSourcePosition
|
||||
import com.intellij.xdebugger.impl.XSourcePositionImpl
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineNumber
|
||||
import org.jetbrains.kotlin.idea.debugger.findElementAtLine
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||
import java.util.*
|
||||
|
||||
fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass: Class<*>): Boolean {
|
||||
val psiFile = PsiManager.getInstance(project).findFile(file)
|
||||
|
||||
if (psiFile == null || psiFile.virtualFile?.fileType != KotlinFileType.INSTANCE) {
|
||||
return false
|
||||
}
|
||||
|
||||
val document = FileDocumentManager.getInstance().getDocument(file) ?: return false
|
||||
|
||||
var result: Class<*>? = null
|
||||
XDebuggerUtil.getInstance().iterateLine(project, document, line, fun (el: PsiElement): Boolean {
|
||||
// avoid comments
|
||||
if (el is PsiWhiteSpace || PsiTreeUtil.getParentOfType(el, PsiComment::class.java, false) != null) {
|
||||
return true
|
||||
}
|
||||
|
||||
var element = el
|
||||
var parent = element.parent
|
||||
while (parent != null) {
|
||||
val offset = parent.textOffset
|
||||
if (offset >= 0 && document.getLineNumber(offset) != line) break
|
||||
|
||||
element = parent
|
||||
parent = element.parent
|
||||
}
|
||||
|
||||
if (element is KtProperty || element is KtParameter) {
|
||||
result = if ((element is KtParameter && element.hasValOrVar()) || (element is KtProperty && !element.isLocal)) {
|
||||
KotlinFieldBreakpointType::class.java
|
||||
}
|
||||
else {
|
||||
KotlinLineBreakpointType::class.java
|
||||
}
|
||||
return false
|
||||
}
|
||||
else {
|
||||
result = KotlinLineBreakpointType::class.java
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return result == breakpointTypeClass
|
||||
}
|
||||
|
||||
fun computeVariants(
|
||||
project: Project,
|
||||
position: XSourcePosition,
|
||||
kotlinBreakpointType: KotlinLineBreakpointType
|
||||
): List<JavaLineBreakpointType.JavaBreakpointVariant> {
|
||||
val file = PsiManager.getInstance(project).findFile(position.file) as? KtFile ?: return emptyList()
|
||||
|
||||
val pos = SourcePosition.createFromLine(file, position.line)
|
||||
val lambdas = getLambdasAtLineIfAny(pos)
|
||||
if (lambdas.isEmpty()) return emptyList()
|
||||
|
||||
val result = LinkedList<JavaLineBreakpointType.JavaBreakpointVariant>()
|
||||
|
||||
val elementAt = pos.elementAt.parentsWithSelf.firstIsInstance<KtElement>()
|
||||
val mainMethod = KotlinLineBreakpointType.getContainingMethod(elementAt)
|
||||
if (mainMethod != null) {
|
||||
result.add(
|
||||
kotlinBreakpointType.LineJavaBreakpointVariant(
|
||||
position,
|
||||
CodeInsightUtils.getTopmostElementAtOffset(elementAt, pos.offset),
|
||||
-1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
lambdas.forEachIndexed { ordinal, lambda ->
|
||||
val positionImpl = XSourcePositionImpl.createByElement(lambda.bodyExpression)
|
||||
|
||||
if (positionImpl != null) {
|
||||
result.add(kotlinBreakpointType.LambdaJavaBreakpointVariant(positionImpl, lambda, ordinal))
|
||||
}
|
||||
}
|
||||
|
||||
result.add(kotlinBreakpointType.JavaBreakpointVariant(position))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun getLambdasAtLineIfAny(sourcePosition: SourcePosition): List<KtFunction> {
|
||||
val file = sourcePosition.file as? KtFile ?: return emptyList()
|
||||
val lineNumber = sourcePosition.line
|
||||
return getLambdasAtLineIfAny(file, lineNumber)
|
||||
}
|
||||
|
||||
fun getLambdasAtLineIfAny(file: KtFile, line: Int): List<KtFunction> {
|
||||
val lineElement = findElementAtLine(file, line) as? KtElement ?: return emptyList()
|
||||
|
||||
val start = lineElement.startOffset
|
||||
val end = lineElement.endOffset
|
||||
|
||||
val allLiterals = CodeInsightUtils.
|
||||
findElementsOfClassInRange(file, start, end, KtFunction::class.java)
|
||||
.filterIsInstance<KtFunction>()
|
||||
// filter function literals and functional expressions
|
||||
.filter { it is KtFunctionLiteral || it.name == null }
|
||||
.toSet()
|
||||
|
||||
return allLiterals.filter {
|
||||
val statement = it.bodyBlockExpression?.statements?.firstOrNull() ?: it
|
||||
statement.getLineNumber() == line && statement.getLineNumber(false) == line
|
||||
}
|
||||
}
|
||||
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.debugger.breakpoints.dialog.AddFieldBreakpointDialog">
|
||||
<grid id="dbe86" binding="myPanel" layout-manager="GridLayoutManager" row-count="6" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="6">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="74" y="134" width="245" height="152"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="9636d" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myClassChooser">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="c2ef3" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myFieldChooser">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<xy id="3cfba" layout-manager="XYLayout" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="5" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="2" fill="1" indent="0" use-parent-layout="false">
|
||||
<minimum-size width="-1" height="1"/>
|
||||
<maximum-size width="-1" height="1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="bevel-raised"/>
|
||||
<children/>
|
||||
</xy>
|
||||
<vspacer id="339be">
|
||||
<constraints>
|
||||
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<component id="e8c64" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="label.add.field.breakpoint.dialog.field.name"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="c159b" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="label.add.field.breakpoint.dialog.fq.name"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.breakpoints.dialog;
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle;
|
||||
import com.intellij.ide.util.MemberChooser;
|
||||
import com.intellij.ide.util.TreeClassChooser;
|
||||
import com.intellij.ide.util.TreeClassChooserFactory;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import kotlin.Unit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.idea.core.util.DescriptorMemberChooserObject;
|
||||
import org.jetbrains.kotlin.idea.core.util.UiUtilsKt;
|
||||
import org.jetbrains.kotlin.psi.KtProperty;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AddFieldBreakpointDialog extends DialogWrapper {
|
||||
private final Project myProject;
|
||||
private JPanel myPanel;
|
||||
private TextFieldWithBrowseButton myFieldChooser;
|
||||
private TextFieldWithBrowseButton myClassChooser;
|
||||
|
||||
public AddFieldBreakpointDialog(Project project) {
|
||||
super(project, true);
|
||||
myProject = project;
|
||||
setTitle(DebuggerBundle.message("add.field.breakpoint.dialog.title"));
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
UiUtilsKt.onTextChange(
|
||||
myClassChooser.getTextField(),
|
||||
(DocumentEvent e) -> {
|
||||
updateUI();
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
);
|
||||
|
||||
myClassChooser.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
PsiClass currentClass = getSelectedClass();
|
||||
TreeClassChooser chooser = TreeClassChooserFactory.getInstance(myProject).createAllProjectScopeChooser(
|
||||
DebuggerBundle.message("add.field.breakpoint.dialog.classchooser.title"));
|
||||
if (currentClass != null) {
|
||||
PsiFile containingFile = currentClass.getContainingFile();
|
||||
if (containingFile != null) {
|
||||
PsiDirectory containingDirectory = containingFile.getContainingDirectory();
|
||||
if (containingDirectory != null) {
|
||||
chooser.selectDirectory(containingDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
chooser.showDialog();
|
||||
PsiClass selectedClass = chooser.getSelected();
|
||||
if (selectedClass != null) {
|
||||
myClassChooser.setText(selectedClass.getQualifiedName());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
myFieldChooser.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull ActionEvent e) {
|
||||
PsiClass selectedClass = getSelectedClass();
|
||||
DescriptorMemberChooserObject[] properties = FieldBreakpointDialogUtilKt.collectProperties(selectedClass);
|
||||
MemberChooser<DescriptorMemberChooserObject> chooser = new MemberChooser<DescriptorMemberChooserObject>(properties, false, false, myProject);
|
||||
chooser.setTitle(DebuggerBundle.message("add.field.breakpoint.dialog.field.chooser.title", properties.length));
|
||||
chooser.setCopyJavadocVisible(false);
|
||||
chooser.show();
|
||||
List<DescriptorMemberChooserObject> selectedElements = chooser.getSelectedElements();
|
||||
if (selectedElements != null && selectedElements.size() == 1) {
|
||||
KtProperty field = (KtProperty) selectedElements.get(0).getElement();
|
||||
myFieldChooser.setText(field.getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
myFieldChooser.setEnabled(false);
|
||||
return myPanel;
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
PsiClass selectedClass = getSelectedClass();
|
||||
myFieldChooser.setEnabled(selectedClass != null);
|
||||
}
|
||||
|
||||
private PsiClass getSelectedClass() {
|
||||
PsiManager psiManager = PsiManager.getInstance(myProject);
|
||||
String classQName = myClassChooser.getText();
|
||||
if (classQName == null || classQName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return JavaPsiFacade.getInstance(psiManager.getProject()).findClass(classQName, GlobalSearchScope.allScope(myProject));
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return myClassChooser.getTextField();
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return myClassChooser.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDimensionServiceKey() {
|
||||
return "#com.intellij.debugger.ui.breakpoints.BreakpointsConfigurationDialogFactory.BreakpointsConfigurationDialog.AddFieldBreakpointDialog";
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return myFieldChooser.getText();
|
||||
}
|
||||
|
||||
protected abstract boolean validateData();
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
if (validateData()) {
|
||||
super.doOKAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* 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.debugger.breakpoints.dialog
|
||||
|
||||
import com.intellij.psi.PsiClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.util.DescriptorMemberChooserObject
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
|
||||
fun PsiClass.collectProperties(): Array<DescriptorMemberChooserObject> {
|
||||
if (this is KtLightClassForFacade) {
|
||||
val result = arrayListOf<DescriptorMemberChooserObject>()
|
||||
this.files.forEach {
|
||||
it.declarations.filterIsInstance<KtProperty>().forEach {
|
||||
result.add(DescriptorMemberChooserObject(it, it.unsafeResolveToDescriptor()))
|
||||
}
|
||||
}
|
||||
return result.toTypedArray()
|
||||
}
|
||||
if (this is KtLightClass) {
|
||||
val origin = this.kotlinOrigin
|
||||
if (origin != null) {
|
||||
return origin.declarations.filterIsInstance<KtProperty>().map {
|
||||
DescriptorMemberChooserObject(it, it.unsafeResolveToDescriptor())
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
||||
return emptyArray()
|
||||
}
|
||||
|
||||
+256
@@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.sun.jdi.*
|
||||
import com.sun.tools.jdi.LocalVariableImpl
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClass
|
||||
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
|
||||
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
|
||||
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmTypes
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
import org.jetbrains.kotlin.idea.core.KotlinFileTypeFactory
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineEndOffset
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
import java.util.*
|
||||
|
||||
fun Location.isInKotlinSources(): Boolean {
|
||||
val declaringType = declaringType()
|
||||
val fileExtension = declaringType.safeSourceName()?.substringAfterLast('.')?.toLowerCase() ?: ""
|
||||
return fileExtension in KotlinFileTypeFactory.KOTLIN_EXTENSIONS || declaringType.containsKotlinStrata()
|
||||
}
|
||||
|
||||
fun ReferenceType.containsKotlinStrata() = availableStrata().contains(KOTLIN_STRATA_NAME)
|
||||
|
||||
fun isInsideInlineArgument(
|
||||
inlineArgument: KtFunction,
|
||||
location: Location,
|
||||
debugProcess: DebugProcessImpl,
|
||||
bindingContext: BindingContext = KotlinDebuggerCaches.getOrCreateTypeMapper(inlineArgument).bindingContext
|
||||
): Boolean {
|
||||
val visibleVariables = location.visibleVariables(debugProcess)
|
||||
val markerLocalVariables = visibleVariables.filter { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) }
|
||||
|
||||
val context = KotlinDebuggerCaches.getOrCreateTypeMapper(inlineArgument).bindingContext
|
||||
val lambdaOrdinal = runReadAction { lambdaOrdinalByArgument(inlineArgument, context) }
|
||||
val functionName = runReadAction { functionNameByArgument(inlineArgument, context) }
|
||||
|
||||
return markerLocalVariables
|
||||
.map { it.name().drop(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT.length) }
|
||||
.any { variableName ->
|
||||
if (variableName.startsWith("-")) {
|
||||
val lambdaClassName = asmTypeForAnonymousClass(bindingContext, inlineArgument)
|
||||
.internalName.substringAfterLast("/")
|
||||
|
||||
variableName == "-$functionName-$lambdaClassName"
|
||||
} else {
|
||||
// For Kotlin up to 1.3.10
|
||||
lambdaOrdinalByLocalVariable(variableName) == lambdaOrdinal
|
||||
&& functionNameByLocalVariable(variableName) == functionName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Any> DebugProcessImpl.invokeInManagerThread(f: (DebuggerContextImpl) -> T?): T? {
|
||||
var result: T? = null
|
||||
val command: DebuggerCommandImpl = object : DebuggerCommandImpl() {
|
||||
override fun action() {
|
||||
result = runReadAction { f(debuggerContext) }
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
DebuggerManagerThreadImpl.isManagerThread() ->
|
||||
managerThread.invoke(command)
|
||||
else ->
|
||||
managerThread.invokeAndWait(command)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun lambdaOrdinalByArgument(elementAt: KtFunction, context: BindingContext): Int {
|
||||
val type = CodegenBinding.asmTypeForAnonymousClass(context, elementAt)
|
||||
return type.className.substringAfterLast("$").toInt()
|
||||
}
|
||||
|
||||
private fun functionNameByArgument(elementAt: KtFunction, context: BindingContext): String {
|
||||
val inlineArgumentDescriptor = InlineUtil.getInlineArgumentDescriptor(elementAt, context)
|
||||
return inlineArgumentDescriptor?.containingDeclaration?.name?.asString() ?: "unknown"
|
||||
}
|
||||
|
||||
private fun Location.visibleVariables(debugProcess: DebugProcessImpl): List<LocalVariable> {
|
||||
val stackFrame = MockStackFrame(this, debugProcess.virtualMachineProxy.virtualMachine)
|
||||
return stackFrame.visibleVariables()
|
||||
}
|
||||
|
||||
private fun lambdaOrdinalByLocalVariable(name: String): Int {
|
||||
try {
|
||||
val nameWithoutPrefix = name.removePrefix(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT)
|
||||
return Integer.parseInt(nameWithoutPrefix.substringBefore("$", nameWithoutPrefix))
|
||||
}
|
||||
catch(e: NumberFormatException) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun functionNameByLocalVariable(name: String): String {
|
||||
val nameWithoutPrefix = name.removePrefix(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT)
|
||||
return nameWithoutPrefix.substringAfterLast("$", "unknown")
|
||||
}
|
||||
|
||||
private class MockStackFrame(private val location: Location, private val vm: VirtualMachine) : StackFrame {
|
||||
private var visibleVariables: Map<String, LocalVariable>? = null
|
||||
|
||||
override fun location() = location
|
||||
override fun thread() = null
|
||||
override fun thisObject() = null
|
||||
|
||||
private fun createVisibleVariables() {
|
||||
if (visibleVariables == null) {
|
||||
val allVariables = location.method().safeVariables() ?: emptyList()
|
||||
val map = HashMap<String, LocalVariable>(allVariables.size)
|
||||
|
||||
for (allVariable in allVariables) {
|
||||
val variable = allVariable as LocalVariableImpl
|
||||
val name = variable.name()
|
||||
if (variable.isVisible(this)) {
|
||||
map.put(name, variable)
|
||||
}
|
||||
}
|
||||
visibleVariables = map
|
||||
}
|
||||
}
|
||||
|
||||
override fun visibleVariables(): List<LocalVariable> {
|
||||
createVisibleVariables()
|
||||
val mapAsList = ArrayList(visibleVariables!!.values)
|
||||
Collections.sort(mapAsList)
|
||||
return mapAsList
|
||||
}
|
||||
|
||||
override fun visibleVariableByName(name: String): LocalVariable? {
|
||||
createVisibleVariables()
|
||||
return visibleVariables!![name]
|
||||
}
|
||||
|
||||
override fun getValue(variable: LocalVariable) = null
|
||||
override fun getValues(variables: List<LocalVariable>): Map<LocalVariable, Value> = emptyMap()
|
||||
override fun setValue(variable: LocalVariable, value: Value) {
|
||||
}
|
||||
|
||||
override fun getArgumentValues(): List<Value> = emptyList()
|
||||
override fun virtualMachine() = vm
|
||||
}
|
||||
|
||||
private const val DO_RESUME_SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Throwable;)Ljava/lang/Object;"
|
||||
private const val INVOKE_SUSPEND_SIGNATURE = "(Ljava/lang/Object;)Ljava/lang/Object;"
|
||||
|
||||
fun isInSuspendMethod(location: Location): Boolean {
|
||||
val method = location.method()
|
||||
val signature = method.signature()
|
||||
|
||||
for (continuationAsmType in continuationAsmTypes()) {
|
||||
if (signature.contains(continuationAsmType.toString()) ||
|
||||
(method.name() == DO_RESUME_METHOD_NAME && signature == DO_RESUME_SIGNATURE) ||
|
||||
(method.name() == INVOKE_SUSPEND_METHOD_NAME && signature == INVOKE_SUSPEND_SIGNATURE)
|
||||
) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun suspendFunctionFirstLineLocation(location: Location): Int? {
|
||||
if (!isInSuspendMethod(location)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val lineNumber = location.method().location()?.lineNumber()
|
||||
if (lineNumber == -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
return lineNumber
|
||||
}
|
||||
|
||||
fun isOnSuspendReturnOrReenter(location: Location): Boolean {
|
||||
val suspendStartLineNumber = suspendFunctionFirstLineLocation(location) ?: return false
|
||||
return suspendStartLineNumber == location.lineNumber()
|
||||
}
|
||||
|
||||
fun isLastLineLocationInMethod(location: Location): Boolean {
|
||||
val knownLines = location.method().safeAllLineLocations().map { it.lineNumber() }.filter { it != -1 }
|
||||
if (knownLines.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return knownLines.max() == location.lineNumber()
|
||||
}
|
||||
|
||||
fun isOneLineMethod(location: Location): Boolean {
|
||||
val allLineLocations = location.method().safeAllLineLocations()
|
||||
val firstLine = allLineLocations.firstOrNull()?.lineNumber()
|
||||
val lastLine = allLineLocations.lastOrNull()?.lineNumber()
|
||||
|
||||
return firstLine != null && firstLine == lastLine
|
||||
}
|
||||
|
||||
fun findElementAtLine(file: KtFile, line: Int): PsiElement? {
|
||||
val lineStartOffset = file.getLineStartOffset(line) ?: return null
|
||||
val lineEndOffset = file.getLineEndOffset(line) ?: return null
|
||||
|
||||
var topMostElement: PsiElement? = null
|
||||
var elementAt: PsiElement?
|
||||
for (offset in lineStartOffset until lineEndOffset) {
|
||||
elementAt = file.findElementAt(offset)
|
||||
if (elementAt != null) {
|
||||
topMostElement = CodeInsightUtils.getTopmostElementAtOffset(elementAt, offset)
|
||||
if (topMostElement is KtElement) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return topMostElement
|
||||
}
|
||||
|
||||
fun findCallByEndToken(element: PsiElement): KtCallExpression? {
|
||||
if (element is KtElement) return null
|
||||
|
||||
return when (element.node.elementType) {
|
||||
KtTokens.RPAR -> (element.parent as? KtValueArgumentList)?.parent as? KtCallExpression
|
||||
KtTokens.RBRACE -> {
|
||||
val braceParent = CodeInsightUtils.getTopParentWithEndOffset(element, KtCallExpression::class.java)
|
||||
when (braceParent) {
|
||||
is KtCallExpression -> braceParent
|
||||
is KtLambdaArgument -> braceParent.parent as? KtCallExpression
|
||||
is KtValueArgument -> (braceParent.parent as? KtValueArgumentList)?.parent as? KtCallExpression
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
val DebuggerContextImpl.canRunEvaluation: Boolean
|
||||
get() = debugProcess?.canRunEvaluation ?: false
|
||||
|
||||
val DebugProcessImpl.canRunEvaluation: Boolean
|
||||
get() = suspendManager.pausedContext != null
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.DebuggerInvocationUtil
|
||||
import com.intellij.debugger.engine.ContextUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.ui.EditorEvaluationCommand
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.psi.CommonClassNames
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.sun.jdi.ClassType
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.eval4j.jdi.asValue
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.debugger.getClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
|
||||
abstract class KotlinRuntimeTypeEvaluator(
|
||||
editor: Editor?,
|
||||
expression: KtExpression,
|
||||
context: DebuggerContextImpl,
|
||||
indicator: ProgressIndicator
|
||||
) : EditorEvaluationCommand<KotlinType>(editor, expression, context, indicator) {
|
||||
|
||||
override fun threadAction() {
|
||||
var type: KotlinType? = null
|
||||
try {
|
||||
type = evaluate()
|
||||
} catch (ignored: ProcessCanceledException) {
|
||||
} catch (ignored: EvaluateException) {
|
||||
} finally {
|
||||
typeCalculationFinished(type)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun typeCalculationFinished(type: KotlinType?)
|
||||
|
||||
override fun evaluate(evaluationContext: EvaluationContextImpl): KotlinType? {
|
||||
val project = evaluationContext.project
|
||||
|
||||
val evaluator = DebuggerInvocationUtil.commitAndRunReadAction<ExpressionEvaluator>(project) {
|
||||
val codeFragment = KtPsiFactory(myElement.project)
|
||||
.createExpressionCodeFragment(myElement.text, myElement.containingFile.context)
|
||||
|
||||
val codeFragmentFactory = DebuggerUtilsEx.getCodeFragmentFactory(codeFragment.context, KotlinFileType.INSTANCE)
|
||||
codeFragmentFactory.evaluatorBuilder.build(codeFragment, ContextUtil.getSourcePosition(evaluationContext))
|
||||
}
|
||||
|
||||
val value = evaluator.evaluate(evaluationContext)
|
||||
if (value != null) {
|
||||
return runReadAction { getCastableRuntimeType(evaluationContext.debugProcess.searchScope, value) }
|
||||
}
|
||||
|
||||
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.surrounded.expression.null"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun getCastableRuntimeType(scope: GlobalSearchScope, value: Value): KotlinType? {
|
||||
val myValue = value.asValue()
|
||||
var psiClass = myValue.asmType.getClassDescriptor(scope)
|
||||
if (psiClass != null) {
|
||||
return psiClass.defaultType
|
||||
}
|
||||
|
||||
val type = value.type()
|
||||
if (type is ClassType) {
|
||||
val superclass = type.superclass()
|
||||
if (superclass != null && CommonClassNames.JAVA_LANG_OBJECT != superclass.name()) {
|
||||
psiClass = AsmType.getType(superclass.signature()).getClassDescriptor(scope)
|
||||
if (psiClass != null) {
|
||||
return psiClass.defaultType
|
||||
}
|
||||
}
|
||||
|
||||
for (interfaceType in type.interfaces()) {
|
||||
psiClass = AsmType.getType(interfaceType.signature()).getClassDescriptor(scope)
|
||||
if (psiClass != null) {
|
||||
return psiClass.defaultType
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.filter
|
||||
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.ui.classFilter.ClassFilter
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings
|
||||
|
||||
private val KOTLIN_STDLIB_FILTER = "kotlin.*"
|
||||
|
||||
fun addKotlinStdlibDebugFilterIfNeeded() {
|
||||
if (!KotlinDebuggerSettings.getInstance().DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED) {
|
||||
val settings = DebuggerSettings.getInstance()!!
|
||||
val newFilters = (settings.steppingFilters + ClassFilter(KOTLIN_STDLIB_FILTER))
|
||||
|
||||
settings.steppingFilters = newFilters
|
||||
|
||||
KotlinDebuggerSettings.getInstance().DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED = true
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.filter
|
||||
|
||||
import com.intellij.ui.classFilter.ClassFilter
|
||||
import com.intellij.ui.classFilter.DebuggerClassFilterProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings
|
||||
|
||||
private val FILTERS = listOf(
|
||||
ClassFilter("kotlin.jvm*"),
|
||||
ClassFilter("kotlin.reflect*"),
|
||||
ClassFilter("kotlin.NoWhenBranchMatchedException"),
|
||||
ClassFilter("kotlin.TypeCastException"),
|
||||
ClassFilter("kotlin.KotlinNullPointerException")
|
||||
)
|
||||
|
||||
class KotlinDebuggerInternalClassesFilterProvider : DebuggerClassFilterProvider {
|
||||
override fun getFilters(): List<ClassFilter>? {
|
||||
return if (KotlinDebuggerSettings.getInstance().DEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES) FILTERS else listOf()
|
||||
}
|
||||
}
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.filter
|
||||
|
||||
import com.intellij.debugger.engine.SyntheticTypeComponentProvider
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.safeAllLineLocations
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import kotlin.jvm.internal.FunctionReference
|
||||
import kotlin.jvm.internal.PropertyReference
|
||||
|
||||
class KotlinSyntheticTypeComponentProvider: SyntheticTypeComponentProvider {
|
||||
override fun isSynthetic(typeComponent: TypeComponent?): Boolean {
|
||||
if (typeComponent !is Method) return false
|
||||
|
||||
val containingType = typeComponent.declaringType()
|
||||
val typeName = containingType.name()
|
||||
if (!FqNameUnsafe.isValid(typeName)) return false
|
||||
|
||||
// TODO: this is most likely not necessary since KT-28453 is fixed, but still can be useful when debugging old compiled code
|
||||
if (containingType.isCallableReferenceSyntheticClass()) {
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeComponent.isDelegateToDefaultInterfaceImpl()) return true
|
||||
|
||||
if (typeComponent.location()?.lineNumber() != 1) return false
|
||||
|
||||
if (typeComponent.allLineLocations().any { it.lineNumber() != 1 }) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !typeComponent.declaringType().allLineLocations().any { it.lineNumber() != 1 }
|
||||
}
|
||||
catch(e: AbsentInformationException) {
|
||||
return false
|
||||
}
|
||||
catch(e: UnsupportedOperationException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private tailrec fun ReferenceType?.isCallableReferenceSyntheticClass(): Boolean {
|
||||
if (this !is ClassType) return false
|
||||
val superClass = this.superclass() ?: return false
|
||||
val superClassName = superClass.name()
|
||||
if (superClassName == PropertyReference::class.java.name || superClassName == FunctionReference::class.java.name) {
|
||||
return true
|
||||
}
|
||||
|
||||
// The direct supertype may be PropertyReference0 or something
|
||||
return if (superClassName.startsWith("kotlin.jvm.internal."))
|
||||
superClass.isCallableReferenceSyntheticClass()
|
||||
else
|
||||
false
|
||||
}
|
||||
|
||||
private fun Method.isDelegateToDefaultInterfaceImpl(): Boolean {
|
||||
if (safeAllLineLocations().size != 1) return false
|
||||
if (!virtualMachine().canGetBytecodes()) return false
|
||||
|
||||
if (!hasOnlyInvokeStatic(this)) return false
|
||||
|
||||
return hasInterfaceWithImplementation(this)
|
||||
}
|
||||
|
||||
private val LOAD_INSTRUCTIONS_WITH_INDEX = Opcodes.ILOAD.toByte()..Opcodes.ALOAD.toByte()
|
||||
private val LOAD_INSTRUCTIONS = (Opcodes.ALOAD + 1).toByte()..(Opcodes.IALOAD - 1).toByte()
|
||||
|
||||
private val RETURN_INSTRUCTIONS = Opcodes.IRETURN.toByte()..Opcodes.RETURN.toByte()
|
||||
|
||||
// Check that method contains only load and invokeStatic instructions. Note that if after load goes ldc instruction it could be checkParametersNotNull method invocation
|
||||
private fun hasOnlyInvokeStatic(m: Method): Boolean {
|
||||
val bytecodes = m.bytecodes()
|
||||
var i = 0
|
||||
var isALoad0BeforeStaticCall = false
|
||||
while (i < bytecodes.size) {
|
||||
val instr = bytecodes[i]
|
||||
when {
|
||||
instr == 42.toByte() /* ALOAD_0 */ -> {
|
||||
i += 1
|
||||
isALoad0BeforeStaticCall = true
|
||||
}
|
||||
instr in LOAD_INSTRUCTIONS_WITH_INDEX || instr in LOAD_INSTRUCTIONS -> {
|
||||
i += 1
|
||||
if (instr in LOAD_INSTRUCTIONS_WITH_INDEX) i += 1
|
||||
val nextInstr = bytecodes[i]
|
||||
if (nextInstr == Opcodes.LDC.toByte()) {
|
||||
i += 2
|
||||
isALoad0BeforeStaticCall = false
|
||||
}
|
||||
}
|
||||
instr == Opcodes.INVOKESTATIC.toByte() -> {
|
||||
i += 3
|
||||
if (isALoad0BeforeStaticCall && i == (bytecodes.size - 1)) {
|
||||
val nextInstr = bytecodes[i]
|
||||
return nextInstr in RETURN_INSTRUCTIONS
|
||||
}
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: class DefaultImpl can be not loaded
|
||||
private fun hasInterfaceWithImplementation(method: Method): Boolean {
|
||||
val declaringType = method.declaringType() as? ClassType ?: return false
|
||||
val interfaces = declaringType.allInterfaces()
|
||||
val vm = declaringType.virtualMachine()
|
||||
val traitImpls = interfaces.flatMap { vm.classesByName(it.name() + JvmAbi.DEFAULT_IMPLS_SUFFIX) }
|
||||
return traitImpls.any { !it.methodsByName(method.name()).isEmpty() }
|
||||
}
|
||||
}
|
||||
+85
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.render
|
||||
|
||||
import com.intellij.debugger.DebuggerContext
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.ui.impl.watch.FieldDescriptorImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiExpression
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
class DelegatedPropertyFieldDescriptor(
|
||||
project: Project,
|
||||
objectRef: ObjectReference,
|
||||
val delegate: Field,
|
||||
private val renderDelegatedProperty: Boolean
|
||||
) : FieldDescriptorImpl(project, objectRef, delegate) {
|
||||
|
||||
override fun calcValue(evaluationContext: EvaluationContextImpl?): Value? {
|
||||
if (evaluationContext == null) return null
|
||||
if (!renderDelegatedProperty) return super.calcValue(evaluationContext)
|
||||
|
||||
val method = findGetterForDelegatedProperty()
|
||||
val threadReference = evaluationContext.suspendContext.thread?.threadReference
|
||||
if (method == null || threadReference == null) {
|
||||
return super.calcValue(evaluationContext)
|
||||
}
|
||||
|
||||
try {
|
||||
return evaluationContext.debugProcess.invokeInstanceMethod(
|
||||
evaluationContext,
|
||||
`object`,
|
||||
method,
|
||||
listOf<Nothing>(),
|
||||
evaluationContext.suspendContext.suspendPolicy
|
||||
)
|
||||
}
|
||||
catch(e: EvaluateException) {
|
||||
return e.exceptionFromTargetVM
|
||||
}
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return delegate.name().removeSuffix(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX)
|
||||
}
|
||||
|
||||
override fun getDescriptorEvaluation(context: DebuggerContext?): PsiExpression? {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findGetterForDelegatedProperty(): Method? {
|
||||
val fieldName = name
|
||||
if (!Name.isValidIdentifier(fieldName)) return null
|
||||
|
||||
return `object`.referenceType().methodsByName(JvmAbi.getterName(fieldName))?.firstOrNull()
|
||||
}
|
||||
|
||||
override fun getDeclaredType(): String? {
|
||||
val getter = findGetterForDelegatedProperty() ?: return null
|
||||
val returnType = try {
|
||||
getter.returnType()
|
||||
} catch (e: ClassNotLoadedException) {
|
||||
// Behavior copied from LocalVariableDescriptorImpl (in platform)
|
||||
return "<unknown>"
|
||||
}
|
||||
return returnType?.name()
|
||||
}
|
||||
}
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.render
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||
import com.intellij.debugger.settings.NodeRendererSettings
|
||||
import com.intellij.debugger.ui.impl.watch.MessageDescriptor
|
||||
import com.intellij.debugger.ui.impl.watch.NodeManagerImpl
|
||||
import com.intellij.debugger.ui.tree.DebuggerTreeNode
|
||||
import com.intellij.debugger.ui.tree.ValueDescriptor
|
||||
import com.intellij.debugger.ui.tree.render.ChildrenBuilder
|
||||
import com.intellij.debugger.ui.tree.render.ClassRenderer
|
||||
import com.intellij.debugger.ui.tree.render.DescriptorLabelListener
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.xdebugger.settings.XDebuggerSettingsManager
|
||||
import com.sun.jdi.*
|
||||
import com.sun.jdi.Type
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings
|
||||
import org.jetbrains.kotlin.idea.debugger.ToggleKotlinVariablesState
|
||||
import org.jetbrains.kotlin.idea.debugger.canRunEvaluation
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import java.util.*
|
||||
import com.sun.jdi.Type as JdiType
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
|
||||
private val LOG = Logger.getInstance(KotlinClassWithDelegatedPropertyRenderer::class.java)
|
||||
private fun notPreparedClassMessage(referenceType: ReferenceType) =
|
||||
"$referenceType ${referenceType.isPrepared} ${referenceType.sourceName()}"
|
||||
|
||||
class KotlinClassWithDelegatedPropertyRenderer : ClassRenderer() {
|
||||
private val rendererSettings = NodeRendererSettings.getInstance()
|
||||
|
||||
override fun isApplicable(jdiType: Type?): Boolean {
|
||||
if (!super.isApplicable(jdiType)) return false
|
||||
|
||||
if (jdiType !is ReferenceType) return false
|
||||
|
||||
if (!jdiType.isPrepared) {
|
||||
LOG.info(notPreparedClassMessage(jdiType))
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
return jdiType.allFields().any { it.name().endsWith(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX) }
|
||||
} catch (notPrepared: ClassNotPreparedException) {
|
||||
LOG.error(notPreparedClassMessage(jdiType), notPrepared)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun calcLabel(
|
||||
descriptor: ValueDescriptor,
|
||||
evaluationContext: EvaluationContext,
|
||||
listener: DescriptorLabelListener
|
||||
): String {
|
||||
val res = calcToStringLabel(descriptor, evaluationContext, listener)
|
||||
if (res != null) {
|
||||
return res
|
||||
}
|
||||
|
||||
return super.calcLabel(descriptor, evaluationContext, listener)
|
||||
}
|
||||
|
||||
private fun calcToStringLabel(
|
||||
descriptor: ValueDescriptor, evaluationContext: EvaluationContext,
|
||||
listener: DescriptorLabelListener
|
||||
): String? {
|
||||
val toStringRenderer = rendererSettings.toStringRenderer
|
||||
if (toStringRenderer.isEnabled && DebuggerManagerEx.getInstanceEx(evaluationContext.project).context.canRunEvaluation) {
|
||||
if (toStringRenderer.isApplicable(descriptor.type)) {
|
||||
return toStringRenderer.calcLabel(descriptor, evaluationContext, listener)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun buildChildren(value: Value?, builder: ChildrenBuilder, context: EvaluationContext) {
|
||||
DebuggerManagerThreadImpl.assertIsManagerThread()
|
||||
|
||||
if (value !is ObjectReference) return
|
||||
|
||||
val nodeManager = builder.nodeManager!!
|
||||
val nodeDescriptorFactory = builder.descriptorManager!!
|
||||
|
||||
val fields = value.referenceType().allFields()
|
||||
if (fields.isEmpty()) {
|
||||
builder.setChildren(listOf(nodeManager.createMessageNode(MessageDescriptor.CLASS_HAS_NO_FIELDS.label)))
|
||||
return
|
||||
}
|
||||
|
||||
val children = ArrayList<DebuggerTreeNode>()
|
||||
for (field in fields) {
|
||||
if (!shouldDisplay(context, value, field)) {
|
||||
continue
|
||||
}
|
||||
|
||||
val fieldDescriptor = nodeDescriptorFactory.getFieldDescriptor(builder.parentDescriptor, value, field)
|
||||
|
||||
if (field.name().endsWith(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX)) {
|
||||
val shouldRenderDelegatedProperty = KotlinDebuggerSettings.getInstance().DEBUG_RENDER_DELEGATED_PROPERTIES
|
||||
if (shouldRenderDelegatedProperty && !ToggleKotlinVariablesState.getService().kotlinVariableView) {
|
||||
children.add(nodeManager.createNode(fieldDescriptor, context))
|
||||
}
|
||||
|
||||
val delegatedPropertyDescriptor = DelegatedPropertyFieldDescriptor(
|
||||
context.debugProcess.project!!,
|
||||
value,
|
||||
field,
|
||||
shouldRenderDelegatedProperty
|
||||
)
|
||||
children.add(nodeManager.createNode(delegatedPropertyDescriptor, context))
|
||||
} else {
|
||||
children.add(nodeManager.createNode(fieldDescriptor, context))
|
||||
}
|
||||
}
|
||||
|
||||
if (XDebuggerSettingsManager.getInstance()!!.dataViewSettings.isSortValues) {
|
||||
children.sortedWith(NodeManagerImpl.getNodeComparator())
|
||||
}
|
||||
|
||||
builder.setChildren(children)
|
||||
}
|
||||
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.debugger.stepping;
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl;
|
||||
import com.intellij.debugger.engine.RequestHint;
|
||||
import com.intellij.debugger.engine.SuspendContextImpl;
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
|
||||
import com.sun.jdi.request.StepRequest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class DebugProcessImplHelper {
|
||||
public static DebugProcessImpl.StepOverCommand createStepOverCommandWithCustomFilter(
|
||||
SuspendContextImpl suspendContext,
|
||||
boolean ignoreBreakpoints,
|
||||
KotlinSuspendCallStepOverFilter methodFilter
|
||||
) {
|
||||
DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
|
||||
return debugProcess.new StepOverCommand(suspendContext, ignoreBreakpoints, StepRequest.STEP_LINE) {
|
||||
@NotNull
|
||||
@Override
|
||||
protected RequestHint getHint(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl stepThread) {
|
||||
@SuppressWarnings("MagicConstant")
|
||||
RequestHint hint = new RequestHintWithMethodFilter(stepThread, suspendContext, StepRequest.STEP_OVER, methodFilter);
|
||||
hint.setRestoreBreakpoints(ignoreBreakpoints);
|
||||
hint.setIgnoreFilters(ignoreBreakpoints || debugProcess.getSession().shouldIgnoreSteppingFilters());
|
||||
|
||||
return hint;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+211
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.stepping;
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl;
|
||||
import com.intellij.debugger.engine.SuspendContextImpl;
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException;
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx;
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl;
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
|
||||
import com.intellij.debugger.settings.DebuggerSettings;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.extensions.Extensions;
|
||||
import com.intellij.ui.classFilter.ClassFilter;
|
||||
import com.intellij.ui.classFilter.DebuggerClassFilterProvider;
|
||||
import com.sun.jdi.Location;
|
||||
import com.sun.jdi.ObjectCollectedException;
|
||||
import com.sun.jdi.ReferenceType;
|
||||
import com.sun.jdi.ThreadReference;
|
||||
import com.sun.jdi.request.EventRequest;
|
||||
import com.sun.jdi.request.EventRequestManager;
|
||||
import com.sun.jdi.request.StepRequest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.idea.debugger.NoStrataPositionManagerHelperKt;
|
||||
import org.jetbrains.kotlin.psi.KtFunctionLiteral;
|
||||
import org.jetbrains.kotlin.psi.KtNamedFunction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
public class DebuggerSteppingHelper {
|
||||
private static Logger LOG = Logger.getInstance(DebuggerSteppingHelper.class);
|
||||
|
||||
public static DebugProcessImpl.ResumeCommand createStepOverCommand(
|
||||
final SuspendContextImpl suspendContext,
|
||||
final boolean ignoreBreakpoints,
|
||||
final KotlinSteppingCommandProvider.KotlinSourcePosition kotlinSourcePosition
|
||||
) {
|
||||
final DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
|
||||
|
||||
return debugProcess.new ResumeCommand(suspendContext) {
|
||||
@Override
|
||||
public void contextAction() {
|
||||
boolean isDexDebug = NoStrataPositionManagerHelperKt.isDexDebug(suspendContext.getDebugProcess());
|
||||
|
||||
try {
|
||||
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
|
||||
if (frameProxy != null) {
|
||||
Action action = KotlinSteppingCommandProviderKt.getStepOverAction(
|
||||
frameProxy.location(),
|
||||
kotlinSourcePosition,
|
||||
frameProxy,
|
||||
isDexDebug
|
||||
);
|
||||
|
||||
createStepRequest(
|
||||
suspendContext, getContextThread(),
|
||||
debugProcess.getVirtualMachineProxy().eventRequestManager(),
|
||||
StepRequest.STEP_LINE, StepRequest.STEP_OUT);
|
||||
|
||||
action.apply(debugProcess, suspendContext, ignoreBreakpoints);
|
||||
return;
|
||||
}
|
||||
|
||||
debugProcess.createStepOutCommand(suspendContext).contextAction();
|
||||
}
|
||||
catch (EvaluateException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static DebugProcessImpl.ResumeCommand createStepOutCommand(
|
||||
final SuspendContextImpl suspendContext,
|
||||
final boolean ignoreBreakpoints,
|
||||
final List<KtNamedFunction> inlineFunctions,
|
||||
final KtFunctionLiteral inlineArgument
|
||||
) {
|
||||
final DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
|
||||
return debugProcess.new ResumeCommand(suspendContext) {
|
||||
@Override
|
||||
public void contextAction() {
|
||||
try {
|
||||
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
|
||||
if (frameProxy != null) {
|
||||
Action action = KotlinSteppingCommandProviderKt.getStepOutAction(
|
||||
frameProxy.location(),
|
||||
suspendContext,
|
||||
inlineFunctions,
|
||||
inlineArgument
|
||||
);
|
||||
|
||||
createStepRequest(
|
||||
suspendContext, getContextThread(),
|
||||
debugProcess.getVirtualMachineProxy().eventRequestManager(),
|
||||
StepRequest.STEP_LINE, StepRequest.STEP_OUT);
|
||||
|
||||
action.apply(debugProcess, suspendContext, ignoreBreakpoints);
|
||||
return;
|
||||
}
|
||||
|
||||
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction();
|
||||
}
|
||||
catch (EvaluateException ignored) {
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// copied from DebugProcessImpl.doStep
|
||||
private static void createStepRequest(
|
||||
@NotNull SuspendContextImpl suspendContext,
|
||||
@Nullable ThreadReferenceProxyImpl stepThread,
|
||||
@NotNull EventRequestManager requestManager,
|
||||
int size, int depth
|
||||
) {
|
||||
if (stepThread == null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
ThreadReference stepThreadReference = stepThread.getThreadReference();
|
||||
|
||||
requestManager.deleteEventRequests(requestManager.stepRequests());
|
||||
|
||||
StepRequest stepRequest = requestManager.createStepRequest(stepThreadReference, size, depth);
|
||||
|
||||
List<ClassFilter> activeFilters = getActiveFilters();
|
||||
|
||||
if (!activeFilters.isEmpty()) {
|
||||
String currentClassName = getCurrentClassName(stepThread);
|
||||
if (currentClassName == null || !DebuggerUtilsEx.isFiltered(currentClassName, activeFilters)) {
|
||||
// add class filters
|
||||
for (ClassFilter filter : activeFilters) {
|
||||
stepRequest.addClassExclusionFilter(filter.getPattern());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// suspend policy to match the suspend policy of the context:
|
||||
// if all threads were suspended, then during stepping all the threads must be suspended
|
||||
// if only event thread were suspended, then only this particular thread must be suspended during stepping
|
||||
stepRequest.setSuspendPolicy(suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD
|
||||
? EventRequest.SUSPEND_EVENT_THREAD
|
||||
: EventRequest.SUSPEND_ALL);
|
||||
|
||||
stepRequest.enable();
|
||||
}
|
||||
catch (ObjectCollectedException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// copied from DebugProcessImpl.getActiveFilters
|
||||
@NotNull
|
||||
private static List<ClassFilter> getActiveFilters() {
|
||||
List<ClassFilter> activeFilters = new ArrayList<ClassFilter>();
|
||||
DebuggerSettings settings = DebuggerSettings.getInstance();
|
||||
if (settings.TRACING_FILTERS_ENABLED) {
|
||||
for (ClassFilter filter : settings.getSteppingFilters()) {
|
||||
if (filter.isEnabled()) {
|
||||
activeFilters.add(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
for (DebuggerClassFilterProvider provider : Extensions.getExtensions(DebuggerClassFilterProvider.EP_NAME)) {
|
||||
for (ClassFilter filter : provider.getFilters()) {
|
||||
if (filter.isEnabled()) {
|
||||
activeFilters.add(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
return activeFilters;
|
||||
}
|
||||
|
||||
// copied from DebugProcessImpl.getActiveFilters
|
||||
@Nullable
|
||||
private static String getCurrentClassName(ThreadReferenceProxyImpl thread) {
|
||||
try {
|
||||
if (thread != null && thread.frameCount() > 0) {
|
||||
StackFrameProxyImpl stackFrame = thread.frame(0);
|
||||
if (stackFrame != null) {
|
||||
Location location = stackFrame.location();
|
||||
ReferenceType referenceType = location == null ? null : location.declaringType();
|
||||
if (referenceType != null) {
|
||||
return referenceType.name();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (EvaluateException ignored) {
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+95
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.NamedMethodFilter
|
||||
import com.intellij.psi.SmartPsiElementPointer
|
||||
import com.intellij.util.Range
|
||||
import com.sun.jdi.Location
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.DECLARATION
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
|
||||
class KotlinBasicStepMethodFilter(
|
||||
private val declarationPtr: SmartPsiElementPointer<KtDeclaration>?,
|
||||
private val isInvoke: Boolean,
|
||||
private val targetMethodName: String,
|
||||
private val myCallingExpressionLines: Range<Int>
|
||||
) : NamedMethodFilter {
|
||||
init {
|
||||
assert(declarationPtr != null || isInvoke)
|
||||
}
|
||||
|
||||
override fun getCallingExpressionLines() = myCallingExpressionLines
|
||||
|
||||
override fun getMethodName() = targetMethodName
|
||||
|
||||
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
|
||||
val method = location.method()
|
||||
if (targetMethodName != method.name()) return false
|
||||
|
||||
val positionManager = process.positionManager
|
||||
|
||||
val (currentDescriptor, currentDeclaration) = runReadAction {
|
||||
val elementAt = positionManager.getSourcePosition(location)?.elementAt
|
||||
|
||||
val declaration = elementAt?.getParentOfTypesAndPredicate(false, KtDeclaration::class.java) {
|
||||
it !is KtProperty || !it.isLocal
|
||||
}
|
||||
|
||||
if (declaration is KtClass && method.name() == "<init>") {
|
||||
declaration.resolveToDescriptorIfAny()?.unsubstitutedPrimaryConstructor to declaration
|
||||
} else {
|
||||
declaration?.resolveToDescriptorIfAny() to declaration
|
||||
}
|
||||
}
|
||||
|
||||
if (currentDescriptor == null || currentDeclaration == null) {
|
||||
return false
|
||||
}
|
||||
|
||||
@Suppress("FoldInitializerAndIfToElvis")
|
||||
if (currentDescriptor !is CallableMemberDescriptor) return false
|
||||
if (currentDescriptor.kind != DECLARATION) return false
|
||||
|
||||
if (isInvoke) {
|
||||
// There can be only one 'invoke' target at the moment so consider position as expected.
|
||||
// Descriptors can be not-equal, say when parameter has type `(T) -> T` and lambda is `Int.() -> Int`.
|
||||
return true
|
||||
}
|
||||
|
||||
val declaration = declarationPtr?.element
|
||||
?: return true // Element is lost. But we know that name is matches, so stop.
|
||||
|
||||
if (currentDeclaration.isEquivalentTo(declaration)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return DescriptorUtils.getAllOverriddenDescriptors(currentDescriptor).any { baseOfCurrent ->
|
||||
val currentBaseDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(currentDeclaration.project, baseOfCurrent)
|
||||
declaration.isEquivalentTo(currentBaseDeclaration)
|
||||
}
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.BreakpointStepMethodFilter
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.util.Range
|
||||
import com.sun.jdi.Location
|
||||
import org.jetbrains.kotlin.codegen.coroutines.isResumeImplMethodNameFromAnyLanguageSettings
|
||||
import org.jetbrains.kotlin.idea.core.util.isMultiLine
|
||||
import org.jetbrains.kotlin.idea.debugger.isInsideInlineArgument
|
||||
import org.jetbrains.kotlin.psi.KtBlockExpression
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
class KotlinLambdaMethodFilter(
|
||||
private val lambda: KtFunction,
|
||||
private val myCallingExpressionLines: Range<Int>,
|
||||
private val isInline: Boolean,
|
||||
private val isSuspend: Boolean
|
||||
) : BreakpointStepMethodFilter {
|
||||
private val myFirstStatementPosition: SourcePosition?
|
||||
private val myLastStatementLine: Int
|
||||
|
||||
init {
|
||||
val body = lambda.bodyExpression
|
||||
if (body != null && lambda.isMultiLine()) {
|
||||
var firstStatementPosition: SourcePosition? = null
|
||||
var lastStatementPosition: SourcePosition? = null
|
||||
val statements = (body as? KtBlockExpression)?.statements ?: listOf(body)
|
||||
if (statements.isNotEmpty()) {
|
||||
firstStatementPosition = SourcePosition.createFromElement(statements.first())
|
||||
if (firstStatementPosition != null) {
|
||||
val lastStatement = statements.last()
|
||||
lastStatementPosition = SourcePosition.createFromOffset(firstStatementPosition.file, lastStatement.textRange.endOffset)
|
||||
}
|
||||
}
|
||||
myFirstStatementPosition = firstStatementPosition
|
||||
myLastStatementLine = if (lastStatementPosition != null) lastStatementPosition.line else -1
|
||||
} else {
|
||||
myFirstStatementPosition = SourcePosition.createFromElement(lambda)
|
||||
myLastStatementLine = myFirstStatementPosition!!.line
|
||||
}
|
||||
}
|
||||
|
||||
override fun getBreakpointPosition() = myFirstStatementPosition
|
||||
override fun getLastStatementLine() = myLastStatementLine
|
||||
|
||||
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
|
||||
val method = location.method()
|
||||
|
||||
if (isInline) {
|
||||
return isInsideInlineArgument(lambda, location, process)
|
||||
}
|
||||
|
||||
return isLambdaName(method.name())
|
||||
}
|
||||
|
||||
override fun getCallingExpressionLines() = if (isInline) Range(0, Int.MAX_VALUE) else myCallingExpressionLines
|
||||
|
||||
private fun isLambdaName(name: String?): Boolean {
|
||||
if (isSuspend && name != null) {
|
||||
return isResumeImplMethodNameFromAnyLanguageSettings(name)
|
||||
}
|
||||
|
||||
return name == OperatorNameConventions.INVOKE.asString()
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.actions.SmartStepTarget
|
||||
import com.intellij.util.Range
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinIcons
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
import javax.swing.Icon
|
||||
|
||||
class KotlinLambdaSmartStepTarget(
|
||||
label: String,
|
||||
highlightElement: KtFunction,
|
||||
lines: Range<Int>,
|
||||
val isInline: Boolean,
|
||||
val isSuspend: Boolean
|
||||
) : SmartStepTarget(label, highlightElement, true, lines) {
|
||||
override fun getIcon(): Icon = KotlinIcons.LAMBDA
|
||||
|
||||
fun getLambda() = highlightElement as KtFunction
|
||||
|
||||
companion object {
|
||||
fun calcLabel(descriptor: DeclarationDescriptor, paramName: Name): String {
|
||||
return "${descriptor.name.asString()}: ${paramName.asString()}.${OperatorNameConventions.INVOKE.asString()}()"
|
||||
}
|
||||
}
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
package org.jetbrains.kotlin.idea.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.actions.SmartStepTarget
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.util.Range
|
||||
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.KotlinIcons
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
|
||||
import org.jetbrains.kotlin.renderer.PropertyAccessorRenderingPolicy
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
|
||||
import javax.swing.Icon
|
||||
|
||||
class KotlinMethodSmartStepTarget(
|
||||
descriptor: CallableMemberDescriptor,
|
||||
val declaration: KtDeclaration?,
|
||||
label: String,
|
||||
highlightElement: PsiElement,
|
||||
lines: Range<Int>
|
||||
) : SmartStepTarget(label, highlightElement, false, lines) {
|
||||
val isInvoke = descriptor is FunctionInvokeDescriptor
|
||||
|
||||
init {
|
||||
assert(declaration != null || isInvoke)
|
||||
}
|
||||
|
||||
private val isExtension = descriptor.isExtension
|
||||
|
||||
val targetMethodName: String = when (descriptor) {
|
||||
is ClassDescriptor, is ConstructorDescriptor -> "<init>"
|
||||
is PropertyAccessorDescriptor -> JvmAbi.getterName(descriptor.correspondingProperty.name.asString())
|
||||
else -> descriptor.name.asString()
|
||||
}
|
||||
|
||||
override fun getIcon(): Icon? {
|
||||
return when {
|
||||
isExtension -> KotlinIcons.EXTENSION_FUNCTION
|
||||
else -> KotlinIcons.FUNCTION
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val renderer = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_NO_ANNOTATIONS.withOptions {
|
||||
parameterNameRenderingPolicy = ParameterNameRenderingPolicy.NONE
|
||||
withoutReturnType = true
|
||||
propertyAccessorRenderingPolicy = PropertyAccessorRenderingPolicy.PRETTY
|
||||
startFromName = true
|
||||
modifiers = emptySet()
|
||||
}
|
||||
|
||||
fun calcLabel(descriptor: DeclarationDescriptor): String {
|
||||
return renderer.render(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
|
||||
if (other == null || other !is KotlinMethodSmartStepTarget) return false
|
||||
|
||||
if (isInvoke && other.isInvoke) {
|
||||
// Don't allow to choose several invoke targets in smart step into as we can't distinguish them reliably during debug
|
||||
return true
|
||||
}
|
||||
|
||||
return declaration === other.declaration
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
if (isInvoke) {
|
||||
// Predefined value to make all FunctionInvokeDescriptor targets equal
|
||||
return 42
|
||||
}
|
||||
return declaration!!.hashCode()
|
||||
}
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.SimplePropertyGetterProvider
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
class KotlinSimpleGetterProvider : SimplePropertyGetterProvider {
|
||||
override fun isInsideSimpleGetter(element: PsiElement): Boolean {
|
||||
// class A(val a: Int)
|
||||
if (element is KtParameter) {
|
||||
return true
|
||||
}
|
||||
|
||||
val accessor = PsiTreeUtil.getParentOfType(element, KtPropertyAccessor::class.java)
|
||||
if (accessor != null && accessor.isGetter) {
|
||||
val body = accessor.bodyExpression
|
||||
return when (body) {
|
||||
is KtBlockExpression -> {
|
||||
// val a: Int get() { return field }
|
||||
val returnedExpression = (body.statements.singleOrNull() as? KtReturnExpression)?.returnedExpression ?: return false
|
||||
returnedExpression.textMatches("field")
|
||||
}
|
||||
is KtExpression -> body.textMatches("field") // val a: Int get() = field
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
val property = PsiTreeUtil.getParentOfType(element, KtProperty::class.java)
|
||||
// val a = foo()
|
||||
if (property != null) {
|
||||
return property.getter == null && !property.isLocal
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
+251
@@ -0,0 +1,251 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.actions.JvmSmartStepIntoHandler
|
||||
import com.intellij.debugger.actions.MethodSmartStepTarget
|
||||
import com.intellij.debugger.actions.SmartStepTarget
|
||||
import com.intellij.debugger.engine.MethodFilter
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiMethod
|
||||
import com.intellij.util.Range
|
||||
import com.intellij.util.containers.OrderedSet
|
||||
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
|
||||
import org.jetbrains.kotlin.builtins.isBuiltinFunctionalType
|
||||
import org.jetbrains.kotlin.builtins.isSuspendFunctionType
|
||||
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
|
||||
import org.jetbrains.kotlin.config.JvmTarget
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.isFromJava
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentCall
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
|
||||
class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
|
||||
|
||||
override fun isAvailable(position: SourcePosition?) = position?.file is KtFile
|
||||
|
||||
override fun findSmartStepTargets(position: SourcePosition): List<SmartStepTarget> {
|
||||
val file = position.file
|
||||
|
||||
val elementAtOffset = position.elementAt ?: return emptyList()
|
||||
|
||||
val element = CodeInsightUtils.getTopmostElementAtOffset(elementAtOffset, elementAtOffset.textRange.startOffset) as? KtElement
|
||||
?: return emptyList()
|
||||
|
||||
val elementTextRange = element.textRange ?: return emptyList()
|
||||
|
||||
val doc = PsiDocumentManager.getInstance(file.project).getDocument(file) ?: return emptyList()
|
||||
|
||||
val lines = Range(doc.getLineNumber(elementTextRange.startOffset), doc.getLineNumber(elementTextRange.endOffset))
|
||||
@Suppress("DEPRECATION")
|
||||
val bindingContext = element.analyzeWithAllCompilerChecks().bindingContext
|
||||
val result = OrderedSet<SmartStepTarget>()
|
||||
|
||||
// TODO support class initializers, local functions, delegated properties with specified type, setter for properties
|
||||
element.accept(object : KtTreeVisitorVoid() {
|
||||
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
|
||||
recordFunctionLiteral(lambdaExpression.functionLiteral)
|
||||
}
|
||||
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
if (!recordFunctionLiteral(function)) {
|
||||
super.visitNamedFunction(function)
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordFunctionLiteral(function: KtFunction): Boolean {
|
||||
val context = function.analyze()
|
||||
val resolvedCall = function.getParentCall(context).getResolvedCall(context)
|
||||
if (resolvedCall != null) {
|
||||
val arguments = resolvedCall.valueArguments
|
||||
for ((param, argument) in arguments) {
|
||||
if (argument.arguments.any { getArgumentExpression(it) == function }) {
|
||||
val resultingDescriptor = resolvedCall.resultingDescriptor
|
||||
val label = KotlinLambdaSmartStepTarget.calcLabel(resultingDescriptor, param.name)
|
||||
result.add(
|
||||
KotlinLambdaSmartStepTarget(
|
||||
label, function, lines, InlineUtil.isInline(resultingDescriptor), param.type.isSuspendFunctionType
|
||||
)
|
||||
)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getArgumentExpression(it: ValueArgument) =
|
||||
(it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||
|
||||
override fun visitObjectLiteralExpression(expression: KtObjectLiteralExpression) {
|
||||
// skip calls in object declarations
|
||||
}
|
||||
|
||||
override fun visitIfExpression(expression: KtIfExpression) {
|
||||
expression.condition?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitWhileExpression(expression: KtWhileExpression) {
|
||||
expression.condition?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitDoWhileExpression(expression: KtDoWhileExpression) {
|
||||
expression.condition?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitForExpression(expression: KtForExpression) {
|
||||
expression.loopRange?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitWhenExpression(expression: KtWhenExpression) {
|
||||
expression.subjectExpression?.accept(this)
|
||||
}
|
||||
|
||||
override fun visitArrayAccessExpression(expression: KtArrayAccessExpression) {
|
||||
recordFunction(expression)
|
||||
super.visitArrayAccessExpression(expression)
|
||||
}
|
||||
|
||||
override fun visitUnaryExpression(expression: KtUnaryExpression) {
|
||||
recordFunction(expression.operationReference)
|
||||
super.visitUnaryExpression(expression)
|
||||
}
|
||||
|
||||
override fun visitBinaryExpression(expression: KtBinaryExpression) {
|
||||
recordFunction(expression.operationReference)
|
||||
super.visitBinaryExpression(expression)
|
||||
}
|
||||
|
||||
override fun visitCallExpression(expression: KtCallExpression) {
|
||||
val calleeExpression = expression.calleeExpression
|
||||
if (calleeExpression != null) {
|
||||
recordFunction(calleeExpression)
|
||||
}
|
||||
super.visitCallExpression(expression)
|
||||
}
|
||||
|
||||
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
|
||||
recordGetter(expression)
|
||||
super.visitSimpleNameExpression(expression)
|
||||
}
|
||||
|
||||
private fun recordGetter(expression: KtSimpleNameExpression) {
|
||||
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return
|
||||
val propertyDescriptor = resolvedCall.resultingDescriptor as? PropertyDescriptor ?: return
|
||||
|
||||
val getterDescriptor = propertyDescriptor.getter
|
||||
if (getterDescriptor == null || getterDescriptor.isDefault) return
|
||||
|
||||
val ktDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, getterDescriptor) as? KtDeclaration ?: return
|
||||
|
||||
val delegatedResolvedCall = bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getterDescriptor]
|
||||
if (delegatedResolvedCall != null) {
|
||||
val delegatedPropertyGetterDescriptor = delegatedResolvedCall.resultingDescriptor
|
||||
val label = "${propertyDescriptor.name}." + KotlinMethodSmartStepTarget.calcLabel(delegatedPropertyGetterDescriptor)
|
||||
result.add(KotlinMethodSmartStepTarget(delegatedPropertyGetterDescriptor, ktDeclaration, label, expression, lines))
|
||||
} else {
|
||||
if (ktDeclaration is KtPropertyAccessor && ktDeclaration.hasBody()) {
|
||||
val label = KotlinMethodSmartStepTarget.calcLabel(getterDescriptor)
|
||||
result.add(KotlinMethodSmartStepTarget(getterDescriptor, ktDeclaration, label, expression, lines))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun recordFunction(expression: KtExpression) {
|
||||
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return
|
||||
|
||||
val descriptor = resolvedCall.resultingDescriptor
|
||||
if (descriptor !is FunctionDescriptor || isIntrinsic(descriptor)) return
|
||||
|
||||
val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor)
|
||||
if (descriptor.isFromJava) {
|
||||
(declaration as? PsiMethod)?.let {
|
||||
result.add(MethodSmartStepTarget(it, null, declaration, false, lines))
|
||||
}
|
||||
} else {
|
||||
if (declaration == null && !isInvokeInBuiltinFunction(descriptor)) {
|
||||
return
|
||||
}
|
||||
|
||||
if (declaration !is KtDeclaration?) return
|
||||
|
||||
if (descriptor is ConstructorDescriptor && descriptor.isPrimary) {
|
||||
if (declaration is KtClass && declaration.getAnonymousInitializers().isEmpty()) {
|
||||
// There is no constructor or init block, so do not show it in smart step into
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val callLabel = KotlinMethodSmartStepTarget.calcLabel(descriptor)
|
||||
val label = when (descriptor) {
|
||||
is FunctionInvokeDescriptor -> {
|
||||
when (expression) {
|
||||
is KtSimpleNameExpression -> "${runReadAction { expression.text }}.$callLabel"
|
||||
else -> callLabel
|
||||
}
|
||||
}
|
||||
else -> callLabel
|
||||
}
|
||||
|
||||
result.add(KotlinMethodSmartStepTarget(descriptor, declaration, label, expression, lines))
|
||||
}
|
||||
}
|
||||
}, null)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? {
|
||||
return when (stepTarget) {
|
||||
is KotlinMethodSmartStepTarget ->
|
||||
KotlinBasicStepMethodFilter(
|
||||
stepTarget.declaration?.createSmartPointer(),
|
||||
stepTarget.isInvoke,
|
||||
stepTarget.targetMethodName,
|
||||
stepTarget.callingExpressionLines!!
|
||||
)
|
||||
is KotlinLambdaSmartStepTarget ->
|
||||
KotlinLambdaMethodFilter(
|
||||
stepTarget.getLambda(), stepTarget.callingExpressionLines!!, stepTarget.isInline, stepTarget.isSuspend
|
||||
)
|
||||
else -> super.createMethodFilter(stepTarget)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val methods = IntrinsicMethods(JvmTarget.JVM_1_6)
|
||||
|
||||
private fun isIntrinsic(descriptor: CallableMemberDescriptor): Boolean {
|
||||
return methods.getIntrinsic(descriptor) != null
|
||||
}
|
||||
|
||||
private fun isInvokeInBuiltinFunction(descriptor: DeclarationDescriptor): Boolean {
|
||||
if (descriptor !is FunctionInvokeDescriptor) return false
|
||||
val classDescriptor = descriptor.containingDeclaration as? ClassDescriptor ?: return false
|
||||
return classDescriptor.defaultType.isBuiltinFunctionalType
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* 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.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.*
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerSession
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.util.EventDispatcher
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import com.sun.jdi.request.StepRequest
|
||||
import java.lang.reflect.Field
|
||||
|
||||
// Mass-copy-paste code for commands behaviour from com.intellij.debugger.engine.DebugProcessImpl
|
||||
@SuppressWarnings("UnnecessaryFinalOnLocalVariableOrParameter")
|
||||
class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
|
||||
abstract class KotlinStepAction {
|
||||
abstract fun contextAction(suspendContext: SuspendContextImpl)
|
||||
}
|
||||
|
||||
fun createKotlinStepOverInlineAction(smartStepFilter: KotlinMethodFilter): KotlinStepAction {
|
||||
return StepOverInlineCommand(smartStepFilter, StepRequest.STEP_LINE)
|
||||
}
|
||||
|
||||
private val debuggerContext: DebuggerContextImpl get() = debuggerProcess.debuggerContext
|
||||
private val suspendManager: SuspendManager get() = debuggerProcess.suspendManager
|
||||
private val project: Project get() = debuggerProcess.project
|
||||
private val session: DebuggerSession get() = debuggerProcess.session
|
||||
|
||||
// TODO: ask for better API
|
||||
// Should be safe to use reflection as field is protected and not obfuscated
|
||||
private val debugProcessDispatcher: EventDispatcher<DebugProcessListener> = getFromField("myDebugProcessDispatcher")
|
||||
|
||||
// TODO: ask for better API
|
||||
// Get field by type as it private and obfuscated in Ultimate
|
||||
private val threadBlockedMonitor: ThreadBlockedMonitor = getFromField(ThreadBlockedMonitor::class.java)
|
||||
|
||||
private fun showStatusText(message: String) {
|
||||
debuggerProcess.showStatusText(message)
|
||||
}
|
||||
|
||||
// TODO: ask for better API
|
||||
// Should be safe to use reflection as method is protected and not obfuscated
|
||||
private fun doStep(
|
||||
suspendContext: SuspendContextImpl,
|
||||
stepThread: ThreadReferenceProxyImpl,
|
||||
size: Int, depth: Int, hint: RequestHint
|
||||
) {
|
||||
val doStepMethod = DebugProcessImpl::class.java.getDeclaredMethod(
|
||||
"doStep",
|
||||
SuspendContextImpl::class.java, ThreadReferenceProxyImpl::class.java,
|
||||
Integer.TYPE, Integer.TYPE, RequestHint::class.java
|
||||
)
|
||||
|
||||
doStepMethod.isAccessible = true
|
||||
|
||||
doStepMethod.invoke(debuggerProcess, suspendContext, stepThread, size, depth, hint)
|
||||
}
|
||||
|
||||
private fun <T> getFromField(fieldType: Class<T>): T {
|
||||
return getFromField(DebugProcessImpl::class.java.declaredFields.single { it.type == fieldType })
|
||||
}
|
||||
|
||||
private fun <T> getFromField(fieldName: String): T {
|
||||
return getFromField(DebugProcessImpl::class.java.getDeclaredField(fieldName))
|
||||
}
|
||||
|
||||
private fun <T> getFromField(field: Field?): T {
|
||||
field!!.isAccessible = true
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return field.get(debuggerProcess) as T
|
||||
}
|
||||
|
||||
private inner class StepOverInlineCommand(private val mySmartStepFilter: KotlinMethodFilter, private val myStepSize: Int) :
|
||||
KotlinStepAction() {
|
||||
private fun getContextThread(suspendContext: SuspendContextImpl): ThreadReferenceProxyImpl? {
|
||||
val contextThread = debuggerContext.threadProxy
|
||||
return contextThread ?: suspendContext.thread
|
||||
}
|
||||
|
||||
// See: ResumeCommand.applyThreadFilter()
|
||||
private fun applyThreadFilter(suspendContext: SuspendContextImpl, thread: ThreadReferenceProxyImpl) {
|
||||
if (suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
|
||||
// there could be explicit resume as a result of call to voteSuspend()
|
||||
// e.g. when breakpoint was considered invalid, in that case the filter will be applied _after_
|
||||
// resuming and all breakpoints in other threads will be ignored.
|
||||
// As resume() implicitly cleares the filter, the filter must be always applied _before_ any resume() action happens
|
||||
val breakpointManager = DebuggerManagerEx.getInstanceEx(project).breakpointManager
|
||||
breakpointManager.applyThreadFilter(debuggerProcess, thread.threadReference)
|
||||
}
|
||||
}
|
||||
|
||||
// See: StepCommand.resumeAction()
|
||||
private fun resumeAction(suspendContext: SuspendContextImpl, thread: ThreadReferenceProxyImpl) {
|
||||
if (suspendContext.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD || isResumeOnlyCurrentThread) {
|
||||
threadBlockedMonitor.startWatching(thread)
|
||||
}
|
||||
if (isResumeOnlyCurrentThread && suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
|
||||
suspendManager.resumeThread(suspendContext, thread)
|
||||
} else {
|
||||
suspendManager.resume(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
// See: StepIntoCommand.contextAction()
|
||||
override fun contextAction(suspendContext: SuspendContextImpl) {
|
||||
showStatusText("Stepping over inline")
|
||||
val stepThread = getContextThread(suspendContext)
|
||||
|
||||
if (stepThread == null) {
|
||||
// TODO: Intellij code doesn't bother to check thread for null, so probably it's not-null actually
|
||||
debuggerProcess.createStepOverCommand(suspendContext, true).contextAction(suspendContext)
|
||||
return
|
||||
}
|
||||
|
||||
val hint = KotlinStepOverInlinedLinesHint(stepThread, suspendContext, mySmartStepFilter)
|
||||
hint.isResetIgnoreFilters = !session.shouldIgnoreSteppingFilters()
|
||||
|
||||
try {
|
||||
session.setIgnoreStepFiltersFlag(stepThread.frameCount())
|
||||
} catch (e: EvaluateException) {
|
||||
LOG.info(e)
|
||||
}
|
||||
|
||||
applyThreadFilter(suspendContext, stepThread)
|
||||
|
||||
doStep(suspendContext, stepThread, myStepSize, StepRequest.STEP_OVER, hint)
|
||||
|
||||
showStatusText("Process resumed")
|
||||
resumeAction(suspendContext, stepThread)
|
||||
debugProcessDispatcher.multicaster.resumed(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(KotlinStepActionFactory::class.java)
|
||||
|
||||
private val isResumeOnlyCurrentThread: Boolean
|
||||
get() = DebuggerSettings.getInstance().RESUME_ONLY_CURRENT_THREAD
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.util.Range
|
||||
import com.sun.jdi.LocalVariable
|
||||
import com.sun.jdi.Location
|
||||
import org.jetbrains.kotlin.idea.debugger.ktLocationInfo
|
||||
|
||||
class StepOverFilterData(
|
||||
val lineNumber: Int,
|
||||
val stepOverLines: Set<Int>,
|
||||
val inlineRangeVariables: List<LocalVariable>,
|
||||
val isDexDebug: Boolean,
|
||||
val skipAfterCodeIndex: Long = -1
|
||||
)
|
||||
|
||||
class KotlinStepOverInlineFilter(val project: Project, val data: StepOverFilterData) : KotlinMethodFilter {
|
||||
private fun Location.ktLineNumber() = ktLocationInfo(this, data.isDexDebug, project).first
|
||||
|
||||
override fun locationMatches(context: SuspendContextImpl, location: Location): Boolean {
|
||||
val frameProxy = context.frameProxy ?: return true
|
||||
|
||||
if (data.skipAfterCodeIndex != -1L && location.codeIndex() > data.skipAfterCodeIndex) {
|
||||
return false
|
||||
}
|
||||
|
||||
val currentLine = location.ktLineNumber()
|
||||
if (!(data.stepOverLines.contains(currentLine))) {
|
||||
return currentLine != data.lineNumber
|
||||
}
|
||||
|
||||
val visibleInlineVariables = getInlineRangeLocalVariables(frameProxy)
|
||||
|
||||
// Our ranges check missed exit from inline function. This is when breakpoint was in last statement of inline functions.
|
||||
// This can be observed by inline local range-variables. Absence of any means step out was done.
|
||||
return data.inlineRangeVariables.any { !visibleInlineVariables.contains(it) }
|
||||
}
|
||||
|
||||
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
|
||||
throw IllegalStateException() // Should not be called from Kotlin hint
|
||||
}
|
||||
|
||||
override fun getCallingExpressionLines(): Range<Int>? = null
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* 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.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.RequestHint
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.sun.jdi.VMDisconnectedException
|
||||
import com.sun.jdi.request.StepRequest
|
||||
|
||||
// Originally copied from RequestHint
|
||||
class KotlinStepOverInlinedLinesHint(
|
||||
stepThread: ThreadReferenceProxyImpl,
|
||||
suspendContext: SuspendContextImpl,
|
||||
methodFilter: KotlinMethodFilter
|
||||
) : RequestHint(stepThread, suspendContext, methodFilter) {
|
||||
|
||||
private val LOG = Logger.getInstance(KotlinStepOverInlinedLinesHint::class.java)
|
||||
|
||||
private val filter = methodFilter
|
||||
|
||||
override fun getDepth(): Int = StepRequest.STEP_OVER
|
||||
|
||||
override fun getNextStepDepth(context: SuspendContextImpl): Int {
|
||||
try {
|
||||
val frameProxy = context.frameProxy
|
||||
if (frameProxy != null) {
|
||||
if (isTheSameFrame(context)) {
|
||||
return if (filter.locationMatches(context, frameProxy.location())) {
|
||||
STOP
|
||||
} else {
|
||||
StepRequest.STEP_OVER
|
||||
}
|
||||
}
|
||||
|
||||
if (isSteppedOut) {
|
||||
return STOP
|
||||
}
|
||||
|
||||
return StepRequest.STEP_OUT
|
||||
}
|
||||
} catch (ignored: VMDisconnectedException) {
|
||||
} catch (e: EvaluateException) {
|
||||
LOG.error(e)
|
||||
}
|
||||
|
||||
return STOP
|
||||
}
|
||||
}
|
||||
+599
@@ -0,0 +1,599 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.NoDataException
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.MethodFilter
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.impl.JvmSteppingCommandProvider
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.xdebugger.impl.XSourcePositionImpl
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.LocalVariable
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.Method
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.builtins.isFunctionType
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.*
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.GOTO
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.MOVE
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN_OBJECT
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN_VOID
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN_WIDE
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineNumber
|
||||
import org.jetbrains.kotlin.idea.core.util.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.utils.keysToMap
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
|
||||
override fun getStepOverCommand(
|
||||
suspendContext: SuspendContextImpl?,
|
||||
ignoreBreakpoints: Boolean,
|
||||
stepSize: Int
|
||||
): DebugProcessImpl.ResumeCommand? {
|
||||
if (suspendContext == null || suspendContext.isResumed) return null
|
||||
|
||||
val sourcePosition = suspendContext.debugProcess.debuggerContext.sourcePosition ?: return null
|
||||
return getStepOverCommand(suspendContext, ignoreBreakpoints, sourcePosition)
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun getStepOverCommand(
|
||||
suspendContext: SuspendContextImpl,
|
||||
ignoreBreakpoints: Boolean,
|
||||
debuggerContext: DebuggerContextImpl
|
||||
): DebugProcessImpl.ResumeCommand? {
|
||||
return getStepOverCommand(suspendContext, ignoreBreakpoints, debuggerContext.sourcePosition)
|
||||
}
|
||||
|
||||
private fun getStepOverCommand(
|
||||
suspendContext: SuspendContextImpl,
|
||||
ignoreBreakpoints: Boolean,
|
||||
sourcePosition: SourcePosition
|
||||
): DebugProcessImpl.ResumeCommand? {
|
||||
val kotlinSourcePosition = KotlinSourcePosition.create(sourcePosition) ?: return null
|
||||
|
||||
if (isSpecialStepOverNeeded(kotlinSourcePosition)) {
|
||||
return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, kotlinSourcePosition)
|
||||
}
|
||||
|
||||
val file = sourcePosition.elementAt.containingFile
|
||||
val location = suspendContext.debugProcess.invokeInManagerThread { suspendContext.frameProxy?.safeLocation() } ?: return null
|
||||
if (isInSuspendMethod(location) && !isOnSuspendReturnOrReenter(location) && !isLastLineLocationInMethod(location)) {
|
||||
return DebugProcessImplHelper.createStepOverCommandWithCustomFilter(
|
||||
suspendContext, ignoreBreakpoints, KotlinSuspendCallStepOverFilter(sourcePosition.line, file, ignoreBreakpoints)
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
data class KotlinSourcePosition(
|
||||
val file: KtFile, val function: KtNamedFunction,
|
||||
val linesRange: IntRange, val sourcePosition: SourcePosition
|
||||
) {
|
||||
companion object {
|
||||
fun create(sourcePosition: SourcePosition): KotlinSourcePosition? {
|
||||
val file = sourcePosition.file as? KtFile ?: return null
|
||||
if (sourcePosition.line < 0) return null
|
||||
|
||||
val elementAt = sourcePosition.elementAt ?: return null
|
||||
val containingFunction = elementAt.parents
|
||||
.filterIsInstance<KtNamedFunction>()
|
||||
.firstOrNull { !it.isLocal } ?: return null
|
||||
|
||||
val startLineNumber = containingFunction.getLineNumber(true) + 1
|
||||
val endLineNumber = containingFunction.getLineNumber(false) + 1
|
||||
if (startLineNumber > endLineNumber) return null
|
||||
|
||||
val linesRange = startLineNumber..endLineNumber
|
||||
|
||||
return KotlinSourcePosition(file, containingFunction, linesRange, sourcePosition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSpecialStepOverNeeded(kotlinSourcePosition: KotlinSourcePosition): Boolean {
|
||||
val sourcePosition = kotlinSourcePosition.sourcePosition
|
||||
|
||||
val hasInlineCallsOnLine = getInlineFunctionCallsIfAny(sourcePosition).isNotEmpty()
|
||||
if (hasInlineCallsOnLine) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Step over calls to lambda arguments in inline function while execution is already in that function
|
||||
val containingFunctionDescriptor = kotlinSourcePosition.function.unsafeResolveToDescriptor()
|
||||
if (InlineUtil.isInline(containingFunctionDescriptor)) {
|
||||
val inlineArgumentsCallsIfAny = getInlineArgumentsCallsIfAny(sourcePosition, containingFunctionDescriptor)
|
||||
if (inlineArgumentsCallsIfAny != null && inlineArgumentsCallsIfAny.isNotEmpty()) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun getStepOutCommand(suspendContext: SuspendContextImpl, debugContext: DebuggerContextImpl): DebugProcessImpl.ResumeCommand? {
|
||||
return getStepOutCommand(suspendContext, debugContext.sourcePosition)
|
||||
}
|
||||
|
||||
override fun getStepOutCommand(suspendContext: SuspendContextImpl?, stepSize: Int): DebugProcessImpl.ResumeCommand? {
|
||||
if (suspendContext == null || suspendContext.isResumed) return null
|
||||
|
||||
val sourcePosition = suspendContext.debugProcess.debuggerContext.sourcePosition ?: return null
|
||||
return getStepOutCommand(suspendContext, sourcePosition)
|
||||
}
|
||||
|
||||
private fun getStepOutCommand(suspendContext: SuspendContextImpl, sourcePosition: SourcePosition): DebugProcessImpl.ResumeCommand? {
|
||||
val file = sourcePosition.file as? KtFile ?: return null
|
||||
if (sourcePosition.line < 0) return null
|
||||
|
||||
val lineStartOffset = file.getLineStartOffset(sourcePosition.line) ?: return null
|
||||
|
||||
val inlineFunctions = getInlineFunctionsIfAny(file, lineStartOffset)
|
||||
val inlinedArgument = getInlineArgumentIfAny(sourcePosition.elementAt)
|
||||
|
||||
if (inlineFunctions.isEmpty() && inlinedArgument == null) return null
|
||||
|
||||
return DebuggerSteppingHelper.createStepOutCommand(suspendContext, true, inlineFunctions, inlinedArgument)
|
||||
}
|
||||
}
|
||||
|
||||
private fun PsiElement?.contains(element: PsiElement): Boolean {
|
||||
return this?.textRange?.contains(element.textRange) ?: false
|
||||
}
|
||||
|
||||
private fun getInlineCallFunctionArgumentsIfAny(sourcePosition: SourcePosition): List<KtFunction> {
|
||||
val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition)
|
||||
return getInlineArgumentsIfAny(inlineFunctionCalls)
|
||||
}
|
||||
|
||||
private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List<KtNamedFunction> {
|
||||
val elementAt = file.findElementAt(offset) ?: return emptyList()
|
||||
val containingFunction = elementAt.getParentOfType<KtNamedFunction>(false) ?: return emptyList()
|
||||
|
||||
val descriptor = containingFunction.unsafeResolveToDescriptor()
|
||||
if (!InlineUtil.isInline(descriptor)) return emptyList()
|
||||
|
||||
return DebuggerUtils.analyzeElementWithInline(containingFunction, false).filterIsInstance<KtNamedFunction>()
|
||||
}
|
||||
|
||||
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> {
|
||||
return inlineFunctionCalls.flatMap {
|
||||
it.valueArguments
|
||||
.map(::getArgumentExpression)
|
||||
.filterIsInstance<KtFunction>()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getArgumentExpression(it: ValueArgument) =
|
||||
(it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
|
||||
|
||||
private fun getInlineArgumentsCallsIfAny(
|
||||
sourcePosition: SourcePosition,
|
||||
declarationDescriptor: DeclarationDescriptor
|
||||
): List<KtCallExpression>? {
|
||||
if (declarationDescriptor !is CallableDescriptor) return null
|
||||
|
||||
val valueParameters = declarationDescriptor.valueParameters.filter { it.type.isFunctionType }.toSet()
|
||||
|
||||
if (valueParameters.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
fun isCallOfArgument(ktCallExpression: KtCallExpression): Boolean {
|
||||
val resolvedCall = ktCallExpression.resolveToCall() as? VariableAsFunctionResolvedCall ?: return false
|
||||
|
||||
val candidateDescriptor = resolvedCall.variableCall.candidateDescriptor
|
||||
|
||||
return candidateDescriptor in valueParameters
|
||||
}
|
||||
|
||||
return findCallsOnPosition(sourcePosition, ::isCallOfArgument)
|
||||
}
|
||||
|
||||
private fun getInlineFunctionCallsIfAny(sourcePosition: SourcePosition): List<KtCallExpression> {
|
||||
fun isInlineCall(expr: KtCallExpression): Boolean {
|
||||
val resolvedCall = expr.resolveToCall() ?: return false
|
||||
return InlineUtil.isInline(resolvedCall.resultingDescriptor)
|
||||
}
|
||||
|
||||
return findCallsOnPosition(sourcePosition, ::isInlineCall)
|
||||
}
|
||||
|
||||
private fun findCallsOnPosition(sourcePosition: SourcePosition, filter: (KtCallExpression) -> Boolean): List<KtCallExpression> {
|
||||
val file = sourcePosition.file as? KtFile ?: return emptyList()
|
||||
val lineNumber = sourcePosition.line
|
||||
|
||||
val lineElement = findElementAtLine(file, lineNumber)
|
||||
|
||||
if (lineElement !is KtElement) {
|
||||
if (lineElement != null) {
|
||||
val call = findCallByEndToken(lineElement)
|
||||
if (call != null && filter(call)) {
|
||||
return listOf(call)
|
||||
}
|
||||
}
|
||||
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val start = lineElement.startOffset
|
||||
val end = lineElement.endOffset
|
||||
|
||||
val allFilteredCalls = CodeInsightUtils.findElementsOfClassInRange(file, start, end, KtExpression::class.java)
|
||||
.map { KtPsiUtil.getParentCallIfPresent(it as KtExpression) }
|
||||
.filterIsInstance<KtCallExpression>()
|
||||
.filter { filter(it) }
|
||||
.toSet()
|
||||
|
||||
// It is necessary to check range because of multiline assign
|
||||
var linesRange = lineNumber..lineNumber
|
||||
return allFilteredCalls.filter {
|
||||
val shouldInclude = it.getLineNumber() in linesRange
|
||||
if (shouldInclude) {
|
||||
linesRange = min(linesRange.start, it.getLineNumber())..max(linesRange.endInclusive, it.getLineNumber(false))
|
||||
}
|
||||
shouldInclude
|
||||
}
|
||||
}
|
||||
|
||||
sealed class Action(
|
||||
val position: XSourcePositionImpl? = null,
|
||||
val stepOverInlineData: StepOverFilterData? = null
|
||||
) {
|
||||
class STEP_OVER : Action() {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) =
|
||||
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(suspendContext)
|
||||
}
|
||||
|
||||
class STEP_OUT : Action() {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) =
|
||||
debugProcess.createStepOutCommand(suspendContext).contextAction(suspendContext)
|
||||
}
|
||||
|
||||
class RUN_TO_CURSOR(position: XSourcePositionImpl) : Action(position) {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
|
||||
return runReadAction {
|
||||
debugProcess.createRunToCursorCommand(suspendContext, position!!, ignoreBreakpoints)
|
||||
}.contextAction(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
class STEP_OVER_INLINED(stepOverInlineData: StepOverFilterData) : Action(stepOverInlineData = stepOverInlineData) {
|
||||
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
|
||||
return KotlinStepActionFactory(debugProcess).createKotlinStepOverInlineAction(
|
||||
KotlinStepOverInlineFilter(debugProcess.project, stepOverInlineData!!)
|
||||
).contextAction(suspendContext)
|
||||
}
|
||||
}
|
||||
|
||||
abstract fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean)
|
||||
}
|
||||
|
||||
interface KotlinMethodFilter : MethodFilter {
|
||||
fun locationMatches(context: SuspendContextImpl, location: Location): Boolean
|
||||
}
|
||||
|
||||
fun getStepOverAction(
|
||||
location: Location,
|
||||
kotlinSourcePosition: KotlinSteppingCommandProvider.KotlinSourcePosition,
|
||||
frameProxy: StackFrameProxyImpl,
|
||||
isDexDebug: Boolean
|
||||
): Action {
|
||||
val inlineArgumentsToSkip = runReadAction {
|
||||
getInlineCallFunctionArgumentsIfAny(kotlinSourcePosition.sourcePosition)
|
||||
}
|
||||
|
||||
return getStepOverAction(
|
||||
location, kotlinSourcePosition.file, kotlinSourcePosition.linesRange,
|
||||
inlineArgumentsToSkip, frameProxy, isDexDebug
|
||||
)
|
||||
}
|
||||
|
||||
fun getStepOverAction(
|
||||
location: Location,
|
||||
sourceFile: KtFile,
|
||||
range: IntRange,
|
||||
inlineFunctionArguments: List<KtElement>,
|
||||
frameProxy: StackFrameProxyImpl,
|
||||
isDexDebug: Boolean
|
||||
): Action {
|
||||
location.declaringType() ?: return Action.STEP_OVER()
|
||||
|
||||
val project = sourceFile.project
|
||||
|
||||
val methodLocations = location.method().safeAllLineLocations()
|
||||
if (methodLocations.isEmpty()) {
|
||||
return Action.STEP_OVER()
|
||||
}
|
||||
|
||||
val locationsLineAndFile = methodLocations.keysToMap { ktLocationInfo(it, isDexDebug, project, true) }
|
||||
|
||||
fun Location.ktLineNumber(): Int = (locationsLineAndFile[this] ?: ktLocationInfo(this, isDexDebug, project, true)).first
|
||||
fun Location.ktFileName(): String {
|
||||
val ktFile = (locationsLineAndFile[this] ?: ktLocationInfo(this, isDexDebug, project, true)).second
|
||||
// File is not null only for inlined locations. Get file name from debugger information otherwise.
|
||||
return ktFile?.name ?: this.sourceName(KOTLIN_STRATA_NAME)
|
||||
}
|
||||
|
||||
fun isThisMethodLocation(nextLocation: Location): Boolean {
|
||||
if (nextLocation.method() != location.method()) {
|
||||
return false
|
||||
}
|
||||
|
||||
val ktLineNumber = nextLocation.ktLineNumber()
|
||||
if (ktLineNumber !in range) {
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
return nextLocation.ktFileName() == sourceFile.name
|
||||
} catch (e: AbsentInformationException) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
fun isBackEdgeLocation(): Boolean {
|
||||
val previousSuitableLocation = methodLocations.reversed()
|
||||
.dropWhile { it != location }
|
||||
.drop(1)
|
||||
.filter(::isThisMethodLocation)
|
||||
.dropWhile { it.ktLineNumber() == location.ktLineNumber() }
|
||||
.firstOrNull()
|
||||
|
||||
return previousSuitableLocation != null && previousSuitableLocation.ktLineNumber() > location.ktLineNumber()
|
||||
}
|
||||
|
||||
val patchedLocation = if (isBackEdgeLocation()) {
|
||||
// Pretend we had already done a backing step
|
||||
methodLocations
|
||||
.filter(::isThisMethodLocation)
|
||||
.firstOrNull { it.ktLineNumber() == location.ktLineNumber() } ?: location
|
||||
} else {
|
||||
location
|
||||
}
|
||||
|
||||
val patchedLineNumber = patchedLocation.ktLineNumber()
|
||||
|
||||
val lambdaArgumentRanges = runReadAction {
|
||||
inlineFunctionArguments.map {
|
||||
val startLineNumber = it.getLineNumber(true) + 1
|
||||
val endLineNumber = it.getLineNumber(false) + 1
|
||||
|
||||
startLineNumber..endLineNumber
|
||||
}
|
||||
}
|
||||
|
||||
val inlineRangeVariables = getInlineRangeLocalVariables(frameProxy)
|
||||
|
||||
// Try to find the range for step over:
|
||||
// - Lines from other files and from functions that are not in range of current one are definitely inlined and should be stepped over.
|
||||
// - Lines in function arguments of inlined functions are inlined too as we found them starting from the position of inlined call.
|
||||
// - Current line locations should also be stepped over.
|
||||
//
|
||||
// We might erroneously extend this range too much when there's a call of function argument or other
|
||||
// inline function in last statement of inline function. The list of inlineRangeVariables will be used later to overcome it.
|
||||
val stepOverLocations = methodLocations
|
||||
.dropWhile { it != patchedLocation }
|
||||
.drop(1)
|
||||
.dropWhile { it.ktLineNumber() == patchedLineNumber }
|
||||
.takeWhile { loc ->
|
||||
!isThisMethodLocation(loc) || lambdaArgumentRanges.any { loc.ktLineNumber() in it } || loc.ktLineNumber() == patchedLineNumber
|
||||
}
|
||||
|
||||
if (!stepOverLocations.isEmpty()) {
|
||||
// Some Kotlin inlined methods with 'for' (and maybe others) generates bytecode that after dexing have a strange artifact.
|
||||
// GOTO instructions are moved to the end of method and as they don't have proper line, line is obtained from the previous
|
||||
// instruction. It might be method return or previous GOTO from the inlining. Simple stepping over such function is really
|
||||
// terrible. On each iteration position jumps to the method end or some previous inline call and then returns back. To prevent
|
||||
// this filter locations with too big code indexes manually
|
||||
val returnCodeIndex: Long = if (isDexDebug) {
|
||||
val method = location.method()
|
||||
val locationsOfLine = method.safeLocationsOfLine(range.last)
|
||||
if (locationsOfLine.isNotEmpty()) {
|
||||
locationsOfLine.map { it.codeIndex() }.max() ?: -1L
|
||||
} else {
|
||||
findReturnFromDexBytecode(location.method())
|
||||
}
|
||||
} else -1L
|
||||
|
||||
return Action.STEP_OVER_INLINED(
|
||||
StepOverFilterData(
|
||||
patchedLineNumber,
|
||||
stepOverLocations.map { it.ktLineNumber() }.toSet(),
|
||||
inlineRangeVariables,
|
||||
isDexDebug,
|
||||
returnCodeIndex
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return Action.STEP_OVER()
|
||||
}
|
||||
|
||||
fun getStepOutAction(
|
||||
location: Location,
|
||||
suspendContext: SuspendContextImpl,
|
||||
inlineFunctions: List<KtNamedFunction>,
|
||||
inlinedArgument: KtFunctionLiteral?
|
||||
): Action {
|
||||
val computedReferenceType = location.declaringType() ?: return Action.STEP_OUT()
|
||||
|
||||
val locations = computedReferenceType.safeAllLineLocations()
|
||||
val nextLineLocations = locations
|
||||
.dropWhile { it != location }
|
||||
.drop(1)
|
||||
.filter { it.method() == location.method() }
|
||||
.dropWhile { it.lineNumber() == location.lineNumber() }
|
||||
|
||||
if (inlineFunctions.isNotEmpty()) {
|
||||
val position = suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions)
|
||||
return position?.let { Action.RUN_TO_CURSOR(it) } ?: Action.STEP_OVER()
|
||||
}
|
||||
|
||||
if (inlinedArgument != null) {
|
||||
val position = suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument)
|
||||
return position?.let { Action.RUN_TO_CURSOR(it) } ?: Action.STEP_OVER()
|
||||
}
|
||||
|
||||
return Action.STEP_OVER()
|
||||
}
|
||||
|
||||
private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction(
|
||||
locations: List<Location>,
|
||||
inlineFunctionsToSkip: List<KtNamedFunction>
|
||||
): XSourcePositionImpl? {
|
||||
return getNextPositionWithFilter(locations) { offset, elementAt ->
|
||||
if (inlineFunctionsToSkip.any { it.textRange.contains(offset) }) {
|
||||
return@getNextPositionWithFilter true
|
||||
}
|
||||
|
||||
getInlineArgumentIfAny(elementAt) != null
|
||||
}
|
||||
}
|
||||
|
||||
private fun SuspendContextImpl.getXPositionForStepOutFromInlinedArgument(
|
||||
locations: List<Location>,
|
||||
inlinedArgumentToSkip: KtFunctionLiteral
|
||||
): XSourcePositionImpl? {
|
||||
return getNextPositionWithFilter(locations) { offset, _ ->
|
||||
inlinedArgumentToSkip.textRange.contains(offset)
|
||||
}
|
||||
}
|
||||
|
||||
private fun SuspendContextImpl.getNextPositionWithFilter(
|
||||
locations: List<Location>,
|
||||
skip: (Int, PsiElement) -> Boolean
|
||||
): XSourcePositionImpl? {
|
||||
for (location in locations) {
|
||||
val position = runReadAction l@{
|
||||
val sourcePosition = try {
|
||||
this.debugProcess.positionManager.getSourcePosition(location)
|
||||
} catch (e: NoDataException) {
|
||||
null
|
||||
} ?: return@l null
|
||||
|
||||
val file = sourcePosition.file as? KtFile ?: return@l null
|
||||
val elementAt = sourcePosition.elementAt ?: return@l null
|
||||
val currentLine = location.lineNumber() - 1
|
||||
val lineStartOffset = file.getLineStartOffset(currentLine) ?: return@l null
|
||||
if (skip(lineStartOffset, elementAt)) return@l null
|
||||
|
||||
XSourcePositionImpl.createByElement(elementAt)
|
||||
}
|
||||
if (position != null) {
|
||||
return position
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
fun getInlineRangeLocalVariables(stackFrame: StackFrameProxyImpl): List<LocalVariable> {
|
||||
return stackFrame.safeVisibleVariables()
|
||||
.filter {
|
||||
val name = it.name()
|
||||
name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION)
|
||||
}
|
||||
.map { it.variable }
|
||||
}
|
||||
|
||||
private fun getInlineArgumentIfAny(elementAt: PsiElement?): KtFunctionLiteral? {
|
||||
val functionLiteralExpression = elementAt?.getParentOfType<KtLambdaExpression>(false) ?: return null
|
||||
|
||||
val context = functionLiteralExpression.analyze(BodyResolveMode.PARTIAL)
|
||||
if (!InlineUtil.isInlinedArgument(functionLiteralExpression.functionLiteral, context, false)) return null
|
||||
|
||||
return functionLiteralExpression.functionLiteral
|
||||
}
|
||||
|
||||
private fun findReturnFromDexBytecode(method: Method): Long {
|
||||
val methodLocations = method.safeAllLineLocations()
|
||||
if (methodLocations.isEmpty()) {
|
||||
return -1L
|
||||
}
|
||||
|
||||
var lastMethodCodeIndex = methodLocations.last().codeIndex()
|
||||
// Continue while it's possible to get location
|
||||
while (true) {
|
||||
if (method.locationOfCodeIndex(lastMethodCodeIndex + 1) != null) {
|
||||
lastMethodCodeIndex++
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var returnIndex = lastMethodCodeIndex + 1
|
||||
|
||||
val bytecode = method.bytecodes()
|
||||
var i = bytecode.size
|
||||
|
||||
while (i >= 2) {
|
||||
// Can step only through two-byte instructions and abort on any unknown one
|
||||
i -= 2
|
||||
returnIndex -= 1
|
||||
|
||||
val instruction = bytecode[i].toInt()
|
||||
|
||||
if (instruction == RETURN_VOID || instruction == RETURN || instruction == RETURN_WIDE || instruction == RETURN_OBJECT) {
|
||||
// Instruction found
|
||||
return returnIndex
|
||||
} else if (instruction == MOVE || instruction == GOTO) {
|
||||
// proceed
|
||||
} else {
|
||||
// Don't know the instruction and it's length. Abort.
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return -1L
|
||||
}
|
||||
|
||||
object DexBytecode {
|
||||
val RETURN_VOID = 0x0e
|
||||
val RETURN = 0x0f
|
||||
val RETURN_WIDE = 0x10
|
||||
val RETURN_OBJECT = 0x11
|
||||
|
||||
val GOTO = 0x28
|
||||
val MOVE = 0x01
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingConfigurableUi">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="99d36" class="javax.swing.JCheckBox" binding="ignoreKotlinMethods">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<selected value="false"/>
|
||||
<text resource-bundle="org/jetbrains/kotlin/idea/KotlinBundle" key="debugger.filter.ignore.internal.classes"/>
|
||||
</properties>
|
||||
</component>
|
||||
<vspacer id="c37da">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2010-2015 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.debugger.stepping;
|
||||
|
||||
|
||||
import com.intellij.openapi.options.ConfigurableUi;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class KotlinSteppingConfigurableUi implements ConfigurableUi<KotlinDebuggerSettings> {
|
||||
private JCheckBox ignoreKotlinMethods;
|
||||
private JPanel myPanel;
|
||||
|
||||
@Override
|
||||
public void reset(@NotNull KotlinDebuggerSettings settings) {
|
||||
boolean flag = settings.getDEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES();
|
||||
ignoreKotlinMethods.setSelected(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified(@NotNull KotlinDebuggerSettings settings) {
|
||||
return settings.getDEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES() != ignoreKotlinMethods.isSelected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(@NotNull KotlinDebuggerSettings settings) {
|
||||
settings.setDEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES(ignoreKotlinMethods.isSelected());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return myPanel;
|
||||
}
|
||||
}
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.MethodFilter
|
||||
import com.intellij.debugger.engine.RequestHint
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.util.Range
|
||||
import com.intellij.xdebugger.impl.XSourcePositionImpl
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import org.jetbrains.kotlin.idea.debugger.isOnSuspendReturnOrReenter
|
||||
import org.jetbrains.kotlin.idea.debugger.suspendFunctionFirstLineLocation
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
|
||||
class KotlinSuspendCallStepOverFilter(
|
||||
private val line: Int,
|
||||
private val file: PsiFile,
|
||||
private val ignoreBreakpoints: Boolean
|
||||
) : MethodFilter {
|
||||
override fun getCallingExpressionLines(): Range<Int>? = Range(line, line)
|
||||
|
||||
override fun locationMatches(process: DebugProcessImpl, location: Location?): Boolean {
|
||||
return location != null && isOnSuspendReturnOrReenter(location)
|
||||
}
|
||||
|
||||
override fun onReached(context: SuspendContextImpl, hint: RequestHint): Int {
|
||||
val location = context.frameProxy?.location() ?: return RequestHint.STOP
|
||||
val suspendStartLineNumber = suspendFunctionFirstLineLocation(location) ?: return RequestHint.STOP
|
||||
|
||||
val debugProcess = context.debugProcess
|
||||
val breakpointManager = DebuggerManagerEx.getInstanceEx(debugProcess.project).breakpointManager
|
||||
breakpointManager.applyThreadFilter(debugProcess, null)
|
||||
|
||||
createRunToCursorBreakpoint(context, suspendStartLineNumber - 1, file, ignoreBreakpoints)
|
||||
return RequestHint.RESUME
|
||||
}
|
||||
}
|
||||
|
||||
private fun createRunToCursorBreakpoint(context: SuspendContextImpl, line: Int, file: PsiFile, ignoreBreakpoints: Boolean) {
|
||||
val position = XSourcePositionImpl.create(file.virtualFile, line) ?: return
|
||||
val process = context.debugProcess
|
||||
process.showStatusText(DebuggerBundle.message("status.run.to.cursor"))
|
||||
process.cancelRunToCursorBreakpoint()
|
||||
|
||||
if (ignoreBreakpoints) {
|
||||
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.disableBreakpoints(process)
|
||||
}
|
||||
|
||||
val runToCursorBreakpoint =
|
||||
runReadAction {
|
||||
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.addRunToCursorBreakpoint(position, ignoreBreakpoints)
|
||||
} ?: return
|
||||
|
||||
runToCursorBreakpoint.suspendPolicy = when {
|
||||
context.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD -> DebuggerSettings.SUSPEND_THREAD
|
||||
else -> DebuggerSettings.SUSPEND_ALL
|
||||
}
|
||||
|
||||
runToCursorBreakpoint.createRequest(process)
|
||||
process.setRunToCursorBreakpoint(runToCursorBreakpoint)
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.debugger.stepping
|
||||
|
||||
import com.intellij.debugger.engine.BreakpointStepMethodFilter
|
||||
import com.intellij.debugger.engine.MethodFilter
|
||||
import com.intellij.debugger.engine.RequestHint
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.sun.jdi.VMDisconnectedException
|
||||
import com.sun.jdi.request.StepRequest
|
||||
import org.intellij.lang.annotations.MagicConstant
|
||||
import java.lang.reflect.Field
|
||||
|
||||
internal class RequestHintWithMethodFilter(
|
||||
stepThread: ThreadReferenceProxyImpl,
|
||||
suspendContext: SuspendContextImpl,
|
||||
@MagicConstant(
|
||||
intValues = longArrayOf(
|
||||
StepRequest.STEP_INTO.toLong(),
|
||||
StepRequest.STEP_OVER.toLong(),
|
||||
StepRequest.STEP_OUT.toLong()
|
||||
)
|
||||
) depth: Int,
|
||||
methodFilter: MethodFilter
|
||||
) : RequestHint(stepThread, suspendContext, methodFilter) {
|
||||
private var targetMethodMatched = false
|
||||
|
||||
init {
|
||||
// NOTE: Debugger API. Open RequestHint constructor with depth
|
||||
if (depth != StepRequest.STEP_INTO) {
|
||||
findFieldWithValue(StepRequest.STEP_INTO, Integer.TYPE)?.setInt(this, depth)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findFieldWithValue(value: Int, type: Class<*>): Field? {
|
||||
return RequestHint::class.java.declaredFields.firstOrNull { field ->
|
||||
if (field.type == type) {
|
||||
field.isAccessible = true
|
||||
if (field.getInt(this) == value) {
|
||||
return@firstOrNull true
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getNextStepDepth(context: SuspendContextImpl): Int {
|
||||
try {
|
||||
val frameProxy = context.frameProxy
|
||||
val filter = methodFilter
|
||||
|
||||
if (filter != null && frameProxy != null && filter !is BreakpointStepMethodFilter) {
|
||||
/*NODE: Debugger API. Base implementation works only for smart step into, and calls filter only if !isTheSameFrame(context). */
|
||||
if (filter.locationMatches(context.debugProcess, frameProxy.location())) {
|
||||
targetMethodMatched = true
|
||||
return filter.onReached(context, this)
|
||||
}
|
||||
}
|
||||
} catch (ignored: VMDisconnectedException) {
|
||||
return STOP
|
||||
} catch (e: EvaluateException) {
|
||||
LOG.error(e)
|
||||
return STOP
|
||||
}
|
||||
|
||||
return super.getNextStepDepth(context)
|
||||
}
|
||||
|
||||
override fun wasStepTargetMethodMatched(): Boolean {
|
||||
return super.wasStepTargetMethodMatched() || targetMethodMatched
|
||||
}
|
||||
}
|
||||
|
||||
private val LOG = Logger.getInstance(RequestHintWithMethodFilter::class.java)
|
||||
Reference in New Issue
Block a user