[DevTools] Fix symbolication with Index Source Maps (#34300)
Sebastian "Sebbie" Silbermann committed
Aug 26, 2025 at 15:18 UTC
ad4ecb6e6eb0663dcabb5967fe23a8c4331c18d0
2 files changed
+35
-61
packages/react-devtools-shared/src/hooks/SourceMapConsumer.js
+34
-60
@@ -136,9 +136,9 @@ function BasicSourceMapConsumer(sourceMapJSON: BasicSourceMap) {
136
}
137
138
type Section = {
139
- +generatedColumn: number,
140
- +generatedLine: number,
141
- +map: MixedSourceMap,
139
+ +offsetColumn0: number,
140
+ +offsetLine0: number,
141
+ +map: BasicSourceMap,
142
143
// Lazily parsed only when/as the section is needed.
144
sourceMapConsumer: SourceMapConsumerType | null,
@@ -160,12 +160,12 @@ function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) {
160
column: number,
161
...
162
} = section.offset;
163
- const offsetLine = offset.line;
164
- const offsetColumn = offset.column;
163
+ const offsetLine0 = offset.line;
164
+ const offsetColumn0 = offset.column;
165
166
if (
167
- offsetLine < lastOffset.line ||
168
- (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)
167
+ offsetLine0 < lastOffset.line ||
168
+ (offsetLine0 === lastOffset.line && offsetColumn0 < lastOffset.column)
169
) {
170
throw new Error('Section offsets must be ordered and non-overlapping.');
171
}
@@ -173,9 +173,8 @@ function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) {
173
lastOffset = offset;
174
175
return {
176
- // The offset fields are 0-based, but we use 1-based indices when encoding/decoding from VLQ.
177
- generatedLine: offsetLine + 1,
178
- generatedColumn: offsetColumn + 1,
176
+ offsetLine0,
177
+ offsetColumn0,
178
map: section.map,
179
sourceMapConsumer: null,
180
};
@@ -186,55 +185,29 @@ function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) {
185
lineNumber,
186
}: SearchPosition): ResultPosition {
187
// Error.prototype.stack columns are 1-based (like most IDEs) but ASTs are 0-based.
189
- const targetColumnNumber = columnNumber - 1;
190
-
191
- let section = null;
192
-
193
- let startIndex = 0;
194
- let stopIndex = sections.length - 1;
195
- let index = -1;
196
- while (startIndex <= stopIndex) {
197
- index = Math.floor((stopIndex + startIndex) / 2);
198
- section = sections[index];
199
-
200
- const currentLine = section.generatedLine;
201
- if (currentLine === lineNumber) {
202
- const currentColumn = section.generatedColumn;
203
- if (currentColumn === lineNumber) {
204
- break;
205
- } else {
206
- if (currentColumn > targetColumnNumber) {
207
- if (stopIndex - index > 0) {
208
- stopIndex = index;
209
- } else {
210
- index = stopIndex;
211
- break;
212
- }
213
- } else {
214
- if (index - startIndex > 0) {
215
- startIndex = index;
216
- } else {
217
- index = startIndex;
218
- break;
219
- }
220
- }
221
- }
188
+ const column0 = columnNumber - 1;
189
+ const line0 = lineNumber - 1;
190
+
191
+ // Sections must not overlap and must be sorted: https://tc39.es/source-map/#section-object
192
+ // Therefore the last section that has an offset less than or equal to the frame is the applicable one.
193
+ let left = 0;
194
+ let right = sections.length - 1;
195
+ let section: Section | null = null;
196
+
197
+ while (left <= right) {
198
+ // fast Math.floor
199
+ const middle = ~~((left + right) / 2);
200
+ const currentSection = sections[middle];
201
+
202
+ if (
203
+ currentSection.offsetLine0 < line0 ||
204
+ (currentSection.offsetLine0 === line0 &&
205
+ currentSection.offsetColumn0 <= column0)
206
+ ) {
207
+ section = currentSection;
208
+ left = middle + 1;
209
} else {
223
- if (currentLine > lineNumber) {
224
- if (stopIndex - index > 0) {
225
- stopIndex = index;
226
- } else {
227
- index = stopIndex;
228
- break;
229
- }
230
- } else {
231
- if (index - startIndex > 0) {
232
- startIndex = index;
233
- } else {
234
- index = startIndex;
235
- break;
236
- }
237
- }
210
+ right = middle - 1;
211
}
212
}
213
@@ -252,8 +225,9 @@ function IndexedSourceMapConsumer(sourceMapJSON: IndexSourceMap) {
225
}
226
227
return section.sourceMapConsumer.originalPositionFor({
255
- columnNumber,
256
- lineNumber,
228
+ // The mappings in a Source Map section are relative to the section offset.
229
+ columnNumber: columnNumber - section.offsetColumn0,
230
+ lineNumber: lineNumber - section.offsetLine0,
231
});
232
}
233
packages/react-devtools-shared/src/hooks/SourceMapTypes.js
+1
-1
@@ -29,7 +29,7 @@ export type BasicSourceMap = {
29
};
30
31
export type IndexSourceMapSection = {
32
- map: IndexSourceMap | BasicSourceMap,
32
+ map: BasicSourceMap,
33
offset: {
34
line: number,
35
column: number,