Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
boingoing authored and akroshg committed Feb 10, 2020
1 parent 2e33d82 commit a9aee51
Show file tree
Hide file tree
Showing 10 changed files with 241 additions and 6 deletions.
20 changes: 20 additions & 0 deletions lib/Parser/Parse.cpp
Expand Up @@ -1738,6 +1738,20 @@ void Parser::BindPidRefsInScope(IdentPtr pid, Symbol *sym, int blockId, uint max
}
}

if (m_currentNodeFunc && m_currentNodeFunc->pnodeName && pid == m_currentNodeFunc->pnodeName->pid && !m_currentNodeFunc->IsDeclaration() && m_currentNodeFunc->IsBodyAndParamScopeMerged())
{
Scope* funcExprScope = m_currentNodeFunc->scope;
Assert(funcExprScope->GetScopeType() == ScopeType_FuncExpr);

ParseNodeBlock* bodyScope = m_currentNodeFunc->pnodeBodyScope;
Assert(bodyScope->blockType == PnodeBlockType::Function);

if (ref->GetScopeId() < bodyScope->blockId && ref->GetScopeId() > blockId)
{
funcExprScope->SetIsObject();
}
}

if (ref->GetScopeId() == blockId)
{
break;
Expand Down Expand Up @@ -4938,6 +4952,12 @@ ParseNodeFnc * Parser::ParseFncDeclInternal(ushort flags, LPCOLESTR pNameHint, c
pnodeFnc->SetIsBaseClassConstructor((flags & fFncBaseClassConstructor) != 0);
pnodeFnc->SetHomeObjLocation(Js::Constants::NoRegister);

if (this->m_currentScope && this->m_currentScope->GetScopeType() == ScopeType_Parameter)
{
pnodeFnc->SetIsDeclaredInParamScope();
this->m_currentScope->SetHasNestedParamFunc();
}

IdentPtr pFncNamePid = nullptr;
bool needScanRCurly = true;
ParseFncDeclHelper<buildAST>(pnodeFnc, pNameHint, flags, fUnaryOrParen, noStmtContext, &needScanRCurly, fModule, &pFncNamePid, fAllowIn);
Expand Down
4 changes: 3 additions & 1 deletion lib/Parser/ptree.h
Expand Up @@ -445,7 +445,7 @@ enum FncFlags : uint
kFunctionIsStaticMember = 1 << 24,
kFunctionIsGenerator = 1 << 25, // Function is an ES6 generator function
kFunctionAsmjsMode = 1 << 26,
// Free = 1 << 27,
kFunctionIsDeclaredInParamScope = 1 << 27, // Function is declared in parameter scope (ex: inside default argument)
kFunctionIsAsync = 1 << 28, // function is async
kFunctionHasDirectSuper = 1 << 29, // super()
kFunctionIsDefaultModuleExport = 1 << 30, // function is the default export of a module
Expand Down Expand Up @@ -583,6 +583,7 @@ class ParseNodeFnc : public ParseNode
void SetHasHomeObj(bool set = true) { SetFlags(kFunctionHasHomeObj, set); }
void SetUsesArguments(bool set = true) { SetFlags(kFunctionUsesArguments, set); }
void SetIsDefaultModuleExport(bool set = true) { SetFlags(kFunctionIsDefaultModuleExport, set); }
void SetIsDeclaredInParamScope(bool set = true) { SetFlags(kFunctionIsDeclaredInParamScope, set); }
void SetNestedFuncEscapes(bool set = true) { nestedFuncEscapes = set; }
void SetCanBeDeferred(bool set = true) { canBeDeferred = set; }
void ResetBodyAndParamScopeMerged() { isBodyAndParamScopeMerged = false; }
Expand Down Expand Up @@ -623,6 +624,7 @@ class ParseNodeFnc : public ParseNode
bool HasHomeObj() const { return HasFlags(kFunctionHasHomeObj); }
bool UsesArguments() const { return HasFlags(kFunctionUsesArguments); }
bool IsDefaultModuleExport() const { return HasFlags(kFunctionIsDefaultModuleExport); }
bool IsDeclaredInParamScope() const { return HasFlags(kFunctionIsDeclaredInParamScope); }
bool NestedFuncEscapes() const { return nestedFuncEscapes; }
bool CanBeDeferred() const { return canBeDeferred; }
bool IsBodyAndParamScopeMerged() { return isBodyAndParamScopeMerged; }
Expand Down
9 changes: 7 additions & 2 deletions lib/Runtime/ByteCode/ByteCodeEmitter.cpp
Expand Up @@ -3411,8 +3411,6 @@ void ByteCodeGenerator::EmitScopeList(ParseNode *pnode, ParseNode *breakOnBodySc
}
this->StartEmitFunction(pnode->AsParseNodeFnc());

PushFuncInfo(_u("StartEmitFunction"), funcInfo);

if (!funcInfo->IsBodyAndParamScopeMerged())
{
this->EmitScopeList(pnode->AsParseNodeFnc()->pnodeBodyScope->pnodeScopes);
Expand Down Expand Up @@ -3789,6 +3787,11 @@ void ByteCodeGenerator::StartEmitFunction(ParseNodeFnc *pnodeFnc)
else if (pnodeFnc->IsBodyAndParamScopeMerged() || bodyScope->GetScopeSlotCount() != 0)
{
bodyScope->SetMustInstantiate(funcInfo->frameSlotsRegister != Js::Constants::NoRegister);

if (pnodeFnc->IsBodyAndParamScopeMerged() && paramScope && paramScope->GetHasNestedParamFunc())
{
paramScope->SetMustInstantiate(funcInfo->frameSlotsRegister != Js::Constants::NoRegister);
}
}

if (!pnodeFnc->IsBodyAndParamScopeMerged())
Expand Down Expand Up @@ -3816,6 +3819,8 @@ void ByteCodeGenerator::StartEmitFunction(ParseNodeFnc *pnodeFnc)
}
}

PushFuncInfo(_u("StartEmitFunction"), funcInfo);

if (!funcInfo->IsBodyAndParamScopeMerged())
{
ParseNodeBlock * paramBlock = pnodeFnc->pnodeScopes;
Expand Down
17 changes: 15 additions & 2 deletions lib/Runtime/ByteCode/ByteCodeGenerator.cpp
Expand Up @@ -1818,7 +1818,7 @@ FuncInfo *ByteCodeGenerator::FindEnclosingNonLambda()
return nullptr;
}

FuncInfo* GetParentFuncInfo(FuncInfo* child)
FuncInfo* ByteCodeGenerator::GetParentFuncInfo(FuncInfo* child)
{
for (Scope* scope = child->GetBodyScope(); scope; scope = scope->GetEnclosingScope())
{
Expand All @@ -1831,6 +1831,19 @@ FuncInfo* GetParentFuncInfo(FuncInfo* child)
return nullptr;
}

FuncInfo* ByteCodeGenerator::GetEnclosingFuncInfo()
{
FuncInfo* top = this->funcInfoStack->Pop();

Assert(!this->funcInfoStack->Empty());

FuncInfo* second = this->funcInfoStack->Top();

this->funcInfoStack->Push(top);

return second;
}

bool ByteCodeGenerator::CanStackNestedFunc(FuncInfo * funcInfo, bool trace)
{
#if ENABLE_DEBUG_CONFIG_OPTIONS
Expand Down Expand Up @@ -2605,7 +2618,7 @@ void AssignFuncSymRegister(ParseNodeFnc * pnodeFnc, ByteCodeGenerator * byteCode
Assert(byteCodeGenerator->GetCurrentScope()->GetFunc() == sym->GetScope()->GetFunc());
if (byteCodeGenerator->GetCurrentScope()->GetFunc() != sym->GetScope()->GetFunc())
{
Assert(GetParentFuncInfo(byteCodeGenerator->GetCurrentScope()->GetFunc()) == sym->GetScope()->GetFunc());
Assert(ByteCodeGenerator::GetParentFuncInfo(byteCodeGenerator->GetCurrentScope()->GetFunc()) == sym->GetScope()->GetFunc());
sym->GetScope()->SetMustInstantiate(true);
byteCodeGenerator->ProcessCapturedSym(sym);
sym->GetScope()->GetFunc()->SetHasLocalInClosure(true);
Expand Down
2 changes: 2 additions & 0 deletions lib/Runtime/ByteCode/ByteCodeGenerator.h
Expand Up @@ -376,6 +376,8 @@ class ByteCodeGenerator
void PopulateFormalsScope(uint beginOffset, FuncInfo *funcInfo, ParseNodeFnc *pnodeFnc);
void InsertPropertyToDebuggerScope(FuncInfo* funcInfo, Js::DebuggerScope* debuggerScope, Symbol* sym);
FuncInfo *FindEnclosingNonLambda();
static FuncInfo* GetParentFuncInfo(FuncInfo* child);
FuncInfo* GetEnclosingFuncInfo();

bool CanStackNestedFunc(FuncInfo * funcInfo, bool trace = false);
void CheckDeferParseHasMaybeEscapedNestedFunc();
Expand Down
5 changes: 5 additions & 0 deletions lib/Runtime/ByteCode/Scope.h
Expand Up @@ -41,6 +41,7 @@ class Scope
BYTE canMergeWithBodyScope : 1;
BYTE hasLocalInClosure : 1;
BYTE isBlockInLoop : 1;
BYTE hasNestedParamFunc : 1;
public:
#if DBG
BYTE isRestored : 1;
Expand All @@ -60,6 +61,7 @@ class Scope
canMergeWithBodyScope(true),
hasLocalInClosure(false),
isBlockInLoop(false),
hasNestedParamFunc(false),
location(Js::Constants::NoRegister),
m_symList(nullptr),
m_count(0),
Expand Down Expand Up @@ -261,6 +263,9 @@ class Scope
void SetIsBlockInLoop(bool is = true) { isBlockInLoop = is; }
bool IsBlockInLoop() const { return isBlockInLoop; }

void SetHasNestedParamFunc(bool is = true) { hasNestedParamFunc = is; }
bool GetHasNestedParamFunc() const { return hasNestedParamFunc; }

bool HasInnerScopeIndex() const { return innerScopeIndex != (uint)-1; }
uint GetInnerScopeIndex() const { return innerScopeIndex; }
void SetInnerScopeIndex(uint index) { innerScopeIndex = index; }
Expand Down
26 changes: 25 additions & 1 deletion lib/Runtime/ByteCode/ScopeInfo.cpp
Expand Up @@ -112,8 +112,17 @@ namespace Js
ScopeInfo * ScopeInfo::SaveScopeInfo(ByteCodeGenerator* byteCodeGenerator, Scope * scope, ScriptContext * scriptContext)
{
// Advance past scopes that will be excluded from the closure environment. (But note that we always want the body scope.)
while (scope && (!scope->GetMustInstantiate() && scope != scope->GetFunc()->GetBodyScope()))
while (scope)
{
FuncInfo* func = scope->GetFunc();

if (scope->GetMustInstantiate() ||
func->GetBodyScope() == scope ||
(func->GetParamScope() == scope && func->IsBodyAndParamScopeMerged() && scope->GetHasNestedParamFunc()))
{
break;
}

scope = scope->GetEnclosingScope();
}

Expand Down Expand Up @@ -162,6 +171,21 @@ namespace Js
Scope* currentScope = byteCodeGenerator->GetCurrentScope();
Assert(currentScope->GetFunc() == funcInfo);

if (funcInfo->root->IsDeclaredInParamScope())
{
Assert(currentScope->GetScopeType() == ScopeType_FunctionBody);
Assert(currentScope->GetEnclosingScope());

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

if (func->IsBodyAndParamScopeMerged())
{
currentScope = func->GetParamScope();
Assert(currentScope->GetScopeType() == ScopeType_Parameter);
}
}

while (currentScope->GetFunc() == funcInfo)
{
currentScope = currentScope->GetEnclosingScope();
Expand Down
132 changes: 132 additions & 0 deletions test/Bugs/bug_OS18926499.js
@@ -0,0 +1,132 @@
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

let throwingFunctions = [{
msg: "Split-scope parent, param-scope child capturing symbol from parent body scope",
body: function foo(a = (()=>+x)()) {
function bar() { eval(''); }
var x;
}
},
{
msg: "Split-scope parent, param-scope child capturing symbol from parent body scope",
body: function foo(a = (()=>+x)()) {
eval('');
var x;
}
},
{
msg: "Merged-scope parent, param-scope child capturing symbol from parent body scope",
body: function foo(a = () => +x) {
var x = 1;
return a();
}
},
{
msg: "Merged-scope parent, param-scope child capturing symbol from parent body scope",
body: function foo(a = (()=>+x)()) {
var x;
}
},
{
msg: "Func expr parent, param-scope func expr child capturing symbol from parent body scope",
body: foo3 = function foo3a(a = (function foo3b() { return +x; })()) {
var x = 123;
}
},
{
msg: "Func expr parent, param-scope func expr child capturing symbol from parent body scope",
body: foo3 = function foo3a(a = (function foo3b() { return +x; })()) {
eval('');
var x = 123;
}
},
{
msg: "Func expr parent, param-scope func expr child capturing symbol from parent body scope",
body: foo3 = function foo3a(a = (function foo3b() { return +x; })()) {
function bar() { eval(''); }
var x = 123;
}
},
{
msg: "Param-scope func expr child with nested func expr capturing symbol from parent body scope",
body: foo5 = function foo5a(a = (function(){(function(b = 123) { +x; })()})()) {
function bar() { eval(''); }
var x;
}
},
{
msg: "Multiple nested func expr, inner param-scope function capturing outer func expr name",
body: foo3 = function foo3a(a = (function foo3b(b = (function foo3c(c = (function foo3d() { +x; })()){})()){})()){ var x;}
}];

let nonThrowingFunctions = [{
msg: "Func expr parent, param-scope func expr child capturing parent func expr name",
body: foo3 = function foo3a(a = (function foo3b() { +foo3a; })()) {
return +a;
}
},
{
msg: "Func expr parent, param-scope func expr child capturing own func expr name",
body: foo3 = function foo3a(a = (function foo3b() { +foo3b; })()) {
return +a;
}
},
{
msg: "Func expr parent, param-scope func expr child capturing expression name hint",
body: foo3 = function foo3a(a = (function foo3b() { +foo3; })()) {
return +a;
}
},
{
msg: "Func expr parent, param-scope func expr child capturing parent argument name",
body: foo3 = function foo3a(b = 123, a = (function foo3b() { return +b; })()) {
if (123 !== a) throw 123;
}
},
{
msg: "Func expr parent, param-scope func expr child capturing symbol from parent body scope",
body: foo3 = function foo3a(b = 123, a = (function foo3b() { return +b; })()) {
if (123 !== a) throw 123;
}
},
{
msg: "Multiple nested func expr, inner param-scope function capturing outer func expr name",
body: foo3 = function foo3a(a = (function foo3b(b = (function foo3c(c = (function foo3d() { foo3d; })()){})()){})()){}
},
{
msg: "Multiple nested func expr, inner param-scope function capturing outer func expr name",
body: foo3 = function foo3a(a = (function foo3b(b = (function foo3c(c = (function foo3d() { foo3c; })()){})()){})()){}
},
{
msg: "Multiple nested func expr, inner param-scope function capturing outer func expr name",
body: foo3 = function foo3a(a = (function foo3b(b = (function foo3c(c = (function foo3d() { foo3b; })()){})()){})()){}
},
{
msg: "Multiple nested func expr, inner param-scope function capturing outer func expr name",
body: foo3 = function foo3a(a = (function foo3b(b = (function foo3c(c = (function foo3d() { foo3a; })()){})()){})()){}
},
{
msg: "Multiple nested func expr, inner param-scope function capturing outer func expr name",
body: foo3 = function foo3a(a = (function foo3b(b = (function foo3c(c = (function foo3d() { foo3; })()){})()){})()){}
}];

for (let fn of throwingFunctions) {
try {
fn.body();
console.log(`fail: ${fn.msg}`);
} catch (e) {
console.log("pass");
}
}

for (let fn of nonThrowingFunctions) {
try {
fn.body();
console.log("pass");
} catch (e) {
console.log(`fail: ${fn.msg}`);
}
}
20 changes: 20 additions & 0 deletions test/Bugs/bug_OS23102586.js
@@ -0,0 +1,20 @@
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

// force:deferparse

function test0() {
var k;

function foo(a = function() { +k; }) {
a();
function bar() { a }
};

eval('')
foo();
}
test0();
console.log('pass')
12 changes: 12 additions & 0 deletions test/Bugs/rlexe.xml
Expand Up @@ -536,4 +536,16 @@
<compile-flags>-esdynamicimport -mutehosterrormsg -args summary -endargs</compile-flags>
</default>
</test>
<test>
<default>
<files>bug_OS18926499.js</files>
<compile-flags>-force:deferparse</compile-flags>
</default>
</test>
<test>
<default>
<files>bug_OS23102586.js</files>
<compile-flags>-force:deferparse</compile-flags>
</default>
</test>
</regress-exe>

0 comments on commit a9aee51

Please sign in to comment.