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