From f479b685310899d9be7f403279509aad287e0c77 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Tue, 24 May 2022 08:42:21 -0700 Subject: [PATCH 1/2] [JENKINS-63766] Work around JDK-8231454 --- .../sandbox/groovy/SecureGroovyScript.java | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java b/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java index f4cca7010..96493f89e 100644 --- a/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java +++ b/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java @@ -35,6 +35,7 @@ import hudson.model.Item; import hudson.model.TaskListener; import hudson.util.FormValidation; +import io.jenkins.lib.versionnumber.JavaSpecificationVersion; import java.beans.Introspector; import java.io.Serializable; @@ -206,6 +207,7 @@ private static void cleanUpClass(Class clazz, Set encounteredLoa if (encounteredClasses.add(clazz)) { LOGGER.log(Level.FINER, "found {0}", clazz.getName()); Introspector.flushFromCaches(clazz); + cleanUpClassInfoCache(clazz); cleanUpGlobalClassSet(clazz); cleanUpClassHelperCache(clazz); cleanUpObjectStreamClassCaches(clazz); @@ -266,6 +268,45 @@ private static void cleanUpGlobalClassValue(@NonNull ClassLoader loader) throws } } + private static void cleanUpClassInfoCache(Class clazz) { + JavaSpecificationVersion current = JavaSpecificationVersion.forCurrentJVM(); + if (current.isNewerThan(new JavaSpecificationVersion("1.8")) + && current.isOlderThan(new JavaSpecificationVersion("16"))) { + try { + // TODO Work around JDK-8231454. + Class classInfoC = Class.forName("com.sun.beans.introspect.ClassInfo"); + Field cacheF = classInfoC.getDeclaredField("CACHE"); + try { + cacheF.setAccessible(true); + } catch (RuntimeException e) { // TOOD Java 9+ InaccessibleObjectException + /* + * Not running with "--add-opens java.desktop/com.sun.beans.introspect=ALL-UNNAMED". + * Until core adds this to its --add-opens configuration, and until that core + * change is widely adopted, avoid unnecessary log spam and return early. + */ + if (LOGGER.isLoggable(Level.FINER)) { + LOGGER.log(Level.FINER, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e); + } + return; + } + Object cache = cacheF.get(null); + Class cacheC = Class.forName("com.sun.beans.util.Cache"); + if (LOGGER.isLoggable(Level.FINER)) { + LOGGER.log(Level.FINER, "Cleaning up " + clazz.getName() + " from ClassInfo#CACHE."); + } + Method removeM = cacheC.getMethod("remove", Object.class); + removeM.invoke(cache, clazz); + } catch (ReflectiveOperationException e) { + /* + * Should never happen, but if it does, ensure the failure is isolated to this + * method and does not prevent other cleanup logic from executing. + */ + LOGGER.log(Level.WARNING, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e); + } + } + } + + private static void cleanUpGlobalClassSet(@NonNull Class clazz) throws Exception { Class classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo"); // or just ClassInfo.class, but unclear whether this will always be there Field globalClassSetF = classInfoC.getDeclaredField("globalClassSet"); @@ -313,15 +354,18 @@ private static void cleanUpObjectStreamClassCaches(@NonNull Class clazz) thro for (String cacheFName : new String[] {"localDescs", "reflectors"}) { Field cacheF = cachesC.getDeclaredField(cacheFName); cacheF.setAccessible(true); - ConcurrentMap>, ?> cache = (ConcurrentMap) cacheF.get(null); - Iterator>, ?>> iterator = cache.entrySet().iterator(); - while (iterator.hasNext()) { - if (iterator.next().getKey().get() == clazz) { - iterator.remove(); - if (LOGGER.isLoggable(Level.FINER)) { - LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[] {clazz.getName(), cacheFName}); + Object cache = cacheF.get(null); + if (cache instanceof ConcurrentMap) { + // Prior to JDK-8277072 + Iterator>, ?>> iterator = ((ConcurrentMap) cache).entrySet().iterator(); + while (iterator.hasNext()) { + if (iterator.next().getKey().get() == clazz) { + iterator.remove(); + if (LOGGER.isLoggable(Level.FINER)) { + LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[]{clazz.getName(), cacheFName}); + } + break; } - break; } } } From 0ce0e84d79fa64f1fa3933bc86be4b0e19c65841 Mon Sep 17 00:00:00 2001 From: Basil Crow Date: Tue, 24 May 2022 12:11:26 -0700 Subject: [PATCH 2/2] Update src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java Co-authored-by: Jesse Glick --- .../scriptsecurity/sandbox/groovy/SecureGroovyScript.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java b/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java index 96493f89e..1c28e60d3 100644 --- a/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java +++ b/src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java @@ -278,7 +278,7 @@ private static void cleanUpClassInfoCache(Class clazz) { Field cacheF = classInfoC.getDeclaredField("CACHE"); try { cacheF.setAccessible(true); - } catch (RuntimeException e) { // TOOD Java 9+ InaccessibleObjectException + } catch (RuntimeException e) { // TODO Java 9+ InaccessibleObjectException /* * Not running with "--add-opens java.desktop/com.sun.beans.introspect=ALL-UNNAMED". * Until core adds this to its --add-opens configuration, and until that core