Skip to content

Commit 4bd517d

Browse files
authored
Merge pull request #420 from basil/JENKINS-63766
[JENKINS-63766] Work around JDK-8231454
2 parents 35f6a0b + 0ce0e84 commit 4bd517d

File tree

1 file changed

+52
-8
lines changed

1 file changed

+52
-8
lines changed

src/main/java/org/jenkinsci/plugins/scriptsecurity/sandbox/groovy/SecureGroovyScript.java

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import hudson.model.Item;
3636
import hudson.model.TaskListener;
3737
import hudson.util.FormValidation;
38+
import io.jenkins.lib.versionnumber.JavaSpecificationVersion;
3839

3940
import java.beans.Introspector;
4041
import java.io.Serializable;
@@ -206,6 +207,7 @@ private static void cleanUpClass(Class<?> clazz, Set<ClassLoader> encounteredLoa
206207
if (encounteredClasses.add(clazz)) {
207208
LOGGER.log(Level.FINER, "found {0}", clazz.getName());
208209
Introspector.flushFromCaches(clazz);
210+
cleanUpClassInfoCache(clazz);
209211
cleanUpGlobalClassSet(clazz);
210212
cleanUpClassHelperCache(clazz);
211213
cleanUpObjectStreamClassCaches(clazz);
@@ -266,6 +268,45 @@ private static void cleanUpGlobalClassValue(@NonNull ClassLoader loader) throws
266268
}
267269
}
268270

271+
private static void cleanUpClassInfoCache(Class<?> clazz) {
272+
JavaSpecificationVersion current = JavaSpecificationVersion.forCurrentJVM();
273+
if (current.isNewerThan(new JavaSpecificationVersion("1.8"))
274+
&& current.isOlderThan(new JavaSpecificationVersion("16"))) {
275+
try {
276+
// TODO Work around JDK-8231454.
277+
Class<?> classInfoC = Class.forName("com.sun.beans.introspect.ClassInfo");
278+
Field cacheF = classInfoC.getDeclaredField("CACHE");
279+
try {
280+
cacheF.setAccessible(true);
281+
} catch (RuntimeException e) { // TODO Java 9+ InaccessibleObjectException
282+
/*
283+
* Not running with "--add-opens java.desktop/com.sun.beans.introspect=ALL-UNNAMED".
284+
* Until core adds this to its --add-opens configuration, and until that core
285+
* change is widely adopted, avoid unnecessary log spam and return early.
286+
*/
287+
if (LOGGER.isLoggable(Level.FINER)) {
288+
LOGGER.log(Level.FINER, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e);
289+
}
290+
return;
291+
}
292+
Object cache = cacheF.get(null);
293+
Class<?> cacheC = Class.forName("com.sun.beans.util.Cache");
294+
if (LOGGER.isLoggable(Level.FINER)) {
295+
LOGGER.log(Level.FINER, "Cleaning up " + clazz.getName() + " from ClassInfo#CACHE.");
296+
}
297+
Method removeM = cacheC.getMethod("remove", Object.class);
298+
removeM.invoke(cache, clazz);
299+
} catch (ReflectiveOperationException e) {
300+
/*
301+
* Should never happen, but if it does, ensure the failure is isolated to this
302+
* method and does not prevent other cleanup logic from executing.
303+
*/
304+
LOGGER.log(Level.WARNING, "Failed to clean up " + clazz.getName() + " from ClassInfo#CACHE. A metaspace leak may have occurred.", e);
305+
}
306+
}
307+
}
308+
309+
269310
private static void cleanUpGlobalClassSet(@NonNull Class<?> clazz) throws Exception {
270311
Class<?> classInfoC = Class.forName("org.codehaus.groovy.reflection.ClassInfo"); // or just ClassInfo.class, but unclear whether this will always be there
271312
Field globalClassSetF = classInfoC.getDeclaredField("globalClassSet");
@@ -313,15 +354,18 @@ private static void cleanUpObjectStreamClassCaches(@NonNull Class<?> clazz) thro
313354
for (String cacheFName : new String[] {"localDescs", "reflectors"}) {
314355
Field cacheF = cachesC.getDeclaredField(cacheFName);
315356
cacheF.setAccessible(true);
316-
ConcurrentMap<Reference<Class<?>>, ?> cache = (ConcurrentMap) cacheF.get(null);
317-
Iterator<? extends Map.Entry<Reference<Class<?>>, ?>> iterator = cache.entrySet().iterator();
318-
while (iterator.hasNext()) {
319-
if (iterator.next().getKey().get() == clazz) {
320-
iterator.remove();
321-
if (LOGGER.isLoggable(Level.FINER)) {
322-
LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[] {clazz.getName(), cacheFName});
357+
Object cache = cacheF.get(null);
358+
if (cache instanceof ConcurrentMap) {
359+
// Prior to JDK-8277072
360+
Iterator<? extends Map.Entry<Reference<Class<?>>, ?>> iterator = ((ConcurrentMap) cache).entrySet().iterator();
361+
while (iterator.hasNext()) {
362+
if (iterator.next().getKey().get() == clazz) {
363+
iterator.remove();
364+
if (LOGGER.isLoggable(Level.FINER)) {
365+
LOGGER.log(Level.FINER, "cleaning up {0} from ObjectStreamClass.Caches.{1}", new Object[]{clazz.getName(), cacheFName});
366+
}
367+
break;
323368
}
324-
break;
325369
}
326370
}
327371
}

0 commit comments

Comments
 (0)