Exception.__init__ raises TypeError if overridden and called by subclass
>>> class A(Exception):
... def __init__(self):
... Exception.__init__(self)
...
>>> a = A()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in __init__
TypeError: argument should be a 'Exception' not a 'A'
Why is Exception being enforced here, even if A is a subclass of Exception? That makes it hard to subclass from Exception and override __init__.
py/runtime: 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. This check ensures that a TypeError is raised when this happens.
Also fix a typo in a related comment.
This is an attempt at fixing the following crash in Pybricks MicroPython
from pybricks.parameters import Port
from pybricks.pupdevices import Motor # Builtin type
class NewMotor(Motor):
def __init__(self, port):
# Would crash. After this PR it raises a TypeError.
print(self.angle())
super().__init__(port)
# Would work.
print(self.angle())
motor = NewMotor(Port.A)
EDIT: This isn't quite the right solution as seen in the tests, so I'll have to have another look.
This variant appears to work without breaking the other tests, but still needs some cleaning up:
diff --git a/py/objtype.c b/py/objtype.c
index e0753aace..101a82beb 100644
--- a/py/objtype.c
+++ b/py/objtype.c
@@ -91,7 +91,7 @@ STATIC mp_obj_t native_base_init_wrapper(size_t n_args, const mp_obj_t *args) {
self->subobj[0] = native_base->make_new(native_base, n_args - 1, 0, args + 1);
return mp_const_none;
}
-STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(native_base_init_wrapper_obj, 1, MP_OBJ_FUN_ARGS_MAX, native_base_init_wrapper);
+MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(native_base_init_wrapper_obj, 1, MP_OBJ_FUN_ARGS_MAX, native_base_init_wrapper);
#if !MICROPY_CPYTHON_COMPAT
STATIC
diff --git a/py/runtime.c b/py/runtime.c
index 5a7474fba..62254af0d 100644
--- a/py/runtime.c
+++ b/py/runtime.c
@@ -1111,13 +1111,13 @@ void mp_convert_member_lookup(mp_obj_t self, const mp_obj_type_t *type, mp_obj_t
#endif
else {
// Return a (built-in) bound method, with self being this object.
- #if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG
- // Ensures that self is not uninitialized object.
- dest[0] = mp_obj_new_checked_fun(type, member);
- #else
dest[0] = member;
- #endif
dest[1] = self;
+ extern const mp_obj_fun_builtin_var_t native_base_init_wrapper_obj;
+ if (self == MP_OBJ_FROM_PTR(&native_base_init_wrapper_obj)) {
+ // object not initialized yet
+ mp_raise_msg(&mp_type_RuntimeError, MP_ERROR_TEXT("call super().__init__() first"));
+ }
}
} else {
// Return a bound method, with self being this object.