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();
}
}
}