diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java index ce9292b010cf..9b94571431dc 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessEvents.java @@ -49,7 +49,12 @@ import com.intellij.util.ConcurrencyUtil; import com.intellij.util.TimeoutUtil; import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.containers.ContainerUtil; +import com.intellij.xdebugger.DapMode; +import com.intellij.xdebugger.BreakpointErrorData; +import com.intellij.xdebugger.XBreakpointBehaviorPolicy.BreakpointErrorAction; import com.intellij.xdebugger.XDebugSession; +import com.intellij.xdebugger.XBreakpointBehaviorPolicy; +import com.intellij.xdebugger.XDebuggerManager; import com.intellij.xdebugger.breakpoints.XBreakpoint; import com.intellij.xdebugger.impl.XDebugSessionImpl; import com.intellij.xdebugger.impl.XDebuggerManagerImpl; @@ -766,13 +771,30 @@ public class DebugProcessEvents extends DebugProcessImpl { catch (final LocatableEventRequestor.EventProcessingException ex) { // stop timer here to prevent reporting dialog opened time endTimeNs = System.nanoTime(); - String exceptionMessage = ex.getMessage(); + String exceptionMessage = Objects.toString(ex.getMessage(), ""); if (LOG.isDebugEnabled()) { LOG.debug(exceptionMessage); } String title = ex.getTitle(); + XBreakpoint xBreakpoint = requestor instanceof Breakpoint breakpoint ? breakpoint.getXBreakpoint() : null; + XDebugSession xDebugSession = getSession().getXDebugSession(); + BreakpointErrorData errorData = new BreakpointErrorData(title, exceptionMessage, ex.getCause()); + BreakpointErrorAction policyAction = (xDebugSession == null || xBreakpoint == null) + ? BreakpointErrorAction.UNHANDLED + : XBreakpointBehaviorPolicy.doChooseBreakpointErrorAction(xDebugSession, xBreakpoint, errorData); + final String displayName = DebuggerUtilsImpl.getRequestorStringForUser(requestor); - requestHit = DebuggerUtilsImpl.askAboutPauseOnException(getProject(), displayName, exceptionMessage, title); + if (policyAction != BreakpointErrorAction.UNHANDLED) { + requestHit = policyAction == BreakpointErrorAction.PAUSE; + } + else { + requestHit = DebuggerUtilsImpl.askAboutPauseOnException(getProject(), displayName, exceptionMessage, title); + } + + if (xDebugSession != null && xBreakpoint != null) { + XDebuggerManagerImpl debuggerManager = (XDebuggerManagerImpl)XDebuggerManager.getInstance(xDebugSession.getProject()); + debuggerManager.getBreakpointManager().fireBreakpointError(xBreakpoint, xDebugSession, errorData); + } resumePreferred = !requestHit; } catch (VMDisconnectedException e) { diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/Breakpoint.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/Breakpoint.java index adee31cfbbfe..8396566571eb 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/Breakpoint.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/Breakpoint.java @@ -67,11 +67,17 @@ import com.intellij.util.ThreeState; import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.containers.ContainerUtil; import com.intellij.xdebugger.XExpression; +import com.intellij.xdebugger.BreakpointErrorData; +import com.intellij.xdebugger.DapMode; +import com.intellij.xdebugger.XDebugSession; +import com.intellij.xdebugger.XDebuggerManager; +import com.intellij.xdebugger.XExpression; import com.intellij.xdebugger.breakpoints.SuspendPolicy; import com.intellij.xdebugger.breakpoints.XBreakpoint; import com.intellij.xdebugger.breakpoints.XLineBreakpoint; import com.intellij.xdebugger.impl.XDebugSessionImpl; import com.intellij.xdebugger.impl.XDebuggerHistoryManager; +import com.intellij.xdebugger.impl.XDebuggerManagerImpl; import com.intellij.xdebugger.impl.ui.DebuggerUIUtil; import com.intellij.xdebugger.impl.breakpoints.XBreakpointBase; import com.intellij.xdebugger.impl.breakpoints.XBreakpointUtil; @@ -414,9 +420,20 @@ public abstract class Breakpoint

implements } catch (EvaluateException e) { JavaDebuggerEvaluatorStatisticsCollector.logEvaluationResult(myProject, evaluator, false, XEvaluationOrigin.BREAKPOINT_LOG); - buf.append(JavaDebuggerBundle.message("error.unable.to.evaluate.expression")) - .append(" \"").append(logMessage).append("\"") - .append(" : ").append(e.getMessage()); + String errorMessage = JavaDebuggerBundle.message("error.unable.to.evaluate.expression") + + " \"" + logMessage + "\"" + + " : " + e.getMessage(); + buf.append(errorMessage); + + XDebugSession session = debugProcess.getSession().getXDebugSession(); + if (session != null) { + XDebuggerManagerImpl debuggerManager = (XDebuggerManagerImpl)XDebuggerManager.getInstance(myProject); + debuggerManager.getBreakpointManager().fireBreakpointError(getXBreakpoint(), + session, + new BreakpointErrorData(JavaDebuggerBundle.message("title.error.evaluating.breakpoint.action"), + errorMessage, + e)); + } } buf.append("\n"); } diff --git a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointManager.java b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointManager.java index 396915c1020b..da1745b713fe 100644 --- a/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointManager.java +++ b/java/debugger/impl/src/com/intellij/debugger/ui/breakpoints/BreakpointManager.java @@ -47,6 +47,7 @@ import com.intellij.util.concurrency.AppExecutorUtil; import com.intellij.util.concurrency.ThreadingAssertions; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.messages.MessageBusConnection; +import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XDebuggerManager; import com.intellij.xdebugger.XDebuggerUtil; import com.intellij.xdebugger.XSourcePosition; @@ -694,5 +695,10 @@ public class BreakpointManager { void multicastLogMessage(Breakpoint breakpoint, String message, DebugProcessImpl debugProcess) { myLogMessageDispatcher.getMulticaster().onLogMessage(breakpoint, message, debugProcess); + XDebugSession session = debugProcess.getSession().getXDebugSession(); + XBreakpointManagerImpl manager = XDebuggerManager.getInstance(myProject).getBreakpointManager() instanceof XBreakpointManagerImpl managerImpl ? managerImpl : null; + if (session != null && manager != null) { + manager.fireBreakpointLogMessage(breakpoint.getXBreakpoint(), session, message); + } } } diff --git a/platform/xdebugger-api/api-dump-experimental.txt b/platform/xdebugger-api/api-dump-experimental.txt index 865c3f3c3940..4556031a5bf8 100644 --- a/platform/xdebugger-api/api-dump-experimental.txt +++ b/platform/xdebugger-api/api-dump-experimental.txt @@ -1,7 +1,33 @@ +*f:com.intellij.xdebugger.BreakpointErrorData +- (java.lang.String,java.lang.String,java.lang.Throwable):V +- f:component1():java.lang.String +- f:component2():java.lang.String +- f:component3():java.lang.Throwable +- f:copy(java.lang.String,java.lang.String,java.lang.Throwable):com.intellij.xdebugger.BreakpointErrorData +- bs:copy$default(com.intellij.xdebugger.BreakpointErrorData,java.lang.String,java.lang.String,java.lang.Throwable,I,java.lang.Object):com.intellij.xdebugger.BreakpointErrorData +- equals(java.lang.Object):Z +- f:getCause():java.lang.Throwable +- f:getMessage():java.lang.String +- f:getTitle():java.lang.String +- hashCode():I *:com.intellij.xdebugger.XAlternativeSourceHandler - a:getAlternativePosition(com.intellij.xdebugger.frame.XStackFrame):com.intellij.xdebugger.XSourcePosition - a:getAlternativeSourceKindState():kotlinx.coroutines.flow.StateFlow - a:isAlternativeSourceKindPreferred(com.intellij.xdebugger.frame.XSuspendContext):Z +*:com.intellij.xdebugger.XBreakpointBehaviorPolicy +- *sf:Companion:com.intellij.xdebugger.XBreakpointBehaviorPolicy$Companion +- a:chooseBreakpointErrorAction(com.intellij.xdebugger.XDebugSession,com.intellij.xdebugger.breakpoints.XBreakpoint,com.intellij.xdebugger.BreakpointErrorData):com.intellij.xdebugger.XBreakpointBehaviorPolicy$BreakpointErrorAction +- s:doChooseBreakpointErrorAction(com.intellij.xdebugger.XDebugSession,com.intellij.xdebugger.breakpoints.XBreakpoint,com.intellij.xdebugger.BreakpointErrorData):com.intellij.xdebugger.XBreakpointBehaviorPolicy$BreakpointErrorAction +*e:com.intellij.xdebugger.XBreakpointBehaviorPolicy$BreakpointErrorAction +- java.lang.Enum +- sf:PAUSE:com.intellij.xdebugger.XBreakpointBehaviorPolicy$BreakpointErrorAction +- sf:RESUME:com.intellij.xdebugger.XBreakpointBehaviorPolicy$BreakpointErrorAction +- sf:UNHANDLED:com.intellij.xdebugger.XBreakpointBehaviorPolicy$BreakpointErrorAction +- s:getEntries():kotlin.enums.EnumEntries +- s:valueOf(java.lang.String):com.intellij.xdebugger.XBreakpointBehaviorPolicy$BreakpointErrorAction +- s:values():com.intellij.xdebugger.XBreakpointBehaviorPolicy$BreakpointErrorAction[] +*f:com.intellij.xdebugger.XBreakpointBehaviorPolicy$Companion +- f:doChooseBreakpointErrorAction(com.intellij.xdebugger.XDebugSession,com.intellij.xdebugger.breakpoints.XBreakpoint,com.intellij.xdebugger.BreakpointErrorData):com.intellij.xdebugger.XBreakpointBehaviorPolicy$BreakpointErrorAction a:com.intellij.xdebugger.XDebugProcess - *:dependsOnPlugin(com.intellij.ide.plugins.IdeaPluginDescriptor):Z - *:getAlternativeSourceHandler():com.intellij.xdebugger.XAlternativeSourceHandler @@ -25,6 +51,10 @@ com.intellij.xdebugger.XDebugSessionListener - a:areInlineBreakpointsDisabled(com.intellij.openapi.vfs.VirtualFile):Z *f:com.intellij.xdebugger.breakpoints.InlineBreakpointsDisabler$Companion - f:getEP():com.intellij.openapi.extensions.ExtensionPointName +com.intellij.xdebugger.breakpoints.XBreakpointListener +- java.util.EventListener +- *:breakpointError(com.intellij.xdebugger.breakpoints.XBreakpoint,com.intellij.xdebugger.XDebugSession,com.intellij.xdebugger.BreakpointErrorData):V +- *:breakpointLogMessage(com.intellij.xdebugger.breakpoints.XBreakpoint,com.intellij.xdebugger.XDebugSession,java.lang.String):V a:com.intellij.xdebugger.evaluation.XDebuggerEditorsProvider - *:isEvaluateExpressionFieldEnabled():Z *:com.intellij.xdebugger.frame.XDropFrameHandler diff --git a/platform/xdebugger-api/resources/intellij.platform.debugger.content.xml b/platform/xdebugger-api/resources/intellij.platform.debugger.content.xml index 0d8d19c1d138..0985101a0dc2 100644 --- a/platform/xdebugger-api/resources/intellij.platform.debugger.content.xml +++ b/platform/xdebugger-api/resources/intellij.platform.debugger.content.xml @@ -24,5 +24,7 @@ dynamic="false"/> + diff --git a/platform/xdebugger-api/src/com/intellij/xdebugger/BreakpointErrorData.kt b/platform/xdebugger-api/src/com/intellij/xdebugger/BreakpointErrorData.kt new file mode 100644 index 000000000000..5a92a00c5ffb --- /dev/null +++ b/platform/xdebugger-api/src/com/intellij/xdebugger/BreakpointErrorData.kt @@ -0,0 +1,13 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.xdebugger + +import com.intellij.openapi.util.NlsContexts +import org.jetbrains.annotations.ApiStatus + +@ApiStatus.Experimental +data class BreakpointErrorData( + @get:NlsContexts.DialogTitle + val title: String, + val message: String, + val cause: Throwable? +) \ No newline at end of file diff --git a/platform/xdebugger-api/src/com/intellij/xdebugger/XBreakpointBehaviorPolicy.kt b/platform/xdebugger-api/src/com/intellij/xdebugger/XBreakpointBehaviorPolicy.kt new file mode 100644 index 000000000000..6bf7117ef8ae --- /dev/null +++ b/platform/xdebugger-api/src/com/intellij/xdebugger/XBreakpointBehaviorPolicy.kt @@ -0,0 +1,79 @@ +// Copyright 2000-2026 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.intellij.xdebugger + +import com.intellij.openapi.diagnostic.logger +import com.intellij.openapi.extensions.ExtensionPointName +import com.intellij.xdebugger.breakpoints.XBreakpoint +import org.jetbrains.annotations.ApiStatus + +private val logger = logger() + +/** + * Defines a policy to handle breakpoint errors during a debugging session. + * + * This interface allows the customization of behavior when a breakpoint error occurs, + * enabling extension points to determine the appropriate action to take. + * + * Currently supported only for JVM debugger. + */ +@ApiStatus.Experimental +interface XBreakpointBehaviorPolicy { + companion object { + private val EP_NAME: ExtensionPointName = + ExtensionPointName.create("com.intellij.xdebugger.breakpointBehaviorPolicy") + + /** + * Determines the appropriate action to take for a breakpoint error in a debugging session. + * + * This method evaluates all available breakpoint behavior policies and applies the first + * non-default action provided. If no policy-specific action is defined, a default action + * of `UNHANDLED` is returned. + * + * @param session the debugging session in which the breakpoint error occurred + * @param breakpoint the specific breakpoint associated with the error + * @param errorData the error details, including its title, message, and optional cause + * @return the chosen action to handle the breakpoint error, defaults to `UNHANDLED` if no policies apply + */ + @JvmStatic + fun doChooseBreakpointErrorAction( + session: XDebugSession, + breakpoint: XBreakpoint<*>, + errorData: BreakpointErrorData, + ): BreakpointErrorAction { + for (policy in EP_NAME.extensionList) { + try { + val action = policy.chooseBreakpointErrorAction(session, breakpoint, errorData) + if (action != BreakpointErrorAction.UNHANDLED) { + return action + } + } + catch (t: Throwable) { + logger.error("Failed to evaluate breakpoint behavior policy: " + policy.javaClass.getName(), t) + } + } + return BreakpointErrorAction.UNHANDLED + } + } + + enum class BreakpointErrorAction { + UNHANDLED, + PAUSE, + RESUME + } + + /** + * Determines the appropriate action to take for a breakpoint error in a debugging session, + * e.g. when a conditional expression is incorrect. + * + * An implementation may choose to pause the session, resume execution, or keep the default behavior. + * + * If all such methods return [BreakpointErrorAction.UNHANDLED] in the EP chain, the dialog will be shown to a user. + * + * For any unrelared breakpoints [BreakpointErrorAction.UNHANDLED] must be returned. + */ + fun chooseBreakpointErrorAction( + session: XDebugSession, + breakpoint: XBreakpoint<*>, + error: BreakpointErrorData, + ): BreakpointErrorAction +} diff --git a/platform/xdebugger-api/src/com/intellij/xdebugger/breakpoints/XBreakpointListener.java b/platform/xdebugger-api/src/com/intellij/xdebugger/breakpoints/XBreakpointListener.java index 70cf98e16d03..ee7ddb8b4554 100644 --- a/platform/xdebugger-api/src/com/intellij/xdebugger/breakpoints/XBreakpointListener.java +++ b/platform/xdebugger-api/src/com/intellij/xdebugger/breakpoints/XBreakpointListener.java @@ -2,7 +2,9 @@ package com.intellij.xdebugger.breakpoints; import com.intellij.util.messages.Topic; +import com.intellij.xdebugger.BreakpointErrorData; import com.intellij.xdebugger.XDebugSession; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,4 +26,22 @@ public interface XBreakpointListener> extends EventList default void breakpointPresentationUpdated(@NotNull B breakpoint, @Nullable XDebugSession session) { } + + /** + * Called when a breakpoint error is occurred, e.g., when a condition expression is invalid + *

+ * Currently supported only for JVM debugger + */ + @ApiStatus.Experimental + default void breakpointError(@NotNull B breakpoint, @NotNull XDebugSession session, @NotNull BreakpointErrorData error) { + } + + /** + * Called when a breakpoint with a log expression is hit + *

+ * Currently supported only for JVM debugger + */ + @ApiStatus.Experimental + default void breakpointLogMessage(@NotNull B breakpoint, @NotNull XDebugSession session, @NotNull String message) { + } } diff --git a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointManagerImpl.java b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointManagerImpl.java index 474c1220b858..4225dc16d2e0 100644 --- a/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointManagerImpl.java +++ b/platform/xdebugger-impl/src/com/intellij/xdebugger/impl/breakpoints/XBreakpointManagerImpl.java @@ -31,6 +31,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.MultiMap; import com.intellij.util.messages.SimpleMessageBusConnection; import com.intellij.util.ui.UIUtil; +import com.intellij.xdebugger.BreakpointErrorData; import com.intellij.xdebugger.SplitDebuggerMode; import com.intellij.xdebugger.XDebugSession; import com.intellij.xdebugger.XDebuggerUtil; @@ -286,6 +287,22 @@ public final class XBreakpointManagerImpl implements XBreakpointManager { } } + public void fireBreakpointError(@NotNull XBreakpoint breakpoint, + @NotNull XDebugSession session, + @NotNull BreakpointErrorData error) { + if (isRegistered(breakpoint)) { + sendBreakpointEvent(breakpoint.getType(), listener -> listener.breakpointError(breakpoint, session, error)); + } + } + + public void fireBreakpointLogMessage(@NotNull XBreakpoint breakpoint, + @NotNull XDebugSession session, + @NotNull String message) { + if (isRegistered(breakpoint)) { + sendBreakpointEvent(breakpoint.getType(), listener -> listener.breakpointLogMessage(breakpoint, session, message)); + } + } + @Override public void removeBreakpoint(final @NotNull XBreakpoint breakpoint) { removeBreakpoints(List.of(breakpoint));