diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/CompoundPositionManager.kt b/java/debugger/impl/src/com/intellij/debugger/engine/CompoundPositionManager.kt index bcd6979bec49..38f709657faa 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/CompoundPositionManager.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/CompoundPositionManager.kt @@ -10,16 +10,20 @@ import com.intellij.debugger.engine.evaluation.EvaluationContext import com.intellij.debugger.impl.DebuggerUtilsAsync import com.intellij.debugger.impl.DebuggerUtilsEx import com.intellij.debugger.impl.DebuggerUtilsImpl +import com.intellij.debugger.impl.suppressExceptions import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.requests.ClassPrepareRequestor import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl import com.intellij.execution.filters.LineNumbersMapping import com.intellij.openapi.application.ReadAction +import com.intellij.openapi.application.readAction import com.intellij.openapi.application.runReadAction import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.fileTypes.FileTypeManager import com.intellij.openapi.fileTypes.UnknownFileType import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.blockingContext +import com.intellij.openapi.progress.checkCanceled import com.intellij.util.ThreeState import com.intellij.xdebugger.frame.XStackFrame import com.sun.jdi.Location @@ -52,21 +56,23 @@ class CompoundPositionManager() : PositionManagerWithConditionEvaluation, MultiR mySourcePositionCache.clear() } - private fun iterate(position: SourcePosition, defaultValue: T?, processor: (PositionManager) -> T?): T? { + private inline fun iterate(position: SourcePosition, defaultValue: T?, processor: (PositionManager) -> T?): T? { val fileType = position.getFile().getFileType() - return iterate(defaultValue, fileType, true, processor) + return iterate(defaultValue, fileType, true, ProgressManager::checkCanceled, processor) } - private fun iterate(defaultValue: T?, fileType: FileType?, ignorePCE: Boolean, processor: (PositionManager) -> T?): T? { + private inline fun iterate( + defaultValue: T?, fileType: FileType?, ignorePCE: Boolean, + cancellationCheck: () -> Unit, + processor: (PositionManager) -> T?, + ): T? { for (positionManager in myPositionManagers) { if (!acceptsFileType(positionManager, fileType)) continue try { if (!ignorePCE) { - ProgressManager.checkCanceled() + cancellationCheck() } - return DebuggerUtilsImpl.suppressExceptions( - { processor(positionManager) }, - defaultValue, ignorePCE, NoDataException::class.java) + return suppressExceptions(defaultValue, ignorePCE, NoDataException::class.java) { processor(positionManager) } } catch (_: NoDataException) { } @@ -74,68 +80,50 @@ class CompoundPositionManager() : PositionManagerWithConditionEvaluation, MultiR return defaultValue } - private fun iterateAsync( - fileType: FileType?, - ignorePCE: Boolean, - processor: (PositionManager) -> CompletableFuture, - ): CompletableFuture { - var res = CompletableFuture.failedFuture(NoDataException.INSTANCE) - for (positionManager in myPositionManagers) { - if (!acceptsFileType(positionManager, fileType)) continue - res = res.exceptionallyCompose { e: Throwable -> - val unwrap = DebuggerUtilsAsync.unwrap(e) - if (unwrap is NoDataException) { - if (!ignorePCE) { - ProgressManager.checkCanceled() - } - return@exceptionallyCompose processor(positionManager) - } - CompletableFuture.failedFuture(unwrap) - } - } - return res + fun getSourcePositionFuture(location: Location?): CompletableFuture = invokeCommandAsCompletableFuture { + getSourcePositionAsync(location) } - override fun getSourcePositionAsync(location: Location?): CompletableFuture = - getCachedSourcePosition(location) { fileType: FileType? -> - iterateAsync(fileType, false) { getSourcePositionAsync(it, location) } + override suspend fun getSourcePositionAsync(location: Location?): SourcePosition? = + getCachedSourcePosition(location, { action -> readAction(action) }) { fileType: FileType? -> + iterate(null, fileType, false, { checkCanceled() }) { getSourcePositionAsync(it, location) } } - private fun getCachedSourcePosition( + override fun getSourcePosition(location: Location?): SourcePosition? = + getCachedSourcePosition(location, { action -> runReadAction(action) }) { fileType: FileType? -> + ReadAction.nonBlocking { + iterate(null, fileType, false, ProgressManager::checkCanceled) { it.getSourcePosition(location) } + }.executeSynchronously() + } + + private inline fun getCachedSourcePosition( location: Location?, - producer: (FileType?) -> CompletableFuture, - ): CompletableFuture { - if (location == null) return CompletableFuture.completedFuture(null) - var res: SourcePosition? = null + insideReadAction: (() -> Unit) -> Unit, + producer: (FileType?) -> SourcePosition?, + ): SourcePosition? { + if (location == null) return null try { - res = mySourcePositionCache[location] + val position = mySourcePositionCache[location] + if (position != null && checkCacheEntry(position, location.declaringType().name(), insideReadAction)) { + return position + } } catch (_: IllegalArgumentException) { // Invalid method id } - if (checkCacheEntry(res, location)) return CompletableFuture.completedFuture(res) - val fileType = runReadAction { - val sourceName = DebuggerUtilsEx.getSourceName(location, null) - if (sourceName != null) FileTypeManager.getInstance().getFileTypeByFileName(sourceName) else null + val sourceName = DebuggerUtilsEx.getSourceName(location, null) + val fileType = if (sourceName != null) + callInReadAction(insideReadAction) { FileTypeManager.getInstance().getFileTypeByFileName(sourceName) } + else + null + + val position = producer(fileType) + try { + mySourcePositionCache.put(location, position) } - return producer(fileType) - .thenApply { p: SourcePosition? -> - try { - mySourcePositionCache.put(location, p) - } - catch (_: IllegalArgumentException) { // Invalid method id - } - p - } - } - - override fun getSourcePosition(location: Location?): SourcePosition? { - return getCachedSourcePosition(location) { fileType: FileType? -> - val sourcePosition = ReadAction.nonBlocking { - iterate(null, fileType, false) { it.getSourcePosition(location) } - }.executeSynchronously() - CompletableFuture.completedFuture(sourcePosition) - }.getNow(null) + catch (_: IllegalArgumentException) { // Invalid method id + } + return position } override fun getAllClasses(classPosition: SourcePosition): MutableList = @@ -173,7 +161,7 @@ class CompoundPositionManager() : PositionManagerWithConditionEvaluation, MultiR }!! fun createStackFrames(descriptor: StackFrameDescriptorImpl): MutableList? = - iterate(null, null, false) { + iterate(null, null, false, ProgressManager::checkCanceled) { if (it is PositionManagerWithMultipleStackFrames) { val stackFrames = it.createStackFrames(descriptor) if (stackFrames != null) { @@ -222,26 +210,32 @@ private fun acceptsFileType(positionManager: PositionManager, fileType: FileType return types.contains(fileType) } -private fun getSourcePositionAsync(positionManager: PositionManager, location: Location?): CompletableFuture { +private suspend fun getSourcePositionAsync(positionManager: PositionManager, location: Location?): SourcePosition? { if (positionManager is PositionManagerAsync) { return positionManager.getSourcePositionAsync(location) } try { - val sourcePosition = ReadAction.nonBlocking { positionManager.getSourcePosition(location) }.executeSynchronously() - return CompletableFuture.completedFuture(sourcePosition) + return blockingContext { + ReadAction.nonBlocking { positionManager.getSourcePosition(location) }.executeSynchronously() + } } catch (e: Exception) { - return CompletableFuture.failedFuture(DebuggerUtilsAsync.unwrap(e)) + throw DebuggerUtilsAsync.unwrap(e) } } -private fun checkCacheEntry(position: SourcePosition?, location: Location): Boolean { - if (position == null) return false - return runReadAction { +private inline fun checkCacheEntry(position: SourcePosition, className: String, insideReadAction: (() -> Unit) -> Unit): Boolean = + callInReadAction(insideReadAction) { val psiFile = position.getFile() - if (!psiFile.isValid()) return@runReadAction false - val url = DebuggerUtilsEx.getAlternativeSourceUrl(location.declaringType().name(), psiFile.getProject()) ?: return@runReadAction true + if (!psiFile.isValid()) return@callInReadAction false + val url = DebuggerUtilsEx.getAlternativeSourceUrl(className, psiFile.getProject()) ?: return@callInReadAction true val file = psiFile.getVirtualFile() file != null && url == file.url - } +} + +@Suppress("UNCHECKED_CAST") +private inline fun callInReadAction(insideReadAction: (() -> Unit) -> Unit, crossinline action: () -> T): T { + var result: T? = null + insideReadAction { result = action() } + return result as T } diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerAsync.kt b/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerAsync.kt index 8017b34a614b..59d2aed0770d 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerAsync.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/PositionManagerAsync.kt @@ -1,12 +1,29 @@ // Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.debugger.engine +import com.intellij.debugger.PositionManager import com.intellij.debugger.SourcePosition +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.diagnostic.fileLogger +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.progress.runBlockingMaybeCancellable +import com.intellij.util.concurrency.annotations.RequiresBlockingContext import com.sun.jdi.Location import org.jetbrains.annotations.ApiStatus -import java.util.concurrent.CompletableFuture @ApiStatus.Experimental -interface PositionManagerAsync { - fun getSourcePositionAsync(location: Location?): CompletableFuture +interface PositionManagerAsync : PositionManager { + suspend fun getSourcePositionAsync(location: Location?): SourcePosition? + + @RequiresBlockingContext + override fun getSourcePosition(location: Location?): SourcePosition? { + if (ApplicationManager.getApplication().isInternal + && ApplicationManager.getApplication().isReadAccessAllowed + && !ProgressManager.getInstance().hasProgressIndicator()) { + fileLogger().error("Call runBlocking from read action without indicator") + } + return runBlockingMaybeCancellable { + getSourcePositionAsync(location) + } + } } diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/DebugUtils.kt b/java/debugger/impl/src/com/intellij/debugger/impl/DebugUtils.kt new file mode 100644 index 000000000000..6f736407e9ab --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebugUtils.kt @@ -0,0 +1,54 @@ +// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.debugger.impl + +import com.intellij.openapi.diagnostic.fileLogger +import com.intellij.openapi.progress.ProcessCanceledException +import com.sun.jdi.InternalException +import com.sun.jdi.ObjectCollectedException +import com.sun.jdi.VMDisconnectedException +import kotlin.coroutines.cancellation.CancellationException + +inline fun suppressExceptions( + defaultValue: T?, + ignorePCE: Boolean, + rethrow: Class? = null, + supplier: () -> T?, +): T? { + try { + return supplier() + } + catch (e: ProcessCanceledException) { + if (!ignorePCE) { + throw e + } + } + catch (e: CancellationException) { + throw e + } + catch (e: VMDisconnectedException) { + throw e + } + catch (e: ObjectCollectedException) { + throw e + } + catch (e: InternalException) { + fileLogger().info(e) + } + catch (e: Exception) { + if (rethrow != null && rethrow.isInstance(e)) { + throw e + } + else { + fileLogger().error(e) + } + } + catch (e: AssertionError) { + if (rethrow != null && rethrow.isInstance(e)) { + throw e + } + else { + fileLogger().error(e) + } + } + return defaultValue +} diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsAsync.java b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsAsync.java index eaa45adc5650..d4e5fdee51a5 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsAsync.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsAsync.java @@ -530,7 +530,12 @@ public final class DebuggerUtilsAsync { } public static Throwable unwrap(@Nullable Throwable throwable) { - return throwable instanceof CompletionException || throwable instanceof ExecutionException ? throwable.getCause() : throwable; + while (throwable instanceof CompletionException || throwable instanceof ExecutionException) { + Throwable cause = throwable.getCause(); + if (cause == throwable) break; + throwable = cause; + } + return throwable; } public static T logError(@NotNull Throwable throwable) { diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java index 3164bae5b6b7..30235d77e96f 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java @@ -249,40 +249,6 @@ public final class DebuggerUtilsImpl extends DebuggerUtilsEx { action.accept(wrapIntoThrowable ? new Throwable(e) : e); } - public static T suppressExceptions(ThrowableComputable supplier, - T defaultValue) throws E { - return suppressExceptions(supplier, defaultValue, true, null); - } - - public static T suppressExceptions(ThrowableComputable supplier, - T defaultValue, - boolean ignorePCE, - Class rethrow) throws E { - try { - return supplier.compute(); - } - catch (ProcessCanceledException e) { - if (!ignorePCE) { - throw e; - } - } - catch (VMDisconnectedException | ObjectCollectedException e) { - throw e; - } - catch (InternalException e) { - LOG.info(e); - } - catch (Exception | AssertionError e) { - if (rethrow != null && rethrow.isInstance(e)) { - throw e; - } - else { - LOG.error(e); - } - } - return defaultValue; - } - public static @NlsContexts.Label String getConnectionWaitStatus(@NotNull RemoteConnection connection) { String connectionName = ObjectUtils.doIfNotNull(connection, DebuggerUtilsImpl::getConnectionDisplayName); return connection instanceof RemoteConnectionStub diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/StackFrameDescriptorImpl.java b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/StackFrameDescriptorImpl.java index b5ee38e1e0e0..4cfab4a60290 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/StackFrameDescriptorImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/impl/watch/StackFrameDescriptorImpl.java @@ -86,7 +86,7 @@ public class StackFrameDescriptorImpl extends NodeDescriptorImpl implements Stac try { Location location = frame.location(); CompoundPositionManager positionManager = frame.getVirtualMachine().getDebugProcess().getPositionManager(); - return positionManager.getSourcePositionAsync(location); + return positionManager.getSourcePositionFuture(location); } catch (Exception e) { return CompletableFuture.failedFuture(e); diff --git a/java/debugger/openapi/src/com/intellij/debugger/PositionManager.java b/java/debugger/openapi/src/com/intellij/debugger/PositionManager.java index df9007fcc895..706ca8496adf 100644 --- a/java/debugger/openapi/src/com/intellij/debugger/PositionManager.java +++ b/java/debugger/openapi/src/com/intellij/debugger/PositionManager.java @@ -17,6 +17,7 @@ package com.intellij.debugger; import com.intellij.debugger.requests.ClassPrepareRequestor; import com.intellij.openapi.fileTypes.FileType; +import com.intellij.util.concurrency.annotations.RequiresBlockingContext; import com.sun.jdi.Location; import com.sun.jdi.ReferenceType; import com.sun.jdi.request.ClassPrepareRequest; @@ -41,6 +42,7 @@ public interface PositionManager { * @throws NoDataException if the location is not in the code managed by this {@code PositionManager} */ @Nullable + @RequiresBlockingContext SourcePosition getSourcePosition(@Nullable Location location) throws NoDataException; /** diff --git a/plugins/kotlin/jvm-debugger/base/util/src/org/jetbrains/kotlin/idea/debugger/base/util/safeUtil.kt b/plugins/kotlin/jvm-debugger/base/util/src/org/jetbrains/kotlin/idea/debugger/base/util/safeUtil.kt index c7518431989b..4efe9cdf9efc 100644 --- a/plugins/kotlin/jvm-debugger/base/util/src/org/jetbrains/kotlin/idea/debugger/base/util/safeUtil.kt +++ b/plugins/kotlin/jvm-debugger/base/util/src/org/jetbrains/kotlin/idea/debugger/base/util/safeUtil.kt @@ -6,6 +6,7 @@ import com.intellij.debugger.NoDataException import com.intellij.debugger.PositionManager import com.intellij.debugger.SourcePosition import com.intellij.debugger.engine.DebugProcess.JAVA_STRATUM +import com.intellij.debugger.engine.PositionManagerAsync import com.intellij.debugger.engine.evaluation.AbsentInformationEvaluateException import com.intellij.debugger.engine.evaluation.EvaluateException import com.intellij.debugger.engine.evaluation.EvaluationContextImpl @@ -15,6 +16,7 @@ import com.intellij.debugger.jdi.LocalVariableProxyImpl import com.intellij.debugger.jdi.StackFrameProxyImpl import com.intellij.debugger.jdi.ThreadReferenceProxyImpl import com.intellij.debugger.ui.impl.watch.ValueDescriptorImpl +import com.intellij.util.concurrency.annotations.RequiresBlockingContext import com.sun.jdi.* import org.jetbrains.annotations.ApiStatus import org.jetbrains.kotlin.idea.debugger.base.util.KotlinDebuggerConstants.KOTLIN_STRATA_NAME @@ -136,11 +138,21 @@ fun ValueDescriptorImpl.safeCalcValue(context: EvaluationContextImpl): Value? { return wrapEvaluateException { calcValue(context) } } +@RequiresBlockingContext @ApiStatus.Internal fun PositionManager.safeGetSourcePosition(location: Location): SourcePosition? { return try { getSourcePosition(location) - } catch (ex: NoDataException) { + } catch (_: NoDataException) { + null + } +} + +@ApiStatus.Internal +suspend fun PositionManagerAsync.safeGetSourcePositionAsync(location: Location): SourcePosition? { + return try { + getSourcePositionAsync(location) + } catch (_: NoDataException) { null } } diff --git a/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/KotlinPositionManager.kt b/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/KotlinPositionManager.kt index 43bd1d55f161..0bd305a7f41c 100644 --- a/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/KotlinPositionManager.kt +++ b/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/core/KotlinPositionManager.kt @@ -17,12 +17,10 @@ import com.intellij.debugger.jdi.VirtualMachineProxyImpl import com.intellij.debugger.requests.ClassPrepareRequestor import com.intellij.debugger.ui.breakpoints.Breakpoint import com.intellij.debugger.ui.impl.watch.StackFrameDescriptorImpl -import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.readAction import com.intellij.openapi.application.runReadAction import com.intellij.openapi.application.smartReadAction import com.intellij.openapi.fileTypes.FileType -import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.runBlockingMaybeCancellable import com.intellij.openapi.project.DumbService import com.intellij.openapi.project.Project @@ -125,23 +123,7 @@ class KotlinPositionManager(private val debugProcess: DebugProcess) : MultiReque return listOf(KotlinStackFrame(descriptor, visibleVariables)) } - override fun getSourcePositionAsync(location: Location?): CompletableFuture = - invokeCommandAsCompletableFuture { - getSourcePositionInternal(location) - } - - override fun getSourcePosition(location: Location?): SourcePosition? { - if (ApplicationManager.getApplication().isInternal - && ApplicationManager.getApplication().isReadAccessAllowed - && !ProgressManager.getInstance().hasProgressIndicator()) { - LOG.error("Call runBlocking from read action without indicator") - } - return runBlockingMaybeCancellable { - getSourcePositionInternal(location) - } - } - - private suspend fun getSourcePositionInternal(location: Location?): SourcePosition? { + override suspend fun getSourcePositionAsync(location: Location?): SourcePosition? { DebuggerManagerThreadImpl.assertIsManagerThread() if (location == null) throw NoDataException.INSTANCE diff --git a/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/stepping/smartStepInto/KotlinSmartStepTargetFiltererAdapter.kt b/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/stepping/smartStepInto/KotlinSmartStepTargetFiltererAdapter.kt index cc6b5c146ebd..7d2509f237eb 100644 --- a/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/stepping/smartStepInto/KotlinSmartStepTargetFiltererAdapter.kt +++ b/plugins/kotlin/jvm-debugger/core/src/org/jetbrains/kotlin/idea/debugger/stepping/smartStepInto/KotlinSmartStepTargetFiltererAdapter.kt @@ -1,11 +1,11 @@ // Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package org.jetbrains.kotlin.idea.debugger.stepping.smartStepInto -import com.intellij.debugger.PositionManager +import com.intellij.debugger.engine.PositionManagerAsync import com.intellij.openapi.application.readAction import com.intellij.psi.util.parentOfType import com.sun.jdi.Location -import org.jetbrains.kotlin.idea.debugger.base.util.safeGetSourcePosition +import org.jetbrains.kotlin.idea.debugger.base.util.safeGetSourcePositionAsync import org.jetbrains.kotlin.idea.debugger.base.util.safeMethod import org.jetbrains.kotlin.idea.debugger.core.getInlineFunctionAndArgumentVariablesToBordersMap import org.jetbrains.kotlin.idea.debugger.core.isInlineFunctionMarkerVariableName @@ -60,7 +60,7 @@ internal class KotlinSmartStepTargetFiltererAdapter( suspend fun visitTrace( targetFilterer: KotlinSmartStepTargetFilterer, - positionManager: PositionManager + positionManager: PositionManagerAsync ): Pair, List> { for (element in visitedTrace) { visitTraceElement(element, targetFilterer, positionManager) @@ -76,7 +76,7 @@ internal class KotlinSmartStepTargetFiltererAdapter( private suspend fun visitTraceElement( element: BytecodeTraceElement, targetFilterer: KotlinSmartStepTargetFilterer, - positionManager: PositionManager + positionManager: PositionManagerAsync ) { when (element) { is BytecodeTraceElement.InlineCall -> { @@ -122,7 +122,7 @@ private fun extractInlineCalls(location: Location): List = locat // Filter already visible variable to support smart-step-into while inside an inline function .filterNot { location.codeIndex() in it.bciRange } -private suspend fun getCalledInlineFunction(positionManager: PositionManager, location: Location): KtNamedFunction? { - val sourcePosition = positionManager.safeGetSourcePosition(location) ?: return null +private suspend fun getCalledInlineFunction(positionManager: PositionManagerAsync, location: Location): KtNamedFunction? { + val sourcePosition = positionManager.safeGetSourcePositionAsync(location) ?: return null return readAction { sourcePosition.elementAt?.parentOfType() } }