Skip to content

Commit b992a5d

Browse files
authoredMar 14, 2021
Implementation of Error cause proposal (#6621)
Implement stage 3 proposal: https://github.com/tc39/proposal-error-cause
1 parent 3588f4b commit b992a5d

10 files changed

+4945
-4798
lines changed
 

‎lib/Runtime/Base/JnDirectFields.h

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ ENTRY(JsBuiltIn)
107107
ENTRY(call)
108108
ENTRY(CanvasPixelArray)
109109
ENTRY(cast)
110+
ENTRY(cause)
110111
ENTRY2(catch_, _u("catch")) // "catch" cannot be an identifier in C++ so using "catch_" instead
111112
ENTRY(charAt)
112113
ENTRY(charCodeAt)

‎lib/Runtime/ByteCode/ByteCodeCacheReleaseFileVersion.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// NOTE: If there is a merge conflict the correct fix is to make a new GUID.
77
// This file was generated with tools/regenByteCode.py
88

9-
// {22c3f621-427e-42cc-a0dc-9a79c8bb6be9}
9+
// {ba0f47b7-ac35-42ef-8dfa-1935af336123}
1010
const GUID byteCodeCacheReleaseFileVersion =
11-
{ 0x22c3f621, 0x427e, 0x42cc, {0xa0, 0xdc, 0x9a, 0x79, 0xc8, 0xbb, 0x6b, 0xe9 } };
11+
{ 0xba0f47b7, 0xac35, 0x42ef, {0x8d, 0xfa, 0x19, 0x35, 0xaf, 0x33, 0x61, 0x23 } };
1212

‎lib/Runtime/Library/InJavascript/JsBuiltIn.bc.32b.h

+1,186-1,186
Large diffs are not rendered by default.

‎lib/Runtime/Library/InJavascript/JsBuiltIn.bc.64b.h

+1,195-1,195
Large diffs are not rendered by default.

‎lib/Runtime/Library/InJavascript/JsBuiltIn.nojit.bc.32b.h

+1,202-1,202
Large diffs are not rendered by default.

‎lib/Runtime/Library/InJavascript/JsBuiltIn.nojit.bc.64b.h

+1,207-1,207
Large diffs are not rendered by default.

‎lib/Runtime/Library/JavascriptError.cpp

+14-5
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ namespace Js
4747
SetEnumerable(propertyId, false);
4848
}
4949

50-
Var JavascriptError::NewInstance(RecyclableObject* function, JavascriptError* pError, CallInfo callInfo, Var newTarget, Var message)
50+
Var JavascriptError::NewInstance(RecyclableObject* function, JavascriptError* pError, CallInfo callInfo, Var newTarget, Var message, Var options)
5151
{
5252
ScriptContext* scriptContext = function->GetScriptContext();
5353

@@ -65,6 +65,14 @@ namespace Js
6565
pError->SetNotEnumerable(PropertyIds::message);
6666
}
6767

68+
if (JavascriptOperators::IsObject(options) && JavascriptOperators::HasProperty(UnsafeVarTo<RecyclableObject>(options), PropertyIds::cause))
69+
{
70+
Var cause = JavascriptOperators::GetPropertyNoCache(UnsafeVarTo<RecyclableObject>(options), PropertyIds::cause, scriptContext);
71+
JavascriptOperators::SetProperty(pError, pError, PropertyIds::cause, cause, scriptContext);
72+
pError->SetNotEnumerable(PropertyIds::cause);
73+
}
74+
75+
6876
JavascriptExceptionContext exceptionContext;
6977
JavascriptExceptionOperators::WalkStackForExceptionContext(*scriptContext, exceptionContext, pError,
7078
JavascriptExceptionOperators::StackCrawlLimitOnThrow(pError, *scriptContext), /*returnAddress=*/ nullptr, /*isThrownException=*/ false, /*resetSatck=*/ false);
@@ -84,7 +92,8 @@ namespace Js
8492
JavascriptError* pError = scriptContext->GetLibrary()->Create##name(); \
8593
Var newTarget = args.GetNewTarget(); \
8694
Var message = args.Info.Count > 1 ? args[1] : scriptContext->GetLibrary()->GetUndefined(); \
87-
return JavascriptError::NewInstance(function, pError, callInfo, newTarget, message); \
95+
Var options = args.Info.Count > 2 ? args[2] : scriptContext->GetLibrary()->GetUndefined(); \
96+
return JavascriptError::NewInstance(function, pError, callInfo, newTarget, message, options); \
8897
}
8998
NEW_ERROR(Error);
9099
NEW_ERROR(EvalError);
@@ -115,7 +124,7 @@ namespace Js
115124
JavascriptError::ThrowTypeError(scriptContext, JSERR_This_NeedObject, _u("Error.prototype.toString"));
116125
}
117126

118-
RecyclableObject * thisError = VarTo<RecyclableObject>(args[0]);
127+
RecyclableObject* thisError = VarTo<RecyclableObject>(args[0]);
119128
Var value = NULL;
120129
JavascriptString *outputStr, *message;
121130

@@ -148,8 +157,8 @@ namespace Js
148157

149158
if (nameLen > 0 && msgLen > 0)
150159
{
151-
outputStr = JavascriptString::Concat(outputStr, scriptContext->GetLibrary()->CreateStringFromCppLiteral(_u(": ")));
152-
outputStr = JavascriptString::Concat(outputStr, message);
160+
outputStr = JavascriptString::Concat(outputStr, scriptContext->GetLibrary()->CreateStringFromCppLiteral(_u(": ")));
161+
outputStr = JavascriptString::Concat(outputStr, message);
153162
}
154163
else if (msgLen > 0)
155164
{

‎lib/Runtime/Library/JavascriptError.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
//-------------------------------------------------------------------------------------------------------
22
// Copyright (C) Microsoft. All rights reserved.
3+
// Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved.
34
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
45
//-------------------------------------------------------------------------------------------------------
56
#pragma once
@@ -41,7 +42,7 @@ namespace Js
4142

4243
void SetNotEnumerable(PropertyId propertyId);
4344

44-
static Var NewInstance(RecyclableObject* function, JavascriptError* pError, CallInfo callInfo, Var newTarget, Var message);
45+
static Var NewInstance(RecyclableObject* function, JavascriptError* pError, CallInfo callInfo, Var newTarget, Var message, Var options);
4546
class EntryInfo
4647
{
4748
public:

‎test/Error/error_cause.js

+130
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//-------------------------------------------------------------------------------------------------------
2+
// Copyright (C) Microsoft Corporation and contributors. All rights reserved.
3+
// Copyright (c) 2021 ChakraCore Project Contributors. All rights reserved.
4+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
5+
//-------------------------------------------------------------------------------------------------------
6+
7+
WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js");
8+
9+
10+
function makeTestsFor(ErrorConstructor) {
11+
const name = ErrorConstructor.name;
12+
const message = "Test message";
13+
const o = {};
14+
return [
15+
{
16+
name: `"cause" in ${ name }.prototype.cause === false`,
17+
body: function () {
18+
assert.isFalse("cause" in ErrorConstructor.prototype, `Cause property must not exist in ${ name }.prototype`);
19+
}
20+
},
21+
{
22+
name: `"cause" in ${ name }().cause === false`,
23+
body: function () {
24+
assert.isFalse("cause" in ErrorConstructor(), `${ name }().cause should not be defined if not specified by cause property of options parameter in ${ name } constructor`);
25+
}
26+
},
27+
{
28+
name: `message property is defined when ${ name } called with one argument, not cause`,
29+
body: function () {
30+
assert.isTrue("message" in ErrorConstructor(message), `message property is defined when ${ name } called with one argument, not cause`);
31+
assert.isTrue(ErrorConstructor(message).message === message, `message property is defined when ${ name } called with one argument, not cause`);
32+
assert.isFalse("cause" in ErrorConstructor(message), `message property is defined when ${ name } called with one argument, not cause`);
33+
}
34+
},
35+
{
36+
name: `${ name }(${ message }, { cause: o })'s descriptor`,
37+
body: function () {
38+
const e = ErrorConstructor(message, { cause: o });
39+
const desc = Object.getOwnPropertyDescriptor(e, "cause");
40+
assert.areEqual(desc.configurable, true, "e.cause should be configurable");
41+
assert.areEqual(desc.writable, true, "e.cause should be writable");
42+
assert.areEqual(desc.enumerable, false, "e.cause should not be enumerable");
43+
}
44+
},
45+
{
46+
name: `o === ${ name }(${ message }, { cause: o }).cause`,
47+
body: function () {
48+
const e = Error();
49+
assert.areEqual(ErrorConstructor("", { cause: o }).cause, o, `Cause property value should be kept as-is`);
50+
assert.areEqual(ErrorConstructor("", { cause: 0 }).cause, 0, `Cause property value should be kept as-is`);
51+
assert.areEqual(ErrorConstructor("", { cause: e }).cause, e, `Cause property value should be kept as-is`);
52+
assert.areEqual(ErrorConstructor("", { cause: "A cause" }).cause, "A cause", `Cause property value should be kept as-is`);
53+
}
54+
},
55+
{
56+
name: "Options with cause property as getter",
57+
body: function () {
58+
var getCounter = 0;
59+
const options = {
60+
get cause() {
61+
getCounter++;
62+
return o;
63+
}
64+
}
65+
ErrorConstructor(message, options);
66+
assert.areEqual(getCounter, 1, `getCounter should be 1`);
67+
}
68+
},
69+
{
70+
name: "Options with cause property as getter which throws",
71+
body: function () {
72+
const options = {
73+
get cause() {
74+
throw ErrorConstructor();
75+
}
76+
}
77+
assert.throws(() => ErrorConstructor(message, options), ErrorConstructor);
78+
}
79+
},
80+
{
81+
name: "Proxy options parameter",
82+
body: function () {
83+
const options = new Proxy({ cause: o }, {
84+
has(target, p) {
85+
hasCounter++;
86+
return p in target;
87+
},
88+
get(target, p) {
89+
getCounter++;
90+
return target[p];
91+
}
92+
});
93+
var hasCounter = 0, getCounter = 0;
94+
const e = ErrorConstructor("test", options);
95+
assert.areEqual(hasCounter, 1, `hasCounter should be 1`);
96+
assert.areEqual(getCounter, 1, `getCounter should be 1`);
97+
assert.areEqual(e.cause, o, `Cause property value should be kept as-is`);
98+
assert.areEqual(hasCounter, 1, `hasCounter should be 1`);
99+
assert.areEqual(getCounter, 1, `getCounter should be 1`);
100+
}
101+
},
102+
{
103+
name: "Cause property is not added to error if options parameter doesn't have the cause property",
104+
body: function () {
105+
assert.isFalse('cause' in ErrorConstructor(message, { }), `Cause property must not be added to error if options parameter doesn't have the cause property`);
106+
}
107+
},
108+
{
109+
name: "Cause property is not added to error if options parameter isn't typeof object",
110+
body: function () {
111+
Number.prototype.cause = 0;
112+
String.prototype.cause = 0;
113+
assert.isFalse('cause' in ErrorConstructor(message, 0), `Cause property must not be added to error if options parameter isn't typeof object`);
114+
assert.isFalse('cause' in ErrorConstructor(message, ""), `Cause property must not be added to error if options parameter isn't typeof object`);
115+
}
116+
}
117+
]
118+
}
119+
const tests = [
120+
...makeTestsFor(Error),
121+
...makeTestsFor(TypeError),
122+
...makeTestsFor(ReferenceError),
123+
...makeTestsFor(SyntaxError),
124+
...makeTestsFor(RangeError),
125+
...makeTestsFor(EvalError),
126+
// TODO: Uncomment when #6301 is landed
127+
// ...makeTestsFor(AggregateError)
128+
];
129+
130+
testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

‎test/Error/rlexe.xml

+6
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<regress-exe>
3+
<test>
4+
<default>
5+
<files>error_cause.js</files>
6+
<compile-flags>-args summary -endargs</compile-flags>
7+
</default>
8+
</test>
39
<test>
410
<default>
511
<files>errorProps.js</files>

0 commit comments

Comments
 (0)