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
12 changes: 12 additions & 0 deletions src/main/java/build/buf/protovalidate/AstExpression.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import dev.cel.common.CelAbstractSyntaxTree;
import dev.cel.common.CelValidationException;
import dev.cel.common.CelValidationResult;
import dev.cel.common.ast.CelExpr.ExprKind;
import dev.cel.common.navigation.CelNavigableAst;
import dev.cel.common.types.CelKind;
import dev.cel.compiler.CelCompiler;

Expand Down Expand Up @@ -67,4 +69,14 @@ static AstExpression newAstExpression(CelCompiler cel, Expression expr)
}
return new AstExpression(ast, expr);
}

/** Returns true if the AST references the given identifier name anywhere in its tree. */
static boolean referencesIdentifier(CelAbstractSyntaxTree ast, String name) {
return CelNavigableAst.fromAst(ast)
.getRoot()
.allNodes()
.anyMatch(
node ->
node.getKind() == ExprKind.Kind.IDENT && node.expr().ident().name().equals(name));
}
}
17 changes: 16 additions & 1 deletion src/main/java/build/buf/protovalidate/CelPrograms.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,13 @@ final class CelPrograms implements Evaluator {
/** A list of {@link CompiledProgram} that will be executed against the input message. */
private final List<CompiledProgram> programs;

/**
* Whether any program in {@link #programs} references the {@code now} variable. Computed once at
* construction so {@link #evaluate} can skip the {@link NowVariable} wrapper when no program
* needs it.
*/
private final boolean anyUsesNow;

/**
* Constructs a new {@link CelPrograms}.
*
Expand All @@ -35,6 +42,14 @@ final class CelPrograms implements Evaluator {
CelPrograms(@Nullable ValueEvaluator valueEvaluator, List<CompiledProgram> compiledPrograms) {
this.helper = new RuleViolationHelper(valueEvaluator);
this.programs = compiledPrograms;
boolean anyUsesNow = false;
for (CompiledProgram program : compiledPrograms) {
if (program.usesNow()) {
anyUsesNow = true;
break;
}
}
this.anyUsesNow = anyUsesNow;
}

@Override
Expand All @@ -45,7 +60,7 @@ public boolean tautology() {
@Override
public List<RuleViolation.Builder> evaluate(Value val, boolean failFast)
throws ExecutionException {
CelVariableResolver bindings = Variable.newThisVariable(val.value(Object.class));
CelVariableResolver bindings = Variable.newThisVariable(val.value(Object.class), anyUsesNow);
List<RuleViolation.Builder> violations = new ArrayList<>();
for (CompiledProgram program : programs) {
RuleViolation.Builder violation = program.eval(val, bindings);
Expand Down
12 changes: 11 additions & 1 deletion src/main/java/build/buf/protovalidate/CompiledProgram.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,25 +44,35 @@ final class CompiledProgram {
*/
@Nullable private final CelVariableResolver globals;

/** Whether the compiled expression references the {@code now} variable. */
private final boolean usesNow;

/**
* Constructs a new {@link CompiledProgram}.
*
* @param program The compiled CEL program.
* @param source The original expression that was compiled into the program.
* @param rulePath The field path from the FieldRules to the rule value.
* @param ruleValue The rule value.
* @param usesNow Whether the source expression references the {@code now} variable.
*/
CompiledProgram(
Program program,
Expression source,
@Nullable FieldPath rulePath,
@Nullable Value ruleValue,
@Nullable CelVariableResolver globals) {
@Nullable CelVariableResolver globals,
boolean usesNow) {
this.program = program;
this.source = source;
this.rulePath = rulePath;
this.ruleValue = ruleValue;
this.globals = globals;
this.usesNow = usesNow;
}

boolean usesNow() {
return usesNow;
}

/**
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/build/buf/protovalidate/EvaluatorBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -544,13 +544,16 @@ private static List<CompiledProgram> compileRules(
FieldPath.newBuilder().addElements(fieldPathElement.toBuilder().setIndex(i)).build();
}
try {
boolean usesNow =
AstExpression.referencesIdentifier(astExpression.ast, NowVariable.NOW_NAME);
compiledPrograms.add(
new CompiledProgram(
cel.createProgram(astExpression.ast),
astExpression.source,
rulePath,
new MessageValue(rules.get(i)),
null));
null,
usesNow));
} catch (CelEvaluationException e) {
throw new CompilationException("failed to evaluate rule " + rules.get(i).getId(), e);
}
Expand Down
15 changes: 12 additions & 3 deletions src/main/java/build/buf/protovalidate/RuleCache.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,19 @@ private static class CelRule {
final Program program;
final FieldDescriptor field;
final FieldPath rulePath;
final boolean usesNow;

private CelRule(
AstExpression astExpression, Program program, FieldDescriptor field, FieldPath rulePath) {
AstExpression astExpression,
Program program,
FieldDescriptor field,
FieldPath rulePath,
boolean usesNow) {
this.astExpression = astExpression;
this.program = program;
this.field = field;
this.rulePath = rulePath;
this.usesNow = usesNow;
}
}

Expand Down Expand Up @@ -129,7 +135,8 @@ List<CompiledProgram> compile(
rule.astExpression.source,
rule.rulePath,
new ObjectValue(rule.field, fieldValue),
Variable.newRuleVariable(message, ProtoAdapter.toCel(rule.field, fieldValue))));
Variable.newRuleVariable(message, ProtoAdapter.toCel(rule.field, fieldValue)),
rule.usesNow));
}
return Collections.unmodifiableList(programs);
}
Expand Down Expand Up @@ -187,7 +194,9 @@ private List<CelRule> buildCelRules(
throw new CompilationException(
"failed to create program for rule " + astExpression.source.id, e);
}
celRules.add(new CelRule(astExpression, program, ruleFieldDesc, rulePath));
boolean usesNow =
AstExpression.referencesIdentifier(astExpression.ast, NowVariable.NOW_NAME);
celRules.add(new CelRule(astExpression, program, ruleFieldDesc, rulePath, usesNow));
}
return celRules;
}
Expand Down
17 changes: 11 additions & 6 deletions src/main/java/build/buf/protovalidate/Variable.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,19 @@ private Variable(String name, @Nullable Object val) {
}

/**
* Creates a "this" variable.
* Creates a resolver for the {@code this} variable. When {@code includeNow} is false the {@code
* now} resolver and the hierarchical wrapper are skipped — three allocations become one.
*
* @param val the value.
* @return {@link Variable}.
* @param val the value bound to {@code this}.
* @param includeNow whether the resolver should also expose the {@code now} variable.
* @return a {@link CelVariableResolver} bound to {@code this} (and {@code now} when requested).
*/
static CelVariableResolver newThisVariable(@Nullable Object val) {
return CelVariableResolver.hierarchicalVariableResolver(
new NowVariable(), new Variable(THIS_NAME, val));
static CelVariableResolver newThisVariable(@Nullable Object val, boolean includeNow) {
Variable thisVar = new Variable(THIS_NAME, val);
if (!includeNow) {
return thisVar;
}
return CelVariableResolver.hierarchicalVariableResolver(new NowVariable(), thisVar);
}

/**
Expand Down
Loading