Debugger: Find classes using its inner classes (initial implementation)
This commit is contained in:
committed by
Yan Zhulanow
parent
3ee65ac499
commit
42e14961de
@@ -231,7 +231,24 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
for (name in names) {
|
for (name in names) {
|
||||||
result.addAll(myDebugProcess.virtualMachineProxy.classesByName(name))
|
result.addAll(myDebugProcess.virtualMachineProxy.classesByName(name))
|
||||||
}
|
}
|
||||||
return result
|
|
||||||
|
val allClasses = runReadAction { myDebugProcess.getAllClassesAtLine(sourcePosition) }
|
||||||
|
// TEMP START
|
||||||
|
if (result.isNotEmpty()) {
|
||||||
|
val r1 = result.toSet().toList().sorted()
|
||||||
|
val r2 = allClasses.toSet().toList().sorted()
|
||||||
|
for ((i, r11) in r1.withIndex()) {
|
||||||
|
if (i >= r2.size) {
|
||||||
|
println("MIS: $r11")
|
||||||
|
}
|
||||||
|
else if (r11 != r2[i]) {
|
||||||
|
println("OLD: $r11")
|
||||||
|
println("NEW: ${r2[i]}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TEMP END
|
||||||
|
return allClasses
|
||||||
}
|
}
|
||||||
|
|
||||||
if (psiFile is ClsFileImpl) {
|
if (psiFile is ClsFileImpl) {
|
||||||
@@ -301,4 +318,4 @@ class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiReq
|
|||||||
|
|
||||||
inline fun <U, V> U.readAction(crossinline f: (U) -> V): V {
|
inline fun <U, V> U.readAction(crossinline f: (U) -> V): V {
|
||||||
return runReadAction { f(this) }
|
return runReadAction { f(this) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,249 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2015 KtBrains 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.ReferenceType
|
||||||
|
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||||
|
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||||
|
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
|
||||||
|
import org.jetbrains.kotlin.fileClasses.getFileClassInternalName
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||||
|
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.isObjectLiteral
|
||||||
|
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||||
|
|
||||||
|
fun DebugProcess.getAllClassesAtLine(position: SourcePosition): List<ReferenceType> {
|
||||||
|
val result = hashSetOf<ReferenceType>()
|
||||||
|
|
||||||
|
result.addAll(getAllClassesAtElement(position.elementAt, position.line))
|
||||||
|
|
||||||
|
getLambdasAtLineIfAny(position).forEach {
|
||||||
|
result.addAll(getAllClassesAtElement(it, position.line))
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.toList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DebugProcess.getAllClassesAtElement(elementAt: PsiElement, lineAt: Int): List<ReferenceType> {
|
||||||
|
|
||||||
|
var depthToLocalOrAnonymousClass = 0
|
||||||
|
|
||||||
|
fun calc(element: KtElement?): String? {
|
||||||
|
when (element) {
|
||||||
|
null -> return null
|
||||||
|
is KtClassOrObject -> {
|
||||||
|
if (element.isLocal) {
|
||||||
|
depthToLocalOrAnonymousClass++
|
||||||
|
return calc(element.getElementToCalculateClassName())
|
||||||
|
}
|
||||||
|
|
||||||
|
return element.getNameForNonAnonymousClass()
|
||||||
|
}
|
||||||
|
is KtFunctionLiteral -> {
|
||||||
|
if (!isInlinedLambda(element)) {
|
||||||
|
depthToLocalOrAnonymousClass++
|
||||||
|
}
|
||||||
|
return calc(element.getElementToCalculateClassName())
|
||||||
|
}
|
||||||
|
is KtClassInitializer -> {
|
||||||
|
val parent = element.getElementToCalculateClassName()
|
||||||
|
|
||||||
|
if (parent is KtObjectDeclaration && parent.isCompanion()) {
|
||||||
|
// Companion-object initializer
|
||||||
|
return calc(parent.getElementToCalculateClassName())
|
||||||
|
}
|
||||||
|
|
||||||
|
return calc(parent)
|
||||||
|
}
|
||||||
|
is KtPropertyAccessor -> {
|
||||||
|
return calc(element.getClassOfFile())
|
||||||
|
}
|
||||||
|
is KtProperty -> {
|
||||||
|
if (element.isTopLevel || element.isLocal) {
|
||||||
|
return calc(element.getElementToCalculateClassName())
|
||||||
|
}
|
||||||
|
|
||||||
|
val containingClass = element.getClassOfFile()
|
||||||
|
if (containingClass is KtObjectDeclaration && containingClass.isCompanion()) {
|
||||||
|
// Properties from companion object are moved into class
|
||||||
|
val descriptor = element.resolveToDescriptor() as PropertyDescriptor
|
||||||
|
if (AsmUtil.isPropertyWithBackingFieldCopyInOuterClass(descriptor)) {
|
||||||
|
return calc(containingClass.getElementToCalculateClassName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return calc(containingClass)
|
||||||
|
}
|
||||||
|
is KtSecondaryConstructor -> {
|
||||||
|
return calc(element.getElementToCalculateClassName())
|
||||||
|
}
|
||||||
|
is KtNamedFunction -> {
|
||||||
|
if (element.name == null) {
|
||||||
|
val descriptor = element.readAction { InlineUtil.getInlineArgumentDescriptor(it, element.analyze()) }
|
||||||
|
if (descriptor == null || descriptor.isCrossinline) {
|
||||||
|
depthToLocalOrAnonymousClass++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (element.isLocal) {
|
||||||
|
depthToLocalOrAnonymousClass++
|
||||||
|
}
|
||||||
|
val parent = element.getElementToCalculateClassName()
|
||||||
|
if (parent is KtClassInitializer) {
|
||||||
|
// TODO BUG? anonymous functions from companion object constructor should be inner class of companion object, not class
|
||||||
|
return calc(parent.getElementToCalculateClassName())
|
||||||
|
}
|
||||||
|
|
||||||
|
return calc(parent)
|
||||||
|
}
|
||||||
|
is KtFile -> {
|
||||||
|
return NoResolveFileClassesProvider.getFileClassInternalName(element)
|
||||||
|
}
|
||||||
|
else -> throw IllegalStateException("Unsupported container ${element.javaClass}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val elementToCalcClassName = elementAt.getElementToCalculateClassName(true)
|
||||||
|
val className = calc(elementToCalcClassName) ?: return emptyList()
|
||||||
|
|
||||||
|
if (depthToLocalOrAnonymousClass == 0) {
|
||||||
|
return virtualMachineProxy.classesByName(className)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the name is a parent class for a local or anonymous class
|
||||||
|
val outers = virtualMachineProxy.classesByName(className)
|
||||||
|
return outers.map { findNested(it, 0, depthToLocalOrAnonymousClass, elementAt, lineAt) }.filterNotNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtClassOrObject.getNameForNonAnonymousClass(addTraitImplSuffix: Boolean = true): String? {
|
||||||
|
if (isLocal) return null
|
||||||
|
if (this.isObjectLiteral()) return null
|
||||||
|
|
||||||
|
val name = name ?: return null
|
||||||
|
|
||||||
|
val parentClass = PsiTreeUtil.getParentOfType(this, KtClassOrObject::class.java, true)
|
||||||
|
if (parentClass != null) {
|
||||||
|
val shouldAddTraitImplSuffix = !(parentClass is KtClass && this is KtObjectDeclaration && this.isCompanion())
|
||||||
|
val parentName = parentClass.getNameForNonAnonymousClass(shouldAddTraitImplSuffix)
|
||||||
|
if (parentName == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return parentName + "$" + name
|
||||||
|
}
|
||||||
|
|
||||||
|
val className = if (addTraitImplSuffix && this is KtClass && this.isInterface()) name + JvmAbi.DEFAULT_IMPLS_SUFFIX else name
|
||||||
|
|
||||||
|
val packageFqName = this.containingKtFile.packageFqName
|
||||||
|
return if (packageFqName.isRoot) className else packageFqName.asString() + "." + className
|
||||||
|
}
|
||||||
|
|
||||||
|
private val TYPES_TO_CALCULATE_CLASSNAME: Array<Class<out KtElement>> =
|
||||||
|
arrayOf(KtFile::class.java,
|
||||||
|
KtClass::class.java,
|
||||||
|
KtObjectDeclaration::class.java,
|
||||||
|
KtEnumEntry::class.java,
|
||||||
|
KtFunctionLiteral::class.java,
|
||||||
|
KtNamedFunction::class.java,
|
||||||
|
KtPropertyAccessor::class.java,
|
||||||
|
KtProperty::class.java,
|
||||||
|
KtClassInitializer::class.java,
|
||||||
|
KtSecondaryConstructor::class.java)
|
||||||
|
|
||||||
|
private fun PsiElement?.getElementToCalculateClassName(withItSelf: Boolean = false): KtElement? {
|
||||||
|
if (withItSelf && this?.let { it::class.java } as Class<*> in TYPES_TO_CALCULATE_CLASSNAME) return this as KtElement
|
||||||
|
|
||||||
|
return readAction { PsiTreeUtil.getParentOfType(this, *TYPES_TO_CALCULATE_CLASSNAME) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement.getClassOfFile(): KtElement? {
|
||||||
|
return PsiTreeUtil.getParentOfType(this, KtFile::class.java, KtClassOrObject::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun DebugProcess.findNested(
|
||||||
|
fromClass: ReferenceType,
|
||||||
|
currentDepth: Int,
|
||||||
|
requiredDepth: Int,
|
||||||
|
elementAt: PsiElement,
|
||||||
|
lineAt: Int
|
||||||
|
): ReferenceType? {
|
||||||
|
val vmProxy = virtualMachineProxy
|
||||||
|
if (fromClass.isPrepared) {
|
||||||
|
try {
|
||||||
|
if (currentDepth < requiredDepth) {
|
||||||
|
val nestedTypes = vmProxy.nestedTypes(fromClass)
|
||||||
|
for (nested in nestedTypes) {
|
||||||
|
val found = findNested(nested, currentDepth + 1, requiredDepth, elementAt, lineAt)
|
||||||
|
if (found != null) {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
for (location in fromClass.allLineLocations()) {
|
||||||
|
val locationLine = location.lineNumber() - 1
|
||||||
|
if (locationLine <= 0) {
|
||||||
|
// such locations are not correspond to real lines in code
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val method = location.method()
|
||||||
|
if (method == null || DebuggerUtils.isSynthetic(method) || method.isBridge) {
|
||||||
|
// skip synthetic methods
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lineAt == locationLine) {
|
||||||
|
// TODO: compare elementAt, can be significant for lambdas
|
||||||
|
// val candidatePosition = KotlinPositionManager(this).getSourcePosition(location)
|
||||||
|
// if (candidatePosition?.elementAt == elementAt) {
|
||||||
|
return fromClass
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (ignored: AbsentInformationException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isInlinedLambda(functionLiteral: KtFunctionLiteral): Boolean {
|
||||||
|
val functionLiteralExpression = functionLiteral.parent
|
||||||
|
|
||||||
|
var parent = functionLiteralExpression.parent
|
||||||
|
|
||||||
|
while (parent is KtParenthesizedExpression || parent is KtBinaryExpressionWithTypeRHS || parent is KtLabeledExpression) {
|
||||||
|
parent = parent.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
while (parent is ValueArgument || parent is KtValueArgumentList) {
|
||||||
|
parent = parent.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parent !is KtElement) return false
|
||||||
|
|
||||||
|
return InlineUtil.isInlinedArgument(functionLiteral, functionLiteral.analyze(), true)
|
||||||
|
}
|
||||||
@@ -87,7 +87,7 @@ public class MockReferenceType implements ReferenceType {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPrepared() {
|
public boolean isPrepared() {
|
||||||
throw new UnsupportedOperationException();
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
Reference in New Issue
Block a user