Skip to content

Commit 4115143

Browse files
committed
[1.8>1.9] [MERGE #4629 @boingoing] OS#15334058: Fix class extends null and this access for super property reference
Merge pull request #4629 from boingoing:ClassExtendsNull We were supporting some old version of the spec language around classes which extend from null. Instead of leaving `this` undeclared, we were constructing an object and assigning it the `this` binding. This meant we were not throwing ReferenceErrors on access to `this` when we should have been. Also fixed the case where a super property reference failed to access `this` resulting in trying to load a property from null. Fixes: https://microsoft.visualstudio.com/OS/_workitems/edit/15334058
2 parents 8c0764e + 5ebaf10 commit 4115143

File tree

4 files changed

+45
-76
lines changed

4 files changed

+45
-76
lines changed

Diff for: lib/Runtime/ByteCode/ByteCodeEmitter.cpp

+20-31
Original file line numberDiff line numberDiff line change
@@ -2134,31 +2134,20 @@ void ByteCodeGenerator::LoadThisObject(FuncInfo *funcInfo, bool thisLoadedFromPa
21342134

21352135
if (this->scriptContext->GetConfig()->IsES6ClassAndExtendsEnabled() && funcInfo->IsClassConstructor())
21362136
{
2137-
// Derived class constructors initialize 'this' to be Undecl except "extends null" cases
2137+
// Derived class constructors initialize 'this' to be Undecl
21382138
// - we'll check this value during a super call and during 'this' access
21392139
//
2140-
// Base class constructors or "extends null" cases initialize 'this' to a new object using new.target
2140+
// Base class constructors initialize 'this' to a new object using new.target
21412141
if (funcInfo->IsBaseClassConstructor())
21422142
{
2143-
EmitBaseClassConstructorThisObject(funcInfo);
2143+
Symbol* newTargetSym = funcInfo->GetNewTargetSymbol();
2144+
Assert(newTargetSym);
2145+
2146+
this->Writer()->Reg2(Js::OpCode::NewScObjectNoCtorFull, thisSym->GetLocation(), newTargetSym->GetLocation());
21442147
}
21452148
else
21462149
{
2147-
Js::ByteCodeLabel thisLabel = this->Writer()->DefineLabel();
2148-
Js::ByteCodeLabel skipLabel = this->Writer()->DefineLabel();
2149-
2150-
Js::RegSlot tmpReg = funcInfo->AcquireTmpRegister();
2151-
this->Writer()->Reg1(Js::OpCode::LdFuncObj, tmpReg);
2152-
this->Writer()->BrReg1(Js::OpCode::BrOnBaseConstructorKind, thisLabel, tmpReg); // branch when [[ConstructorKind]]=="base"
2153-
funcInfo->ReleaseTmpRegister(tmpReg);
2154-
2155-
this->m_writer.Reg1(Js::OpCode::InitUndecl, thisSym->GetLocation()); // not "extends null" case
2156-
this->Writer()->Br(Js::OpCode::Br, skipLabel);
2157-
2158-
this->Writer()->MarkLabel(thisLabel);
2159-
EmitBaseClassConstructorThisObject(funcInfo); // "extends null" case
2160-
2161-
this->Writer()->MarkLabel(skipLabel);
2150+
this->m_writer.Reg1(Js::OpCode::InitUndecl, thisSym->GetLocation());
21622151
}
21632152
}
21642153
else if (!funcInfo->IsGlobalFunction())
@@ -2207,11 +2196,11 @@ void ByteCodeGenerator::LoadSuperConstructorObject(FuncInfo *funcInfo)
22072196
{
22082197
Symbol* superConstructorSym = funcInfo->GetSuperConstructorSymbol();
22092198
Assert(superConstructorSym);
2210-
Assert(!funcInfo->IsLambda());
2199+
Assert(!funcInfo->IsLambda());
22112200
Assert(funcInfo->IsDerivedClassConstructor());
22122201

2213-
m_writer.Reg1(Js::OpCode::LdFuncObj, superConstructorSym->GetLocation());
2214-
}
2202+
m_writer.Reg1(Js::OpCode::LdFuncObj, superConstructorSym->GetLocation());
2203+
}
22152204

22162205
void ByteCodeGenerator::LoadSuperObject(FuncInfo *funcInfo)
22172206
{
@@ -2338,11 +2327,6 @@ void ByteCodeGenerator::EmitClassConstructorEndCode(FuncInfo *funcInfo)
23382327
}
23392328
}
23402329

2341-
void ByteCodeGenerator::EmitBaseClassConstructorThisObject(FuncInfo *funcInfo)
2342-
{
2343-
this->Writer()->Reg2(Js::OpCode::NewScObjectNoCtorFull, funcInfo->GetThisSymbol()->GetLocation(), funcInfo->GetNewTargetSymbol()->GetLocation());
2344-
}
2345-
23462330
void ByteCodeGenerator::EmitThis(FuncInfo *funcInfo, Js::RegSlot lhsLocation, Js::RegSlot fromRegister)
23472331
{
23482332
if (funcInfo->byteCodeFunction->GetIsStrictMode() && !funcInfo->IsGlobalFunction() && !funcInfo->IsLambda())
@@ -4916,14 +4900,14 @@ void ByteCodeGenerator::EmitPropLoadThis(Js::RegSlot lhsLocation, ParseNode *pno
49164900
}
49174901
else
49184902
{
4919-
this->EmitPropLoad(lhsLocation, pnode->sxPid.sym, pnode->sxPid.pid, funcInfo, true);
4903+
this->EmitPropLoad(lhsLocation, pnode->sxPid.sym, pnode->sxPid.pid, funcInfo, true);
49204904

4921-
if ((!sym || sym->GetNeedDeclaration()) && chkUndecl)
4922-
{
4923-
this->Writer()->Reg1(Js::OpCode::ChkUndecl, lhsLocation);
4905+
if ((!sym || sym->GetNeedDeclaration()) && chkUndecl)
4906+
{
4907+
this->Writer()->Reg1(Js::OpCode::ChkUndecl, lhsLocation);
4908+
}
49244909
}
49254910
}
4926-
}
49274911

49284912
void ByteCodeGenerator::EmitPropStoreForSpecialSymbol(Js::RegSlot rhsLocation, Symbol *sym, IdentPtr pid, FuncInfo *funcInfo, bool init)
49294913
{
@@ -6804,6 +6788,11 @@ void EmitAssignment(
68046788

68056789
if (ByteCodeGenerator::IsSuper(lhs->sxBin.pnode1))
68066790
{
6791+
// We need to emit the 'this' node for the super reference even if we aren't planning to use the 'this' value.
6792+
// This is because we might be in a derived class constructor where we haven't yet called super() to bind the 'this' value.
6793+
// See ecma262 abstract operation 'MakeSuperPropertyReference'
6794+
Emit(lhs->sxSuperReference.pnodeThis, byteCodeGenerator, funcInfo, false);
6795+
funcInfo->ReleaseLoc(lhs->sxSuperReference.pnodeThis);
68076796
targetLocation = byteCodeGenerator->EmitLdObjProto(Js::OpCode::LdHomeObjProto, targetLocation, funcInfo);
68086797
}
68096798

Diff for: lib/Runtime/Language/JavascriptOperators.cpp

-5
Original file line numberDiff line numberDiff line change
@@ -7087,11 +7087,6 @@ namespace Js
70877087
ctorProtoObj->EnsureProperty(Js::PropertyIds::constructor);
70887088
ctorProtoObj->SetEnumerable(Js::PropertyIds::constructor, FALSE);
70897089

7090-
if (ScriptFunctionBase::Is(constructor))
7091-
{
7092-
ScriptFunctionBase::FromVar(constructor)->GetFunctionInfo()->SetBaseConstructorKind();
7093-
}
7094-
70957090
break;
70967091
}
70977092

Diff for: test/Basics/SpecialSymbolCapture.js

-25
Original file line numberDiff line numberDiff line change
@@ -516,31 +516,6 @@ var tests = [
516516
}
517517
}
518518
assert.throws(() => new DerivedSuper(), TypeError, "Class derived from null can't make a super call", "Function is not a constructor");
519-
520-
class DerivedEmpty extends null {
521-
constructor() { }
522-
}
523-
assert.areEqual(DerivedEmpty, new DerivedEmpty().constructor, "Default instance for 'extends null' case is a real instance of the derived class");
524-
525-
var called = false;
526-
class DerivedVerifyNewTarget extends null {
527-
constructor() {
528-
assert.areEqual(DerivedVerifyNewTarget, new.target, "Derived class called as new expression gets new.target");
529-
called = true;
530-
}
531-
}
532-
assert.areEqual(DerivedVerifyNewTarget, new DerivedVerifyNewTarget().constructor, "Default instance for 'extends null' case is a real instance of the derived class");
533-
assert.isTrue(called, "Constructor was actually called");
534-
535-
called = false;
536-
class DerivedVerifyThis extends null {
537-
constructor() {
538-
assert.areEqual(DerivedVerifyThis, this.constructor, "Derived from null class called as new expression gets this instance of the derived class");
539-
called = true;
540-
}
541-
}
542-
assert.areEqual(DerivedVerifyThis, new DerivedVerifyThis().constructor, "Default instance for 'extends null' case is a real instance of the derived class");
543-
assert.isTrue(called, "Constructor was actually called");
544519
}
545520
},
546521
{

Diff for: test/es6/ES6Class_BaseClassConstruction.js

+25-15
Original file line numberDiff line numberDiff line change
@@ -108,16 +108,13 @@ var tests = [
108108
}
109109
},
110110
{
111-
name: "Class that extends null binds this in constructor",
111+
name: "Class that extends null doesn't bind 'this' implicitly",
112112
body: function () {
113-
var thisVal;
114113
class B extends null {
115-
constructor() { thisVal = this; }
114+
constructor() { }
116115
}
117116

118-
var b = new B();
119-
assert.areEqual(true, b instanceof B);
120-
assert.areEqual(thisVal, b);
117+
assert.throws(() => new B(), ReferenceError, "implicit return of 'this' throws", "Use before declaration");
121118
}
122119
},
123120
{
@@ -140,27 +137,40 @@ var tests = [
140137
}
141138
},
142139
{
143-
name: "Class that extends null with implicit return in constructor",
140+
name: "Class that extends null with explicit return in constructor",
144141
body: function () {
145142
class A extends null {
146-
constructor() {}
143+
constructor() { return {}; }
147144
}
148145

149146
var a;
150147
assert.doesNotThrow(()=>{a = new A()});
151-
assert.areEqual(A.prototype, Object.getPrototypeOf(a));
148+
assert.areEqual(Object.prototype, Object.getPrototypeOf(a));
152149
}
153150
},
154151
{
155-
name: "Class that extends null with explicit return in constructor",
152+
name: "Class that extends null with super references",
156153
body: function () {
157154
class A extends null {
158-
constructor() { return {}; }
155+
constructor() { super['prop'] = 'something'; return {}; }
159156
}
160-
161-
var a;
162-
assert.doesNotThrow(()=>{a = new A()});
163-
assert.areEqual(Object.prototype, Object.getPrototypeOf(a));
157+
assert.throws(() => new A(), ReferenceError, "super reference loads 'this' and throws if it's undecl ", "Use before declaration");
158+
159+
var prop = 'prop';
160+
class B extends null {
161+
constructor() { super[prop] = 'something'; return {}; }
162+
}
163+
assert.throws(() => new B(), ReferenceError, "super reference loads 'this' and throws if it's undecl ", "Use before declaration");
164+
165+
class C extends null {
166+
constructor() { super['prop']; return {}; }
167+
}
168+
assert.throws(() => new C(), ReferenceError, "super reference loads 'this' and throws if it's undecl ", "Use before declaration");
169+
170+
class D extends null {
171+
constructor() { super[prop]; return {}; }
172+
}
173+
assert.throws(() => new D(), ReferenceError, "super reference loads 'this' and throws if it's undecl ", "Use before declaration");
164174
}
165175
},
166176
];

0 commit comments

Comments
 (0)