Skip to content

Fix sourcemap lookup when line numbers are enabled#81

Merged
nsavoire merged 1 commit intomainfrom
nsavoire/fix_sourcemap_lookup
Mar 9, 2023
Merged

Fix sourcemap lookup when line numbers are enabled#81
nsavoire merged 1 commit intomainfrom
nsavoire/fix_sourcemap_lookup

Conversation

@nsavoire
Copy link
Copy Markdown

When calling sites line numbers are reported (StartProfiling called with CpuProfilingMode::kCallerLineNumbers), sometimes locations point to the part of a code line before the first word. In that case, SourceMapConsumer.originalPositionFor will locate with a binary search the first generated position less or equal to the input. Since the input location does not point to a generated location and is at the start of a line, the binary search will return the last generated location from the previous line. But since the found location is on a different line from the input location, originalPositionFor will return null.
Changes done to fix this:

  • do a first search with LEAST_UPPER_BOUND instead of GREATEST_LOWER_BOUND: binary search will search for the first element greater or equal to the input location. As a result, this will work for locations pointing to blank parts at the start of a line. Still it will not work for locations pointing at the end of a line, but this case does not seem to occur, therefore it makes sense to start with LEAST_UPPER_BOUND
  • if previous search fails, do a second lookup with GREATEST_LOWER_BOUND

When calling sites line numbers are reported (StartProfiling called
with CpuProfilingMode::kCallerLineNumbers), sometimes locations point
to the part of a code line before the first word. In that case,
SourceMapConsumer.originalPositionFor will locate with a binary search
the first generated position less or equal to the input. Since the
input location does not point to a generated location and is at the
start of a line, the binary search will return the last generated
location from the previous line. But since the found location is on a
different line from the input location, originalPositionFor will return
null.
Changes done to fix this:
* do a first search with LEAST_UPPER_BOUND instead of
  GREATEST_LOWER_BOUND: binary search will search for the first element
  greater or equal to the input location. As a result, this will work
  for locations pointing to blank parts at the start of a line.
  Still it will not work for locations pointing at the end of a line,
  but this case does not seem to occur, therefore it makes sense to
  start with LEAST_UPPER_BOUND
* if previous search fails, do a second lookup with GREATEST_LOWER_BOUND
@nsavoire nsavoire added the semver-patch Bug or security fixes, mainly label Feb 28, 2023
@nsavoire nsavoire requested review from Qard and r1viollet February 28, 2023 14:49
@github-actions
Copy link
Copy Markdown

Overall package size

Self size: 1.18 MB
Deduped: 2.06 MB
No deduping: 2.06 MB

Dependency sizes

name version self size total size
pprof-format 2.0.7 588.12 kB 588.12 kB
source-map 0.7.4 226 kB 226 kB
split 1.0.1 12.29 kB 24.82 kB
p-limit 3.1.0 7.75 kB 13.78 kB
delay 5.0.0 11.17 kB 11.17 kB
pify 5.0.0 8.87 kB 8.87 kB
node-gyp-build 3.9.0 8.81 kB 8.81 kB

🤖 This report was automatically generated by heaviest-objects-in-the-universe

@nsavoire nsavoire merged commit cfebbb3 into main Mar 9, 2023
@nsavoire nsavoire deleted the nsavoire/fix_sourcemap_lookup branch March 9, 2023 15:50
nsavoire added a commit that referenced this pull request Mar 16, 2023
When calling sites line numbers are reported (StartProfiling called
with CpuProfilingMode::kCallerLineNumbers), sometimes locations point
to the part of a code line before the first word. In that case,
SourceMapConsumer.originalPositionFor will locate with a binary search
the first generated position less or equal to the input. Since the
input location does not point to a generated location and is at the
start of a line, the binary search will return the last generated
location from the previous line. But since the found location is on a
different line from the input location, originalPositionFor will return
null.
Changes done to fix this:
* do a first search with LEAST_UPPER_BOUND instead of
  GREATEST_LOWER_BOUND: binary search will search for the first element
  greater or equal to the input location. As a result, this will work
  for locations pointing to blank parts at the start of a line.
  Still it will not work for locations pointing at the end of a line,
  but this case does not seem to occur, therefore it makes sense to
  start with LEAST_UPPER_BOUND
* if previous search fails, do a second lookup with GREATEST_LOWER_BOUND
nsavoire added a commit that referenced this pull request Mar 16, 2023
When calling sites line numbers are reported (StartProfiling called
with CpuProfilingMode::kCallerLineNumbers), sometimes locations point
to the part of a code line before the first word. In that case,
SourceMapConsumer.originalPositionFor will locate with a binary search
the first generated position less or equal to the input. Since the
input location does not point to a generated location and is at the
start of a line, the binary search will return the last generated
location from the previous line. But since the found location is on a
different line from the input location, originalPositionFor will return
null.
Changes done to fix this:
* do a first search with LEAST_UPPER_BOUND instead of
  GREATEST_LOWER_BOUND: binary search will search for the first element
  greater or equal to the input location. As a result, this will work
  for locations pointing to blank parts at the start of a line.
  Still it will not work for locations pointing at the end of a line,
  but this case does not seem to occur, therefore it makes sense to
  start with LEAST_UPPER_BOUND
* if previous search fails, do a second lookup with GREATEST_LOWER_BOUND
nsavoire added a commit that referenced this pull request Mar 22, 2023
When calling sites line numbers are reported (StartProfiling called
with CpuProfilingMode::kCallerLineNumbers), sometimes locations point
to the part of a code line before the first word. In that case,
SourceMapConsumer.originalPositionFor will locate with a binary search
the first generated position less or equal to the input. Since the
input location does not point to a generated location and is at the
start of a line, the binary search will return the last generated
location from the previous line. But since the found location is on a
different line from the input location, originalPositionFor will return
null.
Changes done to fix this:
* do a first search with LEAST_UPPER_BOUND instead of
  GREATEST_LOWER_BOUND: binary search will search for the first element
  greater or equal to the input location. As a result, this will work
  for locations pointing to blank parts at the start of a line.
  Still it will not work for locations pointing at the end of a line,
  but this case does not seem to occur, therefore it makes sense to
  start with LEAST_UPPER_BOUND
* if previous search fails, do a second lookup with GREATEST_LOWER_BOUND
nsavoire added a commit that referenced this pull request Jun 14, 2023
nsavoire added a commit that referenced this pull request Jun 14, 2023
nsavoire added a commit that referenced this pull request Jun 15, 2023
nsavoire added a commit that referenced this pull request Jun 15, 2023
szegedi added a commit that referenced this pull request Mar 13, 2026
Add tests for SourceMapper.mappingInfo with a synthetic webpack-style
single-line bundle to document the known limitation introduced by #248.

Background
----------
PR #81 changed originalPositionFor to always try LEAST_UPPER_BOUND
first (then fall back to GREATEST_LOWER_BOUND). This was reverted in
#106 because it broke webpack source maps: for real non-zero columns
LEAST_UPPER_BOUND finds the *next* mapping (≥ column) rather than the
one at the column, returning wrong function names.

PR #248 fixed that regression by using LEAST_UPPER_BOUND only when
column === 0, and GREATEST_LOWER_BOUND otherwise. This correctly
handles Node.js ≥ 25 where V8's LineTick struct carries real column
numbers.

Residual limitation (Node.js < 25)
-----------------------------------
On Node.js < 25, the LineTick struct has no column field. The C++ layer
therefore always emits column=0 for every LineTick sample. With
column=0, the sourcemapper uses LEAST_UPPER_BOUND, which finds the
*first* mapping on the line. In a webpack bundle (all output on one
line) every function maps to the same first source function in the map.

This is not a regression vs. the pre-#248 state: before #248, those
functions were simply unmapped (column=0 + GREATEST_LOWER_BOUND →
nothing ≤ 0 → null → falls back to generated name/file). Both outcomes
are imperfect; #248 trades "unmapped" for "mapped to first function",
which may or may not be preferable depending on the use case.

The two new tests pin both behaviours explicitly so any future change
to this logic is immediately visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

semver-patch Bug or security fixes, mainly

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants