diff --git a/src/binaryDecode.js b/src/binaryDecode.js
index f5ef77313422b..4572a4ff2d18e 100644
--- a/src/binaryDecode.js
+++ b/src/binaryDecode.js
@@ -2,7 +2,16 @@
// Closure may analyze through the WASM_BINARY_DATA placeholder string into this
// function, leading into incorrect results.
/** @noinline */
+#if ASSERTIONS
+function binaryDecode(bin, expectedLength) {
+ // Each byte of the binary is one character of the string, so if the lengths
+ // differ, the file was not read as UTF-8 (for example a page without
+ // loading it from file://), and the bytes are wrong.
+ // Say so here, rather than let it fail as a confusing wasm CompileError.
+ assert(bin.length === expectedLength, `The embedded Wasm binary is ${bin.length} characters long, but should be ${expectedLength}: this file was not read as UTF-8 (SINGLE_FILE_BINARY_ENCODE). Serve it with charset=utf-8, add to the page, or build with -sSINGLE_FILE_BINARY_ENCODE=0`);
+#else
function binaryDecode(bin) {
+#endif
for (var i = 0, l = bin.length, o = new Uint8Array(l), c; i < l; ++i) {
c = bin.charCodeAt(i);
o[i] = ~c >> 8 & c; // Recover the null byte in a manner that is compatible with https://crbug.com/453961758
diff --git a/src/postamble_minimal.js b/src/postamble_minimal.js
index a721f98a42259..61537bdfc6391 100644
--- a/src/postamble_minimal.js
+++ b/src/postamble_minimal.js
@@ -154,7 +154,11 @@ var instantiatePromise;
#endif
#if SINGLE_FILE && SINGLE_FILE_BINARY_ENCODE && !WASM2JS
+#if ASSERTIONS
+Module['wasm'] = binaryDecode("<<< WASM_BINARY_DATA >>>", "<<< WASM_BINARY_SIZE >>>");
+#else
Module['wasm'] = binaryDecode("<<< WASM_BINARY_DATA >>>");
+#endif
#elif SINGLE_FILE && WASM == 1 && !WASM2JS
Module['wasm'] = base64Decode('<<< WASM_BINARY_DATA >>>');
#endif
diff --git a/src/preamble.js b/src/preamble.js
index c6656a977b60f..3d3aac9f40be4 100644
--- a/src/preamble.js
+++ b/src/preamble.js
@@ -418,7 +418,11 @@ function getWasmBinary(file) {}
function findWasmBinary() {
#if SINGLE_FILE && SINGLE_FILE_BINARY_ENCODE && !WASM2JS
+#if ASSERTIONS
+ return binaryDecode("<<< WASM_BINARY_DATA >>>", "<<< WASM_BINARY_SIZE >>>");
+#else
return binaryDecode("<<< WASM_BINARY_DATA >>>");
+#endif
#elif SINGLE_FILE
return base64Decode('<<< WASM_BINARY_DATA >>>');
#elif AUDIO_WORKLET || !EXPORT_ES6 // For an Audio Worklet, we cannot use `new URL()`.
diff --git a/test/test_browser.py b/test/test_browser.py
index 5b2da81f03563..9182c10ac385b 100644
--- a/test/test_browser.py
+++ b/test/test_browser.py
@@ -4727,6 +4727,18 @@ def test_single_file_locate_file(self):
self.run_browser('test.html', '/report_result?0')
+ # Tests that a SINGLE_FILE build loaded from a page whose charset is not UTF-8
+ # reports that, rather than failing to compile the mangled Wasm binary.
+ def test_single_file_not_utf8(self):
+ self.compile_btest('browser_test_hello_world.c', ['-o', 'test.js', '-sSINGLE_FILE', '-sASSERTIONS'])
+
+ create_file('test.html', '''
+
+
+ ''')
+
+ self.run_browser('test.html', 'this file was not read as UTF-8')
+
# Tests that SINGLE_FILE works as intended in a Worker in JS output
def test_single_file_worker_js(self):
self.btest_exit('browser_test_hello_world.c', cflags=['-sSINGLE_FILE'], run_in_worker=True)
diff --git a/test/test_other.py b/test/test_other.py
index f4162deb73fad..47318cd892831 100644
--- a/test/test_other.py
+++ b/test/test_other.py
@@ -9445,12 +9445,23 @@ def test_single_file_instantiate_failure(self):
self.run_process([EMCC, test_file('hello_world.c'), '-sSINGLE_FILE', '-sASSERTIONS=1', '-o', 'out.js'])
code = read_file('out.js')
# Stub out binaryDecode so that it returns an invalid wasm binary.
- code = code.replace('function binaryDecode(bin) {', 'function binaryDecode(bin) { return new Uint8Array([0]);')
+ code = code.replace('function binaryDecode(bin, expectedLength) {', 'function binaryDecode(bin, expectedLength) { return new Uint8Array([0]);')
write_file('out_bad.js', code)
out = self.run_js('out_bad.js', assert_returncode=1)
self.assertContained('Aborted(CompileError', out)
self.assertNotContained('startsWith is not a function', out)
+ def test_single_file_not_utf8(self):
+ # A SINGLE_FILE_BINARY_ENCODE build read in an encoding other than UTF-8
+ # (as a browser does from a page with no charset) gets a mangled Wasm
+ # binary. With ASSERTIONS it says so instead of failing to compile it.
+ self.run_process([EMCC, test_file('hello_world.c'), '-sSINGLE_FILE', '-sASSERTIONS', '-o', 'out.js'])
+ # Each byte read as one Latin-1 character, as a Windows-1252 read does.
+ write_file('out_latin1.js', read_binary('out.js').decode('latin-1'))
+ out = self.run_js('out_latin1.js', assert_returncode=1)
+ self.assertContained('this file was not read as UTF-8', out)
+ self.assertNotContained('CompileError', out)
+
def test_single_file_disables_source_map(self):
cmd = [EMCC, test_file('hello_world.c'), '-sSINGLE_FILE', '-gsource-map']
stderr = self.run_process(cmd, stderr=PIPE).stderr
diff --git a/tools/link.py b/tools/link.py
index 062816b97464c..7bbf9a2009e77 100644
--- a/tools/link.py
+++ b/tools/link.py
@@ -2453,6 +2453,8 @@ def phase_binaryen(target, wasm_target):
if settings.SINGLE_FILE_BINARY_ENCODE:
js = do_replace(js, '"<<< WASM_BINARY_DATA >>>"', binary_encode(wasm_target))
+ if settings.ASSERTIONS:
+ js = do_replace(js, '"<<< WASM_BINARY_SIZE >>>"', str(os.path.getsize(wasm_target)))
else:
js = do_replace(js, '<<< WASM_BINARY_DATA >>>', base64_encode(wasm_target))
delete_file(wasm_target)