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:
@@ -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>";
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -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);
|
||||
}
|
||||
}
|
||||
+41
@@ -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
|
||||
}
|
||||
}
|
||||
+298
@@ -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
|
||||
}
|
||||
+89
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+146
@@ -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
|
||||
}
|
||||
}
|
||||
+102
@@ -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
|
||||
}
|
||||
}
|
||||
+64
@@ -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")
|
||||
}
|
||||
}
|
||||
+159
@@ -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")
|
||||
+461
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+27
@@ -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)
|
||||
}
|
||||
}
|
||||
+49
@@ -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")
|
||||
+108
@@ -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)
|
||||
}
|
||||
}
|
||||
+78
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -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 }
|
||||
}
|
||||
}
|
||||
+37
@@ -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)
|
||||
}
|
||||
}
|
||||
+27
@@ -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>
|
||||
+50
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+191
@@ -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) }
|
||||
}
|
||||
+28
@@ -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);
|
||||
}
|
||||
}
|
||||
+32
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
+334
@@ -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
|
||||
+168
@@ -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>
|
||||
+403
@@ -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());
|
||||
}
|
||||
}
|
||||
+36
@@ -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)
|
||||
+403
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
+88
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+178
@@ -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
|
||||
}
|
||||
+169
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -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
|
||||
}
|
||||
}
|
||||
+146
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+62
@@ -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>
|
||||
+144
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+46
@@ -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
|
||||
}
|
||||
}
|
||||
+40
@@ -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
|
||||
}
|
||||
}
|
||||
+429
@@ -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()
|
||||
}
|
||||
}
|
||||
+255
@@ -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)
|
||||
+647
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
+111
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+48
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+26
@@ -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?
|
||||
}
|
||||
+57
@@ -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)
|
||||
}
|
||||
}
|
||||
+24
@@ -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() {}
|
||||
}
|
||||
+94
@@ -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
|
||||
}
|
||||
}
|
||||
+20
@@ -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)
|
||||
+132
@@ -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
|
||||
}
|
||||
}
|
||||
+359
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -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()
|
||||
}
|
||||
}
|
||||
+129
@@ -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() }
|
||||
}
|
||||
}
|
||||
+81
@@ -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()
|
||||
}
|
||||
}
|
||||
+134
@@ -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) }
|
||||
+45
@@ -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;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+208
@@ -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;
|
||||
}
|
||||
}
|
||||
+112
@@ -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
|
||||
}
|
||||
+85
@@ -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()
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -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()}()"
|
||||
}
|
||||
}
|
||||
}
|
||||
+62
@@ -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()
|
||||
}
|
||||
}
|
||||
+54
@@ -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
|
||||
}
|
||||
}
|
||||
+229
@@ -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
|
||||
}
|
||||
}
|
||||
+160
@@ -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
|
||||
}
|
||||
}
|
||||
+64
@@ -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
|
||||
}
|
||||
}
|
||||
+70
@@ -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
|
||||
}
|
||||
}
|
||||
+586
@@ -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
|
||||
}
|
||||
+27
@@ -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>
|
||||
+51
@@ -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;
|
||||
}
|
||||
}
|
||||
+81
@@ -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)
|
||||
}
|
||||
+91
@@ -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)
|
||||
+34
@@ -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;
|
||||
}
|
||||
}
|
||||
+115
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+110
@@ -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
|
||||
}
|
||||
+34
@@ -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
Reference in New Issue
Block a user