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 + +