runtime/type-builtins.cpp (1,127 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) #include "type-builtins.h" #include "attributedict.h" #include "builtins.h" #include "bytecode.h" #include "capi.h" #include "dict-builtins.h" #include "frame.h" #include "globals.h" #include "ic.h" #include "list-builtins.h" #include "module-builtins.h" #include "mro.h" #include "object-builtins.h" #include "objects.h" #include "runtime.h" #include "str-builtins.h" #include "thread.h" #include "typeslots.h" namespace py { static RawObject addBuiltinTypeWithLayout(Thread* thread, const Layout& layout, SymbolId name, LayoutId builtin_base, LayoutId superclass_id, word flags) { HandleScope scope(thread); Runtime* runtime = thread->runtime(); Type type(&scope, runtime->newType()); type.setName(runtime->symbols()->at(name)); Type superclass(&scope, runtime->typeAt(superclass_id)); type.setInstanceLayout(*layout); type.setInstanceLayoutId(layout.id()); flags |= superclass.flags() & Type::kInheritableFlags; if (builtin_base == layout.id()) { flags |= Type::Flag::kIsFixedAttributeBase; } type.setFlagsAndBuiltinBase(static_cast<Type::Flag>(flags), builtin_base); type.setBases(runtime->newTupleWith1(superclass)); layout.setDescribedType(*type); return *type; } RawObject addBuiltinType(Thread* thread, SymbolId name, LayoutId layout_id, LayoutId superclass_id, View<BuiltinAttribute> attrs, word size, bool basetype) { HandleScope scope(thread); Runtime* runtime = thread->runtime(); Layout layout(&scope, runtime->layoutCreateSubclassWithBuiltins( thread, layout_id, superclass_id, attrs, size)); runtime->layoutAtPut(layout_id, *layout); LayoutId builtin_base = attrs.length() == 0 ? superclass_id : layout_id; word flags = basetype ? Type::Flag::kIsBasetype : Type::Flag::kNone; return addBuiltinTypeWithLayout(thread, layout, name, builtin_base, superclass_id, flags); } RawObject addImmediateBuiltinType(Thread* thread, SymbolId name, LayoutId layout_id, LayoutId builtin_base, LayoutId superclass_id, bool basetype) { HandleScope scope(thread); Runtime* runtime = thread->runtime(); Layout layout(&scope, runtime->newLayout(layout_id)); runtime->layoutAtPut(layout_id, *layout); word flags = basetype ? Type::Flag::kIsBasetype : Type::Flag::kNone; return addBuiltinTypeWithLayout(thread, layout, name, builtin_base, superclass_id, flags); } void builtinTypeEnableTupleOverflow(Thread* thread, const Type& type) { DCHECK(type.mro().isNoneType(), "Enabling overflow is unsafe after initialization"); thread->runtime()->layoutSetTupleOverflow( Layout::cast(type.instanceLayout())); } RawObject findBuiltinTypeWithName(Thread* thread, const Object& name) { DCHECK(Runtime::isInternedStr(thread, name), "must be interned str"); HandleScope scope(thread); Runtime* runtime = thread->runtime(); Object layout(&scope, NoneType::object()); Object type_obj(&scope, NoneType::object()); for (int i = 0; i <= static_cast<int>(LayoutId::kLastBuiltinId); i++) { layout = runtime->layoutAtSafe(static_cast<LayoutId>(i)); if (layout.isErrorNotFound()) continue; type_obj = Layout::cast(*layout).describedType(); if (!type_obj.isType()) continue; if (Type::cast(*type_obj).name() == name) { return *type_obj; } } return Error::notFound(); } RawObject raiseTypeErrorCannotSetImmutable(Thread* thread, const Type& type) { HandleScope scope(thread); Object type_name(&scope, type.name()); return thread->raiseWithFmt( LayoutId::kTypeError, "can't set attributes of built-in/extension type '%S'", &type_name); } RawObject typeAt(const Type& type, const Object& name) { RawObject result = NoneType::object(); if (!attributeAt(*type, *name, &result)) return Error::notFound(); return result; } RawObject typeAtById(Thread* thread, const Type& type, SymbolId id) { RawObject name = thread->runtime()->symbols()->at(id); RawObject result = NoneType::object(); if (!attributeAt(*type, name, &result)) return Error::notFound(); return result; } // Returns type flags updated with the given attribute. static word computeAttributeTypeFlags(Thread* thread, const Type& type, SymbolId name, word flags) { Runtime* runtime = thread->runtime(); // Custom MROs can contain types that are not part of the subclass hierarchy // which does not fit our cache invalidation strategy for the flags. Be safe // and do not set any! if (flags & Type::Flag::kHasCustomMro) { return flags & ~Type::kAttributeFlags; } if (name == ID(__getattribute__)) { RawObject value = typeLookupInMroById(thread, *type, name); if (value == runtime->objectDunderGetattribute()) { flags |= Type::Flag::kHasObjectDunderGetattribute; } else { flags &= ~Type::Flag::kHasObjectDunderGetattribute; } if (value == runtime->typeDunderGetattribute()) { flags |= Type::Flag::kHasTypeDunderGetattribute; } else { flags &= ~Type::Flag::kHasTypeDunderGetattribute; } if (value == runtime->moduleDunderGetattribute()) { flags |= Type::Flag::kHasModuleDunderGetattribute; } else { flags &= ~Type::Flag::kHasModuleDunderGetattribute; } return flags; } if (name == ID(__new__)) { RawObject value = typeLookupInMroById(thread, *type, name); if (value == runtime->objectDunderNew()) { flags |= Type::Flag::kHasObjectDunderNew; } else { flags &= ~Type::Flag::kHasObjectDunderNew; } return flags; } if (name == ID(__hash__)) { RawObject value = typeLookupInMroById(thread, *type, name); if (value == runtime->objectDunderHash()) { flags |= Type::Flag::kHasObjectDunderHash; } else if (value == runtime->strDunderHash()) { flags |= Type::Flag::kHasStrDunderHash; } else { flags &= ~(Type::Flag::kHasObjectDunderHash | Type::Flag::kHasStrDunderHash); } return flags; } if (name == ID(__bool__)) { RawObject value = typeLookupInMroById(thread, *type, name); if (!value.isErrorNotFound()) { flags |= Type::Flag::kHasDunderBool; } else { flags &= ~Type::Flag::kHasDunderBool; } return flags; } if (name == ID(__len__)) { RawObject value = typeLookupInMroById(thread, *type, name); if (!value.isErrorNotFound()) { flags |= Type::Flag::kHasDunderLen; } else { flags &= ~Type::Flag::kHasDunderLen; } return flags; } if (name == ID(__class__)) { RawObject value = typeLookupInMroById(thread, *type, name); if (value == runtime->objectDunderClass()) { flags |= Type::Flag::kHasObjectDunderClass; } else { flags &= ~Type::Flag::kHasObjectDunderClass; } return flags; } if (name == ID(__eq__)) { RawObject value = typeLookupInMroById(thread, *type, name); if (value == runtime->objectDunderEq()) { flags |= Type::Flag::kHasObjectDunderEq; } else { flags &= ~Type::Flag::kHasObjectDunderEq; } return flags; } if (name == ID(__get__)) { RawObject value = typeLookupInMroById(thread, *type, name); if (!value.isErrorNotFound()) { flags |= Type::Flag::kHasDunderGet; } else { flags &= ~Type::Flag::kHasDunderGet; } return flags; } if (name == ID(__set__)) { RawObject value = typeLookupInMroById(thread, *type, name); if (!value.isErrorNotFound()) { flags |= Type::Flag::kHasDunderSet; } else { flags &= ~Type::Flag::kHasDunderSet; } return flags; } if (name == ID(__delete__)) { RawObject value = typeLookupInMroById(thread, *type, name); if (!value.isErrorNotFound()) { flags |= Type::Flag::kHasDunderDelete; } else { flags &= ~Type::Flag::kHasDunderDelete; } return flags; } return flags; } // Propagate attribute type flags through `type`'s descendents. static void typePropagateAttributeTypeFlag(Thread* thread, const Type& type, SymbolId attr_name) { Type::Flag new_flags = Type::Flag::kNone; new_flags = static_cast<Type::Flag>( computeAttributeTypeFlags(thread, type, attr_name, type.flags())); if (new_flags == type.flags()) { // Stop tree traversal since flags doesn't change for this type and its // subclases. return; } type.setFlags(new_flags); if (type.subclasses().isNoneType()) { return; } HandleScope scope(thread); List subclasses(&scope, type.subclasses()); Type subclass(&scope, thread->runtime()->typeAt(LayoutId::kObject)); for (word i = 0, length = subclasses.numItems(); i < length; ++i) { RawObject referent = WeakRef::cast(subclasses.at(i)).referent(); if (referent.isNoneType()) continue; subclass = referent.rawCast<RawType>(); if (typeAtById(thread, subclass, attr_name).isErrorNotFound()) { // subclass inherits the attribute for `attr_name`. typePropagateAttributeTypeFlag(thread, subclass, attr_name); } } } static const SymbolId kAttributesForTypeFlags[] = { ID(__getattribute__), ID(__new__), ID(__hash__), ID(__bool__), ID(__len__), ID(__class__), ID(__get__), ID(__set__), ID(__delete__), ID(__eq__), }; // Returns `SymbolId` for `attr_name` if given `attr_name` is marked in // Type::Flags. Returns `SymbolId::kInvalid` otherwise. See Type::Flags. static SymbolId attributeForTypeFlag(Thread* thread, const Object& attr_name) { Symbols* symbols = thread->runtime()->symbols(); for (SymbolId id : kAttributesForTypeFlags) { if (attr_name == symbols->at(id)) return id; } return SymbolId::kInvalid; } RawObject typeAtPut(Thread* thread, const Type& type, const Object& name, const Object& value) { DCHECK(thread->runtime()->isInternedStr(thread, name), "name should be an interned str"); RawValueCell value_cell = ValueCell::cast(attributeValueCellAtPut(thread, type, name)); value_cell.setValue(*value); if (!value_cell.dependencyLink().isNoneType()) { HandleScope scope(thread); ValueCell value_cell_obj(&scope, value_cell); icInvalidateAttr(thread, type, name, value_cell_obj); return *value_cell_obj; } SymbolId attr_name_for_type_flag = attributeForTypeFlag(thread, name); if (attributeForTypeFlag(thread, name) != SymbolId::kInvalid) { typePropagateAttributeTypeFlag(thread, type, attr_name_for_type_flag); } return value_cell; } RawObject typeAtPutById(Thread* thread, const Type& type, SymbolId id, const Object& value) { HandleScope scope(thread); Object name(&scope, thread->runtime()->symbols()->at(id)); return typeAtPut(thread, type, name, value); } RawObject typeAtSetLocation(RawType type, RawObject name, word hash, Object* location) { RawObject result = NoneType::object(); if (!attributeValueCellAtWithHash(type, name, hash, &result) || ValueCell::cast(result).isPlaceholder()) { return Error::notFound(); } if (location != nullptr) { *location = result; } return ValueCell::cast(result).value(); } bool typeIsDataDescriptor(RawType type) { word flags = type.flags(); return (flags & Type::Flag::kHasDunderSet) || (flags & Type::Flag::kHasDunderDelete); } bool typeIsNonDataDescriptor(RawType type) { word flags = type.flags(); return flags & Type::Flag::kHasDunderGet; } RawObject resolveDescriptorGet(Thread* thread, const Object& descr, const Object& instance, const Object& instance_type) { if (!typeIsNonDataDescriptor( thread->runtime()->typeOf(*descr).rawCast<RawType>())) { return *descr; } return Interpreter::callDescriptorGet(thread, descr, instance, instance_type); } RawObject typeAssignFromDict(Thread* thread, const Type& type, const Dict& dict) { HandleScope scope(thread); Object key(&scope, NoneType::object()); Object value(&scope, NoneType::object()); for (word i = 0; dictNextItem(dict, &i, &key, &value);) { DCHECK(!(value.isValueCell() && ValueCell::cast(*value).isPlaceholder()), "value should not be a placeholder value cell"); key = attributeName(thread, key); if (key.isErrorException()) return *key; typeAtPut(thread, type, key, value); } return NoneType::object(); } RawObject typeLookupInMroSetLocation(Thread* thread, RawType type, RawObject name, Object* location) { RawTuple mro = Tuple::cast(type.mro()); word hash = internedStrHash(name); for (word i = 0, length = mro.length(); i < length; i++) { DCHECK(thread->runtime()->isInstanceOfType(mro.at(i)), "non-type in MRO"); RawType mro_type = mro.at(i).rawCast<RawType>(); RawObject result = typeAtSetLocation(mro_type, name, hash, location); if (!result.isErrorNotFound()) { return result; } } return Error::notFound(); } RawObject typeDeleteAttribute(Thread* thread, const Type& type, const Object& name) { HandleScope scope(thread); Runtime* runtime = thread->runtime(); terminateIfUnimplementedTypeAttrCacheInvalidation(thread, type, name); if (!type.hasMutableDict()) { return raiseTypeErrorCannotSetImmutable(thread, type); } // Check for a delete descriptor Type metatype(&scope, runtime->typeOf(*type)); Object meta_attr(&scope, typeLookupInMro(thread, *metatype, *name)); if (!meta_attr.isError() && runtime->isDeleteDescriptor(thread, meta_attr)) { return Interpreter::callDescriptorDelete(thread, meta_attr, type); } // No delete descriptor found, attempt to delete from the type dict if (typeRemove(thread, type, name).isErrorNotFound()) { Str type_name(&scope, type.name()); return thread->raiseWithFmt(LayoutId::kAttributeError, "type object '%S' has no attribute '%S'", &type_name, &name); } return NoneType::object(); } RawObject typeLookupInMroById(Thread* thread, RawType type, SymbolId id) { return typeLookupInMro(thread, type, thread->runtime()->symbols()->at(id)); } RawObject typeRemove(Thread* thread, const Type& type, const Object& name) { DCHECK(Runtime::isInternedStr(thread, name), "expected interned str"); HandleScope scope(thread); word index; Object value_cell_obj(&scope, NoneType::object()); if (!attributeFindForRemoval(type, name, &value_cell_obj, &index)) { return Error::notFound(); } ValueCell value_cell(&scope, *value_cell_obj); icInvalidateAttr(thread, type, name, value_cell); attributeRemove(type, index); if (value_cell.isPlaceholder()) return Error::notFound(); SymbolId attr_name_for_type_flag = attributeForTypeFlag(thread, name); if (attributeForTypeFlag(thread, name) != SymbolId::kInvalid) { typePropagateAttributeTypeFlag(thread, type, attr_name_for_type_flag); } return value_cell.value(); } RawObject typeRemoveById(Thread* thread, const Type& type, SymbolId id) { HandleScope scope(thread); Object name(&scope, thread->runtime()->symbols()->at(id)); return typeRemove(thread, type, name); } RawObject typeKeys(Thread* thread, const Type& type) { return attributeKeys(thread, type); } word typeLen(Thread* thread, const Type& type) { return attributeLen(thread, type); } RawObject typeValues(Thread* thread, const Type& type) { return attributeValues(thread, type); } RawObject typeGetAttribute(Thread* thread, const Type& type, const Object& name) { return typeGetAttributeSetLocation(thread, type, name, nullptr); } RawObject typeGetAttributeSetLocation(Thread* thread, const Type& type, const Object& name, Object* location_out) { // Look for the attribute in the meta class HandleScope scope(thread); Runtime* runtime = thread->runtime(); Type meta_type(&scope, runtime->typeOf(*type)); Object meta_attr(&scope, typeLookupInMro(thread, *meta_type, *name)); if (!meta_attr.isError()) { // TODO(T56002494): Remove this once type.__getattribute__ gets cached. if (meta_attr.isProperty()) { Object getter(&scope, Property::cast(*meta_attr).getter()); if (!getter.isNoneType()) { return Interpreter::call1(thread, getter, type); } } Type meta_attr_type(&scope, runtime->typeOf(*meta_attr)); if (typeIsDataDescriptor(*meta_attr_type)) { return Interpreter::callDescriptorGet(thread, meta_attr, type, meta_type); } } // No data descriptor found on the meta class, look in the mro of the type Object attr(&scope, typeLookupInMroSetLocation(thread, *type, *name, location_out)); if (!attr.isError()) { // TODO(T56002494): Remove this once type.__getattribute__ gets cached. if (attr.isFunction()) { // We always return the function object itself instead of a BoundMethod // due to the exception made below and another exception for NoneType in // function.__get__. return *attr; } Type attr_type(&scope, runtime->typeOf(*attr)); if (typeIsNonDataDescriptor(*attr_type)) { // Unfortunately calling `__get__` for a lookup on `type(None)` will look // exactly the same as calling it for a lookup on the `None` object. // To solve the ambiguity we add a special case for `type(None)` here. // Luckily it is impossible for the user to change the type so we can // special case the desired lookup behavior here. // Also see `METH(function, __get__)` for the related special casing // of lookups on the `None` object. if (type.builtinBase() == LayoutId::kNoneType) { return *attr; } if (location_out != nullptr) { *location_out = NoneType::object(); } Object none(&scope, NoneType::object()); return Interpreter::callDescriptorGet(thread, attr, none, type); } return *attr; } // No data descriptor found on the meta class, look on the type Object result(&scope, instanceGetAttribute(thread, type, name)); if (!result.isError()) { return *result; } // No attr found in type or its mro, use the non-data descriptor found in // the metaclass (if any). if (!meta_attr.isError()) { return resolveDescriptorGet(thread, meta_attr, type, meta_type); } return Error::notFound(); } static void addSubclass(Thread* thread, const Type& base, const Type& type) { HandleScope scope(thread); Runtime* runtime = thread->runtime(); if (base.subclasses().isNoneType()) { base.setSubclasses(runtime->newList()); } List subclasses(&scope, base.subclasses()); Object value(&scope, runtime->newWeakRef(thread, type)); runtime->listAdd(thread, subclasses, value); } void typeAddDocstring(Thread* thread, const Type& type) { // If the type dictionary doesn't contain a __doc__, set it from the doc // slot if (typeAtById(thread, type, ID(__doc__)).isErrorNotFound()) { HandleScope scope(thread); Object doc(&scope, type.doc()); typeAtPutById(thread, type, ID(__doc__), doc); } } void typeAddInstanceDict(Thread* thread, const Type& type) { HandleScope scope(thread); Runtime* runtime = thread->runtime(); Object instance_proxy(&scope, runtime->typeAt(LayoutId::kInstanceProxy)); CHECK(instance_proxy.isType(), "instance_proxy not found"); Module under_builtins(&scope, runtime->findModuleById(ID(_builtins))); Function under_instance_dunder_dict_set( &scope, moduleAtById(thread, under_builtins, ID(_instance_dunder_dict_set))); Object none(&scope, NoneType::object()); Object property(&scope, runtime->newProperty(instance_proxy, under_instance_dunder_dict_set, none)); typeAtPutById(thread, type, ID(__dict__), property); } static RawObject fixedAttributeBaseOfType(Thread* thread, const Type& type); // This searches recursively through `bases` for classes with the // `kIsFixedAttributeBase` flag set. The algorithm picks the entry in bases // which leads to a fixed attribute base class that is equal or a superclass of // the fixed attribute bases found by the other bases entries. // If `get_fixed_attr_base` is false, then the fixed attribute base is returned. // If it is true, then the first entry in `bases` that is superclass of the // fixed attribute base is returned. static RawObject computeFixedAttributeBaseImpl(Thread* thread, const Tuple& bases, bool get_fixed_attr_base) { HandleScope scope(thread); Type result(&scope, bases.at(0)); if (!result.isBasetype()) { Object type_name(&scope, result.name()); return thread->raiseWithFmt(LayoutId::kTypeError, "type '%S' is not an acceptable base type", &type_name); } word bases_length = bases.length(); if (!get_fixed_attr_base && bases_length == 1) { return *result; } Type result_fixed_attr_base(&scope, fixedAttributeBaseOfType(thread, result)); Type base(&scope, *result); Type fixed_attr_base(&scope, *result); for (word i = 1, length = bases.length(); i < length; i++) { base = bases.at(i); if (!base.isBasetype()) { Object type_name(&scope, base.name()); return thread->raiseWithFmt(LayoutId::kTypeError, "type '%S' is not an acceptable base type", &type_name); } fixed_attr_base = fixedAttributeBaseOfType(thread, base); if (typeIsSubclass(*result_fixed_attr_base, *fixed_attr_base)) { continue; } if (typeIsSubclass(*fixed_attr_base, *result_fixed_attr_base)) { result = *base; result_fixed_attr_base = *fixed_attr_base; } else { return thread->raiseWithFmt( LayoutId::kTypeError, "multiple bases have instance lay-out conflict"); } } return get_fixed_attr_base ? *result_fixed_attr_base : *result; } // Returns the most generic base of `type` on `type's type hierarchy that // contains all in-object attributes of `type`. Note that this is designed to // simulate `solid_base` from CPython's typeobject.c. static RawObject fixedAttributeBaseOfType(Thread* thread, const Type& type) { if (type.hasFlag(Type::Flag::kIsFixedAttributeBase)) { return *type; } HandleScope scope(thread); Tuple bases(&scope, type.bases()); return computeFixedAttributeBaseImpl(thread, bases, true); } RawObject computeFixedAttributeBase(Thread* thread, const Tuple& bases) { return computeFixedAttributeBaseImpl(thread, bases, false); } static RawObject validateSlots(Thread* thread, const Type& type, const Tuple& slots, bool base_has_instance_dict, bool* add_instance_dict) { HandleScope scope(thread); word slots_len = slots.length(); Runtime* runtime = thread->runtime(); Str dunder_dict(&scope, runtime->symbols()->at(ID(__dict__))); *add_instance_dict = false; List result(&scope, runtime->newList()); Object slot_obj(&scope, NoneType::object()); Str slot_str(&scope, Str::empty()); for (word i = 0; i < slots_len; i++) { slot_obj = slots.at(i); if (!runtime->isInstanceOfStr(*slot_obj)) { return thread->raiseWithFmt(LayoutId::kTypeError, "__slots__ items must be strings, not '%T'", &slot_obj); } slot_str = *slot_obj; if (!strIsIdentifier(slot_str)) { return thread->raiseWithFmt(LayoutId::kTypeError, "__slots__ must be identifiers"); } slot_str = attributeName(thread, slot_str); if (slot_str == dunder_dict) { if (base_has_instance_dict || *add_instance_dict) { return thread->raiseWithFmt( LayoutId::kTypeError, "__dict__ slot disallowed: we already got one"); } *add_instance_dict = true; continue; } if (!typeAt(type, slot_str).isErrorNotFound()) { return thread->raiseWithFmt( LayoutId::kValueError, "'%S' in __slots__ conflicts with class variable", &slot_str); } runtime->listAdd(thread, result, slot_str); } if (result.numItems() == 0) return NoneType::object(); listSort(thread, result); return *result; } static word estimateNumAttributes(Thread* thread, const Type& type) { HandleScope scope(thread); Runtime* runtime = thread->runtime(); // Collect set of in-object attributes by scanning the __init__ method of // each class in the MRO. This is used to determine the number of slots // allocated for in-object attributes when instances are created. Tuple mro(&scope, type.mro()); Dict attr_names(&scope, runtime->newDict()); for (word i = 0; i < mro.length(); i++) { Type mro_type(&scope, mro.at(i)); Object maybe_init(&scope, runtime->classConstructor(mro_type)); if (!maybe_init.isFunction()) { continue; } Function init(&scope, *maybe_init); RawObject maybe_code = init.code(); if (!maybe_code.isCode()) { continue; // native trampoline } Code code(&scope, maybe_code); if (code.code().isSmallInt()) { continue; // builtin trampoline } runtime->collectAttributes(code, attr_names); } return attr_names.numItems(); } static void setSlotAttributes(Thread* thread, const MutableTuple& dst, word start_index, const List& slots) { HandleScope scope(thread); Object descriptor(&scope, NoneType::object()); Object name(&scope, NoneType::object()); Runtime* runtime = thread->runtime(); for (word i = 0, offset = start_index * kPointerSize; i < slots.numItems(); i++, offset += kPointerSize) { descriptor = slots.at(i); AttributeInfo info(offset, AttributeFlags::kInObject | AttributeFlags::kFixedOffset | AttributeFlags::kInitWithUnbound); name = SlotDescriptor::cast(*descriptor).name(); dst.atPut(start_index + i, runtime->layoutNewAttribute(name, info)); SlotDescriptor::cast(*descriptor).setOffset(offset); } } static RawObject typeComputeLayout(Thread* thread, const Type& type, const Type& fixed_attr_base, bool enable_overflow, const Object& slots) { HandleScope scope(thread); Runtime* runtime = thread->runtime(); Tuple bases(&scope, type.bases()); // Create new layout. DCHECK(type.instanceLayout().isNoneType(), "must not set layout multiple times"); LayoutId layout_id = runtime->reserveLayoutId(thread); Layout layout(&scope, runtime->newLayout(layout_id)); // Compute in-object attributes. Layout base_layout(&scope, fixed_attr_base.instanceLayout()); Tuple base_attributes(&scope, base_layout.inObjectAttributes()); word num_base_attributes = base_attributes.length(); word num_slots = slots.isList() ? List::cast(*slots).numItems() : 0; word num_initial_attributes = num_base_attributes + num_slots; if (num_initial_attributes == 0) { layout.setInObjectAttributes(runtime->emptyTuple()); } else { MutableTuple attributes(&scope, runtime->newMutableTuple(num_initial_attributes)); attributes.replaceFromWith(0, *base_attributes, num_base_attributes); if (num_slots > 0) { List slots_list(&scope, *slots); setSlotAttributes(thread, attributes, /*start_index=*/num_base_attributes, slots_list); } layout.setInObjectAttributes(attributes.becomeImmutable()); } word num_extra_attributes = estimateNumAttributes(thread, type); layout.setNumInObjectAttributes(num_initial_attributes + num_extra_attributes); // Determine overflow mode. Type base(&scope, *type); Object overflow(&scope, base_layout.overflowAttributes()); Object base_overflow(&scope, NoneType::object()); bool bases_have_custom_dict = false; for (word i = 0, length = bases.length(); i < length; i++) { base = bases.at(i); if (base.hasCustomDict()) bases_have_custom_dict = true; base_overflow = Layout::cast(base.instanceLayout()).overflowAttributes(); if (overflow == base_overflow || base_overflow.isNoneType()) continue; if (overflow.isNoneType() && base_overflow.isTuple()) { overflow = *base_overflow; continue; } UNIMPLEMENTED("Mixing dict and tuple overflow"); } DCHECK(!overflow.isTuple() || Tuple::cast(*overflow).length() == 0, "base layout must not have tuple overflow attributes assigned"); if (enable_overflow && !bases_have_custom_dict && overflow.isNoneType()) { overflow = runtime->emptyTuple(); } layout.setOverflowAttributes(*overflow); runtime->layoutAtPut(layout_id, *layout); layout.setDescribedType(*type); type.setInstanceLayout(*layout); type.setInstanceLayoutId(layout.id()); type.setBuiltinBase(fixed_attr_base.builtinBase()); return NoneType::object(); } static RawObject getModuleNameAtFrame(Thread* thread, int depth) { HandleScope scope(thread); Frame* frame = thread->currentFrame(); for (int i = 0; i < depth && !frame->isSentinel(); i++) { frame = frame->previousFrame(); } if (frame->isSentinel()) return Error::notFound(); Object module_obj(&scope, frame->function().moduleObject()); // Some functions (e.g C-API extension functions) have no associated module. if (module_obj.isNoneType()) return Error::notFound(); Module module(&scope, *module_obj); return moduleAtById(thread, module, ID(__name__)); } static RawObject typeSetNames(Thread* thread, const Type& type, const Dict& dict) { HandleScope scope(thread); Runtime* runtime = thread->runtime(); Object key(&scope, NoneType::object()); Object value(&scope, NoneType::object()); Object result(&scope, NoneType::object()); Object set_name(&scope, NoneType::object()); for (word i = 0; dictNextItem(dict, &i, &key, &value);) { // If a method is looked up during bootstrapping (which we do during // typeNew(), there is a chance that the MRO won't be present yet. This // check allows dealing with that case gracefully. if (runtime->typeOf(*value).mro().isNoneType()) { continue; } set_name = Interpreter::lookupMethod(thread, value, ID(__set_name__)); if (set_name.isError()) { if (set_name.isErrorException()) { return *set_name; } DCHECK(set_name.isErrorNotFound(), "expected not found"); continue; } result = Interpreter::callMethod3(thread, set_name, value, type, key); if (result.isErrorException()) { Str type_name(&scope, type.name()); return thread->raiseWithFmtChainingPendingAsCause( LayoutId::kRuntimeError, "Error calling __set_name__ on '%T' instance in %S", &value, &type_name); } } return NoneType::object(); } RawObject typeNew(Thread* thread, const Type& metaclass, const Str& name, const Tuple& bases, const Dict& dict, word flags, bool inherit_slots, bool add_instance_dict) { HandleScope scope(thread); Runtime* runtime = thread->runtime(); LayoutId metaclass_id = Layout::cast(metaclass.instanceLayout()).id(); Type type(&scope, runtime->newTypeWithMetaclass(metaclass_id)); type.setName(*name); type.setBases(*bases); Object fixed_attr_base_obj(&scope, computeFixedAttributeBase(thread, bases)); if (fixed_attr_base_obj.isErrorException()) return *fixed_attr_base_obj; Type fixed_attr_base(&scope, *fixed_attr_base_obj); // Determine metaclass.mro method. Set `mro_method` to `None` if it is the // default `type.mro`. Object mro_method(&scope, Unbound::object()); if (metaclass_id != LayoutId::kType) { mro_method = Interpreter::lookupMethod(thread, type, ID(mro)); if (mro_method.isErrorException()) return *mro_method; if (mro_method.isErrorNotFound()) { Object mro_name(&scope, runtime->symbols()->at(ID(mro))); return objectRaiseAttributeError(thread, metaclass, mro_name); } Type builtin_type(&scope, runtime->typeAt(LayoutId::kType)); if (mro_method == typeAtById(thread, builtin_type, ID(mro))) { mro_method = Unbound::object(); } } Object mro_obj(&scope, NoneType::object()); if (mro_method.isUnbound()) { mro_obj = computeMro(thread, type); if (mro_obj.isErrorException()) return *mro_obj; } else { flags |= Type::Flag::kHasCustomMro; mro_obj = Interpreter::callMethod1(thread, mro_method, type); if (mro_obj.isErrorException()) return *mro_obj; if (!mro_obj.isTuple()) { mro_obj = thread->invokeFunction1(ID(builtins), ID(tuple), mro_obj); if (mro_obj.isErrorException()) return *mro_obj; CHECK(mro_obj.isTuple(), "Result of builtins.tuple should be tuple"); } } Tuple mro(&scope, *mro_obj); type.setMro(*mro); Object result(&scope, typeAssignFromDict(thread, type, dict)); if (result.isErrorException()) return *result; if (flags & Type::Flag::kIsCPythonHeaptype) { if (typeAtById(thread, type, ID(__module__)).isErrorNotFound()) { // Use depth=3 to skip over frame of `_type_init`, `type.__new__` // and `type.__call__`. Object module_name(&scope, getModuleNameAtFrame(thread, /*depth=*/3)); if (!module_name.isErrorNotFound()) { typeAtPutById(thread, type, ID(__module__), module_name); } } } else { // Non-heap-types in CPython have no `__module__` unless there is a // "." in `tp_name`. Remove the attribute when it equals "builtins". Object module_name(&scope, typeAtById(thread, type, ID(__module__))); if (module_name.isStr() && Str::cast(*module_name).equals(runtime->symbols()->at(ID(builtins)))) { typeRemoveById(thread, type, ID(__module__)); } } Object qualname(&scope, typeRemoveById(thread, type, ID(__qualname__))); if (qualname.isErrorNotFound()) { qualname = *name; } else if (!runtime->isInstanceOfStr(*qualname)) { return thread->raiseWithFmt(LayoutId::kTypeError, "type __qualname__ must be a str, not %T", &qualname); } type.setQualname(*qualname); // TODO(T53997177): Centralize type initialization typeAddDocstring(thread, type); // Special-case __new__ to be a staticmethod Object dunder_new(&scope, typeAtById(thread, type, ID(__new__))); if (dunder_new.isFunction()) { StaticMethod dunder_new_method(&scope, runtime->newStaticMethod()); dunder_new_method.setFunction(*dunder_new); typeAtPutById(thread, type, ID(__new__), dunder_new_method); } // Special-case __init_subclass__ to be a classmethod Object init_subclass(&scope, typeAtById(thread, type, ID(__init_subclass__))); if (init_subclass.isFunction()) { ClassMethod init_subclass_method(&scope, runtime->newClassMethod()); init_subclass_method.setFunction(*init_subclass); typeAtPutById(thread, type, ID(__init_subclass__), init_subclass_method); } // Special-case __class_getitem__ to be a classmethod Object class_getitem(&scope, typeAtById(thread, type, ID(__class_getitem__))); if (class_getitem.isFunction()) { ClassMethod class_getitem_method(&scope, runtime->newClassMethod()); class_getitem_method.setFunction(*class_getitem); typeAtPutById(thread, type, ID(__class_getitem__), class_getitem_method); } Object class_cell(&scope, typeAtById(thread, type, ID(__classcell__))); if (!class_cell.isErrorNotFound()) { DCHECK(class_cell.isCell(), "class cell must be a cell"); Cell::cast(*class_cell).setValue(*type); Object class_cell_name(&scope, runtime->symbols()->at(ID(__classcell__))); typeRemove(thread, type, class_cell_name); } // Analyze bases: Merge flags; add to subclasses lists; check for attribute // dictionaries. Type base_type(&scope, *type); bool bases_have_instance_dict = false; bool bases_have_type_slots = false; word bases_flags = 0; for (word i = 0; i < bases.length(); i++) { base_type = bases.at(i); bases_flags |= base_type.flags(); addSubclass(thread, base_type, type); bases_have_type_slots |= typeHasSlots(base_type); if (base_type.hasCustomDict()) bases_have_instance_dict = true; if (!Layout::cast(base_type.instanceLayout()).isSealed()) { bases_have_instance_dict = true; } } if (bases_have_instance_dict) add_instance_dict = false; flags |= (bases_flags & Type::kInheritableFlags); // Attribute flags are set explicitly here since `typeAssignFromDict` cannot // compute it properly with a partially created type object: // Computing this properly depends if Type::Flag::kHasCustomMro is set or not. for (SymbolId id : kAttributesForTypeFlags) { flags = computeAttributeTypeFlags(thread, type, id, flags); } // TODO(T66646764): This is a hack to make `type` look finalized. Remove this. type.setFlags(static_cast<Type::Flag>(flags)); if (bases_have_type_slots) { if (inherit_slots) { result = typeInheritSlots(thread, type, fixed_attr_base); if (result.isErrorException()) return *result; } } Object dunder_slots_obj(&scope, typeAtById(thread, type, ID(__slots__))); Object slots_obj(&scope, NoneType::object()); if (!dunder_slots_obj.isErrorNotFound()) { // NOTE: CPython raises an exception when slots are given to a subtype of a // type with type.tp_itemsize != 0, which means having a variable length. // For example, __slots__ in int's subtype or str's type is disallowed. // This behavior is ignored in Pyro since all objects' size in RawObject is // fixed in Pyro. if (runtime->isInstanceOfStr(*dunder_slots_obj)) { dunder_slots_obj = runtime->newTupleWith1(dunder_slots_obj); } else if (!runtime->isInstanceOfTuple(*dunder_slots_obj)) { Type tuple_type(&scope, runtime->typeAt(LayoutId::kTuple)); dunder_slots_obj = Interpreter::call1(thread, tuple_type, dunder_slots_obj); if (dunder_slots_obj.isErrorException()) { return *dunder_slots_obj; } CHECK(dunder_slots_obj.isTuple(), "Converting __slots__ to tuple failed"); } Tuple slots_tuple(&scope, *dunder_slots_obj); slots_obj = validateSlots(thread, type, slots_tuple, bases_have_instance_dict, &add_instance_dict); if (slots_obj.isErrorException()) { return *slots_obj; } if (!slots_obj.isNoneType()) { List slots(&scope, *slots_obj); // Add descriptors that mediate access to __slots__ attributes. Object slot_descriptor(&scope, NoneType::object()); Object slot_name(&scope, NoneType::object()); for (word i = 0, num_items = slots.numItems(); i < num_items; i++) { slot_name = slots.at(i); slot_descriptor = runtime->newSlotDescriptor(type, slot_name); typeAtPut(thread, type, slot_name, slot_descriptor); slots.atPut(i, *slot_descriptor); } } } if (!slots_obj.isNoneType()) { flags |= Type::Flag::kHasSlots; flags |= Type::Flag::kIsFixedAttributeBase; } type.setFlags(static_cast<Type::Flag>(flags)); // Add a `__dict__` descriptor when we added an instance dict. if (add_instance_dict && typeAtById(thread, type, ID(__dict__)).isErrorNotFound()) { typeAddInstanceDict(thread, type); } Function type_dunder_call(&scope, runtime->lookupNameInModule(thread, ID(_builtins), ID(_type_dunder_call))); type.setCtor(*type_dunder_call); result = typeComputeLayout(thread, type, fixed_attr_base, add_instance_dict, slots_obj); if (result.isErrorException()) return *result; result = typeSetNames(thread, type, dict); if (result.isErrorException()) return *result; return *type; } // NOTE: Keep the order of these type attributes same as the one from // rewriteOperation. static const SymbolId kUnimplementedTypeAttrUpdates[] = { // LOAD_ATTR, LOAD_METHOD ID(__getattribute__), // STORE_ATTR ID(__setattr__)}; void terminateIfUnimplementedTypeAttrCacheInvalidation( Thread* thread, const Type& type, const Object& attr_name) { RawObject unused = NoneType::object(); if (!attributeValueCellAt(*type, *attr_name, &unused)) { // No need for cache invalidation due to the absence of the attribute. return; } Runtime* runtime = thread->runtime(); DCHECK(Runtime::isInternedStr(thread, attr_name), "expected interned str"); for (uword i = 0; i < ARRAYSIZE(kUnimplementedTypeAttrUpdates); ++i) { if (attr_name == runtime->symbols()->at(kUnimplementedTypeAttrUpdates[i])) { UNIMPLEMENTED("unimplemented cache invalidation for type.%s update", Str::cast(*attr_name).toCStr()); } } } RawObject typeSetAttr(Thread* thread, const Type& type, const Object& name, const Object& value) { Runtime* runtime = thread->runtime(); DCHECK(runtime->isInternedStr(thread, name), "name must be an interned string"); // Make sure cache invalidation is correctly done for this. terminateIfUnimplementedTypeAttrCacheInvalidation(thread, type, name); HandleScope scope(thread); if (!type.hasMutableDict()) { return raiseTypeErrorCannotSetImmutable(thread, type); } // Check for a data descriptor Type metatype(&scope, runtime->typeOf(*type)); Object meta_attr(&scope, typeLookupInMro(thread, *metatype, *name)); if (!meta_attr.isError()) { Type meta_attr_type(&scope, runtime->typeOf(*meta_attr)); if (typeIsDataDescriptor(*meta_attr_type)) { Object set_result(&scope, Interpreter::callDescriptorSet( thread, meta_attr, type, value)); if (set_result.isError()) return *set_result; return NoneType::object(); } } // No data descriptor found, store the attribute in the type dict typeAtPut(thread, type, name, value); return NoneType::object(); } RawObject typeSetDunderClass(Thread* thread, const Object& self, const Type& new_type) { Runtime* runtime = thread->runtime(); // TODO(T60761420): A module can't change its type since its attributes are // cached based on object identity (and not layout id). This needs extra // cache invalidation code here to support it. if (runtime->isInstanceOfModule(*self)) { UNIMPLEMENTED("Cannot change type of modules"); } HandleScope scope(thread); Type instance_type(&scope, runtime->typeOf(*self)); // Builtin base type must match if (instance_type.builtinBase() != new_type.builtinBase()) { Str type_name(&scope, new_type.name()); return thread->raiseWithFmt( LayoutId::kTypeError, "__class__ assignment '%T' object layout differs from '%S'", &self, &type_name); } // Handle C Extension types if (instance_type.hasFlag(RawType::Flag::kHasNativeData) && new_type.hasFlag(RawType::Flag::kHasNativeData)) { // TODO(T60752528): Handle __class__ setter for C Extension Types UNIMPLEMENTED("Check if native memory is compatible"); } else if (instance_type.hasFlag(RawType::Flag::kHasNativeData) != new_type.hasFlag(RawType::Flag::kHasNativeData)) { Str type_name(&scope, new_type.name()); return thread->raiseWithFmt( LayoutId::kTypeError, "__class__ assignment '%T' object layout differs from '%S'", &self, &type_name); } // LOAD_ATTR_TYPE caches attributes based on the instance layout id of the // type. Create a copy of the layout with a new id, to avoid incorrectly // hitting the cache. if (runtime->isInstanceOfType(*self)) { Type type(&scope, *self); Layout instance_layout(&scope, type.instanceLayout()); Layout new_layout(&scope, runtime->layoutCreateCopy(thread, instance_layout)); type.setInstanceLayout(*new_layout); type.setInstanceLayoutId(new_layout.id()); } // Transition the layout Instance instance(&scope, *self); Layout from_layout(&scope, runtime->layoutOf(*instance)); Layout new_layout( &scope, runtime->layoutSetDescribedType(thread, from_layout, new_type)); instance.setLayoutId(new_layout.id()); return NoneType::object(); } static const BuiltinAttribute kTypeAttributes[] = { {ID(_type__attributes), RawType::kAttributesOffset, AttributeFlags::kHidden}, {ID(_type__attributes_remaining), RawType::kAttributesRemainingOffset, AttributeFlags::kHidden}, {ID(__mro__), RawType::kMroOffset, AttributeFlags::kReadOnly}, {ID(_type__bases), RawType::kBasesOffset, AttributeFlags::kHidden}, {ID(_type__instance_layout), RawType::kInstanceLayoutOffset, AttributeFlags::kHidden}, {ID(_type__instance_layout_id), RawType::kInstanceLayoutIdOffset, AttributeFlags::kHidden}, {ID(_type__name), RawType::kNameOffset, AttributeFlags::kHidden}, {ID(__doc__), RawType::kDocOffset}, {ID(_type__flags), RawType::kFlagsOffset, AttributeFlags::kHidden}, {ID(_type__slots), RawType::kSlotsOffset, AttributeFlags::kHidden}, {ID(_type__abstract_methods), RawType::kAbstractMethodsOffset, AttributeFlags::kHidden}, {ID(_type__subclasses), RawType::kSubclassesOffset, AttributeFlags::kHidden}, {ID(_type__proxy), RawType::kProxyOffset, AttributeFlags::kHidden}, {ID(_type__ctor), RawType::kCtorOffset, AttributeFlags::kHidden}, {ID(_type__qualname), RawType::kQualnameOffset, AttributeFlags::kHidden}, }; static const BuiltinAttribute kTypeProxyAttributes[] = { {ID(_type_proxy__type), TypeProxy::kTypeOffset, AttributeFlags::kHidden}, }; void initializeTypeTypes(Thread* thread) { HandleScope scope(thread); Type type(&scope, addBuiltinType(thread, ID(type), LayoutId::kType, /*superclass_id=*/LayoutId::kObject, kTypeAttributes, Type::kSize, /*basetype=*/true)); word flags = static_cast<word>(type.flags()); flags |= RawType::Flag::kHasCustomDict; type.setFlags(static_cast<Type::Flag>(flags)); addBuiltinType(thread, ID(type_proxy), LayoutId::kTypeProxy, /*superclass_id=*/LayoutId::kObject, kTypeProxyAttributes, TypeProxy::kSize, /*basetype=*/false); } RawObject METH(type, __base__)(Thread* thread, Arguments args) { HandleScope scope(thread); Object self_obj(&scope, args.get(0)); if (!thread->runtime()->isInstanceOfType(*self_obj)) { return thread->raiseRequiresType(self_obj, ID(type)); } Type self(&scope, *self_obj); Tuple bases(&scope, self.bases()); if (bases.length() == 0) { return NoneType::object(); } return computeFixedAttributeBase(thread, bases); } RawObject METH(type, __basicsize__)(Thread* thread, Arguments args) { HandleScope scope(thread); Runtime* runtime = thread->runtime(); Object self_obj(&scope, args.get(0)); if (!runtime->isInstanceOfType(*self_obj)) { return thread->raiseRequiresType(self_obj, ID(type)); } Type self(&scope, *self_obj); uword basicsize = typeGetBasicSize(self); return runtime->newIntFromUnsigned(basicsize); } RawObject METH(type, __delattr__)(Thread* thread, Arguments args) { HandleScope scope(thread); Object self_obj(&scope, args.get(0)); if (!thread->runtime()->isInstanceOfType(*self_obj)) { return thread->raiseRequiresType(self_obj, ID(type)); } Type self(&scope, *self_obj); Object name(&scope, args.get(1)); name = attributeName(thread, name); if (name.isErrorException()) return *name; return typeDeleteAttribute(thread, self, name); } RawObject METH(type, __flags__)(Thread* thread, Arguments args) { HandleScope scope(thread); Object self_obj(&scope, args.get(0)); Runtime* runtime = thread->runtime(); if (!runtime->isInstanceOfType(*self_obj)) { return thread->raiseRequiresType(self_obj, ID(type)); } Type self(&scope, *self_obj); uword cpython_flags = typeGetFlags(self); return runtime->newIntFromUnsigned(cpython_flags); } RawObject METH(type, __getattribute__)(Thread* thread, Arguments args) { HandleScope scope(thread); Object self_obj(&scope, args.get(0)); Runtime* runtime = thread->runtime(); if (!runtime->isInstanceOfType(*self_obj)) { return thread->raiseRequiresType(self_obj, ID(type)); } Type self(&scope, *self_obj); Object name(&scope, args.get(1)); name = attributeName(thread, name); if (name.isErrorException()) return *name; Object result(&scope, typeGetAttribute(thread, self, name)); if (result.isErrorNotFound()) { Object type_name(&scope, self.name()); return thread->raiseWithFmt(LayoutId::kAttributeError, "type object '%S' has no attribute '%S'", &type_name, &name); } return *result; } RawObject METH(type, __setattr__)(Thread* thread, Arguments args) { HandleScope scope(thread); Object self_obj(&scope, args.get(0)); Runtime* runtime = thread->runtime(); if (!runtime->isInstanceOfType(*self_obj)) { return thread->raiseRequiresType(self_obj, ID(type)); } Type self(&scope, *self_obj); Object name(&scope, args.get(1)); name = attributeName(thread, name); if (name.isErrorException()) return *name; Object value(&scope, args.get(2)); return typeSetAttr(thread, self, name, value); } RawObject METH(type, __subclasses__)(Thread* thread, Arguments args) { HandleScope scope(thread); Object self_obj(&scope, args.get(0)); Runtime* runtime = thread->runtime(); if (!runtime->isInstanceOfType(*self_obj)) { return thread->raiseRequiresType(self_obj, ID(type)); } Type self(&scope, *self_obj); Object subclasses_obj(&scope, self.subclasses()); if (subclasses_obj.isNoneType()) { return runtime->newList(); } // Check list for `None` and compact it. List subclasses(&scope, *subclasses_obj); word num_items = subclasses.numItems(); Object ref(&scope, NoneType::object()); Object value(&scope, NoneType::object()); word compact_shift = 0; for (word i = 0; i < num_items; i++) { ref = subclasses.at(i); value = WeakRef::cast(*ref).referent(); if (value.isNoneType()) { compact_shift++; continue; } if (compact_shift > 0) { subclasses.atPut(i - compact_shift, *ref); } } if (compact_shift > 0) { num_items -= compact_shift; subclasses.setNumItems(num_items); } List result(&scope, runtime->newList()); runtime->listEnsureCapacity(thread, result, num_items); for (word i = 0; i < num_items; i++) { ref = subclasses.at(i); value = WeakRef::cast(*ref).referent(); runtime->listAdd(thread, result, value); } return *result; } } // namespace py