// 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