From d37d7abb1768c3fe8a71ba291f2e24082b26c27e Mon Sep 17 00:00:00 2001 From: daguimu Date: Fri, 27 Mar 2026 13:29:37 +0800 Subject: [PATCH] Reject unbalanced parentheses in profile expressions ProfilesParser.parseTokens() silently accepts unbalanced parentheses in profile expressions such as "dev)" or "(dev", treating them as valid. This can lead to unexpected behavior where malformed @Profile annotations are silently interpreted instead of being rejected. This commit tightens the validation in parseTokens() to reject: - Unmatched closing parenthesis at the top level - Unmatched opening parenthesis when tokens are exhausted Also fixes an existing test that inadvertently relied on this lenient behavior by using "spring&framework)" instead of "(spring&framework)". Closes gh-36550 Signed-off-by: daguimu --- .../org/springframework/core/env/ProfilesParser.java | 8 +++----- .../org/springframework/core/env/ProfilesTests.java | 11 ++++++++++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java b/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java index 9d477c9d7d97..326f6f659c4e 100644 --- a/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java +++ b/spring-core/src/main/java/org/springframework/core/env/ProfilesParser.java @@ -88,13 +88,10 @@ private static Profiles parseTokens(String expression, StringTokenizer tokens, C } case "!" -> elements.add(not(parseTokens(expression, tokens, Context.NEGATE))); case ")" -> { - Profiles merged = merge(expression, elements, operator); if (context == Context.PARENTHESIS) { - return merged; + return merge(expression, elements, operator); } - elements.clear(); - elements.add(merged); - operator = null; + assertWellFormed(expression, false); } default -> { Profiles value = equals(token); @@ -105,6 +102,7 @@ private static Profiles parseTokens(String expression, StringTokenizer tokens, C } } } + assertWellFormed(expression, context != Context.PARENTHESIS); return merge(expression, elements, operator); } diff --git a/spring-core/src/test/java/org/springframework/core/env/ProfilesTests.java b/spring-core/src/test/java/org/springframework/core/env/ProfilesTests.java index dde5aa676a97..7cfcb837dceb 100644 --- a/spring-core/src/test/java/org/springframework/core/env/ProfilesTests.java +++ b/spring-core/src/test/java/org/springframework/core/env/ProfilesTests.java @@ -155,7 +155,7 @@ void ofAndExpression() { @Test void ofAndExpressionWithoutSpaces() { - Profiles profiles = Profiles.of("spring&framework)"); + Profiles profiles = Profiles.of("(spring&framework)"); assertAndExpression(profiles); } @@ -292,6 +292,15 @@ void malformedExpressions() { assertMalformed(() -> Profiles.of("a & b | c")); } + @Test + void malformedExpressionsWithUnbalancedParentheses() { + assertMalformed(() -> Profiles.of("dev)")); + assertMalformed(() -> Profiles.of("(dev")); + assertMalformed(() -> Profiles.of("spring&framework)")); + assertMalformed(() -> Profiles.of("((dev)")); + assertMalformed(() -> Profiles.of("(dev))")); + } + @Test void sensibleToString() { assertThat(Profiles.of("spring")).hasToString("spring");