Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,7 @@ cel_android_library(
"//common/annotations",
"//common/exceptions:divide_by_zero",
"//common/exceptions:index_out_of_bounds",
"//common/exceptions:invalid_argument",
"//common/exceptions:numeric_overflow",
"//common/internal:converter",
"//common/values:values_android",
Expand All @@ -451,6 +452,7 @@ java_library(
"//common/annotations",
"//common/exceptions:divide_by_zero",
"//common/exceptions:index_out_of_bounds",
"//common/exceptions:invalid_argument",
"//common/exceptions:numeric_overflow",
"//common/internal:converter",
"//common/values",
Expand Down
20 changes: 17 additions & 3 deletions runtime/src/main/java/dev/cel/runtime/RuntimeHelpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@
import com.google.protobuf.Duration;
import com.google.protobuf.MessageLiteOrBuilder;
import com.google.re2j.Pattern;
import com.google.re2j.PatternSyntaxException;
import dev.cel.common.CelOptions;
import dev.cel.common.annotations.Internal;
import dev.cel.common.exceptions.CelDivideByZeroException;
import dev.cel.common.exceptions.CelIndexOutOfBoundsException;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.exceptions.CelNumericOverflowException;
import dev.cel.common.internal.Converter;
import dev.cel.common.values.NullValue;
Expand Down Expand Up @@ -76,16 +78,24 @@ public static java.time.Duration createJavaDurationFromString(String d) {
}
}

public static boolean matches(String string, String regexp, CelOptions celOptions) {
Pattern pattern = Pattern.compile(regexp);
public static Pattern compileRegexPattern(String regexp, CelOptions celOptions) {
Pattern pattern;
try {
pattern = Pattern.compile(regexp);
} catch (PatternSyntaxException e) {
throw new CelInvalidArgumentException(e);
}
int maxProgramSize = celOptions.maxRegexProgramSize();
if (maxProgramSize >= 0 && pattern.programSize() > maxProgramSize) {
throw new IllegalArgumentException(
throw new CelInvalidArgumentException(
String.format(
"Regex pattern exceeds allowed program size. Allowed: %d, Provided: %d",
maxProgramSize, pattern.programSize()));
}
return pattern;
}

public static boolean matches(String string, Pattern pattern, CelOptions celOptions) {
if (!celOptions.enableRegexPartialMatch()) {
// Uses re2 for consistency across languages.
return pattern.matcher(string).matches();
Expand All @@ -95,6 +105,10 @@ public static boolean matches(String string, String regexp, CelOptions celOption
return pattern.matcher(string).find();
}

public static boolean matches(String string, String regexp, CelOptions celOptions) {
return matches(string, compileRegexPattern(regexp, celOptions), celOptions);
}

/** Concatenates two lists into a new list. */
public static <E> List<E> concat(List<E> first, List<E> second) {
if (first instanceof ConcatenatedListView) {
Expand Down
12 changes: 12 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/planner/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ java_library(
"//common/ast",
"//common/ast:cel_block",
"//common/exceptions:overload_not_found",
"//common/exceptions:runtime_exception",
"//common/types",
"//common/types:type_providers",
"//common/values",
Expand Down Expand Up @@ -470,8 +471,13 @@ java_library(
deps = [
":attribute",
":eval_attribute",
":eval_const",
":eval_unary",
":planned_interpretable",
"//common/ast",
"//common/values",
"//runtime:resolved_overload",
"//runtime/standard:matches",
],
)

Expand Down Expand Up @@ -718,6 +724,7 @@ cel_android_library(
"//common/ast:ast_android",
"//common/ast:cel_block_android",
"//common/exceptions:overload_not_found",
"//common/exceptions:runtime_exception",
"//common/types:type_providers_android",
"//common/types:types_android",
"//common/values:cel_value_provider_android",
Expand Down Expand Up @@ -1137,8 +1144,13 @@ cel_android_library(
deps = [
":attribute_android",
":eval_attribute_android",
":eval_const_android",
":eval_unary_android",
":planned_interpretable_android",
"//common/ast:ast_android",
"//common/values:values_android",
"//runtime:resolved_overload_android",
"//runtime/standard:matches_android",
],
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ Object evalInternal(GlobalResolver resolver, ExecutionFrame frame) {
return constant;
}

Object constantValue() {
return constant;
}

static EvalConstant create(CelExpr expr, Object value) {
return new EvalConstant(expr, value);
}
Expand Down
30 changes: 30 additions & 0 deletions runtime/src/main/java/dev/cel/runtime/planner/PlannerHelpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@
package dev.cel.runtime.planner;

import dev.cel.common.ast.CelConstant;
import dev.cel.common.ast.CelExpr;
import dev.cel.common.values.CelValueConverter;
import dev.cel.runtime.CelResolvedOverload;
import dev.cel.runtime.standard.MatchesFunction;
import java.util.Optional;

final class PlannerHelpers {

Expand Down Expand Up @@ -47,5 +52,30 @@ static Object resolveConstant(CelConstant celConstant) {
}
}

static Optional<PlannedInterpretable> maybePlanRegexMatches(
CelExpr expr,
CelResolvedOverload resolvedOverload,
PlannedInterpretable[] evaluatedArgs,
CelValueConverter celValueConverter) {
PlannedInterpretable regexArg = evaluatedArgs[1];
if (!MatchesFunction.isMatchesOverload(resolvedOverload)
|| !(regexArg instanceof EvalConstant)) {
return Optional.empty();
}
Object rawConstant = ((EvalConstant) regexArg).constantValue();
if (!(rawConstant instanceof String)) {
return Optional.empty();
}
CelResolvedOverload unaryOverload =
MatchesFunction.newPrecompiledOverload(resolvedOverload, (String) rawConstant);
return Optional.of(
EvalUnary.create(
expr,
unaryOverload.getFunctionName(),
unaryOverload,
evaluatedArgs[0],
celValueConverter));
}

private PlannerHelpers() {}
}
21 changes: 17 additions & 4 deletions runtime/src/main/java/dev/cel/runtime/planner/ProgramPlanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import dev.cel.common.ast.CelExpr.ExprKind.Kind;
import dev.cel.common.ast.CelReference;
import dev.cel.common.exceptions.CelOverloadNotFoundException;
import dev.cel.common.exceptions.CelRuntimeException;
import dev.cel.common.types.CelKind;
import dev.cel.common.types.CelType;
import dev.cel.common.types.CelTypeProvider;
Expand Down Expand Up @@ -107,10 +108,15 @@ public PlannedProgram plan(CelAbstractSyntaxTree ast) throws CelEvaluationExcept
.map(celBlock -> planBlock(celBlock, ctx))
.orElseGet(() -> plan(ast.getExpr(), ctx));
} catch (RuntimeException e) {
throw CelEvaluationExceptionBuilder.newBuilder(e.getMessage())
.setMetadata(errorMetadata, ast.getExpr().id())
.setCause(e)
.build();
// Preserve specific CelErrorCode (e.g., INVALID_ARGUMENT, OVERLOAD_NOT_FOUND) from
// plan-time validation failures.
CelEvaluationExceptionBuilder builder;
if (e instanceof CelRuntimeException) {
builder = CelEvaluationExceptionBuilder.newBuilder((CelRuntimeException) e);
} else {
builder = CelEvaluationExceptionBuilder.newBuilder(e.getMessage()).setCause(e);
}
throw builder.setMetadata(errorMetadata, ast.getExpr().id()).build();
}

return PlannedProgram.create(
Expand Down Expand Up @@ -355,6 +361,13 @@ private PlannedInterpretable planCall(CelExpr expr, PlannerContext ctx) {
evaluatedArgs[1],
celValueConverter);
}
PlannedInterpretable regexMatches =
PlannerHelpers.maybePlanRegexMatches(
expr, resolvedOverload, evaluatedArgs, celValueConverter)
.orElse(null);
if (regexMatches != null) {
return regexMatches;
}
return EvalBinary.create(
expr,
functionName,
Expand Down
12 changes: 10 additions & 2 deletions runtime/src/main/java/dev/cel/runtime/standard/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -1045,12 +1045,16 @@ java_library(
deps = [
":standard_overload",
"//common:options",
"//common/exceptions:invalid_argument",
"//common/annotations",
"//runtime:function_binding",
"//runtime:function_overload",
"//runtime:resolved_overload",
"//runtime:runtime_equality",
"//runtime:runtime_helpers",
"//runtime/standard:standard_function",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
"@maven//:com_google_re2j_re2j",
],
)

Expand All @@ -1063,10 +1067,14 @@ cel_android_library(
":standard_function_android",
":standard_overload_android",
"//common:options",
"//common/exceptions:invalid_argument",
"//common/annotations",
"//runtime:function_binding_android",
"//runtime:function_overload_android",
"//runtime:resolved_overload_android",
"//runtime:runtime_equality_android",
"//runtime:runtime_helpers_android",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_re2j_re2j",
"@maven_android//:com_google_guava_guava",
],
)
Expand Down
87 changes: 55 additions & 32 deletions runtime/src/main/java/dev/cel/runtime/standard/MatchesFunction.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,22 @@

package dev.cel.runtime.standard;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.Immutable;
import com.google.re2j.Pattern;
import dev.cel.common.CelOptions;
import dev.cel.common.exceptions.CelInvalidArgumentException;
import dev.cel.common.annotations.Internal;
import dev.cel.runtime.CelFunctionBinding;
import dev.cel.runtime.CelFunctionOverload;
import dev.cel.runtime.CelResolvedOverload;
import dev.cel.runtime.RuntimeEquality;
import dev.cel.runtime.RuntimeHelpers;
import java.util.Arrays;

/** Standard function for {@code matches}. */
public final class MatchesFunction extends CelStandardFunction {
private static final String MATCHES_FUNCTION = "matches";
private static final MatchesFunction ALL_OVERLOADS = create(MatchesOverload.values());

public static MatchesFunction create() {
Expand All @@ -38,51 +44,68 @@ public static MatchesFunction create(Iterable<MatchesFunction.MatchesOverload> o
return new MatchesFunction(ImmutableSet.copyOf(overloads));
}

@Internal
public static boolean isMatchesOverload(CelResolvedOverload resolvedOverload) {
return resolvedOverload.getDefinition() instanceof StandardMatchesOverload;
}

@Internal
public static CelResolvedOverload newPrecompiledOverload(
CelResolvedOverload resolvedOverload, String regexPattern) {
CelOptions celOptions = ((StandardMatchesOverload) resolvedOverload.getDefinition()).celOptions;
Pattern compiledPattern = RuntimeHelpers.compileRegexPattern(regexPattern, celOptions);
String overloadId = resolvedOverload.getOverloadId();
CelFunctionBinding binding =
CelFunctionBinding.from(
overloadId,
String.class,
target -> RuntimeHelpers.matches(target, compiledPattern, celOptions));
return CelResolvedOverload.of(
MATCHES_FUNCTION,
overloadId,
binding.getDefinition(),
binding.isStrict(),
binding.getArgTypes());
}

/** Overloads for the standard function. */
public enum MatchesOverload implements CelStandardOverload {
MATCHES(
(celOptions, runtimeEquality) ->
CelFunctionBinding.from(
"matches",
String.class,
String.class,
(String string, String regexp) -> {
try {
return RuntimeHelpers.matches(string, regexp, celOptions);
} catch (RuntimeException e) {
throw new CelInvalidArgumentException(e);
}
})),
MATCHES(MATCHES_FUNCTION),
// Duplicate receiver-style matches overload.
MATCHES_STRING(
(celOptions, runtimeEquality) ->
CelFunctionBinding.from(
"matches_string",
String.class,
String.class,
(String string, String regexp) -> {
try {
return RuntimeHelpers.matches(string, regexp, celOptions);
} catch (RuntimeException e) {
throw new CelInvalidArgumentException(e);
}
})),
MATCHES_STRING("matches_string"),
;

private final CelStandardOverload standardOverload;
private final String overloadId;

@Override
public CelFunctionBinding newFunctionBinding(
CelOptions celOptions, RuntimeEquality runtimeEquality) {
return standardOverload.newFunctionBinding(celOptions, runtimeEquality);
return CelFunctionBinding.from(
overloadId,
ImmutableList.of(String.class, String.class),
new StandardMatchesOverload(celOptions));
}

MatchesOverload(String overloadId) {
this.overloadId = overloadId;
}
}

@Immutable
private static final class StandardMatchesOverload implements CelFunctionOverload {
private final CelOptions celOptions;

@Override
public Object apply(Object[] args) {
return RuntimeHelpers.matches((String) args[0], (String) args[1], celOptions);
}

MatchesOverload(CelStandardOverload standardOverload) {
this.standardOverload = standardOverload;
private StandardMatchesOverload(CelOptions celOptions) {
this.celOptions = celOptions;
}
}

private MatchesFunction(ImmutableSet<CelStandardOverload> overloads) {
super("matches", overloads);
super(MATCHES_FUNCTION, overloads);
}
}
Loading
Loading