author | Luke Wagner <luke@mozilla.com> |
Wed, 04 Apr 2018 17:44:11 -0500 | |
changeset 414096 | 350013b0e02d8f89925d8749f5cbd884e1eb1032 |
parent 414095 | d9f589a760631ef61885b4846c6f5479fb5addc2 |
child 414097 | a5aa0c6be992161aa110cfe91ad813a81a81767f |
push id | 33858 |
push user | ncsoregi@mozilla.com |
push date | Tue, 17 Apr 2018 21:55:44 +0000 |
treeherder | mozilla-central@d6eb5597d744 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | bbouvier |
bugs | 1425076 |
milestone | 61.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
|
--- 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>(); }