ext/Objects/object.cpp (595 lines of code) (raw):

// Copyright (c) Facebook, Inc. and its affiliates. (http://www.facebook.com) // object.c implementation #include "cpython-data.h" #include "cpython-func.h" #include "api-handle.h" #include "attributedict.h" #include "builtins-module.h" #include "bytes-builtins.h" #include "capi-typeslots.h" #include "capi.h" #include "dict-builtins.h" #include "extension-object.h" #include "frame.h" #include "list-builtins.h" #include "module-builtins.h" #include "object-builtins.h" #include "object-utils.h" #include "runtime.h" #include "str-builtins.h" #include "type-builtins.h" namespace py { PY_EXPORT PyTypeObject* PyBaseObject_Type_Ptr() { Runtime* runtime = Thread::current()->runtime(); return reinterpret_cast<PyTypeObject*>(ApiHandle::borrowedReference( runtime, runtime->typeAt(LayoutId::kObject))); } PY_EXPORT PyObject* PyEllipsis_Ptr() { Runtime* runtime = Thread::current()->runtime(); return ApiHandle::borrowedReference(runtime, runtime->ellipsis()); } PY_EXPORT PyTypeObject* PyEllipsis_Type_Ptr() { Runtime* runtime = Thread::current()->runtime(); return reinterpret_cast<PyTypeObject*>(ApiHandle::borrowedReference( runtime, runtime->typeAt(LayoutId::kEllipsis))); } PY_EXPORT PyTypeObject* PyEnum_Type_Ptr() { Runtime* runtime = Thread::current()->runtime(); return reinterpret_cast<PyTypeObject*>(ApiHandle::borrowedReference( runtime, runtime->typeAt(LayoutId::kEnumerate))); } PY_EXPORT PyObject* PyNone_Ptr() { return ApiHandle::handleFromImmediate(NoneType::object()); } PY_EXPORT PyObject* PyNotImplemented_Ptr() { return ApiHandle::handleFromImmediate(NotImplementedType::object()); } PY_EXPORT void _Py_Dealloc(PyObject* pyobj) { Thread* thread = Thread::current(); Runtime* runtime = thread->runtime(); HandleScope scope(thread); Object obj(&scope, ApiHandle::fromPyObject(pyobj)->asObject()); if (!runtime->isInstanceOfNativeProxy(*obj)) return; Type obj_type(&scope, runtime->typeOf(*obj)); // Do nothing for builtin types since we have our own GC to deallocate objects if (typeHasSlots(obj_type)) { destructor dealloc = reinterpret_cast<destructor>(typeSlotAt(obj_type, Py_tp_dealloc)); (dealloc)(pyobj); } } PY_EXPORT void _Py_NewReference(PyObject*) { UNIMPLEMENTED("_Py_NewReference"); } PY_EXPORT void Py_INCREF_Func(PyObject* obj) { ApiHandle* handle = ApiHandle::fromPyObject(obj); handle->incref(); } PY_EXPORT Py_ssize_t Py_REFCNT_Func(PyObject* obj) { ApiHandle* handle = ApiHandle::fromPyObject(obj); return handle->refcnt(); } PY_EXPORT void Py_SET_REFCNT_Func(PyObject* obj, Py_ssize_t refcnt) { ApiHandle* handle = ApiHandle::fromPyObject(obj); handle->setRefcnt(refcnt); } PY_EXPORT void Py_DECREF_Func(PyObject* obj) { ApiHandle* handle = ApiHandle::fromPyObject(obj); if (handle->isImmediate()) return; handle->decrefNoImmediate(); DCHECK(handle->refcnt() > 0 || !Thread::current()->runtime()->isInstanceOfNativeProxy( ApiHandle::fromPyObject(obj)->asObjectNoImmediate()), "native proxies should not reach refcount 0 without GC"); } PY_EXPORT Py_ssize_t* Py_SIZE_Func(PyVarObject* obj) { // Cannot call this on builtin types like `int`. DCHECK(Thread::current()->runtime()->isInstanceOfNativeProxy( ApiHandle::fromPyObject(reinterpret_cast<PyObject*>(obj)) ->asObject()), "must only be called on extension object"); return &(obj->ob_size); } PY_EXPORT int PyCallable_Check(PyObject* obj) { if (obj == nullptr) return 0; Thread* thread = Thread::current(); HandleScope scope(thread); Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); return thread->runtime()->isCallable(thread, object); } PY_EXPORT PyObject* PyObject_ASCII(PyObject* pyobj) { Thread* thread = Thread::current(); Runtime* runtime = thread->runtime(); if (pyobj == nullptr) { return ApiHandle::newReference(runtime, SmallStr::fromCStr("<NULL>")); } HandleScope scope(thread); Object obj(&scope, ApiHandle::fromPyObject(pyobj)->asObject()); Object result(&scope, thread->invokeFunction1(ID(builtins), ID(ascii), obj)); if (result.isError()) { return nullptr; } return ApiHandle::newReference(runtime, *result); } PY_EXPORT PyObject* PyObject_Bytes(PyObject* pyobj) { Thread* thread = Thread::current(); HandleScope scope(thread); Runtime* runtime = thread->runtime(); if (pyobj == nullptr) { static const byte value[] = "<NULL>"; return ApiHandle::newReference(runtime, SmallBytes::fromBytes(value)); } ApiHandle* handle = ApiHandle::fromPyObject(pyobj); Object obj(&scope, handle->asObject()); if (obj.isBytes()) { handle->incref(); return pyobj; } Object result(&scope, thread->invokeMethod1(obj, ID(__bytes__))); if (result.isError()) { if (result.isErrorException()) return nullptr; // Attribute lookup failed result = thread->invokeFunction1(ID(builtins), ID(_bytes_new), obj); if (result.isErrorException()) return nullptr; DCHECK(!result.isError(), "Couldn't call builtins._bytes_new"); } else if (!runtime->isInstanceOfBytes(*result)) { thread->raiseWithFmt(LayoutId::kTypeError, "__bytes__ returned non-bytes (type %T)", &result); return nullptr; } return ApiHandle::newReference(runtime, *result); } PY_EXPORT void PyObject_CallFinalizer(PyObject* self) { PyTypeObject* type = Py_TYPE(self); auto finalizer = reinterpret_cast<destructor>(PyType_GetSlot(type, Py_tp_finalize)); if (finalizer == nullptr) { // Nothing to finalize. return; } bool is_gc = (PyType_GetFlags(type) & Py_TPFLAGS_HAVE_GC) != 0; if (is_gc) { // TODO(T55208267): Support GC types UNIMPLEMENTED( "PyObject_CallFinalizer with finalizer and gc type is not " "yet supported"); } // TODO(T55208267): Check if the type has GC flags and the object is already // finalized and return early. tp_finalize should only be called once. (*finalizer)(self); // TODO(T55208267): Check if the type has GC flags set a bit on the object to // indicate that it has been finalized already. } PY_EXPORT int PyObject_CallFinalizerFromDealloc(PyObject* self) { DCHECK(self != nullptr, "self cannot be null"); if (Py_REFCNT(self) != 0) { Py_FatalError( "PyObject_CallFinalizerFromDealloc called on " "object with a non-zero refcount"); } // Temporarily resurrect the object. self->ob_refcnt = 1; // Finalize the object. PyObject_CallFinalizer(self); if (self->ob_refcnt == 1) { // tp_finalize did not resurrect the object, so undo the temporary // resurrection and put it to rest. self->ob_refcnt--; return 0; } DCHECK(Py_REFCNT(self) > 0, "refcnt must be positive"); // If we get here, tp_finalize resurrected the object. return -1; } PY_EXPORT int PyObject_DelAttr(PyObject* obj, PyObject* attr_name) { Thread* thread = Thread::current(); HandleScope scope(thread); Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); Object name_obj(&scope, ApiHandle::fromPyObject(attr_name)->asObject()); Object result(&scope, delAttribute(thread, object, name_obj)); return result.isErrorException() ? -1 : 0; } PY_EXPORT int PyObject_DelAttrString(PyObject* obj, const char* attr_name) { PyObject* str = PyUnicode_FromString(attr_name); if (str == nullptr) return -1; int result = PyObject_DelAttr(obj, str); Py_DECREF(str); return result; } PY_EXPORT PyObject* PyObject_Dir(PyObject* obj) { Thread* thread = Thread::current(); Frame* frame = thread->currentFrame(); if (obj == nullptr && frame->isSentinel()) { return nullptr; } HandleScope scope(thread); Runtime* runtime = thread->runtime(); if (obj == nullptr) { Object locals(&scope, frameLocals(thread, frame)); Object list_obj(&scope, NoneType::object()); if (locals.isDict()) { Dict locals_dict(&scope, *locals); list_obj = dictKeys(thread, locals_dict); } else if (locals.isModuleProxy()) { ModuleProxy module_proxy(&scope, *locals); Module module(&scope, module_proxy.module()); list_obj = moduleKeys(thread, module); } else { return nullptr; } List list(&scope, *list_obj); listSort(thread, list); return ApiHandle::newReference(runtime, *list); } Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); Type type(&scope, runtime->typeOf(*object)); Object func(&scope, typeLookupInMroById(thread, *type, ID(__dir__))); if (func.isError() || !func.isFunction()) { return nullptr; } Object sequence(&scope, Interpreter::call1(thread, func, object)); if (sequence.isError()) { return nullptr; } if (sequence.isList()) { List list(&scope, *sequence); listSort(thread, list); return ApiHandle::newReference(runtime, *list); } List list(&scope, runtime->newList()); Object result(&scope, thread->invokeMethodStatic2(LayoutId::kList, ID(extend), list, sequence)); if (result.isError()) { return nullptr; } listSort(thread, list); return ApiHandle::newReference(runtime, *list); } PY_EXPORT PyObject* PyObject_GenericGetAttr(PyObject* obj, PyObject* name) { Thread* thread = Thread::current(); HandleScope scope(thread); Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); Object name_obj(&scope, ApiHandle::fromPyObject(name)->asObject()); name_obj = attributeName(thread, name_obj); if (name_obj.isErrorException()) return nullptr; Object result(&scope, objectGetAttribute(thread, object, name_obj)); if (result.isError()) { if (result.isErrorNotFound()) { objectRaiseAttributeError(thread, object, name_obj); } return nullptr; } return ApiHandle::newReference(thread->runtime(), *result); } PY_EXPORT int _PyObject_LookupAttr(PyObject* obj, PyObject* name, PyObject** result) { // Replacements of PyObject_GetAttr() and _PyObject_GetAttrId() which don't // raise AttributeError. // Return 1 and set *result != NULL if an attribute is found. // Return 0 and set *result == NULL if an attribute is not found; an // AttributeError is silenced. // Return -1 and set *result == NULL if an error other than AttributeError is // raised. Thread* thread = Thread::current(); HandleScope scope(thread); Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); Object name_obj(&scope, ApiHandle::fromPyObject(name)->asObject()); Object name_str(&scope, attributeName(thread, name_obj)); if (name_str.isErrorException()) { // name was not a str instance *result = nullptr; return -1; } Runtime* runtime = thread->runtime(); Object result_obj(&scope, runtime->attributeAt(thread, object, name_obj)); if (!result_obj.isError()) { *result = ApiHandle::newReference(runtime, *result_obj); return 1; } DCHECK(result_obj.isErrorException(), "result should only be an exception"); if (thread->pendingExceptionMatches(LayoutId::kAttributeError)) { *result = nullptr; thread->clearPendingException(); return 0; } *result = nullptr; return -1; } PY_EXPORT int PyObject_GenericSetAttr(PyObject* obj, PyObject* name, PyObject* value) { Thread* thread = Thread::current(); HandleScope scope(thread); Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); Object name_obj(&scope, ApiHandle::fromPyObject(name)->asObject()); name_obj = attributeName(thread, name_obj); if (name_obj.isErrorException()) return -1; Object value_obj(&scope, ApiHandle::fromPyObject(value)->asObject()); Object result(&scope, objectSetAttr(thread, object, name_obj, value_obj)); if (result.isErrorException()) { return -1; } return 0; } PY_EXPORT int PyObject_GenericSetDict(PyObject* /* j */, PyObject* /* e */, void* /* t */) { UNIMPLEMENTED("PyObject_GenericSetDict"); } PY_EXPORT PyObject* PyObject_GetAttr(PyObject* obj, PyObject* name) { Thread* thread = Thread::current(); HandleScope scope(thread); Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); Object name_obj(&scope, ApiHandle::fromPyObject(name)->asObject()); Object result(&scope, getAttribute(thread, object, name_obj)); return result.isError() ? nullptr : ApiHandle::newReference(thread->runtime(), *result); } PY_EXPORT PyObject* PyObject_GetAttrString(PyObject* pyobj, const char* name) { DCHECK(pyobj != nullptr, "pyobj must not be nullptr"); DCHECK(name != nullptr, "name must not be nullptr"); Thread* thread = Thread::current(); HandleScope scope(thread); Object object(&scope, ApiHandle::fromPyObject(pyobj)->asObject()); Runtime* runtime = thread->runtime(); Object result(&scope, runtime->attributeAtByCStr(thread, object, name)); if (result.isError()) return nullptr; return ApiHandle::newReference(runtime, *result); } PY_EXPORT int PyObject_HasAttr(PyObject* pyobj, PyObject* pyname) { Thread* thread = Thread::current(); HandleScope scope(thread); Object obj(&scope, ApiHandle::fromPyObject(pyobj)->asObject()); Object name(&scope, ApiHandle::fromPyObject(pyname)->asObject()); Object result(&scope, hasAttribute(thread, obj, name)); if (result.isBool()) return Bool::cast(*result).value(); thread->clearPendingException(); return false; } PY_EXPORT int PyObject_HasAttrString(PyObject* pyobj, const char* name) { Thread* thread = Thread::current(); HandleScope scope(thread); Object obj(&scope, ApiHandle::fromPyObject(pyobj)->asObject()); Object name_str(&scope, Runtime::internStrFromCStr(thread, name)); Object result(&scope, thread->runtime()->attributeAt(thread, obj, name_str)); if (!result.isErrorException()) return true; thread->clearPendingException(); return false; } PY_EXPORT Py_hash_t PyObject_Hash(PyObject* obj) { DCHECK(obj != nullptr, "obj should not be nullptr"); Thread* thread = Thread::current(); HandleScope scope(thread); Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); Object result(&scope, Interpreter::hash(thread, object)); if (result.isErrorException()) return -1; return SmallInt::cast(*result).value(); } PY_EXPORT Py_hash_t PyObject_HashNotImplemented(PyObject* /* v */) { Thread* thread = Thread::current(); thread->raiseWithFmt(LayoutId::kTypeError, "unhashable type"); return -1; } PY_EXPORT PyObject* PyObject_Init(PyObject* obj, PyTypeObject* typeobj) { if (obj == nullptr) return PyErr_NoMemory(); // Create a managed proxy for the native instance Thread* thread = Thread::current(); Runtime* runtime = thread->runtime(); HandleScope scope(thread); Type type_obj(&scope, ApiHandle::fromPyTypeObject(typeobj)->asObject()); Layout layout(&scope, type_obj.instanceLayout()); Object instance(&scope, runtime->newInstance(layout)); return initializeExtensionObject(thread, obj, typeobj, instance); } PY_EXPORT PyVarObject* PyObject_InitVar(PyVarObject* obj, PyTypeObject* type, Py_ssize_t size) { if (obj == nullptr) return reinterpret_cast<PyVarObject*>(PyErr_NoMemory()); obj->ob_size = size; PyObject_Init(reinterpret_cast<PyObject*>(obj), type); return obj; } PY_EXPORT int PyObject_IsTrue(PyObject* obj) { DCHECK(obj != nullptr, "nullptr passed into PyObject_IsTrue"); Thread* thread = Thread::current(); HandleScope scope(thread); Object obj_obj(&scope, ApiHandle::fromPyObject(obj)->asObject()); Object result(&scope, Interpreter::isTrue(thread, *obj_obj)); if (result.isError()) { return -1; } return Bool::cast(*result).value(); } PY_EXPORT int PyObject_Not(PyObject* obj) { int res = PyObject_IsTrue(obj); if (res < 0) { return res; } return res == 0; } PY_EXPORT int PyObject_Print(PyObject* obj, FILE* fp, int flags) { if (PyErr_CheckSignals()) return -1; std::clearerr(fp); // Clear any previous error condition if (obj == nullptr) { std::fprintf(fp, "<nil>"); } else { PyObject* str = flags & Py_PRINT_RAW ? PyObject_Str(obj) : PyObject_Repr(obj); if (str == nullptr) return -1; if (!PyUnicode_Check(str)) { Thread::current()->raiseWithFmt(LayoutId::kTypeError, "str() or repr() returned '%s'", _PyType_Name(Py_TYPE(str))); Py_DECREF(str); return -1; } PyObject* bytes = PyUnicode_AsEncodedString(str, "utf-8", "backslashreplace"); Py_DECREF(str); if (bytes == nullptr) { return -1; } char* c_str = PyBytes_AsString(bytes); std::fputs(c_str, fp); Py_DECREF(bytes); } if (std::ferror(fp)) { PyErr_SetFromErrno(PyExc_IOError); std::clearerr(fp); return -1; } return 0; } // TODO(T38571506): Handle recursive objects safely. PY_EXPORT PyObject* PyObject_Repr(PyObject* obj) { Thread* thread = Thread::current(); Runtime* runtime = thread->runtime(); if (obj == nullptr) { return ApiHandle::newReference(runtime, SmallStr::fromCStr("<NULL>")); } HandleScope scope(thread); Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); Object result(&scope, thread->invokeMethod1(object, ID(__repr__))); if (result.isError()) { return nullptr; } if (!runtime->isInstanceOfStr(*result)) { thread->raiseWithFmt(LayoutId::kTypeError, "__repr__ returned non-str instance"); return nullptr; } return ApiHandle::newReference(runtime, *result); } PY_EXPORT PyObject* PyObject_RichCompare(PyObject* v, PyObject* w, int op) { DCHECK(CompareOp::LT <= op && op <= CompareOp::GE, "Bad op"); Thread* thread = Thread::current(); if (v == nullptr || w == nullptr) { if (!thread->hasPendingException()) { thread->raiseBadInternalCall(); } return nullptr; } // TODO(emacs): Recursive call check HandleScope scope(thread); Object left(&scope, ApiHandle::fromPyObject(v)->asObject()); Object right(&scope, ApiHandle::fromPyObject(w)->asObject()); Object result(&scope, Interpreter::compareOperation( thread, static_cast<CompareOp>(op), left, right)); if (result.isError()) { return nullptr; } return ApiHandle::newReference(thread->runtime(), *result); } PY_EXPORT int PyObject_RichCompareBool(PyObject* left, PyObject* right, int op) { // Quick result when objects are the same. Guarantees that identity implies // equality. if (left == right) { if (op == Py_EQ) { return 1; } if (op == Py_NE) { return 0; } } PyObject* res = PyObject_RichCompare(left, right, op); if (res == nullptr) { return -1; } int ok; if (PyBool_Check(res)) { ok = (res == Py_True); } else { ok = PyObject_IsTrue(res); } Py_DECREF(res); return ok; } PY_EXPORT PyObject* PyObject_SelfIter(PyObject* obj) { Py_INCREF(obj); return obj; } PY_EXPORT int PyObject_SetAttr(PyObject* obj, PyObject* name, PyObject* value) { if (value == nullptr) { return PyObject_DelAttr(obj, name); } Thread* thread = Thread::current(); HandleScope scope(thread); Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); Object name_obj(&scope, ApiHandle::fromPyObject(name)->asObject()); Object value_obj(&scope, ApiHandle::fromPyObject(value)->asObject()); Object result(&scope, setAttribute(thread, object, name_obj, value_obj)); return result.isErrorException() ? -1 : 0; } PY_EXPORT int PyObject_SetAttrString(PyObject* v, const char* name, PyObject* w) { PyObject* str = PyUnicode_FromString(name); if (str == nullptr) return -1; int result = PyObject_SetAttr(v, str, w); Py_DECREF(str); return result; } // TODO(T38571506): Handle recursive objects safely. PY_EXPORT PyObject* PyObject_Str(PyObject* obj) { Thread* thread = Thread::current(); Runtime* runtime = thread->runtime(); if (obj == nullptr) { return ApiHandle::newReference(runtime, SmallStr::fromCStr("<NULL>")); } HandleScope scope(thread); Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); Object result(&scope, thread->invokeMethod1(object, ID(__str__))); if (result.isError()) { return nullptr; } if (!runtime->isInstanceOfStr(*result)) { thread->raiseWithFmt(LayoutId::kTypeError, "__str__ returned non-str instance"); return nullptr; } return ApiHandle::newReference(runtime, *result); } PY_EXPORT void Py_DecRef(PyObject* obj) { if (obj == nullptr) return; Py_DECREF_Func(obj); } PY_EXPORT void Py_IncRef(PyObject* obj) { if (obj == nullptr) return; Py_INCREF_Func(obj); } PY_EXPORT int Py_ReprEnter(PyObject* obj) { Thread* thread = Thread::current(); HandleScope scope(thread); Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); Object result(&scope, thread->reprEnter(object)); if (result.isError()) { return -1; } return Bool::cast(*result).value(); } PY_EXPORT void Py_ReprLeave(PyObject* obj) { Thread* thread = Thread::current(); HandleScope scope(thread); Object object(&scope, ApiHandle::fromPyObject(obj)->asObject()); thread->reprLeave(object); } PY_EXPORT PyObject* _PyObject_GetAttrId(PyObject* /* v */, _Py_Identifier* /* e */) { UNIMPLEMENTED("_PyObject_GetAttrId"); } PY_EXPORT int _PyObject_HasAttrId(PyObject* /* v */, _Py_Identifier* /* e */) { UNIMPLEMENTED("_PyObject_HasAttrId"); } PY_EXPORT PyObject* _PyObject_New(PyTypeObject* type) { Thread* thread = Thread::current(); HandleScope scope(thread); Type type_obj(&scope, ApiHandle::fromPyTypeObject(type)->asObject()); if (!type_obj.hasNativeData()) { // Since the type will be pointed to by the layout as long as there are any // objects of its type, we don't need to INCREF the type object if it // doesn't have NativeData. Layout layout(&scope, type_obj.instanceLayout()); Runtime* runtime = thread->runtime(); return ApiHandle::newReference(runtime, runtime->newInstance(layout)); } PyObject* obj = static_cast<PyObject*>(PyObject_MALLOC(_PyObject_SIZE(type))); if (obj == nullptr) return PyErr_NoMemory(); return PyObject_INIT(obj, type); } PY_EXPORT PyVarObject* _PyObject_NewVar(PyTypeObject* type, Py_ssize_t nitems) { PyObject* obj = static_cast<PyObject*>(PyObject_MALLOC(_PyObject_VAR_SIZE(type, nitems))); if (obj == nullptr) return reinterpret_cast<PyVarObject*>(PyErr_NoMemory()); return PyObject_INIT_VAR(obj, type, nitems); } PY_EXPORT PyTypeObject* _PyNone_Type_Ptr() { Runtime* runtime = Thread::current()->runtime(); return reinterpret_cast<PyTypeObject*>(ApiHandle::borrowedReference( runtime, runtime->typeAt(LayoutId::kNoneType))); } PY_EXPORT PyTypeObject* _PyNotImplemented_Type_Ptr() { Runtime* runtime = Thread::current()->runtime(); return reinterpret_cast<PyTypeObject*>(ApiHandle::borrowedReference( runtime, runtime->typeAt(LayoutId::kNotImplementedType))); } PY_EXPORT int _PyObject_SetAttrId(PyObject* /* v */, _Py_Identifier* /* e */, PyObject* /* w */) { UNIMPLEMENTED("_PyObject_SetAttrId"); } PY_EXPORT void _PyTrash_deposit_object(PyObject* /* p */) { UNIMPLEMENTED("_PyTrash_deposit_object"); } PY_EXPORT void _PyTrash_destroy_chain() { UNIMPLEMENTED("_PyTrash_destroy_chain"); } PY_EXPORT void _PyTrash_thread_deposit_object(PyObject* /* p */) { UNIMPLEMENTED("_PyTrash_thread_deposit_object"); } PY_EXPORT void _PyTrash_thread_destroy_chain() { UNIMPLEMENTED("_PyTrash_thread_destroy_chain"); } } // namespace py