Reland "[runtime] Cache prototype chain enumerable keys in PrototypeInfo"
This is a reland of 5253d7bf15
Original change's description:
> [runtime] Cache prototype chain enumerable keys in PrototypeInfo
>
> This CL adds a prototype_chain_enum_cache to cache the enumeration of a
> prototype and its entire chain on the PrototypeInfo. It can improve for-in
> performance via simply merging the receiver enumeration with this cache.
>
> It improves the score of JetStream2-tagcloud-SP case by ~9% on IA Chromebook.
>
> Contributed by tao.pan@intel.com
>
> Change-Id: Ib40bfe41e772672337155584672f06fa1ba1e70d
> Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1870844
> Commit-Queue: Shiyu Zhang <shiyu.zhang@intel.com>
> Reviewed-by: Toon Verwaest <verwaest@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#65224}
Change-Id: I93b74727c46abbaab163324c50fbd977fcc9bb36
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/1955232
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
Commit-Queue: Shiyu Zhang <shiyu.zhang@intel.com>
Cr-Commit-Position: refs/heads/master@{#65377}
This commit is contained in:
parent
118b23602f
commit
3b7535636f
@ -4402,6 +4402,11 @@ void InvalidateOnePrototypeValidityCellInternal(Map map) {
|
|||||||
Cell cell = Cell::cast(maybe_cell);
|
Cell cell = Cell::cast(maybe_cell);
|
||||||
cell.set_value(Smi::FromInt(Map::kPrototypeChainInvalid));
|
cell.set_value(Smi::FromInt(Map::kPrototypeChainInvalid));
|
||||||
}
|
}
|
||||||
|
Object maybe_prototype_info = map.prototype_info();
|
||||||
|
if (maybe_prototype_info.IsPrototypeInfo()) {
|
||||||
|
PrototypeInfo prototype_info = PrototypeInfo::cast(maybe_prototype_info);
|
||||||
|
prototype_info.set_prototype_chain_enum_cache(Object());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void InvalidatePrototypeChainsInternal(Map map) {
|
void InvalidatePrototypeChainsInternal(Map map) {
|
||||||
|
@ -44,6 +44,42 @@ static bool ContainsOnlyValidKeys(Handle<FixedArray> array) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int AddKey(Object key, Handle<FixedArray> combined_keys,
|
||||||
|
Handle<DescriptorArray> descs, int nof_descriptors,
|
||||||
|
int target) {
|
||||||
|
for (InternalIndex i : InternalIndex::Range(nof_descriptors)) {
|
||||||
|
if (descs->GetKey(i) == key) return 0;
|
||||||
|
}
|
||||||
|
combined_keys->set(target, key);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Handle<FixedArray> CombineKeys(Isolate* isolate,
|
||||||
|
Handle<FixedArray> own_keys,
|
||||||
|
Handle<FixedArray> prototype_chain_keys,
|
||||||
|
Handle<JSReceiver> receiver) {
|
||||||
|
int prototype_chain_keys_length = prototype_chain_keys->length();
|
||||||
|
if (prototype_chain_keys_length == 0) return own_keys;
|
||||||
|
|
||||||
|
Map map = receiver->map();
|
||||||
|
int nof_descriptors = map.NumberOfOwnDescriptors();
|
||||||
|
if (nof_descriptors == 0) return prototype_chain_keys;
|
||||||
|
|
||||||
|
Handle<DescriptorArray> descs(map.instance_descriptors(), isolate);
|
||||||
|
int own_keys_length = own_keys.is_null() ? 0 : own_keys->length();
|
||||||
|
Handle<FixedArray> combined_keys = isolate->factory()->NewFixedArray(
|
||||||
|
own_keys_length + prototype_chain_keys_length);
|
||||||
|
if (own_keys_length != 0) {
|
||||||
|
own_keys->CopyTo(0, *combined_keys, 0, own_keys_length);
|
||||||
|
}
|
||||||
|
int target_keys_length = own_keys_length;
|
||||||
|
for (int i = 0; i < prototype_chain_keys_length; i++) {
|
||||||
|
target_keys_length += AddKey(prototype_chain_keys->get(i), combined_keys,
|
||||||
|
descs, nof_descriptors, target_keys_length);
|
||||||
|
}
|
||||||
|
return FixedArray::ShrinkOrEmpty(isolate, combined_keys, target_keys_length);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
// static
|
// static
|
||||||
@ -68,6 +104,14 @@ Handle<FixedArray> KeyAccumulator::GetKeys(GetKeysConversion convert) {
|
|||||||
Handle<FixedArray> result =
|
Handle<FixedArray> result =
|
||||||
OrderedHashSet::ConvertToKeysArray(isolate(), keys(), convert);
|
OrderedHashSet::ConvertToKeysArray(isolate(), keys(), convert);
|
||||||
DCHECK(ContainsOnlyValidKeys(result));
|
DCHECK(ContainsOnlyValidKeys(result));
|
||||||
|
|
||||||
|
if (try_prototype_info_cache_ && !first_prototype_map_.is_null()) {
|
||||||
|
PrototypeInfo::cast(first_prototype_map_->prototype_info())
|
||||||
|
.set_prototype_chain_enum_cache(*result);
|
||||||
|
Map::GetOrCreatePrototypeChainValidityCell(
|
||||||
|
Handle<Map>(receiver_->map(), isolate_), isolate_);
|
||||||
|
DCHECK(first_prototype_map_->IsPrototypeValidityCellValid());
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,6 +335,9 @@ void FastKeyAccumulator::Prepare() {
|
|||||||
last_prototype = current;
|
last_prototype = current;
|
||||||
has_empty_prototype_ = false;
|
has_empty_prototype_ = false;
|
||||||
}
|
}
|
||||||
|
// Check if we should try to create/use prototype info cache.
|
||||||
|
try_prototype_info_cache_ = TryPrototypeInfoCache(receiver_);
|
||||||
|
if (has_prototype_info_cache_) return;
|
||||||
if (has_empty_prototype_) {
|
if (has_empty_prototype_) {
|
||||||
is_receiver_simple_enum_ =
|
is_receiver_simple_enum_ =
|
||||||
receiver_->map().EnumLength() != kInvalidEnumCacheSentinel &&
|
receiver_->map().EnumLength() != kInvalidEnumCacheSentinel &&
|
||||||
@ -432,6 +479,9 @@ MaybeHandle<FixedArray> FastKeyAccumulator::GetKeys(
|
|||||||
if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>();
|
if (isolate_->has_pending_exception()) return MaybeHandle<FixedArray>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (try_prototype_info_cache_) {
|
||||||
|
return GetKeysWithPrototypeInfoCache(keys_conversion);
|
||||||
|
}
|
||||||
return GetKeysSlow(keys_conversion);
|
return GetKeysSlow(keys_conversion);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -503,12 +553,41 @@ MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysSlow(
|
|||||||
accumulator.set_skip_indices(skip_indices_);
|
accumulator.set_skip_indices(skip_indices_);
|
||||||
accumulator.set_last_non_empty_prototype(last_non_empty_prototype_);
|
accumulator.set_last_non_empty_prototype(last_non_empty_prototype_);
|
||||||
accumulator.set_may_have_elements(may_have_elements_);
|
accumulator.set_may_have_elements(may_have_elements_);
|
||||||
|
accumulator.set_first_prototype_map(first_prototype_map_);
|
||||||
|
accumulator.set_try_prototype_info_cache(try_prototype_info_cache_);
|
||||||
|
|
||||||
MAYBE_RETURN(accumulator.CollectKeys(receiver_, receiver_),
|
MAYBE_RETURN(accumulator.CollectKeys(receiver_, receiver_),
|
||||||
MaybeHandle<FixedArray>());
|
MaybeHandle<FixedArray>());
|
||||||
return accumulator.GetKeys(keys_conversion);
|
return accumulator.GetKeys(keys_conversion);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MaybeHandle<FixedArray> FastKeyAccumulator::GetKeysWithPrototypeInfoCache(
|
||||||
|
GetKeysConversion keys_conversion) {
|
||||||
|
Handle<FixedArray> own_keys = KeyAccumulator::GetOwnEnumPropertyKeys(
|
||||||
|
isolate_, Handle<JSObject>::cast(receiver_));
|
||||||
|
Handle<FixedArray> prototype_chain_keys;
|
||||||
|
if (has_prototype_info_cache_) {
|
||||||
|
prototype_chain_keys =
|
||||||
|
handle(FixedArray::cast(
|
||||||
|
PrototypeInfo::cast(first_prototype_map_->prototype_info())
|
||||||
|
.prototype_chain_enum_cache()),
|
||||||
|
isolate_);
|
||||||
|
} else {
|
||||||
|
KeyAccumulator accumulator(isolate_, mode_, filter_);
|
||||||
|
accumulator.set_is_for_in(is_for_in_);
|
||||||
|
accumulator.set_skip_indices(skip_indices_);
|
||||||
|
accumulator.set_last_non_empty_prototype(last_non_empty_prototype_);
|
||||||
|
accumulator.set_may_have_elements(may_have_elements_);
|
||||||
|
accumulator.set_receiver(receiver_);
|
||||||
|
accumulator.set_first_prototype_map(first_prototype_map_);
|
||||||
|
accumulator.set_try_prototype_info_cache(try_prototype_info_cache_);
|
||||||
|
MAYBE_RETURN(accumulator.CollectKeys(first_prototype_, first_prototype_),
|
||||||
|
MaybeHandle<FixedArray>());
|
||||||
|
prototype_chain_keys = accumulator.GetKeys(keys_conversion);
|
||||||
|
}
|
||||||
|
return CombineKeys(isolate_, own_keys, prototype_chain_keys, receiver_);
|
||||||
|
}
|
||||||
|
|
||||||
bool FastKeyAccumulator::MayHaveElements(JSReceiver receiver) {
|
bool FastKeyAccumulator::MayHaveElements(JSReceiver receiver) {
|
||||||
if (!receiver.IsJSObject()) return true;
|
if (!receiver.IsJSObject()) return true;
|
||||||
JSObject object = JSObject::cast(receiver);
|
JSObject object = JSObject::cast(receiver);
|
||||||
@ -517,6 +596,28 @@ bool FastKeyAccumulator::MayHaveElements(JSReceiver receiver) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FastKeyAccumulator::TryPrototypeInfoCache(Handle<JSReceiver> receiver) {
|
||||||
|
if (may_have_elements_) return false;
|
||||||
|
Handle<JSObject> object = Handle<JSObject>::cast(receiver);
|
||||||
|
if (!object->HasFastProperties()) return false;
|
||||||
|
if (object->HasNamedInterceptor()) return false;
|
||||||
|
if (object->IsAccessCheckNeeded() &&
|
||||||
|
!isolate_->MayAccess(handle(isolate_->context(), isolate_), object)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
HeapObject prototype = receiver->map().prototype();
|
||||||
|
if (prototype.is_null()) return false;
|
||||||
|
if (!prototype.map().is_prototype_map()) return false;
|
||||||
|
first_prototype_ = handle(JSReceiver::cast(prototype), isolate_);
|
||||||
|
Handle<Map> map(prototype.map(), isolate_);
|
||||||
|
first_prototype_map_ = map;
|
||||||
|
has_prototype_info_cache_ = map->IsPrototypeValidityCellValid() &&
|
||||||
|
PrototypeInfo::cast(map->prototype_info())
|
||||||
|
.prototype_chain_enum_cache()
|
||||||
|
.IsFixedArray();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
enum IndexedOrNamed { kIndexed, kNamed };
|
enum IndexedOrNamed { kIndexed, kNamed };
|
||||||
|
@ -87,6 +87,13 @@ class KeyAccumulator final {
|
|||||||
// indices.
|
// indices.
|
||||||
void set_is_for_in(bool value) { is_for_in_ = value; }
|
void set_is_for_in(bool value) { is_for_in_ = value; }
|
||||||
void set_skip_indices(bool value) { skip_indices_ = value; }
|
void set_skip_indices(bool value) { skip_indices_ = value; }
|
||||||
|
void set_first_prototype_map(Handle<Map> value) {
|
||||||
|
first_prototype_map_ = value;
|
||||||
|
}
|
||||||
|
void set_try_prototype_info_cache(bool value) {
|
||||||
|
try_prototype_info_cache_ = value;
|
||||||
|
}
|
||||||
|
void set_receiver(Handle<JSReceiver> object) { receiver_ = object; }
|
||||||
// The last_non_empty_prototype is used to limit the prototypes for which
|
// The last_non_empty_prototype is used to limit the prototypes for which
|
||||||
// we have to keep track of non-enumerable keys that can shadow keys
|
// we have to keep track of non-enumerable keys that can shadow keys
|
||||||
// repeated on the prototype chain.
|
// repeated on the prototype chain.
|
||||||
@ -117,6 +124,8 @@ class KeyAccumulator final {
|
|||||||
// keys a Handle<FixedArray>. The OrderedHashSet is in-place converted to the
|
// keys a Handle<FixedArray>. The OrderedHashSet is in-place converted to the
|
||||||
// result list, a FixedArray containing all collected keys.
|
// result list, a FixedArray containing all collected keys.
|
||||||
Handle<FixedArray> keys_;
|
Handle<FixedArray> keys_;
|
||||||
|
Handle<Map> first_prototype_map_;
|
||||||
|
Handle<JSReceiver> receiver_;
|
||||||
Handle<JSReceiver> last_non_empty_prototype_;
|
Handle<JSReceiver> last_non_empty_prototype_;
|
||||||
Handle<ObjectHashSet> shadowing_keys_;
|
Handle<ObjectHashSet> shadowing_keys_;
|
||||||
KeyCollectionMode mode_;
|
KeyCollectionMode mode_;
|
||||||
@ -127,6 +136,7 @@ class KeyAccumulator final {
|
|||||||
// the shadow check.
|
// the shadow check.
|
||||||
bool skip_shadow_check_ = true;
|
bool skip_shadow_check_ = true;
|
||||||
bool may_have_elements_ = true;
|
bool may_have_elements_ = true;
|
||||||
|
bool try_prototype_info_cache_ = false;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(KeyAccumulator);
|
DISALLOW_COPY_AND_ASSIGN(KeyAccumulator);
|
||||||
};
|
};
|
||||||
@ -160,13 +170,18 @@ class FastKeyAccumulator {
|
|||||||
void Prepare();
|
void Prepare();
|
||||||
MaybeHandle<FixedArray> GetKeysFast(GetKeysConversion convert);
|
MaybeHandle<FixedArray> GetKeysFast(GetKeysConversion convert);
|
||||||
MaybeHandle<FixedArray> GetKeysSlow(GetKeysConversion convert);
|
MaybeHandle<FixedArray> GetKeysSlow(GetKeysConversion convert);
|
||||||
|
MaybeHandle<FixedArray> GetKeysWithPrototypeInfoCache(
|
||||||
|
GetKeysConversion convert);
|
||||||
|
|
||||||
MaybeHandle<FixedArray> GetOwnKeysWithUninitializedEnumCache();
|
MaybeHandle<FixedArray> GetOwnKeysWithUninitializedEnumCache();
|
||||||
|
|
||||||
bool MayHaveElements(JSReceiver receiver);
|
bool MayHaveElements(JSReceiver receiver);
|
||||||
|
bool TryPrototypeInfoCache(Handle<JSReceiver> receiver);
|
||||||
|
|
||||||
Isolate* isolate_;
|
Isolate* isolate_;
|
||||||
Handle<JSReceiver> receiver_;
|
Handle<JSReceiver> receiver_;
|
||||||
|
Handle<Map> first_prototype_map_;
|
||||||
|
Handle<JSReceiver> first_prototype_;
|
||||||
Handle<JSReceiver> last_non_empty_prototype_;
|
Handle<JSReceiver> last_non_empty_prototype_;
|
||||||
KeyCollectionMode mode_;
|
KeyCollectionMode mode_;
|
||||||
PropertyFilter filter_;
|
PropertyFilter filter_;
|
||||||
@ -175,6 +190,8 @@ class FastKeyAccumulator {
|
|||||||
bool is_receiver_simple_enum_ = false;
|
bool is_receiver_simple_enum_ = false;
|
||||||
bool has_empty_prototype_ = false;
|
bool has_empty_prototype_ = false;
|
||||||
bool may_have_elements_ = true;
|
bool may_have_elements_ = true;
|
||||||
|
bool has_prototype_info_cache_ = false;
|
||||||
|
bool try_prototype_info_cache_ = false;
|
||||||
|
|
||||||
DISALLOW_COPY_AND_ASSIGN(FastKeyAccumulator);
|
DISALLOW_COPY_AND_ASSIGN(FastKeyAccumulator);
|
||||||
};
|
};
|
||||||
|
@ -41,6 +41,8 @@ bool PrototypeInfo::HasObjectCreateMap() {
|
|||||||
|
|
||||||
ACCESSORS(PrototypeInfo, module_namespace, Object, kJsModuleNamespaceOffset)
|
ACCESSORS(PrototypeInfo, module_namespace, Object, kJsModuleNamespaceOffset)
|
||||||
ACCESSORS(PrototypeInfo, prototype_users, Object, kPrototypeUsersOffset)
|
ACCESSORS(PrototypeInfo, prototype_users, Object, kPrototypeUsersOffset)
|
||||||
|
ACCESSORS(PrototypeInfo, prototype_chain_enum_cache, Object,
|
||||||
|
kPrototypeChainEnumCacheOffset)
|
||||||
WEAK_ACCESSORS(PrototypeInfo, object_create_map, kObjectCreateMapOffset)
|
WEAK_ACCESSORS(PrototypeInfo, object_create_map, kObjectCreateMapOffset)
|
||||||
SMI_ACCESSORS(PrototypeInfo, registry_slot, kRegistrySlotOffset)
|
SMI_ACCESSORS(PrototypeInfo, registry_slot, kRegistrySlotOffset)
|
||||||
SMI_ACCESSORS(PrototypeInfo, bit_field, kBitFieldOffset)
|
SMI_ACCESSORS(PrototypeInfo, bit_field, kBitFieldOffset)
|
||||||
|
@ -30,6 +30,8 @@ class PrototypeInfo : public Struct {
|
|||||||
// this prototype, or Smi(0) if uninitialized.
|
// this prototype, or Smi(0) if uninitialized.
|
||||||
DECL_ACCESSORS(prototype_users, Object)
|
DECL_ACCESSORS(prototype_users, Object)
|
||||||
|
|
||||||
|
DECL_ACCESSORS(prototype_chain_enum_cache, Object)
|
||||||
|
|
||||||
// [object_create_map]: A field caching the map for Object.create(prototype).
|
// [object_create_map]: A field caching the map for Object.create(prototype).
|
||||||
static inline void SetObjectCreateMap(Handle<PrototypeInfo> info,
|
static inline void SetObjectCreateMap(Handle<PrototypeInfo> info,
|
||||||
Handle<Map> map);
|
Handle<Map> map);
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
extern class PrototypeInfo extends Struct {
|
extern class PrototypeInfo extends Struct {
|
||||||
js_module_namespace: JSModuleNamespace|Undefined;
|
js_module_namespace: JSModuleNamespace|Undefined;
|
||||||
prototype_users: WeakArrayList|Zero;
|
prototype_users: WeakArrayList|Zero;
|
||||||
|
prototype_chain_enum_cache: FixedArray|Object|Undefined;
|
||||||
registry_slot: Smi;
|
registry_slot: Smi;
|
||||||
validity_cell: Object;
|
validity_cell: Object;
|
||||||
object_create_map: Weak<Map>|Undefined;
|
object_create_map: Weak<Map>|Undefined;
|
||||||
|
@ -141,3 +141,149 @@ for_in_string_prototype();
|
|||||||
assertEquals(['prop2', 'prop1'], Accumulate(derived2));
|
assertEquals(['prop2', 'prop1'], Accumulate(derived2));
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
(function for_in_prototype_itself_change() {
|
||||||
|
let prototype1 = {prop: 0, prop1: 1};
|
||||||
|
let derived1 = {prop2: 2, prop3: 3};
|
||||||
|
|
||||||
|
Object.setPrototypeOf(derived1, prototype1);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop2', 'prop3', 'prop', 'prop1'], Accumulate(derived1));
|
||||||
|
}
|
||||||
|
|
||||||
|
prototype1.prop3 = 3;
|
||||||
|
let derived2 = {prop4: 4, prop5: 5};
|
||||||
|
Object.setPrototypeOf(derived2, prototype1);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop4', 'prop5', 'prop', 'prop1', 'prop3'], Accumulate(derived2));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function for_in_prototype_change_property() {
|
||||||
|
let prototype1 = {prop: 0, prop1: 1};
|
||||||
|
let derived1 = {prop2: 2, prop3: 3};
|
||||||
|
|
||||||
|
Object.setPrototypeOf(derived1, prototype1);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop2', 'prop3', 'prop', 'prop1'], Accumulate(derived1));
|
||||||
|
}
|
||||||
|
|
||||||
|
prototype1.__proto__ = {prop4: 4, prop5: 5};
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop2', 'prop3', 'prop', 'prop1', 'prop4', 'prop5'], Accumulate(derived1));
|
||||||
|
}
|
||||||
|
|
||||||
|
derived1.__proto__ = {prop6: 6, prop7: 7};
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop2', 'prop3', 'prop6', 'prop7'], Accumulate(derived1));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function for_in_prototype_change_element() {
|
||||||
|
let prototype1 = {prop: 0, prop1: 1};
|
||||||
|
let derived1 = {prop2: 2, prop3: 3};
|
||||||
|
|
||||||
|
Object.setPrototypeOf(derived1, prototype1);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop2', 'prop3', 'prop', 'prop1'], Accumulate(derived1));
|
||||||
|
}
|
||||||
|
|
||||||
|
prototype1[0] = 4;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop2', 'prop3', '0', 'prop', 'prop1'], Accumulate(derived1));
|
||||||
|
}
|
||||||
|
|
||||||
|
derived1.__proto__ = {1: 1, 3: 3};
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop2', 'prop3', '1', '3'], Accumulate(derived1));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function for_in_non_enumerable1() {
|
||||||
|
let prototype1 = {prop: 0};
|
||||||
|
let derived1 = Object.create(prototype1, {
|
||||||
|
prop1: {enumerable: false, configurable: true, value: 1},
|
||||||
|
});
|
||||||
|
Object.setPrototypeOf(derived1, prototype1);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop'], Accumulate(derived1));
|
||||||
|
}
|
||||||
|
|
||||||
|
let derived2 = {prop2: 2};
|
||||||
|
Object.setPrototypeOf(derived2, prototype1);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop2', 'prop'], Accumulate(derived2));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function for_in_non_enumerable2() {
|
||||||
|
let prototype1 = {prop: 0};
|
||||||
|
let derived1 = {prop1: 1};
|
||||||
|
Object.setPrototypeOf(derived1, prototype1);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop1', 'prop'], Accumulate(derived1));
|
||||||
|
}
|
||||||
|
|
||||||
|
let derived2 = Object.create(prototype1, {
|
||||||
|
prop: {enumerable: false, configurable: true, value: 0},
|
||||||
|
prop2: {enumerable: true, configurable: true, value: 2}
|
||||||
|
});
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop2'], Accumulate(derived2));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function for_in_same_key1() {
|
||||||
|
let prototype1 = {prop: 0, prop1: 1};
|
||||||
|
let derived1 = {prop: 0, prop2: 1};
|
||||||
|
Object.setPrototypeOf(derived1, prototype1);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop', 'prop2', 'prop1'], Accumulate(derived1));
|
||||||
|
}
|
||||||
|
|
||||||
|
let derived2 = {prop3: 3, prop4: 4};
|
||||||
|
Object.setPrototypeOf(derived2, prototype1);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop3', 'prop4', 'prop', 'prop1'], Accumulate(derived2));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function for_in_same_key2() {
|
||||||
|
let prototype1 = {prop: 0, prop1: 1};
|
||||||
|
let derived1 = {prop2: 2, prop3: 3};
|
||||||
|
Object.setPrototypeOf(derived1, prototype1);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop2', 'prop3', 'prop', 'prop1'], Accumulate(derived1));
|
||||||
|
}
|
||||||
|
|
||||||
|
let derived2 = {prop: 0, prop4: 4};
|
||||||
|
Object.setPrototypeOf(derived2, prototype1);
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop', 'prop4', 'prop1'], Accumulate(derived2));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function for_in_redefine_property() {
|
||||||
|
Object.prototype.prop = 0;
|
||||||
|
let object1 = {prop1: 1, prop2: 2};
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop1', 'prop2', 'prop'], Accumulate(object1));
|
||||||
|
}
|
||||||
|
|
||||||
|
let object2 = {prop3: 3, prop4: 4};
|
||||||
|
Object.defineProperty(object2,
|
||||||
|
'prop', {enumerable: false, configurable: true, value: 0});
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals(['prop3', 'prop4'], Accumulate(object2));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
(function for_in_empty_property() {
|
||||||
|
let prototype1 = {prop: 0};
|
||||||
|
let derived1 = Object.create(prototype1, {
|
||||||
|
prop: {enumerable: false, configurable: true, value: 0}
|
||||||
|
});
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
assertEquals([], Accumulate(derived1));
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
Loading…
Reference in New Issue
Block a user