Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Errors trapped in window.onerror lacks stacktrace #26

Closed
rakeshpai opened this Issue · 32 comments
@rakeshpai

I tested this in at least Firefox and Chrome, and the stack trace is completely uninformative if called from window.onerror. I guess this makes sense, since by the time the error has reached the onerror handler, the stack has already been unrolled and discarded. Since there's no error object that can be used, printStackTrace creates it's own error object. Since this is now the new point where the error has been thrown, and since no previous stack trace is available anyway, the stack trace is now filled with only stack frames from within the stacktrace.js function.

If I'm doing something something wrong and don't know about it, please let me know.

My code:

window.onerror = function() {
  alert(printStackTrace({guess:true}).join("\n");
}

function a() {
  b();
}

function b() {
  c();
}

a(); // Throws an error since c is undefined

Output in Firefox:

createException()@http://localhost:4000/stacktrace.js:42
run(null)@http://localhost:4000/stacktrace.js:27
printStackTrace([object Object])@http://localhost:4000/stacktrace.js:18
onerror("c is not defined","http://localhost:4000/test.js",6)@http://localhost:4000/test.html:5

Note that the last line in the trace above seems to be not from the stack, but from the arguments of the window.onerror handler, which I could have got anyway without printStackTrace by inspecting the arguments object in the handler. There's no mention of a or b that lead up to c.

Output in Chrome:

Object.createException (http://localhost:4000/stacktrace.js:42:18)
Object.run (http://localhost:4000/stacktrace.js:27:25)
printStackTrace (http://localhost:4000/stacktrace.js:18:62)
onerror()@http://localhost:4000/test.html:5:11

I tried moving a, and b to a separate js file included with a <script> tag, to see if {guess:true} will be able to help by reading the source, but that didn't help.

Am I doing something wrong?

@oyvindkinsey

I'm wondering if it's actually possible to hook into the debugger presented by modern browsers here..

@rakeshpai

Would be awesome if that was possible, even if it wouldn't work everywhere.

I've been looking at approaches to intercept function invocation in some global way (maybe by modifying Function, if possible) so that stack traces can be built manually, but so far no luck. I can intercept calls to func.call and func.apply by modifying Function.prototype.call, etc. However, that's not how most functions are invoked, so it's pretty useless if I can't intercept calls to func(). Any thoughts/help would be awesome.

If it is possible to intercept function invocation, building a stack trace that's reliable everywhere then becomes trivial.

@eriwen
Owner

@rakeshpal - Actually I spent quite a lot of time trying to figure out how to do real-time tracing by intercepting all function calls. I eventually gave up as well running into slightly different issues. I have fixed similar bugs to this in stacktrace 0.5 and I'll see if your case is covered here.

@oyvindkinsey - That's a good idea, perhaps I can research it. Do you have any insights that you can provide?

@oyvindkinsey

@eriwen For now it doesn't look like this will be accessible for unprivileged javascript due to security reasons.

For my own project, which already had an engine rewriting the served javascript so as to name all anonymous functions when the site was running in debug mode, I was easily able to instrument the code. For an example look at this gist https://gist.github.com/1125181.

It creates an output similar to this:

_populateFilter(Object,Object,true)
populateFilters(Object,Object,Object)
getInitalizationDataOnComplete(Object,Object,'GetQueryFilters')
_webRequestManager_OnWebRequestCompleted(Object,Object)
ExorLive$Client$Web$Js$Widgets_18(Array,Object,'GetListOfRandomNewExercises')
_webRequestManager_OnWebRequestCompleted(Object,Object)
ExorLive$Client$Web$Js$Widgets_2(Object)
responseWrapperHandler(Object,Object,'GetMessage')
_webRequestManager_OnWebRequestCompleted(Object,Object)
ExorLive$Client$Web$Js$Pages$Workouts_20(Array)
@rakeshpai

@oyvindkinsey That's interesting, but requires a separate rewrite step before evaluation. That makes it unsuitable for in-browser real-time computation of the stack trace. I wonder if it'll be possible to do this without rewriting the source-code before evaluation.

@eriwen You mentioned that you've tried different approaches with slightly different issues. Do you have them documented anywhere I can look up? If not, can you please list them here if you don't mind? I'll see if I can take it from where you left off. If intercepting function invocation can take me any far at all, it'll be my best bet.

@eriwen
Owner

@rakeshpai Out of that I came up with the function instrumentation idea (which rewrites functions on the fly), which you'll see as part of stacktrace.js. The best I could do is instrument named, non-private functions. If you try to instrument everything in, say, jQuery though everything becomes unusably slow, so I don't generally recommend doing it in an automatic way.

Let me know if you need more info on how this is done.

@oyvindkinsey

I'm doing the rewriting only on application code, not framework code, and then only in periods where I need the extra information, eg debugging, testing and in the period directly after a new release.

In the end, the only way to be safe is to program defensively, and I've turned more and more to use preconditions to verify that the input to the applications entry points are correct. If not then the error can be reported there and then.

My project JsContract can be used for this: http://kinsey.no/projects/jscontract/

@superfeedr

I have the same issue... all the errors from Chrome are quite useless :/ any hint on how to fix this?

@alexturpin

I would also LOVE to be able to get a stack trace on window.onerror. I'll be following this issue, if anyone has a solution or a partial one please share.

@eriwen eriwen was assigned
@jpoehls

The homepage (http://stacktracejs.org/) specifically states that you can get the stack trace of a thrown error by hooking into window.onerror. It looks like this is not the case. At least you can't get a meaningful stack trace this way. You should probably remove this example from the homepage since it is misleading.

@eriwen
Owner

@jpoehls You're right, it is misleading as it doesn't really give you a meaningful trace. I have a solution in the works for this, but for now I'm removing it from all my examples.

@pepawel

Could you share your solution? I would like to see it, even if it's not finished.

@eriwen
Owner

@pawel: Basically having stacktrace.js register an onerror handler itself that adds the given line and file passed into it to the top of the stack and working backwards from there.

@dachev

Please remove window.onerror from the README. It's misleading and can make people waste HOURS trying to make it work.

@raylu

Note that the window.onerror handler is still on the homepage.

Also, I'd be interested to find out how you plan to "work backwards" given the line and file.

@SunboX

subscribe

@geekdave

+1

(if there is a better way to "subscribe" to notifications on an issue, please let me know... sorry for any n00b'ness)

@JamesMGreene

Subscribe

@rakeshpai

@SunboX @geekdave @JamesMGreene You can subscribe by hitting the link just below this comment box. Don't need to actually leave a comment. :)

@eriwen as @raylu mentioned, I'd be very interested in knowing how you plan to work backwards too. I can't think of a way.

@eriwen
Owner

@rakeshpai A lot depends upon getting the correct line number (and having only one function call on that line) from window.onerror and then going back to that function, figuring out which external functions it calls, and basically constructing a tree of possibilities from there. It's rather brute force and very difficult to get right.

On the other hand, if we can get clever about function instrumentation, we might be able to construct intermediary traces and work that in. Again, very difficult but possibly with a lot more potential.

I'm open to suggestions here, too.

@JamesMGreene

I would earnestly suggest asking browser manufacturers to add a window.getLastError function (so as not to break backward compatibility with the terrible window.onError callback signature). I know it doesn't offer any immediate solution to this problem but it will help in future endeavors!

@JamesMGreene

@eriwen: You could consider using @ariya's Esprima JS parser and Esmorph for more advanced instrumentation possibilities. Still not sure if it would buy you what you need, though.

Checkout out this commentary on my blog and this full blog post from @ariya.

@rquinlivan

I'm looking into this problem. Since window.onerror doesn't provide the Error object, I don't see how you could accomplish this using the normal Error methods – e.g., stack, message, etc. I wonder if window.onerror somehow preserves the execution context for the code that it is reporting on (which is what all the stack trace is, really – the execution context stack).

@eriwen
Owner

@rquinlivan I welcome your ideas. Including an Error object as the 4th argument to window.onerror has been proposed to browser vendors. I don't see much traction in the near future on that front.

@JamesMGreene

Good news! The living HTML5 spec now includes:

  1. A 4th and 5th argument for window.onerror: column and error, respectively. I got the latter added, and it is the actual Error instance, or null if the Error is from another domain.
  2. An actual "error" event on the window object that you can bind to with window.addEventListener. Its only callback argument is an ErrorEvent (note: not Error) object instance.

Great write-up on this: https://mikewest.org/2013/08/debugging-runtime-errors-with-window-onerror

Implementations are just getting started so YMMV.

@brianr

This is fantastic news. Thanks @JamesMGreene for the update and your work on this!

@JamesMGreene

Happy to help, @brianr. We're all in this battle together, after all, and debugging unexpected/unhandled errors blindly is not working out for me (nor is instrumenting all of the code an option)... something had to be done! Luckily for us, HTML5 has finally got the browser vendors to start syncing up, so the time was nigh. :wink:

@FGRibreau

Awesome! Thanks for the tip @JamesMGreene can't wait that all browsers support this!

@EleventyOne

This is great news. Just wanted to say thanks for everyone who is working (and has worked) on this tool. It has saved me SO. MUCH. TIME.

@stripathix

I finally figured out something

function test() {
var a = b;
}

function errorhandler(msg, url, ln) {
try {
var trace = printStackTrace({e: msg});
var traceMsg = printStackTrace({e: msg }).join("\n");
console.log(traceMsg);
console.log(trace);
} catch (error) { // do nothing
alert("Error:" + msg);
return true;
}
}

window.onerror = errorhandler;

window.onload = test();

We have to pass error message of window.onerror handler like this :-1:
printStackTrace({e: msg});

hope this helps....

@JamesMGreene

In IE11, Firefox, and Chrome, you can now get the actual Error object from window.onerror:

window.onerror = function(msg, url, lineno, colno, error) {
  try {
    var trace = printStackTrace({ e: error || msg });
    console.log(trace.join("\n"));
    console.log(trace);
  }
  catch (e) { // do nothing 
    console.log("Fatal Error: " + msg);
    // Don't allow for infinitely recursively unhandled errors
    return true;
  }
};
@eriwen eriwen removed this from the 0.5 milestone
@eriwen
Owner

Thanks @JamesMGreene. Closing this issue in favor of #96.

@eriwen eriwen closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Something went wrong with that request. Please try again.