Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions lib/Parser/Parse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2021,14 +2021,27 @@ void Parser::EnsureStackAvailable()

void Parser::ThrowNewTargetSyntaxErrForGlobalScope()
{
//TODO: (falotfi) we need reliably distinguish eval in global scope vs in a function
// The rule for this syntax error is any time new.target is called at global scope
// we are excluding new.target in eval at global scope for now.
if(GetCurrentNonLambdaFunctionNode() == nullptr && (this->m_grfscr & fscrEvalCode) == 0)
if (GetCurrentNonLambdaFunctionNode() != nullptr)
{
Error(ERRInvalidNewTarget);
return;
}
}

if ((this->m_grfscr & fscrEval) != 0)
{
Js::JavascriptFunction * caller = nullptr;
if (Js::JavascriptStackWalker::GetCaller(&caller, m_scriptContext))
{
Js::FunctionBody * callerBody = caller->GetFunctionBody();
Assert(callerBody);
if (!callerBody->GetIsGlobalFunc() && !(callerBody->IsLambda() && callerBody->GetEnclosedByGlobalFunc()))
{
return;
}
}
}

Error(ERRInvalidNewTarget);
}

template<bool buildAST>
ParseNodePtr Parser::ParseMetaProperty(tokens metaParentKeyword, charcount_t ichMin, _Out_opt_ BOOL* pfCanAssign)
Expand Down
3 changes: 3 additions & 0 deletions lib/Parser/ParserPch.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,6 @@
#include "ByteCode/Scope.h"
#include "ByteCode/FuncInfo.h"
#include "ByteCode/ScopeInfo.h"

#include "Library/JavascriptFunction.h"
#include "Language/JavascriptStackWalker.h"
6 changes: 6 additions & 0 deletions lib/Parser/ptree.h
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,11 @@ struct PnFnc
return (fncFlags & flags) == flags;
}

bool HasNoFlags(uint flags) const
{
return (fncFlags & flags) == 0;
}

public:
void ClearFlags()
{
Expand Down Expand Up @@ -324,6 +329,7 @@ struct PnFnc
bool HasWithStmt() const { return HasFlags(kFunctionHasWithStmt); }
bool IsAccessor() const { return HasFlags(kFunctionIsAccessor); }
bool IsAsync() const { return HasFlags(kFunctionIsAsync); }
bool IsConstructor() const { return HasNoFlags(kFunctionIsAsync|kFunctionIsLambda|kFunctionIsAccessor); }
bool IsClassConstructor() const { return HasFlags(kFunctionIsClassConstructor); }
bool IsBaseClassConstructor() const { return HasFlags(kFunctionIsBaseClassConstructor); }
bool IsClassMember() const { return HasFlags(kFunctionIsClassMember); }
Expand Down
3 changes: 3 additions & 0 deletions lib/Runtime/Base/FunctionBody.h
Original file line number Diff line number Diff line change
Expand Up @@ -1604,6 +1604,9 @@ namespace Js
void SetCapturesThis() { attributes = (Attributes)(attributes | Attributes::CapturesThis); }
bool GetCapturesThis() { return (attributes & Attributes::CapturesThis) != 0; }

void SetEnclosedByGlobalFunc() { attributes = (Attributes)(attributes | Attributes::EnclosedByGlobalFunc ); }
bool GetEnclosedByGlobalFunc() { return (attributes & Attributes::EnclosedByGlobalFunc) != 0; }

void BuildDeferredStubs(ParseNode *pnodeFnc);
DeferredFunctionStub *GetDeferredStubs() const { return static_cast<DeferredFunctionStub *>(this->GetAuxPtr(AuxPointerType::DeferredStubs)); }
void SetDeferredStubs(DeferredFunctionStub *stub) { this->SetAuxPtr(AuxPointerType::DeferredStubs, stub); }
Expand Down
1 change: 1 addition & 0 deletions lib/Runtime/Base/FunctionInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace Js
BuiltInInlinableAsLdFldInlinee = 0x08000,
Async = 0x10000,
Module = 0x20000, // The function is the function body wrapper for a module
EnclosedByGlobalFunc = 0x40000,
};
FunctionInfo(JavascriptMethod entryPoint, Attributes attributes = None, LocalFunctionId functionId = Js::Constants::NoFunctionId, FunctionBody* functionBodyImpl = NULL);

Expand Down
2 changes: 1 addition & 1 deletion lib/Runtime/ByteCode/ByteCodeEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2175,7 +2175,7 @@ void ByteCodeGenerator::LoadNewTargetObject(FuncInfo *funcInfo)
EmitInternalScopedSlotLoad(funcInfo, scope, envIndex, slot, funcInfo->newTargetRegister);
}
}
else if (this->flags & fscrEval)
else if ((funcInfo->IsGlobalFunction() || funcInfo->IsLambda()) && (this->flags & fscrEval))
{
Js::RegSlot scopeLocation;

Expand Down
11 changes: 9 additions & 2 deletions lib/Runtime/ByteCode/ByteCodeGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2477,10 +2477,17 @@ FuncInfo* PostVisitFunction(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerat

if (top->IsLambda())
{
if (byteCodeGenerator->FindEnclosingNonLambda()->isThisLexicallyCaptured)
FuncInfo *enclosingNonLambda = byteCodeGenerator->FindEnclosingNonLambda();

if (enclosingNonLambda->isThisLexicallyCaptured)
{
top->byteCodeFunction->SetCapturesThis();
}

if (enclosingNonLambda->IsGlobalFunction())
{
top->byteCodeFunction->SetEnclosedByGlobalFunc();
}
}

// If this is a named function expression and has deferred child, mark has non-local reference.
Expand Down Expand Up @@ -2882,7 +2889,7 @@ FuncInfo* PostVisitFunction(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerat
top->AssignSuperCtorRegister();
}

if (top->IsClassConstructor())
if ((top->root->sxFnc.IsConstructor() && (top->isNewTargetLexicallyCaptured || top->GetCallsEval() || top->GetChildCallsEval())) || top->IsClassConstructor())
{
if (top->IsBaseClassConstructor())
{
Expand Down
2 changes: 1 addition & 1 deletion lib/Runtime/Debug/DiagObjectModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ namespace Js
*isConst = false;


if (!allowLexicalThis && propertyId == Js::PropertyIds::_lexicalThisSlotSymbol)
if (!allowLexicalThis && (propertyId == Js::PropertyIds::_lexicalThisSlotSymbol || propertyId == Js::PropertyIds::_lexicalNewTargetSymbol))
{
return false;
}
Expand Down
5 changes: 4 additions & 1 deletion lib/Runtime/Library/GlobalObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -599,10 +599,12 @@ namespace Js
if (!scriptContext->IsInEvalMap(key, isIndirect, &pfuncScript))
{
uint32 grfscr = additionalGrfscr | fscrReturnExpression | fscrEval | fscrEvalCode | fscrGlobalCode;

if (isLibraryCode)
{
grfscr |= fscrIsLibraryCode;
}

pfuncScript = library->GetGlobalObject()->EvalHelper(scriptContext, argString->GetSz(), argString->GetLength(), moduleID,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EvalHelper [](start = 54, length = 10)

you are already updating grfscr above, so what is the point of passing the isIndirect bool to the function EvalHelper ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in latest rev.


In reply to: 73385216 [](ancestors = 73385216)

grfscr, Constants::EvalCode, doRegisterDocument, isIndirect, strictMode);
Assert(!pfuncScript->GetFunctionInfo()->IsGenerator());
Expand Down Expand Up @@ -887,7 +889,8 @@ namespace Js

grfscr = grfscr | fscrDynamicCode;

hrParser = parser.ParseCesu8Source(&parseTree, utf8Source, cbSource, grfscr, &se, &sourceContextInfo->nextLocalFunctionId,
// fscrEval signifies direct eval in parser
hrParser = parser.ParseCesu8Source(&parseTree, utf8Source, cbSource, isIndirect ? grfscr & ~fscrEval : grfscr, &se, &sourceContextInfo->nextLocalFunctionId,
sourceContextInfo);
sourceInfo->SetParseFlags(grfscr);

Expand Down
79 changes: 76 additions & 3 deletions test/es6/ES6NewTarget.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,14 @@ var tests = [
assert.areEqual('something', obj.target, "The name 'target' can be used as an identifier");
}
},
/*
// Blocked by 'ReferenceError: '<_lexicalNewTargetSymbol>' is undefined' bug
{
name: "new.target is not valid for assignment",
body: function() {
assert.throws(function() { eval("new.target = 'something';"); }, ReferenceError, "new.target cannot be a lhs in an assignment expression - this is an early reference error", "Invalid left-hand side in assignment");
assert.throws(function() { eval("((new.target)) = 'something';"); }, ReferenceError, "new.target cannot be a lhs in an assignment expression - this is an early reference error", "Invalid left-hand side in assignment");
}
},
*/

{
name: "Simple base class gets new.target correctly",
body: function() {
Expand Down Expand Up @@ -273,8 +271,83 @@ var tests = [
assert.isTrue((foo()).next().value == undefined, "Generator function has new.target set to undefined in the function body");
}
},
{
name: "new.target inside eval() in function",
body: function() {
function func() {
var g = ()=>eval('new.target;');
return g();
}

assert.areEqual(undefined, func(), "plain function call");
assert.areEqual(undefined, eval("func()"), "function call inside eval");
assert.areEqual(undefined, eval("eval('func()')"), "function call inside nested evals");
assert.areEqual(undefined, (()=>func())(), "function call inside arrow function");
assert.areEqual(undefined, (()=>(()=>func())())(), "function call inside nested arrow functions");
assert.areEqual(undefined, eval("(()=>func())()"), "function call inside arrow function inside eval");
assert.areEqual(undefined, (()=>eval("func()"))(), "function call inside eval inside arrow function");
assert.areEqual(undefined, eval("(()=>eval('func()'))()"), "function call inside eval inside arrow function inside eval");

assert.areEqual(func, new func(), "plain constructor call");
assert.areEqual(func, eval("new func()"), "constructor call inside eval");
assert.areEqual(func, eval("eval('new func()')"), "constructor call inside nested evals");
assert.areEqual(func, (()=>new func())(), "constructor call inside arrow function");
assert.areEqual(func, (()=>(()=>new func())())(), "constructor call inside nested arrow functions");
assert.areEqual(func, eval("(()=>new func())()"), "constructor call inside arrow function inside eval");
assert.areEqual(func, (()=>eval("new func()"))(), "constructor call inside eval inside arrow function");
assert.areEqual(func, eval("(()=>eval('new func()'))()"), "constructor call inside eval inside arrow function inside eval");
}
},
{
name: "new.target inside netsted eval, arrow function, and function defintion through eval",
body: function() {
eval("function func() {var f = ()=>{function g() {}; return eval('new.target')}; return f(); }" );

assert.areEqual(undefined, func(), "plain function call");
assert.areEqual(undefined, eval("func()"), "function call inside eval");
assert.areEqual(undefined, eval("eval('func()')"), "function call inside nested evals");
assert.areEqual(undefined, (()=>func())(), "function call inside arrow function");
assert.areEqual(undefined, (()=>(()=>func())())(), "function call inside nested arrow functions");
assert.areEqual(undefined, eval("(()=>func())()"), "function call inside arrow function inside eval");
assert.areEqual(undefined, (()=>eval("func()"))(), "function call inside eval inside arrow function");
assert.areEqual(undefined, eval("(()=>eval('func()'))()"), "function call inside eval inside arrow function inside eval");

assert.areEqual(func, new func(), "plain constructor call");
assert.areEqual(func, eval("new func()"), "constructor call inside eval");
assert.areEqual(func, eval("eval('new func()')"), "constructor call inside nested evals");
assert.areEqual(func, (()=>new func())(), "constructor call inside arrow function");
assert.areEqual(func, (()=>(()=>new func())())(), "constructor call inside nested arrow functions");
assert.areEqual(func, eval("(()=>new func())()"), "constructor call inside arrow function inside eval");
assert.areEqual(func, (()=>eval("new func()"))(), "constructor call inside eval inside arrow function");
assert.areEqual(func, eval("(()=>eval('new func()'))()"), "constructor call inside eval inside arrow function inside eval");
}
},
{
name: "direct and indirect eval with new.target",
body: function() {
function scriptThrows(func, errType, info, errMsg) {
try {
func();
throw Error("No exception thrown");
} catch (err) {
assert.areEqual(errType.name + ':' + errMsg, err.name + ':' + err.message, info);
}
}

scriptThrows(()=>{ WScript.LoadScript("eval('new.target')", "samethread"); }, SyntaxError, "direct eval in global function", "Invalid use of the 'new.target' keyword");
scriptThrows(()=>{ WScript.LoadScript("(()=>eval('new.target'))();", "samethread"); }, SyntaxError, "direct eval in lambda in global function", "Invalid use of the 'new.target' keyword");
scriptThrows(()=>{ WScript.LoadScript("var f=()=>eval('new.target'); (function() { return f(); })();", "samethread"); }, SyntaxError, "direct eval in lambda in global function called by a function", "Invalid use of the 'new.target' keyword");
assert.doesNotThrow(()=>{ WScript.LoadScript("(function() { eval('new.target;') })()", "samethread"); }, "direct eval in function");
assert.doesNotThrow(()=>{ WScript.LoadScript("var f =(function() { return ()=>eval('new.target;') })(); f();", "samethread"); }, "direct eval in lambda defined in function and called by global function");

scriptThrows(()=>{ WScript.LoadScript("(0, eval)('new.target;')", "samethread"); }, SyntaxError, "indirect eval in global function", "Invalid use of the 'new.target' keyword");
scriptThrows(()=>{ WScript.LoadScript("(()=>(0, eval)('new.target'))();", "samethread"); }, SyntaxError, "indirect eval in lambda in global function", "Invalid use of the 'new.target' keyword");
scriptThrows(()=>{ WScript.LoadScript("var f=()=>(0, eval)('new.target'); (function() { return f(); })();", "samethread"); }, SyntaxError, "indirect eval in lambda in global function called by a function", "Invalid use of the 'new.target' keyword");
scriptThrows(()=>{ WScript.LoadScript("(function() { (0, eval)('new.target;') })()", "samethread")}, SyntaxError, "indirect eval in function", "Invalid use of the 'new.target' keyword");
scriptThrows(()=>{ WScript.LoadScript("var f =(function() { return ()=>(0, eval)('new.target;') })(); f();", "samethread"); }, SyntaxError, "indirect eval in lambda defined in function and called by global function", "Invalid use of the 'new.target' keyword");

}
},
];

testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });