From 441ec3c6dd0f7ffd26bfff16ea4542e50547585b Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Fri, 27 Feb 2026 13:37:50 +0100 Subject: [PATCH 1/4] Fix: When symbol is both imported and exported, don't drop import This fixes wrapping syscalls with -sMAIN_MODULE and -sEXPORT_ALL. Resolves issue 26355. --- src/jsifier.mjs | 2 +- test/test_other.py | 25 +++++++++++++++++++++++++ tools/emscripten.py | 11 ++++++++--- 3 files changed, 34 insertions(+), 4 deletions(-) diff --git a/src/jsifier.mjs b/src/jsifier.mjs index 517f32de049da..4a6a7e9050d12 100644 --- a/src/jsifier.mjs +++ b/src/jsifier.mjs @@ -559,7 +559,7 @@ function(${args}) { // if the function was implemented in compiled code, there is no need to // include the js version - if (WASM_EXPORTS.has(symbol)) { + if (WASM_EXPORTS.has(symbol) && !DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.includes(symbol)) { return; } diff --git a/test/test_other.py b/test/test_other.py index 7c38bfab2cad6..64d54fdeeab52 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -1607,6 +1607,31 @@ def test_export_all(self): # Without the `-sEXPORT_ALL` these symbols will not be visible from JS self.do_runf('main.c', '_libf1 is not defined', assert_returncode=NON_ZERO, cflags=['-Oz', '-sMAIN_MODULE', '--pre-js', 'pre.js']) + def test_export_all_syscall_override(self): + create_file('main.c', r''' + #include "stdio.h" + #include + + int syscall_openat_orig(int dirfd, intptr_t path, int flags, void* varags) + __attribute__((__import_module__("env"), + __import_name__("__syscall_openat"), __warn_unused_result__)); + + int __syscall_openat(int dirfd, intptr_t path, int flags, void* varargs) { + printf("__syscall_openat!\n"); + return syscall_openat_orig(dirfd, path, flags, varargs); + } + + int main() { + int fd = open("a.c", O_RDONLY); + printf("fd: %d\n", fd); + } + ''') + + self.do_runf('main.c', '__syscall_openat!', cflags=['-sEXPORT_ALL', '-sNODERAWFS']) + self.do_runf('main.c', '__syscall_openat!', cflags=['-sMAIN_MODULE', '-sNODERAWFS']) + self.do_runf('main.c', '__syscall_openat!', cflags=['-O2', '-sEXPORT_ALL', '-sMAIN_MODULE', '-sNODERAWFS']) + self.do_runf('main.c', '__syscall_openat!', cflags=['-sEXPORT_ALL', '-sMAIN_MODULE', '-sNODERAWFS']) + def test_export_keepalive(self): create_file('main.c', r''' #include diff --git a/tools/emscripten.py b/tools/emscripten.py index 2f3a484a8b7c0..6626899645526 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -969,7 +969,7 @@ def should_export(sym): return settings.EXPORT_ALL or (settings.EXPORT_KEEPALIVE and sym in settings.EXPORTED_FUNCTIONS) -def create_receiving(function_exports, other_exports, library_symbols, aliases): +def create_receiving(function_exports, other_exports, library_symbols, aliases, wasm_imports): generate_dyncall_assignment = 'dynCalls' in library_symbols receiving = ['\n// Imports from the Wasm binary.'] @@ -1019,6 +1019,7 @@ def create_receiving(function_exports, other_exports, library_symbols, aliases): # In debug builds we generate trapping functions in case # folks try to call/use a reference that was taken before the # wasm module is available. + wasm_imports_mangled = {asmjs_mangle(s) for s in wasm_imports} for sym in mangled: module_export = (settings.MODULARIZE or not settings.MINIMAL_RUNTIME) and should_export(sym) and settings.MODULARIZE != 'instance' if not js_manipulation.isidentifier(sym) and not module_export: @@ -1029,7 +1030,11 @@ def create_receiving(function_exports, other_exports, library_symbols, aliases): assignment += f" = Module['{sym}']" else: assignment = f"Module['{sym}']" - receiving.append(f"{assignment} = makeInvalidEarlyAccess('{sym}');") + # Don't generate early access traps for symbols that are also wasm imports. + if sym in wasm_imports_mangled: + receiving.append(f"{assignment};") + else: + receiving.append(f"{assignment} = makeInvalidEarlyAccess('{sym}');") else: # Declare all exports in a single var statement sep = ',\n ' @@ -1100,7 +1105,7 @@ def create_receiving(function_exports, other_exports, library_symbols, aliases): def create_module(metadata, function_exports, other_exports, library_symbols, aliases): module = [] - module.append(create_receiving(function_exports, other_exports, library_symbols, aliases)) + module.append(create_receiving(function_exports, other_exports, library_symbols, aliases, metadata.imports)) sending = create_sending(metadata, library_symbols) if settings.WASM_ESM_INTEGRATION: From 3423c616cd19535d57d5650611e9c096f2aed3c8 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Wed, 4 Mar 2026 11:50:45 +0100 Subject: [PATCH 2/4] Fix --- src/jsifier.mjs | 3 ++- src/settings_internal.js | 3 +++ tools/emscripten.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/jsifier.mjs b/src/jsifier.mjs index 4a6a7e9050d12..a8bafff0c0cdb 100644 --- a/src/jsifier.mjs +++ b/src/jsifier.mjs @@ -29,6 +29,7 @@ import { addToCompileTimeContext, debugLog, error, + printErr, errorOccured, isDecorator, isJsOnlySymbol, @@ -559,7 +560,7 @@ function(${args}) { // if the function was implemented in compiled code, there is no need to // include the js version - if (WASM_EXPORTS.has(symbol) && !DEFAULT_LIBRARY_FUNCS_TO_INCLUDE.includes(symbol)) { + if (WASM_EXPORTS.has(symbol) && !WASM_IMPORTS.includes(symbol)) { return; } diff --git a/src/settings_internal.js b/src/settings_internal.js index e656c83fd455f..4adc2d495d478 100644 --- a/src/settings_internal.js +++ b/src/settings_internal.js @@ -16,6 +16,9 @@ // underscore. var WASM_EXPORTS = []; +// List of symbols imported from JavaScript +var WASM_IMPORTS = []; + // An array of all symbols exported from all the side modules specified on the // command line. // These are raw symbol names and are not mangled to include the leading diff --git a/tools/emscripten.py b/tools/emscripten.py index 6626899645526..ca206fba8d678 100644 --- a/tools/emscripten.py +++ b/tools/emscripten.py @@ -114,8 +114,10 @@ def update_settings_glue(wasm_file, metadata, base_metadata): if base_metadata: settings.WASM_EXPORTS = base_metadata.all_exports + settings.WASM_IMPORTS = base_metadata.imports else: settings.WASM_EXPORTS = metadata.all_exports + settings.WASM_IMPORTS = metadata.imports settings.HAVE_EM_ASM = bool(settings.MAIN_MODULE or len(metadata.em_asm_consts) != 0) if settings.MAIN_MODULE and settings.ASYNCIFY == 1: From 5a1cb602c7a9910e4b1c26ceea01be992dda461e Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Thu, 5 Mar 2026 11:21:56 +0100 Subject: [PATCH 3/4] Remove printErr --- src/jsifier.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/jsifier.mjs b/src/jsifier.mjs index a8bafff0c0cdb..d711dac771ba4 100644 --- a/src/jsifier.mjs +++ b/src/jsifier.mjs @@ -29,7 +29,6 @@ import { addToCompileTimeContext, debugLog, error, - printErr, errorOccured, isDecorator, isJsOnlySymbol, From 58b36c63d180949ac99c407daf4dc23a5979bce6 Mon Sep 17 00:00:00 2001 From: Hood Chatham Date: Thu, 5 Mar 2026 11:30:20 +0100 Subject: [PATCH 4/4] Add ES6 test --- test/test_other.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/test_other.py b/test/test_other.py index 64d54fdeeab52..61c3c8b00dcef 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -1632,6 +1632,16 @@ def test_export_all_syscall_override(self): self.do_runf('main.c', '__syscall_openat!', cflags=['-O2', '-sEXPORT_ALL', '-sMAIN_MODULE', '-sNODERAWFS']) self.do_runf('main.c', '__syscall_openat!', cflags=['-sEXPORT_ALL', '-sMAIN_MODULE', '-sNODERAWFS']) + # Test with ES6 module output + self.run_process([EMCC, 'main.c', '-o', 'main.mjs', '-sEXPORT_ALL', '-sMAIN_MODULE', '-sNODERAWFS']) + create_file('run.mjs', ''' + import {default as loadModule} from './main.mjs' + + await loadModule(); + ''') + + self.assertContained('__syscall_openat!', self.run_js('run.mjs')) + def test_export_keepalive(self): create_file('main.c', r''' #include