Fix leak in debugger (KT-24903)

#KT-24903 Fixed
This commit is contained in:
Nikolay Krasko
2018-06-13 16:59:23 +03:00
parent 64f14ad89b
commit dc8a3ab534
4 changed files with 123 additions and 102 deletions
@@ -18,17 +18,14 @@ 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.intellij.util.SofterReference
import com.sun.jdi.Location
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
import org.jetbrains.kotlin.codegen.SamCodegenUtil
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.*
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.core.getDirectlyOverriddenDeclarations
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtProperty
@@ -36,32 +33,26 @@ import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate
import org.jetbrains.kotlin.resolve.DescriptorUtils
class KotlinBasicStepMethodFilter(
targetDescriptor: CallableMemberDescriptor,
private val declarationPtr: SmartPsiElementPointer<KtDeclaration>?,
private val isInvoke: Boolean,
private val targetMethodName: String,
private val myCallingExpressionLines: Range<Int>
) : NamedMethodFilter {
private val myTargetMethodName: String = when (targetDescriptor) {
is ClassDescriptor, is ConstructorDescriptor -> "<init>"
is PropertyAccessorDescriptor -> JvmAbi.getterName(targetDescriptor.correspondingProperty.name.asString())
else -> targetDescriptor.name.asString()
init {
assert(declarationPtr != null || isInvoke)
}
private val _targetDescriptor = SofterReference(
(targetDescriptor as? FunctionDescriptor)?.let { SamCodegenUtil.getOriginalIfSamAdapter(it) } ?: targetDescriptor
)
override fun getCallingExpressionLines() = myCallingExpressionLines
override fun getMethodName() = myTargetMethodName
override fun getMethodName() = targetMethodName
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
val targetDescriptor = _targetDescriptor.get() ?: return true
val method = location.method()
if (myTargetMethodName != method.name()) return false
if (targetMethodName != method.name()) return false
val positionManager = process.positionManager
val currentDescriptor = runReadAction {
val (currentDescriptor, currentDeclaration) = runReadAction {
val elementAt = positionManager.getSourcePosition(location)?.elementAt
val declaration = elementAt?.getParentOfTypesAndPredicate(false, KtDeclaration::class.java) {
@@ -69,44 +60,36 @@ class KotlinBasicStepMethodFilter(
}
if (declaration is KtClass && method.name() == "<init>") {
declaration.resolveToDescriptorIfAny()?.unsubstitutedPrimaryConstructor
declaration.resolveToDescriptorIfAny()?.unsubstitutedPrimaryConstructor to declaration
} else {
declaration?.resolveToDescriptorIfAny()
declaration?.resolveToDescriptorIfAny() to declaration
}
} ?: return false // TODO: Check that we can always find a descriptor (libraries with sources, libraries without sources)
}
if (currentDescriptor == null || currentDeclaration == null) {
return false
}
@Suppress("FoldInitializerAndIfToElvis")
if (currentDescriptor !is CallableMemberDescriptor) return false
if (currentDescriptor.kind != DECLARATION) return false
if (targetDescriptor is FunctionInvokeDescriptor) {
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
}
if (compareDescriptors(currentDescriptor, targetDescriptor)) return true
val declaration = declarationPtr?.element
?: return true // Element is lost. But we know that name is matches, so stop.
// We should stop if current descriptor overrides the target one or some base descriptor of target
// (if target descriptor is delegation or fake override)
val baseDescriptors = when (targetDescriptor.kind) {
DELEGATION, FAKE_OVERRIDE ->
targetDescriptor.getDirectlyOverriddenDeclarations()
DECLARATION, SYNTHESIZED ->
listOf(targetDescriptor)
}
if (baseDescriptors.any { baseOfTarget -> compareDescriptors(baseOfTarget, currentDescriptor) }) {
if (currentDeclaration.isEquivalentTo(declaration)) {
return true
}
return DescriptorUtils.getAllOverriddenDescriptors(currentDescriptor).any { baseOfCurrent ->
baseDescriptors.any { baseOfTarget -> compareDescriptors(baseOfCurrent, baseOfTarget) }
val currentBaseDeclaration = DescriptorToSourceUtilsIde.getAnyDeclaration(currentDeclaration.project, baseOfCurrent)
declaration.isEquivalentTo(currentBaseDeclaration)
}
}
}
private fun compareDescriptors(d1: DeclarationDescriptor, d2: DeclarationDescriptor): Boolean {
return d1 == d2 || d1.original == d2.original
}
@@ -4,23 +4,39 @@ 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.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
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.resolve.descriptorUtil.isExtension
import javax.swing.Icon
class KotlinMethodSmartStepTarget(
val descriptor: CallableMemberDescriptor,
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 {
descriptor.isExtension -> KotlinIcons.EXTENSION_FUNCTION
isExtension -> KotlinIcons.EXTENSION_FUNCTION
else -> KotlinIcons.FUNCTION
}
}
@@ -44,19 +60,19 @@ class KotlinMethodSmartStepTarget(
if (other == null || other !is KotlinMethodSmartStepTarget) return false
if (descriptor is FunctionInvokeDescriptor && other.descriptor is FunctionInvokeDescriptor) {
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 descriptor == other.descriptor
return declaration === other.declaration
}
override fun hashCode(): Int {
if (descriptor is FunctionInvokeDescriptor) {
if (isInvoke) {
// Predefined value to make all FunctionInvokeDescriptor targets equal
return 42
}
return descriptor.hashCode()
return declaration!!.hashCode()
}
}
@@ -26,13 +26,11 @@ 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.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
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.CodeInsightUtils
@@ -40,6 +38,7 @@ import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
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
@@ -150,62 +149,69 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
}
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
val resolvedCall = expression.getResolvedCall(bindingContext)
if (resolvedCall != null) {
val propertyDescriptor = resolvedCall.resultingDescriptor
if (propertyDescriptor is PropertyDescriptor) {
val getterDescriptor = propertyDescriptor.getter
if (getterDescriptor != null && !getterDescriptor.isDefault) {
val delegatedResolvedCall = bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getterDescriptor]
if (delegatedResolvedCall == null) {
val getter = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, getterDescriptor)
if (getter is KtPropertyAccessor && getter.hasBody()) {
val label = KotlinMethodSmartStepTarget.calcLabel(getterDescriptor)
result.add(KotlinMethodSmartStepTarget(getterDescriptor, label, expression, lines))
}
} else {
val delegatedPropertyGetterDescriptor = delegatedResolvedCall.resultingDescriptor
val label =
"${propertyDescriptor.name}." + KotlinMethodSmartStepTarget.calcLabel(delegatedPropertyGetterDescriptor)
result.add(KotlinMethodSmartStepTarget(delegatedPropertyGetterDescriptor, label, expression, lines))
}
}
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))
}
}
super.visitSimpleNameExpression(expression)
}
private fun recordFunction(expression: KtExpression) {
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return
val descriptor = resolvedCall.resultingDescriptor
if (descriptor is FunctionDescriptor && !isIntrinsic(descriptor)) {
if (descriptor.isFromJava) {
(DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor) as? PsiMethod)?.let {
result.add(MethodSmartStepTarget(it, null, expression, false, lines))
}
} else {
if (descriptor is ConstructorDescriptor && descriptor.isPrimary) {
val psiElement = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor)
if (psiElement is KtClass && psiElement.getAnonymousInitializers().isEmpty()) {
// There is no constructor or init block, so do not show it in smart step into
return
}
}
if (descriptor !is FunctionDescriptor || isIntrinsic(descriptor)) 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, label, expression, lines))
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)
@@ -216,7 +222,12 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? {
return when (stepTarget) {
is KotlinMethodSmartStepTarget ->
KotlinBasicStepMethodFilter(stepTarget.descriptor, stepTarget.callingExpressionLines!!)
KotlinBasicStepMethodFilter(
stepTarget.declaration?.createSmartPointer(),
stepTarget.isInvoke,
stepTarget.targetMethodName,
stepTarget.callingExpressionLines!!
)
is KotlinLambdaSmartStepTarget ->
KotlinLambdaMethodFilter(
stepTarget.getLambda(), stepTarget.callingExpressionLines!!, stepTarget.isInline, stepTarget.isSuspend
@@ -231,4 +242,10 @@ class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
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
}
}
@@ -59,6 +59,7 @@ import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType
import org.jetbrains.kotlin.idea.debugger.stepping.*
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.psi.psiUtil.createSmartPointer
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes
@@ -246,7 +247,11 @@ abstract class KotlinDebuggerTestBase : KotlinDebuggerTestCase() {
KotlinLambdaMethodFilter(
stepTarget.getLambda(), stepTarget.getCallingExpressionLines()!!, stepTarget.isInline, stepTarget.isSuspend)
is KotlinMethodSmartStepTarget ->
KotlinBasicStepMethodFilter(stepTarget.descriptor, stepTarget.getCallingExpressionLines()!!)
KotlinBasicStepMethodFilter(
stepTarget.declaration?.createSmartPointer(),
stepTarget.isInvoke,
stepTarget.targetMethodName,
stepTarget.getCallingExpressionLines()!!)
is MethodSmartStepTarget -> BasicStepMethodFilter(stepTarget.method, stepTarget.getCallingExpressionLines())
else -> null
}