Skip to content

Commit 2628ca8

Browse files
Merge branch 'master' into vanity-cel-go-import
2 parents fc6a53c + 4715f2f commit 2628ca8

72 files changed

Lines changed: 6509 additions & 358 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

MODULE.bazel

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,20 @@ bazel_dep(
3737
bazel_dep(name = "rules_shell", version = "0.6.1")
3838
bazel_dep(name = "rules_license", version = "1.0.0")
3939

40-
4140
# local_path_override(
4241
# module_name = "cel-spec",
4342
# path = "../cel-spec",
4443
# )
4544
bazel_dep(
4645
name = "cel-spec",
47-
version = "0.25.1",
46+
version = "0.25.2",
4847
repo_name = "dev_cel_expr",
4948
)
49+
git_override(
50+
module_name = "cel-spec",
51+
commit = "ba58ae5007845f3a1279b488cdeb79645ce958bb",
52+
remote = "https://github.com/cel-expr/cel-spec",
53+
)
5054

5155
go_sdk = use_extension("@io_bazel_rules_go//go:extensions.bzl", "go_sdk")
5256
go_sdk.download(version = "1.23.0")

cel/cel_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1683,6 +1683,64 @@ func TestVariadicLogicalOperators(t *testing.T) {
16831683
}
16841684
}
16851685

1686+
func TestCostTrackingWithStateTracking(t *testing.T) {
1687+
// Cost tracking and state tracking install separate observers. Every observer has to see
1688+
// every evaluation step, whichever combination of them is configured.
1689+
env := testEnv(t, Variable("a", StringType))
1690+
ast, iss := env.Compile(`a.startsWith("x") && a.contains("yz")`)
1691+
if iss.Err() != nil {
1692+
t.Fatalf("env.Compile() failed: %v", iss.Err())
1693+
}
1694+
baseline, _ := evalCostAndState(t, env, ast, CostTracking(nil))
1695+
if baseline == 0 {
1696+
t.Fatalf("cost tracking alone reported a cost of 0")
1697+
}
1698+
tests := []struct {
1699+
name string
1700+
opts []ProgramOption
1701+
wantState bool
1702+
wantEqCost bool
1703+
}{
1704+
{name: "cost", opts: []ProgramOption{CostTracking(nil)}, wantEqCost: true},
1705+
{name: "cost and state", opts: []ProgramOption{CostTracking(nil), EvalOptions(OptTrackState)},
1706+
wantState: true, wantEqCost: true},
1707+
{name: "cost and exhaustive", opts: []ProgramOption{CostTracking(nil), EvalOptions(OptExhaustiveEval)},
1708+
wantState: true, wantEqCost: true},
1709+
}
1710+
for _, tst := range tests {
1711+
tc := tst
1712+
t.Run(tc.name, func(t *testing.T) {
1713+
cost, hasState := evalCostAndState(t, env, ast, tc.opts...)
1714+
if tc.wantEqCost && cost != baseline {
1715+
t.Errorf("actual cost got %d, wanted %d", cost, baseline)
1716+
}
1717+
if hasState != tc.wantState {
1718+
t.Errorf("state tracked got %t, wanted %t", hasState, tc.wantState)
1719+
}
1720+
})
1721+
}
1722+
}
1723+
1724+
// evalCostAndState evaluates the ast and reports the tracked cost along with whether evaluation
1725+
// state was recorded.
1726+
func evalCostAndState(t *testing.T, env *Env, ast *Ast, opts ...ProgramOption) (uint64, bool) {
1727+
t.Helper()
1728+
prg, err := env.Program(ast, opts...)
1729+
if err != nil {
1730+
t.Fatalf("env.Program() failed: %v", err)
1731+
}
1732+
_, det, err := prg.Eval(map[string]any{"a": "xyz-abcdefghij"})
1733+
if err != nil {
1734+
t.Fatalf("prg.Eval() failed: %v", err)
1735+
}
1736+
cost := det.ActualCost()
1737+
if cost == nil {
1738+
t.Fatalf("det.ActualCost() returned nil")
1739+
}
1740+
state := det.State()
1741+
return *cost, state != nil && len(state.IDs()) != 0
1742+
}
1743+
16861744
func TestParseError(t *testing.T) {
16871745
env := testEnv(t)
16881746
_, iss := env.Parse("invalid & logical_and")

checker/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ go_library(
2525
"//common:go_default_library",
2626
"//common/ast:go_default_library",
2727
"//common/containers:go_default_library",
28+
"//common/cost:go_default_library",
2829
"//common/debug:go_default_library",
2930
"//common/decls:go_default_library",
3031
"//common/operators:go_default_library",

checker/cost.go

Lines changed: 35 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919

2020
"cel.dev/cel-go/common"
2121
"cel.dev/cel-go/common/ast"
22+
"cel.dev/cel-go/common/cost"
2223
"cel.dev/cel-go/common/overloads"
2324
"cel.dev/cel-go/common/types"
2425
"cel.dev/cel-go/parser"
@@ -115,35 +116,35 @@ func FixedSizeEstimate(size uint64) SizeEstimate {
115116
// If add would result in an uint64 overflow, the result is math.MaxUint64.
116117
func (se SizeEstimate) Add(sizeEstimate SizeEstimate) SizeEstimate {
117118
return SizeEstimate{
118-
addUint64NoOverflow(se.Min, sizeEstimate.Min),
119-
addUint64NoOverflow(se.Max, sizeEstimate.Max),
119+
cost.SafeAdd(se.Min, sizeEstimate.Min),
120+
cost.SafeAdd(se.Max, sizeEstimate.Max),
120121
}
121122
}
122123

123124
// Multiply multiplies by another SizeEstimate and returns the product.
124125
// If multiply would result in an uint64 overflow, the result is math.MaxUint64.
125126
func (se SizeEstimate) Multiply(sizeEstimate SizeEstimate) SizeEstimate {
126127
return SizeEstimate{
127-
multiplyUint64NoOverflow(se.Min, sizeEstimate.Min),
128-
multiplyUint64NoOverflow(se.Max, sizeEstimate.Max),
128+
cost.SafeMultiply(se.Min, sizeEstimate.Min),
129+
cost.SafeMultiply(se.Max, sizeEstimate.Max),
129130
}
130131
}
131132

132133
// MultiplyByCostFactor multiplies a SizeEstimate by a cost factor and returns the CostEstimate with the
133134
// nearest integer of the result, rounded up.
134135
func (se SizeEstimate) MultiplyByCostFactor(costPerUnit float64) CostEstimate {
135136
return CostEstimate{
136-
multiplyByCostFactor(se.Min, costPerUnit),
137-
multiplyByCostFactor(se.Max, costPerUnit),
137+
cost.SafeMultiplyByFactor(se.Min, costPerUnit),
138+
cost.SafeMultiplyByFactor(se.Max, costPerUnit),
138139
}
139140
}
140141

141142
// MultiplyByCost multiplies by the cost and returns the product.
142143
// If multiply would result in an uint64 overflow, the result is math.MaxUint64.
143-
func (se SizeEstimate) MultiplyByCost(cost CostEstimate) CostEstimate {
144+
func (se SizeEstimate) MultiplyByCost(estimate CostEstimate) CostEstimate {
144145
return CostEstimate{
145-
multiplyUint64NoOverflow(se.Min, cost.Min),
146-
multiplyUint64NoOverflow(se.Max, cost.Max),
146+
cost.SafeMultiply(se.Min, estimate.Min),
147+
cost.SafeMultiply(se.Max, estimate.Max),
147148
}
148149
}
149150

@@ -176,34 +177,34 @@ func UnknownCostEstimate() CostEstimate {
176177
}
177178

178179
// FixedCostEstimate returns a cost with a fixed min and max range.
179-
func FixedCostEstimate(cost uint64) CostEstimate {
180-
return CostEstimate{Min: cost, Max: cost}
180+
func FixedCostEstimate(fixedCost uint64) CostEstimate {
181+
return CostEstimate{Min: fixedCost, Max: fixedCost}
181182
}
182183

183184
// Add adds the costs and returns the sum.
184185
// If add would result in an uint64 overflow for the min or max, the value is set to math.MaxUint64.
185-
func (ce CostEstimate) Add(cost CostEstimate) CostEstimate {
186+
func (ce CostEstimate) Add(estimate CostEstimate) CostEstimate {
186187
return CostEstimate{
187-
Min: addUint64NoOverflow(ce.Min, cost.Min),
188-
Max: addUint64NoOverflow(ce.Max, cost.Max),
188+
Min: cost.SafeAdd(ce.Min, estimate.Min),
189+
Max: cost.SafeAdd(ce.Max, estimate.Max),
189190
}
190191
}
191192

192193
// Multiply multiplies by the cost and returns the product.
193194
// If multiply would result in an uint64 overflow, the result is math.MaxUint64.
194-
func (ce CostEstimate) Multiply(cost CostEstimate) CostEstimate {
195+
func (ce CostEstimate) Multiply(estimate CostEstimate) CostEstimate {
195196
return CostEstimate{
196-
Min: multiplyUint64NoOverflow(ce.Min, cost.Min),
197-
Max: multiplyUint64NoOverflow(ce.Max, cost.Max),
197+
Min: cost.SafeMultiply(ce.Min, estimate.Min),
198+
Max: cost.SafeMultiply(ce.Max, estimate.Max),
198199
}
199200
}
200201

201202
// MultiplyByCostFactor multiplies a CostEstimate by a cost factor and returns the CostEstimate with the
202203
// nearest integer of the result, rounded up.
203204
func (ce CostEstimate) MultiplyByCostFactor(costPerUnit float64) CostEstimate {
204205
return CostEstimate{
205-
Min: multiplyByCostFactor(ce.Min, costPerUnit),
206-
Max: multiplyByCostFactor(ce.Max, costPerUnit),
206+
Min: cost.SafeMultiplyByFactor(ce.Min, costPerUnit),
207+
Max: cost.SafeMultiplyByFactor(ce.Max, costPerUnit),
207208
}
208209
}
209210

@@ -219,37 +220,6 @@ func (ce CostEstimate) Union(size CostEstimate) CostEstimate {
219220
return result
220221
}
221222

222-
// addUint64NoOverflow adds non-negative ints. If the result is exceeds math.MaxUint64, math.MaxUint64
223-
// is returned.
224-
func addUint64NoOverflow(x, y uint64) uint64 {
225-
if y > 0 && x > math.MaxUint64-y {
226-
return math.MaxUint64
227-
}
228-
return x + y
229-
}
230-
231-
// multiplyUint64NoOverflow multiplies non-negative ints. If the result is exceeds math.MaxUint64, math.MaxUint64
232-
// is returned.
233-
func multiplyUint64NoOverflow(x, y uint64) uint64 {
234-
if y != 0 && x > math.MaxUint64/y {
235-
return math.MaxUint64
236-
}
237-
return x * y
238-
}
239-
240-
// multiplyByFactor multiplies an integer by a cost factor float and returns the nearest integer value, rounded up.
241-
func multiplyByCostFactor(x uint64, y float64) uint64 {
242-
xFloat := float64(x)
243-
if xFloat > 0 && y > 0 && xFloat > math.MaxUint64/y {
244-
return math.MaxUint64
245-
}
246-
ceil := math.Ceil(xFloat * y)
247-
if ceil >= doubleTwoTo64 {
248-
return math.MaxUint64
249-
}
250-
return uint64(ceil)
251-
}
252-
253223
// CostOption configures flags which affect cost computations.
254224
type CostOption func(*coster) error
255225

@@ -465,32 +435,32 @@ func (c *coster) cost(e ast.Expr) CostEstimate {
465435
if e == nil {
466436
return CostEstimate{}
467437
}
468-
var cost CostEstimate
438+
var estimate CostEstimate
469439
switch e.Kind() {
470440
case ast.LiteralKind:
471-
cost = constCost
441+
estimate = constCost
472442
case ast.IdentKind:
473-
cost = c.costIdent(e)
443+
estimate = c.costIdent(e)
474444
case ast.SelectKind:
475-
cost = c.costSelect(e)
445+
estimate = c.costSelect(e)
476446
case ast.CallKind:
477-
cost = c.costCall(e)
447+
estimate = c.costCall(e)
478448
case ast.ListKind:
479-
cost = c.costCreateList(e)
449+
estimate = c.costCreateList(e)
480450
case ast.MapKind:
481-
cost = c.costCreateMap(e)
451+
estimate = c.costCreateMap(e)
482452
case ast.StructKind:
483-
cost = c.costCreateStruct(e)
453+
estimate = c.costCreateStruct(e)
484454
case ast.ComprehensionKind:
485455
if c.isBind(e) {
486-
cost = c.costBind(e)
456+
estimate = c.costBind(e)
487457
} else {
488-
cost = c.costComprehension(e)
458+
estimate = c.costComprehension(e)
489459
}
490460
default:
491461
return CostEstimate{}
492462
}
493-
return cost
463+
return estimate
494464
}
495465

496466
func (c *coster) costIdent(e ast.Expr) CostEstimate {
@@ -1013,14 +983,14 @@ func computeExprSize(expr ast.Expr) *SizeEstimate {
1013983
default:
1014984
return nil
1015985
}
1016-
cost := FixedSizeEstimate(v)
1017-
return &cost
986+
size := FixedSizeEstimate(v)
987+
return &size
1018988
}
1019989

1020990
func computeTypeSize(t *types.Type) *SizeEstimate {
1021991
if isScalar(t) {
1022-
cost := FixedSizeEstimate(1)
1023-
return &cost
992+
size := FixedSizeEstimate(1)
993+
return &size
1024994
}
1025995
return nil
1026996
}
@@ -1041,8 +1011,6 @@ func isScalar(t *types.Type) bool {
10411011
}
10421012

10431013
var (
1044-
doubleTwoTo64 = math.Ldexp(1.0, 64)
1045-
10461014
unknownSizeEstimate = SizeEstimate{Min: 0, Max: math.MaxUint64}
10471015
unknownCostEstimate = unknownSizeEstimate.MultiplyByCostFactor(1)
10481016

common/cost/BUILD.bazel

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
package(
4+
default_visibility = ["//visibility:public"],
5+
licenses = ["notice"], # Apache 2.0
6+
)
7+
8+
go_library(
9+
name = "go_default_library",
10+
srcs = [
11+
"cost.go",
12+
],
13+
importpath = "cel.dev/cel-go/common/cost",
14+
)
15+
16+
go_test(
17+
name = "go_default_test",
18+
size = "small",
19+
srcs = [
20+
"cost_test.go",
21+
],
22+
embed = [
23+
":go_default_library",
24+
],
25+
)

0 commit comments

Comments
 (0)