sentry-javascript: Incorrect line numbers on Safari

Package + Version

  • @sentry/browser
  • @sentry/node
  • raven-js
  • raven-node (raven for node)
  • other:

Version:

@sentry/browser 5.15.4
raven-js  3.27.0

Description

I noticed that some of our errors were reporting incorrect line numbers on Safari (and chrome on iOS devices). It’s really obvious the line numbers are wrong because of the cause of the exception (a function is undefined).

Issue from production https://sentry.io/organizations/zocdoc/issues/1546967608/?project=1236964&referrer=slack

Locally Reproduction of issue

Safari https://sentry.io/organizations/zocdoc/issues/1623879654/?project=1225899&query=is%3Aunresolved&statsPeriod=1h

Chrome https://sentry.io/organizations/zocdoc/issues/1623923814/?project=1225899&query=is%3Aunresolved&statsPeriod=1h

You’ll notice that the line:column numbers are different for the two browsers when they shouldn’t be. (taken from raw + minified in the sentry ui)

Chrome

TypeError: P is not a function
  at ? (/home/scripts/home.8e8c42c241fa27853b6b.min.js:98:54862)
  at ? (/home/scripts/home.8e8c42c241fa27853b6b.min.js:98:51029)
  at Object.next(/home/scripts/home.8e8c42c241fa27853b6b.min.js:98:51134)
  at ? (/home/scripts/home.8e8c42c241fa27853b6b.min.js:98:50046)
  at new Promise(<anonymous>)
  at r(/home/scripts/home.8e8c42c241fa27853b6b.min.js:98:49791)
  at Fe(/home/scripts/home.8e8c42c241fa27853b6b.min.js:98:53720)
  at onClick(/home/scripts/home.8e8c42c241fa27853b6b.min.js:98:69241)
  at Object.m(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:1025)
  at w(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:1168)
  at ? (/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:1314)
  at C(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:1400)
  at T(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:1849)
  at I(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:1661)
  at k(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:2009)
  at _n(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:29002)
  at ue(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:114672)
  at Tn(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:29617)
  at Pn(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:30574)
  at kn(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:29862)
  at t.unstable_runWithPriority(/home/scripts/home.8e8c42c241fa27853b6b.min.js:79:3462)
  at Yo(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:45441)
  at ce(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:114449)
  at In(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:29285)
  at HTMLDocument.r(/home/scripts/home.8e8c42c241fa27853b6b.min.js:28:25914)

Safari

TypeError: P is not a function. (In 'P(e)', 'P' is undefined)
  at ? (/home/scripts/home.8e8c42c241fa27853b6b.min.js:98:54917)
  at ? (/home/scripts/home.8e8c42c241fa27853b6b.min.js:98:51087)
  at ? (/home/scripts/home.8e8c42c241fa27853b6b.min.js:98:50104)
  at Promise([native code])
  at ? (/home/scripts/home.8e8c42c241fa27853b6b.min.js:98:49864)
  at m(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:1273)
  at w(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:1416)
  at ? (/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:1562)
  at C(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:1643)
  at T(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:2093)
  at I(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:1908)
  at k(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:2253)
  at _n(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:29246)
  at ue(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:114916)
  at Tn(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:29862)
  at Pn(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:30819)
  at kn(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:30107)
  at kn([native code])
  at ? (/home/scripts/home.8e8c42c241fa27853b6b.min.js:79:3705)
  at ce(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:114694)
  at In(/home/scripts/home.8e8c42c241fa27853b6b.min.js:63:29530)
  at In([native code])
  at r(/home/scripts/home.8e8c42c241fa27853b6b.min.js:28:25967)

Taking those stacktraces and the souremaps, I ran those though this program to get the original line numbers. The chrome stacktrace gives me the correct mappings, the safari one displays the incorrect line numbers.

const fs = require('fs');
var sourceMap = require('source-map');

async function main() {
    const mapfile = fs.readdirSync(__dirname).filter(f => f.endsWith(".min.js.map"))[0]

    console.log(mapfile);
    var smc = await new sourceMap.SourceMapConsumer(fs.readFileSync(mapfile, "utf8"));
    
    const lines = fs.readFileSync("stack.txt").toString().split("\n");
    
    lines.forEach(l => {
        let re = /([\.-\w\/]+\.js):(\d+):(\d+)+/;
        const matches = re.exec(l);
        if (matches) {
            console.log(l)
    
            console.log(smc.originalPositionFor({
                line: parseInt(matches[2], 10),
                column: parseInt(matches[3], 10)
            }));
        } else {
          console.log(l);
          console.log("[no sourcemap match]");
        }
        
    })
}

main().then(() => console.log("done"))

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 4
  • Comments: 16 (4 by maintainers)

Most upvoted comments

I wonder if it’s something specific to our setups. Otherwise I feel like more people would have reported this.

Hi all. I work for Bugsnag and I’ve been investigating what I think is the same issue. I too was surprised that this was the only mention of the issue I could find, especially now I seem to have worked out what the problem is. Hopefully, the following is of some use to you.

If you’re using Webpack chances are you have comment blocks containing license info in your bundle, like this:

/*
object-assign
(c) Sindre Sorhus
@license MIT
*/var n=Object.getOwnPropertySy[...]

This seems to be confusing Safari’s ability to report correct line numbers.

Consider this:

window.onload = () => {
  console.log('something unrelated');const e = new Error("new error");console.log(e.stack);console.log('something unrelated');
}

Running this in Safari you will see in the console: http://127.0.0.1:8081/build/main.js:2:57

2:57 is between the r and the ( in new Error("new error"). Seems reasonable enough.

Now try this:

window.onload = () => {/*
  something about this comment means the line number gets reported incorrectly in the stack
  */console.log('something unrelated');const e = new Error("new error");console.log(e.stack);console.log('something unrelated');
}

Safari console: http://127.0.0.1:8081/build/main.js:3:161

3:161 doesn’t even exist!

I ran this test on Safari version 14.0.2 (16610.3.7.1.9) with the script as an external file pulled in with <script src="./test.js">. We’re seeing it on multiple versions of Safari.

We’ll be raising an issue against the Safari bug tracker.

One special thing about Safari is it implemented Proper Tail Calls. It means some frames in the stack trace are literally eliminated, comparing to all other browsers that don’t have this optimization implemented.

I kind of accepted that in our project all issues will be in 2 versions: safari vs all other browsers. Interesting that in your case Firefox also produces a different col number in the top-most frame.

However, in your examples the line numbers of Safari stack frames are correct. It’s just that some frames are missing. It’s a different issue from the one discussed here, where Safari produces plain wrong line number, sometimes not even existing in the compiled .js file.

EDIT: actually, your safari stack trace has some extra frames with [native] calls. That’s fun.

@djskinner I can confirm that removing comments fixed the issue. Thank you! Still surprised more people don’t run into this, given how popular Safari is on mobile.

For those out there wanting to fix it in webpack 4, this did it for me:

    optimization: {
        minimizer: [new TerserPlugin({
            terserOptions: {
                output: {
                    comments: false,
                }
            },
            sourceMap: true
        })],
    },

And if you’re curious, I did not need to wrap this in a conditional for -p or --mode=production. It seems the optimization is already only applied in these cases and ignored in development mode.