mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-05 18:24:07 +00:00
Merge pull request #120097 from jpbetz/fix-ppc64le
Fix CEL cost handling of zero length replacement strings
This commit is contained in:
commit
07f47d8b40
@ -1683,17 +1683,19 @@ func TestCostEstimation(t *testing.T) {
|
|||||||
name: "extended library replace",
|
name: "extended library replace",
|
||||||
schemaGenerator: func(max *int64) *schema.Structural {
|
schemaGenerator: func(max *int64) *schema.Structural {
|
||||||
strType := withMaxLength(primitiveType("string", ""), max)
|
strType := withMaxLength(primitiveType("string", ""), max)
|
||||||
|
beforeLen := int64(2)
|
||||||
|
afterLen := int64(4)
|
||||||
objType := objectType(map[string]schema.Structural{
|
objType := objectType(map[string]schema.Structural{
|
||||||
"str": strType,
|
"str": strType,
|
||||||
"before": strType,
|
"before": withMaxLength(primitiveType("string", ""), &beforeLen),
|
||||||
"after": strType,
|
"after": withMaxLength(primitiveType("string", ""), &afterLen),
|
||||||
})
|
})
|
||||||
objType = withRule(objType, "self.str.replace(self.before, self.after) == 'does not matter'")
|
objType = withRule(objType, "self.str.replace(self.before, self.after) == 'does not matter'")
|
||||||
return &objType
|
return &objType
|
||||||
},
|
},
|
||||||
expectedCalcCost: 629152, // cost is based on the result size of the replace() call
|
expectedCalcCost: 629154, // cost is based on the result size of the replace() call
|
||||||
setMaxElements: 10,
|
setMaxElements: 4,
|
||||||
expectedSetCost: 14,
|
expectedSetCost: 12,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "extended library split",
|
name: "extended library split",
|
||||||
|
@ -126,13 +126,51 @@ func (l *CostEstimator) EstimateCallCost(function, overloadId string, target *ch
|
|||||||
sz := l.sizeEstimate(*target)
|
sz := l.sizeEstimate(*target)
|
||||||
toReplaceSz := l.sizeEstimate(args[0])
|
toReplaceSz := l.sizeEstimate(args[0])
|
||||||
replaceWithSz := l.sizeEstimate(args[1])
|
replaceWithSz := l.sizeEstimate(args[1])
|
||||||
// smallest possible result: smallest input size composed of the largest possible substrings being replaced by smallest possible replacement
|
|
||||||
minSz := uint64(math.Ceil(float64(sz.Min)/float64(toReplaceSz.Max))) * replaceWithSz.Min
|
var replaceCount, retainedSz checker.SizeEstimate
|
||||||
// largest possible result: largest input size composed of the smallest possible substrings being replaced by largest possible replacement
|
// find the longest replacement:
|
||||||
maxSz := uint64(math.Ceil(float64(sz.Max)/float64(toReplaceSz.Min))) * replaceWithSz.Max
|
if toReplaceSz.Min == 0 {
|
||||||
|
// if the string being replaced is empty, replace surrounds all characters in the input string with the replacement.
|
||||||
|
if sz.Max < math.MaxUint64 {
|
||||||
|
replaceCount.Max = sz.Max + 1
|
||||||
|
} else {
|
||||||
|
replaceCount.Max = sz.Max
|
||||||
|
}
|
||||||
|
// Include the length of the longest possible original string length.
|
||||||
|
retainedSz.Max = sz.Max
|
||||||
|
} else if replaceWithSz.Max <= toReplaceSz.Min {
|
||||||
|
// If the replacement does not make the result longer, use the original string length.
|
||||||
|
replaceCount.Max = 0
|
||||||
|
retainedSz.Max = sz.Max
|
||||||
|
} else {
|
||||||
|
// Replace the smallest possible substrings with the largest possible replacement
|
||||||
|
// as many times as possible.
|
||||||
|
replaceCount.Max = uint64(math.Ceil(float64(sz.Max) / float64(toReplaceSz.Min)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// find the shortest replacement:
|
||||||
|
if toReplaceSz.Max == 0 {
|
||||||
|
// if the string being replaced is empty, replace surrounds all characters in the input string with the replacement.
|
||||||
|
if sz.Min < math.MaxUint64 {
|
||||||
|
replaceCount.Min = sz.Min + 1
|
||||||
|
} else {
|
||||||
|
replaceCount.Min = sz.Min
|
||||||
|
}
|
||||||
|
// Include the length of the shortest possible original string length.
|
||||||
|
retainedSz.Min = sz.Min
|
||||||
|
} else if toReplaceSz.Max <= replaceWithSz.Min {
|
||||||
|
// If the replacement does not make the result shorter, use the original string length.
|
||||||
|
replaceCount.Min = 0
|
||||||
|
retainedSz.Min = sz.Min
|
||||||
|
} else {
|
||||||
|
// Replace the largest possible substrings being with the smallest possible replacement
|
||||||
|
// as many times as possible.
|
||||||
|
replaceCount.Min = uint64(math.Ceil(float64(sz.Min) / float64(toReplaceSz.Max)))
|
||||||
|
}
|
||||||
|
size := replaceCount.Multiply(replaceWithSz).Add(retainedSz)
|
||||||
|
|
||||||
// cost is the traversal plus the construction of the result
|
// cost is the traversal plus the construction of the result
|
||||||
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor), ResultSize: &checker.SizeEstimate{Min: minSz, Max: maxSz}}
|
return &checker.CallEstimate{CostEstimate: sz.MultiplyByCostFactor(2 * common.StringTraversalCostFactor), ResultSize: &size}
|
||||||
}
|
}
|
||||||
case "split":
|
case "split":
|
||||||
if target != nil {
|
if target != nil {
|
||||||
|
@ -263,6 +263,18 @@ func TestStringLibrary(t *testing.T) {
|
|||||||
expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
|
expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
|
||||||
expectRuntimeCost: 3,
|
expectRuntimeCost: 3,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "replace between all chars",
|
||||||
|
expr: "'abc 123 def 123'.replace('', 'x')",
|
||||||
|
expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
|
||||||
|
expectRuntimeCost: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "replace with empty",
|
||||||
|
expr: "'abc 123 def 123'.replace('123', '')",
|
||||||
|
expectEsimatedCost: checker.CostEstimate{Min: 3, Max: 3},
|
||||||
|
expectRuntimeCost: 3,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "replace with limit",
|
name: "replace with limit",
|
||||||
expr: "'abc 123 def 123'.replace('123', '456', 1)",
|
expr: "'abc 123 def 123'.replace('123', '456', 1)",
|
||||||
@ -437,6 +449,107 @@ func testCost(t *testing.T, expr string, expectEsimatedCost checker.CostEstimate
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSize(t *testing.T) {
|
||||||
|
exactSize := func(size int) checker.SizeEstimate {
|
||||||
|
return checker.SizeEstimate{Min: uint64(size), Max: uint64(size)}
|
||||||
|
}
|
||||||
|
exactSizes := func(sizes ...int) []checker.SizeEstimate {
|
||||||
|
results := make([]checker.SizeEstimate, len(sizes))
|
||||||
|
for i, size := range sizes {
|
||||||
|
results[i] = exactSize(size)
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
function string
|
||||||
|
overload string
|
||||||
|
targetSize checker.SizeEstimate
|
||||||
|
argSizes []checker.SizeEstimate
|
||||||
|
expectSize checker.SizeEstimate
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "replace empty with char",
|
||||||
|
function: "replace",
|
||||||
|
targetSize: exactSize(3), // e.g. abc
|
||||||
|
argSizes: exactSizes(0, 1), // e.g. replace "" with "_"
|
||||||
|
expectSize: exactSize(7), // e.g. _a_b_c_
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "maybe replace char with empty",
|
||||||
|
function: "replace",
|
||||||
|
targetSize: exactSize(3),
|
||||||
|
argSizes: exactSizes(1, 0),
|
||||||
|
expectSize: checker.SizeEstimate{Min: 0, Max: 3},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "maybe replace repeated",
|
||||||
|
function: "replace",
|
||||||
|
targetSize: exactSize(4),
|
||||||
|
argSizes: exactSizes(2, 4),
|
||||||
|
expectSize: checker.SizeEstimate{Min: 4, Max: 8},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "maybe replace empty",
|
||||||
|
function: "replace",
|
||||||
|
targetSize: exactSize(4),
|
||||||
|
argSizes: []checker.SizeEstimate{{Min: 0, Max: 1}, {Min: 0, Max: 2}},
|
||||||
|
expectSize: checker.SizeEstimate{Min: 0, Max: 14}, // len(__a__a__a__a__) == 14
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "replace non-empty size range, maybe larger",
|
||||||
|
function: "replace",
|
||||||
|
targetSize: exactSize(4),
|
||||||
|
argSizes: []checker.SizeEstimate{{Min: 1, Max: 1}, {Min: 1, Max: 2}},
|
||||||
|
expectSize: checker.SizeEstimate{Min: 4, Max: 8},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "replace non-empty size range, maybe smaller",
|
||||||
|
function: "replace",
|
||||||
|
targetSize: exactSize(4),
|
||||||
|
argSizes: []checker.SizeEstimate{{Min: 1, Max: 2}, {Min: 1, Max: 1}},
|
||||||
|
expectSize: checker.SizeEstimate{Min: 2, Max: 4},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
est := &CostEstimator{SizeEstimator: &testCostEstimator{}}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var targetNode checker.AstNode = testSizeNode{size: tc.targetSize}
|
||||||
|
argNodes := make([]checker.AstNode, len(tc.argSizes))
|
||||||
|
for i, arg := range tc.argSizes {
|
||||||
|
argNodes[i] = testSizeNode{size: arg}
|
||||||
|
}
|
||||||
|
result := est.EstimateCallCost(tc.function, tc.overload, &targetNode, argNodes)
|
||||||
|
if result.ResultSize == nil {
|
||||||
|
t.Fatalf("Expected ResultSize but got none")
|
||||||
|
}
|
||||||
|
if *result.ResultSize != tc.expectSize {
|
||||||
|
t.Fatalf("Expected %+v but got %+v", tc.expectSize, *result.ResultSize)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testSizeNode struct {
|
||||||
|
size checker.SizeEstimate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testSizeNode) Path() []string {
|
||||||
|
return nil // not needed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testSizeNode) Type() *expr.Type {
|
||||||
|
return nil // not needed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testSizeNode) Expr() *expr.Expr {
|
||||||
|
return nil // not needed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t testSizeNode) ComputedSize() *checker.SizeEstimate {
|
||||||
|
return &t.size
|
||||||
|
}
|
||||||
|
|
||||||
type testCostEstimator struct {
|
type testCostEstimator struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user