This is an automated email from the ASF dual-hosted git repository.

henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git


The following commit(s) were added to refs/heads/master by this push:
     new adf2b1fe JEXL: hardened class loader handling; - nit on release notes;
adf2b1fe is described below

commit adf2b1fe5d12ee6edf65ab8e0071aad3c20d5a72
Author: Henrib <[email protected]>
AuthorDate: Thu Jan 1 16:57:08 2026 +0100

    JEXL: hardened class loader handling;
    - nit on release notes;
---
 RELEASE-NOTES.txt                                  |  4 +-
 .../jexl3/internal/introspection/Introspector.java | 61 +++++++++++++---------
 2 files changed, 38 insertions(+), 27 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 8e8c2b66..a0818283 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -114,8 +114,8 @@ Bugs Fixed in 3.6.0:
 * JEXL-447:     Regression in script-defined functions
 * JEXL-446:     ClassTool module inspection is too strict
 * JEXL-442:     Local variables are not resolved in interpolation string 
expression
-* JEXL-441:     Tokenization error if "\n" in template expression.
-* JEXL-439:     When using reference capture, incorrect scoping when local 
variable redefines a captured symbo
+* JEXL-441:     Tokenization error if "\n" in template expression
+* JEXL-439:     When using reference capture, incorrect scoping when local 
variable redefines a captured symbol
 * JEXL-437:     Semicolons actually not optional between function calls on 
separate lines
 
 
diff --git 
a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
 
b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
index 2341154d..adef421b 100644
--- 
a/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
+++ 
b/src/main/java/org/apache/commons/jexl3/internal/introspection/Introspector.java
@@ -51,6 +51,7 @@ public final class Introspector {
         /** The constructor used as cache-miss. */
         @SuppressWarnings("unused")
         public CacheMiss() {
+            // empty
         }
     }
     /**
@@ -80,10 +81,6 @@ public final class Introspector {
      * the logger.
      */
     private final Log logger;
-    /**
-     * The class loader used to solve constructors if needed.
-     */
-    private ClassLoader loader;
     /**
      * The permissions.
      */
@@ -105,6 +102,12 @@ public final class Introspector {
      * Holds the set of classes we have introspected.
      */
     private final Map<String, Class<?>> constructibleClasses = new HashMap<>();
+    /**
+     * The class loader used to solve constructors if needed.
+     * <p>Field is read/written under lock.</p>
+     */
+    @SuppressWarnings("java:S3077")
+    private volatile ClassLoader loader;
 
     /**
      * Create the introspector.
@@ -137,7 +140,7 @@ public final class Introspector {
      */
     public Class<?> getClassByName(final String className) {
         try {
-            final Class<?> clazz = Class.forName(className, false, loader);
+            final Class<?> clazz = Class.forName(className, false, 
getLoader());
             return permissions.allow(clazz)? clazz : null;
         } catch (final ClassNotFoundException xignore) {
             return null;
@@ -182,19 +185,20 @@ public final class Introspector {
                     if (c != null && c.getName().equals(key.getMethod())) {
                         clazz = c;
                     } else {
+                        // read under lock
                         clazz = loader.loadClass(constructorName);
                     }
                     // add it to list of known loaded classes
                     constructibleClasses.put(constructorName, clazz);
                 }
-                final List<Constructor<?>> l = new ArrayList<>();
+                final List<Constructor<?>> constructors = new ArrayList<>();
                 for (final Constructor<?> ictor : clazz.getConstructors()) {
                     if (permissions.allow(ictor)) {
-                        l.add(ictor);
+                        constructors.add(ictor);
                     }
                 }
                 // try to find one
-                ctor = key.getMostSpecificConstructor(l.toArray(new 
Constructor<?>[0]));
+                ctor = key.getMostSpecificConstructor(constructors.toArray(new 
Constructor<?>[0]));
                 if (ctor != null) {
                     constructorsMap.put(key, ctor);
                 } else {
@@ -262,7 +266,12 @@ public final class Introspector {
      * @return the class loader
      */
     public ClassLoader getLoader() {
-        return loader;
+        lock.readLock().lock();
+        try {
+            return loader;
+        } finally {
+            lock.readLock().unlock();
+        }
     }
 
     /**
@@ -369,35 +378,37 @@ public final class Introspector {
      * @param classLoader the class loader; if null, use this instance class 
loader
      */
     public void setLoader(final ClassLoader classLoader) {
-        final ClassLoader previous = loader;
         final ClassLoader current = classLoader == null ? 
getClass().getClassLoader() : classLoader;
-        if (!current.equals(loader)) {
-            lock.writeLock().lock();
-            try {
+        lock.writeLock().lock();
+        try {
+            final ClassLoader previous = loader;
+            if (!current.equals(previous)) {
                 // clean up constructor and class maps
-                final Iterator<Map.Entry<MethodKey, Constructor<?>>> mentries 
= constructorsMap.entrySet().iterator();
-                while (mentries.hasNext()) {
-                    final Map.Entry<MethodKey, Constructor<?>> entry = 
mentries.next();
+                final Iterator<Map.Entry<MethodKey, Constructor<?>>> 
constructorEntries = constructorsMap.entrySet().iterator();
+                while (constructorEntries.hasNext()) {
+                    final Map.Entry<MethodKey, Constructor<?>> entry = 
constructorEntries.next();
                     final Class<?> clazz = 
entry.getValue().getDeclaringClass();
                     if (isLoadedBy(previous, clazz)) {
-                        mentries.remove();
-                        // the method name is the name of the class
-                        
constructibleClasses.remove(entry.getKey().getMethod());
+                        constructorEntries.remove();
+                        if (!CTOR_MISS.equals(entry.getValue())) {
+                            // the method name is the name of the class
+                            
constructibleClasses.remove(entry.getKey().getMethod());
+                        }
                     }
                 }
                 // clean up method maps
-                final Iterator<Map.Entry<Class<?>, ClassMap>> centries = 
classMethodMaps.entrySet().iterator();
-                while (centries.hasNext()) {
-                    final Map.Entry<Class<?>, ClassMap> entry = 
centries.next();
+                final Iterator<Map.Entry<Class<?>, ClassMap>> classMapEntries 
= classMethodMaps.entrySet().iterator();
+                while (classMapEntries.hasNext()) {
+                    final Map.Entry<Class<?>, ClassMap> entry = 
classMapEntries.next();
                     final Class<?> clazz = entry.getKey();
                     if (isLoadedBy(previous, clazz)) {
-                        centries.remove();
+                        classMapEntries.remove();
                     }
                 }
                 loader = current;
-            } finally {
-                lock.writeLock().unlock();
             }
+        } finally {
+            lock.writeLock().unlock();
         }
     }
 }

Reply via email to