[debugger] IDEA-361080 Add workaround to fix ObjectCollectedException during evaluating conditional breakpoints

(cherry picked from commit 9922fc513d0c4e4d7daa4cc89d078fbb6b5a8f72)

IJ-CR-147314

GitOrigin-RevId: ffc72ca98c6b98842cde50e1a9e76f88c89f9228
This commit is contained in:
Alexey Merkulov
2024-10-21 16:09:54 +02:00
committed by intellij-monorepo-bot
parent 5d73c19f18
commit 720ce629f0
7 changed files with 65 additions and 6 deletions

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -351,14 +351,14 @@ public final class DebuggerUtilsImpl extends DebuggerUtilsEx {
}
@Override
public <R, T extends Value> R processCollectibleValue(
public <R, T> R processCollectibleValue(
@NotNull ThrowableComputable<? extends T, ? extends EvaluateException> valueComputable,
@NotNull Function<? super T, ? extends R> 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) {

View File

@@ -474,7 +474,15 @@ public abstract class Breakpoint<P extends JavaBreakpointProperties> 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;

View File

@@ -143,7 +143,7 @@ public abstract class DebuggerUtils {
}
@ApiStatus.Internal
public abstract <R, T extends Value> R processCollectibleValue(
public abstract <R, T> R processCollectibleValue(
@NotNull ThrowableComputable<? extends T, ? extends EvaluateException> valueComputable,
@NotNull Function<? super T, ? extends R> processor,
@NotNull EvaluationContext evaluationContext) throws EvaluateException;

View File

@@ -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

View File

@@ -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<InterpreterResult, EvaluateException> {
override fun compute() = interpreterLoop()
}, { keepResult(it); it }, context.evaluationContext)
} else {
interpreterLoop()
}
}
}