Improve diagnostics in SpEL for `matches` operator
Supplying a large regular expression to the `matches` operator in a SpEL expression can result in errors that are not very helpful to the user. This commit improves the diagnostics in SpEL for the `matches` operator by throwing a SpelEvaluationException with a meaningful error message to better assist the user. Closes gh-30144
This commit is contained in:
		
							parent
							
								
									5529294ec9
								
							
						
					
					
						commit
						8010de8b63
					
				|  | @ -268,7 +268,11 @@ public enum SpelMessage { | |||
| 
 | ||||
| 	/** @since 5.3.26 */ | ||||
| 	MAX_REPEATED_TEXT_SIZE_EXCEEDED(Kind.ERROR, 1076, | ||||
| 			"Repeated text results in too many characters, exceeding the threshold of ''{0}''"); | ||||
| 			"Repeated text results in too many characters, exceeding the threshold of ''{0}''"), | ||||
| 
 | ||||
| 	/** @since 5.3.26 */ | ||||
| 	MAX_REGEX_LENGTH_EXCEEDED(Kind.ERROR, 1077, | ||||
| 			"Regular expression contains too many characters, exceeding the threshold of ''{0}''"); | ||||
| 
 | ||||
| 
 | ||||
| 	private final Kind kind; | ||||
|  |  | |||
|  | @ -43,6 +43,12 @@ public class OperatorMatches extends Operator { | |||
| 
 | ||||
| 	private static final int PATTERN_ACCESS_THRESHOLD = 1000000; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Maximum number of characters permitted in a regular expression. | ||||
| 	 * @since 5.3.26 | ||||
| 	 */ | ||||
| 	private static final int MAX_REGEX_LENGTH = 256; | ||||
| 
 | ||||
| 	private final ConcurrentMap<String, Pattern> patternCache; | ||||
| 
 | ||||
| 
 | ||||
|  | @ -78,25 +84,27 @@ public class OperatorMatches extends Operator { | |||
| 	public BooleanTypedValue getValueInternal(ExpressionState state) throws EvaluationException { | ||||
| 		SpelNodeImpl leftOp = getLeftOperand(); | ||||
| 		SpelNodeImpl rightOp = getRightOperand(); | ||||
| 		String left = leftOp.getValue(state, String.class); | ||||
| 		Object right = getRightOperand().getValue(state); | ||||
| 
 | ||||
| 		if (left == null) { | ||||
| 		String input = leftOp.getValue(state, String.class); | ||||
| 		if (input == null) { | ||||
| 			throw new SpelEvaluationException(leftOp.getStartPosition(), | ||||
| 					SpelMessage.INVALID_FIRST_OPERAND_FOR_MATCHES_OPERATOR, (Object) null); | ||||
| 		} | ||||
| 		if (!(right instanceof String rightString)) { | ||||
| 
 | ||||
| 		Object right = rightOp.getValue(state); | ||||
| 		if (!(right instanceof String regex)) { | ||||
| 			throw new SpelEvaluationException(rightOp.getStartPosition(), | ||||
| 					SpelMessage.INVALID_SECOND_OPERAND_FOR_MATCHES_OPERATOR, right); | ||||
| 		} | ||||
| 
 | ||||
| 		try { | ||||
| 			Pattern pattern = this.patternCache.get(rightString); | ||||
| 			Pattern pattern = this.patternCache.get(regex); | ||||
| 			if (pattern == null) { | ||||
| 				pattern = Pattern.compile(rightString); | ||||
| 				this.patternCache.putIfAbsent(rightString, pattern); | ||||
| 				checkRegexLength(regex); | ||||
| 				pattern = Pattern.compile(regex); | ||||
| 				this.patternCache.putIfAbsent(regex, pattern); | ||||
| 			} | ||||
| 			Matcher matcher = pattern.matcher(new MatcherInput(left, new AccessCount())); | ||||
| 			Matcher matcher = pattern.matcher(new MatcherInput(input, new AccessCount())); | ||||
| 			return BooleanTypedValue.forValue(matcher.matches()); | ||||
| 		} | ||||
| 		catch (PatternSyntaxException ex) { | ||||
|  | @ -109,6 +117,13 @@ public class OperatorMatches extends Operator { | |||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	private void checkRegexLength(String regex) { | ||||
| 		if (regex.length() > MAX_REGEX_LENGTH) { | ||||
| 			throw new SpelEvaluationException(getStartPosition(), | ||||
| 					SpelMessage.MAX_REGEX_LENGTH_EXCEEDED, MAX_REGEX_LENGTH); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 
 | ||||
| 	private static class AccessCount { | ||||
| 
 | ||||
|  |  | |||
|  | @ -482,6 +482,20 @@ class EvaluationTests extends AbstractExpressionTests { | |||
| 			evaluateAndCheckError(expression, SpelMessage.FLAWED_PATTERN); | ||||
| 		} | ||||
| 
 | ||||
| 		@Test | ||||
| 		void matchesWithPatternLengthThreshold() { | ||||
| 			String pattern = "(0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" + | ||||
| 					"0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" + | ||||
| 					"01234567890123456789012345678901234567890123456789|abc)"; | ||||
| 			assertThat(pattern).hasSize(256); | ||||
| 			Expression expr = parser.parseExpression("'abc' matches '" + pattern + "'"); | ||||
| 			assertThat(expr.getValue(context, Boolean.class)).isTrue(); | ||||
| 
 | ||||
| 			pattern += "?"; | ||||
| 			assertThat(pattern).hasSize(257); | ||||
| 			evaluateAndCheckError("'abc' matches '" + pattern + "'", Boolean.class, SpelMessage.MAX_REGEX_LENGTH_EXCEEDED); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	@Nested | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue