Commit 0753cbea authored by Joyee Cheung's avatar Joyee Cheung Committed by Commit Bot

[class] maintain private brand information on SFI

When an empty class is nested inside a class with private instance
methods, like this:

  class Outer {
    constructor() {}
    #method() {}
    factory() {
      class Inner {
        constructor() {  }
      }
      return Inner;
    }
    run(obj) {
      obj.#method();
    }
  }

The bytecode generator previously generate private brand
initialization for the constructor of Inner by mistake,
because during scope chain serialization/deserialization,
the outer scopes of Inner and factory() are not allocated
or serialized (as they are empty). In the eyes of the bytecode
generator, it then appeared as if Outer is the direct outer
scope of Inner's constructor.

In order to work around this information loss, in this patch
we rely on SharedFunctionInfo instead of the Context/ScopeInfo
chain to maintain the information about private brand initialization.
This is done by shrinking expected_nof_properties to 8 bits and
freeing 8 bits for a second bitfield on the SFI.

Design doc: https://docs.google.com/document/d/14maU596YbHcWR7XR-_iXM_ANhAAmiuRlJZysM61lqaE/edit#
Bug: v8:9839, v8:8330, v8:10098

Change-Id: I4370a0459bfc0da388052ad5a91aac59582d811d
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/2056889
Commit-Queue: Joyee Cheung <joyee@igalia.com>
Reviewed-by: 's avatarLeszek Swirski <leszeks@chromium.org>
Reviewed-by: 's avatarToon Verwaest <verwaest@chromium.org>
Cr-Commit-Position: refs/heads/master@{#66575}
parent e0c03dc3
...@@ -278,17 +278,6 @@ std::unique_ptr<char[]> FunctionLiteral::GetDebugName() const { ...@@ -278,17 +278,6 @@ std::unique_ptr<char[]> FunctionLiteral::GetDebugName() const {
return result; return result;
} }
bool FunctionLiteral::requires_brand_initialization() const {
Scope* outer = scope_->outer_scope();
// If there are no variables declared in the outer scope other than
// the class name variable, the outer class scope may be elided when
// the function is deserialized after preparsing.
if (!outer->is_class_scope()) return false;
return outer->AsClassScope()->brand() != nullptr;
}
bool FunctionLiteral::private_name_lookup_skips_outer_class() const { bool FunctionLiteral::private_name_lookup_skips_outer_class() const {
return scope()->private_name_lookup_skips_outer_class(); return scope()->private_name_lookup_skips_outer_class();
} }
......
...@@ -2218,7 +2218,12 @@ class FunctionLiteral final : public Expression { ...@@ -2218,7 +2218,12 @@ class FunctionLiteral final : public Expression {
return RequiresInstanceMembersInitializer::decode(bit_field_); return RequiresInstanceMembersInitializer::decode(bit_field_);
} }
bool requires_brand_initialization() const; void set_class_scope_has_private_brand(bool value) {
bit_field_ = ClassScopeHasPrivateBrandField::update(bit_field_, value);
}
bool class_scope_has_private_brand() const {
return ClassScopeHasPrivateBrandField::decode(bit_field_);
}
bool private_name_lookup_skips_outer_class() const; bool private_name_lookup_skips_outer_class() const;
...@@ -2270,7 +2275,9 @@ class FunctionLiteral final : public Expression { ...@@ -2270,7 +2275,9 @@ class FunctionLiteral final : public Expression {
HasDuplicateParameters::Next<BailoutReason, 8>; HasDuplicateParameters::Next<BailoutReason, 8>;
using RequiresInstanceMembersInitializer = using RequiresInstanceMembersInitializer =
DontOptimizeReasonField::Next<bool, 1>; DontOptimizeReasonField::Next<bool, 1>;
using HasBracesField = RequiresInstanceMembersInitializer::Next<bool, 1>; using ClassScopeHasPrivateBrandField =
RequiresInstanceMembersInitializer::Next<bool, 1>;
using HasBracesField = ClassScopeHasPrivateBrandField::Next<bool, 1>;
using OneshotIIFEBit = HasBracesField::Next<bool, 1>; using OneshotIIFEBit = HasBracesField::Next<bool, 1>;
// expected_property_count_ is the sum of instance fields and properties. // expected_property_count_ is the sum of instance fields and properties.
......
...@@ -828,6 +828,9 @@ const char* AstPrinter::PrintProgram(FunctionLiteral* program) { ...@@ -828,6 +828,9 @@ const char* AstPrinter::PrintProgram(FunctionLiteral* program) {
if (program->requires_instance_members_initializer()) { if (program->requires_instance_members_initializer()) {
Print(" REQUIRES INSTANCE FIELDS INITIALIZER\n"); Print(" REQUIRES INSTANCE FIELDS INITIALIZER\n");
} }
if (program->class_scope_has_private_brand()) {
Print(" CLASS SCOPE HAS PRIVATE BRAND\n");
}
PrintParameters(program->scope()); PrintParameters(program->scope());
PrintDeclarations(program->scope()->declarations()); PrintDeclarations(program->scope()->declarations());
PrintStatements(program->body()); PrintStatements(program->body());
......
...@@ -511,6 +511,9 @@ void SetSharedFunctionFlagsFromLiteral(FunctionLiteral* literal, ...@@ -511,6 +511,9 @@ void SetSharedFunctionFlagsFromLiteral(FunctionLiteral* literal,
if (literal->dont_optimize_reason() != BailoutReason::kNoReason) { if (literal->dont_optimize_reason() != BailoutReason::kNoReason) {
shared_info.DisableOptimization(literal->dont_optimize_reason()); shared_info.DisableOptimization(literal->dont_optimize_reason());
} }
shared_info.set_class_scope_has_private_brand(
literal->class_scope_has_private_brand());
shared_info.set_is_safe_to_skip_arguments_adaptor( shared_info.set_is_safe_to_skip_arguments_adaptor(
literal->SafeToSkipArgumentsAdaptor()); literal->SafeToSkipArgumentsAdaptor());
} }
......
...@@ -1364,6 +1364,9 @@ void SharedFunctionInfo::SharedFunctionInfoPrint(std::ostream& os) { // NOLINT ...@@ -1364,6 +1364,9 @@ void SharedFunctionInfo::SharedFunctionInfoPrint(std::ostream& os) { // NOLINT
if (HasInferredName()) { if (HasInferredName()) {
os << "\n - inferred name: " << Brief(inferred_name()); os << "\n - inferred name: " << Brief(inferred_name());
} }
if (class_scope_has_private_brand()) {
os << "\n - class_scope_has_private_brand";
}
os << "\n - kind: " << kind(); os << "\n - kind: " << kind();
os << "\n - syntax kind: " << syntax_kind(); os << "\n - syntax kind: " << syntax_kind();
if (needs_home_object()) { if (needs_home_object()) {
......
...@@ -1331,7 +1331,7 @@ void BytecodeGenerator::GenerateBytecodeBody() { ...@@ -1331,7 +1331,7 @@ void BytecodeGenerator::GenerateBytecodeBody() {
// The derived constructor case is handled in VisitCallSuper. // The derived constructor case is handled in VisitCallSuper.
if (IsBaseConstructor(function_kind())) { if (IsBaseConstructor(function_kind())) {
if (literal->requires_brand_initialization()) { if (literal->class_scope_has_private_brand()) {
BuildPrivateBrandInitialization(builder()->Receiver()); BuildPrivateBrandInitialization(builder()->Receiver());
} }
...@@ -4998,7 +4998,7 @@ void BytecodeGenerator::VisitCallSuper(Call* expr) { ...@@ -4998,7 +4998,7 @@ void BytecodeGenerator::VisitCallSuper(Call* expr) {
Register instance = register_allocator()->NewRegister(); Register instance = register_allocator()->NewRegister();
builder()->StoreAccumulatorInRegister(instance); builder()->StoreAccumulatorInRegister(instance);
if (info()->literal()->requires_brand_initialization()) { if (info()->literal()->class_scope_has_private_brand()) {
BuildPrivateBrandInitialization(instance); BuildPrivateBrandInitialization(instance);
} }
......
...@@ -4971,6 +4971,7 @@ void SharedFunctionInfo::Init(ReadOnlyRoots ro_roots, int unique_id) { ...@@ -4971,6 +4971,7 @@ void SharedFunctionInfo::Init(ReadOnlyRoots ro_roots, int unique_id) {
// All flags default to false or 0, except ConstructAsBuiltinBit just because // All flags default to false or 0, except ConstructAsBuiltinBit just because
// we're using the kIllegal builtin. // we're using the kIllegal builtin.
set_flags(ConstructAsBuiltinBit::encode(true)); set_flags(ConstructAsBuiltinBit::encode(true));
set_flags2(0);
UpdateFunctionMapIndex(); UpdateFunctionMapIndex();
...@@ -5365,6 +5366,10 @@ void SharedFunctionInfo::InitFromFunctionLiteral( ...@@ -5365,6 +5366,10 @@ void SharedFunctionInfo::InitFromFunctionLiteral(
IsClassConstructor(lit->kind())); IsClassConstructor(lit->kind()));
shared_info->set_requires_instance_members_initializer( shared_info->set_requires_instance_members_initializer(
lit->requires_instance_members_initializer()); lit->requires_instance_members_initializer());
DCHECK_IMPLIES(lit->class_scope_has_private_brand(),
IsClassConstructor(lit->kind()));
shared_info->set_class_scope_has_private_brand(
lit->class_scope_has_private_brand());
shared_info->set_is_toplevel(is_toplevel); shared_info->set_is_toplevel(is_toplevel);
DCHECK(shared_info->outer_scope_info().IsTheHole()); DCHECK(shared_info->outer_scope_info().IsTheHole());
...@@ -5438,7 +5443,11 @@ uint16_t SharedFunctionInfo::get_property_estimate_from_literal( ...@@ -5438,7 +5443,11 @@ uint16_t SharedFunctionInfo::get_property_estimate_from_literal(
void SharedFunctionInfo::UpdateExpectedNofPropertiesFromEstimate( void SharedFunctionInfo::UpdateExpectedNofPropertiesFromEstimate(
FunctionLiteral* literal) { FunctionLiteral* literal) {
set_expected_nof_properties(get_property_estimate_from_literal(literal)); // Limit actual estimate to fit in a 8 bit field, we will never allocate
// more than this in any case.
STATIC_ASSERT(JSObject::kMaxInObjectProperties <= kMaxUInt8);
int estimate = get_property_estimate_from_literal(literal);
set_expected_nof_properties(std::min(estimate, kMaxUInt8));
} }
void SharedFunctionInfo::UpdateAndFinalizeExpectedNofPropertiesFromEstimate( void SharedFunctionInfo::UpdateAndFinalizeExpectedNofPropertiesFromEstimate(
......
...@@ -110,11 +110,12 @@ INT_ACCESSORS(SharedFunctionInfo, unique_id, kUniqueIdOffset) ...@@ -110,11 +110,12 @@ INT_ACCESSORS(SharedFunctionInfo, unique_id, kUniqueIdOffset)
UINT16_ACCESSORS(SharedFunctionInfo, length, kLengthOffset) UINT16_ACCESSORS(SharedFunctionInfo, length, kLengthOffset)
UINT16_ACCESSORS(SharedFunctionInfo, internal_formal_parameter_count, UINT16_ACCESSORS(SharedFunctionInfo, internal_formal_parameter_count,
kFormalParameterCountOffset) kFormalParameterCountOffset)
UINT16_ACCESSORS(SharedFunctionInfo, expected_nof_properties, UINT8_ACCESSORS(SharedFunctionInfo, expected_nof_properties,
kExpectedNofPropertiesOffset) kExpectedNofPropertiesOffset)
UINT16_ACCESSORS(SharedFunctionInfo, raw_function_token_offset, UINT16_ACCESSORS(SharedFunctionInfo, raw_function_token_offset,
kFunctionTokenOffsetOffset) kFunctionTokenOffsetOffset)
RELAXED_INT32_ACCESSORS(SharedFunctionInfo, flags, kFlagsOffset) RELAXED_INT32_ACCESSORS(SharedFunctionInfo, flags, kFlagsOffset)
UINT8_ACCESSORS(SharedFunctionInfo, flags2, kFlags2Offset)
bool SharedFunctionInfo::HasSharedName() const { bool SharedFunctionInfo::HasSharedName() const {
Object value = name_or_scope_info(); Object value = name_or_scope_info();
...@@ -184,6 +185,9 @@ int SharedFunctionInfo::function_token_position() const { ...@@ -184,6 +185,9 @@ int SharedFunctionInfo::function_token_position() const {
} }
} }
BIT_FIELD_ACCESSORS(SharedFunctionInfo, flags2, class_scope_has_private_brand,
SharedFunctionInfo::ClassScopeHasPrivateBrandBit)
BIT_FIELD_ACCESSORS(SharedFunctionInfo, flags, syntax_kind, BIT_FIELD_ACCESSORS(SharedFunctionInfo, flags, syntax_kind,
SharedFunctionInfo::FunctionSyntaxKindBits) SharedFunctionInfo::FunctionSyntaxKindBits)
......
...@@ -172,6 +172,7 @@ class SharedFunctionInfo : public HeapObject { ...@@ -172,6 +172,7 @@ class SharedFunctionInfo : public HeapObject {
public: public:
NEVER_READ_ONLY_SPACE NEVER_READ_ONLY_SPACE
DEFINE_TORQUE_GENERATED_SHARED_FUNCTION_INFO_FLAGS() DEFINE_TORQUE_GENERATED_SHARED_FUNCTION_INFO_FLAGS()
DEFINE_TORQUE_GENERATED_SHARED_FUNCTION_INFO_FLAGS2()
// This initializes the SharedFunctionInfo after allocation. It must // This initializes the SharedFunctionInfo after allocation. It must
// initialize all fields, and leave the SharedFunctionInfo in a state where // initialize all fields, and leave the SharedFunctionInfo in a state where
...@@ -279,7 +280,7 @@ class SharedFunctionInfo : public HeapObject { ...@@ -279,7 +280,7 @@ class SharedFunctionInfo : public HeapObject {
// [expected_nof_properties]: Expected number of properties for the // [expected_nof_properties]: Expected number of properties for the
// function. The value is only reliable when the function has been compiled. // function. The value is only reliable when the function has been compiled.
DECL_UINT16_ACCESSORS(expected_nof_properties) DECL_UINT8_ACCESSORS(expected_nof_properties)
// [function_literal_id] - uniquely identifies the FunctionLiteral this // [function_literal_id] - uniquely identifies the FunctionLiteral this
// SharedFunctionInfo represents within its script, or -1 if this // SharedFunctionInfo represents within its script, or -1 if this
...@@ -402,6 +403,11 @@ class SharedFunctionInfo : public HeapObject { ...@@ -402,6 +403,11 @@ class SharedFunctionInfo : public HeapObject {
// [flags] Bit field containing various flags about the function. // [flags] Bit field containing various flags about the function.
DECL_INT32_ACCESSORS(flags) DECL_INT32_ACCESSORS(flags)
DECL_UINT8_ACCESSORS(flags2)
// True if the outer class scope contains a private brand for
// private instance methdos.
DECL_BOOLEAN_ACCESSORS(class_scope_has_private_brand)
// Is this function a top-level function (scripts, evals). // Is this function a top-level function (scripts, evals).
DECL_BOOLEAN_ACCESSORS(is_toplevel) DECL_BOOLEAN_ACCESSORS(is_toplevel)
......
...@@ -42,6 +42,10 @@ bitfield struct SharedFunctionInfoFlags extends uint32 { ...@@ -42,6 +42,10 @@ bitfield struct SharedFunctionInfoFlags extends uint32 {
private_name_lookup_skips_outer_class: bool: 1 bit; private_name_lookup_skips_outer_class: bool: 1 bit;
} }
bitfield struct SharedFunctionInfoFlags2 extends uint8 {
class_scope_has_private_brand: bool: 1 bit;
}
extern class SharedFunctionInfo extends HeapObject { extern class SharedFunctionInfo extends HeapObject {
weak function_data: Object; weak function_data: Object;
name_or_scope_info: String|NoSharedNameSentinel|ScopeInfo; name_or_scope_info: String|NoSharedNameSentinel|ScopeInfo;
...@@ -49,9 +53,9 @@ extern class SharedFunctionInfo extends HeapObject { ...@@ -49,9 +53,9 @@ extern class SharedFunctionInfo extends HeapObject {
script_or_debug_info: Script|DebugInfo|Undefined; script_or_debug_info: Script|DebugInfo|Undefined;
length: int16; length: int16;
formal_parameter_count: uint16; formal_parameter_count: uint16;
// Currently set to uint16, can be set to uint8 to save space.
expected_nof_properties: uint16;
function_token_offset: int16; function_token_offset: int16;
expected_nof_properties: uint8;
flags2: SharedFunctionInfoFlags2;
flags: SharedFunctionInfoFlags; flags: SharedFunctionInfoFlags;
function_literal_id: int32; function_literal_id: int32;
@if(V8_SFI_HAS_UNIQUE_ID) unique_id: int32; @if(V8_SFI_HAS_UNIQUE_ID) unique_id: int32;
......
...@@ -81,6 +81,7 @@ void ParseInfo::SetFunctionInfo(T function) { ...@@ -81,6 +81,7 @@ void ParseInfo::SetFunctionInfo(T function) {
set_function_syntax_kind(function->syntax_kind()); set_function_syntax_kind(function->syntax_kind());
set_requires_instance_members_initializer( set_requires_instance_members_initializer(
function->requires_instance_members_initializer()); function->requires_instance_members_initializer());
set_class_scope_has_private_brand(function->class_scope_has_private_brand());
set_toplevel(function->is_toplevel()); set_toplevel(function->is_toplevel());
set_is_oneshot_iife(function->is_oneshot_iife()); set_is_oneshot_iife(function->is_oneshot_iife());
} }
......
...@@ -93,6 +93,8 @@ class V8_EXPORT_PRIVATE ParseInfo { ...@@ -93,6 +93,8 @@ class V8_EXPORT_PRIVATE ParseInfo {
FLAG_ACCESSOR(kRequiresInstanceMembersInitializer, FLAG_ACCESSOR(kRequiresInstanceMembersInitializer,
requires_instance_members_initializer, requires_instance_members_initializer,
set_requires_instance_members_initializer) set_requires_instance_members_initializer)
FLAG_ACCESSOR(kClassScopeHasPrivateBrand, class_scope_has_private_brand,
set_class_scope_has_private_brand)
FLAG_ACCESSOR(kMightAlwaysOpt, might_always_opt, set_might_always_opt) FLAG_ACCESSOR(kMightAlwaysOpt, might_always_opt, set_might_always_opt)
FLAG_ACCESSOR(kAllowNativeSyntax, allow_natives_syntax, FLAG_ACCESSOR(kAllowNativeSyntax, allow_natives_syntax,
set_allow_natives_syntax) set_allow_natives_syntax)
...@@ -333,6 +335,7 @@ class V8_EXPORT_PRIVATE ParseInfo { ...@@ -333,6 +335,7 @@ class V8_EXPORT_PRIVATE ParseInfo {
kAllowHarmonyNullish = 1u << 28, kAllowHarmonyNullish = 1u << 28,
kAllowHarmonyTopLevelAwait = 1u << 29, kAllowHarmonyTopLevelAwait = 1u << 29,
kREPLMode = 1u << 30, kREPLMode = 1u << 30,
kClassScopeHasPrivateBrand = 1u << 31,
}; };
//------------- Inputs to parsing and scope analysis ----------------------- //------------- Inputs to parsing and scope analysis -----------------------
......
...@@ -961,6 +961,8 @@ FunctionLiteral* Parser::DoParseFunction(Isolate* isolate, ParseInfo* info, ...@@ -961,6 +961,8 @@ FunctionLiteral* Parser::DoParseFunction(Isolate* isolate, ParseInfo* info,
if (has_error()) return nullptr; if (has_error()) return nullptr;
result->set_requires_instance_members_initializer( result->set_requires_instance_members_initializer(
info->requires_instance_members_initializer()); info->requires_instance_members_initializer());
result->set_class_scope_has_private_brand(
info->class_scope_has_private_brand());
if (info->is_oneshot_iife()) { if (info->is_oneshot_iife()) {
result->mark_as_oneshot_iife(); result->mark_as_oneshot_iife();
} }
...@@ -2972,6 +2974,10 @@ Expression* Parser::RewriteClassLiteral(ClassScope* block_scope, ...@@ -2972,6 +2974,10 @@ Expression* Parser::RewriteClassLiteral(ClassScope* block_scope,
class_info->instance_fields->length()); class_info->instance_fields->length());
} }
if (class_info->requires_brand) {
class_info->constructor->set_class_scope_has_private_brand(true);
}
ClassLiteral* class_literal = factory()->NewClassLiteral( ClassLiteral* class_literal = factory()->NewClassLiteral(
block_scope, class_info->extends, class_info->constructor, block_scope, class_info->extends, class_info->constructor,
class_info->public_members, class_info->private_members, class_info->public_members, class_info->private_members,
......
Test empty inner classes with private instance methods in the outer class
Running test: testScopesPaused
undefined
// Copyright 2020 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
let {session, contextGroup, Protocol} = InspectorTest.start(
'Test empty inner classes with private instance methods in the outer class');
contextGroup.addScript(`
function run() {
class Outer {
#method() {}
factory() {
return class Inner {
fn() {
debugger;
}
};
}
};
const a = new Outer();
const Inner = a.factory();
(new Inner).fn();
}`);
InspectorTest.runAsyncTestSuite([async function testScopesPaused() {
Protocol.Debugger.enable();
Protocol.Runtime.evaluate({expression: 'run()'});
let {params: {callFrames}} =
await Protocol.Debugger.oncePaused(); // inside fn()
let frame = callFrames[0];
let {result} =
await Protocol.Runtime.getProperties({objectId: frame.this.objectId});
InspectorTest.logObject(result.privateProperties);
Protocol.Debugger.resume();
Protocol.Debugger.disable();
}]);
// Copyright 2020 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
// This tests that empty inner classes don't assign private brands of outer
// classes in their instances after scope chain deserialization.
'use strict';
class Outer {
constructor() {}
#method(){}
factory() {
class Inner {
constructor() {}
}
return Inner;
}
run(obj) {
obj.#method();
}
}
const instance = new Outer();
const Inner = instance.factory();
// It should not pass the brand check.
assertThrows(() => instance.run(new Inner()), TypeError);
// It should pass the brand check.
instance.run(new Outer());
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment