Skip to content
Permalink
Browse files

Names declared in the body scope of a function can be incorrectly cap…

…tured by a function in the param scope

```javascript
function foo(a = (()=>x)()) {
  ()=>eval("");
  var x;
}
```

In above function foo, the name `x` should not be visible to the lambda assigned to `a`. However, foo has merged body and param scope so when we restore the ScopeInfo for foo in order to defer-parse the lambda in param scope we will set the enclosing scope of the lambda to be foo's body scope. This means we will bind the name `x` to the decl of `x` in the body scope of foo. This causes us to try and load `x` from a slot, but we don't initialize this slot value before running the lambda because we call `DefineUserVars` after we emit the default arguments.

Use a parse node flag to mark functions located in param scope and construct their ScopeInfo chain correctly nesting such functions in the param scope of their parent.

Fixes:
https://microsoft.visualstudio.com/OS/_workitems/edit/18926499/
  • Loading branch information...
boingoing committed Jun 7, 2019
1 parent c60faa4 commit 784bff567b5cac83e03155be27ea535f73afd624
Showing with 50 additions and 1 deletion.
  1. +1 −0 lib/Parser/Parse.cpp
  2. +3 −1 lib/Parser/ptree.h
  3. +15 −0 lib/Runtime/ByteCode/ScopeInfo.cpp
  4. +25 −0 test/Bugs/bug_OS18926499.js
  5. +6 −0 test/Bugs/rlexe.xml
@@ -5310,6 +5310,7 @@ ParseNodeFnc * Parser::ParseFncDeclInternal(ushort flags, LPCOLESTR pNameHint, c
pnodeFnc->SetIsModule(fModule);
pnodeFnc->SetIsClassConstructor((flags & fFncClassConstructor) != 0);
pnodeFnc->SetIsBaseClassConstructor((flags & fFncBaseClassConstructor) != 0);
pnodeFnc->SetIsDeclaredInParamScope(this->m_currentScope && this->m_currentScope->GetScopeType() == ScopeType_Parameter);
pnodeFnc->SetHomeObjLocation(Js::Constants::NoRegister);

IdentPtr pFncNamePid = nullptr;
@@ -474,7 +474,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
@@ -612,6 +612,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; }
@@ -652,6 +653,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; }
@@ -162,6 +162,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 = currentScope->GetEnclosingScope()->GetFunc();
Assert(func);

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

while (currentScope->GetFunc() == funcInfo)
{
currentScope = currentScope->GetEnclosingScope();
@@ -0,0 +1,25 @@
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------

function foo(a = (()=>+x)()) {
function bar() { eval(''); }
var x;
}

try {
foo();
console.log('fail');
} catch {
console.log("pass");
}

function foo2(a = () => x) { var x = 1; return a(); }

try {
foo2()
console.log('fail');
} catch {
console.log('pass');
}
@@ -582,4 +582,10 @@
<compile-flags>-args summary -endargs</compile-flags>
</default>
</test>
<test>
<default>
<files>bug_OS18926499.js</files>
<compile-flags>-force:deferparse</compile-flags>
</default>
</test>
</regress-exe>

0 comments on commit 784bff5

Please sign in to comment.
You can’t perform that action at this time.