Closed Bug 226507 Opened 22 years ago Closed 22 years ago

[patch] No recursion check in js_EmitTree

Categories

(Core :: JavaScript Engine, defect, P2)

defect

Tracking

()

VERIFIED FIXED
mozilla1.6beta

People

(Reporter: igor, Assigned: brendan)

Details

(Keywords: crash, js1.5)

Attachments

(3 files, 2 obsolete files)

js_EmitTree from jsemit.c while calling itself recursively does not check for allowed stack depth which deliberately constructed scripts can use to cause stack overflow and segmentation fault.
Although emitter somewhat protected against stack overflow by recursion checks in the parser, some recursion sequences consumes more stack space then corresponding code in the parser and still can cause stack overflow. For example, with N in the test set to 35, I got on my RedHat Linux 10 box a segmentation fault from js after setting the stack limit to 100K. When I set the stack limit to 20K I still got the segmentation fault, only after -s was changed to 15K too deep recursion was detected: ~/w/js/x> ulimit -s 100 ~/w/js/x> js fintest.js Segmentation fault ~/w/js/x> js -S $((20*1024)) fintest.js Segmentation fault ~/w/js/x> js -S $((15*1024)) fintest.js fintest.js:19: InternalError: too much recursion After playing with numbers it seems that while processing try/finally the recursion in js_Emit takes 10 times more space the corresponding recursion in the parser.
With the patch I have on my box for a debug build of js: ~/w/js/x> ulimit -s 100 ~/w/js/x> js -S $((90*1024)) fintest.js fintest.js:19: InternalError: too much recursion ~/w/js/x> js -S $((91*1024)) fintest.js fintest.js:19: InternalError: too much recursion ~/w/js/x> js -S $((92*1024)) fintest.js Segmentation fault or for session with stack limitted to 1000K: ~/w/js/x> ulimit -s 1000 ~/w/js/x> js -S $((990*1024)) fintest.js fintest.js:19: InternalError: too much recursion ~/w/js/x> js -S $((992*1024)) fintest.js Segmentation fault which tells that on RedHat 10 K system stack consumes about 8-9 K and since this number remains the same, there is no unaccounted recursion at least for this test case.
Attachment #136103 - Flags: review?(brendan)
Although the patch seems to fix the problem, I am still curious what exactly causes consumtion of over 3K of stack space in js_EmitTree per recusrion.
Severity: normal → critical
Keywords: crash
Summary: No recursion check in js_EmitTree → [patch] No recursion check in js_EmitTree
The actual number is about 1500 bytes between recursive js_EmitTree invocations, or 375 4-byte variables and it is hard to see where they come from.
Severity: critical → normal
1K of it comes from http://lxr.mozilla.org/seamonkey/source/js/src/jsemit.c#2233. The rest will come from the sum of the various local declarations; gcc doesn't reuse stack slots very well, so pretty much every local declared (whether function- or block-scoped) will add to the total. StmtInfos are 32 bytes a piece, and there are two of those, etc. Putting the intmap onto the heap or into a pool would help a lot, I think.
But what about then moving processing of the whole TOK_SWITCH into a separated function? It has a few locals besides intmap_space and allocating of 1K on stack for intmap_space does not seems to be a good idea as well.
The patch changes stack space consumtion between recursive js_EmitTree invocations down to 304 bytes (from about 1500).
Even with the last patch recursion detection still happens in js_EmitTree, not during pasing, but now N in the test increases from 31 to 148 before the test case hits 100K barier of stack space.
Attachment #136103 - Flags: review?(brendan)
Attachment #136103 - Attachment is obsolete: true
Attachment #136118 - Flags: review?(brendan)
Can probably get this into 1.6b. /be
Assignee: general → brendan
Keywords: js1.5
Priority: -- → P2
Target Milestone: --- → mozilla1.6beta
A few nits about variables decl/init order picked, plus a comment typo fix, plus there's no need for the ptop out param. /be
Attachment #136118 - Attachment is obsolete: true
Attachment #136118 - Flags: review?(brendan)
Comment on attachment 136242 [details] [diff] [review] my version of last patch Shaver should bless this, as it's really mostly code motion plus Igor's changes, with a few of mine. So a third party review would be good. /be
Attachment #136242 - Flags: review?(shaver)
Comment on attachment 136242 [details] [diff] [review] my version of last patch Do the code-a-motion with meeeee! bless=shaver
Attachment #136242 - Flags: review?(shaver) → review+
Comment on attachment 136242 [details] [diff] [review] my version of last patch This is the definition of a safe fix. The substantive change adds a stack overflow check that we know works uniformly (it's used in the parser and interpreter). The rest if taking code out of line, and cosmetics. Risk < epsilon. /be
Attachment #136242 - Flags: approval1.6b?
showing just var decl/init reordering. /be
Attachment #136242 - Flags: approval1.6b? → approval1.6b+
Fixed, thanks. /be
Status: NEW → RESOLVED
Closed: 22 years ago
Resolution: --- → FIXED
Igor's testcase added to JS testsuite: mozilla/js/tests/js1_5/Regress/regress-226507.js
On my WinNT4.0 box, after the fix, an optimized JS shell gives InternalError: too much recursion if I set the stack size too low, but a debug JS shell crashes, just as it did before the fix: NTDLL! 77f7645c() CheckStackGrowthDirection(int * 0x0012ee90, unsigned long 2148728632) line 1772 + 39 bytes JS_SetThreadStackLimit(JSContext * 0x00301c60, unsigned long 2148728632) line 1783 + 13 bytes Process(JSContext * 0x00301c60, JSObject * 0x002fb348, char * 0x00301f28) line 331 + 14 bytes ProcessArgs(JSContext * 0x00301c60, JSObject * 0x002fb348, char * * 0x00301ec4, int 6) line 544 + 23 bytes main(int 6, char * * 0x00301ec4, char * * 0x00300e70) line 2318 + 21 bytes JS! mainCRTStartup + 227 bytes KERNEL32! 77f1bbb5() CRASHPOINT: #ifdef DEBUG static void CheckStackGrowthDirection(int *dummy1addr, jsuword limitAddr) { int dummy2; #if JS_STACK_GROWTH_DIRECTION > 0 JS_ASSERT(dummy1addr < &dummy2); JS_ASSERT((jsuword)&dummy2 < limitAddr); #else /* Stack grows downward, the common case on modern architectures. */ JS_ASSERT(&dummy2 < dummy1addr); JS_ASSERT(limitAddr < (jsuword)&dummy2); <<<----------------CRASHED HERE #endif } #endif Here is the exact method using the test driver's -o option to pass the -S option to the JS shell: -S 10^32 PASSES (because there's enough stack ???): perl jsDriver.pl -e smdebug -fTEST.html -o "-S 100000000000000000000000000000000" -k -l js1_5/Regress/regress-226507.js -S 10^31 CRASHES (in debug shell): perl jsDriver.pl -e smdebug -fTEST.html -o "-S 10000000000000000000000000000000" -k -l js1_5/Regress/regress-226507.js I have two questions: 1. Is the debug crash of any importance, or should I mark this bug verified? 2. The number 10^32 seems absurdly high, doesn't it? Does my box have that much stack ???
Comment on attachment 136242 [details] [diff] [review] my version of last patch a=asa (on behalf of drivers) for checkin to 1.6beta
oops, sorry 'bout that belated and redundant approval. We don't yet have collision warnings for attachment updates.
[~/src/trunk-opt/mozilla/js/src]$ ./t 10000000000000000000000000000000 2147483647 [~/src/trunk-opt/mozilla/js/src]$ ./t 100000000000000000000000000000000 2147483647 [~/src/trunk-opt/mozilla/js/src]$ ./t 10000000000000000000000000000000 2147483647 [~/src/trunk-opt/mozilla/js/src]$ ./t 1000000000000000000000000000000 2147483647 [~/src/trunk-opt/mozilla/js/src]$ cat t.c #include <stdlib.h> #include <stdio.h> int main(int argc, char **argv) { printf("%d\n", atoi(argv[1])); return 0; } Phil: all overlarge inputs to atoi (after strtol) should result in LONG_MAX, 2147483647, being returned. So I don't see why you get different results in the two cases you show. Can you debug a bit, see what the return value from atoi is? The DEBUG-only crash should be fixed, but I'd rather this bug not be reopened. File a new one? /be
Phil, did you use the original test case with N set to 35 to get crash under the debug build? I used N=35 to cause SM to hit on Linux 100K of stack consumption without the patch. As far as I remember the default stack size on Windows is arround 100K so assuming that VisualC can not eliminate 1K of switch array data and given that the test case causes 2 recursions of EmitTree per each N, 100K of stack space can be exhausted on Windows as well without the patch. With the patch it should be arround N * 2 * 300 = 21000 or less if VisualC can recycle some locals and it explains success of the optimized build. To explain the problem with the debug build, some information about stack size of debug builds on Windows is required. Or perhaps debug builds uses 2 pages per frame = 8K to detect buffer overflows? Try to run test case with -S set to 15000, then both builds should report too deep recursion errors.
Here are the results of the test program on WinNT and Win2K: [D:/JS_TRUNK/mozilla/js]./t 1000000000000000000000000000000 (10^30) 1073741824 [D:/JS_TRUNK/mozilla/js]./t 10000000000000000000000000000000 (10^31) -2147483648 [D:/JS_TRUNK/mozilla/js]./t 100000000000000000000000000000000 (10^32) 0 [D:/JS_TRUNK/mozilla/js]./t 1000000000000000000000000000000000 (10^33) 0 [D:/JS_TRUNK/mozilla/js]./t 10000000000000000000000000000000000 (10^34) 0 So that explains the earlier change in behavior I noticed at 10^32.
Results from my Win2K machine, before and after the patch. Note the value passed to -S to avoid "too much recursion": -------------------------- BEFORE PATCH -------------------------- (OPTIMIZED SHELL) [ ] perl jsDriver.pl -e smopt -fTEST.html -o "-S 10000 " -k -l js1_5/Regress/regress-226507.js -#- Executing 1 test(s). *-* Testcase js1_5/Regress/regress-226507.js failed: Expected exit code 0, got 3 Testcase terminated with signal 0 Complete testcase output was: ./js1_5/Regress/regress-226507.js:151: InternalError: too much recursion: ./js1_5/Regress/regress-226507.js:151: try { f(); } finally { ./js1_5/Regress/regress-226507.js:151: ......^ -#- Wrote results to 'TEST.html'. -#- 1 test(s) failed [ ]perl jsDriver.pl -e smopt -fTEST.html -o "-S 11000 " -k -l js1_5/Regress/regress-226507.js -#- Executing 1 test(s). -#- Wrote results to 'TEST.html'. -#- 0 test(s) failed (DEBUG SHELL) [ ] perl jsDriver.pl -e smdebug -fTEST.html -o "-S 15000 " -k -l js1_5/Regress/regress-226507.js -#- Executing 1 test(s). *-* Testcase js1_5/Regress/regress-226507.js failed: Expected exit code 0, got 3 Testcase terminated with signal 0 Complete testcase output was: ./js1_5/Regress/regress-226507.js:119: InternalError: too much recursion -#- Wrote results to 'TEST.html'. -#- 1 test(s) failed [ ] perl jsDriver.pl -e smdebug -fTEST.html -o "-S 16000 " -k -l js1_5/Regress/regress-226507.js -#- Executing 1 test(s). -#- Wrote results to 'TEST.html'. -#- 0 test(s) failed -------------------------- AFTER PATCH -------------------------- (OPTIMIZED SHELL) [ ] perl jsDriver.pl -e smopt -fTEST.html -o "-S 15000 " -k -l js1_5/Regress/regress-226507.js -#- Executing 1 test(s). *-* Testcase js1_5/Regress/regress-226507.js failed: Expected exit code 0, got 3 Testcase terminated with signal 0 Complete testcase output was: ./js1_5/Regress/regress-226507.js:119: InternalError: too much recursion -#- Wrote results to 'TEST.html'. -#- 1 test(s) failed [ ] perl jsDriver.pl -e smopt -fTEST.html -o "-S 16000 " -k -l js1_5/Regress/regress-226507.js -#- Executing 1 test(s). -#- Wrote results to 'TEST.html'. -#- 0 test(s) failed (DEBUG SHELL) [ ] perl jsDriver.pl -e smdebug -fTEST.html -o "-S 32000 " -k -l js1_5/Regress/regress-226507.js -#- Executing 1 test(s). *-* Testcase js1_5/Regress/regress-226507.js failed: Expected exit code 0, got 3 Testcase terminated with signal 0 Complete testcase output was: ./js1_5/Regress/regress-226507.js:119: InternalError: too much recursion -#- Wrote results to 'TEST.html'. -#- 1 test(s) failed [ ] perl jsDriver.pl -e smdebug -fTEST.html -o "-S 33000 " -k -l js1_5/Regress/regress-226507.js -#- Executing 1 test(s). -#- Wrote results to 'TEST.html'. -#- 0 test(s) failed
Win2K SUMMARY The patch has forced me to increase the amount of stacksize provided to the JS shell in order to make the testcase pass. Here are my findings from above in condensed form. I am showing the rough "division point" between the testcase failing from too much recursion, vs. passing: -------------------------- BEFORE PATCH -------------------------- (OPTIMIZED SHELL) -S 10000 fail -S 11000 pass (DEBUG SHELL) -S 15000 fail -S 16000 pass -------------------------- AFTER PATCH -------------------------- (OPTIMIZED SHELL) -S 15000 fail -S 16000 pass (DEBUG SHELL) -S 32000 fail -S 33000 pass Do these results look good?
The results are perfectly OK. Since recursion in the parser uses less space then recursion in the emitter, without the check in the emitter the shell detects only maximum reached in the parser which is less then 11000 so -S 11000 does not trigger an exception. With the patch the additional check in the emitter will see the real stack consumtion, that is more then 15000 so execution should trigger an exception when -S 15000 is passed. Notice that before the patch it was parser that complained with -S 10000 so you see line and column in the error output: /js1_5/Regress/regress-226507.js:151: InternalError: too much recursion: ./js1_5/Regress/regress-226507.js:151: try { f(); } finally { ./js1_5/Regress/regress-226507.js:151: ......^ After the patch it was the emitter turn to complain about stack overflow after the parser made the tree while staying within the stack bound given by -S 15000 (no line source or column info): ./js1_5/Regress/regress-226507.js:119: InternalError: too much recursion The same holds for the debug build.
Igor: thank you for such a clear explanation! Marking Verified FIXED. I filed bug 226782, "Crash in debug JS shell with certain stack sizes set", for the issue I raised in Comment #17
Status: RESOLVED → VERIFIED
Flags: testcase+
<http://test.bclary.com/tests/mozilla.org/js/js-test-driver-standards.html?test=js1_5/Regress/regress-226507.js;language=language;javascript> Note this passes in the shell in windows/mac/linux with stack 524288 and in the browser on windows and mac but fails in linux. This failure goes back to at least 2005-12-02 on 1.8/1.9. Unfortunately I just trashed my old logs and don't have earlier results.
(In reply to comment #27) to clarify, this fails in the browser on linux 1.8.*, 1.9 optimized builds only. Should I file a new bug on this, or is it just noise?
(In reply to comment #28) > (In reply to comment #27) > to clarify, this fails in the browser on linux 1.8.*, 1.9 optimized builds > only. Should I file a new bug on this, or is it just noise? Fails, as in crashes? What's the stack (top 20 or so frames, at least)? /be
(In reply to comment #29) fails as in does not catch the recursion error.
attempt to catch the exception if it exists. /cvsroot/mozilla/js/tests/js1_5/extensions/regress-226507.js,v <-- regress-226507.js new revision: 1.3; previous revision: 1.2
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Creator:
Created:
Updated:
Size: