Bug 1425076 - Baldr: warn on invalid name section (r=bbouvier)
authorLuke Wagner <luke@mozilla.com>
Wed, 04 Apr 2018 17:44:11 -0500
changeset 414096 350013b0e02d8f89925d8749f5cbd884e1eb1032
parent 414095 d9f589a760631ef61885b4846c6f5479fb5addc2
child 414097 a5aa0c6be992161aa110cfe91ad813a81a81767f
push id33858
push userncsoregi@mozilla.com
push dateTue, 17 Apr 2018 21:55:44 +0000
treeherdermozilla-central@d6eb5597d744 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbouvier
bugs1425076
milestone61.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1425076 - Baldr: warn on invalid name section (r=bbouvier)
js/src/jit-test/lib/asserts.js
js/src/jit-test/tests/wasm/binary.js
js/src/js.msg
js/src/wasm/WasmBinaryConstants.h
js/src/wasm/WasmBinaryToAST.cpp
js/src/wasm/WasmCompile.cpp
js/src/wasm/WasmCompile.h
js/src/wasm/WasmJS.cpp
js/src/wasm/WasmModule.cpp
js/src/wasm/WasmTypes.h
js/src/wasm/WasmValidate.cpp
js/src/wasm/WasmValidate.h
--- a/js/src/jit-test/lib/asserts.js
+++ b/js/src/jit-test/lib/asserts.js
@@ -1,56 +1,53 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
 load(libdir + "../../tests/non262/shell.js");
 
 if (typeof assertWarning === 'undefined') {
-    var assertWarning = function assertWarning(f, errorClass, msg) {
+    var assertWarning = function assertWarning(f, pattern) {
         var hadWerror = options().split(",").indexOf("werror") !== -1;
 
         // Ensure the "werror" option is disabled.
         if (hadWerror)
             options("werror");
 
         try {
             f();
         } catch (exc) {
             if (hadWerror)
                 options("werror");
 
-            // print() rather than throw a different exception value, in case
-            // the caller wants exc.stack.
-            if (msg)
-                print("assertWarning: " + msg);
             print("assertWarning: Unexpected exception calling " + f +
                   " with warnings-as-errors disabled");
             throw exc;
         }
 
         // Enable the "werror" option.
         options("werror");
 
         try {
-            assertThrowsInstanceOf(f, errorClass, msg);
+            f();
         } catch (exc) {
-            if (msg)
-                print("assertWarning: " + msg);
-            throw exc;
+            if (!String(exc).match(pattern))
+                throw new Error(`assertWarning failed: "${exc}" does not match "${pattern}"`);
+            return;
         } finally {
             if (!hadWerror)
                 options("werror");
         }
+        throw new Error("assertWarning failed: no warning");
     };
 }
 
 if (typeof assertNoWarning === 'undefined') {
-    var assertNoWarning = function assertWarning(f, msg) {
+    var assertNoWarning = function assertNoWarning(f, msg) {
         // Ensure the "werror" option is enabled.
         var hadWerror = options().split(",").indexOf("werror") !== -1;
         if (!hadWerror)
             options("werror");
 
         try {
             f();
         } catch (exc) {
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -227,44 +227,51 @@ function elemSection(elemArrays) {
         body.push(...varU32(EndCode));
         body.push(...varU32(array.elems.length));
         for (let elem of array.elems)
             body.push(...varU32(elem));
     }
     return { name: elemId, body };
 }
 
-function nameSection(moduleName, funcNames) {
+function moduleNameSubsection(moduleName) {
+    var body = [];
+    body.push(...varU32(nameTypeModule));
+
+    var subsection = encodedString(moduleName);
+    body.push(...varU32(subsection.length));
+    body.push(...subsection);
+
+    return body;
+}
+
+function funcNameSubsection(funcNames) {
+    var body = [];
+    body.push(...varU32(nameTypeFunction));
+
+    var subsection = varU32(funcNames.length);
+
+    var funcIndex = 0;
+    for (let f of funcNames) {
+        subsection.push(...varU32(f.index ? f.index : funcIndex));
+        subsection.push(...encodedString(f.name, f.nameLen));
+        funcIndex++;
+    }
+
+    body.push(...varU32(subsection.length));
+    body.push(...subsection);
+    return body;
+}
+
+function nameSection(subsections) {
     var body = [];
     body.push(...string(nameName));
 
-    if (moduleName) {
-        body.push(...varU32(nameTypeModule));
-
-        var subsection = encodedString(moduleName);
-
-        body.push(...varU32(subsection.length));
-        body.push(...subsection);
-    }
-
-    if (funcNames) {
-        body.push(...varU32(nameTypeFunction));
-
-        var subsection = varU32(funcNames.length);
-
-        var funcIndex = 0;
-        for (let f of funcNames) {
-            subsection.push(...varU32(f.index ? f.index : funcIndex));
-            subsection.push(...encodedString(f.name, f.nameLen));
-            funcIndex++;
-        }
-
-        body.push(...varU32(subsection.length));
-        body.push(...subsection);
-    }
+    for (let ss of subsections)
+        body.push(...ss);
 
     return { name: userDefinedId, body };
 }
 
 function customSection(name, ...body) {
     return { name: userDefinedId, body: [...string(name), ...body] };
 }
 
@@ -390,17 +397,17 @@ var tooBigNameSection = {
     body: [...string(nameName), ...varU32(Math.pow(2, 31))] // declare 2**31 functions.
 };
 wasmEval(moduleWithSections([tooBigNameSection]));
 
 // Skip custom sections before any expected section
 var customDefSec = customSection("wee", 42, 13);
 var declSec = declSection([0]);
 var bodySec = bodySection([v2vBody]);
-var nameSec = nameSection(null, [{name:'hi'}]);
+var nameSec = nameSection([funcNameSubsection([{name:'hi'}])]);
 wasmEval(moduleWithSections([customDefSec, v2vSigSection, declSec, bodySec]));
 wasmEval(moduleWithSections([v2vSigSection, customDefSec, declSec, bodySec]));
 wasmEval(moduleWithSections([v2vSigSection, declSec, customDefSec, bodySec]));
 wasmEval(moduleWithSections([v2vSigSection, declSec, bodySec, customDefSec]));
 wasmEval(moduleWithSections([customDefSec, customDefSec, v2vSigSection, declSec, bodySec]));
 wasmEval(moduleWithSections([customDefSec, customDefSec, v2vSigSection, customDefSec, declSec, customDefSec, bodySec]));
 
 // custom sections reflection:
@@ -427,16 +434,36 @@ var arr = Module.customSections(m, "thre
 assertEq(arr.length, 3);
 checkCustomSection(arr[0], 4);
 checkCustomSection(arr[1], 5);
 checkCustomSection(arr[2], 6);
 var arr = Module.customSections(m, "name");
 assertEq(arr.length, 1);
 assertEq(arr[0].byteLength, nameSec.body.length - 5 /* 4name */);
 
+// Test name/custom section warnings:
+const nameWarning = /validated with warning.*'name' custom section/;
+const okNameSec = nameSection([]);
+assertNoWarning(() => wasmEval(moduleWithSections([v2vSigSection, declSec, bodySec, okNameSec])));
+const badNameSec1 = nameSection([]);
+badNameSec1.body.push(1);
+assertWarning(() => wasmEval(moduleWithSections([v2vSigSection, declSec, bodySec, badNameSec1])), nameWarning);
+const badNameSec2 = nameSection([funcNameSubsection([{name:'blah'}])]);
+badNameSec2.body.push(100, 20, 42, 83);
+assertWarning(() => wasmEval(moduleWithSections([v2vSigSection, declSec, bodySec, badNameSec2])), nameWarning);
+const badNameSec3 = nameSection([funcNameSubsection([{name:'blah'}])]);
+badNameSec3.body.pop();
+assertWarning(() => wasmEval(moduleWithSections([v2vSigSection, declSec, bodySec, badNameSec3])), nameWarning);
+assertNoWarning(() => wasmEval(moduleWithSections([nameSection([moduleNameSubsection('hi')])])));
+assertWarning(() => wasmEval(moduleWithSections([nameSection([moduleNameSubsection('hi'), moduleNameSubsection('boo')])])), nameWarning);
+// Unknown name subsection
+assertNoWarning(() => wasmEval(moduleWithSections([nameSection([moduleNameSubsection('hi'), [4, 0]])])));
+assertWarning(() => wasmEval(moduleWithSections([nameSection([moduleNameSubsection('hi'), [4, 1]])])), nameWarning);
+assertNoWarning(() => wasmEval(moduleWithSections([nameSection([moduleNameSubsection('hi'), [4, 1, 42]])])));
+
 // Diagnose nonstandard block signature types.
 for (var bad of [0xff, 0, 1, 0x3f])
     assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, bad, EndCode]})])])), CompileError, /invalid inline block type/);
 
 // Ensure all invalid opcodes rejected
 for (let i = FirstInvalidOpcode; i <= LastInvalidOpcode; i++) {
     let binary = moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[i]})])]);
     assertErrorMessage(() => wasmEval(binary), CompileError, /unrecognized opcode/);
@@ -494,18 +521,24 @@ function runStackTraceTest(moduleName, f
         sigSection([v2vSig]),
         importSection([{sigIndex:0, module:"env", func:"callback"}]),
         declSection([0]),
         exportSection([{funcIndex:1, name: "run"}]),
         bodySection([funcBody({locals: [], body: [CallCode, varU32(0)]})]),
         customSection("whoa"),
         customSection("wee", 42),
     ];
-    if (moduleName || funcNames)
-        sections.push(nameSection(moduleName, funcNames));
+    if (moduleName || funcNames) {
+        var subsections = [];
+        if (moduleName)
+            subsections.push(moduleNameSubsection(moduleName));
+        if (funcNames)
+            subsections.push(funcNameSubsection(funcNames));
+        sections.push(nameSection(subsections));
+    }
     sections.push(customSection("yay", 13));
 
     var result = "";
     var callback = () => {
         var prevFrameEntry = new Error().stack.split('\n')[1];
         result = prevFrameEntry.split('@')[0];
     };
     wasmEval(moduleWithSections(sections), {"env": { callback }}).run();
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -352,16 +352,17 @@ MSG_DEF(JSMSG_BAD_NEWTARGET,           0
 MSG_DEF(JSMSG_ESCAPED_KEYWORD,         0, JSEXN_SYNTAXERR, "keywords must be written literally, without embedded escapes")
 
 // asm.js
 MSG_DEF(JSMSG_USE_ASM_TYPE_FAIL,       1, JSEXN_TYPEERR, "asm.js type error: {0}")
 MSG_DEF(JSMSG_USE_ASM_LINK_FAIL,       1, JSEXN_TYPEERR, "asm.js link error: {0}")
 MSG_DEF(JSMSG_USE_ASM_TYPE_OK,         1, JSEXN_WARN,    "Successfully compiled asm.js code ({0})")
 
 // wasm
+MSG_DEF(JSMSG_WASM_COMPILE_WARNING,    1, JSEXN_WARN,    "WebAssembly module validated with warning: {0}")
 MSG_DEF(JSMSG_WASM_COMPILE_ERROR,      1, JSEXN_WASMCOMPILEERROR, "{0}")
 MSG_DEF(JSMSG_WASM_NO_SHMEM_COMPILE,   0, JSEXN_WASMCOMPILEERROR, "shared memory is disabled")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_TYPE,    2, JSEXN_WASMLINKERROR, "import object field '{0}' is not a {1}")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_SIG,     2, JSEXN_WASMLINKERROR, "imported function '{0}.{1}' signature mismatch")
 MSG_DEF(JSMSG_WASM_BAD_IMP_SIZE,       1, JSEXN_WASMLINKERROR, "imported {0} with incompatible size")
 MSG_DEF(JSMSG_WASM_BAD_IMP_MAX,        1, JSEXN_WASMLINKERROR, "imported {0} with incompatible maximum size")
 MSG_DEF(JSMSG_WASM_IMP_SHARED_REQD,    0, JSEXN_WASMLINKERROR, "imported unshared memory but shared required")
 MSG_DEF(JSMSG_WASM_IMP_SHARED_BANNED,  0, JSEXN_WASMLINKERROR, "imported shared memory but unshared required")
--- a/js/src/wasm/WasmBinaryConstants.h
+++ b/js/src/wasm/WasmBinaryConstants.h
@@ -587,24 +587,24 @@ struct OpBytes
 
     explicit OpBytes(Op x) {
         b0 = uint16_t(x);
         b1 = 0;
     }
     OpBytes() = default;
 };
 
-static const char NameSectionName[]      = "name";
+static const char NameSectionName[]             = "name";
 static const char SourceMappingURLSectionName[] = "sourceMappingURL";
 
 enum class NameType
 {
-    Module                               = 0,
-    Function                             = 1,
-    Local                                = 2
+    Module   = 0,
+    Function = 1,
+    Local    = 2
 };
 
 // These limits are agreed upon with other engines for consistency.
 
 static const unsigned MaxTypes               =  1000000;
 static const unsigned MaxFuncs               =  1000000;
 static const unsigned MaxImports             =   100000;
 static const unsigned MaxExports             =   100000;
--- a/js/src/wasm/WasmBinaryToAST.cpp
+++ b/js/src/wasm/WasmBinaryToAST.cpp
@@ -2272,17 +2272,17 @@ bool
 wasm::BinaryToAst(JSContext* cx, const uint8_t* bytes, uint32_t length, LifoAlloc& lifo,
                   AstModule** module)
 {
     AstModule* result = new(lifo) AstModule(lifo);
     if (!result || !result->init())
         return false;
 
     UniqueChars error;
-    Decoder d(bytes, bytes + length, 0, &error, /* resilient */ true);
+    Decoder d(bytes, bytes + length, 0, &error, nullptr, /* resilient */ true);
     AstDecodeContext c(cx, lifo, d, *result, true);
 
     if (!AstDecodeEnvironment(c) ||
         !AstDecodeCodeSection(c) ||
         !AstDecodeModuleTail(c))
     {
         if (error) {
             JS_ReportErrorNumberUTF8(c.cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR,
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -415,21 +415,22 @@ InitialCompileFlags(const CompileArgs& a
         *mode = CompileMode::Once;
         *tier = debugEnabled || !ionEnabled ? Tier::Baseline : Tier::Ion;
     }
 
     *debug = debugEnabled ? DebugEnabled::True : DebugEnabled::False;
 }
 
 SharedModule
-wasm::CompileBuffer(const CompileArgs& args, const ShareableBytes& bytecode, UniqueChars* error)
+wasm::CompileBuffer(const CompileArgs& args, const ShareableBytes& bytecode, UniqueChars* error,
+                    UniqueCharsVector* warnings)
 {
     MOZ_RELEASE_ASSERT(wasm::HaveSignalHandlers());
 
-    Decoder d(bytecode.bytes, 0, error);
+    Decoder d(bytecode.bytes, 0, error, warnings);
 
     CompileMode mode;
     Tier tier;
     DebugEnabled debug;
     InitialCompileFlags(args, d, &mode, &tier, &debug);
 
     ModuleEnvironment env(mode, tier, debug, args.gcTypesEnabled,
                           args.sharedMemoryEnabled ? Shareable::True : Shareable::False);
@@ -481,18 +482,18 @@ class StreamingDecoder
 {
     Decoder d_;
     const ExclusiveStreamEnd& streamEnd_;
     const Atomic<bool>& cancelled_;
 
   public:
     StreamingDecoder(const ModuleEnvironment& env, const Bytes& begin,
                      const ExclusiveStreamEnd& streamEnd, const Atomic<bool>& cancelled,
-                     UniqueChars* error)
-      : d_(begin, env.codeSection->start, error),
+                     UniqueChars* error, UniqueCharsVector* warnings)
+      : d_(begin, env.codeSection->start, error, warnings),
         streamEnd_(streamEnd),
         cancelled_(cancelled)
     {}
 
     bool fail(const char* msg) {
         return d_.fail(msg);
     }
 
@@ -562,24 +563,25 @@ CreateBytecode(const Bytes& env, const B
 
 SharedModule
 wasm::CompileStreaming(const CompileArgs& args,
                        const Bytes& envBytes,
                        const Bytes& codeBytes,
                        const ExclusiveStreamEnd& codeStreamEnd,
                        const ExclusiveTailBytesPtr& tailBytesPtr,
                        const Atomic<bool>& cancelled,
-                       UniqueChars* error)
+                       UniqueChars* error,
+                       UniqueCharsVector* warnings)
 {
     MOZ_ASSERT(wasm::HaveSignalHandlers());
 
     Maybe<ModuleEnvironment> env;
 
     {
-        Decoder d(envBytes, 0, error);
+        Decoder d(envBytes, 0, error, warnings);
 
         CompileMode mode;
         Tier tier;
         DebugEnabled debug;
         InitialCompileFlags(args, d, &mode, &tier, &debug);
 
         env.emplace(mode, tier, debug, args.gcTypesEnabled,
                     args.sharedMemoryEnabled ? Shareable::True : Shareable::False);
@@ -590,17 +592,17 @@ wasm::CompileStreaming(const CompileArgs
     }
 
     ModuleGenerator mg(args, env.ptr(), &cancelled, error);
     if (!mg.init())
         return nullptr;
 
     {
         MOZ_ASSERT(env->codeSection->size == codeBytes.length());
-        StreamingDecoder d(*env, codeBytes, codeStreamEnd, cancelled, error);
+        StreamingDecoder d(*env, codeBytes, codeStreamEnd, cancelled, error, warnings);
 
         if (!DecodeCodeSection(*env, d, mg))
             return nullptr;
 
         MOZ_ASSERT(d.done());
     }
 
     {
@@ -610,17 +612,17 @@ wasm::CompileStreaming(const CompileArgs
                 return nullptr;
             tailBytesPtrGuard.wait();
         }
     }
 
     const Bytes& tailBytes = *tailBytesPtr.lock();
 
     {
-        Decoder d(tailBytes, env->codeSection->end(), error);
+        Decoder d(tailBytes, env->codeSection->end(), error, warnings);
 
         if (!DecodeModuleTail(d, env.ptr()))
             return nullptr;
 
         MOZ_ASSERT(d.done());
     }
 
     SharedBytes bytecode = CreateBytecode(envBytes, codeBytes, tailBytes, error);
--- a/js/src/wasm/WasmCompile.h
+++ b/js/src/wasm/WasmCompile.h
@@ -81,17 +81,20 @@ EstimateCompiledCodeSize(Tier tier, size
 
 // Compile the given WebAssembly bytecode with the given arguments into a
 // wasm::Module. On success, the Module is returned. On failure, the returned
 // SharedModule pointer is null and either:
 //  - *error points to a string description of the error
 //  - *error is null and the caller should report out-of-memory.
 
 SharedModule
-CompileBuffer(const CompileArgs& args, const ShareableBytes& bytecode, UniqueChars* error);
+CompileBuffer(const CompileArgs& args,
+              const ShareableBytes& bytecode,
+              UniqueChars* error,
+              UniqueCharsVector* warnings);
 
 // Attempt to compile the second tier of the given wasm::Module, returning whether
 // tier-2 compilation succeeded and Module::finishTier2 was called.
 
 bool
 CompileTier2(const CompileArgs& args, Module& module, Atomic<bool>* cancelled);
 
 // Compile the given WebAssembly module which has been broken into three
@@ -116,14 +119,15 @@ typedef ExclusiveWaitableData<const Byte
 
 SharedModule
 CompileStreaming(const CompileArgs& args,
                  const Bytes& envBytes,
                  const Bytes& codeBytes,
                  const ExclusiveStreamEnd& codeStreamEnd,
                  const ExclusiveTailBytesPtr& tailBytesPtr,
                  const Atomic<bool>& cancelled,
-                 UniqueChars* error);
+                 UniqueChars* error,
+                 UniqueCharsVector* warnings);
 
 }  // namespace wasm
 }  // namespace js
 
 #endif // namespace wasm_compile_h
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -332,17 +332,18 @@ wasm::Eval(JSContext* cx, Handle<TypedAr
     if (!DescribeScriptedCaller(cx, &scriptedCaller))
         return false;
 
     MutableCompileArgs compileArgs = cx->new_<CompileArgs>();
     if (!compileArgs || !compileArgs->initFromContext(cx, Move(scriptedCaller)))
         return false;
 
     UniqueChars error;
-    SharedModule module = CompileBuffer(*compileArgs, *bytecode, &error);
+    UniqueCharsVector warnings;
+    SharedModule module = CompileBuffer(*compileArgs, *bytecode, &error, &warnings);
     if (!module) {
         if (error) {
             JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR,
                                      error.get());
             return false;
         }
         ReportOutOfMemory(cx);
         return false;
@@ -866,16 +867,37 @@ InitCompileArgs(JSContext* cx)
         return nullptr;
 
     if (!compileArgs->initFromContext(cx, Move(scriptedCaller)))
         return nullptr;
 
     return compileArgs;
 }
 
+static bool
+ReportCompileWarnings(JSContext* cx, const UniqueCharsVector& warnings)
+{
+    // Avoid spamming the console.
+    size_t numWarnings = Min<size_t>(warnings.length(), 3);
+
+    for (size_t i = 0; i < numWarnings; i++) {
+        if (!JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_WARNING, GetErrorMessage, nullptr,
+                                               JSMSG_WASM_COMPILE_WARNING, warnings[i].get()))
+            return false;
+    }
+
+    if (warnings.length() > numWarnings) {
+        if (!JS_ReportErrorFlagsAndNumberASCII(cx, JSREPORT_WARNING, GetErrorMessage, nullptr,
+                                               JSMSG_WASM_COMPILE_WARNING, "other warnings suppressed"))
+            return false;
+    }
+
+    return true;
+}
+
 /* static */ bool
 WasmModuleObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs callArgs = CallArgsFromVp(argc, vp);
 
     if (!ThrowIfNotConstructing(cx, callArgs, "Module"))
         return false;
 
@@ -891,27 +913,31 @@ WasmModuleObject::construct(JSContext* c
     if (!GetBufferSource(cx, &callArgs[0].toObject(), JSMSG_WASM_BAD_BUF_ARG, &bytecode))
         return false;
 
     SharedCompileArgs compileArgs = InitCompileArgs(cx);
     if (!compileArgs)
         return false;
 
     UniqueChars error;
-    SharedModule module = CompileBuffer(*compileArgs, *bytecode, &error);
+    UniqueCharsVector warnings;
+    SharedModule module = CompileBuffer(*compileArgs, *bytecode, &error, &warnings);
     if (!module) {
         if (error) {
             JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR,
                                      error.get());
             return false;
         }
         ReportOutOfMemory(cx);
         return false;
     }
 
+    if (!ReportCompileWarnings(cx, warnings))
+        return false;
+
     RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
     RootedObject moduleObj(cx, WasmModuleObject::create(cx, *module, proto));
     if (!moduleObj)
         return false;
 
     callArgs.rval().setObject(*moduleObj);
     return true;
 }
@@ -2329,17 +2355,18 @@ RejectWithPendingException(JSContext* cx
     RootedValue rejectionValue(cx);
     if (!GetAndClearException(cx, &rejectionValue))
         return false;
 
     return PromiseObject::reject(cx, promise, rejectionValue);
 }
 
 static bool
-Reject(JSContext* cx, const CompileArgs& args, UniqueChars error, Handle<PromiseObject*> promise)
+Reject(JSContext* cx, const CompileArgs& args, Handle<PromiseObject*> promise,
+       const UniqueChars& error)
 {
     if (!error) {
         ReportOutOfMemory(cx);
         return RejectWithPendingException(cx, promise);
     }
 
     RootedObject stack(cx, promise->allocationSite());
     RootedString filename(cx, JS_NewStringCopyZ(cx, args.scriptedCaller.filename.get()));
@@ -2366,18 +2393,21 @@ Reject(JSContext* cx, const CompileArgs&
         return false;
 
     RootedValue rejectionValue(cx, ObjectValue(*errorObj));
     return PromiseObject::reject(cx, promise, rejectionValue);
 }
 
 static bool
 Resolve(JSContext* cx, Module& module, Handle<PromiseObject*> promise, bool instantiate,
-        HandleObject importObj)
+        HandleObject importObj, const UniqueCharsVector& warnings)
 {
+    if (!ReportCompileWarnings(cx, warnings))
+        return false;
+
     RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmModule).toObject());
     RootedObject moduleObj(cx, WasmModuleObject::create(cx, module, proto));
     if (!moduleObj)
         return RejectWithPendingException(cx, promise);
 
     RootedValue resolutionValue(cx);
     if (instantiate) {
         RootedWasmInstanceObject instanceObj(cx);
@@ -2408,16 +2438,17 @@ Resolve(JSContext* cx, Module& module, H
     return true;
 }
 
 struct CompileBufferTask : PromiseHelperTask
 {
     MutableBytes           bytecode;
     SharedCompileArgs      compileArgs;
     UniqueChars            error;
+    UniqueCharsVector      warnings;
     SharedModule           module;
     bool                   instantiate;
     PersistentRootedObject importObj;
 
     CompileBufferTask(JSContext* cx, Handle<PromiseObject*> promise, HandleObject importObj)
       : PromiseHelperTask(cx, promise),
         instantiate(true),
         importObj(cx, importObj)
@@ -2431,23 +2462,23 @@ struct CompileBufferTask : PromiseHelper
     bool init(JSContext* cx) {
         compileArgs = InitCompileArgs(cx);
         if (!compileArgs)
             return false;
         return PromiseHelperTask::init(cx);
     }
 
     void execute() override {
-        module = CompileBuffer(*compileArgs, *bytecode, &error);
+        module = CompileBuffer(*compileArgs, *bytecode, &error, &warnings);
     }
 
     bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
         return module
-               ? Resolve(cx, *module, promise, instantiate, importObj)
-               : Reject(cx, *compileArgs, Move(error), promise);
+               ? Resolve(cx, *module, promise, instantiate, importObj, warnings)
+               : Reject(cx, *compileArgs, promise, error);
     }
 };
 
 static bool
 RejectWithPendingException(JSContext* cx, Handle<PromiseObject*> promise, CallArgs& callArgs)
 {
     if (!RejectWithPendingException(cx, promise))
         return false;
@@ -2635,16 +2666,17 @@ class CompileStreamTask : public Promise
     Bytes                        tailBytes_;       // immutable after Tail state
     ExclusiveTailBytesPtr        exclusiveTailBytes_;
     Maybe<uint32_t>              streamError_;
     Atomic<bool>                 streamFailed_;
 
     // Mutated on helper thread (execute()):
     SharedModule                 module_;
     UniqueChars                  compileError_;
+    UniqueCharsVector            warnings_;
 
     // Called on some thread before consumeChunk() or streamClosed():
 
     void noteResponseURLs(const char* url, const char* sourceMapUrl) override {
         if (url)
             compileArgs_->responseURLs.baseURL = DuplicateString(url);
         if (sourceMapUrl)
             compileArgs_->responseURLs.sourceMapURL = DuplicateString(sourceMapUrl);
@@ -2768,17 +2800,17 @@ class CompileStreamTask : public Promise
           case JS::StreamConsumer::EndOfFile:
             switch (streamState_.lock().get()) {
               case Env: {
                 SharedBytes bytecode = js_new<ShareableBytes>(Move(envBytes_));
                 if (!bytecode) {
                     rejectAndDestroyBeforeHelperThreadStarted(JSMSG_OUT_OF_MEMORY);
                     return;
                 }
-                module_ = CompileBuffer(*compileArgs_, *bytecode, &compileError_);
+                module_ = CompileBuffer(*compileArgs_, *bytecode, &compileError_, &warnings_);
                 setClosedAndDestroyBeforeHelperThreadStarted();
                 return;
               }
               case Code:
               case Tail:
                 {
                     auto tailBytes = exclusiveTailBytes_.lock();
                     tailBytes.get() = &tailBytes_;
@@ -2805,38 +2837,44 @@ class CompileStreamTask : public Promise
             break;
         }
         MOZ_CRASH("unreachable");
     }
 
     // Called on a helper thread:
 
     void execute() override {
-        module_ = CompileStreaming(*compileArgs_, envBytes_, codeBytes_, exclusiveCodeStreamEnd_,
-                                   exclusiveTailBytes_, streamFailed_, &compileError_);
+        module_ = CompileStreaming(*compileArgs_,
+                                   envBytes_,
+                                   codeBytes_,
+                                   exclusiveCodeStreamEnd_,
+                                   exclusiveTailBytes_,
+                                   streamFailed_,
+                                   &compileError_,
+                                   &warnings_);
 
         // When execute() returns, the CompileStreamTask will be dispatched
         // back to its JS thread to call resolve() and then be destroyed. We
         // can't let this happen until the stream has been closed lest
         // consumeChunk() or streamClosed() be called on a dead object.
         auto streamState = streamState_.lock();
         while (streamState != Closed)
             streamState.wait(/* stream closed */);
     }
 
     // Called on a JS thread after streaming compilation completes/errors:
 
     bool resolve(JSContext* cx, Handle<PromiseObject*> promise) override {
         MOZ_ASSERT(streamState_.lock() == Closed);
         MOZ_ASSERT_IF(module_, !streamFailed_ && !streamError_ && !compileError_);
         return module_
-               ? Resolve(cx, *module_, promise, instantiate_, importObj_)
+               ? Resolve(cx, *module_, promise, instantiate_, importObj_, warnings_)
                : streamError_
                  ? RejectWithErrorNumber(cx, *streamError_, promise)
-                 : Reject(cx, *compileArgs_, Move(compileError_), promise);
+                 : Reject(cx, *compileArgs_, promise, compileError_);
     }
 
   public:
     CompileStreamTask(JSContext* cx, Handle<PromiseObject*> promise,
                       CompileArgs& compileArgs, bool instantiate,
                       HandleObject importObj)
       : PromiseHelperTask(cx, promise),
         compileArgs_(&compileArgs),
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -598,17 +598,18 @@ wasm::DeserializeModule(PRFileDesc* byte
     //
     // (We would prefer to store this value with the Assumptions when
     // serializing, and for the caller of the deserialization machinery to
     // provide the value from the originating context.)
 
     args->sharedMemoryEnabled = true;
 
     UniqueChars error;
-    return CompileBuffer(*args, *bytecode, &error);
+    UniqueCharsVector warnings;
+    return CompileBuffer(*args, *bytecode, &error, &warnings);
 }
 
 /* virtual */ void
 Module::addSizeOfMisc(MallocSizeOf mallocSizeOf,
                       Metadata::SeenSet* seenMetadata,
                       ShareableBytes::SeenSet* seenBytes,
                       Code::SeenSet* seenCode,
                       size_t* code,
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -103,16 +103,17 @@ class Instance;
 class Table;
 
 typedef Vector<uint32_t, 0, SystemAllocPolicy> Uint32Vector;
 typedef Vector<uint8_t, 0, SystemAllocPolicy> Bytes;
 typedef UniquePtr<Bytes> UniqueBytes;
 typedef UniquePtr<const Bytes> UniqueConstBytes;
 typedef Vector<char, 0, SystemAllocPolicy> UTF8Bytes;
 typedef Vector<Instance*, 0, SystemAllocPolicy> InstanceVector;
+typedef Vector<UniqueChars, 0, SystemAllocPolicy> UniqueCharsVector;
 
 // To call Vector::podResizeToFit, a type must specialize mozilla::IsPod
 // which is pretty verbose to do within js::wasm, so factor that process out
 // into a macro.
 
 #define WASM_DECLARE_POD_VECTOR(Type, VectorName)                               \
 } } namespace mozilla {                                                         \
 template <> struct IsPod<js::wasm::Type> : TrueType {};                         \
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -14,44 +14,62 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "wasm/WasmValidate.h"
 
 #include "mozilla/CheckedInt.h"
+#include "mozilla/Unused.h"
 
 #include "jit/JitOptions.h"
 #include "js/Printf.h"
 #include "vm/JSCompartment.h"
 #include "vm/JSContext.h"
 #include "wasm/WasmOpIter.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::CheckedInt;
+using mozilla::Unused;
 
 // Decoder implementation.
 
 bool
 Decoder::failf(const char* msg, ...)
 {
     va_list ap;
     va_start(ap, msg);
     UniqueChars str(JS_vsmprintf(msg, ap));
     va_end(ap);
     if (!str)
         return false;
 
     return fail(str.get());
 }
 
+void
+Decoder::warnf(const char* msg, ...)
+{
+    if (!warnings_)
+        return;
+
+    va_list ap;
+    va_start(ap, msg);
+    UniqueChars str(JS_vsmprintf(msg, ap));
+    va_end(ap);
+    if (!str)
+        return;
+
+    Unused << warnings_->append(Move(str));
+}
+
 bool
 Decoder::fail(size_t errorOffset, const char* msg)
 {
     MOZ_ASSERT(error_);
     UniqueChars strWithOffset(JS_smprintf("at offset %zu: %s", errorOffset, msg));
     if (!strWithOffset)
         return false;
 
@@ -187,32 +205,60 @@ Decoder::startCustomSection(const char* 
 
         // If this is the expected custom section, we're done.
         if (!expected || (expectedLength == name.length && !memcmp(cur_, expected, name.length))) {
             cur_ += name.length;
             return true;
         }
 
         // Otherwise, blindly skip the custom section and keep looking.
-        finishCustomSection(**range);
+        skipAndFinishCustomSection(**range);
         range->reset();
     }
     MOZ_CRASH("unreachable");
 
   rewind:
     cur_ = initialCur;
     env->customSections.shrinkTo(initialCustomSectionsLength);
     return true;
 
   fail:
     return fail("failed to start custom section");
 }
 
 void
-Decoder::finishCustomSection(const SectionRange& range)
+Decoder::finishCustomSection(const char* name, const SectionRange& range)
+{
+    MOZ_ASSERT(cur_ >= beg_);
+    MOZ_ASSERT(cur_ <= end_);
+
+    if (error_ && *error_) {
+        warnf("in the '%s' custom section: %s", name, error_->get());
+        skipAndFinishCustomSection(range);
+        return;
+    }
+
+    uint32_t actualSize = currentOffset() - range.start;
+    if (range.size != actualSize) {
+        if (actualSize < range.size) {
+            warnf("in the '%s' custom section: %" PRIu32 " unconsumed bytes",
+                  name, uint32_t(range.size - actualSize));
+        } else {
+            warnf("in the '%s' custom section: %" PRIu32 " bytes consumed past the end",
+                  name, uint32_t(actualSize - range.size));
+        }
+        skipAndFinishCustomSection(range);
+        return;
+    }
+
+    // Nothing to do! (c.f. skipAndFinishCustomSection())
+}
+
+void
+Decoder::skipAndFinishCustomSection(const SectionRange& range)
 {
     MOZ_ASSERT(cur_ >= beg_);
     MOZ_ASSERT(cur_ <= end_);
     cur_ = (beg_ + (range.start - offsetInModule_)) + range.size;
     MOZ_ASSERT(cur_ <= end_);
     clearError();
 }
 
@@ -220,48 +266,78 @@ bool
 Decoder::skipCustomSection(ModuleEnvironment* env)
 {
     MaybeSectionRange range;
     if (!startCustomSection(nullptr, 0, env, &range))
         return false;
     if (!range)
         return fail("expected custom section");
 
-    finishCustomSection(*range);
+    skipAndFinishCustomSection(*range);
     return true;
 }
 
 bool
 Decoder::startNameSubsection(NameType nameType, Maybe<uint32_t>* endOffset)
 {
     MOZ_ASSERT(!*endOffset);
 
-    const uint8_t* initialPosition = cur_;
+    const uint8_t* const initialPosition = cur_;
 
     uint8_t nameTypeValue;
     if (!readFixedU8(&nameTypeValue))
-        return false;
+        goto rewind;
 
-    if (nameTypeValue != uint8_t(nameType)) {
-        cur_ = initialPosition;
-        return true;
-    }
+    if (nameTypeValue != uint8_t(nameType))
+        goto rewind;
 
     uint32_t payloadLength;
     if (!readVarU32(&payloadLength) || payloadLength > bytesRemain())
-        return false;
+        return fail("bad name subsection payload length");
 
     *endOffset = Some(currentOffset() + payloadLength);
     return true;
+
+  rewind:
+    cur_ = initialPosition;
+    return true;
 }
 
 bool
-Decoder::finishNameSubsection(uint32_t endOffset)
+Decoder::finishNameSubsection(uint32_t expected)
+{
+    uint32_t actual = currentOffset();
+    if (expected != actual) {
+        return failf("bad name subsection length (expected: %" PRIu32 ", actual: %" PRIu32 ")",
+                     expected, actual);
+    }
+
+    return true;
+}
+
+bool
+Decoder::skipNameSubsection()
 {
-    return endOffset == uint32_t(currentOffset());
+    uint8_t nameTypeValue;
+    if (!readFixedU8(&nameTypeValue))
+        return fail("unable to read name subsection id");
+
+    switch (nameTypeValue) {
+      case uint8_t(NameType::Module):
+      case uint8_t(NameType::Function):
+        return fail("out of order name subsections");
+      default:
+        break;
+    }
+
+    uint32_t payloadLength;
+    if (!readVarU32(&payloadLength) || !readBytes(payloadLength))
+        return fail("bad name subsection payload length");
+
+    return true;
 }
 
 // Misc helpers.
 
 bool
 wasm::EncodeLocalEntries(Encoder& e, const ValTypeVector& locals)
 {
     if (locals.length() > MaxLocals)
@@ -1880,21 +1956,21 @@ DecodeModuleNameSubsection(Decoder& d)
 
     // Don't use NameInBytecode for module name; instead store a copy of the
     // string. This way supplying a module name doesn't need to save the whole
     // bytecode. While function names are likely to be stripped in practice,
     // module names aren't necessarily.
 
     uint32_t nameLength;
     if (!d.readVarU32(&nameLength))
-        return false;
+        return d.fail("failed to read module name length");
 
     const uint8_t* bytes;
     if (!d.readBytes(nameLength, &bytes))
-        return false;
+        return d.fail("failed to read module name bytes");
 
     // Do nothing with module name for now; a future patch will incorporate the
     // module name into the callstack format.
 
     return d.finishNameSubsection(*endOffset);
 }
 
 static bool
@@ -1903,43 +1979,43 @@ DecodeFunctionNameSubsection(Decoder& d,
     Maybe<uint32_t> endOffset;
     if (!d.startNameSubsection(NameType::Function, &endOffset))
         return false;
     if (!endOffset)
         return true;
 
     uint32_t nameCount = 0;
     if (!d.readVarU32(&nameCount) || nameCount > MaxFuncs)
-        return false;
+        return d.fail("bad function name count");
 
     NameInBytecodeVector funcNames;
 
     for (uint32_t i = 0; i < nameCount; ++i) {
         uint32_t funcIndex = 0;
         if (!d.readVarU32(&funcIndex))
-            return false;
+            return d.fail("unable to read function index");
 
         // Names must refer to real functions and be given in ascending order.
         if (funcIndex >= env->numFuncs() || funcIndex < funcNames.length())
-            return false;
+            return d.fail("invalid function index");
 
         uint32_t nameLength = 0;
         if (!d.readVarU32(&nameLength) || nameLength > MaxStringLength)
-            return false;
+            return d.fail("unable to read function name length");
 
         if (!nameLength)
             continue;
 
         if (!funcNames.resize(funcIndex + 1))
             return false;
 
         funcNames[funcIndex] = NameInBytecode(d.currentOffset(), nameLength);
 
         if (!d.readBytes(nameLength))
-            return false;
+            return d.fail("unable to read function name bytes");
     }
 
     if (!d.finishNameSubsection(*endOffset))
         return false;
 
     // To encourage fully valid function names subsections; only save names if
     // the entire subsection decoded correctly.
     env->funcNames = Move(funcNames);
@@ -1958,22 +2034,23 @@ DecodeNameSection(Decoder& d, ModuleEnvi
     // Once started, custom sections do not report validation errors.
 
     if (!DecodeModuleNameSubsection(d))
         goto finish;
 
     if (!DecodeFunctionNameSubsection(d, env))
         goto finish;
 
-    // The names we care about have already been extracted into 'env' so don't
-    // bother decoding the rest of the name section. finishCustomSection() will
-    // skip to the end of the name section (as it would for any other error).
+    while (d.currentOffset() < range->end()) {
+        if (!d.skipNameSubsection())
+            goto finish;
+    }
 
   finish:
-    d.finishCustomSection(*range);
+    d.finishCustomSection(NameSectionName, *range);
     return true;
 }
 
 bool
 wasm::DecodeModuleTail(Decoder& d, ModuleEnvironment* env)
 {
     if (!DecodeDataSection(d, env))
         return false;
--- a/js/src/wasm/WasmValidate.h
+++ b/js/src/wasm/WasmValidate.h
@@ -349,16 +349,17 @@ class Encoder
 
 class Decoder
 {
     const uint8_t* const beg_;
     const uint8_t* const end_;
     const uint8_t* cur_;
     const size_t offsetInModule_;
     UniqueChars* error_;
+    UniqueCharsVector* warnings_;
     bool resilientMode_;
 
     template <class T>
     MOZ_MUST_USE bool read(T* out) {
         if (bytesRemain() < sizeof(T))
             return false;
         memcpy((void*)out, cur_, sizeof(T));
         cur_ += sizeof(T);
@@ -434,38 +435,42 @@ class Decoder
         if ((byte & mask) != ((byte & (1 << (remainderBits - 1))) ? mask : 0))
             return false;
         *out = s | UInt(byte) << shift;
         return true;
     }
 
   public:
     Decoder(const uint8_t* begin, const uint8_t* end, size_t offsetInModule, UniqueChars* error,
-            bool resilientMode = false)
+            UniqueCharsVector* warnings = nullptr, bool resilientMode = false)
       : beg_(begin),
         end_(end),
         cur_(begin),
         offsetInModule_(offsetInModule),
         error_(error),
+        warnings_(warnings),
         resilientMode_(resilientMode)
     {
         MOZ_ASSERT(begin <= end);
     }
-    explicit Decoder(const Bytes& bytes, size_t offsetInModule = 0, UniqueChars* error = nullptr)
+    explicit Decoder(const Bytes& bytes, size_t offsetInModule = 0, UniqueChars* error = nullptr,
+                     UniqueCharsVector* warnings = nullptr)
       : beg_(bytes.begin()),
         end_(bytes.end()),
         cur_(bytes.begin()),
         offsetInModule_(offsetInModule),
         error_(error),
+        warnings_(warnings),
         resilientMode_(false)
     {}
 
     // These convenience functions use currentOffset() as the errorOffset.
     bool fail(const char* msg) { return fail(currentOffset(), msg); }
     bool failf(const char* msg, ...) MOZ_FORMAT_PRINTF(2, 3);
+    void warnf(const char* msg, ...) MOZ_FORMAT_PRINTF(2, 3);
 
     // Report an error at the given offset (relative to the whole module).
     bool fail(size_t errorOffset, const char* msg);
 
     void clearError() {
         if (error_)
             error_->reset();
     }
@@ -589,31 +594,36 @@ class Decoder
 
     // Custom sections do not cause validation errors unless the error is in
     // the section header itself.
 
     MOZ_MUST_USE bool startCustomSection(const char* expected,
                                          size_t expectedLength,
                                          ModuleEnvironment* env,
                                          MaybeSectionRange* range);
+
     template <size_t NameSizeWith0>
     MOZ_MUST_USE bool startCustomSection(const char (&name)[NameSizeWith0],
                                          ModuleEnvironment* env,
                                          MaybeSectionRange* range)
     {
         MOZ_ASSERT(name[NameSizeWith0 - 1] == '\0');
         return startCustomSection(name, NameSizeWith0 - 1, env, range);
     }
-    void finishCustomSection(const SectionRange& range);
+
+    void finishCustomSection(const char* name, const SectionRange& range);
+    void skipAndFinishCustomSection(const SectionRange& range);
+
     MOZ_MUST_USE bool skipCustomSection(ModuleEnvironment* env);
 
     // The Name section has its own optional subsections.
 
     MOZ_MUST_USE bool startNameSubsection(NameType nameType, Maybe<uint32_t>* endOffset);
     MOZ_MUST_USE bool finishNameSubsection(uint32_t endOffset);
+    MOZ_MUST_USE bool skipNameSubsection();
 
     // The infallible "unchecked" decoding functions can be used when we are
     // sure that the bytes are well-formed (by construction or due to previous
     // validation).
 
     uint8_t uncheckedReadFixedU8() {
         return uncheckedRead<uint8_t>();
     }