// Copyright 2018 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_INL_H_
#define V8_PROTOTYPE_INL_H_

#include "src/prototype.h"

#include "src/handles-inl.h"
#include "src/objects/js-proxy.h"
#include "src/objects/map-inl.h"

namespace v8 {
namespace internal {

PrototypeIterator::PrototypeIterator(Isolate* isolate,
                                     Handle<JSReceiver> receiver,
                                     WhereToStart where_to_start,
                                     WhereToEnd where_to_end)
    : isolate_(isolate),
      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::PrototypeIterator(Isolate* isolate, JSReceiver receiver,
                                     WhereToStart where_to_start,
                                     WhereToEnd where_to_end)
    : isolate_(isolate),
      object_(receiver),
      where_to_end_(where_to_end),
      is_at_end_(false),
      seen_proxies_(0) {
  if (where_to_start == kStartAtPrototype) Advance();
}

PrototypeIterator::PrototypeIterator(Isolate* isolate, Map receiver_map,
                                     WhereToEnd where_to_end)
    : isolate_(isolate),
      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();
  }
}

PrototypeIterator::PrototypeIterator(Isolate* isolate, Handle<Map> receiver_map,
                                     WhereToEnd where_to_end)
    : isolate_(isolate),
      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();
  }
}

bool PrototypeIterator::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(), isolate_),
                               Handle<JSObject>::cast(handle_));
  }
  return true;
}

void PrototypeIterator::Advance() {
  if (handle_.is_null() && object_->IsJSProxy()) {
    is_at_end_ = true;
    object_ = ReadOnlyRoots(isolate_).null_value();
    return;
  } else if (!handle_.is_null() && handle_->IsJSProxy()) {
    is_at_end_ = true;
    handle_ = isolate_->factory()->null_value();
    return;
  }
  AdvanceIgnoringProxies();
}

void PrototypeIterator::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_);
  }
}

V8_WARN_UNUSED_RESULT bool PrototypeIterator::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();
}

V8_WARN_UNUSED_RESULT bool
PrototypeIterator::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;
}

}  // namespace internal
}  // namespace v8

#endif  // V8_PROTOTYPE_INL_H_