From 34dda16fa6025f3d0283485a93d3ce79ce780d6c Mon Sep 17 00:00:00 2001 From: jsek Date: Sun, 25 Aug 2024 05:27:03 +0200 Subject: [PATCH] fix(nested): Prevent infinite loops when resolving path --- packages/vuetify/src/components/VTreeview/VTreeview.ts | 2 ++ packages/vuetify/src/composables/nested/nested.ts | 2 ++ .../vuetify/src/composables/nested/openStrategies.ts | 4 ++++ .../vuetify/src/composables/nested/selectStrategies.ts | 2 ++ packages/vuetify/src/labs/VTreeview/VTreeview.tsx | 2 ++ packages/vuetify/src/util/nested.ts | 9 +++++++++ 6 files changed, 21 insertions(+) create mode 100644 packages/vuetify/src/util/nested.ts diff --git a/packages/vuetify/src/components/VTreeview/VTreeview.ts b/packages/vuetify/src/components/VTreeview/VTreeview.ts index 0c5b0feed73e..29d662d7a9aa 100644 --- a/packages/vuetify/src/components/VTreeview/VTreeview.ts +++ b/packages/vuetify/src/components/VTreeview/VTreeview.ts @@ -28,6 +28,7 @@ import { filterTreeItems, filterTreeItem, } from './util/filterTreeItems' +import { preventLoops } from '@/util/nested' type VTreeviewNodeInstance = InstanceType @@ -307,6 +308,7 @@ export default mixins( const parents = [] while (parent !== null) { + preventLoops(parents, parent) parents.push(parent) parent = this.nodes[parent].parent } diff --git a/packages/vuetify/src/composables/nested/nested.ts b/packages/vuetify/src/composables/nested/nested.ts index 5f80f4da0906..753f159fddc6 100644 --- a/packages/vuetify/src/composables/nested/nested.ts +++ b/packages/vuetify/src/composables/nested/nested.ts @@ -18,6 +18,7 @@ import { leafSingleSelectStrategy, } from './selectStrategies' import { getCurrentInstance, getUid, propsFactory } from '@/util' +import { preventLoops } from '@/util/nested' // Types import type { InjectionKey, PropType, Ref } from 'vue' @@ -182,6 +183,7 @@ export const useNested = (props: NestedProps) => { let parent: unknown = id while (parent != null) { + preventLoops(path, parent) path.unshift(parent) parent = parents.value.get(parent) } diff --git a/packages/vuetify/src/composables/nested/openStrategies.ts b/packages/vuetify/src/composables/nested/openStrategies.ts index ff0f2b3d514b..93656820b9c4 100644 --- a/packages/vuetify/src/composables/nested/openStrategies.ts +++ b/packages/vuetify/src/composables/nested/openStrategies.ts @@ -1,5 +1,6 @@ // Utilities import { toRaw } from 'vue' +import { preventLoops } from '@/util/nested' export type OpenStrategyFn = (data: { id: unknown @@ -34,6 +35,7 @@ export const singleOpenStrategy: OpenStrategy = { let parent = parents.get(id) while (parent != null) { + preventLoops(newOpened, parent) newOpened.add(parent) parent = parents.get(parent) } @@ -54,6 +56,7 @@ export const multipleOpenStrategy: OpenStrategy = { opened.add(id) while (parent != null && parent !== id) { + preventLoops(opened, parent) opened.add(parent) parent = toRaw(parents.get(parent)) } @@ -77,6 +80,7 @@ export const listOpenStrategy: OpenStrategy = { let parent = parents.get(id) while (parent != null) { + preventLoops(path, parent) path.push(parent) parent = parents.get(parent) } diff --git a/packages/vuetify/src/composables/nested/selectStrategies.ts b/packages/vuetify/src/composables/nested/selectStrategies.ts index 42eade6c6ae9..08e037432d2e 100644 --- a/packages/vuetify/src/composables/nested/selectStrategies.ts +++ b/packages/vuetify/src/composables/nested/selectStrategies.ts @@ -1,6 +1,7 @@ /* eslint-disable sonarjs/no-identical-functions */ // Utilities import { toRaw } from 'vue' +import { preventLoops } from '@/util/nested' export type SelectStrategyFn = (data: { id: unknown @@ -163,6 +164,7 @@ export const classicSelectStrategy = (mandatory?: boolean): SelectStrategy => { const everySelected = childrenIds.every(cid => selected.get(toRaw(cid)) === 'on') const noneSelected = childrenIds.every(cid => !selected.has(toRaw(cid)) || selected.get(toRaw(cid)) === 'off') + preventLoops(selected, parent) selected.set(parent, everySelected ? 'on' : noneSelected ? 'off' : 'indeterminate') parent = toRaw(parents.get(parent)) diff --git a/packages/vuetify/src/labs/VTreeview/VTreeview.tsx b/packages/vuetify/src/labs/VTreeview/VTreeview.tsx index d4079447da04..237c0543a1b3 100644 --- a/packages/vuetify/src/labs/VTreeview/VTreeview.tsx +++ b/packages/vuetify/src/labs/VTreeview/VTreeview.tsx @@ -10,6 +10,7 @@ import { useProxiedModel } from '@/composables/proxiedModel' // Utilities import { computed, provide, ref, toRaw, toRef } from 'vue' import { genericComponent, omit, propsFactory, useRender } from '@/util' +import { preventLoops } from '@/util/nested' // Types import { VTreeviewSymbol } from './shared' @@ -98,6 +99,7 @@ export const VTreeview = genericComponent( const path: unknown[] = [] let parent: unknown = id while (parent != null) { + preventLoops(path, parent) path.unshift(parent) parent = vListRef.value?.parents.get(parent) } diff --git a/packages/vuetify/src/util/nested.ts b/packages/vuetify/src/util/nested.ts new file mode 100644 index 000000000000..488e144c9ef9 --- /dev/null +++ b/packages/vuetify/src/util/nested.ts @@ -0,0 +1,9 @@ +export function preventLoops (path: T[] | Set | Map, itemToPush: T) { + if ( + (Array.isArray(path) && path.includes(itemToPush)) || + (path instanceof Set && path.has(itemToPush)) || + (path instanceof Map && path.has(itemToPush)) + ) { + throw new Error('[Vuetify] Could not resolve nested path because of duplicated identifiers') + } +}