Skip to content

Commit 1158355

Browse files
author
Irina Yatsenko
committed
[1.10>master] [MERGE #5497 @irinayat-MS] Move re-throw of exception out of catch
Merge pull request #5497 from irinayat-MS:StackOverflow Fixes OS.18246154 Re-throw from catch doesn't unwind the stack (only resets the active frame pointer) so, if there are nested try/catch blocks that refrow, the stack keeps growing, which is unhelpful if the initial exception is trying to prevent Stack Overflow. To avoid this, move re-throw outside of the catch (so the stack fully unwinds on each handling site). Took the opportunity to add more test coverage for typeof and fix exception handling deviations from the spec (and V8).
2 parents e0f049c + a3c6bac commit 1158355

File tree

5 files changed

+169
-100
lines changed

5 files changed

+169
-100
lines changed

lib/Runtime/Language/JavascriptOperators.h

+30-17
Original file line numberDiff line numberDiff line change
@@ -22,36 +22,47 @@ namespace Js
2222
CONTEXT ep##c; \
2323
EXCEPTION_POINTERS ep = {&ep##er, &ep##c};
2424

25+
// Typeof operator should return 'undefined' for undeclared or hoisted vars
26+
// and propagate all other exceptions.
27+
//
28+
// NB: Re-throw from catch unwinds the active frame but doesn't clear the stack
29+
// (catch clauses keep accumulating at the top of the stack until a catch
30+
// that doesn't re-throw). This is problematic if we've detected potential
31+
// stack overflow and report it via exceptions: the handling of throw
32+
// might actually overflow the stack and cause system SO exception.
33+
// Thus, use catch to cache the exception, and re-throw it outside of the catch.
2534
#define TYPEOF_ERROR_HANDLER_CATCH(scriptContext, var) \
2635
} \
2736
catch (const JavascriptException& err) \
2837
{ \
29-
JavascriptExceptionObject* exceptionObject = err.GetAndClear(); \
38+
exceptionObject = err.GetAndClear(); \
39+
var = scriptContext->GetLibrary()->GetUndefined(); \
40+
41+
#define TYPEOF_ERROR_HANDLER_THROW(scriptContext, var) \
42+
} \
43+
if (exceptionObject != nullptr) \
44+
{ \
3045
Js::Var errorObject = exceptionObject->GetThrownObject(nullptr); \
31-
if (errorObject != nullptr && Js::JavascriptError::Is(errorObject)) \
46+
HRESULT hr = (errorObject != nullptr && Js::JavascriptError::Is(errorObject)) \
47+
? Js::JavascriptError::GetRuntimeError(Js::RecyclableObject::FromVar(errorObject), nullptr) \
48+
: S_OK; \
49+
if (JavascriptError::GetErrorNumberFromResourceID(JSERR_UndefVariable) != (int32)hr) \
3250
{ \
33-
HRESULT hr = Js::JavascriptError::GetRuntimeError(Js::RecyclableObject::FromVar(errorObject), nullptr); \
34-
if (JavascriptError::GetErrorNumberFromResourceID(JSERR_Property_CannotGet_NullOrUndefined) == (int32)hr \
35-
|| JavascriptError::GetErrorNumberFromResourceID(JSERR_UseBeforeDeclaration) == (int32)hr) \
51+
if (scriptContext->IsScriptContextInDebugMode()) \
3652
{ \
37-
if (scriptContext->IsScriptContextInDebugMode()) \
38-
{ \
39-
JavascriptExceptionOperators::ThrowExceptionObject(exceptionObject, scriptContext, true); \
40-
} \
41-
else \
42-
{ \
43-
JavascriptExceptionOperators::DoThrow(exceptionObject, scriptContext); \
44-
} \
53+
JavascriptExceptionOperators::ThrowExceptionObject(exceptionObject, scriptContext, true); \
54+
} \
55+
else \
56+
{ \
57+
JavascriptExceptionOperators::DoThrowCheckClone(exceptionObject, scriptContext); \
4558
} \
4659
} \
47-
var = scriptContext->GetLibrary()->GetUndefined();
48-
49-
#define TYPEOF_ERROR_HANDLER_THROW(scriptContext, var) \
5060
} \
5161
if (scriptContext->IsUndeclBlockVar(var)) \
5262
{ \
5363
JavascriptError::ThrowReferenceError(scriptContext, JSERR_UseBeforeDeclaration); \
54-
}
64+
} \
65+
}
5566

5667
#ifdef ENABLE_SCRIPT_DEBUGGING
5768
#define BEGIN_TYPEOF_ERROR_HANDLER_DEBUGGER_THROW_IS_INTERNAL \
@@ -80,6 +91,8 @@ namespace Js
8091
#endif
8192

8293
#define BEGIN_TYPEOF_ERROR_HANDLER(scriptContext) \
94+
{ \
95+
JavascriptExceptionObject* exceptionObject = nullptr; \
8396
try { \
8497
Js::JavascriptExceptionOperators::AutoCatchHandlerExists autoCatchHandlerExists(scriptContext); \
8598
BEGIN_TYPEOF_ERROR_HANDLER_DEBUGGER_THROW_IS_INTERNAL

test/Basics/rlexe.xml

-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,6 @@
148148
<test>
149149
<default>
150150
<files>typeof.js</files>
151-
<baseline>typeof.baseline</baseline>
152151
<compile-flags>-Intl-</compile-flags>
153152
</default>
154153
</test>

test/Basics/typeof.baseline

-21
This file was deleted.

test/Basics/typeof.js

+85-52
Original file line numberDiff line numberDiff line change
@@ -3,63 +3,96 @@
33
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
44
//-------------------------------------------------------------------------------------------------------
55

6-
WScript.Echo("typeof (new String()) : " + typeof (new String("")));
7-
WScript.Echo("typeof () : " + typeof (""));
6+
WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
87

9-
WScript.Echo("typeof (new Boolean(false)) : " + typeof (new Boolean(false)));
10-
WScript.Echo("typeof (false) : " + typeof (false));
8+
var tests = [
9+
{
10+
name: "typeof of literals, built-in types and object wrappers",
11+
body: function () {
12+
var arr = [42];
1113

12-
WScript.Echo("typeof (new Number(0)) : " + typeof (new Number(0)));
13-
WScript.Echo("typeof (0) : " + typeof (0));
14+
assert.areEqual("object", typeof null, "typeof null");
15+
assert.areEqual("undefined", typeof undefined, "typeof undefined");
16+
assert.areEqual("string", typeof "", "typeof empty string");
17+
assert.areEqual("boolean", typeof false, "typeof false");
18+
assert.areEqual("number", typeof 0, "typeof 0");
19+
assert.areEqual("number", typeof 12345.678, "typeof 12345.678");
20+
assert.areEqual("object", typeof {}, "typeof {}");
21+
assert.areEqual("object", typeof arr, "typeof array");
22+
assert.areEqual("number", typeof arr[0], "typeof arr[0]");
23+
assert.areEqual("undefined", typeof arr[1], "typeof arr[1]");
24+
assert.areEqual("object", typeof /abc/, "typeof /abc/");
25+
assert.areEqual("function", typeof (function (){}), "typeof (function (){})");
26+
assert.areEqual("function", typeof (() => 42), "typeof (() => 42)");
27+
assert.areEqual("symbol", typeof Symbol(), "typeof Symbol()");
1428

29+
// built-in object and object wrappers
30+
assert.areEqual("object", typeof (new String("")), "typeof empty string object wrapper");
31+
assert.areEqual("object", typeof (new Boolean(false)), "typeof (new Boolean(false))");
32+
assert.areEqual("object", typeof (new Number(0)), "typeof (new Number(0))");
33+
assert.areEqual("object", typeof (new Number(12345.678)), "typeof (new Number(12345.678))");
34+
assert.areEqual("object", typeof (new Object()), "typeof (new Object())");
35+
assert.areEqual("object", typeof (new Array()), "typeof (new Array())");
36+
assert.areEqual("object", typeof (new Date(123)), "typeof (new Date(123))");
37+
}
38+
},
39+
{
40+
name: "typeof of expressions",
41+
body: function () {
42+
function f() {
43+
function g() { }
44+
return g;
45+
}
46+
assert.areEqual("function", typeof f(), "typeof of function call");
47+
assert.areEqual("number", typeof eval(7*6), "typeof of direct eval");
48+
}
49+
},
50+
{
51+
name: "typeof handling of undefined variables",
52+
body: function () {
53+
assert.areEqual("undefined", typeof x, "typeof of undeclaired var");
54+
assert.areEqual("undefined", typeof {}.x, "typeof {}.x");
1555

16-
WScript.Echo("typeof (new Number(12345.678)) : " + typeof (new Number(12345.678)));
17-
WScript.Echo("typeof (12345.678) : " + typeof (12345.678));
56+
assert.areEqual("undefined", typeof hoisted, "typeof of hoisted var");
57+
var hoisted = 42;
1858

19-
function f() {
20-
function g() { }
21-
return g;
22-
}
59+
function inner() { var scoped = 42; }
60+
assert.areEqual("undefined", typeof scoped, "typeof of var when it's out of scope");
2361

24-
WScript.Echo("typeof function : " + typeof (f));
25-
WScript.Echo("typeof function returning function : " + typeof (f()));
62+
assert.throws(()=>{ typeof x.y; }, ReferenceError, "typeof of property on undefined obj", "'x' is not defined");
63+
assert.throws(()=>{ typeof x[0]; }, ReferenceError, "typeof of property on undefined obj", "'x' is not defined");
64+
assert.throws(()=>{ typeof (()=>x)(); }, ReferenceError, "reference error in the function invoked by typeof", "'x' is not defined");
2665

27-
function exc() {
28-
try {
29-
WScript.Echo(q);
30-
}
31-
catch (e) {
32-
WScript.Echo("Caught JS error on undefined var");
33-
WScript.Echo(typeof (q));
34-
}
35-
}
36-
exc();
66+
assert.throws(()=>{ typeof x_let; }, ReferenceError, "typeof of 'let' variable in its dead zone", "Use before declaration");
67+
let x_let = 42;
3768

38-
var x = {};
39-
WScript.Echo("typeof empty obj : " + typeof (x));
40-
WScript.Echo("typeof obj : " + typeof (new Object()));
41-
42-
var y = [];
43-
y[0] = 0;
44-
WScript.Echo("typeof array element with number : " + typeof (y[0]));
45-
WScript.Echo("typeof undef element array : " + typeof (y[1]));
46-
WScript.Echo("typeof array : " + typeof (y));
47-
48-
var verr = new Error("aaa");
49-
WScript.Echo("typeof (err) : " + typeof (verr));
50-
51-
var vDate = new Date(123);
52-
WScript.Echo("typeof ( new Date) : " + typeof (vDate));
53-
54-
WScript.Echo("typeof (new Array()) : " + typeof (new Array()));
55-
56-
var regx = /abc/;
57-
WScript.Echo("typeof(regex) : " + typeof (regx));
58-
59-
var s;
60-
var map = {};
61-
function CEvent() {
62-
do {
63-
} while(typeof (s = map.x) != "undefined");
64-
}
65-
CEvent();
69+
assert.throws(()=>{ typeof x_const; }, ReferenceError, "typeof of 'const' variable in its dead zone", "Use before declaration");
70+
const x_const = 42;
71+
}
72+
},
73+
{
74+
name: "typeof should propagate user exceptions",
75+
body: function () {
76+
function foo() { throw new Error("abc"); }
77+
assert.throws(()=>{typeof foo()}, Error, "exception raised from the invoked function", "abc");
78+
79+
var obj = { get x() { throw new Error("xyz")}};
80+
assert.throws(()=>{typeof obj.x}, Error, "exception raised from the getter", "xyz");
81+
}
82+
},
83+
{
84+
name: "typeof should propagate stack overflow",
85+
body: function () {
86+
var obj = {};
87+
var handler = {
88+
get: function () {
89+
return obj.x;
90+
}
91+
};
92+
obj = new Proxy(obj, handler);
93+
assert.throws(()=>{typeof obj.x}, Error, "recursive proxy should hit SO", "Out of stack space");
94+
}
95+
},
96+
];
97+
98+
testRunner.runTests(tests, { verbose: false /*so no need to provide baseline*/ });

test/EH/StackOverflow.js

+54-9
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,59 @@
22
// // Copyright (C) Microsoft. All rights reserved.
33
// // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
44
// //-------------------------------------------------------------------------------------------------------
5+
WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
56

6-
function foo(a)
7-
{
8-
try { a[0] } finally {}
9-
try { foo(0) } catch(e) {}
10-
try { foo() } catch(e) {}
11-
}
7+
var tests = [
8+
{
9+
name: "plain recursive call with modified arguments",
10+
body: function () {
11+
function recursive(a) {
12+
recursive(a + 1);
13+
}
14+
try {
15+
recursive(42);
16+
assert(false); // should never reach this line
17+
}
18+
catch (e) {
19+
assert.areNotEqual(-1, e.message.indexOf("Out of stack space"), "Should be SO exception");
20+
}
21+
}
22+
},
23+
{
24+
name: "plain recursive call with no arguments",
25+
body: function () {
26+
function recursive() {
27+
recursive();
28+
}
29+
try {
30+
recursive();
31+
assert(false); // should never reach this line
32+
}
33+
catch (e) {
34+
assert.areNotEqual(-1, e.message.indexOf("Out of stack space"), "Should be SO exception");
35+
}
36+
}
37+
},
38+
{
39+
name: "recursive call to getter via proxy",
40+
body: function () {
41+
var obj = {};
42+
var handler = {
43+
get: function () {
44+
return obj.x;
45+
}
46+
};
47+
obj = new Proxy(obj, handler);
1248

13-
foo(0)
14-
15-
WScript.Echo("PASS");
49+
try {
50+
var y = obj.x;
51+
assert(false); // should never reach this line
52+
}
53+
catch (e) {
54+
assert.areNotEqual(-1, e.message.indexOf("Out of stack space"), "Should be SO exception");
55+
}
56+
}
57+
},
58+
];
59+
60+
testRunner.runTests(tests, { verbose: false /*so no need to provide baseline*/ });

0 commit comments

Comments
 (0)