Debugger: Extract Smart step visitor

This commit is contained in:
Yan Zhulanow
2019-08-09 16:42:44 +09:00
parent 8325216d38
commit d2b058c1e5
6 changed files with 238 additions and 225 deletions
@@ -1,251 +1,55 @@
/*
* 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.
* 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.stepping
package org.jetbrains.kotlin.idea.debugger.stepping.smartStepInto
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.idea.core.util.CodeInsightUtils.getTopmostElementAtOffset
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinBasicStepMethodFilter
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinLambdaMethodFilter
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinLambdaSmartStepTarget
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinMethodSmartStepTarget
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 element = position.elementAt ?: return emptyList()
val ktElement = getTopmostElementAtOffset(element, element.textRange.startOffset) as? KtElement
val elementTextRange = ktElement?.textRange ?: return emptyList()
val document = PsiDocumentManager.getInstance(file.project).getDocument(file) ?: return emptyList()
val lines = Range(document.getLineNumber(elementTextRange.startOffset), document.getLineNumber(elementTextRange.endOffset))
val elementAtOffset = position.elementAt ?: return emptyList()
val consumer = OrderedSet<SmartStepTarget>()
val visitor = SmartStepTargetVisitor(ktElement, lines, consumer)
ktElement.accept(visitor, null)
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) {
if (declaration is PsiMethod) {
result.add(MethodSmartStepTarget(declaration, null, expression, 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
return consumer
}
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
)
is KotlinMethodSmartStepTarget -> {
val declarationPtr = stepTarget.declaration?.createSmartPointer()
val lines = stepTarget.callingExpressionLines ?: return null
KotlinBasicStepMethodFilter(declarationPtr, stepTarget.isInvoke, stepTarget.targetMethodName, lines)
}
is KotlinLambdaSmartStepTarget -> {
val lines = stepTarget.callingExpressionLines ?: return null
KotlinLambdaMethodFilter(stepTarget.getLambda(), lines, 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
}
}
@@ -0,0 +1,208 @@
/*
* 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.stepping.smartStepInto
import com.intellij.debugger.actions.MethodSmartStepTarget
import com.intellij.debugger.actions.SmartStepTarget
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.resolveToCall
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinLambdaSmartStepTarget
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinMethodSmartStepTarget
import org.jetbrains.kotlin.idea.project.platform
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.isFromJava
import org.jetbrains.kotlin.platform.jvm.JdkPlatform
import org.jetbrains.kotlin.psi.*
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
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
// TODO support class initializers, local functions, delegated properties with specified type, setter for properties
class SmartStepTargetVisitor(
private val element: KtElement,
private val lines: Range<Int>,
private val consumer: OrderedSet<SmartStepTarget>
) : KtTreeVisitorVoid() {
private fun append(target: SmartStepTarget) {
consumer += target
}
private val intrinsicMethods = run {
val jvmTarget = element.platform.firstIsInstanceOrNull<JdkPlatform>()?.targetVersion ?: JvmTarget.DEFAULT
IntrinsicMethods(jvmTarget)
}
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
recordFunction(lambdaExpression.functionLiteral)
}
override fun visitNamedFunction(function: KtNamedFunction) {
if (!recordFunction(function)) {
super.visitNamedFunction(function)
}
}
private fun recordFunction(function: KtFunction): Boolean {
val context = function.analyze()
val resolvedCall = function.getParentCall(context).getResolvedCall(context) ?: return false
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)
val isInline = InlineUtil.isInline(resultingDescriptor)
val isSuspend = param.type.isSuspendFunctionType
val target = KotlinLambdaSmartStepTarget(label, function, lines, isInline, isSuspend)
append(target)
return true
}
}
return false
}
private fun getArgumentExpression(it: ValueArgument): KtExpression? {
return (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) {
recordFunctionCall(expression)
super.visitArrayAccessExpression(expression)
}
override fun visitUnaryExpression(expression: KtUnaryExpression) {
recordFunctionCall(expression.operationReference)
super.visitUnaryExpression(expression)
}
override fun visitBinaryExpression(expression: KtBinaryExpression) {
recordFunctionCall(expression.operationReference)
super.visitBinaryExpression(expression)
}
override fun visitCallExpression(expression: KtCallExpression) {
val calleeExpression = expression.calleeExpression
if (calleeExpression != null) {
recordFunctionCall(calleeExpression)
}
super.visitCallExpression(expression)
}
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
recordGetter(expression)
super.visitSimpleNameExpression(expression)
}
private fun recordGetter(expression: KtSimpleNameExpression) {
val bindingContext = expression.analyze()
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(element.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)
append(KotlinMethodSmartStepTarget(delegatedPropertyGetterDescriptor, ktDeclaration, label, expression, lines))
} else {
if (ktDeclaration is KtPropertyAccessor && ktDeclaration.hasBody()) {
val label = KotlinMethodSmartStepTarget.calcLabel(getterDescriptor)
append(KotlinMethodSmartStepTarget(getterDescriptor, ktDeclaration, label, expression, lines))
}
}
}
private fun recordFunctionCall(expression: KtExpression) {
val resolvedCall = expression.resolveToCall() ?: return
val descriptor = resolvedCall.resultingDescriptor
if (descriptor !is FunctionDescriptor || isIntrinsic(descriptor)) return
val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(element.project, descriptor)
if (descriptor.isFromJava) {
if (declaration is PsiMethod) {
append(MethodSmartStepTarget(declaration, null, expression, 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
}
append(KotlinMethodSmartStepTarget(descriptor, declaration, label, expression, lines))
}
}
private fun isIntrinsic(descriptor: CallableMemberDescriptor): Boolean {
return intrinsicMethods.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
}
}
@@ -7,7 +7,7 @@ package org.jetbrains.kotlin.idea.debugger.test
import com.intellij.testFramework.fixtures.JavaCodeInsightTestFixture
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinSmartStepIntoHandler
import org.jetbrains.kotlin.idea.debugger.stepping.smartStepInto.KotlinSmartStepIntoHandler
import org.jetbrains.kotlin.idea.debugger.test.mock.MockSourcePosition
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
@@ -14,6 +14,7 @@ import com.intellij.debugger.impl.PositionUtil
import com.intellij.execution.process.ProcessOutputTypes
import com.sun.jdi.request.StepRequest
import org.jetbrains.kotlin.idea.debugger.stepping.*
import org.jetbrains.kotlin.idea.debugger.stepping.smartStepInto.KotlinSmartStepIntoHandler
import org.jetbrains.kotlin.idea.debugger.test.util.SteppingInstruction
import org.jetbrains.kotlin.idea.debugger.test.util.SteppingInstructionKind
import org.jetbrains.kotlin.idea.debugger.test.util.renderSourcePosition
+1 -1
View File
@@ -83,7 +83,7 @@
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.debugger.ToggleKotlinVariablesState"/>
<debugger.asyncStackTraceProvider implementation="org.jetbrains.kotlin.idea.debugger.KotlinCoroutinesAsyncStackTraceProvider"/>
<debugger.jvmSmartStepIntoHandler implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSmartStepIntoHandler"/>
<debugger.jvmSmartStepIntoHandler implementation="org.jetbrains.kotlin.idea.debugger.stepping.smartStepInto.KotlinSmartStepIntoHandler"/>
<debugger.positionManagerFactory implementation="org.jetbrains.kotlin.idea.debugger.KotlinPositionManagerFactory"/>
<debugger.codeFragmentFactory implementation="org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory"/>
<debuggerEditorTextProvider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider"/>
+1 -1
View File
@@ -88,7 +88,7 @@
<applicationService serviceImplementation="org.jetbrains.kotlin.idea.debugger.ToggleKotlinVariablesState"/>
<debugger.asyncStackTraceProvider implementation="org.jetbrains.kotlin.idea.debugger.KotlinCoroutinesAsyncStackTraceProvider"/>
<debugger.jvmSmartStepIntoHandler implementation="org.jetbrains.kotlin.idea.debugger.stepping.KotlinSmartStepIntoHandler"/>
<debugger.jvmSmartStepIntoHandler implementation="org.jetbrains.kotlin.idea.debugger.stepping.smartStepInto.KotlinSmartStepIntoHandler"/>
<debugger.positionManagerFactory implementation="org.jetbrains.kotlin.idea.debugger.KotlinPositionManagerFactory"/>
<debugger.codeFragmentFactory implementation="org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory"/>
<debuggerEditorTextProvider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider"/>