diff --git a/ChangeLog.md b/ChangeLog.md index 05d25fe02c842..c8aec8d73c0a7 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -20,6 +20,11 @@ See docs/process.md for more on how version tagging works. 3.1.75 (in development) ----------------------- +- When using `-sMODULARIZE` we now assert if the factory function is called with + the JS `new` keyword. e.g. `a = new Module()` rather than `b = Module()`. + This paves the way for marking the function as `async` which does not allow + `new` to be used. This usage of `new` here was never documented and is + considered and antipattern. (#23210) - `PATH.basename()` no longer calls `PATH.normalize()`, so that `PATH.basename("a/.")` returns `"."` instead of `"a"` and `PATH.basename("a/b/..")` returns `".."` instead of `"a"`. This is in line with diff --git a/test/test_browser.py b/test/test_browser.py index 04d3353faf1bc..6398a5b917d4d 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -4795,28 +4795,19 @@ def test_browser_run_from_different_directory(self): # Similar to `test_browser_run_from_different_directory`, but asynchronous because of `-sMODULARIZE` def test_browser_run_from_different_directory_async(self): - for args, creations in [ - (['-sMODULARIZE'], [ - 'Module();', # documented way for using modularize - 'new Module();' # not documented as working, but we support it - ]), - ]: - print(args) - # compile the code with the modularize feature and the preload-file option enabled - self.compile_btest('browser_test_hello_world.c', ['-o', 'test.js', '-O3'] + args) - ensure_dir('subdir') - shutil.move('test.js', Path('subdir/test.js')) - shutil.move('test.wasm', Path('subdir/test.wasm')) - for creation in creations: - print(creation) - # Make sure JS is loaded from subdirectory - create_file('test-subdir.html', ''' - - - ''' % creation) - self.run_browser('test-subdir.html', '/report_result?0') + # compile the code with the modularize feature and the preload-file option enabled + self.compile_btest('browser_test_hello_world.c', ['-o', 'test.js', '-O3', '-sMODULARIZE']) + ensure_dir('subdir') + shutil.move('test.js', Path('subdir/test.js')) + shutil.move('test.wasm', Path('subdir/test.wasm')) + # Make sure JS is loaded from subdirectory + create_file('test-subdir.html', ''' + + + ''') + self.run_browser('test-subdir.html', '/report_result?0') # Similar to `test_browser_run_from_different_directory`, but # also also we eval the initial code, so currentScript is not present. That prevents us diff --git a/test/test_other.py b/test/test_other.py index 79c7c8d426263..17fd201531c02 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -6774,6 +6774,12 @@ def test_modularize_strict(self): output = self.run_js('run.js') self.assertEqual(output, 'hello, world!\n') + def test_modularize_new_misuse(self): + self.run_process([EMCC, test_file('hello_world.c'), '-sMODULARIZE', '-sEXPORT_NAME=Foo']) + create_file('run.js', 'var m = require("./a.out.js"); new m();') + err = self.run_js('run.js', assert_returncode=NON_ZERO) + self.assertContained('Error: Foo() should not be called with `new Foo()`', err) + @parameterized({ '': ([],), 'export_name': (['-sEXPORT_NAME=Foo'],), diff --git a/tools/link.py b/tools/link.py index 7f75ef4edb435..88ef0822e8a23 100644 --- a/tools/link.py +++ b/tools/link.py @@ -2452,6 +2452,19 @@ def modularize(): 'wrapper_function': wrapper_function, } + if settings.ASSERTIONS and settings.MODULARIZE != 'instance': + src += '''\ +(() => { + // Create a small, never-async wrapper around %(EXPORT_NAME)s which + // checks for callers incorrectly using it with `new`. + var real_%(EXPORT_NAME)s = %(EXPORT_NAME)s; + %(EXPORT_NAME)s = function(arg) { + if (new.target) throw new Error("%(EXPORT_NAME)s() should not be called with `new %(EXPORT_NAME)s()`"); + return real_%(EXPORT_NAME)s(arg); + } +})(); +''' % {'EXPORT_NAME': settings.EXPORT_NAME} + # Given the async nature of how the Module function and Module object # come into existence in AudioWorkletGlobalScope, store the Module # function under a different variable name so that AudioWorkletGlobalScope