-
Notifications
You must be signed in to change notification settings - Fork 38.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
SpEL incorrectly splits string arguments by comma for Object...
varargs method
#33013
Comments
@manoharank reported the same observation here: #27582 (comment) |
Object...
varargs method
Hi @weiw005, Congratulations on submitting your first issue for the Spring Framework! 👍 Thanks for the Demo! Though, please submit sample applications using Java (unless Kotlin is required to reproduce the issue). I've reduced the demo to the following standalone Java test class. import java.lang.reflect.Method;
import org.junit.jupiter.api.Test;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
class SpelVarargsTests {
@Test
void formatStringVarargs_EmptyString() {
assertThat(evaluateExpression("#formatStringVarargs('Hello %s', '')"))
.isEqualTo("Hello ");
}
@Test
void formatObjectVarargs_EmptyString() {
assertThat(evaluateExpression("#formatObjectVarargs('Hello %s', '')"))
.isEqualTo("Hello ");
}
@Test
void formatStringVarargs_SingleArgumentWithComma() {
assertThat(evaluateExpression("#formatStringVarargs('Hello %s', 'a, b')"))
.isEqualTo("Hello a, b");
}
@Test
void formatObjectVarargs_SingleArgumentWithComma() {
assertThat(evaluateExpression("#formatObjectVarargs('Hello %s', 'a, b')"))
.isEqualTo("Hello a, b");
}
@Test
void formatStringVarargs_MultiArgumentWithComma() {
assertThat(evaluateExpression("#formatStringVarargs('Hello %s; %s', 'a, b', 'c, d')"))
.isEqualTo("Hello a, b; c, d");
}
@Test
void formatObjectVarargs_MultiArgumentWithComma() {
assertThat(evaluateExpression("#formatObjectVarargs('Hello %s; %s', 'a, b', 'c, d')"))
.isEqualTo("Hello a, b; c, d");
}
private static final Method formatObjectVarargs =
ReflectionUtils.findMethod(SpelVarargsTests.class, "formatObjectVarargs", String.class, Object[].class);
private static final Method formatStringVarargs =
ReflectionUtils.findMethod(SpelVarargsTests.class, "formatStringVarargs", String.class, String[].class);
private static Object evaluateExpression(String expression) {
SpelExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("formatObjectVarargs", formatObjectVarargs);
context.registerFunction("formatStringVarargs", formatStringVarargs);
return exp.getValue(context);
}
static String formatObjectVarargs(String format, Object... args) {
return String.format(format, args);
}
static String formatStringVarargs(String format, String... args) {
return String.format(format, (Object[]) args);
}
} When running against We'll look into fixing this. Cheers, Sam |
While investigating this, I noticed that (in theory) the same bug must exist for a function registered as a |
This has been fixed in This will also be backported to |
Thanks, @sbrannen I did some testing in our project with the latest release ( As part of the verification, I found another behavior also changed in terms of how array arguments work as
The new behavior is probably more clear and consistent IMHO. Thanks again for the fix! |
Hi @weiw005,
Great! Glad to hear that resolved your issues.
Perhaps, but just to be certain... would you mind creating a new issue pointing out what used to work and what now doesn't (with simple, concrete examples that we can run (preferably in Java))? That would at least allow us to assess if the change in behavior is intentional or acceptable. Cheers, Sam |
In evaluate("formatObjectVarargs('x -> %s %s %s', new String[]{'a', 'b', 'c'})", "x -> a b c", String.class); That passes as expected. Thus, I assume you mean you are supplying an inline list as follows. evaluate("formatObjectVarargs('x -> %s %s %s', {'hello', 123, 456})", "x -> hello 123 456", String.class); That fails, because the inline list (an instance of Thus, perhaps you are saying that a Can you please confirm that the above scenario is the change in behavior you experienced? Thanks, Sam |
Hi @sbrannen Yes, it's about inline list. Here is a self-contained unit test to demonstrate the difference: import org.junit.jupiter.api.Test;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.util.ReflectionUtils;
import static org.assertj.core.api.Assertions.assertThat;
import java.lang.reflect.Method;
public class SpelVarargArrayTest {
@Test
public void arrayFlattenedAsVararg_Before_6_1_10() {
assertThat(evaluateExpression("#objectVarargDemo({'World', 123})"))
.isEqualTo("There are 2 argument(s): World 123");
}
@Test
public void arrayFlattenedAsVararg_After_6_1_11() {
assertThat(evaluateExpression("#objectVarargDemo({'World', 123})"))
.isEqualTo("There are 1 argument(s): [World, 123]");
}
private static final Method demoMethod =
ReflectionUtils.findMethod(SpelVarargArrayTest.class, "objectVarargDemo", Object[].class);
private static Object evaluateExpression(String expression) {
SpelExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(expression);
StandardEvaluationContext context = new StandardEvaluationContext();
context.registerFunction("objectVarargDemo", demoMethod);
return exp.getValue(context);
}
static String objectVarargDemo(Object... args) {
StringBuilder sb = new StringBuilder();
sb.append(String.format("There are %d argument(s): ", args.length));
for (Object arg : args) {
sb.append(arg).append(" ");
}
return sb.toString().trim();
}
} You can see that earlier versions would flatten the inline list as separate elements for vararg functions, and the new version simply treat the whole list as a single argument. |
Thanks for the feedback and example, @weiw005.
OK. I can see how it used to do that. As I mentioned before, I believe that behavior was accidental and that recent bug fixes in the varargs processing now implement the correct behavior. Of course, if the recent changes result in major regressions within the community, we may potentially attempt to support the list (or potentially only inline list) to array conversion there. Having said that, however, I think we should aim to stick with the current behavior since a list is in fact not an array, and the current behavior in SpEL aligns with the standard Java behavior for varargs invocations. |
Prior to this commit, the Spring Expression Language (SpEL) incorrectly split single String arguments by comma for Object... varargs method and constructor invocations. This commit addresses this by checking if the single argument type is already "assignable" to the varargs component type instead of "equal" to the varargs component type. See gh-33013 Closes gh-33189
Prior to this commit, the Spring Expression Language (SpEL) incorrectly split single String arguments by comma for Object... varargs method and constructor invocations. This commit addresses this by checking if the single argument type is already "assignable" to the varargs component type instead of "equal" to the varargs component type. See gh-33013 Closes gh-33188 (cherry picked from commit d33f66d)
Due to #33315, we have decided to revert the backports to |
The changes made in conjunction with gh-33013 resulted in a regression for varargs support in SpEL expressions. Specifically, before gh-33013 one could supply a list -- for example, an "inline list" -- as the varargs array when invoking a varargs constructor, method, or function within a SpEL expression. However, after gh-33013 an inline list (or collection in general) is no longer converted to an array for varargs invocations. Instead, the list is supplied as a single argument of the resulting varargs array which breaks applications that depend on the previous behavior. Although it was never intended that one could supply a collection as the set of varargs, we concede that this is a regression in existing behavior, and this commit therefore restores support for supplying a java.util.List as the varargs "array". In addition, this commit introduces the same "list to array" conversion support for MethodHandle-based functions that accept varargs. Note, however, that this commit does not restore support for converting arbitrary single objects to an array for varargs invocations if the single object is already an instance of the varargs array's component type. See gh-33013 Closes gh-33315
Affects
4.1.2.RELEASE
or higherSummary
In SpEL, a registered varargs function incorrectly splits string arguments by comma when varargs type is
Object
/Any
.Overview
This is related to previously reported #27582. The issue was likely not fully fixed yet.
I created a unit test here to demo the bug: https://github.com/weiw005/playground/blob/e6f1ae9336b8ac5e294758a90d2639c6a075df5b/spel-vararg/src/test/kotlin/SpelVarargTest.kt
It seems that when a registered function has
vararg args: Any
as the argument type, SpEL would have inconsistent behavior when there is single vs. multiple argument(s) of string type:This bug doesn't manifest when the function signature declares
vararg args: String
instead (all arguments would be taken as is correctly).I have also confirmed that version
4.1.1.RELEASE
or lower did not have this bug.Related Issues
The text was updated successfully, but these errors were encountered: