| // Copyright 2015 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/api-natives.h" |
| |
| #include "src/api.h" |
| #include "src/isolate-inl.h" |
| #include "src/lookup.h" |
| #include "src/messages.h" |
| |
| namespace v8 { |
| namespace internal { |
| |
| |
| namespace { |
| |
| MaybeHandle<JSObject> InstantiateObject(Isolate* isolate, |
| Handle<ObjectTemplateInfo> data); |
| |
| |
| MaybeHandle<JSFunction> InstantiateFunction(Isolate* isolate, |
| Handle<FunctionTemplateInfo> data, |
| Handle<Name> name = Handle<Name>()); |
| |
| |
| MaybeHandle<Object> Instantiate(Isolate* isolate, Handle<Object> data, |
| Handle<Name> name = Handle<Name>()) { |
| if (data->IsFunctionTemplateInfo()) { |
| return InstantiateFunction(isolate, |
| Handle<FunctionTemplateInfo>::cast(data), name); |
| } else if (data->IsObjectTemplateInfo()) { |
| return InstantiateObject(isolate, Handle<ObjectTemplateInfo>::cast(data)); |
| } else { |
| return data; |
| } |
| } |
| |
| |
| MaybeHandle<Object> DefineAccessorProperty(Isolate* isolate, |
| Handle<JSObject> object, |
| Handle<Name> name, |
| Handle<Object> getter, |
| Handle<Object> setter, |
| PropertyAttributes attributes) { |
| if (!getter->IsUndefined()) { |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, getter, |
| InstantiateFunction(isolate, |
| Handle<FunctionTemplateInfo>::cast(getter)), |
| Object); |
| } |
| if (!setter->IsUndefined()) { |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, setter, |
| InstantiateFunction(isolate, |
| Handle<FunctionTemplateInfo>::cast(setter)), |
| Object); |
| } |
| RETURN_ON_EXCEPTION(isolate, JSObject::DefineAccessor(object, name, getter, |
| setter, attributes), |
| Object); |
| return object; |
| } |
| |
| |
| MaybeHandle<Object> DefineDataProperty(Isolate* isolate, |
| Handle<JSObject> object, |
| Handle<Name> name, |
| Handle<Object> prop_data, |
| PropertyAttributes attributes) { |
| Handle<Object> value; |
| ASSIGN_RETURN_ON_EXCEPTION(isolate, value, |
| Instantiate(isolate, prop_data, name), Object); |
| |
| LookupIterator it = LookupIterator::PropertyOrElement( |
| isolate, object, name, LookupIterator::OWN_SKIP_INTERCEPTOR); |
| |
| #ifdef DEBUG |
| Maybe<PropertyAttributes> maybe = JSReceiver::GetPropertyAttributes(&it); |
| DCHECK(maybe.IsJust()); |
| if (it.IsFound()) { |
| THROW_NEW_ERROR( |
| isolate, |
| NewTypeError(MessageTemplate::kDuplicateTemplateProperty, name), |
| Object); |
| } |
| #endif |
| |
| MAYBE_RETURN_NULL( |
| Object::AddDataProperty(&it, value, attributes, Object::THROW_ON_ERROR, |
| Object::CERTAINLY_NOT_STORE_FROM_KEYED)); |
| return value; |
| } |
| |
| |
| void DisableAccessChecks(Isolate* isolate, Handle<JSObject> object) { |
| Handle<Map> old_map(object->map()); |
| // Copy map so it won't interfere constructor's initial map. |
| Handle<Map> new_map = Map::Copy(old_map, "DisableAccessChecks"); |
| new_map->set_is_access_check_needed(false); |
| JSObject::MigrateToMap(Handle<JSObject>::cast(object), new_map); |
| } |
| |
| |
| void EnableAccessChecks(Isolate* isolate, Handle<JSObject> object) { |
| Handle<Map> old_map(object->map()); |
| // Copy map so it won't interfere constructor's initial map. |
| Handle<Map> new_map = Map::Copy(old_map, "EnableAccessChecks"); |
| new_map->set_is_access_check_needed(true); |
| JSObject::MigrateToMap(object, new_map); |
| } |
| |
| |
| class AccessCheckDisableScope { |
| public: |
| AccessCheckDisableScope(Isolate* isolate, Handle<JSObject> obj) |
| : isolate_(isolate), |
| disabled_(obj->map()->is_access_check_needed()), |
| obj_(obj) { |
| if (disabled_) { |
| DisableAccessChecks(isolate_, obj_); |
| } |
| } |
| ~AccessCheckDisableScope() { |
| if (disabled_) { |
| EnableAccessChecks(isolate_, obj_); |
| } |
| } |
| |
| private: |
| Isolate* isolate_; |
| const bool disabled_; |
| Handle<JSObject> obj_; |
| }; |
| |
| |
| Object* GetIntrinsic(Isolate* isolate, v8::Intrinsic intrinsic) { |
| Handle<Context> native_context = isolate->native_context(); |
| DCHECK(!native_context.is_null()); |
| switch (intrinsic) { |
| #define GET_INTRINSIC_VALUE(name, iname) \ |
| case v8::k##name: \ |
| return native_context->iname(); |
| V8_INTRINSICS_LIST(GET_INTRINSIC_VALUE) |
| #undef GET_INTRINSIC_VALUE |
| } |
| return nullptr; |
| } |
| |
| |
| MaybeHandle<JSObject> ConfigureInstance(Isolate* isolate, Handle<JSObject> obj, |
| Handle<TemplateInfo> data) { |
| auto property_list = handle(data->property_list(), isolate); |
| if (property_list->IsUndefined()) return obj; |
| // TODO(dcarney): just use a FixedArray here. |
| NeanderArray properties(property_list); |
| if (properties.length() == 0) return obj; |
| HandleScope scope(isolate); |
| // Disable access checks while instantiating the object. |
| AccessCheckDisableScope access_check_scope(isolate, obj); |
| |
| int i = 0; |
| for (int c = 0; c < data->number_of_properties(); c++) { |
| auto name = handle(Name::cast(properties.get(i++)), isolate); |
| auto bit = handle(properties.get(i++), isolate); |
| if (bit->IsSmi()) { |
| PropertyDetails details(Smi::cast(*bit)); |
| PropertyAttributes attributes = details.attributes(); |
| PropertyKind kind = details.kind(); |
| |
| if (kind == kData) { |
| auto prop_data = handle(properties.get(i++), isolate); |
| |
| RETURN_ON_EXCEPTION(isolate, DefineDataProperty(isolate, obj, name, |
| prop_data, attributes), |
| JSObject); |
| } else { |
| auto getter = handle(properties.get(i++), isolate); |
| auto setter = handle(properties.get(i++), isolate); |
| RETURN_ON_EXCEPTION(isolate, |
| DefineAccessorProperty(isolate, obj, name, getter, |
| setter, attributes), |
| JSObject); |
| } |
| } else { |
| // Intrinsic data property --- Get appropriate value from the current |
| // context. |
| PropertyDetails details(Smi::cast(properties.get(i++))); |
| PropertyAttributes attributes = details.attributes(); |
| DCHECK_EQ(kData, details.kind()); |
| |
| v8::Intrinsic intrinsic = |
| static_cast<v8::Intrinsic>(Smi::cast(properties.get(i++))->value()); |
| auto prop_data = handle(GetIntrinsic(isolate, intrinsic), isolate); |
| |
| RETURN_ON_EXCEPTION(isolate, DefineDataProperty(isolate, obj, name, |
| prop_data, attributes), |
| JSObject); |
| } |
| } |
| return obj; |
| } |
| |
| |
| MaybeHandle<JSObject> InstantiateObject(Isolate* isolate, |
| Handle<ObjectTemplateInfo> data) { |
| // Enter a new scope. Recursion could otherwise create a lot of handles. |
| HandleScope scope(isolate); |
| // Fast path. |
| Handle<JSObject> result; |
| auto info = Handle<ObjectTemplateInfo>::cast(data); |
| auto constructor = handle(info->constructor(), isolate); |
| Handle<JSFunction> cons; |
| if (constructor->IsUndefined()) { |
| cons = isolate->object_function(); |
| } else { |
| auto cons_templ = Handle<FunctionTemplateInfo>::cast(constructor); |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, cons, InstantiateFunction(isolate, cons_templ), JSFunction); |
| } |
| auto object = isolate->factory()->NewJSObject(cons); |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, result, ConfigureInstance(isolate, object, info), JSFunction); |
| // TODO(dcarney): is this necessary? |
| JSObject::MigrateSlowToFast(result, 0, "ApiNatives::InstantiateObject"); |
| return scope.CloseAndEscape(result); |
| } |
| |
| |
| void CacheFunction(Isolate* isolate, Handle<Smi> serial_number, |
| Handle<JSFunction> function) { |
| auto cache = isolate->function_cache(); |
| auto new_cache = ObjectHashTable::Put(cache, serial_number, function); |
| isolate->native_context()->set_function_cache(*new_cache); |
| } |
| |
| |
| void UncacheFunction(Isolate* isolate, Handle<Smi> serial_number) { |
| auto cache = isolate->function_cache(); |
| bool was_present = false; |
| auto new_cache = ObjectHashTable::Remove(cache, serial_number, &was_present); |
| DCHECK(was_present); |
| isolate->native_context()->set_function_cache(*new_cache); |
| } |
| |
| |
| MaybeHandle<JSFunction> InstantiateFunction(Isolate* isolate, |
| Handle<FunctionTemplateInfo> data, |
| Handle<Name> name) { |
| auto serial_number = handle(Smi::cast(data->serial_number()), isolate); |
| // Probe cache. |
| if (!data->do_not_cache()) { |
| auto cache = isolate->function_cache(); |
| Object* element = cache->Lookup(serial_number); |
| if (element->IsJSFunction()) { |
| return handle(JSFunction::cast(element), isolate); |
| } |
| } |
| // Enter a new scope. Recursion could otherwise create a lot of handles. |
| HandleScope scope(isolate); |
| Handle<JSObject> prototype; |
| if (!data->remove_prototype()) { |
| auto prototype_templ = handle(data->prototype_template(), isolate); |
| if (prototype_templ->IsUndefined()) { |
| prototype = isolate->factory()->NewJSObject(isolate->object_function()); |
| } else { |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, prototype, |
| InstantiateObject(isolate, |
| Handle<ObjectTemplateInfo>::cast(prototype_templ)), |
| JSFunction); |
| } |
| auto parent = handle(data->parent_template(), isolate); |
| if (!parent->IsUndefined()) { |
| Handle<JSFunction> parent_instance; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, parent_instance, |
| InstantiateFunction(isolate, |
| Handle<FunctionTemplateInfo>::cast(parent)), |
| JSFunction); |
| // TODO(dcarney): decide what to do here. |
| Handle<Object> parent_prototype; |
| ASSIGN_RETURN_ON_EXCEPTION( |
| isolate, parent_prototype, |
| JSObject::GetProperty(parent_instance, |
| isolate->factory()->prototype_string()), |
| JSFunction); |
| MAYBE_RETURN(JSObject::SetPrototype(prototype, parent_prototype, false, |
| Object::THROW_ON_ERROR), |
| MaybeHandle<JSFunction>()); |
| } |
| } |
| auto function = ApiNatives::CreateApiFunction( |
| isolate, data, prototype, ApiNatives::JavaScriptObjectType); |
| if (!name.is_null() && name->IsString()) { |
| function->shared()->set_name(*name); |
| } |
| if (!data->do_not_cache()) { |
| // Cache the function. |
| CacheFunction(isolate, serial_number, function); |
| } |
| auto result = ConfigureInstance(isolate, function, data); |
| if (result.is_null()) { |
| // Uncache on error. |
| if (!data->do_not_cache()) { |
| UncacheFunction(isolate, serial_number); |
| } |
| return MaybeHandle<JSFunction>(); |
| } |
| return scope.CloseAndEscape(function); |
| } |
| |
| |
| class InvokeScope { |
| public: |
| explicit InvokeScope(Isolate* isolate) |
| : isolate_(isolate), save_context_(isolate) {} |
| ~InvokeScope() { |
| bool has_exception = isolate_->has_pending_exception(); |
| if (has_exception) { |
| isolate_->ReportPendingMessages(); |
| } else { |
| isolate_->clear_pending_message(); |
| } |
| } |
| |
| private: |
| Isolate* isolate_; |
| SaveContext save_context_; |
| }; |
| |
| |
| void AddPropertyToPropertyList(Isolate* isolate, Handle<TemplateInfo> templ, |
| int length, Handle<Object>* data) { |
| auto list = handle(templ->property_list(), isolate); |
| if (list->IsUndefined()) { |
| list = NeanderArray(isolate).value(); |
| templ->set_property_list(*list); |
| } |
| templ->set_number_of_properties(templ->number_of_properties() + 1); |
| NeanderArray array(list); |
| for (int i = 0; i < length; i++) { |
| Handle<Object> value = |
| data[i].is_null() |
| ? Handle<Object>::cast(isolate->factory()->undefined_value()) |
| : data[i]; |
| array.add(isolate, value); |
| } |
| } |
| |
| } // namespace |
| |
| |
| MaybeHandle<JSFunction> ApiNatives::InstantiateFunction( |
| Handle<FunctionTemplateInfo> data) { |
| Isolate* isolate = data->GetIsolate(); |
| InvokeScope invoke_scope(isolate); |
| return ::v8::internal::InstantiateFunction(isolate, data); |
| } |
| |
| |
| MaybeHandle<JSObject> ApiNatives::InstantiateObject( |
| Handle<ObjectTemplateInfo> data) { |
| Isolate* isolate = data->GetIsolate(); |
| InvokeScope invoke_scope(isolate); |
| return ::v8::internal::InstantiateObject(isolate, data); |
| } |
| |
| |
| MaybeHandle<FunctionTemplateInfo> ApiNatives::ConfigureInstance( |
| Isolate* isolate, Handle<FunctionTemplateInfo> desc, |
| Handle<JSObject> instance) { |
| // Configure the instance by adding the properties specified by the |
| // instance template. |
| if (desc->instance_template()->IsUndefined()) return desc; |
| InvokeScope invoke_scope(isolate); |
| Handle<ObjectTemplateInfo> instance_template( |
| ObjectTemplateInfo::cast(desc->instance_template()), isolate); |
| RETURN_ON_EXCEPTION(isolate, ::v8::internal::ConfigureInstance( |
| isolate, instance, instance_template), |
| FunctionTemplateInfo); |
| return desc; |
| } |
| |
| |
| void ApiNatives::AddDataProperty(Isolate* isolate, Handle<TemplateInfo> info, |
| Handle<Name> name, Handle<Object> value, |
| PropertyAttributes attributes) { |
| const int kSize = 3; |
| PropertyDetails details(attributes, DATA, 0, PropertyCellType::kNoCell); |
| auto details_handle = handle(details.AsSmi(), isolate); |
| Handle<Object> data[kSize] = {name, details_handle, value}; |
| AddPropertyToPropertyList(isolate, info, kSize, data); |
| } |
| |
| |
| void ApiNatives::AddDataProperty(Isolate* isolate, Handle<TemplateInfo> info, |
| Handle<Name> name, v8::Intrinsic intrinsic, |
| PropertyAttributes attributes) { |
| const int kSize = 4; |
| auto value = handle(Smi::FromInt(intrinsic), isolate); |
| auto intrinsic_marker = isolate->factory()->true_value(); |
| PropertyDetails details(attributes, DATA, 0, PropertyCellType::kNoCell); |
| auto details_handle = handle(details.AsSmi(), isolate); |
| Handle<Object> data[kSize] = {name, intrinsic_marker, details_handle, value}; |
| AddPropertyToPropertyList(isolate, info, kSize, data); |
| } |
| |
| |
| void ApiNatives::AddAccessorProperty(Isolate* isolate, |
| Handle<TemplateInfo> info, |
| Handle<Name> name, |
| Handle<FunctionTemplateInfo> getter, |
| Handle<FunctionTemplateInfo> setter, |
| PropertyAttributes attributes) { |
| const int kSize = 4; |
| PropertyDetails details(attributes, ACCESSOR, 0, PropertyCellType::kNoCell); |
| auto details_handle = handle(details.AsSmi(), isolate); |
| Handle<Object> data[kSize] = {name, details_handle, getter, setter}; |
| AddPropertyToPropertyList(isolate, info, kSize, data); |
| } |
| |
| |
| void ApiNatives::AddNativeDataProperty(Isolate* isolate, |
| Handle<TemplateInfo> info, |
| Handle<AccessorInfo> property) { |
| auto list = handle(info->property_accessors(), isolate); |
| if (list->IsUndefined()) { |
| list = NeanderArray(isolate).value(); |
| info->set_property_accessors(*list); |
| } |
| NeanderArray array(list); |
| array.add(isolate, property); |
| } |
| |
| |
| Handle<JSFunction> ApiNatives::CreateApiFunction( |
| Isolate* isolate, Handle<FunctionTemplateInfo> obj, |
| Handle<Object> prototype, ApiInstanceType instance_type) { |
| Handle<Code> code; |
| if (obj->call_code()->IsCallHandlerInfo() && |
| CallHandlerInfo::cast(obj->call_code())->fast_handler()->IsCode()) { |
| code = isolate->builtins()->HandleFastApiCall(); |
| } else { |
| code = isolate->builtins()->HandleApiCall(); |
| } |
| Handle<Code> construct_stub = |
| prototype.is_null() ? isolate->builtins()->ConstructedNonConstructable() |
| : isolate->builtins()->JSConstructStubApi(); |
| |
| obj->set_instantiated(true); |
| Handle<JSFunction> result; |
| if (obj->remove_prototype()) { |
| result = isolate->factory()->NewFunctionWithoutPrototype( |
| isolate->factory()->empty_string(), code); |
| } else { |
| int internal_field_count = 0; |
| if (!obj->instance_template()->IsUndefined()) { |
| Handle<ObjectTemplateInfo> instance_template = Handle<ObjectTemplateInfo>( |
| ObjectTemplateInfo::cast(obj->instance_template())); |
| internal_field_count = |
| Smi::cast(instance_template->internal_field_count())->value(); |
| } |
| |
| // TODO(svenpanne) Kill ApiInstanceType and refactor things by generalizing |
| // JSObject::GetHeaderSize. |
| int instance_size = kPointerSize * internal_field_count; |
| InstanceType type; |
| switch (instance_type) { |
| case JavaScriptObjectType: |
| type = JS_OBJECT_TYPE; |
| instance_size += JSObject::kHeaderSize; |
| break; |
| case GlobalObjectType: |
| type = JS_GLOBAL_OBJECT_TYPE; |
| instance_size += JSGlobalObject::kSize; |
| break; |
| case GlobalProxyType: |
| type = JS_GLOBAL_PROXY_TYPE; |
| instance_size += JSGlobalProxy::kSize; |
| break; |
| default: |
| UNREACHABLE(); |
| type = JS_OBJECT_TYPE; // Keep the compiler happy. |
| break; |
| } |
| |
| result = isolate->factory()->NewFunction( |
| isolate->factory()->empty_string(), code, prototype, type, |
| instance_size, obj->read_only_prototype(), true); |
| } |
| |
| result->shared()->set_length(obj->length()); |
| Handle<Object> class_name(obj->class_name(), isolate); |
| if (class_name->IsString()) { |
| result->shared()->set_instance_class_name(*class_name); |
| result->shared()->set_name(*class_name); |
| } |
| result->shared()->set_function_data(*obj); |
| result->shared()->set_construct_stub(*construct_stub); |
| result->shared()->DontAdaptArguments(); |
| |
| if (obj->remove_prototype()) { |
| DCHECK(result->shared()->IsApiFunction()); |
| DCHECK(!result->has_initial_map()); |
| DCHECK(!result->has_prototype()); |
| return result; |
| } |
| |
| #ifdef DEBUG |
| LookupIterator it(handle(JSObject::cast(result->prototype())), |
| isolate->factory()->constructor_string(), |
| LookupIterator::OWN_SKIP_INTERCEPTOR); |
| MaybeHandle<Object> maybe_prop = Object::GetProperty(&it); |
| DCHECK(it.IsFound()); |
| DCHECK(maybe_prop.ToHandleChecked().is_identical_to(result)); |
| #endif |
| |
| // Down from here is only valid for API functions that can be used as a |
| // constructor (don't set the "remove prototype" flag). |
| |
| Handle<Map> map(result->initial_map()); |
| |
| // Mark as undetectable if needed. |
| if (obj->undetectable()) { |
| map->set_is_undetectable(); |
| } |
| |
| // Mark as hidden for the __proto__ accessor if needed. |
| if (obj->hidden_prototype()) { |
| map->set_is_hidden_prototype(); |
| } |
| |
| // Mark as needs_access_check if needed. |
| if (obj->needs_access_check()) { |
| map->set_is_access_check_needed(true); |
| } |
| |
| // Set interceptor information in the map. |
| if (!obj->named_property_handler()->IsUndefined()) { |
| map->set_has_named_interceptor(); |
| } |
| if (!obj->indexed_property_handler()->IsUndefined()) { |
| map->set_has_indexed_interceptor(); |
| } |
| |
| // Mark instance as callable in the map. |
| if (!obj->instance_call_handler()->IsUndefined()) { |
| map->set_is_callable(); |
| map->set_is_constructor(true); |
| } |
| |
| // Recursively copy parent instance templates' accessors, |
| // 'data' may be modified. |
| int max_number_of_additional_properties = 0; |
| int max_number_of_static_properties = 0; |
| FunctionTemplateInfo* info = *obj; |
| while (true) { |
| if (!info->instance_template()->IsUndefined()) { |
| Object* props = ObjectTemplateInfo::cast(info->instance_template()) |
| ->property_accessors(); |
| if (!props->IsUndefined()) { |
| Handle<Object> props_handle(props, isolate); |
| NeanderArray props_array(props_handle); |
| max_number_of_additional_properties += props_array.length(); |
| } |
| } |
| if (!info->property_accessors()->IsUndefined()) { |
| Object* props = info->property_accessors(); |
| if (!props->IsUndefined()) { |
| Handle<Object> props_handle(props, isolate); |
| NeanderArray props_array(props_handle); |
| max_number_of_static_properties += props_array.length(); |
| } |
| } |
| Object* parent = info->parent_template(); |
| if (parent->IsUndefined()) break; |
| info = FunctionTemplateInfo::cast(parent); |
| } |
| |
| Map::EnsureDescriptorSlack(map, max_number_of_additional_properties); |
| |
| // Use a temporary FixedArray to acculumate static accessors |
| int valid_descriptors = 0; |
| Handle<FixedArray> array; |
| if (max_number_of_static_properties > 0) { |
| array = isolate->factory()->NewFixedArray(max_number_of_static_properties); |
| } |
| |
| while (true) { |
| // Install instance descriptors |
| if (!obj->instance_template()->IsUndefined()) { |
| Handle<ObjectTemplateInfo> instance = Handle<ObjectTemplateInfo>( |
| ObjectTemplateInfo::cast(obj->instance_template()), isolate); |
| Handle<Object> props = |
| Handle<Object>(instance->property_accessors(), isolate); |
| if (!props->IsUndefined()) { |
| Map::AppendCallbackDescriptors(map, props); |
| } |
| } |
| // Accumulate static accessors |
| if (!obj->property_accessors()->IsUndefined()) { |
| Handle<Object> props = Handle<Object>(obj->property_accessors(), isolate); |
| valid_descriptors = |
| AccessorInfo::AppendUnique(props, array, valid_descriptors); |
| } |
| // Climb parent chain |
| Handle<Object> parent = Handle<Object>(obj->parent_template(), isolate); |
| if (parent->IsUndefined()) break; |
| obj = Handle<FunctionTemplateInfo>::cast(parent); |
| } |
| |
| // Install accumulated static accessors |
| for (int i = 0; i < valid_descriptors; i++) { |
| Handle<AccessorInfo> accessor(AccessorInfo::cast(array->get(i))); |
| JSObject::SetAccessor(result, accessor).Assert(); |
| } |
| |
| DCHECK(result->shared()->IsApiFunction()); |
| return result; |
| } |
| |
| } // namespace internal |
| } // namespace v8 |