Skip to content

Commit adfb5cc

Browse files
committed
Support function expression with param scope functions capturing function expression name
1 parent 6ae6c0a commit adfb5cc

File tree

4 files changed

+173
-90
lines changed

4 files changed

+173
-90
lines changed

lib/Parser/Parse.cpp

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1826,6 +1826,26 @@ void Parser::FinishParseBlock(ParseNodeBlock *pnodeBlock, bool needScanRCurly)
18261826
}
18271827
}
18281828

1829+
void CheckFncExprNameCapturedInParamScope(ParseNodeFnc* pnodeFnc, ParseNodeBlock* pnodeFncExprScope)
1830+
{
1831+
ParseNodeBlock* bodyScope = pnodeFnc->pnodeBodyScope;
1832+
ParseNodePtr pnodeName = pnodeFnc->pnodeName;
1833+
1834+
if (bodyScope == nullptr || pnodeName == nullptr || !pnodeFnc->IsBodyAndParamScopeMerged())
1835+
{
1836+
return;
1837+
}
1838+
1839+
for (PidRefStack* ref = pnodeName->AsParseNodeVar()->pid->GetTopRef(); ref && ref->id > pnodeFncExprScope->blockId; ref = ref->prev)
1840+
{
1841+
if (ref->id < bodyScope->blockId && ref->id > pnodeFncExprScope->blockId)
1842+
{
1843+
pnodeFncExprScope->scope->SetIsObject();
1844+
return;
1845+
}
1846+
}
1847+
}
1848+
18291849
void Parser::FinishParseFncExprScope(ParseNodeFnc * pnodeFnc, ParseNodeBlock * pnodeFncExprScope)
18301850
{
18311851
int fncExprScopeId = pnodeFncExprScope->blockId;
@@ -5378,9 +5398,14 @@ ParseNodeFnc * Parser::ParseFncDeclInternal(ushort flags, LPCOLESTR pNameHint, c
53785398
pnodeFnc->SetIsModule(fModule);
53795399
pnodeFnc->SetIsClassConstructor((flags & fFncClassConstructor) != 0);
53805400
pnodeFnc->SetIsBaseClassConstructor((flags & fFncBaseClassConstructor) != 0);
5381-
pnodeFnc->SetIsDeclaredInParamScope(this->m_currentScope && this->m_currentScope->GetScopeType() == ScopeType_Parameter);
53825401
pnodeFnc->SetHomeObjLocation(Js::Constants::NoRegister);
53835402

5403+
if (this->m_currentScope && this->m_currentScope->GetScopeType() == ScopeType_Parameter)
5404+
{
5405+
pnodeFnc->SetIsDeclaredInParamScope();
5406+
this->m_currentScope->SetHasNestedParamFunc();
5407+
}
5408+
53845409
IdentPtr pFncNamePid = nullptr;
53855410
bool needScanRCurly = true;
53865411
ParseFncDeclHelper<buildAST>(pnodeFnc, pNameHint, flags, fUnaryOrParen, noStmtContext, &needScanRCurly, fModule, &pFncNamePid, fAllowIn);
@@ -6013,6 +6038,11 @@ void Parser::ParseFncDeclHelper(ParseNodeFnc * pnodeFnc, LPCOLESTR pNameHint, us
60136038

60146039
if (pnodeBlock)
60156040
{
6041+
if (pnodeFncExprScope)
6042+
{
6043+
CheckFncExprNameCapturedInParamScope(pnodeFnc, pnodeFncExprScope);
6044+
}
6045+
60166046
FinishParseBlock(pnodeBlock, *pNeedScanRCurly);
60176047
}
60186048

lib/Runtime/ByteCode/Scope.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class Scope
4141
BYTE canMergeWithBodyScope : 1;
4242
BYTE hasLocalInClosure : 1;
4343
BYTE isBlockInLoop : 1;
44+
BYTE hasNestedParamFunc : 1;
4445
public:
4546
#if DBG
4647
BYTE isRestored : 1;
@@ -60,6 +61,7 @@ class Scope
6061
canMergeWithBodyScope(true),
6162
hasLocalInClosure(false),
6263
isBlockInLoop(false),
64+
hasNestedParamFunc(false),
6365
location(Js::Constants::NoRegister),
6466
m_symList(nullptr),
6567
m_count(0),
@@ -252,6 +254,9 @@ class Scope
252254
void SetIsBlockInLoop(bool is = true) { isBlockInLoop = is; }
253255
bool IsBlockInLoop() const { return isBlockInLoop; }
254256

257+
void SetHasNestedParamFunc(bool is = true) { hasNestedParamFunc = is; }
258+
bool GetHasNestedParamFunc() const { return hasNestedParamFunc; }
259+
255260
bool HasInnerScopeIndex() const { return innerScopeIndex != (uint)-1; }
256261
uint GetInnerScopeIndex() const { return innerScopeIndex; }
257262
void SetInnerScopeIndex(uint index) { innerScopeIndex = index; }

lib/Runtime/ByteCode/ScopeInfo.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,17 @@ namespace Js
112112
ScopeInfo * ScopeInfo::SaveScopeInfo(ByteCodeGenerator* byteCodeGenerator, Scope * scope, ScriptContext * scriptContext)
113113
{
114114
// Advance past scopes that will be excluded from the closure environment. (But note that we always want the body scope.)
115-
while (scope && (!scope->GetMustInstantiate() && scope != scope->GetFunc()->GetBodyScope()))
115+
while (scope)
116116
{
117+
FuncInfo* func = scope->GetFunc();
118+
119+
if (scope->GetMustInstantiate() ||
120+
func->GetBodyScope() == scope ||
121+
(func->GetParamScope() == scope && func->IsBodyAndParamScopeMerged() && scope->GetHasNestedParamFunc()))
122+
{
123+
break;
124+
}
125+
117126
scope = scope->GetEnclosingScope();
118127
}
119128

@@ -162,14 +171,15 @@ namespace Js
162171
Scope* currentScope = byteCodeGenerator->GetCurrentScope();
163172
Assert(currentScope->GetFunc() == funcInfo);
164173

165-
if (funcInfo->root->IsDeclaredInParamScope()) {
174+
if (funcInfo->root->IsDeclaredInParamScope())
175+
{
166176
Assert(currentScope->GetScopeType() == ScopeType_FunctionBody);
167177
Assert(currentScope->GetEnclosingScope());
168178

169179
FuncInfo* func = byteCodeGenerator->GetEnclosingFuncInfo();
170180
Assert(func);
171181

172-
if (func->IsBodyAndParamScopeMerged() && !(func->funcExprScope && func->funcExprScope->GetCanMerge()))
182+
if (func->IsBodyAndParamScopeMerged())
173183
{
174184
currentScope = func->GetParamScope();
175185
Assert(currentScope->GetScopeType() == ScopeType_Parameter);

test/Bugs/bug_OS18926499.js

Lines changed: 124 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -3,92 +3,130 @@
33
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
44
//-------------------------------------------------------------------------------------------------------
55

6-
function foo(a = (()=>+x)()) {
7-
function bar() { eval(''); }
8-
var x;
6+
let throwingFunctions = [{
7+
msg: "Split-scope parent, param-scope child capturing symbol from parent body scope",
8+
body: function foo(a = (()=>+x)()) {
9+
function bar() { eval(''); }
10+
var x;
11+
}
12+
},
13+
{
14+
msg: "Split-scope parent, param-scope child capturing symbol from parent body scope",
15+
body: function foo(a = (()=>+x)()) {
16+
eval('');
17+
var x;
18+
}
19+
},
20+
{
21+
msg: "Merged-scope parent, param-scope child capturing symbol from parent body scope",
22+
body: function foo(a = () => +x) {
23+
var x = 1;
24+
return a();
25+
}
26+
},
27+
{
28+
msg: "Merged-scope parent, param-scope child capturing symbol from parent body scope",
29+
body: function foo(a = (()=>+x)()) {
30+
var x;
31+
}
32+
},
33+
{
34+
msg: "Func expr parent, param-scope func expr child capturing symbol from parent body scope",
35+
body: foo3 = function foo3a(a = (function foo3b() { return +x; })()) {
36+
var x = 123;
37+
}
38+
},
39+
{
40+
msg: "Func expr parent, param-scope func expr child capturing symbol from parent body scope",
41+
body: foo3 = function foo3a(a = (function foo3b() { return +x; })()) {
42+
eval('');
43+
var x = 123;
44+
}
45+
},
46+
{
47+
msg: "Func expr parent, param-scope func expr child capturing symbol from parent body scope",
48+
body: foo3 = function foo3a(a = (function foo3b() { return +x; })()) {
49+
function bar() { eval(''); }
50+
var x = 123;
51+
}
52+
},
53+
{
54+
msg: "Param-scope func expr child with nested func expr capturing symbol from parent body scope",
55+
body: foo5 = function foo5a(a = (function(){(function(b = 123) { +x; })()})()) {
56+
function bar() { eval(''); }
57+
var x;
58+
}
59+
},
60+
{
61+
msg: "Multiple nested func expr, inner param-scope function capturing outer func expr name",
62+
body: foo3 = function foo3a(a = (function foo3b(b = (function foo3c(c = (function foo3d() { +x; })()){})()){})()){ var x;}
63+
}];
64+
65+
let nonThrowingFunctions = [{
66+
msg: "Func expr parent, param-scope func expr child capturing parent func expr name",
67+
body: foo3 = function foo3a(a = (function foo3b() { +foo3a; })()) {
68+
return +a;
69+
}
70+
},
71+
{
72+
msg: "Func expr parent, param-scope func expr child capturing own func expr name",
73+
body: foo3 = function foo3a(a = (function foo3b() { +foo3b; })()) {
74+
return +a;
75+
}
76+
},
77+
{
78+
msg: "Func expr parent, param-scope func expr child capturing expression name hint",
79+
body: foo3 = function foo3a(a = (function foo3b() { +foo3; })()) {
80+
return +a;
81+
}
82+
},
83+
{
84+
msg: "Func expr parent, param-scope func expr child capturing parent argument name",
85+
body: foo3 = function foo3a(b = 123, a = (function foo3b() { return +b; })()) {
86+
if (123 !== a) throw 123;
87+
}
88+
},
89+
{
90+
msg: "Func expr parent, param-scope func expr child capturing symbol from parent body scope",
91+
body: foo3 = function foo3a(b = 123, a = (function foo3b() { return +b; })()) {
92+
if (123 !== a) throw 123;
93+
}
94+
},
95+
{
96+
msg: "Multiple nested func expr, inner param-scope function capturing outer func expr name",
97+
body: foo3 = function foo3a(a = (function foo3b(b = (function foo3c(c = (function foo3d() { foo3d; })()){})()){})()){}
98+
},
99+
{
100+
msg: "Multiple nested func expr, inner param-scope function capturing outer func expr name",
101+
body: foo3 = function foo3a(a = (function foo3b(b = (function foo3c(c = (function foo3d() { foo3c; })()){})()){})()){}
102+
},
103+
{
104+
msg: "Multiple nested func expr, inner param-scope function capturing outer func expr name",
105+
body: foo3 = function foo3a(a = (function foo3b(b = (function foo3c(c = (function foo3d() { foo3b; })()){})()){})()){}
106+
},
107+
{
108+
msg: "Multiple nested func expr, inner param-scope function capturing outer func expr name",
109+
body: foo3 = function foo3a(a = (function foo3b(b = (function foo3c(c = (function foo3d() { foo3a; })()){})()){})()){}
110+
},
111+
{
112+
msg: "Multiple nested func expr, inner param-scope function capturing outer func expr name",
113+
body: foo3 = function foo3a(a = (function foo3b(b = (function foo3c(c = (function foo3d() { foo3; })()){})()){})()){}
114+
}];
115+
116+
for (let fn of throwingFunctions) {
117+
try {
118+
fn.body();
119+
console.log(`fail: ${fn.msg}`);
120+
} catch {
121+
console.log("pass");
122+
}
9123
}
10124

11-
try {
12-
foo();
13-
console.log('fail');
14-
} catch {
15-
console.log("pass");
16-
}
17-
18-
function foo2(a = () => x) { var x = 1; return a(); }
19-
20-
try {
21-
foo2()
22-
console.log('fail');
23-
} catch {
24-
console.log('pass');
25-
}
26-
27-
var foo3 = function foo3a(a = (function foo3b() { +foo3a; })()) {
28-
a;
29-
};
30-
31-
try {
32-
foo3()
33-
console.log('pass');
34-
} catch {
35-
console.log('fail');
36-
}
37-
38-
var foo4 = function foo4a(a = (function() { +x; })()) {
39-
function bar() { eval(''); }
40-
var x;
41-
};
42-
43-
try {
44-
foo4()
45-
console.log('fail');
46-
} catch {
47-
console.log('pass');
48-
}
49-
50-
var foo5 = function foo5a(a = (function(){(function(b = 123) { +x; })()})()) {
51-
function bar() { eval(''); }
52-
var x;
53-
};
54-
55-
try {
56-
foo5();
57-
console.log('fail');
58-
} catch {
59-
console.log('pass');
60-
}
61-
62-
function foo6(a, b = (function() {a; +x;})()) {
63-
function bar() { eval(''); }
64-
var x;
65-
};
66-
67-
try {
68-
foo6();
69-
console.log('fail');
70-
} catch {
71-
console.log('pass');
72-
}
73-
74-
var foo8 = function foo8a(b, a = (function foo8b() { console.log(x); })()) {
75-
a;
76-
var x = 'fail';
77-
};
78-
79-
try {
80-
foo8()
81-
console.log('fail');
82-
} catch {
83-
console.log('pass');
84-
}
85-
86-
var foo9 = function foo9a(b = 'pass', a = (function foo9b() { console.log(b); })()) {
87-
};
88-
89-
try {
90-
foo9()
91-
console.log('pass');
92-
} catch {
93-
console.log('fail');
125+
for (let fn of nonThrowingFunctions) {
126+
try {
127+
fn.body();
128+
console.log("pass");
129+
} catch {
130+
console.log(`fail: ${fn.msg}`);
131+
}
94132
}

0 commit comments

Comments
 (0)