| // Copyright 2014 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. |
| |
| #ifndef V8_PROTOTYPE_H_ |
| #define V8_PROTOTYPE_H_ |
| |
| #include "src/isolate.h" |
| #include "src/objects.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| /** |
| * A class to uniformly access the prototype of any Object and walk its |
| * prototype chain. |
| * |
| * The PrototypeIterator can either start at the prototype (default), or |
| * include the receiver itself. If a PrototypeIterator is constructed for a |
| * Map, it will always start at the prototype. |
| * |
| * The PrototypeIterator can either run to the null_value(), the first |
| * non-hidden prototype, or a given object. |
| */ |
| |
| class PrototypeIterator { |
| public: |
| enum WhereToEnd { END_AT_NULL, END_AT_NON_HIDDEN }; |
| |
| PrototypeIterator(Isolate* isolate, Handle<JSReceiver> receiver, |
| WhereToStart where_to_start = kStartAtPrototype, |
| WhereToEnd where_to_end = END_AT_NULL) |
| : isolate_(isolate), |
| object_(nullptr), |
| handle_(receiver), |
| where_to_end_(where_to_end), |
| is_at_end_(false), |
| seen_proxies_(0) { |
| CHECK(!handle_.is_null()); |
| if (where_to_start == kStartAtPrototype) Advance(); |
| } |
| |
| PrototypeIterator(Isolate* isolate, JSReceiver* receiver, |
| WhereToStart where_to_start = kStartAtPrototype, |
| WhereToEnd where_to_end = END_AT_NULL) |
| : isolate_(isolate), |
| object_(receiver), |
| where_to_end_(where_to_end), |
| is_at_end_(false), |
| seen_proxies_(0) { |
| if (where_to_start == kStartAtPrototype) Advance(); |
| } |
| |
| explicit PrototypeIterator(Map* receiver_map, |
| WhereToEnd where_to_end = END_AT_NULL) |
| : isolate_(receiver_map->GetIsolate()), |
| object_(receiver_map->GetPrototypeChainRootMap(isolate_)->prototype()), |
| where_to_end_(where_to_end), |
| is_at_end_(object_->IsNull(isolate_)), |
| seen_proxies_(0) { |
| if (!is_at_end_ && where_to_end_ == END_AT_NON_HIDDEN) { |
| DCHECK(object_->IsJSReceiver()); |
| Map* map = JSReceiver::cast(object_)->map(); |
| is_at_end_ = !map->has_hidden_prototype(); |
| } |
| } |
| |
| explicit PrototypeIterator(Handle<Map> receiver_map, |
| WhereToEnd where_to_end = END_AT_NULL) |
| : isolate_(receiver_map->GetIsolate()), |
| object_(nullptr), |
| handle_(receiver_map->GetPrototypeChainRootMap(isolate_)->prototype(), |
| isolate_), |
| where_to_end_(where_to_end), |
| is_at_end_(handle_->IsNull(isolate_)), |
| seen_proxies_(0) { |
| if (!is_at_end_ && where_to_end_ == END_AT_NON_HIDDEN) { |
| DCHECK(handle_->IsJSReceiver()); |
| Map* map = JSReceiver::cast(*handle_)->map(); |
| is_at_end_ = !map->has_hidden_prototype(); |
| } |
| } |
| |
| ~PrototypeIterator() {} |
| |
| bool HasAccess() const { |
| // We can only perform access check in the handlified version of the |
| // PrototypeIterator. |
| DCHECK(!handle_.is_null()); |
| if (handle_->IsAccessCheckNeeded()) { |
| return isolate_->MayAccess(handle(isolate_->context()), |
| Handle<JSObject>::cast(handle_)); |
| } |
| return true; |
| } |
| |
| template <typename T = Object> |
| T* GetCurrent() const { |
| DCHECK(handle_.is_null()); |
| return T::cast(object_); |
| } |
| |
| template <typename T = Object> |
| static Handle<T> GetCurrent(const PrototypeIterator& iterator) { |
| DCHECK(!iterator.handle_.is_null()); |
| DCHECK_NULL(iterator.object_); |
| return Handle<T>::cast(iterator.handle_); |
| } |
| |
| void Advance() { |
| if (handle_.is_null() && object_->IsJSProxy()) { |
| is_at_end_ = true; |
| object_ = isolate_->heap()->null_value(); |
| return; |
| } else if (!handle_.is_null() && handle_->IsJSProxy()) { |
| is_at_end_ = true; |
| handle_ = isolate_->factory()->null_value(); |
| return; |
| } |
| AdvanceIgnoringProxies(); |
| } |
| |
| void AdvanceIgnoringProxies() { |
| Object* object = handle_.is_null() ? object_ : *handle_; |
| Map* map = HeapObject::cast(object)->map(); |
| |
| Object* prototype = map->prototype(); |
| is_at_end_ = where_to_end_ == END_AT_NON_HIDDEN |
| ? !map->has_hidden_prototype() |
| : prototype->IsNull(isolate_); |
| |
| if (handle_.is_null()) { |
| object_ = prototype; |
| } else { |
| handle_ = handle(prototype, isolate_); |
| } |
| } |
| |
| // Returns false iff a call to JSProxy::GetPrototype throws. |
| // TODO(neis): This should probably replace Advance(). |
| MUST_USE_RESULT bool AdvanceFollowingProxies() { |
| DCHECK(!(handle_.is_null() && object_->IsJSProxy())); |
| if (!HasAccess()) { |
| // Abort the lookup if we do not have access to the current object. |
| handle_ = isolate_->factory()->null_value(); |
| is_at_end_ = true; |
| return true; |
| } |
| return AdvanceFollowingProxiesIgnoringAccessChecks(); |
| } |
| |
| MUST_USE_RESULT bool AdvanceFollowingProxiesIgnoringAccessChecks() { |
| if (handle_.is_null() || !handle_->IsJSProxy()) { |
| AdvanceIgnoringProxies(); |
| return true; |
| } |
| |
| // Due to possible __proto__ recursion limit the number of Proxies |
| // we visit to an arbitrarily chosen large number. |
| seen_proxies_++; |
| if (seen_proxies_ > JSProxy::kMaxIterationLimit) { |
| isolate_->StackOverflow(); |
| return false; |
| } |
| MaybeHandle<Object> proto = |
| JSProxy::GetPrototype(Handle<JSProxy>::cast(handle_)); |
| if (!proto.ToHandle(&handle_)) return false; |
| is_at_end_ = |
| where_to_end_ == END_AT_NON_HIDDEN || handle_->IsNull(isolate_); |
| return true; |
| } |
| |
| bool IsAtEnd() const { return is_at_end_; } |
| Isolate* isolate() const { return isolate_; } |
| |
| private: |
| Isolate* isolate_; |
| Object* object_; |
| Handle<Object> handle_; |
| WhereToEnd where_to_end_; |
| bool is_at_end_; |
| int seen_proxies_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PrototypeIterator); |
| }; |
| |
| |
| } // namespace internal |
| |
| } // namespace v8 |
| |
| #endif // V8_PROTOTYPE_H_ |