// 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(); } // 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