Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

errors: annotate frames for prepareStackTrace() #30984

Closed
wants to merge 2 commits into from

Conversation

bcoe
Copy link
Contributor

@bcoe bcoe commented Dec 16, 2019

When source-maps are enabled, overriding Error.prepareStackTrace()
is again supported. Error.prepareStackTrace() will now be passed
stack frames with the methods getOriginalFileName(),
getOriginalLineNumber(), and getOriginalColumnNumber() which provide
information parsed from source map.


I think this is a good solution to the exception @dougwilson raises in #29994, and provides tools like depd an option for taking advantage of source-map information.

fixes #29994

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines

@devsnek
Copy link
Member

devsnek commented Dec 16, 2019

I'm very uncomfortable adding more surface to this API. I'd prefer something not attached to any JS/engine built-ins.

@joyeecheung
Copy link
Member

joyeecheung commented Dec 16, 2019

While I share @devsnek's concerns in general, it seems the interop between --enable-source-map and Error.prepareStackTrace is important for the option to get used and eventually get out of experiments, and therefore this deserves an exception.

I am not sure whether what's proposed in this PR should be the way forward, though...it seems at the moment, the least intrusive first step would be to implement what @targos suggested in #29994 (comment)

if Error.prepareStackTrace == the original function from V8: add source map information
else: do not interfere and let the user land function do its thing

(though in practice there's no original function - it's either undefined, or set by the user).

That is, instead of having --enable-source-map override Error.prepareStackTrace, the latter overrides the former - --enable-source-map becomes a noop when Error.prepareStackTrace is set. Maybe that's already enough for packages that already use the error stack trace hook in the wild? And this does not add any more surface to this API.

@bcoe
Copy link
Contributor Author

bcoe commented Dec 16, 2019

@joyeecheung @devsnek I think it would be extremely valuable to upstream tooling to expose the applied source-map information, in the form of an annotated stack trace.

I can understand why folks want to override Error.prepareStackTrace, since working with an array of stack frame objects is much more malleable than an error string.

I was picturing that we get feedback from folks on the V8 side of things, and make sure that, if we extend this API surface, it's on their radar.

@bcoe
Copy link
Contributor Author

bcoe commented Dec 16, 2019

Alternative approach

I could imagine an alternative where we expose findSourceMap(t.getFileName(), error), and the SourceMap class, and allow the upstream library to perform the logic themselves... but since what the user surely wants is the original line position of a stack frame, just giving them this felt potentially more elegant.

Should Error.prepareStackTrace override source-maps?

--enable-source-map becomes a noop when Error.prepareStackTrace is set. Maybe that's already enough for packages that already use the error stack trace hook in the wild?

I don't think this is enough for libraries in the wild. I looked at why express is throwing an exception. It's not that they themselves are handling source-maps, it's that they're annotating errors with deprecation warnings (in depd), and our failure to respect Error.prepareStackTrace stepped on the toes of this process.

Given that express is one of the most popular deployment environments for Node.js, and would benefit from user friendly stack traces (with the original source position). I think figuring out an API to support them would be a great litmus test for the design of the source-map feature.

User testing

This is particularly on my mind, because I asked @iansu to try out source-maps at Node interactive, and the first thing he did was start an express app with --enable-source-maps, and immediately get a difficult to debug exception in depd.

@joyeecheung
Copy link
Member

This is particularly on my mind, because I asked @iansu to try out source-maps at Node interactive, and the first thing he did was start an express app with --enable-source-maps, and immediately get a difficult to debug exception in depd.

How does it work without --enable-source-maps? Just stack traces but with only the actual source positions instead of original source positions? IIUC depd would still have to be updated even with this change to display the original source positions. It also has to detect Node.js versions in the code to make this work with multiple Node.js versions. Having an array with objects whose methods are uncertain do not seem to result in very clean code in that case - I guess an alternative would be to pass a second array to the hook function that has source-mapped call sites, instead of mutating the original one. This probably also works better when there are fewer call sites in the original code (e.g. when one line of code is mapped to many lines of polyfills / transpiled code).

@bcoe
Copy link
Contributor Author

bcoe commented Dec 16, 2019

IIUC depd would still have to be updated even with this change to display the original source positions.

correct 👍 I would like to:

  1. stop depd from throwing, since this is a bad experience.
  2. figure an API that would allow depd and similar libraries to be extended upon to provide remapped error information.

I guess an alternative would be to pass a second array to the hook function that has source-mapped call sites

If we took this approach, I think this would make the upstream algorithm a bit of a hassle, because you tend to have source-maps for some of the stack trace, but not other parts. And, as in our solution, you might want to show both the original and remapped position ...

If we were going to go down the path of adding a parameter to prepareStackTrace perhaps a better API would be a WeakMap, which you can pass the callSite to, and get the remapped information?

@devsnek
Copy link
Member

devsnek commented Dec 16, 2019

i think we should enable prepareStackTrace using the regular trace (no modified or inserted call sites) in the short term. This will make all the weird libs that depend on it continue to function. Longer term we can then explore new apis, modifications to the api, etc, that will let source maps play better with it.

Copy link
Member

@addaleax addaleax left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’m good with this, personally.

@joyeecheung
Copy link
Member

If we took this approach, I think this would make the upstream algorithm a bit of a hassle, because you tend to have source-maps for some of the stack trace, but not other parts. And, as in our solution, you might want to show both the original and remapped position ...

I am not sure whether it actually comes up in real world, but isn't it also possible that there might be multiple original frames that only correspond to one runtime frame? (e.g. if the transpiler "inlines" some calls as an optimization). In that case how would the user get this information out of the mutated array?

@bcoe
Copy link
Contributor Author

bcoe commented Dec 24, 2019

@joyeecheung a source map can map from one transpiled file, to many original files, but I don't think that a call site could be in more than one of these files?

I like the suggestion of supplementing the API with an additional parameter that allows the user to lookup, rather than annotating the object that originates in V8. I'll work on a PR over the next few days that adds back support for overriding prepareStackTrace, in one PR, and in another PR proposers how we might pass a lookup object to the overridden prepareStackTrace...

This would potentially allow us to support edge-cases like you're suggesting, by returning an array.

When source-maps are enabled, overriding Error.prepareStackTrace()
is again supported.  Error.prepareStackTrace() will now be passed
stack frames with the methods getOriginalFileName(),
getOriginalLineNumber(), and getOriginalColumnNumber() which provide
information parsed from source map.
Copy link
Member

@benjamingr benjamingr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand the reservations but I believe this makes node more holistic for developers.

@benjamingr
Copy link
Member

@devsnek @joyeecheung - do either of you feel strongly enough to block? I noticed this currently has 2 approvals and I wanted to make sure that if you wish to object you do :]

@joyeecheung
Copy link
Member

joyeecheung commented Dec 28, 2019

If we were going to go down the path of adding a parameter to prepareStackTrace perhaps a better API would be a WeakMap, which you can pass the callSite to, and get the remapped information?

Do we need to provide a WeakMap through the prepareStackTrace API? Can we just expose a function that allows the user to get the mapping out-of-band? Judging from how CoffeScript maps the stack I think this would already be enough:

  1. If Error.prepareStackTrace is not set by the user when --enable-source-map is on, we can use our version of the callback for best-effort (although looking at the examples at https://coffeescript.org/ and https://livescript.net/ I am pretty sure this would not be enough - many of them map comprehension-like constructs or simple functions into multiple nested functions or IIFEs and seeing those intermediate functions in the stack trace would not really help users much, or would just be even more confusing, but then only the transpiler really knows how to eliminate those and Node core only has very limited knowledge about this)
  2. If the Error.prepareStackTrace is set by the user when --enable-source-map is on, call the the user-land callback and do not call our own callback. Essentially this means --enable-source-map is a noop at the moment if that hook is set and we'll allow the user do their own thing without fiddling with them. The user can get the source map information from a separate API and use it in their own callback (we could even just return an object serialized from sourceMapCacheToObject and let the user deal with it themselves?), this is basically what CoffeScript has been doing.

Maybe some insight from people working on other transpiled languages would be useful here, but from what I can tell mutating the callsites do not seem to be necessary for this kind of support, then I'd like to avoid this if possible.

Copy link
Member

@joyeecheung joyeecheung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll put my -1 here unless the mutation to the callsites is proven necessary (at the moment I believe it's not)

@bcoe bcoe closed this Dec 28, 2019
@bcoe
Copy link
Contributor Author

bcoe commented Dec 28, 2019

I like the idea of exposing an out of band API for looking up source maps, which upstream tooling could use. I'll work on drafting something up soon; I think we can pretty much just expose the API we already have internally.

@joyeecheung
Copy link
Member

joyeecheung commented Dec 28, 2019

By the way, I think it might still be possible to provide in-band information in the callback that's useful enough for users if source maps supports "stack tracing mapping without knowledge of the source language" - which does not seem to be specified at the moment? I am pretty ignorant about the source map standardization so I can't tell if that's a wont-fix or a TODO.

@devsnek
Copy link
Member

devsnek commented Dec 28, 2019

@bcoe are you going to put up a pr to re-enable unmodified prepareStackTrace? If not I might be able to get around to it.

@bcoe
Copy link
Contributor Author

bcoe commented Dec 29, 2019

@devsnek I'll have a PR within a few hours that exposes source_map.findSourceMap(path[, error]) with documentation, and reenables prepareStackTrace with tests, I think it's a pretty reasonable compromise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Error.prepareStackTrace not called when --enable-source-maps
5 participants