You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
In MethodReference, cachedExecutor is declared volatile. That implies that this class is expected to be used by multiple threads, but almost none of the methods that access that field do so safely.
We're ending up in this method due to Spring Security using SpEL expressions for permission checks. Under light load, the system works fine. Under heavier load, we're seeing NullPointerException stacks that are topped like this:
2014-09-27 21:01:04,386 ERROR [threadpool:thread-28592] c.a.s.i.c.StateTransferringExecutor Error while processing asynchronous task
java.lang.NullPointerException: null
at org.springframework.expression.spel.ast.MethodReference.getValueInternal(MethodReference.java:86) ~[MethodReference.class:4.1.0.RELEASE]
at org.springframework.expression.spel.ast.SpelNodeImpl.getTypedValue(SpelNodeImpl.java:126) ~[SpelNodeImpl.class:4.1.0.RELEASE]
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:296) ~[SpelExpression.class:4.1.0.RELEASE]
A look at MethodReference on line 86 shows:
TypedValue result = getValueInternal(evaluationContext, value, targetType, arguments);
if (cachedExecutor.get() instanceof ReflectiveMethodExecutor) {
ReflectiveMethodExecutor executor = (ReflectiveMethodExecutor) cachedExecutor.get();
This has 2 problems:
There's no null check, but there are multiple other codepaths that can set cachedExecutor to null
Even if there was, a la CachedMethodExecutor.isCompilable, they won't actually protect anything; they're just a race condition.
Since cachedExecutor is volatile, all of the code that uses it, if they need to touch it multiple times, needs to first assign it to a local variable to freeze its state and then access the local variable instead. Otherwise it will always be susceptible to race conditions. This appears to be missing in:
MethodReference.getValueInternal(ExpressionState)
MethodValueRef.getValue()
CachedMethodExecutor.isCompilable()
CachedMethodExecutor.generateCode() (This method only touches cachedExecutor once, but that's only because it's not null checking)
MethodReference.getCachedExecutor has exactly the type of code it seems like every method should have:
Bryan Turner opened SPR-12269 and commented
In
MethodReference
,cachedExecutor
is declaredvolatile
. That implies that this class is expected to be used by multiple threads, but almost none of the methods that access that field do so safely.We're ending up in this method due to Spring Security using SpEL expressions for permission checks. Under light load, the system works fine. Under heavier load, we're seeing
NullPointerException
stacks that are topped like this:A look at
MethodReference
on line 86 shows:This has 2 problems:
null
check, but there are multiple other codepaths that can setcachedExecutor
tonull
CachedMethodExecutor.isCompilable
, they won't actually protect anything; they're just a race condition.Since
cachedExecutor
isvolatile
, all of the code that uses it, if they need to touch it multiple times, needs to first assign it to a local variable to freeze its state and then access the local variable instead. Otherwise it will always be susceptible to race conditions. This appears to be missing in:MethodReference.getValueInternal(ExpressionState)
MethodValueRef.getValue()
CachedMethodExecutor.isCompilable()
CachedMethodExecutor.generateCode()
(This method only touchescachedExecutor
once, but that's only because it's notnull
checking)MethodReference.getCachedExecutor
has exactly the type of code it seems like every method should have:Affects: 4.1 GA
Referenced from: commits 0cc877a, c508a70
The text was updated successfully, but these errors were encountered: