[class] implement access of private methods

This patch implements the access of private methods:

- When building property loads, check whether it requires
  a brand check. If so, build the brand check and load the
  property (the method) from the context instead.
- Throw type errors when there is an attempted write to private
  methods.

Design: https://docs.google.com/document/d/1T-Ql6HOIH2U_8YjWkwK2rTfywwb7b3Qe8d3jkz72KwA/edit#

Bug: v8:8330
Change-Id: Ic917d2a0030196c1940b0c0ba65a340af736c769
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1610383
Commit-Queue: Joyee Cheung <joyee@igalia.com>
Reviewed-by: Ross McIlroy <rmcilroy@chromium.org>
Cr-Commit-Position: refs/heads/master@{#62292}
This commit is contained in:
Joyee Cheung 2019-06-19 17:59:28 +08:00 committed by Commit Bot
parent b4e9d6c0a8
commit 31a951d875
8 changed files with 377 additions and 112 deletions

View File

@ -415,6 +415,7 @@ namespace internal {
"Read of private field % from an object which did not contain the field") \ "Read of private field % from an object which did not contain the field") \
T(InvalidPrivateFieldWrite, \ T(InvalidPrivateFieldWrite, \
"Write of private field % to an object which did not contain the field") \ "Write of private field % to an object which did not contain the field") \
T(InvalidPrivateMethodWrite, "Private method '%' is not writable") \
T(JsonParseUnexpectedEOS, "Unexpected end of JSON input") \ T(JsonParseUnexpectedEOS, "Unexpected end of JSON input") \
T(JsonParseUnexpectedToken, "Unexpected token % in JSON at position %") \ T(JsonParseUnexpectedToken, "Unexpected token % in JSON at position %") \
T(JsonParseUnexpectedTokenNumber, "Unexpected number in JSON at position %") \ T(JsonParseUnexpectedTokenNumber, "Unexpected number in JSON at position %") \

View File

@ -2201,6 +2201,19 @@ void BytecodeGenerator::VisitInitializeClassMembersStatement(
} }
} }
void BytecodeGenerator::BuildThrowPrivateMethodWriteError(
const AstRawString* name) {
RegisterAllocationScope register_scope(this);
RegisterList args = register_allocator()->NewRegisterList(2);
builder()
->LoadLiteral(Smi::FromEnum(MessageTemplate::kInvalidPrivateMethodWrite))
.StoreAccumulatorInRegister(args[0])
.LoadLiteral(name)
.StoreAccumulatorInRegister(args[1])
.CallRuntime(Runtime::kNewTypeError, args)
.Throw();
}
void BytecodeGenerator::BuildPrivateBrandInitialization(Register receiver) { void BytecodeGenerator::BuildPrivateBrandInitialization(Register receiver) {
RegisterList brand_args = register_allocator()->NewRegisterList(2); RegisterList brand_args = register_allocator()->NewRegisterList(2);
Variable* brand = info()->scope()->outer_scope()->AsClassScope()->brand(); Variable* brand = info()->scope()->outer_scope()->AsClassScope()->brand();
@ -3746,9 +3759,10 @@ void BytecodeGenerator::BuildAssignment(
lhs_data.super_property_args()); lhs_data.super_property_args());
break; break;
} }
case PRIVATE_METHOD: case PRIVATE_METHOD: {
// TODO(joyee): Throw TypeError because private methods are not writable. BuildThrowPrivateMethodWriteError(lhs_data.name());
UNREACHABLE(); break;
}
} }
} }
@ -3794,9 +3808,10 @@ void BytecodeGenerator::VisitCompoundAssignment(CompoundAssignment* expr) {
lhs_data.super_property_args().Truncate(3)); lhs_data.super_property_args().Truncate(3));
break; break;
} }
case PRIVATE_METHOD: case PRIVATE_METHOD: {
// TODO(joyee): Throw TypeError because private methods are not writable. BuildThrowPrivateMethodWriteError(lhs_data.name());
UNREACHABLE(); break;
}
} }
BinaryOperation* binop = expr->AsCompoundAssignment()->binary_operation(); BinaryOperation* binop = expr->AsCompoundAssignment()->binary_operation();
FeedbackSlot slot = feedback_spec()->AddBinaryOpICSlot(); FeedbackSlot slot = feedback_spec()->AddBinaryOpICSlot();
@ -4254,8 +4269,23 @@ void BytecodeGenerator::VisitPropertyLoad(Register obj, Property* property) {
case KEYED_SUPER_PROPERTY: case KEYED_SUPER_PROPERTY:
VisitKeyedSuperPropertyLoad(property, Register::invalid_value()); VisitKeyedSuperPropertyLoad(property, Register::invalid_value());
break; break;
case PRIVATE_METHOD: case PRIVATE_METHOD: {
UNREACHABLE(); // TODO(joyee): implement private method access. Variable* private_name = property->key()->AsVariableProxy()->var();
// Perform the brand check.
DCHECK(private_name->requires_brand_check());
ClassScope* scope = private_name->scope()->AsClassScope();
Variable* brand = scope->brand();
BuildVariableLoadForAccumulatorValue(brand, HoleCheckMode::kElided);
builder()->SetExpressionPosition(property);
builder()->LoadKeyedProperty(
obj, feedback_index(feedback_spec()->AddKeyedLoadICSlot()));
// In the case of private methods, property->key() is the function to be
// loaded (stored in a context slot), so load this directly.
VisitForAccumulatorValue(property->key());
break;
}
} }
} }
@ -4806,8 +4836,9 @@ void BytecodeGenerator::VisitCountOperation(CountOperation* expr) {
break; break;
} }
case PRIVATE_METHOD: { case PRIVATE_METHOD: {
// TODO(joyee): Throw TypeError because private methods are not writable. BuildThrowPrivateMethodWriteError(
UNREACHABLE(); property->key()->AsVariableProxy()->raw_name());
break;
} }
} }
@ -4876,8 +4907,9 @@ void BytecodeGenerator::VisitCountOperation(CountOperation* expr) {
break; break;
} }
case PRIVATE_METHOD: { case PRIVATE_METHOD: {
// TODO(joyee): Throw TypeError because private methods are not writable. BuildThrowPrivateMethodWriteError(
UNREACHABLE(); property->key()->AsVariableProxy()->raw_name());
break;
} }
} }

View File

@ -294,6 +294,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
void VisitArgumentsObject(Variable* variable); void VisitArgumentsObject(Variable* variable);
void VisitRestArgumentsArray(Variable* rest); void VisitRestArgumentsArray(Variable* rest);
void VisitCallSuper(Call* call); void VisitCallSuper(Call* call);
void BuildThrowPrivateMethodWriteError(const AstRawString* name);
void BuildPrivateClassMemberNameAssignment(ClassLiteral::Property* property); void BuildPrivateClassMemberNameAssignment(ClassLiteral::Property* property);
void BuildClassLiteral(ClassLiteral* expr, Register name); void BuildClassLiteral(ClassLiteral* expr, Register name);
void VisitClassLiteral(ClassLiteral* expr, Register name); void VisitClassLiteral(ClassLiteral* expr, Register name);

View File

@ -11,47 +11,57 @@ snippet: "
{ {
class A { class A {
#a() { return 1; } #a() { return 1; }
callA() { return this.#a(); }
} }
new A; const a = new A;
a.callA();
} }
" "
frame size: 7 frame size: 9
parameter count: 1 parameter count: 1
bytecode array length: 62 bytecode array length: 80
bytecodes: [ bytecodes: [
/* 30 E> */ B(StackCheck), /* 30 E> */ B(StackCheck),
B(CreateBlockContext), U8(0), B(CreateBlockContext), U8(0),
B(PushContext), R(2), B(PushContext), R(3),
B(LdaTheHole), B(LdaTheHole),
B(Star), R(6), B(Star), R(7),
B(CreateClosure), U8(2), U8(0), U8(2), B(CreateClosure), U8(2), U8(0), U8(2),
B(Star), R(3),
B(LdaConstant), U8(1),
B(Star), R(4), B(Star), R(4),
B(LdaConstant), U8(1),
B(Star), R(5),
B(CreateClosure), U8(3), U8(1), U8(2), B(CreateClosure), U8(3), U8(1), U8(2),
B(StaCurrentContextSlot), U8(4), B(StaCurrentContextSlot), U8(4),
B(Mov), R(3), R(5), B(CreateClosure), U8(4), U8(2), U8(2),
B(CallRuntime), U16(Runtime::kDefineClass), R(4), U8(3), B(Star), R(8),
B(Star), R(4), B(Mov), R(4), R(6),
B(Mov), R(5), R(1), B(CallRuntime), U16(Runtime::kDefineClass), R(5), U8(4),
B(LdaConstant), U8(4),
B(Star), R(5), B(Star), R(5),
B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(5), U8(1), B(Mov), R(6), R(2),
B(LdaConstant), U8(5),
B(Star), R(6),
B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(6), U8(1),
B(StaCurrentContextSlot), U8(5), B(StaCurrentContextSlot), U8(5),
B(PopContext), R(2), B(PopContext), R(3),
B(Mov), R(1), R(0), B(Mov), R(2), R(0),
/* 78 S> */ B(Ldar), R(0), /* 122 S> */ B(Ldar), R(0),
/* 78 E> */ B(Construct), R(0), R(0), U8(0), U8(0), /* 122 E> */ B(Construct), R(0), R(0), U8(0), U8(0),
B(Star), R(1),
/* 133 S> */ B(LdaNamedProperty), R(1), U8(6), U8(2),
B(Star), R(3),
/* 133 E> */ B(CallProperty0), R(3), R(1), U8(4),
B(LdaUndefined), B(LdaUndefined),
/* 87 S> */ B(Return), /* 143 S> */ B(Return),
] ]
constant pool: [ constant pool: [
SCOPE_INFO_TYPE, SCOPE_INFO_TYPE,
FIXED_ARRAY_TYPE, FIXED_ARRAY_TYPE,
SHARED_FUNCTION_INFO_TYPE, SHARED_FUNCTION_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE, SHARED_FUNCTION_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE,
ONE_BYTE_INTERNALIZED_STRING_TYPE ["A"], ONE_BYTE_INTERNALIZED_STRING_TYPE ["A"],
ONE_BYTE_INTERNALIZED_STRING_TYPE ["callA"],
] ]
handlers: [ handlers: [
] ]
@ -60,79 +70,90 @@ handlers: [
snippet: " snippet: "
{ {
class D { class D {
#d() {} #d() { return 1; }
callD() { return this.#d(); }
} }
class E extends D { class E extends D {
#e() {} #e() { return 2; }
callE() { return this.callD() + this.#e(); }
} }
new D; const e = new E;
new E; e.callE();
} }
" "
frame size: 9 frame size: 11
parameter count: 1 parameter count: 1
bytecode array length: 121 bytecode array length: 138
bytecodes: [ bytecodes: [
/* 30 E> */ B(StackCheck), /* 30 E> */ B(StackCheck),
B(CreateBlockContext), U8(0), B(CreateBlockContext), U8(0),
B(PushContext), R(4), B(PushContext), R(5),
B(LdaTheHole), B(LdaTheHole),
B(Star), R(8), B(Star), R(9),
B(CreateClosure), U8(2), U8(0), U8(2), B(CreateClosure), U8(2), U8(0), U8(2),
B(Star), R(5),
B(LdaConstant), U8(1),
B(Star), R(6), B(Star), R(6),
B(LdaConstant), U8(1),
B(Star), R(7),
B(CreateClosure), U8(3), U8(1), U8(2), B(CreateClosure), U8(3), U8(1), U8(2),
B(StaCurrentContextSlot), U8(4), B(StaCurrentContextSlot), U8(4),
B(Mov), R(5), R(7), B(CreateClosure), U8(4), U8(2), U8(2),
B(CallRuntime), U16(Runtime::kDefineClass), R(6), U8(3), B(Star), R(10),
B(Star), R(6), B(Mov), R(6), R(8),
B(Mov), R(7), R(3), B(CallRuntime), U16(Runtime::kDefineClass), R(7), U8(4),
B(LdaConstant), U8(4),
B(Star), R(7), B(Star), R(7),
B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(7), U8(1), B(Mov), R(8), R(4),
B(LdaConstant), U8(5),
B(Star), R(8),
B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(8), U8(1),
B(StaCurrentContextSlot), U8(5), B(StaCurrentContextSlot), U8(5),
B(PopContext), R(4), B(PopContext), R(5),
B(Mov), R(3), R(0), B(Mov), R(4), R(0),
/* 38 E> */ B(CreateBlockContext), U8(5), /* 38 E> */ B(CreateBlockContext), U8(6),
B(PushContext), R(4), B(PushContext), R(5),
/* 83 E> */ B(CreateClosure), U8(7), U8(2), U8(2), /* 128 E> */ B(CreateClosure), U8(8), U8(3), U8(2),
B(Star), R(5),
B(LdaConstant), U8(6),
B(Star), R(6), B(Star), R(6),
B(CreateClosure), U8(8), U8(3), U8(2), B(LdaConstant), U8(7),
B(Star), R(7),
B(CreateClosure), U8(9), U8(4), U8(2),
B(StaCurrentContextSlot), U8(4), B(StaCurrentContextSlot), U8(4),
B(Mov), R(5), R(7), B(CreateClosure), U8(10), U8(5), U8(2),
B(Mov), R(3), R(8), B(Star), R(10),
B(CallRuntime), U16(Runtime::kDefineClass), R(6), U8(3), B(Mov), R(6), R(8),
B(Star), R(6), B(Mov), R(4), R(9),
B(Mov), R(7), R(2), B(CallRuntime), U16(Runtime::kDefineClass), R(7), U8(4),
B(LdaConstant), U8(9),
B(Star), R(7), B(Star), R(7),
B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(7), U8(1), B(Mov), R(8), R(3),
B(LdaConstant), U8(11),
B(Star), R(8),
B(CallRuntime), U16(Runtime::kCreatePrivateNameSymbol), R(8), U8(1),
B(StaCurrentContextSlot), U8(5), B(StaCurrentContextSlot), U8(5),
B(PopContext), R(4), B(PopContext), R(5),
B(Mov), R(2), R(1), B(Mov), R(3), R(1),
/* 106 S> */ B(Ldar), R(3), /* 221 S> */ B(Ldar), R(1),
/* 106 E> */ B(Construct), R(3), R(0), U8(0), U8(0), /* 221 E> */ B(Construct), R(1), R(0), U8(0), U8(0),
/* 115 S> */ B(Ldar), R(2), B(Star), R(2),
/* 115 E> */ B(Construct), R(2), R(0), U8(0), U8(2), /* 232 S> */ B(LdaNamedProperty), R(2), U8(12), U8(2),
B(Star), R(5),
/* 232 E> */ B(CallProperty0), R(5), R(2), U8(4),
B(LdaUndefined), B(LdaUndefined),
/* 124 S> */ B(Return), /* 242 S> */ B(Return),
] ]
constant pool: [ constant pool: [
SCOPE_INFO_TYPE, SCOPE_INFO_TYPE,
FIXED_ARRAY_TYPE, FIXED_ARRAY_TYPE,
SHARED_FUNCTION_INFO_TYPE, SHARED_FUNCTION_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE, SHARED_FUNCTION_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE,
ONE_BYTE_INTERNALIZED_STRING_TYPE ["D"], ONE_BYTE_INTERNALIZED_STRING_TYPE ["D"],
SCOPE_INFO_TYPE, SCOPE_INFO_TYPE,
FIXED_ARRAY_TYPE, FIXED_ARRAY_TYPE,
SHARED_FUNCTION_INFO_TYPE, SHARED_FUNCTION_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE, SHARED_FUNCTION_INFO_TYPE,
SHARED_FUNCTION_INFO_TYPE,
ONE_BYTE_INTERNALIZED_STRING_TYPE ["E"], ONE_BYTE_INTERNALIZED_STRING_TYPE ["E"],
ONE_BYTE_INTERNALIZED_STRING_TYPE ["callE"],
] ]
handlers: [ handlers: [
] ]

View File

@ -2764,26 +2764,29 @@ TEST(PrivateMethods) {
BytecodeExpectationsPrinter printer(CcTest::isolate()); BytecodeExpectationsPrinter printer(CcTest::isolate());
const char* snippets[] = { const char* snippets[] = {
"{\n" R"({
" class A {\n" class A {
" #a() { return 1; }\n" #a() { return 1; }
" }\n" callA() { return this.#a(); }
"\n" }
" new A;\n"
"}\n",
"{\n" const a = new A;
" class D {\n" a.callA();
" #d() {}\n" })",
" }\n" R"({
"\n" class D {
" class E extends D {\n" #d() { return 1; }
" #e() {}\n" callD() { return this.#d(); }
" }\n" }
"\n"
" new D;\n" class E extends D {
" new E;\n" #e() { return 2; }
"}\n"}; callE() { return this.callD() + this.#e(); }
}
const e = new E;
e.callE();
})"};
CHECK(CompareTexts(BuildActual(printer, snippets), CHECK(CompareTexts(BuildActual(printer, snippets),
LoadGolden("PrivateMethods.golden"))); LoadGolden("PrivateMethods.golden")));
i::FLAG_harmony_private_methods = old_methods_flag; i::FLAG_harmony_private_methods = old_methods_flag;

View File

@ -0,0 +1,13 @@
// Copyright 2019 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --harmony-private-methods
class C {
#a() {}
constructor() {
this.#a = 1;
}
}
new C;

View File

@ -0,0 +1,6 @@
*%(basename)s:10: TypeError: Private method '#a' is not writable
this.#a = 1;
^
TypeError: Private method '#a' is not writable
at new C (*%(basename)s:10:13)
at *%(basename)s:13:1

View File

@ -6,57 +6,211 @@
"use strict"; "use strict";
// Basic private method test
{ {
let calledWith;
class C { class C {
#a() {} #a(arg) { calledWith = arg; }
callA(arg) { this.#a(arg); }
} }
new C;
const c = new C;
assertEquals(undefined, c.a);
assertEquals(undefined, calledWith);
c.callA(1);
assertEquals(1, calledWith);
} }
// Call private method in another instance
{ {
class C { class C {
#a() { #a(arg) { this.calledWith = arg; }
class B { callAIn(obj, arg) { obj.#a(arg); }
#a() { } }
}
new B; const c = new C;
const c2 = new C;
assertEquals(undefined, c.a);
assertEquals(undefined, c.calledWith);
assertEquals(undefined, c2.calledWith);
c2.callAIn(c, 'fromC2');
assertEquals('fromC2', c.calledWith);
assertEquals(undefined, c2.calledWith);
c2.callAIn(c2, 'c2');
assertEquals('fromC2', c.calledWith);
assertEquals('c2', c2.calledWith);
assertThrows(() => { c2.callAIn({}); }, TypeError);
}
// Private methods and private fields
{
class C {
#a;
constructor(a) {
this.#a = a;
}
#getAPlus1() {
return this.#a + 1;
}
equals(obj) {
return this.#getAPlus1() === obj.#getAPlus1();
} }
} }
new C; const c = new C(0);
const c2 = new C(2);
const c3 = new C(2);
assertEquals(true, c2.equals(c3));
assertEquals(false, c2.equals(c));
assertEquals(false, c3.equals(c));
} }
// Class inheritance
{ {
class A { class A {
#a() { #val;
class C extends A { constructor(a) {
#c() { } this.#val = a;
}
new C;
} }
#a() { return this.#val; }
getA() { return this.#a(); }
} }
class B extends A {
new A; constructor(b) {
super(b);
}
b() { return this.getA() }
}
const b = new B(1);
assertEquals(1, b.b());
} }
// Private members should be accessed according to the class the
// invoked method is in.
{ {
const C = class { class A {
#a() { } #val;
constructor(a) {
this.#val = a;
}
#getVal() { return this.#val; }
getA() { return this.#getVal(); }
getVal() { return this.#getVal(); }
} }
new C;
class B extends A {
#val;
constructor(a, b) {
super(a);
this.#val = b;
}
#getVal() { return this.#val; }
getB() { return this.#getVal(); }
getVal() { return this.#getVal(); }
}
const b = new B(1, 2);
assertEquals(1, b.getA());
assertEquals(2, b.getB());
assertEquals(1, A.prototype.getVal.call(b));
assertEquals(2, B.prototype.getVal.call(b));
const a = new A(1);
assertEquals(1, a.getA());
assertThrows(() => B.prototype.getB.call(a), TypeError);
} }
// Private methods in nested classes.
{
class C {
#b() {
class B {
#foo(arg) { return arg; }
callFoo(arg) { return this.#foo(arg); }
}
return new B();
}
createB() { return this.#b(); }
}
const c = new C;
const b = c.createB();
assertEquals(1, b.callFoo(1));
}
// Private methods in nested classes with inheritance.
{
class C {
#b() {
class B extends C {
#foo(arg) { return arg; }
callFoo(arg) { return this.#foo(arg); }
}
return new B();
}
createB() { return this.#b(); }
}
const c = new C;
const b = c.createB();
assertEquals(1, b.callFoo(1));
const b2 = b.createB();
assertEquals(1, b2.callFoo(1));
}
// Class expressions.
{ {
const C = class { const C = class {
#a() { #a() { return 1; }
callA(obj) { return obj.#a() }
};
const c = new C;
const c2 = new C;
assertEquals(1, c.callA(c));
assertEquals(1, c.callA(c2));
}
// Nested class expressions.
{
const C = class {
#b() {
const B = class { const B = class {
#a() { } #foo(arg) { return arg; }
} callFoo(arg) { return this.#foo(arg); }
new B; };
return new B();
} }
} createB() { return this.#b(); }
new C; };
const c = new C;
const b = c.createB();
assertEquals(1, b.callFoo(1));
} }
// Nested class expressions with hierarchy.
{
const C = class {
#b() {
const B = class extends C {
#foo(arg) { return arg; }
callFoo(arg) { return this.#foo(arg); }
}
return new B();
}
createB() { return this.#b(); }
}
const c = new C;
const b = c.createB();
assertEquals(1, b.callFoo(1));
const b2 = b.createB();
assertEquals(1, b2.callFoo(1));
}
// Adding the brand twice on the same object should throw.
{ {
class A { class A {
constructor(arg) { constructor(arg) {
@ -72,11 +226,45 @@
} }
} }
// Add the brand twice on the same object.
let c1 = new C({}); let c1 = new C({});
assertThrows(() => new C(c1), TypeError); assertThrows(() => new C(c1), TypeError);
} }
// Private methods should be not visible to proxies.
{
class X {
#x() {}
x() { this.#x(); };
callX(obj) { obj.#x(); }
}
let handlerCalled = false;
const x = new X();
let p = new Proxy(new X, {
apply(target, thisArg, argumentsList) {
handlerCalled = true;
Reflect.apply(target, thisArg, argumentsList);
}
});
assertThrows(() => p.x(), TypeError);
assertThrows(() => x.callX(p), TypeError);
assertThrows(() => X.prototype.x.call(p), TypeError);
assertThrows(() => X.prototype.callX(p), TypeError);
assertEquals(false, handlerCalled);
}
// Reference outside of class.
{
class C {
#a() {}
}
assertThrows('new C().#a()');
}
// Duplicate private names.
{
assertThrows('class C { #a = 1; #a() {} }');
}
{ {
// TODO(v8:9177): test extending a class expression that does not have // TODO(v8:9177): test extending a class expression that does not have
// a private method. // a private method.