Skip to content

Commit 7891321

Browse files
committed
py: Be more restrictive binding self when looking up instance attrs.
When looking up and extracting an attribute of an instance, some attributes must bind self as the first argument to make a working method call. Previously to this patch, any attribute that was callable had self bound as the first argument. But Python specs require the check to be more restrictive, and only functions, closures and generators should have self bound as the first argument Addresses issue adafruit#1675.
1 parent 84b245f commit 7891321

2 files changed

Lines changed: 59 additions & 1 deletion

File tree

py/runtime.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -947,7 +947,11 @@ void mp_convert_member_lookup(mp_obj_t self, const mp_obj_type_t *type, mp_obj_t
947947
} else if (MP_OBJ_IS_TYPE(member, &mp_type_type)) {
948948
// Don't try to bind types (even though they're callable)
949949
dest[0] = member;
950-
} else if (mp_obj_is_callable(member)) {
950+
} else if (MP_OBJ_IS_FUN(member)
951+
|| (MP_OBJ_IS_OBJ(member)
952+
&& (((mp_obj_base_t*)MP_OBJ_TO_PTR(member))->type->name == MP_QSTR_closure
953+
|| ((mp_obj_base_t*)MP_OBJ_TO_PTR(member))->type->name == MP_QSTR_generator))) {
954+
// only functions, closures and generators objects can be bound to self
951955
#if MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG
952956
if (self == MP_OBJ_NULL && mp_obj_get_type(member) == &mp_type_fun_builtin) {
953957
// we extracted a builtin method without a first argument, so we must

tests/basics/class_bind_self.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# test for correct binding of self when accessing attr of an instance
2+
3+
class A:
4+
def __init__(self, arg):
5+
self.val = arg
6+
def __str__(self):
7+
return 'A.__str__ ' + str(self.val)
8+
def __call__(self, arg):
9+
return 'A.__call__', arg
10+
def foo(self, arg):
11+
return 'A.foo', self.val, arg
12+
13+
def make_closure(x_in):
14+
x = x_in
15+
def closure(y):
16+
return x, y is c
17+
return closure
18+
19+
class C:
20+
# these act like methods and bind self
21+
22+
def f1(self, arg):
23+
return 'C.f1', self is c, arg
24+
f2 = lambda self, arg: ('C.f2', self is c, arg)
25+
f3 = make_closure('f3') # closure
26+
def f4(self, arg): # generator
27+
yield self is c, arg
28+
29+
# these act like simple variables and don't bind self
30+
31+
f5 = int # builtin type
32+
f6 = abs # builtin function
33+
f7 = A # user type
34+
f8 = A(8) # user instance which is callable
35+
f9 = A(9).foo # user bound method
36+
37+
c = C()
38+
print(c.f1(1))
39+
print(c.f2(2))
40+
print(c.f3())
41+
print(next(c.f4(4)))
42+
print(c.f5(5))
43+
#print(c.f6(-6)) not working in uPy
44+
print(c.f7(7))
45+
print(c.f8(8))
46+
print(c.f9(9))
47+
48+
# not working in uPy
49+
#class C(list):
50+
# # this acts like a method and binds self
51+
# f1 = list.extend
52+
#c = C()
53+
#c.f1([3, 1, 2])
54+
#print(c)

0 commit comments

Comments
 (0)