Move out JVM debugger functionality

This commit is contained in:
Yan Zhulanow
2019-04-05 15:10:14 +03:00
parent 5843336d42
commit ae7550c5af
318 changed files with 1068 additions and 723 deletions
-1
View File
@@ -20,7 +20,6 @@ dependencies {
compileOnly(intellijPluginDep("copyright"))
compileOnly(intellijPluginDep("properties"))
compileOnly(intellijPluginDep("java-i18n"))
compileOnly(intellijPluginDep("stream-debugger"))
}
@@ -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)"
}
@@ -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
}
}
}
@@ -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>
}
}
@@ -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
)
}
@@ -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>?
}
@@ -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)
}
}
@@ -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>
@@ -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
}
}
}
@@ -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) }
}
@@ -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);
}
}
@@ -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()
}
@@ -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)
}
}
@@ -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>
@@ -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());
}
}
@@ -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)
@@ -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()
}
}
@@ -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()
}
}
}
@@ -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
}
@@ -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
}
}
@@ -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;
}
}
@@ -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
}
}
@@ -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
}
}
@@ -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>
@@ -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();
}
}
}
@@ -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
@@ -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" }
}
@@ -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
}
}
@@ -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")
}
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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
)
}
}
@@ -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)
@@ -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
}
}
}
@@ -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
}
}
}
@@ -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?
}
@@ -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
}
}
@@ -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
}
}
@@ -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
}
@@ -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()
}
}
}
}
@@ -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('/', '.')
@@ -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
}
}
@@ -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 }
@@ -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)
}
@@ -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)
@@ -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
}
}
@@ -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()
}
}
@@ -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() }
}
}
@@ -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()
}
}
@@ -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
}
}
@@ -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()
}
}
@@ -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))
}
@@ -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))
}
@@ -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
}
@@ -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
}
@@ -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()
)
}
@@ -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
}
@@ -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
@@ -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)
}
@@ -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)
}
@@ -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))
}
@@ -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
}
@@ -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) {}
}
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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
}
}
}
@@ -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))
}
@@ -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)
}
@@ -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)
}
@@ -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)
}
@@ -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)
}
@@ -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
}
}
@@ -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)
}
}
@@ -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)
}
}
@@ -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())
}
}
@@ -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)
}
@@ -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()))
}
@@ -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)
}
@@ -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)
}
@@ -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)
}
@@ -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)
}
@@ -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