Property accessors resolved via SpEL's ReflectivePropertyAccessor and
DataBindingPropertyAccessor may be JavaBean-style accessors or plain
accessor methods used to support data classes such as Java records and
Kotlin data classes. However, neither accessor can determine, via
reflection, whether such a method is a side-effect-free read or an
action that happens to return a value. For example, File.delete() is a
public method that returns a boolean and therefore looks like a
plain "property".
To better inform users, this commit updates the Javadoc for
ReflectivePropertyAccessor, DataBindingPropertyAccessor, and
SimpleEvaluationContext (as well as in the SpEL reference
documentation) to clarify that restricting a SimpleEvaluationContext to
read-only data binding governs only whether assignment to a property is
permitted and does not guarantee that reading a property is free of
side effects. The reference documentation's "Security Considerations"
section now also defines what makes a method "accessor-shaped", with
concrete examples of safe versus side-effecting methods that share that
shape (for example, File.delete(), Queue.poll(), and
AtomicInteger.incrementAndGet()).
Building on that clarification, this commit introduces a new "Object
Design" section to the SpEL reference documentation, analogous to the
"Model Design" guidance for web data binding. This new section
recommends that any object reachable from an untrusted SpEL expression
(not only the root object) be a purpose-built, immutable type with a
deliberately limited surface area, and that its accessor-shaped methods
be audited for unsafe side effects. The new section also notes that
reachability is transitive through both property navigation and
indexing (for example, rootObject.child.grandchild or
rootObject.items[0]).
In any case, it remains the responsibility of the code that exposes a
root object or other reachable object to an expression from an
untrusted source to ensure that none of its accessor-shaped methods
perform an unsafe action.
Closes gh-37102
The Javadoc for EvaluationContext, StandardEvaluationContext, and
SimpleEvaluationContext previously repeated the same detailed
explanation of trusted sources and best-effort restrictions in four
places, making it hard to maintain and to digest.
This commit condenses each class-level warning to a succinct summary
that links to the new "Security Considerations" section of the SpEL
reference documentation, which remains the single, detailed source of
truth introduced in 9b42a40a2a.
See gh-36997
This commit adds a "Lifecycle and Reuse" section to the SpEL reference
documentation, immediately following the security considerations
introduced for gh-36997, explaining that AST nodes within a parsed
Expression may cache resolved PropertyAccessor, IndexAccessor,
MethodExecutor, and ConstructorExecutor instances for performance.
We also now document that reusing a parsed Expression across
EvaluationContext instances of the same type and with equivalent
configuration is supported (even if atypical), including when accessors
or resolvers registered with a context change between evaluations, but
that reusing a parsed Expression across contexts with different
security implications (for example, first against a
StandardEvaluationContext and later against a SimpleEvaluationContext)
is not supported, since cached state from a more permissive evaluation
may be reused during a more restrictive one.
The Javadoc for Expression, SpelExpression, EvaluationContext,
StandardEvaluationContext, SimpleEvaluationContext, PropertyAccessor,
IndexAccessor, MethodExecutor, and ConstructorExecutor has also been
updated to make these contracts discoverable via the API as well.
Closes gh-36968
This commit clarifies in the Javadoc for EvaluationContext,
StandardEvaluationContext, and SimpleEvaluationContext (as well as in
the SpEL reference documentation) that StandardEvaluationContext must
never be used to evaluate expressions from an untrusted source, and
that SimpleEvaluationContext's restricted language and feature subset
is only a best-effort measure. The updated documentation also defines a
"trusted" source as a developer or administrator of the application and
points out that it is the responsibility of the code that configures an
EvaluationContext to ensure that no object reachable via the context
exposes dangerous operations.
Closes gh-36997
Prior to this commit, the SpEL Indexer's PropertyAccessorValueRef could
reuse a cached PropertyAccessor for reads and writes even after that
accessor had been removed from the current EvaluationContext, leading
to stale property access if the EvaluationContext changes between
evaluations of the same expression.
This commit aligns PropertyAccessorValueRef with the analogous logic
in PropertyOrFieldReference and Indexer's IndexAccessorValueRef by
verifying that the cached PropertyAccessor is still registered in the
current EvaluationContext before reusing it in getValue() and
setValue().
Closes gh-36986
Prior to this commit, a SpEL ConstructorReference could reuse a cached
ConstructorExecutor even when the current EvaluationContext no longer
had any registered ConstructorResolvers, leading to inconsistent
behavior if the EvaluationContext changes between evaluations of the
same expression.
This commit aligns ConstructorReference with the analogous logic in
PropertyOrFieldReference by discarding the cached ConstructorExecutor
whenever there are no ConstructorResolvers registered in the current
EvaluationContext, ensuring that constructor resolution consistently
fails with a CONSTRUCTOR_NOT_FOUND exception in that scenario.
Closes gh-36985
Prior to this commit, SpEL's InlineList was cached as a mutable list in
compiled mode (i.e., in a static field in the generated byte code).
To address that, this commit modifies InlineList's generateClinitCode()
method so that it wraps both top-level and nested inline lists using
Collections.unmodifiableList(), analogous to what we already do in
createList().
Closes gh-37001
This commit picks up where ce718cf699 left off by ensuring that
findAllLocalMergedAnnotations() also finds annotations declared on
transitive interfaces — that is, on interfaces of the directly
implemented interfaces of the root declaring class.
The previous implementation filtered annotations from a single
TYPE_HIERARCHY search by checking whether the annotation's source was
either the root declaring class or one of its directly declared
interfaces. However, this excluded annotations inherited through an
interface chain such as First -> Second -> Third, where the annotation
is declared on Third but not on Second.
This commit replaces that approach with two targeted searches whose
results are combined:
- DIRECT on the root declaring class, to capture annotations declared
directly on the class (including via composed/meta-annotations)
- TYPE_HIERARCHY on each directly implemented interface, which
naturally traverses the full super-interface chain of each interface
A corresponding test for this scenario has also been added.
Closes gh-36975
Prior to this commit, AnnotationDescriptor's
findAllLocalMergedAnnotations() filtered results using
MergedAnnotationPredicates.firstRunOf(
MergedAnnotation::getAggregateIndex), which retains only annotations
that share the same aggregate index as the first annotation
encountered. Since annotations on the root declaring class have
aggregate index 0 and annotations on interfaces have higher indices,
interface annotations were silently excluded whenever the root
declaring class itself declared the annotation.
This commit fixes the issue by replacing the aggregate-index-based
filter with a source-based filter that explicitly includes annotations
from the root declaring class and from each directly implemented
interface. The Javadoc for findAllLocalMergedAnnotations() has also
been updated to document the ordering guarantee: annotations from the
root declaring class appear first, followed by annotations from
implemented interfaces in declaration order.
Closes gh-36975
This commit addresses warnings across the code base related to:
- internal and public deprecations in Spring Framework
- deprecated Locale constructors
- deprecated URL constructors
- deprecated Thread#getId method
After the upgrade to Gradle 9.6.0/9.6.1, the Gradle build started
emitting warnings due to use of deprecated APIs.
For example, Project.getProperties() is now annotated as @Deprecated
in Gradle 9.6 and will be removed in Gradle 10.0.
To avoid the warnings, this commit modifies:
- TestConventions to use `project.findProperty(...)` instead of
`project.getProperties().get(...)`
- framework-api.gradle to use `rootProject.ext.moduleProjects` instead
of simply `moduleProjects`
- framework-api.gradle and framework-bom.gradle to use
`project(<projectX>)` instead of simply `<projectX>`
- ide.gradle so that it no longer uses the deprecated `javaRuntimeName`
See gh-36952
Due to changes made in commit 41cd6879bd, MimeTypeUtils now raises a
StringIndexOutOfBoundsException instead of an InvalidMimeTypeException
when parsing certain invalid mime types -- for example, for a value
wrapped in double quotes which does not contain a ";" character.
To address that minor regression, this commit replaces
`mimeType.charAt(nextIndex - 1) != '\\'` with
`(nextIndex == 0 || mimeType.charAt(nextIndex - 1) != '\\')` to avoid
invoking `String#charAt` with a negative value.
See gh-36730
Closes gh-36971
Prior to this commit, DefaultServerRequest's ServletParametersMap lost
the original parameter order when entrySet() was invoked.
To address that, this commit revises ServletParametersMap.entrySet() so
that it stores the results in a LinkedHashSet, thereby retaining the
original order.
Closes gh-36966
In order to avoid the staircase scaling effect that results from our
current use of integer division, this commit revises applyJitter(long)
in ExponentialBackOffExecution to use floating-point (double) division
to calculate the applied jitter.
Closes gh-36943
This commit introduces support for tracking operations during SpEL
expression evaluation. If the maximum number of operations is exceeded,
a SpelEvaluationException is thrown.
The limit can be configured either on a per-use-case basis via
SpelParserConfiguration supplied to the SpelExpressionParser or
globally as a JVM system property or Spring property named
`spring.expression.maxOperations`.
Closes gh-36801
Prior to this commit, the `antora` Gradle task silently failed to build
the reference documentation, since Antora uses the latest LTS release
for Node.js by default, and the latest LTS apparently does not work for
us.
Prior to this commit,
MicrometerObservationRegistryTestExecutionListener always attempted to
load the test's ApplicationContext in order to update the
ObservationThreadLocalAccessor in its beforeTestMethod() callback, even
if there was no active ApplicationContext.
To avoid unnecessarily loading an ApplicationContext or attempting to
load an ApplicationContext that cannot be loaded (for example, due to a
context-load failure), this commit applies a hasApplicationContext()
check in beforeTestMethod().
Since the MicrometerObservationRegistryTestExecutionListener is
registered after the DependencyInjectionTestExecutionListener (at least
by default), an active ApplicationContext should be present unless
dependency injection from the context failed or the context failed to
load.
Closes gh-36817
Prior to this commit and the previous commit,
MockitoResetTestExecutionListener always attempted to load the
ApplicationContext to reset mocks in its beforeTestMethod() and
afterTestMethod() callbacks, even if there was no active
ApplicationContext.
The reason this was noticed is that the @BeforeMethod(alwaysRun = true)
and @AfterMethod(alwaysRun = true) lifecycle methods in
AbstractTestNGSpringContextTests are always invoked, even if a previous
lifecycle configuration method failed (for example, due to a
context-load failure).
However, with JUnit Jupiter and the SpringExtension the
beforeTestMethod() and afterTestMethod() callbacks in the
TestExecutionListener API are not invoked if there was a previous
lifecycle failure.
Consequently, the reported drawbacks only exist when using Spring's
TestNG base support classes
This commit picks up where the previous commit left off by applying the
same hasApplicationContext() check in beforeTestMethod().
This commit also introduces unit and integration tests for both Jupiter
and TestNG support.
Closes gh-36782