// 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/api-natives.h" #include "src/api/api-inl.h" #include "src/common/message-template.h" #include "src/execution/isolate-inl.h" #include "src/objects/api-callbacks.h" #include "src/objects/hash-table-inl.h" #include "src/objects/lookup.h" #include "src/objects/property-cell.h" #include "src/objects/templates.h" namespace v8 { namespace internal { namespace { 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_; }; MaybeHandle InstantiateObject(Isolate* isolate, Handle data, Handle new_target, bool is_prototype); MaybeHandle InstantiateFunction( Isolate* isolate, Handle native_context, Handle data, MaybeHandle maybe_name = MaybeHandle()); MaybeHandle InstantiateFunction( Isolate* isolate, Handle data, MaybeHandle maybe_name = MaybeHandle()) { return InstantiateFunction(isolate, isolate->native_context(), data, maybe_name); } MaybeHandle Instantiate( Isolate* isolate, Handle data, MaybeHandle maybe_name = MaybeHandle()) { if (data->IsFunctionTemplateInfo()) { return InstantiateFunction( isolate, Handle::cast(data), maybe_name); } else if (data->IsObjectTemplateInfo()) { return InstantiateObject(isolate, Handle::cast(data), Handle(), false); } else { return data; } } MaybeHandle DefineAccessorProperty( Isolate* isolate, Handle object, Handle name, Handle getter, Handle setter, PropertyAttributes attributes, bool force_instantiate) { DCHECK(!getter->IsFunctionTemplateInfo() || !FunctionTemplateInfo::cast(*getter).do_not_cache()); DCHECK(!setter->IsFunctionTemplateInfo() || !FunctionTemplateInfo::cast(*setter).do_not_cache()); if (getter->IsFunctionTemplateInfo()) { if (force_instantiate || FunctionTemplateInfo::cast(*getter).BreakAtEntry()) { ASSIGN_RETURN_ON_EXCEPTION( isolate, getter, InstantiateFunction(isolate, Handle::cast(getter)), Object); } } if (setter->IsFunctionTemplateInfo()) { if (force_instantiate || FunctionTemplateInfo::cast(*setter).BreakAtEntry()) { ASSIGN_RETURN_ON_EXCEPTION( isolate, setter, InstantiateFunction(isolate, Handle::cast(setter)), Object); } } RETURN_ON_EXCEPTION( isolate, JSObject::DefineAccessor(object, name, getter, setter, attributes), Object); return object; } MaybeHandle DefineDataProperty(Isolate* isolate, Handle object, Handle name, Handle prop_data, PropertyAttributes attributes) { Handle 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 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, Just(ShouldThrow::kThrowOnError), StoreOrigin::kNamed)); return value; } void DisableAccessChecks(Isolate* isolate, Handle object) { Handle old_map(object->map(), isolate); // Copy map so it won't interfere constructor's initial map. Handle new_map = Map::Copy(isolate, old_map, "DisableAccessChecks"); new_map->set_is_access_check_needed(false); JSObject::MigrateToMap(isolate, Handle::cast(object), new_map); } void EnableAccessChecks(Isolate* isolate, Handle object) { Handle old_map(object->map(), isolate); // Copy map so it won't interfere constructor's initial map. Handle new_map = Map::Copy(isolate, old_map, "EnableAccessChecks"); new_map->set_is_access_check_needed(true); new_map->set_may_have_interesting_symbols(true); JSObject::MigrateToMap(isolate, object, new_map); } class AccessCheckDisableScope { public: AccessCheckDisableScope(Isolate* isolate, Handle 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 obj_; }; Object GetIntrinsic(Isolate* isolate, v8::Intrinsic intrinsic) { Handle 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 Object(); } template MaybeHandle ConfigureInstance(Isolate* isolate, Handle obj, Handle data) { HandleScope scope(isolate); // Disable access checks while instantiating the object. AccessCheckDisableScope access_check_scope(isolate, obj); // Walk the inheritance chain and copy all accessors to current object. int max_number_of_properties = 0; TemplateInfoT info = *data; while (!info.is_null()) { Object props = info.property_accessors(); if (!props.IsUndefined(isolate)) { max_number_of_properties += TemplateList::cast(props).length(); } info = info.GetParent(isolate); } if (max_number_of_properties > 0) { int valid_descriptors = 0; // Use a temporary FixedArray to accumulate unique accessors. Handle array = isolate->factory()->NewFixedArray(max_number_of_properties); for (Handle temp(*data, isolate); !temp->is_null(); temp = handle(temp->GetParent(isolate), isolate)) { // Accumulate accessors. Object maybe_properties = temp->property_accessors(); if (!maybe_properties.IsUndefined(isolate)) { valid_descriptors = AccessorInfo::AppendUnique( isolate, handle(maybe_properties, isolate), array, valid_descriptors); } } // Install accumulated accessors. for (int i = 0; i < valid_descriptors; i++) { Handle accessor(AccessorInfo::cast(array->get(i)), isolate); Handle name(Name::cast(accessor->name()), isolate); JSObject::SetAccessor(obj, name, accessor, accessor->initial_property_attributes()) .Assert(); } } Object maybe_property_list = data->property_list(); if (maybe_property_list.IsUndefined(isolate)) return obj; Handle properties(TemplateList::cast(maybe_property_list), isolate); if (properties->length() == 0) return obj; int i = 0; for (int c = 0; c < data->number_of_properties(); c++) { auto name = handle(Name::cast(properties->get(i++)), isolate); Object bit = properties->get(i++); 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, false), 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(Smi::ToInt(properties->get(i++))); auto prop_data = handle(GetIntrinsic(isolate, intrinsic), isolate); RETURN_ON_EXCEPTION( isolate, DefineDataProperty(isolate, obj, name, prop_data, attributes), JSObject); } } return obj; } // Whether or not to cache every instance: when we materialize a getter or // setter from an lazy AccessorPair, we rely on this cache to be able to always // return the same getter or setter. However, objects will be cloned anyways, // so it's not observable if we didn't cache an instance. Furthermore, a badly // behaved embedder might create an unlimited number of objects, so we limit // the cache for those cases. enum class CachingMode { kLimited, kUnlimited }; MaybeHandle ProbeInstantiationsCache( Isolate* isolate, Handle native_context, int serial_number, CachingMode caching_mode) { DCHECK_LE(1, serial_number); if (serial_number <= TemplateInfo::kFastTemplateInstantiationsCacheSize) { FixedArray fast_cache = native_context->fast_template_instantiations_cache(); Handle object{fast_cache.get(serial_number - 1), isolate}; if (object->IsUndefined(isolate)) return {}; return Handle::cast(object); } if (caching_mode == CachingMode::kUnlimited || (serial_number <= TemplateInfo::kSlowTemplateInstantiationsCacheSize)) { SimpleNumberDictionary slow_cache = native_context->slow_template_instantiations_cache(); int entry = slow_cache.FindEntry(isolate, serial_number); if (entry != SimpleNumberDictionary::kNotFound) { return handle(JSObject::cast(slow_cache.ValueAt(entry)), isolate); } } return {}; } void CacheTemplateInstantiation(Isolate* isolate, Handle native_context, int serial_number, CachingMode caching_mode, Handle object) { DCHECK_LE(1, serial_number); if (serial_number <= TemplateInfo::kFastTemplateInstantiationsCacheSize) { Handle fast_cache = handle(native_context->fast_template_instantiations_cache(), isolate); Handle new_cache = FixedArray::SetAndGrow(isolate, fast_cache, serial_number - 1, object); if (*new_cache != *fast_cache) { native_context->set_fast_template_instantiations_cache(*new_cache); } } else if (caching_mode == CachingMode::kUnlimited || (serial_number <= TemplateInfo::kSlowTemplateInstantiationsCacheSize)) { Handle cache = handle(native_context->slow_template_instantiations_cache(), isolate); auto new_cache = SimpleNumberDictionary::Set(isolate, cache, serial_number, object); if (*new_cache != *cache) { native_context->set_slow_template_instantiations_cache(*new_cache); } } } void UncacheTemplateInstantiation(Isolate* isolate, Handle native_context, int serial_number, CachingMode caching_mode) { DCHECK_LE(1, serial_number); if (serial_number <= TemplateInfo::kFastTemplateInstantiationsCacheSize) { FixedArray fast_cache = native_context->fast_template_instantiations_cache(); DCHECK(!fast_cache.get(serial_number - 1).IsUndefined(isolate)); fast_cache.set_undefined(serial_number - 1); } else if (caching_mode == CachingMode::kUnlimited || (serial_number <= TemplateInfo::kSlowTemplateInstantiationsCacheSize)) { Handle cache = handle(native_context->slow_template_instantiations_cache(), isolate); int entry = cache->FindEntry(isolate, serial_number); DCHECK_NE(SimpleNumberDictionary::kNotFound, entry); cache = SimpleNumberDictionary::DeleteEntry(isolate, cache, entry); native_context->set_slow_template_instantiations_cache(*cache); } } bool IsSimpleInstantiation(Isolate* isolate, ObjectTemplateInfo info, JSReceiver new_target) { DisallowHeapAllocation no_gc; if (!new_target.IsJSFunction()) return false; JSFunction fun = JSFunction::cast(new_target); if (fun.shared().function_data() != info.constructor()) return false; if (info.immutable_proto()) return false; return fun.context().native_context() == isolate->raw_native_context(); } MaybeHandle InstantiateObject(Isolate* isolate, Handle info, Handle new_target, bool is_prototype) { Handle constructor; int serial_number = Smi::ToInt(info->serial_number()); if (!new_target.is_null()) { if (IsSimpleInstantiation(isolate, *info, *new_target)) { constructor = Handle::cast(new_target); } else { // Disable caching for subclass instantiation. serial_number = 0; } } // Fast path. Handle result; if (serial_number) { if (ProbeInstantiationsCache(isolate, isolate->native_context(), serial_number, CachingMode::kLimited) .ToHandle(&result)) { return isolate->factory()->CopyJSObject(result); } } if (constructor.is_null()) { Object maybe_constructor_info = info->constructor(); if (maybe_constructor_info.IsUndefined(isolate)) { constructor = isolate->object_function(); } else { // Enter a new scope. Recursion could otherwise create a lot of handles. HandleScope scope(isolate); Handle cons_templ( FunctionTemplateInfo::cast(maybe_constructor_info), isolate); Handle tmp_constructor; ASSIGN_RETURN_ON_EXCEPTION(isolate, tmp_constructor, InstantiateFunction(isolate, cons_templ), JSObject); constructor = scope.CloseAndEscape(tmp_constructor); } if (new_target.is_null()) new_target = constructor; } Handle object; ASSIGN_RETURN_ON_EXCEPTION( isolate, object, JSObject::New(constructor, new_target, Handle::null()), JSObject); if (is_prototype) JSObject::OptimizeAsPrototype(object); ASSIGN_RETURN_ON_EXCEPTION( isolate, result, ConfigureInstance(isolate, object, info), JSObject); if (info->immutable_proto()) { JSObject::SetImmutableProto(object); } if (!is_prototype) { // Keep prototypes in slow-mode. Let them be lazily turned fast later on. // TODO(dcarney): is this necessary? JSObject::MigrateSlowToFast(result, 0, "ApiNatives::InstantiateObject"); // Don't cache prototypes. if (serial_number) { CacheTemplateInstantiation(isolate, isolate->native_context(), serial_number, CachingMode::kLimited, result); result = isolate->factory()->CopyJSObject(result); } } return result; } namespace { MaybeHandle GetInstancePrototype(Isolate* isolate, Object function_template) { // Enter a new scope. Recursion could otherwise create a lot of handles. HandleScope scope(isolate); Handle parent_instance; ASSIGN_RETURN_ON_EXCEPTION( isolate, parent_instance, InstantiateFunction( isolate, handle(FunctionTemplateInfo::cast(function_template), isolate)), JSFunction); Handle instance_prototype; // TODO(cbruni): decide what to do here. ASSIGN_RETURN_ON_EXCEPTION( isolate, instance_prototype, JSObject::GetProperty(isolate, parent_instance, isolate->factory()->prototype_string()), JSFunction); return scope.CloseAndEscape(instance_prototype); } } // namespace MaybeHandle InstantiateFunction( Isolate* isolate, Handle native_context, Handle data, MaybeHandle maybe_name) { int serial_number = Smi::ToInt(data->serial_number()); if (serial_number) { Handle result; if (ProbeInstantiationsCache(isolate, native_context, serial_number, CachingMode::kUnlimited) .ToHandle(&result)) { return Handle::cast(result); } } Handle prototype; if (!data->remove_prototype()) { Object prototype_templ = data->GetPrototypeTemplate(); if (prototype_templ.IsUndefined(isolate)) { Object protoype_provider_templ = data->GetPrototypeProviderTemplate(); if (protoype_provider_templ.IsUndefined(isolate)) { prototype = isolate->factory()->NewJSObject(isolate->object_function()); } else { ASSIGN_RETURN_ON_EXCEPTION( isolate, prototype, GetInstancePrototype(isolate, protoype_provider_templ), JSFunction); } } else { ASSIGN_RETURN_ON_EXCEPTION( isolate, prototype, InstantiateObject( isolate, handle(ObjectTemplateInfo::cast(prototype_templ), isolate), Handle(), true), JSFunction); } Object parent = data->GetParentTemplate(); if (!parent.IsUndefined(isolate)) { Handle parent_prototype; ASSIGN_RETURN_ON_EXCEPTION(isolate, parent_prototype, GetInstancePrototype(isolate, parent), JSFunction); CHECK(parent_prototype->IsHeapObject()); JSObject::ForceSetPrototype(Handle::cast(prototype), Handle::cast(parent_prototype)); } } InstanceType function_type = (!data->needs_access_check() && data->GetNamedPropertyHandler().IsUndefined(isolate) && data->GetIndexedPropertyHandler().IsUndefined(isolate)) ? JS_API_OBJECT_TYPE : JS_SPECIAL_API_OBJECT_TYPE; Handle function = ApiNatives::CreateApiFunction( isolate, native_context, data, prototype, function_type, maybe_name); if (serial_number) { // Cache the function. CacheTemplateInstantiation(isolate, native_context, serial_number, CachingMode::kUnlimited, function); } MaybeHandle result = ConfigureInstance(isolate, function, data); if (result.is_null()) { // Uncache on error. if (serial_number) { UncacheTemplateInstantiation(isolate, native_context, serial_number, CachingMode::kUnlimited); } return MaybeHandle(); } return function; } void AddPropertyToPropertyList(Isolate* isolate, Handle templ, int length, Handle* data) { Object maybe_list = templ->property_list(); Handle list; if (maybe_list.IsUndefined(isolate)) { list = TemplateList::New(isolate, length); } else { list = handle(TemplateList::cast(maybe_list), isolate); } templ->set_number_of_properties(templ->number_of_properties() + 1); for (int i = 0; i < length; i++) { Handle value = data[i].is_null() ? Handle::cast(isolate->factory()->undefined_value()) : data[i]; list = TemplateList::Add(isolate, list, value); } templ->set_property_list(*list); } } // namespace MaybeHandle ApiNatives::InstantiateFunction( Isolate* isolate, Handle native_context, Handle data, MaybeHandle maybe_name) { InvokeScope invoke_scope(isolate); return ::v8::internal::InstantiateFunction(isolate, native_context, data, maybe_name); } MaybeHandle ApiNatives::InstantiateFunction( Handle data, MaybeHandle maybe_name) { Isolate* isolate = data->GetIsolate(); InvokeScope invoke_scope(isolate); return ::v8::internal::InstantiateFunction(isolate, data, maybe_name); } MaybeHandle ApiNatives::InstantiateObject( Isolate* isolate, Handle data, Handle new_target) { InvokeScope invoke_scope(isolate); return ::v8::internal::InstantiateObject(isolate, data, new_target, false); } MaybeHandle ApiNatives::InstantiateRemoteObject( Handle data) { Isolate* isolate = data->GetIsolate(); InvokeScope invoke_scope(isolate); Handle constructor( FunctionTemplateInfo::cast(data->constructor()), isolate); Handle object_map = isolate->factory()->NewMap( JS_SPECIAL_API_OBJECT_TYPE, JSObject::kHeaderSize + data->embedder_field_count() * kEmbedderDataSlotSize, TERMINAL_FAST_ELEMENTS_KIND); object_map->SetConstructor(*constructor); object_map->set_is_access_check_needed(true); object_map->set_may_have_interesting_symbols(true); Handle object = isolate->factory()->NewJSObjectFromMap(object_map); JSObject::ForceSetPrototype(object, isolate->factory()->null_value()); return object; } void ApiNatives::AddDataProperty(Isolate* isolate, Handle info, Handle name, Handle value, PropertyAttributes attributes) { PropertyDetails details(kData, attributes, PropertyCellType::kNoCell); auto details_handle = handle(details.AsSmi(), isolate); Handle data[] = {name, details_handle, value}; AddPropertyToPropertyList(isolate, info, arraysize(data), data); } void ApiNatives::AddDataProperty(Isolate* isolate, Handle info, Handle name, v8::Intrinsic intrinsic, PropertyAttributes attributes) { auto value = handle(Smi::FromInt(intrinsic), isolate); auto intrinsic_marker = isolate->factory()->true_value(); PropertyDetails details(kData, attributes, PropertyCellType::kNoCell); auto details_handle = handle(details.AsSmi(), isolate); Handle data[] = {name, intrinsic_marker, details_handle, value}; AddPropertyToPropertyList(isolate, info, arraysize(data), data); } void ApiNatives::AddAccessorProperty(Isolate* isolate, Handle info, Handle name, Handle getter, Handle setter, PropertyAttributes attributes) { PropertyDetails details(kAccessor, attributes, PropertyCellType::kNoCell); auto details_handle = handle(details.AsSmi(), isolate); Handle data[] = {name, details_handle, getter, setter}; AddPropertyToPropertyList(isolate, info, arraysize(data), data); } void ApiNatives::AddNativeDataProperty(Isolate* isolate, Handle info, Handle property) { Object maybe_list = info->property_accessors(); Handle list; if (maybe_list.IsUndefined(isolate)) { list = TemplateList::New(isolate, 1); } else { list = handle(TemplateList::cast(maybe_list), isolate); } list = TemplateList::Add(isolate, list, property); info->set_property_accessors(*list); } Handle ApiNatives::CreateApiFunction( Isolate* isolate, Handle native_context, Handle obj, Handle prototype, InstanceType type, MaybeHandle maybe_name) { Handle shared = FunctionTemplateInfo::GetOrCreateSharedFunctionInfo(isolate, obj, maybe_name); // To simplify things, API functions always have shared name. DCHECK(shared->HasSharedName()); Handle result = isolate->factory()->NewFunctionFromSharedFunctionInfo(shared, native_context); if (obj->remove_prototype()) { DCHECK(prototype.is_null()); DCHECK(result->shared().IsApiFunction()); DCHECK(!result->IsConstructor()); DCHECK(!result->has_prototype_slot()); return result; } // Down from here is only valid for API functions that can be used as a // constructor (don't set the "remove prototype" flag). DCHECK(result->has_prototype_slot()); if (obj->read_only_prototype()) { result->set_map(*isolate->sloppy_function_with_readonly_prototype_map()); } if (prototype->IsTheHole(isolate)) { prototype = isolate->factory()->NewFunctionPrototype(result); } else if (obj->GetPrototypeProviderTemplate().IsUndefined(isolate)) { JSObject::AddProperty(isolate, Handle::cast(prototype), isolate->factory()->constructor_string(), result, DONT_ENUM); } int embedder_field_count = 0; bool immutable_proto = false; if (!obj->GetInstanceTemplate().IsUndefined(isolate)) { Handle GetInstanceTemplate = Handle( ObjectTemplateInfo::cast(obj->GetInstanceTemplate()), isolate); embedder_field_count = GetInstanceTemplate->embedder_field_count(); immutable_proto = GetInstanceTemplate->immutable_proto(); } // JS_FUNCTION_TYPE requires information about the prototype slot. DCHECK_NE(JS_FUNCTION_TYPE, type); int instance_size = JSObject::GetHeaderSize(type) + kEmbedderDataSlotSize * embedder_field_count; Handle map = isolate->factory()->NewMap(type, instance_size, TERMINAL_FAST_ELEMENTS_KIND); JSFunction::SetInitialMap(result, map, Handle::cast(prototype)); // Mark as undetectable if needed. if (obj->undetectable()) { // We only allow callable undetectable receivers here, since this whole // undetectable business is only to support document.all, which is both // undetectable and callable. If we ever see the need to have an object // that is undetectable but not callable, we need to update the types.h // to allow encoding this. CHECK(!obj->GetInstanceCallHandler().IsUndefined(isolate)); map->set_is_undetectable(true); } // Mark as needs_access_check if needed. if (obj->needs_access_check()) { map->set_is_access_check_needed(true); map->set_may_have_interesting_symbols(true); } // Set interceptor information in the map. if (!obj->GetNamedPropertyHandler().IsUndefined(isolate)) { map->set_has_named_interceptor(true); map->set_may_have_interesting_symbols(true); } if (!obj->GetIndexedPropertyHandler().IsUndefined(isolate)) { map->set_has_indexed_interceptor(true); } // Mark instance as callable in the map. if (!obj->GetInstanceCallHandler().IsUndefined(isolate)) { map->set_is_callable(true); map->set_is_constructor(!obj->undetectable()); } if (immutable_proto) map->set_is_immutable_proto(true); return result; } } // namespace internal } // namespace v8