mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 03:21:12 +07:00
IDEA-323195 "Object has been collected" error during breakpoint condition evaluation - method invoker helper prototype
GitOrigin-RevId: 0c00a502ca662d95e7ef7cb1274cf2031793c885
This commit is contained in:
committed by
intellij-monorepo-bot
parent
89a6bb2175
commit
eac3151e29
@@ -6,6 +6,7 @@ import com.intellij.ReviseWhenPortedToJDK;
|
||||
import com.intellij.debugger.*;
|
||||
import com.intellij.debugger.actions.DebuggerAction;
|
||||
import com.intellij.debugger.engine.evaluation.*;
|
||||
import com.intellij.debugger.engine.evaluation.expression.BoxingEvaluator;
|
||||
import com.intellij.debugger.engine.evaluation.expression.RetryEvaluationException;
|
||||
import com.intellij.debugger.engine.events.DebuggerCommandImpl;
|
||||
import com.intellij.debugger.engine.events.DebuggerContextCommandImpl;
|
||||
@@ -63,14 +64,13 @@ import com.intellij.openapi.wm.impl.status.StatusBarUtil;
|
||||
import com.intellij.psi.CommonClassNames;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiManager;
|
||||
import com.intellij.psi.impl.PsiJavaParserFacadeImpl;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.rt.debugger.MethodInvoker;
|
||||
import com.intellij.ui.awt.AnchoredPoint;
|
||||
import com.intellij.ui.classFilter.ClassFilter;
|
||||
import com.intellij.ui.classFilter.DebuggerClassFilterProvider;
|
||||
import com.intellij.util.Alarm;
|
||||
import com.intellij.util.EventDispatcher;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.intellij.util.SingleEdtTaskScheduler;
|
||||
import com.intellij.util.*;
|
||||
import com.intellij.util.concurrency.EdtScheduler;
|
||||
import com.intellij.util.concurrency.Semaphore;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
@@ -1487,16 +1487,86 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
|
||||
@NotNull List<? extends Value> args,
|
||||
final int invocationOptions,
|
||||
boolean internalEvaluate) throws EvaluateException {
|
||||
return new InvokeCommand<>(method, args, (EvaluationContextImpl)evaluationContext) {
|
||||
@Override
|
||||
protected Value invokeMethod(ThreadReference thread, int invokePolicy, Method method, List<? extends Value> args)
|
||||
throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Invoking " + objRef.type().name() + "." + method.name());
|
||||
if (!internalEvaluate && shouldInvokeWithHandler(method, invocationOptions)) {
|
||||
return invokeWithHelper(method.declaringType(), objRef, method, args, (EvaluationContextImpl)evaluationContext);
|
||||
}
|
||||
else {
|
||||
return new InvokeCommand<>(method, args, (EvaluationContextImpl)evaluationContext) {
|
||||
@Override
|
||||
protected Value invokeMethod(ThreadReference thread, int invokePolicy, Method method, List<? extends Value> args)
|
||||
throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Invoking " + objRef.type().name() + "." + method.name());
|
||||
}
|
||||
return objRef.invokeMethod(thread, method, args, invokePolicy | invocationOptions);
|
||||
}
|
||||
return objRef.invokeMethod(thread, method, args, invokePolicy | invocationOptions);
|
||||
}.start(internalEvaluate);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean shouldInvokeWithHandler(@NotNull Method method, int invocationOptions) {
|
||||
return Registry.is("debugger.evaluate.method.helper") &&
|
||||
!BitUtil.isSet(invocationOptions, ObjectReference.INVOKE_NONVIRTUAL) &&
|
||||
!DebuggerUtils.isPrimitiveType(method.returnTypeName()) &&
|
||||
!DebuggerUtilsEx.isVoid(method) &&
|
||||
method.isPublic();
|
||||
}
|
||||
|
||||
private static @Nullable Value invokeWithHelper(@NotNull ReferenceType type,
|
||||
@Nullable ObjectReference objRef,
|
||||
@NotNull Method method,
|
||||
@NotNull List<? extends Value> args,
|
||||
@NotNull EvaluationContextImpl evaluationContext) throws EvaluateException {
|
||||
try {
|
||||
DebugProcessImpl debugProcess = evaluationContext.getDebugProcess();
|
||||
VirtualMachineProxyImpl virtualMachineProxy = debugProcess.getVirtualMachineProxy();
|
||||
|
||||
ArrayList<Value> invokerArgs = new ArrayList<>();
|
||||
invokerArgs.add(type.classObject()); // class
|
||||
invokerArgs.add(objRef); // object
|
||||
invokerArgs.add(DebuggerUtilsEx.mirrorOfString(method.name(), virtualMachineProxy, evaluationContext)); // method name
|
||||
// argument types
|
||||
List<ClassObjectReference> types = mapArgumentTypes(method, evaluationContext);
|
||||
ArrayType objectArrayClass = (ArrayType)debugProcess.findClass(
|
||||
evaluationContext,
|
||||
CommonClassNames.JAVA_LANG_OBJECT + "[]",
|
||||
evaluationContext.getClassLoader());
|
||||
ArrayReference arrayTypes = DebuggerUtilsEx.mirrorOfArray(objectArrayClass, types, evaluationContext);
|
||||
invokerArgs.add(arrayTypes);
|
||||
// argument values
|
||||
List<Value> boxedArgs = new ArrayList<>(args.size());
|
||||
for (Value arg : args) {
|
||||
boxedArgs.add((Value)BoxingEvaluator.box(arg, evaluationContext));
|
||||
}
|
||||
}.start(internalEvaluate);
|
||||
if (method.isVarArgs() /*|| (method.isBridge() && ContainerUtil.getLastItem(method.argumentTypes()) instanceof ArrayType)*/) {
|
||||
MethodImpl.handleVarArgs(method, boxedArgs);
|
||||
}
|
||||
ArrayReference arrayArgs = DebuggerUtilsEx.mirrorOfArray(objectArrayClass, boxedArgs, evaluationContext);
|
||||
invokerArgs.add(arrayArgs);
|
||||
|
||||
return DebuggerUtilsImpl.invokeHelperMethod(evaluationContext, MethodInvoker.class, "invoke", invokerArgs, false);
|
||||
}
|
||||
catch (ClassNotLoadedException | InvalidTypeException e) {
|
||||
throw new EvaluateException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ClassObjectReference> mapArgumentTypes(@NotNull Method method, @NotNull EvaluationContextImpl evaluationContext)
|
||||
throws ClassNotLoadedException, EvaluateException {
|
||||
List<ClassObjectReference> res = new ArrayList<>();
|
||||
for (Type type : method.argumentTypes()) {
|
||||
if (type instanceof PrimitiveType) {
|
||||
String boxedName = PsiJavaParserFacadeImpl.getPrimitiveType(type.name()).getBoxedTypeName();
|
||||
ReferenceType boxedClass =
|
||||
evaluationContext.getDebugProcess().findClass(evaluationContext, boxedName, method.declaringType().classLoader());
|
||||
Value primitiveClass = boxedClass.getValue(boxedClass.fieldByName("TYPE"));
|
||||
res.add((ClassObjectReference)primitiveClass);
|
||||
}
|
||||
else {
|
||||
res.add(((ReferenceType)type).classObject());
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private static ThreadReferenceProxy getEvaluationThread(final EvaluationContext evaluationContext) throws EvaluateException {
|
||||
@@ -1535,41 +1605,51 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
|
||||
@NotNull List<? extends Value> args,
|
||||
int extraInvocationOptions,
|
||||
boolean internalEvaluate) throws EvaluateException {
|
||||
return new InvokeCommand<>(method, args, (EvaluationContextImpl)evaluationContext) {
|
||||
@Override
|
||||
protected Value invokeMethod(ThreadReference thread, int invokePolicy, Method method, List<? extends Value> args)
|
||||
throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Invoking " + classType.name() + "." + method.name());
|
||||
if (!internalEvaluate && shouldInvokeWithHandler(method, extraInvocationOptions)) {
|
||||
return invokeWithHelper(classType, null, method, args, (EvaluationContextImpl)evaluationContext);
|
||||
}
|
||||
else {
|
||||
return new InvokeCommand<>(method, args, (EvaluationContextImpl)evaluationContext) {
|
||||
@Override
|
||||
protected Value invokeMethod(ThreadReference thread, int invokePolicy, Method method, List<? extends Value> args)
|
||||
throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Invoking " + classType.name() + "." + method.name());
|
||||
}
|
||||
return classType.invokeMethod(thread, method, args, invokePolicy | extraInvocationOptions);
|
||||
}
|
||||
return classType.invokeMethod(thread, method, args, invokePolicy | extraInvocationOptions);
|
||||
}
|
||||
}.start(internalEvaluate);
|
||||
}.start(internalEvaluate);
|
||||
}
|
||||
}
|
||||
|
||||
public Value invokeMethod(EvaluationContext evaluationContext,
|
||||
InterfaceType interfaceType,
|
||||
Method method,
|
||||
List<? extends Value> args) throws EvaluateException {
|
||||
return new InvokeCommand<>(method, args, (EvaluationContextImpl)evaluationContext) {
|
||||
@Override
|
||||
protected Value invokeMethod(ThreadReference thread, int invokePolicy, Method method, List<? extends Value> args)
|
||||
throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Invoking " + interfaceType.name() + "." + method.name());
|
||||
}
|
||||
if (shouldInvokeWithHandler(method, 0)) {
|
||||
return invokeWithHelper(interfaceType, null, method, args, (EvaluationContextImpl)evaluationContext);
|
||||
}
|
||||
else {
|
||||
return new InvokeCommand<>(method, args, (EvaluationContextImpl)evaluationContext) {
|
||||
@Override
|
||||
protected Value invokeMethod(ThreadReference thread, int invokePolicy, Method method, List<? extends Value> args)
|
||||
throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException {
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Invoking " + interfaceType.name() + "." + method.name());
|
||||
}
|
||||
|
||||
try {
|
||||
return interfaceType.invokeMethod(thread, method, args, invokePolicy);
|
||||
try {
|
||||
return interfaceType.invokeMethod(thread, method, args, invokePolicy);
|
||||
}
|
||||
catch (LinkageError e) {
|
||||
throw new IllegalStateException("Interface method invocation is not supported in JVM " +
|
||||
SystemInfo.JAVA_VERSION +
|
||||
". Use JVM 1.8.0_45 or higher to run " +
|
||||
ApplicationNamesInfo.getInstance().getFullProductName());
|
||||
}
|
||||
}
|
||||
catch (LinkageError e) {
|
||||
throw new IllegalStateException("Interface method invocation is not supported in JVM " +
|
||||
SystemInfo.JAVA_VERSION +
|
||||
". Use JVM 1.8.0_45 or higher to run " +
|
||||
ApplicationNamesInfo.getInstance().getFullProductName());
|
||||
}
|
||||
}
|
||||
}.start(false);
|
||||
}.start(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1441,7 +1441,10 @@ public final class EvaluatorBuilderImpl implements EvaluatorBuilder {
|
||||
public void visitMethodReferenceExpression(@NotNull PsiMethodReferenceExpression expression) {
|
||||
PsiElement qualifier = expression.getQualifier();
|
||||
PsiType interfaceType = expression.getFunctionalInterfaceType();
|
||||
if (!Registry.is("debugger.compiling.evaluator.method.refs") && interfaceType != null && qualifier != null) {
|
||||
if (!Registry.is("debugger.compiling.evaluator.method.refs") &&
|
||||
!Registry.is("debugger.evaluate.method.helper") &&
|
||||
interfaceType != null &&
|
||||
qualifier != null) {
|
||||
String code = null;
|
||||
try {
|
||||
PsiElement resolved = expression.resolve();
|
||||
|
||||
@@ -560,20 +560,31 @@ public final class DebuggerUtilsImpl extends DebuggerUtilsEx {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Value invokeHelperMethod(EvaluationContextImpl evaluationContext, Class<?> cls, String methodName, List<Value> arguments)
|
||||
throws EvaluateException {
|
||||
public static Value invokeHelperMethod(EvaluationContextImpl evaluationContext,
|
||||
Class<?> cls,
|
||||
String methodName,
|
||||
List<Value> arguments,
|
||||
boolean keepResult) throws EvaluateException {
|
||||
ClassType helperClass = ClassLoadingUtils.getHelperClass(cls, evaluationContext);
|
||||
if (helperClass != null) {
|
||||
Method method = findMethod(helperClass, methodName, null);
|
||||
if (method != null) {
|
||||
DebugProcessImpl debugProcess = evaluationContext.getDebugProcess();
|
||||
return evaluationContext.computeAndKeep(
|
||||
() -> debugProcess.invokeMethod(evaluationContext, helperClass, method, arguments, MethodImpl.SKIP_ASSIGNABLE_CHECK, true));
|
||||
ThrowableComputable<Value, EvaluateException> invoker =
|
||||
() -> debugProcess.invokeMethod(evaluationContext, helperClass, method, arguments, MethodImpl.SKIP_ASSIGNABLE_CHECK, true);
|
||||
return keepResult ? evaluationContext.computeAndKeep(invoker) : invoker.compute();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Value invokeHelperMethod(EvaluationContextImpl evaluationContext,
|
||||
Class<?> cls,
|
||||
String methodName,
|
||||
List<Value> arguments) throws EvaluateException {
|
||||
return invokeHelperMethod(evaluationContext, cls, methodName, arguments, true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static String getExceptionText(EvaluationContextImpl evaluationContext, @NotNull ObjectReference exceptionObject)
|
||||
throws EvaluateException {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
// 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.rt.debugger;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public final class MethodInvoker {
|
||||
// TODO: may leak objects here
|
||||
static ThreadLocal<Object> returnValue = new ThreadLocal<>();
|
||||
|
||||
public static Object invoke(Class cls, Object obj, String name, Class[] parameterTypes, Object[] args)
|
||||
throws Throwable {
|
||||
ArrayList<Method> methods = new ArrayList<>();
|
||||
// TODO: better collect methods lazily
|
||||
addMatchingMethods(methods, cls, name, parameterTypes);
|
||||
for (Method method : methods) {
|
||||
try {
|
||||
method.setAccessible(true);
|
||||
Object res = method.invoke(obj, args);
|
||||
returnValue.set(res); // to avoid gc for the result object
|
||||
return res;
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
throw e.getCause();
|
||||
}
|
||||
catch (IllegalAccessException | SecurityException e) {
|
||||
// try the next method
|
||||
}
|
||||
catch (Exception e) {
|
||||
// InaccessibleObjectException appeared in jdk 9
|
||||
if (!"java.lang.reflect.InaccessibleObjectException".equals(e.getClass().getName())) {
|
||||
throw e;
|
||||
}
|
||||
// try the next method
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Unable to execute the method " + name);
|
||||
}
|
||||
|
||||
//TODO: avoid recursion
|
||||
private static void addMatchingMethods(List<Method> methods, Class cls, String name, Class[] parameterTypes) {
|
||||
try {
|
||||
methods.add(cls.getDeclaredMethod(name, parameterTypes));
|
||||
}
|
||||
catch (NoSuchMethodException ignored) {
|
||||
}
|
||||
Class superclass = cls.getSuperclass();
|
||||
if (superclass != null) {
|
||||
addMatchingMethods(methods, superclass, name, parameterTypes);
|
||||
}
|
||||
for (Class anInterface : cls.getInterfaces()) {
|
||||
addMatchingMethods(methods, anInterface, name, parameterTypes);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -644,6 +644,8 @@ debugger.variablesView.rss.depth=4
|
||||
debugger.variablesView.rss.depth.description=Depth of search in variables view
|
||||
debugger.attach.to.process.action=false
|
||||
debugger.sa.jdwp.debug=false
|
||||
debugger.evaluate.method.helper=false
|
||||
debugger.evaluate.method.helper.description=Use helper to invoke methods avoiding the immediate return value collection
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user