mirror of
https://github.com/mikefarah/yq.git
synced 2026-06-28 07:57:43 +00:00
recurseNodeObjectEqual and containsObject both used findInArray to
locate keys in a MappingNode's Content array. findInArray steps by 1,
so it matches against both keys (even indices) and values (odd indices).
In recurseNodeObjectEqual, when a null key in the LHS matched a null
value in the RHS at the last position, rhs.Content[indexInRHS+1]
accessed an out-of-bounds index, causing a panic.
In containsObject, a %2 guard prevented the panic but introduced false
negatives: when a null value appeared before the actual null key,
findInArray returned the value's odd index, the guard rejected it, and
the function reported the key as missing.
Both functions now use findKeyInMap, which steps by 2 and compares only
key positions. The %2 guard in containsObject is removed.
Reproducer for the panic (recurseNodeObjectEqual):
echo '? [{~: ~}]
: v1
? [{2: ~}]
: v2' | yq '. += .'
Reproducer for the false negative (containsObject):
printf '? 1\n: ~\n? ~\n: x\n' | yq 'contains({~: "x"})'
Found by OSS-Fuzz via the lima project's FuzzEvaluateExpression target.
https://issues.oss-fuzz.com/issues/383860504
Signed-off-by: Jan Dubois <jan@jandubois.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
107 lines
2.7 KiB
Go
107 lines
2.7 KiB
Go
package yqlib
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
func containsOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) {
|
|
return crossFunction(d, context.ReadOnlyClone(), expressionNode, containsWithNodes, false)
|
|
}
|
|
|
|
func containsArrayElement(array *CandidateNode, item *CandidateNode) (bool, error) {
|
|
for index := 0; index < len(array.Content); index = index + 1 {
|
|
containedInArray, err := contains(array.Content[index], item)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if containedInArray {
|
|
return true, nil
|
|
}
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func containsArray(lhs *CandidateNode, rhs *CandidateNode) (bool, error) {
|
|
if rhs.Kind != SequenceNode {
|
|
return containsArrayElement(lhs, rhs)
|
|
}
|
|
for index := 0; index < len(rhs.Content); index = index + 1 {
|
|
itemInArray, err := containsArrayElement(lhs, rhs.Content[index])
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if !itemInArray {
|
|
return false, nil
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func containsObject(lhs *CandidateNode, rhs *CandidateNode) (bool, error) {
|
|
if rhs.Kind != MappingNode {
|
|
return false, nil
|
|
}
|
|
for index := 0; index < len(rhs.Content); index = index + 2 {
|
|
rhsKey := rhs.Content[index]
|
|
rhsValue := rhs.Content[index+1]
|
|
log.Debugf("Looking for %v in the lhs", rhsKey.Value)
|
|
lhsKeyIndex := findKeyInMap(lhs, rhsKey)
|
|
log.Debugf("index is %v", lhsKeyIndex)
|
|
if lhsKeyIndex < 0 {
|
|
return false, nil
|
|
}
|
|
lhsValue := lhs.Content[lhsKeyIndex+1]
|
|
log.Debugf("lhsValue is %v", lhsValue.Value)
|
|
|
|
itemInArray, err := contains(lhsValue, rhsValue)
|
|
log.Debugf("rhsValue is %v", rhsValue.Value)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if !itemInArray {
|
|
return false, nil
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
func containsScalars(lhs *CandidateNode, rhs *CandidateNode) (bool, error) {
|
|
if lhs.Tag == "!!str" {
|
|
return strings.Contains(lhs.Value, rhs.Value), nil
|
|
}
|
|
return lhs.Value == rhs.Value, nil
|
|
}
|
|
|
|
func contains(lhs *CandidateNode, rhs *CandidateNode) (bool, error) {
|
|
switch lhs.Kind {
|
|
case MappingNode:
|
|
return containsObject(lhs, rhs)
|
|
case SequenceNode:
|
|
return containsArray(lhs, rhs)
|
|
case ScalarNode:
|
|
if rhs.Kind != ScalarNode || lhs.Tag != rhs.Tag {
|
|
return false, nil
|
|
}
|
|
if lhs.Tag == "!!null" {
|
|
return rhs.Tag == "!!null", nil
|
|
}
|
|
return containsScalars(lhs, rhs)
|
|
}
|
|
|
|
return false, fmt.Errorf("%v not yet supported for contains", lhs.Tag)
|
|
}
|
|
|
|
func containsWithNodes(_ *dataTreeNavigator, _ Context, lhs *CandidateNode, rhs *CandidateNode) (*CandidateNode, error) {
|
|
if lhs.Kind != rhs.Kind {
|
|
return nil, fmt.Errorf("%v cannot check contained in %v", rhs.Tag, lhs.Tag)
|
|
}
|
|
|
|
result, err := contains(lhs, rhs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return createBooleanCandidate(lhs, result), nil
|
|
}
|