[debugger] do not leak results values in helper

GitOrigin-RevId: 66073216977cc93dad14823761bbf1551cf1b990
This commit is contained in:
Egor Ushakov
2025-02-06 17:45:24 +00:00
committed by intellij-monorepo-bot
parent 720f8b51e2
commit 58bd28151b
3 changed files with 57 additions and 8 deletions
@@ -6,6 +6,7 @@ import com.intellij.debugger.engine.MethodInvokeUtils.getHelperExceptionStackTra
import com.intellij.debugger.engine.evaluation.EvaluateException
import com.intellij.debugger.engine.evaluation.EvaluationContextImpl
import com.intellij.debugger.engine.evaluation.expression.BoxingEvaluator
import com.intellij.debugger.impl.DebuggerUtilsAsync
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.impl.DebuggerUtilsEx.isVoid
import com.intellij.debugger.impl.DebuggerUtilsImpl
@@ -127,7 +128,14 @@ internal fun tryInvokeWithHelper(
}
try {
return InvocationResult(true, DebuggerUtilsImpl.invokeHelperMethod(evaluationContext, MethodInvoker::class.java, helperMethodName, invokerArgs, false))
var value = DebuggerUtilsImpl.invokeHelperMethod(evaluationContext, MethodInvoker::class.java, helperMethodName, invokerArgs, false)
if (value is ArrayReference) { // wrapped
val wrapper = value
value = value.getValue(0)
DebuggerUtilsAsync.disableCollection(value)
wrapper.setValue(0, null) // clear the reference TODO: make async
}
return InvocationResult(true, value)
}
catch (e: Exception) {
val helperExceptionStackTrace = getHelperExceptionStackTrace(evaluationContext, e)
@@ -1,4 +1,4 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
// Copyright 2000-2025 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.debugger.engine.DebuggerManagerThreadImpl;
@@ -475,6 +475,16 @@ public final class DebuggerUtilsAsync {
return toCompletableFuture(() -> virtualMachine.allClasses());
}
public static CompletableFuture<Void> disableCollection(Value value) {
if (value instanceof ObjectReference objectReference) {
if (value instanceof ObjectReferenceImpl objectReferenceImpl && isAsyncEnabled()) {
return objectReferenceImpl.disableCollectionAsync();
}
return toCompletableFuture(() -> objectReference.disableCollection());
}
return completedFuture(null);
}
/**
* Schedule future completion in a separate command with the same priority and suspend context (if available)
* as in the command being processed at the moment
@@ -5,11 +5,19 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.WrongMethodTypeException;
import java.lang.ref.SoftReference;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
@SuppressWarnings({"SSBasedInspection", "unused"})
public final class MethodInvoker {
// TODO: may leak objects here
static ThreadLocal<Object> keptValue = new ThreadLocal<>();
private static final ThreadLocal<List<Object>> keptValues = new ThreadLocal<List<Object>>() {
@Override
protected List<Object> initialValue() {
return new LinkedList<>(); // LinkedList for fast elements removal
}
};
public static Object invoke0(MethodHandles.Lookup lookup, Class<?> cls, Object obj, String nameAndDescriptor, ClassLoader loader)
throws Throwable {
@@ -211,17 +219,40 @@ public final class MethodInvoker {
}
Object result = method.invokeWithArguments(args);
keptValue.set(result);
return result;
return keepReference(result, false);
}
catch (WrongMethodTypeException | ClassCastException e) {
e.printStackTrace();
keptValue.set(e);
keepReference(e, true);
throw e;
}
catch (Throwable e) {
keptValue.set(e);
keepReference(e, true);
throw e;
}
}
private static Object keepReference(Object ref, boolean soft) {
List<Object> objects = keptValues.get();
// remove stale references first
Iterator<Object> iterator = objects.iterator();
while (iterator.hasNext()) {
Object object = iterator.next();
if (object instanceof Object[]) {
if (((Object[])object)[0] == null) {
iterator.remove();
}
}
else if (object instanceof SoftReference) {
if (((SoftReference<?>)object).get() == null) {
iterator.remove();
}
}
}
Object wrapper = soft ? new SoftReference<>(ref) : new Object[]{ref};
objects.add(wrapper);
return soft ? ref : wrapper;
}
}