diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index 38568cfc02..d2bf28549e 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -2053,9 +2053,7 @@ class MOZ_STACK_CLASS ModuleValidator if (!bytes) return nullptr; - return mg_.finish(*bytes, - DataSegmentVector(), - NameInBytecodeVector()); + return mg_.finish(*bytes); } }; diff --git a/js/src/wasm/WasmBinaryConstants.h b/js/src/wasm/WasmBinaryConstants.h index 8856783528..0d0744da87 100644 --- a/js/src/wasm/WasmBinaryConstants.h +++ b/js/src/wasm/WasmBinaryConstants.h @@ -30,7 +30,7 @@ static const uint32_t EncodingVersion = 0x01; static const uint32_t PrevEncodingVersion = 0x0d; enum class SectionId { - UserDefined = 0, + Custom = 0, Type = 1, Import = 2, Function = 3, diff --git a/js/src/wasm/WasmBinaryToAST.cpp b/js/src/wasm/WasmBinaryToAST.cpp index d041cddf34..096c381843 100644 --- a/js/src/wasm/WasmBinaryToAST.cpp +++ b/js/src/wasm/WasmBinaryToAST.cpp @@ -1825,10 +1825,10 @@ AstDecodeEnvironment(AstDecodeContext& c) } static bool -AstDecodeCodeSection(AstDecodeContext &c) +AstDecodeCodeSection(AstDecodeContext& c) { uint32_t sectionStart, sectionSize; - if (!c.d.startSection(SectionId::Code, §ionStart, §ionSize, "code")) + if (!c.d.startSection(SectionId::Code, &c.env(), §ionStart, §ionSize, "code")) return false; if (sectionStart == Decoder::NotStarted) { @@ -1863,15 +1863,14 @@ AstDecodeCodeSection(AstDecodeContext &c) static const size_t WRAP_DATA_BYTES = 30; static bool -AstDecodeDataSection(AstDecodeContext &c) +AstDecodeModuleTail(AstDecodeContext& c) { MOZ_ASSERT(c.module().memories().length() <= 1, "at most one memory in MVP"); - DataSegmentVector segments; - if (!DecodeDataSection(c.d, c.env(), &segments)) + if (!DecodeModuleTail(c.d, &c.env())) return false; - for (DataSegment& s : segments) { + for (DataSegment& s : c.env().dataSegments) { char16_t* buffer = static_cast(c.lifo.alloc(s.length * sizeof(char16_t))); if (!buffer) return false; @@ -1913,8 +1912,7 @@ wasm::BinaryToAst(JSContext* cx, const uint8_t* bytes, uint32_t length, if (!AstDecodeEnvironment(c) || !AstDecodeCodeSection(c) || - !AstDecodeDataSection(c) || - !DecodeUnknownSections(c.d)) + !AstDecodeModuleTail(c)) { if (error) { JS_ReportErrorNumberASCII(c.cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR, diff --git a/js/src/wasm/WasmCode.cpp b/js/src/wasm/WasmCode.cpp index 4d6085b960..09b3122e08 100644 --- a/js/src/wasm/WasmCode.cpp +++ b/js/src/wasm/WasmCode.cpp @@ -469,6 +469,7 @@ Metadata::serializedSize() const SerializedPodVectorSize(callSites) + SerializedPodVectorSize(callThunks) + SerializedPodVectorSize(funcNames) + + SerializedPodVectorSize(customSections) + filename.serializedSize(); } @@ -488,6 +489,7 @@ Metadata::serialize(uint8_t* cursor) const cursor = SerializePodVector(cursor, callSites); cursor = SerializePodVector(cursor, callThunks); cursor = SerializePodVector(cursor, funcNames); + cursor = SerializePodVector(cursor, customSections); cursor = filename.serialize(cursor); return cursor; } @@ -508,6 +510,7 @@ Metadata::deserialize(const uint8_t* cursor) (cursor = DeserializePodVector(cursor, &callSites)) && (cursor = DeserializePodVector(cursor, &callThunks)) && (cursor = DeserializePodVector(cursor, &funcNames)) && + (cursor = DeserializePodVector(cursor, &customSections)) && (cursor = filename.deserialize(cursor)); return cursor; } @@ -527,6 +530,7 @@ Metadata::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const callSites.sizeOfExcludingThis(mallocSizeOf) + callThunks.sizeOfExcludingThis(mallocSizeOf) + funcNames.sizeOfExcludingThis(mallocSizeOf) + + customSections.sizeOfExcludingThis(mallocSizeOf) + filename.sizeOfExcludingThis(mallocSizeOf); } diff --git a/js/src/wasm/WasmCode.h b/js/src/wasm/WasmCode.h index c6f1ace02e..7bb27a6665 100644 --- a/js/src/wasm/WasmCode.h +++ b/js/src/wasm/WasmCode.h @@ -397,12 +397,32 @@ struct NameInBytecode uint32_t length; NameInBytecode() = default; - NameInBytecode(uint32_t offset, uint32_t length) : offset(offset), length(length) {} + NameInBytecode(uint32_t offset, uint32_t length) + : offset(offset), length(length) + {} }; typedef Vector NameInBytecodeVector; typedef Vector TwoByteName; +// CustomSection represents a custom section in the bytecode which can be +// extracted via Module.customSections. The (offset, length) pair does not +// include the custom section name. + +struct CustomSection +{ + NameInBytecode name; + uint32_t offset; + uint32_t length; + + CustomSection() = default; + CustomSection(NameInBytecode name, uint32_t offset, uint32_t length) + : name(name), offset(offset), length(length) + {} +}; + +typedef Vector CustomSectionVector; + // Metadata holds all the data that is needed to describe compiled wasm code // at runtime (as opposed to data that is only used to statically link or // instantiate a module). @@ -445,6 +465,7 @@ struct Metadata : ShareableBase, MetadataCacheablePod CallSiteVector callSites; CallThunkVector callThunks; NameInBytecodeVector funcNames; + CustomSectionVector customSections; CacheableChars filename; bool usesMemory() const { return UsesMemory(memoryUsage); } diff --git a/js/src/wasm/WasmCompile.cpp b/js/src/wasm/WasmCompile.cpp index b8c113049f..f7ca94b18c 100644 --- a/js/src/wasm/WasmCompile.cpp +++ b/js/src/wasm/WasmCompile.cpp @@ -64,11 +64,11 @@ DecodeFunctionBody(Decoder& d, ModuleGenerator& mg, uint32_t funcIndex) static bool DecodeCodeSection(Decoder& d, ModuleGenerator& mg) { - if (!mg.startFuncDefs()) + uint32_t sectionStart, sectionSize; + if (!d.startSection(SectionId::Code, &mg.mutableEnv(), §ionStart, §ionSize, "code")) return false; - uint32_t sectionStart, sectionSize; - if (!d.startSection(SectionId::Code, §ionStart, §ionSize, "code")) + if (!mg.startFuncDefs()) return false; if (sectionStart == Decoder::NotStarted) { @@ -96,71 +96,6 @@ DecodeCodeSection(Decoder& d, ModuleGenerator& mg) return mg.finishFuncDefs(); } -static void -MaybeDecodeNameSectionBody(Decoder& d, NameInBytecodeVector* pfuncNames) -{ - // For simplicity, ignore all failures, even OOM. Failure will simply result - // in the names section not being included for this module. - - uint32_t numFuncNames; - if (!d.readVarU32(&numFuncNames)) - return; - - if (numFuncNames > MaxFuncs) - return; - - // Use a local vector (and not pfuncNames) since it could result in a - // partially initialized result in case of failure in the middle. - NameInBytecodeVector funcNames; - if (!funcNames.resize(numFuncNames)) - return; - - for (uint32_t i = 0; i < numFuncNames; i++) { - uint32_t numBytes; - if (!d.readVarU32(&numBytes)) - return; - - NameInBytecode name; - name.offset = d.currentOffset(); - name.length = numBytes; - funcNames[i] = name; - - if (!d.readBytes(numBytes)) - return; - - // Skip local names for a function. - uint32_t numLocals; - if (!d.readVarU32(&numLocals)) - return; - for (uint32_t j = 0; j < numLocals; j++) { - uint32_t numBytes; - if (!d.readVarU32(&numBytes)) - return; - if (!d.readBytes(numBytes)) - return; - } - } - - *pfuncNames = Move(funcNames); -} - -static bool -DecodeNameSection(Decoder& d, NameInBytecodeVector* funcNames) -{ - uint32_t sectionStart, sectionSize; - if (!d.startUserDefinedSection(NameSectionName, §ionStart, §ionSize)) - return false; - if (sectionStart == Decoder::NotStarted) - return true; - - // Once started, user-defined sections do not report validation errors. - - MaybeDecodeNameSectionBody(d, funcNames); - - d.finishUserDefinedSection(sectionStart, sectionSize); - return true; -} - bool CompileArgs::initFromContext(ExclusiveContext* cx, ScriptedCaller&& scriptedCaller) { @@ -190,20 +125,10 @@ wasm::Compile(const ShareableBytes& bytecode, const CompileArgs& args, UniqueCha if (!DecodeCodeSection(d, mg)) return nullptr; - DataSegmentVector dataSegments; - if (!DecodeDataSection(d, mg.env(), &dataSegments)) - return nullptr; - - NameInBytecodeVector funcNames; - if (!DecodeNameSection(d, &funcNames)) - return nullptr; - - if (!DecodeUnknownSections(d)) + if (!DecodeModuleTail(d, &mg.mutableEnv())) return nullptr; MOZ_ASSERT(!*error, "unreported error in decoding"); - return mg.finish(bytecode, - Move(dataSegments), - Move(funcNames)); + return mg.finish(bytecode); } diff --git a/js/src/wasm/WasmGenerator.cpp b/js/src/wasm/WasmGenerator.cpp index 3e395738c3..2a8dd01e8e 100644 --- a/js/src/wasm/WasmGenerator.cpp +++ b/js/src/wasm/WasmGenerator.cpp @@ -218,6 +218,15 @@ ModuleGenerator::init(UniqueModuleEnvironment env, const CompileArgs& args, return true; } +ModuleEnvironment& +ModuleGenerator::mutableEnv() +{ + // Mutation is not safe during parallel compilation. + MOZ_ASSERT(!startedFuncDefs_ || finishedFuncDefs_); + return *env_; +} + + bool ModuleGenerator::finishOutstandingTask() { @@ -1020,8 +1029,7 @@ ModuleGenerator::initSigTableElems(uint32_t sigIndex, Uint32Vector&& elemFuncInd } SharedModule -ModuleGenerator::finish(const ShareableBytes& bytecode, DataSegmentVector&& dataSegments, - NameInBytecodeVector&& funcNames) +ModuleGenerator::finish(const ShareableBytes& bytecode) { MOZ_ASSERT(!activeFuncDef_); MOZ_ASSERT(finishedFuncDefs_); @@ -1058,8 +1066,6 @@ ModuleGenerator::finish(const ShareableBytes& bytecode, DataSegmentVector&& data if (!metadata_->callSites.appendAll(masm_.callSites())) return nullptr; - metadata_->funcNames = Move(funcNames); - // The MacroAssembler has accumulated all the memory accesses during codegen. metadata_->memoryAccesses = masm_.extractMemoryAccesses(); metadata_->memoryPatches = masm_.extractMemoryPatches(); @@ -1071,6 +1077,8 @@ ModuleGenerator::finish(const ShareableBytes& bytecode, DataSegmentVector&& data metadata_->maxMemoryLength = env_->maxMemoryLength; metadata_->tables = Move(env_->tables); metadata_->globals = Move(env_->globals); + metadata_->funcNames = Move(env_->funcNames); + metadata_->customSections = Move(env_->customSections); // These Vectors can get large and the excess capacity can be significant, // so realloc them down to size. @@ -1103,7 +1111,7 @@ ModuleGenerator::finish(const ShareableBytes& bytecode, DataSegmentVector&& data Move(linkData_), Move(env_->imports), Move(env_->exports), - Move(dataSegments), + Move(env_->dataSegments), Move(env_->elemSegments), *metadata_, bytecode)); diff --git a/js/src/wasm/WasmGenerator.h b/js/src/wasm/WasmGenerator.h index f20ee70cda..25b7cd7d3b 100644 --- a/js/src/wasm/WasmGenerator.h +++ b/js/src/wasm/WasmGenerator.h @@ -265,6 +265,7 @@ private: Metadata* maybeAsmJSMetadata = nullptr); const ModuleEnvironment& env() const { return *env_; } + ModuleEnvironment& mutableEnv(); bool isAsmJS() const { return metadata_->kind == ModuleKind::AsmJS; } jit::MacroAssembler& masm() { return masm_; } @@ -303,11 +304,8 @@ private: [[nodiscard]] bool addGlobal(ValType type, bool isConst, uint32_t* index); [[nodiscard]] bool addExport(CacheableChars&& fieldChars, uint32_t funcIndex); - // Finish compilation, provided the list of imports and source bytecode. - // Both these Vectors may be empty (viz., b/c asm.js does different things - // for imports and source). - SharedModule finish(const ShareableBytes& bytecode, DataSegmentVector&& dataSegments, - NameInBytecodeVector&& funcNames); + // Finish compilation of the given bytecode. + SharedModule finish(const ShareableBytes& bytecode); }; // A FunctionGenerator encapsulates the generation of a single function body. diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index 6085295bd7..5691b27205 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -20,6 +20,7 @@ #include "mozilla/CheckedInt.h" #include "mozilla/Maybe.h" +#include "mozilla/RangedPtr.h" #include "jsprf.h" @@ -44,6 +45,7 @@ using mozilla::CheckedInt; using mozilla::IsNaN; using mozilla::IsSame; using mozilla::Nothing; +using mozilla::RangedPtr; bool wasm::HasCompilerSupport(ExclusiveContext* cx) @@ -503,6 +505,7 @@ const JSFunctionSpec WasmModuleObject::static_methods[] = { JS_FN("imports", WasmModuleObject::imports, 1, 0), JS_FN("exports", WasmModuleObject::exports, 1, 0), + JS_FN("customSections", WasmModuleObject::customSections, 2, 0), JS_FS_END }; @@ -689,6 +692,58 @@ WasmModuleObject::exports(JSContext* cx, unsigned argc, Value* vp) return true; } +/* static */ bool +WasmModuleObject::customSections(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + + Module* module; + if (!GetModuleArg(cx, args, "WebAssembly.Module.customSections", &module)) + return false; + + Vector name(cx); + { + RootedString str(cx, ToString(cx, args.get(1))); + if (!str) + return false; + + Rooted flat(cx, str->ensureFlat(cx)); + if (!flat) + return false; + + if (!name.initLengthUninitialized(JS::GetDeflatedUTF8StringLength(flat))) + return false; + + JS::DeflateStringToUTF8Buffer(flat, RangedPtr(name.begin(), name.length())); + } + + const uint8_t* bytecode = module->bytecode().begin(); + + AutoValueVector elems(cx); + RootedArrayBufferObject buf(cx); + for (const CustomSection& sec : module->metadata().customSections) { + if (name.length() != sec.name.length) + continue; + if (memcmp(name.begin(), bytecode + sec.name.offset, name.length())) + continue; + + buf = ArrayBufferObject::create(cx, sec.length); + if (!buf) + return false; + + memcpy(buf->dataPointer(), bytecode + sec.offset, sec.length); + if (!elems.append(ObjectValue(*buf))) + return false; + } + + JSObject* arr = NewDenseCopiedArray(cx, elems.length(), elems.begin()); + if (!arr) + return false; + + args.rval().setObject(*arr); + return true; +} + /* static */ WasmModuleObject* WasmModuleObject::create(ExclusiveContext* cx, Module& module, HandleObject proto) { diff --git a/js/src/wasm/WasmJS.h b/js/src/wasm/WasmJS.h index ee1332102f..df552a5f64 100644 --- a/js/src/wasm/WasmJS.h +++ b/js/src/wasm/WasmJS.h @@ -123,6 +123,7 @@ class WasmModuleObject : public NativeObject static void finalize(FreeOp* fop, JSObject* obj); static bool imports(JSContext* cx, unsigned argc, Value* vp); static bool exports(JSContext* cx, unsigned argc, Value* vp); + static bool customSections(JSContext* cx, unsigned argc, Value* vp); public: static const unsigned RESERVED_SLOTS = 1; diff --git a/js/src/wasm/WasmModule.h b/js/src/wasm/WasmModule.h index f36d11d124..bfda398f0d 100644 --- a/js/src/wasm/WasmModule.h +++ b/js/src/wasm/WasmModule.h @@ -136,6 +136,7 @@ class Module : public JS::WasmModule const Metadata& metadata() const { return *metadata_; } const ImportVector& imports() const { return imports_; } const ExportVector& exports() const { return exports_; } + const Bytes& bytecode() const { return bytecode_->bytes; } // Instantiate this module with the given imports: diff --git a/js/src/wasm/WasmValidate.cpp b/js/src/wasm/WasmValidate.cpp index 307af8ab6f..b7da44cd80 100644 --- a/js/src/wasm/WasmValidate.cpp +++ b/js/src/wasm/WasmValidate.cpp @@ -57,6 +57,149 @@ Decoder::fail(UniqueChars msg) return false; } +bool +Decoder::startSection(SectionId id, ModuleEnvironment* env, uint32_t* sectionStart, + uint32_t* sectionSize, const char* sectionName) +{ + // Record state at beginning of section to allow rewinding to this point + // if, after skipping through several custom sections, we don't find the + // section 'id'. + const uint8_t* const initialCur = cur_; + const size_t initialCustomSectionsLength = env->customSections.length(); + + // Maintain a pointer to the current section that gets updated as custom + // sections are skipped. + const uint8_t* currentSectionStart = cur_; + + // Only start a section with 'id', skipping any custom sections before it. + + uint32_t idValue; + if (!readVarU32(&idValue)) + goto rewind; + + while (idValue != uint32_t(id)) { + if (idValue != uint32_t(SectionId::Custom)) + goto rewind; + + // Rewind to the beginning of the current section since this is what + // skipCustomSection() assumes. + cur_ = currentSectionStart; + if (!skipCustomSection(env)) + return false; + + // Having successfully skipped a custom section, consider the next + // section. + currentSectionStart = cur_; + if (!readVarU32(&idValue)) + goto rewind; + } + + // Found it, now start the section. + + if (!readVarU32(sectionSize) || bytesRemain() < *sectionSize) + goto fail; + + *sectionStart = cur_ - beg_; + return true; + + rewind: + cur_ = initialCur; + env->customSections.shrinkTo(initialCustomSectionsLength); + *sectionStart = NotStarted; + return true; + + fail: + return fail("failed to start %s section", sectionName); +} + +bool +Decoder::finishSection(uint32_t sectionStart, uint32_t sectionSize, const char* sectionName) +{ + if (sectionSize != (cur_ - beg_) - sectionStart) + return fail("byte size mismatch in %s section", sectionName); + return true; +} + +bool +Decoder::startCustomSection(const char* expected, size_t expectedLength, ModuleEnvironment* env, + uint32_t* sectionStart, uint32_t* sectionSize) +{ + // Record state at beginning of section to allow rewinding to this point + // if, after skipping through several custom sections, we don't find the + // section 'id'. + const uint8_t* const initialCur = cur_; + const size_t initialCustomSectionsLength = env->customSections.length(); + + while (true) { + // Try to start a custom section. If we can't, rewind to the beginning + // since we may have skipped several custom sections already looking for + // 'expected'. + if (!startSection(SectionId::Custom, env, sectionStart, sectionSize, "custom")) + return false; + if (*sectionStart == NotStarted) + goto rewind; + + NameInBytecode name; + if (!readVarU32(&name.length) || name.length > bytesRemain()) + goto fail; + + name.offset = currentOffset(); + uint32_t payloadOffset = name.offset + name.length; + uint32_t payloadEnd = *sectionStart + *sectionSize; + if (payloadOffset > payloadEnd) + goto fail; + + // Now that we have a valid custom section, record its offsets in the + // metadata which can be queried by the user via Module.customSections. + // Note: after an entry is appended, it may be popped if this loop or + // the loop in startSection needs to rewind. + if (!env->customSections.emplaceBack(name, payloadOffset, payloadEnd - payloadOffset)) + return false; + + // 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(*sectionStart, *sectionSize); + } + MOZ_CRASH("unreachable"); + + rewind: + cur_ = initialCur; + env->customSections.shrinkTo(initialCustomSectionsLength); + return true; + + fail: + return fail("failed to start custom section"); +} + +void +Decoder::finishCustomSection(uint32_t sectionStart, uint32_t sectionSize) +{ + MOZ_ASSERT(cur_ >= beg_); + MOZ_ASSERT(cur_ <= end_); + cur_ = (beg_ + sectionStart) + sectionSize; + MOZ_ASSERT(cur_ <= end_); + clearError(); +} + +bool +Decoder::skipCustomSection(ModuleEnvironment* env) +{ + uint32_t sectionStart, sectionSize; + if (!startCustomSection(nullptr, 0, env, §ionStart, §ionSize)) + return false; + if (sectionStart == NotStarted) + return fail("expected custom section"); + + finishCustomSection(sectionStart, sectionSize); + return true; +} + + // Misc helpers. bool @@ -558,10 +701,10 @@ DecodePreamble(Decoder& d) } static bool -DecodeTypeSection(Decoder& d, SigWithIdVector* sigs) +DecodeTypeSection(Decoder& d, ModuleEnvironment* env) { uint32_t sectionStart, sectionSize; - if (!d.startSection(SectionId::Type, §ionStart, §ionSize, "type")) + if (!d.startSection(SectionId::Type, env, §ionStart, §ionSize, "type")) return false; if (sectionStart == Decoder::NotStarted) return true; @@ -573,7 +716,7 @@ DecodeTypeSection(Decoder& d, SigWithIdVector* sigs) if (numSigs > MaxSigs) return d.fail("too many signatures"); - if (!sigs->resize(numSigs)) + if (!env->sigs.resize(numSigs)) return false; for (uint32_t sigIndex = 0; sigIndex < numSigs; sigIndex++) { @@ -614,7 +757,7 @@ DecodeTypeSection(Decoder& d, SigWithIdVector* sigs) result = ToExprType(type); } - (*sigs)[sigIndex] = Sig(Move(args), result); + env->sigs[sigIndex] = Sig(Move(args), result); } if (!d.finishSection(sectionStart, sectionSize, "type")) @@ -839,7 +982,7 @@ static bool DecodeImportSection(Decoder& d, ModuleEnvironment* env) { uint32_t sectionStart, sectionSize; - if (!d.startSection(SectionId::Import, §ionStart, §ionSize, "import")) + if (!d.startSection(SectionId::Import, env, §ionStart, §ionSize, "import")) return false; if (sectionStart == Decoder::NotStarted) return true; @@ -870,7 +1013,7 @@ static bool DecodeFunctionSection(Decoder& d, ModuleEnvironment* env) { uint32_t sectionStart, sectionSize; - if (!d.startSection(SectionId::Function, §ionStart, §ionSize, "function")) + if (!d.startSection(SectionId::Function, env, §ionStart, §ionSize, "function")) return false; if (sectionStart == Decoder::NotStarted) return true; @@ -901,10 +1044,10 @@ DecodeFunctionSection(Decoder& d, ModuleEnvironment* env) } static bool -DecodeTableSection(Decoder& d, TableDescVector* tables) +DecodeTableSection(Decoder& d, ModuleEnvironment* env) { uint32_t sectionStart, sectionSize; - if (!d.startSection(SectionId::Table, §ionStart, §ionSize, "table")) + if (!d.startSection(SectionId::Table, env, §ionStart, §ionSize, "table")) return false; if (sectionStart == Decoder::NotStarted) return true; @@ -916,7 +1059,7 @@ DecodeTableSection(Decoder& d, TableDescVector* tables) if (numTables != 1) return d.fail("the number of tables must be exactly one"); - if (!DecodeTableLimits(d, tables)) + if (!DecodeTableLimits(d, &env->tables)) return false; if (!d.finishSection(sectionStart, sectionSize, "table")) @@ -929,7 +1072,7 @@ static bool DecodeMemorySection(Decoder& d, ModuleEnvironment* env) { uint32_t sectionStart, sectionSize; - if (!d.startSection(SectionId::Memory, §ionStart, §ionSize, "memory")) + if (!d.startSection(SectionId::Memory, env, §ionStart, §ionSize, "memory")) return false; if (sectionStart == Decoder::NotStarted) return true; @@ -1014,10 +1157,10 @@ DecodeInitializerExpression(Decoder& d, const GlobalDescVector& globals, ValType } static bool -DecodeGlobalSection(Decoder& d, GlobalDescVector* globals) +DecodeGlobalSection(Decoder& d, ModuleEnvironment* env) { uint32_t sectionStart, sectionSize; - if (!d.startSection(SectionId::Global, §ionStart, §ionSize, "global")) + if (!d.startSection(SectionId::Global, env, §ionStart, §ionSize, "global")) return false; if (sectionStart == Decoder::NotStarted) return true; @@ -1026,12 +1169,12 @@ DecodeGlobalSection(Decoder& d, GlobalDescVector* globals) if (!d.readVarU32(&numDefs)) return d.fail("expected number of globals"); - CheckedInt numGlobals = globals->length(); + CheckedInt numGlobals = env->globals.length(); numGlobals += numDefs; if (!numGlobals.isValid() || numGlobals.value() > MaxGlobals) return d.fail("too many globals"); - if (!globals->reserve(numGlobals.value())) + if (!env->globals.reserve(numGlobals.value())) return false; for (uint32_t i = 0; i < numDefs; i++) { @@ -1041,10 +1184,10 @@ DecodeGlobalSection(Decoder& d, GlobalDescVector* globals) return false; InitExpr initializer; - if (!DecodeInitializerExpression(d, *globals, type, &initializer)) + if (!DecodeInitializerExpression(d, env->globals, type, &initializer)) return false; - globals->infallibleAppend(GlobalDesc(initializer, isMutable)); + env->globals.infallibleAppend(GlobalDesc(initializer, isMutable)); } if (!d.finishSection(sectionStart, sectionSize, "global")) @@ -1146,7 +1289,7 @@ static bool DecodeExportSection(Decoder& d, ModuleEnvironment* env) { uint32_t sectionStart, sectionSize; - if (!d.startSection(SectionId::Export, §ionStart, §ionSize, "export")) + if (!d.startSection(SectionId::Export, env, §ionStart, §ionSize, "export")) return false; if (sectionStart == Decoder::NotStarted) return true; @@ -1177,7 +1320,7 @@ static bool DecodeStartSection(Decoder& d, ModuleEnvironment* env) { uint32_t sectionStart, sectionSize; - if (!d.startSection(SectionId::Start, §ionStart, §ionSize, "start")) + if (!d.startSection(SectionId::Start, env, §ionStart, §ionSize, "start")) return false; if (sectionStart == Decoder::NotStarted) return true; @@ -1208,7 +1351,7 @@ static bool DecodeElemSection(Decoder& d, ModuleEnvironment* env) { uint32_t sectionStart, sectionSize; - if (!d.startSection(SectionId::Elem, §ionStart, §ionSize, "elem")) + if (!d.startSection(SectionId::Elem, env, §ionStart, §ionSize, "elem")) return false; if (sectionStart == Decoder::NotStarted) return true; @@ -1266,7 +1409,7 @@ wasm::DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env) if (!DecodePreamble(d)) return false; - if (!DecodeTypeSection(d, &env->sigs)) + if (!DecodeTypeSection(d, env)) return false; if (!DecodeImportSection(d, env)) @@ -1275,13 +1418,13 @@ wasm::DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env) if (!DecodeFunctionSection(d, env)) return false; - if (!DecodeTableSection(d, &env->tables)) + if (!DecodeTableSection(d, env)) return false; if (!DecodeMemorySection(d, env)) return false; - if (!DecodeGlobalSection(d, &env->globals)) + if (!DecodeGlobalSection(d, env)) return false; if (!DecodeExportSection(d, env)) @@ -1318,14 +1461,14 @@ DecodeFunctionBody(Decoder& d, const ModuleEnvironment& env, uint32_t funcIndex) } static bool -DecodeCodeSection(Decoder& d, const ModuleEnvironment& env) +DecodeCodeSection(Decoder& d, ModuleEnvironment* env) { uint32_t sectionStart, sectionSize; - if (!d.startSection(SectionId::Code, §ionStart, §ionSize, "code")) + if (!d.startSection(SectionId::Code, env, §ionStart, §ionSize, "code")) return false; if (sectionStart == Decoder::NotStarted) { - if (env.numFuncDefs() != 0) + if (env->numFuncDefs() != 0) return d.fail("expected function bodies"); return true; } @@ -1334,11 +1477,11 @@ DecodeCodeSection(Decoder& d, const ModuleEnvironment& env) if (!d.readVarU32(&numFuncDefs)) return d.fail("expected function body count"); - if (numFuncDefs != env.numFuncDefs()) + if (numFuncDefs != env->numFuncDefs()) return d.fail("function body count does not match function signature count"); for (uint32_t funcDefIndex = 0; funcDefIndex < numFuncDefs; funcDefIndex++) { - if (!DecodeFunctionBody(d, env, env.numFuncImports() + funcDefIndex)) + if (!DecodeFunctionBody(d, *env, env->numFuncImports() + funcDefIndex)) return false; } @@ -1349,16 +1492,16 @@ DecodeCodeSection(Decoder& d, const ModuleEnvironment& env) } -bool -wasm::DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVector* segments) +static bool +DecodeDataSection(Decoder& d, ModuleEnvironment* env) { uint32_t sectionStart, sectionSize; - if (!d.startSection(SectionId::Data, §ionStart, §ionSize, "data")) + if (!d.startSection(SectionId::Data, env, §ionStart, §ionSize, "data")) return false; if (sectionStart == Decoder::NotStarted) return true; - if (!env.usesMemory()) + if (!env->usesMemory()) return d.fail("data section requires a memory section"); uint32_t numSegments; @@ -1377,7 +1520,7 @@ wasm::DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVec return d.fail("linear memory index must currently be 0"); DataSegment seg; - if (!DecodeInitializerExpression(d, env.globals, ValType::I32, &seg.offset)) + if (!DecodeInitializerExpression(d, env->globals, ValType::I32, &seg.offset)) return false; if (!d.readVarU32(&seg.length)) @@ -1388,7 +1531,7 @@ wasm::DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVec if (!d.readBytes(seg.length)) return d.fail("data segment shorter than declared"); - if (!segments->append(seg)) + if (!env->dataSegments.append(seg)) return false; } @@ -1398,11 +1541,82 @@ wasm::DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVec return true; } -bool -wasm::DecodeUnknownSections(Decoder& d) +static void +MaybeDecodeNameSectionBody(Decoder& d, ModuleEnvironment* env) + { + // For simplicity, ignore all failures, even OOM. Failure will simply result + // in the names section not being included for this module. + + uint32_t numFuncNames; + if (!d.readVarU32(&numFuncNames)) + return; + + if (numFuncNames > MaxFuncs) + return; + + // Use a local vector (and not env->funcNames) since it could result in a + // partially initialized result in case of failure in the middle. + NameInBytecodeVector funcNames; + if (!funcNames.resize(numFuncNames)) + return; + + for (uint32_t i = 0; i < numFuncNames; i++) { + uint32_t numBytes; + if (!d.readVarU32(&numBytes)) + return; + + NameInBytecode name; + name.offset = d.currentOffset(); + name.length = numBytes; + funcNames[i] = name; + + if (!d.readBytes(numBytes)) + return; + + // Skip local names for a function. + uint32_t numLocals; + if (!d.readVarU32(&numLocals)) + return; + for (uint32_t j = 0; j < numLocals; j++) { + uint32_t numBytes; + if (!d.readVarU32(&numBytes)) + return; + if (!d.readBytes(numBytes)) + return; + } + } + + env->funcNames = Move(funcNames); +} + +static bool +DecodeNameSection(Decoder& d, ModuleEnvironment* env) { + uint32_t sectionStart, sectionSize; + if (!d.startCustomSection(NameSectionName, env, §ionStart, §ionSize)) + return false; + if (sectionStart == Decoder::NotStarted) + return true; + + // Once started, custom sections do not report validation errors. + + MaybeDecodeNameSectionBody(d, env); + + d.finishCustomSection(sectionStart, sectionSize); + return true; +} + +bool +wasm::DecodeModuleTail(Decoder& d, ModuleEnvironment* env) +{ + if (!DecodeDataSection(d, env)) + return false; + + if (!DecodeNameSection(d, env)) + return false; + while (!d.done()) { - if (!d.skipUserDefinedSection()) + if (!d.skipCustomSection(env)) return false; } @@ -1421,15 +1635,13 @@ wasm::Validate(const ShareableBytes& bytecode, UniqueChars* error) if (!DecodeModuleEnvironment(d, &env)) return false; - if (!DecodeCodeSection(d, env)) + if (!DecodeCodeSection(d, &env)) return false; - DataSegmentVector dataSegments; - if (!DecodeDataSection(d, env, &dataSegments)) + if (!DecodeModuleTail(d, &env)) return false; - if (!DecodeUnknownSections(d)) - return false; + MOZ_ASSERT(!*error, "unreported error in decoding"); return true; } \ No newline at end of file diff --git a/js/src/wasm/WasmValidate.h b/js/src/wasm/WasmValidate.h index 8138b87016..ca4cd72963 100644 --- a/js/src/wasm/WasmValidate.h +++ b/js/src/wasm/WasmValidate.h @@ -25,6 +25,83 @@ namespace js { namespace wasm { +// ModuleEnvironment contains all the state necessary to validate, process or +// render functions. It is created by decoding all the sections before the wasm +// code section and then used immutably during. When compiling a module using a +// ModuleGenerator, the ModuleEnvironment holds state shared between the +// ModuleGenerator thread and background compile threads. All the threads +// are given a read-only view of the ModuleEnvironment, thus preventing race +// conditions. +struct ModuleEnvironment +{ + ModuleKind kind; + MemoryUsage memoryUsage; + mozilla::Atomic minMemoryLength; + Maybe maxMemoryLength; + + SigWithIdVector sigs; + SigWithIdPtrVector funcSigs; + Uint32Vector funcImportGlobalDataOffsets; + GlobalDescVector globals; + TableDescVector tables; + Uint32Vector asmJSSigToTableIndex; + ImportVector imports; + ExportVector exports; + Maybe startFuncIndex; + ElemSegmentVector elemSegments; + DataSegmentVector dataSegments; + NameInBytecodeVector funcNames; + CustomSectionVector customSections; + + explicit ModuleEnvironment(ModuleKind kind = ModuleKind::Wasm) + : kind(kind), + memoryUsage(MemoryUsage::None), + minMemoryLength(0) + {} + + size_t numTables() const { + return tables.length(); + } + size_t numSigs() const { + return sigs.length(); + } + + size_t numFuncs() const { + // asm.js pre-reserves a bunch of function index space which is + // incrementally filled in during function-body validation. Thus, there + // are a few possible interpretations of numFuncs() (total index space + // size vs. exact number of imports/definitions encountered so far) and + // to simplify things we simply only define this quantity for wasm. + MOZ_ASSERT(!isAsmJS()); + return funcSigs.length(); + } + size_t numFuncDefs() const { + // asm.js overallocates the length of funcSigs and in general does not + // know the number of function definitions until it's done compiling. + MOZ_ASSERT(!isAsmJS()); + return funcSigs.length() - funcImportGlobalDataOffsets.length(); + } + size_t numFuncImports() const { + MOZ_ASSERT(!isAsmJS()); + return funcImportGlobalDataOffsets.length(); + } + bool usesMemory() const { + return UsesMemory(memoryUsage); + } + + bool isAsmJS() const { + return kind == ModuleKind::AsmJS; + } + bool funcIsImport(uint32_t funcIndex) const { + return funcIndex < funcImportGlobalDataOffsets.length(); + } + uint32_t funcIndexToSigIndex(uint32_t funcIndex) const { + return funcSigs[funcIndex] - sigs.begin(); + } +}; + +typedef UniquePtr UniqueModuleEnvironment; + // The Encoder class appends bytes to the Bytes object it is given during // construction. The client is responsible for the Bytes's lifetime and must // keep the Bytes alive as long as the Encoder is used. @@ -196,8 +273,7 @@ class Encoder // end while the size's varU32 must be stored at the beginning. Immediately // after the section length is the string id of the section. - MOZ_MUST_USE bool startSection(SectionId id, size_t* offset) { - MOZ_ASSERT(id != SectionId::UserDefined); // not supported yet + [[nodiscard]] bool startSection(SectionId id, size_t* offset) { return writeVarU32(uint32_t(id)) && writePatchableVarU32(offset); @@ -418,104 +494,34 @@ class Decoder static const uint32_t NotStarted = UINT32_MAX; - MOZ_MUST_USE bool startSection(SectionId id, - uint32_t* startOffset, - uint32_t* size, - const char* sectionName) - { - const uint8_t* const before = cur_; - const uint8_t* beforeId = before; - uint32_t idValue; - if (!readVarU32(&idValue)) - goto backup; - while (idValue != uint32_t(id)) { - if (idValue != uint32_t(SectionId::UserDefined)) - goto backup; - // Rewind to the section id since skipUserDefinedSection expects it. - cur_ = beforeId; - if (!skipUserDefinedSection()) - return false; - beforeId = cur_; - if (!readVarU32(&idValue)) - goto backup; - } - if (!readVarU32(size)) - goto fail; - if (bytesRemain() < *size) - goto fail; - *startOffset = cur_ - beg_; - return true; - backup: - cur_ = before; - *startOffset = NotStarted; - return true; - fail: - return fail("failed to start %s section", sectionName); - } - MOZ_MUST_USE bool finishSection(uint32_t startOffset, uint32_t size, - const char* sectionName) - { - if (size != (cur_ - beg_) - startOffset) - return fail("byte size mismatch in %s section", sectionName); - return true; - } + [[nodiscard]] bool startSection(SectionId id, + ModuleEnvironment* env, + uint32_t* sectionStart, + uint32_t* sectionSize, + const char* sectionName); + [[nodiscard]] bool finishSection(uint32_t sectionStart, + uint32_t sectionSize, + const char* sectionName); - // "User sections" do not cause validation errors unless the error is in - // the user-defined section header itself. + // Custom sections do not cause validation errors unless the error is in + // the section header itself. - MOZ_MUST_USE bool startUserDefinedSection(const char* expectedId, - size_t expectedIdSize, - uint32_t* sectionStart, - uint32_t* sectionSize) + [[nodiscard]] bool startCustomSection(const char* expected, + size_t expectedLength, + ModuleEnvironment* env, + uint32_t* sectionStart, + uint32_t* sectionSize); + template + [[nodiscard]] bool startCustomSection(const char (&name)[NameSizeWith0], + ModuleEnvironment* env, + uint32_t* sectionStart, + uint32_t* sectionSize) { - const uint8_t* const before = cur_; - while (true) { - if (!startSection(SectionId::UserDefined, sectionStart, sectionSize, "user-defined")) - return false; - if (*sectionStart == NotStarted) { - cur_ = before; - return true; - } - uint32_t idSize; - if (!readVarU32(&idSize)) - goto fail; - if (idSize > bytesRemain() || currentOffset() + idSize > *sectionStart + *sectionSize) - goto fail; - if (expectedId && (expectedIdSize != idSize || !!memcmp(cur_, expectedId, idSize))) { - finishUserDefinedSection(*sectionStart, *sectionSize); - continue; - } - cur_ += idSize; - return true; - } - MOZ_CRASH("unreachable"); - fail: - return fail("failed to start user-defined section"); - } - template - MOZ_MUST_USE bool startUserDefinedSection(const char (&id)[IdSizeWith0], - uint32_t* sectionStart, - uint32_t* sectionSize) - { - MOZ_ASSERT(id[IdSizeWith0 - 1] == '\0'); - return startUserDefinedSection(id, IdSizeWith0 - 1, sectionStart, sectionSize); - } - void finishUserDefinedSection(uint32_t sectionStart, uint32_t sectionSize) { - MOZ_ASSERT(cur_ >= beg_); - MOZ_ASSERT(cur_ <= end_); - cur_ = (beg_ + sectionStart) + sectionSize; - MOZ_ASSERT(cur_ <= end_); - clearError(); - } - MOZ_MUST_USE bool skipUserDefinedSection() { - uint32_t sectionStart, sectionSize; - if (!startUserDefinedSection(nullptr, 0, §ionStart, §ionSize)) - return false; - if (sectionStart == NotStarted) - return fail("expected user-defined section"); - finishUserDefinedSection(sectionStart, sectionSize); - return true; + MOZ_ASSERT(name[NameSizeWith0 - 1] == '\0'); + return startCustomSection(name, NameSizeWith0 - 1, env, sectionStart, sectionSize); } + void finishCustomSection(uint32_t sectionStart, uint32_t sectionSize); + [[nodiscard]] bool skipCustomSection(ModuleEnvironment* env); // The infallible "unchecked" decoding functions can be used when we are // sure that the bytes are well-formed (by construction or due to previous @@ -579,11 +585,8 @@ class Decoder } }; -// Reusable macro encoding/decoding functions reused by both the two -// encoders (AsmJS/WasmTextToBinary) and all the decoders -// (WasmCompile/WasmIonCompile/WasmBaselineCompile/WasmBinaryToText). - -// Misc helpers. +// The local entries are part of function bodies and thus serialized by both +// wasm and asm.js and decoded as part of both validation and compilation. [[nodiscard]] bool EncodeLocalEntries(Encoder& d, const ValTypeVector& locals); @@ -599,86 +602,25 @@ DecodeLocalEntries(Decoder& d, ModuleKind kind, ValTypeVector* locals); // are given a read-only view of the ModuleEnvironment, thus preventing race // conditions. -struct ModuleEnvironment -{ - ModuleKind kind; - MemoryUsage memoryUsage; - mozilla::Atomic minMemoryLength; - Maybe maxMemoryLength; - - SigWithIdVector sigs; - SigWithIdPtrVector funcSigs; - Uint32Vector funcImportGlobalDataOffsets; - GlobalDescVector globals; - TableDescVector tables; - Uint32Vector asmJSSigToTableIndex; - ImportVector imports; - ExportVector exports; - Maybe startFuncIndex; - ElemSegmentVector elemSegments; - - explicit ModuleEnvironment(ModuleKind kind = ModuleKind::Wasm) - : kind(kind), - memoryUsage(MemoryUsage::None), - minMemoryLength(0) - {} - - size_t numTables() const { - return tables.length(); - } - size_t numSigs() const { - return sigs.length(); - } - - size_t numFuncs() const { - // asm.js pre-reserves a bunch of function index space which is - // incrementally filled in during function-body validation. Thus, there - // are a few possible interpretations of numFuncs() (total index space - // size vs. exact number of imports/definitions encountered so far) and - // to simplify things we simply only define this quantity for wasm. - MOZ_ASSERT(!isAsmJS()); - return funcSigs.length(); - } - size_t numFuncDefs() const { - // asm.js overallocates the length of funcSigs and in general does not - // know the number of function definitions until it's done compiling. - MOZ_ASSERT(!isAsmJS()); - return funcSigs.length() - funcImportGlobalDataOffsets.length(); - } - size_t numFuncImports() const { - MOZ_ASSERT(!isAsmJS()); - return funcImportGlobalDataOffsets.length(); - } - bool usesMemory() const { - return UsesMemory(memoryUsage); - } - - bool isAsmJS() const { - return kind == ModuleKind::AsmJS; - } - bool funcIsImport(uint32_t funcIndex) const { - return funcIndex < funcImportGlobalDataOffsets.length(); - } - uint32_t funcIndexToSigIndex(uint32_t funcIndex) const { - return funcSigs[funcIndex] - sigs.begin(); - } -}; - -typedef UniquePtr UniqueModuleEnvironment; - -// Section macros. +// Calling DecodeModuleEnvironment decodes all sections up to the code section +// and performs full validation of all those sections. The client must then +// decode the code section itself, reusing ValidateFunctionBody if necessary, +// and finally call DecodeModuleTail to decode all remaining sections after the +// code section (again, performing full validation). [[nodiscard]] bool DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env); [[nodiscard]] bool -DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVector* segments); - -MOZ_MUST_USE bool -DecodeUnknownSections(Decoder& d); +ValidateFunctionBody(const ModuleEnvironment& env, uint32_t funcIndex, Decoder& d); [[nodiscard]] bool - ValidateFunctionBody(const ModuleEnvironment& env, uint32_t funcIndex, Decoder& d); +DecodeModuleTail(Decoder& d, ModuleEnvironment* env); + +// Validate an entire module, returning true if the module was validated +// successfully. If Validate returns false: +// - if *error is null, the caller should report out-of-memory +// - otherwise, there was a legitimate error described by *error [[nodiscard]] bool Validate(const ShareableBytes& bytecode, UniqueChars* error);