diff --git a/js/src/moz.build b/js/src/moz.build index baa4e1a19e..b829d4f863 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -349,7 +349,6 @@ main_deunified_sources = [ 'vm/Xdr.cpp', 'wasm/AsmJS.cpp', 'wasm/WasmBaselineCompile.cpp', - 'wasm/WasmBinaryFormat.cpp', 'wasm/WasmBinaryIterator.cpp', 'wasm/WasmBinaryToAST.cpp', 'wasm/WasmBinaryToExperimentalText.cpp', @@ -368,7 +367,8 @@ main_deunified_sources = [ 'wasm/WasmTable.cpp', 'wasm/WasmTextToBinary.cpp', 'wasm/WasmTextUtils.cpp', - 'wasm/WasmTypes.cpp' + 'wasm/WasmTypes.cpp', + 'wasm/WasmValidate.cpp' ] # jsarray.cpp and jsatom.cpp cannot be built in unified mode because diff --git a/js/src/wasm/AsmJS.cpp b/js/src/wasm/AsmJS.cpp index 195bf89be9..38568cfc02 100644 --- a/js/src/wasm/AsmJS.cpp +++ b/js/src/wasm/AsmJS.cpp @@ -38,12 +38,12 @@ #include "vm/StringBuffer.h" #include "vm/Time.h" #include "vm/TypedArrayObject.h" -#include "wasm/WasmBinaryFormat.h" #include "wasm/WasmCompile.h" #include "wasm/WasmGenerator.h" #include "wasm/WasmInstance.h" #include "wasm/WasmJS.h" #include "wasm/WasmSerialize.h" +#include "wasm/WasmValidate.h" #include "jsobjinlines.h" diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp index a431bf68de..6428046b35 100644 --- a/js/src/wasm/WasmBaselineCompile.cpp +++ b/js/src/wasm/WasmBaselineCompile.cpp @@ -117,10 +117,10 @@ # include "jit/x86-shared/Assembler-x86-shared.h" #endif -#include "wasm/WasmBinaryFormat.h" #include "wasm/WasmBinaryIterator.h" #include "wasm/WasmGenerator.h" #include "wasm/WasmSignalHandlers.h" +#include "wasm/WasmValidate.h" #include "jit/MacroAssembler-inl.h" diff --git a/js/src/wasm/WasmBinaryIterator.h b/js/src/wasm/WasmBinaryIterator.h index ccad80f9dd..79f62884de 100644 --- a/js/src/wasm/WasmBinaryIterator.h +++ b/js/src/wasm/WasmBinaryIterator.h @@ -24,7 +24,7 @@ #include "jsprf.h" #include "jit/AtomicOp.h" -#include "wasm/WasmBinaryFormat.h" +#include "wasm/WasmValidate.h" namespace js { namespace wasm { diff --git a/js/src/wasm/WasmBinaryToAST.cpp b/js/src/wasm/WasmBinaryToAST.cpp index 18c22d7cb9..d041cddf34 100644 --- a/js/src/wasm/WasmBinaryToAST.cpp +++ b/js/src/wasm/WasmBinaryToAST.cpp @@ -23,8 +23,8 @@ #include "jscntxt.h" -#include "wasm/WasmBinaryFormat.h" #include "wasm/WasmBinaryIterator.h" +#include "wasm/WasmValidate.h" using namespace js; using namespace js::wasm; diff --git a/js/src/wasm/WasmCompile.cpp b/js/src/wasm/WasmCompile.cpp index 42ae5418f3..51f27f17a8 100644 --- a/js/src/wasm/WasmCompile.cpp +++ b/js/src/wasm/WasmCompile.cpp @@ -17,408 +17,17 @@ #include "wasm/WasmCompile.h" -#include "mozilla/CheckedInt.h" - #include "jsprf.h" -#include "wasm/WasmBinaryFormat.h" #include "wasm/WasmBinaryIterator.h" #include "wasm/WasmGenerator.h" #include "wasm/WasmSignalHandlers.h" +#include "wasm/WasmValidate.h" using namespace js; using namespace js::jit; using namespace js::wasm; -using mozilla::CheckedInt; -using mozilla::IsNaN; - -namespace { - -struct ValidatingPolicy : OpIterPolicy -{ - // Validation is what we're all about here. - static const bool Validate = true; -}; - -typedef OpIter ValidatingOpIter; - -class FunctionDecoder -{ - const ModuleGenerator& mg_; - const ValTypeVector& locals_; - ValidatingOpIter iter_; - - public: - FunctionDecoder(const ModuleGenerator& mg, const ValTypeVector& locals, Decoder& d) - : mg_(mg), locals_(locals), iter_(d) - {} - const ModuleGenerator& mg() const { return mg_; } - ValidatingOpIter& iter() { return iter_; } - const ValTypeVector& locals() const { return locals_; } - - bool checkHasMemory() { - if (!mg().usesMemory()) - return iter().fail("can't touch memory without memory"); - return true; - } -}; - -} // end anonymous namespace - -static bool -DecodeCallArgs(FunctionDecoder& f, const Sig& sig) -{ - const ValTypeVector& args = sig.args(); - uint32_t numArgs = args.length(); - for (size_t i = 0; i < numArgs; ++i) { - ValType argType = args[i]; - if (!f.iter().readCallArg(argType, numArgs, i, nullptr)) - return false; - } - - return f.iter().readCallArgsEnd(numArgs); -} - -static bool -DecodeCallReturn(FunctionDecoder& f, const Sig& sig) -{ - return f.iter().readCallReturn(sig.ret()); -} - -static bool -DecodeCall(FunctionDecoder& f) -{ - uint32_t funcIndex; - if (!f.iter().readCall(&funcIndex)) - return false; - - if (funcIndex >= f.mg().numFuncs()) - return f.iter().fail("callee index out of range"); - - if (!f.iter().inReachableCode()) - return true; - - const Sig* sig = &f.mg().funcSig(funcIndex); - - return DecodeCallArgs(f, *sig) && - DecodeCallReturn(f, *sig); -} - -static bool -DecodeCallIndirect(FunctionDecoder& f) -{ - if (!f.mg().numTables()) - return f.iter().fail("can't call_indirect without a table"); - - uint32_t sigIndex; - if (!f.iter().readCallIndirect(&sigIndex, nullptr)) - return false; - - if (sigIndex >= f.mg().numSigs()) - return f.iter().fail("signature index out of range"); - - if (!f.iter().inReachableCode()) - return true; - - const Sig& sig = f.mg().sig(sigIndex); - if (!DecodeCallArgs(f, sig)) - return false; - - return DecodeCallReturn(f, sig); -} - -static bool -DecodeBrTable(FunctionDecoder& f) -{ - uint32_t tableLength; - ExprType type = ExprType::Limit; - if (!f.iter().readBrTable(&tableLength, &type, nullptr, nullptr)) - return false; - - uint32_t depth; - for (size_t i = 0, e = tableLength; i < e; ++i) { - if (!f.iter().readBrTableEntry(&type, nullptr, &depth)) - return false; - } - - // Read the default label. - return f.iter().readBrTableDefault(&type, nullptr, &depth); -} - -static bool -DecodeFunctionBodyExprs(FunctionDecoder& f) -{ -#define CHECK(c) if (!(c)) return false; break - - while (true) { - uint16_t op; - if (!f.iter().readOp(&op)) - return false; - - switch (op) { - case uint16_t(Op::End): - if (!f.iter().readEnd(nullptr, nullptr, nullptr)) - return false; - if (f.iter().controlStackEmpty()) - return true; - break; - case uint16_t(Op::Nop): - CHECK(f.iter().readNop()); - case uint16_t(Op::Drop): - CHECK(f.iter().readDrop()); - case uint16_t(Op::Call): - CHECK(DecodeCall(f)); - case uint16_t(Op::CallIndirect): - CHECK(DecodeCallIndirect(f)); - case uint16_t(Op::I32Const): - CHECK(f.iter().readI32Const(nullptr)); - case uint16_t(Op::I64Const): - CHECK(f.iter().readI64Const(nullptr)); - case uint16_t(Op::F32Const): - CHECK(f.iter().readF32Const(nullptr)); - case uint16_t(Op::F64Const): - CHECK(f.iter().readF64Const(nullptr)); - case uint16_t(Op::GetLocal): - CHECK(f.iter().readGetLocal(f.locals(), nullptr)); - case uint16_t(Op::SetLocal): - CHECK(f.iter().readSetLocal(f.locals(), nullptr, nullptr)); - case uint16_t(Op::TeeLocal): - CHECK(f.iter().readTeeLocal(f.locals(), nullptr, nullptr)); - case uint16_t(Op::GetGlobal): - CHECK(f.iter().readGetGlobal(f.mg().globals(), nullptr)); - case uint16_t(Op::SetGlobal): - CHECK(f.iter().readSetGlobal(f.mg().globals(), nullptr, nullptr)); - case uint16_t(Op::Select): - CHECK(f.iter().readSelect(nullptr, nullptr, nullptr, nullptr)); - case uint16_t(Op::Block): - CHECK(f.iter().readBlock()); - case uint16_t(Op::Loop): - CHECK(f.iter().readLoop()); - case uint16_t(Op::If): - CHECK(f.iter().readIf(nullptr)); - case uint16_t(Op::Else): - CHECK(f.iter().readElse(nullptr, nullptr)); - case uint16_t(Op::I32Clz): - case uint16_t(Op::I32Ctz): - case uint16_t(Op::I32Popcnt): - CHECK(f.iter().readUnary(ValType::I32, nullptr)); - case uint16_t(Op::I64Clz): - case uint16_t(Op::I64Ctz): - case uint16_t(Op::I64Popcnt): - CHECK(f.iter().readUnary(ValType::I64, nullptr)); - case uint16_t(Op::F32Abs): - case uint16_t(Op::F32Neg): - case uint16_t(Op::F32Ceil): - case uint16_t(Op::F32Floor): - case uint16_t(Op::F32Sqrt): - case uint16_t(Op::F32Trunc): - case uint16_t(Op::F32Nearest): - CHECK(f.iter().readUnary(ValType::F32, nullptr)); - case uint16_t(Op::F64Abs): - case uint16_t(Op::F64Neg): - case uint16_t(Op::F64Ceil): - case uint16_t(Op::F64Floor): - case uint16_t(Op::F64Sqrt): - case uint16_t(Op::F64Trunc): - case uint16_t(Op::F64Nearest): - CHECK(f.iter().readUnary(ValType::F64, nullptr)); - case uint16_t(Op::I32Add): - case uint16_t(Op::I32Sub): - case uint16_t(Op::I32Mul): - case uint16_t(Op::I32DivS): - case uint16_t(Op::I32DivU): - case uint16_t(Op::I32RemS): - case uint16_t(Op::I32RemU): - case uint16_t(Op::I32And): - case uint16_t(Op::I32Or): - case uint16_t(Op::I32Xor): - case uint16_t(Op::I32Shl): - case uint16_t(Op::I32ShrS): - case uint16_t(Op::I32ShrU): - case uint16_t(Op::I32Rotl): - case uint16_t(Op::I32Rotr): - CHECK(f.iter().readBinary(ValType::I32, nullptr, nullptr)); - case uint16_t(Op::I64Add): - case uint16_t(Op::I64Sub): - case uint16_t(Op::I64Mul): - case uint16_t(Op::I64DivS): - case uint16_t(Op::I64DivU): - case uint16_t(Op::I64RemS): - case uint16_t(Op::I64RemU): - case uint16_t(Op::I64And): - case uint16_t(Op::I64Or): - case uint16_t(Op::I64Xor): - case uint16_t(Op::I64Shl): - case uint16_t(Op::I64ShrS): - case uint16_t(Op::I64ShrU): - case uint16_t(Op::I64Rotl): - case uint16_t(Op::I64Rotr): - CHECK(f.iter().readBinary(ValType::I64, nullptr, nullptr)); - case uint16_t(Op::F32Add): - case uint16_t(Op::F32Sub): - case uint16_t(Op::F32Mul): - case uint16_t(Op::F32Div): - case uint16_t(Op::F32Min): - case uint16_t(Op::F32Max): - case uint16_t(Op::F32CopySign): - CHECK(f.iter().readBinary(ValType::F32, nullptr, nullptr)); - case uint16_t(Op::F64Add): - case uint16_t(Op::F64Sub): - case uint16_t(Op::F64Mul): - case uint16_t(Op::F64Div): - case uint16_t(Op::F64Min): - case uint16_t(Op::F64Max): - case uint16_t(Op::F64CopySign): - CHECK(f.iter().readBinary(ValType::F64, nullptr, nullptr)); - case uint16_t(Op::I32Eq): - case uint16_t(Op::I32Ne): - case uint16_t(Op::I32LtS): - case uint16_t(Op::I32LtU): - case uint16_t(Op::I32LeS): - case uint16_t(Op::I32LeU): - case uint16_t(Op::I32GtS): - case uint16_t(Op::I32GtU): - case uint16_t(Op::I32GeS): - case uint16_t(Op::I32GeU): - CHECK(f.iter().readComparison(ValType::I32, nullptr, nullptr)); - case uint16_t(Op::I64Eq): - case uint16_t(Op::I64Ne): - case uint16_t(Op::I64LtS): - case uint16_t(Op::I64LtU): - case uint16_t(Op::I64LeS): - case uint16_t(Op::I64LeU): - case uint16_t(Op::I64GtS): - case uint16_t(Op::I64GtU): - case uint16_t(Op::I64GeS): - case uint16_t(Op::I64GeU): - CHECK(f.iter().readComparison(ValType::I64, nullptr, nullptr)); - case uint16_t(Op::F32Eq): - case uint16_t(Op::F32Ne): - case uint16_t(Op::F32Lt): - case uint16_t(Op::F32Le): - case uint16_t(Op::F32Gt): - case uint16_t(Op::F32Ge): - CHECK(f.iter().readComparison(ValType::F32, nullptr, nullptr)); - case uint16_t(Op::F64Eq): - case uint16_t(Op::F64Ne): - case uint16_t(Op::F64Lt): - case uint16_t(Op::F64Le): - case uint16_t(Op::F64Gt): - case uint16_t(Op::F64Ge): - CHECK(f.iter().readComparison(ValType::F64, nullptr, nullptr)); - case uint16_t(Op::I32Eqz): - CHECK(f.iter().readConversion(ValType::I32, ValType::I32, nullptr)); - case uint16_t(Op::I64Eqz): - case uint16_t(Op::I32WrapI64): - CHECK(f.iter().readConversion(ValType::I64, ValType::I32, nullptr)); - case uint16_t(Op::I32TruncSF32): - case uint16_t(Op::I32TruncUF32): - case uint16_t(Op::I32ReinterpretF32): - CHECK(f.iter().readConversion(ValType::F32, ValType::I32, nullptr)); - case uint16_t(Op::I32TruncSF64): - case uint16_t(Op::I32TruncUF64): - CHECK(f.iter().readConversion(ValType::F64, ValType::I32, nullptr)); - case uint16_t(Op::I64ExtendSI32): - case uint16_t(Op::I64ExtendUI32): - CHECK(f.iter().readConversion(ValType::I32, ValType::I64, nullptr)); - case uint16_t(Op::I64TruncSF32): - case uint16_t(Op::I64TruncUF32): - CHECK(f.iter().readConversion(ValType::F32, ValType::I64, nullptr)); - case uint16_t(Op::I64TruncSF64): - case uint16_t(Op::I64TruncUF64): - case uint16_t(Op::I64ReinterpretF64): - CHECK(f.iter().readConversion(ValType::F64, ValType::I64, nullptr)); - case uint16_t(Op::F32ConvertSI32): - case uint16_t(Op::F32ConvertUI32): - case uint16_t(Op::F32ReinterpretI32): - CHECK(f.iter().readConversion(ValType::I32, ValType::F32, nullptr)); - case uint16_t(Op::F32ConvertSI64): - case uint16_t(Op::F32ConvertUI64): - CHECK(f.iter().readConversion(ValType::I64, ValType::F32, nullptr)); - case uint16_t(Op::F32DemoteF64): - CHECK(f.iter().readConversion(ValType::F64, ValType::F32, nullptr)); - case uint16_t(Op::F64ConvertSI32): - case uint16_t(Op::F64ConvertUI32): - CHECK(f.iter().readConversion(ValType::I32, ValType::F64, nullptr)); - case uint16_t(Op::F64ConvertSI64): - case uint16_t(Op::F64ConvertUI64): - case uint16_t(Op::F64ReinterpretI64): - CHECK(f.iter().readConversion(ValType::I64, ValType::F64, nullptr)); - case uint16_t(Op::F64PromoteF32): - CHECK(f.iter().readConversion(ValType::F32, ValType::F64, nullptr)); - case uint16_t(Op::I32Extend8S): - case uint16_t(Op::I32Extend16S): - CHECK(f.iter().readConversion(ValType::I32, ValType::I32, nullptr)); - case uint16_t(Op::I64Extend8S): - case uint16_t(Op::I64Extend16S): - case uint16_t(Op::I64Extend32S): - CHECK(f.iter().readConversion(ValType::I64, ValType::I64, nullptr)); - case uint16_t(Op::I32Load8S): - case uint16_t(Op::I32Load8U): - CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 1, nullptr)); - case uint16_t(Op::I32Load16S): - case uint16_t(Op::I32Load16U): - CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 2, nullptr)); - case uint16_t(Op::I32Load): - CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 4, nullptr)); - case uint16_t(Op::I64Load8S): - case uint16_t(Op::I64Load8U): - CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 1, nullptr)); - case uint16_t(Op::I64Load16S): - case uint16_t(Op::I64Load16U): - CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 2, nullptr)); - case uint16_t(Op::I64Load32S): - case uint16_t(Op::I64Load32U): - CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 4, nullptr)); - case uint16_t(Op::I64Load): - CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 8, nullptr)); - case uint16_t(Op::F32Load): - CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::F32, 4, nullptr)); - case uint16_t(Op::F64Load): - CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::F64, 8, nullptr)); - case uint16_t(Op::I32Store8): - CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 1, nullptr, nullptr)); - case uint16_t(Op::I32Store16): - CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 2, nullptr, nullptr)); - case uint16_t(Op::I32Store): - CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 4, nullptr, nullptr)); - case uint16_t(Op::I64Store8): - CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 1, nullptr, nullptr)); - case uint16_t(Op::I64Store16): - CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 2, nullptr, nullptr)); - case uint16_t(Op::I64Store32): - CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 4, nullptr, nullptr)); - case uint16_t(Op::I64Store): - CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 8, nullptr, nullptr)); - case uint16_t(Op::F32Store): - CHECK(f.checkHasMemory() && f.iter().readStore(ValType::F32, 4, nullptr, nullptr)); - case uint16_t(Op::F64Store): - CHECK(f.checkHasMemory() && f.iter().readStore(ValType::F64, 8, nullptr, nullptr)); - case uint16_t(Op::GrowMemory): - CHECK(f.checkHasMemory() && f.iter().readGrowMemory(nullptr)); - case uint16_t(Op::CurrentMemory): - CHECK(f.checkHasMemory() && f.iter().readCurrentMemory()); - case uint16_t(Op::Br): - CHECK(f.iter().readBr(nullptr, nullptr, nullptr)); - case uint16_t(Op::BrIf): - CHECK(f.iter().readBrIf(nullptr, nullptr, nullptr, nullptr)); - case uint16_t(Op::BrTable): - CHECK(DecodeBrTable(f)); - case uint16_t(Op::Return): - CHECK(f.iter().readReturn(nullptr)); - case uint16_t(Op::Unreachable): - CHECK(f.iter().readUnreachable()); - default: - return f.iter().unrecognizedOpcode(op); - } - } - - MOZ_CRASH("unreachable"); - -#undef CHECK -} - static bool DecodeFunctionBody(Decoder& d, ModuleGenerator& mg, uint32_t funcIndex) { @@ -436,23 +45,7 @@ DecodeFunctionBody(Decoder& d, ModuleGenerator& mg, uint32_t funcIndex) if (!mg.startFuncDef(offsetInModule, &fg)) return false; - ValTypeVector locals; - const Sig& sig = mg.funcSig(funcIndex); - if (!locals.appendAll(sig.args())) - return false; - - if (!DecodeLocalEntries(d, ModuleKind::Wasm, &locals)) - return false; - - FunctionDecoder f(mg, locals, d); - - if (!f.iter().readFunctionStart(sig.ret())) - return false; - - if (!DecodeFunctionBodyExprs(f)) - return false; - - if (!f.iter().readFunctionEnd()) + if (!ValidateFunctionBody(mg.env(), funcIndex, d)) return false; if (d.currentPosition() != bodyBegin + bodySize) diff --git a/js/src/wasm/WasmGenerator.cpp b/js/src/wasm/WasmGenerator.cpp index 422cf3a652..ce05cc2aae 100644 --- a/js/src/wasm/WasmGenerator.cpp +++ b/js/src/wasm/WasmGenerator.cpp @@ -801,18 +801,6 @@ ModuleGenerator::numFuncDefs() const return env_->funcSigs.length() - numFuncImports(); } -uint32_t -ModuleGenerator::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 env_->funcSigs.length(); -} - const SigWithId& ModuleGenerator::funcSig(uint32_t funcIndex) const { @@ -978,7 +966,7 @@ ModuleGenerator::finishFuncDefs() MOZ_ASSERT(funcCodeRange(i).funcIndex() == i); } else { MOZ_ASSERT(numFinishedFuncDefs_ == numFuncDefs()); - for (uint32_t i = 0; i < numFuncs(); i++) + for (uint32_t i = 0; i < env_->numFuncs(); i++) MOZ_ASSERT(funcCodeRange(i).funcIndex() == i); } #endif diff --git a/js/src/wasm/WasmGenerator.h b/js/src/wasm/WasmGenerator.h index 9fa5863d1e..844e9c9ea1 100644 --- a/js/src/wasm/WasmGenerator.h +++ b/js/src/wasm/WasmGenerator.h @@ -19,8 +19,8 @@ #define wasm_generator_h #include "jit/MacroAssembler.h" -#include "wasm/WasmBinaryFormat.h" #include "wasm/WasmModule.h" +#include "wasm/WasmValidate.h" namespace js { namespace wasm { @@ -289,7 +289,6 @@ private: // Functions declarations: uint32_t numFuncImports() const; uint32_t numFuncDefs() const; - uint32_t numFuncs() const; // Function definitions: MOZ_MUST_USE bool startFuncDefs(); diff --git a/js/src/wasm/WasmIonCompile.cpp b/js/src/wasm/WasmIonCompile.cpp index 8c31274b83..c4e533215f 100644 --- a/js/src/wasm/WasmIonCompile.cpp +++ b/js/src/wasm/WasmIonCompile.cpp @@ -23,10 +23,10 @@ #include "jit/CodeGenerator.h" #include "wasm/WasmBaselineCompile.h" -#include "wasm/WasmBinaryFormat.h" #include "wasm/WasmBinaryIterator.h" #include "wasm/WasmGenerator.h" #include "wasm/WasmSignalHandlers.h" +#include "wasm/WasmValidate.h" using namespace js; using namespace js::jit; diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index dea2bddc46..6085295bd7 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -31,6 +31,7 @@ #include "wasm/WasmInstance.h" #include "wasm/WasmModule.h" #include "wasm/WasmSignalHandlers.h" +#include "wasm/WasmValidate.h" #include "jsobjinlines.h" @@ -1927,12 +1928,8 @@ WebAssembly_validate(JSContext* cx, unsigned argc, Value* vp) if (!GetBufferSource(cx, callArgs, "WebAssembly.validate", &bytecode)) return false; - CompileArgs compileArgs; - if (!InitCompileArgs(cx, &compileArgs)) - return false; - UniqueChars error; - bool validated = !!Compile(*bytecode, compileArgs, &error); + bool validated = Validate(*bytecode, &error); // If the reason for validation failure was OOM (signalled by null error // message), report out-of-memory so that validate's return is always diff --git a/js/src/wasm/WasmTextToBinary.cpp b/js/src/wasm/WasmTextToBinary.cpp index c3a707b550..3e0dfdf27b 100644 --- a/js/src/wasm/WasmTextToBinary.cpp +++ b/js/src/wasm/WasmTextToBinary.cpp @@ -30,8 +30,8 @@ #include "js/CharacterEncoding.h" #include "js/HashTable.h" #include "wasm/WasmAST.h" -#include "wasm/WasmBinaryFormat.h" #include "wasm/WasmTypes.h" +#include "wasm/WasmValidate.h" using namespace js; using namespace js::wasm; diff --git a/js/src/wasm/WasmBinaryFormat.cpp b/js/src/wasm/WasmValidate.cpp similarity index 59% rename from js/src/wasm/WasmBinaryFormat.cpp rename to js/src/wasm/WasmValidate.cpp index 34dc95eb03..307af8ab6f 100644 --- a/js/src/wasm/WasmBinaryFormat.cpp +++ b/js/src/wasm/WasmValidate.cpp @@ -15,15 +15,17 @@ * limitations under the License. */ -#include "wasm/WasmBinaryFormat.h" +#include "wasm/WasmValidate.h" #include "mozilla/CheckedInt.h" #include "jsprf.h" #include "jit/JitOptions.h" +#include "wasm/WasmBinaryIterator.h" using namespace js; +using namespace js::jit; using namespace js::wasm; using mozilla::CheckedInt; @@ -140,6 +142,405 @@ wasm::DecodeLocalEntries(Decoder& d, ModuleKind kind, ValTypeVector* locals) return true; } +// Function body validation. + +struct ValidatingPolicy : OpIterPolicy +{ + // Validation is what we're all about here. + static const bool Validate = true; +}; + +typedef OpIter ValidatingOpIter; + +class FunctionDecoder +{ + const ModuleEnvironment& env_; + const ValTypeVector& locals_; + ValidatingOpIter iter_; + + public: + FunctionDecoder(const ModuleEnvironment& env, const ValTypeVector& locals, Decoder& d) + : env_(env), locals_(locals), iter_(d) + {} + + const ModuleEnvironment& env() const { return env_; } + ValidatingOpIter& iter() { return iter_; } + const ValTypeVector& locals() const { return locals_; } + + bool checkHasMemory() { + if (!env().usesMemory()) + return iter().fail("can't touch memory without memory"); + return true; + } +}; + +static bool +DecodeCallArgs(FunctionDecoder& f, const Sig& sig) +{ + const ValTypeVector& args = sig.args(); + uint32_t numArgs = args.length(); + for (size_t i = 0; i < numArgs; ++i) { + ValType argType = args[i]; + if (!f.iter().readCallArg(argType, numArgs, i, nullptr)) + return false; + } + + return f.iter().readCallArgsEnd(numArgs); +} + +static bool +DecodeCallReturn(FunctionDecoder& f, const Sig& sig) +{ + return f.iter().readCallReturn(sig.ret()); +} + +static bool +DecodeCall(FunctionDecoder& f) +{ + uint32_t funcIndex; + if (!f.iter().readCall(&funcIndex)) + return false; + + if (funcIndex >= f.env().numFuncs()) + return f.iter().fail("callee index out of range"); + + if (!f.iter().inReachableCode()) + return true; + + const Sig* sig = f.env().funcSigs[funcIndex]; + + return DecodeCallArgs(f, *sig) && + DecodeCallReturn(f, *sig); +} + +static bool +DecodeCallIndirect(FunctionDecoder& f) +{ + if (!f.env().numTables()) + return f.iter().fail("can't call_indirect without a table"); + + uint32_t sigIndex; + if (!f.iter().readCallIndirect(&sigIndex, nullptr)) + return false; + + if (sigIndex >= f.env().numSigs()) + return f.iter().fail("signature index out of range"); + + if (!f.iter().inReachableCode()) + return true; + + const Sig& sig = f.env().sigs[sigIndex]; + if (!DecodeCallArgs(f, sig)) + return false; + + return DecodeCallReturn(f, sig); +} + +static bool +DecodeBrTable(FunctionDecoder& f) +{ + uint32_t tableLength; + ExprType type = ExprType::Limit; + if (!f.iter().readBrTable(&tableLength, &type, nullptr, nullptr)) + return false; + + uint32_t depth; + for (size_t i = 0, e = tableLength; i < e; ++i) { + if (!f.iter().readBrTableEntry(&type, nullptr, &depth)) + return false; + } + + // Read the default label. + return f.iter().readBrTableDefault(&type, nullptr, &depth); +} + +static bool +DecodeFunctionBodyExprs(FunctionDecoder& f) +{ +#define CHECK(c) if (!(c)) return false; break + + while (true) { + uint16_t op; + if (!f.iter().readOp(&op)) + return false; + + switch (op) { + case uint16_t(Op::End): + if (!f.iter().readEnd(nullptr, nullptr, nullptr)) + return false; + if (f.iter().controlStackEmpty()) + return true; + break; + case uint16_t(Op::Nop): + CHECK(f.iter().readNop()); + case uint16_t(Op::Drop): + CHECK(f.iter().readDrop()); + case uint16_t(Op::Call): + CHECK(DecodeCall(f)); + case uint16_t(Op::CallIndirect): + CHECK(DecodeCallIndirect(f)); + case uint16_t(Op::I32Const): + CHECK(f.iter().readI32Const(nullptr)); + case uint16_t(Op::I64Const): + CHECK(f.iter().readI64Const(nullptr)); + case uint16_t(Op::F32Const): + CHECK(f.iter().readF32Const(nullptr)); + case uint16_t(Op::F64Const): + CHECK(f.iter().readF64Const(nullptr)); + case uint16_t(Op::GetLocal): + CHECK(f.iter().readGetLocal(f.locals(), nullptr)); + case uint16_t(Op::SetLocal): + CHECK(f.iter().readSetLocal(f.locals(), nullptr, nullptr)); + case uint16_t(Op::TeeLocal): + CHECK(f.iter().readTeeLocal(f.locals(), nullptr, nullptr)); + case uint16_t(Op::GetGlobal): + CHECK(f.iter().readGetGlobal(f.env().globals, nullptr)); + case uint16_t(Op::SetGlobal): + CHECK(f.iter().readSetGlobal(f.env().globals, nullptr, nullptr)); + case uint16_t(Op::Select): + CHECK(f.iter().readSelect(nullptr, nullptr, nullptr, nullptr)); + case uint16_t(Op::Block): + CHECK(f.iter().readBlock()); + case uint16_t(Op::Loop): + CHECK(f.iter().readLoop()); + case uint16_t(Op::If): + CHECK(f.iter().readIf(nullptr)); + case uint16_t(Op::Else): + CHECK(f.iter().readElse(nullptr, nullptr)); + case uint16_t(Op::I32Clz): + case uint16_t(Op::I32Ctz): + case uint16_t(Op::I32Popcnt): + CHECK(f.iter().readUnary(ValType::I32, nullptr)); + case uint16_t(Op::I64Clz): + case uint16_t(Op::I64Ctz): + case uint16_t(Op::I64Popcnt): + CHECK(f.iter().readUnary(ValType::I64, nullptr)); + case uint16_t(Op::F32Abs): + case uint16_t(Op::F32Neg): + case uint16_t(Op::F32Ceil): + case uint16_t(Op::F32Floor): + case uint16_t(Op::F32Sqrt): + case uint16_t(Op::F32Trunc): + case uint16_t(Op::F32Nearest): + CHECK(f.iter().readUnary(ValType::F32, nullptr)); + case uint16_t(Op::F64Abs): + case uint16_t(Op::F64Neg): + case uint16_t(Op::F64Ceil): + case uint16_t(Op::F64Floor): + case uint16_t(Op::F64Sqrt): + case uint16_t(Op::F64Trunc): + case uint16_t(Op::F64Nearest): + CHECK(f.iter().readUnary(ValType::F64, nullptr)); + case uint16_t(Op::I32Add): + case uint16_t(Op::I32Sub): + case uint16_t(Op::I32Mul): + case uint16_t(Op::I32DivS): + case uint16_t(Op::I32DivU): + case uint16_t(Op::I32RemS): + case uint16_t(Op::I32RemU): + case uint16_t(Op::I32And): + case uint16_t(Op::I32Or): + case uint16_t(Op::I32Xor): + case uint16_t(Op::I32Shl): + case uint16_t(Op::I32ShrS): + case uint16_t(Op::I32ShrU): + case uint16_t(Op::I32Rotl): + case uint16_t(Op::I32Rotr): + CHECK(f.iter().readBinary(ValType::I32, nullptr, nullptr)); + case uint16_t(Op::I64Add): + case uint16_t(Op::I64Sub): + case uint16_t(Op::I64Mul): + case uint16_t(Op::I64DivS): + case uint16_t(Op::I64DivU): + case uint16_t(Op::I64RemS): + case uint16_t(Op::I64RemU): + case uint16_t(Op::I64And): + case uint16_t(Op::I64Or): + case uint16_t(Op::I64Xor): + case uint16_t(Op::I64Shl): + case uint16_t(Op::I64ShrS): + case uint16_t(Op::I64ShrU): + case uint16_t(Op::I64Rotl): + case uint16_t(Op::I64Rotr): + CHECK(f.iter().readBinary(ValType::I64, nullptr, nullptr)); + case uint16_t(Op::F32Add): + case uint16_t(Op::F32Sub): + case uint16_t(Op::F32Mul): + case uint16_t(Op::F32Div): + case uint16_t(Op::F32Min): + case uint16_t(Op::F32Max): + case uint16_t(Op::F32CopySign): + CHECK(f.iter().readBinary(ValType::F32, nullptr, nullptr)); + case uint16_t(Op::F64Add): + case uint16_t(Op::F64Sub): + case uint16_t(Op::F64Mul): + case uint16_t(Op::F64Div): + case uint16_t(Op::F64Min): + case uint16_t(Op::F64Max): + case uint16_t(Op::F64CopySign): + CHECK(f.iter().readBinary(ValType::F64, nullptr, nullptr)); + case uint16_t(Op::I32Eq): + case uint16_t(Op::I32Ne): + case uint16_t(Op::I32LtS): + case uint16_t(Op::I32LtU): + case uint16_t(Op::I32LeS): + case uint16_t(Op::I32LeU): + case uint16_t(Op::I32GtS): + case uint16_t(Op::I32GtU): + case uint16_t(Op::I32GeS): + case uint16_t(Op::I32GeU): + CHECK(f.iter().readComparison(ValType::I32, nullptr, nullptr)); + case uint16_t(Op::I64Eq): + case uint16_t(Op::I64Ne): + case uint16_t(Op::I64LtS): + case uint16_t(Op::I64LtU): + case uint16_t(Op::I64LeS): + case uint16_t(Op::I64LeU): + case uint16_t(Op::I64GtS): + case uint16_t(Op::I64GtU): + case uint16_t(Op::I64GeS): + case uint16_t(Op::I64GeU): + CHECK(f.iter().readComparison(ValType::I64, nullptr, nullptr)); + case uint16_t(Op::F32Eq): + case uint16_t(Op::F32Ne): + case uint16_t(Op::F32Lt): + case uint16_t(Op::F32Le): + case uint16_t(Op::F32Gt): + case uint16_t(Op::F32Ge): + CHECK(f.iter().readComparison(ValType::F32, nullptr, nullptr)); + case uint16_t(Op::F64Eq): + case uint16_t(Op::F64Ne): + case uint16_t(Op::F64Lt): + case uint16_t(Op::F64Le): + case uint16_t(Op::F64Gt): + case uint16_t(Op::F64Ge): + CHECK(f.iter().readComparison(ValType::F64, nullptr, nullptr)); + case uint16_t(Op::I32Eqz): + CHECK(f.iter().readConversion(ValType::I32, ValType::I32, nullptr)); + case uint16_t(Op::I64Eqz): + case uint16_t(Op::I32WrapI64): + CHECK(f.iter().readConversion(ValType::I64, ValType::I32, nullptr)); + case uint16_t(Op::I32TruncSF32): + case uint16_t(Op::I32TruncUF32): + case uint16_t(Op::I32ReinterpretF32): + CHECK(f.iter().readConversion(ValType::F32, ValType::I32, nullptr)); + case uint16_t(Op::I32TruncSF64): + case uint16_t(Op::I32TruncUF64): + CHECK(f.iter().readConversion(ValType::F64, ValType::I32, nullptr)); + case uint16_t(Op::I64ExtendSI32): + case uint16_t(Op::I64ExtendUI32): + CHECK(f.iter().readConversion(ValType::I32, ValType::I64, nullptr)); + case uint16_t(Op::I64TruncSF32): + case uint16_t(Op::I64TruncUF32): + CHECK(f.iter().readConversion(ValType::F32, ValType::I64, nullptr)); + case uint16_t(Op::I64TruncSF64): + case uint16_t(Op::I64TruncUF64): + case uint16_t(Op::I64ReinterpretF64): + CHECK(f.iter().readConversion(ValType::F64, ValType::I64, nullptr)); + case uint16_t(Op::F32ConvertSI32): + case uint16_t(Op::F32ConvertUI32): + case uint16_t(Op::F32ReinterpretI32): + CHECK(f.iter().readConversion(ValType::I32, ValType::F32, nullptr)); + case uint16_t(Op::F32ConvertSI64): + case uint16_t(Op::F32ConvertUI64): + CHECK(f.iter().readConversion(ValType::I64, ValType::F32, nullptr)); + case uint16_t(Op::F32DemoteF64): + CHECK(f.iter().readConversion(ValType::F64, ValType::F32, nullptr)); + case uint16_t(Op::F64ConvertSI32): + case uint16_t(Op::F64ConvertUI32): + CHECK(f.iter().readConversion(ValType::I32, ValType::F64, nullptr)); + case uint16_t(Op::F64ConvertSI64): + case uint16_t(Op::F64ConvertUI64): + case uint16_t(Op::F64ReinterpretI64): + CHECK(f.iter().readConversion(ValType::I64, ValType::F64, nullptr)); + case uint16_t(Op::F64PromoteF32): + CHECK(f.iter().readConversion(ValType::F32, ValType::F64, nullptr)); + case uint16_t(Op::I32Load8S): + case uint16_t(Op::I32Load8U): + CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 1, nullptr)); + case uint16_t(Op::I32Load16S): + case uint16_t(Op::I32Load16U): + CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 2, nullptr)); + case uint16_t(Op::I32Load): + CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I32, 4, nullptr)); + case uint16_t(Op::I64Load8S): + case uint16_t(Op::I64Load8U): + CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 1, nullptr)); + case uint16_t(Op::I64Load16S): + case uint16_t(Op::I64Load16U): + CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 2, nullptr)); + case uint16_t(Op::I64Load32S): + case uint16_t(Op::I64Load32U): + CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 4, nullptr)); + case uint16_t(Op::I64Load): + CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::I64, 8, nullptr)); + case uint16_t(Op::F32Load): + CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::F32, 4, nullptr)); + case uint16_t(Op::F64Load): + CHECK(f.checkHasMemory() && f.iter().readLoad(ValType::F64, 8, nullptr)); + case uint16_t(Op::I32Store8): + CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 1, nullptr, nullptr)); + case uint16_t(Op::I32Store16): + CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 2, nullptr, nullptr)); + case uint16_t(Op::I32Store): + CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I32, 4, nullptr, nullptr)); + case uint16_t(Op::I64Store8): + CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 1, nullptr, nullptr)); + case uint16_t(Op::I64Store16): + CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 2, nullptr, nullptr)); + case uint16_t(Op::I64Store32): + CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 4, nullptr, nullptr)); + case uint16_t(Op::I64Store): + CHECK(f.checkHasMemory() && f.iter().readStore(ValType::I64, 8, nullptr, nullptr)); + case uint16_t(Op::F32Store): + CHECK(f.checkHasMemory() && f.iter().readStore(ValType::F32, 4, nullptr, nullptr)); + case uint16_t(Op::F64Store): + CHECK(f.checkHasMemory() && f.iter().readStore(ValType::F64, 8, nullptr, nullptr)); + case uint16_t(Op::GrowMemory): + CHECK(f.checkHasMemory() && f.iter().readGrowMemory(nullptr)); + case uint16_t(Op::CurrentMemory): + CHECK(f.checkHasMemory() && f.iter().readCurrentMemory()); + case uint16_t(Op::Br): + CHECK(f.iter().readBr(nullptr, nullptr, nullptr)); + case uint16_t(Op::BrIf): + CHECK(f.iter().readBrIf(nullptr, nullptr, nullptr, nullptr)); + case uint16_t(Op::BrTable): + CHECK(DecodeBrTable(f)); + case uint16_t(Op::Return): + CHECK(f.iter().readReturn(nullptr)); + case uint16_t(Op::Unreachable): + CHECK(f.iter().readUnreachable()); + default: + return f.iter().unrecognizedOpcode(op); + } + } + + MOZ_CRASH("unreachable"); + +#undef CHECK +} + +bool +wasm::ValidateFunctionBody(const ModuleEnvironment& env, uint32_t funcIndex, Decoder& d) +{ + ValTypeVector locals; + const Sig& sig = *env.funcSigs[funcIndex]; + if (!locals.appendAll(sig.args())) + return false; + + if (!DecodeLocalEntries(d, ModuleKind::Wasm, &locals)) + return false; + + FunctionDecoder f(env, locals, d); + + if (!f.iter().readFunctionStart(sig.ret())) + return false; + + if (!DecodeFunctionBodyExprs(f)) + return false; + + return f.iter().readFunctionEnd(); +} // Section macros. static bool @@ -625,11 +1026,12 @@ DecodeGlobalSection(Decoder& d, GlobalDescVector* globals) if (!d.readVarU32(&numDefs)) return d.fail("expected number of globals"); - uint32_t numGlobals = globals->length() + numDefs; - if (numGlobals > MaxGlobals) + CheckedInt numGlobals = globals->length(); + numGlobals += numDefs; + if (!numGlobals.isValid() || numGlobals.value() > MaxGlobals) return d.fail("too many globals"); - if (!globals->reserve(numGlobals)) + if (!globals->reserve(numGlobals.value())) return false; for (uint32_t i = 0; i < numDefs; i++) { @@ -894,6 +1296,59 @@ wasm::DecodeModuleEnvironment(Decoder& d, ModuleEnvironment* env) return true; } +static bool +DecodeFunctionBody(Decoder& d, const ModuleEnvironment& env, uint32_t funcIndex) +{ + uint32_t bodySize; + if (!d.readVarU32(&bodySize)) + return d.fail("expected number of function body bytes"); + + if (d.bytesRemain() < bodySize) + return d.fail("function body length too big"); + + const uint8_t* bodyBegin = d.currentPosition(); + + if (!ValidateFunctionBody(env, funcIndex, d)) + return false; + + if (d.currentPosition() != bodyBegin + bodySize) + return d.fail("function body length mismatch"); + + return true; +} + +static bool +DecodeCodeSection(Decoder& d, const ModuleEnvironment& env) +{ + uint32_t sectionStart, sectionSize; + if (!d.startSection(SectionId::Code, §ionStart, §ionSize, "code")) + return false; + + if (sectionStart == Decoder::NotStarted) { + if (env.numFuncDefs() != 0) + return d.fail("expected function bodies"); + return true; + } + + uint32_t numFuncDefs; + if (!d.readVarU32(&numFuncDefs)) + return d.fail("expected function body count"); + + 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)) + return false; + } + + if (!d.finishSection(sectionStart, sectionSize, "code")) + return false; + + return true; +} + + bool wasm::DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVector* segments) { @@ -951,5 +1406,30 @@ wasm::DecodeUnknownSections(Decoder& d) return false; } + return true; +} + + +// Validate algorithm. + +bool +wasm::Validate(const ShareableBytes& bytecode, UniqueChars* error) +{ + Decoder d(bytecode.begin(), bytecode.end(), error); + + ModuleEnvironment env; + if (!DecodeModuleEnvironment(d, &env)) + return false; + + if (!DecodeCodeSection(d, env)) + return false; + + DataSegmentVector dataSegments; + if (!DecodeDataSection(d, env, &dataSegments)) + return false; + + if (!DecodeUnknownSections(d)) + return false; + return true; } \ No newline at end of file diff --git a/js/src/wasm/WasmBinaryFormat.h b/js/src/wasm/WasmValidate.h similarity index 97% rename from js/src/wasm/WasmBinaryFormat.h rename to js/src/wasm/WasmValidate.h index 17ea83be2e..8138b87016 100644 --- a/js/src/wasm/WasmBinaryFormat.h +++ b/js/src/wasm/WasmValidate.h @@ -16,10 +16,11 @@ * limitations under the License. */ -#ifndef wasm_binary_format_h -#define wasm_binary_format_h +#ifndef wasm_validate_h +#define wasm_validate_h #include "wasm/WasmCode.h" +#include "wasm/WasmTypes.h" namespace js { namespace wasm { @@ -622,6 +623,13 @@ struct ModuleEnvironment 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 @@ -637,6 +645,10 @@ struct ModuleEnvironment MOZ_ASSERT(!isAsmJS()); return funcSigs.length() - funcImportGlobalDataOffsets.length(); } + size_t numFuncImports() const { + MOZ_ASSERT(!isAsmJS()); + return funcImportGlobalDataOffsets.length(); + } bool usesMemory() const { return UsesMemory(memoryUsage); } @@ -665,6 +677,12 @@ DecodeDataSection(Decoder& d, const ModuleEnvironment& env, DataSegmentVector* s MOZ_MUST_USE bool DecodeUnknownSections(Decoder& d); +[[nodiscard]] bool + ValidateFunctionBody(const ModuleEnvironment& env, uint32_t funcIndex, Decoder& d); + +[[nodiscard]] bool +Validate(const ShareableBytes& bytecode, UniqueChars* error); + } // namespace wasm } // namespace js