10000 bpo-47162: Add call trampoline to mitigate bad fpcasts on Emscripten … · python/cpython@581c443 · GitHub
[go: up one dir, main page]

Skip to content

Commit 581c443

Browse files
authored
bpo-47162: Add call trampoline to mitigate bad fpcasts on Emscripten (GH-32189)
1 parent 795c00b commit 581c443

File tree

8 files changed

+99
-18
lines changed

8 files changed

+99
-18
lines changed

Include/internal/pycore_object.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,33 @@ extern PyObject* _PyType_GetSubclasses(PyTypeObject *);
242242

243243
PyAPI_FUNC(PyObject *) _PyObject_LookupSpecial(PyObject *, PyObject *);
244244

245+
/* C function call trampolines to mitigate bad function pointer casts.
246+
*
247+
* Typical native ABIs ignore additional arguments or fill in missing
248+
* values with 0/NULL in function pointer cast. Compilers do not show
249+
* warnings when a function pointer is explicitly casted to an
250+
* incompatible type.
251+
*
252+
* Bad fpcasts are an issue in WebAssembly. WASM's indirect_call has strict
253+
* function signature checks. Argument count, types, and return type must
254+
* match.
255+
*
256+
* Third party code unintentionally rely on problematic fpcasts. The call
257+
* trampoline mitigates common occurences of bad fpcasts on Emscripten.
258+
*/
259+
#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
260+
#define _PyCFunction_TrampolineCall(meth, self, args) \
261+
_PyCFunctionWithKeywords_TrampolineCall( \
262+
(*(PyCFunctionWithKeywords)(void(*)(void))meth), self, args, NULL)
263+
extern PyObject* _PyCFunctionWithKeywords_TrampolineCall(
264+
PyCFunctionWithKeywords meth, PyObject *, PyObject *, PyObject *);
265+
#else
266+
#define _PyCFunction_TrampolineCall(meth, self, args) \
267+
(meth)((self), (args))
268+
#define _PyCFunctionWithKeywords_TrampolineCall(meth, self, args, kw) \
269+
(meth)((self), (args), (kw))
270+
#endif // __EMSCRIPTEN__ && PY_CALL_TRAMPOLINE
271+
245272
#ifdef __cplusplus
246273
}
247274
#endif
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
WebAssembly cannot deal with bad function pointer casts (different count
2+
or types of arguments). Python can now use call trampolines to mitigate
3+
the problem. Define :c:macro:`PY_CALL_TRAMPOLINE` to enable call
4+
trampolines.

Objects/call.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,8 @@ _PyObject_MakeTpCall(PyThreadState *tstate, PyObject *callable,
211211
PyObject *result = NULL;
212212
if (_Py_EnterRecursiveCall(tstate, " while calling a Python object") == 0)
213213
{
214-
result = call(callable, argstuple, kwdict);
214+
result = _PyCFunctionWithKeywords_TrampolineCall(
215+
(PyCFunctionWithKeywords)call, callable, argstuple, kwdict);
215216
_Py_LeaveRecursiveCall(tstate);
216217
}
217218

Objects/descrobject.c

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,25 @@ class property "propertyobject *" "&PyProperty_Type"
1313
[clinic start generated code]*/
1414
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=556352653fd4c02e]*/
1515

16+
// see pycore_object.h
17+
#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
18+
#include <emscripten.h>
19+
EM_JS(PyObject*, descr_set_trampoline_call, (setter set, PyObject *obj, PyObject *value, void *closure), {
20+
return wasmTable.get(set)(obj, value, closure);
21+
});
22+
23+
EM_JS(PyObject*, descr_get_trampoline_call, (getter get, PyObject *obj, void *closure), {
24+
return wasmTable.get(get)(obj, closure);
25+
});
26+
#else
27+
#define descr_set_trampoline_call(set, obj, value, closure) \
28+
(set)((obj), (value), (closure))
29+
30+
#define descr_get_trampoline_call(get, obj, closure) \
31+
(get)((obj), (closure))
32+
33+
#endif // __EMSCRIPTEN__ && PY_CALL_TRAMPOLINE
34+
1635
static void
1736
descr_dealloc(PyDescrObject *descr)
1837
{
@@ -180,7 +199,8 @@ getset_get(PyGetSetDescrObject *descr, PyObject *obj, PyObject *type)
180199
return NULL;
181200
}
182201
if (descr->d_getset->get != NULL)
183-
return descr->d_getset->get(obj, descr->d_getset->closure);
202+
return descr_get_trampoline_call(
203+
descr->d_getset->get, obj, descr->d_getset->closure);
184204
PyErr_Format(PyExc_AttributeError,
185205
"attribute '%V' of '%.100s' objects is not readable",
186206
descr_name((PyDescrObject *)descr), "?",
@@ -232,8 +252,9 @@ getset_set(PyGetSetDescrObject *descr, PyObject *obj, PyObject *value)
232252
return -1;
233253
}
234254
if (descr->d_getset->set != NULL) {
235-
return descr->d_getset->set(obj, value,
236-
descr->d_getset->closure);
255+
return descr_set_trampoline_call(
256+
descr->d_getset->set, obj, value,
257+
descr->d_getset->closure);
237258
}
238259
PyErr_Format(PyExc_AttributeError,
239260
"attribute '%V' of '%.100s' objects is not writable",
@@ -306,7 +327,8 @@ method_vectorcall_VARARGS(
306327
Py_DECREF(argstuple);
307328
return NULL;
308329
}
309-
PyObject *result = meth(args[0], argstuple);
330+
PyObject *result = _PyCFunction_TrampolineCall(
331+
meth, args[0], argstuple);
310332
Py_DECREF(argstuple);
311333
_Py_LeaveRecursiveCall(tstate);
312334
return result;
@@ -339,7 +361,8 @@ method_vectorcall_VARARGS_KEYWORDS(
339361
if (meth == NULL) {
340362
goto exit;
341363
}
342-
result = meth(args[0], argstuple, kwdict);
364+
result = _PyCFunctionWithKeywords_TrampolineCall(
365+
meth, args[0], argstuple, kwdict);
343366
_Py_LeaveRecursiveCall(tstate);
344367
exit:
345368
Py_DECREF(argstuple);
@@ -427,7 +450,7 @@ method_vectorcall_NOARGS(
427450
if (meth == NULL) {
428451
return NULL;
429452
}
430-
PyObject *result = meth(args[0], NULL);
453+
PyObject *result = _PyCFunction_TrampolineCall(meth, args[0], NULL);
431454
_Py_LeaveRecursiveCall(tstate);
432455
return result;
433456
}
@@ -455,7 +478,7 @@ method_vectorcall_O(
455478
if (meth == NULL) {
456479
return NULL;
457480
}
458-
PyObject *result = meth(args[0], args[1]);
481+
PyObject *result = _PyCFunction_TrampolineCall(meth, args[0], args[1]);
459482
_Py_LeaveRecursiveCall(tstate);
460483
return result;
461484
}

Objects/methodobject.c

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -483,7 +483,8 @@ cfunction_vectorcall_NOARGS(
483483
if (meth == NULL) {
484484
return NULL;
485485
}
486-
PyObject *result = meth(PyCFunction_GET_SELF(func), NULL);
486+
PyObject *result = _PyCFunction_TrampolineCall(
487+
meth, PyCFunction_GET_SELF(func), NULL);
487488
_Py_LeaveRecursiveCall(tstate);
488489
return result;
489490
}
@@ -510,7 +511,8 @@ cfunction_vectorcall_O(
510511
if (meth == NULL) {
511512
return NULL;
512513
}
513-
PyObject *result = meth(PyCFunction_GET_SELF(func), args[0]);
514+
PyObject *result = _PyCFunction_TrampolineCall(
515+
meth, PyCFunction_GET_SELF(func), args[0]);
514516
_Py_LeaveRecursiveCall(tstate);
515517
return result;
516518
}
@@ -537,7 +539,9 @@ cfunction_call(PyObject *func, PyObject *args, PyObject *kwargs)
537539

538540
PyObject *result;
539541
if (flags & METH_KEYWORDS) {
540-
result = (*(PyCFunctionWithKeywords)(void(*)(void))meth)(self, args, kwargs);
542+
result = _PyCFunctionWithKeywords_TrampolineCall(
543+
(*(PyCFunctionWithKeywords)(void(*)(void))meth),
544+
self, args, kwargs);
541545
}
542546
else {
543547
if (kwargs != NULL && PyDict_GET_SIZE(kwargs) != 0) {
@@ -546,7 +550,15 @@ cfunction_call(PyObject *func, PyObject *args, PyObject *kwargs)
546550
((PyCFunctionObject*)func)->m_ml->ml_name);
547551
return NULL;
548552
}
549-
result = meth(self, args);
553+
result = _PyCFunction_TrampolineCall(meth, self, args);
550554
}
551555
return _Py_CheckFunctionResult(tstate, func, result, NULL);
552556
}
557+
558+
#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
559+
#include <emscripten.h>
560+
561+
EM_JS(PyObject*, _PyCFunctionWithKeywords_TrampolineCall, (PyCFunctionWithKeywords func, PyObject *self, PyObject *args, PyObject *kw), {
562+
return wasmTable.get(func)(self, args, kw);
563+
});
564+
#endif

Python/import.c

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ import_find_extension(PyThreadState *tstate, PyObject *name,
527527
else {
528528
if (def->m_base.m_init == NULL)
529529
return NULL;
530-
mod = def->m_base.m_init();
530+
mod = _PyImport_InitFunc_TrampolineCall(def->m_base.m_init);
531531
if (mod == NULL)
532532
return NULL;
533533
if (PyObject_SetItem(modules, name, mod) == -1) {
@@ -958,6 +958,13 @@ PyImport_GetImporter(PyObject *path)
958958
return get_path_importer(tstate, path_importer_cache, path_hooks, path);
959959
}
960960

961+
#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
962+
#include <emscripten.h>
963+
EM_JS(PyObject*, _PyImport_InitFunc_TrampolineCall, (PyModInitFunction func), {
964+
return wasmTable.get(func)();
965+
});
966+
#endif // __EMSCRIPTEN__ && PY_CALL_TRAMPOLINE
967+
961968
static PyObject*
962969
create_builtin(PyThreadState *tstate, PyObject *name, PyObject *spec)
963970
{
@@ -973,8 +980,7 @@ create_builtin(PyThreadState *tstate, PyObject *name, PyObject *spec)
973980
/* Cannot re-init internal module ("sys" or "builtins") */
974981
return PyImport_AddModuleObject(name);
975982
}
976-
977-
mod = (*p->initfunc)();
983+
mod = _PyImport_InitFunc_TrampolineCall(*p->initfunc);
978984
if (mod == NULL) {
979985
return NULL;
980986
}

Python/importdl.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ _PyImport_LoadDynamicModuleWithSpec(PyObject *spec, FILE *fp)
102102
const char *oldcontext;
103103
dl_funcptr exportfunc;
104104
PyModuleDef *def;
105-
PyObject *(*p0)(void);
105+
PyModInitFunction p0;
106106

107107
name_unicode = PyObject_GetAttrString(spec, "name");
108108
if (name_unicode == NULL) {
@@ -157,7 +157,7 @@ _PyImport_LoadDynamicModuleWithSpec(PyObject *spec, FILE *fp)
157157
goto error;
158158
}
159159

160-
p0 = (PyObject *(*)(void))exportfunc;
160+
p0 = (PyModInitFunction)exportfunc;
161161

162162
/* Package context is needed for single-phase init */
163163
oldcontext = _Py_PackageContext;
@@ -166,7 +166,7 @@ _PyImport_LoadDynamicModuleWithSpec(PyObject *spec, FILE *fp)
166166
_Py_PackageContext = oldcontext;
167167
goto error;
168168
}
169-
m = p0();
169+
m = _PyImport_InitFunc_TrampolineCall(p0);
170170
_Py_PackageContext = oldcontext;
171171

172172
if (m == NULL) {

Python/importdl.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ extern const char *_PyImport_DynLoadFiletab[];
1010

1111
extern PyObject *_PyImport_LoadDynamicModuleWithSpec(PyObject *spec, FILE *);
1212

13+
typedef PyObject *(*PyModInitFunction)(void);
14+
15+
#if defined(__EMSCRIPTEN__) && defined(PY_CALL_TRAMPOLINE)
16+
extern PyObject *_PyImport_InitFunc_TrampolineCall(PyModInitFunction func);
17+
#else
18+
#define _PyImport_InitFunc_TrampolineCall(func) (func)()
19+
#endif
20+
1321
/* Max length of module suffix searched for -- accommodates "module.slb" */
1422
#define MAXSUFFIXSIZE 12
1523

0 commit comments

Comments
 (0)
0