Debugger: Fix infinite recursion in inline callable searcher (EA-108938)
This commit is contained in:
+27
-18
@@ -104,12 +104,12 @@ class DebuggerClassNameProvider(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val result = getOrComputeClassNames(relevantElement) { element ->
|
val result = getOrComputeClassNames(relevantElement) { element ->
|
||||||
getOuterClassNamesForElement(element)
|
getOuterClassNamesForElement(element, emptySet())
|
||||||
}.toMutableSet()
|
}.toMutableSet()
|
||||||
|
|
||||||
for (lambda in position.readAction(::getLambdasAtLineIfAny)) {
|
for (lambda in position.readAction(::getLambdasAtLineIfAny)) {
|
||||||
result += getOrComputeClassNames(lambda) { element ->
|
result += getOrComputeClassNames(lambda) { element ->
|
||||||
getOuterClassNamesForElement(element)
|
getOuterClassNamesForElement(element, emptySet())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,7 +118,10 @@ class DebuggerClassNameProvider(
|
|||||||
|
|
||||||
@PublishedApi
|
@PublishedApi
|
||||||
@Suppress("NON_TAIL_RECURSIVE_CALL")
|
@Suppress("NON_TAIL_RECURSIVE_CALL")
|
||||||
internal tailrec fun getOuterClassNamesForElement(element: PsiElement?): ComputedClassNames {
|
internal tailrec fun getOuterClassNamesForElement(element: PsiElement?, alreadyVisited: Set<PsiElement>): ComputedClassNames {
|
||||||
|
// 'alreadyVisited' is used in inline callable searcher to prevent infinite recursion.
|
||||||
|
// In normal cases we only go from leaves to topmost parents upwards.
|
||||||
|
|
||||||
if (element == null) return EMPTY
|
if (element == null) return EMPTY
|
||||||
|
|
||||||
return when (element) {
|
return when (element) {
|
||||||
@@ -135,9 +138,9 @@ class DebuggerClassNameProvider(
|
|||||||
when {
|
when {
|
||||||
enclosingElementForLocal != null ->
|
enclosingElementForLocal != null ->
|
||||||
// A local class
|
// A local class
|
||||||
getOuterClassNamesForElement(enclosingElementForLocal)
|
getOuterClassNamesForElement(enclosingElementForLocal, alreadyVisited)
|
||||||
runReadAction { element.isObjectLiteral() } ->
|
runReadAction { element.isObjectLiteral() } ->
|
||||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
getOuterClassNamesForElement(element.relevantParentInReadAction, alreadyVisited)
|
||||||
else ->
|
else ->
|
||||||
// Guaranteed to be non-local class or object
|
// Guaranteed to be non-local class or object
|
||||||
element.readAction { _ ->
|
element.readAction { _ ->
|
||||||
@@ -157,12 +160,12 @@ class DebuggerClassNameProvider(
|
|||||||
is KtProperty -> {
|
is KtProperty -> {
|
||||||
val nonInlineClasses = if (runReadAction { isTopLevelInFileOrScript(element) }) {
|
val nonInlineClasses = if (runReadAction { isTopLevelInFileOrScript(element) }) {
|
||||||
// Top level property
|
// Top level property
|
||||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
getOuterClassNamesForElement(element.relevantParentInReadAction, alreadyVisited)
|
||||||
} else {
|
} else {
|
||||||
val enclosingElementForLocal = runReadAction { KtPsiUtil.getEnclosingElementForLocalDeclaration(element) }
|
val enclosingElementForLocal = runReadAction { KtPsiUtil.getEnclosingElementForLocalDeclaration(element) }
|
||||||
if (enclosingElementForLocal != null) {
|
if (enclosingElementForLocal != null) {
|
||||||
// Local class
|
// Local class
|
||||||
getOuterClassNamesForElement(enclosingElementForLocal)
|
getOuterClassNamesForElement(enclosingElementForLocal, alreadyVisited)
|
||||||
} else {
|
} else {
|
||||||
val containingClassOrFile = runReadAction {
|
val containingClassOrFile = runReadAction {
|
||||||
PsiTreeUtil.getParentOfType(element, KtFile::class.java, KtClassOrObject::class.java)
|
PsiTreeUtil.getParentOfType(element, KtFile::class.java, KtClassOrObject::class.java)
|
||||||
@@ -170,12 +173,12 @@ class DebuggerClassNameProvider(
|
|||||||
|
|
||||||
if (containingClassOrFile is KtObjectDeclaration && containingClassOrFile.isCompanionInReadAction) {
|
if (containingClassOrFile is KtObjectDeclaration && containingClassOrFile.isCompanionInReadAction) {
|
||||||
// Properties from the companion object can be placed in the companion object's containing class
|
// Properties from the companion object can be placed in the companion object's containing class
|
||||||
(getOuterClassNamesForElement(containingClassOrFile.relevantParentInReadAction) +
|
(getOuterClassNamesForElement(containingClassOrFile.relevantParentInReadAction, alreadyVisited) +
|
||||||
getOuterClassNamesForElement(containingClassOrFile)).distinct()
|
getOuterClassNamesForElement(containingClassOrFile, alreadyVisited)).distinct()
|
||||||
} else if (containingClassOrFile != null) {
|
} else if (containingClassOrFile != null) {
|
||||||
getOuterClassNamesForElement(containingClassOrFile)
|
getOuterClassNamesForElement(containingClassOrFile, alreadyVisited)
|
||||||
} else {
|
} else {
|
||||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
getOuterClassNamesForElement(element.relevantParentInReadAction, alreadyVisited)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -184,13 +187,16 @@ class DebuggerClassNameProvider(
|
|||||||
element.isInlineInReadAction ||
|
element.isInlineInReadAction ||
|
||||||
runReadAction { element.accessors.any { it.hasModifier(KtTokens.INLINE_KEYWORD) } })
|
runReadAction { element.accessors.any { it.hasModifier(KtTokens.INLINE_KEYWORD) } })
|
||||||
) {
|
) {
|
||||||
nonInlineClasses + inlineUsagesSearcher.findInlinedCalls(element) { this.getOuterClassNamesForElement(it) }
|
val inlinedCalls = inlineUsagesSearcher.findInlinedCalls(element, alreadyVisited) { el, newAlreadyVisited ->
|
||||||
|
this.getOuterClassNamesForElement(el, newAlreadyVisited)
|
||||||
|
}
|
||||||
|
nonInlineClasses + inlinedCalls
|
||||||
} else {
|
} else {
|
||||||
return NonCached(nonInlineClasses.classNames)
|
return NonCached(nonInlineClasses.classNames)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is KtNamedFunction -> {
|
is KtNamedFunction -> {
|
||||||
val classNamesOfContainingDeclaration = getOuterClassNamesForElement(element.relevantParentInReadAction)
|
val classNamesOfContainingDeclaration = getOuterClassNamesForElement(element.relevantParentInReadAction, alreadyVisited)
|
||||||
|
|
||||||
var nonInlineClasses: ComputedClassNames = classNamesOfContainingDeclaration
|
var nonInlineClasses: ComputedClassNames = classNamesOfContainingDeclaration
|
||||||
|
|
||||||
@@ -205,7 +211,9 @@ class DebuggerClassNameProvider(
|
|||||||
return NonCached(nonInlineClasses.classNames)
|
return NonCached(nonInlineClasses.classNames)
|
||||||
}
|
}
|
||||||
|
|
||||||
val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls(element) { this.getOuterClassNamesForElement(it) }
|
val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls(element, alreadyVisited) { el, newAlreadyVisited ->
|
||||||
|
this.getOuterClassNamesForElement(el, newAlreadyVisited)
|
||||||
|
}
|
||||||
|
|
||||||
nonInlineClasses + inlineCallSiteClasses
|
nonInlineClasses + inlineCallSiteClasses
|
||||||
}
|
}
|
||||||
@@ -213,10 +221,11 @@ class DebuggerClassNameProvider(
|
|||||||
val initializerOwner = runReadAction { element.containingDeclaration }
|
val initializerOwner = runReadAction { element.containingDeclaration }
|
||||||
|
|
||||||
if (initializerOwner is KtObjectDeclaration && initializerOwner.isCompanionInReadAction) {
|
if (initializerOwner is KtObjectDeclaration && initializerOwner.isCompanionInReadAction) {
|
||||||
return getOuterClassNamesForElement(runReadAction { initializerOwner.containingClassOrObject })
|
val containingClass = runReadAction { initializerOwner.containingClassOrObject }
|
||||||
|
return getOuterClassNamesForElement(containingClass, alreadyVisited)
|
||||||
}
|
}
|
||||||
|
|
||||||
getOuterClassNamesForElement(initializerOwner)
|
getOuterClassNamesForElement(initializerOwner, alreadyVisited)
|
||||||
}
|
}
|
||||||
is KtFunctionLiteral -> {
|
is KtFunctionLiteral -> {
|
||||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||||
@@ -233,9 +242,9 @@ class DebuggerClassNameProvider(
|
|||||||
return names
|
return names
|
||||||
}
|
}
|
||||||
|
|
||||||
names + getOuterClassNamesForElement(element.relevantParentInReadAction)
|
names + getOuterClassNamesForElement(element.relevantParentInReadAction, alreadyVisited)
|
||||||
}
|
}
|
||||||
else -> getOuterClassNamesForElement(element.relevantParentInReadAction)
|
else -> getOuterClassNamesForElement(element.relevantParentInReadAction, alreadyVisited)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
-10
@@ -22,6 +22,7 @@ import com.intellij.openapi.progress.EmptyProgressIndicator
|
|||||||
import com.intellij.openapi.progress.ProgressManager
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
import com.intellij.openapi.ui.MessageType
|
import com.intellij.openapi.ui.MessageType
|
||||||
import com.intellij.psi.PsiElement
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiReference
|
||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import com.intellij.psi.search.searches.ReferencesSearch
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||||
@@ -37,15 +38,15 @@ import org.jetbrains.kotlin.idea.util.application.runReadAction
|
|||||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||||
import org.jetbrains.kotlin.psi.KtElement
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
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.BindingContext
|
||||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||||
|
|
||||||
class InlineCallableUsagesSearcher(private val myDebugProcess: DebugProcess) {
|
class InlineCallableUsagesSearcher(private val myDebugProcess: DebugProcess) {
|
||||||
fun findInlinedCalls(
|
fun findInlinedCalls(
|
||||||
declaration: KtDeclaration,
|
declaration: KtDeclaration,
|
||||||
|
alreadyVisited: Set<PsiElement>,
|
||||||
bindingContext: BindingContext = KotlinDebuggerCaches.getOrCreateTypeMapper(declaration).bindingContext,
|
bindingContext: BindingContext = KotlinDebuggerCaches.getOrCreateTypeMapper(declaration).bindingContext,
|
||||||
transformer: (PsiElement) -> ComputedClassNames
|
transformer: (PsiElement, Set<PsiElement>) -> ComputedClassNames
|
||||||
): ComputedClassNames {
|
): ComputedClassNames {
|
||||||
if (!checkIfInline(declaration, bindingContext)) {
|
if (!checkIfInline(declaration, bindingContext)) {
|
||||||
return ComputedClassNames.EMPTY
|
return ComputedClassNames.EMPTY
|
||||||
@@ -55,13 +56,8 @@ class InlineCallableUsagesSearcher(private val myDebugProcess: DebugProcess) {
|
|||||||
val declarationName = runReadAction { declaration.name }
|
val declarationName = runReadAction { declaration.name }
|
||||||
|
|
||||||
val task = Runnable {
|
val task = Runnable {
|
||||||
ReferencesSearch.search(declaration, getScopeForInlineDeclarationUsages(declaration)).forEach {
|
for (reference in ReferencesSearch.search(declaration, getScopeForInlineDeclarationUsages(declaration))) {
|
||||||
if (!runReadAction { it.isImportUsage() }) {
|
processReference(declaration, reference, alreadyVisited)?.let { searchResult += it }
|
||||||
val usage = (it.element as? KtElement)?.let(::getRelevantElement)
|
|
||||||
if (usage != null && !runReadAction { declaration.isAncestor(usage) }) {
|
|
||||||
searchResult.add(usage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,11 +81,27 @@ class InlineCallableUsagesSearcher(private val myDebugProcess: DebugProcess) {
|
|||||||
).notify(myDebugProcess.project)
|
).notify(myDebugProcess.project)
|
||||||
}
|
}
|
||||||
|
|
||||||
val results = searchResult.map { transformer(it) }
|
val newAlreadyVisited = HashSet<PsiElement>().apply {
|
||||||
|
addAll(alreadyVisited)
|
||||||
|
addAll(searchResult)
|
||||||
|
add(declaration)
|
||||||
|
}
|
||||||
|
|
||||||
|
val results = searchResult.map { transformer(it, newAlreadyVisited) }
|
||||||
return ComputedClassNames(results.flatMap { it.classNames }, shouldBeCached = results.all { it.shouldBeCached })
|
return ComputedClassNames(results.flatMap { it.classNames }, shouldBeCached = results.all { it.shouldBeCached })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun processReference(declaration: KtDeclaration, reference: PsiReference, alreadyVisited: Set<PsiElement>): PsiElement? {
|
||||||
|
if (runReadAction { reference.isImportUsage() }) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val usage = (reference.element as? KtElement)?.let(::getRelevantElement) ?: return null
|
||||||
|
val shouldAnalyze = runReadAction { !declaration.isAncestor(usage) && usage !in alreadyVisited }
|
||||||
|
return if (shouldAnalyze) usage else null
|
||||||
|
}
|
||||||
|
|
||||||
private fun checkIfInline(declaration: KtDeclaration, bindingContext: BindingContext): Boolean {
|
private fun checkIfInline(declaration: KtDeclaration, bindingContext: BindingContext): Boolean {
|
||||||
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration) ?: return false
|
val descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, declaration) ?: return false
|
||||||
return when (descriptor) {
|
return when (descriptor) {
|
||||||
|
|||||||
+1
-1
@@ -237,7 +237,7 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
}
|
}
|
||||||
|
|
||||||
val internalClassNames = DebuggerClassNameProvider(myDebugProcess, alwaysReturnLambdaParentClass = false)
|
val internalClassNames = DebuggerClassNameProvider(myDebugProcess, alwaysReturnLambdaParentClass = false)
|
||||||
.getOuterClassNamesForElement(literal.firstChild)
|
.getOuterClassNamesForElement(literal.firstChild, emptySet())
|
||||||
.classNames
|
.classNames
|
||||||
|
|
||||||
if (internalClassNames.any { it == currentLocationClassName }) {
|
if (internalClassNames.any { it == currentLocationClassName }) {
|
||||||
|
|||||||
Reference in New Issue
Block a user