mirror of
https://github.com/morluto/rea
synced 2026-10-27 14:42:18 -04:00
fix(browser): validate and bound source-map evidence
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
import { TraceMap, decodedMappings } from "@jridgewell/trace-mapping";
|
||||
import {
|
||||
SourceMapFormatFailure,
|
||||
type SourceMapLeaf,
|
||||
} from "./SourceMapFormat.js";
|
||||
|
||||
/** Determine whether a leaf-local mapping falls before its enclosing boundary. */
|
||||
export const isBeforeSourceMapLeafStop = (
|
||||
leaf: SourceMapLeaf,
|
||||
line: number,
|
||||
column: number,
|
||||
): boolean => {
|
||||
if (leaf.stop === null) return true;
|
||||
const generatedLine = leaf.offset.line + line;
|
||||
const generatedColumn = column + (line === 0 ? leaf.offset.column : 0);
|
||||
return (
|
||||
generatedLine < leaf.stop.line ||
|
||||
(generatedLine === leaf.stop.line && generatedColumn < leaf.stop.column)
|
||||
);
|
||||
};
|
||||
|
||||
/** Resolve one declaration with the same pinned upstream implementation used by full maps. */
|
||||
export const resolveSourceMapSource = (
|
||||
source: string | null,
|
||||
sourceRoot: string | null | undefined,
|
||||
mapUrl: string,
|
||||
): string => {
|
||||
const map = new TraceMap(
|
||||
{
|
||||
version: 3,
|
||||
names: [],
|
||||
sources: [source ?? ""],
|
||||
...(sourceRoot == null ? {} : { sourceRoot }),
|
||||
mappings: "",
|
||||
},
|
||||
mapUrl,
|
||||
);
|
||||
const resolved = map.resolvedSources[0];
|
||||
if (resolved === undefined)
|
||||
throw new SourceMapFormatFailure(
|
||||
"format",
|
||||
"Source-map resolver did not return the requested source identity.",
|
||||
);
|
||||
return resolved;
|
||||
};
|
||||
|
||||
/** Decode leaf mappings and validate producer indices before any consumer publishes them. */
|
||||
export const decodeValidatedSourceMapLeaves = (
|
||||
leaves: readonly SourceMapLeaf[],
|
||||
mapUrl: string,
|
||||
check?: () => void,
|
||||
): readonly {
|
||||
readonly leaf: SourceMapLeaf;
|
||||
readonly rows: ReturnType<typeof decodedMappings>;
|
||||
readonly resolvedSources: readonly string[];
|
||||
}[] =>
|
||||
leaves.map((leaf) => {
|
||||
check?.();
|
||||
const map = new TraceMap(JSON.stringify(leaf.map), mapUrl);
|
||||
const rows = decodedMappings(map);
|
||||
for (const [line, row] of rows.entries()) {
|
||||
if ((line & 255) === 0) check?.();
|
||||
for (const segment of row) {
|
||||
const generatedLine = leaf.offset.line + line;
|
||||
const generatedColumn =
|
||||
segment[0] + (line === 0 ? leaf.offset.column : 0);
|
||||
if (
|
||||
!Number.isSafeInteger(generatedLine) ||
|
||||
!Number.isSafeInteger(generatedColumn) ||
|
||||
![1, 4, 5].includes(segment.length) ||
|
||||
segment.some((value) => !Number.isSafeInteger(value) || value < 0) ||
|
||||
(segment.length !== 1 && segment[1] >= leaf.map.sources.length) ||
|
||||
(segment.length === 5 && segment[4] >= leaf.map.names.length)
|
||||
)
|
||||
throw new SourceMapFormatFailure(
|
||||
"format",
|
||||
"Decoded local or absolute position, source index or name index is invalid.",
|
||||
);
|
||||
}
|
||||
}
|
||||
return { leaf, rows, resolvedSources: map.resolvedSources };
|
||||
});
|
||||
@@ -311,18 +311,10 @@ export const inspectSourceMap = (
|
||||
};
|
||||
};
|
||||
|
||||
/** Validate shared source-map structure before a consumer decodes mappings. */
|
||||
export const validateSourceMapStructure = (
|
||||
/** Return validated leaves when a consumer needs decoded rows after preflight. */
|
||||
export const inspectSourceMapValue = (
|
||||
value: unknown,
|
||||
options: SourceMapStructureOptions = {},
|
||||
): void => {
|
||||
inspectSourceMapValue(value, options, false);
|
||||
};
|
||||
|
||||
const inspectSourceMapValue = (
|
||||
value: unknown,
|
||||
options: SourceMapStructureOptions = {},
|
||||
collect = true,
|
||||
): {
|
||||
readonly format: "regular" | "indexed";
|
||||
readonly reportedFile: string | null;
|
||||
@@ -351,10 +343,9 @@ const inspectSourceMapValue = (
|
||||
continue;
|
||||
}
|
||||
const leaf = inspectLeaf(node, budget, options);
|
||||
if (collect)
|
||||
leaves.push({ map: leaf, offset: node.offset, stop: node.stop });
|
||||
for (const [sourceIndex, source] of leaf.sources.entries())
|
||||
if (collect)
|
||||
leaves.push({ map: leaf, offset: node.offset, stop: node.stop });
|
||||
if (options.profile !== "browser-collection")
|
||||
for (const [sourceIndex, source] of leaf.sources.entries())
|
||||
declarations.push({
|
||||
sectionPath: node.path,
|
||||
sourceIndex,
|
||||
|
||||
@@ -20,6 +20,10 @@ import {
|
||||
SourceMapFormatFailure,
|
||||
type SourceMapSourceDeclaration,
|
||||
} from "./SourceMapFormat.js";
|
||||
import {
|
||||
decodeValidatedSourceMapLeaves,
|
||||
isBeforeSourceMapLeafStop,
|
||||
} from "./DecodedSourceMap.js";
|
||||
|
||||
/** Decode with the pinned upstream codec; preserve every equal-position candidate. */
|
||||
export const traceSourceMap = (
|
||||
@@ -28,7 +32,21 @@ export const traceSourceMap = (
|
||||
position: WebSourcePosition,
|
||||
): WebSourceMapReport => {
|
||||
const inspected = inspectSourceMap(text);
|
||||
const { map, rows } = decodeValidatedMap(inspected, url);
|
||||
const map = new AnyMap(inspected.jsonText, url);
|
||||
const decodedLeaves = decodeValidatedSourceMapLeaves(inspected.leaves, url);
|
||||
for (const { leaf, rows } of decodedLeaves)
|
||||
for (const [line, row] of rows.entries())
|
||||
for (const segment of row)
|
||||
if (!isBeforeSourceMapLeafStop(leaf, line, segment[0]))
|
||||
throw new SourceMapFormatFailure(
|
||||
"format",
|
||||
"Embedded source-map mappings overlap the following section boundary.",
|
||||
);
|
||||
const rows =
|
||||
inspected.format === "regular"
|
||||
? (decodedLeaves[0]?.rows ?? [])
|
||||
: decodedMappings(map);
|
||||
validateFlattenedRows(inspected, rows, map);
|
||||
const matched = greatestPosition(rows, position);
|
||||
const segments =
|
||||
matched === null
|
||||
@@ -77,49 +95,14 @@ export const traceSourceMap = (
|
||||
});
|
||||
};
|
||||
|
||||
const decodeValidatedMap = (
|
||||
const validateFlattenedRows = (
|
||||
inspected: ReturnType<typeof inspectSourceMap>,
|
||||
url: string,
|
||||
) => {
|
||||
let regularRows: ReturnType<typeof decodedMappings> | undefined;
|
||||
for (const leaf of inspected.leaves) {
|
||||
const decoded = decodedMappings(
|
||||
new TraceMap(JSON.stringify(leaf.map), url),
|
||||
);
|
||||
if (inspected.format === "regular") regularRows = decoded;
|
||||
for (const [line, row] of decoded.entries())
|
||||
for (const segment of row) {
|
||||
if (
|
||||
segment.some((value) => !Number.isSafeInteger(value) || value < 0) ||
|
||||
(segment.length !== 1 && segment[1] >= leaf.map.sources.length) ||
|
||||
(segment.length === 5 && segment[4] >= leaf.map.names.length)
|
||||
)
|
||||
throw new SourceMapFormatFailure(
|
||||
"format",
|
||||
"Decoded section-local position, source index or name index is invalid.",
|
||||
);
|
||||
const generatedLine = leaf.offset.line + line;
|
||||
const generatedColumn =
|
||||
segment[0] + (line === 0 ? leaf.offset.column : 0);
|
||||
if (
|
||||
leaf.stop !== null &&
|
||||
(generatedLine > leaf.stop.line ||
|
||||
(generatedLine === leaf.stop.line &&
|
||||
generatedColumn >= leaf.stop.column))
|
||||
)
|
||||
throw new SourceMapFormatFailure(
|
||||
"format",
|
||||
"Embedded source-map mappings overlap the following section boundary.",
|
||||
);
|
||||
}
|
||||
}
|
||||
const map = new AnyMap(inspected.jsonText, url);
|
||||
const rows = regularRows ?? decodedMappings(map);
|
||||
rows: ReturnType<typeof decodedMappings>,
|
||||
map: ReturnType<typeof AnyMap>,
|
||||
): void => {
|
||||
for (const row of rows)
|
||||
for (const segment of row) {
|
||||
if (
|
||||
![1, 4, 5].includes(segment.length) ||
|
||||
segment.some((v) => !Number.isSafeInteger(v) || v < 0) ||
|
||||
(segment.length !== 1 && segment[1] >= inspected.declarations.length) ||
|
||||
(segment.length === 5 && segment[4] >= map.names.length)
|
||||
)
|
||||
@@ -128,8 +111,6 @@ const decodeValidatedMap = (
|
||||
"Decoded mapping has invalid component count, position, source index or name index.",
|
||||
);
|
||||
}
|
||||
|
||||
return { map, rows };
|
||||
};
|
||||
|
||||
const greatestPosition = (
|
||||
|
||||
Reference in New Issue
Block a user