Extracted Kotlin.JVM IDE into separate module

This change is required to have possibility to build plugin against
minor IDEs, which don't have Java. So we want to extract idea-jvm
This commit is contained in:
Alexander Podkhalyuzin
2017-12-21 15:34:44 +03:00
parent 2bb02beb84
commit 8203d1c3fe
195 changed files with 862 additions and 337 deletions
+3 -1
View File
@@ -6,7 +6,7 @@ dependencies {
compile(project(":compiler:light-classes"))
compile(project(":compiler:frontend.java"))
compileOnly(ideaSdkDeps("openapi", "idea"))
compileOnly(ideaSdkDeps("openapi", "idea", "velocity", "boot", "gson", "swingx-core", "jsr305", "forms_rt"))
compile(ideaPluginDeps("idea-junit", plugin = "junit"))
compile(ideaPluginDeps("testng", "testng-plugin", plugin = "testng"))
@@ -20,3 +20,5 @@ sourceSets {
"main" { projectDefault() }
"test" { none() }
}
configureInstrumentation()
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports unresolved references to .properties file keys and resource bundles in Kotlin files.
</body>
</html>
@@ -0,0 +1,46 @@
/*
* 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;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ApplicationComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.debugger.filter.DebuggerFiltersUtilKt;
public class JvmPluginStartupComponent implements ApplicationComponent {
public static JvmPluginStartupComponent getInstance() {
return ApplicationManager.getApplication().getComponent(JvmPluginStartupComponent.class);
}
@Override
@NotNull
public String getComponentName() {
return JvmPluginStartupComponent.class.getName();
}
@Override
public void initComponent() {
if (ApplicationManager.getApplication().isUnitTestMode()) {
ThreadTrackerPatcherForTeamCityTesting.INSTANCE.patchThreadTracker();
}
DebuggerFiltersUtilKt.addKotlinStdlibDebugFilterIfNeeded();
}
@Override
public void disposeComponent() {}
}
@@ -0,0 +1,88 @@
/*
* 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
import com.intellij.debugger.NoDataException
import com.intellij.debugger.engine.ExtraSteppingFilter
import com.intellij.debugger.engine.SuspendContext
import com.intellij.debugger.settings.DebuggerSettings
import com.sun.jdi.Location
import com.sun.jdi.request.StepRequest
import org.jetbrains.kotlin.idea.debugger.KotlinPositionManager
import org.jetbrains.kotlin.idea.debugger.isOnSuspendReturnOrReenter
import org.jetbrains.kotlin.idea.debugger.isOneLineMethod
import org.jetbrains.kotlin.idea.util.application.runReadAction
class KotlinExtraSteppingFilter : ExtraSteppingFilter {
override fun isApplicable(context: SuspendContext?): Boolean {
if (context == null) {
return false
}
val debugProcess = context.debugProcess ?: return false
val positionManager = KotlinPositionManager(debugProcess)
val location = context.frameProxy?.location() ?: return false
return runReadAction {
shouldFilter(positionManager, location)
}
}
private fun shouldFilter(positionManager: KotlinPositionManager, location: Location): Boolean {
val defaultStrata = location.declaringType()?.defaultStratum()
if ("Kotlin" != defaultStrata) {
return false
}
val sourcePosition =
try {
positionManager.getSourcePosition(location)
}
catch(e: NoDataException) {
return false
} ?: return false
if (isOnSuspendReturnOrReenter(location) && !isOneLineMethod(location)) {
return true
}
val settings = DebuggerSettings.getInstance()
if (settings.TRACING_FILTERS_ENABLED) {
val classNames = positionManager.originalClassNamesForPosition(sourcePosition).map { it.replace('/', '.') }
if (classNames.isEmpty()) {
return false
}
for (className in classNames) {
for (filter in settings.steppingFilters) {
if (filter.isEnabled) {
if (filter.matches(className)) {
return true
}
}
}
}
}
return false
}
override fun getStepRequestDepth(context: SuspendContext?): Int {
return StepRequest.STEP_INTO
}
}
@@ -0,0 +1,77 @@
/*
* 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
import com.intellij.concurrency.IdeaForkJoinWorkerThreadFactory
import com.intellij.testFramework.ThreadTracker
import java.lang.reflect.Modifier
import java.util.concurrent.ForkJoinPool
import java.util.concurrent.atomic.AtomicBoolean
/*
Workaround for ThreadTracker.checkLeak() failures on TeamCity.
Currently TeamCity runs tests in classloader without boot.jar where IdeaForkJoinWorkerThreadFactory is defined. That
makes ForkJoinPool.commonPool() silently ignores java.util.concurrent.ForkJoinPool.common.threadFactory
(because of java.lang.ClassNotFoundException) option during factory initialization.
Standard names for ForkJoinPool threads doesn't pass ThreadTracker.checkLeak() check and that ruins tests constantly.
As it's allowed to reorder tests on TeamCity and any test can be first at some point, this patch should be applied at
some common place.
*/
object ThreadTrackerPatcherForTeamCityTesting {
private val patched = AtomicBoolean(false)
fun patchThreadTracker() {
if (patched.get()) return
patched.compareAndSet(false, true)
IdeaForkJoinWorkerThreadFactory.setupForkJoinCommonPool()
// Check setup was successful and patching isn't needed
val commonPoolFactoryName = ForkJoinPool.commonPool().factory::class.java.name
if (commonPoolFactoryName == IdeaForkJoinWorkerThreadFactory::class.java.name) {
return
}
try {
val wellKnownOffendersField = try {
ThreadTracker::class.java.getDeclaredField("wellKnownOffenders")
}
catch (communityPropertyNotFoundEx: NoSuchFieldException) {
ThreadTracker::class.java.declaredFields.single {
Modifier.isStatic(it.modifiers) && MutableSet::class.java.isAssignableFrom(it.type)
}
}
wellKnownOffendersField.isAccessible = true
@Suppress("UNCHECKED_CAST")
val wellKnownOffenders = wellKnownOffendersField.get(null) as MutableSet<String>
wellKnownOffenders.add("ForkJoinPool.commonPool-worker-")
println("Patching ThreadTracker was successful")
}
catch (e: NoSuchFieldException) {
println("Patching ThreadTracker failed: " + e)
}
catch (e: IllegalAccessException) {
println("Patching ThreadTracker failed: " + e)
}
}
}
@@ -0,0 +1,74 @@
/*
* 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.actions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.Messages
import com.intellij.util.PlatformUtils
import org.jetbrains.kotlin.idea.KotlinPluginUtil
import org.jetbrains.kotlin.idea.configuration.*
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
import org.jetbrains.kotlin.js.resolve.JsPlatform
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
abstract class ConfigureKotlinInProjectAction : AnAction() {
abstract fun getApplicableConfigurators(project: Project): Collection<KotlinProjectConfigurator>
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val modules = getConfigurableModules(project)
if (modules.all(::isModuleConfigured)) {
Messages.showInfoMessage("All modules with Kotlin files are configured", e.presentation.text!!)
return
}
val configurators = getApplicableConfigurators(project)
when {
configurators.size == 1 -> configurators.first().configure(project, emptyList())
configurators.isEmpty() -> Messages.showErrorDialog("There aren't configurators available", e.presentation.text!!)
else -> {
val configuratorsPopup = KotlinSetupEnvironmentNotificationProvider.createConfiguratorsPopup(project, configurators.toList())
configuratorsPopup.showInBestPositionFor(e.dataContext)
}
}
}
}
class ConfigureKotlinJsInProjectAction: ConfigureKotlinInProjectAction() {
override fun getApplicableConfigurators(project: Project) = getAbleToRunConfigurators(project).filter {
it.targetPlatform == JsPlatform
}
override fun update(e: AnActionEvent) {
val project = e.project
if (!PlatformUtils.isIntelliJ() && (project == null || project.allModules().all(KotlinPluginUtil::isAndroidGradleModule))) {
e.presentation.isEnabledAndVisible = false
}
}
}
class ConfigureKotlinJavaInProjectAction: ConfigureKotlinInProjectAction() {
override fun getApplicableConfigurators(project: Project) = getAbleToRunConfigurators(project).filter {
it.targetPlatform is JvmPlatform
}
}
@@ -0,0 +1,52 @@
/*
* 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.actions
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.wm.ToolWindowAnchor
import com.intellij.openapi.wm.ToolWindowManager
import com.intellij.ui.content.ContentFactory
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.internal.KotlinBytecodeToolWindow
class ShowKotlinBytecodeAction : AnAction() {
val TOOLWINDOW_ID = "Kotlin Bytecode"
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val toolWindowManager = ToolWindowManager.getInstance(project)
var toolWindow = toolWindowManager.getToolWindow(TOOLWINDOW_ID)
if (toolWindow == null) {
toolWindow = toolWindowManager.registerToolWindow("Kotlin Bytecode", false, ToolWindowAnchor.RIGHT)
toolWindow.icon = KotlinIcons.SMALL_LOGO_13
val contentManager = toolWindow.contentManager
val contentFactory = ContentFactory.SERVICE.getInstance()
contentManager.addContent(contentFactory.createContent(KotlinBytecodeToolWindow(project, toolWindow), "", false))
}
toolWindow.activate(null)
}
override fun update(e: AnActionEvent) {
val file = e.getData(CommonDataKeys.PSI_FILE)
e.presentation.isEnabled = e.project != null && file?.fileType == KotlinFileType.INSTANCE
}
}
@@ -0,0 +1,115 @@
/*
* 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.compiler;
import com.intellij.diagnostic.PluginException;
import com.intellij.ide.plugins.PluginManagerCore;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.compiler.*;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.KotlinFileType;
import org.jetbrains.kotlin.js.JavaScript;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.Set;
import static org.jetbrains.kotlin.config.CompilerRunnerConstants.INTERNAL_ERROR_PREFIX;
import static org.jetbrains.kotlin.config.CompilerRunnerConstants.KOTLIN_COMPILER_NAME;
public class KotlinCompilerManager extends AbstractProjectComponent {
private static final Logger LOG = Logger.getInstance(KotlinCompilerManager.class);
// Comes from external make
private static final String PREFIX_WITH_COMPILER_NAME = KOTLIN_COMPILER_NAME + ": " + INTERNAL_ERROR_PREFIX;
private static final Set<String> FILE_EXTS_WHICH_NEEDS_REFRESH = ContainerUtil.immutableSet(JavaScript.DOT_EXTENSION, ".map");
public KotlinCompilerManager(Project project, CompilerManager manager) {
super(project);
manager.addCompilableFileType(KotlinFileType.INSTANCE);
manager.addCompilationStatusListener(new CompilationStatusListener() {
@Override
public void compilationFinished(boolean aborted, int errors, int warnings, CompileContext compileContext) {
for (CompilerMessage error : compileContext.getMessages(CompilerMessageCategory.ERROR)) {
String message = error.getMessage();
if (message.startsWith(INTERNAL_ERROR_PREFIX) || message.startsWith(PREFIX_WITH_COMPILER_NAME)) {
LOG.error(new KotlinCompilerException(message));
}
}
}
@Override
public void fileGenerated(String outputRoot, String relativePath) {
if (ApplicationManager.getApplication().isUnitTestMode()) return;
String ext = FileUtilRt.getExtension(relativePath).toLowerCase();
if (FILE_EXTS_WHICH_NEEDS_REFRESH.contains(ext)) {
String outFile = outputRoot + "/" + relativePath;
VirtualFile virtualFile = LocalFileSystem.getInstance().findFileByPath(outFile);
assert virtualFile != null : "Virtual file not found for generated file path: " + outFile;
virtualFile.refresh(/*async =*/ false, /*recursive =*/ false);
}
}
}, project);
}
// Extending PluginException ensures that Exception Analyzer recognizes this as a Kotlin exception
private static class KotlinCompilerException extends PluginException {
private final String text;
public KotlinCompilerException(String text) {
super("", PluginManagerCore.getPluginByClassName(KotlinCompilerManager.class.getName()));
this.text = text;
}
@Override
public void printStackTrace(PrintWriter s) {
s.print(text);
}
@Override
public void printStackTrace(@NotNull PrintStream s) {
s.print(text);
}
@NotNull
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
@Override
public StackTraceElement[] getStackTrace() {
LOG.error("Somebody called getStackTrace() on KotlinCompilerException");
// Return some stack trace that originates in Kotlin
return new UnsupportedOperationException().getStackTrace();
}
@Override
public String getMessage() {
return "<Exception from standalone Kotlin compiler>";
}
}
}
@@ -0,0 +1,26 @@
/*
* 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.compiler.configuration
import com.intellij.compiler.server.BuildManager
import com.intellij.openapi.project.Project
class ClearBuildManagerState : ClearBuildStateExtension() {
override fun clearState(project: Project) {
BuildManager.getInstance().clearState(project);
}
}
@@ -0,0 +1,41 @@
/*
* 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.compiler.configuration
import com.intellij.compiler.server.BuildProcessParametersProvider
import org.jetbrains.kotlin.idea.PluginStartupComponent
class KotlinBuildProcessParametersProvider(private val compilerWorkspaceSettings: KotlinCompilerWorkspaceSettings,
private val kotlinPluginStartupComponent: PluginStartupComponent
): BuildProcessParametersProvider() {
override fun getVMArguments(): MutableList<String> {
val res = arrayListOf<String>()
if (compilerWorkspaceSettings.preciseIncrementalEnabled) {
res.add("-Dkotlin.incremental.compilation=true")
}
if (compilerWorkspaceSettings.enableDaemon) {
res.add("-Dkotlin.daemon.enabled")
}
kotlinPluginStartupComponent.aliveFlagPath.let {
if (!it.isBlank()) {
// TODO: consider taking the property name from compiler/daemon/common (check whether dependency will be not too heavy)
res.add("-Dkotlin.daemon.client.alive.path=\"$it\"")
}
}
return res
}
}
@@ -0,0 +1,298 @@
/*
* 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.configuration
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.JavaSdkVersion
import com.intellij.openapi.roots.DependencyScope
import com.intellij.openapi.roots.LibraryOrderEntry
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.impl.libraries.LibraryEx
import com.intellij.openapi.roots.libraries.PersistentLibraryKind
import com.intellij.openapi.util.Computable
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiJavaModule
import com.intellij.psi.search.DelegatingGlobalSearchScope
import com.intellij.psi.search.FileTypeIndex
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.framework.JSLibraryKind
import org.jetbrains.kotlin.idea.quickfix.KotlinAddRequiredModuleFix
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.findFirstPsiJavaModule
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
import org.jetbrains.kotlin.resolve.jvm.modules.KOTLIN_STDLIB_MODULE_NAME
import org.jetbrains.kotlin.utils.ifEmpty
data class RepositoryDescription(val id: String, val name: String, val url: String, val bintrayUrl: String?, val isSnapshot: Boolean)
val SNAPSHOT_REPOSITORY = RepositoryDescription(
"sonatype.oss.snapshots",
"Sonatype OSS Snapshot Repository",
"http://oss.sonatype.org/content/repositories/snapshots",
null,
isSnapshot = true)
val EAP_REPOSITORY = RepositoryDescription(
"bintray.kotlin.eap",
"Bintray Kotlin EAP Repository",
"http://dl.bintray.com/kotlin/kotlin-eap",
"https://bintray.com/kotlin/kotlin-eap/kotlin/",
isSnapshot = false)
val EAP_11_REPOSITORY = RepositoryDescription(
"bintray.kotlin.eap",
"Bintray Kotlin 1.1 EAP Repository",
"http://dl.bintray.com/kotlin/kotlin-eap-1.1",
"https://bintray.com/kotlin/kotlin-eap-1.1/kotlin/",
isSnapshot = false)
val EAP_12_REPOSITORY = RepositoryDescription(
"bintray.kotlin.eap",
"Bintray Kotlin 1.2 EAP Repository",
"http://dl.bintray.com/kotlin/kotlin-eap-1.2",
"https://bintray.com/kotlin/kotlin-eap-1.2/kotlin/",
isSnapshot = false)
val MAVEN_CENTRAL = "mavenCentral()"
val JCENTER = "jcenter()"
val KOTLIN_GROUP_ID = "org.jetbrains.kotlin"
fun isRepositoryConfigured(repositoriesBlockText: String): Boolean =
repositoriesBlockText.contains(MAVEN_CENTRAL) || repositoriesBlockText.contains(JCENTER)
fun DependencyScope.toGradleCompileScope(isAndroidModule: Boolean) = when (this) {
DependencyScope.COMPILE -> "compile"
// TODO: We should add testCompile or androidTestCompile
DependencyScope.TEST -> if (isAndroidModule) "compile" else "testCompile"
DependencyScope.RUNTIME -> "runtime"
DependencyScope.PROVIDED -> "compile"
else -> "compile"
}
fun RepositoryDescription.toGroovyRepositorySnippet() = "maven {\n url '$url'\n}"
fun RepositoryDescription.toKotlinRepositorySnippet() = "maven {\n setUrl(\"$url\")\n}"
fun getRepositoryForVersion(version: String): RepositoryDescription? = when {
isSnapshot(version) -> SNAPSHOT_REPOSITORY
useEapRepository(2, version) -> EAP_12_REPOSITORY
useEapRepository(1, version) -> EAP_11_REPOSITORY
isEap(version) -> EAP_REPOSITORY
else -> null
}
fun isModuleConfigured(moduleSourceRootGroup: ModuleSourceRootGroup): Boolean {
return allConfigurators().any {
it.getStatus(moduleSourceRootGroup) == ConfigureKotlinStatus.CONFIGURED
}
}
fun getModulesWithKotlinFiles(project: Project): Collection<Module> {
if (!runReadAction {
!project.isDisposed && FileTypeIndex.containsFileOfType (KotlinFileType.INSTANCE, GlobalSearchScope.projectScope(project))
}) {
return emptyList()
}
return project.allModules()
.filter { module ->
runReadAction {
!project.isDisposed && FileTypeIndex.containsFileOfType(KotlinFileType.INSTANCE, module.getModuleScope(true))
}
}
}
fun getConfigurableModulesWithKotlinFiles(project: Project): List<ModuleSourceRootGroup> {
val modules = getModulesWithKotlinFiles(project)
if (modules.isEmpty()) return emptyList()
return ModuleSourceRootMap(project).groupByBaseModules(modules)
}
fun showConfigureKotlinNotificationIfNeeded(module: Module) {
val moduleGroup = module.toModuleGroup()
if (isNotConfiguredNotificationRequired(moduleGroup)) return
ConfigureKotlinNotificationManager.notify(module.project)
}
fun showConfigureKotlinNotificationIfNeeded(project: Project, excludeModules: List<Module> = emptyList()) {
val notificationString = DumbService.getInstance(project).runReadActionInSmartMode(Computable {
val modules = getConfigurableModulesWithKotlinFiles(project).exclude(excludeModules)
if (modules.all(::isNotConfiguredNotificationRequired))
null
else
ConfigureKotlinNotification.getNotificationString(project, excludeModules)
})
if (notificationString != null) {
ApplicationManager.getApplication().invokeLater {
ConfigureKotlinNotificationManager.notify(project, ConfigureKotlinNotification(project, excludeModules, notificationString))
}
}
}
fun isNotConfiguredNotificationRequired(moduleGroup: ModuleSourceRootGroup): Boolean {
return !SuppressNotificationState.isKotlinNotConfiguredSuppressed(moduleGroup) && isModuleConfigured(moduleGroup)
}
fun getAbleToRunConfigurators(project: Project): Collection<KotlinProjectConfigurator> {
val modules = getConfigurableModules(project)
return allConfigurators().filter { configurator ->
modules.any { configurator.getStatus(it) == ConfigureKotlinStatus.CAN_BE_CONFIGURED }
}
}
fun getConfigurableModules(project: Project): List<ModuleSourceRootGroup> {
return getConfigurableModulesWithKotlinFiles(project).ifEmpty {
ModuleSourceRootMap(project).groupByBaseModules(project.allModules())
}
}
fun getAbleToRunConfigurators(module: Module): Collection<KotlinProjectConfigurator> {
val moduleGroup = module.toModuleGroup()
return allConfigurators().filter {
it.getStatus(moduleGroup) == ConfigureKotlinStatus.CAN_BE_CONFIGURED
}
}
fun getConfiguratorByName(name: String): KotlinProjectConfigurator? {
return allConfigurators().firstOrNull { it.name == name }
}
fun allConfigurators() = Extensions.getExtensions(KotlinProjectConfigurator.EP_NAME)
fun getCanBeConfiguredModules(project: Project, configurator: KotlinProjectConfigurator): List<Module> {
return ModuleSourceRootMap(project).groupByBaseModules(project.allModules())
.filter { configurator.canConfigure(it) }
.map { it.baseModule }
}
private fun KotlinProjectConfigurator.canConfigure(moduleSourceRootGroup: ModuleSourceRootGroup) =
getStatus(moduleSourceRootGroup) == ConfigureKotlinStatus.CAN_BE_CONFIGURED &&
(allConfigurators().toList() - this).none { it.getStatus(moduleSourceRootGroup) == ConfigureKotlinStatus.CONFIGURED }
fun getCanBeConfiguredModulesWithKotlinFiles(project: Project, configurator: KotlinProjectConfigurator): List<Module> {
val modules = getConfigurableModulesWithKotlinFiles(project)
return modules.filter { configurator.getStatus(it) == ConfigureKotlinStatus.CAN_BE_CONFIGURED }.map { it.baseModule }
}
fun getCanBeConfiguredModulesWithKotlinFiles(project: Project, excludeModules: Collection<Module> = emptyList()): Collection<Module> {
val modulesWithKotlinFiles = getConfigurableModulesWithKotlinFiles(project).exclude(excludeModules)
val configurators = allConfigurators()
return modulesWithKotlinFiles.filter { moduleSourceRootGroup ->
configurators.any { it.getStatus(moduleSourceRootGroup) == ConfigureKotlinStatus.CAN_BE_CONFIGURED }
}.map { it.baseModule }
}
fun findApplicableConfigurator(module: Module): KotlinProjectConfigurator {
val moduleGroup = module.toModuleGroup()
return allConfigurators().find { it.getStatus(moduleGroup) != ConfigureKotlinStatus.NON_APPLICABLE }
?: KotlinJavaModuleConfigurator.instance
}
fun hasAnyKotlinRuntimeInScope(module: Module): Boolean {
return runReadAction {
val scope = module.getModuleWithDependenciesAndLibrariesScope(hasKotlinFilesOnlyInTests(module))
getKotlinJvmRuntimeMarkerClass(module.project, scope) != null ||
hasKotlinJsKjsmFile(module.project, LibraryKindSearchScope(module, scope, JSLibraryKind) ) ||
hasKotlinCommonRuntimeInScope(scope)
}
}
fun hasKotlinJvmRuntimeInScope(module: Module): Boolean {
return runReadAction {
val scope = module.getModuleWithDependenciesAndLibrariesScope(hasKotlinFilesOnlyInTests(module))
getKotlinJvmRuntimeMarkerClass(module.project, scope) != null
}
}
fun hasKotlinJsRuntimeInScope(module: Module): Boolean {
return runReadAction {
val scope = module.getModuleWithDependenciesAndLibrariesScope(hasKotlinFilesOnlyInTests(module))
hasKotlinJsKjsmFile(module.project, LibraryKindSearchScope(module, scope, JSLibraryKind))
}
}
fun hasKotlinCommonRuntimeInScope(scope: GlobalSearchScope): Boolean {
return IDEVirtualFileFinder(scope).hasMetadataPackage(KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME)
}
fun hasKotlinFilesOnlyInTests(module: Module): Boolean {
return !hasKotlinFilesInSources(module) && FileTypeIndex.containsFileOfType(KotlinFileType.INSTANCE, module.getModuleScope(true))
}
fun hasKotlinFilesInSources(module: Module): Boolean {
return FileTypeIndex.containsFileOfType(KotlinFileType.INSTANCE, module.getModuleScope(false))
}
fun isEap(version: String): Boolean {
return version.contains("rc") || version.contains("eap")
}
fun useEapRepository(minorKotlinVersion: Int, version: String): Boolean {
return Regex("1\\.$minorKotlinVersion(\\.\\d\\d?)?-[A-Za-z][A-Za-z0-9-]*").matches(version) &&
!version.startsWith("1.$minorKotlinVersion.0-dev")
}
private class LibraryKindSearchScope(val module: Module,
val baseScope: GlobalSearchScope,
val libraryKind: PersistentLibraryKind<*>
) : DelegatingGlobalSearchScope(baseScope) {
override fun contains(file: VirtualFile): Boolean {
if (!super.contains(file)) return false
val orderEntry = ModuleRootManager.getInstance(module).fileIndex.getOrderEntryForFile(file)
if (orderEntry is LibraryOrderEntry) {
return (orderEntry.library as LibraryEx).kind == libraryKind
}
return true
}
}
fun addStdlibToJavaModuleInfo(module: Module, collector: NotificationMessageCollector): Boolean {
if (module.sdk?.version?.isAtLeast(JavaSdkVersion.JDK_1_9) != true) return false
val project = module.project
val javaModule: PsiJavaModule = findFirstPsiJavaModule(module) ?: return false
val success = WriteCommandAction.runWriteCommandAction(project, Computable<Boolean> {
KotlinAddRequiredModuleFix.addModuleRequirement(javaModule, KOTLIN_STDLIB_MODULE_NAME)
})
if (!success) return false
collector.addMessage("Added $KOTLIN_STDLIB_MODULE_NAME requirement to module-info in ${module.name}")
return true
}
@@ -0,0 +1,89 @@
/*
* 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.configuration
import com.intellij.notification.Notification
import com.intellij.notification.NotificationsManager
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.configuration.ui.notifications.ConfigureKotlinNotification
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.reflect.KClass
object ConfigureKotlinNotificationManager: KotlinSingleNotificationManager<ConfigureKotlinNotification> {
fun notify(project: Project, excludeModules: List<Module> = emptyList()) {
val notificationString = ConfigureKotlinNotification.getNotificationString(project, excludeModules)
if (notificationString != null) {
notify(project, ConfigureKotlinNotification(project, excludeModules, notificationString))
}
}
fun getVisibleNotifications(project: Project): Array<out ConfigureKotlinNotification> {
return NotificationsManager.getNotificationsManager().getNotificationsOfType(ConfigureKotlinNotification::class.java, project)
}
fun expireOldNotifications(project: Project) {
expireOldNotifications(project, ConfigureKotlinNotification::class)
}
}
interface KotlinSingleNotificationManager<in T: Notification> {
fun notify(project: Project, notification: T) {
if (!expireOldNotifications(project, notification::class, notification)) {
notification.notify(project)
}
}
fun expireOldNotifications(project: Project, notificationClass: KClass<out T>, notification: T? = null): Boolean {
val notificationsManager = NotificationsManager.getNotificationsManager()
var isNotificationExists = false
val notifications = notificationsManager.getNotificationsOfType(notificationClass.java, project)
for (oldNotification in notifications) {
if (oldNotification == notification) {
isNotificationExists = true
}
else {
oldNotification?.expire()
}
}
return isNotificationExists
}
}
private val checkInProgress = AtomicBoolean(false)
fun checkHideNonConfiguredNotifications(project: Project) {
if (checkInProgress.get() || ConfigureKotlinNotificationManager.getVisibleNotifications(project).isEmpty()) return
ApplicationManager.getApplication().executeOnPooledThread {
if (!checkInProgress.compareAndSet(false, true)) return@executeOnPooledThread
DumbService.getInstance(project).waitForSmartMode()
if (getConfigurableModulesWithKotlinFiles(project).all(::isNotConfiguredNotificationRequired)) {
ApplicationManager.getApplication().invokeLater {
ConfigureKotlinNotificationManager.expireOldNotifications(project)
checkInProgress.set(false)
}
}
else {
checkInProgress.set(false)
}
}
}
@@ -0,0 +1,40 @@
/*
* 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.configuration
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType
import org.jetbrains.kotlin.idea.configuration.ui.KotlinConfigurationCheckerComponent
class KotlinExternalSystemSyncListener : ExternalSystemTaskNotificationListenerAdapter() {
override fun onStart(id: ExternalSystemTaskId) {
if (id.type == ExternalSystemTaskType.RESOLVE_PROJECT) {
id.findProject()?.let { project ->
KotlinConfigurationCheckerComponent.getInstance(project).syncStarted()
}
}
}
override fun onEnd(id: ExternalSystemTaskId) {
if (id.type == ExternalSystemTaskType.RESOLVE_PROJECT) {
id.findProject()?.let { project ->
KotlinConfigurationCheckerComponent.getInstance(project).syncDone()
}
}
}
}
@@ -0,0 +1,146 @@
/*
* 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.configuration
import com.intellij.openapi.extensions.Extensions
import com.intellij.openapi.externalSystem.service.project.IdeModifiableModelsProviderImpl
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.JavaSdkVersion
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.LibraryOrderEntry
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.impl.libraries.LibraryEx
import com.intellij.openapi.roots.libraries.Library
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.TargetPlatformKind
import org.jetbrains.kotlin.idea.compiler.configuration.Kotlin2JvmCompilerArgumentsHolder
import org.jetbrains.kotlin.idea.facet.getOrCreateFacet
import org.jetbrains.kotlin.idea.facet.initializeIfNeeded
import org.jetbrains.kotlin.idea.framework.JavaRuntimeLibraryDescription
import org.jetbrains.kotlin.idea.framework.JsLibraryStdDetectionUtil
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.LibraryJarDescriptor
import org.jetbrains.kotlin.idea.versions.isKotlinJavaRuntime
import org.jetbrains.kotlin.resolve.TargetPlatform
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
open class KotlinJavaModuleConfigurator protected constructor() : KotlinWithLibraryConfigurator() {
override fun isApplicable(module: Module): Boolean {
return super.isApplicable(module) && !hasBrokenJsRuntime(module)
}
override fun isConfigured(module: Module): Boolean {
return hasKotlinJvmRuntimeInScope(module)
}
override val libraryName: String
get() = JavaRuntimeLibraryDescription.LIBRARY_NAME
override val dialogTitle: String
get() = JavaRuntimeLibraryDescription.DIALOG_TITLE
override val libraryCaption: String
get() = JavaRuntimeLibraryDescription.LIBRARY_CAPTION
override val messageForOverrideDialog: String
get() = JavaRuntimeLibraryDescription.JAVA_RUNTIME_LIBRARY_CREATION
override val presentableText: String
get() = "Java"
override val name: String
get() = NAME
override val targetPlatform: TargetPlatform
get() = JvmPlatform
override fun getLibraryJarDescriptors(sdk: Sdk?): List<LibraryJarDescriptor> {
var result = listOf(
LibraryJarDescriptor.RUNTIME_JAR,
LibraryJarDescriptor.RUNTIME_SRC_JAR,
LibraryJarDescriptor.REFLECT_JAR,
LibraryJarDescriptor.REFLECT_SRC_JAR,
LibraryJarDescriptor.TEST_JAR,
LibraryJarDescriptor.TEST_SRC_JAR)
val sdkVersion = sdk?.version ?: return result
if (sdkVersion.isAtLeast(JavaSdkVersion.JDK_1_7)) {
result += listOf(LibraryJarDescriptor.RUNTIME_JDK7_JAR, LibraryJarDescriptor.RUNTIME_JDK7_SOURCES_JAR)
}
if (sdkVersion.isAtLeast(JavaSdkVersion.JDK_1_8)) {
result += listOf(LibraryJarDescriptor.RUNTIME_JDK8_JAR, LibraryJarDescriptor.RUNTIME_JDK8_SOURCES_JAR)
}
return result
}
override val libraryMatcher: (Library) -> Boolean
get() = ::isKotlinJavaRuntime
override fun configureKotlinSettings(modules: List<Module>) {
val project = modules.firstOrNull()?.project ?: return
val canChangeProjectSettings = project.allModules().all {
it.sdk?.version?.isAtLeast(JavaSdkVersion.JDK_1_8) ?: true
}
if (canChangeProjectSettings) {
Kotlin2JvmCompilerArgumentsHolder.getInstance(project).update {
jvmTarget = "1.8"
}
}
else {
for (module in modules) {
val sdkVersion = module.sdk?.version
if (sdkVersion != null && sdkVersion.isAtLeast(JavaSdkVersion.JDK_1_8)) {
val modelsProvider = IdeModifiableModelsProviderImpl(project)
val facet = module.getOrCreateFacet(modelsProvider, useProjectSettings = false, commitModel = true)
val facetSettings = facet.configuration.settings
facetSettings.initializeIfNeeded(module, null, TargetPlatformKind.Jvm(JvmTarget.JVM_1_8))
(facetSettings.compilerArguments as? K2JVMCompilerArguments)?.jvmTarget = "1.8"
}
}
}
}
override fun configureModule(
module: Module,
classesPath: String,
sourcesPath: String,
collector: NotificationMessageCollector,
forceJarState: FileState?,
useBundled: Boolean
) {
super.configureModule(module, classesPath, sourcesPath, collector, forceJarState, useBundled)
addStdlibToJavaModuleInfo(module, collector)
}
companion object {
val NAME = "java"
val instance: KotlinJavaModuleConfigurator
get() = Extensions.findExtension(KotlinProjectConfigurator.EP_NAME, KotlinJavaModuleConfigurator::class.java)
}
private fun hasBrokenJsRuntime(module: Module): Boolean {
for (orderEntry in ModuleRootManager.getInstance(module).orderEntries) {
val library = (orderEntry as? LibraryOrderEntry)?.library as? LibraryEx ?: continue
if (JsLibraryStdDetectionUtil.hasJsStdlibJar(library, ignoreKind = true)) return true
}
return false
}
}
@@ -0,0 +1,102 @@
/*
* 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.configuration
import com.intellij.openapi.module.Module
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.LibraryOrderEntry
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.impl.libraries.LibraryEx
import com.intellij.openapi.roots.libraries.DummyLibraryProperties
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.roots.libraries.LibraryType
import org.jetbrains.kotlin.idea.framework.JSLibraryKind
import org.jetbrains.kotlin.idea.framework.JSLibraryStdDescription
import org.jetbrains.kotlin.idea.framework.JSLibraryType
import org.jetbrains.kotlin.idea.framework.JsLibraryStdDetectionUtil
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.versions.LibraryJarDescriptor
import org.jetbrains.kotlin.idea.versions.isKotlinJsRuntime
import org.jetbrains.kotlin.js.JavaScript
import org.jetbrains.kotlin.js.resolve.JsPlatform
import org.jetbrains.kotlin.resolve.TargetPlatform
open class KotlinJsModuleConfigurator : KotlinWithLibraryConfigurator() {
override val name: String
get() = NAME
override val targetPlatform: TargetPlatform
get() = JsPlatform
override val presentableText: String
get() = JavaScript.FULL_NAME
override fun isConfigured(module: Module) = hasKotlinJsRuntimeInScope(module)
override val libraryName: String
get() = JSLibraryStdDescription.LIBRARY_NAME
override val dialogTitle: String
get() = JSLibraryStdDescription.DIALOG_TITLE
override val libraryCaption: String
get() = JSLibraryStdDescription.LIBRARY_CAPTION
override val messageForOverrideDialog: String
get() = JSLibraryStdDescription.JAVA_SCRIPT_LIBRARY_CREATION
override fun getLibraryJarDescriptors(sdk: Sdk?): List<LibraryJarDescriptor> =
listOf(LibraryJarDescriptor.JS_STDLIB_JAR,
LibraryJarDescriptor.JS_STDLIB_SRC_JAR)
override val libraryMatcher: (Library) -> Boolean = ::isKotlinJsRuntime
override val libraryType: LibraryType<DummyLibraryProperties>?
get() = JSLibraryType.getInstance()
companion object {
const val NAME = JavaScript.LOWER_NAME
}
/**
* Migrate pre-1.1.3 projects which didn't have explicitly specified kind for JS libraries.
*/
override fun findAndFixBrokenKotlinLibrary(module: Module, collector: NotificationMessageCollector): Library? {
val allLibraries = mutableListOf<LibraryEx>()
var brokenStdlib: Library? = null
for (orderEntry in ModuleRootManager.getInstance(module).orderEntries) {
val library = (orderEntry as? LibraryOrderEntry)?.library as? LibraryEx ?: continue
allLibraries.add(library)
if (JsLibraryStdDetectionUtil.hasJsStdlibJar(library, ignoreKind = true) && library.kind == null) {
brokenStdlib = library
}
}
if (brokenStdlib != null) {
runWriteAction {
for (library in allLibraries.filter { it.kind == null }) {
library.modifiableModel.apply {
kind = JSLibraryKind
commit()
}
}
}
collector.addMessage("Updated JavaScript libraries in module ${module.name}")
}
return brokenStdlib
}
}
@@ -0,0 +1,64 @@
/*
* 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.configuration
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ExternalLibraryDescriptor
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.config.ApiVersion
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.idea.versions.LibraryJarDescriptor
import org.jetbrains.kotlin.resolve.TargetPlatform
enum class ConfigureKotlinStatus {
/** Kotlin is correctly configured using this configurator. */
CONFIGURED,
/** The configurator is not applicable to the current project type. */
NON_APPLICABLE,
/** The configurator is applicable to the current project type and can configure Kotlin automatically. */
CAN_BE_CONFIGURED,
/**
* The configurator is applicable to the current project type and Kotlin is not configured,
* but the state of the project doesn't allow to configure Kotlin automatically.
*/
BROKEN
}
interface KotlinProjectConfigurator {
fun getStatus(moduleSourceRootGroup: ModuleSourceRootGroup): ConfigureKotlinStatus
@JvmSuppressWildcards fun configure(project: Project, excludeModules: Collection<Module>)
val presentableText: String
val name: String
val targetPlatform: TargetPlatform
fun updateLanguageVersion(module: Module, languageVersion: String?, apiVersion: String?, requiredStdlibVersion: ApiVersion, forTests: Boolean)
fun changeCoroutineConfiguration(module: Module, state: LanguageFeature.State)
fun addLibraryDependency(module: Module, element: PsiElement, library: ExternalLibraryDescriptor, libraryJarDescriptors: List<LibraryJarDescriptor>)
companion object {
val EP_NAME = ExtensionPointName.create<KotlinProjectConfigurator>("org.jetbrains.kotlin.projectConfigurator")
}
}
@@ -0,0 +1,159 @@
/*
* 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.configuration
import com.intellij.ProjectTopics
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleUtilCore
import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectBundle
import com.intellij.openapi.roots.ModuleRootAdapter
import com.intellij.openapi.roots.ModuleRootEvent
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ModuleRootModificationUtil
import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.ui.popup.ListPopup
import com.intellij.openapi.ui.popup.PopupStep
import com.intellij.openapi.ui.popup.util.BaseListPopupStep
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import com.intellij.ui.EditorNotificationPanel
import com.intellij.ui.EditorNotifications
import org.jetbrains.kotlin.idea.KotlinFileType
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.configuration.ui.KotlinConfigurationCheckerComponent
import org.jetbrains.kotlin.idea.project.TargetPlatformDetector
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.versions.SuppressNotificationState
import org.jetbrains.kotlin.idea.versions.UnsupportedAbiVersionNotificationPanelProvider
import org.jetbrains.kotlin.idea.versions.createComponentActionLabel
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
// Code is partially copied from com.intellij.codeInsight.daemon.impl.SetupSDKNotificationProvider
class KotlinSetupEnvironmentNotificationProvider(
private val myProject: Project,
notifications: EditorNotifications) : EditorNotifications.Provider<EditorNotificationPanel>() {
init {
myProject.messageBus.connect(myProject).subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootAdapter() {
override fun rootsChanged(event: ModuleRootEvent?) {
notifications.updateAllNotifications()
}
})
}
override fun getKey(): Key<EditorNotificationPanel> = KEY
override fun createNotificationPanel(file: VirtualFile, fileEditor: FileEditor): EditorNotificationPanel? {
if (file.fileType != KotlinFileType.INSTANCE) {
return null
}
val psiFile = PsiManager.getInstance(myProject).findFile(file) as? KtFile ?: return null
if (psiFile.language !== KotlinLanguage.INSTANCE) {
return null
}
val module = ModuleUtilCore.findModuleForPsiElement(psiFile) ?: return null
if (!ModuleRootManager.getInstance(module).fileIndex.isInSourceContent(file)) {
return null
}
if (ModuleRootManager.getInstance(module).sdk == null &&
TargetPlatformDetector.getPlatform(psiFile) == JvmPlatform) {
return createSetupSdkPanel(myProject, psiFile)
}
if (!KotlinConfigurationCheckerComponent.getInstance(module.project).isSyncing &&
!SuppressNotificationState.isKotlinNotConfiguredSuppressed(module.toModuleGroup()) &&
!hasAnyKotlinRuntimeInScope(module) &&
UnsupportedAbiVersionNotificationPanelProvider.collectBadRoots(module).isEmpty()
) {
return createKotlinNotConfiguredPanel(module)
}
return null
}
companion object {
private val KEY = Key.create<EditorNotificationPanel>("Setup SDK")
private fun createSetupSdkPanel(project: Project, file: PsiFile): EditorNotificationPanel {
return EditorNotificationPanel().apply {
setText(ProjectBundle.message("project.sdk.not.defined"))
createActionLabel(ProjectBundle.message("project.sdk.setup")) {
ProjectSettingsService.getInstance(project).chooseAndSetSdk() ?: return@createActionLabel
runWriteAction {
val module = ModuleUtilCore.findModuleForPsiElement(file)
if (module != null) {
ModuleRootModificationUtil.setSdkInherited(module)
}
}
}
}
}
private fun createKotlinNotConfiguredPanel(module: Module): EditorNotificationPanel {
return EditorNotificationPanel().apply {
setText("Kotlin not configured")
val configurators = getAbleToRunConfigurators(module).toList()
if (!configurators.isEmpty()) {
createComponentActionLabel("Configure") { label ->
val singleConfigurator = configurators.singleOrNull()
if (singleConfigurator != null) {
singleConfigurator.apply(module.project)
}
else {
val configuratorsPopup = createConfiguratorsPopup(module.project, configurators)
configuratorsPopup.showUnderneathOf(label)
}
}
createComponentActionLabel("Ignore") {
SuppressNotificationState.suppressKotlinNotConfigured(module)
EditorNotifications.getInstance(module.project).updateAllNotifications()
}
}
}
}
private fun KotlinProjectConfigurator.apply(project: Project) {
configure(project, emptyList())
EditorNotifications.getInstance(project).updateAllNotifications()
checkHideNonConfiguredNotifications(project)
}
fun createConfiguratorsPopup(project: Project, configurators: List<KotlinProjectConfigurator>): ListPopup {
val step = object : BaseListPopupStep<KotlinProjectConfigurator>("Choose Configurator", configurators) {
override fun getTextFor(value: KotlinProjectConfigurator?) = value?.presentableText ?: "<none>"
override fun onChosen(selectedValue: KotlinProjectConfigurator?, finalChoice: Boolean): PopupStep<*>? {
return doFinalStep {
selectedValue?.apply(project)
}
}
}
return JBPopupFactory.getInstance().createListPopup(step)
}
}
}
@@ -0,0 +1,23 @@
/*
* 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.configuration
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.libraries.ui.FileTypeBasedRootFilter
import org.jetbrains.kotlin.idea.KotlinFileType
class KotlinSourceRootDetector : FileTypeBasedRootFilter(OrderRootType.SOURCES, false, KotlinFileType.INSTANCE, "sources")
@@ -0,0 +1,461 @@
/*
* 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.configuration
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.module.Module
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.*
import com.intellij.openapi.roots.libraries.*
import com.intellij.openapi.vfs.JarFileSystem
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VfsUtil
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.psi.PsiElement
import org.jetbrains.annotations.Contract
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.idea.KotlinPluginUtil
import org.jetbrains.kotlin.idea.compiler.configuration.KotlinCommonCompilerArgumentsHolder
import org.jetbrains.kotlin.idea.facet.getRuntimeLibraryVersion
import org.jetbrains.kotlin.idea.framework.ui.CreateLibraryDialogWithModules
import org.jetbrains.kotlin.idea.framework.ui.FileUIUtils
import org.jetbrains.kotlin.idea.quickfix.askUpdateRuntime
import org.jetbrains.kotlin.idea.util.application.runWriteAction
import org.jetbrains.kotlin.idea.util.projectStructure.sdk
import org.jetbrains.kotlin.idea.versions.LibraryJarDescriptor
import org.jetbrains.kotlin.idea.versions.findAllUsedLibraries
import org.jetbrains.kotlin.idea.versions.findKotlinRuntimeLibrary
import java.io.File
import java.util.*
abstract class KotlinWithLibraryConfigurator internal constructor() : KotlinProjectConfigurator {
protected abstract val libraryName: String
protected abstract val messageForOverrideDialog: String
protected abstract val dialogTitle: String
protected abstract val libraryCaption: String
open val libraryType: LibraryType<DummyLibraryProperties>? = null
protected val libraryKind: PersistentLibraryKind<*>? = libraryType?.kind
override fun getStatus(moduleSourceRootGroup: ModuleSourceRootGroup): ConfigureKotlinStatus {
val module = moduleSourceRootGroup.baseModule
if (!isApplicable(module)) {
return ConfigureKotlinStatus.NON_APPLICABLE
}
if (isConfigured(module)) {
return ConfigureKotlinStatus.CONFIGURED
}
return ConfigureKotlinStatus.CAN_BE_CONFIGURED
}
abstract fun isConfigured(module: Module): Boolean
@JvmSuppressWildcards
override fun configure(project: Project, excludeModules: Collection<Module>) {
val defaultPathToJar = getDefaultPathToJarFile(project)
val showPathToJarPanel = needToChooseJarPath(project)
var nonConfiguredModules = if (!ApplicationManager.getApplication().isUnitTestMode)
getCanBeConfiguredModules(project, this)
else
Arrays.asList(*ModuleManager.getInstance(project).modules)
nonConfiguredModules -= excludeModules
var modulesToConfigure = nonConfiguredModules
var copyLibraryIntoPath: String? = null
if (nonConfiguredModules.size > 1 || showPathToJarPanel) {
val dialog = CreateLibraryDialogWithModules(
project, this, defaultPathToJar, showPathToJarPanel,
dialogTitle,
libraryCaption,
excludeModules)
if (!ApplicationManager.getApplication().isUnitTestMode) {
dialog.show()
if (!dialog.isOK) return
}
else {
dialog.close(0)
}
modulesToConfigure = dialog.modulesToConfigure
copyLibraryIntoPath = dialog.copyIntoPath
}
val collector = createConfigureKotlinNotificationCollector(project)
for (module in modulesToConfigure) {
configureModule(module, defaultPathToJar, copyLibraryIntoPath, collector)
}
configureKotlinSettings(modulesToConfigure)
KotlinCommonCompilerArgumentsHolder.getInstance(project).update {
languageVersionView = VersionView.Specific(LanguageVersion.LATEST_STABLE)
apiVersionView = VersionView.Specific(LanguageVersion.LATEST_STABLE)
}
collector.showNotification()
}
@Suppress("unused") // Please do not delete this function (used in ProcessingKt plugin)
fun configureSilently(project: Project) {
val defaultPathToJar = getDefaultPathToJarFile(project)
val collector = createConfigureKotlinNotificationCollector(project)
for (module in ModuleManager.getInstance(project).modules) {
configureModule(module, defaultPathToJar, null, collector)
}
}
protected fun configureModule(
module: Module,
defaultPath: String,
pathFromDialog: String?,
collector: NotificationMessageCollector
) {
val classesPath = getPathToCopyFileTo(module.project, OrderRootType.CLASSES, defaultPath, pathFromDialog)
val sourcesPath = getPathToCopyFileTo(module.project, OrderRootType.SOURCES, defaultPath, pathFromDialog)
configureModule(module, classesPath, sourcesPath, collector, useBundled = pathFromDialog == null)
}
open fun configureModule(
module: Module,
classesPath: String,
sourcesPath: String,
collector: NotificationMessageCollector,
forceJarState: FileState? = null,
useBundled: Boolean = false
) {
configureModuleWithLibrary(module, classesPath, sourcesPath, collector, forceJarState, useBundled)
}
private fun configureModuleWithLibrary(
module: Module,
classesPath: String,
sourcesPath: String,
collector: NotificationMessageCollector,
forceJarState: FileState? = null,
useBundled: Boolean = false
) {
val project = module.project
val library = findAndFixBrokenKotlinLibrary(module, collector)
?: getKotlinLibrary(module)
?: getKotlinLibrary(project)
?: createNewLibrary(project, collector)
val sdk = module.sdk
val model = library.modifiableModel
for (descriptor in getLibraryJarDescriptors(sdk)) {
val dirToCopyJar = if (descriptor.orderRootType == OrderRootType.SOURCES)
sourcesPath
else
classesPath
val runtimeState = forceJarState ?: getJarState(project,
File(dirToCopyJar, descriptor.jarName),
descriptor.orderRootType, useBundled)
configureLibraryJar(model, runtimeState, dirToCopyJar, descriptor, collector)
}
ApplicationManager.getApplication().runWriteAction { model.commit() }
addLibraryToModuleIfNeeded(module, library, collector)
}
fun configureLibraryJar(
library: Library.ModifiableModel,
jarState: FileState,
dirToCopyJarTo: String,
libraryJarDescriptor: LibraryJarDescriptor,
collector: NotificationMessageCollector
) {
val jarFile = if (jarState == KotlinWithLibraryConfigurator.FileState.DO_NOT_COPY)
libraryJarDescriptor.getPathInPlugin()
else
File(dirToCopyJarTo, libraryJarDescriptor.jarName)
if (jarState == KotlinWithLibraryConfigurator.FileState.COPY) {
copyFileToDir(libraryJarDescriptor.getPathInPlugin(), dirToCopyJarTo, collector)
}
val jarVFile = LocalFileSystem.getInstance().findFileByIoFile(jarFile)
if (jarVFile == null) {
collector.addMessage("Can't find library JAR file " + jarFile)
return
}
val jarRoot = JarFileSystem.getInstance().getJarRootForLocalFile(jarVFile)
if (jarRoot == null) {
collector.addMessage("Couldn't configure library; JAR file $jarVFile may be corrupted")
return
}
if (jarRoot !in library.getFiles(libraryJarDescriptor.orderRootType)) {
library.addRoot(jarRoot, libraryJarDescriptor.orderRootType)
collector.addMessage("Added $jarFile to library configuration")
}
}
fun getKotlinLibrary(project: Project): Library? {
return LibraryTablesRegistrar.getInstance().getLibraryTable(project).libraries.firstOrNull(this::isKotlinLibrary) ?:
LibraryTablesRegistrar.getInstance().libraryTable.libraries.firstOrNull(this::isKotlinLibrary)
}
@Contract("!null, _, _ -> !null")
fun copyFileToDir(file: File?, toDir: String, collector: NotificationMessageCollector): File? {
if (file == null) return null
val copy = FileUIUtils.copyWithOverwriteDialog(messageForOverrideDialog, toDir, file)
if (copy != null) {
collector.addMessage(file.name + " was copied to " + toDir)
}
return copy
}
protected fun getPathFromLibrary(project: Project, type: OrderRootType): String? {
return getPathFromLibrary(getKotlinLibrary(project), type)
}
fun addLibraryToModuleIfNeeded(module: Module, library: Library, collector: NotificationMessageCollector) {
val expectedDependencyScope = getDependencyScope(module)
val kotlinLibrary = getKotlinLibrary(module)
if (kotlinLibrary == null) {
ModuleRootModificationUtil.addDependency(module, library, expectedDependencyScope, false)
collector.addMessage(library.name + " library was added to module " + module.name)
}
else {
val libraryEntry = findLibraryOrderEntry(ModuleRootManager.getInstance(module).orderEntries, kotlinLibrary)
if (libraryEntry != null) {
val libraryDependencyScope = libraryEntry.scope
if (expectedDependencyScope != libraryDependencyScope) {
libraryEntry.scope = expectedDependencyScope
collector.addMessage(
kotlinLibrary.name + " library scope has changed from " + libraryDependencyScope +
" to " + expectedDependencyScope + " for module " + module.name)
}
}
}
}
fun createNewLibrary(
project: Project,
collector: NotificationMessageCollector
): Library {
val table = LibraryTablesRegistrar.getInstance().getLibraryTable(project)
val library = runWriteAction {
table.modifiableModel.run {
val library = createLibrary(libraryName, libraryKind)
commit()
library
}
}
collector.addMessage(library.name!! + " library was created")
return library!!
}
private fun isProjectLibraryPresent(project: Project): Boolean {
val library = getKotlinLibrary(project)
return library != null && library.getUrls(OrderRootType.CLASSES).size > 0
}
protected abstract val libraryMatcher: (Library) -> Boolean
fun getKotlinLibrary(module: Module): Library? {
return findKotlinRuntimeLibrary(module, this::isKotlinLibrary)
}
private fun isKotlinLibrary(library: Library) = library.name == libraryName || libraryMatcher(library)
protected fun needToChooseJarPath(project: Project): Boolean {
val defaultPath = getDefaultPathToJarFile(project)
return !isProjectLibraryPresent(project) &&
!File(defaultPath, getLibraryJarDescriptors(null).first().jarName).exists()
}
open fun getDefaultPathToJarFile(project: Project): String {
return FileUIUtils.createRelativePath(project, project.baseDir, DEFAULT_LIBRARY_DIR)
}
enum class FileState {
EXISTS,
COPY,
DO_NOT_COPY
}
protected fun getJarState(
project: Project,
targetFile: File,
jarType: OrderRootType,
useBundled: Boolean
): FileState = when {
targetFile.exists() -> FileState.EXISTS
getPathFromLibrary(project, jarType) != null -> FileState.COPY
useBundled -> FileState.DO_NOT_COPY
else -> FileState.COPY
}
private fun getPathToCopyFileTo(
project: Project,
jarType: OrderRootType,
defaultDir: String,
pathFromDialog: String?
): String {
if (pathFromDialog != null) {
return pathFromDialog
}
val pathFromLibrary = getPathFromLibrary(project, jarType)
if (pathFromLibrary != null) {
return pathFromLibrary
}
return defaultDir
}
abstract fun getLibraryJarDescriptors(sdk: Sdk?): List<LibraryJarDescriptor>
protected open fun configureKotlinSettings(modules: List<Module>) {
}
protected open fun findAndFixBrokenKotlinLibrary(module: Module, collector: NotificationMessageCollector): Library? = null
protected open fun isApplicable(module: Module): Boolean {
return !KotlinPluginUtil.isAndroidGradleModule(module) &&
!KotlinPluginUtil.isMavenModule(module) &&
!KotlinPluginUtil.isGradleModule(module)
}
override fun changeCoroutineConfiguration(module: Module, state: LanguageFeature.State) {
val runtimeUpdateRequired = state != LanguageFeature.State.DISABLED &&
(getRuntimeLibraryVersion(module)?.startsWith("1.0") ?: false)
if (runtimeUpdateRequired && !askUpdateRuntime(module, LanguageFeature.Coroutines.sinceApiVersion)) {
return
}
val facetSettings = KotlinFacetSettingsProvider.getInstance(module.project).getInitializedSettings(module)
ModuleRootModificationUtil.updateModel(module) {
facetSettings.coroutineSupport = state
facetSettings.apiLevel = LanguageVersion.KOTLIN_1_1
facetSettings.languageLevel = LanguageVersion.KOTLIN_1_1
}
}
override fun updateLanguageVersion(module: Module, languageVersion: String?, apiVersion: String?, requiredStdlibVersion: ApiVersion, forTests: Boolean) {
val runtimeUpdateRequired = getRuntimeLibraryVersion(module)?.let { ApiVersion.parse(it) }?.let { runtimeVersion ->
runtimeVersion < requiredStdlibVersion
} ?: false
if (runtimeUpdateRequired && !askUpdateRuntime(module, requiredStdlibVersion)) {
return
}
val facetSettings = KotlinFacetSettingsProvider.getInstance(module.project).getInitializedSettings(module)
ModuleRootModificationUtil.updateModel(module) {
with(facetSettings) {
if (languageVersion != null) {
languageLevel = LanguageVersion.fromVersionString(languageVersion)
}
if (apiVersion != null) {
apiLevel = LanguageVersion.fromVersionString(apiVersion)
}
}
}
}
override fun addLibraryDependency(module: Module, element: PsiElement, library: ExternalLibraryDescriptor, libraryJarDescriptors: List<LibraryJarDescriptor>) {
val project = module.project
val collector = createConfigureKotlinNotificationCollector(project)
for (library in findAllUsedLibraries(project).keySet()) {
val runtimeJar = LibraryJarDescriptor.RUNTIME_JAR.findExistingJar(library) ?: continue
val model = library.modifiableModel
val libFilesDir = VfsUtilCore.virtualToIoFile(runtimeJar).parent
for (libraryJarDescriptor in libraryJarDescriptors) {
if (libraryJarDescriptor.findExistingJar(library) != null) continue
val libFile = libraryJarDescriptor.getPathInPlugin()
if (!libFile.exists()) continue
val libIoFile = File(libFilesDir, libraryJarDescriptor.jarName)
if (libIoFile.exists()) {
model.addRoot(VfsUtil.getUrlForLibraryRoot(libIoFile), libraryJarDescriptor.orderRootType)
}
else {
val copied = copyFileToDir(libFile, libFilesDir, collector)!!
model.addRoot(VfsUtil.getUrlForLibraryRoot(copied), libraryJarDescriptor.orderRootType)
}
}
model.commit()
}
collector.showNotification()
}
companion object {
val DEFAULT_LIBRARY_DIR = "lib"
fun getPathFromLibrary(library: Library?, type: OrderRootType): String? {
if (library == null) return null
val libraryFiles = library.getUrls(type)
return getPathFromLibraryUrls(libraryFiles)
}
fun getPathFromLibraryUrls(libraryFiles: Array<String>): String? {
if (libraryFiles.size < 1) return null
val pathToJarInLib = VfsUtilCore.urlToPath(libraryFiles[0])
val parentDir = VfsUtil.getParentDir(VfsUtil.getParentDir(pathToJarInLib)) ?: return null
val parentDirFile = File(parentDir)
if (!parentDirFile.exists() && !parentDirFile.mkdirs()) {
return null
}
return parentDir
}
private fun findLibraryOrderEntry(orderEntries: Array<OrderEntry>, library: Library): LibraryOrderEntry? {
for (orderEntry in orderEntries) {
if (orderEntry is LibraryOrderEntry && library == orderEntry.library) {
return orderEntry
}
}
return null
}
private fun getDependencyScope(module: Module): DependencyScope {
if (hasKotlinFilesOnlyInTests(module)) {
return DependencyScope.TEST
}
return DependencyScope.COMPILE
}
}
}
@@ -0,0 +1,101 @@
/*
* 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.configuration
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.util.projectStructure.allModules
class ModuleSourceRootGroup(val baseModule: Module,
val sourceRootModules: List<Module>)
class ModuleSourceRootMap(val modules: Collection<Module>) {
private val baseModuleByExternalPath: Map<String, Module>
private val allModulesByExternalPath: Map<String, List<Module>>
constructor(project: Project): this(project.allModules())
init {
allModulesByExternalPath = modules
.filter { it.externalProjectPath != null && it.externalProjectId != null }
.groupBy { it.externalProjectPath!! }
baseModuleByExternalPath = allModulesByExternalPath
.mapValues { (path, modules) ->
modules.reduce { m1, m2 ->
if (isSourceRootPrefix(m2.externalProjectId!!, m1.externalProjectId!!)) m2 else m1
}
}
}
fun groupByBaseModules(modules: Collection<Module>): List<ModuleSourceRootGroup> {
return modules
.groupBy { module ->
val externalPath = module.externalProjectPath
if (externalPath == null) module else (baseModuleByExternalPath[externalPath] ?: module)
}
.map { (module, sourceRootModules) ->
ModuleSourceRootGroup(module,
if (sourceRootModules.size > 1) sourceRootModules - module else sourceRootModules)
}
}
fun toModuleGroup(module: Module): ModuleSourceRootGroup = groupByBaseModules(listOf(module)).single()
fun getWholeModuleGroup(module: Module): ModuleSourceRootGroup {
val externalPath = module.externalProjectPath
val baseModule = (if (externalPath != null) baseModuleByExternalPath[externalPath] else null) ?:
return ModuleSourceRootGroup(module, listOf(module))
val externalPathModules = allModulesByExternalPath[externalPath] ?: listOf()
return ModuleSourceRootGroup(baseModule, if (externalPathModules.size > 1) externalPathModules - module else externalPathModules)
}
}
fun Module.toModuleGroup() = ModuleSourceRootMap(project).toModuleGroup(this)
fun Module.getWholeModuleGroup() = ModuleSourceRootMap(project).getWholeModuleGroup(this)
private fun isSourceRootPrefix(externalId: String, previousModuleExternalId: String)
= externalId.length < previousModuleExternalId.length && previousModuleExternalId.startsWith(externalId)
val Module.externalProjectId: String?
get() = ExternalSystemApiUtil.getExternalProjectId(this)
val Module.externalProjectPath: String?
get() = ExternalSystemApiUtil.getExternalProjectPath(this)
fun ModuleSourceRootGroup.allModules(): Set<Module> {
val result = LinkedHashSet<Module>()
result.add(baseModule)
result.addAll(sourceRootModules)
return result
}
fun List<ModuleSourceRootGroup>.exclude(excludeModules: Collection<Module>): List<ModuleSourceRootGroup> {
return mapNotNull {
if (it.baseModule in excludeModules)
null
else {
val remainingSourceRootModules = it.sourceRootModules - excludeModules
if (remainingSourceRootModules.isEmpty())
null
else
ModuleSourceRootGroup(it.baseModule, remainingSourceRootModules)
}
}
}
@@ -0,0 +1,27 @@
/*
* 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.configuration
import com.intellij.openapi.module.Module
import org.jetbrains.kotlin.idea.actions.NewKotlinFileHook
import org.jetbrains.kotlin.psi.KtFile
class NewKotlinFileConfigurationHook : NewKotlinFileHook() {
override fun postProcess(createdElement: KtFile, module: Module) {
showConfigureKotlinNotificationIfNeeded(module)
}
}
@@ -0,0 +1,49 @@
/*
* 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.configuration
import com.intellij.notification.Notification
import com.intellij.notification.NotificationType
import com.intellij.notification.Notifications
import com.intellij.openapi.project.Project
import java.util.*
open class NotificationMessageCollector(private val project: Project,
private val groupDisplayId: String,
private val title: String) {
private val messages = ArrayList<String>()
fun addMessage(message: String): NotificationMessageCollector {
messages.add(message)
return this
}
fun showNotification() {
if (messages.isEmpty()) return
Notifications.Bus.notify(Notification(groupDisplayId, title, resultMessage, NotificationType.INFORMATION), project)
}
private val resultMessage: String get() {
val singleMessage = messages.singleOrNull()
if (singleMessage != null) return singleMessage
return messages.joinToString(separator = "<br/><br/>")
}
}
fun createConfigureKotlinNotificationCollector(project: Project) =
NotificationMessageCollector(project, "Configure Kotlin: info notification", "Configure Kotlin")
@@ -0,0 +1,108 @@
/*
* 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.configuration.ui
import com.intellij.ProjectTopics
import com.intellij.notification.NotificationDisplayType
import com.intellij.notification.NotificationsConfiguration
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.AbstractProjectComponent
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.ModuleRootEvent
import com.intellij.openapi.roots.ModuleRootListener
import com.intellij.openapi.startup.StartupManager
import org.jetbrains.kotlin.idea.configuration.checkHideNonConfiguredNotifications
import org.jetbrains.kotlin.idea.configuration.getModulesWithKotlinFiles
import org.jetbrains.kotlin.idea.configuration.showConfigureKotlinNotificationIfNeeded
import org.jetbrains.kotlin.idea.project.getAndCacheLanguageLevelByDependencies
import org.jetbrains.kotlin.idea.versions.collectModulesWithOutdatedRuntime
import org.jetbrains.kotlin.idea.versions.findOutdatedKotlinLibraries
import org.jetbrains.kotlin.idea.versions.notifyOutdatedKotlinRuntime
import java.util.concurrent.atomic.AtomicInteger
class KotlinConfigurationCheckerComponent(project: Project) : AbstractProjectComponent(project) {
private val syncDepth = AtomicInteger()
@Volatile private var notificationPostponed = false
init {
NotificationsConfiguration.getNotificationsConfiguration().register(CONFIGURE_NOTIFICATION_GROUP_ID, NotificationDisplayType.STICKY_BALLOON, true)
val connection = project.messageBus.connect()
connection.subscribe(ProjectTopics.PROJECT_ROOTS, object : ModuleRootListener {
override fun rootsChanged(event: ModuleRootEvent?) {
if (notificationPostponed && !isSyncing) {
ApplicationManager.getApplication().executeOnPooledThread {
DumbService.getInstance(myProject).waitForSmartMode()
if (!isSyncing) {
notificationPostponed = false
showConfigureKotlinNotificationIfNeeded(myProject,
collectModulesWithOutdatedRuntime(findOutdatedKotlinLibraries(myProject)))
}
}
}
checkHideNonConfiguredNotifications(project)
}
})
}
override fun projectOpened() {
super.projectOpened()
StartupManager.getInstance(myProject).registerPostStartupActivity {
ApplicationManager.getApplication().executeOnPooledThread {
DumbService.getInstance(myProject).waitForSmartMode()
for (module in getModulesWithKotlinFiles(myProject)) {
module.getAndCacheLanguageLevelByDependencies()
}
val libraries = findOutdatedKotlinLibraries(myProject)
if (!libraries.isEmpty()) {
ApplicationManager.getApplication().invokeLater {
notifyOutdatedKotlinRuntime(myProject, libraries)
}
}
if (!isSyncing) {
val excludeModules = collectModulesWithOutdatedRuntime(libraries)
showConfigureKotlinNotificationIfNeeded(myProject, excludeModules)
}
else {
notificationPostponed = true
}
}
}
}
val isSyncing: Boolean get() = syncDepth.get() > 0
fun syncStarted() {
syncDepth.incrementAndGet()
}
fun syncDone() {
syncDepth.decrementAndGet()
}
companion object {
val CONFIGURE_NOTIFICATION_GROUP_ID = "Configure Kotlin in Project"
fun getInstance(project: Project): KotlinConfigurationCheckerComponent
= project.getComponent(KotlinConfigurationCheckerComponent::class.java)
}
}
@@ -0,0 +1,78 @@
/*
* 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.configuration.ui.notifications
import com.intellij.notification.Notification
import com.intellij.notification.NotificationListener
import com.intellij.notification.NotificationType
import com.intellij.openapi.module.Module
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.idea.configuration.KotlinProjectConfigurator
import org.jetbrains.kotlin.idea.configuration.getAbleToRunConfigurators
import org.jetbrains.kotlin.idea.configuration.getConfiguratorByName
import org.jetbrains.kotlin.idea.configuration.getCanBeConfiguredModulesWithKotlinFiles
import org.jetbrains.kotlin.idea.configuration.ui.KotlinConfigurationCheckerComponent
import javax.swing.event.HyperlinkEvent
class ConfigureKotlinNotification(
project: Project,
excludeModules: List<Module>,
notificationString: String) : Notification(KotlinConfigurationCheckerComponent.CONFIGURE_NOTIFICATION_GROUP_ID, "Configure Kotlin",
notificationString,
NotificationType.WARNING, NotificationListener { notification, event ->
if (event.eventType == HyperlinkEvent.EventType.ACTIVATED) {
val configurator = getConfiguratorByName(event.description) ?: throw AssertionError("Missed action: " + event.description)
notification.expire()
configurator.configure(project, excludeModules)
}
}) {
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is ConfigureKotlinNotification) return false
if (content != o.content) return false
return true
}
override fun hashCode(): Int {
return content.hashCode()
}
companion object {
fun getNotificationString(project: Project, excludeModules: Collection<Module>): String? {
val modules = getCanBeConfiguredModulesWithKotlinFiles(project, excludeModules)
val isOnlyOneModule = modules.size == 1
val modulesString = if (isOnlyOneModule) "'${modules.first().name}' module" else "modules"
val ableToRunConfigurators = getAbleToRunConfigurators(project)
if (ableToRunConfigurators.isEmpty()) return null
val links = ableToRunConfigurators.joinToString(separator = "<br/>") {
configurator -> getLink(configurator, isOnlyOneModule)
}
return "Configure $modulesString in '${project.name}' project<br/> $links"
}
private fun getLink(configurator: KotlinProjectConfigurator, isOnlyOneModule: Boolean): String {
return "<a href=\"${configurator.name}\">as Kotlin (${configurator.presentableText}) module${if(!isOnlyOneModule) "s" else ""}</a>"
}
}
}
@@ -0,0 +1,53 @@
/*
* 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.copyright;
import com.intellij.openapi.module.Module;
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.PsiWhiteSpace;
import com.maddyhome.idea.copyright.CopyrightProfile;
import com.maddyhome.idea.copyright.psi.UpdatePsiFileCopyright;
class UpdateKotlinCopyright extends UpdatePsiFileCopyright {
UpdateKotlinCopyright(Project project, Module module, VirtualFile root, CopyrightProfile copyrightProfile) {
super(project, module, root, copyrightProfile);
}
@Override
protected void scanFile() {
PsiElement first = getFile().getFirstChild();
PsiElement last = first;
PsiElement next = first;
while (next != null) {
if (next instanceof PsiComment || next instanceof PsiWhiteSpace) {
next = getNextSibling(next);
}
else {
break;
}
last = next;
}
if (first != null) {
checkComments(first, last, true);
}
}
}
@@ -0,0 +1,32 @@
/*
* 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.copyright;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.maddyhome.idea.copyright.CopyrightProfile;
import com.maddyhome.idea.copyright.psi.UpdateCopyright;
import com.maddyhome.idea.copyright.psi.UpdateCopyrightsProvider;
public class UpdateKotlinCopyrightsProvider extends UpdateCopyrightsProvider {
@Override
public UpdateCopyright createInstance(Project project, Module module, VirtualFile file, FileType base, CopyrightProfile options) {
return new UpdateKotlinCopyright(project, module, file, options);
}
}
@@ -0,0 +1,301 @@
/*
* 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.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
import com.sun.jdi.AbsentInformationException
import com.sun.jdi.ReferenceType
import org.jetbrains.kotlin.codegen.binding.CodegenBinding.asmTypeForAnonymousClass
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,
scopes: List<GlobalSearchScope>,
val findInlineUseSites: Boolean = true,
val alwaysReturnLambdaParentClass: Boolean = true
) {
companion object {
internal 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? {
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, scopes)
/**
* Returns classes in which the given line number *is* present.
*/
fun getClassesForPosition(position: SourcePosition): List<ReferenceType> = with (debugProcess) {
val lineNumber = 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 KtFile -> {
val fileClassName = runReadAction { JvmFileClassUtil.getFileClassInternalName(element) }.toJdiName()
ComputedClassNames.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 (it is KtClass && runReadAction { it.isInterface() }) {
val name = getNameForNonLocalClass(it)
if (name != null)
Cached(listOf(name, name + JvmAbi.DEFAULT_IMPLS_SUFFIX))
else
ComputedClassNames.EMPTY
}
else {
getNameForNonLocalClass(it)?.let { ComputedClassNames.Cached(it) } ?: ComputedClassNames.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 typeMapper = KotlinDebuggerCaches.getOrCreateTypeMapper(element)
val classNamesOfContainingDeclaration = getOuterClassNamesForElement(element.relevantParentInReadAction)
val nonInlineClasses: ComputedClassNames = if (runReadAction { element.name == null || element.isLocal }) {
classNamesOfContainingDeclaration + ComputedClassNames.Cached(
asmTypeForAnonymousClass(typeMapper.bindingContext, element).internalName.toJdiName())
}
else {
classNamesOfContainingDeclaration
}
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 ComputedClassNames.Cached(nonInlinedLambdaClassName)
}
ComputedClassNames.Cached(nonInlinedLambdaClassName) + getOuterClassNamesForElement(element.relevantParentInReadAction)
}
else -> getOuterClassNamesForElement(element.relevantParentInReadAction)
}
}
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
if (!outerClass.isPrepared) 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
}
@@ -0,0 +1,227 @@
/*
* 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 org.jetbrains.kotlin.asJava.finder.JavaElementFinder
import org.jetbrains.kotlin.descriptors.CallableDescriptor
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 {
fun findSourceFileForClassIncludeLibrarySources(
project: Project,
scope: GlobalSearchScope,
className: JvmClassName,
fileName: String): KtFile? {
return runReadAction {
findSourceFileForClass(
project,
listOf(scope, KotlinSourceFilterScope.librarySources(GlobalSearchScope.allScope(project), project)),
className,
fileName)
}
}
fun findSourceFileForClass(
project: Project,
scopes: List<GlobalSearchScope>,
className: JvmClassName,
fileName: String): KtFile? {
if (!isKotlinSourceFile(fileName)) return null
if (DumbService.getInstance(project).isDumb) return null
val filesWithExactName = scopes.findFirstNotEmpty { findFilesByNameInPackage(className, fileName, project, it) } ?: return null
if (filesWithExactName.isEmpty()) return null
if (filesWithExactName.size == 1) {
return filesWithExactName.single()
}
// Static facade or inner class of such facade?
val partFqName = className.fqNameForClassNameWithoutDollars
val filesForPart = scopes.findFirstNotEmpty { StaticFacadeIndexUtil.findFilesForFilePart(partFqName, it, project) } ?: return null
if (!filesForPart.isEmpty()) {
for (file in filesForPart) {
if (file.name == fileName) {
return file
}
}
// Do not fall back to decompiled files (which have different name).
return null
}
return filesWithExactName.first()
}
private fun <T, R> Collection<T>.findFirstNotEmpty(predicate: (T) -> Collection<R>): Collection<R>? {
var result: Collection<R> = emptyList()
for (e in this) {
result = predicate(e)
if (result.isNotEmpty()) break
}
return result
}
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 inlineFunctions = HashSet<KtNamedFunction>()
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
if (InlineUtil.isInline(descriptor) && (analyzeInlineFunctions || hasReifiedTypeParameters(descriptor))) {
val declaration = DescriptorToSourceUtilsIde.getAnyDeclaration(project, descriptor)
if (declaration != null && declaration is KtNamedFunction && !analyzedElements.contains(declaration)) {
inlineFunctions.add(declaration)
}
}
}
})
analyzedElements.add(element)
if (!inlineFunctions.isEmpty() && deep < 10) {
for (inlineFunction in inlineFunctions) {
val body = inlineFunction.bodyExpression
if (body != null) {
innerContexts.add(analyzeElementWithInline(resolutionFacade, inlineFunction, deep + 1, analyzedElements, analyzeInlineFunctions))
}
}
analyzedElements.addAll(inlineFunctions)
}
return CompositeBindingContext.create(innerContexts)
}
private fun hasReifiedTypeParameters(descriptor: CallableDescriptor): Boolean {
return descriptor.typeParameters.any() { it.isReified }
}
}
@@ -0,0 +1,37 @@
/*
* 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.KtFile
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
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 KtFile && containingFile.suppressDiagnosticsInDebugMode) {
val diagnosticFactory = diagnostic.factory
return diagnosticFactory == Errors.UNSAFE_CALL
}
return false
}
}
@@ -0,0 +1,111 @@
/*
* 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.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.resolve.BindingContext
import org.jetbrains.kotlin.resolve.inline.InlineUtil
class InlineCallableUsagesSearcher(
private val myDebugProcess: DebugProcess,
val scopes: List<GlobalSearchScope>
) {
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) {
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)) {
GlobalSearchScope.union(scopes.toTypedArray())
}
else {
myDebugProcess.searchScope
}
}
}
@@ -0,0 +1,68 @@
/*
* 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.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.components.StoragePathMacros
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(file = StoragePathMacros.APP_CONFIG + "/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?) {
if (state != null) XmlSerializerUtil.copyBean<KotlinDebuggerSettings>(state, this)
}
}
@@ -0,0 +1,27 @@
<?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>
@@ -0,0 +1,50 @@
/*
* 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;
}
}
@@ -0,0 +1,145 @@
/*
* 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
}
}
val parent = ktElement.parent
val newExpression = when (parent) {
is KtThisExpression -> parent
is KtSuperExpression -> {
val pparent = parent.parent
when (pparent) {
is KtQualifiedExpression -> pparent
else -> parent
}
}
is KtReferenceExpression -> {
val pparent = parent.parent
if (pparent is KtQualifiedExpression && pparent.selectorExpression == parent) {
pparent
}
else {
parent
}
}
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
}
}
}
@@ -0,0 +1,191 @@
/*
* 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.analyzeFully
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.*
class KotlinFrameExtraVariablesProvider : FrameExtraVariablesProvider {
override fun isAvailable(sourcePosition: SourcePosition, evalContext: EvaluationContext): Boolean {
if (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 = Math.max(limit.startOffset, line)
while (startLine - 1 > limit.startOffset && shouldSkipLine(file, doc, startLine - 1)) {
startLine--
}
var endLine = Math.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 {
val context = expression.analyzeFully()
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
}
}
@@ -0,0 +1,30 @@
/*
* 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;
}
}
@@ -0,0 +1,304 @@
/*
* 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.jdi.StackFrameProxyImpl
import com.intellij.debugger.requests.ClassPrepareRequestor
import com.intellij.psi.PsiFile
import com.intellij.psi.impl.compiled.ClsFileImpl
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.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.KtClass
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtFunction
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 scopes: List<GlobalSearchScope> = listOf(
myDebugProcess.searchScope,
KotlinSourceFilterScope.librarySources(GlobalSearchScope.allScope(myDebugProcess.project), myDebugProcess.project)
)
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.declaringType().containsKotlinStrata()) {
return KotlinStackFrame(frame)
}
return null
}
override fun getSourcePosition(location: Location?): SourcePosition? {
if (location == null) throw NoDataException.INSTANCE
val fileName = location.safeSourceName ?: throw NoDataException.INSTANCE
if (!DebuggerUtils.isKotlinSourceFile(fileName)) throw NoDataException.INSTANCE
val psiFile = getPsiFileByLocation(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, scopes, javaClassName, javaSourceFileName)
if (defaultPsiFile != null) {
return SourcePosition.createFromLine(defaultPsiFile, 0)
}
}
catch(e: AbsentInformationException) {
// ignored
}
}
throw NoDataException.INSTANCE
}
val sourceLineNumber = try {
location.lineNumber() - 1
}
catch (e: InternalError) {
-1
}
if (sourceLineNumber < 0) {
throw NoDataException.INSTANCE
}
val lambdaOrFunIfInside = getLambdaOrFunIfInside(location, psiFile as KtFile, 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)
}
return SourcePosition.createFromLine(psiFile, sourceLineNumber)
}
// 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)) {
return literal
}
continue
}
val internalClassNames = DebuggerClassNameProvider(myDebugProcess, scopes, alwaysReturnLambdaParentClass = false)
.getOuterClassNamesForElement(literal.firstChild)
.classNames
if (internalClassNames.any { it == currentLocationClassName }) {
return literal
}
}
return null
}
private val Location.safeSourceName: String? get() {
return try {
sourceName()
}
catch (e: AbsentInformationException) {
null
}
catch (e: InternalError) {
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, scopes, className, sourceName)
}
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, scopes).getClassesForPosition(sourcePosition)
}
if (psiFile is ClsFileImpl) {
val decompiledPsiFile = psiFile.readAction { it.decompiledPsiFile }
if (decompiledPsiFile is KtClsFile && 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, scopes, 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
}
val classNames = DebuggerClassNameProvider(myDebugProcess, scopes).getOuterClassNamesForPosition(position)
return classNames.flatMap { name ->
listOfNotNull(
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, name),
myDebugProcess.requestsManager.createClassPrepareRequest(requestor, "$name$*")
)
}
}
private fun ReferenceType.containsKotlinStrata() = availableStrata().contains(KOTLIN_STRATA_NAME)
}
inline fun <U, V> U.readAction(crossinline f: (U) -> V): V {
return runReadAction { f(this) }
}
@@ -0,0 +1,28 @@
/*
* 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;
public class KotlinPositionManagerFactory extends PositionManagerFactory {
@Override
public PositionManager createPositionManager(DebugProcess process) {
return new KotlinPositionManager(process);
}
}
@@ -0,0 +1,32 @@
/*
* 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
}
}
@@ -0,0 +1,139 @@
/*
* 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) {
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
}
}
@@ -0,0 +1,32 @@
/*
* 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.JavaStackFrame
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 org.jetbrains.kotlin.codegen.inline.isFakeLocalVariableForInline
class KotlinStackFrame(frame: StackFrameProxyImpl) : JavaStackFrame(StackFrameDescriptorImpl(frame, MethodsTracker()), true) {
override fun getVisibleVariables(): List<LocalVariableProxyImpl>? {
return super.getVisibleVariables().filter {
!isFakeLocalVariableForInline(it.name())
}
}
}
@@ -0,0 +1,334 @@
/*
* 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.ConcurrentWeakFactoryMap
import com.intellij.util.containers.ContainerUtil
import com.sun.jdi.Location
import com.sun.jdi.ReferenceType
import org.jetbrains.kotlin.codegen.inline.API
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.lexer.KtTokens
import org.jetbrains.kotlin.load.kotlin.VirtualFileFinder
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.*
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()
}
class WeakBytecodeDebugInfoStorage : ConcurrentWeakFactoryMap<BinaryCacheKey, BytecodeDebugInfo?>() {
override fun create(key: BinaryCacheKey): BytecodeDebugInfo? {
val bytes = readClassFileImpl(key.project, key.jvmName, key.file) ?: return null
val smapData = readDebugInfo(bytes)
val lineNumberMapping = readLineNumberTableMapping(bytes)
return BytecodeDebugInfo(smapData, lineNumberMapping)
}
override fun createMap(): Map<BinaryCacheKey, BytecodeDebugInfo?> {
return ContainerUtil.createConcurrentWeakKeyWeakValueMap()
}
}
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()))
val fileFinder = VirtualFileFinder.getInstance(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(API) {
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.ASM5, 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() =
(emulateDexDebugInTests && ApplicationManager.getApplication().isUnitTestMode) ||
(this.virtualMachineProxy as? VirtualMachineProxyImpl)?.virtualMachine?.name() == "Dalvik" // TODO: check other machine names
@@ -0,0 +1,168 @@
<?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>
@@ -0,0 +1,403 @@
/*
* 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());
}
}
@@ -0,0 +1,36 @@
/*
* 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)
@@ -0,0 +1,403 @@
/*
* 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.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(psiFile: PsiFile?) {
val property = getProperty(sourcePosition)
if (property != null) {
setFieldName(property.name!!)
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.allLineLocations().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
}
}
override fun getInvalidIcon(isMuted: Boolean): Icon {
return when {
isMuted -> AllIcons.Debugger.Db_muted_invalid_field_breakpoint
else -> AllIcons.Debugger.Db_invalid_field_breakpoint
}
}
override fun getVerifiedIcon(isMuted: Boolean): Icon {
return when {
isMuted -> AllIcons.Debugger.Db_muted_verified_field_breakpoint
else -> AllIcons.Debugger.Db_verified_field_breakpoint
}
}
override fun getVerifiedWarningsIcon(isMuted: Boolean): Icon {
return when {
isMuted -> AllIcons.Debugger.Db_muted_field_warning_breakpoint
else -> AllIcons.Debugger.Db_field_warning_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()
}
}
@@ -0,0 +1,88 @@
/*
* 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()
}
}
}
@@ -0,0 +1,178 @@
/*
* 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
}
@@ -0,0 +1,169 @@
/*
* 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 kotlin.text.StringsKt;
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.KotlinIcons;
import org.jetbrains.kotlin.psi.KtClassInitializer;
import org.jetbrains.kotlin.psi.KtFunction;
import javax.swing.*;
import java.util.List;
public class KotlinLineBreakpointType extends JavaLineBreakpointType {
public KotlinLineBreakpointType() {
super("kotlin-line", "Kotlin Line Breakpoints");
}
@Override
public boolean matchesPosition(@NotNull LineBreakpoint<?> breakpoint, @NotNull SourcePosition position) {
JavaBreakpointProperties properties = getProperties(breakpoint);
if (properties == null || properties instanceof JavaLineBreakpointProperties) {
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;
}
public class KotlinLambdaBreakpointVariant extends ExactJavaBreakpointVariant {
public KotlinLambdaBreakpointVariant(@NotNull XSourcePosition position, @Nullable KtFunction function, Integer lambdaOrdinal) {
super(position, function, lambdaOrdinal);
}
@Override
public Icon getIcon() {
return KotlinIcons.LAMBDA;
}
}
public class KotlinLineBreakpointVariant extends ExactJavaBreakpointVariant {
public KotlinLineBreakpointVariant(XSourcePosition position, PsiElement element) {
super(position, element, -1);
}
@Override
public String getText() {
return StringsKt.replace(super.getText(), " ", "", true);
}
@Override
public Icon getIcon() {
return KotlinIcons.FUNCTION;
}
}
}
@@ -0,0 +1,41 @@
/*
* 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
}
}
@@ -0,0 +1,146 @@
/*
* 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.KotlinLineBreakpointVariant(
XSourcePositionImpl.createByElement(mainMethod),
CodeInsightUtils.getTopmostElementAtOffset(elementAt, pos.offset)))
}
lambdas.forEachIndexed { ordinal, lambda ->
val positionImpl = XSourcePositionImpl.createByElement(lambda.bodyExpression)
if (positionImpl != null) {
result.add(kotlinBreakpointType.KotlinLambdaBreakpointVariant(positionImpl, lambda, ordinal))
}
}
val allBreakpoint = (kotlinBreakpointType as JavaLineBreakpointType).JavaBreakpointVariant(position)
result.addFirst(allBreakpoint)
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.bodyExpression as? KtBlockExpression)?.statements?.firstOrNull() ?: it
statement.getLineNumber() == line && statement.getLineNumber(false) == line
}
}
@@ -0,0 +1,62 @@
<?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>
@@ -0,0 +1,144 @@
/*
* 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 com.intellij.ui.DocumentAdapter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.core.util.DescriptorMemberChooserObject;
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() {
myClassChooser.getTextField().getDocument().addDocumentListener(new DocumentAdapter() {
@Override
public void textChanged(DocumentEvent event) {
updateUI();
}
});
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();
}
}
}
@@ -0,0 +1,46 @@
/*
* 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()
}
@@ -0,0 +1,237 @@
/*
* 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.DebugProcessImpl
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
import com.intellij.debugger.engine.events.DebuggerCommandImpl
import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.debugger.jdi.LocalVariableProxyImpl
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.coroutines.CONTINUATION_ASM_TYPE
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
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 java.util.*
fun isInsideInlineFunctionBody(visibleVariables: List<LocalVariableProxyImpl>): Boolean {
return visibleVariables.any { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) }
}
fun numberOfInlinedFunctions(visibleVariables: List<LocalVariableProxyImpl>): Int {
return visibleVariables.count { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION) }
}
fun isInsideInlineArgument(inlineArgument: KtFunction, location: Location, debugProcess: DebugProcessImpl): Boolean {
val visibleVariables = location.visibleVariables(debugProcess)
val context = KotlinDebuggerCaches.getOrCreateTypeMapper(inlineArgument).bindingContext
val lambdaOrdinal = runReadAction { lambdaOrdinalByArgument(inlineArgument, context) }
val markerLocalVariables = visibleVariables.filter { it.name().startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_ARGUMENT) }
val functionName = runReadAction { functionNameByArgument(inlineArgument, context) }
return markerLocalVariables.firstOrNull {
lambdaOrdinalByLocalVariable(it.name()) == lambdaOrdinal && functionNameByLocalVariable(it.name()) == functionName
} != null
}
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().variables()
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 val DO_RESUME_SIGNATURE = "(Ljava/lang/Object;Ljava/lang/Throwable;)Ljava/lang/Object;"
fun isInSuspendMethod(location: Location): Boolean {
val method = location.method()
val signature = method.signature()
return signature.contains(CONTINUATION_ASM_TYPE.toString()) ||
(method.name() == DO_RESUME_METHOD_NAME && signature == DO_RESUME_SIGNATURE)
}
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().allLineLocations().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().allLineLocations()
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
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2000-2014 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 org.jetbrains.annotations.NotNull;
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;
public class CompilingEvaluatorUtils {
// Copied from com.intellij.debugger.ui.impl.watch.CompilingEvaluator.changeSuperToMagicAccessor
public static byte[] changeSuperToMagicAccessor(byte[] bytes) {
ClassWriter classWriter = new ClassWriter(0);
ClassVisitor classVisitor = new ClassVisitor(Opcodes.API_VERSION, classWriter) {
@Override
public void visit(int version, int access, @NotNull String name, String signature, String superName, String[] interfaces) {
if ("java/lang/Object".equals(superName)) {
superName = "sun/reflect/MagicAccessorImpl";
}
super.visit(version, access, name, signature, superName, interfaces);
}
};
new ClassReader(bytes).accept(classVisitor, 0);
return classWriter.toByteArray();
}
}
@@ -0,0 +1,251 @@
/*
* 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.engine.evaluation.EvaluateExceptionUtil
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.sun.jdi.ClassType
import com.sun.jdi.InvalidStackFrameException
import com.sun.jdi.ObjectReference
import org.jetbrains.eval4j.Value
import org.jetbrains.eval4j.jdi.asJdiValue
import org.jetbrains.eval4j.jdi.asValue
import org.jetbrains.eval4j.obj
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.inline.INLINE_FUN_VAR_SUFFIX
import org.jetbrains.kotlin.codegen.inline.INLINE_TRANSFORMATION_SUFFIX
import org.jetbrains.kotlin.codegen.inline.NUMBERED_FUNCTION_PREFIX
import org.jetbrains.kotlin.idea.debugger.isInsideInlineFunctionBody
import org.jetbrains.kotlin.idea.debugger.numberOfInlinedFunctions
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.org.objectweb.asm.Type
class FrameVisitor(context: EvaluationContextImpl) {
private val scope = context.debugProcess.searchScope
private val frame = context.frameProxy
companion object {
val OBJECT_TYPE = Type.getType(Any::class.java)
}
fun findValue(name: String, asmType: Type?, checkType: Boolean, failIfNotFound: Boolean): Value? {
if (frame == null) return null
try {
when (name) {
THIS_NAME -> {
val thisValue = findThis(asmType)
if (thisValue != null) {
return thisValue
}
}
else -> {
if (isInsideInlineFunctionBody(frame.visibleVariables())) {
val number = numberOfInlinedFunctions(frame.visibleVariables())
for (inlineFunctionIndex in number downTo 1) {
val inlineFunVar = findLocalVariableForInlineArgument(name, inlineFunctionIndex, asmType, true)
if (inlineFunVar != null) {
return inlineFunVar
}
}
}
if (isFunctionType(asmType)) {
val variableForLocalFun = findLocalVariableForLocalFun(name, asmType, checkType)
if (variableForLocalFun != null) {
return variableForLocalFun
}
}
val localVariable = findLocalVariable(name, asmType, checkType)
if (localVariable != null) {
return localVariable
}
getCapturedFieldNames(name).asSequence()
.mapNotNull { findCapturedLocalVariable(it, asmType, checkType) }
.firstOrNull()
?.let { return it }
}
}
return fail("Cannot find local variable: name = $name${if (checkType) ", type = " + asmType.toString() else ""}", failIfNotFound)
}
catch(e: InvalidStackFrameException) {
throw EvaluateExceptionUtil.createEvaluateException("Local variable $name is unavailable in current frame")
}
}
private fun fail(message: String, shouldFail: Boolean): Value? {
return if (shouldFail) throw EvaluateExceptionUtil.createEvaluateException(message) else null
}
private fun findThis(asmType: Type?): Value? {
if (isInsideInlineFunctionBody(frame!!.visibleVariables())) {
val number = numberOfInlinedFunctions(frame.visibleVariables())
val inlineFunVar = findLocalVariableForInlineArgument("this_", number, asmType, true)
if (inlineFunVar != null) {
return inlineFunVar
}
}
val thisObject = frame.thisObject()
if (thisObject != null) {
val eval4jValue = thisObject.asValue()
if (isValueOfCorrectType(eval4jValue, asmType, true)) return eval4jValue
}
val receiver = findValue(RECEIVER_NAME, asmType, checkType = true, failIfNotFound = false)
if (receiver != null) return receiver
val this0 = findValue(AsmUtil.CAPTURED_THIS_FIELD, asmType, checkType = true, failIfNotFound = false)
if (this0 != null) return this0
val `$this` = findValue("\$this", asmType, checkType = false, failIfNotFound = false)
if (`$this` != null) return `$this`
return null
}
private fun findLocalVariableForLocalFun(name: String, asmType: Type?, checkType: Boolean): Value? {
return findLocalVariable(name + "$", asmType, checkType)
}
private fun findLocalVariableForInlineArgument(name: String, number: Int, asmType: Type?, checkType: Boolean): Value? {
return findLocalVariable(name + INLINE_FUN_VAR_SUFFIX.repeat(number), asmType, checkType)
}
private fun isFunctionType(type: Type?): Boolean {
return type?.sort == Type.OBJECT &&
type.internalName.startsWith(NUMBERED_FUNCTION_PREFIX)
}
private fun findLocalVariable(name: String, asmType: Type?, checkType: Boolean): Value? {
val localVariable = frame!!.visibleVariableByName(name) ?: return null
val eval4jValue = frame.getValue(localVariable).asValue()
val sharedVarValue = getValueIfSharedVar(eval4jValue, asmType, checkType)
if (sharedVarValue != null) {
return sharedVarValue
}
if (isValueOfCorrectType(eval4jValue, asmType, checkType)) {
return eval4jValue
}
return null
}
private fun findCapturedLocalVariable(name: String, asmType: Type?, checkType: Boolean): Value? {
val thisObject = frame?.thisObject() ?: return null
var thisObj: Value? = thisObject.asValue()
var capturedVal: Value? = null
while (capturedVal == null && thisObj != null) {
capturedVal = getField(thisObj, name, asmType, checkType)
if (capturedVal == null) {
thisObj = getField(thisObj, AsmUtil.CAPTURED_THIS_FIELD, null, false)
}
}
if (capturedVal != null) {
val sharedVarValue = getValueIfSharedVar(capturedVal, asmType, checkType)
if (sharedVarValue != null) {
return sharedVarValue
}
return capturedVal
}
return null
}
private fun isValueOfCorrectType(value: Value, asmType: Type?, shouldCheckType: Boolean): Boolean {
if (!shouldCheckType || asmType == null || value.asmType == asmType) return true
if (asmType == OBJECT_TYPE) return true
if ((value.obj() as? com.sun.jdi.ObjectReference)?.referenceType().isSubclass(asmType.className)) {
return true
}
val thisDesc = value.asmType.getClassDescriptor(scope)
val expDesc = asmType.getClassDescriptor(scope)
return thisDesc != null && expDesc != null && runReadAction { DescriptorUtils.isSubclass(thisDesc, expDesc) }
}
private fun getField(owner: Value, name: String, asmType: Type?, checkType: Boolean): Value? {
try {
val obj = owner.asJdiValue(frame!!.virtualMachine.virtualMachine, owner.asmType)
if (obj !is ObjectReference) return null
val _class = obj.referenceType()
val field = _class.fieldByName(name) ?: return null
val fieldValue = obj.getValue(field).asValue()
if (isValueOfCorrectType(fieldValue, asmType, checkType)) return fieldValue
return null
}
catch (e: Exception) {
return null
}
}
private fun Value.isSharedVar(): Boolean {
return this.asmType.sort == Type.OBJECT && this.asmType.internalName.startsWith(AsmTypes.REF_TYPE_PREFIX)
}
fun getValueIfSharedVar(value: Value, expectedType: Type?, checkType: Boolean): Value? {
if (!value.isSharedVar()) return null
val sharedVarValue = getField(value, "element", expectedType, checkType)
if (sharedVarValue != null && isValueOfCorrectType(sharedVarValue, expectedType, checkType)) {
return sharedVarValue
}
return null
}
private fun getCapturedFieldNames(name: String): List<String> = when (name) {
RECEIVER_NAME -> listOf(AsmUtil.CAPTURED_RECEIVER_FIELD)
THIS_NAME -> listOf(AsmUtil.CAPTURED_THIS_FIELD)
AsmUtil.CAPTURED_RECEIVER_FIELD -> listOf(name)
AsmUtil.CAPTURED_THIS_FIELD -> listOf(name)
else -> {
val simpleName = "$$name"
listOf(simpleName, simpleName + INLINE_TRANSFORMATION_SUFFIX)
}
}
private fun com.sun.jdi.Type?.isSubclass(superClassName: String): Boolean {
if (this !is ClassType) return false
if (allInterfaces().any { it.name() == superClassName }) {
return true
}
var superClass = this.superclass()
while (superClass != null) {
if (superClass.name() == superClassName) {
return true
}
superClass = superClass.superclass()
}
return false
}
}
@@ -0,0 +1,429 @@
/*
* 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.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.debugger.ui.impl.watch.NodeDescriptorImpl
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.intellij.xdebugger.XDebuggerManager
import com.intellij.xdebugger.impl.XDebugSessionImpl
import com.intellij.xdebugger.impl.ui.tree.ValueMarkup
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.core.quoteIfNeeded
import org.jetbrains.kotlin.idea.debugger.KotlinEditorTextProvider
import org.jetbrains.kotlin.idea.j2k.J2kPostProcessor
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.versions.getKotlinJvmRuntimeMarkerClass
import org.jetbrains.kotlin.j2k.AfterConversionPass
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.typeUtil.makeNullable
import java.util.concurrent.atomic.AtomicReference
class KotlinCodeFragmentFactory: CodeFragmentFactory() {
private val LOG = Logger.getInstance(this::class.java)
override fun createCodeFragment(item: TextWithImports, context: PsiElement?, project: Project): JavaCodeFragment {
val contextElement = getWrappedContextElement(project, context)
if (contextElement == null) {
LOG.warn("CodeFragment with null context created:\noriginalContext = ${context?.getElementTextWithContext()}")
}
val codeFragment = if (item.kind == CodeFragmentKind.EXPRESSION) {
KtExpressionCodeFragment(
project,
"fragment.kt",
item.text,
initImports(item.imports),
contextElement
)
}
else {
KtBlockCodeFragment(
project,
"fragment.kt",
item.text,
initImports(item.imports),
contextElement
)
}
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, "this_0", 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}_java_locals_debug_fun_() {\n$kotlinVariablesText\n}"
val fakeFile = createFakeFileWithJavaContextElement(fakeFunctionText, contextElement)
val fakeFunction = fakeFile.declarations.firstOrNull() as? KtFunction
val fakeContext = (fakeFunction?.bodyExpression as? KtBlockExpression)?.statements?.lastOrNull()
return@putCopyableUserData wrapContextIfNeeded(project, contextElement, fakeContext) ?: emptyFile
})
}
return codeFragment
}
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 = frame?.let {
val values = it.getValues(it.visibleVariables())
values.filterValues { it != null }
} ?: emptyMap()
frameInfo = FrameInfo(frame?.thisObject(), visibleVariables)
}
catch(ignored: AbsentInformationException) {
// Debug info unavailable
}
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
}
private fun getWrappedContextElement(project: Project, context: PsiElement?): PsiElement? {
val newContext = getContextElement(context)
if (newContext !is KtElement) return newContext
return wrapContextIfNeeded(project, context, newContext)
}
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) {
var convertedFragment: KtExpressionCodeFragment? = null
project.executeWriteCommand("Convert java expression to kotlin in Evaluate Expression") {
val newText = javaExpression.j2kText()
val newImports = importList?.j2kText()
if (newText != null) {
convertedFragment = KtExpressionCodeFragment(
project,
kotlinCodeFragment.name,
newText,
newImports,
kotlinCodeFragment.context
)
AfterConversionPass(project, J2kPostProcessor(formatCode = false)).run(convertedFragment!!, range = null)
}
}
return convertedFragment ?: kotlinCodeFragment
}
}
return kotlinCodeFragment
}
override fun isContextAccepted(contextElement: PsiElement?): Boolean {
return 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.INSTANCE
override fun getEvaluatorBuilder() = KotlinEvaluationBuilder
companion object {
val LABEL_VARIABLE_VALUE_KEY: Key<Value> = Key.create<Value>("_label_variable_value_key_")
val DEBUG_LABEL_SUFFIX: String = "_DebugLabel"
@TestOnly val DEBUG_CONTEXT_FOR_TESTS: Key<DebuggerContextImpl> = Key.create("DEBUG_CONTEXT_FOR_TESTS")
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
}
//internal for tests
fun createCodeFragmentForLabeledObjects(project: Project, markupMap: Map<*, ValueMarkup>): Pair<String, Map<String, Value>> {
@Suppress("UNCHECKED_CAST")
val variables = markupMap.entries.associate {
val (value, markup) = it
"${markup.text}$DEBUG_LABEL_SUFFIX" to value as? Value
}.filterValues { it != null } as Map<String, Value>
return variables.kotlinVariablesAsText(project) to variables
}
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 wrapContextIfNeeded(project: Project, originalContext: PsiElement?, newContext: KtElement?): KtElement? {
val markupMap: Map<*, ValueMarkup>? =
if (ApplicationManager.getApplication().isUnitTestMode)
NodeDescriptorImpl.getMarkupMap(originalContext?.getCopyableUserData(DEBUG_CONTEXT_FOR_TESTS)?.debugProcess)
else
(XDebuggerManager.getInstance(project).currentSession as? XDebugSessionImpl)?.valueMarkers?.allMarkers
if (markupMap == null || markupMap.isEmpty()) return newContext
val (text, labels) = createCodeFragmentForLabeledObjects(project, markupMap)
if (text.isEmpty()) return newContext
return createWrappingContext(text, labels, newContext, project)
}
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)
}
// internal for test
fun createWrappingContext(
newFragmentText: String,
labels: Map<String, Value>,
originalContext: KtElement?,
project: Project
): KtElement? {
val codeFragment = KtPsiFactory(project).createBlockCodeFragment(newFragmentText, originalContext)
codeFragment.accept(object : KtTreeVisitorVoid() {
override fun visitProperty(property: KtProperty) {
val reference = labels.get(property.name)
if (reference != null) {
property.putUserData(LABEL_VARIABLE_VALUE_KEY, reference)
}
}
})
return codeFragment.getContentElement().statements.lastOrNull()
}
}
@@ -0,0 +1,255 @@
/*
* 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.EvaluationContextImpl
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.apache.log4j.Logger
import org.jetbrains.annotations.TestOnly
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.descriptors.ClassDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyzeAndGetResult
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFullyAndGetResult
import org.jetbrains.kotlin.idea.debugger.BinaryCacheKey
import org.jetbrains.kotlin.idea.debugger.BytecodeDebugInfo
import org.jetbrains.kotlin.idea.debugger.WeakBytecodeDebugInfoStorage
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
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.DescriptorUtils
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<PsiElement, List<String>>(),
PsiModificationTracker.MODIFICATION_COUNT)
}, false)
private val cachedTypeMappers = CachedValuesManager.getManager(project).createCachedValue(
{
CachedValueProvider.Result<MutableMap<PsiElement, KotlinTypeMapper>>(
ConcurrentHashMap<PsiElement, KotlinTypeMapper>(),
PsiModificationTracker.MODIFICATION_COUNT)
}, false)
private val debugInfoCache = CachedValuesManager.getManager(project).createCachedValue(
{
CachedValueProvider.Result<WeakBytecodeDebugInfoStorage>(
WeakBytecodeDebugInfoStorage(),
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 getOrCreateCompiledData(
codeFragment: KtCodeFragment,
sourcePosition: SourcePosition,
evaluationContext: EvaluationContextImpl,
create: (KtCodeFragment, SourcePosition) -> CompiledDataDescriptor
): CompiledDataDescriptor {
val evaluateExpressionCache = getInstance(codeFragment.project)
val text = "${codeFragment.importsToString()}\n${codeFragment.text}"
val cached = synchronized<Collection<CompiledDataDescriptor>>(evaluateExpressionCache.cachedCompiledData) {
val cache = evaluateExpressionCache.cachedCompiledData.value!!
cache[text]
}
val answer = cached.firstOrNull {
it.sourcePosition == sourcePosition || evaluateExpressionCache.canBeEvaluatedInThisContext(it, evaluationContext)
}
if (answer != null) {
return answer
}
val newCompiledData = create(codeFragment, sourcePosition)
LOG.debug("Compile bytecode for ${codeFragment.text}")
synchronized(evaluateExpressionCache.cachedCompiledData) {
evaluateExpressionCache.cachedCompiledData.value.putValue(text, newCompiledData)
}
return newCompiledData
}
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 = 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.analyzeFullyAndGetResult().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
}
}
private fun canBeEvaluatedInThisContext(compiledData: CompiledDataDescriptor, context: EvaluationContextImpl): Boolean {
val frameVisitor = FrameVisitor(context)
return compiledData.parameters.all { p ->
val (name, jetType) = p
val value = frameVisitor.findValue(name, asmType = null, checkType = false, failIfNotFound = false)
if (value == null) return@all false
val thisDescriptor = value.asmType.getClassDescriptor(context.debugProcess.searchScope)
val superClassDescriptor = jetType.constructor.declarationDescriptor as? ClassDescriptor
return@all thisDescriptor != null && superClassDescriptor != null && runReadAction { DescriptorUtils.isSubclass(thisDescriptor, superClassDescriptor) }
}
}
data class CompiledDataDescriptor(
val classes: List<ClassToLoad>,
val sourcePosition: SourcePosition,
val parameters: ParametersDescriptor
)
class ParametersDescriptor : Iterable<Parameter> {
private val list = ArrayList<Parameter>()
fun add(name: String, jetType: KotlinType, value: Value? = null) {
list.add(Parameter(name, jetType, value))
}
override fun iterator() = list.iterator()
}
data class Parameter(val callText: String, val type: KotlinType, val value: Value? = null)
class ComputedClassNames(val classNames: List<String>, val shouldBeCached: Boolean) {
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)
}
}
private fun String?.toList() = if (this == null) emptyList() else listOf(this)
@@ -0,0 +1,647 @@
/*
* 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.SuspendContext
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.diagnostic.LogMessageEx
import com.intellij.openapi.diagnostic.Attachment
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.vfs.CharsetToolkit
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileFactory
import com.intellij.psi.impl.PsiFileFactoryImpl
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.testFramework.LightVirtualFile
import com.intellij.util.ExceptionUtil
import com.sun.jdi.*
import com.sun.jdi.request.EventRequest
import org.jetbrains.eval4j.*
import org.jetbrains.eval4j.Value
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.caches.resolve.KotlinCacheService
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CompilerConfiguration
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.KotlinLanguage
import org.jetbrains.kotlin.idea.caches.resolve.getJavaClassDescriptor
import org.jetbrains.kotlin.idea.core.quoteIfNeeded
import org.jetbrains.kotlin.idea.core.quoteSegmentsIfNeeded
import org.jetbrains.kotlin.idea.debugger.DebuggerUtils
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.CompiledDataDescriptor
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinDebuggerCaches.ParametersDescriptor
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClasses
import org.jetbrains.kotlin.idea.debugger.evaluate.compilingEvaluator.loadClassesSafely
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.ExtractionResult
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
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.platform.JavaToKotlinClassMap
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.codeFragmentUtil.debugTypeInfo
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.BindingTrace
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.kotlin.resolve.jvm.diagnostics.ErrorsJvm
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.Opcodes.ASM5
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.tree.ClassNode
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.util.*
internal val RECEIVER_NAME = "\$receiver"
internal val THIS_NAME = "this"
internal val LOG = Logger.getInstance("#org.jetbrains.kotlin.idea.debugger.evaluate.KotlinEvaluator")
internal val GENERATED_FUNCTION_NAME = "generated_for_debugger_kotlin_rulezzzz"
private val DEBUG_MODE = false
object KotlinEvaluationBuilder : EvaluatorBuilder {
override fun build(codeFragment: PsiElement, position: SourcePosition?): ExpressionEvaluator {
if (codeFragment !is KtCodeFragment || position == null) {
return EvaluatorBuilderImpl.getInstance()!!.build(codeFragment, position)
}
if (position.line < 0) {
throw EvaluateExceptionUtil.createEvaluateException("Couldn't evaluate kotlin expression at $position")
}
val file = position.file
if (file is KtFile) {
val document = PsiDocumentManager.getInstance(file.project).getDocument(file)
if (document == null || document.lineCount < position.line) {
throw EvaluateExceptionUtil.createEvaluateException(
"Couldn't evaluate kotlin expression: breakpoint is placed outside the file. " +
"It may happen when you've changed source file after starting a debug process.")
}
}
if (codeFragment.context !is KtElement) {
val attachments = arrayOf(attachmentByPsiFile(position.file),
attachmentByPsiFile(codeFragment),
Attachment("breakpoint.info", "line: ${position.line}"))
LOG.error("Trying to evaluate ${codeFragment::class.java} with context ${codeFragment.context?.javaClass}", mergeAttachments(*attachments))
throw EvaluateExceptionUtil.createEvaluateException("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()
}
var isCompiledDataFromCache = true
try {
val compiledData = KotlinDebuggerCaches.getOrCreateCompiledData(codeFragment, sourcePosition, context) {
fragment, position ->
isCompiledDataFromCache = false
extractAndCompile(fragment, position, context)
}
val classLoaderHandler = loadClassesSafely(context, compiledData.classes)
val result = if (classLoaderHandler != null) {
try {
evaluateWithCompilation(context, compiledData) ?: runEval4j(context, compiledData)
} finally {
classLoaderHandler.dispose()
}
}
else {
runEval4j(context, compiledData)
}
// 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) {
// We need only lambda classes here cause we using only eval4j evaluation method
val classLoaderHandler = loadClasses(context, compiledData.classes.drop(1))
try {
return runEval4j(context, extractAndCompile(codeFragment, sourcePosition, context)).toJdiValue(context)
} finally {
classLoaderHandler?.dispose()
}
}
return if (result is InterpreterResult) {
result.toJdiValue(context)
} else {
result
}
}
catch(e: EvaluateException) {
throw e
}
catch(e: ProcessCanceledException) {
LOG.debug(e)
exception(e)
}
catch (e: Exception) {
val isSpecialException = isSpecialException(e)
if (isSpecialException) {
exception(e)
}
val text = runReadAction { codeFragment.context?.text ?: "null" }
val attachments = arrayOf(attachmentByPsiFile(sourcePosition.file),
attachmentByPsiFile(codeFragment),
Attachment("breakpoint.info", "line: ${sourcePosition.line}"),
Attachment("context.info", text))
LOG.error(LogMessageEx.createEvent(
"Couldn't evaluate expression",
ExceptionUtil.getThrowableText(e),
mergeAttachments(*attachments)))
val cause = if (e.message != null) ": ${e.message}" else ""
exception("An exception occurs during Evaluate Expression Action $cause")
}
}
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
}
}
override fun getModifier(): Modifier? {
return null
}
companion object {
private fun extractAndCompile(codeFragment: KtCodeFragment, sourcePosition: SourcePosition, context: EvaluationContextImpl): CompiledDataDescriptor {
codeFragment.checkForErrors()
val extractionResult = getFunctionForExtractedFragment(codeFragment, sourcePosition.file, sourcePosition.line)
?: throw IllegalStateException("Code fragment cannot be extracted to function: ${codeFragment.text}")
val parametersDescriptor = extractionResult.getParametersForDebugger(codeFragment)
val extractedFunction = extractionResult.declaration as KtNamedFunction
if (LOG.isDebugEnabled) {
LOG.debug("Extracted function:\n" + runReadAction { extractedFunction.text })
}
val classFileFactory = createClassFileFactory(codeFragment, extractedFunction, context, parametersDescriptor)
val outputFiles = classFileFactory.asList().filterClassFiles()
.sortedBy { it.relativePath.length }
for (file in outputFiles) {
if (LOG.isDebugEnabled) {
LOG.debug("Output file generated: ${file.relativePath}")
}
if (DEBUG_MODE) {
println(file.asText())
}
}
val additionalFiles = outputFiles.map { ClassToLoad(getClassName(it.relativePath), it.relativePath, it.asByteArray()) }
return CompiledDataDescriptor(
additionalFiles,
sourcePosition,
parametersDescriptor)
}
private fun getClassName(fileName: String): String {
return fileName.substringBeforeLast(".class").replace("/", ".")
}
private val CompiledDataDescriptor.mainClass
get() = classes.firstOrNull() ?: error(
"Can't find main class for " + sourcePosition.elementAt.getParentOfType<KtDeclaration>(strict = false))
private fun evaluateWithCompilation(context: EvaluationContextImpl, compiledData: CompiledDataDescriptor): Any? {
val vm = context.debugProcess.virtualMachineProxy.virtualMachine
val classLoader = context.classLoader ?: return null
val mainClassBytecode = compiledData.mainClass.bytes
try {
val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, ClassReader.SKIP_CODE) }
val mainClassJdiName = mainClassAsmNode.name.replace('/', '.')
assert(mainClassAsmNode.methods.size == 1)
val methodToInvoke = mainClassAsmNode.methods[0]
assert(methodToInvoke.parameters == null || methodToInvoke.parameters.isEmpty())
val mainClass = context.debugProcess.findClass(context, mainClassJdiName, classLoader) as ClassType
val thread = context.suspendContext.thread?.threadReference!!
val invokePolicy = context.suspendContext.getInvokePolicy()
val eval = JDIEval(vm, classLoader, thread, invokePolicy)
return vm.executeWithBreakpointsDisabled {
// Prepare the main class
eval.loadClass(Type.getObjectType(mainClassAsmNode.name), classLoader)
val argumentTypes = Type.getArgumentTypes(methodToInvoke.desc)
val args = context.getArgumentsForEval4j(compiledData.parameters, argumentTypes)
.zip(argumentTypes)
.map { (value, type) ->
// Make argument type classes prepared for sure
eval.loadClass(type, classLoader)
boxOrUnboxArgumentIfNeeded(eval, value, type).asJdiValue(vm, type)
}
mainClass.invokeMethod(thread, mainClass.methods().single(), args, invokePolicy)
}
} catch (e: Throwable) {
LOG.debug("Unable to evaluate expression with compilation", e)
return null
}
}
private fun runEval4j(context: EvaluationContextImpl, compiledData: CompiledDataDescriptor): InterpreterResult {
val virtualMachine = context.debugProcess.virtualMachineProxy.virtualMachine
var resultValue: InterpreterResult? = null
// assert [0] with some context
val mainClassBytecode = compiledData.mainClass.bytes
ClassReader(mainClassBytecode).accept(object : ClassVisitor(ASM5) {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): MethodVisitor? {
if (name == GENERATED_FUNCTION_NAME) {
val argumentTypes = Type.getArgumentTypes(desc)
val args = context.getArgumentsForEval4j(compiledData.parameters, argumentTypes)
return object : MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) {
override fun visitEnd() {
virtualMachine.executeWithBreakpointsDisabled {
val eval = JDIEval(virtualMachine,
context.classLoader,
context.suspendContext.thread?.threadReference!!,
context.suspendContext.getInvokePolicy())
resultValue = interpreterLoop(
this,
makeInitialFrame(this, args.zip(argumentTypes).map { boxOrUnboxArgumentIfNeeded(eval, it.first, it.second) }),
eval
)
}
}
}
}
return super.visitMethod(access, name, desc, signature, exceptions)
}
}, 0)
return resultValue ?: throw IllegalStateException("resultValue is null: cannot find method " + GENERATED_FUNCTION_NAME)
}
private inline 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 boxOrUnboxArgumentIfNeeded(eval: JDIEval, argumentValue: Value, parameterType: Type): Value {
val argumentType = argumentValue.asmType
if (AsmUtil.isPrimitive(parameterType) && !AsmUtil.isPrimitive(argumentType)) {
try {
val unboxedType = AsmUtil.unboxType(argumentType)
if (parameterType == unboxedType) {
return eval.unboxType(argumentValue, parameterType)
}
}
catch(ignored: UnsupportedOperationException) {
}
}
if (!AsmUtil.isPrimitive(parameterType) && AsmUtil.isPrimitive(argumentType)) {
if (parameterType == FrameVisitor.OBJECT_TYPE || parameterType == AsmUtil.boxType(argumentType)) {
return eval.boxType(argumentValue)
}
}
return argumentValue
}
private fun InterpreterResult.toJdiValue(context: EvaluationContextImpl): com.sun.jdi.Value? {
val jdiValue = when (this) {
is ValueReturned -> result
is ExceptionThrown -> {
when {
this.kind == ExceptionThrown.ExceptionKind.FROM_EVALUATED_CODE ->
exception(InvocationException(this.exception.value as ObjectReference))
this.kind == ExceptionThrown.ExceptionKind.BROKEN_CODE ->
throw exception.value as Throwable
else ->
exception(exception.toString())
}
}
is AbnormalTermination -> exception(message)
else -> throw IllegalStateException("Unknown result value produced by eval4j")
}
val vm = context.debugProcess.virtualMachineProxy.virtualMachine
val sharedVar = FrameVisitor(context).getValueIfSharedVar(jdiValue, jdiValue.asmType, false)
return sharedVar?.asJdiValue(vm, sharedVar.asmType) ?: jdiValue.asJdiValue(vm, jdiValue.asmType)
}
private fun ExtractionResult.getParametersForDebugger(fragment: KtCodeFragment): ParametersDescriptor {
return runReadAction {
val valuesForLabels = HashMap<String, Value>()
val contextElementFile = fragment.context?.containingFile
if (contextElementFile is KtCodeFragment) {
contextElementFile.accept(object : KtTreeVisitorVoid() {
override fun visitProperty(property: KtProperty) {
val value = property.getUserData(KotlinCodeFragmentFactory.LABEL_VARIABLE_VALUE_KEY)
if (value != null) {
valuesForLabels.put(property.name?.quoteIfNeeded()!!, value.asValue())
}
}
})
}
val parameters = ParametersDescriptor()
val receiver = config.descriptor.receiverParameter
if (receiver != null) {
parameters.add(THIS_NAME, receiver.getParameterType(true))
}
for (param in config.descriptor.parameters) {
val paramName = when {
param.argumentText.contains("@") -> param.argumentText.substringBefore("@")
param.argumentText.startsWith("::") -> param.argumentText.substring(2)
else -> param.argumentText
}
parameters.add(paramName, param.getParameterType(true), valuesForLabels[paramName])
}
parameters
}
}
private fun EvaluationContextImpl.getArgumentsForEval4j(parameters: ParametersDescriptor, parameterTypes: Array<Type>): List<Value> {
val frameVisitor = FrameVisitor(this)
return parameters.zip(parameterTypes).map {
val result = if (it.first.value != null) {
it.first.value!!
}
else {
frameVisitor.findValue(it.first.callText, it.second, checkType = false, failIfNotFound = true)!!
}
if (LOG.isDebugEnabled) {
LOG.debug("Parameter for eval4j: name = ${it.first.callText}, type = ${it.second}, value = $result")
}
result
}
}
private fun createClassFileFactory(
codeFragment: KtCodeFragment,
extractedFunction: KtNamedFunction,
context: EvaluationContextImpl,
parameters: ParametersDescriptor
): ClassFileFactory {
return runReadAction {
val fileForDebugger = createFileForDebugger(codeFragment, extractedFunction)
if (LOG.isDebugEnabled) {
LOG.debug("File for eval4j:\n${runReadAction { fileForDebugger.text }}")
}
val (bindingContext, moduleDescriptor, files) = fileForDebugger.checkForErrors(true, codeFragment.getContextContainingFile())
val generateClassFilter = object : GenerationState.GenerateClassFilter() {
override fun shouldGeneratePackagePart(ktFile: KtFile) = ktFile == fileForDebugger
override fun shouldAnnotateClass(processingClassOrObject: KtClassOrObject) = true
override fun shouldGenerateClass(processingClassOrObject: KtClassOrObject) = processingClassOrObject.containingKtFile == fileForDebugger
override fun shouldGenerateScript(script: KtScript) = false
}
val state = GenerationState.Builder(
fileForDebugger.project,
if (!DEBUG_MODE) ClassBuilderFactories.binaries(false) else ClassBuilderFactories.TEST,
moduleDescriptor,
bindingContext,
files,
CompilerConfiguration.EMPTY
).generateDeclaredClassFilter(generateClassFilter).build()
val frameVisitor = FrameVisitor(context)
extractedFunction.receiverTypeReference?.let {
state.bindingTrace.recordAnonymousType(it, THIS_NAME, frameVisitor)
}
val valueParameters = extractedFunction.valueParameters
var paramIndex = 0
for (param in parameters) {
val valueParameter = valueParameters[paramIndex++]
val paramRef = valueParameter.typeReference
if (paramRef == null) {
LOG.error("Each parameter for extracted function should have a type reference",
Attachment("codeFragment.txt", codeFragment.text),
Attachment("extractedFunction.txt", extractedFunction.text))
exception("An exception occurs during Evaluate Expression Action")
}
state.bindingTrace.recordAnonymousType(paramRef, param.callText, frameVisitor)
}
KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION)
state.factory
}
}
private fun BindingTrace.recordAnonymousType(typeReference: KtTypeReference, localVariableName: String, visitor: FrameVisitor) {
val paramAnonymousType = typeReference.debugTypeInfo
if (paramAnonymousType != null) {
val declarationDescriptor = paramAnonymousType.constructor.declarationDescriptor
if (declarationDescriptor is ClassDescriptor) {
val localVariable = visitor.findValue(localVariableName, asmType = null, checkType = false, failIfNotFound = false)
?: exception("Couldn't find local variable this in current frame to get classType for anonymous type $paramAnonymousType}")
record(CodegenBinding.ASM_TYPE, declarationDescriptor, localVariable.asmType)
if (LOG.isDebugEnabled) {
LOG.debug("Asm type ${localVariable.asmType.className} was recorded for ${declarationDescriptor.name}")
}
}
}
}
private fun exception(msg: String): Nothing = throw EvaluateExceptionUtil.createEvaluateException(msg)
private fun exception(e: Throwable): Nothing = throw EvaluateExceptionUtil.createEvaluateException(e)
private val IGNORED_DIAGNOSTICS: Set<DiagnosticFactory<*>> = Errors.INVISIBLE_REFERENCE_DIAGNOSTICS
// contextFile must be NotNull when analyzeInlineFunctions = true
private fun KtFile.checkForErrors(analyzeInlineFunctions: Boolean = false, contextFile: KtFile? = null): ExtendedAnalysisResult {
return runInReadActionWithWriteActionPriorityWithPCE {
try {
AnalyzingUtils.checkForSyntacticErrors(this)
}
catch (e: IllegalArgumentException) {
throw EvaluateExceptionUtil.createEvaluateException(e.message)
}
val filesToAnalyze = if (contextFile == null) listOf(this) else listOf(this, contextFile)
val resolutionFacade = KotlinCacheService.getInstance(project).getResolutionFacade(filesToAnalyze)
val analysisResult = resolutionFacade.analyzeFullyAndGetResult(filesToAnalyze)
if (analysisResult.isError()) {
exception(analysisResult.error)
}
val bindingContext = analysisResult.bindingContext
val filteredDiagnostics = bindingContext.diagnostics.filter { it.factory !in IGNORED_DIAGNOSTICS }
filteredDiagnostics.firstOrNull { it.severity == Severity.ERROR }?.let {
if (it.psiElement.containingFile == this) {
exception(DefaultErrorMessages.render(it))
}
}
if (analyzeInlineFunctions) {
val (newBindingContext, files) = DebuggerUtils.analyzeInlinedFunctions(resolutionFacade, this, false)
ExtendedAnalysisResult(newBindingContext, analysisResult.moduleDescriptor, files)
}
else {
ExtendedAnalysisResult(bindingContext, analysisResult.moduleDescriptor, Collections.singletonList(this))
}
}
}
private data class ExtendedAnalysisResult(val bindingContext: BindingContext, val moduleDescriptor: ModuleDescriptor, val files: List<KtFile>)
}
}
private val template = """
!PACKAGE!
!IMPORT_LIST!
!FUNCTION!
"""
private fun createFileForDebugger(codeFragment: KtCodeFragment,
extractedFunction: KtNamedFunction
): KtFile {
val containingContextFile = codeFragment.getContextContainingFile()
val importsFromContextFile = containingContextFile?.importList?.let { it.text + "\n" } ?: ""
var fileText = template.replace(
"!IMPORT_LIST!",
importsFromContextFile + codeFragment.importsToString().split(KtCodeFragment.IMPORT_SEPARATOR).joinToString("\n")
)
val packageFromContextFile = containingContextFile?.packageFqName?.let {
if (!it.isRoot) "package ${it.quoteSegmentsIfNeeded()}" else ""
} ?: ""
fileText = fileText.replace("!PACKAGE!", packageFromContextFile)
val extractedFunctionText = extractedFunction.text
assert(extractedFunctionText != null) { "Text of extracted function shouldn't be null" }
fileText = fileText.replace("!FUNCTION!", extractedFunction.text!!)
val jetFile = codeFragment.createKtFile("debugFile.kt", fileText)
jetFile.suppressDiagnosticsInDebugMode = true
val list = jetFile.declarations
val function = list[0] as KtNamedFunction
function.receiverTypeReference?.debugTypeInfo = extractedFunction.receiverTypeReference?.debugTypeInfo
for ((newParam, oldParam) in function.valueParameters.zip(extractedFunction.valueParameters)) {
newParam.typeReference?.debugTypeInfo = oldParam.typeReference?.debugTypeInfo
}
function.typeReference?.debugTypeInfo = extractedFunction.typeReference?.debugTypeInfo
return jetFile
}
private fun PsiElement.createKtFile(fileName: String, fileText: String): KtFile {
// Not using KtPsiFactory because we need a virtual file attached to the KtFile
val virtualFile = LightVirtualFile(fileName, KotlinLanguage.INSTANCE, fileText)
virtualFile.charset = CharsetToolkit.UTF8_CHARSET
val jetFile = (PsiFileFactory.getInstance(project) as PsiFileFactoryImpl)
.trySetupPsiForFile(virtualFile, KotlinLanguage.INSTANCE, true, false) as KtFile
jetFile.analysisContext = this
return jetFile
}
internal fun SuspendContext.getInvokePolicy(): Int {
return if (suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD) ObjectReference.INVOKE_SINGLE_THREADED else 0
}
fun Type.getClassDescriptor(scope: GlobalSearchScope): ClassDescriptor? {
if (AsmUtil.isPrimitive(this)) return null
val jvmName = JvmClassName.byInternalName(internalName).fqNameForClassNameWithoutDollars
// TODO: use the correct built-ins from the module instead of DefaultBuiltIns here
JavaToKotlinClassMap.mapJavaToKotlin(jvmName)?.let(
DefaultBuiltIns.Instance.builtInsModule::findClassAcrossModuleDependencies
)?.let { return it }
return runReadAction {
val classes = JavaPsiFacade.getInstance(scope.project).findClasses(jvmName.asString(), scope)
if (classes.isEmpty()) null
else {
classes.first().getJavaClassDescriptor()
}
}
}
@@ -0,0 +1,111 @@
/*
* 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.EvaluatingComputable
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.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, EvaluatingComputable {
val codeFragment = KtPsiFactory(myElement.project).createExpressionCodeFragment(
myElement.text, myElement.containingFile.context)
KotlinEvaluationBuilder.build(codeFragment, ContextUtil.getSourcePosition(evaluationContext))
})
val value = evaluator.evaluate(evaluationContext)
if (value != null) {
return 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
}
}
}
@@ -0,0 +1,48 @@
/*
* 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.DebugProcessImpl
import com.intellij.debugger.engine.evaluation.EvaluationContext
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.sun.jdi.*
abstract class AbstractAndroidClassLoadingAdapter : ClassLoadingAdapter {
protected fun dex(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ByteArray? {
return AndroidDexer.getInstances(context.project).single().dex(classes)
}
protected fun wrapToByteBuffer(bytes: ArrayReference, context: EvaluationContext, process: DebugProcessImpl): ObjectReference {
val byteBufferClass = process.findClass(context, "java.nio.ByteBuffer", context.classLoader) as ClassType
val wrapMethod = byteBufferClass.concreteMethodByName("wrap", "([B)Ljava/nio/ByteBuffer;")
?: error("'wrap' method not found")
return process.invokeMethod(context, byteBufferClass, wrapMethod, listOf(bytes)) as ObjectReference
}
protected fun DebugProcessImpl.tryLoadClass(
context: EvaluationContextImpl,
fqName: String,
classLoader: ClassLoaderReference?
): ReferenceType? {
return try {
loadClass(context, fqName, classLoader)
} catch (e: Throwable) {
null
}
}
}
@@ -0,0 +1,26 @@
/*
* 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?
}
@@ -0,0 +1,57 @@
/*
* 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.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.sun.jdi.*
import org.jetbrains.kotlin.idea.debugger.isDexDebug
class AndroidOClassLoadingAdapter : AbstractAndroidClassLoadingAdapter() {
override fun isApplicable(context: EvaluationContextImpl, hasAdditionalClasses: Boolean, hasLoops: Boolean): Boolean {
return (hasAdditionalClasses || hasLoops) && context.debugProcess.isDexDebug()
}
private fun resolveClassLoaderClass(context: EvaluationContextImpl): ClassType? {
try {
return context.debugProcess.tryLoadClass(
context, "dalvik.system.InMemoryDexClassLoader", context.classLoader) as? ClassType
} catch (e: EvaluateException) {
return null
}
}
override fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler {
val process = context.debugProcess
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, process)
val dexByteBuffer = wrapToByteBuffer(dexBytesMirror, context, process)
val newClassLoader = process.newInstance(context, inMemoryClassLoaderClass, constructorMethod,
listOf(dexByteBuffer, context.classLoader))
DebuggerUtilsEx.keep(newClassLoader, context)
return ClassLoaderHandler(newClassLoader as ClassLoaderReference)
}
}
@@ -0,0 +1,24 @@
/*
* 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.openapi.Disposable
import com.sun.jdi.ClassLoaderReference
open class ClassLoaderHandler(val reference: ClassLoaderReference?) : Disposable {
override fun dispose() {}
}
@@ -0,0 +1,94 @@
/*
* 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.DebugProcessImpl
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.sun.jdi.ArrayReference
import com.sun.jdi.ArrayType
import com.sun.jdi.Value
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.tree.ClassNode
import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode
import org.jetbrains.org.objectweb.asm.tree.LabelNode
interface ClassLoadingAdapter {
companion object {
private val ADAPTERS = listOf(
AndroidOClassLoadingAdapter(),
OrdinaryClassLoadingAdapter())
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler? {
val hasAdditionalClasses = classes.size > 1
val hasLoops = classes.isNotEmpty() && doesContainLoops(classes.first().bytes)
for (adapter in ADAPTERS) {
if (adapter.isApplicable(
context,
hasAdditionalClasses = hasAdditionalClasses,
hasLoops = hasLoops
)) {
return adapter.loadClasses(context, classes)
}
}
return null
}
private fun doesContainLoops(clazz: ByteArray): Boolean {
val classNode = ClassNode().apply { ClassReader(clazz).accept(this, ClassReader.EXPAND_FRAMES) }
val methodToRun = classNode.methods.single()
val labelsVisited = hashSetOf<Label>()
var currentInsn = methodToRun.instructions.first
while (currentInsn != null) {
if (currentInsn is LabelNode) {
labelsVisited += currentInsn.label
}
else if (currentInsn is JumpInsnNode) {
if (currentInsn.label.label in labelsVisited) {
return true
}
}
currentInsn = currentInsn.next
}
return false
}
}
fun isApplicable(context: EvaluationContextImpl, hasAdditionalClasses: Boolean, hasLoops: Boolean): Boolean
fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler
fun mirrorOfByteArray(bytes: ByteArray, context: EvaluationContextImpl, process: DebugProcessImpl): ArrayReference {
val arrayClass = process.findClass(context, "byte[]", context.classLoader) as ArrayType
val reference = process.newInstance(arrayClass, bytes.size)
DebuggerUtilsEx.keep(reference, context)
val mirrors = ArrayList<Value>(bytes.size)
for (byte in bytes) {
mirrors += process.virtualMachineProxy.mirrorOf(byte)
}
reference.values = mirrors
return reference
}
}
@@ -0,0 +1,20 @@
/*
* 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
@Suppress("ArrayInDataClass")
data class ClassToLoad(val className: String, val relativeFileName: String, val bytes: ByteArray)
@@ -0,0 +1,132 @@
/*
* 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.DebugProcessImpl
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.impl.ClassLoadingUtils
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.openapi.projectRoots.JdkVersionUtil
import com.intellij.openapi.util.SystemInfo
import com.sun.jdi.ClassLoaderReference
import com.sun.jdi.ClassType
import org.jetbrains.kotlin.idea.debugger.evaluate.CompilingEvaluatorUtils
import org.jetbrains.kotlin.idea.debugger.isDexDebug
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"))
}
override fun isApplicable(context: EvaluationContextImpl, hasAdditionalClasses: Boolean, hasLoops: Boolean): Boolean {
return (hasAdditionalClasses || hasLoops) && context.classLoader != null && !context.debugProcess.isDexDebug()
}
override fun loadClasses(context: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler {
val process = context.debugProcess
val classLoader = try {
ClassLoadingUtils.getClassLoader(context, process)
}
catch (e: Exception) {
throw EvaluateException("Error creating evaluation class loader: " + e, e)
}
val version = process.virtualMachineProxy.version()
val sdkVersion = JdkVersionUtil.getVersion(version)
if (!SystemInfo.isJavaVersionAtLeast(sdkVersion.description)) {
throw EvaluateException(
"Unable to compile for target level ${sdkVersion.description}. " +
"Need to run IDEA on java version at least ${sdkVersion.description}, " +
"currently running on ${SystemInfo.JAVA_RUNTIME_VERSION}")
}
try {
defineClasses(classes, context, process, classLoader)
}
catch (e: Exception) {
throw EvaluateException("Error during classes definition " + e, e)
}
return ClassLoaderHandler(classLoader)
}
private fun defineClasses(
classes: Collection<ClassToLoad>,
context: EvaluationContextImpl,
process: DebugProcessImpl,
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 = CompilingEvaluatorUtils.changeSuperToMagicAccessor(bytes)
defineClass(className, patchedBytes, context, process, classLoader)
}
}
fun defineClass(
name: String,
bytes: ByteArray,
context: EvaluationContextImpl,
process: DebugProcessImpl,
classLoader: ClassLoaderReference
) {
try {
val vm = process.virtualMachineProxy
val classLoaderType = classLoader.referenceType() as ClassType
val defineMethod = classLoaderType.concreteMethodByName("defineClass", "(Ljava/lang/String;[BII)Ljava/lang/Class;")
val nameObj = vm.mirrorOf(name)
DebuggerUtilsEx.keep(nameObj, context)
process.invokeMethod(
context, classLoader, defineMethod,
listOf(nameObj, mirrorOfByteArray(bytes, context, process), vm.mirrorOf(0), vm.mirrorOf(bytes.size)))
}
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()
}
}
}
}
@@ -0,0 +1,40 @@
/*
* 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.EvaluationContextImpl
import org.jetbrains.kotlin.idea.debugger.evaluate.LOG
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassLoaderHandler
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassLoadingAdapter
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
fun loadClassesSafely(evaluationContext: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler? {
try {
return loadClasses(evaluationContext, classes)
} catch (e: Throwable) {
LOG.debug("Failed to evaluate expression", e)
return null
}
}
fun loadClasses(evaluationContext: EvaluationContextImpl, classes: Collection<ClassToLoad>): ClassLoaderHandler? {
if (classes.isEmpty()) return ClassLoaderHandler(evaluationContext.classLoader)
return ClassLoadingAdapter.loadClasses(evaluationContext, classes)?.apply {
evaluationContext.classLoader = this.reference
}
}
@@ -0,0 +1,359 @@
/*
* 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.engine.evaluation.EvaluateExceptionUtil
import com.intellij.diagnostic.LogMessageEx
import com.intellij.openapi.diagnostic.Attachment
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.util.ExceptionUtil
import org.jetbrains.kotlin.idea.actions.internal.KotlinInternalMode
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.core.replaced
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.*
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.ErrorMessage
import org.jetbrains.kotlin.idea.refactoring.introduce.extractionEngine.AnalysisResult.Status
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriorityWithPCE
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.idea.util.psi.patternMatching.toRange
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.codeFragmentUtil.suppressDiagnosticsInDebugMode
import org.jetbrains.kotlin.psi.psiUtil.findDescendantOfType
import org.jetbrains.kotlin.psi.psiUtil.forEachDescendantOfType
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext.SMARTCAST
fun getFunctionForExtractedFragment(
codeFragment: KtCodeFragment,
breakpointFile: PsiFile,
breakpointLine: Int
): ExtractionResult? {
fun getErrorMessageForExtractFunctionResult(analysisResult: AnalysisResult, tmpFile: KtFile): String {
if (KotlinInternalMode.enabled) {
val attachments = arrayOf(attachmentByPsiFile(tmpFile),
attachmentByPsiFile(breakpointFile),
attachmentByPsiFile(codeFragment),
Attachment("breakpoint.info", "line: $breakpointLine"),
Attachment("context.info", codeFragment.context?.text ?: "null"),
Attachment("errors.info", analysisResult.messages.joinToString("\n") { "$it: ${it.renderMessage()}" }))
LOG.error(LogMessageEx.createEvent(
"Internal error during evaluate expression",
ExceptionUtil.getThrowableText(Throwable("Extract function fails with ${analysisResult.messages.joinToString { it.name }}")),
mergeAttachments(*attachments)))
}
return analysisResult.messages.joinToString(", ") { errorMessage ->
val message = when(errorMessage) {
ErrorMessage.NO_EXPRESSION -> "Cannot perform an action without an expression"
ErrorMessage.NO_CONTAINER -> "Cannot perform an action at this breakpoint ${breakpointFile.name}:$breakpointLine"
ErrorMessage.SYNTAX_ERRORS -> "Cannot perform an action due to erroneous code"
ErrorMessage.SUPER_CALL -> "Cannot perform an action for expression with super call"
ErrorMessage.DENOTABLE_TYPES -> "Cannot perform an action because following types are unavailable from debugger scope"
ErrorMessage.ERROR_TYPES -> "Cannot perform an action because this code fragment contains erroneous types"
ErrorMessage.MULTIPLE_EXIT_POINTS,
ErrorMessage.DECLARATIONS_OUT_OF_SCOPE,
ErrorMessage.OUTPUT_AND_EXIT_POINT,
ErrorMessage.DECLARATIONS_ARE_USED_OUTSIDE -> "Cannot perform an action for this expression"
ErrorMessage.MULTIPLE_OUTPUT -> throw AssertionError("Unexpected error: $errorMessage")
}
errorMessage.additionalInfo?.let { "$message: ${it.joinToString(", ")}" } ?: message
}
}
fun generateFunction(): ExtractionResult? {
val originalFile = codeFragment.getContextContainingFile() ?: return null
val newDebugExpressions = addDebugExpressionIntoTmpFileForExtractFunction(originalFile, codeFragment, breakpointLine)
if (newDebugExpressions.isEmpty()) return null
val tmpFile = newDebugExpressions.first().containingKtFile
if (LOG.isDebugEnabled) {
LOG.debug("TMP_FILE:\n${runReadAction { tmpFile.text }}")
}
val targetSibling = tmpFile.declarations.firstOrNull() ?: return null
val options = ExtractionOptions(inferUnitTypeForUnusedValues = false,
enableListBoxing = true,
allowSpecialClassNames = true,
captureLocalFunctions = true,
canWrapInWith = true)
val analysisResult = ExtractionData(tmpFile, newDebugExpressions.toRange(), targetSibling, null, options).performAnalysis()
if (analysisResult.status != Status.SUCCESS) {
throw EvaluateExceptionUtil.createEvaluateException(getErrorMessageForExtractFunctionResult(analysisResult, tmpFile))
}
val validationResult = analysisResult.descriptor!!.validate()
if (!validationResult.conflicts.isEmpty) {
throw EvaluateExceptionUtil.createEvaluateException("Following declarations are unavailable in debug scope: ${validationResult.conflicts.keySet().joinToString(",") { it.text }}")
}
val generatorOptions = ExtractionGeneratorOptions(inTempFile = true,
dummyName = GENERATED_FUNCTION_NAME,
allowExpressionBody = false)
return ExtractionGeneratorConfiguration(validationResult.descriptor, generatorOptions).generateDeclaration()
}
return runReadAction { generateFunction() }
}
fun addDebugExpressionIntoTmpFileForExtractFunction(originalFile: KtFile, codeFragment: KtCodeFragment, line: Int): List<KtExpression> {
codeFragment.markContextElement()
codeFragment.markSmartCasts()
val tmpFile = originalFile.copy() as KtFile
tmpFile.suppressDiagnosticsInDebugMode = true
tmpFile.analysisContext = originalFile.analysisContext
val contextElement = getExpressionToAddDebugExpressionBefore(tmpFile, codeFragment.getOriginalContext(), line) ?: return emptyList()
addImportsToFile(codeFragment.importsAsImportList(), tmpFile)
val contentElementsInTmpFile = addDebugExpressionBeforeContextElement(codeFragment, contextElement)
contentElementsInTmpFile.forEach { it.insertSmartCasts() }
codeFragment.clearContextElement()
codeFragment.clearSmartCasts()
return contentElementsInTmpFile
}
private var PsiElement.IS_CONTEXT_ELEMENT: Boolean by NotNullablePsiCopyableUserDataProperty(Key.create("IS_CONTEXT_ELEMENT"), false)
private fun KtCodeFragment.markContextElement() {
getOriginalContext()?.IS_CONTEXT_ELEMENT = true
}
private fun KtCodeFragment.clearContextElement() {
getOriginalContext()?.IS_CONTEXT_ELEMENT = false
}
private fun KtFile.findContextElement(): KtElement? {
return this.findDescendantOfType { it.IS_CONTEXT_ELEMENT == true }
}
private var PsiElement.DEBUG_SMART_CAST: PsiElement? by CopyablePsiUserDataProperty(Key.create("DEBUG_SMART_CAST"))
private fun KtCodeFragment.markSmartCasts() {
val bindingContext = runInReadActionWithWriteActionPriorityWithPCE { analyzeFully() }
val factory = KtPsiFactory(project)
getContentElement()?.forEachDescendantOfType<KtExpression> { expression ->
val smartCast = bindingContext.get(SMARTCAST, expression)?.defaultType
if (smartCast != null) {
val smartCastedExpression = factory.createExpressionByPattern(
"($0 as ${DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(smartCast)})",
expression) as KtParenthesizedExpression
expression.DEBUG_SMART_CAST = smartCastedExpression
}
}
}
private fun KtExpression.insertSmartCasts() {
forEachDescendantOfType<KtExpression> {
val replacement = it.DEBUG_SMART_CAST
if (replacement != null) runReadAction { it.replace(replacement) }
}
}
private fun KtCodeFragment.clearSmartCasts() {
getContentElement()?.forEachDescendantOfType<KtExpression> { it.DEBUG_SMART_CAST = null }
}
private fun addImportsToFile(newImportList: KtImportList?, tmpFile: KtFile) {
if (newImportList != null && newImportList.imports.isNotEmpty()) {
val tmpFileImportList = tmpFile.importList
val psiFactory = KtPsiFactory(tmpFile)
if (tmpFileImportList == null) {
val packageDirective = tmpFile.packageDirective
tmpFile.addAfter(psiFactory.createNewLine(), packageDirective)
tmpFile.addAfter(newImportList, tmpFile.packageDirective)
}
else {
newImportList.imports.forEach {
tmpFileImportList.add(psiFactory.createNewLine())
tmpFileImportList.add(it)
}
tmpFileImportList.add(psiFactory.createNewLine())
}
}
}
private fun getExpressionToAddDebugExpressionBefore(tmpFile: KtFile, contextElement: PsiElement?, line: Int): PsiElement? {
if (contextElement == null) {
val lineStart = CodeInsightUtils.getStartLineOffset(tmpFile, line) ?: return null
val elementAtOffset = tmpFile.findElementAt(lineStart) ?: return null
return CodeInsightUtils.getTopmostElementAtOffset(elementAtOffset, lineStart)
}
fun shouldStop(el: PsiElement?, p: PsiElement?) = p is KtBlockExpression || el is KtDeclaration || el is KtFile
val elementAt = tmpFile.findContextElement()
var parent = elementAt?.parent
if (shouldStop(elementAt, parent)) {
return elementAt
}
var parentOfParent = parent?.parent
while (parent != null && parentOfParent != null) {
if (shouldStop(parent, parentOfParent)) {
break
}
parent = parent.parent
parentOfParent = parent?.parent
}
return parent
}
private fun addDebugExpressionBeforeContextElement(codeFragment: KtCodeFragment, contextElement: PsiElement): List<KtExpression> {
val elementBefore = findElementBefore(contextElement)
val parent = elementBefore?.parent ?: return emptyList()
val psiFactory = KtPsiFactory(codeFragment)
parent.addBefore(psiFactory.createNewLine(), elementBefore)
fun insertExpression(expr: KtElement?): List<KtExpression> {
when (expr) {
is KtBlockExpression -> return expr.statements.flatMap(::insertExpression)
is KtExpression -> {
val newDebugExpression = parent.addBefore(expr, elementBefore)
if (newDebugExpression == null) {
LOG.error("Couldn't insert debug expression ${expr.text} to context file before ${elementBefore.text}")
return emptyList()
}
parent.addBefore(psiFactory.createNewLine(), elementBefore)
return listOf(newDebugExpression as KtExpression)
}
}
return emptyList()
}
val containingFile = codeFragment.context?.containingFile
if (containingFile is KtCodeFragment) {
insertExpression(containingFile.getContentElement() as? KtExpression)
}
val debugExpression = codeFragment.getContentElement() ?: return emptyList()
return insertExpression(debugExpression)
}
private fun findElementBefore(contextElement: PsiElement): PsiElement? {
val psiFactory = KtPsiFactory(contextElement)
fun insertNewInitializer(classBody: KtClassBody): PsiElement? {
val initializer = psiFactory.createAnonymousInitializer()
val newInitializer = (classBody.addAfter(initializer, classBody.firstChild) as KtAnonymousInitializer)
val block = newInitializer.body as KtBlockExpression?
return block?.lastChild
}
return when {
contextElement is KtFile -> {
val fakeFunction = psiFactory.createFunction("fun _debug_fun_() {}")
contextElement.add(psiFactory.createNewLine())
val newFakeFun = contextElement.add(fakeFunction) as KtNamedFunction
newFakeFun.bodyExpression!!.lastChild
}
contextElement is KtProperty && !contextElement.isLocal -> {
val delegateExpressionOrInitializer = contextElement.delegateExpressionOrInitializer
if (delegateExpressionOrInitializer != null) {
wrapInLambdaCall(delegateExpressionOrInitializer)
}
else {
val getter = contextElement.getter
val bodyExpression = getter?.bodyExpression
if (getter != null && bodyExpression != null) {
if (!getter.hasBlockBody()) {
wrapInLambdaCall(bodyExpression)
}
else {
(bodyExpression as KtBlockExpression).statements.first()
}
}
else {
contextElement
}
}
}
contextElement is KtParameter -> {
val ownerFunction = contextElement.ownerFunction!!
findElementBefore(ownerFunction)
}
contextElement is KtPrimaryConstructor -> {
val classOrObject = contextElement.getContainingClassOrObject()
insertNewInitializer(classOrObject.getOrCreateBody())
}
contextElement is KtClassOrObject -> {
insertNewInitializer(contextElement.getOrCreateBody())
}
contextElement is KtFunctionLiteral -> {
val block = contextElement.bodyExpression!!
block.statements.firstOrNull() ?: block.lastChild
}
contextElement is KtDeclarationWithBody && !contextElement.hasBody() -> {
val block = psiFactory.createBlock("")
val newBlock = contextElement.add(block) as KtBlockExpression
newBlock.rBrace
}
contextElement is KtDeclarationWithBody && !contextElement.hasBlockBody() -> {
wrapInLambdaCall(contextElement.bodyExpression!!)
}
contextElement is KtDeclarationWithBody && contextElement.hasBlockBody() -> {
val block = contextElement.bodyExpression as KtBlockExpression
val last = block.statements.lastOrNull()
last as? KtReturnExpression ?: block.rBrace
}
contextElement is KtWhenEntry -> {
val entryExpression = contextElement.expression
if (entryExpression is KtBlockExpression) {
entryExpression.statements.firstOrNull() ?: entryExpression.lastChild
}
else {
wrapInLambdaCall(entryExpression!!)
}
}
else -> {
contextElement
}
}
}
private fun replaceByLambdaCall(expression: KtExpression): KtCallExpression {
val callExpression = KtPsiFactory(expression).createExpression("{ \n${expression.text} \n}()") as KtCallExpression
return expression.replaced(callExpression)
}
private fun wrapInLambdaCall(expression: KtExpression): PsiElement? {
val replacedBody = replaceByLambdaCall(expression)
return (replacedBody.calleeExpression as? KtLambdaExpression)?.bodyExpression?.firstChild
}
@@ -0,0 +1,35 @@
/*
* 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
}
}
@@ -0,0 +1,35 @@
/*
* 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()
}
}
@@ -0,0 +1,129 @@
/*
* 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.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
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 (allLineLocations().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() }
}
}
@@ -0,0 +1,81 @@
/*
* 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.Field
import com.sun.jdi.Method
import com.sun.jdi.ObjectReference
import com.sun.jdi.Value
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? {
return findGetterForDelegatedProperty()?.returnType()?.name()
}
}
@@ -0,0 +1,134 @@
/*
* 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.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(private val rendererSettings: NodeRendererSettings) : ClassRenderer() {
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.isEvaluationPossible) {
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) {
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)
}
}
@@ -0,0 +1,96 @@
/*
* 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.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.codegen.inline.API
import org.jetbrains.kotlin.codegen.inline.FileMapping
import org.jetbrains.kotlin.codegen.inline.SMAP
import org.jetbrains.kotlin.codegen.inline.SMAPParser
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassVisitor
enum class SourceLineKind {
CALL_LINE,
EXECUTED_LINE
}
fun mapStacktraceLineToSource(smapData: SmapData,
line: Int,
project: Project,
lineKind: SourceLineKind,
searchScope: GlobalSearchScope): Pair<KtFile, Int>? {
val smap = when (lineKind) {
SourceLineKind.CALL_LINE -> smapData.kotlinDebugStrata
SourceLineKind.EXECUTED_LINE -> smapData.kotlinStrata
} ?: return null
val mappingInfo = smap.fileMappings.firstOrNull {
it.getIntervalIfContains(line) != null
} ?: return null
val jvmName = JvmClassName.byInternalName(mappingInfo.path)
val sourceFile = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(
project, searchScope, jvmName, mappingInfo.name) ?: return null
val interval = mappingInfo.getIntervalIfContains(line)!!
val sourceLine = when (lineKind) {
SourceLineKind.CALL_LINE -> interval.source - 1
SourceLineKind.EXECUTED_LINE -> interval.mapDestToSource(line) - 1
}
return sourceFile to sourceLine
}
fun readDebugInfo(bytes: ByteArray): SmapData? {
val cr = ClassReader(bytes)
var debugInfo: String? = null
cr.accept(object : ClassVisitor(API) {
override fun visitSource(source: String?, debug: String?) {
debugInfo = debug
}
}, ClassReader.SKIP_FRAMES and ClassReader.SKIP_CODE)
return debugInfo?.let(::SmapData)
}
class SmapData(debugInfo: String) {
var kotlinStrata: SMAP?
var kotlinDebugStrata: SMAP?
init {
val intervals = debugInfo.split(SMAP.END).filter(String::isNotBlank)
when (intervals.count()) {
1 -> {
kotlinStrata = SMAPParser.parse(intervals[0] + SMAP.END)
kotlinDebugStrata = null
}
2 -> {
kotlinStrata = SMAPParser.parse(intervals[0] + SMAP.END)
kotlinDebugStrata = SMAPParser.parse(intervals[1] + SMAP.END)
}
else -> {
kotlinStrata = null
kotlinDebugStrata = null
}
}
}
}
private fun FileMapping.getIntervalIfContains(destLine: Int) = lineMappings.firstOrNull { it.contains(destLine) }
@@ -0,0 +1,45 @@
/*
* 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.stepping;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.RequestHint;
import com.intellij.debugger.engine.SuspendContextImpl;
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
import com.sun.jdi.request.StepRequest;
import org.jetbrains.annotations.NotNull;
public class DebugProcessImplHelper {
public static DebugProcessImpl.StepOverCommand createStepOverCommandWithCustomFilter(
SuspendContextImpl suspendContext,
boolean ignoreBreakpoints,
KotlinSuspendCallStepOverFilter methodFilter) {
DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
return debugProcess.new StepOverCommand(suspendContext, ignoreBreakpoints, StepRequest.STEP_LINE) {
@NotNull
@Override
protected RequestHint getHint(SuspendContextImpl suspendContext, ThreadReferenceProxyImpl stepThread) {
@SuppressWarnings("MagicConstant")
RequestHint hint = new RequestHintWithMethodFilter(stepThread, suspendContext, StepRequest.STEP_OVER, methodFilter);
hint.setRestoreBreakpoints(ignoreBreakpoints);
hint.setIgnoreFilters(ignoreBreakpoints || debugProcess.getSession().shouldIgnoreSteppingFilters());
return hint;
}
};
}
}
@@ -0,0 +1,208 @@
/*
* 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.stepping;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.SuspendContextImpl;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.impl.DebuggerUtilsEx;
import com.intellij.debugger.jdi.StackFrameProxyImpl;
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl;
import com.intellij.debugger.settings.DebuggerSettings;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.ui.classFilter.ClassFilter;
import com.intellij.ui.classFilter.DebuggerClassFilterProvider;
import com.sun.jdi.Location;
import com.sun.jdi.ObjectCollectedException;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.ThreadReference;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.EventRequestManager;
import com.sun.jdi.request.StepRequest;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.debugger.NoStrataPositionManagerHelperKt;
import org.jetbrains.kotlin.psi.KtFunctionLiteral;
import org.jetbrains.kotlin.psi.KtNamedFunction;
import java.util.ArrayList;
import java.util.List;
public class DebuggerSteppingHelper {
public static DebugProcessImpl.ResumeCommand createStepOverCommand(
final SuspendContextImpl suspendContext,
final boolean ignoreBreakpoints,
final KotlinSteppingCommandProvider.KotlinSourcePosition kotlinSourcePosition
) {
final DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
return debugProcess.new ResumeCommand(suspendContext) {
@Override
public void contextAction() {
boolean isDexDebug = NoStrataPositionManagerHelperKt.isDexDebug(suspendContext.getDebugProcess());
try {
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
if (frameProxy != null) {
Action action = KotlinSteppingCommandProviderKt.getStepOverAction(
frameProxy.location(),
kotlinSourcePosition,
frameProxy,
isDexDebug
);
createStepRequest(
suspendContext, getContextThread(),
debugProcess.getVirtualMachineProxy().eventRequestManager(),
StepRequest.STEP_LINE, StepRequest.STEP_OUT);
action.apply(debugProcess, suspendContext, ignoreBreakpoints);
return;
}
debugProcess.createStepOutCommand(suspendContext).contextAction();
}
catch (EvaluateException ignored) {
}
}
};
}
public static DebugProcessImpl.ResumeCommand createStepOutCommand(
final SuspendContextImpl suspendContext,
final boolean ignoreBreakpoints,
final List<KtNamedFunction> inlineFunctions,
final KtFunctionLiteral inlineArgument
) {
final DebugProcessImpl debugProcess = suspendContext.getDebugProcess();
return debugProcess.new ResumeCommand(suspendContext) {
@Override
public void contextAction() {
try {
StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
if (frameProxy != null) {
Action action = KotlinSteppingCommandProviderKt.getStepOutAction(
frameProxy.location(),
suspendContext,
inlineFunctions,
inlineArgument
);
createStepRequest(
suspendContext, getContextThread(),
debugProcess.getVirtualMachineProxy().eventRequestManager(),
StepRequest.STEP_LINE, StepRequest.STEP_OUT);
action.apply(debugProcess, suspendContext, ignoreBreakpoints);
return;
}
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction();
}
catch (EvaluateException ignored) {
}
}
};
}
// copied from DebugProcessImpl.doStep
private static void createStepRequest(
@NotNull SuspendContextImpl suspendContext,
@Nullable ThreadReferenceProxyImpl stepThread,
@NotNull EventRequestManager requestManager,
int size, int depth
) {
if (stepThread == null) {
return;
}
try {
ThreadReference stepThreadReference = stepThread.getThreadReference();
requestManager.deleteEventRequests(requestManager.stepRequests());
StepRequest stepRequest = requestManager.createStepRequest(stepThreadReference, size, depth);
List<ClassFilter> activeFilters = getActiveFilters();
if (!activeFilters.isEmpty()) {
String currentClassName = getCurrentClassName(stepThread);
if (currentClassName == null || !DebuggerUtilsEx.isFiltered(currentClassName, activeFilters)) {
// add class filters
for (ClassFilter filter : activeFilters) {
stepRequest.addClassExclusionFilter(filter.getPattern());
}
}
}
// suspend policy to match the suspend policy of the context:
// if all threads were suspended, then during stepping all the threads must be suspended
// if only event thread were suspended, then only this particular thread must be suspended during stepping
stepRequest.setSuspendPolicy(suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD
? EventRequest.SUSPEND_EVENT_THREAD
: EventRequest.SUSPEND_ALL);
stepRequest.enable();
}
catch (ObjectCollectedException ignored) {
}
}
// copied from DebugProcessImpl.getActiveFilters
@NotNull
private static List<ClassFilter> getActiveFilters() {
List<ClassFilter> activeFilters = new ArrayList<ClassFilter>();
DebuggerSettings settings = DebuggerSettings.getInstance();
if (settings.TRACING_FILTERS_ENABLED) {
for (ClassFilter filter : settings.getSteppingFilters()) {
if (filter.isEnabled()) {
activeFilters.add(filter);
}
}
}
for (DebuggerClassFilterProvider provider : Extensions.getExtensions(DebuggerClassFilterProvider.EP_NAME)) {
for (ClassFilter filter : provider.getFilters()) {
if (filter.isEnabled()) {
activeFilters.add(filter);
}
}
}
return activeFilters;
}
// copied from DebugProcessImpl.getActiveFilters
@Nullable
private static String getCurrentClassName(ThreadReferenceProxyImpl thread) {
try {
if (thread != null && thread.frameCount() > 0) {
StackFrameProxyImpl stackFrame = thread.frame(0);
if (stackFrame != null) {
Location location = stackFrame.location();
ReferenceType referenceType = location == null ? null : location.declaringType();
if (referenceType != null) {
return referenceType.name();
}
}
}
}
catch (EvaluateException ignored) {
}
return null;
}
}
@@ -0,0 +1,112 @@
/*
* 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.stepping
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.NamedMethodFilter
import com.intellij.util.Range
import com.intellij.util.SofterReference
import com.sun.jdi.Location
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
import org.jetbrains.kotlin.codegen.SamCodegenUtil
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor.Kind.*
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
import org.jetbrains.kotlin.idea.core.getDirectlyOverriddenDeclarations
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.psi.psiUtil.getParentOfTypesAndPredicate
import org.jetbrains.kotlin.resolve.DescriptorUtils
class KotlinBasicStepMethodFilter(
targetDescriptor: CallableMemberDescriptor,
private val myCallingExpressionLines: Range<Int>
) : NamedMethodFilter {
private val myTargetMethodName: String = when (targetDescriptor) {
is ClassDescriptor, is ConstructorDescriptor -> "<init>"
is PropertyAccessorDescriptor -> JvmAbi.getterName(targetDescriptor.correspondingProperty.name.asString())
else -> targetDescriptor.name.asString()
}
private val _targetDescriptor = SofterReference(
(targetDescriptor as? FunctionDescriptor)?.let { SamCodegenUtil.getOriginalIfSamAdapter(it) } ?: targetDescriptor
)
override fun getCallingExpressionLines() = myCallingExpressionLines
override fun getMethodName() = myTargetMethodName
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
val targetDescriptor = _targetDescriptor.get() ?: return true
val method = location.method()
if (myTargetMethodName != method.name()) return false
val positionManager = process.positionManager
val currentDescriptor = runReadAction {
val elementAt = positionManager.getSourcePosition(location)?.elementAt
val declaration = elementAt?.getParentOfTypesAndPredicate(false, KtDeclaration::class.java) {
it !is KtProperty || !it.isLocal
}
if (declaration is KtClass && method.name() == "<init>") {
(declaration.resolveToDescriptorIfAny() as? ClassDescriptor)?.unsubstitutedPrimaryConstructor
} else {
declaration?.resolveToDescriptorIfAny()
}
} ?: return false // TODO: Check that we can always find a descriptor (libraries with sources, libraries without sources)
@Suppress("FoldInitializerAndIfToElvis")
if (currentDescriptor !is CallableMemberDescriptor) return false
if (currentDescriptor.kind != DECLARATION) return false
if (targetDescriptor is FunctionInvokeDescriptor) {
// There can be only one 'invoke' target at the moment so consider position as expected.
// Descriptors can be not-equal, say when parameter has type `(T) -> T` and lambda is `Int.() -> Int`.
return true
}
if (compareDescriptors(currentDescriptor, targetDescriptor)) return true
// We should stop if current descriptor overrides the target one or some base descriptor of target
// (if target descriptor is delegation or fake override)
val baseDescriptors = when (targetDescriptor.kind) {
DELEGATION, FAKE_OVERRIDE ->
targetDescriptor.getDirectlyOverriddenDeclarations()
DECLARATION, SYNTHESIZED ->
listOf(targetDescriptor)
}
if (baseDescriptors.any { baseOfTarget -> compareDescriptors(baseOfTarget, currentDescriptor) }) {
return true
}
return DescriptorUtils.getAllOverriddenDescriptors(currentDescriptor).any { baseOfCurrent ->
baseDescriptors.any { baseOfTarget -> compareDescriptors(baseOfCurrent, baseOfTarget) }
}
}
}
private fun compareDescriptors(d1: DeclarationDescriptor, d2: DeclarationDescriptor): Boolean {
return d1 == d2 || d1.original == d2.original
}
@@ -0,0 +1,85 @@
/*
* 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.stepping
import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.BreakpointStepMethodFilter
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.util.Range
import com.sun.jdi.Location
import org.jetbrains.kotlin.codegen.coroutines.DO_RESUME_METHOD_NAME
import org.jetbrains.kotlin.idea.refactoring.isMultiLine
import org.jetbrains.kotlin.idea.debugger.isInsideInlineArgument
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.util.OperatorNameConventions
class KotlinLambdaMethodFilter(
private val lambda: KtFunction,
private val myCallingExpressionLines: Range<Int>,
private val isInline: Boolean,
private val isSuspend: Boolean
): BreakpointStepMethodFilter {
private val myFirstStatementPosition: SourcePosition?
private val myLastStatementLine: Int
init {
val body = lambda.bodyExpression
if (body != null && lambda.isMultiLine()) {
var firstStatementPosition: SourcePosition? = null
var lastStatementPosition: SourcePosition? = null
val statements = (body as? KtBlockExpression)?.statements ?: listOf(body)
if (statements.isNotEmpty()) {
firstStatementPosition = SourcePosition.createFromElement(statements.first())
if (firstStatementPosition != null) {
val lastStatement = statements.last()
lastStatementPosition = SourcePosition.createFromOffset(firstStatementPosition.file, lastStatement.textRange.endOffset)
}
}
myFirstStatementPosition = firstStatementPosition
myLastStatementLine = if (lastStatementPosition != null) lastStatementPosition.line else -1
}
else {
myFirstStatementPosition = SourcePosition.createFromElement(lambda)
myLastStatementLine = myFirstStatementPosition!!.line
}
}
override fun getBreakpointPosition() = myFirstStatementPosition
override fun getLastStatementLine() = myLastStatementLine
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
val method = location.method()
if (isInline) {
return isInsideInlineArgument(lambda, location, process)
}
return isLambdaName(method.name())
}
override fun getCallingExpressionLines() = if (isInline) Range(0, 999) else myCallingExpressionLines
private fun isLambdaName(name: String?): Boolean {
if (isSuspend) {
return name == DO_RESUME_METHOD_NAME
}
return name == OperatorNameConventions.INVOKE.asString()
}
}
@@ -0,0 +1,44 @@
/*
* 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.stepping
import com.intellij.debugger.actions.SmartStepTarget
import com.intellij.util.Range
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.util.OperatorNameConventions
import javax.swing.Icon
class KotlinLambdaSmartStepTarget(
label: String,
highlightElement: KtFunction,
lines: Range<Int>,
val isInline: Boolean,
val isSuspend: Boolean
): SmartStepTarget(label, highlightElement, true, lines) {
override fun getIcon(): Icon = KotlinIcons.LAMBDA
fun getLambda() = highlightElement as KtFunction
companion object {
fun calcLabel(descriptor: DeclarationDescriptor, paramName: Name): String {
return "${descriptor.name.asString()}: ${paramName.asString()}.${OperatorNameConventions.INVOKE.asString()}()"
}
}
}
@@ -0,0 +1,62 @@
package org.jetbrains.kotlin.idea.debugger.stepping
import com.intellij.debugger.actions.SmartStepTarget
import com.intellij.psi.PsiElement
import com.intellij.util.Range
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.KotlinIcons
import org.jetbrains.kotlin.idea.util.IdeDescriptorRenderers
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
import org.jetbrains.kotlin.resolve.descriptorUtil.isExtension
import javax.swing.Icon
class KotlinMethodSmartStepTarget(
val descriptor: CallableMemberDescriptor,
label: String,
highlightElement: PsiElement,
lines: Range<Int>
): SmartStepTarget(label, highlightElement, false, lines) {
override fun getIcon(): Icon? {
return when {
descriptor.isExtension -> KotlinIcons.EXTENSION_FUNCTION
else -> KotlinIcons.FUNCTION
}
}
companion object {
private val renderer = IdeDescriptorRenderers.SOURCE_CODE_SHORT_NAMES_IN_TYPES.withOptions {
parameterNameRenderingPolicy = ParameterNameRenderingPolicy.NONE
withoutReturnType = true
renderAccessors = true
startFromName = true
modifiers = emptySet()
}
fun calcLabel(descriptor: DeclarationDescriptor): String {
return renderer.render(descriptor)
}
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || other !is KotlinMethodSmartStepTarget) return false
if (descriptor is FunctionInvokeDescriptor && other.descriptor is FunctionInvokeDescriptor) {
// Don't allow to choose several invoke targets in smart step into as we can't distinguish them reliably during debug
return true
}
return descriptor == other.descriptor
}
override fun hashCode(): Int {
if (descriptor is FunctionInvokeDescriptor) {
// Predefined value to make all FunctionInvokeDescriptor targets equal
return 42
}
return descriptor.hashCode()
}
}
@@ -0,0 +1,54 @@
/*
* 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.stepping
import com.intellij.debugger.engine.SimplePropertyGetterProvider
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.psi.*
class KotlinSimpleGetterProvider : SimplePropertyGetterProvider {
override fun isInsideSimpleGetter(element: PsiElement): Boolean {
// class A(val a: Int)
if (element is KtParameter) {
return true
}
val accessor = PsiTreeUtil.getParentOfType(element, KtPropertyAccessor::class.java)
if (accessor != null && accessor.isGetter) {
val body = accessor.bodyExpression
return when (body) {
// val a: Int get() { return field }
is KtBlockExpression -> {
val returnedExpression = (body.statements.singleOrNull() as? KtReturnExpression)?.returnedExpression ?: return false
returnedExpression.textMatches("field")
}
// val a: Int get() = field
is KtExpression -> body.textMatches("field")
else -> false
}
}
val property = PsiTreeUtil.getParentOfType(element, KtProperty::class.java)
// val a = foo()
if (property != null) {
return property.getter == null && !property.isLocal
}
return false
}
}
@@ -0,0 +1,229 @@
/*
* 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.stepping
import com.intellij.debugger.SourcePosition
import com.intellij.debugger.actions.JvmSmartStepIntoHandler
import com.intellij.debugger.actions.MethodSmartStepTarget
import com.intellij.debugger.actions.SmartStepTarget
import com.intellij.debugger.engine.MethodFilter
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiMethod
import com.intellij.util.Range
import com.intellij.util.containers.OrderedSet
import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor
import org.jetbrains.kotlin.builtins.isSuspendFunctionType
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.analyzeFully
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.isFromJava
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentCall
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.inline.InlineUtil
class KotlinSmartStepIntoHandler : JvmSmartStepIntoHandler() {
override fun isAvailable(position: SourcePosition?) = position?.file is KtFile
override fun findSmartStepTargets(position: SourcePosition): List<SmartStepTarget> {
val file = position.file
val elementAtOffset = position.elementAt ?: return emptyList()
val element = CodeInsightUtils.getTopmostElementAtOffset(elementAtOffset, elementAtOffset.textRange.startOffset) as? KtElement ?:
return emptyList()
val elementTextRange = element.textRange ?: return emptyList()
val doc = PsiDocumentManager.getInstance(file.project).getDocument(file) ?: return emptyList()
val lines = Range(doc.getLineNumber(elementTextRange.startOffset), doc.getLineNumber(elementTextRange.endOffset))
val bindingContext = element.analyzeFully()
val result = OrderedSet<SmartStepTarget>()
// TODO support class initializers, local functions, delegated properties with specified type, setter for properties
element.accept(object: KtTreeVisitorVoid() {
override fun visitLambdaExpression(lambdaExpression: KtLambdaExpression) {
recordFunctionLiteral(lambdaExpression.functionLiteral)
}
override fun visitNamedFunction(function: KtNamedFunction) {
if (!recordFunctionLiteral(function)) {
super.visitNamedFunction(function)
}
}
private fun recordFunctionLiteral(function: KtFunction): Boolean {
val context = function.analyze()
val resolvedCall = function.getParentCall(context).getResolvedCall(context)
if (resolvedCall != null) {
val arguments = resolvedCall.valueArguments
for ((param, argument) in arguments) {
if (argument.arguments.any { getArgumentExpression(it) == function }) {
val resultingDescriptor = resolvedCall.resultingDescriptor
val label = KotlinLambdaSmartStepTarget.calcLabel(resultingDescriptor, param.name)
result.add(KotlinLambdaSmartStepTarget(
label, function, lines, InlineUtil.isInline(resultingDescriptor), param.type.isSuspendFunctionType))
return true
}
}
}
return false
}
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
override fun visitObjectLiteralExpression(expression: KtObjectLiteralExpression) {
// skip calls in object declarations
}
override fun visitIfExpression(expression: KtIfExpression) {
expression.condition?.accept(this)
}
override fun visitWhileExpression(expression: KtWhileExpression) {
expression.condition?.accept(this)
}
override fun visitDoWhileExpression(expression: KtDoWhileExpression) {
expression.condition?.accept(this)
}
override fun visitForExpression(expression: KtForExpression) {
expression.loopRange?.accept(this)
}
override fun visitWhenExpression(expression: KtWhenExpression) {
expression.subjectExpression?.accept(this)
}
override fun visitArrayAccessExpression(expression: KtArrayAccessExpression) {
recordFunction(expression)
super.visitArrayAccessExpression(expression)
}
override fun visitUnaryExpression(expression: KtUnaryExpression) {
recordFunction(expression.operationReference)
super.visitUnaryExpression(expression)
}
override fun visitBinaryExpression(expression: KtBinaryExpression) {
recordFunction(expression.operationReference)
super.visitBinaryExpression(expression)
}
override fun visitCallExpression(expression: KtCallExpression) {
val calleeExpression = expression.calleeExpression
if (calleeExpression != null) {
recordFunction(calleeExpression)
}
super.visitCallExpression(expression)
}
override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) {
val resolvedCall = expression.getResolvedCall(bindingContext)
if (resolvedCall != null) {
val propertyDescriptor = resolvedCall.resultingDescriptor
if (propertyDescriptor is PropertyDescriptor) {
val getterDescriptor = propertyDescriptor.getter
if (getterDescriptor != null && !getterDescriptor.isDefault) {
val delegatedResolvedCall = bindingContext[BindingContext.DELEGATED_PROPERTY_RESOLVED_CALL, getterDescriptor]
if (delegatedResolvedCall == null) {
val getter = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, getterDescriptor)
if (getter is KtPropertyAccessor && getter.hasBody()) {
val label = KotlinMethodSmartStepTarget.calcLabel(getterDescriptor)
result.add(KotlinMethodSmartStepTarget(getterDescriptor, label, expression, lines))
}
}
else {
val delegatedPropertyGetterDescriptor = delegatedResolvedCall.resultingDescriptor
val label = "${propertyDescriptor.name}." + KotlinMethodSmartStepTarget.calcLabel(delegatedPropertyGetterDescriptor)
result.add(KotlinMethodSmartStepTarget(delegatedPropertyGetterDescriptor, label, expression, lines))
}
}
}
}
super.visitSimpleNameExpression(expression)
}
private fun recordFunction(expression: KtExpression) {
val resolvedCall = expression.getResolvedCall(bindingContext) ?: return
val descriptor = resolvedCall.resultingDescriptor
if (descriptor is FunctionDescriptor && !isIntrinsic(descriptor)) {
if (descriptor.isFromJava) {
(DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor) as? PsiMethod)?.let {
result.add(MethodSmartStepTarget(it, null, expression, false, lines))
}
}
else {
if (descriptor is ConstructorDescriptor && descriptor.isPrimary) {
val psiElement = DescriptorToSourceUtilsIde.getAnyDeclaration(file.project, descriptor)
if (psiElement is KtClass && psiElement.getAnonymousInitializers().isEmpty()) {
// There is no constructor or init block, so do not show it in smart step into
return
}
}
val callLabel = KotlinMethodSmartStepTarget.calcLabel(descriptor)
val label = when (descriptor) {
is FunctionInvokeDescriptor -> {
when (expression) {
is KtSimpleNameExpression -> "${runReadAction { expression.text }}.$callLabel"
else -> callLabel
}
}
else -> callLabel
}
result.add(KotlinMethodSmartStepTarget(descriptor, label, expression, lines))
}
}
}
}, null)
return result
}
override fun createMethodFilter(stepTarget: SmartStepTarget?): MethodFilter? {
return when (stepTarget) {
is KotlinMethodSmartStepTarget ->
KotlinBasicStepMethodFilter(stepTarget.descriptor, stepTarget.callingExpressionLines!!)
is KotlinLambdaSmartStepTarget ->
KotlinLambdaMethodFilter(
stepTarget.getLambda(), stepTarget.callingExpressionLines!!, stepTarget.isInline, stepTarget.isSuspend)
else -> super.createMethodFilter(stepTarget)
}
}
private val methods = IntrinsicMethods(JvmTarget.JVM_1_6)
private fun isIntrinsic(descriptor: CallableMemberDescriptor): Boolean {
return methods.getIntrinsic(descriptor) != null
}
}
@@ -0,0 +1,160 @@
/*
* 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.stepping
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.engine.*
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.debugger.impl.DebuggerSession
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.debugger.settings.DebuggerSettings
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.util.EventDispatcher
import com.sun.jdi.request.EventRequest
import com.sun.jdi.request.StepRequest
import java.lang.reflect.Field
// Mass-copy-paste code for commands behaviour from com.intellij.debugger.engine.DebugProcessImpl
@SuppressWarnings("UnnecessaryFinalOnLocalVariableOrParameter")
class KotlinStepActionFactory(private val debuggerProcess: DebugProcessImpl) {
abstract class KotlinStepAction {
abstract fun contextAction(suspendContext: SuspendContextImpl)
}
fun createKotlinStepOverInlineAction(smartStepFilter: KotlinMethodFilter): KotlinStepAction {
return StepOverInlineCommand(smartStepFilter, StepRequest.STEP_LINE)
}
private val debuggerContext: DebuggerContextImpl get() = debuggerProcess.debuggerContext
private val suspendManager: SuspendManager get() = debuggerProcess.suspendManager
private val project: Project get() = debuggerProcess.project
private val session: DebuggerSession get() = debuggerProcess.session
// TODO: ask for better API
// Should be safe to use reflection as field is protected and not obfuscated
private val debugProcessDispatcher: EventDispatcher<DebugProcessListener> = getFromField("myDebugProcessDispatcher")
// TODO: ask for better API
// Get field by type as it private and obfuscated in Ultimate
private val threadBlockedMonitor: ThreadBlockedMonitor = getFromField(ThreadBlockedMonitor::class.java)
private fun showStatusText(message: String) {
debuggerProcess.showStatusText(message)
}
// TODO: ask for better API
// Should be safe to use reflection as method is protected and not obfuscated
private fun doStep(
suspendContext: SuspendContextImpl,
stepThread: ThreadReferenceProxyImpl,
size: Int, depth: Int, hint: RequestHint) {
val doStepMethod = DebugProcessImpl::class.java.getDeclaredMethod(
"doStep",
SuspendContextImpl::class.java, ThreadReferenceProxyImpl::class.java,
Integer.TYPE, Integer.TYPE, RequestHint::class.java)
doStepMethod.isAccessible = true
doStepMethod.invoke(debuggerProcess, suspendContext, stepThread, size, depth, hint)
}
private fun <T> getFromField(fieldType: Class<T>): T {
return getFromField(DebugProcessImpl::class.java.declaredFields.single { it.type == fieldType })
}
private fun <T> getFromField(fieldName: String): T {
return getFromField(DebugProcessImpl::class.java.getDeclaredField(fieldName))
}
private fun <T> getFromField(field: Field?): T {
field!!.isAccessible = true
@Suppress("UNCHECKED_CAST")
return field.get(debuggerProcess) as T
}
private inner class StepOverInlineCommand(private val mySmartStepFilter: KotlinMethodFilter, private val myStepSize: Int) : KotlinStepAction() {
private fun getContextThread(suspendContext: SuspendContextImpl): ThreadReferenceProxyImpl? {
val contextThread = debuggerContext.threadProxy
return contextThread ?: suspendContext.thread
}
// See: ResumeCommand.applyThreadFilter()
private fun applyThreadFilter(suspendContext: SuspendContextImpl, thread: ThreadReferenceProxyImpl) {
if (suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
// there could be explicit resume as a result of call to voteSuspend()
// e.g. when breakpoint was considered invalid, in that case the filter will be applied _after_
// resuming and all breakpoints in other threads will be ignored.
// As resume() implicitly cleares the filter, the filter must be always applied _before_ any resume() action happens
val breakpointManager = DebuggerManagerEx.getInstanceEx(project).breakpointManager
breakpointManager.applyThreadFilter(debuggerProcess, thread.threadReference)
}
}
// See: StepCommand.resumeAction()
private fun resumeAction(suspendContext: SuspendContextImpl, thread: ThreadReferenceProxyImpl) {
if (suspendContext.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD || isResumeOnlyCurrentThread) {
threadBlockedMonitor.startWatching(thread)
}
if (isResumeOnlyCurrentThread && suspendContext.suspendPolicy == EventRequest.SUSPEND_ALL) {
suspendManager.resumeThread(suspendContext, thread)
}
else {
suspendManager.resume(suspendContext)
}
}
// See: StepIntoCommand.contextAction()
override fun contextAction(suspendContext: SuspendContextImpl) {
showStatusText("Stepping over inline")
val stepThread = getContextThread(suspendContext)
if (stepThread == null) {
// TODO: Intellij code doesn't bother to check thread for null, so probably it's not-null actually
debuggerProcess.createStepOverCommand(suspendContext, true).contextAction(suspendContext)
return
}
val hint = KotlinStepOverInlinedLinesHint(stepThread, suspendContext, mySmartStepFilter)
hint.isResetIgnoreFilters = !session.shouldIgnoreSteppingFilters()
try {
session.setIgnoreStepFiltersFlag(stepThread.frameCount())
}
catch (e: EvaluateException) {
LOG.info(e)
}
applyThreadFilter(suspendContext, stepThread)
doStep(suspendContext, stepThread, myStepSize, StepRequest.STEP_OVER, hint)
showStatusText("Process resumed")
resumeAction(suspendContext, stepThread)
debugProcessDispatcher.multicaster.resumed(suspendContext)
}
}
companion object {
private val LOG = Logger.getInstance(KotlinStepActionFactory::class.java)
private val isResumeOnlyCurrentThread: Boolean
get() = DebuggerSettings.getInstance().RESUME_ONLY_CURRENT_THREAD
}
}
@@ -0,0 +1,64 @@
/*
* 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.stepping
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.openapi.project.Project
import com.intellij.util.Range
import com.sun.jdi.LocalVariable
import com.sun.jdi.Location
import org.jetbrains.kotlin.idea.debugger.ktLocationInfo
class StepOverFilterData(
val lineNumber: Int,
val stepOverLines: Set<Int>,
val inlineRangeVariables: List<LocalVariable>,
val isDexDebug: Boolean,
val skipAfterCodeIndex: Long = -1
)
class KotlinStepOverInlineFilter(val project: Project, val data: StepOverFilterData) : KotlinMethodFilter {
private fun Location.ktLineNumber() = ktLocationInfo(this, data.isDexDebug, project).first
override fun locationMatches(context: SuspendContextImpl, location: Location): Boolean {
val frameProxy = context.frameProxy ?: return true
if (data.skipAfterCodeIndex != -1L && location.codeIndex() > data.skipAfterCodeIndex) {
return false
}
val currentLine = location.ktLineNumber()
if (!(data.stepOverLines.contains(currentLine))) {
return currentLine != data.lineNumber
}
val visibleInlineVariables = getInlineRangeLocalVariables(frameProxy)
// Our ranges check missed exit from inline function. This is when breakpoint was in last statement of inline functions.
// This can be observed by inline local range-variables. Absence of any means step out was done.
return data.inlineRangeVariables.any { !visibleInlineVariables.contains(it) }
}
override fun locationMatches(process: DebugProcessImpl, location: Location): Boolean {
throw IllegalStateException() // Should not be called from Kotlin hint
}
override fun getCallingExpressionLines(): Range<Int>? {
throw IllegalStateException() // Should not be called from Kotlin hint
}
}
@@ -0,0 +1,70 @@
/*
* 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.stepping
import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.*
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.jdi.StackFrameProxy
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.Computable
import com.sun.jdi.VMDisconnectedException
import com.sun.jdi.request.StepRequest
// Originally copied from RequestHint
class KotlinStepOverInlinedLinesHint(
stepThread: ThreadReferenceProxyImpl,
suspendContext: SuspendContextImpl,
methodFilter: KotlinMethodFilter) : RequestHint(stepThread, suspendContext, methodFilter) {
private val LOG = Logger.getInstance(KotlinStepOverInlinedLinesHint::class.java)
private val filter = methodFilter
override fun getDepth(): Int = StepRequest.STEP_OVER
override fun getNextStepDepth(context: SuspendContextImpl): Int {
try {
val frameProxy = context.frameProxy
if (frameProxy != null) {
if (isTheSameFrame(context)) {
return if (filter.locationMatches(context, frameProxy.location())) {
STOP
}
else {
StepRequest.STEP_OVER
}
}
if (isSteppedOut) {
return STOP
}
return StepRequest.STEP_OUT
}
}
catch (ignored: VMDisconnectedException) {
}
catch (e: EvaluateException) {
LOG.error(e)
}
return STOP
}
}
@@ -0,0 +1,586 @@
/*
* 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.stepping
import com.intellij.debugger.NoDataException
import com.intellij.debugger.SourcePosition
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.MethodFilter
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.debugger.impl.JvmSteppingCommandProvider
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.psi.PsiElement
import com.intellij.xdebugger.impl.XSourcePositionImpl
import com.sun.jdi.AbsentInformationException
import com.sun.jdi.LocalVariable
import com.sun.jdi.Location
import com.sun.jdi.Method
import org.jetbrains.annotations.TestOnly
import org.jetbrains.kotlin.builtins.isFunctionType
import org.jetbrains.kotlin.codegen.inline.KOTLIN_STRATA_NAME
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor
import org.jetbrains.kotlin.idea.codeInsight.CodeInsightUtils
import org.jetbrains.kotlin.idea.debugger.*
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.GOTO
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.MOVE
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN_OBJECT
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN_VOID
import org.jetbrains.kotlin.idea.debugger.stepping.DexBytecode.RETURN_WIDE
import org.jetbrains.kotlin.idea.refactoring.getLineNumber
import org.jetbrains.kotlin.idea.refactoring.getLineStartOffset
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
import org.jetbrains.kotlin.psi.psiUtil.parents
import org.jetbrains.kotlin.psi.psiUtil.startOffset
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCallImpl
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.utils.keysToMap
class KotlinSteppingCommandProvider : JvmSteppingCommandProvider() {
override fun getStepOverCommand(
suspendContext: SuspendContextImpl?,
ignoreBreakpoints: Boolean,
stepSize: Int
): DebugProcessImpl.ResumeCommand? {
if (suspendContext == null || suspendContext.isResumed) return null
val sourcePosition = suspendContext.debugProcess.debuggerContext.sourcePosition ?: return null
return getStepOverCommand(suspendContext, ignoreBreakpoints, sourcePosition)
}
@TestOnly
fun getStepOverCommand(
suspendContext: SuspendContextImpl,
ignoreBreakpoints: Boolean,
debuggerContext: DebuggerContextImpl
): DebugProcessImpl.ResumeCommand? {
return getStepOverCommand(suspendContext, ignoreBreakpoints, debuggerContext.sourcePosition)
}
private fun getStepOverCommand(
suspendContext: SuspendContextImpl,
ignoreBreakpoints: Boolean,
sourcePosition: SourcePosition): DebugProcessImpl.ResumeCommand? {
val kotlinSourcePosition = KotlinSourcePosition.create(sourcePosition) ?: return null
if (isSpecialStepOverNeeded(kotlinSourcePosition)) {
return DebuggerSteppingHelper.createStepOverCommand(suspendContext, ignoreBreakpoints, kotlinSourcePosition)
}
val file = sourcePosition.elementAt.containingFile
val location = suspendContext.debugProcess.invokeInManagerThread { suspendContext.frameProxy?.location() } ?: return null
if (isInSuspendMethod(location) && !isOnSuspendReturnOrReenter(location) && !isLastLineLocationInMethod(location)) {
return DebugProcessImplHelper.createStepOverCommandWithCustomFilter(
suspendContext, ignoreBreakpoints, KotlinSuspendCallStepOverFilter(sourcePosition.line, file, ignoreBreakpoints))
}
return null
}
data class KotlinSourcePosition(val file: KtFile, val function: KtNamedFunction,
val linesRange: IntRange, val sourcePosition: SourcePosition) {
companion object {
fun create(sourcePosition: SourcePosition): KotlinSourcePosition? {
val file = sourcePosition.file as? KtFile ?: return null
if (sourcePosition.line < 0) return null
val elementAt = sourcePosition.elementAt ?: return null
val containingFunction = elementAt.parents
.filterIsInstance<KtNamedFunction>()
.firstOrNull { !it.isLocal } ?: return null
val startLineNumber = containingFunction.getLineNumber(true) + 1
val endLineNumber = containingFunction.getLineNumber(false) + 1
if (startLineNumber > endLineNumber) return null
val linesRange = startLineNumber..endLineNumber
return KotlinSourcePosition(file, containingFunction, linesRange, sourcePosition)
}
}
}
private fun isSpecialStepOverNeeded(kotlinSourcePosition: KotlinSourcePosition): Boolean {
val sourcePosition = kotlinSourcePosition.sourcePosition
val hasInlineCallsOnLine = getInlineFunctionCallsIfAny(sourcePosition).isNotEmpty()
if (hasInlineCallsOnLine) {
return true
}
// Step over calls to lambda arguments in inline function while execution is already in that function
val containingFunctionDescriptor = kotlinSourcePosition.function.unsafeResolveToDescriptor()
if (InlineUtil.isInline(containingFunctionDescriptor)) {
val inlineArgumentsCallsIfAny = getInlineArgumentsCallsIfAny(sourcePosition, containingFunctionDescriptor)
if (inlineArgumentsCallsIfAny != null && inlineArgumentsCallsIfAny.isNotEmpty()) {
return true
}
}
return false
}
@TestOnly
fun getStepOutCommand(suspendContext: SuspendContextImpl, debugContext: DebuggerContextImpl): DebugProcessImpl.ResumeCommand? {
return getStepOutCommand(suspendContext, debugContext.sourcePosition)
}
override fun getStepOutCommand(suspendContext: SuspendContextImpl?, stepSize: Int): DebugProcessImpl.ResumeCommand? {
if (suspendContext == null || suspendContext.isResumed) return null
val sourcePosition = suspendContext.debugProcess.debuggerContext.sourcePosition ?: return null
return getStepOutCommand(suspendContext, sourcePosition)
}
private fun getStepOutCommand(suspendContext: SuspendContextImpl, sourcePosition: SourcePosition): DebugProcessImpl.ResumeCommand? {
val file = sourcePosition.file as? KtFile ?: return null
if (sourcePosition.line < 0) return null
val lineStartOffset = file.getLineStartOffset(sourcePosition.line) ?: return null
val inlineFunctions = getInlineFunctionsIfAny(file, lineStartOffset)
val inlinedArgument = getInlineArgumentIfAny(sourcePosition.elementAt)
if (inlineFunctions.isEmpty() && inlinedArgument == null) return null
return DebuggerSteppingHelper.createStepOutCommand(suspendContext, true, inlineFunctions, inlinedArgument)
}
}
private fun PsiElement?.contains(element: PsiElement): Boolean {
return this?.textRange?.contains(element.textRange) ?: false
}
private fun getInlineCallFunctionArgumentsIfAny(sourcePosition: SourcePosition): List<KtFunction> {
val inlineFunctionCalls = getInlineFunctionCallsIfAny(sourcePosition)
return getInlineArgumentsIfAny(inlineFunctionCalls)
}
private fun getInlineFunctionsIfAny(file: KtFile, offset: Int): List<KtNamedFunction> {
val elementAt = file.findElementAt(offset) ?: return emptyList()
val containingFunction = elementAt.getParentOfType<KtNamedFunction>(false) ?: return emptyList()
val descriptor = containingFunction.unsafeResolveToDescriptor()
if (!InlineUtil.isInline(descriptor)) return emptyList()
return DebuggerUtils.analyzeElementWithInline(containingFunction, false).filterIsInstance<KtNamedFunction>()
}
private fun getInlineArgumentsIfAny(inlineFunctionCalls: List<KtCallExpression>): List<KtFunction> {
return inlineFunctionCalls.flatMap {
it.valueArguments
.map(::getArgumentExpression)
.filterIsInstance<KtFunction>()
}
}
private fun getArgumentExpression(it: ValueArgument) = (it.getArgumentExpression() as? KtLambdaExpression)?.functionLiteral ?: it.getArgumentExpression()
private fun getInlineArgumentsCallsIfAny(sourcePosition: SourcePosition, declarationDescriptor: DeclarationDescriptor): List<KtCallExpression>? {
if (declarationDescriptor !is CallableDescriptor) return null
val valueParameters = declarationDescriptor.valueParameters.filter { it.type.isFunctionType }.toSet()
if (valueParameters.isEmpty()) {
return null
}
fun isCallOfArgument(ktCallExpression: KtCallExpression): Boolean {
val context = ktCallExpression.analyze(BodyResolveMode.PARTIAL)
val resolvedCall = ktCallExpression.getResolvedCall(context) as? VariableAsFunctionResolvedCallImpl ?: return false
val candidateDescriptor = resolvedCall.variableCall.candidateDescriptor
return candidateDescriptor in valueParameters
}
return findCallsOnPosition(sourcePosition, ::isCallOfArgument)
}
private fun getInlineFunctionCallsIfAny(sourcePosition: SourcePosition): List<KtCallExpression> {
fun isInlineCall(expr: KtCallExpression): Boolean {
val context = expr.analyze(BodyResolveMode.PARTIAL)
val resolvedCall = expr.getResolvedCall(context) ?: return false
return InlineUtil.isInline(resolvedCall.resultingDescriptor)
}
return findCallsOnPosition(sourcePosition, ::isInlineCall)
}
private fun findCallsOnPosition(sourcePosition: SourcePosition, filter: (KtCallExpression) -> Boolean): List<KtCallExpression> {
val file = sourcePosition.file as? KtFile ?: return emptyList()
val lineNumber = sourcePosition.line
val lineElement = findElementAtLine(file, lineNumber)
if (lineElement !is KtElement) {
if (lineElement != null) {
val call = findCallByEndToken(lineElement)
if (call != null && filter(call)) {
return listOf(call)
}
}
return emptyList()
}
val start = lineElement.startOffset
val end = lineElement.endOffset
val allFilteredCalls = CodeInsightUtils.
findElementsOfClassInRange(file, start, end, KtExpression::class.java)
.map { KtPsiUtil.getParentCallIfPresent(it as KtExpression) }
.filterIsInstance<KtCallExpression>()
.filter { filter(it) }
.toSet()
// It is necessary to check range because of multiline assign
var linesRange = lineNumber..lineNumber
return allFilteredCalls.filter {
val shouldInclude = it.getLineNumber() in linesRange
if (shouldInclude) {
linesRange = Math.min(linesRange.start, it.getLineNumber())..Math.max(linesRange.endInclusive, it.getLineNumber(false))
}
shouldInclude
}
}
sealed class Action(val position: XSourcePositionImpl? = null,
val stepOverInlineData: StepOverFilterData? = null) {
class STEP_OVER : Action() {
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) =
debugProcess.createStepOverCommand(suspendContext, ignoreBreakpoints).contextAction(suspendContext)
}
class STEP_OUT : Action() {
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) =
debugProcess.createStepOutCommand(suspendContext).contextAction(suspendContext)
}
class RUN_TO_CURSOR(position: XSourcePositionImpl) : Action(position) {
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
return runReadAction {
debugProcess.createRunToCursorCommand(suspendContext, position!!, ignoreBreakpoints)
}.contextAction(suspendContext)
}
}
class STEP_OVER_INLINED(stepOverInlineData: StepOverFilterData) : Action(stepOverInlineData = stepOverInlineData) {
override fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean) {
return KotlinStepActionFactory(debugProcess).createKotlinStepOverInlineAction(
KotlinStepOverInlineFilter(debugProcess.project, stepOverInlineData!!)).contextAction(suspendContext)
}
}
abstract fun apply(debugProcess: DebugProcessImpl, suspendContext: SuspendContextImpl, ignoreBreakpoints: Boolean)
}
interface KotlinMethodFilter : MethodFilter {
fun locationMatches(context: SuspendContextImpl, location: Location): Boolean
}
fun getStepOverAction(
location: Location,
kotlinSourcePosition: KotlinSteppingCommandProvider.KotlinSourcePosition,
frameProxy: StackFrameProxyImpl,
isDexDebug: Boolean
): Action {
val inlineArgumentsToSkip = runReadAction {
getInlineCallFunctionArgumentsIfAny(kotlinSourcePosition.sourcePosition)
}
return getStepOverAction(location, kotlinSourcePosition.file, kotlinSourcePosition.linesRange,
inlineArgumentsToSkip, frameProxy, isDexDebug)
}
fun getStepOverAction(
location: Location,
sourceFile: KtFile,
range: IntRange,
inlineFunctionArguments: List<KtElement>,
frameProxy: StackFrameProxyImpl,
isDexDebug: Boolean
): Action {
location.declaringType() ?: return Action.STEP_OVER()
val project = sourceFile.project
val methodLocations = location.method().allLineLocations()
val locationsLineAndFile = methodLocations.keysToMap { ktLocationInfo(it, isDexDebug, project, true) }
fun Location.ktLineNumber(): Int = (locationsLineAndFile[this] ?: ktLocationInfo(this, isDexDebug, project, true)).first
fun Location.ktFileName(): String {
val ktFile = (locationsLineAndFile[this] ?: ktLocationInfo(this, isDexDebug, project, true)).second
// File is not null only for inlined locations. Get file name from debugger information otherwise.
return ktFile?.name ?: this.sourceName(KOTLIN_STRATA_NAME)
}
fun isLocationSuitable(nextLocation: Location): Boolean {
if (nextLocation.method() != location.method()) {
return false
}
val ktLineNumber = nextLocation.ktLineNumber()
if (ktLineNumber !in range) {
return false
}
try {
return nextLocation.ktFileName() == sourceFile.name
}
catch(e: AbsentInformationException) {
return true
}
}
fun isBackEdgeLocation(): Boolean {
val previousSuitableLocation = methodLocations.reversed()
.dropWhile { it != location }
.drop(1)
.filter(::isLocationSuitable)
.dropWhile { it.ktLineNumber() == location.ktLineNumber() }
.firstOrNull()
return previousSuitableLocation != null && previousSuitableLocation.ktLineNumber() > location.ktLineNumber()
}
val patchedLocation = if (isBackEdgeLocation()) {
// Pretend we had already done a backing step
methodLocations
.filter(::isLocationSuitable)
.firstOrNull { it.ktLineNumber() == location.ktLineNumber() } ?: location
}
else {
location
}
val patchedLineNumber = patchedLocation.ktLineNumber()
val lambdaArgumentRanges = runReadAction {
inlineFunctionArguments.map {
val startLineNumber = it.getLineNumber(true) + 1
val endLineNumber = it.getLineNumber(false) + 1
startLineNumber..endLineNumber
}
}
val inlineRangeVariables = getInlineRangeLocalVariables(frameProxy)
// Try to find the range of inlined lines:
// - Lines from other files and from functions that are not in range of current one are definitely inlined
// - Lines in function arguments of inlined functions are inlined too as we found them starting from the position of inlined call.
//
// It also thinks that too many lines are inlined when there's a call of function argument or other
// inline function in last statement of inline function. The list of inlineRangeVariables is used to overcome it.
val probablyInlinedLocations = methodLocations
.dropWhile { it != patchedLocation }
.drop(1)
.dropWhile { it.ktLineNumber() == patchedLineNumber }
.takeWhile { loc ->
!isLocationSuitable(loc) || lambdaArgumentRanges.any { loc.ktLineNumber() in it }
}
.dropWhile { it.ktLineNumber() == patchedLineNumber }
if (!probablyInlinedLocations.isEmpty()) {
// Some Kotlin inlined methods with 'for' (and maybe others) generates bytecode that after dexing have a strange artifact.
// GOTO instructions are moved to the end of method and as they don't have proper line, line is obtained from the previous
// instruction. It might be method return or previous GOTO from the inlining. Simple stepping over such function is really
// terrible. On each iteration position jumps to the method end or some previous inline call and then returns back. To prevent
// this filter locations with too big code indexes manually
val returnCodeIndex: Long = if (isDexDebug) {
val method = location.method()
val locationsOfLine = method.locationsOfLine(range.last)
if (locationsOfLine.isNotEmpty()) {
locationsOfLine.map { it.codeIndex() }.max() ?: -1L
}
else {
findReturnFromDexBytecode(location.method())
}
}
else -1L
return Action.STEP_OVER_INLINED(StepOverFilterData(
patchedLineNumber,
probablyInlinedLocations.map { it.ktLineNumber() }.toSet(),
inlineRangeVariables,
isDexDebug,
returnCodeIndex
))
}
return Action.STEP_OVER()
}
fun getStepOutAction(
location: Location,
suspendContext: SuspendContextImpl,
inlineFunctions: List<KtNamedFunction>,
inlinedArgument: KtFunctionLiteral?
): Action {
val computedReferenceType = location.declaringType() ?: return Action.STEP_OUT()
val locations = computedReferenceType.allLineLocations()
val nextLineLocations = locations
.dropWhile { it != location }
.drop(1)
.filter { it.method() == location.method() }
.dropWhile { it.lineNumber() == location.lineNumber() }
if (inlineFunctions.isNotEmpty()) {
val position = suspendContext.getXPositionForStepOutFromInlineFunction(nextLineLocations, inlineFunctions)
return position?.let { Action.RUN_TO_CURSOR(it) } ?: Action.STEP_OVER()
}
if (inlinedArgument != null) {
val position = suspendContext.getXPositionForStepOutFromInlinedArgument(nextLineLocations, inlinedArgument)
return position?.let { Action.RUN_TO_CURSOR(it) } ?: Action.STEP_OVER()
}
return Action.STEP_OVER()
}
private fun SuspendContextImpl.getXPositionForStepOutFromInlineFunction(
locations: List<Location>,
inlineFunctionsToSkip: List<KtNamedFunction>
): XSourcePositionImpl? {
return getNextPositionWithFilter(locations) {
offset, elementAt ->
if (inlineFunctionsToSkip.any { it.textRange.contains(offset) }) {
return@getNextPositionWithFilter true
}
getInlineArgumentIfAny(elementAt) != null
}
}
private fun SuspendContextImpl.getXPositionForStepOutFromInlinedArgument(
locations: List<Location>,
inlinedArgumentToSkip: KtFunctionLiteral
): XSourcePositionImpl? {
return getNextPositionWithFilter(locations) {
offset, _ ->
inlinedArgumentToSkip.textRange.contains(offset)
}
}
private fun SuspendContextImpl.getNextPositionWithFilter(
locations: List<Location>,
skip: (Int, PsiElement) -> Boolean
): XSourcePositionImpl? {
for (location in locations) {
val position = runReadAction l@ {
val sourcePosition = try {
this.debugProcess.positionManager.getSourcePosition(location)
}
catch(e: NoDataException) {
null
} ?: return@l null
val file = sourcePosition.file as? KtFile ?: return@l null
val elementAt = sourcePosition.elementAt ?: return@l null
val currentLine = location.lineNumber() - 1
val lineStartOffset = file.getLineStartOffset(currentLine) ?: return@l null
if (skip(lineStartOffset, elementAt)) return@l null
XSourcePositionImpl.createByElement(elementAt)
}
if (position != null) {
return position
}
}
return null
}
fun getInlineRangeLocalVariables(stackFrame: StackFrameProxyImpl): List<LocalVariable> {
return stackFrame.visibleVariables()
.filter {
val name = it.name()
name.startsWith(JvmAbi.LOCAL_VARIABLE_NAME_PREFIX_INLINE_FUNCTION)
}
.map { it.variable }
}
private fun getInlineArgumentIfAny(elementAt: PsiElement?): KtFunctionLiteral? {
val functionLiteralExpression = elementAt?.getParentOfType<KtLambdaExpression>(false) ?: return null
val context = functionLiteralExpression.analyze(BodyResolveMode.PARTIAL)
if (!InlineUtil.isInlinedArgument(functionLiteralExpression.functionLiteral, context, false)) return null
return functionLiteralExpression.functionLiteral
}
private fun findReturnFromDexBytecode(method: Method): Long {
val methodLocations = method.allLineLocations()
if (methodLocations.isEmpty()) return -1L
var lastMethodCodeIndex = methodLocations.last().codeIndex()
// Continue while it's possible to get location
while (true) {
if (method.locationOfCodeIndex(lastMethodCodeIndex + 1) != null) {
lastMethodCodeIndex++
}
else {
break
}
}
var returnIndex = lastMethodCodeIndex + 1
val bytecode = method.bytecodes()
var i = bytecode.size
while (i >= 2) {
// Can step only through two-byte instructions and abort on any unknown one
i -= 2
returnIndex -= 1
val instruction = bytecode[i].toInt()
if (instruction == RETURN_VOID || instruction == RETURN || instruction == RETURN_WIDE || instruction == RETURN_OBJECT) {
// Instruction found
return returnIndex
}
else if (instruction == MOVE || instruction == GOTO) {
// proceed
}
else {
// Don't know the instruction and it's length. Abort.
break
}
}
return -1L
}
object DexBytecode {
val RETURN_VOID = 0x0e
val RETURN = 0x0f
val RETURN_WIDE = 0x10
val RETURN_OBJECT = 0x11
val GOTO = 0x28
val MOVE = 0x01
}
@@ -0,0 +1,27 @@
<?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.stepping.KotlinSteppingConfigurableUi">
<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="ignoreKotlinMethods">
<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.filter.ignore.internal.classes"/>
</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>
@@ -0,0 +1,51 @@
/*
* 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.stepping;
import com.intellij.openapi.options.ConfigurableUi;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.debugger.KotlinDebuggerSettings;
import javax.swing.*;
public class KotlinSteppingConfigurableUi implements ConfigurableUi<KotlinDebuggerSettings> {
private JCheckBox ignoreKotlinMethods;
private JPanel myPanel;
@Override
public void reset(@NotNull KotlinDebuggerSettings settings) {
boolean flag = settings.getDEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES();
ignoreKotlinMethods.setSelected(flag);
}
@Override
public boolean isModified(@NotNull KotlinDebuggerSettings settings) {
return settings.getDEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES() != ignoreKotlinMethods.isSelected();
}
@Override
public void apply(@NotNull KotlinDebuggerSettings settings) {
settings.setDEBUG_DISABLE_KOTLIN_INTERNAL_CLASSES(ignoreKotlinMethods.isSelected());
}
@NotNull
@Override
public JComponent getComponent() {
return myPanel;
}
}
@@ -0,0 +1,81 @@
/*
* 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.stepping
import com.intellij.debugger.DebuggerBundle
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.MethodFilter
import com.intellij.debugger.engine.RequestHint
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.settings.DebuggerSettings
import com.intellij.psi.PsiFile
import com.intellij.util.Range
import com.intellij.xdebugger.impl.XSourcePositionImpl
import com.sun.jdi.Location
import com.sun.jdi.request.EventRequest
import org.jetbrains.kotlin.idea.debugger.isOnSuspendReturnOrReenter
import org.jetbrains.kotlin.idea.debugger.suspendFunctionFirstLineLocation
import org.jetbrains.kotlin.idea.util.application.runReadAction
class KotlinSuspendCallStepOverFilter(
private val line: Int,
private val file: PsiFile,
private val ignoreBreakpoints: Boolean) : MethodFilter {
override fun getCallingExpressionLines(): Range<Int>? = Range(line, line)
override fun locationMatches(process: DebugProcessImpl, location: Location?): Boolean {
return location != null && isOnSuspendReturnOrReenter(location)
}
override fun onReached(context: SuspendContextImpl, hint: RequestHint): Int {
val location = context.frameProxy?.location() ?: return RequestHint.STOP
val suspendStartLineNumber = suspendFunctionFirstLineLocation(location) ?: return RequestHint.STOP
val debugProcess = context.debugProcess
val breakpointManager = DebuggerManagerEx.getInstanceEx(debugProcess.project).breakpointManager
breakpointManager.applyThreadFilter(debugProcess, null)
createRunToCursorBreakpoint(context, suspendStartLineNumber - 1, file, ignoreBreakpoints)
return RequestHint.RESUME
}
}
private fun createRunToCursorBreakpoint(context: SuspendContextImpl, line: Int, file: PsiFile, ignoreBreakpoints: Boolean) {
val position = XSourcePositionImpl.create(file.virtualFile, line) ?: return
val process = context.debugProcess
process.showStatusText(DebuggerBundle.message("status.run.to.cursor"))
process.cancelRunToCursorBreakpoint()
if (ignoreBreakpoints) {
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.disableBreakpoints(process)
}
val runToCursorBreakpoint =
runReadAction {
DebuggerManagerEx.getInstanceEx(process.project).breakpointManager.addRunToCursorBreakpoint(position, ignoreBreakpoints)
} ?:
return
runToCursorBreakpoint.suspendPolicy = when {
context.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD -> DebuggerSettings.SUSPEND_THREAD
else -> DebuggerSettings.SUSPEND_ALL
}
runToCursorBreakpoint.createRequest(process)
process.setRunToCursorBreakpoint(runToCursorBreakpoint)
}
@@ -0,0 +1,91 @@
/*
* 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.stepping
import com.intellij.debugger.engine.BreakpointStepMethodFilter
import com.intellij.debugger.engine.MethodFilter
import com.intellij.debugger.engine.RequestHint
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
import com.intellij.openapi.diagnostic.Logger
import com.sun.jdi.VMDisconnectedException
import com.sun.jdi.request.StepRequest
import org.intellij.lang.annotations.MagicConstant
import java.lang.reflect.Field
internal class RequestHintWithMethodFilter(
stepThread: ThreadReferenceProxyImpl,
suspendContext: SuspendContextImpl,
@MagicConstant(intValues = longArrayOf(
StepRequest.STEP_INTO.toLong(),
StepRequest.STEP_OVER.toLong(),
StepRequest.STEP_OUT.toLong())) depth: Int,
methodFilter: MethodFilter
) : RequestHint(stepThread, suspendContext, methodFilter) {
private var targetMethodMatched = false
init {
// NOTE: Debugger API. Open RequestHint constructor with depth
if (depth != StepRequest.STEP_INTO) {
findFieldWithValue(StepRequest.STEP_INTO, Integer.TYPE)?.setInt(this, depth)
}
}
private fun findFieldWithValue(value: Int, type: Class<*>): Field? {
return RequestHint::class.java.declaredFields.firstOrNull { field ->
if (field.type == type) {
field.isAccessible = true
if (field.getInt(this) == value) {
return@firstOrNull true
}
}
false
}
}
override fun getNextStepDepth(context: SuspendContextImpl): Int {
try {
val frameProxy = context.frameProxy
val filter = methodFilter
if (filter != null && frameProxy != null && filter !is BreakpointStepMethodFilter) {
/*NODE: Debugger API. Base implementation works only for smart step into, and calls filter only if !isTheSameFrame(context). */
if (filter.locationMatches(context.debugProcess, frameProxy.location())) {
targetMethodMatched = true
return filter.onReached(context, this)
}
}
}
catch (ignored: VMDisconnectedException) {
return STOP
}
catch (e: EvaluateException) {
LOG.error(e)
return STOP
}
return super.getNextStepDepth(context)
}
override fun wasStepTargetMethodMatched(): Boolean {
return super.wasStepTargetMethodMatched() || targetMethodMatched
}
}
private val LOG = Logger.getInstance(RequestHintWithMethodFilter::class.java)
@@ -0,0 +1,34 @@
/*
* 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.surroundWith;
import com.intellij.lang.surroundWith.Surrounder;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.codeInsight.surroundWith.expression.KotlinExpressionSurroundDescriptor;
public class KotlinDebuggerExpressionSurroundDescriptor extends KotlinExpressionSurroundDescriptor {
private static final Surrounder[] SURROUNDERS = {
new KotlinRuntimeTypeCastSurrounder()
};
@Override
@NotNull
public Surrounder[] getSurrounders() {
return SURROUNDERS;
}
}
@@ -0,0 +1,115 @@
/*
* 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.surroundWith
import com.intellij.codeInsight.CodeInsightBundle
import com.intellij.debugger.DebuggerBundle
import com.intellij.debugger.DebuggerInvocationUtil
import com.intellij.debugger.DebuggerManagerEx
import com.intellij.debugger.impl.DebuggerContextImpl
import com.intellij.openapi.application.Result
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ScrollType
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.util.ProgressWindowWithNotification
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.codeInsight.surroundWith.expression.KotlinExpressionSurrounder
import org.jetbrains.kotlin.idea.debugger.evaluate.KotlinRuntimeTypeEvaluator
import org.jetbrains.kotlin.idea.core.ShortenReferences
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
class KotlinRuntimeTypeCastSurrounder: KotlinExpressionSurrounder() {
override fun isApplicable(expression: KtExpression): Boolean {
if (!super.isApplicable(expression)) return false
if (!expression.isPhysical) return false
val file = expression.containingFile
if (file !is KtCodeFragment) return false
val type = expression.analyze(BodyResolveMode.PARTIAL).getType(expression) ?: return false
return TypeUtils.canHaveSubtypes(KotlinTypeChecker.DEFAULT, type)
}
override fun surroundExpression(project: Project, editor: Editor, expression: KtExpression): TextRange? {
val debuggerContext = DebuggerManagerEx.getInstanceEx(project).context
val debuggerSession = debuggerContext.debuggerSession
if (debuggerSession != null) {
val progressWindow = ProgressWindowWithNotification(true, expression.project)
val worker = SurroundWithCastWorker(editor, expression, debuggerContext, progressWindow)
progressWindow.title = DebuggerBundle.message("title.evaluating")
debuggerContext.debugProcess?.managerThread?.startProgress(worker, progressWindow)
}
return null
}
override fun getTemplateDescription(): String {
return KotlinBundle.message("surround.with.runtime.type.cast.template")
}
private inner class SurroundWithCastWorker(
private val myEditor: Editor,
expression: KtExpression,
context: DebuggerContextImpl,
indicator: ProgressIndicator
): KotlinRuntimeTypeEvaluator(myEditor, expression, context, indicator) {
override fun typeCalculationFinished(type: KotlinType?) {
if (type == null) return
hold()
val project = myEditor.project
DebuggerInvocationUtil.invokeLater(project, Runnable {
object : WriteCommandAction<Any>(project, CodeInsightBundle.message("command.name.surround.with.runtime.cast")) {
override fun run(result: Result<Any>) {
try {
val factory = KtPsiFactory(myElement.project)
val fqName = DescriptorUtils.getFqName(type.constructor.declarationDescriptor!!)
val parentCast = factory.createExpression("(expr as " + fqName.asString() + ")") as KtParenthesizedExpression
val cast = parentCast.expression as KtBinaryExpressionWithTypeRHS
cast.left.replace(myElement)
val expr = myElement.replace(parentCast) as KtExpression
ShortenReferences.DEFAULT.process(expr)
val range = expr.textRange
myEditor.selectionModel.setSelection(range.startOffset, range.endOffset)
myEditor.caretModel.moveToOffset(range.endOffset)
myEditor.scrollingModel.scrollToCaret(ScrollType.RELATIVE)
}
finally {
release()
}
}
}.execute()
}, myProgressIndicator.modalityState)
}
}
}
@@ -0,0 +1,110 @@
/*
* 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.facet
import com.intellij.facet.impl.ui.libraries.LibrariesValidatorContext
import com.intellij.facet.ui.FacetConfigurationQuickFix
import com.intellij.facet.ui.FacetValidatorsManager
import com.intellij.facet.ui.ValidationResult
import com.intellij.facet.ui.libraries.FrameworkLibraryValidator
import com.intellij.ide.IdeBundle
import com.intellij.openapi.roots.ui.configuration.libraries.AddCustomLibraryDialog
import com.intellij.openapi.roots.ui.configuration.libraries.CustomLibraryDescription
import com.intellij.openapi.roots.ui.configuration.libraries.LibraryPresentationManager
import org.jetbrains.kotlin.config.TargetPlatformKind
import org.jetbrains.kotlin.idea.framework.CommonStandardLibraryDescription
import org.jetbrains.kotlin.idea.framework.JSLibraryStdDescription
import org.jetbrains.kotlin.idea.framework.JavaRuntimeLibraryDescription
import javax.swing.JComponent
// Based on com.intellij.facet.impl.ui.libraries.FrameworkLibraryValidatorImpl
class FrameworkLibraryValidatorWithDynamicDescription(
private val context: LibrariesValidatorContext,
private val validatorsManager: FacetValidatorsManager,
private val libraryCategoryName: String,
private val getTargetPlatform: () -> TargetPlatformKind<*>
) : FrameworkLibraryValidator() {
private val TargetPlatformKind<*>.libraryDescription: CustomLibraryDescription
get() {
val project = context.module.project
return when (this) {
is TargetPlatformKind.Jvm -> JavaRuntimeLibraryDescription(project)
is TargetPlatformKind.JavaScript -> JSLibraryStdDescription(project)
is TargetPlatformKind.Common -> CommonStandardLibraryDescription(project)
}
}
private fun checkLibraryIsConfigured(targetPlatform: TargetPlatformKind<*>): Boolean {
// TODO: propose to configure kotlin-stdlib-common once it's available
if (targetPlatform == TargetPlatformKind.Common) return true
if (KotlinVersionInfoProvider.EP_NAME.extensions.any {
it.getLibraryVersions(context.module, targetPlatform, context.rootModel).isNotEmpty()
}) return true
val libraryDescription = targetPlatform.libraryDescription
val libraryKinds = libraryDescription.suitableLibraryKinds
var found = false
val presentationManager = LibraryPresentationManager.getInstance()
context.rootModel
.orderEntries()
.using(context.modulesProvider)
.recursively()
.librariesOnly()
.forEachLibrary { library ->
if (presentationManager.isLibraryOfKind(library, context.librariesContainer, libraryKinds)) {
found = true
}
!found
}
return found
}
override fun check(): ValidationResult {
val targetPlatform = getTargetPlatform()
if (checkLibraryIsConfigured(targetPlatform)) {
val conflictingPlatforms = TargetPlatformKind.ALL_PLATFORMS.filter {
it != TargetPlatformKind.Common && it.name != targetPlatform.name && checkLibraryIsConfigured(it)
}
if (conflictingPlatforms.isNotEmpty()) {
val platformText = conflictingPlatforms.mapTo(LinkedHashSet()) { it.name }.joinToString()
return ValidationResult("Libraries for the following platform are also present in the module dependencies: $platformText")
}
return ValidationResult.OK
}
return ValidationResult(
IdeBundle.message("label.missed.libraries.text", libraryCategoryName),
LibrariesQuickFix(targetPlatform.libraryDescription)
)
}
private inner class LibrariesQuickFix(
private val myDescription: CustomLibraryDescription
) : FacetConfigurationQuickFix(IdeBundle.message("button.fix")) {
override fun run(place: JComponent) {
val dialog = AddCustomLibraryDialog.createDialog(myDescription, context.librariesContainer,
context.module, context.modifiableRootModel,
null)
dialog.show()
validatorsManager.validate()
}
}
}
@@ -0,0 +1,34 @@
/*
* 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.facet
import com.intellij.facet.impl.ui.libraries.DelegatingLibrariesValidatorContext
import com.intellij.facet.ui.FacetEditorContext
import com.intellij.facet.ui.FacetEditorValidator
import com.intellij.facet.ui.FacetValidatorsManager
import org.jetbrains.kotlin.config.TargetPlatformKind
import org.jetbrains.kotlin.idea.facet.KotlinFacetEditorGeneralTab.EditorComponent
class KotlinLibraryValidatorCreator : KotlinFacetValidatorCreator() {
override fun create(editor: EditorComponent, validatorsManager: FacetValidatorsManager, editorContext: FacetEditorContext): FacetEditorValidator {
return FrameworkLibraryValidatorWithDynamicDescription(
DelegatingLibrariesValidatorContext(editorContext),
validatorsManager,
"kotlin"
) { editor.targetPlatformComboBox.selectedItem as TargetPlatformKind<*> }
}
}
@@ -0,0 +1,147 @@
/*
* 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.filters
import com.intellij.execution.filters.*
import com.intellij.execution.filters.impl.HyperlinkInfoFactoryImpl
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.search.FilenameIndex
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.idea.debugger.*
import org.jetbrains.kotlin.resolve.jvm.JvmClassName
import java.util.*
import java.util.regex.Pattern
class KotlinExceptionFilter(private val searchScope: GlobalSearchScope) : Filter {
private val exceptionFilter = ExceptionFilter(searchScope)
override fun applyFilter(line: String, entireLength: Int): Filter.Result? {
val result = exceptionFilter.applyFilter(line, entireLength)
return if (result == null) null else patchResult(result, line)
}
private fun patchResult(result: Filter.Result, line: String): Filter.Result {
val newHyperlinkInfo = createHyperlinkInfo(line, result) ?: return result
return Filter.Result(result.resultItems.map {
Filter.ResultItem(it.getHighlightStartOffset(), it.getHighlightEndOffset(), newHyperlinkInfo, it.getHighlightAttributes())
})
}
private fun createHyperlinkInfo(line: String, defaultResult: Filter.Result): HyperlinkInfo? {
val project = searchScope.project ?: return null
val stackTraceElement = parseStackTraceLine(line) ?: return null
// All true classes should be handled correctly in the default ExceptionFilter. Special cases:
// - static facades;
// - package facades / package parts (generated by pre-M13 compiled);
// - local classes (and closures) in top-level function and property declarations.
// - bad line numbers for inline functions
// - already applied smap for inline functions
val fileName = stackTraceElement.fileName
if (!DebuggerUtils.isKotlinSourceFile(fileName)) return null
// fullyQualifiedName is of format "package.Class$Inner"
val fullyQualifiedName = stackTraceElement.className
val lineNumber = stackTraceElement.lineNumber - 1
val internalName = fullyQualifiedName.replace('.', '/')
val jvmClassName = JvmClassName.byInternalName(internalName)
val file = DebuggerUtils.findSourceFileForClassIncludeLibrarySources(project, searchScope, jvmClassName, fileName)
if (file == null) {
// File can't be found by class name and file name this can happen when smap info is already applied.
// Default filter favours looking for file from class name and that can lead to wrong navigation to inline fun call file and
// line from inline function definition.
val defaultLinkFileNames = defaultResult.resultItems.mapNotNullTo(HashSet()) { (it as? FileHyperlinkInfo)?.descriptor?.file?.name }
if (!defaultLinkFileNames.contains(fileName)) {
val filesByName = FilenameIndex.getFilesByName(project, fileName, searchScope).mapNotNullTo(HashSet()) {
if (!it.isValid) return@mapNotNullTo null
it.virtualFile
}
if (filesByName.isNotEmpty()) {
return if (filesByName.size > 1) {
HyperlinkInfoFactoryImpl.getInstance().createMultipleFilesHyperlinkInfo(filesByName.toList(), lineNumber, project)
}
else {
OpenFileHyperlinkInfo(project, filesByName.first(), lineNumber)
}
}
}
return null
}
val virtualFile = file.virtualFile ?: return null
val hyperlinkInfoForInline = createHyperlinks(jvmClassName, virtualFile, lineNumber + 1, project)
if (hyperlinkInfoForInline != null) {
return hyperlinkInfoForInline
}
return OpenFileHyperlinkInfo(project, virtualFile, lineNumber)
}
private fun createHyperlinks(jvmName: JvmClassName, file: VirtualFile, line: Int, project: Project): InlineFunctionHyperLinkInfo? {
if (!isInlineFunctionLineNumber(file, line, project)) return null
val debugInfo = readBytecodeInfo(project, jvmName, file) ?: return null
val smapData = debugInfo.smapData ?: return null
val inlineInfos = arrayListOf<InlineFunctionHyperLinkInfo.InlineInfo>()
val (inlineFunctionBodyFile, inlineFunctionBodyLine) =
mapStacktraceLineToSource(smapData, line, project, SourceLineKind.EXECUTED_LINE, searchScope) ?: return null
inlineInfos.add(InlineFunctionHyperLinkInfo.InlineInfo.InlineFunctionBodyInfo(
inlineFunctionBodyFile.virtualFile,
inlineFunctionBodyLine))
val inlineFunCallInfo = mapStacktraceLineToSource(smapData, line, project, SourceLineKind.CALL_LINE, searchScope)
if (inlineFunCallInfo != null) {
val (callSiteFile, callSiteLine) = inlineFunCallInfo
inlineInfos.add(InlineFunctionHyperLinkInfo.InlineInfo.CallSiteInfo(callSiteFile.virtualFile, callSiteLine))
}
return InlineFunctionHyperLinkInfo(project, inlineInfos)
}
companion object {
// Matches strings like "\tat test.TestPackage$foo$f$1.invoke(a.kt:3)\n"
// or "\tBreakpoint reached at test.TestPackage$foo$f$1.invoke(a.kt:3)\n"
private val STACK_TRACE_ELEMENT_PATTERN = Pattern.compile("^[\\w|\\s]*at\\s+(.+)\\.(.+)\\((.+):(\\d+)\\)\\s*$")
private fun parseStackTraceLine(line: String): StackTraceElement? {
val matcher = STACK_TRACE_ELEMENT_PATTERN.matcher(line)
if (matcher.matches()) {
val declaringClass = matcher.group(1)
val methodName = matcher.group(2)
val fileName = matcher.group(3)
val lineNumber = matcher.group(4)
//noinspection ConstantConditions
return StackTraceElement(declaringClass, methodName, fileName, Integer.parseInt(lineNumber))
}
return null
}
}
}
@@ -0,0 +1,30 @@
/*
* 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.filters;
import com.intellij.execution.filters.ExceptionFilterFactory;
import com.intellij.execution.filters.Filter;
import com.intellij.psi.search.GlobalSearchScope;
import org.jetbrains.annotations.NotNull;
public class KotlinExceptionFilterFactory implements ExceptionFilterFactory {
@NotNull
@Override
public Filter create(@NotNull GlobalSearchScope searchScope) {
return new KotlinExceptionFilter(searchScope);
}
}
@@ -0,0 +1,38 @@
/*
* 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.framework
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.DummyLibraryProperties
import com.intellij.openapi.roots.libraries.LibraryType
import com.intellij.openapi.roots.libraries.NewLibraryConfiguration
import com.intellij.openapi.roots.libraries.ui.LibraryEditorComponent
import com.intellij.openapi.vfs.VirtualFile
import org.jetbrains.kotlin.idea.KotlinIcons
import javax.swing.JComponent
object CommonLibraryType : LibraryType<DummyLibraryProperties>(CommonLibraryKind) {
override fun createPropertiesEditor(editorComponent: LibraryEditorComponent<DummyLibraryProperties>) = null
override fun getCreateActionName() = null
override fun createNewLibrary(parentComponent: JComponent,
contextDirectory: VirtualFile?,
project: Project): NewLibraryConfiguration? = null
override fun getIcon(properties: DummyLibraryProperties?) = KotlinIcons.MPP
}
@@ -0,0 +1,34 @@
/*
* 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.framework
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.libraries.LibraryKind
class CommonStandardLibraryDescription(project: Project?) : CustomLibraryDescriptorWithDeferredConfig(
// TODO: KotlinCommonModuleConfigurator
project, "common", LIBRARY_NAME, DIALOG_TITLE, LIBRARY_CAPTION, KOTLIN_COMMON_STDLIB_KIND, SUITABLE_LIBRARY_KINDS
) {
companion object {
val KOTLIN_COMMON_STDLIB_KIND = LibraryKind.create("kotlin-stdlib-common")
val LIBRARY_NAME = "KotlinStdlibCommon"
val DIALOG_TITLE = "Create Kotlin Common Standard Library"
val LIBRARY_CAPTION = "Kotlin Common Standard Library"
val SUITABLE_LIBRARY_KINDS: Set<LibraryKind> = setOf(KOTLIN_COMMON_STDLIB_KIND)
}
}

Some files were not shown because too many files have changed in this diff Show More