Support inline properties in debugger class search

This commit is contained in:
Yan Zhulanow
2017-04-10 18:12:46 +03:00
committed by Yan Zhulanow
parent cb9e90183a
commit 9e61eea758
7 changed files with 154 additions and 40 deletions
@@ -38,6 +38,7 @@ import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companio
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.Cached
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
@@ -137,38 +138,52 @@ class DebuggerClassNameProvider(
Cached(listOf(name, name + JvmAbi.DEFAULT_IMPLS_SUFFIX))
else
ComputedClassNames.EMPTY
} else {
}
else {
getNameForNonLocalClass(it)?.let { ComputedClassNames.Cached(it) } ?: ComputedClassNames.EMPTY
}
}
}
}
is KtProperty -> {
// inline top level property!
if (runReadAction { element.isTopLevel }) {
return getOuterClassNamesForElement(element.relevantParentInReadAction)
}
val enclosingElementForLocal = runReadAction { KtPsiUtil.getEnclosingElementForLocalDeclaration(element) }
if (enclosingElementForLocal != null) {
return getOuterClassNamesForElement(enclosingElementForLocal)
}
// Handle non-local property
val containingClassOrFile = runReadAction {
PsiTreeUtil.getParentOfType(element, KtFile::class.java, KtClassOrObject::class.java)
}
if (containingClassOrFile is KtObjectDeclaration && runReadAction { containingClassOrFile.isCompanion() }) {
// Properties from the companion object can be placed in the companion object's containing class
return (getOuterClassNamesForElement(containingClassOrFile.relevantParentInReadAction) +
getOuterClassNamesForElement(containingClassOrFile)).distinct()
}
if (containingClassOrFile != null)
getOuterClassNamesForElement(containingClassOrFile)
else
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 typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
@@ -180,25 +195,23 @@ class DebuggerClassNameProvider(
if (runReadAction { element.name == null || element.isLocal } || isFunctionWithSuspendStateMachine(descriptor, typeMapper.bindingContext)) {
nonInlineClasses = classNamesOfContainingDeclaration + ComputedClassNames.Cached(
asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName.toJdiName())
} else {
}
else {
nonInlineClasses = classNamesOfContainingDeclaration
}
if (!findInlineUseSites) {
if (!findInlineUseSites || !element.isInlineInReadAction) {
return NonCached(nonInlineClasses.classNames)
}
val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls(
element,
KotlinDebuggerCaches.getOrCreateTypeMapper(element).bindingContext
) { this.getOuterClassNamesForElement(it) }
val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls(element) { this.getOuterClassNamesForElement(it) }
nonInlineClasses + inlineCallSiteClasses
}
is KtAnonymousInitializer -> {
val initializerOwner = runReadAction { element.containingDeclaration }
if (initializerOwner is KtObjectDeclaration && runReadAction { initializerOwner.isCompanion() }) {
if (initializerOwner is KtObjectDeclaration && initializerOwner.isCompanionInReadAction) {
return getOuterClassNamesForElement(runReadAction { initializerOwner.containingClassOrObject })
}
@@ -237,6 +250,12 @@ class DebuggerClassNameProvider(
return descriptor is SimpleFunctionDescriptor && descriptor.isSuspend && descriptor.containsNonTailSuspensionCalls(bindingContext)
}
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) }
}
@@ -25,6 +25,8 @@ 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.search.usagesSearch.isImportUsage
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.util.application.runReadAction
@@ -32,19 +34,24 @@ import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.inline.InlineUtil
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
class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes: List<GlobalSearchScope>) {
fun findInlinedCalls(function: KtNamedFunction, context: BindingContext, transformer: (PsiElement) -> ComputedClassNames): ComputedClassNames {
if (!InlineUtil.isInline(context.get(BindingContext.DECLARATION_TO_DESCRIPTOR, function))) {
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 functionName = runReadAction { function.name }
val declarationName = runReadAction { declaration.name }
val task = Runnable {
ReferencesSearch.search(function, getScopeForInlineFunctionUsages(function)).forEach {
ReferencesSearch.search(declaration, getScopeForInlineDeclarationUsages(declaration)).forEach {
if (!runReadAction { it.isImportUsage() }) {
val usage = (it.element as? KtElement)?.let(::getRelevantElement)
if (usage != null) {
@@ -61,7 +68,7 @@ class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes:
{
isSuccess = ProgressManager.getInstance().runProcessWithProgressSynchronously(
task,
"Compute class names for function $functionName",
"Compute class names for declaration $declarationName",
true,
myDebugProcess.project)
}, ModalityState.NON_MODAL)
@@ -73,7 +80,8 @@ class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes:
if (!isSuccess) {
XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(
"Debugger can skip some executions of $functionName method, because the computation of class names was interrupted", MessageType.WARNING
"Debugger can skip some executions of $declarationName because the computation of class names was interrupted",
MessageType.WARNING
).notify(myDebugProcess.project)
}
@@ -82,8 +90,17 @@ class InlineCallableUsagesSearcher(val myDebugProcess: DebugProcess, val scopes:
}
}
private fun getScopeForInlineFunctionUsages(inlineFunction: KtNamedFunction): GlobalSearchScope {
val virtualFile = runReadAction { inlineFunction.containingFile.virtualFile }
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 }
if (virtualFile != null && ProjectRootsUtil.isLibraryFile(myDebugProcess.project, virtualFile)) {
return GlobalSearchScope.union(scopes.toTypedArray())
}
@@ -0,0 +1,9 @@
LineBreakpoint created at inlineProperties.kt:8
LineBreakpoint created at inlineProperties.kt:20
Run Java
Connected to the target VM
inlineProperties.kt:8
inlineProperties.kt:20
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,9 @@
LineBreakpoint created at inlinePropertyAccessors.kt:8
LineBreakpoint created at inlinePropertyAccessors.kt:20
Run Java
Connected to the target VM
inlinePropertyAccessors.kt:8
inlinePropertyAccessors.kt:20
Disconnected from the target VM
Process finished with exit code 0
@@ -0,0 +1,24 @@
package inlineProperties
fun main(args: Array<String>) {
class A {
inline val a: String
get() {
//Breakpoint!
return System.nanoTime().toString()
}
}
A().apply { a }
B().apply { b }
}
class B {
inline val b: String
get() {
//Breakpoint!
return System.nanoTime().toString()
}
}
// RESUME: 2
@@ -0,0 +1,24 @@
package inlinePropertyAccessors
fun main(args: Array<String>) {
class A {
val a: String
inline get() {
//Breakpoint!
return System.nanoTime().toString()
}
}
A().apply { a }
B().apply { b }
}
class B {
val b: String
inline get() {
//Breakpoint!
return System.nanoTime().toString()
}
}
// RESUME: 2
@@ -1070,6 +1070,18 @@ public class KotlinSteppingTestGenerated extends AbstractKotlinSteppingTest {
doCustomTest(fileName);
}
@TestMetadata("inlineProperties.kt")
public void testInlineProperties() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/inlineProperties.kt");
doCustomTest(fileName);
}
@TestMetadata("inlinePropertyAccessors.kt")
public void testInlinePropertyAccessors() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/inlinePropertyAccessors.kt");
doCustomTest(fileName);
}
@TestMetadata("kt15823.kt")
public void testKt15823() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/debugger/tinyApp/src/stepping/custom/kt15823.kt");