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>
This commit is contained in:
parent
19b1333cb1
commit
9ca668f881
@ -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);
|
||||
}
|
||||
|
||||
37
tests/misc/cexample_subclass.py
Normal file
37
tests/misc/cexample_subclass.py
Normal file
@ -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()
|
||||
5
tests/misc/cexample_subclass.py.exp
Normal file
5
tests/misc/cexample_subclass.py.exp
Normal file
@ -0,0 +1,5 @@
|
||||
<function>
|
||||
AttributeError
|
||||
AttributeError
|
||||
0
|
||||
123
|
||||
Loading…
x
Reference in New Issue
Block a user