/* * This file is part of the MicroPython project, http://micropython.org/ * * The MIT License (MIT) * * Copyright (c) 2026 Damien P. George * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "py/gc.h" #include "py/runtime.h" #if MICROPY_PY_WEAKREF // Macros to obfuscate a heap pointer as a small integer object. #define PTR_TO_INT_OBJ(ptr) (MP_OBJ_NEW_SMALL_INT(((uintptr_t)ptr) >> 1)) #define PTR_FROM_INT_OBJ(obj) ((void *)(MP_OBJ_SMALL_INT_VALUE((obj)) << 1)) // Macros to convert between a weak reference and a heap pointer. #define WEAK_REFERENCE_FROM_HEAP_PTR(ptr) PTR_TO_INT_OBJ(ptr) #define WEAK_REFERENCE_TO_HEAP_PTR(weak_ref) PTR_FROM_INT_OBJ(weak_ref) // Macros to manage ref-finalizer linked-list pointers. // - mp_obj_ref_t is obfuscated as a small integer object so it's not traced by the GC. // - mp_obj_finalize_t is stored as-is so it is traced by the GC. #define REF_FIN_LIST_OBJ_IS_FIN(r) (!mp_obj_is_small_int((r))) #define REF_FIN_LIST_OBJ_TO_PTR(r) ((mp_obj_ref_t *)(mp_obj_is_small_int((r)) ? PTR_FROM_INT_OBJ((r)) : MP_OBJ_TO_PTR((r)))) #define REF_FIN_LIST_OBJ_FROM_REF(r) (PTR_TO_INT_OBJ((r))) #define REF_FIN_LIST_OBJ_FROM_FIN(r) (MP_OBJ_FROM_PTR((r))) #define REF_FIN_LIST_OBJ_TAIL (PTR_TO_INT_OBJ(NULL)) // weakref.ref() instance. typedef struct _mp_obj_ref_t { mp_obj_base_t base; mp_obj_t ref_fin_next; mp_obj_t obj_weak_ref; mp_obj_t callback; } mp_obj_ref_t; // weakref.finalize() instance. // This is an extension of weakref.ref() and shares a lot of code with it. typedef struct _mp_obj_finalize_t { mp_obj_ref_t base; size_t n_args; size_t n_kw; mp_obj_t *args; } mp_obj_finalize_t; static const mp_obj_type_t mp_type_ref; static const mp_obj_type_t mp_type_finalize; static mp_obj_t ref___del__(mp_obj_t self_in); void gc_weakref_about_to_be_freed(void *ptr) { mp_obj_t idx = WEAK_REFERENCE_FROM_HEAP_PTR(ptr); mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_weakref_map), idx, MP_MAP_LOOKUP); if (elem != NULL) { // Mark element as being freed. elem->key = mp_const_none; } } void gc_weakref_sweep(void) { mp_map_t *map = &MP_STATE_VM(mp_weakref_map); for (size_t i = 0; i < map->alloc; i++) { if (map->table[i].key == mp_const_none) { // Element was just freed, so call all the registered callbacks. --map->used; map->table[i].key = MP_OBJ_SENTINEL; mp_obj_ref_t *ref = REF_FIN_LIST_OBJ_TO_PTR(map->table[i].value); map->table[i].value = MP_OBJ_NULL; while (ref != NULL) { // Invalidate the weak reference. assert(ref->obj_weak_ref != mp_const_none); ref->obj_weak_ref = mp_const_none; // Call any registered callbacks. if (ref->callback != mp_const_none) { nlr_buf_t nlr; if (nlr_push(&nlr) == 0) { if (ref->base.type == &mp_type_ref) { // weakref.ref() type. mp_call_function_1(ref->callback, MP_OBJ_FROM_PTR(ref)); } else { // weakref.finalize() type. mp_obj_finalize_t *fin = (mp_obj_finalize_t *)ref; mp_call_function_n_kw(fin->base.callback, fin->n_args, fin->n_kw, fin->args); } nlr_pop(); } else { mp_printf(MICROPY_ERROR_PRINTER, "Unhandled exception in weakref callback:\n"); mp_obj_print_exception(MICROPY_ERROR_PRINTER, MP_OBJ_FROM_PTR(nlr.ret_val)); } } // Unlink the node. mp_obj_ref_t *ref_fin_next = REF_FIN_LIST_OBJ_TO_PTR(ref->ref_fin_next); ref->ref_fin_next = REF_FIN_LIST_OBJ_TAIL; ref = ref_fin_next; } } } } static mp_obj_t mp_obj_ref_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { if (type == &mp_type_ref) { // weakref.ref() type. mp_arg_check_num(n_args, n_kw, 1, 2, false); } else { // weakref.finalize() type. mp_arg_check_num(n_args, n_kw, 2, MP_OBJ_FUN_ARGS_MAX, true); } // Validate the input object can have a weakref. void *ptr = NULL; if (mp_obj_is_obj(args[0])) { ptr = MP_OBJ_TO_PTR(args[0]); if (gc_nbytes(ptr) == 0) { ptr = NULL; } } if (ptr == NULL) { mp_raise_TypeError(MP_ERROR_TEXT("not a heap object")); } // Create or get the entry in mp_weakref_map corresponding to this object. mp_obj_t obj_weak_reference = WEAK_REFERENCE_FROM_HEAP_PTR(ptr); mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_weakref_map), obj_weak_reference, MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); if (elem->value == MP_OBJ_NULL) { // This heap object does not have any existing weakref's, so initialise it. elem->value = REF_FIN_LIST_OBJ_TAIL; gc_weakref_mark(ptr); } mp_obj_ref_t *self; if (type == &mp_type_ref) { // Create a new weakref.ref() object. self = mp_obj_malloc_with_finaliser(mp_obj_ref_t, type); // Link this new ref into the list of all refs/finalizers pointing to this object. // To ensure it will *NOT* be traced by the GC (the user must manually hold onto it), // store an integer version of the object after any weakref.finalize() objects (so // the weakref.finalize() objects continue to be traced by the GC). mp_obj_t *link = &elem->value; while (REF_FIN_LIST_OBJ_IS_FIN(*link)) { link = &REF_FIN_LIST_OBJ_TO_PTR(*link)->ref_fin_next; } self->ref_fin_next = *link; *link = REF_FIN_LIST_OBJ_FROM_REF(self); } else { // Create a new weakref.finalize() object. mp_obj_finalize_t *self_fin = mp_obj_malloc(mp_obj_finalize_t, type); self_fin->n_args = n_args - 2; self_fin->n_kw = n_kw; size_t n_args_kw = self_fin->n_args + self_fin->n_kw * 2; if (n_args_kw == 0) { self_fin->args = NULL; } else { self_fin->args = m_new(mp_obj_t, n_args_kw); memcpy(self_fin->args, args + 2, n_args_kw * sizeof(mp_obj_t)); } self = &self_fin->base; // Link this new finalizer into the list of all refs/finalizers pointing to this object. // To ensure it will be traced by the GC, store its pointer at the start of the list. self->ref_fin_next = elem->value; elem->value = REF_FIN_LIST_OBJ_FROM_FIN(self_fin); } // Populate the object weak reference, and the callback. self->obj_weak_ref = obj_weak_reference; if (n_args > 1) { self->callback = args[1]; } else { self->callback = mp_const_none; } return MP_OBJ_FROM_PTR(self); } static mp_obj_t mp_obj_ref_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_obj_ref_t *self = MP_OBJ_TO_PTR(self_in); if (self->obj_weak_ref == mp_const_none) { return mp_const_none; } if (self->base.type == &mp_type_ref) { // weakref.ref() type. return MP_OBJ_FROM_PTR(WEAK_REFERENCE_TO_HEAP_PTR(self->obj_weak_ref)); } else { // weakref.finalize() type. mp_obj_finalize_t *self_fin = MP_OBJ_TO_PTR(self_in); ref___del__(self_in); return mp_call_function_n_kw(self_fin->base.callback, self_fin->n_args, self_fin->n_kw, self_fin->args); } } static mp_obj_t ref___del__(mp_obj_t self_in) { mp_obj_ref_t *self = MP_OBJ_TO_PTR(self_in); mp_map_elem_t *elem = mp_map_lookup(&MP_STATE_VM(mp_weakref_map), self->obj_weak_ref, MP_MAP_LOOKUP); if (elem != NULL) { for (mp_obj_t *link = &elem->value; REF_FIN_LIST_OBJ_TO_PTR(*link) != NULL; link = &REF_FIN_LIST_OBJ_TO_PTR(*link)->ref_fin_next) { if (self == REF_FIN_LIST_OBJ_TO_PTR(*link)) { // Unlink and clear this node. *link = self->ref_fin_next; self->ref_fin_next = REF_FIN_LIST_OBJ_TAIL; self->obj_weak_ref = mp_const_none; break; } } } return mp_const_none; } static MP_DEFINE_CONST_FUN_OBJ_1(ref___del___obj, ref___del__); static mp_obj_t finalize_peek_detach_helper(mp_obj_t self_in, bool detach) { mp_obj_finalize_t *self = MP_OBJ_TO_PTR(self_in); if (self->base.obj_weak_ref == mp_const_none) { return mp_const_none; } mp_obj_t tuple[4] = { MP_OBJ_FROM_PTR(WEAK_REFERENCE_TO_HEAP_PTR(self->base.obj_weak_ref)), self->base.callback, mp_obj_new_tuple(self->n_args, self->args), mp_obj_dict_make_new(&mp_type_dict, 0, self->n_kw, self->args + self->n_args), }; if (detach) { ref___del__(self_in); } return mp_obj_new_tuple(MP_ARRAY_SIZE(tuple), tuple); } static mp_obj_t finalize_peek(mp_obj_t self_in) { return finalize_peek_detach_helper(self_in, false); } static MP_DEFINE_CONST_FUN_OBJ_1(finalize_peek_obj, finalize_peek); static mp_obj_t finalize_detach(mp_obj_t self_in) { return finalize_peek_detach_helper(self_in, true); } static MP_DEFINE_CONST_FUN_OBJ_1(finalize_detach_obj, finalize_detach); static void mp_obj_finalize_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { if (dest[0] != MP_OBJ_NULL) { // Store/delete attribute, unsupported. return; } if (attr == MP_QSTR_alive) { mp_obj_finalize_t *self = MP_OBJ_TO_PTR(self_in); dest[0] = mp_obj_new_bool(self->base.obj_weak_ref != mp_const_none); return; } else if (attr == MP_QSTR_peek) { dest[0] = MP_OBJ_FROM_PTR(&finalize_peek_obj); dest[1] = self_in; } else if (attr == MP_QSTR_detach) { dest[0] = MP_OBJ_FROM_PTR(&finalize_detach_obj); dest[1] = self_in; } } static const mp_rom_map_elem_t ref_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&ref___del___obj) }, }; static MP_DEFINE_CONST_DICT(ref_locals_dict, ref_locals_dict_table); static MP_DEFINE_CONST_OBJ_TYPE( mp_type_ref, MP_QSTR_ref, MP_TYPE_FLAG_NONE, make_new, mp_obj_ref_make_new, call, mp_obj_ref_call, locals_dict, &ref_locals_dict ); static MP_DEFINE_CONST_OBJ_TYPE( mp_type_finalize, MP_QSTR_finalize, MP_TYPE_FLAG_NONE, make_new, mp_obj_ref_make_new, call, mp_obj_ref_call, attr, mp_obj_finalize_attr ); static const mp_rom_map_elem_t mp_module_weakref_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_weakref) }, { MP_ROM_QSTR(MP_QSTR_ref), MP_ROM_PTR(&mp_type_ref) }, { MP_ROM_QSTR(MP_QSTR_finalize), MP_ROM_PTR(&mp_type_finalize) }, }; static MP_DEFINE_CONST_DICT(mp_module_weakref_globals, mp_module_weakref_globals_table); const mp_obj_module_t mp_module_weakref = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&mp_module_weakref_globals, }; MP_REGISTER_ROOT_POINTER(mp_map_t mp_weakref_map); MP_REGISTER_MODULE(MP_QSTR_weakref, mp_module_weakref); #endif // MICROPY_PY_WEAKREF