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
27 changes: 21 additions & 6 deletions runtime/src/main/java/dev/cel/runtime/CelAttributePattern.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.primitives.UnsignedLong;
import com.google.errorprone.annotations.Immutable;

/**
Expand Down Expand Up @@ -124,9 +125,7 @@ public boolean isMatch(CelAttribute attribute) {
int qualifierCount = min(qualifiers().size(), attribute.qualifiers().size());
int i = 0;
for (; i < qualifierCount; i++) {
CelAttribute.Qualifier pattern = qualifiers().get(i);
CelAttribute.Qualifier qualifier = attribute.qualifiers().get(i);
if (pattern.kind() != CelAttribute.Qualifier.Kind.WILD_CARD && !pattern.equals(qualifier)) {
if (!matches(qualifiers().get(i), attribute.qualifiers().get(i))) {
return false;
}
}
Expand Down Expand Up @@ -154,9 +153,7 @@ public boolean isPartialMatch(CelAttribute attribute) {
}
int qualifierCount = min(qualifiers().size(), attribute.qualifiers().size());
for (int i = 0; i < qualifierCount; i++) {
CelAttribute.Qualifier pattern = qualifiers().get(i);
CelAttribute.Qualifier qualifier = attribute.qualifiers().get(i);
if (pattern.kind() != CelAttribute.Qualifier.Kind.WILD_CARD && !pattern.equals(qualifier)) {
if (!matches(qualifiers().get(i), attribute.qualifiers().get(i))) {
return false;
}
}
Expand All @@ -165,6 +162,24 @@ public boolean isPartialMatch(CelAttribute attribute) {
return true;
}

// Numeric qualifiers match by value across int and uint, as map keys and list indices do.
private static boolean matches(CelAttribute.Qualifier pattern, CelAttribute.Qualifier qualifier) {
if (pattern.kind() == CelAttribute.Qualifier.Kind.WILD_CARD || pattern.equals(qualifier)) {
return true;
}
if (pattern.kind() == CelAttribute.Qualifier.Kind.AS_INT
&& qualifier.kind() == CelAttribute.Qualifier.Kind.AS_UINT) {
return intEqualsUint(pattern.asInt(), qualifier.asUint());
}
return pattern.kind() == CelAttribute.Qualifier.Kind.AS_UINT
&& qualifier.kind() == CelAttribute.Qualifier.Kind.AS_INT
&& intEqualsUint(qualifier.asInt(), pattern.asUint());
}

private static boolean intEqualsUint(long intValue, UnsignedLong uintValue) {
return intValue >= 0 && UnsignedLong.valueOf(intValue).equals(uintValue);
}

/**
* Return an appropriate attribute for a pattern match.
*
Expand Down
8 changes: 8 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 @@ -320,6 +320,9 @@ java_library(
name = "eval_index",
srcs = ["EvalIndex.java"],
deps = [
":attribute",
":eval_attribute",
":eval_const",
":eval_helpers",
":planned_interpretable",
"//common/ast",
Expand All @@ -329,6 +332,7 @@ java_library(
"//runtime:interpretable",
"//runtime:resolved_overload",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven//:com_google_guava_guava",
],
)

Expand Down Expand Up @@ -985,6 +989,9 @@ cel_android_library(
name = "eval_index_android",
srcs = ["EvalIndex.java"],
deps = [
":attribute_android",
":eval_attribute_android",
":eval_const_android",
":eval_helpers_android",
":planned_interpretable_android",
"//common/ast:ast_android",
Expand All @@ -994,6 +1001,7 @@ cel_android_library(
"//runtime:interpretable_android",
"//runtime:resolved_overload_android",
"@maven//:com_google_errorprone_error_prone_annotations",
"@maven_android//:com_google_guava_guava",
],
)

Expand Down
21 changes: 12 additions & 9 deletions runtime/src/main/java/dev/cel/runtime/planner/EvalHelpers.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,23 +59,26 @@ static Object evalStrictly(
Object val;
try {
val = interpretable.eval(resolver, frame);
} catch (LocalizedEvaluationException e) {
// Already localized - propagate as-is to preserve inner expression ID
throw e;
} catch (CelRuntimeException e) {
// Wrap with current interpretable's location
throw new LocalizedEvaluationException(e, interpretable.expr().id());
} catch (Exception e) {
// Wrap generic exceptions with location
throw new LocalizedEvaluationException(
e, CelErrorCode.INTERNAL_ERROR, interpretable.expr().id());
throw localize(e, interpretable.expr().id());
}
if (val instanceof ErrorValue) {
throw ((ErrorValue) val).value();
}
return val;
}

/** Localizes {@code e} to {@code exprId} unless an inner expression already did. */
static LocalizedEvaluationException localize(Exception e, long exprId) {
if (e instanceof LocalizedEvaluationException) {
return (LocalizedEvaluationException) e;
}
if (e instanceof CelRuntimeException) {
return new LocalizedEvaluationException((CelRuntimeException) e, exprId);
}
return new LocalizedEvaluationException(e, CelErrorCode.INTERNAL_ERROR, exprId);
}

static Object dispatch(
String functionName,
CelResolvedOverload overload,
Expand Down
66 changes: 65 additions & 1 deletion runtime/src/main/java/dev/cel/runtime/planner/EvalIndex.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import static dev.cel.runtime.planner.EvalHelpers.evalNonstrictly;
import static dev.cel.runtime.planner.EvalHelpers.evalStrictly;

import com.google.common.primitives.UnsignedLong;
import com.google.errorprone.annotations.Immutable;
import dev.cel.common.ast.CelExpr;
import dev.cel.common.values.CelValueConverter;
Expand Down Expand Up @@ -53,16 +54,79 @@ Object evalInternal(GlobalResolver resolver, ExecutionFrame frame) throws CelEva
functionName, resolvedOverload, celValueConverter, targetVal, indexVal);
}

static EvalIndex create(
/** Plans {@code target[index]}. */
static PlannedInterpretable create(
CelExpr expr,
String functionName,
CelResolvedOverload resolvedOverload,
PlannedInterpretable target,
PlannedInterpretable index,
CelValueConverter celValueConverter) {
// Qualifies the attribute so unknown patterns match through constant indices, as in CEL-C++.
if (target instanceof EvalAttribute
&& index instanceof EvalConstant
&& resolvedOverload.isStrict()) {
Object constant = ((EvalConstant) index).constantValue();
// Kinds representable as a CelAttribute.Qualifier.
if (constant instanceof Long
|| constant instanceof UnsignedLong
|| constant instanceof String
|| constant instanceof Boolean) {
return ((EvalAttribute) target)
.addQualifier(
expr,
new ConstantIndexQualifier(
expr.id(), resolvedOverload, constant, celValueConverter));
}
}
return new EvalIndex(expr, functionName, resolvedOverload, target, index, celValueConverter);
}

/** Indexes an attribute's value by a constant through the {@code _[_]} overload. */
@Immutable
private static final class ConstantIndexQualifier implements Qualifier {
private final long exprId;
private final CelResolvedOverload resolvedOverload;

@SuppressWarnings("Immutable") // Immutable CEL constant.
private final Object index;

private final CelValueConverter celValueConverter;

@Override
public Object value() {
return index;
}

@Override
public Object qualify(Object operand) {
// Mirrors EvalIndex: the overload receives the same adapted target, and failures are
// localized to the index expression.
try {
return celValueConverter.toTraversalTarget(
EvalHelpers.dispatch(
resolvedOverload.getFunctionName(),
resolvedOverload,
celValueConverter,
EvalHelpers.convertAndAdaptResult(celValueConverter, operand),
index));
} catch (Exception e) {
throw EvalHelpers.localize(e, exprId);
}
}

private ConstantIndexQualifier(
long exprId,
CelResolvedOverload resolvedOverload,
Object index,
CelValueConverter celValueConverter) {
this.exprId = exprId;
this.resolvedOverload = resolvedOverload;
this.index = index;
this.celValueConverter = celValueConverter;
}
}

private EvalIndex(
CelExpr expr,
String functionName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,9 +114,9 @@ public Object resolve(long exprId, GlobalResolver ctx, ExecutionFrame frame) {
}

// The name itself could be a fully qualified reference to an enum value
// (e.g: my.enum_type.BAR)
// (e.g: my.enum_type.BAR), which can't be qualified further.
int lastDotIndex = name.lastIndexOf('.');
if (lastDotIndex > 0) {
if (lastDotIndex > 0 && qualifiers.isEmpty()) {
String enumTypeName = name.substring(0, lastDotIndex);
String enumValueQualifier = name.substring(lastDotIndex + 1);

Expand Down
31 changes: 31 additions & 0 deletions runtime/src/test/java/dev/cel/runtime/CelAttributePatternTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import static org.junit.Assert.assertThrows;

import com.google.common.collect.ImmutableList;
import com.google.common.primitives.UnsignedLong;
import dev.cel.runtime.CelAttribute.Qualifier;
import org.junit.Test;
import org.junit.runner.RunWith;
Expand Down Expand Up @@ -244,6 +245,36 @@ public void isPartialMatch_doesNotMatch() {
.isFalse();
}

@Test
public void isMatch_intAndUintQualifiersWithSameValue_match() {
CelAttributePattern intPattern =
CelAttributePattern.create("identifier").qualify(Qualifier.ofInt(1));
CelAttributePattern uintPattern =
CelAttributePattern.create("identifier").qualify(Qualifier.ofUint(1));
CelAttribute intAttribute = CelAttribute.create("identifier").qualify(Qualifier.ofInt(1));
CelAttribute uintAttribute = CelAttribute.create("identifier").qualify(Qualifier.ofUint(1));

assertThat(intPattern.isMatch(uintAttribute)).isTrue();
assertThat(uintPattern.isMatch(intAttribute)).isTrue();
assertThat(intPattern.isPartialMatch(uintAttribute)).isTrue();
assertThat(uintPattern.isPartialMatch(intAttribute)).isTrue();
}

@Test
public void isMatch_negativeIntAndUintWithSameBits_doNotMatch() {
CelAttributePattern intPattern =
CelAttributePattern.create("identifier").qualify(Qualifier.ofInt(-1));
CelAttributePattern uintPattern =
CelAttributePattern.create("identifier")
.qualify(Qualifier.ofUint(UnsignedLong.fromLongBits(-1)));
CelAttribute intAttribute = CelAttribute.create("identifier").qualify(Qualifier.ofInt(-1));
CelAttribute uintAttribute =
CelAttribute.create("identifier").qualify(Qualifier.ofUint(UnsignedLong.fromLongBits(-1)));

assertThat(intPattern.isMatch(uintAttribute)).isFalse();
assertThat(uintPattern.isMatch(intAttribute)).isFalse();
}

@Test
public void simplify_simplifies() {
assertThat(
Expand Down
Loading
Loading