runtime/object-builtins.cpp (558 lines of code) (raw):
// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com)
#include "object-builtins.h"
#include <cinttypes>
#include "attributedict.h"
#include "builtins.h"
#include "descriptor-builtins.h"
#include "dict-builtins.h"
#include "frame.h"
#include "globals.h"
#include "ic.h"
#include "module-builtins.h"
#include "objects.h"
#include "runtime.h"
#include "thread.h"
#include "type-builtins.h"
namespace py {
RawObject objectRaiseAttributeError(Thread* thread, const Object& object,
const Object& name) {
return thread->raiseWithFmt(LayoutId::kAttributeError,
"'%T' object has no attribute '%S'", &object,
&name);
}
RawObject objectDeleteAttribute(Thread* thread, const Object& object,
const Object& name) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
// Check for a descriptor with __delete__
Type type(&scope, runtime->typeOf(*object));
Object type_attr(&scope, typeLookupInMro(thread, *type, *name));
if (!type_attr.isError() && runtime->isDeleteDescriptor(thread, type_attr)) {
return Interpreter::callDescriptorDelete(thread, type_attr, object);
}
// No delete descriptor found, delete from the instance
if (object.isInstance()) {
Instance instance(&scope, *object);
Object result(&scope, instanceDelAttr(thread, instance, name));
if (!result.isErrorNotFound()) return *result;
}
return thread->raiseWithFmt(LayoutId::kAttributeError,
"'%T' object has no attribute '%S'", &object,
&name);
}
RawObject instanceDelAttr(Thread* thread, const Instance& instance,
const Object& name) {
HandleScope scope(thread);
// Remove the reference to the attribute value from the instance
Runtime* runtime = thread->runtime();
Layout layout(&scope, runtime->layoutOf(*instance));
AttributeInfo info;
if (!Runtime::layoutFindAttribute(*layout, name, &info)) {
if (layout.hasDictOverflow()) {
word offset = layout.dictOverflowOffset();
Object overflow_dict_obj(&scope, instance.instanceVariableAt(offset));
if (!overflow_dict_obj.isNoneType()) {
Dict overflow_dict(&scope, *overflow_dict_obj);
Object result(&scope, dictRemoveByStr(thread, overflow_dict, name));
if (result.isError()) return *result;
return NoneType::object();
}
}
return Error::notFound();
}
if (info.isHidden()) {
return Error::notFound();
}
if (info.isReadOnly()) {
return thread->raiseWithFmt(LayoutId::kAttributeError,
"'%S' attribute is read-only", &name);
}
// Make the attribute invisible
Layout new_layout(&scope,
runtime->layoutDeleteAttribute(thread, layout, name, info));
LayoutId new_layout_id = new_layout.id();
instance.setHeader(instance.header().withLayoutId(new_layout_id));
if (info.isInObject()) {
instance.instanceVariableAtPut(info.offset(), NoneType::object());
} else {
MutableTuple overflow(
&scope, instance.instanceVariableAt(new_layout.overflowOffset()));
overflow.atPut(info.offset(), NoneType::object());
}
return NoneType::object();
}
RawObject instanceGetAttributeSetLocation(Thread* thread,
const Instance& instance,
const Object& name,
Object* location_out) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Layout layout(&scope, runtime->layoutOf(*instance));
AttributeInfo info;
if (Runtime::layoutFindAttribute(*layout, name, &info)) {
if (info.isHidden()) {
return Error::notFound();
}
if (info.isInObject()) {
if (location_out != nullptr) {
*location_out = SmallInt::fromWord(info.offset());
}
return instance.instanceVariableAt(info.offset());
}
word offset = info.offset();
if (location_out != nullptr) {
*location_out = SmallInt::fromWord(-offset - 1);
}
word tuple_offset = layout.overflowOffset();
return Tuple::cast(instance.instanceVariableAt(tuple_offset)).at(offset);
}
if (layout.hasDictOverflow()) {
word offset = layout.dictOverflowOffset();
Object overflow_dict_obj(&scope, instance.instanceVariableAt(offset));
if (!overflow_dict_obj.isNoneType()) {
Dict overflow_dict(&scope, *overflow_dict_obj);
return dictAtByStr(thread, overflow_dict, name);
}
}
return Error::notFound();
}
RawObject instanceGetAttribute(Thread* thread, const Instance& instance,
const Object& name) {
return instanceGetAttributeSetLocation(thread, instance, name, nullptr);
}
void instanceGrowOverflow(Thread* thread, const Instance& instance,
word length) {
HandleScope scope(thread);
Layout layout(&scope, thread->runtime()->layoutOf(*instance));
Tuple overflow(&scope, instance.instanceVariableAt(layout.overflowOffset()));
DCHECK(overflow.length() < length, "unexpected overflow");
MutableTuple new_overflow(&scope, thread->runtime()->newMutableTuple(length));
new_overflow.replaceFromWith(0, *overflow, overflow.length());
instance.instanceVariableAtPut(layout.overflowOffset(), *new_overflow);
}
static RawObject instanceSetAttrSetLocation(Thread* thread,
const Instance& instance,
const Object& name,
const Object& value,
Object* location_out) {
HandleScope scope(thread);
// If the attribute doesn't exist we'll need to transition the layout
Runtime* runtime = thread->runtime();
Layout layout(&scope, runtime->layoutOf(*instance));
AttributeInfo info;
if (!Runtime::layoutFindAttribute(*layout, name, &info)) {
if (!layout.hasTupleOverflow()) {
if (layout.hasDictOverflow()) {
word offset = layout.dictOverflowOffset();
Object overflow_dict_obj(&scope, instance.instanceVariableAt(offset));
if (overflow_dict_obj.isNoneType()) {
overflow_dict_obj = runtime->newDict();
instance.instanceVariableAtPut(offset, *overflow_dict_obj);
}
Dict overflow_dict(&scope, *overflow_dict_obj);
dictAtPutByStr(thread, overflow_dict, name, value);
return NoneType::object();
}
if (layout.isSealed()) {
return thread->raiseWithFmt(
LayoutId::kAttributeError,
"Cannot set attribute '%S' on sealed class '%T'", &name, &instance);
}
}
// Transition the layout.
Layout new_layout(
&scope, runtime->layoutAddAttribute(thread, layout, name, 0, &info));
if (info.isOverflow() &&
info.offset() >=
Tuple::cast(instance.instanceVariableAt(layout.overflowOffset()))
.length()) {
instanceGrowOverflow(thread, instance, info.offset() + 1);
}
instance.setLayoutId(new_layout.id());
layout = *new_layout;
} else if (info.isReadOnly()) {
return thread->raiseWithFmt(LayoutId::kAttributeError,
"'%T.%S' attribute is read-only", &instance,
&name);
} else if (info.isHidden()) {
return thread->raiseWithFmt(LayoutId::kAttributeError,
"'%T.%S' attribute cannot be set", &instance,
&name);
}
DCHECK(!thread->runtime()->isInstanceOfType(*instance),
"must not cache type attributes");
// Store the attribute
if (info.isInObject()) {
instance.instanceVariableAtPut(info.offset(), *value);
if (location_out != nullptr) {
*location_out = SmallInt::fromWord(info.offset());
}
} else {
MutableTuple::cast(instance.instanceVariableAt(layout.overflowOffset()))
.atPut(info.offset(), *value);
if (location_out != nullptr) {
*location_out = SmallInt::fromWord(-info.offset() - 1);
}
}
return NoneType::object();
}
RawObject instanceSetAttr(Thread* thread, const Instance& instance,
const Object& name, const Object& value) {
return instanceSetAttrSetLocation(thread, instance, name, value, nullptr);
}
RawObject objectGetAttributeSetLocation(Thread* thread, const Object& object,
const Object& name,
Object* location_out,
LoadAttrKind* kind) {
// Look for the attribute in the class
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Type type(&scope, runtime->typeOf(*object));
Object type_attr_location(&scope, NoneType::object());
Object type_attr(&scope, typeLookupInMroSetLocation(thread, *type, *name,
&type_attr_location));
if (!type_attr.isError()) {
// TODO(T56252621): Remove this once property gets cached.
if (type_attr.isProperty()) {
Object getter(&scope, Property::cast(*type_attr).getter());
DCHECK(!object.isNoneType(), "object cannot be NoneType");
if (getter.isFunction()) {
// We cache only function objects as a getter to simplify its usage.
if (location_out != nullptr) {
*location_out = *getter;
*kind = LoadAttrKind::kInstanceProperty;
}
return Interpreter::call1(thread, getter, object);
}
}
if (type_attr.isSlotDescriptor()) {
SlotDescriptor slot_descriptor(&scope, *type_attr);
Object owner(&scope, NoneType::object());
Object result(&scope,
slotDescriptorGet(thread, slot_descriptor, object, owner));
if (!result.isErrorException() && location_out != nullptr) {
// Cache slot_descriptor at success only to avoid type checking
// afterwards. However, unbound check should be performed when
// cache is hit.
*location_out = SmallInt::fromWord(slot_descriptor.offset());
*kind = LoadAttrKind::kInstanceSlotDescr;
}
return *result;
}
Type type_attr_type(&scope, runtime->typeOf(*type_attr));
if (typeIsDataDescriptor(*type_attr_type)) {
if (location_out != nullptr) {
*location_out = *type_attr;
*kind = LoadAttrKind::kInstanceTypeDescr;
}
return Interpreter::callDescriptorGet(thread, type_attr, object, type);
}
}
// No data descriptor found on the class, look at the instance.
if (object.isInstance()) {
Instance instance(&scope, *object);
Object result(&scope, instanceGetAttributeSetLocation(thread, instance,
name, location_out));
if (!result.isError()) {
if (location_out != nullptr) *kind = LoadAttrKind::kInstanceOffset;
return *result;
}
}
// Nothing found in the instance, if we found a non-data descriptor via the
// class search, use it.
if (!type_attr.isError()) {
if (type_attr.isFunction()) {
if (location_out != nullptr) {
*location_out = *type_attr;
*kind = LoadAttrKind::kInstanceFunction;
}
return runtime->newBoundMethod(type_attr, object);
}
Type type_attr_type(&scope, thread->runtime()->typeOf(*type_attr));
if (!typeIsNonDataDescriptor(*type_attr_type)) {
if (location_out != nullptr) {
*location_out = *type_attr;
*kind = LoadAttrKind::kInstanceType;
}
return *type_attr;
}
if (location_out != nullptr) {
*location_out = *type_attr;
*kind = LoadAttrKind::kInstanceTypeDescr;
}
return Interpreter::callDescriptorGet(thread, type_attr, object, type);
}
return Error::notFound();
}
RawObject objectGetAttribute(Thread* thread, const Object& object,
const Object& name) {
return objectGetAttributeSetLocation(thread, object, name, nullptr, nullptr);
}
RawObject objectNew(Thread* thread, const Type& type) {
HandleScope scope(thread);
if (!type.hasFlag(Type::Flag::kIsAbstract)) {
Layout layout(&scope, type.instanceLayout());
LayoutId id = layout.id();
Runtime* runtime = thread->runtime();
if (!isInstanceLayout(id)) {
Object type_name(&scope, type.name());
return thread->raiseWithFmt(
LayoutId::kTypeError,
"object.__new__(%S) is not safe. Use %S.__new__()", &type_name,
&type_name);
}
Instance result(&scope, runtime->newInstance(layout));
if (type.hasFlag(Type::Flag::kHasSlots)) {
Tuple attributes(&scope, layout.inObjectAttributes());
for (word i = 0, length = attributes.length(); i < length; i++) {
AttributeInfo info(Tuple::cast(attributes.at(i)).at(1));
if (info.isInitWithUnbound()) {
DCHECK(info.isInObject(), "in-object is expected");
result.instanceVariableAtPut(info.offset(), Unbound::object());
}
}
}
return *result;
}
// `type` is an abstract class and cannot be instantiated.
Object name(&scope, type.name());
Object comma(&scope, SmallStr::fromCStr(", "));
Object methods(&scope, type.abstractMethods());
Object sorted(&scope,
thread->invokeFunction1(ID(builtins), ID(sorted), methods));
if (sorted.isError()) return *sorted;
Object joined(&scope, thread->invokeMethod2(comma, ID(join), sorted));
if (joined.isError()) return *joined;
return thread->raiseWithFmt(
LayoutId::kTypeError,
"Can't instantiate abstract class %S with abstract methods %S", &name,
&joined);
}
RawObject objectSetAttrSetLocation(Thread* thread, const Object& object,
const Object& name, const Object& value,
Object* location_out) {
Runtime* runtime = thread->runtime();
// Check for a data descriptor
HandleScope scope(thread);
Type type(&scope, runtime->typeOf(*object));
Object type_attr(&scope, typeLookupInMro(thread, *type, *name));
if (!type_attr.isError()) {
if (type_attr.isSlotDescriptor()) {
SlotDescriptor slot_descriptor(&scope, *type_attr);
Object result(&scope,
slotDescriptorSet(thread, slot_descriptor, object, value));
if (!result.isErrorException() && location_out != nullptr) {
// Cache slot_descriptor at success only to avoid type checking
// afterwards. Note that writes via slot_descriptor are treated equally
// as ones to in-object instance attributes since the same cache
// invalidation rule applies to them.
*location_out = SmallInt::fromWord(slot_descriptor.offset());
}
return *result;
}
Type type_attr_type(&scope, runtime->typeOf(*type_attr));
if (typeIsDataDescriptor(*type_attr_type)) {
// Do not cache data descriptors.
Object set_result(&scope, Interpreter::callDescriptorSet(
thread, type_attr, object, value));
if (set_result.isError()) return *set_result;
return NoneType::object();
}
}
// No data descriptor found, store on the instance.
if (object.isInstance()) {
Instance instance(&scope, *object);
return instanceSetAttrSetLocation(thread, instance, name, value,
location_out);
}
return objectRaiseAttributeError(thread, object, name);
}
RawObject objectSetAttr(Thread* thread, const Object& object,
const Object& name, const Object& value) {
return objectSetAttrSetLocation(thread, object, name, value, nullptr);
}
RawObject objectDelItem(Thread* thread, const Object& object,
const Object& key) {
HandleScope scope(thread);
Object result(&scope, thread->invokeMethod2(object, ID(__delitem__), key));
if (result.isErrorNotFound()) {
return thread->raiseWithFmt(LayoutId::kTypeError,
"'%T' object does not support item deletion",
&object);
}
return *result;
}
RawObject objectGetItem(Thread* thread, const Object& object,
const Object& key) {
HandleScope scope(thread);
// This logic is replicated in Interpreter::binarySubscrUpdateCache for
// optimization.
Object result(&scope, thread->invokeMethod2(object, ID(__getitem__), key));
if (result.isErrorNotFound()) {
Runtime* runtime = thread->runtime();
if (runtime->isInstanceOfType(*object)) {
Type object_as_type(&scope, *object);
Str dunder_class_getitem_name(
&scope, runtime->symbols()->at(ID(__class_getitem__)));
Object class_getitem(&scope, typeGetAttribute(thread, object_as_type,
dunder_class_getitem_name));
if (!class_getitem.isErrorNotFound()) {
return Interpreter::call1(thread, class_getitem, key);
}
}
return thread->raiseWithFmt(LayoutId::kTypeError,
"'%T' object is not subscriptable", &object);
}
return *result;
}
RawObject objectSetItem(Thread* thread, const Object& object, const Object& key,
const Object& value) {
HandleScope scope(thread);
// Short-cut for the common case of dict. This also helps during bootstrapping
// as it allows us to use `objectSetItem` before `dict.__setitem__` is added.
if (object.isDict()) {
Dict object_dict(&scope, *object);
RawObject hash = Interpreter::hash(thread, key);
if (hash.isErrorException()) return hash;
dictAtPut(thread, object_dict, key, SmallInt::cast(hash).value(), value);
return NoneType::object();
}
Object result(&scope,
thread->invokeMethod3(object, ID(__setitem__), key, value));
if (result.isErrorNotFound()) {
return thread->raiseWithFmt(LayoutId::kTypeError,
"'%T' object does not support item assignment",
&object);
}
return *result;
}
RawObject METH(object, __delattr__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Object self(&scope, args.get(0));
Object name(&scope, args.get(1));
name = attributeName(thread, name);
if (name.isErrorException()) return *name;
Object result(&scope, objectDeleteAttribute(thread, self, name));
if (!result.isErrorException()) return *result;
if (runtime->isInstanceOfType(*self) || runtime->isInstanceOfModule(*self)) {
thread->clearPendingException();
return thread->raiseWithFmt(LayoutId::kTypeError,
"can't apply this __delattr__ to type '%T'",
&self);
}
return *result;
}
bool METH(object, __eq___intrinsic)(Thread* thread) {
RawObject result = Bool::trueObj();
if (thread->stackTop() != thread->stackPeek(1)) {
result = NotImplementedType::object();
}
thread->stackDrop(2);
thread->stackSetTop(result);
return true;
}
RawObject METH(object, __getattribute__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object name(&scope, args.get(1));
name = attributeName(thread, name);
if (name.isErrorException()) return *name;
Object result(&scope, objectGetAttribute(thread, self, name));
if (result.isErrorNotFound()) {
return objectRaiseAttributeError(thread, self, name);
}
return *result;
}
RawObject METH(object, __hash__)(Thread* thread, Arguments args) {
return SmallInt::fromWord(thread->runtime()->hash(args.get(0)));
}
RawObject METH(object, __init__)(Thread* thread, Arguments args) {
// Too many arguments were given. Determine if the __new__ was not overwritten
// or the __init__ was to throw a TypeError.
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Object self(&scope, args.get(0));
Tuple starargs(&scope, args.get(1));
Dict kwargs(&scope, args.get(2));
if (starargs.length() == 0 && kwargs.numItems() == 0) {
// object.__init__ doesn't do anything except throw a TypeError if the
// wrong number of arguments are given. It only throws if __new__ is not
// overloaded or __init__ was overloaded, else it allows the excess
// arguments.
return NoneType::object();
}
Type type(&scope, runtime->typeOf(*self));
if ((typeLookupInMroById(thread, *type, ID(__new__)) ==
runtime->objectDunderNew()) ||
(typeLookupInMroById(thread, *type, ID(__init__)) !=
runtime->objectDunderInit())) {
// Throw a TypeError if extra arguments were passed, and __new__ was not
// overwritten by self, or __init__ was overloaded by self.
return thread->raiseWithFmt(LayoutId::kTypeError,
"object.__init__() takes no parameters");
}
// Else it's alright to have extra arguments.
return NoneType::object();
}
RawObject METH(object, __new__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object type_obj(&scope, args.get(0));
if (!thread->runtime()->isInstanceOfType(*type_obj)) {
return thread->raiseRequiresType(type_obj, ID(type));
}
Type type(&scope, args.get(0));
return objectNew(thread, type);
}
RawObject METH(object, __setattr__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object self(&scope, args.get(0));
Object name(&scope, args.get(1));
name = attributeName(thread, name);
if (name.isErrorException()) return *name;
Object value(&scope, args.get(2));
return objectSetAttr(thread, self, name, value);
}
RawObject METH(object, __sizeof__)(Thread* thread, Arguments args) {
HandleScope scope(thread);
Object obj(&scope, args.get(0));
if (obj.isHeapObject()) {
HeapObject heap_obj(&scope, *obj);
return SmallInt::fromWord(heap_obj.size());
}
return SmallInt::fromWord(kPointerSize);
}
RawObject METH(NoneType, __new__)(Thread*, Arguments) {
return NoneType::object();
}
RawObject METH(NoneType, __repr__)(Thread* thread, Arguments args) {
if (!args.get(0).isNoneType()) {
return thread->raiseWithFmt(LayoutId::kTypeError,
"__repr__ expects None as first argument");
}
return thread->runtime()->symbols()->at(ID(None));
}
static const BuiltinAttribute kInstanceProxyAttributes[] = {
{ID(_instance), RawInstanceProxy::kInstanceOffset},
};
static const BuiltinAttribute kEnumerateAttributes[] = {
{ID(iterator), RawEnumerate::kIteratorOffset},
{ID(index), RawEnumerate::kIndexOffset}};
static void addObjectType(Thread* thread) {
HandleScope scope(thread);
Runtime* runtime = thread->runtime();
Layout layout(&scope, runtime->newLayout(LayoutId::kObject));
runtime->layoutAtPut(LayoutId::kObject, *layout);
Type type(&scope, runtime->newType());
layout.setDescribedType(*type);
type.setName(runtime->symbols()->at(ID(object)));
Tuple mro(&scope, runtime->newTupleWith1(type));
type.setMro(*mro);
type.setInstanceLayout(*layout);
type.setInstanceLayoutId(layout.id());
type.setBases(runtime->emptyTuple());
word flags = Type::Flag::kIsBasetype | Type::Flag::kIsFixedAttributeBase;
type.setFlagsAndBuiltinBase(static_cast<Type::Flag>(flags),
LayoutId::kObject);
// Manually create `__getattribute__` method to avoid bootstrap problems.
MutableTuple parameter_names(&scope, runtime->newMutableTuple(2));
parameter_names.atPut(0, runtime->symbols()->at(ID(self)));
parameter_names.atPut(1, runtime->symbols()->at(ID(name)));
Object name(&scope, runtime->symbols()->at(ID(__getattribute__)));
Object parameter_names_tuple(&scope, parameter_names.becomeImmutable());
Code code(&scope,
runtime->newBuiltinCode(
/*argcount=*/2, /*posonlyargcount=*/2, /*kwonlyargcount=*/0,
/*flags=*/0, METH(object, __getattribute__),
parameter_names_tuple, name));
Object qualname(
&scope, Runtime::internStrFromCStr(thread, "object.__getattribute__"));
Object module_obj(&scope, NoneType::object());
Function dunder_getattribute(
&scope, runtime->newFunctionWithCode(thread, qualname, code, module_obj));
typeAtPutById(thread, type, ID(__getattribute__), dunder_getattribute);
}
void initializeObjectTypes(Thread* thread) {
addObjectType(thread);
addImmediateBuiltinType(thread, ID(NoneType), LayoutId::kNoneType,
/*builtin_base=*/LayoutId::kNoneType,
/*superclass_id=*/LayoutId::kObject,
/*basetype=*/false);
addImmediateBuiltinType(thread, ID(NotImplementedType),
LayoutId::kNotImplementedType,
/*builtin_base=*/LayoutId::kNotImplementedType,
/*superclass_id=*/LayoutId::kObject,
/*basetype=*/false);
addImmediateBuiltinType(thread, ID(_UnboundType), LayoutId::kUnbound,
/*builtin_base=*/LayoutId::kUnbound,
/*superclass_id=*/LayoutId::kObject,
/*basetype=*/false);
addBuiltinType(thread, ID(instance_proxy), LayoutId::kInstanceProxy,
/*superclass_id=*/LayoutId::kObject, kInstanceProxyAttributes,
RawInstanceProxy::kSize, /*basetype=*/false);
addBuiltinType(thread, ID(enumerate), LayoutId::kEnumerate,
/*superclass_id=*/LayoutId::kObject, kEnumerateAttributes,
RawEnumerate::kSize, /*basetype=*/true);
}
} // namespace py