From 720ce629f067d7c1e4cd572b734164348c09807f Mon Sep 17 00:00:00 2001 From: Alexey Merkulov Date: Mon, 21 Oct 2024 16:09:54 +0200 Subject: [PATCH] [debugger] IDEA-361080 Add workaround to fix ObjectCollectedException during evaluating conditional breakpoints (cherry picked from commit 9922fc513d0c4e4d7daa4cc89d078fbb6b5a8f72) IJ-CR-147314 GitOrigin-RevId: ffc72ca98c6b98842cde50e1a9e76f88c89f9228 --- .../evaluation/EvaluationContextImpl.java | 12 +++++++++++ .../expression/ExpressionEvaluatorImpl.java | 20 +++++++++++++++++- .../debugger/impl/DebuggerUtilsImpl.java | 4 ++-- .../debugger/ui/breakpoints/Breakpoint.java | 10 ++++++++- .../debugger/engine/DebuggerUtils.java | 2 +- .../util/resources/misc/registry.properties | 2 ++ .../evaluate/KotlinEvaluatorBuilder.kt | 21 ++++++++++++++++++- 7 files changed, 65 insertions(+), 6 deletions(-) diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/EvaluationContextImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/EvaluationContextImpl.java index 1dbe7f07b2d2..16d3819f681e 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/EvaluationContextImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/EvaluationContextImpl.java @@ -28,6 +28,8 @@ public final class EvaluationContextImpl implements EvaluationContext { private @Nullable ThreadReferenceProxyImpl myPreferableThread = null; + private boolean myMayRetryEvaluation = false; + private EvaluationContextImpl(@NotNull SuspendContextImpl suspendContext, @Nullable StackFrameProxyImpl frameProxy, @NotNull DebuggerComputableValue thisObjectComputableValue) { @@ -177,4 +179,14 @@ public final class EvaluationContextImpl implements EvaluationContext { return "Evaluating requested on " + myPreferableThread + ", started on " + myThreadForEvaluation + " for " + mySuspendContext; } } + + @ApiStatus.Internal + public boolean isMayRetryEvaluation() { + return myMayRetryEvaluation; + } + + @ApiStatus.Internal + public void setMayRetryEvaluation(boolean mayRetryEvaluation) { + myMayRetryEvaluation = mayRetryEvaluation; + } } diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/ExpressionEvaluatorImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/ExpressionEvaluatorImpl.java index ec97c72f77af..135d6bb57f0c 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/ExpressionEvaluatorImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/evaluation/expression/ExpressionEvaluatorImpl.java @@ -16,6 +16,7 @@ package com.intellij.debugger.engine.evaluation.expression; import com.intellij.debugger.JavaDebuggerBundle; +import com.intellij.debugger.engine.DebuggerUtils; import com.intellij.debugger.engine.evaluation.EvaluateException; import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil; import com.intellij.debugger.engine.evaluation.EvaluationContext; @@ -48,7 +49,24 @@ public class ExpressionEvaluatorImpl implements ExpressionEvaluator { throw EvaluateExceptionUtil.NULL_STACK_FRAME; } - Object value = myEvaluator.evaluate((EvaluationContextImpl)context); + EvaluationContextImpl evaluationContextImpl = (EvaluationContextImpl)context; + + final Object value; + if (evaluationContextImpl.isMayRetryEvaluation()) { + value = DebuggerUtils.getInstance().processCollectibleValue( + () -> myEvaluator.evaluate(evaluationContextImpl), + r -> { + if (r instanceof Value v) { + evaluationContextImpl.keep(v); + } + return r; + }, + context + ); + } + else { + value = myEvaluator.evaluate(evaluationContextImpl); + } if (value != null && !(value instanceof Value)) { throw EvaluateExceptionUtil 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 30cebbd651e5..6087d9813096 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerUtilsImpl.java @@ -351,14 +351,14 @@ public final class DebuggerUtilsImpl extends DebuggerUtilsEx { } @Override - public R processCollectibleValue( + public R processCollectibleValue( @NotNull ThrowableComputable valueComputable, @NotNull Function processor, @NotNull EvaluationContext evaluationContext) throws EvaluateException { int retries = 3; while (true) { - T result = valueComputable.compute(); try { + T result = valueComputable.compute(); return processor.apply(result); } catch (ObjectCollectedException oce) { 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 28ad1c85c822..696fce5cc1e0 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 @@ -474,7 +474,15 @@ public abstract class Breakpoint

implements condition, this::createConditionCodeFragment)); }); - boolean evaluationResult = DebuggerUtilsEx.evaluateBoolean(evaluator, context); + boolean evaluationResult; + try { + if (Registry.is("debugger.retry.conditional.breakpoints", true)) { + context.setMayRetryEvaluation(true); + } + evaluationResult = DebuggerUtilsEx.evaluateBoolean(evaluator, context); + } finally { + context.setMayRetryEvaluation(false); + } JavaDebuggerEvaluatorStatisticsCollector.logEvaluationResult(myProject, evaluator, true, XEvaluationOrigin.BREAKPOINT_CONDITION); if (!evaluationResult) { return false; diff --git a/java/debugger/openapi/src/com/intellij/debugger/engine/DebuggerUtils.java b/java/debugger/openapi/src/com/intellij/debugger/engine/DebuggerUtils.java index 5daf72cbc6bf..c8239a322c57 100644 --- a/java/debugger/openapi/src/com/intellij/debugger/engine/DebuggerUtils.java +++ b/java/debugger/openapi/src/com/intellij/debugger/engine/DebuggerUtils.java @@ -143,7 +143,7 @@ public abstract class DebuggerUtils { } @ApiStatus.Internal - public abstract R processCollectibleValue( + public abstract R processCollectibleValue( @NotNull ThrowableComputable valueComputable, @NotNull Function processor, @NotNull EvaluationContext evaluationContext) throws EvaluateException; diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 040db60d42f5..35f2faf1ebbd 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -648,6 +648,8 @@ debugger.evaluate.single.threaded.timeout=1000 debugger.evaluate.single.threaded.timeout.description=Number of milliseconds to evaluate resuming only the current thread, then resume all threads debugger.new.invocation.watcher=true debugger.new.invocation.watcher.description=When resume all, take into account the debugger model state of contexts and theirs resumed threads +debugger.retry.conditional.breakpoints=true +debugger.retry.conditional.breakpoints.description=Retry to evaluate a condition in conditional breakpoints in case of ObjectCollectedException debugger.call.tracing=false debugger.call.tracing.arguments=true debugger.renderers.annotations=true diff --git a/plugins/kotlin/jvm-debugger/evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluatorBuilder.kt b/plugins/kotlin/jvm-debugger/evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluatorBuilder.kt index 6e794b7f6898..52dd204aeae5 100644 --- a/plugins/kotlin/jvm-debugger/evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluatorBuilder.kt +++ b/plugins/kotlin/jvm-debugger/evaluation/src/org/jetbrains/kotlin/idea/debugger/evaluate/KotlinEvaluatorBuilder.kt @@ -3,6 +3,7 @@ package org.jetbrains.kotlin.idea.debugger.evaluate import com.intellij.debugger.SourcePosition +import com.intellij.debugger.engine.DebuggerUtils import com.intellij.debugger.engine.evaluation.EvaluateException import com.intellij.debugger.engine.evaluation.EvaluateExceptionUtil import com.intellij.debugger.engine.evaluation.EvaluationContextImpl @@ -14,6 +15,7 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.project.IndexNotReadyException import com.intellij.openapi.project.Project +import com.intellij.openapi.util.ThrowableComputable import com.intellij.psi.PsiElement import com.intellij.psi.util.CachedValueProvider import com.intellij.psi.util.CachedValuesManager @@ -232,6 +234,7 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, private val sourcePositi compiledData: CompiledCodeFragmentData, classLoader: ClassLoaderReference? ): InterpreterResult { + val mayRetry = context.evaluationContext.isMayRetryEvaluation val mainClassBytecode = compiledData.mainClass.bytes val mainClassAsmNode = ClassNode().apply { ClassReader(mainClassBytecode).accept(this, 0) } val mainMethod = mainClassAsmNode.methods.first { it.isEvaluationEntryPoint } @@ -279,7 +282,23 @@ class KotlinEvaluator(val codeFragment: KtCodeFragment, private val sourcePositi return context.debugProcess.findClass(context.evaluationContext, classType.className, classLoader) } } - interpreterLoop(mainMethod, makeInitialFrame(mainMethod, args.map { it.asValue() }), eval) + + fun interpreterLoop() = interpreterLoop(mainMethod, makeInitialFrame(mainMethod, args.map { it.asValue() }), eval) + fun keepResult(result: InterpreterResult) { + val jdiObject = when (result) { + is ValueReturned -> result.result + is ExceptionThrown -> result.exception + else -> return + }.obj() as? ObjectReference? ?: return + context.evaluationContext.keep(jdiObject) + } + if (mayRetry) { + DebuggerUtils.getInstance().processCollectibleValue(object : ThrowableComputable { + override fun compute() = interpreterLoop() + }, { keepResult(it); it }, context.evaluationContext) + } else { + interpreterLoop() + } } }