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)