diff --git a/extmod/vfs_lfsx_file.c b/extmod/vfs_lfsx_file.c index ab5cce50088b9..56daa53e06869 100644 --- a/extmod/vfs_lfsx_file.c +++ b/extmod/vfs_lfsx_file.c @@ -90,9 +90,9 @@ mp_obj_t MP_VFS_LFSx(file_open)(mp_obj_t self_in, mp_obj_t path_in, mp_obj_t mod } #if LFS_BUILD_VERSION == 1 - MP_OBJ_VFS_LFSx_FILE *o = mp_obj_malloc_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, uint8_t, self->lfs.cfg->prog_size, type); + MP_OBJ_VFS_LFSx_FILE *o = mp_obj_malloc_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, file_buffer, uint8_t, self->lfs.cfg->prog_size, type); #else - MP_OBJ_VFS_LFSx_FILE *o = mp_obj_malloc_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, uint8_t, self->lfs.cfg->cache_size, type); + MP_OBJ_VFS_LFSx_FILE *o = mp_obj_malloc_var_with_finaliser(MP_OBJ_VFS_LFSx_FILE, file_buffer, uint8_t, self->lfs.cfg->cache_size, type); #endif o->vfs = self; #if !MICROPY_GC_CONSERVATIVE_CLEAR diff --git a/py/obj.h b/py/obj.h index de740bf4ccf4d..6157ee83cb6c2 100644 --- a/py/obj.h +++ b/py/obj.h @@ -558,6 +558,10 @@ typedef mp_obj_t (*mp_fun_kw_t)(size_t n, const mp_obj_t *, mp_map_t *); // If MP_TYPE_FLAG_INSTANCE_TYPE is set then this is an instance type (i.e. defined in Python). // If MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE is set then the "subscr" slot allows a stack // allocated slice to be passed in (no references to it will be retained after the call). +// If MP_TYPE_FLAG_IS_INSTANCED is set, then instances of this class have been created, +// i.e. possibly on non-finaliser-marked allocations if MP_TYPE_FLAG_HAS_FINALISER is unset. +// If MP_TYPE_FLAG_HAS_FINALISER is set, then instances of this class need to be instantiated +// using finalising allocations. #define MP_TYPE_FLAG_NONE (0x0000) #define MP_TYPE_FLAG_IS_SUBCLASSED (0x0001) #define MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS (0x0002) @@ -572,6 +576,8 @@ typedef mp_obj_t (*mp_fun_kw_t)(size_t n, const mp_obj_t *, mp_map_t *); #define MP_TYPE_FLAG_ITER_IS_STREAM (MP_TYPE_FLAG_ITER_IS_ITERNEXT | MP_TYPE_FLAG_ITER_IS_CUSTOM) #define MP_TYPE_FLAG_INSTANCE_TYPE (0x0200) #define MP_TYPE_FLAG_SUBSCR_ALLOWS_STACK_SLICE (0x0400) +#define MP_TYPE_FLAG_IS_INSTANCED (0x0800) +#define MP_TYPE_FLAG_HAS_FINALISER (0x1000) typedef enum { PRINT_STR = 0, @@ -947,11 +953,11 @@ void *mp_obj_malloc_helper(size_t num_bytes, const mp_obj_type_t *type); // Object allocation macros for allocating objects that have a finaliser. #if MICROPY_ENABLE_FINALISER #define mp_obj_malloc_with_finaliser(struct_type, obj_type) ((struct_type *)mp_obj_malloc_with_finaliser_helper(sizeof(struct_type), obj_type)) -#define mp_obj_malloc_var_with_finaliser(struct_type, var_type, var_num, obj_type) ((struct_type *)mp_obj_malloc_with_finaliser_helper(sizeof(struct_type) + sizeof(var_type) * (var_num), obj_type)) +#define mp_obj_malloc_var_with_finaliser(struct_type, var_field, var_type, var_num, obj_type) ((struct_type *)mp_obj_malloc_with_finaliser_helper(offsetof(struct_type, var_field) + sizeof(var_type) * (var_num), obj_type)) void *mp_obj_malloc_with_finaliser_helper(size_t num_bytes, const mp_obj_type_t *type); #else #define mp_obj_malloc_with_finaliser(struct_type, obj_type) mp_obj_malloc(struct_type, obj_type) -#define mp_obj_malloc_var_with_finaliser(struct_type, var_type, var_num, obj_type) mp_obj_malloc_var(struct_type, var_type, var_num, obj_type) +#define mp_obj_malloc_var_with_finaliser(struct_type, var_field, var_type, var_num, obj_type) mp_obj_malloc_var(struct_type, var_field, var_type, var_num, obj_type) #endif // These macros are derived from more primitive ones and are used to diff --git a/py/objtype.c b/py/objtype.c index d40f619fae241..890262cd7aab1 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -98,11 +98,22 @@ static MP_DEFINE_CONST_FUN_OBJ_KW(native_base_init_wrapper_obj, 1, native_base_i #if !MICROPY_CPYTHON_COMPAT static #endif -mp_obj_instance_t *mp_obj_new_instance(const mp_obj_type_t *class, const mp_obj_type_t **native_base) { +mp_obj_instance_t *mp_obj_new_instance(mp_obj_type_t *class, const mp_obj_type_t **native_base) { size_t num_native_bases = instance_count_native_bases(class, native_base); assert(num_native_bases < 2); - mp_obj_instance_t *o = mp_obj_malloc_var(mp_obj_instance_t, subobj, mp_obj_t, num_native_bases, class); + mp_obj_instance_t *o; + #if MICROPY_ENABLE_FINALISER + if (class->flags & MP_TYPE_FLAG_HAS_FINALISER) { + o = mp_obj_malloc_var_with_finaliser(mp_obj_instance_t, subobj, mp_obj_t, num_native_bases, class); + } else + #endif + { + o = mp_obj_malloc_var(mp_obj_instance_t, subobj, mp_obj_t, num_native_bases, class); + } mp_map_init(&o->members, 0); + if (mp_obj_is_instance_type(class)) { + class->flags |= MP_TYPE_FLAG_IS_INSTANCED; + } // Initialise the native base-class slot (should be 1 at most) with a valid // object. It doesn't matter which object, so long as it can be uniquely // distinguished from a native class that is initialised. @@ -285,7 +296,7 @@ static void instance_print(const mp_print_t *print, mp_obj_t self_in, mp_print_k mp_printf(print, "<%s object at %p>", mp_obj_get_type_str(self_in), self); } -static mp_obj_t mp_obj_instance_make_new(const mp_obj_type_t *self, size_t n_args, size_t n_kw, const mp_obj_t *args) { +static mp_obj_t mp_obj_instance_make_new(mp_obj_type_t *self, size_t n_args, size_t n_kw, const mp_obj_t *args) { assert(mp_obj_is_instance_type(self)); // look for __new__ function @@ -975,6 +986,15 @@ static bool check_for_special_accessors(mp_obj_t key, mp_obj_t value) { } #endif +#if MICROPY_ENABLE_FINALISER +static bool check_for_finaliser(mp_obj_t key) { + if (key == MP_OBJ_NEW_QSTR(MP_QSTR___del__)) { + return true; + } + return false; +} +#endif + #if MICROPY_PY_DESCRIPTORS // Shared data layout for the __set_name__ call and a linked list of calls to be made. typedef union _setname_list_t setname_list_t; @@ -1146,6 +1166,19 @@ static void type_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) { } #endif + #if MICROPY_ENABLE_FINALISER + // Check if we add a finaliser with this store + if (!(self->flags & MP_TYPE_FLAG_HAS_FINALISER)) { + if (check_for_finaliser(MP_OBJ_NEW_QSTR(attr))) { + if (self->flags & MP_TYPE_FLAG_IS_INSTANCED) { + // This class is already instanced, so can't have a finaliser added + mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("can't add special method to already-instanced class")); + } + self->flags |= MP_TYPE_FLAG_HAS_FINALISER; + } + } + #endif + // store attribute mp_map_elem_t *elem = mp_map_lookup(locals_map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND); elem->value = dest[1]; @@ -1199,12 +1232,15 @@ static mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals MP_ERROR_TEXT("type '%q' isn't an acceptable base type"), t->name); #endif } - #if ENABLE_SPECIAL_ACCESSORS if (mp_obj_is_instance_type(t)) { + #if ENABLE_SPECIAL_ACCESSORS t->flags |= MP_TYPE_FLAG_IS_SUBCLASSED; base_flags |= t->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS; + #endif + #if MICROPY_ENABLE_FINALISER + base_flags |= t->flags & MP_TYPE_FLAG_HAS_FINALISER; + #endif } - #endif } const void *base_protocol = NULL; @@ -1260,31 +1296,48 @@ static mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals setname_list_t *setname_tail = &setname_list; #endif - #if ENABLE_SPECIAL_ACCESSORS + #if ENABLE_SPECIAL_ACCESSORS || MICROPY_ENABLE_FINALISER || MICROPY_PY_DESCRIPTORS // Check if the class has any special accessor methods, + // check if it has a __del__ finaliser, // and accumulate bound __set_name__ methods that need to be called for (size_t i = 0; i < locals_ptr->map.alloc; i++) { - #if !MICROPY_PY_DESCRIPTORS - // __set_name__ needs to scan the entire locals map, can't early-terminate - if (o->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS) { - break; + if (true + #if ENABLE_SPECIAL_ACCESSORS + && (o->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS) + #endif + #if MICROPY_ENABLE_FINALISER + && (o->flags & MP_TYPE_FLAG_HAS_FINALISER) + #endif + #if MICROPY_PY_DESCRIPTORS + && false + #endif + ) { + break; // early terminate if everything we might possibly find has already been found } - #endif if (mp_map_slot_is_filled(&locals_ptr->map, i)) { const mp_map_elem_t *elem = &locals_ptr->map.table[i]; - if (!(o->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS) // elidable when the early-termination check is enabled + #if ENABLE_SPECIAL_ACCESSORS + if (!(o->flags & MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS) && check_for_special_accessors(elem->key, elem->value)) { o->flags |= MP_TYPE_FLAG_HAS_SPECIAL_ACCESSORS; } + #endif + + #if MICROPY_ENABLE_FINALISER + if (!(o->flags & MP_TYPE_FLAG_HAS_FINALISER) + && check_for_finaliser(elem->key)) { + o->flags |= MP_TYPE_FLAG_HAS_FINALISER; + } + #endif #if MICROPY_PY_DESCRIPTORS setname_tail = setname_maybe_bind_append(setname_tail, elem->key, elem->value); #endif } } - #endif // ENABLE_SPECIAL_ACCESSORS + #endif const mp_obj_type_t *native_base; size_t num_native_bases = instance_count_native_bases(o, &native_base); diff --git a/py/objtype.h b/py/objtype.h index 839cc6d146bb1..4e94e4a90bae8 100644 --- a/py/objtype.h +++ b/py/objtype.h @@ -39,7 +39,7 @@ typedef struct _mp_obj_instance_t { #if MICROPY_CPYTHON_COMPAT // this is needed for object.__new__ -mp_obj_instance_t *mp_obj_new_instance(const mp_obj_type_t *cls, const mp_obj_type_t **native_base); +mp_obj_instance_t *mp_obj_new_instance(mp_obj_type_t *cls, const mp_obj_type_t **native_base); #endif // these need to be exposed so mp_obj_is_callable can work correctly diff --git a/tests/cpydiff/core_class_delnotimpl.py b/tests/basics/class_del1.py similarity index 54% rename from tests/cpydiff/core_class_delnotimpl.py rename to tests/basics/class_del1.py index 6fac764ac9828..a50ec29bb1b50 100644 --- a/tests/cpydiff/core_class_delnotimpl.py +++ b/tests/basics/class_del1.py @@ -1,8 +1,8 @@ """ categories: Core,Classes -description: Special method __del__ not implemented for user-defined classes -cause: Unknown -workaround: Unknown +description: Special method __del__ is implemented for user-defined classes +cause: Known +workaround: Unnecessary """ import gc diff --git a/tests/basics/class_del_alreadyinstanced.py b/tests/basics/class_del_alreadyinstanced.py new file mode 100644 index 0000000000000..a2d45df2fae90 --- /dev/null +++ b/tests/basics/class_del_alreadyinstanced.py @@ -0,0 +1,20 @@ + +import gc + + +class Foo: + pass + + +def del_function(self): + print("__del__") + + +f = Foo() +try: + Foo.__del__ = del_function +except AttributeError: + print("AttributeError") +del f + +gc.collect() diff --git a/tests/basics/class_del_alreadyinstanced.py.exp b/tests/basics/class_del_alreadyinstanced.py.exp new file mode 100644 index 0000000000000..d169edffb4cfb --- /dev/null +++ b/tests/basics/class_del_alreadyinstanced.py.exp @@ -0,0 +1 @@ +AttributeError diff --git a/tests/basics/class_del_postadded.py b/tests/basics/class_del_postadded.py new file mode 100644 index 0000000000000..8912b2a462da3 --- /dev/null +++ b/tests/basics/class_del_postadded.py @@ -0,0 +1,17 @@ + +import gc + + +class Foo: + pass + + +def del_function(self): + print("__del__") + + +Foo.__del__ = del_function +f = Foo() +del f + +gc.collect()