Skip to content

Commit 45aa65b

Browse files
committed
webassembly/objjsproxy: Fix binding of self to JavaScript methods.
Fixes a bug in the binding of self/this to JavaScript methods. The new semantics match Pyodide's behaviour, at least for the included tests. Signed-off-by: Damien George <damien@micropython.org>
1 parent 9b61bb9 commit 45aa65b

File tree

5 files changed

+138
-37
lines changed

5 files changed

+138
-37
lines changed

ports/webassembly/modjs.c

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,7 @@
3434
// js module
3535

3636
void mp_module_js_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
37-
mp_obj_jsproxy_t global_this;
38-
global_this.ref = MP_OBJ_JSPROXY_REF_GLOBAL_THIS;
39-
mp_obj_jsproxy_attr(MP_OBJ_FROM_PTR(&global_this), attr, dest);
37+
mp_obj_jsproxy_global_this_attr(attr, dest);
4038
}
4139

4240
static const mp_rom_map_elem_t mp_module_js_globals_table[] = {

ports/webassembly/objjsproxy.c

Lines changed: 81 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ EM_JS(bool, has_attr, (int jsref, const char *str), {
4343
});
4444

4545
// *FORMAT-OFF*
46-
EM_JS(bool, lookup_attr, (int jsref, const char *str, uint32_t * out), {
46+
EM_JS(int, lookup_attr, (int jsref, const char *str, uint32_t * out), {
4747
const base = proxy_js_ref[jsref];
4848
const attr = UTF8ToString(str);
4949

@@ -54,23 +54,17 @@ EM_JS(bool, lookup_attr, (int jsref, const char *str, uint32_t * out), {
5454
// - Otherwise, the attribute does not exist.
5555
let value = base[attr];
5656
if (value !== undefined || attr in base) {
57-
if (typeof value === "function") {
58-
if (base !== globalThis) {
59-
if ("_ref" in value) {
60-
// This is a proxy of a Python function, it doesn't need
61-
// binding. And not binding it means if it's passed back
62-
// to Python then it can be extracted from the proxy as a
63-
// true Python function.
64-
} else {
65-
// A function that is not a Python function. Bind it.
66-
value = value.bind(base);
67-
}
68-
}
69-
}
7057
proxy_convert_js_to_mp_obj_jsside(value, out);
71-
return true;
58+
if (typeof value === "function" && !("_ref" in value)) {
59+
// Attribute found and it's a JavaScript function.
60+
return 2;
61+
} else {
62+
// Attribute found.
63+
return 1;
64+
}
7265
} else {
73-
return false;
66+
// Attribute not found.
67+
return 0;
7468
}
7569
});
7670
// *FORMAT-ON*
@@ -98,45 +92,65 @@ EM_JS(void, call0, (int f_ref, uint32_t * out), {
9892
proxy_convert_js_to_mp_obj_jsside(ret, out);
9993
});
10094

101-
EM_JS(int, call1, (int f_ref, uint32_t * a0, uint32_t * out), {
95+
EM_JS(int, call1, (int f_ref, bool via_call, uint32_t * a0, uint32_t * out), {
10296
const a0_js = proxy_convert_mp_to_js_obj_jsside(a0);
10397
const f = proxy_js_ref[f_ref];
104-
const ret = f(a0_js);
98+
let ret;
99+
if (via_call) {
100+
ret = f.call(a0_js);
101+
} else {
102+
ret = f(a0_js);
103+
}
105104
proxy_convert_js_to_mp_obj_jsside(ret, out);
106105
});
107106

108-
EM_JS(int, call2, (int f_ref, uint32_t * a0, uint32_t * a1, uint32_t * out), {
107+
EM_JS(int, call2, (int f_ref, bool via_call, uint32_t * a0, uint32_t * a1, uint32_t * out), {
109108
const a0_js = proxy_convert_mp_to_js_obj_jsside(a0);
110109
const a1_js = proxy_convert_mp_to_js_obj_jsside(a1);
111110
const f = proxy_js_ref[f_ref];
112-
const ret = f(a0_js, a1_js);
111+
let ret;
112+
if (via_call) {
113+
ret = f.call(a0_js, a1_js);
114+
} else {
115+
ret = f(a0_js, a1_js);
116+
}
113117
proxy_convert_js_to_mp_obj_jsside(ret, out);
114118
});
115119

116-
EM_JS(int, calln, (int f_ref, uint32_t n_args, uint32_t * value, uint32_t * out), {
120+
EM_JS(int, calln, (int f_ref, bool via_call, uint32_t n_args, uint32_t * value, uint32_t * out), {
117121
const f = proxy_js_ref[f_ref];
118122
const a = [];
119123
for (let i = 0; i < n_args; ++i) {
120124
const v = proxy_convert_mp_to_js_obj_jsside(value + i * 3 * 4);
121125
a.push(v);
122126
}
123-
const ret = f(... a);
127+
let ret;
128+
if (via_call) {
129+
ret = f.call(... a);
130+
} else {
131+
ret = f(... a);
132+
}
124133
proxy_convert_js_to_mp_obj_jsside(ret, out);
125134
});
126135

127-
EM_JS(void, call0_kwarg, (int f_ref, uint32_t n_kw, uint32_t * key, uint32_t * value, uint32_t * out), {
136+
EM_JS(void, call0_kwarg, (int f_ref, bool via_call, uint32_t n_kw, uint32_t * key, uint32_t * value, uint32_t * out), {
128137
const f = proxy_js_ref[f_ref];
129138
const a = {};
130139
for (let i = 0; i < n_kw; ++i) {
131140
const k = UTF8ToString(getValue(key + i * 4, "i32"));
132141
const v = proxy_convert_mp_to_js_obj_jsside(value + i * 3 * 4);
133142
a[k] = v;
134143
}
135-
const ret = f(a);
144+
let ret;
145+
if (via_call) {
146+
ret = f.call(a);
147+
} else {
148+
ret = f(a);
149+
}
136150
proxy_convert_js_to_mp_obj_jsside(ret, out);
137151
});
138152

139-
EM_JS(void, call1_kwarg, (int f_ref, uint32_t * arg0, uint32_t n_kw, uint32_t * key, uint32_t * value, uint32_t * out), {
153+
EM_JS(void, call1_kwarg, (int f_ref, bool via_call, uint32_t * arg0, uint32_t n_kw, uint32_t * key, uint32_t * value, uint32_t * out), {
140154
const f = proxy_js_ref[f_ref];
141155
const a0 = proxy_convert_mp_to_js_obj_jsside(arg0);
142156
const a = {};
@@ -145,7 +159,12 @@ EM_JS(void, call1_kwarg, (int f_ref, uint32_t * arg0, uint32_t n_kw, uint32_t *
145159
const v = proxy_convert_mp_to_js_obj_jsside(value + i * 3 * 4);
146160
a[k] = v;
147161
}
148-
const ret = f(a0, a);
162+
let ret;
163+
if (via_call) {
164+
ret = f.call(a0, a);
165+
} else {
166+
ret = f(a0, a);
167+
}
149168
proxy_convert_js_to_mp_obj_jsside(ret, out);
150169
});
151170

@@ -208,12 +227,12 @@ static mp_obj_t jsproxy_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const
208227
}
209228
uint32_t out[3];
210229
if (n_args == 0) {
211-
call0_kwarg(self->ref, n_kw, key, value, out);
230+
call0_kwarg(self->ref, self->bind_to_self, n_kw, key, value, out);
212231
} else {
213232
// n_args == 1
214233
uint32_t arg0[PVN];
215234
proxy_convert_mp_to_js_obj_cside(args[0], arg0);
216-
call1_kwarg(self->ref, arg0, n_kw, key, value, out);
235+
call1_kwarg(self->ref, self->bind_to_self, arg0, n_kw, key, value, out);
217236
}
218237
return proxy_convert_js_to_mp_obj_cside(out);
219238
}
@@ -226,23 +245,23 @@ static mp_obj_t jsproxy_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const
226245
uint32_t arg0[PVN];
227246
uint32_t out[PVN];
228247
proxy_convert_mp_to_js_obj_cside(args[0], arg0);
229-
call1(self->ref, arg0, out);
248+
call1(self->ref, self->bind_to_self, arg0, out);
230249
return proxy_convert_js_to_mp_obj_cside(out);
231250
} else if (n_args == 2) {
232251
uint32_t arg0[PVN];
233252
proxy_convert_mp_to_js_obj_cside(args[0], arg0);
234253
uint32_t arg1[PVN];
235254
proxy_convert_mp_to_js_obj_cside(args[1], arg1);
236255
uint32_t out[3];
237-
call2(self->ref, arg0, arg1, out);
256+
call2(self->ref, self->bind_to_self, arg0, arg1, out);
238257
return proxy_convert_js_to_mp_obj_cside(out);
239258
} else {
240259
uint32_t value[PVN * n_args];
241260
for (int i = 0; i < n_args; ++i) {
242261
proxy_convert_mp_to_js_obj_cside(args[i], &value[i * PVN]);
243262
}
244263
uint32_t out[3];
245-
calln(self->ref, n_args, value, out);
264+
calln(self->ref, self->bind_to_self, n_args, value, out);
246265
return proxy_convert_js_to_mp_obj_cside(out);
247266
}
248267
}
@@ -298,17 +317,26 @@ static mp_obj_t jsproxy_subscr(mp_obj_t self_in, mp_obj_t index, mp_obj_t value)
298317
}
299318
}
300319

301-
void mp_obj_jsproxy_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
320+
static void mp_obj_jsproxy_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
302321
mp_obj_jsproxy_t *self = MP_OBJ_TO_PTR(self_in);
303322
if (dest[0] == MP_OBJ_NULL) {
304323
// Load attribute.
324+
int lookup_ret;
305325
uint32_t out[PVN];
306326
if (attr == MP_QSTR___del__) {
307327
// For finaliser.
308328
dest[0] = MP_OBJ_FROM_PTR(&jsproxy___del___obj);
309329
dest[1] = self_in;
310-
} else if (lookup_attr(self->ref, qstr_str(attr), out)) {
330+
} else if ((lookup_ret = lookup_attr(self->ref, qstr_str(attr), out)) != 0) {
311331
dest[0] = proxy_convert_js_to_mp_obj_cside(out);
332+
if (lookup_ret == 2) {
333+
// The loaded attribute is a JavaScript method, which should be called
334+
// with f.call(self, ...). Indicate this via the bind_to_self member.
335+
// This will either be called immediately (due to the mp_load_method
336+
// optimisation) or turned into a bound_method and called later.
337+
dest[1] = self_in;
338+
((mp_obj_jsproxy_t *)dest[0])->bind_to_self = true;
339+
}
312340
} else if (attr == MP_QSTR_new) {
313341
// Special case to handle construction of JS objects.
314342
// JS objects don't have a ".new" attribute, doing "Obj.new" is a Pyodide idiom for "new Obj".
@@ -546,5 +574,25 @@ MP_DEFINE_CONST_OBJ_TYPE(
546574
mp_obj_t mp_obj_new_jsproxy(int ref) {
547575
mp_obj_jsproxy_t *o = mp_obj_malloc_with_finaliser(mp_obj_jsproxy_t, &mp_type_jsproxy);
548576
o->ref = ref;
577+
o->bind_to_self = false;
549578
return MP_OBJ_FROM_PTR(o);
550579
}
580+
581+
// Load/delete/store an attribute from/to the JavaScript globalThis entity.
582+
void mp_obj_jsproxy_global_this_attr(qstr attr, mp_obj_t *dest) {
583+
if (dest[0] == MP_OBJ_NULL) {
584+
// Load attribute.
585+
uint32_t out[PVN];
586+
if (lookup_attr(MP_OBJ_JSPROXY_REF_GLOBAL_THIS, qstr_str(attr), out)) {
587+
dest[0] = proxy_convert_js_to_mp_obj_cside(out);
588+
}
589+
} else if (dest[1] == MP_OBJ_NULL) {
590+
// Delete attribute.
591+
} else {
592+
// Store attribute.
593+
uint32_t value[PVN];
594+
proxy_convert_mp_to_js_obj_cside(dest[1], value);
595+
store_attr(MP_OBJ_JSPROXY_REF_GLOBAL_THIS, qstr_str(attr), value);
596+
dest[0] = MP_OBJ_NULL;
597+
}
598+
}

ports/webassembly/proxy_c.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
typedef struct _mp_obj_jsproxy_t {
3939
mp_obj_base_t base;
4040
int ref;
41+
bool bind_to_self;
4142
} mp_obj_jsproxy_t;
4243

4344
extern const mp_obj_type_t mp_type_jsproxy;
@@ -52,7 +53,7 @@ void proxy_convert_mp_to_js_obj_cside(mp_obj_t obj, uint32_t *out);
5253
void proxy_convert_mp_to_js_exc_cside(void *exc, uint32_t *out);
5354

5455
mp_obj_t mp_obj_new_jsproxy(int ref);
55-
void mp_obj_jsproxy_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest);
56+
void mp_obj_jsproxy_global_this_attr(qstr attr, mp_obj_t *dest);
5657

5758
static inline bool mp_obj_is_jsproxy(mp_obj_t o) {
5859
return mp_obj_get_type(o) == &mp_type_jsproxy;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Test how JavaScript binds self/this when methods are called from Python.
2+
3+
const mp = await (await import(process.argv[2])).loadMicroPython();
4+
5+
// Test accessing and calling JavaScript methods from Python.
6+
mp.runPython(`
7+
import js
8+
9+
# Get the push method to call later on.
10+
push = js.Array.prototype.push
11+
12+
# Create initial array.
13+
ar = js.Array(1, 2)
14+
js.console.log(ar)
15+
16+
# Add an element using a method (should implicitly supply "ar" as context).
17+
print(ar.push(3))
18+
js.console.log(ar)
19+
20+
# Add an element using prototype function, need to explicitly provide "ar" as context.
21+
print(push.call(ar, 4))
22+
js.console.log(ar)
23+
24+
# Add an element using a method with call and explicit context.
25+
print(ar.push.call(ar, 5))
26+
js.console.log(ar)
27+
28+
# Add an element using a different instances method with call and explicit context.
29+
print(js.Array().push.call(ar, 6))
30+
js.console.log(ar)
31+
`);
32+
33+
// Test assigning Python functions to JavaScript objects, and using them like a method.
34+
mp.runPython(`
35+
import js
36+
37+
a = js.Object()
38+
a.meth1 = lambda *x: print("meth1", x)
39+
a.meth1(1, 2)
40+
41+
js.Object.prototype.meth2 = lambda *x: print("meth2", x)
42+
a.meth2(3, 4)
43+
`);
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[ 1, 2 ]
2+
3
3+
[ 1, 2, 3 ]
4+
4
5+
[ 1, 2, 3, 4 ]
6+
5
7+
[ 1, 2, 3, 4, 5 ]
8+
6
9+
[ 1, 2, 3, 4, 5, 6 ]
10+
meth1 (1, 2)
11+
meth2 (3, 4)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy