Skip to content

Commit d30d432

Browse files
committed
Add new proxying modes foo__proxy: 'abort' and foo__proxy: 'abort_debug', which will add compile time checks to verify that given JS function is not called in pthreads or Wasm Workers.
1 parent f508b43 commit d30d432

File tree

7 files changed

+160
-28
lines changed

7 files changed

+160
-28
lines changed

ChangeLog.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ See docs/process.md for more on how version tagging works.
2929
- The number of arguments passed to Embind function calls is now only verified
3030
with ASSERTIONS enabled. (#22591)
3131
- Optional arguments can now be omitted from Embind function calls. (#22591)
32+
- Added two new proxying directives. foo__proxy: 'abort' will abort program
33+
execution if JS function foo is called from a pthread or a Wasm Worker.
34+
foo__proxy: 'abort_debug' will do the same, but only in ASSERTIONS builds, and
35+
in non-ASSERTIONS builds will be no-op.
36+
Use these proxying directives to annotate JS functions that should not be
37+
getting called from Workers. (#22648)
3238

3339
3.1.67 - 09/17/24
3440
-----------------

src/jsifier.mjs

Lines changed: 29 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -319,39 +319,40 @@ function(${args}) {
319319

320320
const proxyingMode = LibraryManager.library[symbol + '__proxy'];
321321
if (proxyingMode) {
322-
if (proxyingMode !== 'sync' && proxyingMode !== 'async' && proxyingMode !== 'none') {
323-
throw new Error(`Invalid proxyingMode ${symbol}__proxy: '${proxyingMode}' specified!`);
322+
const possibleProxyingModes = ['sync', 'async', 'none', 'abort', 'abort_debug'];
323+
if (!possibleProxyingModes.includes(proxyingMode)) {
324+
throw new Error(`Invalid proxyingMode ${symbol}__proxy: '${proxyingMode}' specified! Possible modes: ${possibleProxyingModes.join(',')}`);
324325
}
325326
if (SHARED_MEMORY) {
326-
const sync = proxyingMode === 'sync';
327-
if (PTHREADS) {
328-
snippet = modifyJSFunction(snippet, (args, body, async_, oneliner) => {
329-
if (oneliner) {
330-
body = `return ${body}`;
327+
snippet = modifyJSFunction(snippet, (args, body, async_, oneliner) => {
328+
if (oneliner) {
329+
body = `return ${body}`;
330+
}
331+
const rtnType = sig && sig.length ? sig[0] : null;
332+
const proxyFunc =
333+
MEMORY64 && rtnType == 'p' ? 'proxyToMainThreadPtr' : 'proxyToMainThread';
334+
var prefix = '';
335+
336+
if (proxyingMode == 'sync' || proxyingMode == 'async') {
337+
if (PTHREADS) {
338+
deps.push('$' + proxyFunc);
339+
prefix = `if (ENVIRONMENT_IS_PTHREAD) return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${+(proxyingMode === 'sync')}${args ? ', ' : ''}${args});`;
331340
}
332-
const rtnType = sig && sig.length ? sig[0] : null;
333-
const proxyFunc =
334-
MEMORY64 && rtnType == 'p' ? 'proxyToMainThreadPtr' : 'proxyToMainThread';
335-
deps.push('$' + proxyFunc);
336-
return `
337-
function(${args}) {
338-
if (ENVIRONMENT_IS_PTHREAD)
339-
return ${proxyFunc}(${proxiedFunctionTable.length}, 0, ${+sync}${args ? ', ' : ''}${args});
341+
if (WASM_WORKERS && ASSERTIONS) {
342+
prefix += `\nassert(!ENVIRONMENT_IS_WASM_WORKER, "Attempted to call proxied function '${mangled}' in a Wasm Worker, but in Wasm Worker enabled builds, proxied function architecture is not available!");`;
343+
}
344+
} else if (proxyingMode == 'abort' || (proxyingMode == 'abort_debug' && ASSERTIONS)) {
345+
const insideWorker = (PTHREADS && WASM_WORKERS)
346+
? '(ENVIRONMENT_IS_PTHREAD || ENVIRONMENT_IS_WASM_WORKER)'
347+
: (PTHREADS ? 'ENVIRONMENT_IS_PTHREAD' : 'ENVIRONMENT_IS_WASM_WORKER');
348+
prefix = `assert(!${insideWorker}, "Attempted to call function '${mangled}' inside a pthread/Wasm Worker, but this function has been declared to only be callable from the main browser thread");`;
349+
}
350+
351+
return `function(${args}) {
352+
${prefix}
340353
${body}
341354
}\n`;
342-
});
343-
} else if (WASM_WORKERS && ASSERTIONS) {
344-
// In ASSERTIONS builds add runtime checks that proxied functions are not attempted to be called in Wasm Workers
345-
// (since there is no automatic proxying architecture available)
346-
snippet = modifyJSFunction(
347-
snippet,
348-
(args, body) => `
349-
function(${args}) {
350-
assert(!ENVIRONMENT_IS_WASM_WORKER, "Attempted to call proxied function '${mangled}' in a Wasm Worker, but in Wasm Worker enabled builds, proxied function architecture is not available!");
351-
${body}
352-
}\n`,
353-
);
354-
}
355+
});
355356
proxiedFunctionTable.push(mangled);
356357
}
357358
}

test/pthread/proxy_abort.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include <emscripten.h>
2+
#include <pthread.h>
3+
#include <assert.h>
4+
5+
// Tests behavior of __proxy: 'abort' and __proxy: 'abort_debug' in pthreads.
6+
7+
void proxied_js_function(void);
8+
9+
int might_throw(void(*func)())
10+
{
11+
int threw = EM_ASM_INT({
12+
// Patch over assert() so that it does not abort execution on assert failure, but instead
13+
// throws a catchable exception.
14+
assert = function(condition, text) {
15+
if (!condition) {
16+
throw 'Assertion failed' + (text ? ": " + text : "");
17+
}
18+
};
19+
20+
try {
21+
getWasmTableEntry($0)();
22+
} catch(e) {
23+
console.error('Threw an exception: ' + e);
24+
return e.toString().includes('this function has been declared to only be callable from the main browser thread');
25+
}
26+
console.error('Function did not throw');
27+
return 0;
28+
}, func);
29+
return threw;
30+
}
31+
32+
void test()
33+
{
34+
proxied_js_function();
35+
}
36+
37+
void *thread_main(void *arg)
38+
{
39+
REPORT_RESULT(might_throw(test));
40+
return 0;
41+
}
42+
43+
char stack[1024];
44+
45+
int main()
46+
{
47+
proxied_js_function(); // Should be callable from main thread
48+
49+
pthread_t thread;
50+
pthread_attr_t attr;
51+
pthread_attr_init(&attr);
52+
pthread_create(&thread, &attr, thread_main, 0);
53+
}

test/test_browser.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5107,6 +5107,18 @@ def test_wasm_worker_no_proxied_js_functions(self):
51075107
self.btest('wasm_worker/no_proxied_js_functions.c', expected='0',
51085108
args=['--js-library', test_file('wasm_worker/no_proxied_js_functions.js')])
51095109

5110+
# Tests that the proxying directives foo__proxy: 'abort' and foo__proxy: 'abort_debug' work.
5111+
@parameterized({
5112+
'': ('1', ['--js-library', test_file('wasm_worker/proxy_abort.js')],),
5113+
'debug': ('1', ['--js-library', test_file('wasm_worker/proxy_abort_debug.js'), '-sASSERTIONS'],),
5114+
'debug_no_assertions': ('0', ['--js-library', test_file('wasm_worker/proxy_abort_debug.js'), '-sASSERTIONS=0'],),
5115+
})
5116+
def test_proxy_abort(self, expect_result, args):
5117+
self.btest('pthread/proxy_abort.c', expected=expect_result, args=args + ['-pthread'])
5118+
5119+
self.set_setting('WASM_WORKERS')
5120+
self.btest('wasm_worker/proxy_abort.c', expected=expect_result, args=args)
5121+
51105122
# Tests emscripten_semaphore_init(), emscripten_semaphore_waitinf_acquire() and emscripten_semaphore_release()
51115123
@also_with_minimal_runtime
51125124
def test_wasm_worker_semaphore_waitinf_acquire(self):

test/wasm_worker/proxy_abort.c

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#include <emscripten.h>
2+
#include <emscripten/wasm_worker.h>
3+
#include <assert.h>
4+
5+
// Tests behavior of __proxy: 'abort' and __proxy: 'abort_debug' in Wasm Workers.
6+
7+
void proxied_js_function(void);
8+
9+
int might_throw(void(*func)())
10+
{
11+
int threw = EM_ASM_INT({
12+
// Patch over assert() so that it does not abort execution on assert failure, but instead
13+
// throws a catchable exception.
14+
assert = function(condition, text) {
15+
if (!condition) {
16+
throw 'Assertion failed' + (text ? ": " + text : "");
17+
}
18+
};
19+
20+
try {
21+
getWasmTableEntry($0)();
22+
} catch(e) {
23+
console.error('Threw an exception: ' + e);
24+
return e.toString().includes('this function has been declared to only be callable from the main browser thread');
25+
}
26+
console.error('Function did not throw');
27+
return 0;
28+
}, func);
29+
return threw;
30+
}
31+
32+
void test()
33+
{
34+
proxied_js_function();
35+
}
36+
37+
void worker_main()
38+
{
39+
REPORT_RESULT(might_throw(test));
40+
}
41+
42+
char stack[1024];
43+
44+
int main()
45+
{
46+
proxied_js_function(); // Should be callable from main thread
47+
emscripten_wasm_worker_post_function_v(emscripten_malloc_wasm_worker(1024), worker_main);
48+
}

test/wasm_worker/proxy_abort.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
addToLibrary({
2+
proxied_js_function__proxy: 'abort',
3+
proxied_js_function: function() {
4+
console.log('In proxied_js_function');
5+
}
6+
});
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
addToLibrary({
2+
proxied_js_function__proxy: 'abort_debug',
3+
proxied_js_function: function() {
4+
console.log('In proxied_js_function');
5+
}
6+
});

0 commit comments

Comments
 (0)