Skip to content

Commit 06593fb

Browse files
committed
py: Use a wrapper to explicitly check self argument of builtin methods.
Previous to this patch a call such as list.append(1, 2) would lead to a seg fault. This is because list.append is a builtin method and the first argument to such methods is always assumed to have the correct type. Now, when a builtin method is extracted like this it is wrapped in a checker object which checks the the type of the first argument before calling the builtin function. This feature is contrelled by MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG and is enabled by default. See issue adafruit#1216.
1 parent a193ced commit 06593fb

7 files changed

Lines changed: 112 additions & 3 deletions

File tree

bare-arm/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#define MICROPY_ENABLE_SOURCE_LINE (0)
1919
#define MICROPY_ENABLE_DOC_STRING (0)
2020
#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE)
21+
#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0)
2122
#define MICROPY_PY_BUILTINS_BYTEARRAY (0)
2223
#define MICROPY_PY_BUILTINS_MEMORYVIEW (0)
2324
#define MICROPY_PY_BUILTINS_FROZENSET (0)

minimal/mpconfigport.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#define MICROPY_ENABLE_SOURCE_LINE (0)
2020
#define MICROPY_ENABLE_DOC_STRING (0)
2121
#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE)
22+
#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0)
2223
#define MICROPY_PY_BUILTINS_BYTEARRAY (0)
2324
#define MICROPY_PY_BUILTINS_MEMORYVIEW (0)
2425
#define MICROPY_PY_BUILTINS_ENUMERATE (0)

py/mpconfig.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,6 +393,15 @@ typedef double mp_float_t;
393393
#define MICROPY_CAN_OVERRIDE_BUILTINS (0)
394394
#endif
395395

396+
// Whether to check that the "self" argument of a builtin method has the
397+
// correct type. Such an explicit check is only needed if a builtin
398+
// method escapes to Python land without a first argument, eg
399+
// list.append([], 1). Without this check such calls will have undefined
400+
// behaviour (usually segfault) if the first argument is the wrong type.
401+
#ifndef MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG
402+
#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (1)
403+
#endif
404+
396405
/*****************************************************************************/
397406
/* Fine control over Python builtins, classes, modules, etc */
398407

py/runtime.c

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,51 @@ mp_obj_t mp_load_attr(mp_obj_t base, qstr attr) {
887887
}
888888
}
889889

890+
#if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG
891+
892+
// The following "checked fun" type is local to the mp_convert_member_lookup
893+
// function, and serves to check that the first argument to a builtin function
894+
// has the correct type.
895+
896+
typedef struct _mp_obj_checked_fun_t {
897+
mp_obj_base_t base;
898+
const mp_obj_type_t *type;
899+
mp_obj_t fun;
900+
} mp_obj_checked_fun_t;
901+
902+
STATIC mp_obj_t checked_fun_call(mp_obj_t self_in, mp_uint_t n_args, mp_uint_t n_kw, const mp_obj_t *args) {
903+
mp_obj_checked_fun_t *self = self_in;
904+
if (n_args > 0) {
905+
const mp_obj_type_t *arg0_type = mp_obj_get_type(args[0]);
906+
if (arg0_type != self->type) {
907+
if (MICROPY_ERROR_REPORTING != MICROPY_ERROR_REPORTING_DETAILED) {
908+
nlr_raise(mp_obj_new_exception_msg(&mp_type_TypeError,
909+
"argument has wrong type"));
910+
} else {
911+
nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_TypeError,
912+
"argument should be a '%q' not a '%q'", self->type->name, arg0_type->name));
913+
}
914+
}
915+
}
916+
return mp_call_function_n_kw(self->fun, n_args, n_kw, args);
917+
}
918+
919+
STATIC const mp_obj_type_t mp_type_checked_fun = {
920+
{ &mp_type_type },
921+
.name = MP_QSTR_function,
922+
.call = checked_fun_call,
923+
};
924+
925+
STATIC mp_obj_t mp_obj_new_checked_fun(const mp_obj_type_t *type, mp_obj_t fun) {
926+
mp_obj_checked_fun_t *o = m_new_obj(mp_obj_checked_fun_t);
927+
o->base.type = &mp_type_checked_fun;
928+
o->type = type;
929+
o->fun = fun;
930+
return o;
931+
}
932+
933+
#endif // MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG
934+
890935
// Given a member that was extracted from an instance, convert it correctly
891936
// and put the result in the dest[] array for a possible method call.
892937
// Conversion means dealing with static/class methods, callables, and values.
@@ -903,9 +948,18 @@ void mp_convert_member_lookup(mp_obj_t self, const mp_obj_type_t *type, mp_obj_t
903948
// Don't try to bind types (even though they're callable)
904949
dest[0] = member;
905950
} else if (mp_obj_is_callable(member)) {
906-
// return a bound method, with self being this object
907-
dest[0] = member;
908-
dest[1] = self;
951+
#if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG
952+
if (self == MP_OBJ_NULL && mp_obj_get_type(member) == &mp_type_fun_builtin) {
953+
// we extracted a builtin method without a first argument, so we must
954+
// wrap this function in a type checker
955+
dest[0] = mp_obj_new_checked_fun(type, member);
956+
} else
957+
#endif
958+
{
959+
// return a bound method, with self being this object
960+
dest[0] = member;
961+
dest[1] = self;
962+
}
909963
} else {
910964
// class member is a value, so just return that value
911965
dest[0] = member;

tests/basics/class_use_other.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# check that we can use an instance of B in a method of A
2+
3+
class A:
4+
def store(a, b):
5+
a.value = b
6+
7+
class B:
8+
pass
9+
10+
b = B()
11+
A.store(b, 1)
12+
print(b.value)

tests/basics/self_type_check.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# make sure type of first arg (self) to a builtin method is checked
2+
3+
list.append
4+
5+
try:
6+
list.append()
7+
except TypeError as e:
8+
print("TypeError")
9+
10+
try:
11+
list.append(1)
12+
except TypeError as e:
13+
print("TypeError")
14+
15+
try:
16+
list.append(1, 2)
17+
except TypeError as e:
18+
print("TypeError")
19+
20+
l = []
21+
list.append(l, 2)
22+
print(l)
23+
24+
try:
25+
getattr(list, "append")(1, 2)
26+
except TypeError as e:
27+
print("TypeError")
28+
29+
l = []
30+
getattr(list, "append")(l, 2)
31+
print(l)

unix/mpconfigport_minimal.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#define MICROPY_OPT_COMPUTED_GOTO (0)
4545
#define MICROPY_OPT_CACHE_MAP_LOOKUP_IN_BYTECODE (0)
4646
#define MICROPY_CAN_OVERRIDE_BUILTINS (0)
47+
#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (0)
4748
#define MICROPY_CPYTHON_COMPAT (0)
4849
#define MICROPY_PY_BUILTINS_BYTEARRAY (0)
4950
#define MICROPY_PY_BUILTINS_MEMORYVIEW (0)

0 commit comments

Comments
 (0)