py/obj: Add static safety checks to mp_obj_is_type().

Commit d96cfd13e3 introduced a regression by breaking existing
users of mp_obj_is_type(.., &mp_obj_bool).  This function (and associated
helpers like mp_obj_is_int()) have some specific nuances, and mistakes like
this one can happen again.

This commit adds mp_obj_is_exact_type() which behaves like the the old
mp_obj_is_type().  The new mp_obj_is_type() has the same prototype but it
attempts to statically assert that it's not called with types which should
be checked using mp_obj_is_type().  If called with any of these types: int,
str, bool, NoneType - it will cause a compilation error.  Additional
checked types (e.g function types) can be added in the future.

Existing users of mp_obj_is_type() with the now "invalid" types, were
translated to use mp_obj_is_exact_type().

The use of MP_STATIC_ASSERT() is not bulletproof - usually GCC (and other
compilers) can't statically check conditions that are only known during
link-time (like variables' addresses comparison).  However, in this case,
GCC is able to statically detect these conditions, probably because it's
the exact same object - `&mp_type_int == &mp_type_int` is detected.
Misuses of this function with runtime-chosen types (e.g:
`mp_obj_type_t *x = ...; mp_obj_is_type(..., x);` won't be detected.  MSC
is unable to detect this, so we use MP_STATIC_ASSERT_NOT_MSC().

Compiling with this commit and without the fix for d96cfd13e3 shows
that it detects the problem.

Signed-off-by: Yonatan Goldschmidt <yon.goldschmidt@gmail.com>
This commit is contained in:
Yonatan Goldschmidt
2020-01-22 13:34:19 +01:00
committed by Damien George
parent 6670281472
commit 2a6ba47110
10 changed files with 38 additions and 24 deletions

View File

@@ -58,7 +58,7 @@ mp_obj_t mp_obj_int_from_bytes_impl(bool big_endian, size_t len, const byte *buf
}
void mp_obj_int_to_bytes_impl(mp_obj_t self_in, bool big_endian, size_t len, byte *buf) {
assert(mp_obj_is_type(self_in, &mp_type_int));
assert(mp_obj_is_exact_type(self_in, &mp_type_int));
mp_obj_int_t *self = self_in;
long long val = self->val;
if (big_endian) {
@@ -131,13 +131,13 @@ mp_obj_t mp_obj_int_binary_op(mp_binary_op_t op, mp_obj_t lhs_in, mp_obj_t rhs_i
if (mp_obj_is_small_int(lhs_in)) {
lhs_val = MP_OBJ_SMALL_INT_VALUE(lhs_in);
} else {
assert(mp_obj_is_type(lhs_in, &mp_type_int));
assert(mp_obj_is_exact_type(lhs_in, &mp_type_int));
lhs_val = ((mp_obj_int_t *)lhs_in)->val;
}
if (mp_obj_is_small_int(rhs_in)) {
rhs_val = MP_OBJ_SMALL_INT_VALUE(rhs_in);
} else if (mp_obj_is_type(rhs_in, &mp_type_int)) {
} else if (mp_obj_is_exact_type(rhs_in, &mp_type_int)) {
rhs_val = ((mp_obj_int_t *)rhs_in)->val;
} else {
// delegate to generic function to check for extra cases
@@ -284,7 +284,7 @@ mp_int_t mp_obj_int_get_checked(mp_const_obj_t self_in) {
#if MICROPY_PY_BUILTINS_FLOAT
mp_float_t mp_obj_int_as_float_impl(mp_obj_t self_in) {
assert(mp_obj_is_type(self_in, &mp_type_int));
assert(mp_obj_is_exact_type(self_in, &mp_type_int));
mp_obj_int_t *self = self_in;
return self->val;
}