Closed
Bug 123177
Opened 23 years ago
Closed 23 years ago
RFE: Add function stack trace for Error() objects
Categories
(Core :: JavaScript Engine, enhancement)
Tracking
()
VERIFIED
FIXED
mozilla0.9.9
People
(Reporter: WeirdAl, Assigned: brendan)
References
Details
Attachments
(3 files, 2 obsolete files)
10.61 KB,
patch
|
rginda
:
review+
shaver
:
superreview+
|
Details | Diff | Splinter Review |
7.46 KB,
patch
|
rginda
:
review+
shaver
:
superreview+
|
Details | Diff | Splinter Review |
1.14 KB,
text/html
|
Details |
Myself, bz, and shaver have been discussing an idea I had, to add a JavaScript function trace to JavaScript errors. I'm thinking this would be very useful information for people attempting to track down bugs in their JavaScript code, which may not be caused strictly by the function where the error came from. I wrote up a sample script and posted it to JSENG earlier. For reference, I'm reposting it here. function a() { return b() } function b() { return c() } function c() { return d() } function d() { try { throw new Error("DA MOW MOW!!!") } catch (e) { return f() } return true } function f() { var k = "arguments.callee" var response = {} var arr = [] try { while (eval(k + "['caller']")) { arr[arr.length] = eval(k + ".caller") k += ".caller" if (eval("arguments in " + k)) { k+=".arguments.callee" } } } catch(e) {} // we will have an exception, this is expected. response.functionStack = arr return response } function test() { alert(a().functionStack.join("\n\n")) } test() This script is a proof-of-concept only, and is highly inefficient. (It took me some time to get rid of the strict warnings.) A native-code solution would be better by far. bz has raised a concern about untrusted JavaScript accessing chrome functions, but believes following the caller chain would be adequate protection against that. I suggest Error.prototype.functionTrace would be an array of objects, each with a function, its arguments, and the line number last executed in that function as properties. This way we can pinpoint precisely where in each function we moved to the next function, or to the exception. Opinions?
Assignee | ||
Comment 2•23 years ago
|
||
The property is named 'stack', and its value is a string that you can split on ';' to get each frame's description, which can be split on ':' to get function name (or 'anonymous', or 'script') followed by filename followed by line number. Comments please. /be
Assignee | ||
Comment 3•23 years ago
|
||
Format is "fun(args):file:line;..." If no function, fun is empty string; if no args, empty string between (). Readers can split on ; to get per-frame substring, and split on : to get fun(args) followed by file followed by line. /be
Comment 4•23 years ago
|
||
I like the idea, few comments... + for (fp = cx->fp->down; fp; fp = fp->down) { + v = (fp->fun && fp->argv) ? fp->argv[-2] : JSVAL_NULL; + if (!JSVAL_IS_PRIMITIVE(v) && + checkAccess && + !checkAccess(cx, fp->fun->object, callerid, JSACC_READ, &v)) { + JS_free(cx, stackbp); + return JS_FALSE; + } Is |checkAccess| a security thing? If so, should we fail to create the Exception, or just omit the |stack| property? + argstr = JS_ValueToString(cx, fp->argv[i]); That string may have ";" or ":" in it, making the result unparsable. Maybe we should String.prototype.quote() or escape() it. + stackbp = JS_sprintf_append(stackbp, "):%s:%u:%s", + (fp->script && fp->script->filename) + ? fp->script->filename + : "stdin", Perhaps "<none>", or "<null>" is a better default for filename. There are a number of script objects in Mozilla that have a NULL filename and didn't come from stdin.
Assignee | ||
Comment 5•23 years ago
|
||
The split-ability goal seems bogus now, in light of all the possible chars in filenames, never mind in arguments -- it was something I hoped would be easy to satisfy only before shaver pointed out that function actual arguments would be helpful in the stack trace (and only if one avoids : in filenames). Now, readability is king, without wasting too much space on words like "called from file ... at line ..." (which also require localization). A \n terminates each stack frame description (contrast to ; as separator). A : separates file and line, as is traditional (ambiguous on Mac as a split char, though -- but not ambiguous to readers on Macs, I trust). The @ sign is used to separate fun(args) from file:line, and args are generated using js_ValueToSource for best quoting. While figuring out how to deal with errors and exceptions incurred during stack formatting (to fix the problem Rob pointed out where checkAccess failure would stop the script creating a new Error or Exception object, rather than simply result in a truncated stack trace string), I found some less-than-ideal wording in jsapi.h, and over-punctuated or otherwise overlong (or just plain wrong -- what was "Mountain View" doing there?!) patterns in jscntxt.c. Those two files contain cosmetic fixes only. Hoping for r= and sr= now, or soon. /be
Attachment #67623 -
Attachment is obsolete: true
Assignee | ||
Updated•23 years ago
|
Attachment #67624 -
Attachment is obsolete: true
Reporter | ||
Comment 6•23 years ago
|
||
Um, I think a : character would be legal in a query-string. http://www.jslab.org/test?name=value1:value2:value3 So might almost any other character following a URI. Spaces are a notable exception. Spaces have to be encoded. If we're not able to safely determine characters for separators, would it be harder to return an array of objects, as I originally suggested?
Even with colons in the filename, as you are pretty much guaranteed to have with URLs, you're fine: everything after the last colon is lineno, and the rest is the filename (excluding that final colon). Regular expressions, String.prototype.lastIndexOf, strrchr(3): take your pick. (I thought literal newlines were legal in javascript: and perhaps data: URLs, if perhaps not as attribute values in HTML.) I think brendan is remiss in not demonstrating his work, so I'll do that for him. This is "script:file.js": function f(g, a) { g("75", a); } function e(prop) { f(error, Math[prop]); } function error(a1, a2) { var x = a1 + a2; throw Error(); } try { e("PI"); } catch (ex if ex instanceof Error) { print("stack: ", ex.stack); } : tip; Linux_All_DBG.OBJ/js -f script:file.js stack: error("75",3.141592653589793)@script:file.js:11 f(function error(a1, a2) {var x = a1 + a2;throw Error();},3.141592653589793)@script:file.js:2 e("PI")@script:file.js:6 ()@script:file.js:15 The line numbers match up, and everything. No locals, but really, this isn't meant to supplant debuggers.
Comment on attachment 67643 [details] [diff] [review] best yet, can it be better still? Move the declarations of v and argsrc down to the beginning of their respective for-blocks? Locality is next to godliness, or something, though I guess prevailing style is sort of otherwise. sr=shaver.
Attachment #67643 -
Flags: superreview+
Reporter | ||
Comment 9•23 years ago
|
||
For documentation and reuse purposes, I'll write a script to read and parse this string once I have a build with the patch enabled, and I'll attach it to this bug. We'll need that anyway for other utilities to use this (such as JS Console)
Comment 10•23 years ago
|
||
Comment on attachment 67643 [details] [diff] [review] best yet, can it be better still? + * so callers must stop using the pointer returned from Save after calling the + * appropriate Release or Drop API. appropriate Restore or Drop API? r=rginda
Attachment #67643 -
Flags: review+
Comment 11•23 years ago
|
||
I second Alex's request that the value of this property be an array, not a delimited string. The latter may be appropriate to a low-level language where storage concerns are paramount, but the former seems more in keeping with a high-level language like JavaScript. 1. Example: the return value of a regexp with backreferences, say /(a)(b)(c)/("abc"), is an array, not a delimited string. 2. How would it look in a textbook on JavaScript? "You capture the call stack like this:" var str = err.stack; var arr = str.split(some_delimiter); What delimiter? Oh, you have to memorize that. And every time you use this property you'll have to look this up. OR: var arr = err.stack; 3. The developer will most likely have need of the frames of the call stack, not the concatenation of all the frames together. 4. Finally, the array approach is more in keeping with what many JavaScript programmers currently do do capture call stacks: function getCallStack(f) { var arr = []; while (f) { arr.push(f); f = f.caller; } return arr; }
Assignee | ||
Comment 12•23 years ago
|
||
A JS backtrace function can make an array easily, but should the engine allocate an object and a bunch of strings from code that is creating an exception object, possibly under duress? Especially in embeddings that run into low-memory conditions normally? /be
Comment 13•23 years ago
|
||
Oops, I didn't think of that!
Reporter | ||
Comment 14•23 years ago
|
||
As I said, I'll write a function to parse the string appropriately once I have a build with that property. Unfortunately, my Internet time has suddenly become very very short, and it will be a little while before I'm able to download the build. How about this as an idea, however: a parseStack() method of Error.prototype, to return an array as I suggested? (It would need its own toString(), I suspect, though.) I note there are only a couple minor suggestions from r= and sr=, and that the tree is open. Might we have this checked in right soon?
Assignee | ||
Comment 15•23 years ago
|
||
I declare victory. I'm not going to bloat compiled code footprint (or source code brainprint for that matter) with a gratuitous native method to parse the stack string, when it's easy enough to write in JS. If parsing could be made easier by a better format, please open a new bug on that. I still think the first goal is concise readability, but admit that I err on the side of concise in general. But I've checked in the fix for this bug, so at least we have a stack property of exception objects now. Thanks for the prodding to do this, Alex! /be
Status: NEW → RESOLVED
Closed: 23 years ago
Resolution: --- → FIXED
Assignee | ||
Comment 16•23 years ago
|
||
Argh, mccabe didn't make the internal js_ErrorToException code construct the Exception object, so try { foo.bar } catch (e) { print(e.stack) } where foo is not defined will not get a predefined stack property. Only explicitly new'ed Exception, Error, etc. classes. Patch to construct coming up. /be
Status: RESOLVED → REOPENED
Resolution: FIXED → ---
Assignee | ||
Comment 17•23 years ago
|
||
I'll fix. /be
Assignee: khanson → brendan
Status: REOPENED → NEW
Target Milestone: --- → mozilla0.9.9
Comment 18•23 years ago
|
||
This checkin added 2 "may be used uninitialized" warnings: js/src/jsexn.c:329 `older' might be used uninitialized in this function js/src/jsexn.c:330 `state' might be used uninitialized in this function Even though this is the case of compiler not being smart enough, it would still be nice not to have those warnings so that real bugs are not missed in the pile of warnings.
Assignee | ||
Comment 19•23 years ago
|
||
All exception objects are constructed now, so they get the stack property. I also finally extended the API to provide JS_ConstructObjectWithArguments, which lots of people have requested, and which is a trivial layer on top of the needed js_ConstructObject extension (to take argc/argv) used by jsexn.c. Looking for review for 0.9.9. /be
Comment 20•23 years ago
|
||
Comment on attachment 68739 [details] [diff] [review] proposed followup fix r=rginda
Attachment #68739 -
Flags: review+
Comment on attachment 68739 [details] [diff] [review] proposed followup fix I like the __GNUC__ bit, pushing the cost of the unnecessary assignments onto the loserly GNU compilers. sr=shaver.
Attachment #68739 -
Flags: superreview+
Assignee | ||
Comment 22•23 years ago
|
||
I'll check in today. Phil, there's a test in the suite that wrongly expects 4 to be the value of Error.length (e.g., SyntaxError.length, etc. too), because the stack property is enumerable. I think that test should just stipulate the number of arguments it that expects length to reflect, instead of trying to count with a for..in loop. /be
Assignee | ||
Comment 23•23 years ago
|
||
Fix is in. /be
Status: NEW → RESOLVED
Closed: 23 years ago → 23 years ago
Resolution: --- → FIXED
Reporter | ||
Comment 24•23 years ago
|
||
FYI: I've filed bug 125612 for adding the function stack trace to the JS Console.
Comment 25•23 years ago
|
||
Comment 26•23 years ago
|
||
The testcase is for the browser, but may be run in the JS shell by simply excising the <SCRIPT> tags; no further changes are necessary. The test generates a SyntaxError, a ReferenceError, and a TypeError. Note the first produces a stack with one more frame than the other two. This is because I used eval() to generate the error, making one more function call for the stack to record -
Comment 27•23 years ago
|
||
Verified FIXED using the latest WinNT Mozilla binary and JS shell. I have tested that the stack property is accurate for the above testcase. I have also tested a variation producing errors from an external JS file; and again the stacks were accurate. I also tested in the JS shell - I really like the use of '\n' as the delimiter; I find the display quite clear and intuitive -
Status: RESOLVED → VERIFIED
Comment 28•23 years ago
|
||
The testcase Brendan mentioned in Comment #22 has now been repaired: mozilla/js/tests/js1_5/Exceptions/regress-123002.js
Updated•23 years ago
|
Attachment #69615 -
Attachment mime type: text/html → text/plain
Updated•23 years ago
|
Attachment #69615 -
Attachment mime type: text/plain → text/html
Reporter | ||
Comment 29•21 years ago
|
||
FYI, I've filed bug 229824 for adding a similar stack trace to DOM exceptions. Anyone willing to translate Brendan's C code into C++ to get the same effect? (I'm guessing DOMException.cpp is the right place.)
Assignee | ||
Comment 30•21 years ago
|
||
See bug 229824 comment 1. /be
Updated•19 years ago
|
Flags: testcase+
You need to log in
before you can comment on or make changes to this bug.
Description
•