Skip to content

Commit 6720e77

Browse files
committed
py/objtype: Eliminate __set_name__ hazard.
Signed-off-by: Anson Mansfield <amansfield@mantaro.com>
1 parent 0c5e8c2 commit 6720e77

File tree

1 file changed

+43
-14
lines changed

1 file changed

+43
-14
lines changed

py/objtype.c

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -974,6 +974,48 @@ static bool check_for_special_accessors(mp_obj_t key, mp_obj_t value) {
974974
}
975975
#endif
976976

977+
#if MICROPY_PY_DESCRIPTORS
978+
static void run_set_name_hooks(mp_map_t *locals_map_orig, mp_obj_t owner) {
979+
// copy the dict so we can iterate safely even while __set_name__ potentially modifies the original
980+
mp_map_t locals_map = *locals_map_orig;
981+
locals_map.table = mp_local_alloc(locals_map.alloc * sizeof(mp_map_elem_t));
982+
memcpy(locals_map.table, locals_map_orig->table, locals_map.alloc * sizeof(mp_map_elem_t));
983+
984+
#if MICROPY_ENABLE_PYSTACK
985+
nlr_buf_t nlr;
986+
if (nlr_push(&nlr) == 0)
987+
// Note: on !MICROPY_ENABLE_PYSTACK ports, `mp_local_alloc` is just `alloca` and `mp_local_free` is a no-op.
988+
// Therefore we don't need to set an exception trap; the exception handler implicitly frees as it unwinds the stack.
989+
#endif
990+
{
991+
// use the copy to call __set_name__ on each
992+
for (size_t i = 0; i < locals_map.alloc; i++) {
993+
if (mp_map_slot_is_filled(&locals_map, i)) {
994+
mp_map_elem_t *elem = &(locals_map.table[i]);
995+
mp_obj_t set_name_method[4];
996+
mp_load_method_maybe(elem->value, MP_QSTR___set_name__, set_name_method);
997+
if (set_name_method[1] != MP_OBJ_NULL) {
998+
set_name_method[2] = owner;
999+
set_name_method[3] = elem->key;
1000+
mp_call_method_n_kw(2, 0, set_name_method);
1001+
}
1002+
}
1003+
}
1004+
1005+
#if MICROPY_ENABLE_PYSTACK
1006+
nlr_pop();
1007+
#endif
1008+
mp_local_free(locals_map.table);
1009+
}
1010+
#if MICROPY_ENABLE_PYSTACK
1011+
else {
1012+
mp_local_free(locals_map.table);
1013+
nlr_raise(nlr.ret_val); // TODO cpython raises a RuntimeError in this situation
1014+
}
1015+
#endif
1016+
}
1017+
#endif
1018+
9771019
static void type_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
9781020
(void)kind;
9791021
mp_obj_type_t *self = MP_OBJ_TO_PTR(self_in);
@@ -1242,20 +1284,7 @@ mp_obj_t mp_obj_new_type(qstr name, mp_obj_t bases_tuple, mp_obj_t locals_dict)
12421284
}
12431285

12441286
#if MICROPY_PY_DESCRIPTORS
1245-
// call __set_name__ on all entries (especially descriptors)
1246-
for (size_t i = 0; i < locals_map->alloc; i++) {
1247-
if (mp_map_slot_is_filled(locals_map, i)) {
1248-
elem = &(locals_map->table[i]);
1249-
1250-
mp_obj_t set_name_method[4];
1251-
mp_load_method_maybe(elem->value, MP_QSTR___set_name__, set_name_method);
1252-
if (set_name_method[1] != MP_OBJ_NULL) {
1253-
set_name_method[2] = MP_OBJ_FROM_PTR(o);
1254-
set_name_method[3] = elem->key;
1255-
mp_call_method_n_kw(2, 0, set_name_method);
1256-
}
1257-
}
1258-
}
1287+
run_set_name_hooks(locals_map, MP_OBJ_FROM_PTR(o));
12591288
#endif
12601289

12611290
return MP_OBJ_FROM_PTR(o);

0 commit comments

Comments
 (0)