// Copyright 2011 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/objects/property-descriptor.h"

#include "src/execution/isolate-inl.h"
#include "src/heap/factory.h"
#include "src/heap/heap-inl.h"  // For ToBoolean. TODO(jkummerow): Drop.
#include "src/init/bootstrapper.h"
#include "src/objects/lookup.h"
#include "src/objects/objects-inl.h"
#include "src/objects/property-descriptor-object-inl.h"

namespace v8 {
namespace internal {

namespace {

// Helper function for ToPropertyDescriptor. Comments describe steps for
// "enumerable", other properties are handled the same way.
// Returns false if an exception was thrown.
bool GetPropertyIfPresent(Handle<JSReceiver> receiver, Handle<String> name,
                          Handle<Object>* value) {
  LookupIterator it(receiver->GetIsolate(), receiver, name, receiver);
  // 4. Let hasEnumerable be HasProperty(Obj, "enumerable").
  Maybe<bool> has_property = JSReceiver::HasProperty(&it);
  // 5. ReturnIfAbrupt(hasEnumerable).
  if (has_property.IsNothing()) return false;
  // 6. If hasEnumerable is true, then
  if (has_property.FromJust() == true) {
    // 6a. Let enum be ToBoolean(Get(Obj, "enumerable")).
    // 6b. ReturnIfAbrupt(enum).
    if (!Object::GetProperty(&it).ToHandle(value)) return false;
  }
  return true;
}

// Helper function for ToPropertyDescriptor. Handles the case of "simple"
// objects: nothing on the prototype chain, just own fast data properties.
// Must not have observable side effects, because the slow path will restart
// the entire conversion!
bool ToPropertyDescriptorFastPath(Isolate* isolate, Handle<JSReceiver> obj,
                                  PropertyDescriptor* desc) {
  if (!obj->IsJSObject()) return false;
  Handle<Map> map(Handle<JSObject>::cast(obj)->map(), isolate);
  if (map->instance_type() != JS_OBJECT_TYPE) return false;
  if (map->is_access_check_needed()) return false;
  if (map->prototype() != *isolate->initial_object_prototype()) return false;
  // During bootstrapping, the object_function_prototype_map hasn't been
  // set up yet.
  if (isolate->bootstrapper()->IsActive()) return false;
  if (JSObject::cast(map->prototype()).map() !=
      isolate->native_context()->object_function_prototype_map()) {
    return false;
  }
  // TODO(jkummerow): support dictionary properties?
  if (map->is_dictionary_map()) return false;
  Handle<DescriptorArray> descs =
      Handle<DescriptorArray>(map->instance_descriptors(), isolate);
  for (InternalIndex i : map->IterateOwnDescriptors()) {
    PropertyDetails details = descs->GetDetails(i);
    Handle<Object> value;
    if (details.location() == kField) {
      if (details.kind() == kData) {
        value = JSObject::FastPropertyAt(Handle<JSObject>::cast(obj),
                                         details.representation(),
                                         FieldIndex::ForDescriptor(*map, i));
      } else {
        DCHECK_EQ(kAccessor, details.kind());
        // Bail out to slow path.
        return false;
      }

    } else {
      DCHECK_EQ(kDescriptor, details.location());
      if (details.kind() == kData) {
        value = handle(descs->GetStrongValue(i), isolate);
      } else {
        DCHECK_EQ(kAccessor, details.kind());
        // Bail out to slow path.
        return false;
      }
    }
    Name key = descs->GetKey(i);
    ReadOnlyRoots roots(isolate);
    if (key == roots.enumerable_string()) {
      desc->set_enumerable(value->BooleanValue(isolate));
    } else if (key == roots.configurable_string()) {
      desc->set_configurable(value->BooleanValue(isolate));
    } else if (key == roots.value_string()) {
      desc->set_value(value);
    } else if (key == roots.writable_string()) {
      desc->set_writable(value->BooleanValue(isolate));
    } else if (key == roots.get_string()) {
      // Bail out to slow path to throw an exception if necessary.
      if (!value->IsCallable()) return false;
      desc->set_get(value);
    } else if (key == roots.set_string()) {
      // Bail out to slow path to throw an exception if necessary.
      if (!value->IsCallable()) return false;
      desc->set_set(value);
    }
  }
  if ((desc->has_get() || desc->has_set()) &&
      (desc->has_value() || desc->has_writable())) {
    // Bail out to slow path to throw an exception.
    return false;
  }
  return true;
}

void CreateDataProperty(Handle<JSObject> object, Handle<String> name,
                        Handle<Object> value) {
  LookupIterator it(object->GetIsolate(), object, name, object,
                    LookupIterator::OWN_SKIP_INTERCEPTOR);
  Maybe<bool> result = JSObject::CreateDataProperty(&it, value);
  CHECK(result.IsJust() && result.FromJust());
}

}  // namespace

// ES6 6.2.4.4 "FromPropertyDescriptor"
Handle<Object> PropertyDescriptor::ToObject(Isolate* isolate) {
  DCHECK(!(PropertyDescriptor::IsAccessorDescriptor(this) &&
           PropertyDescriptor::IsDataDescriptor(this)));
  Factory* factory = isolate->factory();
  if (IsRegularAccessorProperty()) {
    // Fast case for regular accessor properties.
    Handle<JSObject> result = factory->NewJSObjectFromMap(
        isolate->accessor_property_descriptor_map());
    result->InObjectPropertyAtPut(JSAccessorPropertyDescriptor::kGetIndex,
                                  *get());
    result->InObjectPropertyAtPut(JSAccessorPropertyDescriptor::kSetIndex,
                                  *set());
    result->InObjectPropertyAtPut(
        JSAccessorPropertyDescriptor::kEnumerableIndex,
        isolate->heap()->ToBoolean(enumerable()));
    result->InObjectPropertyAtPut(
        JSAccessorPropertyDescriptor::kConfigurableIndex,
        isolate->heap()->ToBoolean(configurable()));
    return result;
  }
  if (IsRegularDataProperty()) {
    // Fast case for regular data properties.
    Handle<JSObject> result =
        factory->NewJSObjectFromMap(isolate->data_property_descriptor_map());
    result->InObjectPropertyAtPut(JSDataPropertyDescriptor::kValueIndex,
                                  *value());
    result->InObjectPropertyAtPut(JSDataPropertyDescriptor::kWritableIndex,
                                  isolate->heap()->ToBoolean(writable()));
    result->InObjectPropertyAtPut(JSDataPropertyDescriptor::kEnumerableIndex,
                                  isolate->heap()->ToBoolean(enumerable()));
    result->InObjectPropertyAtPut(JSDataPropertyDescriptor::kConfigurableIndex,
                                  isolate->heap()->ToBoolean(configurable()));
    return result;
  }
  Handle<JSObject> result = factory->NewJSObject(isolate->object_function());
  if (has_value()) {
    CreateDataProperty(result, factory->value_string(), value());
  }
  if (has_writable()) {
    CreateDataProperty(result, factory->writable_string(),
                       factory->ToBoolean(writable()));
  }
  if (has_get()) {
    CreateDataProperty(result, factory->get_string(), get());
  }
  if (has_set()) {
    CreateDataProperty(result, factory->set_string(), set());
  }
  if (has_enumerable()) {
    CreateDataProperty(result, factory->enumerable_string(),
                       factory->ToBoolean(enumerable()));
  }
  if (has_configurable()) {
    CreateDataProperty(result, factory->configurable_string(),
                       factory->ToBoolean(configurable()));
  }
  return result;
}

// ES6 6.2.4.5
// Returns false in case of exception.
// static
bool PropertyDescriptor::ToPropertyDescriptor(Isolate* isolate,
                                              Handle<Object> obj,
                                              PropertyDescriptor* desc) {
  // 1. ReturnIfAbrupt(Obj).
  // 2. If Type(Obj) is not Object, throw a TypeError exception.
  if (!obj->IsJSReceiver()) {
    isolate->Throw(*isolate->factory()->NewTypeError(
        MessageTemplate::kPropertyDescObject, obj));
    return false;
  }
  // 3. Let desc be a new Property Descriptor that initially has no fields.
  DCHECK(desc->is_empty());

  Handle<JSReceiver> receiver = Handle<JSReceiver>::cast(obj);
  if (ToPropertyDescriptorFastPath(isolate, receiver, desc)) {
    return true;
  }

  // enumerable?
  Handle<Object> enumerable;
  // 4 through 6b.
  if (!GetPropertyIfPresent(receiver, isolate->factory()->enumerable_string(),
                            &enumerable)) {
    return false;
  }
  // 6c. Set the [[Enumerable]] field of desc to enum.
  if (!enumerable.is_null()) {
    desc->set_enumerable(enumerable->BooleanValue(isolate));
  }

  // configurable?
  Handle<Object> configurable;
  // 7 through 9b.
  if (!GetPropertyIfPresent(receiver, isolate->factory()->configurable_string(),
                            &configurable)) {
    return false;
  }
  // 9c. Set the [[Configurable]] field of desc to conf.
  if (!configurable.is_null()) {
    desc->set_configurable(configurable->BooleanValue(isolate));
  }

  // value?
  Handle<Object> value;
  // 10 through 12b.
  if (!GetPropertyIfPresent(receiver, isolate->factory()->value_string(),
                            &value)) {
    return false;
  }
  // 12c. Set the [[Value]] field of desc to value.
  if (!value.is_null()) desc->set_value(value);

  // writable?
  Handle<Object> writable;
  // 13 through 15b.
  if (!GetPropertyIfPresent(receiver, isolate->factory()->writable_string(),
                            &writable)) {
    return false;
  }
  // 15c. Set the [[Writable]] field of desc to writable.
  if (!writable.is_null()) desc->set_writable(writable->BooleanValue(isolate));

  // getter?
  Handle<Object> getter;
  // 16 through 18b.
  if (!GetPropertyIfPresent(receiver, isolate->factory()->get_string(),
                            &getter)) {
    return false;
  }
  if (!getter.is_null()) {
    // 18c. If IsCallable(getter) is false and getter is not undefined,
    // throw a TypeError exception.
    if (!getter->IsCallable() && !getter->IsUndefined(isolate)) {
      isolate->Throw(*isolate->factory()->NewTypeError(
          MessageTemplate::kObjectGetterCallable, getter));
      return false;
    }
    // 18d. Set the [[Get]] field of desc to getter.
    desc->set_get(getter);
  }
  // setter?
  Handle<Object> setter;
  // 19 through 21b.
  if (!GetPropertyIfPresent(receiver, isolate->factory()->set_string(),
                            &setter)) {
    return false;
  }
  if (!setter.is_null()) {
    // 21c. If IsCallable(setter) is false and setter is not undefined,
    // throw a TypeError exception.
    if (!setter->IsCallable() && !setter->IsUndefined(isolate)) {
      isolate->Throw(*isolate->factory()->NewTypeError(
          MessageTemplate::kObjectSetterCallable, setter));
      return false;
    }
    // 21d. Set the [[Set]] field of desc to setter.
    desc->set_set(setter);
  }

  // 22. If either desc.[[Get]] or desc.[[Set]] is present, then
  // 22a. If either desc.[[Value]] or desc.[[Writable]] is present,
  // throw a TypeError exception.
  if ((desc->has_get() || desc->has_set()) &&
      (desc->has_value() || desc->has_writable())) {
    isolate->Throw(*isolate->factory()->NewTypeError(
        MessageTemplate::kValueAndAccessor, obj));
    return false;
  }

  // 23. Return desc.
  return true;
}

// ES6 6.2.4.6
// static
void PropertyDescriptor::CompletePropertyDescriptor(Isolate* isolate,
                                                    PropertyDescriptor* desc) {
  // 1. ReturnIfAbrupt(Desc).
  // 2. Assert: Desc is a Property Descriptor.
  // 3. Let like be Record{
  //        [[Value]]: undefined, [[Writable]]: false,
  //        [[Get]]: undefined, [[Set]]: undefined,
  //        [[Enumerable]]: false, [[Configurable]]: false}.
  // 4. If either IsGenericDescriptor(Desc) or IsDataDescriptor(Desc) is true,
  // then:
  if (!IsAccessorDescriptor(desc)) {
    // 4a. If Desc does not have a [[Value]] field, set Desc.[[Value]] to
    //     like.[[Value]].
    if (!desc->has_value()) {
      desc->set_value(isolate->factory()->undefined_value());
    }
    // 4b. If Desc does not have a [[Writable]] field, set Desc.[[Writable]]
    //     to like.[[Writable]].
    if (!desc->has_writable()) desc->set_writable(false);
  } else {
    // 5. Else,
    // 5a. If Desc does not have a [[Get]] field, set Desc.[[Get]] to
    //     like.[[Get]].
    if (!desc->has_get()) {
      desc->set_get(isolate->factory()->undefined_value());
    }
    // 5b. If Desc does not have a [[Set]] field, set Desc.[[Set]] to
    //     like.[[Set]].
    if (!desc->has_set()) {
      desc->set_set(isolate->factory()->undefined_value());
    }
  }
  // 6. If Desc does not have an [[Enumerable]] field, set
  //    Desc.[[Enumerable]] to like.[[Enumerable]].
  if (!desc->has_enumerable()) desc->set_enumerable(false);
  // 7. If Desc does not have a [[Configurable]] field, set
  //    Desc.[[Configurable]] to like.[[Configurable]].
  if (!desc->has_configurable()) desc->set_configurable(false);
  // 8. Return Desc.
}

Handle<PropertyDescriptorObject> PropertyDescriptor::ToPropertyDescriptorObject(
    Isolate* isolate) {
  Handle<PropertyDescriptorObject> obj =
      isolate->factory()->NewPropertyDescriptorObject();

  int flags =
      PropertyDescriptorObject::IsEnumerableBit::encode(enumerable_) |
      PropertyDescriptorObject::HasEnumerableBit::encode(has_enumerable_) |
      PropertyDescriptorObject::IsConfigurableBit::encode(configurable_) |
      PropertyDescriptorObject::HasConfigurableBit::encode(has_configurable_) |
      PropertyDescriptorObject::IsWritableBit::encode(writable_) |
      PropertyDescriptorObject::HasWritableBit::encode(has_writable_) |
      PropertyDescriptorObject::HasValueBit::encode(has_value()) |
      PropertyDescriptorObject::HasGetBit::encode(has_get()) |
      PropertyDescriptorObject::HasSetBit::encode(has_set());

  obj->set_flags(flags);

  if (has_value()) obj->set_value(*value_);
  if (has_get()) obj->set_get(*get_);
  if (has_set()) obj->set_set(*set_);

  return obj;
}

}  // namespace internal
}  // namespace v8