Skip to content

Commit ced299a

Browse files
committed
Assert if MODULARIZE factory function is used with new.
Once we start using `async` for for this function then `new` stops working: emscripten-core#23157. Using `new` in this was was always an antipattern but there was nothing stopping users from doing this.
1 parent dea1bf9 commit ced299a

File tree

4 files changed

+37
-22
lines changed

4 files changed

+37
-22
lines changed

ChangeLog.md

+5
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ See docs/process.md for more on how version tagging works.
2020

2121
3.1.75 (in development)
2222
-----------------------
23+
- When using `-sMODULARIZE` we now assert if the factory function is called with
24+
the JS `new` keyword. e.g. `var a = new Module()` rather than `var b =
25+
Module()`. This paves the way for marking the function as `async` which does
26+
not allow `new` to be used. This usage of `new` here was never documented and
27+
is considered and antipattern. (#)
2328
- `PATH.basename()` no longer calls `PATH.normalize()`, so that
2429
`PATH.basename("a/.")` returns `"."` instead of `"a"` and
2530
`PATH.basename("a/b/..")` returns `".."` instead of `"a"`. This is in line with

test/test_browser.py

+13-22
Original file line numberDiff line numberDiff line change
@@ -4795,28 +4795,19 @@ def test_browser_run_from_different_directory(self):
47954795

47964796
# Similar to `test_browser_run_from_different_directory`, but asynchronous because of `-sMODULARIZE`
47974797
def test_browser_run_from_different_directory_async(self):
4798-
for args, creations in [
4799-
(['-sMODULARIZE'], [
4800-
'Module();', # documented way for using modularize
4801-
'new Module();' # not documented as working, but we support it
4802-
]),
4803-
]:
4804-
print(args)
4805-
# compile the code with the modularize feature and the preload-file option enabled
4806-
self.compile_btest('browser_test_hello_world.c', ['-o', 'test.js', '-O3'] + args)
4807-
ensure_dir('subdir')
4808-
shutil.move('test.js', Path('subdir/test.js'))
4809-
shutil.move('test.wasm', Path('subdir/test.wasm'))
4810-
for creation in creations:
4811-
print(creation)
4812-
# Make sure JS is loaded from subdirectory
4813-
create_file('test-subdir.html', '''
4814-
<script src="subdir/test.js"></script>
4815-
<script>
4816-
%s
4817-
</script>
4818-
''' % creation)
4819-
self.run_browser('test-subdir.html', '/report_result?0')
4798+
# compile the code with the modularize feature and the preload-file option enabled
4799+
self.compile_btest('browser_test_hello_world.c', ['-o', 'test.js', '-O3', '-sMODULARIZE'])
4800+
ensure_dir('subdir')
4801+
shutil.move('test.js', Path('subdir/test.js'))
4802+
shutil.move('test.wasm', Path('subdir/test.wasm'))
4803+
# Make sure JS is loaded from subdirectory
4804+
create_file('test-subdir.html', '''
4805+
<script src="subdir/test.js"></script>
4806+
<script>
4807+
Module();
4808+
</script>
4809+
''')
4810+
self.run_browser('test-subdir.html', '/report_result?0')
48204811

48214812
# Similar to `test_browser_run_from_different_directory`, but
48224813
# also also we eval the initial code, so currentScript is not present. That prevents us

test/test_other.py

+6
Original file line numberDiff line numberDiff line change
@@ -6774,6 +6774,12 @@ def test_modularize_strict(self):
67746774
output = self.run_js('run.js')
67756775
self.assertEqual(output, 'hello, world!\n')
67766776

6777+
def test_modularize_new_misuse(self):
6778+
self.run_process([EMCC, test_file('hello_world.c'), '-sMODULARIZE', '-sEXPORT_NAME=Foo'])
6779+
create_file('run.js', 'var m = require("./a.out.js"); new m();')
6780+
err = self.run_js('run.js', assert_returncode=NON_ZERO)
6781+
self.assertContained('Error: Foo() should not be called with `new Foo()`', err)
6782+
67776783
@parameterized({
67786784
'': ([],),
67796785
'export_name': (['-sEXPORT_NAME=Foo'],),

tools/link.py

+13
Original file line numberDiff line numberDiff line change
@@ -2452,6 +2452,19 @@ def modularize():
24522452
'wrapper_function': wrapper_function,
24532453
}
24542454

2455+
if settings.ASSERTIONS and settings.MODULARIZE != 'instance':
2456+
src += '''\
2457+
(() => {
2458+
// Create a small, never-async wrapper around %(EXPORT_NAME)s which
2459+
// checks for callers incorrectly using it with `new`.
2460+
var real_%(EXPORT_NAME)s = %(EXPORT_NAME)s;
2461+
%(EXPORT_NAME)s = function(arg) {
2462+
if (new.target) throw new Error("%(EXPORT_NAME)s() should not be called with `new %(EXPORT_NAME)s()`");
2463+
return real_%(EXPORT_NAME)s(arg);
2464+
}
2465+
})();
2466+
''' % {'EXPORT_NAME': settings.EXPORT_NAME}
2467+
24552468
# Given the async nature of how the Module function and Module object
24562469
# come into existence in AudioWorkletGlobalScope, store the Module
24572470
# function under a different variable name so that AudioWorkletGlobalScope

0 commit comments

Comments
 (0)