diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java index 9b9ed7551444..aab43410977f 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java @@ -1506,9 +1506,9 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb private static boolean shouldInvokeWithHandler(@NotNull Method method, int invocationOptions) { return Registry.is("debugger.evaluate.method.helper") && - !BitUtil.isSet(invocationOptions, ObjectReference.INVOKE_NONVIRTUAL) && + !BitUtil.isSet(invocationOptions, ObjectReference.INVOKE_NONVIRTUAL) && // TODO: support !DebuggerUtils.isPrimitiveType(method.returnTypeName()) && - !DebuggerUtilsEx.isVoid(method) && + (!DebuggerUtilsEx.isVoid(method) || method.isConstructor()) && !"clone".equals(method.name()); } @@ -1517,38 +1517,34 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb @NotNull Method method, @NotNull List args, @NotNull EvaluationContextImpl evaluationContext) throws EvaluateException { - try { - DebugProcessImpl debugProcess = evaluationContext.getDebugProcess(); - VirtualMachineProxyImpl virtualMachineProxy = debugProcess.getVirtualMachineProxy(); + DebugProcessImpl debugProcess = evaluationContext.getDebugProcess(); + VirtualMachineProxyImpl virtualMachineProxy = debugProcess.getVirtualMachineProxy(); + ArrayList invokerArgs = new ArrayList<>(); - ArrayList 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 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 boxedArgs = new ArrayList<>(args.size()); - for (Value arg : args) { - boxedArgs.add((Value)BoxingEvaluator.box(arg, evaluationContext)); - } - 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); + ReferenceType lookupClass = + debugProcess.findClass(evaluationContext, "java.lang.invoke.MethodHandles$Lookup", evaluationContext.getClassLoader()); + ObjectReference implLookup = (ObjectReference)lookupClass.getValue(lookupClass.fieldByName("IMPL_LOOKUP")); - return DebuggerUtilsImpl.invokeHelperMethod(evaluationContext, MethodInvoker.class, "invoke", invokerArgs, false); - } - catch (ClassNotLoadedException | InvalidTypeException e) { - throw new EvaluateException(e.getMessage(), e); + invokerArgs.add(implLookup); // lookup + invokerArgs.add(type.classObject()); // class + invokerArgs.add(objRef); // object + invokerArgs.add(DebuggerUtilsEx.mirrorOfString(method.name(), virtualMachineProxy, evaluationContext)); // method name + invokerArgs.add(DebuggerUtilsEx.mirrorOfString(method.signature(), virtualMachineProxy, evaluationContext)); // method descriptor + + // argument values + List boxedArgs = new ArrayList<>(args.size()); + for (Value arg : args) { + boxedArgs.add((Value)BoxingEvaluator.box(arg, evaluationContext)); } + ArrayType objectArrayClass = (ArrayType)debugProcess.findClass( + evaluationContext, + CommonClassNames.JAVA_LANG_OBJECT + "[]", + evaluationContext.getClassLoader()); + + ArrayReference arrayArgs = DebuggerUtilsEx.mirrorOfArray(objectArrayClass, boxedArgs, evaluationContext); + invokerArgs.add(arrayArgs); + invokerArgs.add(evaluationContext.getClassLoader()); // class laoder + return DebuggerUtilsImpl.invokeHelperMethod(evaluationContext, MethodInvoker.class, "invoke", invokerArgs, false); } private static List mapArgumentTypes(@NotNull Method method, @NotNull EvaluationContextImpl evaluationContext) @@ -1679,17 +1675,22 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb @NotNull List args, final int invocationOptions, boolean internalEvaluate) throws EvaluateException { - InvokeCommand invokeCommand = new InvokeCommand<>(method, args, (EvaluationContextImpl)evaluationContext) { - @Override - protected ObjectReference invokeMethod(ThreadReference thread, int invokePolicy, Method method, List args) - throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException { - if (LOG.isDebugEnabled()) { - LOG.debug("New instance " + classType.name() + "." + method.name()); + if (!internalEvaluate && shouldInvokeWithHandler(method, invocationOptions)) { + return (ObjectReference)invokeWithHelper(classType, null, method, args, (EvaluationContextImpl)evaluationContext); + } + else { + InvokeCommand invokeCommand = new InvokeCommand<>(method, args, (EvaluationContextImpl)evaluationContext) { + @Override + protected ObjectReference invokeMethod(ThreadReference thread, int invokePolicy, Method method, List args) + throws InvocationException, ClassNotLoadedException, IncompatibleThreadStateException, InvalidTypeException { + if (LOG.isDebugEnabled()) { + LOG.debug("New instance " + classType.name() + "." + method.name()); + } + return classType.newInstance(thread, method, args, invokePolicy | invocationOptions); } - return classType.newInstance(thread, method, args, invokePolicy | invocationOptions); - } - }; - return invokeCommand.start(internalEvaluate); + }; + return invokeCommand.start(internalEvaluate); + } } public void clearCashes(@MagicConstant(flagsFromClass = EventRequest.class) int suspendPolicy) { diff --git a/java/java-runtime/src/com/intellij/rt/debugger/MethodInvoker.java b/java/java-runtime/src/com/intellij/rt/debugger/MethodInvoker.java index b7d7e9b267e5..816b6870962e 100644 --- a/java/java-runtime/src/com/intellij/rt/debugger/MethodInvoker.java +++ b/java/java-runtime/src/com/intellij/rt/debugger/MethodInvoker.java @@ -1,57 +1,52 @@ // 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; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.lang.invoke.WrongMethodTypeException; public final class MethodInvoker { // TODO: may leak objects here static ThreadLocal returnValue = new ThreadLocal<>(); - public static Object invoke(Class cls, Object obj, String name, Class[] parameterTypes, Object[] args) + public static Object invoke(MethodHandles.Lookup lookup, + Class cls, + Object obj, + String name, + String descriptor, + Object[] args, + ClassLoader loader) throws Throwable { - ArrayList 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 methods, Class cls, String name, Class[] parameterTypes) { try { - methods.add(cls.getDeclaredMethod(name, parameterTypes)); + MethodType mt = MethodType.fromMethodDescriptorString(descriptor, loader); + MethodHandle method; + if ("".equals(name)) { + method = lookup.findConstructor(cls, mt); + } + else if (obj != null) { + method = lookup.findVirtual(cls, name, mt).bindTo(obj); + } + else { + method = lookup.findStatic(cls, name, mt); + } + + Object result; + + // handle the case where null is passed as the vararg array + // TODO: handle when vararg is not the only parameter + if (mt.parameterCount() == 1 && args.length == 1 && args[0] == null && mt.parameterType(0).isArray()) { + result = method.invoke((Object[])null); + } + else { + result = method.invokeWithArguments(args); + } + returnValue.set(result); + return result; } - 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); + catch (WrongMethodTypeException | ClassCastException e) { + e.printStackTrace(); + throw e; } } }