[symbol-as-weakmap-key] Implement Symbol as WeakMap Keys

Allow non-registered symbols as keys in weakmap and weakset.
Allow non-registered symbols as target and unregisterToken in
WeakRef and FinalizationRegistry.

Bug: v8:12947
Change-Id: Ieb63bda66e3cc378879ac651e23300b71caed627
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/3865056
Reviewed-by: Dominik Inführ <dinfuehr@chromium.org>
Commit-Queue: Marja Hölttä <marja@chromium.org>
Reviewed-by: Jakob Linke <jgruber@chromium.org>
Cr-Commit-Position: refs/heads/main@{#83313}
This commit is contained in:
jameslahm 2022-09-19 21:04:35 +08:00 committed by V8 LUCI CQ
parent 4ad770242b
commit c400af48b5
26 changed files with 533 additions and 260 deletions

View File

@ -439,9 +439,9 @@ extern enum MessageTemplate {
kWasmTrapStringOffsetOutOfBounds,
kWasmObjectsAreOpaque,
kWeakRefsRegisterTargetAndHoldingsMustNotBeSame,
kWeakRefsRegisterTargetMustBeObject,
kWeakRefsUnregisterTokenMustBeObject,
kWeakRefsWeakRefConstructorTargetMustBeObject,
kInvalidWeakRefsRegisterTarget,
kInvalidWeakRefsUnregisterToken,
kInvalidWeakRefsWeakRefConstructorTarget,
...
}
@ -918,10 +918,10 @@ macro Float64IsNaN(n: float64): bool {
// The type of all tagged values that can safely be compared with TaggedEqual.
@if(V8_ENABLE_WEBASSEMBLY)
type TaggedWithIdentity = JSReceiver | FixedArrayBase | Oddball | Map |
WeakCell | Context | EmptyString | WasmInternalFunction;
WeakCell | Context | EmptyString | Symbol | WasmInternalFunction;
@ifnot(V8_ENABLE_WEBASSEMBLY)
type TaggedWithIdentity = JSReceiver | FixedArrayBase | Oddball | Map |
WeakCell | Context | EmptyString;
WeakCell | Context | EmptyString | Symbol;
extern operator '==' macro TaggedEqual(TaggedWithIdentity, Object): bool;
extern operator '==' macro TaggedEqual(Object, TaggedWithIdentity): bool;

View File

@ -22,130 +22,6 @@ namespace internal {
template <class T>
using TVariable = compiler::TypedCodeAssemblerVariable<T>;
class BaseCollectionsAssembler : public CodeStubAssembler {
public:
explicit BaseCollectionsAssembler(compiler::CodeAssemblerState* state)
: CodeStubAssembler(state) {}
virtual ~BaseCollectionsAssembler() = default;
protected:
enum Variant { kMap, kSet, kWeakMap, kWeakSet };
// Adds an entry to a collection. For Maps, properly handles extracting the
// key and value from the entry (see LoadKeyValue()).
void AddConstructorEntry(Variant variant, TNode<Context> context,
TNode<Object> collection, TNode<Object> add_function,
TNode<Object> key_value,
Label* if_may_have_side_effects = nullptr,
Label* if_exception = nullptr,
TVariable<Object>* var_exception = nullptr);
// Adds constructor entries to a collection. Choosing a fast path when
// possible.
void AddConstructorEntries(Variant variant, TNode<Context> context,
TNode<Context> native_context,
TNode<HeapObject> collection,
TNode<Object> initial_entries);
// Fast path for adding constructor entries. Assumes the entries are a fast
// JS array (see CodeStubAssembler::BranchIfFastJSArray()).
void AddConstructorEntriesFromFastJSArray(Variant variant,
TNode<Context> context,
TNode<Context> native_context,
TNode<Object> collection,
TNode<JSArray> fast_jsarray,
Label* if_may_have_side_effects);
// Adds constructor entries to a collection using the iterator protocol.
void AddConstructorEntriesFromIterable(Variant variant,
TNode<Context> context,
TNode<Context> native_context,
TNode<Object> collection,
TNode<Object> iterable);
// Constructs a collection instance. Choosing a fast path when possible.
TNode<JSObject> AllocateJSCollection(TNode<Context> context,
TNode<JSFunction> constructor,
TNode<JSReceiver> new_target);
// Fast path for constructing a collection instance if the constructor
// function has not been modified.
TNode<JSObject> AllocateJSCollectionFast(TNode<JSFunction> constructor);
// Fallback for constructing a collection instance if the constructor function
// has been modified.
TNode<JSObject> AllocateJSCollectionSlow(TNode<Context> context,
TNode<JSFunction> constructor,
TNode<JSReceiver> new_target);
// Allocates the backing store for a collection.
virtual TNode<HeapObject> AllocateTable(
Variant variant, TNode<IntPtrT> at_least_space_for) = 0;
// Main entry point for a collection constructor builtin.
void GenerateConstructor(Variant variant,
Handle<String> constructor_function_name,
TNode<Object> new_target, TNode<IntPtrT> argc,
TNode<Context> context);
// Retrieves the collection function that adds an entry. `set` for Maps and
// `add` for Sets.
TNode<Object> GetAddFunction(Variant variant, TNode<Context> context,
TNode<Object> collection);
// Retrieves the collection constructor function.
TNode<JSFunction> GetConstructor(Variant variant,
TNode<Context> native_context);
// Retrieves the initial collection function that adds an entry. Should only
// be called when it is certain that a collection prototype's map hasn't been
// changed.
TNode<JSFunction> GetInitialAddFunction(Variant variant,
TNode<Context> native_context);
// Checks whether {collection}'s initial add/set function has been modified
// (depending on {variant}, loaded from {native_context}).
void GotoIfInitialAddFunctionModified(Variant variant,
TNode<NativeContext> native_context,
TNode<HeapObject> collection,
Label* if_modified);
// Gets root index for the name of the add/set function.
RootIndex GetAddFunctionNameIndex(Variant variant);
// Retrieves the offset to access the backing table from the collection.
int GetTableOffset(Variant variant);
// Estimates the number of entries the collection will have after adding the
// entries passed in the constructor. AllocateTable() can use this to avoid
// the time of growing/rehashing when adding the constructor entries.
TNode<IntPtrT> EstimatedInitialSize(TNode<Object> initial_entries,
TNode<BoolT> is_fast_jsarray);
void GotoIfCannotBeWeakKey(const TNode<Object> obj,
Label* if_cannot_be_weak_key);
// Determines whether the collection's prototype has been modified.
TNode<BoolT> HasInitialCollectionPrototype(Variant variant,
TNode<Context> native_context,
TNode<Object> collection);
// Gets the initial prototype map for given collection {variant}.
TNode<Map> GetInitialCollectionPrototype(Variant variant,
TNode<Context> native_context);
// Loads an element from a fixed array. If the element is the hole, returns
// `undefined`.
TNode<Object> LoadAndNormalizeFixedArrayElement(TNode<FixedArray> elements,
TNode<IntPtrT> index);
// Loads an element from a fixed double array. If the element is the hole,
// returns `undefined`.
TNode<Object> LoadAndNormalizeFixedDoubleArrayElement(
TNode<HeapObject> elements, TNode<IntPtrT> index);
};
void BaseCollectionsAssembler::AddConstructorEntry(
Variant variant, TNode<Context> context, TNode<Object> collection,
TNode<Object> add_function, TNode<Object> key_value,
@ -523,16 +399,28 @@ TNode<IntPtrT> BaseCollectionsAssembler::EstimatedInitialSize(
[=] { return IntPtrConstant(0); });
}
// https://tc39.es/proposal-symbols-as-weakmap-keys/#sec-canbeheldweakly-abstract-operation
void BaseCollectionsAssembler::GotoIfCannotBeWeakKey(
const TNode<Object> obj, Label* if_cannot_be_weak_key) {
Label check_symbol_key(this);
Label end(this);
GotoIf(TaggedIsSmi(obj), if_cannot_be_weak_key);
TNode<Uint16T> instance_type = LoadMapInstanceType(LoadMap(CAST(obj)));
GotoIfNot(IsJSReceiverInstanceType(instance_type), if_cannot_be_weak_key);
GotoIfNot(IsJSReceiverInstanceType(instance_type), &check_symbol_key);
// TODO(v8:12547) Shared structs and arrays should only be able to point
// to shared values in weak collections. For now, disallow them as weak
// collection keys.
GotoIf(IsJSSharedStructInstanceType(instance_type), if_cannot_be_weak_key);
GotoIf(IsJSSharedArrayInstanceType(instance_type), if_cannot_be_weak_key);
Goto(&end);
Bind(&check_symbol_key);
GotoIfNot(HasHarmonySymbolAsWeakmapKeyFlag(), if_cannot_be_weak_key);
GotoIfNot(IsSymbolInstanceType(instance_type), if_cannot_be_weak_key);
TNode<Uint32T> flags = LoadSymbolFlags(CAST(obj));
GotoIf(Word32And(flags, Symbol::IsInPublicSymbolTableBit::kMask),
if_cannot_be_weak_key);
Goto(&end);
Bind(&end);
}
TNode<Map> BaseCollectionsAssembler::GetInitialCollectionPrototype(
@ -2414,67 +2302,6 @@ TF_BUILTIN(FindOrderedHashSetEntry, CollectionsBuiltinsAssembler) {
Return(SmiConstant(-1));
}
class WeakCollectionsBuiltinsAssembler : public BaseCollectionsAssembler {
public:
explicit WeakCollectionsBuiltinsAssembler(compiler::CodeAssemblerState* state)
: BaseCollectionsAssembler(state) {}
protected:
void AddEntry(TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
TNode<Object> key, TNode<Object> value,
TNode<IntPtrT> number_of_elements);
TNode<HeapObject> AllocateTable(Variant variant,
TNode<IntPtrT> at_least_space_for) override;
// Generates and sets the identity for a JSRececiver.
TNode<Smi> CreateIdentityHash(TNode<Object> receiver);
TNode<IntPtrT> EntryMask(TNode<IntPtrT> capacity);
// Builds code that finds the EphemeronHashTable entry for a {key} using the
// comparison code generated by {key_compare}. The key index is returned if
// the {key} is found.
using KeyComparator =
std::function<void(TNode<Object> entry_key, Label* if_same)>;
TNode<IntPtrT> FindKeyIndex(TNode<HeapObject> table, TNode<IntPtrT> key_hash,
TNode<IntPtrT> entry_mask,
const KeyComparator& key_compare);
// Builds code that finds an EphemeronHashTable entry available for a new
// entry.
TNode<IntPtrT> FindKeyIndexForInsertion(TNode<HeapObject> table,
TNode<IntPtrT> key_hash,
TNode<IntPtrT> entry_mask);
// Builds code that finds the EphemeronHashTable entry with key that matches
// {key} and returns the entry's key index. If {key} cannot be found, jumps to
// {if_not_found}.
TNode<IntPtrT> FindKeyIndexForKey(TNode<HeapObject> table, TNode<Object> key,
TNode<IntPtrT> hash,
TNode<IntPtrT> entry_mask,
Label* if_not_found);
TNode<Word32T> InsufficientCapacityToAdd(TNode<IntPtrT> capacity,
TNode<IntPtrT> number_of_elements,
TNode<IntPtrT> number_of_deleted);
TNode<IntPtrT> KeyIndexFromEntry(TNode<IntPtrT> entry);
TNode<IntPtrT> LoadNumberOfElements(TNode<EphemeronHashTable> table,
int offset);
TNode<IntPtrT> LoadNumberOfDeleted(TNode<EphemeronHashTable> table,
int offset = 0);
TNode<EphemeronHashTable> LoadTable(TNode<JSWeakCollection> collection);
TNode<IntPtrT> LoadTableCapacity(TNode<EphemeronHashTable> table);
void RemoveEntry(TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
TNode<IntPtrT> number_of_elements);
TNode<BoolT> ShouldRehash(TNode<IntPtrT> number_of_elements,
TNode<IntPtrT> number_of_deleted);
TNode<Word32T> ShouldShrink(TNode<IntPtrT> capacity,
TNode<IntPtrT> number_of_elements);
TNode<IntPtrT> ValueIndexFromKeyIndex(TNode<IntPtrT> key_index);
};
void WeakCollectionsBuiltinsAssembler::AddEntry(
TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
TNode<Object> key, TNode<Object> value, TNode<IntPtrT> number_of_elements) {
@ -2490,6 +2317,25 @@ void WeakCollectionsBuiltinsAssembler::AddEntry(
SmiFromIntPtr(number_of_elements));
}
TNode<IntPtrT> WeakCollectionsBuiltinsAssembler::GetHash(
const TNode<HeapObject> key, Label* if_no_hash) {
TVARIABLE(IntPtrT, var_hash);
Label if_symbol(this);
Label return_result(this);
GotoIfNot(IsJSReceiver(key), &if_symbol);
var_hash = LoadJSReceiverIdentityHash(CAST(key), if_no_hash);
Goto(&return_result);
Bind(&if_symbol);
CSA_DCHECK(this, IsSymbol(key));
CSA_DCHECK(this, Word32BinaryNot(
Word32And(LoadSymbolFlags(CAST(key)),
Symbol::IsInPublicSymbolTableBit::kMask)));
var_hash = ChangeInt32ToIntPtr(LoadNameHash(CAST(key), nullptr));
Goto(&return_result);
Bind(&return_result);
return var_hash.value();
}
TNode<HeapObject> WeakCollectionsBuiltinsAssembler::AllocateTable(
Variant variant, TNode<IntPtrT> at_least_space_for) {
// See HashTable::New().
@ -2719,8 +2565,7 @@ TF_BUILTIN(WeakMapLookupHashIndex, WeakCollectionsBuiltinsAssembler) {
GotoIfCannotBeWeakKey(key, &if_cannot_be_weak_key);
TNode<IntPtrT> hash =
LoadJSReceiverIdentityHash(CAST(key), &if_cannot_be_weak_key);
TNode<IntPtrT> hash = GetHash(CAST(key), &if_cannot_be_weak_key);
TNode<IntPtrT> capacity = LoadTableCapacity(table);
TNode<IntPtrT> key_index = FindKeyIndexForKey(
table, key, hash, EntryMask(capacity), &if_cannot_be_weak_key);
@ -2785,8 +2630,7 @@ TF_BUILTIN(WeakCollectionDelete, WeakCollectionsBuiltinsAssembler) {
GotoIfCannotBeWeakKey(key, &if_cannot_be_weak_key);
TNode<IntPtrT> hash =
LoadJSReceiverIdentityHash(CAST(key), &if_cannot_be_weak_key);
TNode<IntPtrT> hash = GetHash(CAST(key), &if_cannot_be_weak_key);
TNode<EphemeronHashTable> table = LoadTable(collection);
TNode<IntPtrT> capacity = LoadTableCapacity(table);
TNode<IntPtrT> key_index = FindKeyIndexForKey(
@ -2810,10 +2654,10 @@ TF_BUILTIN(WeakCollectionDelete, WeakCollectionsBuiltinsAssembler) {
TF_BUILTIN(WeakCollectionSet, WeakCollectionsBuiltinsAssembler) {
auto context = Parameter<Context>(Descriptor::kContext);
auto collection = Parameter<JSWeakCollection>(Descriptor::kCollection);
auto key = Parameter<JSReceiver>(Descriptor::kKey);
auto key = Parameter<HeapObject>(Descriptor::kKey);
auto value = Parameter<Object>(Descriptor::kValue);
CSA_DCHECK(this, IsJSReceiver(key));
CSA_DCHECK(this, Word32Or(IsJSReceiver(key), IsSymbol(key)));
Label call_runtime(this), if_no_hash(this), if_not_found(this);
@ -2821,7 +2665,7 @@ TF_BUILTIN(WeakCollectionSet, WeakCollectionsBuiltinsAssembler) {
TNode<IntPtrT> capacity = LoadTableCapacity(table);
TNode<IntPtrT> entry_mask = EntryMask(capacity);
TVARIABLE(IntPtrT, var_hash, LoadJSReceiverIdentityHash(key, &if_no_hash));
TVARIABLE(IntPtrT, var_hash, GetHash(key, &if_no_hash));
TNode<IntPtrT> key_index = FindKeyIndexForKey(table, key, var_hash.value(),
entry_mask, &if_not_found);
@ -2830,6 +2674,7 @@ TF_BUILTIN(WeakCollectionSet, WeakCollectionsBuiltinsAssembler) {
BIND(&if_no_hash);
{
CSA_DCHECK(this, IsJSReceiver(key));
var_hash = SmiUntag(CreateIdentityHash(key));
Goto(&if_not_found);
}

View File

@ -20,6 +20,192 @@ void BranchIfIterableWithOriginalValueSetIterator(
TNode<Context> context, compiler::CodeAssemblerLabel* if_true,
compiler::CodeAssemblerLabel* if_false);
class BaseCollectionsAssembler : public CodeStubAssembler {
public:
explicit BaseCollectionsAssembler(compiler::CodeAssemblerState* state)
: CodeStubAssembler(state) {}
virtual ~BaseCollectionsAssembler() = default;
void GotoIfCannotBeWeakKey(const TNode<Object> obj,
Label* if_cannot_be_weak_key);
protected:
enum Variant { kMap, kSet, kWeakMap, kWeakSet };
// Adds an entry to a collection. For Maps, properly handles extracting the
// key and value from the entry (see LoadKeyValue()).
void AddConstructorEntry(Variant variant, TNode<Context> context,
TNode<Object> collection, TNode<Object> add_function,
TNode<Object> key_value,
Label* if_may_have_side_effects = nullptr,
Label* if_exception = nullptr,
TVariable<Object>* var_exception = nullptr);
// Adds constructor entries to a collection. Choosing a fast path when
// possible.
void AddConstructorEntries(Variant variant, TNode<Context> context,
TNode<Context> native_context,
TNode<HeapObject> collection,
TNode<Object> initial_entries);
// Fast path for adding constructor entries. Assumes the entries are a fast
// JS array (see CodeStubAssembler::BranchIfFastJSArray()).
void AddConstructorEntriesFromFastJSArray(Variant variant,
TNode<Context> context,
TNode<Context> native_context,
TNode<Object> collection,
TNode<JSArray> fast_jsarray,
Label* if_may_have_side_effects);
// Adds constructor entries to a collection using the iterator protocol.
void AddConstructorEntriesFromIterable(Variant variant,
TNode<Context> context,
TNode<Context> native_context,
TNode<Object> collection,
TNode<Object> iterable);
// Constructs a collection instance. Choosing a fast path when possible.
TNode<JSObject> AllocateJSCollection(TNode<Context> context,
TNode<JSFunction> constructor,
TNode<JSReceiver> new_target);
// Fast path for constructing a collection instance if the constructor
// function has not been modified.
TNode<JSObject> AllocateJSCollectionFast(TNode<JSFunction> constructor);
// Fallback for constructing a collection instance if the constructor function
// has been modified.
TNode<JSObject> AllocateJSCollectionSlow(TNode<Context> context,
TNode<JSFunction> constructor,
TNode<JSReceiver> new_target);
// Allocates the backing store for a collection.
virtual TNode<HeapObject> AllocateTable(
Variant variant, TNode<IntPtrT> at_least_space_for) = 0;
// Main entry point for a collection constructor builtin.
void GenerateConstructor(Variant variant,
Handle<String> constructor_function_name,
TNode<Object> new_target, TNode<IntPtrT> argc,
TNode<Context> context);
// Retrieves the collection function that adds an entry. `set` for Maps and
// `add` for Sets.
TNode<Object> GetAddFunction(Variant variant, TNode<Context> context,
TNode<Object> collection);
// Retrieves the collection constructor function.
TNode<JSFunction> GetConstructor(Variant variant,
TNode<Context> native_context);
// Retrieves the initial collection function that adds an entry. Should only
// be called when it is certain that a collection prototype's map hasn't been
// changed.
TNode<JSFunction> GetInitialAddFunction(Variant variant,
TNode<Context> native_context);
// Checks whether {collection}'s initial add/set function has been modified
// (depending on {variant}, loaded from {native_context}).
void GotoIfInitialAddFunctionModified(Variant variant,
TNode<NativeContext> native_context,
TNode<HeapObject> collection,
Label* if_modified);
// Gets root index for the name of the add/set function.
RootIndex GetAddFunctionNameIndex(Variant variant);
// Retrieves the offset to access the backing table from the collection.
int GetTableOffset(Variant variant);
// Estimates the number of entries the collection will have after adding the
// entries passed in the constructor. AllocateTable() can use this to avoid
// the time of growing/rehashing when adding the constructor entries.
TNode<IntPtrT> EstimatedInitialSize(TNode<Object> initial_entries,
TNode<BoolT> is_fast_jsarray);
// Determines whether the collection's prototype has been modified.
TNode<BoolT> HasInitialCollectionPrototype(Variant variant,
TNode<Context> native_context,
TNode<Object> collection);
// Gets the initial prototype map for given collection {variant}.
TNode<Map> GetInitialCollectionPrototype(Variant variant,
TNode<Context> native_context);
// Loads an element from a fixed array. If the element is the hole, returns
// `undefined`.
TNode<Object> LoadAndNormalizeFixedArrayElement(TNode<FixedArray> elements,
TNode<IntPtrT> index);
// Loads an element from a fixed double array. If the element is the hole,
// returns `undefined`.
TNode<Object> LoadAndNormalizeFixedDoubleArrayElement(
TNode<HeapObject> elements, TNode<IntPtrT> index);
};
class WeakCollectionsBuiltinsAssembler : public BaseCollectionsAssembler {
public:
explicit WeakCollectionsBuiltinsAssembler(compiler::CodeAssemblerState* state)
: BaseCollectionsAssembler(state) {}
protected:
void AddEntry(TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
TNode<Object> key, TNode<Object> value,
TNode<IntPtrT> number_of_elements);
TNode<HeapObject> AllocateTable(Variant variant,
TNode<IntPtrT> at_least_space_for) override;
TNode<IntPtrT> GetHash(const TNode<HeapObject> key, Label* if_no_hash);
// Generates and sets the identity for a JSRececiver.
TNode<Smi> CreateIdentityHash(TNode<Object> receiver);
TNode<IntPtrT> EntryMask(TNode<IntPtrT> capacity);
// Builds code that finds the EphemeronHashTable entry for a {key} using the
// comparison code generated by {key_compare}. The key index is returned if
// the {key} is found.
using KeyComparator =
std::function<void(TNode<Object> entry_key, Label* if_same)>;
TNode<IntPtrT> FindKeyIndex(TNode<HeapObject> table, TNode<IntPtrT> key_hash,
TNode<IntPtrT> entry_mask,
const KeyComparator& key_compare);
// Builds code that finds an EphemeronHashTable entry available for a new
// entry.
TNode<IntPtrT> FindKeyIndexForInsertion(TNode<HeapObject> table,
TNode<IntPtrT> key_hash,
TNode<IntPtrT> entry_mask);
// Builds code that finds the EphemeronHashTable entry with key that matches
// {key} and returns the entry's key index. If {key} cannot be found, jumps to
// {if_not_found}.
TNode<IntPtrT> FindKeyIndexForKey(TNode<HeapObject> table, TNode<Object> key,
TNode<IntPtrT> hash,
TNode<IntPtrT> entry_mask,
Label* if_not_found);
TNode<Word32T> InsufficientCapacityToAdd(TNode<IntPtrT> capacity,
TNode<IntPtrT> number_of_elements,
TNode<IntPtrT> number_of_deleted);
TNode<IntPtrT> KeyIndexFromEntry(TNode<IntPtrT> entry);
TNode<IntPtrT> LoadNumberOfElements(TNode<EphemeronHashTable> table,
int offset);
TNode<IntPtrT> LoadNumberOfDeleted(TNode<EphemeronHashTable> table,
int offset = 0);
TNode<EphemeronHashTable> LoadTable(TNode<JSWeakCollection> collection);
TNode<IntPtrT> LoadTableCapacity(TNode<EphemeronHashTable> table);
void RemoveEntry(TNode<EphemeronHashTable> table, TNode<IntPtrT> key_index,
TNode<IntPtrT> number_of_elements);
TNode<BoolT> ShouldRehash(TNode<IntPtrT> number_of_elements,
TNode<IntPtrT> number_of_deleted);
TNode<Word32T> ShouldShrink(TNode<IntPtrT> capacity,
TNode<IntPtrT> number_of_elements);
TNode<IntPtrT> ValueIndexFromKeyIndex(TNode<IntPtrT> key_index);
};
} // namespace internal
} // namespace v8

View File

@ -9,6 +9,7 @@
namespace v8 {
namespace internal {
// https://tc39.es/proposal-symbols-as-weakmap-keys/#sec-finalization-registry.prototype.unregister
BUILTIN(FinalizationRegistryUnregister) {
HandleScope scope(isolate);
const char* method_name = "FinalizationRegistry.prototype.unregister";
@ -24,16 +25,29 @@ BUILTIN(FinalizationRegistryUnregister) {
Handle<Object> unregister_token = args.atOrUndefined(isolate, 1);
// 4. If Type(unregisterToken) is not Object, throw a TypeError exception.
if (!unregister_token->IsJSReceiver()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate,
NewTypeError(MessageTemplate::kWeakRefsUnregisterTokenMustBeObject,
unregister_token));
// 4. If CanBeHeldWeakly(unregisterToken) is false, throw a TypeError
// exception.
if (v8_flags.harmony_symbol_as_weakmap_key) {
if (!unregister_token->IsJSReceiver() &&
(!unregister_token->IsSymbol() ||
Handle<Symbol>::cast(unregister_token)->is_in_public_symbol_table())) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate,
NewTypeError(MessageTemplate::kInvalidWeakRefsUnregisterToken,
unregister_token));
}
} else {
// 4. If Type(unregisterToken) is not Object, throw a TypeError exception.
if (!unregister_token->IsJSReceiver()) {
THROW_NEW_ERROR_RETURN_FAILURE(
isolate,
NewTypeError(MessageTemplate::kInvalidWeakRefsUnregisterToken,
unregister_token));
}
}
bool success = JSFinalizationRegistry::Unregister(
finalization_registry, Handle<JSReceiver>::cast(unregister_token),
finalization_registry, Handle<HeapObject>::cast(unregister_token),
isolate);
return *isolate->factory()->ToBoolean(success);

View File

@ -697,6 +697,21 @@ Cast<JSReceiver|Null>(o: HeapObject): JSReceiver|Null
}
}
Cast<JSReceiver|Symbol>(implicit context: Context)(o: Object): JSReceiver|Symbol
labels CastError {
typeswitch (o) {
case (o: JSReceiver): {
return o;
}
case (o: Symbol): {
return o;
}
case (Object): {
goto CastError;
}
}
}
Cast<Smi|PromiseReaction>(o: Object): Smi|PromiseReaction labels CastError {
typeswitch (o) {
case (o: Smi): {

View File

@ -1,6 +1,7 @@
// 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.
#include "src/builtins/builtins-collections-gen.h"
namespace runtime {
extern runtime
@ -15,6 +16,9 @@ extern transitioning macro
RemoveFinalizationRegistryCellFromUnregisterTokenMap(
JSFinalizationRegistry, WeakCell): void;
extern macro WeakCollectionsBuiltinsAssembler::GotoIfCannotBeWeakKey(JSAny):
void labels NotWeakKey;
macro SplitOffTail(weakCell: WeakCell): WeakCell|Undefined {
const weakCellTail = weakCell.next;
weakCell.next = Undefined;
@ -125,6 +129,7 @@ FinalizationRegistryConstructor(
return finalizationRegistry;
}
// https://tc39.es/proposal-symbols-as-weakmap-keys/#sec-finalization-registry.prototype.register
transitioning javascript builtin
FinalizationRegistryRegister(
js-implicit context: NativeContext, receiver: JSAny)(...arguments): JSAny {
@ -134,33 +139,32 @@ FinalizationRegistryRegister(
ThrowTypeError(
MessageTemplate::kIncompatibleMethodReceiver,
'FinalizationRegistry.prototype.register', receiver);
// 3. If Type(target) is not Object, throw a TypeError exception.
const target = Cast<JSReceiver>(arguments[0]) otherwise ThrowTypeError(
MessageTemplate::kWeakRefsRegisterTargetMustBeObject);
// 3. If CanBeHeldWeakly(target) is false, throw a TypeError exception.
GotoIfCannotBeWeakKey(arguments[0])
otherwise ThrowTypeError(MessageTemplate::kInvalidWeakRefsRegisterTarget);
const target = UnsafeCast<(JSReceiver | Symbol)>(arguments[0]);
const heldValue = arguments[1];
// 4. If SameValue(target, heldValue), throw a TypeError exception.
if (target == heldValue) {
ThrowTypeError(
MessageTemplate::kWeakRefsRegisterTargetAndHoldingsMustNotBeSame);
}
// 5. If Type(unregisterToken) is not Object,
// 5. If CanBeHeldWeakly(unregisterToken) is false,
// a. If unregisterToken is not undefined, throw a TypeError exception.
// b. Set unregisterToken to empty.
const unregisterTokenRaw = arguments[2];
let unregisterToken: JSReceiver|Undefined;
typeswitch (unregisterTokenRaw) {
case (Undefined): {
unregisterToken = Undefined;
}
case (unregisterTokenObj: JSReceiver): {
unregisterToken = unregisterTokenObj;
}
case (JSAny): deferred {
ThrowTypeError(
MessageTemplate::kWeakRefsUnregisterTokenMustBeObject,
unregisterTokenRaw);
}
let unregisterToken: JSReceiver|Undefined|Symbol;
if (IsUndefined(unregisterTokenRaw)) {
unregisterToken = Undefined;
} else {
GotoIfCannotBeWeakKey(unregisterTokenRaw)
otherwise ThrowTypeError(
MessageTemplate::kInvalidWeakRefsUnregisterToken, unregisterTokenRaw);
unregisterToken = UnsafeCast<(JSReceiver | Symbol)>(unregisterTokenRaw);
}
// 6. Let cell be the Record { [[WeakRefTarget]] : target, [[HeldValue]]:
// heldValue, [[UnregisterToken]]: unregisterToken }.
// Allocate the WeakCell object in the old space, because 1) WeakCell weakness

View File

@ -2,15 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include 'src/builtins/builtins-collections-gen.h'
namespace runtime {
extern runtime JSWeakRefAddToKeptObjects(implicit context: Context)(JSReceiver):
void;
extern runtime JSWeakRefAddToKeptObjects(implicit context: Context)(
JSReceiver | Symbol): void;
} // namespace runtime
namespace weakref {
// https://tc39.es/proposal-symbols-as-weakmap-keys/#sec-weak-ref-target
transitioning javascript builtin
WeakRefConstructor(
js-implicit context: NativeContext, receiver: JSAny, newTarget: JSAny,
@ -19,15 +22,17 @@ WeakRefConstructor(
if (newTarget == Undefined) {
ThrowTypeError(MessageTemplate::kConstructorNotFunction, 'WeakRef');
}
// 2. If Type(target) is not Object, throw a TypeError exception.
const weakTarget = Cast<JSReceiver>(weakTarget) otherwise
ThrowTypeError(
MessageTemplate::kWeakRefsWeakRefConstructorTargetMustBeObject);
// 2. If CanBeHeldWeakly(weakTarget) is false, throw a TypeError exception.
GotoIfCannotBeWeakKey(weakTarget) otherwise ThrowTypeError(
MessageTemplate::kInvalidWeakRefsWeakRefConstructorTarget);
// 3. Let weakRef be ? OrdinaryCreateFromConstructor(NewTarget,
// "%WeakRefPrototype%", « [[WeakRefTarget]] »).
const map = GetDerivedMap(target, UnsafeCast<JSReceiver>(newTarget));
const weakRef = UnsafeCast<JSWeakRef>(AllocateFastOrSlowJSObjectFromMap(map));
// 4. Perfom ! AddToKeptObjects(target).
const weakTarget = UnsafeCast<(JSReceiver | Symbol)>(weakTarget);
runtime::JSWeakRefAddToKeptObjects(weakTarget);
// 5. Set weakRef.[[WeakRefTarget]] to target.
weakRef.target = weakTarget;
@ -52,7 +57,8 @@ WeakRefDeref(js-implicit context: NativeContext, receiver: JSAny)(): JSAny {
if (target != Undefined) {
// JSWeakRefAddToKeptObjects might allocate and cause a GC, but it
// won't clear `target` since we hold it here on the stack.
runtime::JSWeakRefAddToKeptObjects(UnsafeCast<JSReceiver>(target));
runtime::JSWeakRefAddToKeptObjects(
UnsafeCast<(JSReceiver | Symbol)>(target));
}
return target;
}

View File

@ -2728,6 +2728,12 @@ class V8_EXPORT_PRIVATE CodeStubAssembler
ExternalReference::address_of_shared_string_table_flag());
}
TNode<BoolT> HasHarmonySymbolAsWeakmapKeyFlag() {
return LoadRuntimeFlag(
ExternalReference::
address_of_FLAG_harmony_symbol_as_weakmap_key());
}
// True iff |object| is a Smi or a HeapNumber or a BigInt.
TNode<BoolT> IsNumeric(TNode<Object> object);

View File

@ -585,6 +585,11 @@ ExternalReference ExternalReference::address_of_log_or_trace_osr() {
return ExternalReference(&v8_flags.log_or_trace_osr);
}
ExternalReference
ExternalReference::address_of_FLAG_harmony_symbol_as_weakmap_key() {
return ExternalReference(&FLAG_harmony_symbol_as_weakmap_key);
}
ExternalReference ExternalReference::address_of_builtin_subclassing_flag() {
return ExternalReference(&v8_flags.builtin_subclassing);
}

View File

@ -98,7 +98,9 @@ class StatsCounter;
V(abort_with_reason, "abort_with_reason") \
V(address_of_log_or_trace_osr, "v8_flags.log_or_trace_osr") \
V(address_of_FLAG_harmony_regexp_unicode_sets, \
"v8_flags.harmony_regexp_unicdoe_sets") \
"v8_flags.harmony_regexp_unicode_sets") \
V(address_of_FLAG_harmony_symbol_as_weakmap_key, \
"v8_flags.harmony_symbol_as_weakmap_key") \
V(address_of_builtin_subclassing_flag, "v8_flags.builtin_subclassing") \
V(address_of_double_abs_constant, "double_absolute_constant") \
V(address_of_double_neg_constant, "double_negate_constant") \

View File

@ -686,17 +686,15 @@ namespace internal {
T(TraceEventPhaseError, "Trace event phase must be a number.") \
T(TraceEventIDError, "Trace event id must be a number.") \
/* Weak refs */ \
T(WeakRefsUnregisterTokenMustBeObject, \
"unregisterToken ('%') must be an object") \
T(InvalidWeakRefsUnregisterToken, "Invalid unregisterToken ('%')") \
T(WeakRefsCleanupMustBeCallable, \
"FinalizationRegistry: cleanup must be callable") \
T(WeakRefsRegisterTargetMustBeObject, \
"FinalizationRegistry.prototype.register: target must be an object") \
T(InvalidWeakRefsRegisterTarget, \
"FinalizationRegistry.prototype.register: invalid target") \
T(WeakRefsRegisterTargetAndHoldingsMustNotBeSame, \
"FinalizationRegistry.prototype.register: target and holdings must not " \
"be same") \
T(WeakRefsWeakRefConstructorTargetMustBeObject, \
"WeakRef: target must be an object") \
T(InvalidWeakRefsWeakRefConstructorTarget, "WeakRef: invalid target") \
T(OptionalChainingNoNew, "Invalid optional chain from new expression") \
T(OptionalChainingNoSuper, "Invalid optional chain from super property") \
T(OptionalChainingNoTemplate, "Invalid tagged template on optional chain") \

View File

@ -1299,7 +1299,9 @@ void JSSharedArray::JSSharedArrayVerify(Isolate* isolate) {
void WeakCell::WeakCellVerify(Isolate* isolate) {
CHECK(IsWeakCell());
CHECK(target().IsJSReceiver() || target().IsUndefined(isolate));
CHECK(target().IsJSReceiver() || target().IsUndefined(isolate) ||
(target().IsSymbol() &&
!Symbol::cast(target()).is_in_public_symbol_table()));
CHECK(prev().IsWeakCell() || prev().IsUndefined(isolate));
if (prev().IsWeakCell()) {
@ -1327,7 +1329,9 @@ void WeakCell::WeakCellVerify(Isolate* isolate) {
void JSWeakRef::JSWeakRefVerify(Isolate* isolate) {
CHECK(IsJSWeakRef());
JSObjectVerify(isolate);
CHECK(target().IsUndefined(isolate) || target().IsJSReceiver());
CHECK(target().IsUndefined(isolate) || target().IsJSReceiver() ||
(target().IsSymbol() &&
!Symbol::cast(target()).is_in_public_symbol_table()));
}
void JSFinalizationRegistry::JSFinalizationRegistryVerify(Isolate* isolate) {

View File

@ -230,7 +230,8 @@ DEFINE_BOOL(harmony_shipping, true, "enable all shipped harmony features")
V(harmony_shadow_realm, "harmony ShadowRealm") \
V(harmony_struct, "harmony structs, shared structs, and shared arrays") \
V(harmony_change_array_by_copy, "harmony change-Array-by-copy") \
V(harmony_regexp_unicode_sets, "harmony RegExp Unicode Sets")
V(harmony_regexp_unicode_sets, "harmony RegExp Unicode Sets") \
V(harmony_symbol_as_weakmap_key, "harmony symbols as weakmap keys")
#ifdef V8_INTL_SUPPORT
#define HARMONY_INPROGRESS(V) \

View File

@ -6652,7 +6652,7 @@ void Heap::RemoveDirtyFinalizationRegistriesOnContext(NativeContext context) {
set_dirty_js_finalization_registries_list_tail(prev);
}
void Heap::KeepDuringJob(Handle<JSReceiver> target) {
void Heap::KeepDuringJob(Handle<HeapObject> target) {
DCHECK(weak_refs_keep_during_job().IsUndefined() ||
weak_refs_keep_during_job().IsOrderedHashSet());
Handle<OrderedHashSet> table;

View File

@ -998,7 +998,7 @@ class Heap {
return is_finalization_registry_cleanup_task_posted_;
}
V8_EXPORT_PRIVATE void KeepDuringJob(Handle<JSReceiver> target);
V8_EXPORT_PRIVATE void KeepDuringJob(Handle<HeapObject> target);
void ClearKeptObjects();
// ===========================================================================

View File

@ -4506,6 +4506,7 @@ void Genesis::InitializeConsole(Handle<JSObject> extras_binding) {
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_import_assertions)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_class_static_blocks)
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_symbol_as_weakmap_key)
#ifdef V8_INTL_SUPPORT
EMPTY_INITIALIZE_GLOBAL_FOR_FEATURE(harmony_intl_best_fit_matcher)

View File

@ -5,10 +5,9 @@
#ifndef V8_OBJECTS_JS_WEAK_REFS_INL_H_
#define V8_OBJECTS_JS_WEAK_REFS_INL_H_
#include "src/objects/js-weak-refs.h"
#include "src/api/api-inl.h"
#include "src/heap/heap-write-barrier-inl.h"
#include "src/objects/js-weak-refs.h"
#include "src/objects/smi-inl.h"
// Has to be the last include (doesn't have include guards):
@ -55,7 +54,7 @@ void JSFinalizationRegistry::RegisterWeakCellWithUnregisterToken(
bool JSFinalizationRegistry::Unregister(
Handle<JSFinalizationRegistry> finalization_registry,
Handle<JSReceiver> unregister_token, Isolate* isolate) {
Handle<HeapObject> unregister_token, Isolate* isolate) {
// Iterate through the doubly linked list of WeakCells associated with the
// key. Each WeakCell will be in the "active_cells" or "cleared_cells" list of
// its FinalizationRegistry; remove it from there.
@ -66,7 +65,7 @@ bool JSFinalizationRegistry::Unregister(
template <typename GCNotifyUpdatedSlotCallback>
bool JSFinalizationRegistry::RemoveUnregisterToken(
JSReceiver unregister_token, Isolate* isolate,
HeapObject unregister_token, Isolate* isolate,
RemoveUnregisterTokenMode removal_mode,
GCNotifyUpdatedSlotCallback gc_notify_updated_slot) {
// This method is called from both FinalizationRegistry#unregister and for
@ -171,7 +170,9 @@ void WeakCell::Nullify(Isolate* isolate,
// only called for WeakCells which haven't been unregistered yet, so they will
// be in the active_cells list. (The caller must guard against calling this
// for unregistered WeakCells by checking that the target is not undefined.)
DCHECK(target().IsJSReceiver());
DCHECK(target().IsJSReceiver() ||
(target().IsSymbol() &&
!Symbol::cast(target()).is_in_public_symbol_table()));
set_target(ReadOnlyRoots(isolate).undefined_value());
JSFinalizationRegistry fr =

View File

@ -37,7 +37,7 @@ class JSFinalizationRegistry
Handle<WeakCell> weak_cell, Isolate* isolate);
inline static bool Unregister(
Handle<JSFinalizationRegistry> finalization_registry,
Handle<JSReceiver> unregister_token, Isolate* isolate);
Handle<HeapObject> unregister_token, Isolate* isolate);
// RemoveUnregisterToken is called from both Unregister and during GC. Since
// it modifies slots in key_map and WeakCells and the normal write barrier is
@ -49,7 +49,7 @@ class JSFinalizationRegistry
};
template <typename GCNotifyUpdatedSlotCallback>
inline bool RemoveUnregisterToken(
JSReceiver unregister_token, Isolate* isolate,
HeapObject unregister_token, Isolate* isolate,
RemoveUnregisterTokenMode removal_mode,
GCNotifyUpdatedSlotCallback gc_notify_updated_slot);

View File

@ -20,8 +20,8 @@ extern class JSFinalizationRegistry extends JSObject {
extern class WeakCell extends HeapObject {
finalization_registry: Undefined|JSFinalizationRegistry;
target: Undefined|JSReceiver;
unregister_token: Undefined|JSReceiver;
target: Undefined|JSReceiver|Symbol;
unregister_token: Undefined|JSReceiver|Symbol;
holdings: JSAny;
// For storing doubly linked lists of WeakCells in JSFinalizationRegistry's
@ -40,5 +40,5 @@ extern class WeakCell extends HeapObject {
}
extern class JSWeakRef extends JSObject {
target: Undefined|JSReceiver;
target: Undefined|JSReceiver|Symbol;
}

View File

@ -2,10 +2,9 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/runtime/runtime-utils.h"
#include "src/execution/arguments-inl.h"
#include "src/objects/js-weak-refs-inl.h"
#include "src/runtime/runtime-utils.h"
namespace v8 {
namespace internal {
@ -44,7 +43,14 @@ RUNTIME_FUNCTION(
RUNTIME_FUNCTION(Runtime_JSWeakRefAddToKeptObjects) {
HandleScope scope(isolate);
DCHECK_EQ(1, args.length());
Handle<JSReceiver> object = args.at<JSReceiver>(0);
Handle<HeapObject> object = args.at<HeapObject>(0);
if (FLAG_harmony_symbol_as_weakmap_key) {
DCHECK(object->IsJSReceiver() ||
(object->IsSymbol() &&
!(Handle<Symbol>::cast(object))->is_in_public_symbol_table()));
} else {
DCHECK(object->IsJSReceiver());
}
isolate->heap()->KeepDuringJob(object);

View File

@ -1,6 +1,6 @@
*%(basename)s:*: TypeError: FinalizationRegistry.prototype.register: target must be an object
*%(basename)s:*: TypeError: FinalizationRegistry.prototype.register: invalid target
fg.register(1);
^
TypeError: FinalizationRegistry.prototype.register: target must be an object
TypeError: FinalizationRegistry.prototype.register: invalid target
at FinalizationRegistry.register (<anonymous>)
at *%(basename)s:*:4

View File

@ -1,6 +1,6 @@
*%(basename)s:*: TypeError: unregisterToken ('1') must be an object
*%(basename)s:*: TypeError: Invalid unregisterToken ('1')
fg.unregister(1);
^
TypeError: unregisterToken ('1') must be an object
TypeError: Invalid unregisterToken ('1')
at FinalizationRegistry.unregister (<anonymous>)
at *%(basename)s:*:4

View File

@ -0,0 +1,99 @@
// Copyright 2022 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-symbol-as-weakmap-key --expose-gc
(function TestWeakMapWithNonRegisteredSymbolKey() {
const key = Symbol('123');
const value = 1;
const map = new WeakMap();
assertFalse(map.has(key));
assertSame(undefined, map.get(key));
assertFalse(map.delete(key));
assertSame(map, map.set(key, value));
assertSame(value, map.get(key));
assertTrue(map.has(key));
assertTrue(map.delete(key));
assertFalse(map.has(key));
assertSame(undefined, map.get(key));
assertFalse(map.delete(key));
assertFalse(map.has(key));
assertSame(undefined, map.get(key));
})();
(function TestWeakMapWithNonRegisteredSymbolKeyGC() {
const map = new WeakMap();
const outerKey = Symbol('234');
const outerValue = 1;
map.set(outerKey, outerValue);
{
const innerKey = Symbol('123');
const innerValue = 1;
map.set(innerKey, innerValue);
assertTrue(map.has(innerKey));
assertSame(innerValue, map.get(innerKey));
}
gc();
assertTrue(map.has(outerKey));
assertSame(outerValue, map.get(outerKey));
})();
(function TestWeakMapWithRegisteredSymbolKey() {
const key = Symbol.for('123');
const value = 1;
const map = new WeakMap();
assertFalse(map.has(key));
assertSame(undefined, map.get(key));
assertFalse(map.delete(key));
assertThrows(() => {
map.set(key, value);
}, TypeError, 'Invalid value used as weak map key');
assertFalse(map.has(key));
assertSame(undefined, map.get(key));
assertFalse(map.delete(key));
assertFalse(map.has(key));
assertSame(undefined, map.get(key));
})();
(function TestWeakSetWithNonRegisteredSymbolKey() {
const key = Symbol('123');
const set = new WeakSet();
assertFalse(set.has(key));
assertFalse(set.delete(key));
assertSame(set, set.add(key));
assertTrue(set.has(key));
assertTrue(set.delete(key));
assertFalse(set.has(key));
assertFalse(set.delete(key));
assertFalse(set.has(key));
})();
(function TestWeakSetWithNonRegisteredSymbolKeyGC() {
const set = new WeakSet();
const outerKey = Symbol('234');
set.add(outerKey);
{
const innerKey = Symbol('123');
set.add(innerKey);
assertTrue(set.has(innerKey));
}
gc();
assertTrue(set.has(outerKey));
})();
(function TestWeakSetWithRegisteredSymbolKey() {
const key = Symbol.for('123');
const set = new WeakSet();
assertFalse(set.has(key));
assertFalse(set.delete(key));
assertThrows(() => {
assertSame(set, set.add(key));
}, TypeError, 'Invalid value used in weak set');
assertFalse(set.has(key));
assertFalse(set.delete(key));
assertFalse(set.has(key));
})();

View File

@ -47,7 +47,7 @@
(function TestRegisterWithNonObjectTarget() {
let fg = new FinalizationRegistry(() => {});
let message = "FinalizationRegistry.prototype.register: target must be an object";
let message = "FinalizationRegistry.prototype.register: invalid target";
assertThrows(() => fg.register(1, "holdings"), TypeError, message);
assertThrows(() => fg.register(false, "holdings"), TypeError, message);
assertThrows(() => fg.register("foo", "holdings"), TypeError, message);
@ -116,7 +116,7 @@
})();
(function TestWeakRefConstructorWithNonObject() {
let message = "WeakRef: target must be an object";
let message = "WeakRef: invalid target";
assertThrows(() => new WeakRef(), TypeError, message);
assertThrows(() => new WeakRef(1), TypeError, message);
assertThrows(() => new WeakRef(false), TypeError, message);

View File

@ -0,0 +1,39 @@
// Copyright 2022 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-symbol-as-weakmap-key --expose-gc --noincremental-marking
(function TestWeakRefWithSymbolGC() {
let weakRef;
{
const innerKey = Symbol('123');
weakRef = new WeakRef(innerKey);
}
// Since the WeakRef was created during this turn, it is not cleared by GC.
gc();
assertNotEquals(undefined, weakRef.deref());
// Next task.
setTimeout(() => {
gc();
assertEquals(undefined, weakRef.deref());
}, 0);
})();
(function TestFinalizationRegistryWithSymbolGC() {
let cleanUpCalled = false;
const fg = new FinalizationRegistry((target) => {
assertEquals('123', target);
cleanUpCalled = true;
});
(function () {
const innerKey = Symbol('123');
fg.register(innerKey, '123');
})();
gc();
assertFalse(cleanUpCalled);
// Check that cleanup callback was called in a follow up task.
setTimeout(() => {
assertTrue(cleanUpCalled);
}, 0);
})();

View File

@ -0,0 +1,41 @@
// Copyright 2022 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-symbol-as-weakmap-key
(function TestRegisterWithSymbolTarget() {
const fg = new FinalizationRegistry(() => { });
fg.register(Symbol('123'), 'holdings');
// Registered symbols cannot be the target.
assertThrows(() => fg.register(Symbol.for('123'), 'holdings'), TypeError);
})();
(function TestRegisterWithSymbolUnregisterToken() {
const fg = new FinalizationRegistry(() => { });
fg.register({}, 'holdings', Symbol('123'));
// Registered symbols cannot be the unregister token.
assertThrows(() => fg.register({}, 'holdings', Symbol.for('123')), TypeError);
})();
(function TestRegisterSymbolAndHoldingsSameValue() {
const fg = new FinalizationRegistry(() => {});
const obj = Symbol('123');
// SameValue(target, holdings) not ok.
assertThrows(() => fg.register(obj, obj), TypeError);
const holdings = {a: 1};
fg.register(obj, holdings);
})();
(function TestUnregisterWithSymbolUnregisterToken() {
const fg = new FinalizationRegistry(() => {});
fg.unregister(Symbol('123'));
// Registered symbols cannot be the unregister token.
assertThrows(() => fg.unregister(Symbol.for('123')), TypeError);
})();
(function TestWeakRefConstructorWithSymbol() {
new WeakRef(Symbol('123'));
// Registered symbols cannot be the WeakRef target.
assertThrows(() => new WeakRef(Symbol.for('123')), TypeError);
})();