diff --git a/lib/Parser/Parse.cpp b/lib/Parser/Parse.cpp index e140e5e841a..8c6f1f0c0de 100644 --- a/lib/Parser/Parse.cpp +++ b/lib/Parser/Parse.cpp @@ -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 ParseNodePtr Parser::ParseMetaProperty(tokens metaParentKeyword, charcount_t ichMin, _Out_opt_ BOOL* pfCanAssign) diff --git a/lib/Parser/ParserPch.h b/lib/Parser/ParserPch.h index 7be700874d7..9aa31905dc2 100644 --- a/lib/Parser/ParserPch.h +++ b/lib/Parser/ParserPch.h @@ -24,3 +24,6 @@ #include "ByteCode/Scope.h" #include "ByteCode/FuncInfo.h" #include "ByteCode/ScopeInfo.h" + +#include "Library/JavascriptFunction.h" +#include "Language/JavascriptStackWalker.h" diff --git a/lib/Parser/ptree.h b/lib/Parser/ptree.h index f5167f9b584..61b965db47f 100644 --- a/lib/Parser/ptree.h +++ b/lib/Parser/ptree.h @@ -266,6 +266,11 @@ struct PnFnc return (fncFlags & flags) == flags; } + bool HasNoFlags(uint flags) const + { + return (fncFlags & flags) == 0; + } + public: void ClearFlags() { @@ -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); } diff --git a/lib/Runtime/Base/FunctionBody.h b/lib/Runtime/Base/FunctionBody.h index 014931cdb8f..0c34eaed605 100644 --- a/lib/Runtime/Base/FunctionBody.h +++ b/lib/Runtime/Base/FunctionBody.h @@ -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(this->GetAuxPtr(AuxPointerType::DeferredStubs)); } void SetDeferredStubs(DeferredFunctionStub *stub) { this->SetAuxPtr(AuxPointerType::DeferredStubs, stub); } diff --git a/lib/Runtime/Base/FunctionInfo.h b/lib/Runtime/Base/FunctionInfo.h index f763e7541dd..b187c473200 100644 --- a/lib/Runtime/Base/FunctionInfo.h +++ b/lib/Runtime/Base/FunctionInfo.h @@ -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); diff --git a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp index d42659607ea..96d100f9f7a 100644 --- a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp +++ b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp @@ -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; diff --git a/lib/Runtime/ByteCode/ByteCodeGenerator.cpp b/lib/Runtime/ByteCode/ByteCodeGenerator.cpp index c535f1d012b..c8c26b24ea7 100644 --- a/lib/Runtime/ByteCode/ByteCodeGenerator.cpp +++ b/lib/Runtime/ByteCode/ByteCodeGenerator.cpp @@ -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. @@ -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()) { diff --git a/lib/Runtime/Debug/DiagObjectModel.cpp b/lib/Runtime/Debug/DiagObjectModel.cpp index 37eb76bd977..2b90ce836bb 100644 --- a/lib/Runtime/Debug/DiagObjectModel.cpp +++ b/lib/Runtime/Debug/DiagObjectModel.cpp @@ -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; } diff --git a/lib/Runtime/Library/GlobalObject.cpp b/lib/Runtime/Library/GlobalObject.cpp index e2f9d689dca..3505ae89852 100644 --- a/lib/Runtime/Library/GlobalObject.cpp +++ b/lib/Runtime/Library/GlobalObject.cpp @@ -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, grfscr, Constants::EvalCode, doRegisterDocument, isIndirect, strictMode); Assert(!pfuncScript->GetFunctionInfo()->IsGenerator()); @@ -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); diff --git a/test/es6/ES6NewTarget.js b/test/es6/ES6NewTarget.js index b59ccc913a2..3527b47f686 100644 --- a/test/es6/ES6NewTarget.js +++ b/test/es6/ES6NewTarget.js @@ -49,8 +49,6 @@ 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() { @@ -58,7 +56,7 @@ var tests = [ 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() { @@ -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" });