diff --git a/pkg/yqlib/operator_slice.go b/pkg/yqlib/operator_slice.go index 42606134..363322d0 100644 --- a/pkg/yqlib/operator_slice.go +++ b/pkg/yqlib/operator_slice.go @@ -16,6 +16,21 @@ func getSliceNumber(d *dataTreeNavigator, context Context, node *CandidateNode, return parseInt(result.MatchingNodes.Front().Value.(*CandidateNode).Value) } +// clampSliceIndex resolves a possibly-negative slice index against +// length and clamps the result to [0, length]. +func clampSliceIndex(index, length int) int { + if index < 0 { + index += length + } + if index < 0 { + return 0 + } + if index > length { + return length + } + return index +} + func sliceArrayOperator(d *dataTreeNavigator, context Context, expressionNode *ExpressionNode) (Context, error) { log.Debug("slice array operator!") @@ -32,22 +47,13 @@ func sliceArrayOperator(d *dataTreeNavigator, context Context, expressionNode *E if err != nil { return Context{}, err } - relativeFirstNumber := firstNumber - if relativeFirstNumber < 0 { - relativeFirstNumber = len(lhsNode.Content) + firstNumber - } + relativeFirstNumber := clampSliceIndex(firstNumber, len(lhsNode.Content)) secondNumber, err := getSliceNumber(d, context, lhsNode, expressionNode.RHS) if err != nil { return Context{}, err } - - relativeSecondNumber := secondNumber - if relativeSecondNumber < 0 { - relativeSecondNumber = len(lhsNode.Content) + secondNumber - } else if relativeSecondNumber > len(lhsNode.Content) { - relativeSecondNumber = len(lhsNode.Content) - } + relativeSecondNumber := clampSliceIndex(secondNumber, len(lhsNode.Content)) log.Debugf("calculateIndicesToTraverse: slice from %v to %v", relativeFirstNumber, relativeSecondNumber) diff --git a/pkg/yqlib/operator_slice_test.go b/pkg/yqlib/operator_slice_test.go index 29a821eb..c06bf44e 100644 --- a/pkg/yqlib/operator_slice_test.go +++ b/pkg/yqlib/operator_slice_test.go @@ -98,6 +98,37 @@ var sliceArrayScenarios = []expressionScenario{ "D0, P[], (!!seq)::- cat1\n", }, }, + { + // Regression test for https://issues.oss-fuzz.com/issues/438776028 + // Negative second index that underflows after adjustment must + // clamp to zero, yielding an empty sequence. + skipDoc: true, + document: `[a, b, c]`, + expression: `.[0:-99999]`, + expected: []string{ + "D0, P[], (!!seq)::[]\n", + }, + }, + { + // First-index underflow: without clamping, the loop starts at a + // negative index and panics on Content access. + skipDoc: true, + document: `[a, b, c]`, + expression: `.[-99999:3]`, + expected: []string{ + "D0, P[], (!!seq)::- a\n- b\n- c\n", + }, + }, + { + // Both indices underflow: both clamp to zero, yielding an empty + // sequence. + skipDoc: true, + document: `[a, b, c]`, + expression: `.[-99999:-99998]`, + expected: []string{ + "D0, P[], (!!seq)::[]\n", + }, + }, } func TestSliceOperatorScenarios(t *testing.T) {