diff options
author | Laurens Valk <laurens@pybricks.com> | 2022-11-21 21:35:26 +0100 |
---|---|---|
committer | Damien George <damien@micropython.org> | 2024-07-25 12:01:43 +1000 |
commit | 9ca668f881865958cbcc0e6341849a6133c4c7b4 (patch) | |
tree | 5bc69513c0fa68868aa7f6dedb69a542363eb8f1 | |
parent | 19b1333cb1376ef60376a07e8e76a41854014840 (diff) | |
download | micropython-9ca668f881865958cbcc0e6341849a6133c4c7b4.tar.gz micropython-9ca668f881865958cbcc0e6341849a6133c4c7b4.zip |
py/objtype: Avoid crash on calling members of uninitialized native type.
When subclassing a native type, calling native members in `__init__` before
`super().__init__()` has been called could cause a crash. In this
situation, `self` in `mp_convert_member_lookup` is the
`native_base_init_wrapper_obj`. The check added in this commit ensures
that an `AttributeError` is raised before this happens, which is consistent
with other failed lookups.
Also fix a typo in a related comment.
Signed-off-by: Laurens Valk <laurens@pybricks.com>
-rw-r--r-- | py/objtype.c | 8 | ||||
-rw-r--r-- | tests/misc/cexample_subclass.py | 37 | ||||
-rw-r--r-- | tests/misc/cexample_subclass.py.exp | 5 |
3 files changed, 49 insertions, 1 deletions
diff --git a/py/objtype.c b/py/objtype.c index b6d600e943..f7a65a6ca5 100644 --- a/py/objtype.c +++ b/py/objtype.c @@ -82,7 +82,7 @@ static int instance_count_native_bases(const mp_obj_type_t *type, const mp_obj_t } } -// This wrapper function is allows a subclass of a native type to call the +// This wrapper function allows a subclass of a native type to call the // __init__() method (corresponding to type->make_new) of the native type. static mp_obj_t native_base_init_wrapper(size_t n_args, const mp_obj_t *args) { mp_obj_instance_t *self = MP_OBJ_TO_PTR(args[0]); @@ -170,6 +170,12 @@ static void mp_obj_class_lookup(struct class_lookup_data *lookup, const mp_obj_t if (obj != NULL && mp_obj_is_native_type(type) && type != &mp_type_object /* object is not a real type */) { // If we're dealing with native base class, then it applies to native sub-object obj_obj = obj->subobj[0]; + #if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG + if (obj_obj == MP_OBJ_FROM_PTR(&native_base_init_wrapper_obj)) { + // But we shouldn't attempt lookups on object that is not yet instantiated. + mp_raise_msg(&mp_type_AttributeError, MP_ERROR_TEXT("call super().__init__() first")); + } + #endif // MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG } else { obj_obj = MP_OBJ_FROM_PTR(obj); } diff --git a/tests/misc/cexample_subclass.py b/tests/misc/cexample_subclass.py new file mode 100644 index 0000000000..9f52a2c737 --- /dev/null +++ b/tests/misc/cexample_subclass.py @@ -0,0 +1,37 @@ +# test subclassing custom native class + +try: + from cexample import AdvancedTimer +except ImportError: + print("SKIP") + raise SystemExit + + +class SubTimer(AdvancedTimer): + def __init__(self): + # At this point, self does not yet represent a AdvancedTimer instance. + print(self) + + # So lookups via type.attr handler will fail. + try: + self.seconds + except AttributeError: + print("AttributeError") + + # Also applies to builtin methods. + try: + self.time() + except AttributeError: + print("AttributeError") + + # Initialize base class. + super().__init__(self) + + # Now you can access methods and attributes normally. + self.time() + print(self.seconds) + self.seconds = 123 + print(self.seconds) + + +watch = SubTimer() diff --git a/tests/misc/cexample_subclass.py.exp b/tests/misc/cexample_subclass.py.exp new file mode 100644 index 0000000000..a035649e47 --- /dev/null +++ b/tests/misc/cexample_subclass.py.exp @@ -0,0 +1,5 @@ +<function> +AttributeError +AttributeError +0 +123 |