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:
committed by
Damien George
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.
|
// __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) {
|
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]);
|
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 (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
|
// If we're dealing with native base class, then it applies to native sub-object
|
||||||
obj_obj = obj->subobj[0];
|
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 {
|
} else {
|
||||||
obj_obj = MP_OBJ_FROM_PTR(obj);
|
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
|
||||||
Reference in New Issue
Block a user