From d48d89e9935bad2ac24a62ef0fa9611e8e3178ac Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 13:31:11 +0100 Subject: [PATCH] [release-11.6.3] FlameGraph: Fix bug for function names that conflict with JavaScript object prototype properties (#106624) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit FlameGraph: Fix bug for function names that conflict with JavaScript object prototype properties (#106338) * grafana-flamegraph: Fix bug for function names that conflict with JavaScript object prototype properties The bug occurs in the flamegraph data transformation logic where function names from profiling data are used as object keys without proper prototype pollution protection. Fixes #106232 #101551 * Fix top table calculations --------- (cherry picked from commit 6156e9c2d8c67ff067b2e88172cd626d5ac3c707) Co-authored-by: Christian Simon Co-authored-by: vinayteki95 Co-authored-by: Piotr Jamróz --- .../src/FlameGraph/dataTransform.test.ts | 24 +++++++++++++++++++ .../src/FlameGraph/dataTransform.ts | 2 +- .../TopTable/FlameGraphTopTableContainer.tsx | 2 +- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/packages/grafana-flamegraph/src/FlameGraph/dataTransform.test.ts b/packages/grafana-flamegraph/src/FlameGraph/dataTransform.test.ts index 8128544abb8..b3e62ba1efa 100644 --- a/packages/grafana-flamegraph/src/FlameGraph/dataTransform.test.ts +++ b/packages/grafana-flamegraph/src/FlameGraph/dataTransform.test.ts @@ -75,6 +75,30 @@ describe('nestedSetToLevels', () => { expect(levels[0]).toEqual([n1]); expect(levels[1]).toEqual([n2, n3, n4]); }); + + it('handles strings that collide with inherited prototype method names', () => { + const frame = createDataFrame({ + fields: [ + { name: 'level', values: [0, 1, 1, 1] }, + { name: 'value', values: [10, 5, 3, 1] }, + { name: 'label', values: ['toString', 'valueOf', 'hasOwnProperty', 'isPrototypeOf'], type: FieldType.string }, + { name: 'self', values: [10, 5, 3, 1] }, + ], + }); + const [levels] = nestedSetToLevels(new FlameGraphDataContainer(frame, { collapsing: true })); + + const n4: LevelItem = { itemIndexes: [3], start: 8, children: [], value: 1, level: 1 }; + const n3: LevelItem = { itemIndexes: [2], start: 5, children: [], value: 3, level: 1 }; + const n2: LevelItem = { itemIndexes: [1], start: 0, children: [], value: 5, level: 1 }; + const n1: LevelItem = { itemIndexes: [0], start: 0, children: [n2, n3, n4], value: 10, level: 0 }; + + n2.parents = [n1]; + n3.parents = [n1]; + n4.parents = [n1]; + + expect(levels[0]).toEqual([n1]); + expect(levels[1]).toEqual([n2, n3, n4]); + }); }); describe('FlameGraphDataContainer', () => { diff --git a/packages/grafana-flamegraph/src/FlameGraph/dataTransform.ts b/packages/grafana-flamegraph/src/FlameGraph/dataTransform.ts index 51b41f97397..b4d545e90de 100644 --- a/packages/grafana-flamegraph/src/FlameGraph/dataTransform.ts +++ b/packages/grafana-flamegraph/src/FlameGraph/dataTransform.ts @@ -46,7 +46,7 @@ export function nestedSetToLevels( let offset = 0; let parent: LevelItem | undefined = undefined; - const uniqueLabels: Record = {}; + const uniqueLabels: Record = Object.create(null); for (let i = 0; i < container.data.length; i++) { const currentLevel = container.getLevel(i); diff --git a/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx b/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx index 1ee3f172fa4..b3a1be9a3b3 100644 --- a/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx +++ b/packages/grafana-flamegraph/src/TopTable/FlameGraphTopTableContainer.tsx @@ -56,7 +56,7 @@ const FlameGraphTopTableContainer = memo( const table = useMemo(() => { // Group the data by label, we show only one row per label and sum the values // TODO: should be by filename + funcName + linenumber? - let filteredTable: { [key: string]: TableData } = {}; + let filteredTable: { [key: string]: TableData } = Object.create(null); for (let i = 0; i < data.data.length; i++) { const value = data.getValue(i); const valueRight = data.getValueRight(i);