From 4f3f3bfefd4897c56e03d453e712f1af1437fece Mon Sep 17 00:00:00 2001 From: Wyatt Richter Date: Thu, 13 Sep 2018 14:48:07 -0700 Subject: [PATCH] Fixes #5477 - Forces computed property names within classes to be executed in strict mode The ECMA spec requires that a class definition is always strict mode code, this includes computed property names within classes. A flag, forceStrictModeForClassComputedPropertyName, is added into ByteCodeGenerator.h. When set, this flag will force bytecode opcodes to be emitted in strict mode when avaliable. This flag is set outside of emit calls under the condition that the bytecode being emitted corresponds to computed property names within classes. --- lib/Runtime/ByteCode/ByteCodeEmitter.cpp | 33 ++- lib/Runtime/ByteCode/ByteCodeGenerator.cpp | 4 +- lib/Runtime/ByteCode/ByteCodeGenerator.h | 9 +- lib/Runtime/ByteCode/ByteCodeWriter.cpp | 12 +- lib/Runtime/ByteCode/ByteCodeWriter.h | 6 +- test/strict/classComputedPropertyName.js | 245 +++++++++++++++++++++ test/strict/rlexe.xml | 6 + 7 files changed, 298 insertions(+), 17 deletions(-) create mode 100644 test/strict/classComputedPropertyName.js diff --git a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp index 333a5544927..a4f9055e3d1 100644 --- a/lib/Runtime/ByteCode/ByteCodeEmitter.cpp +++ b/lib/Runtime/ByteCode/ByteCodeEmitter.cpp @@ -4733,7 +4733,7 @@ void ByteCodeGenerator::EmitPropStore(Js::RegSlot rhsLocation, Symbol *sym, Iden } else { - this->EmitPatchableRootProperty(GetStFldOpCode(funcInfo, true, isLetDecl, isConstDecl, false), rhsLocation, propertyId, false, true, funcInfo); + this->EmitPatchableRootProperty(GetStFldOpCode(funcInfo, true, isLetDecl, isConstDecl, false, forceStrictModeForClassComputedPropertyName), rhsLocation, propertyId, false, true, funcInfo); } } else if (sym->GetIsFuncExpr()) @@ -5320,7 +5320,7 @@ void ByteCodeGenerator::EmitPropDelete(Js::RegSlot lhsLocation, Symbol *sym, Ide if (this->flags & (fscrEval | fscrImplicitThis)) { this->m_writer.ScopedProperty(Js::OpCode::ScopedDeleteFld, lhsLocation, - funcInfo->FindOrAddReferencedPropertyId(propertyId)); + funcInfo->FindOrAddReferencedPropertyId(propertyId), forceStrictModeForClassComputedPropertyName); } else { @@ -6931,7 +6931,7 @@ void EmitAssignment( { uint cacheId = funcInfo->FindOrAddInlineCacheId(lhs->AsParseNodeBin()->pnode1->location, propertyId, false, true); byteCodeGenerator->Writer()->PatchableProperty( - ByteCodeGenerator::GetStFldOpCode(funcInfo, false, false, false, false), rhsLocation, lhs->AsParseNodeBin()->pnode1->location, cacheId); + ByteCodeGenerator::GetStFldOpCode(funcInfo, false, false, false, false, byteCodeGenerator->forceStrictModeForClassComputedPropertyName), rhsLocation, lhs->AsParseNodeBin()->pnode1->location, cacheId); } break; @@ -8347,7 +8347,19 @@ void EmitMemberNode(ParseNode *memberNode, Js::RegSlot objectLocation, ByteCodeG // Transparently pass the name expr // The Emit will replace this with a temp register if necessary to preserve the value. nameNode->location = nameNode->AsParseNodeUni()->pnode1->location; + + // Save the previous value of the flag to be restored later. + bool prevFlag = byteCodeGenerator->forceStrictModeForClassComputedPropertyName; + + // Strict mode must be enforced on the evaluation of computed property names inside + // classes, thus enable the flag if the computed property name is a class member. + byteCodeGenerator->forceStrictModeForClassComputedPropertyName = isClassMember || prevFlag; + EmitBinaryOpnds(nameNode, exprNode, byteCodeGenerator, funcInfo); + + // Restore the flag's previous value. + byteCodeGenerator->forceStrictModeForClassComputedPropertyName = prevFlag; + if (isFncDecl && !exprNode->AsParseNodeFnc()->IsClassConstructor()) { EmitComputedFunctionNameVar(nameNode, exprNode->AsParseNodeFnc(), byteCodeGenerator); @@ -8374,7 +8386,18 @@ void EmitMemberNode(ParseNode *memberNode, Js::RegSlot objectLocation, ByteCodeG (isClassMember ? Js::OpCode::InitClassMemberSetComputedName : Js::OpCode::InitSetElemI) : (isClassMember ? Js::OpCode::InitClassMemberComputedName : Js::OpCode::InitComputedProperty); - byteCodeGenerator->Writer()->Element(setOp, exprNode->location, objectLocation, nameNode->location, true); + // Save the previous value of the flag to be restored later. + bool prevFlag = byteCodeGenerator->forceStrictModeForClassComputedPropertyName; + byteCodeGenerator->forceStrictModeForClassComputedPropertyName = isClassMember || prevFlag; + + // Strict mode must be enforced on the evaluation of computed property names inside + // classes, thus enable the flag if the computed property name is a class member. + byteCodeGenerator->Writer()->Element(setOp, exprNode->location, objectLocation, nameNode->location, true, + byteCodeGenerator->forceStrictModeForClassComputedPropertyName); + + // Restore the flag's previous value. + byteCodeGenerator->forceStrictModeForClassComputedPropertyName = prevFlag; + funcInfo->ReleaseLoc(exprNode); funcInfo->ReleaseLoc(nameNode); @@ -10634,7 +10657,7 @@ void Emit(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator, FuncInfo *func Js::PropertyId propertyId = pexpr->AsParseNodeBin()->pnode2->AsParseNodeName()->PropertyIdFromNameNode(); funcInfo->AcquireLoc(pnode); byteCodeGenerator->Writer()->Property(Js::OpCode::DeleteFld, pnode->location, pexpr->AsParseNodeBin()->pnode1->location, - funcInfo->FindOrAddReferencedPropertyId(propertyId)); + funcInfo->FindOrAddReferencedPropertyId(propertyId), byteCodeGenerator->forceStrictModeForClassComputedPropertyName); } break; diff --git a/lib/Runtime/ByteCode/ByteCodeGenerator.cpp b/lib/Runtime/ByteCode/ByteCodeGenerator.cpp index 97a2a37e334..4f37b36ce72 100644 --- a/lib/Runtime/ByteCode/ByteCodeGenerator.cpp +++ b/lib/Runtime/ByteCode/ByteCodeGenerator.cpp @@ -1923,9 +1923,9 @@ Scope * ByteCodeGenerator::FindScopeForSym(Scope *symScope, Scope *scope, Js::Pr } /* static */ -Js::OpCode ByteCodeGenerator::GetStFldOpCode(FuncInfo* funcInfo, bool isRoot, bool isLetDecl, bool isConstDecl, bool isClassMemberInit) +Js::OpCode ByteCodeGenerator::GetStFldOpCode(FuncInfo* funcInfo, bool isRoot, bool isLetDecl, bool isConstDecl, bool isClassMemberInit, bool forceStrictModeForClassComputedPropertyName) { - return GetStFldOpCode(funcInfo->GetIsStrictMode(), isRoot, isLetDecl, isConstDecl, isClassMemberInit); + return GetStFldOpCode(funcInfo->GetIsStrictMode() || forceStrictModeForClassComputedPropertyName, isRoot, isLetDecl, isConstDecl, isClassMemberInit); } /* static */ diff --git a/lib/Runtime/ByteCode/ByteCodeGenerator.h b/lib/Runtime/ByteCode/ByteCodeGenerator.h index 8b7455c2f26..07e73d75283 100644 --- a/lib/Runtime/ByteCode/ByteCodeGenerator.h +++ b/lib/Runtime/ByteCode/ByteCodeGenerator.h @@ -65,6 +65,13 @@ class ByteCodeGenerator static const unsigned int MinArgumentsForCallOptimization = 16; bool forceNoNative; + // A flag that when set will force bytecode opcodes to be emitted in strict mode when avaliable. + // This flag is set outside of emit calls under the condition that the bytecode being emitted + // corresponds to computed property names within classes. This fixes a bug where computed property + // names would not enforce strict mode when inside a class even though the spec requires that + // all code within a class must be strict. + bool forceStrictModeForClassComputedPropertyName = false; + ByteCodeGenerator(Js::ScriptContext* scriptContext, Js::ScopeInfo* parentScopeInfo); #if DBG_DUMP @@ -326,7 +333,7 @@ class ByteCodeGenerator isStrictMode ? (isRoot ? Js::OpCode::StRootFldStrict : Js::OpCode::StFldStrict) : isRoot ? Js::OpCode::StRootFld : Js::OpCode::StFld; } - static Js::OpCode GetStFldOpCode(FuncInfo* funcInfo, bool isRoot, bool isLetDecl, bool isConstDecl, bool isClassMemberInit); + static Js::OpCode GetStFldOpCode(FuncInfo* funcInfo, bool isRoot, bool isLetDecl, bool isConstDecl, bool isClassMemberInit, bool forceStrictModeForClassComputedPropertyName = false); static Js::OpCode GetScopedStFldOpCode(bool isStrictMode, bool isConsoleScope = false) { return isStrictMode ? diff --git a/lib/Runtime/ByteCode/ByteCodeWriter.cpp b/lib/Runtime/ByteCode/ByteCodeWriter.cpp index 9ba6c3f8aed..a05a7e1c8da 100644 --- a/lib/Runtime/ByteCode/ByteCodeWriter.cpp +++ b/lib/Runtime/ByteCode/ByteCodeWriter.cpp @@ -1301,7 +1301,7 @@ namespace Js return false; } - void ByteCodeWriter::Element(OpCode op, RegSlot Value, RegSlot Instance, RegSlot Element, bool instanceAtReturnRegOK) + void ByteCodeWriter::Element(OpCode op, RegSlot Value, RegSlot Instance, RegSlot Element, bool instanceAtReturnRegOK, bool forceStrictMode) { CheckOpen(); CheckOp(op, OpLayoutType::ElementI); @@ -1311,7 +1311,7 @@ namespace Js Instance = ConsumeReg(Instance); Element = ConsumeReg(Element); - if (this->m_functionWrite->GetIsStrictMode()) + if (this->m_functionWrite->GetIsStrictMode() || forceStrictMode) { if (op == OpCode::DeleteElemI_A) { @@ -1401,7 +1401,7 @@ namespace Js return false; } - void ByteCodeWriter::ScopedProperty(OpCode op, RegSlot value, PropertyIdIndexType propertyIdIndex) + void ByteCodeWriter::ScopedProperty(OpCode op, RegSlot value, PropertyIdIndexType propertyIdIndex, bool forceStrictMode) { CheckOpen(); CheckOp(op, OpLayoutType::ElementScopedC); @@ -1424,7 +1424,7 @@ namespace Js } #endif - if (this->m_functionWrite->GetIsStrictMode()) + if (this->m_functionWrite->GetIsStrictMode() || forceStrictMode) { if (op == OpCode::ScopedDeleteFld) { @@ -1448,7 +1448,7 @@ namespace Js return false; } - void ByteCodeWriter::Property(OpCode op, RegSlot value, RegSlot instance, PropertyIdIndexType propertyIdIndex) + void ByteCodeWriter::Property(OpCode op, RegSlot value, RegSlot instance, PropertyIdIndexType propertyIdIndex, bool forceStrictMode) { CheckOpen(); CheckOp(op, OpLayoutType::ElementC); @@ -1477,7 +1477,7 @@ namespace Js } #endif - if (this->m_functionWrite->GetIsStrictMode()) + if (this->m_functionWrite->GetIsStrictMode() || forceStrictMode) { if (op == OpCode::DeleteFld) { diff --git a/lib/Runtime/ByteCode/ByteCodeWriter.h b/lib/Runtime/ByteCode/ByteCodeWriter.h index e16f6500f24..77f85584d3a 100644 --- a/lib/Runtime/ByteCode/ByteCodeWriter.h +++ b/lib/Runtime/ByteCode/ByteCodeWriter.h @@ -269,10 +269,10 @@ namespace Js void CallI(OpCode op, RegSlot returnValueRegister, RegSlot functionRegister, ArgSlot givenArgCount, ProfileId callSiteId, CallFlags callFlags = CallFlags_None); void CallIExtended(OpCode op, RegSlot returnValueRegister, RegSlot functionRegister, ArgSlot givenArgCount, CallIExtendedOptions options, const void *buffer, uint byteCount, ProfileId callSiteId, CallFlags callFlags = CallFlags_None); void RemoveEntryForRegSlotFromCacheIdMap(RegSlot functionRegister); - void Element(OpCode op, RegSlot value, RegSlot instance, RegSlot element, bool instanceAtReturnRegOK = false); + void Element(OpCode op, RegSlot value, RegSlot instance, RegSlot element, bool instanceAtReturnRegOK = false, bool forceStrictMode = false); void ElementUnsigned1(OpCode op, RegSlot value, RegSlot instance, uint32 element); - void Property(OpCode op, RegSlot Value, RegSlot Instance, PropertyIdIndexType propertyIdIndex); - void ScopedProperty(OpCode op, RegSlot Value, PropertyIdIndexType propertyIdIndex); + void Property(OpCode op, RegSlot Value, RegSlot Instance, PropertyIdIndexType propertyIdIndex, bool forceStrictMode = false); + void ScopedProperty(OpCode op, RegSlot Value, PropertyIdIndexType propertyIdIndex, bool forceStrictMode = false); void Slot(OpCode op, RegSlot value, RegSlot instance, uint32 slotId); void Slot(OpCode op, RegSlot value, RegSlot instance, uint32 slotId, ProfileId profileId); void SlotI1(OpCode op, RegSlot value, uint32 slotId1); diff --git a/test/strict/classComputedPropertyName.js b/test/strict/classComputedPropertyName.js new file mode 100644 index 00000000000..0887ae0effa --- /dev/null +++ b/test/strict/classComputedPropertyName.js @@ -0,0 +1,245 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +WScript.LoadScriptFile("..\\UnitTestFramework\\UnitTestFramework.js"); + +var tests = [ + { + name: "Assigning an undeclared variable in a class' computed property name", + body: function () { + assert.throws( + function () { + class C { + [f = 5]() { } + } + }, + ReferenceError, + "Computed property names inside classes are specified to execute in strict mode,\ +thus a variable assignment to an undeclared variable should throw a ReferenceError in strict mode", + "Variable undefined in strict mode" + ); + assert.throws( + function () { + class C { + static [f = 5]() { } + } + }, + ReferenceError, + "Computed property names inside classes are specified to execute in strict mode,\ +thus a variable assignment to an undeclared variable should throw a ReferenceError in strict mode", + "Variable undefined in strict mode" + ); + assert.throws( + function () { + "use strict"; + class C { + [f = 5]() { } + } + }, + ReferenceError, + "Computed property names inside classes are specified to execute in strict mode,\ +thus a variable assignment to an undeclared variable should throw a ReferenceError in strict mode", + "Variable undefined in strict mode" + ); + } + }, + { + name: "Writing to a non writable object property in a class' computed property name", + body: function () { + assert.throws( + function () { + var a = {}; + Object.defineProperty(a, 'b', { value: 5, writable: false }); + class C { + [a.b = 6]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode,\ +thus assigning a value to a non writable property should throw a TypeError in strict mode", + "Assignment to read-only properties is not allowed in strict mode" + ); + assert.throws( + function () { + var a = {}; + Object.defineProperty(a, 'b', { value: 5, writable: false }); + class C { + static [a.b = 6]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode,\ +thus assigning a value to a non writable property should throw a TypeError in strict mode", + "Assignment to read-only properties is not allowed in strict mode" + ); + } + }, + { + name: "Writing to a getter-only object property in a class' computed property name", + body: function () { + assert.throws( + function () { + var a = { get b() { return 5; } }; + class C { + [a.b = 6]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode,\ +thus assigning a value to a getter-only property should throw a TypeError in strict mode", + "Assignment to read-only properties is not allowed in strict mode" + ); + assert.throws( + function () { + var a = { get b() { return 5; } }; + class C { + static [a.b = 6]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode,\ +thus assigning a value to a getter-only property should throw a TypeError in strict mode", + "Assignment to read-only properties is not allowed in strict mode" + ); + } + }, + { + name: "Writing to a property of a non-extensible object in a class' computed property name", + body: function () { + assert.throws( + function () { + var a = {}; + Object.preventExtensions(a); + class C { + [a.b = 5]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode,\ +thus assigning a value to a property of a non-extensible object should throw a TypeError in strict mode", + "Cannot create property for a non-extensible object" + ); + assert.throws( + function () { + var a = {}; + Object.preventExtensions(a); + class C { + static [a.b = 5]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode,\ +thus assigning a value to a property of a non-extensible object should throw a TypeError in strict mode", + "Cannot create property for a non-extensible object" + ); + } + }, + { + name: "Calling delete on an undeletable property in a class' computed property name", + body: function () { + assert.throws( + function () { + class C { + [delete Object.prototype]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode,\ +thus calling delete on an undeletable property of object should throw a TypeError in strict mode", + "Calling delete on 'prototype' is not allowed in strict mode" + ); + assert.throws( + function () { + class C { + static [delete Object.prototype]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode,\ +thus calling delete on an undeletable property of object should throw a TypeError in strict mode", + "Calling delete on 'prototype' is not allowed in strict mode" + ); + assert.throws( + function () { + var a = 5; + class C { + [a < 6 ? delete Object.prototype : 5]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode, \ +thus calling delete on an undeletable property of object should throw a TypeError in strict mode", + "Calling delete on 'prototype' is not allowed in strict mode" + ); + assert.throws( + function () { + var a = 5; + class C { + static [a < 6 ? delete Object.prototype : 5]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode, \ +thus calling delete on an undeletable property of object should throw a TypeError in strict mode", + "Calling delete on 'prototype' is not allowed in strict mode" + ); + assert.throws( + function () { + var a = {}; + Object.preventExtensions(a); + class C { + [a && delete Object.prototype]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode, \ +thus calling delete on an undeletable property of object should throw a TypeError in strict mode", + "Calling delete on 'prototype' is not allowed in strict mode" + ); + assert.throws( + function () { + var a = {}; + Object.preventExtensions(a); + class C { + static [a && delete Object.prototype]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode, \ +thus calling delete on an undeletable property of object should throw a TypeError in strict mode", + "Calling delete on 'prototype' is not allowed in strict mode" + ); + assert.throws( + function () { + var a = {}; + Object.defineProperty(a, "x", { value: 5, configurable: false }); + class C { + [delete a["x"]]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode, \ +thus calling delete on an undeletable property of object should throw a TypeError in strict mode", + "Calling delete on 'x' is not allowed in strict mode" + ); + assert.throws( + function () { + var a = {}; + Object.defineProperty(a, "x", { value: 5, configurable: false }); + class C { + static [delete a["x"]]() { } + } + }, + TypeError, + "Computed property names inside classes are specified to execute in strict mode, \ +thus calling delete on an undeletable property of object should throw a TypeError in strict mode", + "Calling delete on 'x' is not allowed in strict mode" + ); + } + }, + +] + +testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" }); \ No newline at end of file diff --git a/test/strict/rlexe.xml b/test/strict/rlexe.xml index 95f7ac61675..2c0c724767e 100644 --- a/test/strict/rlexe.xml +++ b/test/strict/rlexe.xml @@ -716,4 +716,10 @@ exclude_dynapogo + + + classComputedPropertyName.js + -args summary -endargs + +