Skip to content

Commit 3e5240f

Browse files
committed
parser: avoid reparsing flow sequence pair keys
When a flow sequence item is detected as a key/value pair, wrap the already parsed key events in a synthetic flow mapping event instead of rewinding and parsing the key again. Nested flow sequence pairs could otherwise trigger exponential parsing time before construction rejected the resulting complex object key.
1 parent bd7ebb2 commit 3e5240f

2 files changed

Lines changed: 24 additions & 11 deletions

File tree

src/parser/parser.ts

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,18 @@ function addMappingEvent (
148148
})
149149
}
150150

151+
function insertFlowPairMappingEvent (state: ParserState, snapshot: ParserSnapshot) {
152+
state.events.splice(snapshot.eventsLength, 0, {
153+
type: EVENT_MAPPING,
154+
start: snapshot.position,
155+
anchorStart: NO_RANGE,
156+
anchorEnd: NO_RANGE,
157+
tagStart: NO_RANGE,
158+
tagEnd: NO_RANGE,
159+
style: COLLECTION_STYLE_FLOW
160+
})
161+
}
162+
151163
function addScalarEvent (
152164
state: ParserState,
153165
valueStart: number,
@@ -911,14 +923,8 @@ function readFlowCollection (state: ParserState, nodeIndent: number, props: Node
911923
state.position++
912924
skipFlowSeparationSpace(state, nodeIndent)
913925
if (!isMapping) {
914-
restoreState(state, entryStart)
915-
addMappingEvent(state, entryStart.position, NO_RANGE, NO_RANGE, NO_RANGE, NO_RANGE, COLLECTION_STYLE_FLOW)
916-
if (!parseNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true)) {
917-
addEmptyScalarEvent(state)
918-
}
919-
skipFlowSeparationSpace(state, nodeIndent)
920-
state.position++
921-
skipFlowSeparationSpace(state, nodeIndent)
926+
insertFlowPairMappingEvent(state, entryStart)
927+
if (!keyWasRead) addEmptyScalarEvent(state)
922928
} else if (!keyWasRead) {
923929
addEmptyScalarEvent(state)
924930
}
@@ -933,9 +939,8 @@ function readFlowCollection (state: ParserState, nodeIndent: number, props: Node
933939
} else if (isMapping) {
934940
addEmptyScalarEvent(state)
935941
} else if (isPair) {
936-
restoreState(state, entryStart)
937-
addMappingEvent(state, entryStart.position, NO_RANGE, NO_RANGE, NO_RANGE, NO_RANGE, COLLECTION_STYLE_FLOW)
938-
parseNode(state, nodeIndent, CONTEXT_FLOW_IN, false, true)
942+
insertFlowPairMappingEvent(state, entryStart)
943+
if (!keyWasRead) addEmptyScalarEvent(state)
939944
addEmptyScalarEvent(state)
940945
addPopEvent(state)
941946
}

test/core/pathological.test.mjs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ describe('Pathological tests', () => {
4141
})
4242
})
4343

44+
describe('Flow collection pairs', () => {
45+
it('throws YAMLException on nested flow pairs without reparsing keys exponentially', () => {
46+
assertYamlException(() => {
47+
load('[ '.repeat(40) + '1' + ' ]: 0'.repeat(40))
48+
}, /object-based map does not support complex keys/)
49+
})
50+
})
51+
4452
describe('Merge aliases', () => {
4553
it('throws YAMLException when merge chain exceeds maxTotalMergeKeys', () => {
4654
assertYamlException(() => {

0 commit comments

Comments
 (0)