fix: handle prototype-colliding segment names in segment explorer trie (#95403)

Fixes: https://github.com/vercel/next.js/issues/95395

```console
let x = {}
x['constructor']; // function
x['toString']; // function
x = Object.create(null);
x['constructor']; // undefined
x['toString'] // undefined
```
This commit is contained in:
Joseph
2026-07-02 14:35:51 +02:00
committed by GitHub
parent c099530eb7
commit 0d4df38b02
2 changed files with 83 additions and 2 deletions
@@ -105,6 +105,85 @@ describe('Segment Explorer', () => {
})
})
test('handle segments that collide with Object.prototype members', () => {
insertSegmentNode(
createSegmentNode({ pagePath: '/constructor/page.js', type: 'page' })
)
insertSegmentNode(
createSegmentNode({ pagePath: '/toString/page.js', type: 'page' })
)
const { result } = renderHook(useSegmentTree)
expect(result.current).toEqual({
children: {
'': {
children: {
constructor: {
children: {
'page.js': {
children: {},
value: {
pagePath: '/constructor/page.js',
type: 'page',
boundaryType: null,
setBoundaryType: expect.anything(),
},
},
},
value: undefined,
},
toString: {
children: {
'page.js': {
children: {},
value: {
pagePath: '/toString/page.js',
type: 'page',
boundaryType: null,
setBoundaryType: expect.anything(),
},
},
},
value: undefined,
},
},
value: undefined,
},
},
value: undefined,
})
removeSegmentNode(
createSegmentNode({ pagePath: '/constructor/page.js', type: 'page' })
)
expect(result.current).toEqual({
children: {
'': {
children: {
toString: {
children: {
'page.js': {
children: {},
value: {
pagePath: '/toString/page.js',
type: 'page',
boundaryType: null,
setBoundaryType: expect.anything(),
},
},
},
value: undefined,
},
},
value: undefined,
},
},
value: undefined,
})
})
test('remove node in the middle', () => {
insertSegmentNode(
createSegmentNode({ pagePath: '/a/b/@sidebar/page.js', type: 'page' })
@@ -62,9 +62,11 @@ function createTrie<Value = string>({
getCharacters?: (item: Value) => string[]
compare?: (a: Value | undefined, b: Value | undefined) => boolean
}): Trie<Value> {
// Null-prototype children so segment names that collide with
// Object.prototype members (e.g. "constructor", "toString") work correctly.
let root: TrieNode<Value> = {
value: undefined,
children: {},
children: Object.create(null),
}
function markUpdated() {
@@ -82,7 +84,7 @@ function createTrie<Value = string>({
currentNode.children[segment] = {
value: undefined,
// Skip value for intermediate nodes
children: {},
children: Object.create(null),
}
}
currentNode = currentNode.children[segment]