Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions extmod/vfs_lfsx_file.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 8 additions & 2 deletions py/obj.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down
79 changes: 66 additions & 13 deletions py/objtype.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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];
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion py/objtype.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down
20 changes: 20 additions & 0 deletions tests/basics/class_del_alreadyinstanced.py
Original file line number Diff line number Diff line change
@@ -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()
1 change: 1 addition & 0 deletions tests/basics/class_del_alreadyinstanced.py.exp
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
AttributeError
17 changes: 17 additions & 0 deletions tests/basics/class_del_postadded.py
Original file line number Diff line number Diff line change
@@ -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()
Loading