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

Co-authored-by: vinayteki95 <vinayteki95@users.noreply.github.com>

* Fix top table calculations

---------

Co-authored-by: vinayteki95 <vinayteki95@users.noreply.github.com>
Co-authored-by: Piotr Jamróz <pm.jamroz@gmail.com>
This commit is contained in:
Christian Simon
2025-06-12 14:41:33 +02:00
committed by GitHub
co-authored by vinayteki95 Piotr Jamróz
parent de28231f2f
commit 6156e9c2d8
3 changed files with 26 additions and 2 deletions
@@ -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', () => {
@@ -46,7 +46,7 @@ export function nestedSetToLevels(
let offset = 0;
let parent: LevelItem | undefined = undefined;
const uniqueLabels: Record<string, LevelItem[]> = {};
const uniqueLabels: Record<string, LevelItem[]> = Object.create(null);
for (let i = 0; i < container.data.length; i++) {
const currentLevel = container.getLevel(i);
@@ -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);