From 5378001c87bb7397f6812d9bd2e1b9f775787c49 Mon Sep 17 00:00:00 2001 From: Egor Ushakov Date: Tue, 10 Dec 2024 14:58:38 +0100 Subject: [PATCH] IDEA-364527 Helper classes are loaded into a separate classloader for every call GitOrigin-RevId: 7316e26e048ccb91ffc8dd80929893ab2ac7735e --- .../debugger/impl/ClassLoadingUtils.java | 41 +++++++++++++++---- .../impl/RemoteConnectionBuilder.java | 16 ++++---- .../util/resources/misc/registry.properties | 4 ++ 3 files changed, 46 insertions(+), 15 deletions(-) diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/ClassLoadingUtils.java b/java/debugger/impl/src/com/intellij/debugger/impl/ClassLoadingUtils.java index 1b9aa05b3ff8..264f05b3a9f2 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/ClassLoadingUtils.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/ClassLoadingUtils.java @@ -9,6 +9,8 @@ import com.intellij.debugger.engine.evaluation.EvaluateException; import com.intellij.debugger.engine.evaluation.EvaluationContext; import com.intellij.debugger.engine.evaluation.EvaluationContextImpl; import com.intellij.debugger.jdi.VirtualMachineProxyImpl; +import com.intellij.openapi.util.registry.Registry; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.jdi.MethodImpl; import com.sun.jdi.*; import org.jetbrains.annotations.Nullable; @@ -43,7 +45,7 @@ public final class ClassLoadingUtils { byte[] bytes, EvaluationContextImpl context, DebugProcess process, - ClassLoaderReference classLoader) throws EvaluateException { + ObjectReference classLoader) throws EvaluateException { try { VirtualMachineProxyImpl proxy = context.getVirtualMachineProxy(); Method defineMethod = @@ -83,22 +85,45 @@ public final class ClassLoadingUtils { String name = cls.getName(); evaluationContext = evaluationContext.withAutoLoadClasses(true); DebugProcess process = evaluationContext.getDebugProcess(); + ClassLoaderReference currentClassLoader = evaluationContext.getClassLoader(); try { - return (ClassType)process.findClass(evaluationContext, name, evaluationContext.getClassLoader()); + return (ClassType)process.findClass(evaluationContext, name, currentClassLoader); } catch (EvaluateException e) { Throwable cause = e.getCause(); if (cause instanceof InvocationException) { if ("java.lang.ClassNotFoundException".equals(((InvocationException)cause).exception().type().name())) { // need to define - ClassLoaderReference classLoader = getClassLoader(evaluationContext, process); - if (!defineClass(name, cls, evaluationContext, process, classLoader)) return null; - for (String fqn : additionalClassesToLoad) { + ObjectReference classLoader; + boolean newClassLoader = Registry.is("debugger.evaluate.load.helper.in.separate.classloader") || currentClassLoader == null; + if (newClassLoader) { + classLoader = getClassLoader(evaluationContext, process); + } + else { // find the top classloader + classLoader = currentClassLoader; + while (true) { + Method parentMethod = DebuggerUtils.findMethod(classLoader.referenceType(), "getParent", "()Ljava/lang/ClassLoader;"); + Value parent = evaluationContext.getDebugProcess().invokeInstanceMethod( + evaluationContext, classLoader, parentMethod, Collections.emptyList(), 0, true); + if (!(parent instanceof ObjectReference objectReference)) { + break; + } + classLoader = objectReference; + } + } + + for (String fqn : ContainerUtil.prepend(Arrays.asList(additionalClassesToLoad), name)) { if (!defineClass(fqn, cls, evaluationContext, process, classLoader)) return null; } - evaluationContext.setClassLoader(classLoader); - return (ClassType)process.findClass(evaluationContext, name, classLoader); + if (newClassLoader) { + ClassLoaderReference newClassLoaderReference = (ClassLoaderReference)classLoader; + evaluationContext.setClassLoader(newClassLoaderReference); + return (ClassType)process.findClass(evaluationContext, name, newClassLoaderReference); + } + else { + return (ClassType)process.findClass(evaluationContext, name, currentClassLoader); + } } } throw e; @@ -109,7 +134,7 @@ public final class ClassLoadingUtils { Class cls, EvaluationContextImpl evaluationContext, DebugProcess process, - ClassLoaderReference classLoader) throws EvaluateException { + ObjectReference classLoader) throws EvaluateException { try (InputStream stream = cls.getResourceAsStream('/' + name.replace('.', '/') + ".class")) { if (stream == null) return false; defineClass(name, stream.readAllBytes(), evaluationContext, process, classLoader); diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/RemoteConnectionBuilder.java b/java/debugger/impl/src/com/intellij/debugger/impl/RemoteConnectionBuilder.java index 5086451ef445..3613b15e59ef 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/RemoteConnectionBuilder.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/RemoteConnectionBuilder.java @@ -138,13 +138,15 @@ public class RemoteConnectionBuilder { } private static void addRtJar(@NotNull PathsList pathsList) { - if (PluginManagerCore.isRunningFromSources()) { - String path = DebuggerUtilsImpl.getIdeaRtPath(); - pathsList.remove(JavaSdkUtil.getIdeaRtJarPath()); - pathsList.addTail(path); - } - else { - JavaSdkUtil.addRtJar(pathsList); + if (Registry.is("debugger.add.rt.jar", true)) { + if (PluginManagerCore.isRunningFromSources()) { + String path = DebuggerUtilsImpl.getIdeaRtPath(); + pathsList.remove(JavaSdkUtil.getIdeaRtJarPath()); + pathsList.addTail(path); + } + else { + JavaSdkUtil.addRtJar(pathsList); + } } } diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 2ed969a18fc2..600d8ac6b3a3 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -643,10 +643,14 @@ 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.add.rt.jar=true +debugger.add.rt.jar.description=Add rt.jar to the classpath 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.evaluate.load.helper.in.separate.classloader=true +debugger.evaluate.load.helper.in.separate.classloader.description=Always load helper classes in a new classloader 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