Move out JVM debugger functionality
This commit is contained in:
@@ -20,7 +20,6 @@ dependencies {
|
||||
compileOnly(intellijPluginDep("copyright"))
|
||||
compileOnly(intellijPluginDep("properties"))
|
||||
compileOnly(intellijPluginDep("java-i18n"))
|
||||
compileOnly(intellijPluginDep("stream-debugger"))
|
||||
}
|
||||
|
||||
|
||||
|
||||
+1
-1
@@ -26,6 +26,7 @@ import com.intellij.psi.search.GlobalSearchScope
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.configuration.ui.notifications.ConfigureKotlinNotification
|
||||
import org.jetbrains.kotlin.idea.core.util.getKotlinJvmRuntimeMarkerClass
|
||||
import org.jetbrains.kotlin.idea.framework.JSLibraryKind
|
||||
import org.jetbrains.kotlin.idea.framework.effectiveKind
|
||||
import org.jetbrains.kotlin.idea.quickfix.KotlinAddRequiredModuleFix
|
||||
@@ -37,7 +38,6 @@ import org.jetbrains.kotlin.idea.util.projectStructure.allModules
|
||||
import org.jetbrains.kotlin.idea.util.projectStructure.sdk
|
||||
import org.jetbrains.kotlin.idea.util.projectStructure.version
|
||||
import org.jetbrains.kotlin.idea.versions.SuppressNotificationState
|
||||
import org.jetbrains.kotlin.idea.versions.getKotlinJvmRuntimeMarkerClass
|
||||
import org.jetbrains.kotlin.idea.versions.hasKotlinJsKjsmFile
|
||||
import org.jetbrains.kotlin.idea.versions.isSnapshot
|
||||
import org.jetbrains.kotlin.idea.vfilefinder.IDEVirtualFileFinder
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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.ObjectCollectedException
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClass
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClassOrNull
|
||||
import org.jetbrains.kotlin.descriptors.ScriptDescriptor
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.getOrComputeClassNames
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.Cached
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames.Companion.EMPTY
|
||||
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
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isObjectLiteral
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import java.util.*
|
||||
|
||||
class DebuggerClassNameProvider(
|
||||
private val debugProcess: DebugProcess,
|
||||
val findInlineUseSites: Boolean = true,
|
||||
val alwaysReturnLambdaParentClass: Boolean = true
|
||||
) {
|
||||
companion object {
|
||||
private val CLASS_ELEMENT_TYPES = arrayOf<Class<out PsiElement>>(
|
||||
KtFile::class.java,
|
||||
KtClassOrObject::class.java,
|
||||
KtProperty::class.java,
|
||||
KtNamedFunction::class.java,
|
||||
KtFunctionLiteral::class.java,
|
||||
KtAnonymousInitializer::class.java
|
||||
)
|
||||
|
||||
internal fun getRelevantElement(element: PsiElement?): PsiElement? {
|
||||
if (element == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
for (elementType in CLASS_ELEMENT_TYPES) {
|
||||
if (elementType.isInstance(element)) {
|
||||
return element
|
||||
}
|
||||
}
|
||||
|
||||
// Do not copy the array (*elementTypes) if the element is one we look for
|
||||
return runReadAction { PsiTreeUtil.getNonStrictParentOfType(element, *CLASS_ELEMENT_TYPES) }
|
||||
}
|
||||
}
|
||||
|
||||
private val inlineUsagesSearcher = InlineCallableUsagesSearcher(debugProcess)
|
||||
|
||||
/**
|
||||
* Returns classes in which the given line number *is* present.
|
||||
*/
|
||||
fun getClassesForPosition(position: SourcePosition): List<ReferenceType> = with(debugProcess) {
|
||||
val lineNumber = runReadAction { position.line }
|
||||
|
||||
return doGetClassesForPosition(position)
|
||||
.flatMap { className -> virtualMachineProxy.classesByName(className) }
|
||||
.flatMap { referenceType -> findTargetClasses(referenceType, lineNumber) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns classes names in JDI format (my.app.App$Nested) in which the given line number *may be* present.
|
||||
*/
|
||||
fun getOuterClassNamesForPosition(position: SourcePosition): List<String> {
|
||||
return doGetClassesForPosition(position).toList()
|
||||
}
|
||||
|
||||
private fun doGetClassesForPosition(position: SourcePosition): Set<String> {
|
||||
val relevantElement = runReadAction {
|
||||
position.elementAt?.let { getRelevantElement(it) }
|
||||
}
|
||||
|
||||
val result = getOrComputeClassNames(relevantElement) { element ->
|
||||
getOuterClassNamesForElement(element)
|
||||
}.toMutableSet()
|
||||
|
||||
for (lambda in position.readAction(::getLambdasAtLineIfAny)) {
|
||||
result += getOrComputeClassNames(lambda) { element ->
|
||||
getOuterClassNamesForElement(element)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@PublishedApi
|
||||
@Suppress("NON_TAIL_RECURSIVE_CALL")
|
||||
internal tailrec fun getOuterClassNamesForElement(element: PsiElement?): ComputedClassNames {
|
||||
if (element == null) return EMPTY
|
||||
|
||||
return when (element) {
|
||||
is KtScript -> {
|
||||
getClassType(element)?.let { return Cached(it) }
|
||||
return EMPTY
|
||||
}
|
||||
is KtFile -> {
|
||||
val fileClassName = runReadAction { JvmFileClassUtil.getFileClassInternalName(element) }.toJdiName()
|
||||
Cached(fileClassName)
|
||||
}
|
||||
is KtClassOrObject -> {
|
||||
val enclosingElementForLocal = runReadAction { KtPsiUtil.getEnclosingElementForLocalDeclaration(element) }
|
||||
when {
|
||||
enclosingElementForLocal != null ->
|
||||
// A local class
|
||||
getOuterClassNamesForElement(enclosingElementForLocal)
|
||||
runReadAction { element.isObjectLiteral() } ->
|
||||
getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
else ->
|
||||
// Guaranteed to be non-local class or object
|
||||
element.readAction { _ ->
|
||||
if (element is KtClass && runReadAction { element.isInterface() }) {
|
||||
val name = getNameForNonLocalClass(element)
|
||||
|
||||
if (name != null)
|
||||
Cached(listOf(name, name + JvmAbi.DEFAULT_IMPLS_SUFFIX))
|
||||
else
|
||||
EMPTY
|
||||
} else {
|
||||
getNameForNonLocalClass(element)?.let { Cached(it) } ?: EMPTY
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
is KtProperty -> {
|
||||
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 classNamesOfContainingDeclaration = getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
|
||||
var nonInlineClasses: ComputedClassNames = classNamesOfContainingDeclaration
|
||||
|
||||
if (runReadAction { element.name == null || element.isLocal }) {
|
||||
val nameOfAnonymousClass = runReadAction { getClassType(element) }
|
||||
if (nameOfAnonymousClass != null) {
|
||||
nonInlineClasses += Cached(nameOfAnonymousClass)
|
||||
}
|
||||
}
|
||||
|
||||
if (!findInlineUseSites || !element.isInlineInReadAction) {
|
||||
return NonCached(nonInlineClasses.classNames)
|
||||
}
|
||||
|
||||
val inlineCallSiteClasses = inlineUsagesSearcher.findInlinedCalls(element) { this.getOuterClassNamesForElement(it) }
|
||||
|
||||
nonInlineClasses + inlineCallSiteClasses
|
||||
}
|
||||
is KtAnonymousInitializer -> {
|
||||
val initializerOwner = runReadAction { element.containingDeclaration }
|
||||
|
||||
if (initializerOwner is KtObjectDeclaration && initializerOwner.isCompanionInReadAction) {
|
||||
return getOuterClassNamesForElement(runReadAction { initializerOwner.containingClassOrObject })
|
||||
}
|
||||
|
||||
getOuterClassNamesForElement(initializerOwner)
|
||||
}
|
||||
is KtFunctionLiteral -> {
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||
|
||||
val nonInlinedLambdaClassName = runReadAction {
|
||||
asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName.toJdiName()
|
||||
}
|
||||
|
||||
if (!alwaysReturnLambdaParentClass && !InlineUtil.isInlinedArgument(element, typeMapper.bindingContext, true)) {
|
||||
return Cached(nonInlinedLambdaClassName)
|
||||
}
|
||||
|
||||
Cached(nonInlinedLambdaClassName) + getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
else -> getOuterClassNamesForElement(element.relevantParentInReadAction)
|
||||
}
|
||||
}
|
||||
|
||||
// Should be called in a read action
|
||||
private fun getClassType(element: KtElement): String? {
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
|
||||
asmTypeForAnonymousClassOrNull(typeMapper.bindingContext, element)?.let { return it.className }
|
||||
|
||||
val descriptor = typeMapper.bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, element]
|
||||
if (descriptor is ScriptDescriptor) {
|
||||
return typeMapper.mapClass(descriptor).className
|
||||
}
|
||||
|
||||
if (descriptor != null) {
|
||||
val containingDeclaration = descriptor.containingDeclaration
|
||||
if (containingDeclaration is ScriptDescriptor) {
|
||||
return typeMapper.mapClass(containingDeclaration).className
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getNameForNonLocalClass(nonLocalClassOrObject: KtClassOrObject): String? {
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(nonLocalClassOrObject)
|
||||
val descriptor = typeMapper.bindingContext[BindingContext.CLASS, nonLocalClassOrObject] ?: return null
|
||||
|
||||
val type = typeMapper.mapClass(descriptor)
|
||||
if (type.sort != Type.OBJECT) {
|
||||
return null
|
||||
}
|
||||
|
||||
return type.className
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
|
||||
private fun String.toJdiName() = replace('/', '.')
|
||||
|
||||
private fun DebugProcess.findTargetClasses(outerClass: ReferenceType, lineAt: Int): List<ReferenceType> {
|
||||
val vmProxy = virtualMachineProxy
|
||||
|
||||
try {
|
||||
if (!outerClass.isPrepared) {
|
||||
return emptyList()
|
||||
}
|
||||
} catch (e: ObjectCollectedException) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val targetClasses = ArrayList<ReferenceType>(1)
|
||||
|
||||
try {
|
||||
for (location in outerClass.allLineLocations()) {
|
||||
val locationLine = location.lineNumber() - 1
|
||||
if (locationLine < 0) {
|
||||
// such locations are not correspond to real lines in code
|
||||
continue
|
||||
}
|
||||
|
||||
if (lineAt == locationLine) {
|
||||
val method = location.method()
|
||||
if (method == null || DebuggerUtils.isSynthetic(method) || method.isBridge) {
|
||||
// skip synthetic methods
|
||||
continue
|
||||
}
|
||||
|
||||
targetClasses += outerClass
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// The same line number may appear in different classes so we have to scan nested classes as well.
|
||||
// For example, in the next example line 3 appears in both Foo and Foo$Companion.
|
||||
|
||||
/* class Foo {
|
||||
companion object {
|
||||
val a = Foo() /* line 3 */
|
||||
}
|
||||
} */
|
||||
|
||||
val nestedTypes = vmProxy.nestedTypes(outerClass)
|
||||
for (nested in nestedTypes) {
|
||||
targetClasses += findTargetClasses(nested, lineAt)
|
||||
}
|
||||
} catch (_: AbsentInformationException) {
|
||||
}
|
||||
|
||||
return targetClasses
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.io.FileUtilRt
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.sun.jdi.Location
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.asJava.finder.JavaElementFinder
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinFileTypeFactory
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
|
||||
import org.jetbrains.kotlin.idea.resolve.ResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||
import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil.findFilesWithExactPackage
|
||||
import org.jetbrains.kotlin.idea.stubindex.StaticFacadeIndexUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.CompositeBindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedSimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
import java.util.*
|
||||
|
||||
object DebuggerUtils {
|
||||
@get:TestOnly
|
||||
var forceRanking = false
|
||||
|
||||
fun findSourceFileForClassIncludeLibrarySources(
|
||||
project: Project,
|
||||
scope: GlobalSearchScope,
|
||||
className: JvmClassName,
|
||||
fileName: String,
|
||||
location: Location? = null
|
||||
): KtFile? {
|
||||
return runReadAction {
|
||||
findSourceFileForClass(
|
||||
project,
|
||||
listOf(scope, KotlinSourceFilterScope.librarySources(GlobalSearchScope.allScope(project), project)),
|
||||
className,
|
||||
fileName,
|
||||
location
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun findSourceFileForClass(
|
||||
project: Project,
|
||||
scopes: List<GlobalSearchScope>,
|
||||
className: JvmClassName,
|
||||
fileName: String,
|
||||
location: Location?
|
||||
): KtFile? {
|
||||
if (!isKotlinSourceFile(fileName)) return null
|
||||
if (DumbService.getInstance(project).isDumb) return null
|
||||
|
||||
val partFqName = className.fqNameForClassNameWithoutDollars
|
||||
|
||||
for (scope in scopes) {
|
||||
val files = findFilesByNameInPackage(className, fileName, project, scope)
|
||||
|
||||
if (files.isEmpty()) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (files.size == 1 && !forceRanking || location == null) {
|
||||
return files.first()
|
||||
}
|
||||
|
||||
StaticFacadeIndexUtil.findFilesForFilePart(partFqName, scope, project)
|
||||
.singleOrNull { it.name == fileName }
|
||||
?.let { return it }
|
||||
|
||||
return FileRankingCalculatorForIde.findMostAppropriateSource(files, location)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findFilesByNameInPackage(
|
||||
className: JvmClassName,
|
||||
fileName: String,
|
||||
project: Project,
|
||||
searchScope: GlobalSearchScope
|
||||
): List<KtFile> {
|
||||
val files = findFilesWithExactPackage(className.packageFqName, searchScope, project).filter { it.name == fileName }
|
||||
return files.sortedWith(JavaElementFinder.byClasspathComparator(searchScope))
|
||||
}
|
||||
|
||||
fun analyzeInlinedFunctions(
|
||||
resolutionFacadeForFile: ResolutionFacade,
|
||||
file: KtFile,
|
||||
analyzeOnlyReifiedInlineFunctions: Boolean,
|
||||
bindingContext: BindingContext? = null
|
||||
): Pair<BindingContext, List<KtFile>> {
|
||||
val analyzedElements = HashSet<KtElement>()
|
||||
val context = analyzeElementWithInline(
|
||||
resolutionFacadeForFile,
|
||||
file,
|
||||
1,
|
||||
analyzedElements,
|
||||
!analyzeOnlyReifiedInlineFunctions, bindingContext
|
||||
)
|
||||
|
||||
//We processing another files just to annotate anonymous classes within their inline functions
|
||||
//Bytecode not produced for them cause of filtering via generateClassFilter
|
||||
val toProcess = LinkedHashSet<KtFile>()
|
||||
toProcess.add(file)
|
||||
|
||||
for (collectedElement in analyzedElements) {
|
||||
val containingFile = collectedElement.containingKtFile
|
||||
toProcess.add(containingFile)
|
||||
}
|
||||
|
||||
return Pair<BindingContext, List<KtFile>>(context, ArrayList(toProcess))
|
||||
}
|
||||
|
||||
fun analyzeElementWithInline(function: KtNamedFunction, analyzeInlineFunctions: Boolean): Collection<KtElement> {
|
||||
val analyzedElements = HashSet<KtElement>()
|
||||
analyzeElementWithInline(function.getResolutionFacade(), function, 1, analyzedElements, !analyzeInlineFunctions)
|
||||
return analyzedElements
|
||||
}
|
||||
|
||||
fun isKotlinSourceFile(fileName: String): Boolean {
|
||||
val extension = FileUtilRt.getExtension(fileName).toLowerCase()
|
||||
return extension in KotlinFileTypeFactory.KOTLIN_EXTENSIONS
|
||||
}
|
||||
|
||||
private fun analyzeElementWithInline(
|
||||
resolutionFacade: ResolutionFacade,
|
||||
element: KtElement,
|
||||
deep: Int,
|
||||
analyzedElements: MutableSet<KtElement>,
|
||||
analyzeInlineFunctions: Boolean,
|
||||
fullResolveContext: BindingContext? = null
|
||||
): BindingContext {
|
||||
val project = element.project
|
||||
val declarationsWithBody = HashSet<KtDeclarationWithBody>()
|
||||
|
||||
val innerContexts = ArrayList<BindingContext>()
|
||||
innerContexts.addIfNotNull(fullResolveContext)
|
||||
|
||||
element.accept(object : KtTreeVisitorVoid() {
|
||||
override fun visitExpression(expression: KtExpression) {
|
||||
super.visitExpression(expression)
|
||||
|
||||
val bindingContext = resolutionFacade.analyze(expression)
|
||||
innerContexts.add(bindingContext)
|
||||
|
||||
val call = bindingContext.get(BindingContext.CALL, expression) ?: return
|
||||
|
||||
val resolvedCall = bindingContext.get(BindingContext.RESOLVED_CALL, call)
|
||||
checkResolveCall(resolvedCall)
|
||||
}
|
||||
|
||||
override fun visitDestructuringDeclaration(destructuringDeclaration: KtDestructuringDeclaration) {
|
||||
super.visitDestructuringDeclaration(destructuringDeclaration)
|
||||
|
||||
val bindingContext = resolutionFacade.analyze(destructuringDeclaration)
|
||||
innerContexts.add(bindingContext)
|
||||
|
||||
for (entry in destructuringDeclaration.entries) {
|
||||
val resolvedCall = bindingContext.get(BindingContext.COMPONENT_RESOLVED_CALL, entry)
|
||||
checkResolveCall(resolvedCall)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitForExpression(expression: KtForExpression) {
|
||||
super.visitForExpression(expression)
|
||||
|
||||
val bindingContext = resolutionFacade.analyze(expression)
|
||||
innerContexts.add(bindingContext)
|
||||
|
||||
checkResolveCall(bindingContext.get(BindingContext.LOOP_RANGE_ITERATOR_RESOLVED_CALL, expression.loopRange))
|
||||
checkResolveCall(bindingContext.get(BindingContext.LOOP_RANGE_HAS_NEXT_RESOLVED_CALL, expression.loopRange))
|
||||
checkResolveCall(bindingContext.get(BindingContext.LOOP_RANGE_NEXT_RESOLVED_CALL, expression.loopRange))
|
||||
}
|
||||
|
||||
private fun checkResolveCall(resolvedCall: ResolvedCall<*>?) {
|
||||
if (resolvedCall == null) return
|
||||
|
||||
val descriptor = resolvedCall.resultingDescriptor
|
||||
if (descriptor is DeserializedSimpleFunctionDescriptor) return
|
||||
|
||||
isAdditionalResolveNeededForDescriptor(descriptor)
|
||||
|
||||
if (descriptor is PropertyDescriptor) {
|
||||
for (accessor in descriptor.accessors) {
|
||||
isAdditionalResolveNeededForDescriptor(accessor)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isAdditionalResolveNeededForDescriptor(descriptor: CallableDescriptor) {
|
||||
if (!(InlineUtil.isInline(descriptor) && (analyzeInlineFunctions || hasReifiedTypeParameters(descriptor)))) {
|
||||
return
|
||||
}
|
||||
|
||||
val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor)
|
||||
if (declaration != null && declaration is KtDeclarationWithBody && !analyzedElements.contains(declaration)) {
|
||||
declarationsWithBody.add(declaration)
|
||||
return
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
analyzedElements.add(element)
|
||||
|
||||
if (!declarationsWithBody.isEmpty() && deep < 10) {
|
||||
for (inlineFunction in declarationsWithBody) {
|
||||
val body = inlineFunction.bodyExpression
|
||||
if (body != null) {
|
||||
innerContexts.add(
|
||||
analyzeElementWithInline(
|
||||
resolutionFacade,
|
||||
inlineFunction,
|
||||
deep + 1,
|
||||
analyzedElements,
|
||||
analyzeInlineFunctions
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
analyzedElements.addAll(declarationsWithBody)
|
||||
}
|
||||
|
||||
return CompositeBindingContext.create(innerContexts)
|
||||
}
|
||||
|
||||
private fun hasReifiedTypeParameters(descriptor: CallableDescriptor): Boolean {
|
||||
return descriptor.typeParameters.any { it.isReified }
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
|
||||
abstract class DelegateSourcePosition(private var delegate: SourcePosition) : SourcePosition() {
|
||||
override fun getFile(): PsiFile = delegate.file
|
||||
override fun getElementAt(): PsiElement? = delegate.elementAt
|
||||
override fun getLine(): Int = delegate.line
|
||||
override fun getOffset(): Int = delegate.offset
|
||||
|
||||
override fun openEditor(requestFocus: Boolean): Editor = delegate.openEditor(requestFocus)
|
||||
|
||||
override fun canNavigate() = delegate.canNavigate()
|
||||
override fun canNavigateToSource() = delegate.canNavigateToSource()
|
||||
|
||||
override fun navigate(requestFocus: Boolean) {
|
||||
delegate.navigate(requestFocus)
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = delegate.hashCode()
|
||||
override fun equals(other: Any?) = delegate == other
|
||||
|
||||
override fun toString() = "DSP($delegate)"
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import org.jetbrains.kotlin.diagnostics.Diagnostic
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import org.jetbrains.kotlin.resolve.diagnostics.DiagnosticSuppressor
|
||||
|
||||
class DiagnosticSuppressorForDebugger : DiagnosticSuppressor {
|
||||
override fun isSuppressed(diagnostic: Diagnostic): Boolean {
|
||||
val element = diagnostic.psiElement
|
||||
val containingFile = element.containingFile
|
||||
|
||||
if (containingFile is KtCodeFragment) {
|
||||
val diagnosticFactory = diagnostic.factory
|
||||
return diagnosticFactory == Errors.UNSAFE_CALL
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1,359 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilderMode
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.debugger.FileRankingCalculator.Ranking.Companion.LOW
|
||||
import org.jetbrains.kotlin.idea.debugger.FileRankingCalculator.Ranking.Companion.MAJOR
|
||||
import org.jetbrains.kotlin.idea.debugger.FileRankingCalculator.Ranking.Companion.MINOR
|
||||
import org.jetbrains.kotlin.idea.debugger.FileRankingCalculator.Ranking.Companion.NORMAL
|
||||
import org.jetbrains.kotlin.idea.debugger.FileRankingCalculator.Ranking.Companion.ZERO
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypes2
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypes3
|
||||
import org.jetbrains.kotlin.psi.psiUtil.isAncestor
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.varargParameterPosition
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.utils.keysToMap
|
||||
import kotlin.jvm.internal.FunctionBase
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
|
||||
object FileRankingCalculatorForIde : FileRankingCalculator() {
|
||||
override fun analyze(element: KtElement) = element.analyze(BodyResolveMode.PARTIAL)
|
||||
}
|
||||
|
||||
abstract class FileRankingCalculator(private val checkClassFqName: Boolean = true) {
|
||||
abstract fun analyze(element: KtElement): BindingContext
|
||||
|
||||
fun findMostAppropriateSource(files: Collection<KtFile>, location: Location): KtFile {
|
||||
val fileWithRankings: Map<KtFile, Int> = rankFiles(files, location)
|
||||
val fileWithMaxScore = fileWithRankings.maxBy { it.value }!!
|
||||
return fileWithMaxScore.key
|
||||
}
|
||||
|
||||
fun rankFiles(files: Collection<KtFile>, location: Location): Map<KtFile, Int> {
|
||||
assert(files.isNotEmpty())
|
||||
return files.keysToMap { fileRankingSafe(it, location).value }
|
||||
}
|
||||
|
||||
private class Ranking(val value: Int) : Comparable<Ranking> {
|
||||
companion object {
|
||||
val LOW = Ranking(-1000)
|
||||
val ZERO = Ranking(0)
|
||||
val MINOR = Ranking(1)
|
||||
val NORMAL = Ranking(5)
|
||||
val MAJOR = Ranking(10)
|
||||
|
||||
fun minor(condition: Boolean) = if (condition) MINOR else ZERO
|
||||
}
|
||||
|
||||
operator fun unaryMinus() = Ranking(-value)
|
||||
operator fun plus(other: Ranking) = Ranking(value + other.value)
|
||||
override fun compareTo(other: Ranking) = this.value - other.value
|
||||
override fun toString() = value.toString()
|
||||
}
|
||||
|
||||
private fun collect(vararg conditions: Any): Ranking {
|
||||
return conditions
|
||||
.map { condition ->
|
||||
when (condition) {
|
||||
is Boolean -> Ranking.minor(condition)
|
||||
is Int -> Ranking(condition)
|
||||
is Ranking -> condition
|
||||
else -> error("Invalid condition type ${condition.javaClass.name}")
|
||||
}
|
||||
}.fold(ZERO) { sum, r -> sum + r }
|
||||
}
|
||||
|
||||
private fun rankingForClass(clazz: KtClassOrObject, fqName: String, virtualMachine: VirtualMachine): Ranking {
|
||||
val bindingContext = analyze(clazz)
|
||||
val descriptor = bindingContext[BindingContext.CLASS, clazz] ?: return ZERO
|
||||
|
||||
val jdiType = virtualMachine.classesByName(fqName).firstOrNull() ?: run {
|
||||
// Check at least the class name if not found
|
||||
return rankingForClassName(fqName, descriptor, bindingContext)
|
||||
}
|
||||
|
||||
return rankingForClass(clazz, jdiType)
|
||||
}
|
||||
|
||||
private fun rankingForClass(clazz: KtClassOrObject, type: ReferenceType): Ranking {
|
||||
val bindingContext = analyze(clazz)
|
||||
val descriptor = bindingContext[BindingContext.CLASS, clazz] ?: return ZERO
|
||||
|
||||
return collect(
|
||||
rankingForClassName(type.name(), descriptor, bindingContext),
|
||||
Ranking.minor(type.isAbstract && descriptor.modality == Modality.ABSTRACT),
|
||||
Ranking.minor(type.isFinal && descriptor.modality == Modality.FINAL),
|
||||
Ranking.minor(type.isStatic && !descriptor.isInner),
|
||||
rankingForVisibility(descriptor, type)
|
||||
)
|
||||
}
|
||||
|
||||
private fun rankingForClassName(fqName: String, descriptor: ClassDescriptor, bindingContext: BindingContext): Ranking {
|
||||
if (DescriptorUtils.isLocal(descriptor)) return Ranking.ZERO
|
||||
|
||||
val expectedFqName = makeTypeMapper(bindingContext).mapType(descriptor).className
|
||||
return when {
|
||||
checkClassFqName -> if (expectedFqName == fqName) MAJOR else LOW
|
||||
else -> if (expectedFqName.simpleName() == fqName.simpleName()) MAJOR else LOW
|
||||
}
|
||||
}
|
||||
|
||||
private fun rankingForMethod(function: KtFunction, method: Method): Ranking {
|
||||
val bindingContext = analyze(function)
|
||||
val descriptor = bindingContext[BindingContext.DECLARATION_TO_DESCRIPTOR, function] as? CallableMemberDescriptor ?: return ZERO
|
||||
|
||||
if (function !is KtConstructor<*> && method.name() != descriptor.name.asString())
|
||||
return LOW
|
||||
|
||||
return collect(
|
||||
method.isConstructor && function is KtConstructor<*>,
|
||||
method.isAbstract && descriptor.modality == Modality.ABSTRACT,
|
||||
method.isFinal && descriptor.modality == Modality.FINAL,
|
||||
method.isVarArgs && descriptor.varargParameterPosition() >= 0,
|
||||
rankingForVisibility(descriptor, method),
|
||||
descriptor.valueParameters.size == (method.safeArguments()?.size ?: 0)
|
||||
)
|
||||
}
|
||||
|
||||
private fun rankingForAccessor(accessor: KtPropertyAccessor, method: Method): Ranking {
|
||||
val methodName = method.name()
|
||||
val expectedPropertyName = accessor.property.name ?: return ZERO
|
||||
|
||||
if (accessor.isSetter) {
|
||||
if (!methodName.startsWith("set") || method.returnType() !is VoidType || method.argumentTypes().size != 1)
|
||||
return -MAJOR
|
||||
}
|
||||
|
||||
if (accessor.isGetter) {
|
||||
if (!methodName.startsWith("get") && !methodName.startsWith("is"))
|
||||
return -MAJOR
|
||||
else if (method.returnType() is VoidType || method.argumentTypes().isNotEmpty())
|
||||
return -NORMAL
|
||||
}
|
||||
|
||||
val actualPropertyName = getPropertyName(methodName, accessor.isSetter)
|
||||
return if (expectedPropertyName == actualPropertyName) NORMAL else -NORMAL
|
||||
}
|
||||
|
||||
private fun getPropertyName(accessorMethodName: String, isSetter: Boolean): String {
|
||||
if (isSetter) {
|
||||
return accessorMethodName.drop(3)
|
||||
}
|
||||
|
||||
return accessorMethodName.drop(if (accessorMethodName.startsWith("is")) 2 else 3)
|
||||
}
|
||||
|
||||
private fun rankingForProperty(property: KtProperty, method: Method): Ranking {
|
||||
val methodName = method.name()
|
||||
val propertyName = property.name ?: return ZERO
|
||||
|
||||
if (property.isTopLevel && method.name() == "<clinit>") {
|
||||
// For top-level property initializers
|
||||
return MINOR
|
||||
}
|
||||
|
||||
if (!methodName.startsWith("get") && !methodName.startsWith("set"))
|
||||
return -MAJOR
|
||||
|
||||
// boolean is
|
||||
return if (methodName.drop(3) == propertyName.capitalize()) MAJOR else -NORMAL
|
||||
}
|
||||
|
||||
private fun rankingForVisibility(descriptor: DeclarationDescriptorWithVisibility, accessible: Accessible): Ranking {
|
||||
return collect(
|
||||
accessible.isPublic && descriptor.visibility == Visibilities.PUBLIC,
|
||||
accessible.isProtected && descriptor.visibility == Visibilities.PROTECTED,
|
||||
accessible.isPrivate && descriptor.visibility == Visibilities.PRIVATE
|
||||
)
|
||||
}
|
||||
|
||||
private fun fileRankingSafe(file: KtFile, location: Location): Ranking {
|
||||
return try {
|
||||
fileRanking(file, location)
|
||||
} catch (e: ClassNotLoadedException) {
|
||||
LOG.error("ClassNotLoadedException should never happen in FileRankingCalculator", e)
|
||||
ZERO
|
||||
} catch (e: AbsentInformationException) {
|
||||
ZERO
|
||||
} catch (e: InternalException) {
|
||||
ZERO
|
||||
} catch (e: ProcessCanceledException) {
|
||||
throw e
|
||||
} catch (e: RuntimeException) {
|
||||
LOG.error("Exception during Kotlin sources ranking", e)
|
||||
ZERO
|
||||
}
|
||||
}
|
||||
|
||||
private fun fileRanking(file: KtFile, location: Location): Ranking {
|
||||
val locationLineNumber = location.lineNumber() - 1
|
||||
val lineStartOffset = file.getLineStartOffset(locationLineNumber) ?: return LOW
|
||||
val elementAt = file.findElementAt(lineStartOffset) ?: return ZERO
|
||||
|
||||
var overallRanking = ZERO
|
||||
val method = location.method()
|
||||
|
||||
if (method.isLambda()) {
|
||||
val (className, methodName) = method.getContainingClassAndMethodNameForLambda() ?: return ZERO
|
||||
if (method.isBridge && method.isSynthetic) {
|
||||
// It might be a static lambda field accessor
|
||||
val containingClass = elementAt.getParentOfType<KtClassOrObject>(false) ?: return LOW
|
||||
return rankingForClass(containingClass, className, location.virtualMachine())
|
||||
} else {
|
||||
val containingFunctionLiteral = findFunctionLiteralOnLine(elementAt) ?: return LOW
|
||||
|
||||
val containingCallable = findNonLocalCallableParent(containingFunctionLiteral) ?: return LOW
|
||||
when (containingCallable) {
|
||||
is KtFunction -> if (containingCallable.name == methodName) overallRanking += MAJOR
|
||||
is KtProperty -> if (containingCallable.name == methodName) overallRanking += MAJOR
|
||||
is KtPropertyAccessor -> if (containingCallable.property.name == methodName) overallRanking += MAJOR
|
||||
}
|
||||
|
||||
val containingClass = containingCallable.getParentOfType<KtClassOrObject>(false)
|
||||
if (containingClass != null) {
|
||||
overallRanking += rankingForClass(containingClass, className, location.virtualMachine())
|
||||
}
|
||||
|
||||
return overallRanking
|
||||
}
|
||||
}
|
||||
|
||||
// TODO support <clinit>
|
||||
if (method.name() == "<init>") {
|
||||
val containingClass = elementAt.getParentOfType<KtClassOrObject>(false) ?: return LOW
|
||||
val constructorOrInitializer =
|
||||
elementAt.getParentOfTypes2<KtConstructor<*>, KtClassInitializer>()?.takeIf { containingClass.isAncestor(it) }
|
||||
?: containingClass.primaryConstructor?.takeIf { it.getLine() == containingClass.getLine() }
|
||||
|
||||
if (constructorOrInitializer == null
|
||||
&& locationLineNumber < containingClass.getLine()
|
||||
&& locationLineNumber > containingClass.lastChild.getLine()
|
||||
) {
|
||||
return LOW
|
||||
}
|
||||
|
||||
overallRanking += rankingForClass(containingClass, location.declaringType())
|
||||
|
||||
if (constructorOrInitializer is KtConstructor<*>)
|
||||
overallRanking += rankingForMethod(constructorOrInitializer, method)
|
||||
} else {
|
||||
val callable = findNonLocalCallableParent(elementAt) ?: return LOW
|
||||
overallRanking += when (callable) {
|
||||
is KtFunction -> rankingForMethod(callable, method)
|
||||
is KtPropertyAccessor -> rankingForAccessor(callable, method)
|
||||
is KtProperty -> rankingForProperty(callable, method)
|
||||
else -> return LOW
|
||||
}
|
||||
|
||||
val containingClass = elementAt.getParentOfType<KtClassOrObject>(false)
|
||||
if (containingClass != null)
|
||||
overallRanking += rankingForClass(containingClass, location.declaringType())
|
||||
}
|
||||
|
||||
return overallRanking
|
||||
}
|
||||
|
||||
private fun findFunctionLiteralOnLine(element: PsiElement): KtFunctionLiteral? {
|
||||
val literal = element.getParentOfType<KtFunctionLiteral>(false)
|
||||
if (literal != null) {
|
||||
return literal
|
||||
}
|
||||
|
||||
val callExpression = element.getParentOfType<KtCallExpression>(false) ?: return null
|
||||
|
||||
for (lambdaArgument in callExpression.lambdaArguments) {
|
||||
if (element.getLine() == lambdaArgument.getLine()) {
|
||||
val functionLiteral = lambdaArgument.getLambdaExpression()?.functionLiteral
|
||||
if (functionLiteral != null) {
|
||||
return functionLiteral
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private tailrec fun findNonLocalCallableParent(element: PsiElement): PsiElement? {
|
||||
fun PsiElement.isCallableDeclaration() = this is KtProperty || this is KtFunction || this is KtAnonymousInitializer
|
||||
|
||||
// org.jetbrains.kotlin.psi.KtPsiUtil.isLocal
|
||||
fun PsiElement.isLocalDeclaration(): Boolean {
|
||||
val containingDeclaration = getParentOfType<KtDeclaration>(true)
|
||||
return containingDeclaration is KtCallableDeclaration || containingDeclaration is KtPropertyAccessor
|
||||
}
|
||||
|
||||
if (element.isCallableDeclaration() && !element.isLocalDeclaration()) {
|
||||
return element
|
||||
}
|
||||
|
||||
val containingCallable = element.getParentOfTypes3<KtProperty, KtFunction, KtAnonymousInitializer>()
|
||||
?: return null
|
||||
|
||||
if (containingCallable.isLocalDeclaration()) {
|
||||
return findNonLocalCallableParent(containingCallable)
|
||||
}
|
||||
|
||||
return containingCallable
|
||||
}
|
||||
|
||||
private fun Method.getContainingClassAndMethodNameForLambda(): Pair<String, String>? {
|
||||
// TODO this breaks nested classes
|
||||
val declaringClass = declaringType() as ClassType
|
||||
val (className, methodName) = declaringClass.name().split('$', limit = 3)
|
||||
.takeIf { it.size == 3 }
|
||||
?: return null
|
||||
|
||||
return Pair(className, methodName)
|
||||
}
|
||||
|
||||
private fun Method.isLambda(): Boolean {
|
||||
val declaringClass = declaringType() as? ClassType ?: return false
|
||||
|
||||
tailrec fun ClassType.isLambdaClass(): Boolean {
|
||||
if (interfaces().any { it.name() == FunctionBase::class.java.name }) {
|
||||
return true
|
||||
}
|
||||
|
||||
val superClass = superclass() ?: return false
|
||||
return superClass.isLambdaClass()
|
||||
}
|
||||
|
||||
return declaringClass.superclass().isLambdaClass()
|
||||
}
|
||||
|
||||
private fun makeTypeMapper(bindingContext: BindingContext): KotlinTypeMapper {
|
||||
return KotlinTypeMapper(
|
||||
bindingContext,
|
||||
ClassBuilderMode.LIGHT_CLASSES,
|
||||
"debugger",
|
||||
KotlinTypeMapper.LANGUAGE_VERSION_SETTINGS_DEFAULT // TODO use proper LanguageVersionSettings
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LOG = Logger.getInstance("FileRankingCalculator")
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.simpleName() = substringAfterLast('.').substringAfterLast('$')
|
||||
|
||||
private fun PsiElement.getLine(): Int {
|
||||
return DiagnosticUtils.getLineAndColumnInPsiFile(containingFile, textRange).line
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.ui.MessageType
|
||||
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.debugger.DebuggerClassNameProvider.Companion.getRelevantElement
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ComputedClassNames
|
||||
import org.jetbrains.kotlin.idea.search.usagesSearch.isImportUsage
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
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.inline.InlineUtil
|
||||
|
||||
class InlineCallableUsagesSearcher(private val myDebugProcess: DebugProcess) {
|
||||
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 declarationName = runReadAction { declaration.name }
|
||||
|
||||
val task = Runnable {
|
||||
ReferencesSearch.search(declaration, getScopeForInlineDeclarationUsages(declaration)).forEach {
|
||||
if (!runReadAction { it.isImportUsage() }) {
|
||||
val usage = (it.element as? KtElement)?.let(::getRelevantElement)
|
||||
if (usage != null && !runReadAction { declaration.isAncestor(usage) }) {
|
||||
searchResult.add(usage)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var isSuccess = true
|
||||
val applicationEx = ApplicationManagerEx.getApplicationEx()
|
||||
if (applicationEx.isDispatchThread) {
|
||||
isSuccess = ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
task,
|
||||
"Compute class names for declaration $declarationName",
|
||||
true,
|
||||
myDebugProcess.project)
|
||||
}
|
||||
else {
|
||||
ProgressManager.getInstance().runProcess(task, EmptyProgressIndicator())
|
||||
}
|
||||
|
||||
if (!isSuccess) {
|
||||
XDebugSessionImpl.NOTIFICATION_GROUP.createNotification(
|
||||
"Debugger can skip some executions of $declarationName because the computation of class names was interrupted",
|
||||
MessageType.WARNING
|
||||
).notify(myDebugProcess.project)
|
||||
}
|
||||
|
||||
val results = searchResult.map { transformer(it) }
|
||||
return ComputedClassNames(results.flatMap { it.classNames }, shouldBeCached = results.all { it.shouldBeCached })
|
||||
}
|
||||
}
|
||||
|
||||
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 }
|
||||
return if (virtualFile != null && ProjectRootsUtil.isLibraryFile(myDebugProcess.project, virtualFile)) {
|
||||
myDebugProcess.searchScope.uniteWith(
|
||||
KotlinSourceFilterScope.librarySources(GlobalSearchScope.allScope(myDebugProcess.project), myDebugProcess.project))
|
||||
}
|
||||
else {
|
||||
myDebugProcess.searchScope
|
||||
}
|
||||
}
|
||||
}
|
||||
-170
@@ -1,170 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.ide.util.ModuleRendererFactory
|
||||
import com.intellij.openapi.fileEditor.FileEditor
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.ComboBox
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.ui.EditorNotificationPanel
|
||||
import com.intellij.ui.EditorNotifications
|
||||
import com.intellij.ui.components.JBList
|
||||
import com.intellij.xdebugger.XDebuggerManager
|
||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
|
||||
import org.jetbrains.kotlin.idea.stubindex.PackageIndexUtil.findFilesWithExactPackage
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
|
||||
class KotlinAlternativeSourceNotificationProvider(private val myProject: Project) : EditorNotifications.Provider<EditorNotificationPanel>() {
|
||||
override fun getKey(): Key<EditorNotificationPanel> {
|
||||
return KEY
|
||||
}
|
||||
|
||||
override fun createNotificationPanel(file: VirtualFile, fileEditor: FileEditor): EditorNotificationPanel? {
|
||||
if (!DebuggerSettings.getInstance().SHOW_ALTERNATIVE_SOURCE) {
|
||||
return null
|
||||
}
|
||||
|
||||
val session = XDebuggerManager.getInstance(myProject).currentSession
|
||||
if (session == null) {
|
||||
FILE_PROCESSED_KEY.set(file, null)
|
||||
return null
|
||||
}
|
||||
|
||||
val position = session.currentPosition
|
||||
if (file != position?.file) {
|
||||
FILE_PROCESSED_KEY.set(file, null)
|
||||
return null
|
||||
}
|
||||
|
||||
if (DumbService.getInstance(myProject).isDumb) return null
|
||||
|
||||
val ktFile = PsiManager.getInstance(myProject).findFile(file) as? KtFile ?: return null
|
||||
|
||||
val packageFqName = ktFile.packageFqName
|
||||
val fileName = ktFile.name
|
||||
|
||||
val alternativeKtFiles = findFilesWithExactPackage(packageFqName, GlobalSearchScope.allScope(myProject), myProject).filterTo(HashSet()) {
|
||||
it.name == fileName
|
||||
}
|
||||
|
||||
FILE_PROCESSED_KEY.set(file, true)
|
||||
|
||||
if (alternativeKtFiles.size <= 1) {
|
||||
return null
|
||||
}
|
||||
|
||||
val currentFirstAlternatives: Collection<KtFile> = listOf(ktFile) + alternativeKtFiles.filter { it != ktFile }
|
||||
|
||||
val frame = session.currentStackFrame
|
||||
val locationDeclName: String? = when (frame) {
|
||||
is JavaStackFrame -> {
|
||||
val location = frame.descriptor.location
|
||||
location?.declaringType()?.name()
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
return AlternativeSourceNotificationPanel(currentFirstAlternatives, myProject, file, locationDeclName)
|
||||
}
|
||||
|
||||
private class AlternativeSourceNotificationPanel(
|
||||
alternatives: Collection<KtFile>,
|
||||
project: Project,
|
||||
file: VirtualFile,
|
||||
locationDeclName: String?
|
||||
) : EditorNotificationPanel() {
|
||||
private class ComboBoxFileElement(val ktFile: KtFile) {
|
||||
private val label: String by lazy(LazyThreadSafetyMode.NONE) {
|
||||
val factory = ModuleRendererFactory.findInstance(ktFile)
|
||||
val moduleRenderer = factory.moduleRenderer
|
||||
moduleRenderer.getListCellRendererComponent(ourDummyList, ktFile, 1, false, false)
|
||||
moduleRenderer.text ?: ""
|
||||
}
|
||||
|
||||
override fun toString(): String = label
|
||||
|
||||
companion object {
|
||||
private val ourDummyList = JBList<KtFile>()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
setText("Alternative source available for file ${file.name}")
|
||||
|
||||
val items = alternatives.map { ComboBoxFileElement(it) }
|
||||
myLinksPanel.add(
|
||||
ComboBox<ComboBoxFileElement>(items.toTypedArray()).apply {
|
||||
addActionListener {
|
||||
val context = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val session = context.debuggerSession
|
||||
val ktFile = (selectedItem as ComboBoxFileElement).ktFile
|
||||
val vFile = ktFile.containingFile.virtualFile
|
||||
|
||||
when {
|
||||
session != null && vFile != null ->
|
||||
session.process.managerThread.schedule(object : DebuggerCommandImpl() {
|
||||
override fun action() {
|
||||
if (!StringUtil.isEmpty(locationDeclName)) {
|
||||
DebuggerUtilsEx.setAlternativeSourceUrl(locationDeclName, vFile.url, project)
|
||||
}
|
||||
|
||||
DebuggerUIUtil.invokeLater {
|
||||
FileEditorManager.getInstance(project).closeFile(file)
|
||||
session.refresh(true)
|
||||
}
|
||||
}
|
||||
})
|
||||
else -> {
|
||||
FileEditorManager.getInstance(project).closeFile(file)
|
||||
ktFile.navigate(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
createActionLabel("Disable") {
|
||||
DebuggerSettings.getInstance().SHOW_ALTERNATIVE_SOURCE = false
|
||||
FILE_PROCESSED_KEY.set(file, null)
|
||||
val fileEditorManager = FileEditorManager.getInstance(project)
|
||||
val editor = fileEditorManager.getSelectedEditor(file)
|
||||
if (editor != null) {
|
||||
fileEditorManager.removeTopComponent(editor, this)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val KEY = Key.create<EditorNotificationPanel>("KotlinAlternativeSource")
|
||||
|
||||
// FIXME: Share AlternativeSourceNotificationProvider.FILE_PROCESSED_KEY
|
||||
@Suppress("UNCHECKED_CAST", "DEPRECATION")
|
||||
private val FILE_PROCESSED_KEY = Key.findKeyByName("AlternativeSourceCheckDone") as Key<Boolean>
|
||||
}
|
||||
}
|
||||
-191
@@ -1,191 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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
|
||||
|
||||
import com.intellij.debugger.DebuggerContext
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.JavaValue
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.jdi.GeneratedLocation
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
||||
import com.intellij.xdebugger.frame.XNamedValue
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Companion.SUSPEND_LAMBDA_CLASSES
|
||||
|
||||
class KotlinCoroutinesAsyncStackTraceProvider : KotlinCoroutinesAsyncStackTraceProviderBase {
|
||||
private companion object {
|
||||
const val DEBUG_METADATA_KT = "kotlin.coroutines.jvm.internal.DebugMetadataKt"
|
||||
|
||||
tailrec fun findBaseContinuationSuperSupertype(type: ClassType): ClassType? {
|
||||
if (type.name() == "kotlin.coroutines.jvm.internal.BaseContinuationImpl") {
|
||||
return type
|
||||
}
|
||||
|
||||
return findBaseContinuationSuperSupertype(type.superclass() ?: return null)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
|
||||
return getAsyncStackTrace(stackFrame.stackFrameProxy, suspendContext)
|
||||
}
|
||||
|
||||
fun getAsyncStackTrace(frameProxy: StackFrameProxyImpl, suspendContext: SuspendContextImpl): List<StackFrameItem>? {
|
||||
val location = frameProxy.location()
|
||||
if (!location.isInKotlinSources()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val method = location.safeMethod() ?: return null
|
||||
val threadReference = frameProxy.threadProxy().threadReference
|
||||
|
||||
if (threadReference == null || !threadReference.isSuspended || !suspendContext.debugProcess.canRunEvaluation) {
|
||||
return null
|
||||
}
|
||||
|
||||
val evaluationContext = EvaluationContextImpl(suspendContext, frameProxy)
|
||||
val context = ExecutionContext(evaluationContext, frameProxy)
|
||||
|
||||
// DebugMetadataKt not found, probably old kotlin-stdlib version
|
||||
val debugMetadataKtType = context.findClassSafe(DEBUG_METADATA_KT) ?: return null
|
||||
|
||||
val asyncContext = AsyncStackTraceContext(context, method, debugMetadataKtType)
|
||||
return asyncContext.getAsyncStackTraceForSuspendLambda() ?: asyncContext.getAsyncStackTraceForSuspendFunction()
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendLambda(): List<StackFrameItem>? {
|
||||
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
|
||||
return null
|
||||
}
|
||||
|
||||
val thisObject = context.frameProxy.thisObject() ?: return null
|
||||
val thisType = thisObject.referenceType()
|
||||
|
||||
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
|
||||
return null
|
||||
}
|
||||
|
||||
return collectFrames(thisObject)
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getAsyncStackTraceForSuspendFunction(): List<StackFrameItem>? {
|
||||
if ("Lkotlin/coroutines/Continuation;)" !in method.signature()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val frameProxy = context.frameProxy
|
||||
val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
|
||||
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
|
||||
|
||||
return collectFrames(continuation)
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.collectFrames(continuation: ObjectReference): List<StackFrameItem>? {
|
||||
val frames = mutableListOf<StackFrameItem>()
|
||||
collectFramesRecursively(continuation, frames)
|
||||
return frames
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.collectFramesRecursively(continuation: ObjectReference, consumer: MutableList<StackFrameItem>) {
|
||||
val continuationType = continuation.referenceType() as? ClassType ?: return
|
||||
val baseContinuationSupertype = findBaseContinuationSuperSupertype(continuationType) ?: return
|
||||
|
||||
val location = getLocation(continuation)
|
||||
val spilledVariables = getSpilledVariables(continuation) ?: emptyList()
|
||||
|
||||
if (location != null) {
|
||||
consumer += StackFrameItem(location, spilledVariables)
|
||||
}
|
||||
|
||||
val completionField = baseContinuationSupertype.fieldByName("completion") ?: return
|
||||
val completion = continuation.getValue(completionField) as? ObjectReference ?: return
|
||||
collectFramesRecursively(completion, consumer)
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getLocation(continuation: ObjectReference): Location? {
|
||||
val getStackTraceElementMethod = debugMetadataKtType.methodsByName(
|
||||
"getStackTraceElement",
|
||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)Ljava/lang/StackTraceElement;"
|
||||
).firstOrNull() ?: return null
|
||||
|
||||
val args = listOf(continuation)
|
||||
|
||||
val stackTraceElement = context.invokeMethod(debugMetadataKtType, getStackTraceElementMethod, args) as? ObjectReference
|
||||
?: return null
|
||||
|
||||
val stackTraceElementType = stackTraceElement.referenceType().takeIf { it.name() == StackTraceElement::class.java.name }
|
||||
?: return null
|
||||
|
||||
fun getValue(name: String, desc: String): Value? {
|
||||
val method = stackTraceElementType.methodsByName(name, desc).single()
|
||||
return context.invokeMethod(stackTraceElement, method, emptyList())
|
||||
}
|
||||
|
||||
val className = (getValue("getClassName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||
val methodName = (getValue("getMethodName", "()Ljava/lang/String;") as? StringReference)?.value() ?: return null
|
||||
val lineNumber = (getValue("getLineNumber", "()I") as? IntegerValue)?.value()?.takeIf { it >= 0 } ?: return null
|
||||
|
||||
val locationClass = context.findClassSafe(className) ?: return null
|
||||
return GeneratedLocation(context.debugProcess, locationClass, methodName, lineNumber)
|
||||
}
|
||||
|
||||
private fun AsyncStackTraceContext.getSpilledVariables(continuation: ObjectReference): List<XNamedValue>? {
|
||||
val getSpilledVariableFieldMappingMethod = debugMetadataKtType.methodsByName(
|
||||
"getSpilledVariableFieldMapping",
|
||||
"(Lkotlin/coroutines/jvm/internal/BaseContinuationImpl;)[Ljava/lang/String;"
|
||||
).firstOrNull() ?: return null
|
||||
|
||||
val args = listOf(continuation)
|
||||
|
||||
val rawSpilledVariables = context.invokeMethod(debugMetadataKtType, getSpilledVariableFieldMappingMethod, args) as? ArrayReference
|
||||
?: return null
|
||||
|
||||
val length = rawSpilledVariables.length() / 2
|
||||
val spilledVariables = ArrayList<XNamedValue>(length)
|
||||
|
||||
for (index in 0 until length) {
|
||||
val fieldName = (rawSpilledVariables.getValue(2 * index) as? StringReference)?.value() ?: continue
|
||||
val variableName = (rawSpilledVariables.getValue(2 * index + 1) as? StringReference)?.value() ?: continue
|
||||
val field = continuation.referenceType().fieldByName(fieldName) ?: continue
|
||||
|
||||
val valueDescriptor = object : ValueDescriptorImpl(context.project) {
|
||||
override fun calcValueName() = variableName
|
||||
override fun calcValue(evaluationContext: EvaluationContextImpl?) = continuation.getValue(field)
|
||||
override fun getDescriptorEvaluation(context: DebuggerContext?) =
|
||||
throw EvaluateException("Spilled variable evaluation is not supported")
|
||||
}
|
||||
|
||||
spilledVariables += JavaValue.create(
|
||||
null,
|
||||
valueDescriptor,
|
||||
context.evaluationContext,
|
||||
context.debugProcess.xdebugProcess!!.nodeManager,
|
||||
false
|
||||
)
|
||||
}
|
||||
|
||||
return spilledVariables
|
||||
}
|
||||
|
||||
private fun ExecutionContext.findClassSafe(className: String): ClassType? {
|
||||
return try {
|
||||
findClass(className) as? ClassType
|
||||
} catch (e: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private class AsyncStackTraceContext(
|
||||
val context: ExecutionContext,
|
||||
val method: Method,
|
||||
val debugMetadataKtType: ClassType
|
||||
)
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.intellij.debugger.engine.AsyncStackTraceProvider
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
|
||||
interface KotlinCoroutinesAsyncStackTraceProviderBase : AsyncStackTraceProvider {
|
||||
override fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>?
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.memory.utils.StackFrameItem
|
||||
|
||||
interface KotlinCoroutinesAsyncStackTraceProviderBase {
|
||||
fun getAsyncStackTrace(stackFrame: JavaStackFrame, suspendContext: SuspendContextImpl): List<StackFrameItem>?
|
||||
}
|
||||
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2018 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
|
||||
|
||||
|
||||
import com.intellij.openapi.components.State
|
||||
import com.intellij.openapi.components.Storage
|
||||
import com.intellij.openapi.options.Configurable
|
||||
import com.intellij.openapi.options.SimpleConfigurable
|
||||
import com.intellij.openapi.util.Getter
|
||||
import com.intellij.util.xmlb.XmlSerializerUtil
|
||||
import com.intellij.xdebugger.XDebuggerUtil
|
||||
import com.intellij.xdebugger.settings.DebuggerSettingsCategory
|
||||
import com.intellij.xdebugger.settings.XDebuggerSettings
|
||||
import org.jetbrains.kotlin.idea.debugger.stepping.KotlinSteppingConfigurableUi
|
||||
|
||||
@State(name = "KotlinDebuggerSettings", storages = arrayOf(Storage("kotlin_debug.xml")))
|
||||
class KotlinDebuggerSettings : XDebuggerSettings<KotlinDebuggerSettings>("kotlin_debugger"), Getter<KotlinDebuggerSettings> {
|
||||
var DEBUG_RENDER_DELEGATED_PROPERTIES: Boolean = true
|
||||
var DEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES: Boolean = true
|
||||
var DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED: Boolean = false
|
||||
|
||||
companion object {
|
||||
fun getInstance(): KotlinDebuggerSettings {
|
||||
return XDebuggerUtil.getInstance()?.getDebuggerSettings(KotlinDebuggerSettings::class.java)!!
|
||||
}
|
||||
}
|
||||
|
||||
override fun createConfigurables(category: DebuggerSettingsCategory): Collection<Configurable?> {
|
||||
return when (category) {
|
||||
DebuggerSettingsCategory.STEPPING ->
|
||||
listOf(SimpleConfigurable.create(
|
||||
"reference.idesettings.debugger.kotlin.stepping",
|
||||
"Kotlin",
|
||||
KotlinSteppingConfigurableUi::class.java,
|
||||
this))
|
||||
DebuggerSettingsCategory.DATA_VIEWS ->
|
||||
listOf(SimpleConfigurable.create(
|
||||
"reference.idesettings.debugger.kotlin.data.view",
|
||||
"Kotlin",
|
||||
KotlinDelegatedPropertyRendererConfigurableUi::class.java,
|
||||
this))
|
||||
else -> listOf()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getState() = this
|
||||
override fun get() = this
|
||||
|
||||
override fun loadState(state: KotlinDebuggerSettings) {
|
||||
XmlSerializerUtil.copyBean<KotlinDebuggerSettings>(state, this)
|
||||
}
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.debugger.KotlinDelegatedPropertyRendererConfigurableUi">
|
||||
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="99d36" class="javax.swing.JCheckBox" binding="renderDelegatedProperties">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<selected value="false"/>
|
||||
<text resource-bundle="org/jetbrains/kotlin/idea/KotlinBundle" key="debugger.data.view.delegated.properties"/>
|
||||
</properties>
|
||||
</component>
|
||||
<vspacer id="c37da">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
-50
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger;
|
||||
|
||||
|
||||
import com.intellij.openapi.options.ConfigurableUi;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class KotlinDelegatedPropertyRendererConfigurableUi implements ConfigurableUi<KotlinDebuggerSettings> {
|
||||
private JCheckBox renderDelegatedProperties;
|
||||
private JPanel myPanel;
|
||||
|
||||
@Override
|
||||
public void reset(@NotNull KotlinDebuggerSettings settings) {
|
||||
boolean flag = settings.getDEBUG_RENDER_DELEGATED_PROPERTIES();
|
||||
renderDelegatedProperties.setSelected(flag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isModified(@NotNull KotlinDebuggerSettings settings) {
|
||||
return settings.getDEBUG_RENDER_DELEGATED_PROPERTIES() != renderDelegatedProperties.isSelected();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void apply(@NotNull KotlinDebuggerSettings settings) {
|
||||
settings.setDEBUG_RENDER_DELEGATED_PROPERTIES(renderDelegatedProperties.isSelected());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return myPanel;
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImports
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImportsImpl
|
||||
import com.intellij.debugger.impl.EditorTextProvider
|
||||
import com.intellij.openapi.util.Pair
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
|
||||
class KotlinEditorTextProvider : EditorTextProvider {
|
||||
override fun getEditorText(elementAtCaret: PsiElement): TextWithImports? {
|
||||
val expression = findExpressionInner(elementAtCaret, true) ?: return null
|
||||
|
||||
val expressionText = getElementInfo(expression) { it.text }
|
||||
return TextWithImportsImpl(CodeFragmentKind.EXPRESSION, expressionText, "", KotlinFileType.INSTANCE)
|
||||
}
|
||||
|
||||
override fun findExpression(elementAtCaret: PsiElement, allowMethodCalls: Boolean): Pair<PsiElement, TextRange>? {
|
||||
val expression = findExpressionInner(elementAtCaret, allowMethodCalls) ?: return null
|
||||
|
||||
val expressionRange = getElementInfo(expression) { it.textRange }
|
||||
return Pair(expression, expressionRange)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun <T> getElementInfo(expr: KtExpression, f: (PsiElement) -> T): T {
|
||||
var expressionText = f(expr)
|
||||
if (expr is KtProperty) {
|
||||
val nameIdentifier = expr.nameIdentifier
|
||||
if (nameIdentifier != null) {
|
||||
expressionText = f(nameIdentifier)
|
||||
}
|
||||
}
|
||||
return expressionText
|
||||
}
|
||||
|
||||
fun findExpressionInner(element: PsiElement, allowMethodCalls: Boolean): KtExpression? {
|
||||
if (!isAcceptedAsCodeFragmentContext(element)) return null
|
||||
|
||||
val ktElement = PsiTreeUtil.getParentOfType(element, KtElement::class.java) ?: return null
|
||||
|
||||
if (ktElement is KtProperty) {
|
||||
val nameIdentifier = ktElement.nameIdentifier
|
||||
if (nameIdentifier == element) {
|
||||
return ktElement
|
||||
}
|
||||
}
|
||||
|
||||
fun KtExpression.qualifiedParentOrSelf(isSelector: Boolean = true): KtExpression {
|
||||
val parent = parent
|
||||
return if (parent is KtQualifiedExpression && (!isSelector || parent.selectorExpression == this)) parent else this
|
||||
}
|
||||
|
||||
val parent = ktElement.parent
|
||||
|
||||
val newExpression = when (parent) {
|
||||
is KtThisExpression -> parent
|
||||
is KtSuperExpression -> parent.qualifiedParentOrSelf(isSelector = false)
|
||||
is KtArrayAccessExpression -> if (parent.arrayExpression == ktElement) ktElement else parent.qualifiedParentOrSelf()
|
||||
is KtReferenceExpression -> parent.qualifiedParentOrSelf()
|
||||
is KtQualifiedExpression -> if (parent.receiverExpression != ktElement) parent else null
|
||||
is KtOperationExpression -> if (parent.operationReference == ktElement) parent else null
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (!allowMethodCalls && newExpression != null) {
|
||||
fun PsiElement.isCall() = this is KtCallExpression || this is KtOperationExpression || this is KtArrayAccessExpression
|
||||
|
||||
if (newExpression.isCall() || newExpression is KtQualifiedExpression && newExpression.selectorExpression!!.isCall()) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return when {
|
||||
newExpression is KtExpression -> newExpression
|
||||
ktElement is KtSimpleNameExpression -> {
|
||||
val context = ktElement.analyze()
|
||||
val qualifier = context[BindingContext.QUALIFIER, ktElement]
|
||||
if (qualifier != null && !DescriptorUtils.isObject(qualifier.descriptor)) {
|
||||
null
|
||||
}
|
||||
else {
|
||||
ktElement
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private val NOT_ACCEPTED_AS_CONTEXT_TYPES =
|
||||
arrayOf(KtUserType::class.java, KtImportDirective::class.java, KtPackageDirective::class.java, KtValueArgumentName::class.java)
|
||||
|
||||
fun isAcceptedAsCodeFragmentContext(element: PsiElement): Boolean {
|
||||
return !NOT_ACCEPTED_AS_CONTEXT_TYPES.contains(element::class.java as Class<*>) &&
|
||||
PsiTreeUtil.getParentOfType(element, *NOT_ACCEPTED_AS_CONTEXT_TYPES) == null
|
||||
}
|
||||
}
|
||||
}
|
||||
-196
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.FrameExtraVariablesProvider
|
||||
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImports
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImportsImpl
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.util.text.CharArrayUtil
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineEndOffset
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
class KotlinFrameExtraVariablesProvider : FrameExtraVariablesProvider {
|
||||
override fun isAvailable(sourcePosition: SourcePosition, evalContext: EvaluationContext): Boolean {
|
||||
if (runReadAction { sourcePosition.line } < 0) return false
|
||||
return sourcePosition.file.fileType == KotlinFileType.INSTANCE && DebuggerSettings.getInstance().AUTO_VARIABLES_MODE
|
||||
}
|
||||
|
||||
override fun collectVariables(
|
||||
sourcePosition: SourcePosition, evalContext: EvaluationContext, alreadyCollected: MutableSet<String>): Set<TextWithImports> {
|
||||
return runReadAction { findAdditionalExpressions(sourcePosition) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun findAdditionalExpressions(position: SourcePosition): Set<TextWithImports> {
|
||||
val line = position.line
|
||||
val file = position.file
|
||||
|
||||
val vFile = file.virtualFile
|
||||
val doc = if (vFile != null) FileDocumentManager.getInstance().getDocument(vFile) else null
|
||||
if (doc == null || doc.lineCount == 0 || line > (doc.lineCount - 1)) {
|
||||
return emptySet()
|
||||
}
|
||||
|
||||
val offset = file.getLineStartOffset(line)?.takeIf { it > 0 } ?: return emptySet()
|
||||
|
||||
val elem = file.findElementAt(offset) ?: return emptySet()
|
||||
val containingElement = getContainingElement(elem) ?: elem
|
||||
|
||||
val limit = getLineRangeForElement(containingElement, doc)
|
||||
|
||||
var startLine = max(limit.startOffset, line)
|
||||
while (startLine - 1 > limit.startOffset && shouldSkipLine(file, doc, startLine - 1)) {
|
||||
startLine--
|
||||
}
|
||||
|
||||
var endLine = min(limit.endOffset, line)
|
||||
while (endLine + 1 < limit.endOffset && shouldSkipLine(file, doc, endLine + 1)) {
|
||||
endLine++
|
||||
}
|
||||
|
||||
val startOffset = file.getLineStartOffset(startLine) ?: return emptySet()
|
||||
val endOffset = file.getLineEndOffset(endLine) ?: return emptySet()
|
||||
|
||||
if (startOffset >= endOffset) return emptySet()
|
||||
|
||||
val lineRange = TextRange(startOffset, endOffset)
|
||||
if (lineRange.isEmpty) return emptySet()
|
||||
|
||||
val expressions = LinkedHashSet<TextWithImports>()
|
||||
|
||||
val variablesCollector = VariablesCollector(lineRange, expressions)
|
||||
containingElement.accept(variablesCollector)
|
||||
|
||||
return expressions
|
||||
}
|
||||
|
||||
private fun getContainingElement(element: PsiElement): KtElement? {
|
||||
val contElement = PsiTreeUtil.getParentOfType(element, KtDeclaration::class.java) ?: PsiTreeUtil.getParentOfType(element, KtElement::class.java)
|
||||
if (contElement is KtProperty && contElement.isLocal) {
|
||||
val parent = contElement.parent
|
||||
return getContainingElement(parent)
|
||||
}
|
||||
|
||||
if (contElement is KtDeclarationWithBody) {
|
||||
return contElement.bodyExpression
|
||||
}
|
||||
return contElement
|
||||
}
|
||||
|
||||
private fun getLineRangeForElement(containingElement: PsiElement, doc: Document): TextRange {
|
||||
val elemRange = containingElement.textRange
|
||||
return TextRange(doc.getLineNumber(elemRange.startOffset), doc.getLineNumber(elemRange.endOffset))
|
||||
}
|
||||
|
||||
private fun shouldSkipLine(file: PsiFile, doc: Document, line: Int): Boolean {
|
||||
val start = CharArrayUtil.shiftForward(doc.charsSequence, doc.getLineStartOffset(line), " \n\t")
|
||||
val end = doc.getLineEndOffset(line)
|
||||
if (start >= end) {
|
||||
return true
|
||||
}
|
||||
|
||||
val elemAtOffset = file.findElementAt(start)
|
||||
val topmostElementAtOffset = CodeInsightUtils.getTopmostElementAtOffset(elemAtOffset!!, start)
|
||||
return topmostElementAtOffset !is KtDeclaration
|
||||
}
|
||||
|
||||
private class VariablesCollector(
|
||||
private val myLineRange: TextRange,
|
||||
private val myExpressions: MutableSet<TextWithImports>
|
||||
) : KtTreeVisitorVoid() {
|
||||
|
||||
override fun visitKtElement(element: KtElement) {
|
||||
if (element.isInRange()) {
|
||||
super.visitKtElement(element)
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitQualifiedExpression(expression: KtQualifiedExpression) {
|
||||
if (expression.isInRange()) {
|
||||
val selector = expression.selectorExpression
|
||||
if (selector is KtReferenceExpression) {
|
||||
if (isRefToProperty(selector)) {
|
||||
myExpressions.add(expression.createText())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
super.visitQualifiedExpression(expression)
|
||||
}
|
||||
|
||||
private fun isRefToProperty(expression: KtReferenceExpression): Boolean {
|
||||
// NB: analyze() cannot be called here, because DELEGATED_PROPERTY_RESOLVED_CALL will be always null
|
||||
// Looks like a bug
|
||||
@Suppress("DEPRECATION")
|
||||
val context = expression.analyzeWithAllCompilerChecks().bindingContext
|
||||
val descriptor = context[BindingContext.REFERENCE_TARGET, expression]
|
||||
if (descriptor is PropertyDescriptor) {
|
||||
val getter = descriptor.getter
|
||||
return (getter == null || context[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getter] == null) &&
|
||||
descriptor.compileTimeInitializer == null
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun visitReferenceExpression(expression: KtReferenceExpression) {
|
||||
if (expression.isInRange()) {
|
||||
if (isRefToProperty(expression)) {
|
||||
myExpressions.add(expression.createText())
|
||||
}
|
||||
}
|
||||
super.visitReferenceExpression(expression)
|
||||
}
|
||||
|
||||
private fun KtElement.isInRange(): Boolean = myLineRange.intersects(this.textRange)
|
||||
private fun KtElement.createText(): TextWithImports = TextWithImportsImpl(CodeFragmentKind.EXPRESSION, this.text)
|
||||
|
||||
override fun visitClass(klass: KtClass) {
|
||||
// Do not show expressions used in local classes
|
||||
}
|
||||
|
||||
override fun visitNamedFunction(function: KtNamedFunction) {
|
||||
// Do not show expressions used in local functions
|
||||
}
|
||||
|
||||
override fun visitObjectLiteralExpression(expression: KtObjectLiteralExpression) {
|
||||
// Do not show expressions used in anonymous objects
|
||||
}
|
||||
|
||||
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
|
||||
// Do not show expressions used in lambdas
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger;
|
||||
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpointType;
|
||||
import com.jetbrains.javascript.debugger.JavaScriptDebugAware;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinLineBreakpointType;
|
||||
|
||||
public class KotlinJavaScriptDebugAware extends JavaScriptDebugAware {
|
||||
@Nullable
|
||||
@Override
|
||||
public Class<? extends XLineBreakpointType<?>> getBreakpointTypeClass() {
|
||||
return KotlinLineBreakpointType.class;
|
||||
}
|
||||
}
|
||||
@@ -1,359 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.MultiRequestPositionManager
|
||||
import com.intellij.debugger.NoDataException
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.PositionManagerEx
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.requests.ClassPrepareRequestor
|
||||
import com.intellij.openapi.fileTypes.FileType
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.roots.ProjectRootManager
|
||||
import com.intellij.openapi.util.Computable
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.impl.compiled.ClsFileImpl
|
||||
import com.intellij.psi.search.DelegatingGlobalSearchScope
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.ThreeState
|
||||
import com.intellij.xdebugger.frame.XStackFrame
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.request.ClassPrepareRequest
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||
import org.jetbrains.kotlin.idea.KotlinFileTypeFactory
|
||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.getLambdasAtLineIfAny
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.decompiler.classFile.KtClsFile
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineCount
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinSourceFilterScope
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import com.intellij.debugger.engine.DebuggerUtils as JDebuggerUtils
|
||||
|
||||
class KotlinPositionManager(private val myDebugProcess: DebugProcess) : MultiRequestPositionManager, PositionManagerEx() {
|
||||
private val allKotlinFilesScope =
|
||||
object : DelegatingGlobalSearchScope(
|
||||
KotlinSourceFilterScope.projectAndLibrariesSources(GlobalSearchScope.allScope(myDebugProcess.project), myDebugProcess.project)
|
||||
) {
|
||||
private val projectIndex = ProjectRootManager.getInstance(myDebugProcess.project).fileIndex
|
||||
private val scopeComparator =
|
||||
Comparator.comparing(projectIndex::isInSourceContent)
|
||||
.thenComparing(projectIndex::isInLibrarySource)
|
||||
.thenComparing { file1, file2 -> super.compare(file1, file2) }
|
||||
|
||||
override fun compare(file1: VirtualFile, file2: VirtualFile): Int {
|
||||
return scopeComparator.compare(file1, file2)
|
||||
}
|
||||
}
|
||||
|
||||
private val sourceSearchScopes: List<GlobalSearchScope> = listOf(
|
||||
myDebugProcess.searchScope,
|
||||
allKotlinFilesScope
|
||||
)
|
||||
|
||||
override fun getAcceptedFileTypes(): Set<FileType> = KotlinFileTypeFactory.KOTLIN_FILE_TYPES_SET
|
||||
|
||||
override fun evaluateCondition(context: EvaluationContext, frame: StackFrameProxyImpl, location: Location, expression: String): ThreeState? {
|
||||
return ThreeState.UNSURE
|
||||
}
|
||||
|
||||
override fun createStackFrame(frame: StackFrameProxyImpl, debugProcess: DebugProcessImpl, location: Location): XStackFrame? {
|
||||
if (location.isInKotlinSources()) {
|
||||
return KotlinStackFrame(frame)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getSourcePosition(location: Location?): SourcePosition? {
|
||||
if (location == null) throw NoDataException.INSTANCE
|
||||
|
||||
val fileName = location.safeSourceName() ?: throw NoDataException.INSTANCE
|
||||
val lineNumber = location.safeLineNumber()
|
||||
if (lineNumber < 0) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
if (!DebuggerUtils.isKotlinSourceFile(fileName)) throw NoDataException.INSTANCE
|
||||
|
||||
val psiFile = getPsiFileByLocation(location)?.let {
|
||||
replaceWithAlternativeSource(it, location)
|
||||
}
|
||||
|
||||
if (psiFile == null) {
|
||||
val isKotlinStrataAvailable = location.declaringType().containsKotlinStrata()
|
||||
if (isKotlinStrataAvailable) {
|
||||
try {
|
||||
val javaSourceFileName = location.sourceName("Java")
|
||||
val javaClassName = JvmClassName.byInternalName(defaultInternalName(location))
|
||||
val project = myDebugProcess.project
|
||||
|
||||
val defaultPsiFile = DebuggerUtils.findSourceFileForClass(
|
||||
project, sourceSearchScopes, javaClassName, javaSourceFileName, location)
|
||||
|
||||
if (defaultPsiFile != null) {
|
||||
return SourcePosition.createFromLine(defaultPsiFile, 0)
|
||||
}
|
||||
}
|
||||
catch (e: AbsentInformationException) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
if (psiFile !is KtFile) throw NoDataException.INSTANCE
|
||||
|
||||
val sourceLineNumber = location.safeSourceLineNumber()
|
||||
if (sourceLineNumber < 0) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
val lambdaOrFunIfInside = getLambdaOrFunIfInside(location, psiFile, sourceLineNumber)
|
||||
if (lambdaOrFunIfInside != null) {
|
||||
return SourcePosition.createFromElement(lambdaOrFunIfInside.bodyExpression!!)
|
||||
}
|
||||
|
||||
val elementInDeclaration = getElementForDeclarationLine(location, psiFile, sourceLineNumber)
|
||||
if (elementInDeclaration != null) {
|
||||
return SourcePosition.createFromElement(elementInDeclaration)
|
||||
}
|
||||
|
||||
if (sourceLineNumber > psiFile.getLineCount() && myDebugProcess.isDexDebug()) {
|
||||
val (line, ktFile) = ktLocationInfo(location, true, myDebugProcess.project, false, psiFile)
|
||||
return SourcePosition.createFromLine(ktFile ?: psiFile, line - 1)
|
||||
}
|
||||
|
||||
val sameLineLocations = location.safeMethod()?.safeAllLineLocations()?.filter {
|
||||
it.safeLineNumber() == lineNumber && it.safeSourceName() == fileName
|
||||
}
|
||||
|
||||
if (sameLineLocations != null) {
|
||||
// There're several locations for same source line. If same source position would be created for all of them,
|
||||
// breakpoints at this line will stop on every location.
|
||||
// Each location is probably some code in arguments between inlined invocations (otherwise same line locations would
|
||||
// have been merged into one), but it's impossible to correctly map locations to actual source expressions now.
|
||||
val locationIndex = sameLineLocations.indexOf(location)
|
||||
if (locationIndex > 0) {
|
||||
/*
|
||||
`finally {}` block code is placed in the class file twice.
|
||||
Unless the debugger metadata is available, we can't figure out if we are inside `finally {}`, so we have to check it using PSI.
|
||||
This is conceptually wrong and won't work in some cases, but it's still better than nothing.
|
||||
*/
|
||||
val elementAt = psiFile.getLineStartOffset(lineNumber)?.let { psiFile.findElementAt(it) }
|
||||
val isInsideDuplicatedFinally = elementAt != null && elementAt.getStrictParentOfType<KtFinallySection>() != null
|
||||
if (!isInsideDuplicatedFinally) {
|
||||
return KotlinReentrantSourcePosition(SourcePosition.createFromLine(psiFile, sourceLineNumber))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SourcePosition.createFromLine(psiFile, sourceLineNumber)
|
||||
}
|
||||
|
||||
class KotlinReentrantSourcePosition(delegate: SourcePosition) : DelegateSourcePosition(delegate)
|
||||
|
||||
private fun replaceWithAlternativeSource(psiFile: PsiFile, location: Location): PsiFile {
|
||||
fun findAlternativeSource(): PsiFile? {
|
||||
val qName = location.declaringType().name()
|
||||
val alternativeFileUrl = DebuggerUtilsEx.getAlternativeSourceUrl(qName, myDebugProcess.project) ?: return null
|
||||
val alternativePsiFile = VirtualFileManager.getInstance().findFileByUrl(alternativeFileUrl) ?: return null
|
||||
return psiFile.manager.findFile(alternativePsiFile)
|
||||
}
|
||||
|
||||
return findAlternativeSource() ?: psiFile
|
||||
}
|
||||
|
||||
// Returns a property or a constructor if debugger stops at class declaration
|
||||
private fun getElementForDeclarationLine(location: Location, file: KtFile, lineNumber: Int): KtElement? {
|
||||
val lineStartOffset = file.getLineStartOffset(lineNumber) ?: return null
|
||||
val elementAt = file.findElementAt(lineStartOffset)
|
||||
val contextElement = KotlinCodeFragmentFactory.getContextElement(elementAt)
|
||||
|
||||
if (contextElement !is KtClass) return null
|
||||
|
||||
val methodName = location.method().name()
|
||||
return when {
|
||||
JvmAbi.isGetterName(methodName) -> {
|
||||
val parameterForGetter = contextElement.primaryConstructor?.valueParameters?.firstOrNull {
|
||||
it.hasValOrVar() && it.name != null && JvmAbi.getterName(it.name!!) == methodName
|
||||
} ?: return null
|
||||
parameterForGetter
|
||||
}
|
||||
methodName == "<init>" -> contextElement.primaryConstructor
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLambdaOrFunIfInside(location: Location, file: KtFile, lineNumber: Int): KtFunction? {
|
||||
val currentLocationFqName = location.declaringType().name() ?: return null
|
||||
|
||||
val start = CodeInsightUtils.getStartLineOffset(file, lineNumber)
|
||||
val end = CodeInsightUtils.getEndLineOffset(file, lineNumber)
|
||||
if (start == null || end == null) return null
|
||||
|
||||
val literalsOrFunctions = getLambdasAtLineIfAny(file, lineNumber)
|
||||
if (literalsOrFunctions.isEmpty()) return null
|
||||
|
||||
val elementAt = file.findElementAt(start) ?: return null
|
||||
val typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(elementAt)
|
||||
|
||||
val currentLocationClassName = JvmClassName.byFqNameWithoutInnerClasses(FqName(currentLocationFqName))
|
||||
.internalName.replace('/', '.')
|
||||
|
||||
for (literal in literalsOrFunctions) {
|
||||
if (InlineUtil.isInlinedArgument(literal, typeMapper.bindingContext, true)) {
|
||||
if (isInsideInlineArgument(literal, location, myDebugProcess as DebugProcessImpl, typeMapper.bindingContext)) {
|
||||
return literal
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
val internalClassNames = DebuggerClassNameProvider(myDebugProcess, alwaysReturnLambdaParentClass = false)
|
||||
.getOuterClassNamesForElement(literal.firstChild)
|
||||
.classNames
|
||||
|
||||
if (internalClassNames.any { it == currentLocationClassName }) {
|
||||
return literal
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getPsiFileByLocation(location: Location): PsiFile? {
|
||||
val sourceName = location.safeSourceName() ?: return null
|
||||
|
||||
val referenceInternalName = try {
|
||||
if (location.declaringType().containsKotlinStrata()) {
|
||||
//replace is required for windows
|
||||
location.sourcePath().replace('\\', '/')
|
||||
}
|
||||
else {
|
||||
defaultInternalName(location)
|
||||
}
|
||||
}
|
||||
catch (e: AbsentInformationException) {
|
||||
defaultInternalName(location)
|
||||
}
|
||||
|
||||
val className = JvmClassName.byInternalName(referenceInternalName)
|
||||
|
||||
val project = myDebugProcess.project
|
||||
|
||||
return DebuggerUtils.findSourceFileForClass(project, sourceSearchScopes, className, sourceName, location)
|
||||
}
|
||||
|
||||
private fun defaultInternalName(location: Location): String {
|
||||
//no stratum or source path => use default one
|
||||
val referenceFqName = location.declaringType().name()
|
||||
// JDI names are of form "package.Class$InnerClass"
|
||||
return referenceFqName.replace('.', '/')
|
||||
}
|
||||
|
||||
override fun getAllClasses(sourcePosition: SourcePosition): List<ReferenceType> {
|
||||
val psiFile = sourcePosition.file
|
||||
if (psiFile is KtFile) {
|
||||
if (!ProjectRootsUtil.isInProjectOrLibSource(psiFile)) return emptyList()
|
||||
return DebuggerClassNameProvider(myDebugProcess).getClassesForPosition(sourcePosition)
|
||||
}
|
||||
|
||||
if (psiFile is ClsFileImpl) {
|
||||
val decompiledPsiFile = psiFile.readAction { it.decompiledPsiFile }
|
||||
if (decompiledPsiFile is KtClsFile && runReadAction { sourcePosition.line } == -1) {
|
||||
val className = JvmFileClassUtil.getFileClassInternalName(decompiledPsiFile)
|
||||
return myDebugProcess.virtualMachineProxy.classesByName(className)
|
||||
}
|
||||
}
|
||||
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
fun originalClassNamesForPosition(position: SourcePosition): List<String> {
|
||||
return DebuggerClassNameProvider(myDebugProcess, findInlineUseSites = false).getOuterClassNamesForPosition(position)
|
||||
}
|
||||
|
||||
override fun locationsOfLine(type: ReferenceType, position: SourcePosition): List<Location> {
|
||||
if (position.file !is KtFile) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
try {
|
||||
if (myDebugProcess.isDexDebug()) {
|
||||
val inlineLocations = runReadAction { getLocationsOfInlinedLine(type, position, myDebugProcess.searchScope) }
|
||||
if (!inlineLocations.isEmpty()) {
|
||||
return inlineLocations
|
||||
}
|
||||
}
|
||||
|
||||
val line = position.line + 1
|
||||
|
||||
val locations = type.locationsOfLine(KOTLIN_STRATA_NAME, null, line)
|
||||
if (locations == null || locations.isEmpty()) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
return locations.filter { it.sourceName(KOTLIN_STRATA_NAME) == position.file.name }
|
||||
}
|
||||
catch (e: AbsentInformationException) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Since Idea 14.0.3 use createPrepareRequests fun")
|
||||
override fun createPrepareRequest(classPrepareRequestor: ClassPrepareRequestor, sourcePosition: SourcePosition): ClassPrepareRequest? {
|
||||
return createPrepareRequests(classPrepareRequestor, sourcePosition).firstOrNull()
|
||||
}
|
||||
|
||||
override fun createPrepareRequests(requestor: ClassPrepareRequestor, position: SourcePosition): List<ClassPrepareRequest> {
|
||||
if (position.file !is KtFile) {
|
||||
throw NoDataException.INSTANCE
|
||||
}
|
||||
|
||||
return DumbService.getInstance(myDebugProcess.project).runReadActionInSmartMode(Computable {
|
||||
val classNames = DebuggerClassNameProvider(myDebugProcess).getOuterClassNamesForPosition(position)
|
||||
classNames.flatMap { name ->
|
||||
listOfNotNull(
|
||||
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, name),
|
||||
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, "$name$*")
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <U, V> U.readAction(crossinline f: (U) -> V): V {
|
||||
return runReadAction { f(this) }
|
||||
}
|
||||
-29
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger;
|
||||
|
||||
import com.intellij.debugger.PositionManager;
|
||||
import com.intellij.debugger.PositionManagerFactory;
|
||||
import com.intellij.debugger.engine.DebugProcess;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
public class KotlinPositionManagerFactory extends PositionManagerFactory {
|
||||
@Override
|
||||
public PositionManager createPositionManager(@NotNull DebugProcess process) {
|
||||
return new KotlinPositionManager(process);
|
||||
}
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.SourcePositionHighlighter
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import org.jetbrains.kotlin.psi.KtFunctionLiteral
|
||||
|
||||
class KotlinSourcePositionHighlighter: SourcePositionHighlighter() {
|
||||
override fun getHighlightRange(sourcePosition: SourcePosition?): TextRange? {
|
||||
val lambda = sourcePosition?.elementAt?.parent
|
||||
if (lambda is KtFunctionLiteral) {
|
||||
return lambda.textRange
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.SourcePositionProvider
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextUtil
|
||||
import com.intellij.debugger.impl.PositionUtil
|
||||
import com.intellij.debugger.ui.tree.FieldDescriptor
|
||||
import com.intellij.debugger.ui.tree.LocalVariableDescriptor
|
||||
import com.intellij.debugger.ui.tree.NodeDescriptor
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.ClassNotPreparedException
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.psi.KtSimpleNameExpression
|
||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||
import org.jetbrains.kotlin.resolve.BindingContextUtils
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
|
||||
class KotlinSourcePositionProvider: SourcePositionProvider() {
|
||||
override fun computeSourcePosition(descriptor: NodeDescriptor, project: Project, context: DebuggerContextImpl, nearest: Boolean): SourcePosition? {
|
||||
if (context.frameProxy == null) return null
|
||||
|
||||
if (descriptor is FieldDescriptor) {
|
||||
return computeSourcePosition(descriptor, project, context, nearest)
|
||||
}
|
||||
|
||||
if (descriptor is LocalVariableDescriptor) {
|
||||
return computeSourcePosition(descriptor, project, context, nearest)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun computeSourcePosition(descriptor: LocalVariableDescriptor, project: Project, context: DebuggerContextImpl, nearest: Boolean): SourcePosition? {
|
||||
val place = PositionUtil.getContextElement(context) ?: return null
|
||||
if (place.containingFile !is KtFile) return null
|
||||
|
||||
val contextElement = KotlinCodeFragmentFactory.getContextElement(place) ?: return null
|
||||
|
||||
val codeFragment = KtPsiFactory(project).createExpressionCodeFragment(descriptor.name, contextElement)
|
||||
val expression = codeFragment.getContentElement()
|
||||
if (expression is KtSimpleNameExpression) {
|
||||
val bindingContext = expression.analyze(BodyResolveMode.PARTIAL)
|
||||
val declarationDescriptor = BindingContextUtils.extractVariableDescriptorFromReference(bindingContext, expression)
|
||||
val sourceElement = declarationDescriptor?.source
|
||||
if (sourceElement is KotlinSourceElement) {
|
||||
val element = sourceElement.getPsi() ?: return null
|
||||
if (nearest) {
|
||||
return DebuggerContextUtil.findNearest(context, element, element.containingFile)
|
||||
}
|
||||
return SourcePosition.createFromOffset(element.containingFile, element.textOffset)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun computeSourcePosition(descriptor: FieldDescriptor, project: Project, context: DebuggerContextImpl, nearest: Boolean): SourcePosition? {
|
||||
val fieldName = descriptor.field.name()
|
||||
|
||||
if (fieldName == AsmUtil.CAPTURED_THIS_FIELD
|
||||
|| fieldName == AsmUtil.CAPTURED_RECEIVER_FIELD
|
||||
|| fieldName.startsWith(AsmUtil.LABELED_THIS_FIELD)
|
||||
) {
|
||||
return null
|
||||
}
|
||||
|
||||
val type = descriptor.field.declaringType()
|
||||
val myClass = findClassByType(project, type, context)?.navigationElement as? KtClassOrObject ?: return null
|
||||
|
||||
val field = myClass.declarations.firstOrNull { fieldName == it.name } ?: return null
|
||||
|
||||
if (nearest) {
|
||||
return DebuggerContextUtil.findNearest(context, field, myClass.containingFile)
|
||||
}
|
||||
return SourcePosition.createFromOffset(field.containingFile, field.textOffset)
|
||||
}
|
||||
|
||||
private fun findClassByType(project: Project, type: ReferenceType, context: DebuggerContextImpl): PsiElement? {
|
||||
val session = context.debuggerSession
|
||||
val scope = session?.searchScope ?: GlobalSearchScope.allScope(project)
|
||||
val className = JvmClassName.byInternalName(type.name()).fqNameForClassNameWithoutDollars.asString()
|
||||
|
||||
val myClass = JavaPsiFacade.getInstance(project).findClass(className, scope)
|
||||
if (myClass != null) return myClass
|
||||
|
||||
val position = getLastSourcePosition(type, context)
|
||||
if (position != null) {
|
||||
val element = position.elementAt
|
||||
if (element != null) {
|
||||
return element.getStrictParentOfType<KtClassOrObject>()
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getLastSourcePosition(type: ReferenceType, context: DebuggerContextImpl): SourcePosition? {
|
||||
val debugProcess = context.debugProcess
|
||||
if (debugProcess != null) {
|
||||
try {
|
||||
val locations = type.allLineLocations()
|
||||
if (!locations.isEmpty()) {
|
||||
val lastLocation = locations.get(locations.size - 1)
|
||||
return debugProcess.positionManager.getSourcePosition(lastLocation)
|
||||
}
|
||||
}
|
||||
catch (ignored: AbsentInformationException) {
|
||||
}
|
||||
catch (ignored: ClassNotPreparedException) {
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -1,411 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.DebuggerContext
|
||||
import com.intellij.debugger.engine.JavaStackFrame
|
||||
import com.intellij.debugger.engine.JavaValue
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.impl.descriptors.data.DescriptorData
|
||||
import com.intellij.debugger.impl.descriptors.data.DisplayKey
|
||||
import com.intellij.debugger.impl.descriptors.data.SimpleDisplayKey
|
||||
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.ui.impl.watch.MethodsTracker
|
||||
import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl
|
||||
import com.intellij.debugger.ui.impl.watch.ThisDescriptorImpl
|
||||
import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiExpression
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import com.intellij.xdebugger.frame.XValue
|
||||
import com.intellij.xdebugger.frame.XValueChildrenList
|
||||
import com.sun.jdi.ObjectReference
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.Type
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil.THIS
|
||||
import org.jetbrains.kotlin.codegen.DESTRUCTURED_LAMBDA_ARGUMENT_VARIABLE_PREFIX
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||
import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
|
||||
import org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder
|
||||
import org.jetbrains.kotlin.utils.getSafe
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG as DebuggerLog
|
||||
|
||||
class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDescriptorImpl(frame, MethodsTracker()), true) {
|
||||
private val kotlinVariableViewService = ToggleKotlinVariablesState.getService()
|
||||
|
||||
override fun superBuildVariables(evaluationContext: EvaluationContextImpl, children: XValueChildrenList) {
|
||||
if (!kotlinVariableViewService.kotlinVariableView) {
|
||||
return super.superBuildVariables(evaluationContext, children)
|
||||
}
|
||||
|
||||
val nodeManager = evaluationContext.debugProcess.xdebugProcess?.nodeManager
|
||||
|
||||
fun addItem(variable: LocalVariableProxyImpl) {
|
||||
if (nodeManager == null) {
|
||||
return
|
||||
}
|
||||
|
||||
val variableDescriptor = nodeManager.getLocalVariableDescriptor(null, variable)
|
||||
children.add(JavaValue.create(null, variableDescriptor, evaluationContext, nodeManager, false))
|
||||
}
|
||||
|
||||
val (thisReferences, otherVariables) = visibleVariables
|
||||
.partition { it.name() == THIS || it is ThisLocalVariable }
|
||||
|
||||
if (!removeSyntheticThisObject(evaluationContext, children, thisReferences) && thisReferences.isNotEmpty()) {
|
||||
val thisLabels = thisReferences.asSequence()
|
||||
.filterIsInstance<ThisLocalVariable>()
|
||||
.mapNotNullTo(hashSetOf()) { it.label }
|
||||
|
||||
remapThisObjectForOuterThis(evaluationContext, children, thisLabels)
|
||||
}
|
||||
|
||||
thisReferences.forEach(::addItem)
|
||||
otherVariables.forEach(::addItem)
|
||||
}
|
||||
|
||||
private fun removeSyntheticThisObject(
|
||||
evaluationContext: EvaluationContextImpl,
|
||||
children: XValueChildrenList,
|
||||
thisReferences: List<LocalVariableProxyImpl>
|
||||
): Boolean {
|
||||
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return false
|
||||
|
||||
if (thisObject.type().isSubtype(VariableFinder.CONTINUATION_TYPE)) {
|
||||
ExistingInstanceThis.find(children)?.remove()
|
||||
return true
|
||||
}
|
||||
|
||||
val thisObjectType = thisObject.type()
|
||||
if (thisObjectType.isSubtype(Function::class.java.name) && '$' in thisObjectType.signature()) {
|
||||
val existingThis = ExistingInstanceThis.find(children)
|
||||
if (existingThis != null) {
|
||||
existingThis.remove()
|
||||
val javaValue = existingThis.value as? JavaValue
|
||||
if (javaValue != null) {
|
||||
attachCapturedThisFromLambda(evaluationContext, children, javaValue, thisReferences)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return removeCallSiteThisInInlineFunction(evaluationContext, children)
|
||||
}
|
||||
|
||||
private fun removeCallSiteThisInInlineFunction(evaluationContext: EvaluationContextImpl, children: XValueChildrenList): Boolean {
|
||||
val frameProxy = evaluationContext.frameProxy
|
||||
|
||||
val variables = frameProxy?.safeVisibleVariables() ?: return false
|
||||
val inlineDepth = VariableFinder.getInlineDepth(variables)
|
||||
val declarationSiteThis = variables.firstOrNull { v ->
|
||||
val name = v.name()
|
||||
name.endsWith(INLINE_FUN_VAR_SUFFIX) && name.dropInlineSuffix() == AsmUtil.INLINE_DECLARATION_SITE_THIS
|
||||
}
|
||||
|
||||
if (inlineDepth > 0 && declarationSiteThis != null) {
|
||||
ExistingInstanceThis.find(children)?.remove()
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun attachCapturedThisFromLambda(
|
||||
evaluationContext: EvaluationContextImpl,
|
||||
children: XValueChildrenList,
|
||||
javaValue: JavaValue,
|
||||
thisReferences: List<LocalVariableProxyImpl>
|
||||
) {
|
||||
try {
|
||||
val value = javaValue.descriptor.calcValue(evaluationContext) as? ObjectReference ?: return
|
||||
val thisField = value.referenceType().fieldByName(AsmUtil.CAPTURED_THIS_FIELD) ?: return
|
||||
val thisValue = value.getValue(thisField) as? ObjectReference ?: return
|
||||
val thisType = thisValue.referenceType()
|
||||
val unsafeLabel = generateThisLabelUnsafe(thisType) ?: return
|
||||
val label = checkLabel(unsafeLabel)
|
||||
|
||||
if (label != null) {
|
||||
val thisName = getThisName(label)
|
||||
|
||||
if (thisReferences.any { it.name() == thisName }) {
|
||||
// Avoid label duplication
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val thisName = when {
|
||||
thisReferences.isEmpty() -> THIS
|
||||
label != null -> getThisName(label)
|
||||
else -> "$THIS (anonymous fun)"
|
||||
}
|
||||
|
||||
val nodeManager = evaluationContext.debugProcess.xdebugProcess?.nodeManager ?: return
|
||||
val thisDescriptor = nodeManager.getDescriptor(this.descriptor, LabeledThisData(thisName, thisValue))
|
||||
children.add(JavaValue.create(null, thisDescriptor, evaluationContext, nodeManager, false))
|
||||
} catch (e: EvaluateException) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
private fun remapThisObjectForOuterThis(
|
||||
evaluationContext: EvaluationContextImpl,
|
||||
children: XValueChildrenList,
|
||||
existingThisLabels: Set<String>
|
||||
) {
|
||||
val thisObject = evaluationContext.frameProxy?.thisObject() ?: return
|
||||
val variable = ExistingInstanceThis.find(children) ?: return
|
||||
|
||||
val thisLabel = generateThisLabel(thisObject.referenceType())?.takeIf { it !in existingThisLabels }
|
||||
if (thisLabel == null) {
|
||||
variable.remove()
|
||||
return
|
||||
}
|
||||
|
||||
// add additional checks?
|
||||
variable.remapName(getThisName(thisLabel))
|
||||
}
|
||||
|
||||
// Very Dirty Work-around.
|
||||
// Hopefully, there will be an API for that in 2019.1.
|
||||
private class ExistingInstanceThis(
|
||||
private val children: XValueChildrenList,
|
||||
private val index: Int,
|
||||
val value: XValue,
|
||||
private val size: Int
|
||||
) {
|
||||
companion object {
|
||||
private const val THIS_NAME = "this"
|
||||
|
||||
fun find(children: XValueChildrenList): ExistingInstanceThis? {
|
||||
val size = children.size()
|
||||
for (i in 0 until size) {
|
||||
if (children.getName(i) == THIS_NAME) {
|
||||
val valueDescriptor = (children.getValue(i) as? JavaValue)?.descriptor
|
||||
@Suppress("FoldInitializerAndIfToElvis")
|
||||
if (valueDescriptor !is ThisDescriptorImpl) {
|
||||
return null
|
||||
}
|
||||
|
||||
return ExistingInstanceThis(children, i, children.getValue(i), size)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun remapName(newName: String) {
|
||||
val (names, _) = getLists() ?: return
|
||||
names[index] = newName
|
||||
}
|
||||
|
||||
fun remove() {
|
||||
val (names, values) = getLists() ?: return
|
||||
names.removeAt(index)
|
||||
values.removeAt(index)
|
||||
}
|
||||
|
||||
private fun getLists(): Lists? {
|
||||
if (children.size() != size) {
|
||||
throw IllegalStateException("Children list was modified")
|
||||
}
|
||||
|
||||
var namesList: MutableList<Any?>? = null
|
||||
var valuesList: MutableList<Any?>? = null
|
||||
|
||||
for (field in XValueChildrenList::class.java.declaredFields) {
|
||||
val mods = field.modifiers
|
||||
if (Modifier.isPrivate(mods) && Modifier.isFinal(mods) && !Modifier.isStatic(mods) && field.type == List::class.java) {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val list = (field.getSafe(children) as? MutableList<Any?>)?.takeIf { it.size == size } ?: continue
|
||||
|
||||
if (list[index] == THIS_NAME) {
|
||||
namesList = list
|
||||
} else if (list[index] === value) {
|
||||
valuesList = list
|
||||
}
|
||||
}
|
||||
|
||||
if (namesList != null && valuesList != null) {
|
||||
return Lists(namesList, valuesList)
|
||||
}
|
||||
}
|
||||
|
||||
DebuggerLog.error(
|
||||
"Can't find name/value lists, existing fields: "
|
||||
+ Arrays.toString(XValueChildrenList::class.java.declaredFields)
|
||||
)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private data class Lists(val names: MutableList<Any?>, val values: MutableList<Any?>)
|
||||
}
|
||||
|
||||
override fun getVisibleVariables(): List<LocalVariableProxyImpl> {
|
||||
val allVisibleVariables = super.getStackFrameProxy().safeVisibleVariables()
|
||||
|
||||
if (!kotlinVariableViewService.kotlinVariableView) {
|
||||
return allVisibleVariables.map { variable ->
|
||||
if (isFakeLocalVariableForInline(variable.name())) variable.wrapSyntheticInlineVariable() else variable
|
||||
}
|
||||
}
|
||||
|
||||
val inlineDepth = VariableFinder.getInlineDepth(allVisibleVariables)
|
||||
|
||||
val (thisVariables, otherVariables) = allVisibleVariables.asSequence()
|
||||
.filter { !isHidden(it, inlineDepth) }
|
||||
.partition {
|
||||
it.name() == THIS
|
||||
|| it.name() == AsmUtil.THIS_IN_DEFAULT_IMPLS
|
||||
|| it.name().startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|
||||
|| (VariableFinder.inlinedThisRegex.matches(it.name()))
|
||||
}
|
||||
|
||||
val (mainThis, otherThis) = thisVariables
|
||||
.sortedByDescending { it.variable }
|
||||
.let { it.firstOrNull() to it.drop(1) }
|
||||
|
||||
val remappedMainThis = mainThis?.clone(THIS, null)
|
||||
val remappedOther = (otherThis + otherVariables).map { it.remapVariableNameIfNeeded() }
|
||||
return (listOfNotNull(remappedMainThis) + remappedOther).sortedBy { it.variable }
|
||||
}
|
||||
|
||||
private fun isHidden(variable: LocalVariableProxyImpl, inlineDepth: Int): Boolean {
|
||||
val name = variable.name()
|
||||
return isFakeLocalVariableForInline(name)
|
||||
|| name.startsWith(DESTRUCTURED_LAMBDA_ARGUMENT_VARIABLE_PREFIX)
|
||||
|| name.startsWith(AsmUtil.LOCAL_FUNCTION_VARIABLE_PREFIX)
|
||||
|| VariableFinder.getInlineDepth(variable.name()) != inlineDepth
|
||||
|| name == CONTINUATION_VARIABLE_NAME
|
||||
}
|
||||
|
||||
private fun LocalVariableProxyImpl.remapVariableNameIfNeeded(): LocalVariableProxyImpl {
|
||||
val name = this.name().dropInlineSuffix()
|
||||
|
||||
@Suppress("ConvertToStringTemplate")
|
||||
return when {
|
||||
isLabeledThisReference() -> {
|
||||
val label = name.drop(AsmUtil.LABELED_THIS_PARAMETER.length)
|
||||
clone(getThisName(label), label)
|
||||
}
|
||||
name == AsmUtil.THIS_IN_DEFAULT_IMPLS -> clone(THIS + " (outer)", null)
|
||||
name == AsmUtil.RECEIVER_PARAMETER_NAME -> clone(THIS + " (receiver)", null)
|
||||
VariableFinder.inlinedThisRegex.matches(name) -> {
|
||||
val label = generateThisLabel(frame.getValue(this)?.type())
|
||||
if (label != null) {
|
||||
clone(getThisName(label), label)
|
||||
} else {
|
||||
this@remapVariableNameIfNeeded
|
||||
}
|
||||
}
|
||||
name != this.name() -> {
|
||||
object : LocalVariableProxyImpl(frame, variable) {
|
||||
override fun name() = name
|
||||
}
|
||||
}
|
||||
else -> this@remapVariableNameIfNeeded
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateThisLabel(type: Type?): String? {
|
||||
return checkLabel(generateThisLabelUnsafe(type) ?: return null)
|
||||
}
|
||||
|
||||
private fun generateThisLabelUnsafe(type: Type?): String? {
|
||||
val referenceType = type as? ReferenceType ?: return null
|
||||
return referenceType.name().substringAfterLast('.').substringAfterLast('$')
|
||||
}
|
||||
|
||||
private fun checkLabel(label: String): String? {
|
||||
if (label.isEmpty() || label.all { it.isDigit() }) {
|
||||
return null
|
||||
}
|
||||
|
||||
return label
|
||||
}
|
||||
|
||||
private fun String.dropInlineSuffix(): String {
|
||||
val depth = VariableFinder.getInlineDepth(this)
|
||||
if (depth == 0) {
|
||||
return this
|
||||
}
|
||||
|
||||
return dropLast(depth * INLINE_FUN_VAR_SUFFIX.length)
|
||||
}
|
||||
|
||||
private fun LocalVariableProxyImpl.clone(name: String, label: String?): LocalVariableProxyImpl {
|
||||
return object : LocalVariableProxyImpl(frame, variable), ThisLocalVariable {
|
||||
override fun name() = name
|
||||
override val label = label
|
||||
}
|
||||
}
|
||||
|
||||
private fun LocalVariableProxyImpl.isLabeledThisReference(): Boolean {
|
||||
@Suppress("ConvertToStringTemplate")
|
||||
return name().startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|
||||
}
|
||||
}
|
||||
|
||||
private interface ThisLocalVariable {
|
||||
val label: String?
|
||||
}
|
||||
|
||||
private fun LocalVariableProxyImpl.wrapSyntheticInlineVariable(): LocalVariableProxyImpl {
|
||||
val proxyWrapper = object : StackFrameProxyImpl(frame.threadProxy(), frame.stackFrame, frame.indexFromBottom) {
|
||||
override fun getValue(localVariable: LocalVariableProxyImpl): Value {
|
||||
return frame.virtualMachine.mirrorOfVoid()
|
||||
}
|
||||
}
|
||||
return LocalVariableProxyImpl(proxyWrapper, variable)
|
||||
}
|
||||
|
||||
private fun getThisName(label: String): String {
|
||||
return "$THIS (@$label)"
|
||||
}
|
||||
|
||||
private class LabeledThisData(val name: String, val value: ObjectReference) : DescriptorData<ValueDescriptorImpl>() {
|
||||
override fun createDescriptorImpl(project: Project): ValueDescriptorImpl {
|
||||
return object : ValueDescriptorImpl(project, value) {
|
||||
override fun getName() = this@LabeledThisData.name
|
||||
override fun calcValue(evaluationContext: EvaluationContextImpl?) = value
|
||||
override fun canSetValue() = false
|
||||
|
||||
override fun getDescriptorEvaluation(context: DebuggerContext?): PsiExpression {
|
||||
// TODO change to labeled this
|
||||
val elementFactory = JavaPsiFacade.getElementFactory(myProject)
|
||||
try {
|
||||
return elementFactory.createExpressionFromText("this", null)
|
||||
} catch (e: IncorrectOperationException) {
|
||||
throw EvaluateException(e.message, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDisplayKey(): DisplayKey<ValueDescriptorImpl> = SimpleDisplayKey(this)
|
||||
override fun equals(other: Any?) = other is LabeledThisData && other.name == name
|
||||
override fun hashCode() = name.hashCode()
|
||||
}
|
||||
-341
@@ -1,341 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.debugger.jdi.VirtualMachineProxyImpl
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.compiler.CompilerPaths
|
||||
import com.intellij.openapi.compiler.ex.CompilerPathsEx
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ProjectFileIndex
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.containers.ConcurrentFactoryMap
|
||||
import com.sun.jdi.Location
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.VirtualMachine
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineCount
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
|
||||
import org.jetbrains.kotlin.idea.refactoring.toPsiFile
|
||||
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.vfilefinder.IDEVirtualFileFinder
|
||||
import org.jetbrains.kotlin.idea.vfilefinder.IDEVirtualFileFinderFactory
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder
|
||||
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinderFactory
|
||||
import org.jetbrains.kotlin.name.ClassId
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.name.tail
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parents
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||
import org.jetbrains.kotlin.utils.getOrPutNullable
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentMap
|
||||
|
||||
fun isInlineFunctionLineNumber(file: VirtualFile, lineNumber: Int, project: Project): Boolean {
|
||||
if (ProjectRootsUtil.isProjectSourceFile(project, file)) {
|
||||
val linesInFile = file.toPsiFile(project)?.getLineCount() ?: return false
|
||||
return lineNumber > linesInFile
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun readBytecodeInfo(project: Project,
|
||||
jvmName: JvmClassName,
|
||||
file: VirtualFile): BytecodeDebugInfo? {
|
||||
return KotlinDebuggerCaches.getOrReadDebugInfoFromBytecode(project, jvmName, file)
|
||||
}
|
||||
|
||||
fun ktLocationInfo(location: Location, isDexDebug: Boolean, project: Project,
|
||||
preferInlined: Boolean = false, locationFile: KtFile? = null): Pair<Int, KtFile?> {
|
||||
if (isDexDebug && (locationFile == null || location.lineNumber() > locationFile.getLineCount())) {
|
||||
if (!preferInlined) {
|
||||
val thisFunLine = runReadAction { getLastLineNumberForLocation(location, project) }
|
||||
if (thisFunLine != null && thisFunLine != location.lineNumber()) {
|
||||
return thisFunLine to locationFile
|
||||
}
|
||||
}
|
||||
|
||||
val inlinePosition = runReadAction { getOriginalPositionOfInlinedLine(location, project) }
|
||||
if (inlinePosition != null) {
|
||||
val (file, line) = inlinePosition
|
||||
return line + 1 to file
|
||||
}
|
||||
}
|
||||
|
||||
return location.lineNumber() to locationFile
|
||||
}
|
||||
|
||||
/**
|
||||
* Only the first line number is stored for instruction in dex. It can be obtained through location.lineNumber().
|
||||
* This method allows to get last stored linenumber for instruction.
|
||||
*/
|
||||
fun getLastLineNumberForLocation(location: Location, project: Project, searchScope: GlobalSearchScope = GlobalSearchScope.allScope(project)): Int? {
|
||||
val lineNumber = location.lineNumber()
|
||||
val fqName = FqName(location.declaringType().name())
|
||||
val fileName = location.sourceName()
|
||||
|
||||
val method = location.method() ?: return null
|
||||
val name = method.name() ?: return null
|
||||
val signature = method.signature() ?: return null
|
||||
|
||||
val debugInfo = findAndReadClassFile(fqName, fileName, project, searchScope, { isInlineFunctionLineNumber(it, lineNumber, project) }) ?: return null
|
||||
|
||||
val lineMapping = debugInfo.lineTableMapping[BytecodeMethodKey(name, signature)] ?: return null
|
||||
return lineMapping.values.firstOrNull { it.contains(lineNumber) }?.last()
|
||||
}
|
||||
|
||||
fun createWeakBytecodeDebugInfoStorage(): ConcurrentMap<BinaryCacheKey, BytecodeDebugInfo?> {
|
||||
return ConcurrentFactoryMap.createWeakMap<BinaryCacheKey, BytecodeDebugInfo?> { key ->
|
||||
val bytes = readClassFileImpl(key.project, key.jvmName, key.file) ?: return@createWeakMap null
|
||||
|
||||
val smapData = readDebugInfo(bytes)
|
||||
val lineNumberMapping = readLineNumberTableMapping(bytes)
|
||||
|
||||
BytecodeDebugInfo(smapData, lineNumberMapping)
|
||||
}
|
||||
}
|
||||
|
||||
class BytecodeDebugInfo(val smapData: SmapData?, val lineTableMapping: Map<BytecodeMethodKey, Map<String, Set<Int>>>)
|
||||
|
||||
data class BytecodeMethodKey(val methodName: String, val signature: String)
|
||||
|
||||
data class BinaryCacheKey(val project: Project, val jvmName: JvmClassName, val file: VirtualFile)
|
||||
|
||||
private fun readClassFileImpl(project: Project,
|
||||
jvmName: JvmClassName,
|
||||
file: VirtualFile): ByteArray? {
|
||||
val fqNameWithInners = jvmName.fqNameForClassNameWithoutDollars.tail(jvmName.packageFqName)
|
||||
|
||||
fun readFromLibrary(): ByteArray? {
|
||||
if (!ProjectRootsUtil.isLibrarySourceFile(project, file)) return null
|
||||
|
||||
val classId = ClassId(jvmName.packageFqName, Name.identifier(fqNameWithInners.asString()))
|
||||
|
||||
// TODO use debugger search scope
|
||||
val fileFinder = VirtualFileFinderFactory.getInstance(project).create(GlobalSearchScope.allScope(project))
|
||||
val classFile = fileFinder.findVirtualFileWithHeader(classId) ?: return null
|
||||
return classFile.contentsToByteArray(false)
|
||||
}
|
||||
|
||||
fun readFromOutput(isForTestClasses: Boolean): ByteArray? {
|
||||
if (!ProjectRootsUtil.isProjectSourceFile(project, file)) return null
|
||||
|
||||
val module = ProjectFileIndex.SERVICE.getInstance(project).getModuleForFile(file) ?: return null
|
||||
|
||||
val outputPaths = CompilerPathsEx.getOutputPaths(arrayOf(module)).toList()
|
||||
val className = fqNameWithInners.asString().replace('.', '$')
|
||||
var classFile = findClassFileByPaths(jvmName.packageFqName.asString(), className, outputPaths)
|
||||
|
||||
if (classFile == null) {
|
||||
if (!isForTestClasses) {
|
||||
return null
|
||||
}
|
||||
|
||||
val outputDir = CompilerPaths.getModuleOutputDirectory(module, /*forTests = */ isForTestClasses) ?: return null
|
||||
|
||||
val outputModeDirName = outputDir.name
|
||||
// FIXME: It looks like this doesn't work anymore after Kotlin gradle plugin have stopped generating Kotlin classes in java output dir
|
||||
// Originally this code did mapping like 'path/classes/test/debug' -> 'path/classes/androidTest/debug'
|
||||
val androidTestOutputDir = outputDir.parent?.parent?.findChild("androidTest")?.findChild(outputModeDirName) ?: return null
|
||||
|
||||
classFile = findClassFileByPath(jvmName.packageFqName.asString(), className, androidTestOutputDir.path) ?: return null
|
||||
}
|
||||
|
||||
return classFile.readBytes()
|
||||
}
|
||||
|
||||
fun readFromSourceOutput(): ByteArray? = readFromOutput(false)
|
||||
|
||||
fun readFromTestOutput(): ByteArray? = readFromOutput(true)
|
||||
|
||||
return readFromLibrary() ?:
|
||||
readFromSourceOutput() ?:
|
||||
readFromTestOutput()
|
||||
}
|
||||
|
||||
private fun findClassFileByPaths(packageName: String, className: String, paths: List<String>): File? =
|
||||
paths.mapNotNull { path -> findClassFileByPath(packageName, className, path) }.maxBy { it.lastModified() }
|
||||
|
||||
private fun findClassFileByPath(packageName: String, className: String, outputDirPath: String): File? {
|
||||
val outDirFile = File(outputDirPath).takeIf(File::exists) ?: return null
|
||||
|
||||
val parentDirectory = File(outDirFile, packageName.replace(".", File.separator))
|
||||
if (!parentDirectory.exists()) return null
|
||||
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
val beforeDexFileClassFile = File(parentDirectory, className + ".class.before_dex")
|
||||
if (beforeDexFileClassFile.exists()) {
|
||||
return beforeDexFileClassFile
|
||||
}
|
||||
}
|
||||
|
||||
val classFile = File(parentDirectory, className + ".class")
|
||||
if (classFile.exists()) {
|
||||
return classFile
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun readLineNumberTableMapping(bytes: ByteArray): Map<BytecodeMethodKey, Map<String, Set<Int>>> {
|
||||
val lineNumberMapping = HashMap<BytecodeMethodKey, Map<String, Set<Int>>>()
|
||||
|
||||
ClassReader(bytes).accept(object : ClassVisitor(Opcodes.API_VERSION) {
|
||||
override fun visitMethod(access: Int, name: String?, desc: String?, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
|
||||
if (name == null || desc == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val methodKey = BytecodeMethodKey(name, desc)
|
||||
val methodLinesMapping = HashMap<String, MutableSet<Int>>()
|
||||
lineNumberMapping[methodKey] = methodLinesMapping
|
||||
|
||||
return object : MethodVisitor(Opcodes.API_VERSION, null) {
|
||||
override fun visitLineNumber(line: Int, start: Label?) {
|
||||
if (start != null) {
|
||||
methodLinesMapping.getOrPutNullable(start.toString(), { LinkedHashSet<Int>() }).add(line)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}, ClassReader.SKIP_FRAMES and ClassReader.SKIP_CODE)
|
||||
|
||||
return lineNumberMapping
|
||||
}
|
||||
|
||||
internal fun getOriginalPositionOfInlinedLine(location: Location, project: Project): Pair<KtFile, Int>? {
|
||||
val lineNumber = location.lineNumber()
|
||||
val fqName = FqName(location.declaringType().name())
|
||||
val fileName = location.sourceName()
|
||||
val searchScope = GlobalSearchScope.allScope(project)
|
||||
|
||||
val debugInfo = findAndReadClassFile(fqName, fileName, project, searchScope, { isInlineFunctionLineNumber(it, lineNumber, project) }) ?:
|
||||
return null
|
||||
val smapData = debugInfo.smapData ?: return null
|
||||
|
||||
return mapStacktraceLineToSource(smapData, lineNumber, project, SourceLineKind.EXECUTED_LINE, searchScope)
|
||||
}
|
||||
|
||||
private fun findAndReadClassFile(
|
||||
fqName: FqName, fileName: String, project: Project, searchScope: GlobalSearchScope,
|
||||
fileFilter: (VirtualFile) -> Boolean): BytecodeDebugInfo? {
|
||||
val internalName = fqName.asString().replace('.', '/')
|
||||
val jvmClassName = JvmClassName.byInternalName(internalName)
|
||||
|
||||
val file = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, jvmClassName, fileName) ?: return null
|
||||
|
||||
val virtualFile = file.virtualFile ?: return null
|
||||
if (!fileFilter(virtualFile)) return null
|
||||
|
||||
return readBytecodeInfo(project, jvmClassName, virtualFile)
|
||||
}
|
||||
|
||||
internal fun getLocationsOfInlinedLine(type: ReferenceType, position: SourcePosition, sourceSearchScope: GlobalSearchScope): List<Location> {
|
||||
val line = position.line
|
||||
val file = position.file
|
||||
val project = position.file.project
|
||||
|
||||
val lineStartOffset = file.getLineStartOffset(line) ?: return listOf()
|
||||
val element = file.findElementAt(lineStartOffset) ?: return listOf()
|
||||
val ktElement = element.parents.firstIsInstanceOrNull<KtElement>() ?: return listOf()
|
||||
|
||||
val isInInline = runReadAction { element.parents.any { it is KtFunction && it.hasModifier(KtTokens.INLINE_KEYWORD) } }
|
||||
|
||||
if (!isInInline) {
|
||||
// Lambdas passed to crossinline arguments are inlined when they are used in non-inlined lambdas
|
||||
val isInCrossinlineArgument = isInCrossinlineArgument(ktElement)
|
||||
if (!isInCrossinlineArgument) {
|
||||
return listOf()
|
||||
}
|
||||
}
|
||||
|
||||
val lines = inlinedLinesNumbers(line + 1, position.file.name, FqName(type.name()), type.sourceName(), project, sourceSearchScope)
|
||||
|
||||
return lines.flatMap { type.locationsOfLine(it) }
|
||||
}
|
||||
|
||||
fun isInCrossinlineArgument(ktElement: KtElement): Boolean {
|
||||
val argumentFunctions = runReadAction {
|
||||
ktElement.parents.filter {
|
||||
when (it) {
|
||||
is KtFunctionLiteral -> it.parent is KtLambdaExpression && (it.parent.parent is KtValueArgument || it.parent.parent is KtLambdaArgument)
|
||||
is KtFunction -> it.parent is KtValueArgument
|
||||
else -> false
|
||||
}
|
||||
}.filterIsInstance<KtFunction>()
|
||||
}
|
||||
|
||||
val bindingContext = ktElement.analyze(BodyResolveMode.PARTIAL)
|
||||
return argumentFunctions.any {
|
||||
val argumentDescriptor = InlineUtil.getInlineArgumentDescriptor(it, bindingContext)
|
||||
argumentDescriptor?.isCrossinline ?: false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun inlinedLinesNumbers(
|
||||
inlineLineNumber: Int, inlineFileName: String,
|
||||
destinationTypeFqName: FqName, destinationFileName: String,
|
||||
project: Project, sourceSearchScope: GlobalSearchScope): List<Int> {
|
||||
val internalName = destinationTypeFqName.asString().replace('.', '/')
|
||||
val jvmClassName = JvmClassName.byInternalName(internalName)
|
||||
|
||||
val file = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, sourceSearchScope, jvmClassName, destinationFileName) ?:
|
||||
return listOf()
|
||||
|
||||
val virtualFile = file.virtualFile ?: return listOf()
|
||||
|
||||
val debugInfo = readBytecodeInfo(project, jvmClassName, virtualFile) ?: return listOf()
|
||||
val smapData = debugInfo.smapData ?: return listOf()
|
||||
|
||||
val smap = smapData.kotlinStrata ?: return listOf()
|
||||
|
||||
val mappingsToInlinedFile = smap.fileMappings.filter { it.name == inlineFileName }
|
||||
val mappingIntervals = mappingsToInlinedFile.flatMap { it.lineMappings }
|
||||
|
||||
return mappingIntervals.asSequence().
|
||||
filter { rangeMapping -> rangeMapping.hasMappingForSource(inlineLineNumber) }.
|
||||
map { rangeMapping -> rangeMapping.mapSourceToDest(inlineLineNumber) }.
|
||||
filter { line -> line != -1 }.
|
||||
toList()
|
||||
}
|
||||
|
||||
@Volatile var emulateDexDebugInTests: Boolean = false
|
||||
|
||||
fun DebugProcess.isDexDebug(): Boolean {
|
||||
val virtualMachine = (this.virtualMachineProxy as? VirtualMachineProxyImpl)?.virtualMachine
|
||||
return virtualMachine.isDexDebug()
|
||||
}
|
||||
|
||||
fun VirtualMachine?.isDexDebug(): Boolean {
|
||||
// TODO: check other machine names
|
||||
return (emulateDexDebugInTests && ApplicationManager.getApplication().isUnitTestMode) || this?.name() == "Dalvik"
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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
|
||||
|
||||
import com.intellij.ide.util.PropertiesComponent
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.actionSystem.ToggleAction
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.xdebugger.XDebugSession
|
||||
import com.intellij.xdebugger.impl.XDebuggerUtilImpl
|
||||
import org.jetbrains.kotlin.idea.KotlinFileTypeFactory
|
||||
|
||||
class ToggleKotlinVariablesState {
|
||||
companion object {
|
||||
private const val KOTLIN_VARIABLE_VIEW = "debugger.kotlin.variable.view"
|
||||
|
||||
fun getService(): ToggleKotlinVariablesState {
|
||||
return ServiceManager.getService(ToggleKotlinVariablesState::class.java)
|
||||
}
|
||||
}
|
||||
|
||||
var kotlinVariableView = PropertiesComponent.getInstance().getBoolean(KOTLIN_VARIABLE_VIEW, true)
|
||||
set(newValue) {
|
||||
field = newValue
|
||||
PropertiesComponent.getInstance().setValue(KOTLIN_VARIABLE_VIEW, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
class ToggleKotlinVariablesView : ToggleAction() {
|
||||
private val kotlinVariableViewService = ToggleKotlinVariablesState.getService()
|
||||
|
||||
override fun update(e: AnActionEvent) {
|
||||
super.update(e)
|
||||
val session = XDebugSession.DATA_KEY.getData(e.dataContext)
|
||||
e.presentation.isEnabledAndVisible = session != null && session.isInKotlinFile()
|
||||
}
|
||||
|
||||
private fun XDebugSession.isInKotlinFile(): Boolean {
|
||||
val fileExtension = currentPosition?.file?.extension ?: return false
|
||||
return fileExtension in KotlinFileTypeFactory.KOTLIN_EXTENSIONS
|
||||
}
|
||||
|
||||
override fun isSelected(e: AnActionEvent) = kotlinVariableViewService.kotlinVariableView
|
||||
|
||||
override fun setSelected(e: AnActionEvent, state: Boolean) {
|
||||
kotlinVariableViewService.kotlinVariableView = state
|
||||
XDebuggerUtilImpl.rebuildAllSessionsViews(e.project)
|
||||
}
|
||||
}
|
||||
-168
@@ -1,168 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinBreakpointFiltersPanel">
|
||||
<grid id="27dc6" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="500" height="400"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<grid id="5736" binding="myConditionsPanel" layout-manager="GridLayoutManager" row-count="4" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="7">
|
||||
<margin top="2" left="2" bottom="5" right="5"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<clientProperties>
|
||||
<BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithoutIndent"/>
|
||||
</clientProperties>
|
||||
<border type="etched" title-resource-bundle="messages/DebuggerBundle" title-key="label.breakpoint.properties.panel.group.conditions"/>
|
||||
<children>
|
||||
<grid id="8e867" binding="myInstanceFiltersPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="17e11" class="javax.swing.JCheckBox" binding="myInstanceFiltersCheckBox">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<margin top="2" left="2" bottom="0" right="2"/>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="breakpoint.properties.panel.option.instance.filters"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="5231f" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<hspacer id="eeee7">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="15" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
<xy id="28068" binding="myInstanceFiltersFieldPanel" layout-manager="XYLayout" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</xy>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="25884" binding="myClassFiltersPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="729d5" class="javax.swing.JCheckBox" binding="myClassFiltersCheckBox">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<margin top="2" left="2" bottom="0" right="2"/>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="breakpoint.properties.panel.option.class.filters"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="9bef6" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<xy id="e3d10" binding="myClassFiltersFieldPanel" layout-manager="XYLayout" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children/>
|
||||
</xy>
|
||||
<hspacer id="ec2a">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="15" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<grid id="275ca" binding="myPassCountPanel" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="0">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="3" hsize-policy="7" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="27bbc" class="javax.swing.JCheckBox" binding="myPassCountCheckbox">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<margin top="2" left="2" bottom="0" right="2"/>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="breakpoint.properties.panel.option.pass.count"/>
|
||||
</properties>
|
||||
</component>
|
||||
<grid id="71095" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="33fef" class="javax.swing.JTextField" binding="myPassCountField">
|
||||
<constraints>
|
||||
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="15" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties>
|
||||
<enabled value="false"/>
|
||||
<horizontalAlignment value="10"/>
|
||||
</properties>
|
||||
</component>
|
||||
<hspacer id="28e6f">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="0" anchor="0" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="15" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
</hspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
<vspacer id="d1c29">
|
||||
<constraints>
|
||||
<grid row="3" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
</children>
|
||||
</grid>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
-403
@@ -1,403 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints;
|
||||
|
||||
import com.intellij.debugger.InstanceFilter;
|
||||
import com.intellij.debugger.ui.breakpoints.EditClassFiltersDialog;
|
||||
import com.intellij.debugger.ui.breakpoints.EditInstanceFiltersDialog;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiClass;
|
||||
import com.intellij.ui.FieldPanel;
|
||||
import com.intellij.ui.MultiLineTooltipUI;
|
||||
import com.intellij.ui.classFilter.ClassFilter;
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint;
|
||||
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel;
|
||||
import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase;
|
||||
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class KotlinBreakpointFiltersPanel<T extends KotlinPropertyBreakpointProperties, B extends XBreakpoint<T>> extends XBreakpointCustomPropertiesPanel<B> {
|
||||
private JPanel myConditionsPanel;
|
||||
private JPanel myInstanceFiltersPanel;
|
||||
private JCheckBox myInstanceFiltersCheckBox;
|
||||
private JPanel myInstanceFiltersFieldPanel;
|
||||
private JPanel myClassFiltersPanel;
|
||||
private JCheckBox myClassFiltersCheckBox;
|
||||
private JPanel myClassFiltersFieldPanel;
|
||||
private JPanel myPassCountPanel;
|
||||
private JCheckBox myPassCountCheckbox;
|
||||
private JTextField myPassCountField;
|
||||
|
||||
private final FieldPanel myInstanceFiltersField;
|
||||
private final FieldPanel myClassFiltersField;
|
||||
|
||||
private ClassFilter[] myClassFilters = ClassFilter.EMPTY_ARRAY;
|
||||
private ClassFilter[] myClassExclusionFilters = ClassFilter.EMPTY_ARRAY;
|
||||
private InstanceFilter[] myInstanceFilters = InstanceFilter.EMPTY_ARRAY;
|
||||
protected final Project myProject;
|
||||
|
||||
private PsiClass myBreakpointPsiClass;
|
||||
|
||||
public KotlinBreakpointFiltersPanel(Project project) {
|
||||
myProject = project;
|
||||
myInstanceFiltersField = new FieldPanel(new MyTextField(), "", null,
|
||||
new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
reloadInstanceFilters();
|
||||
EditInstanceFiltersDialog _dialog = new EditInstanceFiltersDialog(myProject);
|
||||
_dialog.setFilters(myInstanceFilters);
|
||||
_dialog.show();
|
||||
if (_dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
|
||||
myInstanceFilters = _dialog.getFilters();
|
||||
updateInstanceFilterEditor(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
myClassFiltersField = new FieldPanel(new MyTextField(), "", null,
|
||||
new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
reloadClassFilters();
|
||||
|
||||
com.intellij.ide.util.ClassFilter classFilter = createClassConditionFilter();
|
||||
|
||||
EditClassFiltersDialog _dialog = new EditClassFiltersDialog(myProject, classFilter);
|
||||
_dialog.setFilters(myClassFilters, myClassExclusionFilters);
|
||||
_dialog.show();
|
||||
if (_dialog.getExitCode() == DialogWrapper.OK_EXIT_CODE) {
|
||||
myClassFilters = _dialog.getFilters();
|
||||
myClassExclusionFilters = _dialog.getExclusionFilters();
|
||||
updateClassFilterEditor(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
null
|
||||
);
|
||||
|
||||
ActionListener updateListener = new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
updateCheckboxes();
|
||||
}
|
||||
};
|
||||
|
||||
myPassCountCheckbox.addActionListener(updateListener);
|
||||
myInstanceFiltersCheckBox.addActionListener(updateListener);
|
||||
myClassFiltersCheckBox.addActionListener(updateListener);
|
||||
|
||||
ToolTipManager.sharedInstance().registerComponent(myClassFiltersField.getTextField());
|
||||
ToolTipManager.sharedInstance().registerComponent(myInstanceFiltersField.getTextField());
|
||||
|
||||
insert(myInstanceFiltersFieldPanel, myInstanceFiltersField);
|
||||
insert(myClassFiltersFieldPanel, myClassFiltersField);
|
||||
|
||||
DebuggerUIUtil.focusEditorOnCheck(myPassCountCheckbox, myPassCountField);
|
||||
DebuggerUIUtil.focusEditorOnCheck(myInstanceFiltersCheckBox, myInstanceFiltersField.getTextField());
|
||||
DebuggerUIUtil.focusEditorOnCheck(myClassFiltersCheckBox, myClassFiltersField.getTextField());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public JComponent getComponent() {
|
||||
return myConditionsPanel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isVisibleOnPopup(@NotNull B breakpoint) {
|
||||
JavaBreakpointProperties properties = breakpoint.getProperties();
|
||||
if (properties != null) {
|
||||
return properties.isCOUNT_FILTER_ENABLED() || properties.isCLASS_FILTERS_ENABLED() || properties.isINSTANCE_FILTERS_ENABLED();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveTo(@NotNull B breakpoint) {
|
||||
JavaBreakpointProperties properties = breakpoint.getProperties();
|
||||
if (properties == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean changed = false;
|
||||
try {
|
||||
String text = myPassCountField.getText().trim();
|
||||
int filter = !text.isEmpty() ? Integer.parseInt(text) : 0;
|
||||
if (filter < 0) filter = 0;
|
||||
changed = properties.setCOUNT_FILTER(filter);
|
||||
}
|
||||
catch (Exception ignored) {
|
||||
}
|
||||
|
||||
changed = properties.setCOUNT_FILTER_ENABLED(properties.getCOUNT_FILTER() > 0 && myPassCountCheckbox.isSelected()) || changed;
|
||||
reloadInstanceFilters();
|
||||
reloadClassFilters();
|
||||
updateInstanceFilterEditor(true);
|
||||
updateClassFilterEditor(true);
|
||||
|
||||
changed = properties.setINSTANCE_FILTERS_ENABLED(myInstanceFiltersField.getText().length() > 0 && myInstanceFiltersCheckBox.isSelected()) || changed;
|
||||
changed = properties.setCLASS_FILTERS_ENABLED(myClassFiltersField.getText().length() > 0 && myClassFiltersCheckBox.isSelected()) || changed;
|
||||
changed = properties.setClassFilters(myClassFilters) || changed;
|
||||
changed = properties.setClassExclusionFilters(myClassExclusionFilters) || changed;
|
||||
changed = properties.setInstanceFilters(myInstanceFilters) || changed;
|
||||
if (changed) {
|
||||
((XBreakpointBase)breakpoint).fireBreakpointChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private static void insert(JPanel panel, JComponent component) {
|
||||
panel.setLayout(new BorderLayout());
|
||||
panel.add(component, BorderLayout.CENTER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadFrom(@NotNull B breakpoint) {
|
||||
JavaBreakpointProperties properties = breakpoint.getProperties();
|
||||
if (properties != null) {
|
||||
if (properties.getCOUNT_FILTER() > 0) {
|
||||
myPassCountField.setText(Integer.toString(properties.getCOUNT_FILTER()));
|
||||
}
|
||||
else {
|
||||
myPassCountField.setText("");
|
||||
}
|
||||
|
||||
myPassCountCheckbox.setSelected(properties.isCOUNT_FILTER_ENABLED());
|
||||
|
||||
myInstanceFiltersCheckBox.setSelected(properties.isINSTANCE_FILTERS_ENABLED());
|
||||
myInstanceFiltersField.setEnabled(properties.isINSTANCE_FILTERS_ENABLED());
|
||||
myInstanceFiltersField.getTextField().setEditable(properties.isINSTANCE_FILTERS_ENABLED());
|
||||
myInstanceFilters = properties.getInstanceFilters();
|
||||
updateInstanceFilterEditor(true);
|
||||
|
||||
myClassFiltersCheckBox.setSelected(properties.isCLASS_FILTERS_ENABLED());
|
||||
myClassFiltersField.setEnabled(properties.isCLASS_FILTERS_ENABLED());
|
||||
myClassFiltersField.getTextField().setEditable(properties.isCLASS_FILTERS_ENABLED());
|
||||
myClassFilters = properties.getClassFilters();
|
||||
myClassExclusionFilters = properties.getClassExclusionFilters();
|
||||
updateClassFilterEditor(true);
|
||||
|
||||
XSourcePosition position = breakpoint.getSourcePosition();
|
||||
// TODO: need to calculate psi class
|
||||
//myBreakpointPsiClass = breakpoint.getPsiClass();
|
||||
}
|
||||
updateCheckboxes();
|
||||
}
|
||||
|
||||
private void updateInstanceFilterEditor(boolean updateText) {
|
||||
List<String> filters = new ArrayList<String>();
|
||||
for (InstanceFilter instanceFilter : myInstanceFilters) {
|
||||
if (instanceFilter.isEnabled()) {
|
||||
filters.add(Long.toString(instanceFilter.getId()));
|
||||
}
|
||||
}
|
||||
if (updateText) {
|
||||
myInstanceFiltersField.setText(StringUtil.join(filters, " "));
|
||||
}
|
||||
|
||||
String tipText = concatWithEx(filters, " ", (int)Math.sqrt(myInstanceFilters.length) + 1, "\n");
|
||||
myInstanceFiltersField.getTextField().setToolTipText(tipText);
|
||||
}
|
||||
|
||||
private class MyTextField extends JTextField {
|
||||
public MyTextField() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getToolTipText(MouseEvent event) {
|
||||
reloadClassFilters();
|
||||
updateClassFilterEditor(false);
|
||||
reloadInstanceFilters();
|
||||
updateInstanceFilterEditor(false);
|
||||
String toolTipText = super.getToolTipText(event);
|
||||
return getToolTipText().length() == 0 ? null : toolTipText;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JToolTip createToolTip() {
|
||||
JToolTip toolTip = new JToolTip(){{
|
||||
setUI(new MultiLineTooltipUI());
|
||||
}};
|
||||
toolTip.setComponent(this);
|
||||
return toolTip;
|
||||
}
|
||||
}
|
||||
|
||||
private void reloadClassFilters() {
|
||||
String filtersText = myClassFiltersField.getText();
|
||||
|
||||
ArrayList<ClassFilter> classFilters = new ArrayList<ClassFilter>();
|
||||
ArrayList<ClassFilter> exclusionFilters = new ArrayList<ClassFilter>();
|
||||
int startFilter = -1;
|
||||
for(int i = 0; i <= filtersText.length(); i++) {
|
||||
if(i < filtersText.length() && !Character.isWhitespace(filtersText.charAt(i))){
|
||||
if(startFilter == -1) {
|
||||
startFilter = i;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(startFilter >=0) {
|
||||
if(filtersText.charAt(startFilter) == '-') {
|
||||
exclusionFilters.add(new ClassFilter(filtersText.substring(startFilter + 1, i)));
|
||||
}
|
||||
else {
|
||||
classFilters.add(new ClassFilter(filtersText.substring(startFilter, i)));
|
||||
}
|
||||
startFilter = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (ClassFilter classFilter : myClassFilters) {
|
||||
if (!classFilter.isEnabled()) {
|
||||
classFilters.add(classFilter);
|
||||
}
|
||||
}
|
||||
for (ClassFilter classFilter : myClassExclusionFilters) {
|
||||
if (!classFilter.isEnabled()) {
|
||||
exclusionFilters.add(classFilter);
|
||||
}
|
||||
}
|
||||
myClassFilters = classFilters .toArray(new ClassFilter[classFilters .size()]);
|
||||
myClassExclusionFilters = exclusionFilters.toArray(new ClassFilter[exclusionFilters.size()]);
|
||||
}
|
||||
|
||||
private void reloadInstanceFilters() {
|
||||
String filtersText = myInstanceFiltersField.getText();
|
||||
|
||||
ArrayList<InstanceFilter> idxs = new ArrayList<InstanceFilter>();
|
||||
int startNumber = -1;
|
||||
for(int i = 0; i <= filtersText.length(); i++) {
|
||||
if(i < filtersText.length() && Character.isDigit(filtersText.charAt(i))) {
|
||||
if(startNumber == -1) {
|
||||
startNumber = i;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if(startNumber >=0) {
|
||||
idxs.add(InstanceFilter.create(filtersText.substring(startNumber, i)));
|
||||
startNumber = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (InstanceFilter instanceFilter : myInstanceFilters) {
|
||||
if (!instanceFilter.isEnabled()) {
|
||||
idxs.add(instanceFilter);
|
||||
}
|
||||
}
|
||||
myInstanceFilters = idxs.toArray(new InstanceFilter[idxs.size()]);
|
||||
}
|
||||
|
||||
private void updateClassFilterEditor(boolean updateText) {
|
||||
List<String> filters = new ArrayList<String>();
|
||||
for (ClassFilter classFilter : myClassFilters) {
|
||||
if (classFilter.isEnabled()) {
|
||||
filters.add(classFilter.getPattern());
|
||||
}
|
||||
}
|
||||
List<String> excludeFilters = new ArrayList<String>();
|
||||
for (ClassFilter classFilter : myClassExclusionFilters) {
|
||||
if (classFilter.isEnabled()) {
|
||||
excludeFilters.add("-" + classFilter.getPattern());
|
||||
}
|
||||
}
|
||||
if (updateText) {
|
||||
String editorText = StringUtil.join(filters, " ");
|
||||
if(!filters.isEmpty()) {
|
||||
editorText += " ";
|
||||
}
|
||||
editorText += StringUtil.join(excludeFilters, " ");
|
||||
myClassFiltersField.setText(editorText);
|
||||
}
|
||||
|
||||
int width = (int)Math.sqrt(myClassExclusionFilters.length + myClassFilters.length) + 1;
|
||||
String tipText = concatWithEx(filters, " ", width, "\n");
|
||||
if(!filters.isEmpty()) {
|
||||
tipText += "\n";
|
||||
}
|
||||
tipText += concatWithEx(excludeFilters, " ", width, "\n");
|
||||
myClassFiltersField.getTextField().setToolTipText(tipText);
|
||||
}
|
||||
|
||||
private static String concatWithEx(List<String> s, String concator, int N, String NthConcator) {
|
||||
String result = "";
|
||||
int i = 1;
|
||||
for (Iterator iterator = s.iterator(); iterator.hasNext(); i++) {
|
||||
String str = (String) iterator.next();
|
||||
result += str;
|
||||
if(iterator.hasNext()){
|
||||
if(i % N == 0){
|
||||
result += NthConcator;
|
||||
}
|
||||
else {
|
||||
result += concator;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected com.intellij.ide.util.ClassFilter createClassConditionFilter() {
|
||||
com.intellij.ide.util.ClassFilter classFilter;
|
||||
if(myBreakpointPsiClass != null) {
|
||||
classFilter = new com.intellij.ide.util.ClassFilter() {
|
||||
@Override
|
||||
public boolean isAccepted(PsiClass aClass) {
|
||||
return myBreakpointPsiClass == aClass || aClass.isInheritor(myBreakpointPsiClass, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
else {
|
||||
classFilter = null;
|
||||
}
|
||||
return classFilter;
|
||||
}
|
||||
|
||||
protected void updateCheckboxes() {
|
||||
boolean passCountApplicable = true;
|
||||
if (myInstanceFiltersCheckBox.isSelected() || myClassFiltersCheckBox.isSelected()) {
|
||||
passCountApplicable = false;
|
||||
}
|
||||
myPassCountCheckbox.setEnabled(passCountApplicable);
|
||||
|
||||
boolean passCountSelected = myPassCountCheckbox.isSelected();
|
||||
myInstanceFiltersCheckBox.setEnabled(!passCountSelected);
|
||||
myClassFiltersCheckBox.setEnabled(!passCountSelected);
|
||||
|
||||
myPassCountField.setEditable(myPassCountCheckbox.isSelected());
|
||||
myPassCountField.setEnabled (myPassCountCheckbox.isSelected());
|
||||
|
||||
myInstanceFiltersField.setEnabled(myInstanceFiltersCheckBox.isSelected());
|
||||
myInstanceFiltersField.getTextField().setEditable(myInstanceFiltersCheckBox.isSelected());
|
||||
|
||||
myClassFiltersField.setEnabled(myClassFiltersCheckBox.isSelected());
|
||||
myClassFiltersField.getTextField().setEditable(myClassFiltersCheckBox.isSelected());
|
||||
}
|
||||
}
|
||||
-36
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.JavaBreakpointHandler
|
||||
import com.intellij.debugger.engine.JavaBreakpointHandlerFactory
|
||||
|
||||
class KotlinFieldBreakpointHandlerFactory : JavaBreakpointHandlerFactory {
|
||||
override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? {
|
||||
return KotlinFieldBreakpointHandler(process)
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinLineBreakpointHandlerFactory: JavaBreakpointHandlerFactory {
|
||||
override fun createHandler(process: DebugProcessImpl): JavaBreakpointHandler? {
|
||||
return KotlinLineBreakpointHandler(process)
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinFieldBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinFieldBreakpointType::class.java, process)
|
||||
class KotlinLineBreakpointHandler(process: DebugProcessImpl) : JavaBreakpointHandler(KotlinLineBreakpointType::class.java, process)
|
||||
-397
@@ -1,397 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.impl.PositionUtil
|
||||
import com.intellij.debugger.requests.Requestor
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointCategory
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointWithHighlighter
|
||||
import com.intellij.debugger.ui.breakpoints.FieldBreakpoint
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiFile
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.Method
|
||||
import com.sun.jdi.ReferenceType
|
||||
import com.sun.jdi.event.*
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import com.sun.jdi.request.MethodEntryRequest
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.debugger.safeAllLineLocations
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.psi.KtCallableDeclaration
|
||||
import org.jetbrains.kotlin.psi.KtClassOrObject
|
||||
import org.jetbrains.kotlin.psi.KtParameter
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import javax.swing.Icon
|
||||
|
||||
class KotlinFieldBreakpoint(
|
||||
project: Project,
|
||||
breakpoint: XBreakpoint<KotlinPropertyBreakpointProperties>
|
||||
): BreakpointWithHighlighter<KotlinPropertyBreakpointProperties>(project, breakpoint) {
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.breakpoints.KotlinFieldBreakpoint")
|
||||
private val CATEGORY: Key<FieldBreakpoint> = BreakpointCategory.lookup<FieldBreakpoint>("field_breakpoints")
|
||||
}
|
||||
|
||||
private enum class BreakpointType {
|
||||
FIELD,
|
||||
METHOD
|
||||
}
|
||||
|
||||
private var breakpointType: BreakpointType = BreakpointType.FIELD
|
||||
|
||||
override fun isValid(): Boolean {
|
||||
if (!BreakpointWithHighlighter.isPositionValid(xBreakpoint.sourcePosition)) return false
|
||||
|
||||
return runReadAction {
|
||||
val field = getField()
|
||||
field != null && field.isValid
|
||||
}
|
||||
}
|
||||
|
||||
fun getField(): KtCallableDeclaration? {
|
||||
val sourcePosition = sourcePosition
|
||||
return getProperty(sourcePosition)
|
||||
}
|
||||
|
||||
private fun getProperty(sourcePosition: SourcePosition?): KtCallableDeclaration? {
|
||||
val property: KtProperty? = PositionUtil.getPsiElementAt(project, KtProperty::class.java, sourcePosition)
|
||||
if (property != null) {
|
||||
return property
|
||||
}
|
||||
val parameter: KtParameter? = PositionUtil.getPsiElementAt(project, KtParameter::class.java, sourcePosition)
|
||||
if (parameter != null) {
|
||||
return parameter
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun reload() {
|
||||
super.reload()
|
||||
|
||||
val property = getProperty(sourcePosition) ?: return
|
||||
val propertyName = property.name ?: return
|
||||
setFieldName(propertyName)
|
||||
|
||||
if (property is KtProperty && property.isTopLevel) {
|
||||
properties.myClassName = JvmFileClassUtil.getFileClassInfoNoResolve(property.getContainingKtFile()).fileClassFqName.asString()
|
||||
} else {
|
||||
val ktClass: KtClassOrObject? = PsiTreeUtil.getParentOfType(property, KtClassOrObject::class.java)
|
||||
if (ktClass is KtClassOrObject) {
|
||||
val fqName = ktClass.fqName
|
||||
if (fqName != null) {
|
||||
properties.myClassName = fqName.asString()
|
||||
}
|
||||
}
|
||||
}
|
||||
isInstanceFiltersEnabled = false
|
||||
}
|
||||
|
||||
override fun createRequestForPreparedClass(debugProcess: DebugProcessImpl?, refType: ReferenceType?) {
|
||||
if (debugProcess == null || refType == null) return
|
||||
|
||||
val property = getProperty(sourcePosition) ?: return
|
||||
|
||||
breakpointType = (computeBreakpointType(property) ?: return)
|
||||
|
||||
val vm = debugProcess.virtualMachineProxy
|
||||
try {
|
||||
if (properties.WATCH_INITIALIZATION) {
|
||||
val sourcePosition = sourcePosition
|
||||
if (sourcePosition != null) {
|
||||
debugProcess.positionManager
|
||||
.locationsOfLine(refType, sourcePosition)
|
||||
.filter { it.method().isConstructor || it.method().isStaticInitializer }
|
||||
.forEach {
|
||||
val request = debugProcess.requestsManager.createBreakpointRequest(this, it)
|
||||
debugProcess.requestsManager.enableRequest(request)
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Breakpoint request added")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (breakpointType) {
|
||||
BreakpointType.FIELD -> {
|
||||
val field = refType.fieldByName(getFieldName())
|
||||
if (field != null) {
|
||||
val manager = debugProcess.requestsManager
|
||||
if (properties.WATCH_MODIFICATION && vm.canWatchFieldModification()) {
|
||||
val request = manager.createModificationWatchpointRequest(this, field)
|
||||
debugProcess.requestsManager.enableRequest(request)
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Modification request added")
|
||||
}
|
||||
}
|
||||
if (properties.WATCH_ACCESS && vm.canWatchFieldAccess()) {
|
||||
val request = manager.createAccessWatchpointRequest(this, field)
|
||||
debugProcess.requestsManager.enableRequest(request)
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Field access request added (field = ${field.name()}; refType = ${refType.name()})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
BreakpointType.METHOD -> {
|
||||
val fieldName = getFieldName()
|
||||
|
||||
if (properties.WATCH_ACCESS) {
|
||||
val getter = refType.methodsByName(JvmAbi.getterName(fieldName)).firstOrNull()
|
||||
if (getter != null) {
|
||||
createMethodBreakpoint(debugProcess, refType, getter)
|
||||
}
|
||||
}
|
||||
|
||||
if (properties.WATCH_MODIFICATION) {
|
||||
val setter = refType.methodsByName(JvmAbi.setterName(fieldName)).firstOrNull()
|
||||
if (setter != null) {
|
||||
createMethodBreakpoint(debugProcess, refType, setter)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ex: Exception) {
|
||||
LOG.debug(ex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun computeBreakpointType(property: KtCallableDeclaration): BreakpointType? {
|
||||
return runReadAction {
|
||||
val bindingContext = property.analyze()
|
||||
var descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, property)
|
||||
if (descriptor is ValueParameterDescriptor) {
|
||||
descriptor = bindingContext.get(BindingContext.VALUE_PARAMETER_AS_PROPERTY, descriptor)
|
||||
}
|
||||
|
||||
if (descriptor is PropertyDescriptor) {
|
||||
if (bindingContext.get(BindingContext.BACKING_FIELD_REQUIRED, descriptor)!!) {
|
||||
BreakpointType.FIELD
|
||||
}
|
||||
else {
|
||||
BreakpointType.METHOD
|
||||
}
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createMethodBreakpoint(debugProcess: DebugProcessImpl, refType: ReferenceType, accessor: Method) {
|
||||
val manager = debugProcess.requestsManager
|
||||
val line = accessor.safeAllLineLocations().firstOrNull()
|
||||
if (line != null) {
|
||||
val request = manager.createBreakpointRequest(this, line)
|
||||
debugProcess.requestsManager.enableRequest(request)
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Breakpoint request added")
|
||||
}
|
||||
}
|
||||
else {
|
||||
var entryRequest: MethodEntryRequest? = findRequest(debugProcess, MethodEntryRequest::class.java, this)
|
||||
if (entryRequest == null) {
|
||||
entryRequest = manager.createMethodEntryRequest(this)!!
|
||||
if (LOG.isDebugEnabled) {
|
||||
LOG.debug("Method entry request added (method = ${accessor.name()}; refType = ${refType.name()})")
|
||||
}
|
||||
}
|
||||
else {
|
||||
entryRequest.disable()
|
||||
}
|
||||
entryRequest.addClassFilter(refType)
|
||||
manager.enableRequest(entryRequest)
|
||||
}
|
||||
}
|
||||
|
||||
inline private fun <reified T : EventRequest> findRequest(debugProcess: DebugProcessImpl, requestClass: Class<T>, requestor: Requestor): T? {
|
||||
val requests = debugProcess.requestsManager.findRequests(requestor)
|
||||
for (eventRequest in requests) {
|
||||
if (eventRequest::class.java == requestClass) {
|
||||
return eventRequest as T
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun evaluateCondition(context: EvaluationContextImpl, event: LocatableEvent): Boolean {
|
||||
if (breakpointType == BreakpointType.METHOD && !matchesEvent(event)) {
|
||||
return false
|
||||
}
|
||||
return super.evaluateCondition(context, event)
|
||||
}
|
||||
|
||||
fun matchesEvent(event: LocatableEvent): Boolean {
|
||||
val method = event.location()?.method()
|
||||
// TODO check property type
|
||||
return method != null && method.name() in getMethodsName()
|
||||
}
|
||||
|
||||
private fun getMethodsName(): List<String> {
|
||||
val fieldName = getFieldName()
|
||||
return listOf(JvmAbi.getterName(fieldName), JvmAbi.setterName(fieldName))
|
||||
}
|
||||
|
||||
override fun getEventMessage(event: LocatableEvent): String {
|
||||
val location = event.location()!!
|
||||
val locationQName = location.declaringType().name() + "." + location.method().name()
|
||||
val locationFileName = try {
|
||||
location.sourceName()
|
||||
}
|
||||
catch (e: AbsentInformationException) {
|
||||
fileName
|
||||
}
|
||||
catch (e: InternalError) {
|
||||
fileName
|
||||
}
|
||||
|
||||
val locationLine = location.lineNumber()
|
||||
when (event) {
|
||||
is ModificationWatchpointEvent-> {
|
||||
val field = event.field()
|
||||
return DebuggerBundle.message(
|
||||
"status.static.field.watchpoint.reached.access",
|
||||
field.declaringType().name(),
|
||||
field.name(),
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
is AccessWatchpointEvent -> {
|
||||
val field = event.field()
|
||||
return DebuggerBundle.message(
|
||||
"status.static.field.watchpoint.reached.access",
|
||||
field.declaringType().name(),
|
||||
field.name(),
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
is MethodEntryEvent -> {
|
||||
val method = event.method()
|
||||
return DebuggerBundle.message(
|
||||
"status.method.entry.breakpoint.reached",
|
||||
method.declaringType().name() + "." + method.name() + "()",
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
is MethodExitEvent -> {
|
||||
val method = event.method()
|
||||
return DebuggerBundle.message(
|
||||
"status.method.exit.breakpoint.reached",
|
||||
method.declaringType().name() + "." + method.name() + "()",
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
}
|
||||
return DebuggerBundle.message(
|
||||
"status.line.breakpoint.reached",
|
||||
locationQName,
|
||||
locationFileName,
|
||||
locationLine)
|
||||
}
|
||||
|
||||
fun setFieldName(fieldName: String) {
|
||||
properties.myFieldName = fieldName
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun setWatchAccess(value: Boolean) {
|
||||
properties.WATCH_ACCESS = value
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun setWatchModification(value: Boolean) {
|
||||
properties.WATCH_MODIFICATION = value
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun setWatchInitialization(value: Boolean) {
|
||||
properties.WATCH_INITIALIZATION = value
|
||||
}
|
||||
|
||||
override fun getDisabledIcon(isMuted: Boolean): Icon {
|
||||
val master = DebuggerManagerEx.getInstanceEx(myProject).breakpointManager.findMasterBreakpoint(this)
|
||||
return when {
|
||||
isMuted && master == null -> AllIcons.Debugger.Db_muted_disabled_field_breakpoint
|
||||
isMuted && master != null -> AllIcons.Debugger.Db_muted_dep_field_breakpoint
|
||||
master != null -> AllIcons.Debugger.Db_dep_field_breakpoint
|
||||
else -> AllIcons.Debugger.Db_disabled_field_breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSetIcon(isMuted: Boolean): Icon {
|
||||
return when {
|
||||
isMuted -> AllIcons.Debugger.Db_muted_field_breakpoint
|
||||
else -> AllIcons.Debugger.Db_field_breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
// BUNCH: 182
|
||||
override fun getInvalidIcon(isMuted: Boolean): Icon {
|
||||
return AllIcons.Debugger.Db_invalid_breakpoint
|
||||
}
|
||||
|
||||
override fun getVerifiedIcon(isMuted: Boolean): Icon {
|
||||
return when {
|
||||
isMuted -> AllIcons.Debugger.Db_muted_field_breakpoint
|
||||
else -> AllIcons.Debugger.Db_verified_field_breakpoint
|
||||
}
|
||||
}
|
||||
|
||||
override fun getVerifiedWarningsIcon(isMuted: Boolean): Icon = AllIcons.Debugger.Db_exception_breakpoint
|
||||
|
||||
override fun getCategory() = CATEGORY
|
||||
|
||||
override fun getDisplayName(): String? {
|
||||
if (!isValid) {
|
||||
return DebuggerBundle.message("status.breakpoint.invalid")
|
||||
}
|
||||
val className = className
|
||||
return if (className != null && !className.isEmpty()) className + "." + getFieldName() else getFieldName()
|
||||
}
|
||||
|
||||
private fun getFieldName(): String {
|
||||
val declaration = getField()
|
||||
return runReadAction { declaration?.name } ?: "unknown"
|
||||
}
|
||||
|
||||
override fun getEvaluationElement(): PsiElement? {
|
||||
return getField()
|
||||
}
|
||||
|
||||
}
|
||||
-88
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.ui.IdeBorderFactory
|
||||
import com.intellij.util.ui.DialogUtil
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel
|
||||
import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import java.awt.BorderLayout
|
||||
import javax.swing.Box
|
||||
import javax.swing.JCheckBox
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
import kotlin.properties.Delegates
|
||||
|
||||
class KotlinFieldBreakpointPropertiesPanel: XBreakpointCustomPropertiesPanel<XLineBreakpoint<KotlinPropertyBreakpointProperties>>() {
|
||||
private var myWatchInitializationCheckBox: JCheckBox by Delegates.notNull()
|
||||
private var myWatchAccessCheckBox: JCheckBox by Delegates.notNull()
|
||||
private var myWatchModificationCheckBox: JCheckBox by Delegates.notNull()
|
||||
|
||||
override fun getComponent(): JComponent {
|
||||
myWatchInitializationCheckBox = JCheckBox(KotlinBundle.message("debugger.field.watchpoints.properties.panel.field.initialization.label"))
|
||||
myWatchAccessCheckBox = JCheckBox(KotlinBundle.message("debugger.field.watchpoints.properties.panel.field.access.label"))
|
||||
myWatchModificationCheckBox = JCheckBox(KotlinBundle.message("debugger.field.watchpoints.properties.panel.field.modification.label"))
|
||||
|
||||
DialogUtil.registerMnemonic(myWatchInitializationCheckBox)
|
||||
DialogUtil.registerMnemonic(myWatchAccessCheckBox)
|
||||
DialogUtil.registerMnemonic(myWatchModificationCheckBox)
|
||||
|
||||
fun Box.addNewPanelForCheckBox(checkBox: JCheckBox) {
|
||||
val panel = JPanel(BorderLayout())
|
||||
panel.add(checkBox, BorderLayout.NORTH)
|
||||
this.add(panel)
|
||||
}
|
||||
|
||||
val watchBox = Box.createVerticalBox()
|
||||
watchBox.addNewPanelForCheckBox(myWatchInitializationCheckBox)
|
||||
watchBox.addNewPanelForCheckBox(myWatchAccessCheckBox)
|
||||
watchBox.addNewPanelForCheckBox(myWatchModificationCheckBox)
|
||||
|
||||
val mainPanel = JPanel(BorderLayout())
|
||||
val innerPanel = JPanel(BorderLayout())
|
||||
innerPanel.add(watchBox, BorderLayout.CENTER)
|
||||
innerPanel.add(Box.createHorizontalStrut(3), BorderLayout.WEST)
|
||||
innerPanel.add(Box.createHorizontalStrut(3), BorderLayout.EAST)
|
||||
mainPanel.add(innerPanel, BorderLayout.NORTH)
|
||||
mainPanel.border = IdeBorderFactory.createTitledBorder(DebuggerBundle.message("label.group.watch.events"), true)
|
||||
return mainPanel
|
||||
}
|
||||
|
||||
override fun loadFrom(breakpoint: XLineBreakpoint<KotlinPropertyBreakpointProperties>) {
|
||||
myWatchInitializationCheckBox.isSelected = breakpoint.properties.WATCH_INITIALIZATION
|
||||
myWatchAccessCheckBox.isSelected = breakpoint.properties.WATCH_ACCESS
|
||||
myWatchModificationCheckBox.isSelected = breakpoint.properties.WATCH_MODIFICATION
|
||||
}
|
||||
|
||||
override fun saveTo(breakpoint: XLineBreakpoint<KotlinPropertyBreakpointProperties>) {
|
||||
var changed = breakpoint.properties.WATCH_ACCESS != myWatchAccessCheckBox.isSelected
|
||||
breakpoint.properties.WATCH_ACCESS = myWatchAccessCheckBox.isSelected
|
||||
|
||||
changed = breakpoint.properties.WATCH_MODIFICATION != myWatchModificationCheckBox.isSelected || changed
|
||||
breakpoint.properties.WATCH_MODIFICATION = myWatchModificationCheckBox.isSelected
|
||||
|
||||
changed = breakpoint.properties.WATCH_INITIALIZATION != myWatchInitializationCheckBox.isSelected || changed
|
||||
breakpoint.properties.WATCH_INITIALIZATION = myWatchInitializationCheckBox.isSelected
|
||||
|
||||
if (changed) {
|
||||
(breakpoint as XBreakpointBase<*, *, *>).fireBreakpointChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
-178
@@ -1,178 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.ui.breakpoints.Breakpoint
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointManager
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointWithHighlighter
|
||||
import com.intellij.debugger.ui.breakpoints.JavaBreakpointType
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.Messages
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.xdebugger.XDebuggerManager
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpointType
|
||||
import com.intellij.xdebugger.breakpoints.ui.XBreakpointCustomPropertiesPanel
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForSourceDeclaration
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
|
||||
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||
import org.jetbrains.kotlin.idea.debugger.breakpoints.dialog.AddFieldBreakpointDialog
|
||||
import org.jetbrains.kotlin.idea.util.application.runWriteAction
|
||||
import org.jetbrains.kotlin.psi.KtDeclarationContainer
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import javax.swing.JComponent
|
||||
|
||||
class KotlinFieldBreakpointType : JavaBreakpointType<KotlinPropertyBreakpointProperties>, XLineBreakpointType<KotlinPropertyBreakpointProperties>(
|
||||
"kotlin-field", KotlinBundle.message("debugger.field.watchpoints.tab.title")
|
||||
) {
|
||||
override fun createJavaBreakpoint(project: Project, breakpoint: XBreakpoint<KotlinPropertyBreakpointProperties>): Breakpoint<KotlinPropertyBreakpointProperties> {
|
||||
return KotlinFieldBreakpoint(project, breakpoint)
|
||||
}
|
||||
|
||||
override fun canPutAt(file: VirtualFile, line: Int, project: Project): Boolean {
|
||||
return canPutAt(file, line, project, this::class.java)
|
||||
}
|
||||
|
||||
override fun getPriority() = 120
|
||||
|
||||
override fun createBreakpointProperties(file: VirtualFile, line: Int): KotlinPropertyBreakpointProperties? {
|
||||
return KotlinPropertyBreakpointProperties()
|
||||
}
|
||||
|
||||
override fun addBreakpoint(project: Project, parentComponent: JComponent?): XLineBreakpoint<KotlinPropertyBreakpointProperties>? {
|
||||
var result: XLineBreakpoint<KotlinPropertyBreakpointProperties>? = null
|
||||
|
||||
val dialog = object : AddFieldBreakpointDialog(project) {
|
||||
override fun validateData(): Boolean {
|
||||
val className = className
|
||||
if (className.isEmpty()) {
|
||||
reportError(project, DebuggerBundle.message("error.field.breakpoint.class.name.not.specified"))
|
||||
return false
|
||||
}
|
||||
|
||||
val psiClass = JavaPsiFacade.getInstance(project).findClass(className, GlobalSearchScope.allScope(project))
|
||||
if (psiClass !is KtLightClass) {
|
||||
reportError(project, "Couldn't find '$className' class")
|
||||
return false
|
||||
}
|
||||
|
||||
val fieldName = fieldName
|
||||
if (fieldName.isEmpty()) {
|
||||
reportError(project, DebuggerBundle.message("error.field.breakpoint.field.name.not.specified"))
|
||||
return false
|
||||
}
|
||||
|
||||
result = when (psiClass) {
|
||||
is KtLightClassForFacade -> {
|
||||
psiClass.files.asSequence().mapNotNull { createBreakpointIfPropertyExists(it, it, className, fieldName) }.firstOrNull()
|
||||
}
|
||||
is KtLightClassForSourceDeclaration -> {
|
||||
val jetClass = psiClass.kotlinOrigin
|
||||
createBreakpointIfPropertyExists(jetClass, jetClass.containingKtFile, className, fieldName)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (result == null) {
|
||||
reportError(project, DebuggerBundle.message("error.field.breakpoint.field.not.found", className, fieldName, fieldName))
|
||||
}
|
||||
|
||||
return result != null
|
||||
}
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
return result
|
||||
}
|
||||
|
||||
private fun createBreakpointIfPropertyExists(
|
||||
declaration: KtDeclarationContainer,
|
||||
file: KtFile,
|
||||
className: String,
|
||||
fieldName: String
|
||||
): XLineBreakpoint<KotlinPropertyBreakpointProperties>? {
|
||||
val project = file.project
|
||||
val property = declaration.declarations.firstOrNull { it is KtProperty && it.name == fieldName } ?: return null
|
||||
|
||||
val document = PsiDocumentManager.getInstance(project).getDocument(file) ?: return null
|
||||
val line = document.getLineNumber(property.textOffset)
|
||||
return runWriteAction {
|
||||
XDebuggerManager.getInstance(project).breakpointManager.addLineBreakpoint(
|
||||
this,
|
||||
file.virtualFile.url,
|
||||
line,
|
||||
KotlinPropertyBreakpointProperties(fieldName, className)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun reportError(project: Project, message: String) {
|
||||
Messages.showMessageDialog(project, message, DebuggerBundle.message("add.field.breakpoint.dialog.title"), Messages.getErrorIcon())
|
||||
}
|
||||
|
||||
override fun isAddBreakpointButtonVisible() = true
|
||||
|
||||
override fun getMutedEnabledIcon() = AllIcons.Debugger.Db_muted_field_breakpoint
|
||||
|
||||
override fun getDisabledIcon() = AllIcons.Debugger.Db_disabled_field_breakpoint
|
||||
|
||||
override fun getEnabledIcon() = AllIcons.Debugger.Db_field_breakpoint
|
||||
|
||||
override fun getMutedDisabledIcon() = AllIcons.Debugger.Db_muted_disabled_field_breakpoint
|
||||
|
||||
override fun canBeHitInOtherPlaces() = true
|
||||
|
||||
override fun getShortText(breakpoint: XLineBreakpoint<KotlinPropertyBreakpointProperties>): String? {
|
||||
val properties = breakpoint.properties
|
||||
val className = properties.myClassName
|
||||
return if (!className.isEmpty()) className + "." + properties.myFieldName else properties.myFieldName
|
||||
}
|
||||
|
||||
override fun createProperties(): KotlinPropertyBreakpointProperties? {
|
||||
return KotlinPropertyBreakpointProperties()
|
||||
}
|
||||
|
||||
override fun createCustomPropertiesPanel(): XBreakpointCustomPropertiesPanel<XLineBreakpoint<KotlinPropertyBreakpointProperties>>? {
|
||||
return KotlinFieldBreakpointPropertiesPanel()
|
||||
}
|
||||
|
||||
override fun getDisplayText(breakpoint: XLineBreakpoint<KotlinPropertyBreakpointProperties>): String? {
|
||||
val kotlinBreakpoint = BreakpointManager.getJavaBreakpoint(breakpoint) as? BreakpointWithHighlighter
|
||||
return if (kotlinBreakpoint != null) {
|
||||
kotlinBreakpoint.description
|
||||
}
|
||||
else {
|
||||
super.getDisplayText(breakpoint)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEditorsProvider() = null
|
||||
|
||||
override fun createCustomRightPropertiesPanel(project: Project): XBreakpointCustomPropertiesPanel<XLineBreakpoint<KotlinPropertyBreakpointProperties>>? {
|
||||
return KotlinBreakpointFiltersPanel(project)
|
||||
}
|
||||
|
||||
override fun isSuspendThreadSupported() = true
|
||||
}
|
||||
-66
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* 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.breakpoints
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcess
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.ui.breakpoints.LineBreakpoint
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.xdebugger.XSourcePosition
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpointProperties
|
||||
import com.sun.jdi.AbsentInformationException
|
||||
import com.sun.jdi.ReferenceType
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
||||
|
||||
class KotlinLineBreakpoint(
|
||||
project: Project?,
|
||||
xBreakpoint: XBreakpoint<out XBreakpointProperties<*>>?
|
||||
) : LineBreakpoint<JavaLineBreakpointProperties>(project, xBreakpoint) {
|
||||
override fun processClassPrepare(debugProcess: DebugProcess?, classType: ReferenceType?) {
|
||||
val sourcePosition = xBreakpoint?.sourcePosition
|
||||
|
||||
if (classType != null && sourcePosition != null) {
|
||||
if (!hasTargetLine(classType, sourcePosition)) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
super.processClassPrepare(debugProcess, classType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns false if `classType` definitely does not contain a location for a given `sourcePosition`.
|
||||
*/
|
||||
private fun hasTargetLine(classType: ReferenceType, sourcePosition: XSourcePosition): Boolean {
|
||||
val allLineLocations = DebuggerUtilsEx.allLineLocations(classType) ?: return true
|
||||
|
||||
if (classType.virtualMachine().isDexDebug()) {
|
||||
return true
|
||||
}
|
||||
|
||||
val fileName = sourcePosition.file.name
|
||||
val lineNumber = sourcePosition.line + 1
|
||||
|
||||
for (location in allLineLocations) {
|
||||
try {
|
||||
val kotlinFileName = location.sourceName(KOTLIN_STRATA_NAME)
|
||||
val kotlinLineNumber = location.lineNumber(KOTLIN_STRATA_NAME)
|
||||
if (kotlinFileName == fileName && kotlinLineNumber == lineNumber) {
|
||||
return true
|
||||
}
|
||||
} catch (e: AbsentInformationException) {
|
||||
if (location.sourceName() == fileName && location.lineNumber() == lineNumber) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
-152
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints;
|
||||
|
||||
import com.intellij.debugger.SourcePosition;
|
||||
import com.intellij.debugger.ui.breakpoints.Breakpoint;
|
||||
import com.intellij.debugger.ui.breakpoints.BreakpointManager;
|
||||
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType;
|
||||
import com.intellij.debugger.ui.breakpoints.LineBreakpoint;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.xdebugger.XSourcePosition;
|
||||
import com.intellij.xdebugger.breakpoints.XBreakpoint;
|
||||
import com.intellij.xdebugger.breakpoints.XLineBreakpoint;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties;
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaLineBreakpointProperties;
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinPositionManager;
|
||||
import org.jetbrains.kotlin.psi.KtClassInitializer;
|
||||
import org.jetbrains.kotlin.psi.KtFunction;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class KotlinLineBreakpointType extends JavaLineBreakpointType {
|
||||
public KotlinLineBreakpointType() {
|
||||
super("kotlin-line", "Kotlin Line Breakpoints");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Breakpoint<JavaLineBreakpointProperties> createJavaBreakpoint(
|
||||
Project project, XBreakpoint<JavaLineBreakpointProperties> breakpoint
|
||||
) {
|
||||
return new KotlinLineBreakpoint(project, breakpoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean matchesPosition(@NotNull LineBreakpoint<?> breakpoint, @NotNull SourcePosition position) {
|
||||
JavaBreakpointProperties properties = getProperties(breakpoint);
|
||||
if (properties == null || properties instanceof JavaLineBreakpointProperties) {
|
||||
if (position instanceof KotlinPositionManager.KotlinReentrantSourcePosition) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (properties != null && ((JavaLineBreakpointProperties) properties).getLambdaOrdinal() == null) return true;
|
||||
|
||||
PsiElement containingMethod = getContainingMethod(breakpoint);
|
||||
if (containingMethod == null) return false;
|
||||
return inTheMethod(position, containingMethod);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PsiElement getContainingMethod(@NotNull LineBreakpoint<?> breakpoint) {
|
||||
SourcePosition position = breakpoint.getSourcePosition();
|
||||
if (position == null) return null;
|
||||
|
||||
JavaBreakpointProperties properties = getProperties(breakpoint);
|
||||
if (properties instanceof JavaLineBreakpointProperties) {
|
||||
Integer ordinal = ((JavaLineBreakpointProperties) properties).getLambdaOrdinal();
|
||||
PsiElement lambda = getLambdaByOrdinal(position, ordinal);
|
||||
if (lambda != null) return lambda;
|
||||
}
|
||||
|
||||
return getContainingMethod(position.getElementAt());
|
||||
}
|
||||
|
||||
|
||||
@Nullable
|
||||
private static JavaBreakpointProperties getProperties(@NotNull LineBreakpoint<?> breakpoint) {
|
||||
XBreakpoint<?> xBreakpoint = breakpoint.getXBreakpoint();
|
||||
return xBreakpoint != null ? (JavaBreakpointProperties) xBreakpoint.getProperties() : null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static KtFunction getLambdaByOrdinal(SourcePosition position, Integer ordinal) {
|
||||
if (ordinal != null && ordinal >= 0) {
|
||||
List<KtFunction> lambdas = BreakpointTypeUtilsKt.getLambdasAtLineIfAny(position);
|
||||
if (lambdas.size() > ordinal) {
|
||||
return lambdas.get(ordinal);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PsiElement getContainingMethod(@Nullable PsiElement elem) {
|
||||
//noinspection unchecked
|
||||
return PsiTreeUtil.getParentOfType(elem, KtFunction.class, KtClassInitializer.class);
|
||||
}
|
||||
|
||||
public static boolean inTheMethod(@NotNull SourcePosition pos, @NotNull PsiElement method) {
|
||||
PsiElement elem = pos.getElementAt();
|
||||
if (elem == null) return false;
|
||||
return Comparing.equal(getContainingMethod(elem), method);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canPutAt(@NotNull VirtualFile file, int line, @NotNull Project project) {
|
||||
return BreakpointTypeUtilsKt.canPutAt(file, line, project, getClass());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public List<JavaBreakpointVariant> computeVariants(@NotNull Project project, @NotNull XSourcePosition position) {
|
||||
return BreakpointTypeUtilsKt.computeVariants(project, position, this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public TextRange getHighlightRange(XLineBreakpoint<JavaLineBreakpointProperties> breakpoint) {
|
||||
JavaLineBreakpointProperties properties = breakpoint.getProperties();
|
||||
if (properties != null) {
|
||||
Integer ordinal = properties.getLambdaOrdinal();
|
||||
if (ordinal != null) {
|
||||
Breakpoint javaBreakpoint = BreakpointManager.getJavaBreakpoint(breakpoint);
|
||||
if (javaBreakpoint instanceof LineBreakpoint) {
|
||||
SourcePosition position = ((LineBreakpoint) javaBreakpoint).getSourcePosition();
|
||||
if (position != null) {
|
||||
KtFunction lambda = getLambdaByOrdinal(position, ordinal);
|
||||
if (lambda != null) {
|
||||
return lambda.getTextRange();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.util.xmlb.annotations.Attribute
|
||||
import org.jetbrains.java.debugger.breakpoints.properties.JavaBreakpointProperties
|
||||
|
||||
class KotlinPropertyBreakpointProperties(
|
||||
@Attribute var myFieldName: String = "",
|
||||
@Attribute var myClassName: String = ""
|
||||
): JavaBreakpointProperties<KotlinPropertyBreakpointProperties>() {
|
||||
var WATCH_MODIFICATION: Boolean = true
|
||||
var WATCH_ACCESS: Boolean = false
|
||||
var WATCH_INITIALIZATION: Boolean = false
|
||||
|
||||
override fun getState() = this
|
||||
|
||||
override fun loadState(state: KotlinPropertyBreakpointProperties) {
|
||||
super.loadState(state)
|
||||
|
||||
WATCH_MODIFICATION = state.WATCH_MODIFICATION
|
||||
WATCH_ACCESS = state.WATCH_ACCESS
|
||||
WATCH_INITIALIZATION = state.WATCH_INITIALIZATION
|
||||
myFieldName = state.myFieldName
|
||||
myClassName = state.myClassName
|
||||
}
|
||||
}
|
||||
-150
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.ui.breakpoints.JavaLineBreakpointType
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiComment
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiManager
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.xdebugger.XDebuggerUtil
|
||||
import com.intellij.xdebugger.XSourcePosition
|
||||
import com.intellij.xdebugger.impl.XSourcePositionImpl
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.findElementAtLine
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineNumber
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
|
||||
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||
import java.util.*
|
||||
|
||||
fun canPutAt(file: VirtualFile, line: Int, project: Project, breakpointTypeClass: Class<*>): Boolean {
|
||||
val psiFile = PsiManager.getInstance(project).findFile(file)
|
||||
|
||||
if (psiFile == null || psiFile.virtualFile?.fileType != KotlinFileType.INSTANCE) {
|
||||
return false
|
||||
}
|
||||
|
||||
val document = FileDocumentManager.getInstance().getDocument(file) ?: return false
|
||||
|
||||
var result: Class<*>? = null
|
||||
XDebuggerUtil.getInstance().iterateLine(project, document, line, fun (el: PsiElement): Boolean {
|
||||
// avoid comments
|
||||
if (el is PsiWhiteSpace || PsiTreeUtil.getParentOfType(el, PsiComment::class.java, false) != null) {
|
||||
return true
|
||||
}
|
||||
|
||||
var element = el
|
||||
var parent = element.parent
|
||||
while (parent != null) {
|
||||
val offset = parent.textOffset
|
||||
if (offset >= 0 && document.getLineNumber(offset) != line) break
|
||||
|
||||
element = parent
|
||||
parent = element.parent
|
||||
}
|
||||
|
||||
if (element is KtProperty || element is KtParameter) {
|
||||
result = if ((element is KtParameter && element.hasValOrVar()) || (element is KtProperty && !element.isLocal)) {
|
||||
KotlinFieldBreakpointType::class.java
|
||||
}
|
||||
else {
|
||||
KotlinLineBreakpointType::class.java
|
||||
}
|
||||
return false
|
||||
}
|
||||
else {
|
||||
result = KotlinLineBreakpointType::class.java
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
|
||||
return result == breakpointTypeClass
|
||||
}
|
||||
|
||||
fun computeVariants(
|
||||
project: Project,
|
||||
position: XSourcePosition,
|
||||
kotlinBreakpointType: KotlinLineBreakpointType
|
||||
): List<JavaLineBreakpointType.JavaBreakpointVariant> {
|
||||
val file = PsiManager.getInstance(project).findFile(position.file) as? KtFile ?: return emptyList()
|
||||
|
||||
val pos = SourcePosition.createFromLine(file, position.line)
|
||||
val lambdas = getLambdasAtLineIfAny(pos)
|
||||
if (lambdas.isEmpty()) return emptyList()
|
||||
|
||||
val result = LinkedList<JavaLineBreakpointType.JavaBreakpointVariant>()
|
||||
|
||||
val elementAt = pos.elementAt.parentsWithSelf.firstIsInstance<KtElement>()
|
||||
val mainMethod = KotlinLineBreakpointType.getContainingMethod(elementAt)
|
||||
if (mainMethod != null) {
|
||||
result.add(
|
||||
kotlinBreakpointType.LineJavaBreakpointVariant(
|
||||
position,
|
||||
CodeInsightUtils.getTopmostElementAtOffset(elementAt, pos.offset),
|
||||
-1
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
lambdas.forEachIndexed { ordinal, lambda ->
|
||||
val positionImpl = XSourcePositionImpl.createByElement(lambda.bodyExpression)
|
||||
|
||||
if (positionImpl != null) {
|
||||
result.add(kotlinBreakpointType.LambdaJavaBreakpointVariant(positionImpl, lambda, ordinal))
|
||||
}
|
||||
}
|
||||
|
||||
result.add(kotlinBreakpointType.JavaBreakpointVariant(position))
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
fun getLambdasAtLineIfAny(sourcePosition: SourcePosition): List<KtFunction> {
|
||||
val file = sourcePosition.file as? KtFile ?: return emptyList()
|
||||
val lineNumber = sourcePosition.line
|
||||
return getLambdasAtLineIfAny(file, lineNumber)
|
||||
}
|
||||
|
||||
fun getLambdasAtLineIfAny(file: KtFile, line: Int): List<KtFunction> {
|
||||
val lineElement = findElementAtLine(file, line) as? KtElement ?: return emptyList()
|
||||
|
||||
val start = lineElement.startOffset
|
||||
val end = lineElement.endOffset
|
||||
|
||||
val allLiterals = CodeInsightUtils.
|
||||
findElementsOfClassInRange(file, start, end, KtFunction::class.java)
|
||||
.filterIsInstance<KtFunction>()
|
||||
// filter function literals and functional expressions
|
||||
.filter { it is KtFunctionLiteral || it.name == null }
|
||||
.toSet()
|
||||
|
||||
return allLiterals.filter {
|
||||
val statement = it.bodyBlockExpression?.statements?.firstOrNull() ?: it
|
||||
statement.getLineNumber() == line && statement.getLineNumber(false) == line
|
||||
}
|
||||
}
|
||||
|
||||
-62
@@ -1,62 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="org.jetbrains.kotlin.idea.debugger.breakpoints.dialog.AddFieldBreakpointDialog">
|
||||
<grid id="dbe86" binding="myPanel" layout-manager="GridLayoutManager" row-count="6" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="6">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="74" y="134" width="245" height="152"/>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="9636d" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myClassChooser">
|
||||
<constraints>
|
||||
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="c2ef3" class="com.intellij.openapi.ui.TextFieldWithBrowseButton" binding="myFieldChooser">
|
||||
<constraints>
|
||||
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
|
||||
<preferred-size width="150" height="-1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
</component>
|
||||
<xy id="3cfba" layout-manager="XYLayout" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<grid row="5" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="2" fill="1" indent="0" use-parent-layout="false">
|
||||
<minimum-size width="-1" height="1"/>
|
||||
<maximum-size width="-1" height="1"/>
|
||||
</grid>
|
||||
</constraints>
|
||||
<properties/>
|
||||
<border type="bevel-raised"/>
|
||||
<children/>
|
||||
</xy>
|
||||
<vspacer id="339be">
|
||||
<constraints>
|
||||
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
</vspacer>
|
||||
<component id="e8c64" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="label.add.field.breakpoint.dialog.field.name"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="c159b" class="javax.swing.JLabel">
|
||||
<constraints>
|
||||
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
|
||||
</constraints>
|
||||
<properties>
|
||||
<text resource-bundle="messages/DebuggerBundle" key="label.add.field.breakpoint.dialog.fq.name"/>
|
||||
</properties>
|
||||
</component>
|
||||
</children>
|
||||
</grid>
|
||||
</form>
|
||||
-146
@@ -1,146 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints.dialog;
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle;
|
||||
import com.intellij.ide.util.MemberChooser;
|
||||
import com.intellij.ide.util.TreeClassChooser;
|
||||
import com.intellij.ide.util.TreeClassChooserFactory;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.openapi.ui.TextFieldWithBrowseButton;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import kotlin.Unit;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.kotlin.idea.core.util.DescriptorMemberChooserObject;
|
||||
import org.jetbrains.kotlin.idea.util.UiUtilKt;
|
||||
import org.jetbrains.kotlin.psi.KtProperty;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.DocumentEvent;
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AddFieldBreakpointDialog extends DialogWrapper {
|
||||
private final Project myProject;
|
||||
private JPanel myPanel;
|
||||
private TextFieldWithBrowseButton myFieldChooser;
|
||||
private TextFieldWithBrowseButton myClassChooser;
|
||||
|
||||
public AddFieldBreakpointDialog(Project project) {
|
||||
super(project, true);
|
||||
myProject = project;
|
||||
setTitle(DebuggerBundle.message("add.field.breakpoint.dialog.title"));
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
UiUtilKt.onTextChange(
|
||||
myClassChooser.getTextField(),
|
||||
(DocumentEvent e) -> {
|
||||
updateUI();
|
||||
return Unit.INSTANCE;
|
||||
}
|
||||
);
|
||||
|
||||
myClassChooser.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
PsiClass currentClass = getSelectedClass();
|
||||
TreeClassChooser chooser = TreeClassChooserFactory.getInstance(myProject).createAllProjectScopeChooser(
|
||||
DebuggerBundle.message("add.field.breakpoint.dialog.classchooser.title"));
|
||||
if (currentClass != null) {
|
||||
PsiFile containingFile = currentClass.getContainingFile();
|
||||
if (containingFile != null) {
|
||||
PsiDirectory containingDirectory = containingFile.getContainingDirectory();
|
||||
if (containingDirectory != null) {
|
||||
chooser.selectDirectory(containingDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
chooser.showDialog();
|
||||
PsiClass selectedClass = chooser.getSelected();
|
||||
if (selectedClass != null) {
|
||||
myClassChooser.setText(selectedClass.getQualifiedName());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
myFieldChooser.addActionListener(new ActionListener() {
|
||||
@Override
|
||||
public void actionPerformed(@NotNull ActionEvent e) {
|
||||
PsiClass selectedClass = getSelectedClass();
|
||||
DescriptorMemberChooserObject[] properties = FieldBreakpointDialogUtilKt.collectProperties(selectedClass);
|
||||
MemberChooser<DescriptorMemberChooserObject> chooser = new MemberChooser<DescriptorMemberChooserObject>(properties, false, false, myProject);
|
||||
chooser.setTitle(DebuggerBundle.message("add.field.breakpoint.dialog.field.chooser.title", properties.length));
|
||||
chooser.setCopyJavadocVisible(false);
|
||||
chooser.show();
|
||||
List<DescriptorMemberChooserObject> selectedElements = chooser.getSelectedElements();
|
||||
if (selectedElements != null && selectedElements.size() == 1) {
|
||||
KtProperty field = (KtProperty) selectedElements.get(0).getElement();
|
||||
myFieldChooser.setText(field.getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
myFieldChooser.setEnabled(false);
|
||||
return myPanel;
|
||||
}
|
||||
|
||||
private void updateUI() {
|
||||
PsiClass selectedClass = getSelectedClass();
|
||||
myFieldChooser.setEnabled(selectedClass != null);
|
||||
}
|
||||
|
||||
private PsiClass getSelectedClass() {
|
||||
PsiManager psiManager = PsiManager.getInstance(myProject);
|
||||
String classQName = myClassChooser.getText();
|
||||
if (classQName == null || classQName.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return JavaPsiFacade.getInstance(psiManager.getProject()).findClass(classQName, GlobalSearchScope.allScope(myProject));
|
||||
}
|
||||
|
||||
@Override
|
||||
public JComponent getPreferredFocusedComponent() {
|
||||
return myClassChooser.getTextField();
|
||||
}
|
||||
|
||||
public String getClassName() {
|
||||
return myClassChooser.getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDimensionServiceKey() {
|
||||
return "#com.intellij.debugger.ui.breakpoints.BreakpointsConfigurationDialogFactory.BreakpointsConfigurationDialog.AddFieldBreakpointDialog";
|
||||
}
|
||||
|
||||
public String getFieldName() {
|
||||
return myFieldChooser.getText();
|
||||
}
|
||||
|
||||
protected abstract boolean validateData();
|
||||
|
||||
@Override
|
||||
protected void doOKAction() {
|
||||
if (validateData()) {
|
||||
super.doOKAction();
|
||||
}
|
||||
}
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.breakpoints.dialog
|
||||
|
||||
import com.intellij.psi.PsiClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.util.DescriptorMemberChooserObject
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
|
||||
fun PsiClass.collectProperties(): Array<DescriptorMemberChooserObject> {
|
||||
if (this is KtLightClassForFacade) {
|
||||
val result = arrayListOf<DescriptorMemberChooserObject>()
|
||||
this.files.forEach {
|
||||
it.declarations.filterIsInstance<KtProperty>().forEach {
|
||||
result.add(DescriptorMemberChooserObject(it, it.unsafeResolveToDescriptor()))
|
||||
}
|
||||
}
|
||||
return result.toTypedArray()
|
||||
}
|
||||
if (this is KtLightClass) {
|
||||
val origin = this.kotlinOrigin
|
||||
if (origin != null) {
|
||||
return origin.declarations.filterIsInstance<KtProperty>().map {
|
||||
DescriptorMemberChooserObject(it, it.unsafeResolveToDescriptor())
|
||||
}.toTypedArray()
|
||||
}
|
||||
}
|
||||
return emptyArray()
|
||||
}
|
||||
|
||||
@@ -1,284 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.sun.jdi.*
|
||||
import com.sun.tools.jdi.LocalVariableImpl
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
|
||||
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClass
|
||||
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
|
||||
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
|
||||
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmTypes
|
||||
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
|
||||
import org.jetbrains.kotlin.idea.KotlinFileTypeFactory
|
||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineEndOffset
|
||||
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
|
||||
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.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.inline.InlineUtil
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
import java.util.*
|
||||
|
||||
fun Location.isInKotlinSources(): Boolean {
|
||||
val declaringType = declaringType()
|
||||
val fileExtension = declaringType.safeSourceName()?.substringAfterLast('.')?.toLowerCase() ?: ""
|
||||
return fileExtension in KotlinFileTypeFactory.KOTLIN_EXTENSIONS || declaringType.containsKotlinStrata()
|
||||
}
|
||||
|
||||
fun ReferenceType.containsKotlinStrata() = availableStrata().contains(KOTLIN_STRATA_NAME)
|
||||
|
||||
fun isInsideInlineArgument(
|
||||
inlineArgument: KtFunction,
|
||||
location: Location,
|
||||
debugProcess: DebugProcessImpl,
|
||||
bindingContext: BindingContext = KotlinDebuggerCaches.getOrCreateTypeMapper(inlineArgument).bindingContext
|
||||
): Boolean {
|
||||
val visibleVariables = location.visibleVariables(debugProcess)
|
||||
val markerLocalVariables = visibleVariables.filter { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) }
|
||||
|
||||
val context = KotlinDebuggerCaches.getOrCreateTypeMapper(inlineArgument).bindingContext
|
||||
val lambdaOrdinal = runReadAction { lambdaOrdinalByArgument(inlineArgument, context) }
|
||||
val functionName = runReadAction { functionNameByArgument(inlineArgument, context) }
|
||||
|
||||
return markerLocalVariables
|
||||
.map { it.name().drop(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT.length) }
|
||||
.any { variableName ->
|
||||
if (variableName.startsWith("-")) {
|
||||
val lambdaClassName = asmTypeForAnonymousClass(bindingContext, inlineArgument)
|
||||
.internalName.substringAfterLast("/")
|
||||
|
||||
variableName == "-$functionName-$lambdaClassName"
|
||||
} else {
|
||||
// For Kotlin up to 1.3.10
|
||||
lambdaOrdinalByLocalVariable(variableName) == lambdaOrdinal
|
||||
&& functionNameByLocalVariable(variableName) == functionName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Any> DebugProcessImpl.invokeInManagerThread(f: (DebuggerContextImpl) -> T?): T? {
|
||||
var result: T? = null
|
||||
val command: DebuggerCommandImpl = object : DebuggerCommandImpl() {
|
||||
override fun action() {
|
||||
result = runReadAction { f(debuggerContext) }
|
||||
}
|
||||
}
|
||||
|
||||
when {
|
||||
DebuggerManagerThreadImpl.isManagerThread() ->
|
||||
managerThread.invoke(command)
|
||||
else ->
|
||||
managerThread.invokeAndWait(command)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun lambdaOrdinalByArgument(elementAt: KtFunction, context: BindingContext): Int {
|
||||
val type = CodegenBinding.asmTypeForAnonymousClass(context, elementAt)
|
||||
return type.className.substringAfterLast("$").toInt()
|
||||
}
|
||||
|
||||
private fun functionNameByArgument(elementAt: KtFunction, context: BindingContext): String {
|
||||
val inlineArgumentDescriptor = InlineUtil.getInlineArgumentDescriptor(elementAt, context)
|
||||
return inlineArgumentDescriptor?.containingDeclaration?.name?.asString() ?: "unknown"
|
||||
}
|
||||
|
||||
private fun Location.visibleVariables(debugProcess: DebugProcessImpl): List<LocalVariable> {
|
||||
val stackFrame = MockStackFrame(this, debugProcess.virtualMachineProxy.virtualMachine)
|
||||
return stackFrame.visibleVariables()
|
||||
}
|
||||
|
||||
private fun lambdaOrdinalByLocalVariable(name: String): Int {
|
||||
try {
|
||||
val nameWithoutPrefix = name.removePrefix(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT)
|
||||
return Integer.parseInt(nameWithoutPrefix.substringBefore("$", nameWithoutPrefix))
|
||||
}
|
||||
catch(e: NumberFormatException) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
private fun functionNameByLocalVariable(name: String): String {
|
||||
val nameWithoutPrefix = name.removePrefix(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT)
|
||||
return nameWithoutPrefix.substringAfterLast("$", "unknown")
|
||||
}
|
||||
|
||||
private class MockStackFrame(private val location: Location, private val vm: VirtualMachine) : StackFrame {
|
||||
private var visibleVariables: Map<String, LocalVariable>? = null
|
||||
|
||||
override fun location() = location
|
||||
override fun thread() = null
|
||||
override fun thisObject() = null
|
||||
|
||||
private fun createVisibleVariables() {
|
||||
if (visibleVariables == null) {
|
||||
val allVariables = location.method().safeVariables() ?: emptyList()
|
||||
val map = HashMap<String, LocalVariable>(allVariables.size)
|
||||
|
||||
for (allVariable in allVariables) {
|
||||
val variable = allVariable as LocalVariableImpl
|
||||
val name = variable.name()
|
||||
if (variable.isVisible(this)) {
|
||||
map.put(name, variable)
|
||||
}
|
||||
}
|
||||
visibleVariables = map
|
||||
}
|
||||
}
|
||||
|
||||
override fun visibleVariables(): List<LocalVariable> {
|
||||
createVisibleVariables()
|
||||
val mapAsList = ArrayList(visibleVariables!!.values)
|
||||
Collections.sort(mapAsList)
|
||||
return mapAsList
|
||||
}
|
||||
|
||||
override fun visibleVariableByName(name: String): LocalVariable? {
|
||||
createVisibleVariables()
|
||||
return visibleVariables!![name]
|
||||
}
|
||||
|
||||
override fun getValue(variable: LocalVariable) = null
|
||||
override fun getValues(variables: List<LocalVariable>): Map<LocalVariable, Value> = emptyMap()
|
||||
override fun setValue(variable: LocalVariable, value: Value) {
|
||||
}
|
||||
|
||||
override fun getArgumentValues(): List<Value> = emptyList()
|
||||
override fun virtualMachine() = vm
|
||||
}
|
||||
|
||||
private const val DO_RESUME_SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Throwable;)Ljava/lang/Object;"
|
||||
private const val INVOKE_SUSPEND_SIGNATURE = "(Ljava/lang/Object;)Ljava/lang/Object;"
|
||||
|
||||
fun isInSuspendMethod(location: Location): Boolean {
|
||||
val method = location.method()
|
||||
val signature = method.signature()
|
||||
|
||||
for (continuationAsmType in continuationAsmTypes()) {
|
||||
if (signature.contains(continuationAsmType.toString()) ||
|
||||
(method.name() == DO_RESUME_METHOD_NAME && signature == DO_RESUME_SIGNATURE) ||
|
||||
(method.name() == INVOKE_SUSPEND_METHOD_NAME && signature == INVOKE_SUSPEND_SIGNATURE)
|
||||
) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun suspendFunctionFirstLineLocation(location: Location): Int? {
|
||||
if (!isInSuspendMethod(location)) {
|
||||
return null
|
||||
}
|
||||
|
||||
val lineNumber = location.method().location()?.lineNumber()
|
||||
if (lineNumber == -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
return lineNumber
|
||||
}
|
||||
|
||||
fun isOnSuspendReturnOrReenter(location: Location): Boolean {
|
||||
val suspendStartLineNumber = suspendFunctionFirstLineLocation(location) ?: return false
|
||||
return suspendStartLineNumber == location.lineNumber()
|
||||
}
|
||||
|
||||
fun isLastLineLocationInMethod(location: Location): Boolean {
|
||||
val knownLines = location.method().safeAllLineLocations().map { it.lineNumber() }.filter { it != -1 }
|
||||
if (knownLines.isEmpty()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return knownLines.max() == location.lineNumber()
|
||||
}
|
||||
|
||||
fun isOneLineMethod(location: Location): Boolean {
|
||||
val allLineLocations = location.method().safeAllLineLocations()
|
||||
val firstLine = allLineLocations.firstOrNull()?.lineNumber()
|
||||
val lastLine = allLineLocations.lastOrNull()?.lineNumber()
|
||||
|
||||
return firstLine != null && firstLine == lastLine
|
||||
}
|
||||
|
||||
fun findElementAtLine(file: KtFile, line: Int): PsiElement? {
|
||||
val lineStartOffset = file.getLineStartOffset(line) ?: return null
|
||||
val lineEndOffset = file.getLineEndOffset(line) ?: return null
|
||||
|
||||
var topMostElement: PsiElement? = null
|
||||
var elementAt: PsiElement?
|
||||
for (offset in lineStartOffset until lineEndOffset) {
|
||||
elementAt = file.findElementAt(offset)
|
||||
if (elementAt != null) {
|
||||
topMostElement = CodeInsightUtils.getTopmostElementAtOffset(elementAt, offset)
|
||||
if (topMostElement is KtElement) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return topMostElement
|
||||
}
|
||||
|
||||
fun findCallByEndToken(element: PsiElement): KtCallExpression? {
|
||||
if (element is KtElement) return null
|
||||
|
||||
return when (element.node.elementType) {
|
||||
KtTokens.RPAR -> (element.parent as? KtValueArgumentList)?.parent as? KtCallExpression
|
||||
KtTokens.RBRACE -> {
|
||||
val braceParent = CodeInsightUtils.getTopParentWithEndOffset(element, KtCallExpression::class.java)
|
||||
when (braceParent) {
|
||||
is KtCallExpression -> braceParent
|
||||
is KtLambdaArgument -> braceParent.parent as? KtCallExpression
|
||||
is KtValueArgument -> (braceParent.parent as? KtValueArgumentList)?.parent as? KtCallExpression
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun Type.isSubtype(className: String): Boolean = isSubtype(AsmType.getObjectType(className))
|
||||
|
||||
fun Type.isSubtype(type: AsmType): Boolean {
|
||||
if (this.signature() == type.descriptor) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (type.sort != AsmType.OBJECT || this !is ClassType) {
|
||||
return false
|
||||
}
|
||||
|
||||
val superTypeName = type.className
|
||||
|
||||
if (allInterfaces().any { it.name() == superTypeName }) {
|
||||
return true
|
||||
}
|
||||
|
||||
var superClass = superclass()
|
||||
while (superClass != null) {
|
||||
if (superClass.name() == superTypeName) {
|
||||
return true
|
||||
}
|
||||
superClass = superClass.superclass()
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
val DebuggerContextImpl.canRunEvaluation: Boolean
|
||||
get() = debugProcess?.canRunEvaluation ?: false
|
||||
|
||||
val DebugProcessImpl.canRunEvaluation: Boolean
|
||||
get() = suspendManager.pausedContext != null
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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.evaluate
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
|
||||
import org.jetbrains.kotlin.idea.completion.CompletionInformationProvider
|
||||
|
||||
class DebuggerFieldCompletionInformationProvider : CompletionInformationProvider {
|
||||
override fun getContainerAndReceiverInformation(descriptor: DeclarationDescriptor) =
|
||||
(descriptor as? DebuggerFieldPropertyDescriptor)?.description?.let { " $it" }
|
||||
}
|
||||
-52
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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.evaluate
|
||||
|
||||
import org.jetbrains.kotlin.codegen.StackValue
|
||||
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaPropertyDescriptor
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.synthetic.JavaSyntheticPropertiesScope
|
||||
|
||||
class DebuggerFieldExpressionCodegenExtension : ExpressionCodegenExtension {
|
||||
override fun applyProperty(receiver: StackValue, resolvedCall: ResolvedCall<*>, c: ExpressionCodegenExtension.Context): StackValue? {
|
||||
val propertyDescriptor = resolvedCall.resultingDescriptor as? PropertyDescriptor ?: return null
|
||||
|
||||
if (propertyDescriptor is DebuggerFieldPropertyDescriptor) {
|
||||
return StackValue.StackValueWithSimpleReceiver.field(
|
||||
c.typeMapper.mapType(propertyDescriptor.type),
|
||||
propertyDescriptor.ownerType(c.codegen.state),
|
||||
propertyDescriptor.fieldName,
|
||||
false,
|
||||
receiver
|
||||
)
|
||||
}
|
||||
|
||||
if (propertyDescriptor is JavaPropertyDescriptor) {
|
||||
val containingClass = propertyDescriptor.containingDeclaration as? JavaClassDescriptor
|
||||
if (containingClass != null) {
|
||||
val correspondingGetter = JavaSyntheticPropertiesScope(LockBasedStorageManager.NO_LOCKS, LookupTracker.DO_NOTHING)
|
||||
.getSyntheticExtensionProperties(listOf(containingClass.defaultType), NoLookupLocation.FROM_BACKEND)
|
||||
.firstOrNull { it.name == propertyDescriptor.name }
|
||||
|
||||
if (correspondingGetter != null) {
|
||||
return c.codegen.intermediateValueForProperty(
|
||||
correspondingGetter, false, false,
|
||||
c.codegen.getSuperCallTarget(resolvedCall.call),
|
||||
false, receiver, resolvedCall, false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
-44
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* 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.evaluate
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.CallableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.idea.core.extension.KotlinIndicesHelperExtension
|
||||
import org.jetbrains.kotlin.incremental.components.LookupLocation
|
||||
import org.jetbrains.kotlin.incremental.components.LookupTracker
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.synthetic.JavaSyntheticPropertiesScope
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import java.lang.IllegalStateException
|
||||
|
||||
class DebuggerFieldKotlinIndicesHelperExtension : KotlinIndicesHelperExtension {
|
||||
override fun appendExtensionCallables(
|
||||
consumer: MutableList<in CallableDescriptor>,
|
||||
moduleDescriptor: ModuleDescriptor,
|
||||
receiverTypes: Collection<KotlinType>,
|
||||
nameFilter: (String) -> Boolean,
|
||||
lookupLocation: LookupLocation
|
||||
) {
|
||||
val javaPropertiesScope = JavaSyntheticPropertiesScope(LockBasedStorageManager.NO_LOCKS, LookupTracker.DO_NOTHING)
|
||||
val fieldScope = DebuggerFieldSyntheticScope(javaPropertiesScope)
|
||||
|
||||
for (property in fieldScope.getSyntheticExtensionProperties(receiverTypes, lookupLocation)) {
|
||||
if (nameFilter(property.name.asString())) {
|
||||
consumer += property
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun appendExtensionCallables(
|
||||
consumer: MutableList<in CallableDescriptor>,
|
||||
moduleDescriptor: ModuleDescriptor,
|
||||
receiverTypes: Collection<KotlinType>,
|
||||
nameFilter: (String) -> Boolean
|
||||
) {
|
||||
throw IllegalStateException("Should not be called")
|
||||
}
|
||||
}
|
||||
-206
@@ -1,206 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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.evaluate
|
||||
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
|
||||
import org.jetbrains.kotlin.idea.project.platform
|
||||
import org.jetbrains.kotlin.incremental.KotlinLookupLocation
|
||||
import org.jetbrains.kotlin.incremental.components.LookupLocation
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.load.java.components.JavaSourceElementFactoryImpl
|
||||
import org.jetbrains.kotlin.load.java.components.TypeUsage
|
||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||
import org.jetbrains.kotlin.load.java.lazy.descriptors.LazyJavaClassDescriptor
|
||||
import org.jetbrains.kotlin.load.java.lazy.types.toAttributes
|
||||
import org.jetbrains.kotlin.load.java.structure.classId
|
||||
import org.jetbrains.kotlin.load.kotlin.internalName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.DescriptorFactory
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
|
||||
import org.jetbrains.kotlin.platform.isCommon
|
||||
import org.jetbrains.kotlin.platform.jvm.isJvm
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||
import org.jetbrains.kotlin.resolve.scopes.SyntheticScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.getDescriptorsFiltered
|
||||
import org.jetbrains.kotlin.synthetic.JavaSyntheticPropertiesScope
|
||||
import org.jetbrains.kotlin.synthetic.SyntheticScopeProviderExtension
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.replaceArgumentsWithStarProjections
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
|
||||
class DebuggerFieldSyntheticScopeProvider : SyntheticScopeProviderExtension {
|
||||
override fun getScopes(
|
||||
moduleDescriptor: ModuleDescriptor,
|
||||
javaSyntheticPropertiesScope: JavaSyntheticPropertiesScope
|
||||
): List<SyntheticScope> {
|
||||
return listOf<SyntheticScope>(DebuggerFieldSyntheticScope(javaSyntheticPropertiesScope))
|
||||
}
|
||||
}
|
||||
|
||||
class DebuggerFieldSyntheticScope(val javaSyntheticPropertiesScope: JavaSyntheticPropertiesScope) : SyntheticScope.Default() {
|
||||
private val javaSourceElementFactory = JavaSourceElementFactoryImpl()
|
||||
|
||||
override fun getSyntheticExtensionProperties(
|
||||
receiverTypes: Collection<KotlinType>,
|
||||
name: Name,
|
||||
location: LookupLocation
|
||||
): Collection<PropertyDescriptor> {
|
||||
return getSyntheticExtensionProperties(receiverTypes, location).filter { it.name == name }
|
||||
}
|
||||
|
||||
override fun getSyntheticExtensionProperties(
|
||||
receiverTypes: Collection<KotlinType>,
|
||||
location: LookupLocation
|
||||
): Collection<PropertyDescriptor> {
|
||||
if (!isInEvaluator(location)) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val result = mutableListOf<PropertyDescriptor>()
|
||||
for (type in receiverTypes) {
|
||||
val clazz = type.constructor.declarationDescriptor as? ClassDescriptor ?: continue
|
||||
result += getSyntheticPropertiesForClass(clazz)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun isInEvaluator(location: LookupLocation): Boolean {
|
||||
val element = (location as? KotlinLookupLocation)?.element ?: return false
|
||||
val containingFile = element.containingFile?.takeIf { it.isValid } as? KtFile ?: return false
|
||||
|
||||
val platform = containingFile.platform
|
||||
if (!platform.isJvm() && !platform.isCommon()) {
|
||||
return false
|
||||
}
|
||||
|
||||
return containingFile is KtCodeFragment
|
||||
}
|
||||
|
||||
private fun getSyntheticPropertiesForClass(clazz: ClassDescriptor): Collection<PropertyDescriptor> {
|
||||
val collected = mutableMapOf<Name, PropertyDescriptor>()
|
||||
|
||||
val syntheticPropertyNames = javaSyntheticPropertiesScope
|
||||
.getSyntheticExtensionProperties(listOf(clazz.defaultType), NoLookupLocation.FROM_SYNTHETIC_SCOPE)
|
||||
.mapTo(mutableSetOf()) { it.name }
|
||||
|
||||
collectPropertiesWithParent(clazz, syntheticPropertyNames, collected)
|
||||
return collected.values
|
||||
}
|
||||
|
||||
private tailrec fun collectPropertiesWithParent(
|
||||
clazz: ClassDescriptor,
|
||||
syntheticNames: Set<Name>,
|
||||
consumer: MutableMap<Name, PropertyDescriptor>
|
||||
) {
|
||||
when (clazz) {
|
||||
is LazyJavaClassDescriptor -> collectJavaProperties(clazz, syntheticNames, consumer)
|
||||
is JavaClassDescriptor -> error("Unsupported Java class type")
|
||||
else -> collectKotlinProperties(clazz, consumer)
|
||||
}
|
||||
|
||||
val superClass = clazz.getSuperClassNotAny()
|
||||
if (superClass != null) {
|
||||
collectPropertiesWithParent(superClass, syntheticNames, consumer)
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectKotlinProperties(clazz: ClassDescriptor, consumer: MutableMap<Name, PropertyDescriptor>) {
|
||||
for (descriptor in clazz.unsubstitutedMemberScope.getDescriptorsFiltered(DescriptorKindFilter.VARIABLES)) {
|
||||
val propertyDescriptor = descriptor as? PropertyDescriptor ?: continue
|
||||
val name = propertyDescriptor.name
|
||||
if (propertyDescriptor.backingField == null || name in consumer) continue
|
||||
|
||||
val type = propertyDescriptor.type
|
||||
val sourceElement = propertyDescriptor.source
|
||||
|
||||
consumer[name] = createSyntheticPropertyDescriptor(clazz, type, name.asString(), "Backing field", sourceElement) { state ->
|
||||
state.typeMapper.mapType(clazz.defaultType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun collectJavaProperties(
|
||||
clazz: LazyJavaClassDescriptor,
|
||||
syntheticNames: Set<Name>,
|
||||
consumer: MutableMap<Name, PropertyDescriptor>
|
||||
) {
|
||||
val javaClass = clazz.jClass
|
||||
|
||||
for (field in javaClass.fields) {
|
||||
val fieldName = field.name
|
||||
if (field.isEnumEntry || field.isStatic || fieldName in consumer || fieldName !in syntheticNames) continue
|
||||
|
||||
val ownerClassName = javaClass.classId?.internalName ?: continue
|
||||
val typeResolver = clazz.outerContext.typeResolver
|
||||
|
||||
val type = typeResolver.transformJavaType(field.type, TypeUsage.COMMON.toAttributes()).replaceArgumentsWithStarProjections()
|
||||
val sourceElement = javaSourceElementFactory.source(field)
|
||||
|
||||
consumer[fieldName] = createSyntheticPropertyDescriptor(clazz, type, fieldName.asString(), "Java field", sourceElement) {
|
||||
Type.getObjectType(ownerClassName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSyntheticPropertyDescriptor(
|
||||
clazz: ClassDescriptor,
|
||||
type: KotlinType,
|
||||
fieldName: String,
|
||||
description: String,
|
||||
getterSource: SourceElement,
|
||||
ownerType: (GenerationState) -> Type
|
||||
): PropertyDescriptor {
|
||||
val propertyDescriptor = DebuggerFieldPropertyDescriptor(clazz, fieldName, description, ownerType)
|
||||
|
||||
val extensionReceiverParameter = DescriptorFactory.createExtensionReceiverParameterForCallable(
|
||||
propertyDescriptor,
|
||||
clazz.defaultType.replaceArgumentsWithStarProjections(),
|
||||
Annotations.EMPTY
|
||||
)
|
||||
|
||||
propertyDescriptor.setType(type, emptyList(), null, extensionReceiverParameter)
|
||||
|
||||
val getter = PropertyGetterDescriptorImpl(
|
||||
propertyDescriptor, Annotations.EMPTY, Modality.FINAL,
|
||||
Visibilities.PUBLIC, false, false, false,
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||
null, getterSource
|
||||
)
|
||||
|
||||
propertyDescriptor.initialize(getter, null)
|
||||
|
||||
return propertyDescriptor
|
||||
}
|
||||
}
|
||||
|
||||
internal class DebuggerFieldPropertyDescriptor(
|
||||
containingDeclaration: DeclarationDescriptor,
|
||||
val fieldName: String,
|
||||
val description: String,
|
||||
val ownerType: (GenerationState) -> Type
|
||||
) : PropertyDescriptorImpl(
|
||||
containingDeclaration,
|
||||
null,
|
||||
Annotations.EMPTY,
|
||||
Modality.FINAL,
|
||||
Visibilities.PUBLIC,
|
||||
/*isVar = */true,
|
||||
Name.identifier(fieldName + "_field"),
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||
SourceElement.NO_SOURCE,
|
||||
/*lateInit = */false,
|
||||
/*isConst = */false,
|
||||
/*isExpect = */false,
|
||||
/*isActual = */false,
|
||||
/*isExternal = */false,
|
||||
/*isDelegated = */false
|
||||
)
|
||||
@@ -1,97 +0,0 @@
|
||||
/*
|
||||
* 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.evaluate
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.SuspendContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.debugger.jdi.VirtualMachineProxyImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.sun.jdi.*
|
||||
import com.sun.jdi.request.EventRequest
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
|
||||
class ExecutionContext(val evaluationContext: EvaluationContextImpl, val frameProxy: StackFrameProxyImpl) {
|
||||
val vm: VirtualMachineProxyImpl
|
||||
get() = evaluationContext.debugProcess.virtualMachineProxy
|
||||
|
||||
val classLoader: ClassLoaderReference?
|
||||
get() = evaluationContext.classLoader
|
||||
|
||||
val suspendContext: SuspendContextImpl
|
||||
get() = evaluationContext.suspendContext
|
||||
|
||||
val debugProcess: DebugProcessImpl
|
||||
get() = evaluationContext.debugProcess
|
||||
|
||||
val project: Project
|
||||
get() = evaluationContext.project
|
||||
|
||||
val invokePolicy = run {
|
||||
val suspendContext = evaluationContext.suspendContext
|
||||
if (suspendContext.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD) ObjectReference.INVOKE_SINGLE_THREADED else 0
|
||||
}
|
||||
|
||||
@Throws(EvaluateException::class)
|
||||
fun invokeMethod(obj: ObjectReference, method: Method, args: List<Value?>): Value? {
|
||||
return debugProcess.invokeInstanceMethod(evaluationContext, obj, method, args, invokePolicy)
|
||||
}
|
||||
|
||||
fun invokeMethod(type: ClassType, method: Method, args: List<Value?>): Value? {
|
||||
return debugProcess.invokeMethod(evaluationContext, type, method, args)
|
||||
}
|
||||
|
||||
@Throws(EvaluateException::class)
|
||||
fun newInstance(type: ClassType, constructor: Method, args: List<Value?>): ObjectReference {
|
||||
return debugProcess.newInstance(evaluationContext, type, constructor, args)
|
||||
}
|
||||
|
||||
@Throws(EvaluateException::class)
|
||||
fun newInstance(arrayType: ArrayType, dimension: Int): ArrayReference {
|
||||
return debugProcess.newInstance(arrayType, dimension)
|
||||
}
|
||||
|
||||
@Throws(EvaluateException::class)
|
||||
fun findClass(name: String, classLoader: ClassLoaderReference? = null): ReferenceType? {
|
||||
debugProcess.findClass(evaluationContext, name, classLoader)?.let { return it }
|
||||
|
||||
// If 'isAutoLoadClasses' is true, `findClass()` already did this
|
||||
if (!evaluationContext.isAutoLoadClasses) {
|
||||
try {
|
||||
debugProcess.loadClass(evaluationContext, name, classLoader)
|
||||
} catch (e: InvocationException) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException(e)
|
||||
} catch (e: ClassNotLoadedException) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException(e)
|
||||
} catch (e: IncompatibleThreadStateException) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException(e)
|
||||
} catch (e: InvalidTypeException) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException(e)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
@Throws(EvaluateException::class)
|
||||
fun findClass(asmType: Type, classLoader: ClassLoaderReference? = null): ReferenceType? {
|
||||
if (asmType.sort != Type.OBJECT && asmType.sort != Type.ARRAY) {
|
||||
return null
|
||||
}
|
||||
|
||||
return findClass(asmType.className, classLoader)
|
||||
}
|
||||
|
||||
fun keepReference(reference: ObjectReference) {
|
||||
// Not available in older IDEA versions
|
||||
@Suppress("DEPRECATION")
|
||||
DebuggerUtilsEx.keep(reference, evaluationContext)
|
||||
}
|
||||
}
|
||||
-395
@@ -1,395 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2016 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.evaluation.CodeFragmentFactory
|
||||
import com.intellij.debugger.engine.evaluation.CodeFragmentKind
|
||||
import com.intellij.debugger.engine.evaluation.TextWithImports
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.ide.highlighter.JavaFileType
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.progress.ProgressManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.psi.util.PsiTypesUtil
|
||||
import com.intellij.util.IncorrectOperationException
|
||||
import com.intellij.util.concurrency.Semaphore
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.eval4j.jdi.asValue
|
||||
import org.jetbrains.kotlin.asJava.classes.KtLightClass
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.DebugLabelPropertyDescriptorProvider
|
||||
import org.jetbrains.kotlin.idea.j2k.JavaToKotlinConverterFactory
|
||||
import org.jetbrains.kotlin.idea.refactoring.convertToKotlin
|
||||
import org.jetbrains.kotlin.idea.refactoring.j2k
|
||||
import org.jetbrains.kotlin.idea.refactoring.j2kText
|
||||
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.versions.getKotlinJvmRuntimeMarkerClass
|
||||
import org.jetbrains.kotlin.j2k.AfterConversionPass
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.quoteIfNeeded
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.makeNullable
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
|
||||
class KotlinCodeFragmentFactory : CodeFragmentFactory() {
|
||||
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||
val contextElement = getContextElement(context)
|
||||
|
||||
val constructor = when (item.kind) {
|
||||
null -> error("Code fragment kind should be set")
|
||||
CodeFragmentKind.EXPRESSION -> ::KtExpressionCodeFragment
|
||||
CodeFragmentKind.CODE_BLOCK -> ::KtBlockCodeFragment
|
||||
}
|
||||
|
||||
val codeFragment = constructor(project, "fragment.kt", item.text, initImports(item.imports), contextElement)
|
||||
supplyDebugLabels(codeFragment, context)
|
||||
|
||||
codeFragment.putCopyableUserData(KtCodeFragment.RUNTIME_TYPE_EVALUATOR, { expression: KtExpression ->
|
||||
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val debuggerSession = debuggerContext.debuggerSession
|
||||
if (debuggerSession == null || debuggerContext.suspendContext == null) {
|
||||
null
|
||||
} else {
|
||||
val semaphore = Semaphore()
|
||||
semaphore.down()
|
||||
val nameRef = AtomicReference<KotlinType>()
|
||||
val worker = object : KotlinRuntimeTypeEvaluator(
|
||||
null, expression, debuggerContext, ProgressManager.getInstance().progressIndicator!!
|
||||
) {
|
||||
override fun typeCalculationFinished(type: KotlinType?) {
|
||||
nameRef.set(type)
|
||||
semaphore.up()
|
||||
}
|
||||
}
|
||||
|
||||
debuggerContext.debugProcess?.managerThread?.invoke(worker)
|
||||
|
||||
for (i in 0..50) {
|
||||
ProgressManager.checkCanceled()
|
||||
if (semaphore.waitFor(20)) break
|
||||
}
|
||||
|
||||
nameRef.get()
|
||||
}
|
||||
})
|
||||
|
||||
if (contextElement != null && contextElement !is KtElement) {
|
||||
codeFragment.putCopyableUserData(KtCodeFragment.FAKE_CONTEXT_FOR_JAVA_FILE, {
|
||||
val emptyFile = createFakeFileWithJavaContextElement("", contextElement)
|
||||
|
||||
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
|
||||
val debuggerSession = debuggerContext.debuggerSession
|
||||
if ((debuggerSession == null || debuggerContext.suspendContext == null) && !ApplicationManager.getApplication().isUnitTestMode) {
|
||||
LOG.warn("Couldn't create fake context element for java file, debugger isn't paused on breakpoint")
|
||||
return@putCopyableUserData emptyFile
|
||||
}
|
||||
|
||||
val frameDescriptor = getFrameInfo(contextElement, debuggerContext)
|
||||
if (frameDescriptor == null) {
|
||||
LOG.warn(
|
||||
"Couldn't get info about 'this' and local variables for " +
|
||||
"${debuggerContext.sourcePosition?.file?.name}:${debuggerContext.sourcePosition?.line}"
|
||||
)
|
||||
return@putCopyableUserData emptyFile
|
||||
}
|
||||
|
||||
val receiverTypeReference =
|
||||
frameDescriptor.thisObject?.let { createKotlinProperty(project, FAKE_JAVA_THIS_NAME, it.type().name(), it) }?.typeReference
|
||||
val receiverTypeText = receiverTypeReference?.let { "${it.text}." } ?: ""
|
||||
|
||||
val kotlinVariablesText =
|
||||
frameDescriptor.visibleVariables.entries.associate { it.key.name() to it.value }.kotlinVariablesAsText(project)
|
||||
|
||||
val fakeFunctionText = "fun ${receiverTypeText}$FAKE_JAVA_CONTEXT_FUNCTION_NAME() {\n$kotlinVariablesText\n}"
|
||||
|
||||
val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement)
|
||||
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
|
||||
val fakeContext = fakeFunction?.bodyBlockExpression?.statements?.lastOrNull()
|
||||
|
||||
return@putCopyableUserData fakeContext ?: emptyFile
|
||||
})
|
||||
}
|
||||
|
||||
return codeFragment
|
||||
}
|
||||
|
||||
private fun supplyDebugLabels(codeFragment: KtCodeFragment, context: PsiElement?) {
|
||||
val project = codeFragment.project
|
||||
val debugProcess = getDebugProcess(project, context) ?: return
|
||||
DebugLabelPropertyDescriptorProvider(codeFragment, debugProcess).supplyDebugLabels()
|
||||
}
|
||||
|
||||
private fun getDebugProcess(project: Project, context: PsiElement?): DebugProcessImpl? {
|
||||
return if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
context?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.debugProcess
|
||||
} else {
|
||||
DebuggerManagerEx.getInstanceEx(project).context.debugProcess
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFrameInfo(contextElement: PsiElement?, debuggerContext: DebuggerContextImpl): FrameInfo? {
|
||||
val semaphore = Semaphore()
|
||||
semaphore.down()
|
||||
|
||||
var frameInfo: FrameInfo? = null
|
||||
|
||||
val worker = object : DebuggerCommandImpl() {
|
||||
override fun action() {
|
||||
try {
|
||||
val frame = if (ApplicationManager.getApplication().isUnitTestMode)
|
||||
contextElement?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.frameProxy?.stackFrame
|
||||
else
|
||||
debuggerContext.frameProxy?.stackFrame
|
||||
|
||||
val visibleVariables = if (frame != null) {
|
||||
val values = frame.getValues(frame.visibleVariables())
|
||||
values.filterValues { it != null }
|
||||
} else {
|
||||
emptyMap()
|
||||
}
|
||||
|
||||
frameInfo = FrameInfo(frame?.thisObject(), visibleVariables)
|
||||
} catch (ignored: AbsentInformationException) {
|
||||
// Debug info unavailable
|
||||
} catch (ignored: InvalidStackFrameException) {
|
||||
// Thread is resumed, the frame we have is not valid anymore
|
||||
} finally {
|
||||
semaphore.up()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
debuggerContext.debugProcess?.managerThread?.invoke(worker)
|
||||
|
||||
for (i in 0..50) {
|
||||
if (semaphore.waitFor(20)) break
|
||||
}
|
||||
|
||||
return frameInfo
|
||||
}
|
||||
|
||||
private class FrameInfo(val thisObject: Value?, val visibleVariables: Map<LocalVariable, Value>)
|
||||
|
||||
private fun initImports(imports: String?): String? {
|
||||
if (imports != null && !imports.isEmpty()) {
|
||||
return imports.split(KtCodeFragment.IMPORT_SEPARATOR)
|
||||
.mapNotNull { fixImportIfNeeded(it) }
|
||||
.joinToString(KtCodeFragment.IMPORT_SEPARATOR)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun fixImportIfNeeded(import: String): String? {
|
||||
// skip arrays
|
||||
if (import.endsWith("[]")) {
|
||||
return fixImportIfNeeded(import.removeSuffix("[]").trim())
|
||||
}
|
||||
|
||||
// skip primitive types
|
||||
if (PsiTypesUtil.boxIfPossible(import) != import) {
|
||||
return null
|
||||
}
|
||||
return import
|
||||
}
|
||||
|
||||
override fun createPresentationCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
|
||||
val kotlinCodeFragment = createCodeFragment(item, context, project)
|
||||
if (PsiTreeUtil.hasErrorElements(kotlinCodeFragment) && kotlinCodeFragment is KtExpressionCodeFragment) {
|
||||
val javaExpression = try {
|
||||
PsiElementFactory.SERVICE.getInstance(project).createExpressionFromText(item.text, context)
|
||||
} catch (e: IncorrectOperationException) {
|
||||
null
|
||||
}
|
||||
|
||||
val importList = try {
|
||||
kotlinCodeFragment.importsAsImportList()?.let {
|
||||
(PsiFileFactory.getInstance(project).createFileFromText(
|
||||
"dummy.java", JavaFileType.INSTANCE, it.text
|
||||
) as? PsiJavaFile)?.importList
|
||||
}
|
||||
} catch (e: IncorrectOperationException) {
|
||||
null
|
||||
}
|
||||
|
||||
if (javaExpression != null && !PsiTreeUtil.hasErrorElements(javaExpression)) {
|
||||
var convertedFragment: KtExpressionCodeFragment? = null
|
||||
project.executeWriteCommand("Convert java expression to kotlin in Evaluate Expression") {
|
||||
try {
|
||||
val (elementResults, _, conversionContext) = javaExpression.convertToKotlin() ?: return@executeWriteCommand
|
||||
val newText = elementResults.singleOrNull()?.text
|
||||
val newImports = importList?.j2kText()
|
||||
if (newText != null) {
|
||||
convertedFragment = KtExpressionCodeFragment(
|
||||
project,
|
||||
kotlinCodeFragment.name,
|
||||
newText,
|
||||
newImports,
|
||||
kotlinCodeFragment.context
|
||||
)
|
||||
|
||||
AfterConversionPass(project, JavaToKotlinConverterFactory.createPostProcessor(formatCode = false))
|
||||
.run(
|
||||
convertedFragment!!,
|
||||
conversionContext,
|
||||
range = null
|
||||
)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// ignored because text can be invalid
|
||||
LOG.error("Couldn't convert expression:\n`${javaExpression.text}`", e)
|
||||
}
|
||||
}
|
||||
return convertedFragment ?: kotlinCodeFragment
|
||||
}
|
||||
}
|
||||
return kotlinCodeFragment
|
||||
}
|
||||
|
||||
override fun isContextAccepted(contextElement: PsiElement?): Boolean = runReadAction {
|
||||
when {
|
||||
// PsiCodeBlock -> DummyHolder -> originalElement
|
||||
contextElement is PsiCodeBlock -> isContextAccepted(contextElement.context?.context)
|
||||
contextElement == null -> false
|
||||
contextElement.language == KotlinFileType.INSTANCE.language -> true
|
||||
contextElement.language == JavaFileType.INSTANCE.language -> {
|
||||
getKotlinJvmRuntimeMarkerClass(contextElement.project, contextElement.resolveScope) != null
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFileType(): KotlinFileType = KotlinFileType.INSTANCE
|
||||
|
||||
override fun getEvaluatorBuilder() = KotlinEvaluatorBuilder
|
||||
|
||||
companion object {
|
||||
private val LOG = Logger.getInstance(this::class.java)
|
||||
|
||||
@get:TestOnly
|
||||
val DEBUG_CONTEXT_FOR_TESTS: Key<DebuggerContextImpl> = Key.create("DEBUG_CONTEXT_FOR_TESTS")
|
||||
|
||||
const val FAKE_JAVA_CONTEXT_FUNCTION_NAME = "_java_locals_debug_fun_"
|
||||
const val FAKE_JAVA_THIS_NAME = "\$this\$_java_locals_debug_fun_"
|
||||
|
||||
fun getContextElement(elementAt: PsiElement?): PsiElement? {
|
||||
if (elementAt == null) return null
|
||||
|
||||
if (elementAt is PsiCodeBlock) {
|
||||
return getContextElement(elementAt.context?.context)
|
||||
}
|
||||
|
||||
if (elementAt is KtLightClass) {
|
||||
return getContextElement(elementAt.kotlinOrigin)
|
||||
}
|
||||
|
||||
val containingFile = elementAt.containingFile
|
||||
if (containingFile is PsiJavaFile) return elementAt
|
||||
if (containingFile !is KtFile) return null
|
||||
|
||||
// elementAt can be PsiWhiteSpace when codeFragment is created from line start offset (in case of first opening EE window)
|
||||
val lineStartOffset = if (elementAt is PsiWhiteSpace || elementAt is PsiComment) {
|
||||
PsiTreeUtil.skipSiblingsForward(elementAt, PsiWhiteSpace::class.java, PsiComment::class.java)?.textOffset
|
||||
?: elementAt.textOffset
|
||||
} else {
|
||||
elementAt.textOffset
|
||||
}
|
||||
|
||||
fun KtElement.takeIfAcceptedAsCodeFragmentContext() = takeIf { KotlinEditorTextProvider.isAcceptedAsCodeFragmentContext(it) }
|
||||
|
||||
PsiTreeUtil.findElementOfClassAtOffset(containingFile, lineStartOffset, KtExpression::class.java, false)
|
||||
?.takeIfAcceptedAsCodeFragmentContext()
|
||||
?.let { return CodeInsightUtils.getTopmostElementAtOffset(it, lineStartOffset, KtExpression::class.java) }
|
||||
|
||||
KotlinEditorTextProvider.findExpressionInner(elementAt, true)
|
||||
?.takeIfAcceptedAsCodeFragmentContext()
|
||||
?.let { return it }
|
||||
|
||||
return containingFile
|
||||
}
|
||||
|
||||
private fun Map<String, Value>.kotlinVariablesAsText(project: Project): String {
|
||||
val sb = StringBuilder()
|
||||
|
||||
val psiNameHelper = PsiNameHelper.getInstance(project)
|
||||
for ((variableName, variableValue) in entries) {
|
||||
if (!psiNameHelper.isIdentifier(variableName)) continue
|
||||
|
||||
val variableTypeName = variableValue.type()?.name() ?: continue
|
||||
|
||||
val kotlinProperty = createKotlinProperty(project, variableName, variableTypeName, variableValue) ?: continue
|
||||
|
||||
sb.append("${kotlinProperty.text}\n")
|
||||
}
|
||||
|
||||
sb.append("val _debug_context_val = 1\n")
|
||||
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun createKotlinProperty(project: Project, variableName: String, variableTypeName: String, value: Value): KtProperty? {
|
||||
val actualClassDescriptor = value.asValue().asmType.getClassDescriptor(GlobalSearchScope.allScope(project))
|
||||
if (actualClassDescriptor != null && actualClassDescriptor.defaultType.arguments.isEmpty()) {
|
||||
val renderedType = IdeDescriptorRenderers.SOURCE_CODE.renderType(actualClassDescriptor.defaultType.makeNullable())
|
||||
return KtPsiFactory(project).createProperty(variableName.quoteIfNeeded(), renderedType, false)
|
||||
}
|
||||
|
||||
fun String.addArraySuffix() = if (value is ArrayReference) this + "[]" else this
|
||||
|
||||
val className = variableTypeName.replace("$", ".").substringBefore("[]")
|
||||
val classType = PsiType.getTypeByName(className, project, GlobalSearchScope.allScope(project))
|
||||
val type = (if (value !is PrimitiveValue && classType.resolve() == null)
|
||||
CommonClassNames.JAVA_LANG_OBJECT
|
||||
else
|
||||
className).addArraySuffix()
|
||||
|
||||
val field = PsiElementFactory.SERVICE.getInstance(project)
|
||||
.createField(variableName, PsiType.getTypeByName(type, project, GlobalSearchScope.allScope(project)))
|
||||
val ktField = field.j2k() as? KtProperty
|
||||
ktField?.modifierList?.delete()
|
||||
return ktField
|
||||
}
|
||||
}
|
||||
|
||||
private fun createFakeFileWithJavaContextElement(funWithLocalVariables: String, javaContext: PsiElement): KtFile {
|
||||
val javaFile = javaContext.containingFile as? PsiJavaFile
|
||||
|
||||
val sb = StringBuilder()
|
||||
|
||||
javaFile?.packageName?.takeUnless { it.isBlank() }?.let {
|
||||
sb.append("package ").append(it.quoteIfNeeded()).append("\n")
|
||||
}
|
||||
|
||||
javaFile?.importList?.let { sb.append(it.text).append("\n") }
|
||||
|
||||
sb.append(funWithLocalVariables)
|
||||
|
||||
return KtPsiFactory(javaContext.project).createAnalyzableFile("fakeFileForJavaContextInDebugger.kt", sb.toString(), javaContext)
|
||||
}
|
||||
}
|
||||
-238
@@ -1,238 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.libraries.LibraryUtil
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.util.CachedValueProvider
|
||||
import com.intellij.psi.util.CachedValuesManager
|
||||
import com.intellij.psi.util.PsiModificationTracker
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.apache.log4j.Logger
|
||||
import org.jetbrains.eval4j.Value
|
||||
import org.jetbrains.kotlin.analyzer.AnalysisResult
|
||||
import org.jetbrains.kotlin.codegen.ClassBuilderFactories
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyzeWithAllCompilerChecks
|
||||
import org.jetbrains.kotlin.idea.debugger.BinaryCacheKey
|
||||
import org.jetbrains.kotlin.idea.debugger.BytecodeDebugInfo
|
||||
import org.jetbrains.kotlin.idea.debugger.createWeakBytecodeDebugInfoStorage
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CompiledDataDescriptor
|
||||
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import org.jetbrains.kotlin.psi.KtElement
|
||||
import org.jetbrains.kotlin.psi.KtFile
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
class KotlinDebuggerCaches(project: Project) {
|
||||
private val cachedCompiledData = CachedValuesManager.getManager(project).createCachedValue(
|
||||
{
|
||||
CachedValueProvider.Result<MultiMap<String, CompiledDataDescriptor>>(
|
||||
MultiMap.create(), PsiModificationTracker.MODIFICATION_COUNT
|
||||
)
|
||||
}, false
|
||||
)
|
||||
|
||||
private val cachedClassNames = CachedValuesManager.getManager(project).createCachedValue(
|
||||
{
|
||||
CachedValueProvider.Result<MutableMap<PsiElement, List<String>>>(
|
||||
ConcurrentHashMap(),
|
||||
PsiModificationTracker.MODIFICATION_COUNT
|
||||
)
|
||||
}, false
|
||||
)
|
||||
|
||||
private val cachedTypeMappers = CachedValuesManager.getManager(project).createCachedValue(
|
||||
{
|
||||
CachedValueProvider.Result<MutableMap<PsiElement, KotlinTypeMapper>>(
|
||||
ConcurrentHashMap(),
|
||||
PsiModificationTracker.MODIFICATION_COUNT
|
||||
)
|
||||
}, false
|
||||
)
|
||||
|
||||
private val debugInfoCache = CachedValuesManager.getManager(project).createCachedValue(
|
||||
{
|
||||
CachedValueProvider.Result(
|
||||
createWeakBytecodeDebugInfoStorage(),
|
||||
PsiModificationTracker.MODIFICATION_COUNT
|
||||
)
|
||||
}, false
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val LOG = Logger.getLogger(KotlinDebuggerCaches::class.java)!!
|
||||
|
||||
fun getInstance(project: Project) = ServiceManager.getService(project, KotlinDebuggerCaches::class.java)!!
|
||||
|
||||
fun compileCodeFragmentCacheAware(
|
||||
codeFragment: KtCodeFragment,
|
||||
sourcePosition: SourcePosition?,
|
||||
compileCode: () -> CompiledDataDescriptor,
|
||||
force: Boolean = false
|
||||
): Pair<CompiledDataDescriptor, Boolean> {
|
||||
if (sourcePosition == null) {
|
||||
return Pair(compileCode(), false)
|
||||
}
|
||||
|
||||
val evaluateExpressionCache = getInstance(codeFragment.project)
|
||||
|
||||
val text = "${codeFragment.importsToString()}\n${codeFragment.text}"
|
||||
|
||||
val cachedResults = synchronized<Collection<CompiledDataDescriptor>>(evaluateExpressionCache.cachedCompiledData) {
|
||||
evaluateExpressionCache.cachedCompiledData.value[text]
|
||||
}
|
||||
|
||||
val existingResult = cachedResults.firstOrNull { it.sourcePosition == sourcePosition }
|
||||
if (existingResult != null) {
|
||||
if (force) {
|
||||
synchronized(evaluateExpressionCache.cachedCompiledData) {
|
||||
evaluateExpressionCache.cachedCompiledData.value.remove(text, existingResult)
|
||||
}
|
||||
} else {
|
||||
return Pair(existingResult, true)
|
||||
}
|
||||
}
|
||||
|
||||
val newCompiledData = compileCode()
|
||||
LOG.debug("Compile bytecode for ${codeFragment.text}")
|
||||
|
||||
synchronized(evaluateExpressionCache.cachedCompiledData) {
|
||||
evaluateExpressionCache.cachedCompiledData.value.putValue(text, newCompiledData)
|
||||
}
|
||||
|
||||
return Pair(newCompiledData, false)
|
||||
}
|
||||
|
||||
fun <T : PsiElement> getOrComputeClassNames(psiElement: T?, create: (T) -> ComputedClassNames): List<String> {
|
||||
if (psiElement == null) return Collections.emptyList()
|
||||
|
||||
val cache = getInstance(runReadAction { psiElement.project })
|
||||
|
||||
val classNamesCache = cache.cachedClassNames.value
|
||||
|
||||
val cachedValue = classNamesCache[psiElement]
|
||||
if (cachedValue != null) return cachedValue
|
||||
|
||||
val computedClassNames = create(psiElement)
|
||||
|
||||
if (computedClassNames.shouldBeCached) {
|
||||
classNamesCache[psiElement] = computedClassNames.classNames
|
||||
}
|
||||
|
||||
return computedClassNames.classNames
|
||||
}
|
||||
|
||||
fun getOrCreateTypeMapper(psiElement: PsiElement): KotlinTypeMapper {
|
||||
val cache = getInstance(runReadAction { psiElement.project })
|
||||
|
||||
val file = runReadAction { psiElement.containingFile as KtFile }
|
||||
val isInLibrary = runReadAction { LibraryUtil.findLibraryEntry(file.virtualFile, file.project) } != null
|
||||
|
||||
val key = if (!isInLibrary) file else psiElement
|
||||
|
||||
val typeMappersCache = cache.cachedTypeMappers.value
|
||||
|
||||
val cachedValue = typeMappersCache[key]
|
||||
if (cachedValue != null) return cachedValue
|
||||
|
||||
val newValue = if (!isInLibrary) {
|
||||
createTypeMapperForSourceFile(file)
|
||||
} else {
|
||||
val element = getElementToCreateTypeMapperForLibraryFile(psiElement)
|
||||
createTypeMapperForLibraryFile(element, file)
|
||||
}
|
||||
|
||||
typeMappersCache[key] = newValue
|
||||
return newValue
|
||||
}
|
||||
|
||||
fun getOrReadDebugInfoFromBytecode(
|
||||
project: Project,
|
||||
jvmName: JvmClassName,
|
||||
file: VirtualFile
|
||||
): BytecodeDebugInfo? {
|
||||
val cache = getInstance(project)
|
||||
return cache.debugInfoCache.value[BinaryCacheKey(project, jvmName, file)]
|
||||
}
|
||||
|
||||
private fun getElementToCreateTypeMapperForLibraryFile(element: PsiElement?) =
|
||||
runReadAction { element as? KtElement ?: PsiTreeUtil.getParentOfType(element, KtElement::class.java)!! }
|
||||
|
||||
private fun createTypeMapperForLibraryFile(element: KtElement, file: KtFile): KotlinTypeMapper =
|
||||
runInReadActionWithWriteActionPriorityWithPCE {
|
||||
createTypeMapper(file, element.analyzeAndGetResult())
|
||||
}
|
||||
|
||||
private fun createTypeMapperForSourceFile(file: KtFile): KotlinTypeMapper =
|
||||
runInReadActionWithWriteActionPriorityWithPCE {
|
||||
createTypeMapper(file, file.analyzeWithAllCompilerChecks().apply(AnalysisResult::throwIfError))
|
||||
}
|
||||
|
||||
private fun createTypeMapper(file: KtFile, analysisResult: AnalysisResult): KotlinTypeMapper {
|
||||
val state = GenerationState.Builder(
|
||||
file.project,
|
||||
ClassBuilderFactories.THROW_EXCEPTION,
|
||||
analysisResult.moduleDescriptor,
|
||||
analysisResult.bindingContext,
|
||||
listOf(file),
|
||||
CompilerConfiguration.EMPTY
|
||||
).build()
|
||||
state.beforeCompile()
|
||||
return state.typeMapper
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
fun addTypeMapper(file: KtFile, typeMapper: KotlinTypeMapper) {
|
||||
getInstance(file.project).cachedTypeMappers.value[file] = typeMapper
|
||||
}
|
||||
}
|
||||
|
||||
data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null, val error: EvaluateException? = null)
|
||||
|
||||
class ComputedClassNames(val classNames: List<String>, val shouldBeCached: Boolean) {
|
||||
@Suppress("FunctionName")
|
||||
companion object {
|
||||
val EMPTY = ComputedClassNames.Cached(emptyList())
|
||||
|
||||
fun Cached(classNames: List<String>) = ComputedClassNames(classNames, true)
|
||||
fun Cached(className: String) = ComputedClassNames(Collections.singletonList(className), true)
|
||||
|
||||
fun NonCached(classNames: List<String>) = ComputedClassNames(classNames, false)
|
||||
}
|
||||
|
||||
fun distinct() = ComputedClassNames(classNames.distinct(), shouldBeCached)
|
||||
|
||||
operator fun plus(other: ComputedClassNames) = ComputedClassNames(
|
||||
classNames + other.classNames, shouldBeCached && other.shouldBeCached
|
||||
)
|
||||
}
|
||||
}
|
||||
-448
@@ -1,448 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.expression.*
|
||||
import com.intellij.openapi.diagnostic.Attachment
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.psi.JavaPsiFacade
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.testFramework.runInEdtAndWait
|
||||
import com.sun.jdi.*
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.eval4j.*
|
||||
import org.jetbrains.eval4j.Value as Eval4JValue
|
||||
import org.jetbrains.eval4j.jdi.JDIEval
|
||||
import org.jetbrains.eval4j.jdi.asJdiValue
|
||||
import org.jetbrains.eval4j.jdi.asValue
|
||||
import org.jetbrains.eval4j.jdi.makeInitialFrame
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.jvm.JavaToKotlinClassMap
|
||||
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
|
||||
import org.jetbrains.kotlin.codegen.*
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.findClassAcrossModuleDependencies
|
||||
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
|
||||
import org.jetbrains.kotlin.diagnostics.Errors
|
||||
import org.jetbrains.kotlin.diagnostics.Severity
|
||||
import org.jetbrains.kotlin.diagnostics.rendering.DefaultErrorMessages
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.util.getJavaClassDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.DebuggerUtils
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.Companion.compileCodeFragmentCacheAware
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.EvaluatorValueConverter
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder
|
||||
import org.jetbrains.kotlin.idea.debugger.safeLocation
|
||||
import org.jetbrains.kotlin.idea.debugger.safeMethod
|
||||
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.util.attachment.attachmentByPsiFile
|
||||
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.AnalyzingUtils
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.isInlineClassType
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
import java.util.*
|
||||
|
||||
internal val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluator")
|
||||
internal const val GENERATED_FUNCTION_NAME = "generated_for_debugger_fun"
|
||||
internal const val GENERATED_CLASS_NAME = "Generated_for_debugger_class"
|
||||
|
||||
object KotlinEvaluatorBuilder : EvaluatorBuilder {
|
||||
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
|
||||
if (codeFragment !is KtCodeFragment) {
|
||||
return EvaluatorBuilderImpl.getInstance().build(codeFragment, position)
|
||||
}
|
||||
|
||||
val context = codeFragment.context ?: evaluationException("Cannot evaluate an expression without a context")
|
||||
val file = context.containingFile
|
||||
|
||||
if (file !is KtFile) {
|
||||
reportError(codeFragment, position, "Unknown context${codeFragment.context?.javaClass}")
|
||||
evaluationException("Couldn't evaluate Kotlin expression in this context")
|
||||
}
|
||||
|
||||
return ExpressionEvaluatorImpl(KotlinEvaluator(codeFragment, position))
|
||||
}
|
||||
}
|
||||
|
||||
class KotlinEvaluator(val codeFragment: KtCodeFragment, val sourcePosition: SourcePosition?) : Evaluator {
|
||||
override fun evaluate(context: EvaluationContextImpl): Any? {
|
||||
if (codeFragment.text.isEmpty()) {
|
||||
return context.debugProcess.virtualMachineProxy.mirrorOfVoid()
|
||||
}
|
||||
|
||||
if (DumbService.getInstance(codeFragment.project).isDumb) {
|
||||
evaluationException("Code fragment evaluation is not available in the dumb mode")
|
||||
}
|
||||
|
||||
val frameProxy = context.frameProxy
|
||||
?: evaluationException("Cannot evaluate a code fragment: frame proxy is not available")
|
||||
|
||||
val operatingThread = context.suspendContext.thread
|
||||
?: evaluationException("Cannot evaluate a code fragment: thread is not available")
|
||||
|
||||
if (!operatingThread.isSuspended) {
|
||||
evaluationException("Evaluation is available only for the suspended threads")
|
||||
}
|
||||
|
||||
try {
|
||||
val executionContext = ExecutionContext(context, frameProxy)
|
||||
return evaluateSafe(executionContext)
|
||||
} catch (e: EvaluateException) {
|
||||
throw e
|
||||
} catch (e: ProcessCanceledException) {
|
||||
evaluationException(e)
|
||||
} catch (e: Eval4JInterpretingException) {
|
||||
evaluationException(e.cause)
|
||||
} catch (e: Exception) {
|
||||
val isSpecialException = isSpecialException(e)
|
||||
if (isSpecialException) {
|
||||
evaluationException(e)
|
||||
}
|
||||
|
||||
reportError(codeFragment, sourcePosition, e.message ?: "An exception occurred", e)
|
||||
|
||||
val cause = if (e.message != null) ": ${e.message}" else ""
|
||||
evaluationException("Cannot evaluate the expression: $cause")
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateSafe(context: ExecutionContext): Any? {
|
||||
fun compilerFactory(): CompiledDataDescriptor = compileCodeFragment(context)
|
||||
|
||||
val (compiledData, isCompiledDataFromCache) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory)
|
||||
val classLoaderRef = loadClassesSafely(context, compiledData.classes)
|
||||
|
||||
val result = if (classLoaderRef != null) {
|
||||
evaluateWithCompilation(context, compiledData, classLoaderRef)
|
||||
?: evaluateWithEval4J(context, compiledData, classLoaderRef)
|
||||
} else {
|
||||
evaluateWithEval4J(context, compiledData, classLoaderRef)
|
||||
}
|
||||
|
||||
// If bytecode was taken from cache and exception was thrown - recompile bytecode and run eval4j again
|
||||
if (isCompiledDataFromCache && result is ExceptionThrown && result.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE) {
|
||||
val (recompiledData, _) = compileCodeFragmentCacheAware(codeFragment, sourcePosition, ::compilerFactory, force = true)
|
||||
return evaluateWithEval4J(context, recompiledData, classLoaderRef).toJdiValue(context)
|
||||
}
|
||||
|
||||
return when (result) {
|
||||
is InterpreterResult -> result.toJdiValue(context)
|
||||
else -> result
|
||||
}
|
||||
}
|
||||
|
||||
private fun compileCodeFragment(context: ExecutionContext): CompiledDataDescriptor {
|
||||
val debugProcess = context.debugProcess
|
||||
var analysisResult = checkForErrors(codeFragment, debugProcess)
|
||||
|
||||
if (codeFragment.wrapToStringIfNeeded(analysisResult.bindingContext)) {
|
||||
// Repeat analysis with toString() added
|
||||
analysisResult = checkForErrors(codeFragment, debugProcess)
|
||||
}
|
||||
|
||||
val (bindingContext) = runReadAction {
|
||||
DebuggerUtils.analyzeInlinedFunctions(
|
||||
KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(listOf(codeFragment)),
|
||||
codeFragment, false, analysisResult.bindingContext
|
||||
)
|
||||
}
|
||||
|
||||
val moduleDescriptor = analysisResult.moduleDescriptor
|
||||
|
||||
val result = CodeFragmentCompiler(context).compile(codeFragment, bindingContext, moduleDescriptor)
|
||||
return CompiledDataDescriptor.from(result, sourcePosition)
|
||||
}
|
||||
|
||||
private fun KtCodeFragment.wrapToStringIfNeeded(bindingContext: BindingContext): Boolean {
|
||||
if (this !is KtExpressionCodeFragment) {
|
||||
return false
|
||||
}
|
||||
|
||||
val contentElement = runReadAction { getContentElement() }
|
||||
val expressionType = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, contentElement]?.type
|
||||
if (contentElement != null && expressionType?.isInlineClassType() == true) {
|
||||
val newExpression = runReadAction {
|
||||
val expressionText = contentElement.text
|
||||
KtPsiFactory(project).createExpression("($expressionText).toString()")
|
||||
}
|
||||
runInEdtAndWait {
|
||||
project.executeWriteCommand("Wrap with 'toString()'") {
|
||||
contentElement.replace(newExpression)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private data class ErrorCheckingResult(
|
||||
val bindingContext: BindingContext,
|
||||
val moduleDescriptor: ModuleDescriptor,
|
||||
val files: List<KtFile>
|
||||
)
|
||||
|
||||
private fun checkForErrors(codeFragment: KtCodeFragment, debugProcess: DebugProcessImpl): ErrorCheckingResult {
|
||||
return runInReadActionWithWriteActionPriorityWithPCE {
|
||||
try {
|
||||
AnalyzingUtils.checkForSyntacticErrors(codeFragment)
|
||||
} catch (e: IllegalArgumentException) {
|
||||
evaluationException(e.message ?: e.toString())
|
||||
}
|
||||
|
||||
val filesToAnalyze = listOf(codeFragment)
|
||||
val resolutionFacade = KotlinCacheService.getInstance(codeFragment.project).getResolutionFacade(filesToAnalyze)
|
||||
|
||||
DebugLabelPropertyDescriptorProvider(codeFragment, debugProcess).supplyDebugLabels()
|
||||
|
||||
val analysisResult = resolutionFacade.analyzeWithAllCompilerChecks(filesToAnalyze)
|
||||
|
||||
if (analysisResult.isError()) {
|
||||
evaluationException(analysisResult.error)
|
||||
}
|
||||
|
||||
val bindingContext = analysisResult.bindingContext
|
||||
|
||||
bindingContext.diagnostics
|
||||
.filter { it.factory !in IGNORED_DIAGNOSTICS }
|
||||
.firstOrNull { it.severity == Severity.ERROR && it.psiElement.containingFile == codeFragment }
|
||||
?.let { evaluationException(DefaultErrorMessages.render(it)) }
|
||||
|
||||
ErrorCheckingResult(bindingContext, analysisResult.moduleDescriptor, Collections.singletonList(codeFragment))
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateWithCompilation(
|
||||
context: ExecutionContext,
|
||||
compiledData: CompiledDataDescriptor,
|
||||
classLoader: ClassLoaderReference
|
||||
): Value? {
|
||||
return try {
|
||||
runEvaluation(context, compiledData, classLoader) { args ->
|
||||
val mainClassType = context.findClass(GENERATED_CLASS_NAME, classLoader) as? ClassType
|
||||
?: error("Can not find class \"$GENERATED_CLASS_NAME\"")
|
||||
val mainMethod = mainClassType.methods().single { it.name() == GENERATED_FUNCTION_NAME }
|
||||
val returnValue = context.invokeMethod(mainClassType, mainMethod, args)
|
||||
EvaluatorValueConverter(context).unref(returnValue)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
LOG.error("Unable to evaluate the expression with compilation", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluateWithEval4J(
|
||||
context: ExecutionContext,
|
||||
compiledData: CompiledDataDescriptor,
|
||||
classLoader: ClassLoaderReference?
|
||||
): InterpreterResult {
|
||||
val mainClassBytecode = compiledData.mainClass.bytes
|
||||
val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, 0) }
|
||||
val mainMethod = mainClassAsmNode.methods.first { it.name == GENERATED_FUNCTION_NAME }
|
||||
|
||||
return runEvaluation(context, compiledData, classLoader ?: context.evaluationContext.classLoader) { args ->
|
||||
val vm = context.vm.virtualMachine
|
||||
val thread = context.suspendContext.thread?.threadReference?.takeIf { it.isSuspended }
|
||||
?: error("Can not find a thread to run evaluation on")
|
||||
|
||||
val eval = JDIEval(vm, classLoader, thread, context.invokePolicy)
|
||||
interpreterLoop(mainMethod, makeInitialFrame(mainMethod, args.map { it.asValue() }), eval)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> runEvaluation(
|
||||
context: ExecutionContext,
|
||||
compiledData: CompiledDataDescriptor,
|
||||
classLoader: ClassLoaderReference?,
|
||||
block: (List<Value?>) -> T
|
||||
): T {
|
||||
// Preload additional classes
|
||||
compiledData.classes
|
||||
.filter { !it.isMainClass }
|
||||
.forEach { context.findClass(it.className, classLoader) }
|
||||
|
||||
return context.vm.virtualMachine.executeWithBreakpointsDisabled {
|
||||
for (parameterType in compiledData.mainMethodSignature.parameterTypes) {
|
||||
context.findClass(parameterType, classLoader)
|
||||
}
|
||||
val args = calculateMainMethodCallArguments(context, compiledData)
|
||||
block(args)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateMainMethodCallArguments(context: ExecutionContext, compiledData: CompiledDataDescriptor): List<Value?> {
|
||||
val asmValueParameters = compiledData.mainMethodSignature.parameterTypes
|
||||
val valueParameters = compiledData.parameters
|
||||
require(asmValueParameters.size == valueParameters.size)
|
||||
|
||||
val args = valueParameters.zip(asmValueParameters)
|
||||
val variableFinder = VariableFinder(context)
|
||||
|
||||
return args.map { (parameter, asmType) ->
|
||||
val result = variableFinder.find(parameter, asmType)
|
||||
|
||||
if (result == null) {
|
||||
val name = parameter.debugString
|
||||
|
||||
fun isInsideDefaultInterfaceMethod(): Boolean {
|
||||
val method = context.frameProxy.safeLocation()?.safeMethod() ?: return false
|
||||
val desc = method.signature()
|
||||
return method.name().endsWith("\$default") && DEFAULT_METHOD_MARKERS.any { desc.contains("I${it.descriptor})") }
|
||||
}
|
||||
|
||||
if (parameter in compiledData.crossingBounds) {
|
||||
evaluationException("'$name' is not captured")
|
||||
} else if (parameter.kind == CodeFragmentParameter.Kind.FIELD_VAR) {
|
||||
evaluationException("Cannot find the backing field '${parameter.name}'")
|
||||
} else if (parameter.kind == CodeFragmentParameter.Kind.ORDINARY && isInsideDefaultInterfaceMethod()) {
|
||||
evaluationException("Parameter evaluation is not supported for '\$default' methods")
|
||||
} else {
|
||||
throw VariableFinder.variableNotFound(context, buildString {
|
||||
append("Cannot find local variable: name = '").append(name).append("', type = ").append(asmType.className)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
result.value
|
||||
}
|
||||
}
|
||||
|
||||
override fun getModifier() = null
|
||||
|
||||
companion object {
|
||||
private val IGNORED_DIAGNOSTICS: Set<DiagnosticFactory<*>> =
|
||||
Errors.INVISIBLE_REFERENCE_DIAGNOSTICS + setOf(Errors.EXPERIMENTAL_API_USAGE_ERROR)
|
||||
|
||||
private val DEFAULT_METHOD_MARKERS = listOf(AsmTypes.OBJECT_TYPE, AsmTypes.DEFAULT_CONSTRUCTOR_MARKER)
|
||||
|
||||
private fun InterpreterResult.toJdiValue(context: ExecutionContext): Value? {
|
||||
val jdiValue = when (this) {
|
||||
is ValueReturned -> result
|
||||
is ExceptionThrown -> {
|
||||
when {
|
||||
this.kind == ExceptionThrown.ExceptionKind.FROM_EVALUATED_CODE ->
|
||||
evaluationException(InvocationException(this.exception.value as ObjectReference))
|
||||
this.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE ->
|
||||
throw exception.value as Throwable
|
||||
else ->
|
||||
evaluationException(exception.toString())
|
||||
}
|
||||
}
|
||||
is AbnormalTermination -> evaluationException(message)
|
||||
else -> throw IllegalStateException("Unknown result value produced by eval4j")
|
||||
}
|
||||
|
||||
val sharedVar = if ((jdiValue is AbstractValue<*>)) getValueIfSharedVar(jdiValue, context) else null
|
||||
return sharedVar?.value ?: jdiValue.asJdiValue(context.vm.virtualMachine, jdiValue.asmType)
|
||||
}
|
||||
|
||||
private fun getValueIfSharedVar(value: Eval4JValue, context: ExecutionContext): VariableFinder.Result? {
|
||||
val obj = value.obj(value.asmType) as? ObjectReference ?: return null
|
||||
return VariableFinder.Result(EvaluatorValueConverter(context).unref(obj))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Type.getClassDescriptor(
|
||||
scope: GlobalSearchScope,
|
||||
mapBuiltIns: Boolean = true,
|
||||
moduleDescriptor: ModuleDescriptor = DefaultBuiltIns.Instance.builtInsModule
|
||||
): ClassDescriptor? {
|
||||
if (AsmUtil.isPrimitive(this)) return null
|
||||
|
||||
val jvmName = JvmClassName.byInternalName(internalName).fqNameForClassNameWithoutDollars
|
||||
|
||||
if (mapBuiltIns) {
|
||||
val mappedName = JavaToKotlinClassMap.mapJavaToKotlin(jvmName)
|
||||
if (mappedName != null) {
|
||||
moduleDescriptor.findClassAcrossModuleDependencies(mappedName)?.let { return it }
|
||||
}
|
||||
}
|
||||
|
||||
return runReadAction {
|
||||
val classes = JavaPsiFacade.getInstance(scope.project).findClasses(jvmName.asString(), scope)
|
||||
if (classes.isEmpty()) null
|
||||
else {
|
||||
classes.first().getJavaClassDescriptor()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> VirtualMachine.executeWithBreakpointsDisabled(block: () -> T): T {
|
||||
val allRequests = eventRequestManager().breakpointRequests() + eventRequestManager().classPrepareRequests()
|
||||
|
||||
try {
|
||||
allRequests.forEach { it.disable() }
|
||||
return block()
|
||||
} finally {
|
||||
allRequests.forEach { it.enable() }
|
||||
}
|
||||
}
|
||||
|
||||
private fun isSpecialException(th: Throwable): Boolean {
|
||||
return when (th) {
|
||||
is ClassNotPreparedException,
|
||||
is InternalException,
|
||||
is AbsentInformationException,
|
||||
is ClassNotLoadedException,
|
||||
is IncompatibleThreadStateException,
|
||||
is InconsistentDebugInfoException,
|
||||
is ObjectCollectedException,
|
||||
is VMDisconnectedException -> true
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun reportError(codeFragment: KtCodeFragment, position: SourcePosition?, message: String, throwable: Throwable? = null) {
|
||||
runReadAction {
|
||||
val contextFile = codeFragment.context?.containingFile
|
||||
|
||||
val attachments = arrayOf(
|
||||
attachmentByPsiFile(contextFile),
|
||||
attachmentByPsiFile(codeFragment),
|
||||
Attachment("breakpoint.info", "Position: " + position?.run { "${file.name}:$line" }),
|
||||
Attachment("context.info", runReadAction { codeFragment.context?.text ?: "null" })
|
||||
)
|
||||
|
||||
LOG.error(
|
||||
"Cannot evaluate a code fragment of type " + codeFragment::class.java + ": " + message.decapitalize(),
|
||||
throwable,
|
||||
mergeAttachments(*attachments)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun evaluationException(msg: String): Nothing = throw EvaluateExceptionUtil.createEvaluateException(msg)
|
||||
private fun evaluationException(e: Throwable): Nothing = throw EvaluateExceptionUtil.createEvaluateException(e)
|
||||
-109
@@ -1,109 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate
|
||||
|
||||
import com.intellij.debugger.DebuggerBundle
|
||||
import com.intellij.debugger.DebuggerInvocationUtil
|
||||
import com.intellij.debugger.engine.ContextUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.engine.evaluation.expression.ExpressionEvaluator
|
||||
import com.intellij.debugger.impl.DebuggerContextImpl
|
||||
import com.intellij.debugger.ui.EditorEvaluationCommand
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.psi.CommonClassNames
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.sun.jdi.ClassType
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.eval4j.jdi.asValue
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.psi.KtPsiFactory
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
|
||||
abstract class KotlinRuntimeTypeEvaluator(
|
||||
editor: Editor?,
|
||||
expression: KtExpression,
|
||||
context: DebuggerContextImpl,
|
||||
indicator: ProgressIndicator
|
||||
) : EditorEvaluationCommand<KotlinType>(editor, expression, context, indicator) {
|
||||
|
||||
override fun threadAction() {
|
||||
var type: KotlinType? = null
|
||||
try {
|
||||
type = evaluate()
|
||||
} catch (ignored: ProcessCanceledException) {
|
||||
} catch (ignored: EvaluateException) {
|
||||
} finally {
|
||||
typeCalculationFinished(type)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract fun typeCalculationFinished(type: KotlinType?)
|
||||
|
||||
override fun evaluate(evaluationContext: EvaluationContextImpl): KotlinType? {
|
||||
val project = evaluationContext.project
|
||||
|
||||
val evaluator = DebuggerInvocationUtil.commitAndRunReadAction<ExpressionEvaluator>(project) {
|
||||
val codeFragment = KtPsiFactory(myElement.project).createExpressionCodeFragment(
|
||||
myElement.text, myElement.containingFile.context
|
||||
)
|
||||
KotlinEvaluatorBuilder.build(codeFragment, ContextUtil.getSourcePosition(evaluationContext))
|
||||
}
|
||||
|
||||
val value = evaluator.evaluate(evaluationContext)
|
||||
if (value != null) {
|
||||
return runReadAction { getCastableRuntimeType(evaluationContext.debugProcess.searchScope, value) }
|
||||
}
|
||||
|
||||
throw EvaluateExceptionUtil.createEvaluateException(DebuggerBundle.message("evaluation.error.surrounded.expression.null"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private fun getCastableRuntimeType(scope: GlobalSearchScope, value: Value): KotlinType? {
|
||||
val myValue = value.asValue()
|
||||
var psiClass = myValue.asmType.getClassDescriptor(scope)
|
||||
if (psiClass != null) {
|
||||
return psiClass.defaultType
|
||||
}
|
||||
|
||||
val type = value.type()
|
||||
if (type is ClassType) {
|
||||
val superclass = type.superclass()
|
||||
if (superclass != null && CommonClassNames.JAVA_LANG_OBJECT != superclass.name()) {
|
||||
psiClass = AsmType.getType(superclass.signature()).getClassDescriptor(scope)
|
||||
if (psiClass != null) {
|
||||
return psiClass.defaultType
|
||||
}
|
||||
}
|
||||
|
||||
for (interfaceType in type.interfaces()) {
|
||||
psiClass = AsmType.getType(interfaceType.signature()).getClassDescriptor(scope)
|
||||
if (psiClass != null) {
|
||||
return psiClass.defaultType
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
|
||||
abstract class AbstractAndroidClassLoadingAdapter : ClassLoadingAdapter {
|
||||
protected fun dex(context: ExecutionContext, classes: Collection<ClassToLoad>): ByteArray? {
|
||||
return AndroidDexer.getInstances(context.project).single().dex(classes)
|
||||
}
|
||||
|
||||
protected fun wrapToByteBuffer(bytes: ArrayReference, context: ExecutionContext): ObjectReference {
|
||||
val classLoader = context.classLoader
|
||||
val byteBufferClass = context.findClass("java.nio.ByteBuffer", classLoader) as ClassType
|
||||
val wrapMethod = byteBufferClass.concreteMethodByName("wrap", "([B)Ljava/nio/ByteBuffer;")
|
||||
?: error("'wrap' method not found")
|
||||
|
||||
return context.invokeMethod(byteBufferClass, wrapMethod, listOf(bytes)) as ObjectReference
|
||||
}
|
||||
|
||||
protected fun tryLoadClass(context: ExecutionContext, fqName: String, classLoader: ClassLoaderReference?): ReferenceType? {
|
||||
return try {
|
||||
context.debugProcess.loadClass(context.evaluationContext, fqName, classLoader)
|
||||
} catch (e: Throwable) {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import org.jetbrains.kotlin.extensions.ProjectExtensionDescriptor
|
||||
|
||||
interface AndroidDexer {
|
||||
companion object : ProjectExtensionDescriptor<AndroidDexer>(
|
||||
"org.jetbrains.kotlin.androidDexer", AndroidDexer::class.java
|
||||
)
|
||||
|
||||
fun dex(classes: Collection<ClassToLoad>): ByteArray?
|
||||
}
|
||||
-56
@@ -1,56 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import com.intellij.debugger.engine.JVMNameUtil
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
||||
|
||||
class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
|
||||
override fun isApplicable(context: ExecutionContext, info: ClassLoadingAdapter.Companion.ClassInfoForEvaluator) = with(info) {
|
||||
isCompilingEvaluatorPreferred && context.debugProcess.isDexDebug()
|
||||
}
|
||||
|
||||
private fun resolveClassLoaderClass(context: ExecutionContext): ClassType? {
|
||||
return try {
|
||||
val classLoader = context.classLoader
|
||||
tryLoadClass(context, "dalvik.system.InMemoryDexClassLoader", classLoader) as? ClassType
|
||||
} catch (e: EvaluateException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference {
|
||||
val inMemoryClassLoaderClass = resolveClassLoaderClass(context) ?: error("InMemoryDexClassLoader class not found")
|
||||
val constructorMethod = inMemoryClassLoaderClass.concreteMethodByName(
|
||||
JVMNameUtil.CONSTRUCTOR_NAME, "(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V"
|
||||
) ?: error("Constructor method not found")
|
||||
|
||||
val dexBytes = dex(context, classes) ?: error("Can't dex classes")
|
||||
val dexBytesMirror = mirrorOfByteArray(dexBytes, context)
|
||||
val dexByteBuffer = wrapToByteBuffer(dexBytesMirror, context)
|
||||
|
||||
val classLoader = context.classLoader
|
||||
val args = listOf(dexByteBuffer, classLoader)
|
||||
val newClassLoader = context.newInstance(inMemoryClassLoaderClass, constructorMethod, args) as ClassLoaderReference
|
||||
context.keepReference(newClassLoader)
|
||||
|
||||
return newClassLoader
|
||||
}
|
||||
}
|
||||
-117
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import com.sun.jdi.ArrayReference
|
||||
import com.sun.jdi.ArrayType
|
||||
import com.sun.jdi.ClassLoaderReference
|
||||
import com.sun.jdi.Value
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.GENERATED_FUNCTION_NAME
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.Label
|
||||
import org.jetbrains.org.objectweb.asm.tree.*
|
||||
import kotlin.math.min
|
||||
|
||||
interface ClassLoadingAdapter {
|
||||
companion object {
|
||||
private const val CHUNK_SIZE = 4096
|
||||
|
||||
private val ADAPTERS = listOf(
|
||||
AndroidOClassLoadingAdapter(),
|
||||
OrdinaryClassLoadingAdapter()
|
||||
)
|
||||
|
||||
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
val mainClass = classes.firstOrNull { it.isMainClass } ?: return null
|
||||
|
||||
var info = ClassInfoForEvaluator(containsAdditionalClasses = classes.size > 1)
|
||||
if (!info.containsAdditionalClasses) {
|
||||
info = analyzeClass(mainClass, info)
|
||||
}
|
||||
|
||||
for (adapter in ADAPTERS) {
|
||||
if (adapter.isApplicable(context, info)) {
|
||||
return adapter.loadClasses(context, classes)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
data class ClassInfoForEvaluator(
|
||||
val containsLoops: Boolean = false,
|
||||
val containsCodeUnsupportedInEval4J: Boolean = false,
|
||||
val containsAdditionalClasses: Boolean = false
|
||||
) {
|
||||
val isCompilingEvaluatorPreferred: Boolean
|
||||
get() = containsLoops || containsCodeUnsupportedInEval4J || containsAdditionalClasses
|
||||
}
|
||||
|
||||
private fun analyzeClass(classToLoad: ClassToLoad, info: ClassInfoForEvaluator): ClassInfoForEvaluator {
|
||||
val classNode = ClassNode().apply { ClassReader(classToLoad.bytes).accept(this, 0) }
|
||||
val methodToRun = classNode.methods.single { it.name == GENERATED_FUNCTION_NAME }
|
||||
|
||||
val visitedLabels = hashSetOf<Label>()
|
||||
|
||||
tailrec fun analyzeInsn(insn: AbstractInsnNode, info: ClassInfoForEvaluator): ClassInfoForEvaluator {
|
||||
when (insn) {
|
||||
is LabelNode -> visitedLabels += insn.label
|
||||
is JumpInsnNode -> {
|
||||
if (insn.label.label in visitedLabels) {
|
||||
return info.copy(containsLoops = true)
|
||||
}
|
||||
}
|
||||
is TableSwitchInsnNode, is LookupSwitchInsnNode -> {
|
||||
return info.copy(containsCodeUnsupportedInEval4J = true)
|
||||
}
|
||||
}
|
||||
|
||||
val nextInsn = insn.next ?: return info
|
||||
return analyzeInsn(nextInsn, info)
|
||||
}
|
||||
|
||||
val firstInsn = methodToRun.instructions?.first ?: return info
|
||||
return analyzeInsn(firstInsn, info)
|
||||
}
|
||||
}
|
||||
|
||||
fun isApplicable(context: ExecutionContext, info: ClassInfoForEvaluator): Boolean
|
||||
|
||||
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference
|
||||
|
||||
fun mirrorOfByteArray(bytes: ByteArray, context: ExecutionContext): ArrayReference {
|
||||
val classLoader = context.classLoader
|
||||
val arrayClass = context.findClass("byte[]", classLoader) as ArrayType
|
||||
val reference = context.newInstance(arrayClass, bytes.size)
|
||||
context.keepReference(reference)
|
||||
|
||||
val mirrors = ArrayList<Value>(bytes.size)
|
||||
for (byte in bytes) {
|
||||
mirrors += context.vm.mirrorOf(byte)
|
||||
}
|
||||
|
||||
var loaded = 0
|
||||
while (loaded < mirrors.size) {
|
||||
val chunkSize = min(CHUNK_SIZE, mirrors.size - loaded)
|
||||
reference.setValues(loaded, mirrors, loaded, chunkSize)
|
||||
loaded += chunkSize
|
||||
}
|
||||
|
||||
return reference
|
||||
}
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.GENERATED_CLASS_NAME
|
||||
|
||||
@Suppress("ArrayInDataClass")
|
||||
data class ClassToLoad(val className: String, val relativeFileName: String, val bytes: ByteArray) {
|
||||
val isMainClass: Boolean
|
||||
get() = className == GENERATED_CLASS_NAME
|
||||
}
|
||||
-143
@@ -1,143 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2017 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.classLoading
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.impl.ClassLoadingUtils
|
||||
import com.intellij.openapi.projectRoots.JavaSdkVersion
|
||||
import com.sun.jdi.ClassLoaderReference
|
||||
import com.sun.jdi.ClassType
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.isDexDebug
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.ClassVisitor
|
||||
import org.jetbrains.org.objectweb.asm.ClassWriter
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
|
||||
class OrdinaryClassLoadingAdapter : ClassLoadingAdapter {
|
||||
private companion object {
|
||||
// This list should contain all superclasses of lambda classes.
|
||||
// The order is relevant here: if we load Lambda first instead, during the definition of Lambda the class loader will try
|
||||
// to load its superclass. It will succeed, probably with the help of some parent class loader, and the subsequent attempt to define
|
||||
// the patched version of that superclass will fail with LinkageError (cannot redefine class)
|
||||
private val LAMBDA_SUPERCLASSES = listOf(ClassBytes("kotlin.jvm.internal.Lambda"))
|
||||
|
||||
// Copied from com.intellij.debugger.ui.impl.watch.CompilingEvaluator.changeSuperToMagicAccessor
|
||||
fun changeSuperToMagicAccessor(bytes: ByteArray): ByteArray {
|
||||
val classWriter = ClassWriter(0)
|
||||
val classVisitor = object : ClassVisitor(Opcodes.API_VERSION, classWriter) {
|
||||
override fun visit(
|
||||
version: Int,
|
||||
access: Int,
|
||||
name: String,
|
||||
signature: String?,
|
||||
superName: String?,
|
||||
interfaces: Array<String>?
|
||||
) {
|
||||
var newSuperName = superName
|
||||
if ("java/lang/Object" == newSuperName) {
|
||||
newSuperName = "sun/reflect/MagicAccessorImpl"
|
||||
}
|
||||
|
||||
super.visit(version, access, name, signature, newSuperName, interfaces)
|
||||
}
|
||||
}
|
||||
ClassReader(bytes).accept(classVisitor, 0)
|
||||
return classWriter.toByteArray()
|
||||
}
|
||||
|
||||
fun useMagicAccessor(context: ExecutionContext): Boolean {
|
||||
val rawVersion = context.vm.version()?.substringBefore('_') ?: return false
|
||||
val javaVersion = JavaSdkVersion.fromVersionString(rawVersion) ?: return false
|
||||
return !javaVersion.isAtLeast(JavaSdkVersion.JDK_1_9)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isApplicable(context: ExecutionContext, info: ClassLoadingAdapter.Companion.ClassInfoForEvaluator): Boolean {
|
||||
return info.isCompilingEvaluatorPreferred && context.classLoader != null && !context.debugProcess.isDexDebug()
|
||||
}
|
||||
|
||||
override fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference {
|
||||
val process = context.debugProcess
|
||||
|
||||
val classLoader = try {
|
||||
ClassLoadingUtils.getClassLoader(context.evaluationContext, process)
|
||||
} catch (e: Exception) {
|
||||
throw EvaluateException("Error creating evaluation class loader: $e", e)
|
||||
}
|
||||
|
||||
try {
|
||||
defineClasses(classes, context, classLoader)
|
||||
} catch (e: Exception) {
|
||||
throw EvaluateException("Error during classes definition $e", e)
|
||||
}
|
||||
|
||||
return classLoader
|
||||
}
|
||||
|
||||
private fun defineClasses(
|
||||
classes: Collection<ClassToLoad>,
|
||||
context: ExecutionContext,
|
||||
classLoader: ClassLoaderReference
|
||||
) {
|
||||
val classesToLoad = if (classes.size == 1) {
|
||||
// No need in loading lambda superclass if there're no lambdas
|
||||
classes
|
||||
} else {
|
||||
val lambdaSuperclasses = LAMBDA_SUPERCLASSES.map {
|
||||
ClassToLoad(it.name, it.name.replace('.', '/') + ".class", it.bytes)
|
||||
}
|
||||
lambdaSuperclasses + classes
|
||||
}
|
||||
|
||||
for ((className, _, bytes) in classesToLoad) {
|
||||
val patchedBytes = if (useMagicAccessor(context)) changeSuperToMagicAccessor(bytes) else bytes
|
||||
defineClass(className, patchedBytes, context, classLoader)
|
||||
}
|
||||
}
|
||||
|
||||
private fun defineClass(
|
||||
name: String,
|
||||
bytes: ByteArray,
|
||||
context: ExecutionContext,
|
||||
classLoader: ClassLoaderReference
|
||||
) {
|
||||
try {
|
||||
val vm = context.vm
|
||||
val classLoaderType = classLoader.referenceType() as ClassType
|
||||
val defineMethod = classLoaderType.concreteMethodByName("defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;")
|
||||
val nameObj = vm.mirrorOf(name)
|
||||
|
||||
val args = listOf(nameObj, mirrorOfByteArray(bytes, context), vm.mirrorOf(0), vm.mirrorOf(bytes.size))
|
||||
context.invokeMethod(classLoader, defineMethod, args)
|
||||
} catch (e: Exception) {
|
||||
throw EvaluateException("Error during class $name definition: $e", e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private class ClassBytes(val name: String) {
|
||||
val bytes: ByteArray by lazy {
|
||||
val inputStream = this::class.java.classLoader.getResourceAsStream(name.replace('.', '/') + ".class")
|
||||
?: throw EvaluateException("Couldn't find $name class in current class loader")
|
||||
|
||||
inputStream.use {
|
||||
it.readBytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-284
@@ -1,284 +0,0 @@
|
||||
/*
|
||||
* 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.evaluate.compilation
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.output.OutputFile
|
||||
import org.jetbrains.kotlin.caches.resolve.KotlinCacheService
|
||||
import org.jetbrains.kotlin.codegen.*
|
||||
import org.jetbrains.kotlin.codegen.CodeFragmentCodegen.Companion.getSharedTypeIfApplicable
|
||||
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension.Context as InCo
|
||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
|
||||
import org.jetbrains.kotlin.config.CompilerConfiguration
|
||||
import org.jetbrains.kotlin.config.languageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||
import org.jetbrains.kotlin.idea.project.languageVersionSettings
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.incremental.components.LookupLocation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
|
||||
import org.jetbrains.kotlin.resolve.lazy.data.KtClassOrObjectInfo
|
||||
import org.jetbrains.kotlin.resolve.lazy.data.KtScriptInfo
|
||||
import org.jetbrains.kotlin.resolve.lazy.declarations.PackageMemberDeclarationProvider
|
||||
import org.jetbrains.kotlin.resolve.lazy.descriptors.LazyPackageDescriptor
|
||||
import org.jetbrains.kotlin.resolve.scopes.ChainedMemberScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
|
||||
import org.jetbrains.kotlin.resolve.source.KotlinSourceElement
|
||||
import org.jetbrains.kotlin.storage.LockBasedStorageManager
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.SimpleType
|
||||
import org.jetbrains.kotlin.utils.Printer
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
|
||||
class CodeFragmentCompiler(private val executionContext: ExecutionContext) {
|
||||
data class CompilationResult(
|
||||
val classes: List<ClassToLoad>,
|
||||
val parameterInfo: CodeFragmentParameterInfo,
|
||||
val localFunctionSuffixes: Map<CodeFragmentParameter.Dumb, String>,
|
||||
val mainMethodSignature: MethodSignature
|
||||
)
|
||||
|
||||
data class MethodSignature(val parameterTypes: List<Type>, val returnType: Type)
|
||||
|
||||
fun compile(codeFragment: KtCodeFragment, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor): CompilationResult {
|
||||
return runReadAction { doCompile(codeFragment, bindingContext, moduleDescriptor) }
|
||||
}
|
||||
|
||||
private fun doCompile(
|
||||
codeFragment: KtCodeFragment, bindingContext: BindingContext, moduleDescriptor: ModuleDescriptor
|
||||
): CompilationResult {
|
||||
require(codeFragment is KtBlockCodeFragment || codeFragment is KtExpressionCodeFragment) {
|
||||
"Unsupported code fragment type: $codeFragment"
|
||||
}
|
||||
|
||||
val project = codeFragment.project
|
||||
val resolutionFacade = KotlinCacheService.getInstance(project).getResolutionFacade(listOf(codeFragment))
|
||||
val resolveSession = resolutionFacade.getFrontendService(ResolveSession::class.java)
|
||||
val moduleDescriptorWrapper = EvaluatorModuleDescriptor(codeFragment, moduleDescriptor, resolveSession)
|
||||
|
||||
val defaultReturnType = moduleDescriptor.builtIns.unitType
|
||||
val returnType = getReturnType(codeFragment, bindingContext, defaultReturnType)
|
||||
|
||||
val compilerConfiguration = CompilerConfiguration()
|
||||
compilerConfiguration.languageVersionSettings = codeFragment.languageVersionSettings
|
||||
|
||||
val generationState = GenerationState.Builder(
|
||||
project, ClassBuilderFactories.BINARIES, moduleDescriptorWrapper,
|
||||
bindingContext, listOf(codeFragment), compilerConfiguration
|
||||
).build()
|
||||
|
||||
val parameterInfo = CodeFragmentParameterAnalyzer(executionContext, codeFragment, bindingContext).analyze()
|
||||
val (classDescriptor, methodDescriptor) = createDescriptorsForCodeFragment(
|
||||
codeFragment, Name.identifier(GENERATED_CLASS_NAME), Name.identifier(GENERATED_FUNCTION_NAME),
|
||||
parameterInfo, returnType, moduleDescriptorWrapper.packageFragmentForEvaluator
|
||||
)
|
||||
|
||||
val codegenInfo = CodeFragmentCodegenInfo(classDescriptor, methodDescriptor, parameterInfo.parameters)
|
||||
CodeFragmentCodegen.setCodeFragmentInfo(codeFragment, codegenInfo)
|
||||
|
||||
KotlinCodegenFacade.compileCorrectFiles(generationState, CompilationErrorHandler.THROW_EXCEPTION)
|
||||
|
||||
val classes = generationState.factory.asList().filterClassFiles()
|
||||
.map { ClassToLoad(it.internalClassName, it.relativePath, it.asByteArray()) }
|
||||
|
||||
val methodSignature = getMethodSignature(methodDescriptor, parameterInfo.parameters, generationState)
|
||||
val functionSuffixes = getLocalFunctionSuffixes(parameterInfo.parameters, generationState.typeMapper)
|
||||
|
||||
generationState.destroy()
|
||||
|
||||
return CompilationResult(classes, parameterInfo, functionSuffixes, methodSignature)
|
||||
}
|
||||
|
||||
private fun getLocalFunctionSuffixes(
|
||||
parameters: List<CodeFragmentParameter.Smart>,
|
||||
typeMapper: KotlinTypeMapper
|
||||
): Map<CodeFragmentParameter.Dumb, String> {
|
||||
val result = mutableMapOf<CodeFragmentParameter.Dumb, String>()
|
||||
|
||||
for (parameter in parameters) {
|
||||
if (parameter.kind != CodeFragmentParameter.Kind.LOCAL_FUNCTION) {
|
||||
continue
|
||||
}
|
||||
|
||||
val ownerClassName = typeMapper.mapOwner(parameter.targetDescriptor).internalName
|
||||
val lastDollarIndex = ownerClassName.lastIndexOf('$').takeIf { it >= 0 } ?: continue
|
||||
result[parameter.dumb] = ownerClassName.drop(lastDollarIndex)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getMethodSignature(
|
||||
methodDescriptor: FunctionDescriptor,
|
||||
parameters: List<CodeFragmentParameter.Smart>,
|
||||
state: GenerationState
|
||||
): MethodSignature {
|
||||
val typeMapper = state.typeMapper
|
||||
val asmSignature = typeMapper.mapSignatureSkipGeneric(methodDescriptor)
|
||||
val asmParameters = parameters.zip(asmSignature.valueParameters).map { (param, sigParam) ->
|
||||
getSharedTypeIfApplicable(param.targetDescriptor, typeMapper) ?: sigParam.asmType
|
||||
}
|
||||
|
||||
return MethodSignature(asmParameters, asmSignature.returnType)
|
||||
}
|
||||
|
||||
private fun getReturnType(
|
||||
codeFragment: KtCodeFragment,
|
||||
bindingContext: BindingContext,
|
||||
defaultReturnType: SimpleType
|
||||
): KotlinType {
|
||||
return when (codeFragment) {
|
||||
is KtExpressionCodeFragment -> {
|
||||
val typeInfo = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, codeFragment.getContentElement()]
|
||||
typeInfo?.type ?: defaultReturnType
|
||||
}
|
||||
is KtBlockCodeFragment -> {
|
||||
val blockExpression = codeFragment.getContentElement()
|
||||
val lastStatement = blockExpression.statements.lastOrNull() ?: return defaultReturnType
|
||||
val typeInfo = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, lastStatement]
|
||||
typeInfo?.type ?: defaultReturnType
|
||||
}
|
||||
else -> defaultReturnType
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDescriptorsForCodeFragment(
|
||||
declaration: KtCodeFragment,
|
||||
className: Name,
|
||||
methodName: Name,
|
||||
parameterInfo: CodeFragmentParameterInfo,
|
||||
returnType: KotlinType,
|
||||
packageFragmentDescriptor: PackageFragmentDescriptor
|
||||
): Pair<ClassDescriptor, FunctionDescriptor> {
|
||||
val classDescriptor = ClassDescriptorImpl(
|
||||
packageFragmentDescriptor, className, Modality.FINAL, ClassKind.OBJECT,
|
||||
emptyList(),
|
||||
KotlinSourceElement(declaration),
|
||||
false,
|
||||
LockBasedStorageManager.NO_LOCKS
|
||||
)
|
||||
|
||||
val methodDescriptor = SimpleFunctionDescriptorImpl.create(
|
||||
classDescriptor, Annotations.EMPTY, methodName,
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED, classDescriptor.source
|
||||
)
|
||||
|
||||
val parameters = parameterInfo.parameters.mapIndexed { index, parameter ->
|
||||
ValueParameterDescriptorImpl(
|
||||
methodDescriptor, null, index, Annotations.EMPTY, Name.identifier("p$index"),
|
||||
parameter.targetType,
|
||||
declaresDefaultValue = false,
|
||||
isCrossinline = false,
|
||||
isNoinline = false,
|
||||
varargElementType = null,
|
||||
source = SourceElement.NO_SOURCE
|
||||
)
|
||||
}
|
||||
|
||||
methodDescriptor.initialize(
|
||||
null, classDescriptor.thisAsReceiverParameter, emptyList(),
|
||||
parameters, returnType, Modality.FINAL, Visibilities.PUBLIC
|
||||
)
|
||||
|
||||
val memberScope = EvaluatorMemberScopeForMethod(methodDescriptor)
|
||||
|
||||
val constructor = ClassConstructorDescriptorImpl.create(classDescriptor, Annotations.EMPTY, true, classDescriptor.source)
|
||||
classDescriptor.initialize(memberScope, setOf(constructor), constructor)
|
||||
|
||||
return Pair(classDescriptor, methodDescriptor)
|
||||
}
|
||||
}
|
||||
|
||||
private class EvaluatorMemberScopeForMethod(private val methodDescriptor: SimpleFunctionDescriptor) : MemberScopeImpl() {
|
||||
override fun getContributedFunctions(name: Name, location: LookupLocation): Collection<SimpleFunctionDescriptor> {
|
||||
return if (name == methodDescriptor.name) {
|
||||
listOf(methodDescriptor)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getContributedDescriptors(
|
||||
kindFilter: DescriptorKindFilter,
|
||||
nameFilter: (Name) -> Boolean
|
||||
): Collection<DeclarationDescriptor> {
|
||||
return if (kindFilter.accepts(methodDescriptor) && nameFilter(methodDescriptor.name)) {
|
||||
listOf(methodDescriptor)
|
||||
} else {
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFunctionNames() = setOf(methodDescriptor.name)
|
||||
|
||||
override fun printScopeStructure(p: Printer) {
|
||||
p.println(this::class.java.simpleName)
|
||||
}
|
||||
}
|
||||
|
||||
private class EvaluatorModuleDescriptor(
|
||||
val codeFragment: KtCodeFragment,
|
||||
val moduleDescriptor: ModuleDescriptor,
|
||||
resolveSession: ResolveSession
|
||||
) : ModuleDescriptor by moduleDescriptor {
|
||||
private val declarationProvider = object : PackageMemberDeclarationProvider {
|
||||
override fun getPackageFiles() = listOf(codeFragment)
|
||||
override fun containsFile(file: KtFile) = file == codeFragment
|
||||
|
||||
override fun getDeclarationNames() = emptySet<Name>()
|
||||
override fun getDeclarations(kindFilter: DescriptorKindFilter, nameFilter: (Name) -> Boolean) = emptyList<KtDeclaration>()
|
||||
override fun getClassOrObjectDeclarations(name: Name) = emptyList<KtClassOrObjectInfo<*>>()
|
||||
override fun getAllDeclaredSubPackages(nameFilter: (Name) -> Boolean) = emptyList<FqName>()
|
||||
override fun getFunctionDeclarations(name: Name) = emptyList<KtNamedFunction>()
|
||||
override fun getPropertyDeclarations(name: Name) = emptyList<KtProperty>()
|
||||
override fun getTypeAliasDeclarations(name: Name) = emptyList<KtTypeAlias>()
|
||||
override fun getDestructuringDeclarationsEntries(name: Name) = emptyList<KtDestructuringDeclarationEntry>()
|
||||
override fun getScriptDeclarations(name: Name) = emptyList<KtScriptInfo>()
|
||||
}
|
||||
|
||||
val packageFragmentForEvaluator = LazyPackageDescriptor(this, FqName.ROOT, resolveSession, declarationProvider)
|
||||
|
||||
override fun getPackage(fqName: FqName): PackageViewDescriptor {
|
||||
val originalPackageDescriptor = moduleDescriptor.getPackage(fqName)
|
||||
if (fqName != FqName.ROOT) {
|
||||
return originalPackageDescriptor
|
||||
}
|
||||
|
||||
return object : DeclarationDescriptorImpl(Annotations.EMPTY, fqName.shortNameOrSpecial()), PackageViewDescriptor {
|
||||
override fun getContainingDeclaration() = originalPackageDescriptor.containingDeclaration
|
||||
|
||||
override val fqName get() = originalPackageDescriptor.fqName
|
||||
override val module get() = this@EvaluatorModuleDescriptor
|
||||
|
||||
override val memberScope by lazy {
|
||||
if (fragments.isEmpty()) {
|
||||
MemberScope.Empty
|
||||
} else {
|
||||
val scopes = fragments.map { it.getMemberScope() } + SubpackagesScope(module, fqName)
|
||||
ChainedMemberScope("package view scope for $fqName in ${module.name}", scopes)
|
||||
}
|
||||
}
|
||||
|
||||
override val fragments by lazy { originalPackageDescriptor.fragments + packageFragmentForEvaluator }
|
||||
|
||||
override fun <R, D> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D): R {
|
||||
return visitor.visitPackageViewDescriptor(this, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val OutputFile.internalClassName: String
|
||||
get() = relativePath.removeSuffix(".class").replace('/', '.')
|
||||
-403
@@ -1,403 +0,0 @@
|
||||
/*
|
||||
* 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.evaluate.compilation
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.CodeFragmentCodegenInfo
|
||||
import org.jetbrains.kotlin.codegen.getCallLabelForLambdaArgument
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.impl.LocalVariableDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.impl.SyntheticFieldDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DebuggerFieldPropertyDescriptor
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinCodeFragmentFactory.Companion.FAKE_JAVA_CONTEXT_FUNCTION_NAME
|
||||
import org.jetbrains.kotlin.idea.debugger.safeLocation
|
||||
import org.jetbrains.kotlin.idea.debugger.safeMethod
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import org.jetbrains.kotlin.psi.psiUtil.*
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.checkers.COROUTINE_CONTEXT_1_3_FQ_NAME
|
||||
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitReceiver
|
||||
import org.jetbrains.kotlin.resolve.source.getPsi
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
interface CodeFragmentParameter {
|
||||
val kind: Kind
|
||||
val name: String
|
||||
val debugString: String
|
||||
|
||||
enum class Kind {
|
||||
ORDINARY, DELEGATED, EXTENSION_RECEIVER, DISPATCH_RECEIVER, COROUTINE_CONTEXT, LOCAL_FUNCTION,
|
||||
FAKE_JAVA_OUTER_CLASS, FIELD_VAR, DEBUG_LABEL
|
||||
}
|
||||
|
||||
class Smart(
|
||||
val dumb: Dumb,
|
||||
override val targetType: KotlinType,
|
||||
override val targetDescriptor: DeclarationDescriptor
|
||||
) : CodeFragmentParameter by dumb, CodeFragmentCodegenInfo.IParameter
|
||||
|
||||
data class Dumb(
|
||||
override val kind: Kind,
|
||||
override val name: String,
|
||||
override val debugString: String = name
|
||||
) : CodeFragmentParameter
|
||||
}
|
||||
|
||||
class CodeFragmentParameterInfo(
|
||||
val parameters: List<Smart>,
|
||||
val crossingBounds: Set<Dumb>
|
||||
)
|
||||
|
||||
/*
|
||||
The purpose of this class is to figure out what parameters the received code fragment captures.
|
||||
It handles both directly mentioned names such as local variables or parameters and implicit values (dispatch/extension receivers).
|
||||
*/
|
||||
class CodeFragmentParameterAnalyzer(
|
||||
private val context: ExecutionContext,
|
||||
private val codeFragment: KtCodeFragment,
|
||||
private val bindingContext: BindingContext
|
||||
) {
|
||||
private val parameters = LinkedHashMap<DeclarationDescriptor, Smart>()
|
||||
private val crossingBounds = mutableSetOf<Dumb>()
|
||||
|
||||
private val onceUsedChecker = OnceUsedChecker(CodeFragmentParameterAnalyzer::class.java)
|
||||
|
||||
private val containingPrimaryConstructor: ConstructorDescriptor? by lazy {
|
||||
context.frameProxy.safeLocation()?.safeMethod()?.takeIf { it.isConstructor } ?: return@lazy null
|
||||
val constructor = codeFragment.context?.getParentOfType<KtPrimaryConstructor>(false) ?: return@lazy null
|
||||
bindingContext[BindingContext.CONSTRUCTOR, constructor]
|
||||
}
|
||||
|
||||
fun analyze(): CodeFragmentParameterInfo {
|
||||
onceUsedChecker.trigger()
|
||||
|
||||
codeFragment.accept(object : KtTreeVisitor<Unit>() {
|
||||
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression, data: Unit?): Void? {
|
||||
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return null
|
||||
processResolvedCall(resolvedCall, expression)
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun processResolvedCall(resolvedCall: ResolvedCall<*>, expression: KtSimpleNameExpression) {
|
||||
// Capture dispatch receiver for the extension callable
|
||||
run {
|
||||
val descriptor = resolvedCall.resultingDescriptor
|
||||
val containingClass = descriptor?.containingDeclaration as? ClassDescriptor
|
||||
val extensionParameter = descriptor?.extensionReceiverParameter
|
||||
if (descriptor != null && descriptor !is DebuggerFieldPropertyDescriptor
|
||||
&& extensionParameter != null && containingClass != null
|
||||
) {
|
||||
if (containingClass.kind != ClassKind.OBJECT) {
|
||||
processDispatchReceiver(containingClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (runReadAction { expression.isDotSelector() }) {
|
||||
// The receiver expression is already captured for this reference
|
||||
return
|
||||
}
|
||||
|
||||
if (isCodeFragmentDeclaration(resolvedCall.resultingDescriptor)) {
|
||||
// The reference is from the code fragment we analyze, no need to capture
|
||||
return
|
||||
}
|
||||
|
||||
var processed = false
|
||||
|
||||
val extensionReceiver = resolvedCall.extensionReceiver
|
||||
if (extensionReceiver is ImplicitReceiver) {
|
||||
val descriptor = extensionReceiver.declarationDescriptor
|
||||
val parameter = processReceiver(extensionReceiver)
|
||||
checkBounds(descriptor, expression, parameter)
|
||||
processed = true
|
||||
}
|
||||
|
||||
val dispatchReceiver = resolvedCall.dispatchReceiver
|
||||
if (dispatchReceiver is ImplicitReceiver) {
|
||||
val descriptor = dispatchReceiver.declarationDescriptor
|
||||
val parameter = processReceiver(dispatchReceiver)
|
||||
if (parameter != null) {
|
||||
checkBounds(descriptor, expression, parameter)
|
||||
processed = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!processed && resolvedCall.resultingDescriptor is SyntheticFieldDescriptor) {
|
||||
val descriptor = resolvedCall.resultingDescriptor as SyntheticFieldDescriptor
|
||||
val parameter = processSyntheticFieldVariable(descriptor)
|
||||
if (parameter != null) {
|
||||
checkBounds(descriptor, expression, parameter)
|
||||
processed = true
|
||||
}
|
||||
}
|
||||
|
||||
// If a reference has receivers, we can calculate its value using them, no need to capture
|
||||
if (!processed) {
|
||||
if (resolvedCall is VariableAsFunctionResolvedCall) {
|
||||
processResolvedCall(resolvedCall.functionCall, expression)
|
||||
processResolvedCall(resolvedCall.variableCall, expression)
|
||||
} else {
|
||||
processDescriptor(resolvedCall.resultingDescriptor, expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun processDescriptor(descriptor: DeclarationDescriptor, expression: KtSimpleNameExpression) {
|
||||
val parameter = processDebugLabel(descriptor)
|
||||
?: processCoroutineContextCall(descriptor)
|
||||
?: processSimpleNameExpression(descriptor)
|
||||
checkBounds(descriptor, expression, parameter)
|
||||
}
|
||||
|
||||
override fun visitThisExpression(expression: KtThisExpression, data: Unit?): Void? {
|
||||
val instanceReference = runReadAction { expression.instanceReference }
|
||||
val target = bindingContext[BindingContext.REFERENCE_TARGET, instanceReference]
|
||||
|
||||
if (isCodeFragmentDeclaration(target)) {
|
||||
// The reference is from the code fragment we analyze, no need to capture
|
||||
return null
|
||||
}
|
||||
|
||||
val parameter = when (target) {
|
||||
is ClassDescriptor -> processDispatchReceiver(target)
|
||||
is CallableDescriptor -> {
|
||||
val type = bindingContext[BindingContext.EXPRESSION_TYPE_INFO, expression]?.type
|
||||
type?.let { processExtensionReceiver(target, type, expression.getLabelName()) }
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
if (parameter != null) {
|
||||
checkBounds(target, expression, parameter)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
override fun visitSuperExpression(expression: KtSuperExpression, data: Unit?): Void {
|
||||
throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'super' call expression is not supported")
|
||||
}
|
||||
}, Unit)
|
||||
|
||||
return CodeFragmentParameterInfo(parameters.values.toList(), crossingBounds)
|
||||
}
|
||||
|
||||
private fun processReceiver(receiver: ImplicitReceiver): Smart? {
|
||||
return when (receiver) {
|
||||
is ImplicitClassReceiver -> processDispatchReceiver(receiver.classDescriptor)
|
||||
is ExtensionReceiver -> processExtensionReceiver(receiver.declarationDescriptor, receiver.type, null)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun processDispatchReceiver(descriptor: ClassDescriptor): Smart? {
|
||||
if (descriptor.kind == ClassKind.OBJECT || containingPrimaryConstructor != null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val type = descriptor.defaultType
|
||||
return parameters.getOrPut(descriptor) {
|
||||
Smart(Dumb(Kind.DISPATCH_RECEIVER, "", AsmUtil.THIS + "@" + descriptor.name.asString()), type, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processExtensionReceiver(descriptor: CallableDescriptor, receiverType: KotlinType, label: String?): Smart? {
|
||||
if (isFakeFunctionForJavaContext(descriptor)) {
|
||||
return processFakeJavaCodeReceiver(descriptor)
|
||||
}
|
||||
|
||||
val actualLabel = label ?: getLabel(descriptor) ?: return null
|
||||
val receiverParameter = descriptor.extensionReceiverParameter ?: return null
|
||||
|
||||
return parameters.getOrPut(descriptor) {
|
||||
Smart(Dumb(Kind.EXTENSION_RECEIVER, actualLabel, AsmUtil.THIS + "@" + actualLabel), receiverType, receiverParameter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getLabel(callableDescriptor: CallableDescriptor): String? {
|
||||
val source = callableDescriptor.source.getPsi()
|
||||
|
||||
if (source is KtFunctionLiteral) {
|
||||
getCallLabelForLambdaArgument(source, bindingContext)?.let { return it }
|
||||
}
|
||||
|
||||
return callableDescriptor.name.takeIf { !it.isSpecial }?.asString()
|
||||
}
|
||||
|
||||
private fun isFakeFunctionForJavaContext(descriptor: CallableDescriptor): Boolean {
|
||||
return descriptor is FunctionDescriptor
|
||||
&& descriptor.name.asString() == FAKE_JAVA_CONTEXT_FUNCTION_NAME
|
||||
&& codeFragment.getCopyableUserData(KtCodeFragment.FAKE_CONTEXT_FOR_JAVA_FILE) != null
|
||||
}
|
||||
|
||||
private fun processFakeJavaCodeReceiver(descriptor: CallableDescriptor): Smart? {
|
||||
val receiverParameter = descriptor
|
||||
.takeIf { descriptor is FunctionDescriptor }
|
||||
?.extensionReceiverParameter
|
||||
?: return null
|
||||
|
||||
val label = FAKE_JAVA_CONTEXT_FUNCTION_NAME
|
||||
val type = receiverParameter.type
|
||||
return parameters.getOrPut(descriptor) {
|
||||
Smart(Dumb(Kind.FAKE_JAVA_OUTER_CLASS, label, AsmUtil.THIS), type, receiverParameter)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processSyntheticFieldVariable(descriptor: SyntheticFieldDescriptor): Smart? {
|
||||
val propertyDescriptor = descriptor.propertyDescriptor
|
||||
val fieldName = propertyDescriptor.name.asString()
|
||||
val type = propertyDescriptor.type
|
||||
return parameters.getOrPut(descriptor) {
|
||||
Smart(Dumb(Kind.FIELD_VAR, fieldName, "field"), type, descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun processSimpleNameExpression(target: DeclarationDescriptor): Smart? {
|
||||
if (target is ValueParameterDescriptor && target.isCrossinline) {
|
||||
throw EvaluateExceptionUtil.createEvaluateException("Evaluation of 'crossinline' lambdas is not supported")
|
||||
}
|
||||
|
||||
val isLocalTarget = (target as? DeclarationDescriptorWithVisibility)?.visibility == Visibilities.LOCAL
|
||||
|
||||
val isPrimaryConstructorParameter = !isLocalTarget
|
||||
&& target is PropertyDescriptor
|
||||
&& isContainingPrimaryConstructorParameter(target)
|
||||
|
||||
if (!isLocalTarget && !isPrimaryConstructorParameter) {
|
||||
return null
|
||||
}
|
||||
|
||||
return when (target) {
|
||||
is FunctionDescriptor -> {
|
||||
val type = SingleAbstractMethodUtils.getFunctionTypeForAbstractMethod(target, false)
|
||||
parameters.getOrPut(target) {
|
||||
Smart(Dumb(Kind.LOCAL_FUNCTION, target.name.asString()), type, target)
|
||||
}
|
||||
}
|
||||
is ValueDescriptor -> {
|
||||
parameters.getOrPut(target) {
|
||||
val type = target.type
|
||||
@Suppress("DEPRECATION")
|
||||
val kind = if (target is LocalVariableDescriptor && target.isDelegated) Kind.DELEGATED else Kind.ORDINARY
|
||||
Smart(Dumb(kind, target.name.asString()), type, target)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun isContainingPrimaryConstructorParameter(target: PropertyDescriptor): Boolean {
|
||||
val primaryConstructor = containingPrimaryConstructor ?: return false
|
||||
for (parameter in primaryConstructor.valueParameters) {
|
||||
val property = bindingContext[BindingContext.VALUE_PARAMETER_AS_PROPERTY, parameter]
|
||||
if (target == property) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun processCoroutineContextCall(target: DeclarationDescriptor): Smart? {
|
||||
if (target is PropertyDescriptor && target.fqNameSafe == COROUTINE_CONTEXT_1_3_FQ_NAME) {
|
||||
return parameters.getOrPut(target) {
|
||||
Smart(Dumb(Kind.COROUTINE_CONTEXT, ""), target.type, target)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun processDebugLabel(target: DeclarationDescriptor): Smart? {
|
||||
val debugLabelPropertyDescriptor = target as? DebugLabelPropertyDescriptor ?: return null
|
||||
val labelName = debugLabelPropertyDescriptor.labelName
|
||||
val debugString = debugLabelPropertyDescriptor.name.asString()
|
||||
|
||||
return parameters.getOrPut(target) {
|
||||
val type = debugLabelPropertyDescriptor.type
|
||||
Smart(Dumb(Kind.DEBUG_LABEL, labelName, debugString), type, debugLabelPropertyDescriptor)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkBounds(descriptor: DeclarationDescriptor?, expression: KtExpression, parameter: Smart?) {
|
||||
if (parameter == null || descriptor !is DeclarationDescriptorWithSource) {
|
||||
return
|
||||
}
|
||||
|
||||
val targetPsi = descriptor.source.getPsi()
|
||||
if (targetPsi != null && doesCrossInlineBounds(expression, targetPsi)) {
|
||||
crossingBounds += parameter.dumb
|
||||
}
|
||||
}
|
||||
|
||||
private fun doesCrossInlineBounds(expression: PsiElement, declaration: PsiElement): Boolean {
|
||||
val declarationParent = declaration.parent ?: return false
|
||||
var currentParent: PsiElement? = expression.parent?.takeIf { it.isInside(declarationParent) } ?: return false
|
||||
|
||||
while (currentParent != null && currentParent != declarationParent) {
|
||||
if (currentParent is KtFunction) {
|
||||
val functionDescriptor = bindingContext[BindingContext.FUNCTION, currentParent]
|
||||
if (functionDescriptor != null && !functionDescriptor.isInline) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
currentParent = when (currentParent) {
|
||||
is KtCodeFragment -> currentParent.context
|
||||
else -> currentParent.parent
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun isCodeFragmentDeclaration(descriptor: DeclarationDescriptor?): Boolean {
|
||||
if (descriptor is ValueParameterDescriptor && isCodeFragmentDeclaration(descriptor.containingDeclaration)) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (descriptor !is DeclarationDescriptorWithSource) {
|
||||
return false
|
||||
}
|
||||
|
||||
return descriptor.source.getPsi()?.containingFile is KtCodeFragment
|
||||
}
|
||||
|
||||
private tailrec fun PsiElement.isInside(parent: PsiElement): Boolean {
|
||||
if (parent.isAncestor(this)) {
|
||||
return true
|
||||
}
|
||||
|
||||
val context = (this.containingFile as? KtCodeFragment)?.context ?: return false
|
||||
return context.isInside(parent)
|
||||
}
|
||||
}
|
||||
|
||||
private class OnceUsedChecker(private val clazz: Class<*>) {
|
||||
private var used = false
|
||||
|
||||
fun trigger() {
|
||||
if (used) {
|
||||
error(clazz.name + " may be only used once")
|
||||
}
|
||||
|
||||
used = true
|
||||
}
|
||||
}
|
||||
-48
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* 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.evaluate.compilation
|
||||
|
||||
import com.intellij.debugger.SourcePosition
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||
|
||||
data class CompiledDataDescriptor(
|
||||
val classes: List<ClassToLoad>,
|
||||
val parameters: List<CodeFragmentParameter.Dumb>,
|
||||
val crossingBounds: Set<CodeFragmentParameter.Dumb>,
|
||||
val mainMethodSignature: CodeFragmentCompiler.MethodSignature,
|
||||
val sourcePosition: SourcePosition?
|
||||
) {
|
||||
companion object {
|
||||
fun from(result: CodeFragmentCompiler.CompilationResult, sourcePosition: SourcePosition?): CompiledDataDescriptor {
|
||||
val localFunctionSuffixes = result.localFunctionSuffixes
|
||||
|
||||
val dumbParameters = ArrayList<CodeFragmentParameter.Dumb>(result.parameterInfo.parameters.size)
|
||||
for (parameter in result.parameterInfo.parameters) {
|
||||
val dumb = parameter.dumb
|
||||
if (dumb.kind == CodeFragmentParameter.Kind.LOCAL_FUNCTION) {
|
||||
val suffix = localFunctionSuffixes[dumb]
|
||||
if (suffix != null) {
|
||||
dumbParameters += dumb.copy(name = dumb.name + suffix)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
dumbParameters += dumb
|
||||
}
|
||||
|
||||
return CompiledDataDescriptor(
|
||||
result.classes,
|
||||
dumbParameters,
|
||||
result.parameterInfo.crossingBounds,
|
||||
result.mainMethodSignature,
|
||||
sourcePosition
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val CompiledDataDescriptor.mainClass: ClassToLoad
|
||||
get() = classes.first { it.isMainClass }
|
||||
-221
@@ -1,221 +0,0 @@
|
||||
/*
|
||||
* 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.evaluate.compilation
|
||||
|
||||
import com.intellij.debugger.engine.DebugProcessImpl
|
||||
import com.intellij.debugger.ui.impl.watch.NodeDescriptorImpl
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.xdebugger.impl.XDebugSessionImpl
|
||||
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
|
||||
import org.jetbrains.kotlin.builtins.PrimitiveType
|
||||
import com.sun.jdi.*
|
||||
import com.sun.jdi.Type as JdiType
|
||||
import org.jetbrains.kotlin.backend.common.lower.SimpleMemberScope
|
||||
import org.jetbrains.kotlin.builtins.DefaultBuiltIns
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyDescriptorImpl
|
||||
import org.jetbrains.kotlin.descriptors.impl.PropertyGetterDescriptorImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.getClassDescriptor
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.psi.KtCodeFragment
|
||||
import org.jetbrains.kotlin.psi.externalDescriptors
|
||||
import org.jetbrains.kotlin.platform.TargetPlatform
|
||||
import org.jetbrains.kotlin.platform.jvm.JvmPlatforms
|
||||
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
|
||||
class DebugLabelPropertyDescriptorProvider(val codeFragment: KtCodeFragment, val debugProcess: DebugProcessImpl) {
|
||||
companion object {
|
||||
fun getMarkupMap(debugProcess: DebugProcessImpl) = doGetMarkupMap(debugProcess) ?: emptyMap()
|
||||
|
||||
private fun doGetMarkupMap(debugProcess: DebugProcessImpl): Map<out Value?, ValueMarkup>? {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
return NodeDescriptorImpl.getMarkupMap(debugProcess)
|
||||
}
|
||||
|
||||
val debugSession = debugProcess.session.xDebugSession as? XDebugSessionImpl
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
return debugSession?.valueMarkers?.allMarkers?.filterKeys { it is Value? } as Map<out Value?, ValueMarkup>?
|
||||
}
|
||||
}
|
||||
|
||||
private val moduleDescriptor = DebugLabelModuleDescriptor
|
||||
|
||||
fun supplyDebugLabels() {
|
||||
val packageFragment = object : PackageFragmentDescriptorImpl(moduleDescriptor, FqName.ROOT) {
|
||||
val properties = createDebugLabelDescriptors(this)
|
||||
override fun getMemberScope() = SimpleMemberScope(properties)
|
||||
}
|
||||
|
||||
codeFragment.externalDescriptors = packageFragment.properties
|
||||
}
|
||||
|
||||
private fun createDebugLabelDescriptors(containingDeclaration: PackageFragmentDescriptor): List<PropertyDescriptor> {
|
||||
val markupMap = getMarkupMap(debugProcess)
|
||||
|
||||
val result = ArrayList<PropertyDescriptor>(markupMap.size)
|
||||
|
||||
nextValue@ for ((value, markup) in markupMap) {
|
||||
val labelName = markup.text
|
||||
val kotlinType = value?.type()?.let { convertType(it) } ?: moduleDescriptor.builtIns.nullableAnyType
|
||||
result += createDebugLabelDescriptor(labelName, kotlinType, containingDeclaration)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun createDebugLabelDescriptor(
|
||||
labelName: String,
|
||||
type: KotlinType,
|
||||
containingDeclaration: PackageFragmentDescriptor
|
||||
): PropertyDescriptor {
|
||||
val propertyDescriptor = DebugLabelPropertyDescriptor(containingDeclaration, labelName)
|
||||
propertyDescriptor.setType(type, emptyList(), null, null)
|
||||
|
||||
val getterDescriptor = PropertyGetterDescriptorImpl(
|
||||
propertyDescriptor,
|
||||
Annotations.EMPTY,
|
||||
Modality.FINAL,
|
||||
Visibilities.PUBLIC,
|
||||
/* isDefault = */ false,
|
||||
/* isExternal = */ false,
|
||||
/* isInline = */ false,
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||
/* original = */ null,
|
||||
SourceElement.NO_SOURCE
|
||||
).apply { initialize(type) }
|
||||
|
||||
propertyDescriptor.initialize(getterDescriptor, null)
|
||||
return propertyDescriptor
|
||||
}
|
||||
|
||||
private fun convertType(type: JdiType): KotlinType {
|
||||
val builtIns = moduleDescriptor.builtIns
|
||||
|
||||
return when (type) {
|
||||
is VoidType -> builtIns.unitType
|
||||
is LongType -> builtIns.longType
|
||||
is DoubleType -> builtIns.doubleType
|
||||
is CharType -> builtIns.charType
|
||||
is FloatType -> builtIns.floatType
|
||||
is ByteType -> builtIns.byteType
|
||||
is IntegerType -> builtIns.intType
|
||||
is BooleanType -> builtIns.booleanType
|
||||
is ShortType -> builtIns.shortType
|
||||
is ArrayType -> {
|
||||
when (val componentType = type.componentType()) {
|
||||
is VoidType -> builtIns.getArrayType(Variance.INVARIANT, builtIns.unitType)
|
||||
is LongType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.LONG)
|
||||
is DoubleType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.DOUBLE)
|
||||
is CharType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.CHAR)
|
||||
is FloatType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.FLOAT)
|
||||
is ByteType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.BYTE)
|
||||
is IntegerType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.INT)
|
||||
is BooleanType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.BOOLEAN)
|
||||
is ShortType -> builtIns.getPrimitiveArrayKotlinType(PrimitiveType.SHORT)
|
||||
else -> builtIns.getArrayType(Variance.INVARIANT, convertReferenceType(componentType))
|
||||
}
|
||||
}
|
||||
is ReferenceType -> convertReferenceType(type)
|
||||
else -> builtIns.anyType
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertReferenceType(type: JdiType): KotlinType {
|
||||
require(type is ClassType || type is InterfaceType)
|
||||
|
||||
val asmType = AsmType.getType(type.signature())
|
||||
val project = codeFragment.project
|
||||
val classDescriptor = asmType.getClassDescriptor(GlobalSearchScope.allScope(project), mapBuiltIns = false)
|
||||
?: return moduleDescriptor.builtIns.nullableAnyType
|
||||
return classDescriptor.defaultType
|
||||
}
|
||||
}
|
||||
|
||||
private object DebugLabelModuleDescriptor
|
||||
: DeclarationDescriptorImpl(Annotations.EMPTY, Name.identifier("DebugLabelExtensions")),
|
||||
ModuleDescriptor
|
||||
{
|
||||
override val builtIns: KotlinBuiltIns
|
||||
get() = DefaultBuiltIns.Instance
|
||||
|
||||
override val stableName: Name?
|
||||
get() = name
|
||||
|
||||
override fun shouldSeeInternalsOf(targetModule: ModuleDescriptor) = false
|
||||
|
||||
override fun getPackage(fqName: FqName): PackageViewDescriptor {
|
||||
return object : PackageViewDescriptor, DeclarationDescriptorImpl(Annotations.EMPTY, FqName.ROOT.shortNameOrSpecial()) {
|
||||
override fun getContainingDeclaration(): PackageViewDescriptor? = null
|
||||
|
||||
override val fqName: FqName
|
||||
get() = FqName.ROOT
|
||||
|
||||
override val memberScope: MemberScope
|
||||
get() = MemberScope.Empty
|
||||
|
||||
override val module: ModuleDescriptor
|
||||
get() = this@DebugLabelModuleDescriptor
|
||||
|
||||
override val fragments: List<PackageFragmentDescriptor>
|
||||
get() = emptyList()
|
||||
|
||||
override fun <R : Any?, D : Any?> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D): R {
|
||||
return visitor.visitPackageViewDescriptor(this, data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val platform: TargetPlatform?
|
||||
get() = JvmPlatforms.unspecifiedJvmPlatform
|
||||
|
||||
override fun getSubPackagesOf(fqName: FqName, nameFilter: (Name) -> Boolean): Collection<FqName> {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override val allDependencyModules: List<ModuleDescriptor>
|
||||
get() = emptyList()
|
||||
|
||||
override val expectedByModules: List<ModuleDescriptor>
|
||||
get() = emptyList()
|
||||
|
||||
override fun <T> getCapability(capability: ModuleDescriptor.Capability<T>): T? = null
|
||||
|
||||
override val isValid: Boolean
|
||||
get() = true
|
||||
|
||||
override fun assertValid() {}
|
||||
}
|
||||
|
||||
internal class DebugLabelPropertyDescriptor(
|
||||
containingDeclaration: DeclarationDescriptor,
|
||||
val labelName: String
|
||||
) : PropertyDescriptorImpl(
|
||||
containingDeclaration,
|
||||
null,
|
||||
Annotations.EMPTY,
|
||||
Modality.FINAL,
|
||||
Visibilities.PUBLIC,
|
||||
/*isVar = */false,
|
||||
Name.identifier(labelName + "_DebugLabel"),
|
||||
CallableMemberDescriptor.Kind.SYNTHESIZED,
|
||||
SourceElement.NO_SOURCE,
|
||||
/*lateInit = */false,
|
||||
/*isConst = */false,
|
||||
/*isExpect = */false,
|
||||
/*isActual = */false,
|
||||
/*isExternal = */false,
|
||||
/*isDelegated = */false
|
||||
)
|
||||
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.sun.jdi.ClassLoaderReference
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassLoadingAdapter
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||
|
||||
fun loadClassesSafely(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
return try {
|
||||
loadClasses(context, classes)
|
||||
} catch (e: EvaluateException) {
|
||||
throw e
|
||||
} catch (e: Throwable) {
|
||||
LOG.debug("Failed to evaluate expression", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun loadClasses(context: ExecutionContext, classes: Collection<ClassToLoad>): ClassLoaderReference? {
|
||||
if (classes.isEmpty()) {
|
||||
return null
|
||||
}
|
||||
|
||||
return ClassLoadingAdapter.loadClasses(context, classes)
|
||||
}
|
||||
-264
@@ -1,264 +0,0 @@
|
||||
/*
|
||||
* 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.evaluate.variables
|
||||
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.fileClasses.internalNameWithoutInnerClasses
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.variables.VariableFinder.Result
|
||||
import org.jetbrains.kotlin.idea.debugger.isSubtype
|
||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
import com.sun.jdi.Type as JdiType
|
||||
import kotlin.jvm.internal.Ref
|
||||
|
||||
@Suppress("SpellCheckingInspection")
|
||||
class EvaluatorValueConverter(private val context: ExecutionContext) {
|
||||
private companion object {
|
||||
private val UNBOXING_METHOD_NAMES = mapOf(
|
||||
"java/lang/Boolean" to "booleanValue",
|
||||
"java/lang/Character" to "charValue",
|
||||
"java/lang/Byte" to "byteValue",
|
||||
"java/lang/Short" to "shortValue",
|
||||
"java/lang/Integer" to "intValue",
|
||||
"java/lang/Float" to "floatValue",
|
||||
"java/lang/Long" to "longValue",
|
||||
"java/lang/Double" to "doubleValue"
|
||||
)
|
||||
}
|
||||
|
||||
// Nearly accurate: doesn't do deep checks for Ref wrappers. Use `coerce()` for more precise check.
|
||||
fun typeMatches(requestedType: AsmType, actualTypeObj: JdiType?): Boolean {
|
||||
if (actualTypeObj == null) return true
|
||||
|
||||
// Main path
|
||||
if (requestedType.descriptor == "Ljava/lang/Object;" || actualTypeObj.isSubtype(requestedType)) {
|
||||
return true
|
||||
}
|
||||
|
||||
val actualType = actualTypeObj.asmType()
|
||||
|
||||
fun isRefWrapper(wrapperType: AsmType, objType: AsmType): Boolean {
|
||||
return !objType.isPrimitiveType && wrapperType.className == Ref.ObjectRef::class.java.name
|
||||
}
|
||||
|
||||
if (isRefWrapper(actualType, requestedType) || isRefWrapper(requestedType, actualType)) {
|
||||
return true
|
||||
}
|
||||
|
||||
val unwrappedActualType = unwrap(actualType)
|
||||
val unwrappedRequestedType = unwrap(requestedType)
|
||||
return unwrappedActualType == unwrappedRequestedType
|
||||
}
|
||||
|
||||
fun coerce(value: Value?, type: AsmType): Result? {
|
||||
val unrefResult = coerceRef(value, type) ?: return null
|
||||
return coerceBoxing(unrefResult.value, type)
|
||||
}
|
||||
|
||||
private fun coerceRef(value: Value?, type: AsmType): Result? {
|
||||
when {
|
||||
type.isRefType -> {
|
||||
if (value != null && value.asmType().isRefType) {
|
||||
return Result(value)
|
||||
}
|
||||
|
||||
return Result(ref(value))
|
||||
}
|
||||
value != null && value.asmType().isRefType -> {
|
||||
if (type.isRefType) {
|
||||
return Result(value)
|
||||
}
|
||||
|
||||
return Result(unref(value))
|
||||
}
|
||||
else -> return Result(value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun coerceBoxing(value: Value?, type: AsmType): Result? {
|
||||
when {
|
||||
value == null -> return Result(value)
|
||||
type == AsmType.VOID_TYPE -> return Result(context.vm.mirrorOfVoid())
|
||||
type.isBoxedType -> {
|
||||
if (value.asmType().isBoxedType) {
|
||||
return Result(value)
|
||||
}
|
||||
|
||||
if (value !is PrimitiveValue) {
|
||||
return null
|
||||
}
|
||||
|
||||
return Result(box(value))
|
||||
}
|
||||
type.isPrimitiveType -> {
|
||||
if (value is PrimitiveValue) {
|
||||
return Result(value)
|
||||
}
|
||||
|
||||
if (value !is ObjectReference || !value.asmType().isBoxedType) {
|
||||
return null
|
||||
}
|
||||
|
||||
return Result(unbox(value))
|
||||
}
|
||||
value is PrimitiveValue -> {
|
||||
if (type.sort != AsmType.OBJECT) {
|
||||
return null
|
||||
}
|
||||
|
||||
val boxedValue = box(value)
|
||||
if (!typeMatches(type, boxedValue?.type())) {
|
||||
return null
|
||||
}
|
||||
|
||||
return Result(boxedValue)
|
||||
}
|
||||
else -> return Result(value)
|
||||
}
|
||||
}
|
||||
|
||||
private fun box(value: Value?): Value? {
|
||||
if (value !is PrimitiveValue) {
|
||||
return value
|
||||
}
|
||||
|
||||
val unboxedType = value.asmType()
|
||||
val boxedType = box(unboxedType)
|
||||
|
||||
val boxedTypeClass = (context.findClass(boxedType) as ClassType?)
|
||||
?: error("Class $boxedType is not loaded")
|
||||
|
||||
val methodDesc = AsmType.getMethodDescriptor(boxedType, unboxedType)
|
||||
val valueOfMethod = boxedTypeClass.methodsByName("valueOf", methodDesc).first()
|
||||
|
||||
return context.invokeMethod(boxedTypeClass, valueOfMethod, listOf(value))
|
||||
}
|
||||
|
||||
private fun unbox(value: Value?): Value? {
|
||||
if (value !is ObjectReference) {
|
||||
return value
|
||||
}
|
||||
|
||||
val boxedTypeClass = value.referenceType() as? ClassType ?: return value
|
||||
val boxedType = boxedTypeClass.asmType().takeIf { it.isBoxedType } ?: return value
|
||||
val unboxedType = unbox(boxedType)
|
||||
|
||||
val unboxingMethodName = UNBOXING_METHOD_NAMES.getValue(boxedType.internalName)
|
||||
val methodDesc = AsmType.getMethodDescriptor(unboxedType)
|
||||
val valueMethod = boxedTypeClass.methodsByName(unboxingMethodName, methodDesc).first()
|
||||
return context.invokeMethod(value, valueMethod, emptyList())
|
||||
}
|
||||
|
||||
private fun ref(value: Value?): Value? {
|
||||
if (value is VoidValue) {
|
||||
return value
|
||||
}
|
||||
|
||||
fun wrapRef(value: Value?, refTypeClass: ClassType): Value? {
|
||||
val constructor = refTypeClass.methods().single { it.isConstructor }
|
||||
val ref = context.newInstance(refTypeClass, constructor, emptyList())
|
||||
context.keepReference(ref)
|
||||
|
||||
val elementField = refTypeClass.fieldByName("element") ?: error("'element' field not found")
|
||||
ref.setValue(elementField, value)
|
||||
return ref
|
||||
}
|
||||
|
||||
if (value is PrimitiveValue) {
|
||||
val primitiveType = value.asmType()
|
||||
val refType = PRIMITIVE_TO_REF.getValue(primitiveType)
|
||||
|
||||
val refTypeClass = (context.findClass(refType) as ClassType?)
|
||||
?: error("Class $refType is not loaded")
|
||||
|
||||
return wrapRef(value, refTypeClass)
|
||||
} else {
|
||||
val refType = AsmType.getType(Ref.ObjectRef::class.java)
|
||||
val refTypeClass = (context.findClass(refType) as ClassType?)
|
||||
?: error("Class $refType is not loaded")
|
||||
|
||||
return wrapRef(value, refTypeClass)
|
||||
}
|
||||
}
|
||||
|
||||
fun unref(value: Value?): Value? {
|
||||
if (value !is ObjectReference) {
|
||||
return value
|
||||
}
|
||||
|
||||
val type = value.type()
|
||||
if (type !is ClassType || !type.signature().startsWith("L" + AsmTypes.REF_TYPE_PREFIX)) {
|
||||
return value
|
||||
}
|
||||
|
||||
val field = type.fieldByName("element") ?: return value
|
||||
return value.getValue(field)
|
||||
}
|
||||
}
|
||||
|
||||
private fun unbox(type: AsmType): AsmType {
|
||||
if (type.sort == AsmType.OBJECT) {
|
||||
return BOXED_TO_PRIMITIVE[type] ?: type
|
||||
}
|
||||
|
||||
return type
|
||||
}
|
||||
|
||||
private fun box(type: AsmType): AsmType {
|
||||
if (type.isPrimitiveType) {
|
||||
return PRIMITIVE_TO_BOXED[type] ?: type
|
||||
}
|
||||
|
||||
return type
|
||||
}
|
||||
|
||||
private fun unwrap(type: AsmType): AsmType {
|
||||
if (type.sort != AsmType.OBJECT) {
|
||||
return type
|
||||
}
|
||||
|
||||
return REF_TO_PRIMITIVE[type] ?: BOXED_TO_PRIMITIVE[type] ?: type
|
||||
}
|
||||
|
||||
private val AsmType.isPrimitiveType: Boolean
|
||||
get() = sort != AsmType.OBJECT && sort != AsmType.ARRAY
|
||||
|
||||
private val AsmType.isRefType: Boolean
|
||||
get() = sort == AsmType.OBJECT && this in REF_TYPES
|
||||
|
||||
private val AsmType.isBoxedType: Boolean
|
||||
get() = this in BOXED_TO_PRIMITIVE
|
||||
|
||||
private fun Value.asmType(): AsmType {
|
||||
return type().asmType()
|
||||
}
|
||||
|
||||
private fun JdiType.asmType(): AsmType {
|
||||
return AsmType.getType(signature())
|
||||
}
|
||||
|
||||
private val BOXED_TO_PRIMITIVE: Map<AsmType, AsmType> = JvmPrimitiveType.values()
|
||||
.map { Pair(AsmType.getObjectType(it.wrapperFqName.internalNameWithoutInnerClasses), AsmType.getType(it.desc)) }
|
||||
.toMap()
|
||||
|
||||
private val PRIMITIVE_TO_BOXED: Map<AsmType, AsmType> = BOXED_TO_PRIMITIVE.map { (k, v) -> Pair(v, k) }.toMap()
|
||||
|
||||
private val REF_TO_PRIMITIVE = mapOf(
|
||||
Ref.ByteRef::class.java.name to AsmType.BYTE_TYPE,
|
||||
Ref.ShortRef::class.java.name to AsmType.SHORT_TYPE,
|
||||
Ref.IntRef::class.java.name to AsmType.INT_TYPE,
|
||||
Ref.LongRef::class.java.name to AsmType.LONG_TYPE,
|
||||
Ref.FloatRef::class.java.name to AsmType.FLOAT_TYPE,
|
||||
Ref.DoubleRef::class.java.name to AsmType.DOUBLE_TYPE,
|
||||
Ref.CharRef::class.java.name to AsmType.CHAR_TYPE,
|
||||
Ref.BooleanRef::class.java.name to AsmType.BOOLEAN_TYPE
|
||||
).mapKeys { (k, _) -> AsmType.getObjectType(k.replace('.', '/')) }
|
||||
|
||||
private val PRIMITIVE_TO_REF: Map<AsmType, AsmType> = REF_TO_PRIMITIVE.map { (k, v) -> Pair(v, k) }.toMap()
|
||||
|
||||
private val REF_TYPES: Set<AsmType> = REF_TO_PRIMITIVE.keys + AsmType.getType(Ref.ObjectRef::class.java)
|
||||
-515
@@ -1,515 +0,0 @@
|
||||
/*
|
||||
* 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.evaluate.variables
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil
|
||||
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.intellij.openapi.diagnostic.Attachment
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil.getCapturedFieldName
|
||||
import org.jetbrains.kotlin.codegen.AsmUtil.getLabeledThisName
|
||||
import org.jetbrains.kotlin.codegen.coroutines.CONTINUATION_VARIABLE_NAME
|
||||
import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
|
||||
import org.jetbrains.kotlin.codegen.inline.INLINE_TRANSFORMATION_SUFFIX
|
||||
import org.jetbrains.kotlin.idea.debugger.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.ExecutionContext
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.CodeFragmentParameter.*
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.compilation.DebugLabelPropertyDescriptorProvider
|
||||
import org.jetbrains.kotlin.idea.util.application.runReadAction
|
||||
import org.jetbrains.kotlin.idea.util.attachment.mergeAttachments
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION
|
||||
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
|
||||
import kotlin.coroutines.Continuation
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
import com.sun.jdi.Type as JdiType
|
||||
|
||||
class VariableFinder(private val context: ExecutionContext) {
|
||||
private val frameProxy = context.frameProxy
|
||||
|
||||
companion object {
|
||||
private const val USE_UNSAFE_FALLBACK = true
|
||||
|
||||
val CONTINUATION_TYPE: AsmType = AsmType.getType(Continuation::class.java)
|
||||
|
||||
val SUSPEND_LAMBDA_CLASSES = listOf(
|
||||
"kotlin.coroutines.jvm.internal.SuspendLambda",
|
||||
"kotlin.coroutines.jvm.internal.RestrictedSuspendLambda"
|
||||
)
|
||||
|
||||
fun variableNotFound(context: ExecutionContext, message: String): Exception {
|
||||
val frameProxy = context.frameProxy
|
||||
val location = frameProxy.safeLocation()
|
||||
val scope = context.debugProcess.searchScope
|
||||
|
||||
val locationText = location?.run { "Location: ${sourceName()}:${lineNumber()}" } ?: "No location available"
|
||||
|
||||
val sourceName = location?.sourceName()
|
||||
val declaringTypeName = location?.declaringType()?.name()?.replace('.', '/')?.let { JvmClassName.byInternalName(it) }
|
||||
|
||||
val sourceFile = if (sourceName != null && declaringTypeName != null) {
|
||||
DebuggerUtils.findSourceFileForClassIncludeLibrarySources(context.project, scope, declaringTypeName, sourceName, location)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val sourceFileText = runReadAction { sourceFile?.text }
|
||||
|
||||
if (sourceName != null && sourceFileText != null) {
|
||||
val attachments = mergeAttachments(
|
||||
Attachment(sourceName, sourceFileText),
|
||||
Attachment("location.txt", locationText)
|
||||
)
|
||||
|
||||
LOG.error(message, attachments)
|
||||
}
|
||||
|
||||
return EvaluateExceptionUtil.createEvaluateException(message)
|
||||
}
|
||||
|
||||
val inlinedThisRegex = getLocalVariableNameRegexInlineAware(AsmUtil.INLINE_DECLARATION_SITE_THIS)
|
||||
|
||||
private fun getCapturedVariableNameRegex(capturedName: String): Regex {
|
||||
val escapedName = Regex.escape(capturedName)
|
||||
val escapedSuffix = Regex.escape(INLINE_TRANSFORMATION_SUFFIX)
|
||||
return Regex("^$escapedName(?:$escapedSuffix)?$")
|
||||
}
|
||||
|
||||
private fun getLocalVariableNameRegexInlineAware(name: String): Regex {
|
||||
val escapedName = Regex.escape(name)
|
||||
val escapedSuffix = Regex.escape(INLINE_FUN_VAR_SUFFIX)
|
||||
return Regex("^$escapedName(?:$escapedSuffix)*$")
|
||||
}
|
||||
|
||||
fun getInlineDepth(variables: List<LocalVariableProxyImpl>): Int {
|
||||
val inlineFunVariables = variables
|
||||
.filter { it.name().startsWith(LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) }
|
||||
|
||||
if (inlineFunVariables.isEmpty()) {
|
||||
return 0
|
||||
}
|
||||
|
||||
val closestInlineFun = inlineFunVariables.maxBy { it.variable }!!.variable
|
||||
val inlineLambdaDepth = variables
|
||||
.count { it.name().startsWith(LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) && it.variable > closestInlineFun }
|
||||
|
||||
return maxOf(0, inlineFunVariables.size - inlineLambdaDepth)
|
||||
}
|
||||
|
||||
fun getInlineDepth(variableName: String): Int {
|
||||
var endIndex = variableName.length
|
||||
var depth = 0
|
||||
|
||||
val suffixLen = INLINE_FUN_VAR_SUFFIX.length
|
||||
while (endIndex >= suffixLen) {
|
||||
if (variableName.substring(endIndex - suffixLen, endIndex) != INLINE_FUN_VAR_SUFFIX) {
|
||||
break
|
||||
}
|
||||
|
||||
depth++
|
||||
endIndex -= suffixLen
|
||||
}
|
||||
|
||||
return depth
|
||||
}
|
||||
}
|
||||
|
||||
private val evaluatorValueConverter = EvaluatorValueConverter(context)
|
||||
|
||||
sealed class VariableKind(val asmType: AsmType) {
|
||||
abstract fun capturedNameMatches(name: String): Boolean
|
||||
|
||||
class Ordinary(val name: String, asmType: AsmType, val isDelegated: Boolean) : VariableKind(asmType) {
|
||||
private val capturedNameRegex = getCapturedVariableNameRegex(getCapturedFieldName(this.name))
|
||||
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name)
|
||||
}
|
||||
|
||||
// TODO Support overloaded local functions
|
||||
class LocalFunction(val name: String, asmType: AsmType) : VariableKind(asmType) {
|
||||
@Suppress("ConvertToStringTemplate")
|
||||
override fun capturedNameMatches(name: String) = name == "$" + name
|
||||
}
|
||||
|
||||
class UnlabeledThis(asmType: AsmType) : VariableKind(asmType) {
|
||||
override fun capturedNameMatches(name: String) =
|
||||
(name == AsmUtil.CAPTURED_RECEIVER_FIELD || name.startsWith(getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD)))
|
||||
}
|
||||
|
||||
class OuterClassThis(asmType: AsmType) : VariableKind(asmType) {
|
||||
override fun capturedNameMatches(name: String) = false
|
||||
}
|
||||
|
||||
class FieldVar(val fieldName: String, asmType: AsmType) : VariableKind(asmType) {
|
||||
// Captured 'field' are not supported yet
|
||||
override fun capturedNameMatches(name: String) = false
|
||||
}
|
||||
|
||||
class ExtensionThis(val label: String, asmType: AsmType) : VariableKind(asmType) {
|
||||
val parameterName = getLabeledThisName(label, AsmUtil.LABELED_THIS_PARAMETER, AsmUtil.RECEIVER_PARAMETER_NAME)
|
||||
val fieldName = getLabeledThisName(label, getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD), AsmUtil.CAPTURED_RECEIVER_FIELD)
|
||||
|
||||
private val capturedNameRegex = getCapturedVariableNameRegex(fieldName)
|
||||
override fun capturedNameMatches(name: String) = capturedNameRegex.matches(name)
|
||||
}
|
||||
}
|
||||
|
||||
class Result(val value: Value?)
|
||||
|
||||
private class NamedEntity(val name: String, val type: JdiType?, val value: () -> Value?) {
|
||||
companion object {
|
||||
fun of(field: Field, owner: ObjectReference): NamedEntity {
|
||||
return NamedEntity(field.name(), field.safeType()) { owner.getValue(field) }
|
||||
}
|
||||
|
||||
fun of(variable: LocalVariableProxyImpl, frameProxy: StackFrameProxyImpl): NamedEntity {
|
||||
return NamedEntity(variable.name(), variable.safeType()) { frameProxy.getValue(variable) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun find(parameter: CodeFragmentParameter, asmType: AsmType): Result? {
|
||||
return when (parameter.kind) {
|
||||
Kind.ORDINARY -> findOrdinary(VariableKind.Ordinary(parameter.name, asmType, isDelegated = false))
|
||||
Kind.DELEGATED -> findOrdinary(VariableKind.Ordinary(parameter.name, asmType, isDelegated = true))
|
||||
Kind.FAKE_JAVA_OUTER_CLASS -> thisObject()?.let { Result(it) }
|
||||
Kind.EXTENSION_RECEIVER -> findExtensionThis(VariableKind.ExtensionThis(parameter.name, asmType))
|
||||
Kind.LOCAL_FUNCTION -> findLocalFunction(VariableKind.LocalFunction(parameter.name, asmType))
|
||||
Kind.DISPATCH_RECEIVER -> findDispatchThis(VariableKind.OuterClassThis(asmType))
|
||||
Kind.COROUTINE_CONTEXT -> findCoroutineContext()
|
||||
Kind.FIELD_VAR -> findFieldVariable(VariableKind.FieldVar(parameter.name, asmType))
|
||||
Kind.DEBUG_LABEL -> findDebugLabel(parameter.name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun findOrdinary(kind: VariableKind.Ordinary): Result? {
|
||||
val variables = frameProxy.safeVisibleVariables()
|
||||
|
||||
// Local variables – direct search
|
||||
findLocalVariable(variables, kind, kind.name)?.let { return it }
|
||||
|
||||
// Recursive search in local receiver variables
|
||||
findCapturedVariableInReceiver(variables, kind)?.let { return it }
|
||||
|
||||
// Recursive search in captured this
|
||||
val containingThis = thisObject() ?: return null
|
||||
return findCapturedVariable(kind, containingThis)
|
||||
}
|
||||
|
||||
private fun findFieldVariable(kind: VariableKind.FieldVar): Result? {
|
||||
val thisObject = thisObject()
|
||||
if (thisObject != null) {
|
||||
val field = thisObject.referenceType().fieldByName(kind.fieldName) ?: return null
|
||||
return Result(thisObject.getValue(field))
|
||||
} else {
|
||||
val containingType = frameProxy.safeLocation()?.declaringType() ?: return null
|
||||
val field = containingType.fieldByName(kind.fieldName) ?: return null
|
||||
return Result(containingType.getValue(field))
|
||||
}
|
||||
}
|
||||
|
||||
private fun findLocalFunction(kind: VariableKind.LocalFunction): Result? {
|
||||
val variables = frameProxy.safeVisibleVariables()
|
||||
|
||||
// Local variables – direct search, new convention
|
||||
val newConventionName = AsmUtil.LOCAL_FUNCTION_VARIABLE_PREFIX + kind.name
|
||||
findLocalVariable(variables, kind, newConventionName)?.let { return it }
|
||||
|
||||
// Local variables – direct search, old convention (before 1.3.30)
|
||||
findLocalVariable(variables, kind, kind.name + "$")?.let { return it }
|
||||
|
||||
// Recursive search in local receiver variables
|
||||
findCapturedVariableInReceiver(variables, kind)?.let { return it }
|
||||
|
||||
// Recursive search in captured this
|
||||
val containingThis = thisObject() ?: return null
|
||||
return findCapturedVariable(kind, containingThis)
|
||||
}
|
||||
|
||||
private fun findExtensionThis(kind: VariableKind.ExtensionThis): Result? {
|
||||
val variables = frameProxy.safeVisibleVariables()
|
||||
|
||||
// Local variables – direct search
|
||||
val namePredicate = fun(name: String) = name == kind.parameterName || name.startsWith(kind.parameterName + '$')
|
||||
findLocalVariable(variables, kind, namePredicate)?.let { return it }
|
||||
|
||||
// Recursive search in local receiver variables
|
||||
findCapturedVariableInReceiver(variables, kind)?.let { return it }
|
||||
|
||||
// Recursive search in captured this
|
||||
val containingThis = thisObject()
|
||||
if (containingThis != null) {
|
||||
findCapturedVariable(kind, containingThis)?.let { return it }
|
||||
}
|
||||
|
||||
@Suppress("ConstantConditionIf")
|
||||
if (USE_UNSAFE_FALLBACK) {
|
||||
// Find an unlabeled this with the compatible type
|
||||
findUnlabeledThis(VariableKind.UnlabeledThis(kind.asmType))?.let { return it }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findDispatchThis(kind: VariableKind.OuterClassThis): Result? {
|
||||
val containingThis = thisObject()
|
||||
if (containingThis != null) {
|
||||
findCapturedVariable(kind, containingThis)?.let { return it }
|
||||
}
|
||||
|
||||
if (isInsideDefaultImpls()) {
|
||||
val variables = frameProxy.safeVisibleVariables()
|
||||
findLocalVariable(variables, kind, AsmUtil.THIS_IN_DEFAULT_IMPLS)?.let { return it }
|
||||
}
|
||||
|
||||
val variables = frameProxy.safeVisibleVariables()
|
||||
val inlineDepth = getInlineDepth(variables)
|
||||
|
||||
if (inlineDepth > 0) {
|
||||
variables.namedEntitySequence()
|
||||
.filter { it.name.matches(inlinedThisRegex) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) }
|
||||
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
}
|
||||
|
||||
@Suppress("ConstantConditionIf")
|
||||
if (USE_UNSAFE_FALLBACK) {
|
||||
// Find an unlabeled this with the compatible type
|
||||
findUnlabeledThis(VariableKind.UnlabeledThis(kind.asmType))?.let { return it }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findDebugLabel(name: String): Result? {
|
||||
val markupMap = DebugLabelPropertyDescriptorProvider.getMarkupMap(context.debugProcess)
|
||||
|
||||
for ((value, markup) in markupMap) {
|
||||
if (markup.text == name) {
|
||||
return Result(value)
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findUnlabeledThis(kind: VariableKind.UnlabeledThis): Result? {
|
||||
val variables = frameProxy.safeVisibleVariables()
|
||||
|
||||
// Recursive search in local receiver variables
|
||||
findCapturedVariableInReceiver(variables, kind)?.let { return it }
|
||||
|
||||
val containingThis = thisObject() ?: return null
|
||||
return findCapturedVariable(kind, containingThis)
|
||||
}
|
||||
|
||||
private fun findLocalVariable(variables: List<LocalVariableProxyImpl>, kind: VariableKind, name: String): Result? {
|
||||
return findLocalVariable(variables, kind) { it == name }
|
||||
}
|
||||
|
||||
private fun findLocalVariable(
|
||||
variables: List<LocalVariableProxyImpl>,
|
||||
kind: VariableKind,
|
||||
namePredicate: (String) -> Boolean
|
||||
): Result? {
|
||||
val inlineDepth = getInlineDepth(variables)
|
||||
|
||||
if (inlineDepth > 0) {
|
||||
val inlineAwareNamePredicate = fun(name: String): Boolean {
|
||||
var endIndex = name.length
|
||||
var depth = 0
|
||||
|
||||
val suffixLen = INLINE_FUN_VAR_SUFFIX.length
|
||||
while (endIndex >= suffixLen) {
|
||||
if (name.substring(endIndex - suffixLen, endIndex) != INLINE_FUN_VAR_SUFFIX) {
|
||||
break
|
||||
}
|
||||
|
||||
depth++
|
||||
endIndex -= suffixLen
|
||||
}
|
||||
|
||||
return namePredicate(name.take(endIndex))
|
||||
}
|
||||
|
||||
variables.namedEntitySequence()
|
||||
.filter { inlineAwareNamePredicate(it.name) && getInlineDepth(it.name) == inlineDepth && kind.typeMatches(it.type) }
|
||||
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
}
|
||||
|
||||
variables.namedEntitySequence()
|
||||
.filter { namePredicate(it.name) && kind.typeMatches(it.type) }
|
||||
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun isInsideDefaultImpls(): Boolean {
|
||||
val declaringType = frameProxy.safeLocation()?.declaringType() ?: return false
|
||||
return declaringType.name().endsWith(JvmAbi.DEFAULT_IMPLS_SUFFIX)
|
||||
}
|
||||
|
||||
private fun findCoroutineContext(): Result? {
|
||||
val method = frameProxy.safeLocation()?.safeMethod() ?: return null
|
||||
val result = findCoroutineContextForLambda(method) ?: findCoroutineContextForMethod(method) ?: return null
|
||||
return Result(result)
|
||||
}
|
||||
|
||||
private fun findCoroutineContextForLambda(method: Method): ObjectReference? {
|
||||
if (method.name() != "invokeSuspend" || method.signature() != "(Ljava/lang/Object;)Ljava/lang/Object;") {
|
||||
return null
|
||||
}
|
||||
|
||||
val thisObject = thisObject() ?: return null
|
||||
val thisType = thisObject.referenceType()
|
||||
|
||||
if (SUSPEND_LAMBDA_CLASSES.none { thisType.isSubtype(it) }) {
|
||||
return null
|
||||
}
|
||||
|
||||
return findCoroutineContextForContinuation(thisObject)
|
||||
}
|
||||
|
||||
private fun findCoroutineContextForMethod(method: Method): ObjectReference? {
|
||||
if (CONTINUATION_TYPE.descriptor + ")" !in method.signature()) {
|
||||
return null
|
||||
}
|
||||
|
||||
val continuationVariable = frameProxy.safeVisibleVariableByName(CONTINUATION_VARIABLE_NAME) ?: return null
|
||||
val continuation = frameProxy.getValue(continuationVariable) as? ObjectReference ?: return null
|
||||
return findCoroutineContextForContinuation(continuation)
|
||||
}
|
||||
|
||||
private fun findCoroutineContextForContinuation(continuation: ObjectReference): ObjectReference? {
|
||||
val continuationType = (continuation.referenceType() as? ClassType)
|
||||
?.allInterfaces()?.firstOrNull { it.name() == Continuation::class.java.name }
|
||||
?: return null
|
||||
|
||||
val getContextMethod = continuationType
|
||||
.methodsByName("getContext", "()Lkotlin/coroutines/CoroutineContext;").firstOrNull()
|
||||
?: return null
|
||||
|
||||
return context.invokeMethod(continuation, getContextMethod, emptyList()) as? ObjectReference
|
||||
}
|
||||
|
||||
private fun findCapturedVariableInReceiver(variables: List<LocalVariableProxyImpl>, kind: VariableKind): Result? {
|
||||
fun isReceiverOrPassedThis(name: String) =
|
||||
name.startsWith(AsmUtil.LABELED_THIS_PARAMETER)
|
||||
|| name == AsmUtil.RECEIVER_PARAMETER_NAME
|
||||
|| name == AsmUtil.THIS_IN_DEFAULT_IMPLS
|
||||
|| inlinedThisRegex.matches(name)
|
||||
|
||||
if (kind is VariableKind.ExtensionThis) {
|
||||
variables.namedEntitySequence()
|
||||
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
|
||||
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
}
|
||||
|
||||
return variables.namedEntitySequence()
|
||||
.filter { isReceiverOrPassedThis(it.name) }
|
||||
.mapNotNull { findCapturedVariable(kind, it.value) }
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
private fun findCapturedVariable(kind: VariableKind, parentFactory: () -> Value?): Result? {
|
||||
val parent = getUnwrapDelegate(kind, parentFactory)
|
||||
return findCapturedVariable(kind, parent)
|
||||
}
|
||||
|
||||
private fun findCapturedVariable(kind: VariableKind, parent: Value?): Result? {
|
||||
val acceptsParentValue = kind is VariableKind.UnlabeledThis || kind is VariableKind.OuterClassThis
|
||||
if (parent != null && acceptsParentValue && kind.typeMatches(parent.type())) {
|
||||
return Result(parent)
|
||||
}
|
||||
|
||||
val fields = (parent as? ObjectReference)?.referenceType()?.fields() ?: return null
|
||||
|
||||
if (kind !is VariableKind.OuterClassThis) {
|
||||
// Captured variables - direct search
|
||||
fields.namedEntitySequence(parent)
|
||||
.filter { kind.capturedNameMatches(it.name) && kind.typeMatches(it.type) }
|
||||
.mapNotNull { it.unwrapAndCheck(kind) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
|
||||
// Recursive search in captured receivers
|
||||
fields.namedEntitySequence(parent)
|
||||
.filter { isCapturedReceiverFieldName(it.name) }
|
||||
.mapNotNull { findCapturedVariable(kind, it.value) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
}
|
||||
|
||||
// Recursive search in outer and captured this
|
||||
fields.namedEntitySequence(parent)
|
||||
.filter { it.name == AsmUtil.THIS_IN_DEFAULT_IMPLS || it.name == AsmUtil.CAPTURED_THIS_FIELD }
|
||||
.mapNotNull { findCapturedVariable(kind, it.value) }
|
||||
.firstOrNull()
|
||||
?.let { return it }
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getUnwrapDelegate(kind: VariableKind, valueFactory: () -> Value?): Value? {
|
||||
val rawValue = valueFactory()
|
||||
if (kind !is VariableKind.Ordinary || !kind.isDelegated) {
|
||||
return rawValue
|
||||
}
|
||||
|
||||
val delegateValue = rawValue as? ObjectReference ?: return rawValue
|
||||
val getValueMethod = delegateValue.referenceType()
|
||||
.methodsByName("getValue", "()Ljava/lang/Object;").firstOrNull()
|
||||
?: return rawValue
|
||||
|
||||
return context.invokeMethod(delegateValue, getValueMethod, emptyList())
|
||||
}
|
||||
|
||||
private fun isCapturedReceiverFieldName(name: String): Boolean {
|
||||
return name.startsWith(getCapturedFieldName(AsmUtil.LABELED_THIS_FIELD))
|
||||
|| name == AsmUtil.CAPTURED_RECEIVER_FIELD
|
||||
}
|
||||
|
||||
private fun VariableKind.typeMatches(actualType: JdiType?): Boolean {
|
||||
if (this is VariableKind.Ordinary && isDelegated) {
|
||||
// We can't figure out the actual type of the value yet.
|
||||
// No worries: it will be checked again (and more carefully) in `unwrapAndCheck()`.
|
||||
return true
|
||||
}
|
||||
return evaluatorValueConverter.typeMatches(asmType, actualType)
|
||||
}
|
||||
|
||||
private fun NamedEntity.unwrapAndCheck(kind: VariableKind): Result? {
|
||||
return evaluatorValueConverter.coerce(getUnwrapDelegate(kind, value), kind.asmType)
|
||||
}
|
||||
|
||||
private fun List<Field>.namedEntitySequence(owner: ObjectReference): Sequence<NamedEntity> {
|
||||
return asSequence().map { NamedEntity.of(it, owner) }
|
||||
}
|
||||
|
||||
private fun List<LocalVariableProxyImpl>.namedEntitySequence(): Sequence<NamedEntity> {
|
||||
return asSequence().map { NamedEntity.of(it, frameProxy) }
|
||||
}
|
||||
|
||||
private fun thisObject(): ObjectReference? {
|
||||
val thisObjectFromEvaluation = context.evaluationContext.computeThisObject() as? ObjectReference
|
||||
if (thisObjectFromEvaluation != null) {
|
||||
return thisObjectFromEvaluation
|
||||
}
|
||||
|
||||
return frameProxy.thisObject()
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.filter
|
||||
|
||||
import com.intellij.debugger.settings.DebuggerSettings
|
||||
import com.intellij.ui.classFilter.ClassFilter
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings
|
||||
|
||||
private val KOTLIN_STDLIB_FILTER = "kotlin.*"
|
||||
|
||||
fun addKotlinStdlibDebugFilterIfNeeded() {
|
||||
if (!KotlinDebuggerSettings.getInstance().DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED) {
|
||||
val settings = DebuggerSettings.getInstance()!!
|
||||
val newFilters = (settings.steppingFilters + ClassFilter(KOTLIN_STDLIB_FILTER))
|
||||
|
||||
settings.steppingFilters = newFilters
|
||||
|
||||
KotlinDebuggerSettings.getInstance().DEBUG_IS_FILTER_FOR_STDLIB_ALREADY_ADDED = true
|
||||
}
|
||||
}
|
||||
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.filter
|
||||
|
||||
import com.intellij.ui.classFilter.ClassFilter
|
||||
import com.intellij.ui.classFilter.DebuggerClassFilterProvider
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings
|
||||
|
||||
private val FILTERS = listOf(
|
||||
ClassFilter("kotlin.jvm*"),
|
||||
ClassFilter("kotlin.reflect*"),
|
||||
ClassFilter("kotlin.NoWhenBranchMatchedException"),
|
||||
ClassFilter("kotlin.TypeCastException"),
|
||||
ClassFilter("kotlin.KotlinNullPointerException")
|
||||
)
|
||||
|
||||
class KotlinDebuggerInternalClassesFilterProvider : DebuggerClassFilterProvider {
|
||||
override fun getFilters(): List<ClassFilter>? {
|
||||
return if (KotlinDebuggerSettings.getInstance().DEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES) FILTERS else listOf()
|
||||
}
|
||||
}
|
||||
-131
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.filter
|
||||
|
||||
import com.intellij.debugger.engine.SyntheticTypeComponentProvider
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.idea.debugger.safeAllLineLocations
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.FqNameUnsafe
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||
import kotlin.jvm.internal.FunctionReference
|
||||
import kotlin.jvm.internal.PropertyReference
|
||||
|
||||
class KotlinSyntheticTypeComponentProvider: SyntheticTypeComponentProvider {
|
||||
override fun isSynthetic(typeComponent: TypeComponent?): Boolean {
|
||||
if (typeComponent !is Method) return false
|
||||
|
||||
val containingType = typeComponent.declaringType()
|
||||
val typeName = containingType.name()
|
||||
if (!FqNameUnsafe.isValid(typeName)) return false
|
||||
|
||||
// TODO: this is most likely not necessary since KT-28453 is fixed, but still can be useful when debugging old compiled code
|
||||
if (containingType.isCallableReferenceSyntheticClass()) {
|
||||
return true
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeComponent.isDelegateToDefaultInterfaceImpl()) return true
|
||||
|
||||
if (typeComponent.location()?.lineNumber() != 1) return false
|
||||
|
||||
if (typeComponent.allLineLocations().any { it.lineNumber() != 1 }) {
|
||||
return false
|
||||
}
|
||||
|
||||
return !typeComponent.declaringType().allLineLocations().any { it.lineNumber() != 1 }
|
||||
}
|
||||
catch(e: AbsentInformationException) {
|
||||
return false
|
||||
}
|
||||
catch(e: UnsupportedOperationException) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private tailrec fun ReferenceType?.isCallableReferenceSyntheticClass(): Boolean {
|
||||
if (this !is ClassType) return false
|
||||
val superClass = this.superclass() ?: return false
|
||||
val superClassName = superClass.name()
|
||||
if (superClassName == PropertyReference::class.java.name || superClassName == FunctionReference::class.java.name) {
|
||||
return true
|
||||
}
|
||||
|
||||
// The direct supertype may be PropertyReference0 or something
|
||||
return if (superClassName.startsWith("kotlin.jvm.internal."))
|
||||
superClass.isCallableReferenceSyntheticClass()
|
||||
else
|
||||
false
|
||||
}
|
||||
|
||||
private fun Method.isDelegateToDefaultInterfaceImpl(): Boolean {
|
||||
if (safeAllLineLocations().size != 1) return false
|
||||
if (!virtualMachine().canGetBytecodes()) return false
|
||||
|
||||
if (!hasOnlyInvokeStatic(this)) return false
|
||||
|
||||
return hasInterfaceWithImplementation(this)
|
||||
}
|
||||
|
||||
private val LOAD_INSTRUCTIONS_WITH_INDEX = Opcodes.ILOAD.toByte()..Opcodes.ALOAD.toByte()
|
||||
private val LOAD_INSTRUCTIONS = (Opcodes.ALOAD + 1).toByte()..(Opcodes.IALOAD - 1).toByte()
|
||||
|
||||
private val RETURN_INSTRUCTIONS = Opcodes.IRETURN.toByte()..Opcodes.RETURN.toByte()
|
||||
|
||||
// Check that method contains only load and invokeStatic instructions. Note that if after load goes ldc instruction it could be checkParametersNotNull method invocation
|
||||
private fun hasOnlyInvokeStatic(m: Method): Boolean {
|
||||
val bytecodes = m.bytecodes()
|
||||
var i = 0
|
||||
var isALoad0BeforeStaticCall = false
|
||||
while (i < bytecodes.size) {
|
||||
val instr = bytecodes[i]
|
||||
when {
|
||||
instr == 42.toByte() /* ALOAD_0 */ -> {
|
||||
i += 1
|
||||
isALoad0BeforeStaticCall = true
|
||||
}
|
||||
instr in LOAD_INSTRUCTIONS_WITH_INDEX || instr in LOAD_INSTRUCTIONS -> {
|
||||
i += 1
|
||||
if (instr in LOAD_INSTRUCTIONS_WITH_INDEX) i += 1
|
||||
val nextInstr = bytecodes[i]
|
||||
if (nextInstr == Opcodes.LDC.toByte()) {
|
||||
i += 2
|
||||
isALoad0BeforeStaticCall = false
|
||||
}
|
||||
}
|
||||
instr == Opcodes.INVOKESTATIC.toByte() -> {
|
||||
i += 3
|
||||
if (isALoad0BeforeStaticCall && i == (bytecodes.size - 1)) {
|
||||
val nextInstr = bytecodes[i]
|
||||
return nextInstr in RETURN_INSTRUCTIONS
|
||||
}
|
||||
}
|
||||
else -> return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: class DefaultImpl can be not loaded
|
||||
private fun hasInterfaceWithImplementation(method: Method): Boolean {
|
||||
val declaringType = method.declaringType() as? ClassType ?: return false
|
||||
val interfaces = declaringType.allInterfaces()
|
||||
val vm = declaringType.virtualMachine()
|
||||
val traitImpls = interfaces.flatMap { vm.classesByName(it.name() + JvmAbi.DEFAULT_IMPLS_SUFFIX) }
|
||||
return traitImpls.any { !it.methodsByName(method.name()).isEmpty() }
|
||||
}
|
||||
}
|
||||
-85
@@ -1,85 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.render
|
||||
|
||||
import com.intellij.debugger.DebuggerContext
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
|
||||
import com.intellij.debugger.ui.impl.watch.FieldDescriptorImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.psi.PsiExpression
|
||||
import com.sun.jdi.*
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
|
||||
class DelegatedPropertyFieldDescriptor(
|
||||
project: Project,
|
||||
objectRef: ObjectReference,
|
||||
val delegate: Field,
|
||||
private val renderDelegatedProperty: Boolean
|
||||
) : FieldDescriptorImpl(project, objectRef, delegate) {
|
||||
|
||||
override fun calcValue(evaluationContext: EvaluationContextImpl?): Value? {
|
||||
if (evaluationContext == null) return null
|
||||
if (!renderDelegatedProperty) return super.calcValue(evaluationContext)
|
||||
|
||||
val method = findGetterForDelegatedProperty()
|
||||
val threadReference = evaluationContext.suspendContext.thread?.threadReference
|
||||
if (method == null || threadReference == null) {
|
||||
return super.calcValue(evaluationContext)
|
||||
}
|
||||
|
||||
try {
|
||||
return evaluationContext.debugProcess.invokeInstanceMethod(
|
||||
evaluationContext,
|
||||
`object`,
|
||||
method,
|
||||
listOf<Nothing>(),
|
||||
evaluationContext.suspendContext.suspendPolicy
|
||||
)
|
||||
}
|
||||
catch(e: EvaluateException) {
|
||||
return e.exceptionFromTargetVM
|
||||
}
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return delegate.name().removeSuffix(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX)
|
||||
}
|
||||
|
||||
override fun getDescriptorEvaluation(context: DebuggerContext?): PsiExpression? {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun findGetterForDelegatedProperty(): Method? {
|
||||
val fieldName = name
|
||||
if (!Name.isValidIdentifier(fieldName)) return null
|
||||
|
||||
return `object`.referenceType().methodsByName(JvmAbi.getterName(fieldName))?.firstOrNull()
|
||||
}
|
||||
|
||||
override fun getDeclaredType(): String? {
|
||||
val getter = findGetterForDelegatedProperty() ?: return null
|
||||
val returnType = try {
|
||||
getter.returnType()
|
||||
} catch (e: ClassNotLoadedException) {
|
||||
// Behavior copied from LocalVariableDescriptorImpl (in platform)
|
||||
return "<unknown>"
|
||||
}
|
||||
return returnType?.name()
|
||||
}
|
||||
}
|
||||
-141
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.debugger.render
|
||||
|
||||
import com.intellij.debugger.DebuggerManagerEx
|
||||
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
|
||||
import com.intellij.debugger.engine.evaluation.EvaluationContext
|
||||
import com.intellij.debugger.settings.NodeRendererSettings
|
||||
import com.intellij.debugger.ui.impl.watch.MessageDescriptor
|
||||
import com.intellij.debugger.ui.impl.watch.NodeManagerImpl
|
||||
import com.intellij.debugger.ui.tree.DebuggerTreeNode
|
||||
import com.intellij.debugger.ui.tree.ValueDescriptor
|
||||
import com.intellij.debugger.ui.tree.render.ChildrenBuilder
|
||||
import com.intellij.debugger.ui.tree.render.ClassRenderer
|
||||
import com.intellij.debugger.ui.tree.render.DescriptorLabelListener
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.xdebugger.settings.XDebuggerSettingsManager
|
||||
import com.sun.jdi.*
|
||||
import com.sun.jdi.Type
|
||||
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings
|
||||
import org.jetbrains.kotlin.idea.debugger.ToggleKotlinVariablesState
|
||||
import org.jetbrains.kotlin.idea.debugger.canRunEvaluation
|
||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
||||
import java.util.*
|
||||
import com.sun.jdi.Type as JdiType
|
||||
import org.jetbrains.org.objectweb.asm.Type as AsmType
|
||||
|
||||
private val LOG = Logger.getInstance(KotlinClassWithDelegatedPropertyRenderer::class.java)
|
||||
private fun notPreparedClassMessage(referenceType: ReferenceType) =
|
||||
"$referenceType ${referenceType.isPrepared} ${referenceType.sourceName()}"
|
||||
|
||||
class KotlinClassWithDelegatedPropertyRenderer : ClassRenderer() {
|
||||
private val rendererSettings = NodeRendererSettings.getInstance()
|
||||
|
||||
override fun isApplicable(jdiType: Type?): Boolean {
|
||||
if (!super.isApplicable(jdiType)) return false
|
||||
|
||||
if (jdiType !is ReferenceType) return false
|
||||
|
||||
if (!jdiType.isPrepared) {
|
||||
LOG.info(notPreparedClassMessage(jdiType))
|
||||
return false
|
||||
}
|
||||
|
||||
try {
|
||||
return jdiType.allFields().any { it.name().endsWith(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX) }
|
||||
} catch (notPrepared: ClassNotPreparedException) {
|
||||
LOG.error(notPreparedClassMessage(jdiType), notPrepared)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
override fun calcLabel(
|
||||
descriptor: ValueDescriptor,
|
||||
evaluationContext: EvaluationContext,
|
||||
listener: DescriptorLabelListener
|
||||
): String {
|
||||
val res = calcToStringLabel(descriptor, evaluationContext, listener)
|
||||
if (res != null) {
|
||||
return res
|
||||
}
|
||||
|
||||
return super.calcLabel(descriptor, evaluationContext, listener)
|
||||
}
|
||||
|
||||
private fun calcToStringLabel(
|
||||
descriptor: ValueDescriptor, evaluationContext: EvaluationContext,
|
||||
listener: DescriptorLabelListener
|
||||
): String? {
|
||||
val toStringRenderer = rendererSettings.toStringRenderer
|
||||
if (toStringRenderer.isEnabled && DebuggerManagerEx.getInstanceEx(evaluationContext.project).context.canRunEvaluation) {
|
||||
if (toStringRenderer.isApplicable(descriptor.type)) {
|
||||
return toStringRenderer.calcLabel(descriptor, evaluationContext, listener)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun buildChildren(value: Value?, builder: ChildrenBuilder, context: EvaluationContext) {
|
||||
DebuggerManagerThreadImpl.assertIsManagerThread()
|
||||
|
||||
if (value !is ObjectReference) return
|
||||
|
||||
val nodeManager = builder.nodeManager!!
|
||||
val nodeDescriptorFactory = builder.descriptorManager!!
|
||||
|
||||
val fields = value.referenceType().allFields()
|
||||
if (fields.isEmpty()) {
|
||||
builder.setChildren(listOf(nodeManager.createMessageNode(MessageDescriptor.CLASS_HAS_NO_FIELDS.label)))
|
||||
return
|
||||
}
|
||||
|
||||
val children = ArrayList<DebuggerTreeNode>()
|
||||
for (field in fields) {
|
||||
if (!shouldDisplay(context, value, field)) {
|
||||
continue
|
||||
}
|
||||
|
||||
val fieldDescriptor = nodeDescriptorFactory.getFieldDescriptor(builder.parentDescriptor, value, field)
|
||||
|
||||
if (field.name().endsWith(JvmAbi.DELEGATED_PROPERTY_NAME_SUFFIX)) {
|
||||
val shouldRenderDelegatedProperty = KotlinDebuggerSettings.getInstance().DEBUG_RENDER_DELEGATED_PROPERTIES
|
||||
if (shouldRenderDelegatedProperty && !ToggleKotlinVariablesState.getService().kotlinVariableView) {
|
||||
children.add(nodeManager.createNode(fieldDescriptor, context))
|
||||
}
|
||||
|
||||
val delegatedPropertyDescriptor = DelegatedPropertyFieldDescriptor(
|
||||
context.debugProcess.project!!,
|
||||
value,
|
||||
field,
|
||||
shouldRenderDelegatedProperty
|
||||
)
|
||||
children.add(nodeManager.createNode(delegatedPropertyDescriptor, context))
|
||||
} else {
|
||||
children.add(nodeManager.createNode(fieldDescriptor, context))
|
||||
}
|
||||
}
|
||||
|
||||
if (XDebuggerSettingsManager.getInstance()!!.dataViewSettings.isSortValues) {
|
||||
children.sortedWith(NodeManagerImpl.getNodeComparator())
|
||||
}
|
||||
|
||||
builder.setChildren(children)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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
|
||||
|
||||
import com.intellij.debugger.engine.evaluation.AbsentInformationEvaluateException
|
||||
import com.intellij.debugger.engine.evaluation.EvaluateException
|
||||
import com.intellij.debugger.engine.jdi.StackFrameProxy
|
||||
import com.intellij.debugger.impl.DebuggerUtilsEx
|
||||
import com.intellij.debugger.jdi.LocalVariableProxyImpl
|
||||
import com.intellij.debugger.jdi.StackFrameProxyImpl
|
||||
import com.sun.jdi.*
|
||||
|
||||
fun StackFrameProxyImpl.safeVisibleVariables(): List<LocalVariableProxyImpl> {
|
||||
return wrapAbsentInformationException { visibleVariables() } ?: emptyList()
|
||||
}
|
||||
|
||||
fun StackFrameProxyImpl.safeVisibleVariableByName(name: String): LocalVariableProxyImpl? {
|
||||
return wrapAbsentInformationException { visibleVariableByName(name) }
|
||||
}
|
||||
|
||||
fun Method.safeAllLineLocations(): List<Location> {
|
||||
return DebuggerUtilsEx.allLineLocations(this) ?: emptyList()
|
||||
}
|
||||
|
||||
fun ReferenceType.safeAllLineLocations(): List<Location> {
|
||||
return DebuggerUtilsEx.allLineLocations(this) ?: emptyList()
|
||||
}
|
||||
|
||||
fun ReferenceType.safeSourceName(): String? {
|
||||
return wrapAbsentInformationException { sourceName() }
|
||||
}
|
||||
|
||||
fun Method.safeLocationsOfLine(line: Int): List<Location> {
|
||||
return wrapAbsentInformationException { locationsOfLine(line) } ?: emptyList()
|
||||
}
|
||||
|
||||
fun Method.safeVariables(): List<LocalVariable>? {
|
||||
return wrapAbsentInformationException { variables() }
|
||||
}
|
||||
|
||||
fun Method.safeArguments(): List<LocalVariable>? {
|
||||
return wrapAbsentInformationException { arguments() }
|
||||
}
|
||||
|
||||
fun StackFrameProxy.safeLocation(): Location? {
|
||||
return try {
|
||||
this.location()
|
||||
} catch (e: EvaluateException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun Location.safeSourceName(): String? {
|
||||
return try {
|
||||
sourceName()
|
||||
} catch (e: AbsentInformationException) {
|
||||
null
|
||||
} catch (e: InternalError) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun Location.safeLineNumber(): Int {
|
||||
return DebuggerUtilsEx.getLineNumber(this, false)
|
||||
}
|
||||
|
||||
fun Location.safeSourceLineNumber(): Int {
|
||||
return DebuggerUtilsEx.getLineNumber(this, true)
|
||||
}
|
||||
|
||||
fun Location.safeMethod(): Method? {
|
||||
return DebuggerUtilsEx.getMethod(this)
|
||||
}
|
||||
|
||||
fun LocalVariableProxyImpl.safeType(): Type? {
|
||||
return wrapClassNotLoadedException { type }
|
||||
}
|
||||
|
||||
fun Field.safeType(): Type? {
|
||||
return wrapClassNotLoadedException { type() }
|
||||
}
|
||||
|
||||
private inline fun <T> wrapAbsentInformationException(block: () -> T): T? {
|
||||
return try {
|
||||
block()
|
||||
} catch (e: AbsentInformationException) {
|
||||
null
|
||||
} catch (e: AbsentInformationEvaluateException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <T> wrapClassNotLoadedException(block: () -> T): T? {
|
||||
return try {
|
||||
block()
|
||||
} catch (e: ClassNotLoadedException) {
|
||||
null
|
||||
}
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.lib.collections
|
||||
|
||||
import com.intellij.debugger.streams.lib.IntermediateOperation
|
||||
import com.intellij.debugger.streams.lib.TerminalOperation
|
||||
import com.intellij.debugger.streams.lib.impl.LibrarySupportBase
|
||||
import com.intellij.debugger.streams.resolve.FilterResolver
|
||||
import com.intellij.debugger.streams.resolve.ValuesOrderResolver
|
||||
import com.intellij.debugger.streams.trace.CallTraceInterpreter
|
||||
import com.intellij.debugger.streams.trace.IntermediateCallHandler
|
||||
import com.intellij.debugger.streams.trace.TerminatorCallHandler
|
||||
import com.intellij.debugger.streams.trace.dsl.Dsl
|
||||
import com.intellij.debugger.streams.wrapper.IntermediateStreamCall
|
||||
import com.intellij.debugger.streams.wrapper.TerminatorStreamCall
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.handler.collections.BothSemanticHandlerWrapper
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.handler.collections.BothSemanticsHandler
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.handler.collections.FilterCallHandler
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.interpret.FilterTraceInterpreter
|
||||
|
||||
class KotlinCollectionLibrarySupport : LibrarySupportBase() {
|
||||
init {
|
||||
addOperation(FilterOperation("filter", FilterCallHandler(), true))
|
||||
addOperation(FilterOperation("filterNot", FilterCallHandler(), false))
|
||||
}
|
||||
|
||||
private fun addOperation(operation: CollectionOperation) {
|
||||
addIntermediateOperationsSupport(operation)
|
||||
addTerminationOperationsSupport(operation)
|
||||
}
|
||||
|
||||
private abstract class CollectionOperation(
|
||||
override val name: String,
|
||||
handler: BothSemanticsHandler
|
||||
) : IntermediateOperation, TerminalOperation {
|
||||
|
||||
private val wrapper = BothSemanticHandlerWrapper(handler)
|
||||
|
||||
override fun getTraceHandler(callOrder: Int, call: IntermediateStreamCall, dsl: Dsl): IntermediateCallHandler =
|
||||
wrapper.createIntermediateHandler(callOrder, call, dsl)
|
||||
|
||||
override fun getTraceHandler(call: TerminatorStreamCall, resultExpression: String, dsl: Dsl): TerminatorCallHandler =
|
||||
wrapper.createTerminatorHandler(call, resultExpression, dsl)
|
||||
}
|
||||
|
||||
private class FilterOperation(name: String, handler: BothSemanticsHandler, valueToAccept: Boolean) :
|
||||
CollectionOperation(name, handler) {
|
||||
override val traceInterpreter: CallTraceInterpreter = FilterTraceInterpreter(valueToAccept)
|
||||
override val valuesOrderResolver: ValuesOrderResolver = FilterResolver()
|
||||
}
|
||||
}
|
||||
-35
@@ -1,35 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.lib.collections
|
||||
|
||||
import com.intellij.debugger.streams.lib.LibrarySupport
|
||||
import com.intellij.debugger.streams.lib.LibrarySupportProvider
|
||||
import com.intellij.debugger.streams.trace.TraceExpressionBuilder
|
||||
import com.intellij.debugger.streams.trace.dsl.impl.DslImpl
|
||||
import com.intellij.debugger.streams.wrapper.StreamChainBuilder
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.collections.KotlinCollectionChainBuilder
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinCollectionsPeekCallFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinStatementFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.KotlinTraceExpressionBuilder
|
||||
|
||||
class KotlinCollectionSupportProvider : LibrarySupportProvider {
|
||||
private companion object {
|
||||
val builder: StreamChainBuilder = KotlinCollectionChainBuilder()
|
||||
val support: LibrarySupport = KotlinCollectionLibrarySupport()
|
||||
val dsl = DslImpl(KotlinStatementFactory(KotlinCollectionsPeekCallFactory()))
|
||||
}
|
||||
|
||||
override fun getLanguageId(): String = KotlinLanguage.INSTANCE.id
|
||||
|
||||
override fun getChainBuilder(): StreamChainBuilder = builder
|
||||
|
||||
override fun getLibrarySupport(): LibrarySupport = support
|
||||
|
||||
override fun getExpressionBuilder(project: Project): TraceExpressionBuilder =
|
||||
KotlinTraceExpressionBuilder(dsl, support.createHandlerFactory(dsl))
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.lib.java
|
||||
|
||||
import com.intellij.debugger.streams.lib.LibrarySupport
|
||||
import com.intellij.debugger.streams.lib.LibrarySupportProvider
|
||||
import com.intellij.debugger.streams.lib.impl.StandardLibrarySupport
|
||||
import com.intellij.debugger.streams.trace.TraceExpressionBuilder
|
||||
import com.intellij.debugger.streams.trace.dsl.impl.DslImpl
|
||||
import com.intellij.debugger.streams.wrapper.StreamChainBuilder
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.KotlinChainTransformerImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.PackageBasedCallChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.TerminatedChainBuilder
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.java.JavaStreamChainTypeExtractor
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.java.StandardLibraryCallChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.JavaPeekCallFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinStatementFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.KotlinTraceExpressionBuilder
|
||||
|
||||
class JavaStandardLibrarySupportProvider : LibrarySupportProvider {
|
||||
private companion object {
|
||||
val builder = TerminatedChainBuilder(
|
||||
KotlinChainTransformerImpl(JavaStreamChainTypeExtractor()),
|
||||
StandardLibraryCallChecker(PackageBasedCallChecker("java.util.stream"))
|
||||
)
|
||||
val support = StandardLibrarySupport()
|
||||
val dsl = DslImpl(KotlinStatementFactory(JavaPeekCallFactory()))
|
||||
}
|
||||
|
||||
override fun getLanguageId(): String = KotlinLanguage.INSTANCE.id
|
||||
|
||||
override fun getChainBuilder(): StreamChainBuilder = builder
|
||||
|
||||
override fun getLibrarySupport(): LibrarySupport = support
|
||||
|
||||
override fun getExpressionBuilder(project: Project): TraceExpressionBuilder =
|
||||
KotlinTraceExpressionBuilder(dsl, support.createHandlerFactory(dsl))
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.lib.java
|
||||
|
||||
import com.intellij.debugger.streams.lib.LibrarySupport
|
||||
import com.intellij.debugger.streams.lib.LibrarySupportProvider
|
||||
import com.intellij.debugger.streams.lib.impl.StreamExLibrarySupport
|
||||
import com.intellij.debugger.streams.trace.TraceExpressionBuilder
|
||||
import com.intellij.debugger.streams.trace.dsl.impl.DslImpl
|
||||
import com.intellij.debugger.streams.wrapper.StreamChainBuilder
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.KotlinChainTransformerImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.PackageBasedCallChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.TerminatedChainBuilder
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.java.JavaStreamChainTypeExtractor
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.java.StreamExCallChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.JavaPeekCallFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinStatementFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.KotlinTraceExpressionBuilder
|
||||
|
||||
class StreamExLibrarySupportProvider : LibrarySupportProvider {
|
||||
private companion object {
|
||||
val streamChainBuilder = TerminatedChainBuilder(
|
||||
KotlinChainTransformerImpl(JavaStreamChainTypeExtractor()),
|
||||
StreamExCallChecker(PackageBasedCallChecker("one.util.streamex"))
|
||||
)
|
||||
val support = StreamExLibrarySupport()
|
||||
val dsl = DslImpl(KotlinStatementFactory(JavaPeekCallFactory()))
|
||||
val expressionBuilder = KotlinTraceExpressionBuilder(dsl, support.createHandlerFactory(dsl))
|
||||
}
|
||||
|
||||
override fun getLanguageId(): String = KotlinLanguage.INSTANCE.id
|
||||
|
||||
override fun getChainBuilder(): StreamChainBuilder = streamChainBuilder
|
||||
|
||||
override fun getLibrarySupport(): LibrarySupport = support
|
||||
|
||||
override fun getExpressionBuilder(project: Project): TraceExpressionBuilder = expressionBuilder
|
||||
}
|
||||
-43
@@ -1,43 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.lib.sequence
|
||||
|
||||
import com.intellij.debugger.streams.lib.LibrarySupport
|
||||
import com.intellij.debugger.streams.lib.LibrarySupportProvider
|
||||
import com.intellij.debugger.streams.trace.TraceExpressionBuilder
|
||||
import com.intellij.debugger.streams.trace.dsl.impl.DslImpl
|
||||
import com.intellij.debugger.streams.wrapper.StreamChainBuilder
|
||||
import com.intellij.openapi.project.Project
|
||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.KotlinChainTransformerImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.TerminatedChainBuilder
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.sequence.SequenceCallChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.sequence.SequenceCallCheckerWithNameHeuristics
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.sequence.SequenceTypeExtractor
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinCollectionsPeekCallFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinStatementFactory
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.KotlinTraceExpressionBuilder
|
||||
|
||||
class KotlinSequenceSupportProvider : LibrarySupportProvider {
|
||||
override fun getLanguageId(): String = KotlinLanguage.INSTANCE.id
|
||||
|
||||
private companion object {
|
||||
val builder: StreamChainBuilder = TerminatedChainBuilder(
|
||||
KotlinChainTransformerImpl(SequenceTypeExtractor()),
|
||||
SequenceCallCheckerWithNameHeuristics(SequenceCallChecker())
|
||||
)
|
||||
val support = KotlinSequencesSupport()
|
||||
val dsl = DslImpl(KotlinStatementFactory(KotlinCollectionsPeekCallFactory()))
|
||||
val expressionBuilder = KotlinTraceExpressionBuilder(dsl, support.createHandlerFactory(dsl))
|
||||
}
|
||||
|
||||
override fun getChainBuilder(): StreamChainBuilder = builder
|
||||
|
||||
override fun getLibrarySupport(): LibrarySupport = support
|
||||
|
||||
override fun getExpressionBuilder(project: Project): TraceExpressionBuilder = expressionBuilder
|
||||
|
||||
}
|
||||
-71
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.lib.sequence
|
||||
|
||||
import com.intellij.debugger.streams.lib.IntermediateOperation
|
||||
import com.intellij.debugger.streams.lib.impl.*
|
||||
import com.intellij.debugger.streams.resolve.AppendResolver
|
||||
import com.intellij.debugger.streams.resolve.PairMapResolver
|
||||
import com.intellij.debugger.streams.trace.impl.handler.unified.DistinctTraceHandler
|
||||
import com.intellij.debugger.streams.trace.impl.interpret.SimplePeekCallTraceInterpreter
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.resolve.ChunkedResolver
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.resolve.FilteredMapResolver
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.resolve.WindowedResolver
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.handler.sequence.FilterIsInstanceHandler
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.handler.sequence.KotlinDistinctByHandler
|
||||
|
||||
class KotlinSequencesSupport : LibrarySupportBase() {
|
||||
init {
|
||||
addIntermediateOperationsSupport(
|
||||
*filterOperations(
|
||||
"filter", "filterNot", "filterIndexed",
|
||||
"drop", "dropWhile", "minus", "minusElement", "take", "takeWhile", "onEach", "asSequence"
|
||||
)
|
||||
)
|
||||
|
||||
addIntermediateOperationsSupport(FilterIsInstanceOperationHandler())
|
||||
|
||||
addIntermediateOperationsSupport(
|
||||
*mapOperations(
|
||||
"map", "mapIndexed", "requireNoNulls", "withIndex",
|
||||
"zip", "constrainOnce"
|
||||
)
|
||||
)
|
||||
|
||||
addIntermediateOperationsSupport(*flatMapOperations("flatMap", "flatten"))
|
||||
|
||||
addIntermediateOperationsSupport(*sortedOperations("sorted", "sortedBy", "sortedDescending", "sortedWith"))
|
||||
|
||||
addIntermediateOperationsSupport(DistinctOperation("distinct", ::DistinctTraceHandler))
|
||||
addIntermediateOperationsSupport(DistinctOperation("distinctBy", ::KotlinDistinctByHandler))
|
||||
|
||||
addIntermediateOperationsSupport(ConcatOperation("plus", AppendResolver()))
|
||||
addIntermediateOperationsSupport(ConcatOperation("plusElement", AppendResolver()))
|
||||
|
||||
addIntermediateOperationsSupport(OrderBasedOperation("zipWithNext", PairMapResolver()))
|
||||
|
||||
addIntermediateOperationsSupport(OrderBasedOperation("mapNotNull", FilteredMapResolver()))
|
||||
addIntermediateOperationsSupport(OrderBasedOperation("chunked", ChunkedResolver()))
|
||||
addIntermediateOperationsSupport(OrderBasedOperation("windowed", WindowedResolver()))
|
||||
}
|
||||
|
||||
private fun filterOperations(vararg names: String): Array<IntermediateOperation> =
|
||||
names.map { FilterOperation(it) }.toTypedArray()
|
||||
|
||||
private fun mapOperations(vararg names: String): Array<IntermediateOperation> =
|
||||
names.map { MappingOperation(it) }.toTypedArray()
|
||||
|
||||
private fun flatMapOperations(vararg names: String): Array<IntermediateOperation> =
|
||||
names.map { FlatMappingOperation(it) }.toTypedArray()
|
||||
|
||||
private fun sortedOperations(vararg names: String): Array<IntermediateOperation> =
|
||||
names.map { SortedOperation(it) }.toTypedArray()
|
||||
|
||||
private class FilterIsInstanceOperationHandler : IntermediateOperationBase(
|
||||
"filterIsInstance", ::FilterIsInstanceHandler,
|
||||
SimplePeekCallTraceInterpreter(), FilteredMapResolver()
|
||||
)
|
||||
}
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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.sequence.psi
|
||||
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.sequence.SequenceCallCheckerWithNameHeuristics
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
|
||||
abstract class CallCheckerWithNameHeuristics(private val nestedChecker: StreamCallChecker) : StreamCallChecker {
|
||||
override fun isIntermediateCall(expression: KtCallExpression): Boolean = nestedChecker.isIntermediateCall(expression)
|
||||
|
||||
override fun isTerminationCall(expression: KtCallExpression): Boolean {
|
||||
val name = expression.calleeExpression?.text
|
||||
if (name != null) {
|
||||
return isTerminalCallName(name) && nestedChecker.isTerminationCall(expression)
|
||||
}
|
||||
|
||||
return nestedChecker.isTerminationCall(expression)
|
||||
}
|
||||
|
||||
abstract fun isTerminalCallName(callName: String): Boolean
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.psi
|
||||
|
||||
import com.intellij.debugger.streams.trace.impl.handler.type.GenericType
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
interface CallTypeExtractor {
|
||||
fun extractIntermediateCallTypes(call: KtCallExpression): IntermediateCallTypes
|
||||
fun extractTerminalCallTypes(call: KtCallExpression): TerminatorCallTypes
|
||||
|
||||
data class IntermediateCallTypes(val typeBefore: GenericType, val typeAfter: GenericType)
|
||||
data class TerminatorCallTypes(val typeBefore: GenericType, val resultType: GenericType)
|
||||
|
||||
abstract class Base : CallTypeExtractor {
|
||||
override fun extractIntermediateCallTypes(call: KtCallExpression): IntermediateCallTypes =
|
||||
IntermediateCallTypes(extractItemsType(call.receiverType()), extractItemsType(call.resolveType()))
|
||||
|
||||
|
||||
override fun extractTerminalCallTypes(call: KtCallExpression): TerminatorCallTypes =
|
||||
TerminatorCallTypes(extractItemsType(call.receiverType()), getResultType(call.resolveType()))
|
||||
|
||||
|
||||
protected abstract fun extractItemsType(type: KotlinType?): GenericType
|
||||
protected abstract fun getResultType(type: KotlinType): GenericType
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.psi
|
||||
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.util.approximateFlexibleTypes
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
|
||||
import org.jetbrains.kotlin.psi.KtExpression
|
||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue
|
||||
import org.jetbrains.kotlin.types.FlexibleType
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
object KotlinPsiUtil {
|
||||
fun getTypeName(type: KotlinType): String {
|
||||
if (type is FlexibleType) {
|
||||
return DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(type.approximateFlexibleTypes())
|
||||
}
|
||||
|
||||
return DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(type)
|
||||
}
|
||||
|
||||
fun getTypeWithoutTypeParameters(type: KotlinType): String {
|
||||
val descriptor = type.constructor.declarationDescriptor ?: return getTypeName(type)
|
||||
return descriptor.fqNameSafe.asString()
|
||||
}
|
||||
}
|
||||
|
||||
fun KtExpression.resolveType(): KotlinType =
|
||||
this.analyze(BodyResolveMode.PARTIAL).getType(this)!!
|
||||
|
||||
fun KtCallExpression.callName(): String = this.calleeExpression!!.text
|
||||
|
||||
fun KtCallExpression.receiverValue(): ReceiverValue? {
|
||||
val resolvedCall = getResolvedCall(analyze(BodyResolveMode.PARTIAL)) ?: return null
|
||||
return resolvedCall.dispatchReceiver ?: resolvedCall.extensionReceiver
|
||||
}
|
||||
|
||||
fun KtCallExpression.previousCall(): KtCallExpression? {
|
||||
val parent = this.parent as? KtDotQualifiedExpression ?: return null
|
||||
val receiverExpression = parent.receiverExpression
|
||||
if (receiverExpression is KtCallExpression) return receiverExpression
|
||||
if (receiverExpression is KtDotQualifiedExpression) return receiverExpression.selectorExpression as? KtCallExpression
|
||||
return null
|
||||
}
|
||||
|
||||
fun KtCallExpression.receiverType(): KotlinType? = receiverValue()?.type
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.psi
|
||||
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
|
||||
interface StreamCallChecker {
|
||||
fun isIntermediateCall(expression: KtCallExpression): Boolean
|
||||
fun isTerminationCall(expression: KtCallExpression): Boolean
|
||||
|
||||
fun isStreamCall(expression: KtCallExpression): Boolean = isIntermediateCall(expression) || isTerminationCall(expression)
|
||||
}
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.psi.collections
|
||||
|
||||
import com.intellij.debugger.streams.psi.ChainTransformer
|
||||
import com.intellij.debugger.streams.wrapper.QualifierExpression
|
||||
import com.intellij.debugger.streams.wrapper.StreamChain
|
||||
import com.intellij.debugger.streams.wrapper.impl.StreamChainImpl
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.KotlinChainTransformerImpl
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.resolveType
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
class CollectionChainTransformer : ChainTransformer<KtCallExpression> {
|
||||
private val transformer = KotlinChainTransformerImpl(KotlinCollectionsTypeExtractor())
|
||||
|
||||
override fun transform(chainCalls: List<KtCallExpression>, context: PsiElement): StreamChain {
|
||||
val chain = transformer.transform(chainCalls, context)
|
||||
|
||||
if (chainCalls.first().resolveType().isArray) {
|
||||
val qualifier = WrappedQualifier(chain.qualifierExpression)
|
||||
return StreamChainImpl(qualifier, chain.intermediateCalls, chain.terminationCall, chain.context)
|
||||
}
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
/**
|
||||
* Kotlin arrays have not {@code onEach} extension. But current implementation uses onEach to increment a time counter.
|
||||
* We use asIterable to avoid further issues with the transformed expression evaluation
|
||||
* TODO: Avoid showing "asIterable()" in the tab name in trace window
|
||||
*/
|
||||
private class WrappedQualifier(private val qualifierExpression: QualifierExpression) : QualifierExpression by qualifierExpression {
|
||||
override val text: String
|
||||
get() = qualifierExpression.text + ".asIterable()"
|
||||
}
|
||||
|
||||
private val KotlinType.isArray: Boolean
|
||||
get() = KotlinBuiltIns.isArray(this) || KotlinBuiltIns.isPrimitiveArray(this)
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.psi.collections
|
||||
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.KotlinPsiUtil
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.impl.KotlinChainBuilderBase
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.previousCall
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.receiverType
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
||||
|
||||
class KotlinCollectionChainBuilder
|
||||
: KotlinChainBuilderBase(CollectionChainTransformer()) {
|
||||
private companion object {
|
||||
// TODO: Avoid enumeration of all available types
|
||||
val SUPPORTED_RECEIVERS = setOf(
|
||||
"kotlin.collections.Iterable", "kotlin.CharSequence", "kotlin.Array",
|
||||
"kotlin.BooleanArray", "kotlin.ByteArray", "kotlin.ShortArray", "kotlin.CharArray", "kotlin.IntArray",
|
||||
"kotlin.LongArray", "kotlin.DoubleArray", "kotlin.FloatArray"
|
||||
)
|
||||
}
|
||||
|
||||
private fun isCollectionTransformationCall(expression: KtCallExpression): Boolean {
|
||||
val receiverType = expression.receiverType() ?: return false
|
||||
if (isTypeSuitable(receiverType)) return true
|
||||
return receiverType.supertypes().any { isTypeSuitable(it) }
|
||||
}
|
||||
|
||||
override val existenceChecker: ExistenceChecker = object : ExistenceChecker() {
|
||||
override fun visitCallExpression(expression: KtCallExpression) {
|
||||
if (isFound()) return
|
||||
if (isCollectionTransformationCall(expression)) {
|
||||
fireElementFound()
|
||||
} else {
|
||||
super.visitCallExpression(expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun createChainsBuilder(): ChainBuilder = object : ChainBuilder() {
|
||||
private val previousCalls: MutableMap<KtCallExpression, KtCallExpression> = mutableMapOf()
|
||||
private val visitedCalls: MutableSet<KtCallExpression> = mutableSetOf()
|
||||
|
||||
override fun visitCallExpression(expression: KtCallExpression) {
|
||||
super.visitCallExpression(expression)
|
||||
if (isCollectionTransformationCall(expression)) {
|
||||
visitedCalls.add(expression)
|
||||
val previous = expression.previousCall()
|
||||
if (previous != null && isCollectionTransformationCall(previous)) {
|
||||
previousCalls[expression] = previous
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun chains(): List<List<KtCallExpression>> {
|
||||
val notLastCalls: Set<KtCallExpression> = previousCalls.values.toSet()
|
||||
return visitedCalls.filter { it !in notLastCalls }.map { buildPsiChain(it) }
|
||||
}
|
||||
|
||||
private fun buildPsiChain(expression: KtCallExpression): List<KtCallExpression> {
|
||||
val result = mutableListOf<KtCallExpression>()
|
||||
var current: KtCallExpression? = expression
|
||||
while (current != null) {
|
||||
result.add(current)
|
||||
current = previousCalls[current]
|
||||
}
|
||||
|
||||
result.reverse()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTypeSuitable(type: KotlinType): Boolean =
|
||||
SUPPORTED_RECEIVERS.contains(KotlinPsiUtil.getTypeWithoutTypeParameters(type))
|
||||
}
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.psi.collections
|
||||
|
||||
import com.intellij.debugger.streams.trace.impl.handler.type.GenericType
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.CallTypeExtractor
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.KotlinPsiUtil
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinSequenceTypes
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinSequenceTypes.ANY
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinSequenceTypes.NULLABLE_ANY
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
||||
|
||||
class KotlinCollectionsTypeExtractor : CallTypeExtractor.Base() {
|
||||
private companion object {
|
||||
val LOG = Logger.getInstance(KotlinCollectionsTypeExtractor::class.java)
|
||||
}
|
||||
|
||||
override fun extractItemsType(type: KotlinType?): GenericType {
|
||||
if (type == null) return NULLABLE_ANY
|
||||
|
||||
return tryToFindElementType(type) ?: defaultType(type)
|
||||
}
|
||||
|
||||
override fun getResultType(type: KotlinType): GenericType {
|
||||
val typeName = KotlinPsiUtil.getTypeWithoutTypeParameters(type)
|
||||
return KotlinSequenceTypes.primitiveTypeByName(typeName) ?: KotlinSequenceTypes.primitiveArrayByName(typeName) ?: getAny(type)
|
||||
}
|
||||
|
||||
private fun tryToFindElementType(type: KotlinType): GenericType? {
|
||||
val typeName = KotlinPsiUtil.getTypeWithoutTypeParameters(type)
|
||||
if (typeName == "kotlin.collections.Iterable" || typeName == "kotlin.Array") {
|
||||
if (type.arguments.isEmpty()) return NULLABLE_ANY
|
||||
val itemsType = type.arguments.first().type
|
||||
if (itemsType.isMarkedNullable) return NULLABLE_ANY
|
||||
val primitiveType = KotlinSequenceTypes.primitiveTypeByName(KotlinPsiUtil.getTypeWithoutTypeParameters(itemsType))
|
||||
return primitiveType ?: ANY
|
||||
}
|
||||
|
||||
if (typeName == "kotlin.String" || typeName == "kotlin.CharSequence") return KotlinSequenceTypes.CHAR
|
||||
|
||||
val primitiveArray = KotlinSequenceTypes.primitiveArrayByName(typeName)
|
||||
if (primitiveArray != null) return primitiveArray.elementType
|
||||
|
||||
return type.supertypes().asSequence()
|
||||
.map(this::tryToFindElementType)
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
private fun defaultType(type: KotlinType): GenericType {
|
||||
LOG.warn("Could not find type of items for type ${KotlinPsiUtil.getTypeName(type)}")
|
||||
return getAny(type)
|
||||
}
|
||||
|
||||
private fun getAny(type: KotlinType): GenericType = if (type.isMarkedNullable) NULLABLE_ANY else ANY
|
||||
}
|
||||
-94
@@ -1,94 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.psi.impl
|
||||
|
||||
import com.intellij.debugger.streams.psi.ChainTransformer
|
||||
import com.intellij.debugger.streams.psi.PsiUtil
|
||||
import com.intellij.debugger.streams.wrapper.StreamChain
|
||||
import com.intellij.debugger.streams.wrapper.StreamChainBuilder
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiWhiteSpace
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
|
||||
abstract class KotlinChainBuilderBase(private val transformer: ChainTransformer<KtCallExpression>) : StreamChainBuilder {
|
||||
protected abstract val existenceChecker: ExistenceChecker
|
||||
|
||||
override fun isChainExists(startElement: PsiElement): Boolean {
|
||||
val start = if (startElement is PsiWhiteSpace) PsiUtil.ignoreWhiteSpaces(startElement) else startElement
|
||||
var element = getLatestElementInScope(start)
|
||||
existenceChecker.reset()
|
||||
while (element != null && !existenceChecker.isFound()) {
|
||||
existenceChecker.reset()
|
||||
element.accept(existenceChecker)
|
||||
element = toUpperLevel(element)
|
||||
}
|
||||
|
||||
return existenceChecker.isFound()
|
||||
}
|
||||
|
||||
override fun build(startElement: PsiElement): List<StreamChain> {
|
||||
val visitor = createChainsBuilder()
|
||||
val start = if (startElement is PsiWhiteSpace) PsiUtil.ignoreWhiteSpaces(startElement) else startElement
|
||||
var element = getLatestElementInScope(start)
|
||||
while (element != null) {
|
||||
element.accept(visitor)
|
||||
element = toUpperLevel(element)
|
||||
}
|
||||
|
||||
return visitor.chains().map { transformer.transform(it, startElement) }
|
||||
}
|
||||
|
||||
private fun toUpperLevel(element: PsiElement): PsiElement? {
|
||||
var current = element.parent
|
||||
|
||||
while (current != null && !(current is KtLambdaExpression || current is KtAnonymousInitializer || current is KtObjectDeclaration)) {
|
||||
current = current.parent
|
||||
}
|
||||
|
||||
return getLatestElementInScope(current)
|
||||
}
|
||||
|
||||
protected abstract fun createChainsBuilder(): ChainBuilder
|
||||
|
||||
private fun getLatestElementInScope(element: PsiElement?): PsiElement? {
|
||||
var current = element
|
||||
while (current != null) {
|
||||
if (current is KtNamedFunction && current.hasInitializer()) {
|
||||
break
|
||||
}
|
||||
|
||||
val parent = current.parent
|
||||
if (parent is KtBlockExpression || parent is KtLambdaExpression) {
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
current = parent
|
||||
}
|
||||
|
||||
return current
|
||||
}
|
||||
|
||||
protected abstract class ExistenceChecker : MyTreeVisitor() {
|
||||
private var myIsFound: Boolean = false
|
||||
fun isFound(): Boolean = myIsFound
|
||||
fun reset() = setFound(false)
|
||||
protected fun fireElementFound() = setFound(true)
|
||||
|
||||
private fun setFound(value: Boolean) {
|
||||
myIsFound = value
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract class ChainBuilder : MyTreeVisitor() {
|
||||
abstract fun chains(): List<List<KtCallExpression>>
|
||||
}
|
||||
|
||||
protected abstract class MyTreeVisitor : KtTreeVisitorVoid() {
|
||||
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {}
|
||||
override fun visitBlockExpression(expression: KtBlockExpression) {}
|
||||
}
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.psi.impl
|
||||
|
||||
import com.intellij.debugger.streams.psi.ChainTransformer
|
||||
import com.intellij.debugger.streams.trace.impl.handler.type.GenericType
|
||||
import com.intellij.debugger.streams.wrapper.CallArgument
|
||||
import com.intellij.debugger.streams.wrapper.IntermediateStreamCall
|
||||
import com.intellij.debugger.streams.wrapper.QualifierExpression
|
||||
import com.intellij.debugger.streams.wrapper.StreamChain
|
||||
import com.intellij.debugger.streams.wrapper.impl.*
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.PsiElement
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.CallTypeExtractor
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.KotlinPsiUtil
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.callName
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.resolveType
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.psi.KtDotQualifiedExpression
|
||||
import org.jetbrains.kotlin.psi.KtValueArgument
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getParameterForArgument
|
||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||
|
||||
class KotlinChainTransformerImpl(private val typeExtractor: CallTypeExtractor) : ChainTransformer<KtCallExpression> {
|
||||
override fun transform(callChain: List<KtCallExpression>, context: PsiElement): StreamChain {
|
||||
val intermediateCalls = mutableListOf<IntermediateStreamCall>()
|
||||
for (call in callChain.subList(0, callChain.size - 1)) {
|
||||
val (typeBefore, typeAfter) = typeExtractor.extractIntermediateCallTypes(call)
|
||||
intermediateCalls += IntermediateStreamCallImpl(
|
||||
call.callName(), call.valueArguments.map { createCallArgument(call, it) },
|
||||
typeBefore, typeAfter,
|
||||
call.textRange
|
||||
)
|
||||
}
|
||||
|
||||
val terminationsPsiCall = callChain.last()
|
||||
val (typeBeforeTerminator, resultType) = typeExtractor.extractTerminalCallTypes(terminationsPsiCall)
|
||||
val terminationCall = TerminatorStreamCallImpl(
|
||||
terminationsPsiCall.callName(),
|
||||
terminationsPsiCall.valueArguments.map { createCallArgument(terminationsPsiCall, it) },
|
||||
typeBeforeTerminator, resultType, terminationsPsiCall.textRange
|
||||
)
|
||||
|
||||
val typeAfterQualifier =
|
||||
if (intermediateCalls.isEmpty()) typeBeforeTerminator else intermediateCalls.first().typeBefore
|
||||
|
||||
val qualifier = createQualifier(callChain.first(), typeAfterQualifier)
|
||||
|
||||
return StreamChainImpl(qualifier, intermediateCalls, terminationCall, context)
|
||||
}
|
||||
|
||||
private fun createCallArgument(callExpression: KtCallExpression, arg: KtValueArgument): CallArgument {
|
||||
fun KtValueArgument.toCallArgument(): CallArgument {
|
||||
val argExpression = getArgumentExpression()!!
|
||||
return CallArgumentImpl(KotlinPsiUtil.getTypeName(argExpression.resolveType()), this.text)
|
||||
}
|
||||
|
||||
val bindingContext = callExpression.getResolutionFacade().analyzeWithAllCompilerChecks(listOf(callExpression)).bindingContext
|
||||
val resolvedCall = callExpression.getResolvedCall(bindingContext) ?: return arg.toCallArgument()
|
||||
val parameter = resolvedCall.getParameterForArgument(arg) ?: return arg.toCallArgument()
|
||||
return CallArgumentImpl(KotlinPsiUtil.getTypeName(parameter.type), arg.text)
|
||||
}
|
||||
|
||||
private fun createQualifier(expression: PsiElement, typeAfter: GenericType): QualifierExpression {
|
||||
val parent = expression.parent as? KtDotQualifiedExpression
|
||||
?: return QualifierExpressionImpl("", TextRange.EMPTY_RANGE, typeAfter)
|
||||
val receiver = parent.receiverExpression
|
||||
|
||||
return QualifierExpressionImpl(receiver.text, receiver.textRange, typeAfter)
|
||||
}
|
||||
}
|
||||
-42
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.psi.impl
|
||||
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.KotlinPsiUtil
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.StreamCallChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.receiverType
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.resolveType
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
|
||||
class PackageBasedCallChecker(private val supportedPackage: String) : StreamCallChecker {
|
||||
override fun isIntermediateCall(expression: KtCallExpression): Boolean {
|
||||
return checkReceiverSupported(expression) && checkResultSupported(expression, true)
|
||||
}
|
||||
|
||||
override fun isTerminationCall(expression: KtCallExpression): Boolean {
|
||||
return checkReceiverSupported(expression) && checkResultSupported(expression, false)
|
||||
}
|
||||
|
||||
private fun checkResultSupported(
|
||||
expression: KtCallExpression,
|
||||
shouldSupportResult: Boolean
|
||||
): Boolean {
|
||||
val resultType = expression.resolveType()
|
||||
return shouldSupportResult == isSupportedType(resultType)
|
||||
}
|
||||
|
||||
private fun checkReceiverSupported(expression: KtCallExpression): Boolean {
|
||||
val receiverType = expression.receiverType()
|
||||
return receiverType != null && isSupportedType(receiverType)
|
||||
}
|
||||
|
||||
private fun isSupportedType(type: KotlinType): Boolean {
|
||||
val typeName = KotlinPsiUtil.getTypeWithoutTypeParameters(type)
|
||||
return StringUtil.getPackageName(typeName).startsWith(supportedPackage)
|
||||
}
|
||||
}
|
||||
-75
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.psi.impl
|
||||
|
||||
import com.intellij.debugger.streams.psi.ChainTransformer
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.StreamCallChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.previousCall
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import java.util.*
|
||||
|
||||
open class TerminatedChainBuilder(
|
||||
transformer: ChainTransformer<KtCallExpression>,
|
||||
private val callChecker: StreamCallChecker
|
||||
) : KotlinChainBuilderBase(transformer) {
|
||||
override val existenceChecker: ExistenceChecker = MyExistenceChecker()
|
||||
|
||||
override fun createChainsBuilder(): ChainBuilder = MyBuilderVisitor()
|
||||
|
||||
private inner class MyExistenceChecker : ExistenceChecker() {
|
||||
override fun visitCallExpression(expression: KtCallExpression) {
|
||||
if (callChecker.isTerminationCall(expression)) {
|
||||
fireElementFound()
|
||||
} else {
|
||||
super.visitCallExpression(expression)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class MyBuilderVisitor : ChainBuilder() {
|
||||
private val myTerminationCalls = mutableSetOf<KtCallExpression>()
|
||||
private val myPreviousCalls = mutableMapOf<KtCallExpression, KtCallExpression>()
|
||||
|
||||
override fun visitCallExpression(expression: KtCallExpression) {
|
||||
super.visitCallExpression(expression)
|
||||
if (!myPreviousCalls.containsKey(expression) && callChecker.isStreamCall(expression)) {
|
||||
updateCallTree(expression)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateCallTree(expression: KtCallExpression) {
|
||||
if (callChecker.isTerminationCall(expression)) {
|
||||
myTerminationCalls.add(expression)
|
||||
}
|
||||
|
||||
val parentCall = expression.previousCall()
|
||||
if (parentCall is KtCallExpression && callChecker.isStreamCall(parentCall)) {
|
||||
myPreviousCalls[expression] = parentCall
|
||||
updateCallTree(parentCall)
|
||||
}
|
||||
}
|
||||
|
||||
override fun chains(): List<List<KtCallExpression>> {
|
||||
val chains = ArrayList<List<KtCallExpression>>()
|
||||
for (terminationCall in myTerminationCalls) {
|
||||
val chain = ArrayList<KtCallExpression>()
|
||||
var current: KtCallExpression? = terminationCall
|
||||
while (current != null) {
|
||||
if (!callChecker.isStreamCall(current)) {
|
||||
break
|
||||
}
|
||||
chain.add(current)
|
||||
current = myPreviousCalls[current]
|
||||
}
|
||||
|
||||
chain.reverse()
|
||||
chains.add(chain)
|
||||
}
|
||||
|
||||
return chains
|
||||
}
|
||||
}
|
||||
}
|
||||
-33
@@ -1,33 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.psi.java
|
||||
|
||||
import com.intellij.debugger.streams.trace.impl.handler.type.ClassTypeImpl
|
||||
import com.intellij.debugger.streams.trace.impl.handler.type.GenericType
|
||||
import com.intellij.psi.CommonClassNames
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.CallTypeExtractor
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.KotlinPsiUtil
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinSequenceTypes
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.getImmediateSuperclassNotAny
|
||||
|
||||
class JavaStreamChainTypeExtractor : CallTypeExtractor.Base() {
|
||||
override fun extractItemsType(type: KotlinType?): GenericType {
|
||||
if (type == null) {
|
||||
return KotlinSequenceTypes.NULLABLE_ANY
|
||||
}
|
||||
|
||||
return when (KotlinPsiUtil.getTypeWithoutTypeParameters(type)) {
|
||||
CommonClassNames.JAVA_UTIL_STREAM_INT_STREAM -> KotlinSequenceTypes.INT
|
||||
CommonClassNames.JAVA_UTIL_STREAM_DOUBLE_STREAM -> KotlinSequenceTypes.DOUBLE
|
||||
CommonClassNames.JAVA_UTIL_STREAM_LONG_STREAM -> KotlinSequenceTypes.LONG
|
||||
CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM -> KotlinSequenceTypes.NULLABLE_ANY
|
||||
else -> extractItemsType(type.getImmediateSuperclassNotAny())
|
||||
}
|
||||
}
|
||||
|
||||
override fun getResultType(type: KotlinType): GenericType = ClassTypeImpl(KotlinPsiUtil.getTypeName(type))
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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.sequence.psi.java
|
||||
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.CallCheckerWithNameHeuristics
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.StreamCallChecker
|
||||
|
||||
class StandardLibraryCallChecker(nestedChecker: StreamCallChecker) : CallCheckerWithNameHeuristics(nestedChecker) {
|
||||
private companion object {
|
||||
val TERMINATION_CALLS: Set<String> = setOf(
|
||||
"forEach", "toArray", "reduce", "collect", "min", "max", "count", "sum", "anyMatch", "allMatch", "noneMatch", "findFirst",
|
||||
"findAny", "forEachOrdered", "average", "summaryStatistics"
|
||||
)
|
||||
}
|
||||
|
||||
override fun isTerminalCallName(callName: String): Boolean = TERMINATION_CALLS.contains(callName)
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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.sequence.psi.java
|
||||
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.CallCheckerWithNameHeuristics
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.StreamCallChecker
|
||||
|
||||
class StreamExCallChecker(nestedChecker: StreamCallChecker): CallCheckerWithNameHeuristics(nestedChecker) {
|
||||
private companion object {
|
||||
val TERMINATION_CALLS: Set<String> = setOf(
|
||||
"forEach", "toArray", "reduce", "collect", "min", "max", "count", "sum", "anyMatch", "allMatch", "noneMatch", "findFirst",
|
||||
"findAny", "forEachOrdered", "average", "summaryStatistics", "toList", "toSet", "toCollection", "toListAndThen", "toSetAndThen",
|
||||
"toImmutableList", "toImmutableSet", "toMap", "toSortedMap", "toNavigableMap", "toImmutableMap", "toMapAndThen", "toCustomMap",
|
||||
"partitioningBy", "partitioningTo", "groupingBy", "groupingTo", "grouping", "joining", "toFlatList", "toFlatCollection",
|
||||
"maxBy", "maxByInt", "maxByLong", "maxByDouble", "minBy", "minByInt", "minByLong", "minByDouble", "has", "indexOf", "foldLeft",
|
||||
"foldRight", "scanLeft", "scanRight", "toByteArray", "toCharArray", "toShortArray", "toBitSet", "toFloatArray", "charsToString",
|
||||
"codePointsToString", "forPairs", "forKeyValue", "asByteInputStream", "into"
|
||||
)
|
||||
}
|
||||
|
||||
override fun isTerminalCallName(callName: String): Boolean = TERMINATION_CALLS.contains(callName)
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.psi.sequence
|
||||
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.KotlinPsiUtil
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.StreamCallChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.receiverType
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.resolveType
|
||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
||||
|
||||
class SequenceCallChecker : StreamCallChecker {
|
||||
override fun isIntermediateCall(expression: KtCallExpression): Boolean {
|
||||
val receiverType = expression.receiverType() ?: return false
|
||||
return isSequenceInheritor(receiverType) && isSequenceInheritor(expression.resolveType())
|
||||
}
|
||||
|
||||
override fun isTerminationCall(expression: KtCallExpression): Boolean {
|
||||
val receiverType = expression.receiverType() ?: return false
|
||||
return isSequenceInheritor(receiverType) && !isSequenceInheritor(expression.resolveType())
|
||||
}
|
||||
|
||||
private fun isSequenceInheritor(type: KotlinType): Boolean =
|
||||
isSequenceType(type) || type.supertypes().any(this::isSequenceType)
|
||||
|
||||
private fun isSequenceType(type: KotlinType): Boolean =
|
||||
"kotlin.sequences.Sequence" == KotlinPsiUtil.getTypeWithoutTypeParameters(type)
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2010-2018 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.sequence.psi.sequence
|
||||
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.CallCheckerWithNameHeuristics
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.StreamCallChecker
|
||||
|
||||
class SequenceCallCheckerWithNameHeuristics(nestedChecker: StreamCallChecker) : CallCheckerWithNameHeuristics(nestedChecker) {
|
||||
private companion object {
|
||||
|
||||
val TERMINATION_CALLS: Set<String> = setOf(
|
||||
"all", "any", "associate", "associateBy", "associateByTo", "associateTo", "average", "chunked", "contains", "count", "distinct",
|
||||
"distinctBy", "elementAt", "elementAtOrElse", "elementAtOrNull", "find", "findLast", "first", "firstOrNull", "fold",
|
||||
"foldIndexed", "forEach", "forEachIndexed", "groupBy", "groupByTo", "indexOf", "indexOfFirst", "indexOfLast", "joinToString",
|
||||
"joinTo", "last", "lastIndexOf", "lastOrNull", "max", "maxBy", "maxWith", "min", "minBy", "minWith", "none", "partition",
|
||||
"reduce", "reduceIndexed", "single", "singleOrNull", "sum", "sumBy", "sumByDouble", "toCollection", "toHashSet", "toList",
|
||||
"toMutableList", "toMutableSet", "toSet", "toSortedSet", "unzip"
|
||||
)
|
||||
}
|
||||
|
||||
override fun isTerminalCallName(callName: String): Boolean = TERMINATION_CALLS.contains(callName)
|
||||
}
|
||||
-54
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.psi.sequence
|
||||
|
||||
import com.intellij.debugger.streams.trace.impl.handler.type.ClassTypeImpl
|
||||
import com.intellij.debugger.streams.trace.impl.handler.type.GenericType
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.CallTypeExtractor
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.psi.KotlinPsiUtil
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.dsl.KotlinSequenceTypes
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
||||
|
||||
class SequenceTypeExtractor : CallTypeExtractor.Base() {
|
||||
private companion object {
|
||||
val LOG = Logger.getInstance(SequenceTypeExtractor::class.java)
|
||||
}
|
||||
|
||||
override fun extractItemsType(type: KotlinType?): GenericType {
|
||||
if (type == null) return KotlinSequenceTypes.NULLABLE_ANY
|
||||
|
||||
return tryToFindElementType(type) ?: defaultType(type)
|
||||
}
|
||||
|
||||
override fun getResultType(type: KotlinType): GenericType {
|
||||
val typeName = KotlinPsiUtil.getTypeWithoutTypeParameters(type)
|
||||
return KotlinSequenceTypes.primitiveTypeByName(typeName)
|
||||
?: KotlinSequenceTypes.primitiveArrayByName(typeName)
|
||||
?: ClassTypeImpl(KotlinPsiUtil.getTypeName(type))
|
||||
}
|
||||
|
||||
private fun tryToFindElementType(type: KotlinType): GenericType? {
|
||||
val typeName = KotlinPsiUtil.getTypeWithoutTypeParameters(type)
|
||||
if (typeName == "kotlin.sequences.Sequence") {
|
||||
if (type.arguments.isEmpty()) return KotlinSequenceTypes.NULLABLE_ANY
|
||||
val itemsType = type.arguments.single().type
|
||||
if (itemsType.isMarkedNullable) return KotlinSequenceTypes.NULLABLE_ANY
|
||||
val primitiveType = KotlinSequenceTypes.primitiveTypeByName(KotlinPsiUtil.getTypeWithoutTypeParameters(itemsType))
|
||||
return primitiveType ?: KotlinSequenceTypes.ANY
|
||||
}
|
||||
|
||||
return type.supertypes().asSequence()
|
||||
.map(this::tryToFindElementType)
|
||||
.firstOrNull()
|
||||
}
|
||||
|
||||
private fun defaultType(type: KotlinType): GenericType {
|
||||
LOG.warn("Could not find type of items for type ${KotlinPsiUtil.getTypeName(type)}")
|
||||
return if (type.isMarkedNullable) KotlinSequenceTypes.NULLABLE_ANY else KotlinSequenceTypes.ANY
|
||||
}
|
||||
}
|
||||
-40
@@ -1,40 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.resolve
|
||||
|
||||
import com.intellij.debugger.streams.resolve.ValuesOrderResolver
|
||||
import com.intellij.debugger.streams.trace.TraceElement
|
||||
import com.intellij.debugger.streams.trace.TraceInfo
|
||||
|
||||
class ChunkedResolver : ValuesOrderResolver {
|
||||
override fun resolve(info: TraceInfo): ValuesOrderResolver.Result {
|
||||
val beforeIndex = info.valuesOrderBefore
|
||||
val afterIndex = info.valuesOrderAfter
|
||||
|
||||
val invertedOrder = mutableMapOf<Int, MutableList<Int>>()
|
||||
val beforeTimes = beforeIndex.keys.sorted().toTypedArray()
|
||||
val afterTimes = afterIndex.keys.sorted().toTypedArray()
|
||||
|
||||
var beforeIx = 0
|
||||
for (afterTime in afterTimes) {
|
||||
while (beforeIx < beforeTimes.size && beforeTimes[beforeIx] < afterTime) {
|
||||
invertedOrder.computeIfAbsent(afterTime, { _ -> mutableListOf() }).add(beforeTimes[beforeIx])
|
||||
beforeIx += 1
|
||||
}
|
||||
}
|
||||
|
||||
val direct = mutableMapOf<TraceElement, List<TraceElement>>()
|
||||
val reverse = mutableMapOf<TraceElement, List<TraceElement>>()
|
||||
for ((timeAfter, elementAfter) in afterIndex) {
|
||||
val before: List<Int> = invertedOrder[timeAfter] ?: emptyList()
|
||||
val beforeElements = before.map { beforeIndex[it]!! }
|
||||
beforeElements.forEach { direct[it] = listOf(elementAfter) }
|
||||
reverse[elementAfter] = beforeElements
|
||||
}
|
||||
|
||||
return ValuesOrderResolver.Result.of(direct, reverse)
|
||||
}
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.resolve
|
||||
|
||||
import com.intellij.debugger.streams.resolve.ValuesOrderResolver
|
||||
import com.intellij.debugger.streams.trace.TraceElement
|
||||
import com.intellij.debugger.streams.trace.TraceInfo
|
||||
|
||||
class FilteredMapResolver : ValuesOrderResolver {
|
||||
override fun resolve(info: TraceInfo): ValuesOrderResolver.Result {
|
||||
val before = info.valuesOrderBefore
|
||||
val after = info.valuesOrderAfter
|
||||
|
||||
val invertedOrder = mutableMapOf<Int, Int>()
|
||||
val beforeTimes = before.keys.sorted().toIntArray()
|
||||
val afterTimes = after.keys.sorted().toIntArray()
|
||||
var beforeIndex = 0
|
||||
for (afterTime in afterTimes) {
|
||||
while (beforeIndex < beforeTimes.size && afterTime > beforeTimes[beforeIndex]) beforeIndex += 1
|
||||
val beforeTime = beforeTimes[beforeIndex - 1]
|
||||
if (beforeTime < afterTime) {
|
||||
invertedOrder[afterTime] = beforeTime
|
||||
}
|
||||
}
|
||||
|
||||
val direct = mutableMapOf<TraceElement, List<TraceElement>>()
|
||||
val reverse = mutableMapOf<TraceElement, List<TraceElement>>()
|
||||
|
||||
for ((afterTime, beforeTime) in invertedOrder) {
|
||||
val beforeElement = before.getValue(beforeTime)
|
||||
val afterElement = after.getValue(afterTime)
|
||||
direct[beforeElement] = listOf(afterElement)
|
||||
reverse[afterElement] = listOf(beforeElement)
|
||||
}
|
||||
|
||||
return ValuesOrderResolver.Result.of(direct, reverse)
|
||||
}
|
||||
}
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.resolve
|
||||
|
||||
import com.intellij.debugger.streams.resolve.ValuesOrderResolver
|
||||
import com.intellij.debugger.streams.trace.TraceElement
|
||||
import com.intellij.debugger.streams.trace.TraceInfo
|
||||
|
||||
class WindowedResolver : ValuesOrderResolver {
|
||||
override fun resolve(info: TraceInfo): ValuesOrderResolver.Result {
|
||||
val indexBefore = info.valuesOrderBefore
|
||||
val indexAfter = info.valuesOrderAfter
|
||||
|
||||
val timesBefore = indexBefore.keys.sorted().toIntArray()
|
||||
val timesAfter = indexAfter.keys.sorted().toIntArray()
|
||||
|
||||
if (timesAfter.isEmpty()) return emptyTransitions(indexBefore)
|
||||
|
||||
val direct = mutableMapOf<TraceElement, MutableList<TraceElement>>()
|
||||
val reverse = mutableMapOf<TraceElement, List<TraceElement>>()
|
||||
|
||||
var windowStartIndex = 0
|
||||
var windowEndIndex = calcWindowSize(timesBefore, timesAfter)
|
||||
for (timeAfter in timesAfter) {
|
||||
if (windowEndIndex == timesAfter.size) {
|
||||
windowStartIndex += 1
|
||||
} else {
|
||||
while (windowEndIndex < timesBefore.size && timesBefore[windowEndIndex] < timeAfter) {
|
||||
windowStartIndex += 1
|
||||
windowEndIndex += 1
|
||||
}
|
||||
}
|
||||
|
||||
val window = (windowStartIndex until windowEndIndex).asSequence()
|
||||
.map { indexBefore[timesBefore[it]]!! }
|
||||
.toList()
|
||||
val mappedElement = indexAfter[timeAfter]!!
|
||||
window.forEach { direct.computeIfAbsent(it, { mutableListOf() }).add(mappedElement) }
|
||||
reverse[mappedElement] = window
|
||||
}
|
||||
|
||||
return ValuesOrderResolver.Result.of(direct, reverse)
|
||||
}
|
||||
|
||||
private fun calcWindowSize(before: IntArray, after: IntArray): Int {
|
||||
var size = 0
|
||||
while (size < before.size && before[size] < after[0]) size += 1
|
||||
return size
|
||||
}
|
||||
|
||||
private fun emptyTransitions(indexBefore: MutableMap<Int, TraceElement>): ValuesOrderResolver.Result {
|
||||
val direct = indexBefore.asSequence().sortedBy { it.key }.associate { it.value to emptyList<TraceElement>() }
|
||||
return ValuesOrderResolver.Result.of(direct, emptyMap())
|
||||
}
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.trace.dsl
|
||||
|
||||
import com.intellij.debugger.streams.trace.impl.handler.PeekCall
|
||||
import com.intellij.debugger.streams.trace.impl.handler.type.GenericType
|
||||
import com.intellij.debugger.streams.wrapper.IntermediateStreamCall
|
||||
|
||||
class JavaPeekCallFactory : PeekCallFactory {
|
||||
override fun createPeekCall(elementsType: GenericType, lambda: String): IntermediateStreamCall =
|
||||
PeekCall(lambda, elementsType)
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.trace.dsl
|
||||
|
||||
import com.intellij.debugger.streams.trace.dsl.ArrayVariable
|
||||
import com.intellij.debugger.streams.trace.dsl.Expression
|
||||
import com.intellij.debugger.streams.trace.dsl.VariableDeclaration
|
||||
import com.intellij.debugger.streams.trace.dsl.impl.TextExpression
|
||||
import com.intellij.debugger.streams.trace.dsl.impl.VariableImpl
|
||||
import com.intellij.debugger.streams.trace.impl.handler.type.ArrayType
|
||||
|
||||
class KotlinArrayVariable(override val type: ArrayType, override val name: String) : VariableImpl(type, name), ArrayVariable {
|
||||
override fun get(index: Expression): Expression = TextExpression("$name[${index.toCode()}]!!")
|
||||
|
||||
override fun set(index: Expression, value: Expression): Expression = TextExpression("$name[${index.toCode()}] = ${value.toCode()}")
|
||||
|
||||
override fun defaultDeclaration(size: Expression): VariableDeclaration =
|
||||
KotlinVariableDeclaration(this, false, type.sizedDeclaration(size.toCode()))
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.trace.dsl
|
||||
|
||||
import com.intellij.debugger.streams.trace.dsl.Expression
|
||||
import com.intellij.debugger.streams.trace.dsl.Variable
|
||||
import com.intellij.debugger.streams.trace.dsl.impl.AssignmentStatement
|
||||
|
||||
class KotlinAssignmentStatement(override val variable: Variable, override val expression: Expression) : AssignmentStatement {
|
||||
override fun toCode(indent: Int): String = "${variable.toCode()} = ${expression.toCode()}".withIndent(indent)
|
||||
}
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.trace.dsl
|
||||
|
||||
import com.intellij.debugger.streams.trace.dsl.Expression
|
||||
import com.intellij.debugger.streams.trace.dsl.StatementFactory
|
||||
import com.intellij.debugger.streams.trace.dsl.impl.LineSeparatedCodeBlock
|
||||
|
||||
open class KotlinCodeBlock(statementFactory: StatementFactory) : LineSeparatedCodeBlock(statementFactory) {
|
||||
override fun doReturn(expression: Expression) = addStatement(expression)
|
||||
}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.trace.dsl
|
||||
|
||||
import com.intellij.debugger.streams.trace.impl.handler.type.GenericType
|
||||
import com.intellij.debugger.streams.wrapper.IntermediateStreamCall
|
||||
import org.jetbrains.kotlin.idea.debugger.sequence.trace.impl.handler.OnEachCall
|
||||
|
||||
class KotlinCollectionsPeekCallFactory : PeekCallFactory {
|
||||
override fun createPeekCall(elementsType: GenericType, lambda: String): IntermediateStreamCall =
|
||||
OnEachCall(elementsType, lambda)
|
||||
}
|
||||
-22
@@ -1,22 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.trace.dsl
|
||||
|
||||
import com.intellij.debugger.streams.trace.dsl.Convertable
|
||||
import com.intellij.debugger.streams.trace.dsl.Expression
|
||||
import com.intellij.debugger.streams.trace.dsl.ForLoopBody
|
||||
import com.intellij.debugger.streams.trace.dsl.Variable
|
||||
|
||||
class KotlinForEachLoop(
|
||||
private val iterateVariable: Variable,
|
||||
private val collection: Expression,
|
||||
private val loopBody: ForLoopBody
|
||||
) : Convertable {
|
||||
override fun toCode(indent: Int): String =
|
||||
"for (${iterateVariable.name} in ${collection.toCode()}) {\n".withIndent(indent) +
|
||||
loopBody.toCode(indent + 1) +
|
||||
"}".withIndent(indent)
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2017 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.sequence.trace.dsl
|
||||
|
||||
import com.intellij.debugger.streams.trace.dsl.Convertable
|
||||
import com.intellij.debugger.streams.trace.dsl.Expression
|
||||
import com.intellij.debugger.streams.trace.dsl.ForLoopBody
|
||||
import com.intellij.debugger.streams.trace.dsl.VariableDeclaration
|
||||
|
||||
class KotlinForLoop(
|
||||
private val initialization: VariableDeclaration,
|
||||
private val condition: Expression,
|
||||
private val afterThought: Expression,
|
||||
private val loopBody: ForLoopBody
|
||||
) : Convertable {
|
||||
override fun toCode(indent: Int): String =
|
||||
initialization.toCode(indent) + "\n" +
|
||||
"while (${condition.toCode()}) {\n".withIndent(indent) +
|
||||
loopBody.toCode(indent + 1) +
|
||||
afterThought.toCode(indent + 1) + "\n" +
|
||||
"}".withIndent(indent)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user