/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * * Copyright 2016 Mozilla Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * 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/WasmBinaryToAST.h" #include "mozilla/CheckedInt.h" #include "mozilla/MathAlgorithms.h" #include "mozilla/Sprintf.h" #include "jscntxt.h" #include "wasm/WasmBinaryFormat.h" #include "wasm/WasmBinaryIterator.h" using namespace js; using namespace js::wasm; using mozilla::CheckedInt; using mozilla::FloorLog2; enum AstDecodeTerminationKind { Unknown, End, Else }; struct AstDecodeStackItem { AstExpr* expr; AstDecodeTerminationKind terminationKind; ExprType type; explicit AstDecodeStackItem() : expr(nullptr), terminationKind(AstDecodeTerminationKind::Unknown), type(ExprType::Limit) {} explicit AstDecodeStackItem(AstDecodeTerminationKind terminationKind, ExprType type) : expr(nullptr), terminationKind(terminationKind), type(type) {} explicit AstDecodeStackItem(AstExpr* expr) : expr(expr), terminationKind(AstDecodeTerminationKind::Unknown), type(ExprType::Limit) {} }; // We don't define a Value type because OpIter doesn't push void values, which // we actually need here because we're building an AST, so we maintain our own // stack. struct AstDecodePolicy : OpIterPolicy { // Enable validation because we can be called from wasmBinaryToText on bytes // which are not necessarily valid, and we shouldn't run the decoder in // non-validating mode on invalid code. static const bool Validate = true; static const bool Output = true; }; typedef OpIter AstDecodeOpIter; class AstDecodeContext { public: typedef AstVector AstIndexVector; typedef AstVector AstDecodeStack; typedef AstVector DepthStack; JSContext* cx; LifoAlloc& lifo; Decoder& d; bool generateNames; private: AstModule& module_; AstIndexVector funcDefSigs_; AstDecodeOpIter *iter_; AstDecodeStack exprs_; DepthStack depths_; const ValTypeVector* locals_; GlobalDescVector globals_; AstNameVector blockLabels_; uint32_t currentLabelIndex_; ExprType retType_; public: AstDecodeContext(JSContext* cx, LifoAlloc& lifo, Decoder& d, AstModule& module, bool generateNames) : cx(cx), lifo(lifo), d(d), generateNames(generateNames), module_(module), funcDefSigs_(lifo), iter_(nullptr), exprs_(lifo), depths_(lifo), locals_(nullptr), blockLabels_(lifo), currentLabelIndex_(0), retType_(ExprType::Limit) {} AstModule& module() { return module_; } AstIndexVector& funcDefSigs() { return funcDefSigs_; } AstDecodeOpIter& iter() { return *iter_; } AstDecodeStack& exprs() { return exprs_; } DepthStack& depths() { return depths_; } AstNameVector& blockLabels() { return blockLabels_; } ExprType retType() const { return retType_; } const ValTypeVector& locals() const { return *locals_; } bool addGlobalDesc(ValType type, bool isMutable, bool isImport) { if (isImport) return globals_.append(GlobalDesc(type, isMutable, globals_.length())); // No need to have the precise init expr value; we just need the right // type. Val dummy; switch (type) { case ValType::I32: dummy = Val(uint32_t(0)); break; case ValType::I64: dummy = Val(uint64_t(0)); break; case ValType::F32: dummy = Val(RawF32(0.f)); break; case ValType::F64: dummy = Val(RawF64(0.0)); break; default: return false; } return globals_.append(GlobalDesc(InitExpr(dummy), isMutable)); } const GlobalDescVector& globalDescs() const { return globals_; } void popBack() { return exprs().popBack(); } AstDecodeStackItem popCopy() { return exprs().popCopy(); } AstDecodeStackItem& top() { return exprs().back(); } MOZ_MUST_USE bool push(AstDecodeStackItem item) { return exprs().append(item); } bool needFirst() { for (size_t i = depths().back(); i < exprs().length(); ++i) { if (!exprs()[i].expr->isVoid()) return true; } return false; } AstExpr* handleVoidExpr(AstExpr* voidNode) { MOZ_ASSERT(voidNode->isVoid()); // To attach a node that "returns void" to the middle of an AST, wrap it // in a first node next to the node it should accompany. if (needFirst()) { AstExpr *prev = popCopy().expr; // If the previous/A node is already a First, reuse it. if (prev->kind() == AstExprKind::First) { if (!prev->as().exprs().append(voidNode)) return nullptr; return prev; } AstExprVector exprs(lifo); if (!exprs.append(prev)) return nullptr; if (!exprs.append(voidNode)) return nullptr; return new(lifo) AstFirst(Move(exprs)); } return voidNode; } void startFunction(AstDecodeOpIter* iter, const ValTypeVector* locals, ExprType retType) { iter_ = iter; locals_ = locals; currentLabelIndex_ = 0; retType_ = retType; } void endFunction() { iter_ = nullptr; locals_ = nullptr; retType_ = ExprType::Limit; MOZ_ASSERT(blockLabels_.length() == 0); } uint32_t nextLabelIndex() { return currentLabelIndex_++; } }; static bool GenerateName(AstDecodeContext& c, const AstName& prefix, uint32_t index, AstName* name) { if (!c.generateNames) { *name = AstName(); return true; } AstVector result(c.lifo); if (!result.append(u'$')) return false; if (!result.append(prefix.begin(), prefix.length())) return false; uint32_t tmp = index; do { if (!result.append(u'0')) return false; tmp /= 10; } while (tmp); if (index) { char16_t* p = result.end(); for (tmp = index; tmp; tmp /= 10) *(--p) = u'0' + (tmp % 10); } size_t length = result.length(); char16_t* begin = result.extractOrCopyRawBuffer(); if (!begin) return false; *name = AstName(begin, length); return true; } static bool GenerateRef(AstDecodeContext& c, const AstName& prefix, uint32_t index, AstRef* ref) { MOZ_ASSERT(index != AstNoIndex); if (!c.generateNames) { *ref = AstRef(index); return true; } AstName name; if (!GenerateName(c, prefix, index, &name)) return false; MOZ_ASSERT(!name.empty()); *ref = AstRef(name); ref->setIndex(index); return true; } static bool AstDecodeCallArgs(AstDecodeContext& c, const AstSig& sig, AstExprVector* funcArgs) { MOZ_ASSERT(c.iter().inReachableCode()); const AstValTypeVector& args = sig.args(); uint32_t numArgs = args.length(); if (!funcArgs->resize(numArgs)) return false; for (size_t i = 0; i < numArgs; ++i) { ValType argType = args[i]; AstDecodeStackItem item; if (!c.iter().readCallArg(argType, numArgs, i, nullptr)) return false; (*funcArgs)[i] = c.exprs()[c.exprs().length() - numArgs + i].expr; } c.exprs().shrinkBy(numArgs); return c.iter().readCallArgsEnd(numArgs); } static bool AstDecodeCallReturn(AstDecodeContext& c, const AstSig& sig) { return c.iter().readCallReturn(sig.ret()); } static bool AstDecodeExpr(AstDecodeContext& c); static bool AstDecodeDrop(AstDecodeContext& c) { if (!c.iter().readDrop()) return false; AstDecodeStackItem value = c.popCopy(); AstExpr* tmp = new(c.lifo) AstDrop(*value.expr); if (!tmp) return false; tmp = c.handleVoidExpr(tmp); if (!tmp) return false; if (!c.push(AstDecodeStackItem(tmp))) return false; return true; } static bool AstDecodeCall(AstDecodeContext& c) { uint32_t funcIndex; if (!c.iter().readCall(&funcIndex)) return false; if (!c.iter().inReachableCode()) return true; uint32_t sigIndex; AstRef funcRef; if (funcIndex < c.module().numFuncImports()) { AstImport* import = c.module().imports()[funcIndex]; sigIndex = import->funcSig().index(); funcRef = AstRef(import->name()); } else { uint32_t funcDefIndex = funcIndex - c.module().numFuncImports(); if (funcDefIndex >= c.funcDefSigs().length()) return c.iter().fail("callee index out of range"); sigIndex = c.funcDefSigs()[funcDefIndex]; if (!GenerateRef(c, AstName(u"func"), funcIndex, &funcRef)) return false; } const AstSig* sig = c.module().sigs()[sigIndex]; AstExprVector args(c.lifo); if (!AstDecodeCallArgs(c, *sig, &args)) return false; if (!AstDecodeCallReturn(c, *sig)) return false; AstCall* call = new(c.lifo) AstCall(Op::Call, sig->ret(), funcRef, Move(args)); if (!call) return false; AstExpr* result = call; if (IsVoid(sig->ret())) result = c.handleVoidExpr(call); if (!c.push(AstDecodeStackItem(result))) return false; return true; } static bool AstDecodeCallIndirect(AstDecodeContext& c) { uint32_t sigIndex; if (!c.iter().readCallIndirect(&sigIndex, nullptr)) return false; if (!c.iter().inReachableCode()) return true; if (sigIndex >= c.module().sigs().length()) return c.iter().fail("signature index out of range"); AstDecodeStackItem index = c.popCopy(); AstRef sigRef; if (!GenerateRef(c, AstName(u"type"), sigIndex, &sigRef)) return false; const AstSig* sig = c.module().sigs()[sigIndex]; AstExprVector args(c.lifo); if (!AstDecodeCallArgs(c, *sig, &args)) return false; if (!AstDecodeCallReturn(c, *sig)) return false; AstCallIndirect* call = new(c.lifo) AstCallIndirect(sigRef, sig->ret(), Move(args), index.expr); if (!call) return false; AstExpr* result = call; if (IsVoid(sig->ret())) result = c.handleVoidExpr(call); if (!c.push(AstDecodeStackItem(result))) return false; return true; } static bool AstDecodeGetBlockRef(AstDecodeContext& c, uint32_t depth, AstRef* ref) { if (!c.generateNames || depth >= c.blockLabels().length()) { // Also ignoring if it's a function body label. *ref = AstRef(depth); return true; } uint32_t index = c.blockLabels().length() - depth - 1; if (c.blockLabels()[index].empty()) { if (!GenerateName(c, AstName(u"label"), c.nextLabelIndex(), &c.blockLabels()[index])) return false; } *ref = AstRef(c.blockLabels()[index]); ref->setIndex(depth); return true; } static bool AstDecodeBrTable(AstDecodeContext& c) { uint32_t tableLength; ExprType type; if (!c.iter().readBrTable(&tableLength, &type, nullptr, nullptr)) return false; AstRefVector table(c.lifo); if (!table.resize(tableLength)) return false; uint32_t depth; for (size_t i = 0, e = tableLength; i < e; ++i) { if (!c.iter().readBrTableEntry(&type, nullptr, &depth)) return false; if (!AstDecodeGetBlockRef(c, depth, &table[i])) return false; } // Read the default label. if (!c.iter().readBrTableDefault(&type, nullptr, &depth)) return false; AstDecodeStackItem index = c.popCopy(); AstDecodeStackItem value; if (!IsVoid(type)) value = c.popCopy(); AstRef def; if (!AstDecodeGetBlockRef(c, depth, &def)) return false; AstBranchTable* branchTable = new(c.lifo) AstBranchTable(*index.expr, def, Move(table), value.expr); if (!branchTable) return false; if (!c.push(AstDecodeStackItem(branchTable))) return false; return true; } static bool AstDecodeBlock(AstDecodeContext& c, Op op) { MOZ_ASSERT(op == Op::Block || op == Op::Loop); if (!c.blockLabels().append(AstName())) return false; if (op == Op::Loop) { if (!c.iter().readLoop()) return false; } else { if (!c.iter().readBlock()) return false; } if (!c.depths().append(c.exprs().length())) return false; ExprType type; while (true) { if (!AstDecodeExpr(c)) return false; const AstDecodeStackItem& item = c.top(); if (!item.expr) { // Op::End was found type = item.type; c.popBack(); break; } } AstExprVector exprs(c.lifo); for (auto i = c.exprs().begin() + c.depths().back(), e = c.exprs().end(); i != e; ++i) { if (!exprs.append(i->expr)) return false; } c.exprs().shrinkTo(c.depths().popCopy()); AstName name = c.blockLabels().popCopy(); AstBlock* block = new(c.lifo) AstBlock(op, type, name, Move(exprs)); if (!block) return false; AstExpr* result = block; if (IsVoid(type)) result = c.handleVoidExpr(block); if (!c.push(AstDecodeStackItem(result))) return false; return true; } static bool AstDecodeIf(AstDecodeContext& c) { if (!c.iter().readIf(nullptr)) return false; AstDecodeStackItem cond = c.popCopy(); bool hasElse = false; if (!c.depths().append(c.exprs().length())) return false; if (!c.blockLabels().append(AstName())) return false; ExprType type; while (true) { if (!AstDecodeExpr(c)) return false; const AstDecodeStackItem& item = c.top(); if (!item.expr) { // Op::End was found hasElse = item.terminationKind == AstDecodeTerminationKind::Else; type = item.type; c.popBack(); break; } } AstExprVector thenExprs(c.lifo); for (auto i = c.exprs().begin() + c.depths().back(), e = c.exprs().end(); i != e; ++i) { if (!thenExprs.append(i->expr)) return false; } c.exprs().shrinkTo(c.depths().back()); AstExprVector elseExprs(c.lifo); if (hasElse) { while (true) { if (!AstDecodeExpr(c)) return false; const AstDecodeStackItem& item = c.top(); if (!item.expr) { // Op::End was found c.popBack(); break; } } for (auto i = c.exprs().begin() + c.depths().back(), e = c.exprs().end(); i != e; ++i) { if (!elseExprs.append(i->expr)) return false; } c.exprs().shrinkTo(c.depths().back()); } c.depths().popBack(); AstName name = c.blockLabels().popCopy(); AstIf* if_ = new(c.lifo) AstIf(type, cond.expr, name, Move(thenExprs), Move(elseExprs)); if (!if_) return false; AstExpr* result = if_; if (IsVoid(type)) result = c.handleVoidExpr(if_); if (!c.push(AstDecodeStackItem(result))) return false; return true; } static bool AstDecodeEnd(AstDecodeContext& c) { LabelKind kind; ExprType type; if (!c.iter().readEnd(&kind, &type, nullptr)) return false; if (!c.push(AstDecodeStackItem(AstDecodeTerminationKind::End, type))) return false; return true; } static bool AstDecodeElse(AstDecodeContext& c) { ExprType type; if (!c.iter().readElse(&type, nullptr)) return false; if (!c.push(AstDecodeStackItem(AstDecodeTerminationKind::Else, type))) return false; return true; } static bool AstDecodeNop(AstDecodeContext& c) { if (!c.iter().readNop()) return false; AstExpr* tmp = new(c.lifo) AstNop(); if (!tmp) return false; tmp = c.handleVoidExpr(tmp); if (!tmp) return false; if (!c.push(AstDecodeStackItem(tmp))) return false; return true; } static bool AstDecodeUnary(AstDecodeContext& c, ValType type, Op op) { if (!c.iter().readUnary(type, nullptr)) return false; AstDecodeStackItem operand = c.popCopy(); AstUnaryOperator* unary = new(c.lifo) AstUnaryOperator(op, operand.expr); if (!unary) return false; if (!c.push(AstDecodeStackItem(unary))) return false; return true; } static bool AstDecodeBinary(AstDecodeContext& c, ValType type, Op op) { if (!c.iter().readBinary(type, nullptr, nullptr)) return false; AstDecodeStackItem rhs = c.popCopy(); AstDecodeStackItem lhs = c.popCopy(); AstBinaryOperator* binary = new(c.lifo) AstBinaryOperator(op, lhs.expr, rhs.expr); if (!binary) return false; if (!c.push(AstDecodeStackItem(binary))) return false; return true; } static bool AstDecodeSelect(AstDecodeContext& c) { ValType type; if (!c.iter().readSelect(&type, nullptr, nullptr, nullptr)) return false; AstDecodeStackItem selectFalse = c.popCopy(); AstDecodeStackItem selectTrue = c.popCopy(); AstDecodeStackItem cond = c.popCopy(); AstTernaryOperator* ternary = new(c.lifo) AstTernaryOperator(Op::Select, cond.expr, selectTrue.expr, selectFalse.expr); if (!ternary) return false; if (!c.push(AstDecodeStackItem(ternary))) return false; return true; } static bool AstDecodeComparison(AstDecodeContext& c, ValType type, Op op) { if (!c.iter().readComparison(type, nullptr, nullptr)) return false; AstDecodeStackItem rhs = c.popCopy(); AstDecodeStackItem lhs = c.popCopy(); AstComparisonOperator* comparison = new(c.lifo) AstComparisonOperator(op, lhs.expr, rhs.expr); if (!comparison) return false; if (!c.push(AstDecodeStackItem(comparison))) return false; return true; } static bool AstDecodeConversion(AstDecodeContext& c, ValType fromType, ValType toType, Op op) { if (!c.iter().readConversion(fromType, toType, nullptr)) return false; AstDecodeStackItem operand = c.popCopy(); AstConversionOperator* conversion = new(c.lifo) AstConversionOperator(op, operand.expr); if (!conversion) return false; if (!c.push(AstDecodeStackItem(conversion))) return false; return true; } static AstLoadStoreAddress AstDecodeLoadStoreAddress(const LinearMemoryAddress& addr, const AstDecodeStackItem& item) { uint32_t flags = FloorLog2(addr.align); return AstLoadStoreAddress(item.expr, flags, addr.offset); } static bool AstDecodeLoad(AstDecodeContext& c, ValType type, uint32_t byteSize, Op op) { LinearMemoryAddress addr; if (!c.iter().readLoad(type, byteSize, &addr)) return false; AstDecodeStackItem item = c.popCopy(); AstLoad* load = new(c.lifo) AstLoad(op, AstDecodeLoadStoreAddress(addr, item)); if (!load) return false; if (!c.push(AstDecodeStackItem(load))) return false; return true; } static bool AstDecodeStore(AstDecodeContext& c, ValType type, uint32_t byteSize, Op op) { LinearMemoryAddress addr; if (!c.iter().readStore(type, byteSize, &addr, nullptr)) return false; AstDecodeStackItem value = c.popCopy(); AstDecodeStackItem item = c.popCopy(); AstStore* store = new(c.lifo) AstStore(op, AstDecodeLoadStoreAddress(addr, item), value.expr); if (!store) return false; AstExpr* wrapped = c.handleVoidExpr(store); if (!wrapped) return false; if (!c.push(AstDecodeStackItem(wrapped))) return false; return true; } static bool AstDecodeCurrentMemory(AstDecodeContext& c) { if (!c.iter().readCurrentMemory()) return false; AstCurrentMemory* gm = new(c.lifo) AstCurrentMemory(); if (!gm) return false; if (!c.push(AstDecodeStackItem(gm))) return false; return true; } static bool AstDecodeGrowMemory(AstDecodeContext& c) { if (!c.iter().readGrowMemory(nullptr)) return false; AstDecodeStackItem operand = c.popCopy(); AstGrowMemory* gm = new(c.lifo) AstGrowMemory(operand.expr); if (!gm) return false; if (!c.push(AstDecodeStackItem(gm))) return false; return true; } static bool AstDecodeBranch(AstDecodeContext& c, Op op) { MOZ_ASSERT(op == Op::Br || op == Op::BrIf); uint32_t depth; ExprType type; AstDecodeStackItem value; AstDecodeStackItem cond; if (op == Op::Br) { if (!c.iter().readBr(&depth, &type, nullptr)) return false; if (!IsVoid(type)) value = c.popCopy(); } else { if (!c.iter().readBrIf(&depth, &type, nullptr, nullptr)) return false; if (!IsVoid(type)) value = c.popCopy(); cond = c.popCopy(); } AstRef depthRef; if (!AstDecodeGetBlockRef(c, depth, &depthRef)) return false; if (op == Op::Br || !value.expr) type = ExprType::Void; AstBranch* branch = new(c.lifo) AstBranch(op, type, cond.expr, depthRef, value.expr); if (!branch) return false; if (!c.push(AstDecodeStackItem(branch))) return false; return true; } static bool AstDecodeGetLocal(AstDecodeContext& c) { uint32_t getLocalId; if (!c.iter().readGetLocal(c.locals(), &getLocalId)) return false; AstRef localRef; if (!GenerateRef(c, AstName(u"var"), getLocalId, &localRef)) return false; AstGetLocal* getLocal = new(c.lifo) AstGetLocal(localRef); if (!getLocal) return false; if (!c.push(AstDecodeStackItem(getLocal))) return false; return true; } static bool AstDecodeSetLocal(AstDecodeContext& c) { uint32_t setLocalId; if (!c.iter().readSetLocal(c.locals(), &setLocalId, nullptr)) return false; AstDecodeStackItem setLocalValue = c.popCopy(); AstRef localRef; if (!GenerateRef(c, AstName(u"var"), setLocalId, &localRef)) return false; AstSetLocal* setLocal = new(c.lifo) AstSetLocal(localRef, *setLocalValue.expr); if (!setLocal) return false; AstExpr* expr = c.handleVoidExpr(setLocal); if (!expr) return false; if (!c.push(AstDecodeStackItem(expr))) return false; return true; } static bool AstDecodeTeeLocal(AstDecodeContext& c) { uint32_t teeLocalId; if (!c.iter().readTeeLocal(c.locals(), &teeLocalId, nullptr)) return false; AstDecodeStackItem teeLocalValue = c.popCopy(); AstRef localRef; if (!GenerateRef(c, AstName(u"var"), teeLocalId, &localRef)) return false; AstTeeLocal* teeLocal = new(c.lifo) AstTeeLocal(localRef, *teeLocalValue.expr); if (!teeLocal) return false; if (!c.push(AstDecodeStackItem(teeLocal))) return false; return true; } static bool AstDecodeGetGlobal(AstDecodeContext& c) { uint32_t globalId; if (!c.iter().readGetGlobal(c.globalDescs(), &globalId)) return false; AstRef globalRef; if (!GenerateRef(c, AstName(u"global"), globalId, &globalRef)) return false; auto* getGlobal = new(c.lifo) AstGetGlobal(globalRef); if (!getGlobal) return false; if (!c.push(AstDecodeStackItem(getGlobal))) return false; return true; } static bool AstDecodeSetGlobal(AstDecodeContext& c) { uint32_t globalId; if (!c.iter().readSetGlobal(c.globalDescs(), &globalId, nullptr)) return false; AstDecodeStackItem value = c.popCopy(); AstRef globalRef; if (!GenerateRef(c, AstName(u"global"), globalId, &globalRef)) return false; auto* setGlobal = new(c.lifo) AstSetGlobal(globalRef, *value.expr); if (!setGlobal) return false; AstExpr* expr = c.handleVoidExpr(setGlobal); if (!expr) return false; if (!c.push(AstDecodeStackItem(expr))) return false; return true; } static bool AstDecodeReturn(AstDecodeContext& c) { if (!c.iter().readReturn(nullptr)) return false; AstDecodeStackItem result; if (!IsVoid(c.retType())) result = c.popCopy(); AstReturn* ret = new(c.lifo) AstReturn(result.expr); if (!ret) return false; if (!c.push(AstDecodeStackItem(ret))) return false; return true; } static bool AstDecodeExpr(AstDecodeContext& c) { uint32_t exprOffset = c.iter().currentOffset(); uint16_t op; if (!c.iter().readOp(&op)) return false; AstExpr* tmp; switch (op) { case uint16_t(Op::Nop): if (!AstDecodeNop(c)) return false; break; case uint16_t(Op::Drop): if (!AstDecodeDrop(c)) return false; break; case uint16_t(Op::Call): if (!AstDecodeCall(c)) return false; break; case uint16_t(Op::CallIndirect): if (!AstDecodeCallIndirect(c)) return false; break; case uint16_t(Op::I32Const): int32_t i32; if (!c.iter().readI32Const(&i32)) return false; tmp = new(c.lifo) AstConst(Val((uint32_t)i32)); if (!tmp || !c.push(AstDecodeStackItem(tmp))) return false; break; case uint16_t(Op::I64Const): int64_t i64; if (!c.iter().readI64Const(&i64)) return false; tmp = new(c.lifo) AstConst(Val((uint64_t)i64)); if (!tmp || !c.push(AstDecodeStackItem(tmp))) return false; break; case uint16_t(Op::F32Const): { RawF32 f32; if (!c.iter().readF32Const(&f32)) return false; tmp = new(c.lifo) AstConst(Val(f32)); if (!tmp || !c.push(AstDecodeStackItem(tmp))) return false; break; } case uint16_t(Op::F64Const): { RawF64 f64; if (!c.iter().readF64Const(&f64)) return false; tmp = new(c.lifo) AstConst(Val(f64)); if (!tmp || !c.push(AstDecodeStackItem(tmp))) return false; break; } case uint16_t(Op::GetLocal): if (!AstDecodeGetLocal(c)) return false; break; case uint16_t(Op::SetLocal): if (!AstDecodeSetLocal(c)) return false; break; case uint16_t(Op::TeeLocal): if (!AstDecodeTeeLocal(c)) return false; break; case uint16_t(Op::Select): if (!AstDecodeSelect(c)) return false; break; case uint16_t(Op::Block): case uint16_t(Op::Loop): if (!AstDecodeBlock(c, Op(op))) return false; break; case uint16_t(Op::If): if (!AstDecodeIf(c)) return false; break; case uint16_t(Op::Else): if (!AstDecodeElse(c)) return false; break; case uint16_t(Op::End): if (!AstDecodeEnd(c)) return false; break; case uint16_t(Op::I32Clz): case uint16_t(Op::I32Ctz): case uint16_t(Op::I32Popcnt): if (!AstDecodeUnary(c, ValType::I32, Op(op))) return false; break; case uint16_t(Op::I64Clz): case uint16_t(Op::I64Ctz): case uint16_t(Op::I64Popcnt): if (!AstDecodeUnary(c, ValType::I64, Op(op))) return false; break; 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): if (!AstDecodeUnary(c, ValType::F32, Op(op))) return false; break; 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): if (!AstDecodeUnary(c, ValType::F64, Op(op))) return false; break; 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): if (!AstDecodeBinary(c, ValType::I32, Op(op))) return false; break; 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): if (!AstDecodeBinary(c, ValType::I64, Op(op))) return false; break; 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): if (!AstDecodeBinary(c, ValType::F32, Op(op))) return false; break; 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): if (!AstDecodeBinary(c, ValType::F64, Op(op))) return false; break; 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): if (!AstDecodeComparison(c, ValType::I32, Op(op))) return false; break; 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): if (!AstDecodeComparison(c, ValType::I64, Op(op))) return false; break; 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): if (!AstDecodeComparison(c, ValType::F32, Op(op))) return false; break; 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): if (!AstDecodeComparison(c, ValType::F64, Op(op))) return false; break; case uint16_t(Op::I32Eqz): if (!AstDecodeConversion(c, ValType::I32, ValType::I32, Op(op))) return false; break; case uint16_t(Op::I64Eqz): case uint16_t(Op::I32WrapI64): if (!AstDecodeConversion(c, ValType::I64, ValType::I32, Op(op))) return false; break; case uint16_t(Op::I32TruncSF32): case uint16_t(Op::I32TruncUF32): case uint16_t(Op::I32ReinterpretF32): if (!AstDecodeConversion(c, ValType::F32, ValType::I32, Op(op))) return false; break; case uint16_t(Op::I32TruncSF64): case uint16_t(Op::I32TruncUF64): if (!AstDecodeConversion(c, ValType::F64, ValType::I32, Op(op))) return false; break; case uint16_t(Op::I64ExtendSI32): case uint16_t(Op::I64ExtendUI32): if (!AstDecodeConversion(c, ValType::I32, ValType::I64, Op(op))) return false; break; case uint16_t(Op::I64TruncSF32): case uint16_t(Op::I64TruncUF32): if (!AstDecodeConversion(c, ValType::F32, ValType::I64, Op(op))) return false; break; case uint16_t(Op::I64TruncSF64): case uint16_t(Op::I64TruncUF64): case uint16_t(Op::I64ReinterpretF64): if (!AstDecodeConversion(c, ValType::F64, ValType::I64, Op(op))) return false; break; case uint16_t(Op::F32ConvertSI32): case uint16_t(Op::F32ConvertUI32): case uint16_t(Op::F32ReinterpretI32): if (!AstDecodeConversion(c, ValType::I32, ValType::F32, Op(op))) return false; break; case uint16_t(Op::F32ConvertSI64): case uint16_t(Op::F32ConvertUI64): if (!AstDecodeConversion(c, ValType::I64, ValType::F32, Op(op))) return false; break; case uint16_t(Op::F32DemoteF64): if (!AstDecodeConversion(c, ValType::F64, ValType::F32, Op(op))) return false; break; case uint16_t(Op::F64ConvertSI32): case uint16_t(Op::F64ConvertUI32): if (!AstDecodeConversion(c, ValType::I32, ValType::F64, Op(op))) return false; break; case uint16_t(Op::F64ConvertSI64): case uint16_t(Op::F64ConvertUI64): case uint16_t(Op::F64ReinterpretI64): if (!AstDecodeConversion(c, ValType::I64, ValType::F64, Op(op))) return false; break; case uint16_t(Op::F64PromoteF32): if (!AstDecodeConversion(c, ValType::F32, ValType::F64, Op(op))) return false; break; case uint16_t(Op::I32Load8S): case uint16_t(Op::I32Load8U): if (!AstDecodeLoad(c, ValType::I32, 1, Op(op))) return false; break; case uint16_t(Op::I32Load16S): case uint16_t(Op::I32Load16U): if (!AstDecodeLoad(c, ValType::I32, 2, Op(op))) return false; break; case uint16_t(Op::I32Load): if (!AstDecodeLoad(c, ValType::I32, 4, Op(op))) return false; break; case uint16_t(Op::I64Load8S): case uint16_t(Op::I64Load8U): if (!AstDecodeLoad(c, ValType::I64, 1, Op(op))) return false; break; case uint16_t(Op::I64Load16S): case uint16_t(Op::I64Load16U): if (!AstDecodeLoad(c, ValType::I64, 2, Op(op))) return false; break; case uint16_t(Op::I64Load32S): case uint16_t(Op::I64Load32U): if (!AstDecodeLoad(c, ValType::I64, 4, Op(op))) return false; break; case uint16_t(Op::I64Load): if (!AstDecodeLoad(c, ValType::I64, 8, Op(op))) return false; break; case uint16_t(Op::F32Load): if (!AstDecodeLoad(c, ValType::F32, 4, Op(op))) return false; break; case uint16_t(Op::F64Load): if (!AstDecodeLoad(c, ValType::F64, 8, Op(op))) return false; break; case uint16_t(Op::I32Store8): if (!AstDecodeStore(c, ValType::I32, 1, Op(op))) return false; break; case uint16_t(Op::I32Store16): if (!AstDecodeStore(c, ValType::I32, 2, Op(op))) return false; break; case uint16_t(Op::I32Store): if (!AstDecodeStore(c, ValType::I32, 4, Op(op))) return false; break; case uint16_t(Op::I64Store8): if (!AstDecodeStore(c, ValType::I64, 1, Op(op))) return false; break; case uint16_t(Op::I64Store16): if (!AstDecodeStore(c, ValType::I64, 2, Op(op))) return false; break; case uint16_t(Op::I64Store32): if (!AstDecodeStore(c, ValType::I64, 4, Op(op))) return false; break; case uint16_t(Op::I64Store): if (!AstDecodeStore(c, ValType::I64, 8, Op(op))) return false; break; case uint16_t(Op::F32Store): if (!AstDecodeStore(c, ValType::F32, 4, Op(op))) return false; break; case uint16_t(Op::F64Store): if (!AstDecodeStore(c, ValType::F64, 8, Op(op))) return false; break; case uint16_t(Op::CurrentMemory): if (!AstDecodeCurrentMemory(c)) return false; break; case uint16_t(Op::GrowMemory): if (!AstDecodeGrowMemory(c)) return false; break; case uint16_t(Op::SetGlobal): if (!AstDecodeSetGlobal(c)) return false; break; case uint16_t(Op::GetGlobal): if (!AstDecodeGetGlobal(c)) return false; break; case uint16_t(Op::Br): case uint16_t(Op::BrIf): if (!AstDecodeBranch(c, Op(op))) return false; break; case uint16_t(Op::BrTable): if (!AstDecodeBrTable(c)) return false; break; case uint16_t(Op::Return): if (!AstDecodeReturn(c)) return false; break; case uint16_t(Op::Unreachable): if (!c.iter().readUnreachable()) return false; tmp = new(c.lifo) AstUnreachable(); if (!tmp) return false; if (!c.push(AstDecodeStackItem(tmp))) return false; break; default: return c.iter().unrecognizedOpcode(op); } AstExpr* lastExpr = c.top().expr; if (lastExpr) lastExpr->setOffset(exprOffset); return true; } /*****************************************************************************/ // wasm decoding and generation static bool AstDecodeTypeSection(AstDecodeContext& c, SigWithIdVector* sigs) { if (!DecodeTypeSection(c.d, sigs)) return false; for (size_t sigIndex = 0; sigIndex < sigs->length(); sigIndex++) { const Sig& sig = (*sigs)[sigIndex]; AstValTypeVector args(c.lifo); if (!args.appendAll(sig.args())) return false; AstSig sigNoName(Move(args), sig.ret()); AstName sigName; if (!GenerateName(c, AstName(u"type"), sigIndex, &sigName)) return false; AstSig* astSig = new(c.lifo) AstSig(sigName, Move(sigNoName)); if (!astSig || !c.module().append(astSig)) return false; } return true; } static AstName ToAstName(AstDecodeContext& c, const UniqueChars& name) { size_t len = strlen(name.get()); char16_t* buffer = static_cast(c.lifo.alloc(len * sizeof(char16_t))); if (!buffer) return AstName(); for (size_t i = 0; i < len; i++) buffer[i] = name.get()[i]; return AstName(buffer, len); } static bool AstDecodeImportSection(AstDecodeContext& c, const SigWithIdVector& sigs) { Uint32Vector funcSigIndices; GlobalDescVector globals; TableDescVector tables; Maybe memory; ImportVector imports; if (!DecodeImportSection(c.d, sigs, &funcSigIndices, &globals, &tables, &memory, &imports)) return false; size_t lastFunc = 0; size_t lastGlobal = 0; size_t lastTable = 0; size_t lastMemory = 0; for (size_t importIndex = 0; importIndex < imports.length(); importIndex++) { const Import& import = imports[importIndex]; AstName moduleName = ToAstName(c, import.module); AstName fieldName = ToAstName(c, import.field); AstImport* ast = nullptr; switch (import.kind) { case DefinitionKind::Function: { AstName importName; if (!GenerateName(c, AstName(u"import"), lastFunc, &importName)) return false; AstRef sigRef; if (!GenerateRef(c, AstName(u"type"), funcSigIndices[lastFunc], &sigRef)) return false; ast = new(c.lifo) AstImport(importName, moduleName, fieldName, sigRef); lastFunc++; break; } case DefinitionKind::Global: { AstName importName; if (!GenerateName(c, AstName(u"global"), lastGlobal, &importName)) return false; const GlobalDesc& global = globals[lastGlobal]; ValType type = global.type(); bool isMutable = global.isMutable(); if (!c.addGlobalDesc(type, isMutable, /* import */ true)) return false; ast = new(c.lifo) AstImport(importName, moduleName, fieldName, AstGlobal(importName, type, isMutable)); lastGlobal++; break; } case DefinitionKind::Table: { AstName importName; if (!GenerateName(c, AstName(u"table"), lastTable, &importName)) return false; ast = new(c.lifo) AstImport(importName, moduleName, fieldName, DefinitionKind::Table, tables[lastTable].limits); lastTable++; break; } case DefinitionKind::Memory: { AstName importName; if (!GenerateName(c, AstName(u"memory"), lastMemory, &importName)) return false; ast = new(c.lifo) AstImport(importName, moduleName, fieldName, DefinitionKind::Memory, *memory); lastMemory++; break; } } if (!ast || !c.module().append(ast)) return false; } return true; } static bool AstDecodeFunctionSection(AstDecodeContext& c, const SigWithIdVector& sigs) { Uint32Vector funcSigIndexes; if (!DecodeFunctionSection(c.d, sigs, c.module().numFuncImports(), &funcSigIndexes)) return false; return c.funcDefSigs().appendAll(funcSigIndexes); } static bool AstDecodeTableSection(AstDecodeContext& c) { uint32_t sectionStart, sectionSize; if (!c.d.startSection(SectionId::Table, §ionStart, §ionSize, "table")) return false; if (sectionStart == Decoder::NotStarted) return true; uint32_t numTables; if (!c.d.readVarU32(&numTables)) return c.d.fail("failed to read number of tables"); if (numTables != 1) return c.d.fail("the number of tables must be exactly one"); uint32_t typeConstructorValue; if (!c.d.readVarU32(&typeConstructorValue)) return c.d.fail("expected type constructor kind"); if (typeConstructorValue != uint32_t(TypeCode::AnyFunc)) return c.d.fail("unknown type constructor kind"); Limits table; if (!DecodeLimits(c.d, &table)) return false; if (table.initial > MaxTableElems) return c.d.fail("too many table elements"); if (c.module().hasTable()) return c.d.fail("already have a table"); AstName name; if (!GenerateName(c, AstName(u"table"), c.module().tables().length(), &name)) return false; if (!c.module().addTable(name, table)) return false; if (!c.d.finishSection(sectionStart, sectionSize, "table")) return false; return true; } static bool AstDecodeName(AstDecodeContext& c, AstName* name) { uint32_t length; if (!c.d.readVarU32(&length)) return false; const uint8_t* bytes; if (!c.d.readBytes(length, &bytes)) return false; char16_t* buffer = static_cast(c.lifo.alloc(length * sizeof(char16_t))); for (size_t i = 0; i < length; i++) buffer[i] = bytes[i]; *name = AstName(buffer, length); return true; } static bool AstDecodeMemorySection(AstDecodeContext& c) { bool present; Limits memory; if (!DecodeMemorySection(c.d, c.module().hasMemory(), &memory, &present)) return false; if (present) { AstName name; if (!GenerateName(c, AstName(u"memory"), c.module().memories().length(), &name)) return false; if (!c.module().addMemory(name, memory)) return false; } return true; } static AstExpr* ToAstExpr(AstDecodeContext& c, const InitExpr& initExpr) { switch (initExpr.kind()) { case InitExpr::Kind::Constant: { return new(c.lifo) AstConst(Val(initExpr.val())); } case InitExpr::Kind::GetGlobal: { AstRef globalRef; if (!GenerateRef(c, AstName(u"global"), initExpr.globalIndex(), &globalRef)) return nullptr; return new(c.lifo) AstGetGlobal(globalRef); } } return nullptr; } static bool AstDecodeInitializerExpression(AstDecodeContext& c, ValType type, AstExpr** init) { InitExpr initExpr; if (!DecodeInitializerExpression(c.d, c.globalDescs(), type, &initExpr)) return false; *init = ToAstExpr(c, initExpr); return !!*init; } static bool AstDecodeGlobal(AstDecodeContext& c, uint32_t i, AstGlobal* global) { AstName name; if (!GenerateName(c, AstName(u"global"), i, &name)) return false; ValType type; bool isMutable; if (!DecodeGlobalType(c.d, &type, &isMutable)) return false; AstExpr* init; if (!AstDecodeInitializerExpression(c, type, &init)) return false; if (!c.addGlobalDesc(type, isMutable, /* import */ false)) return false; *global = AstGlobal(name, type, isMutable, Some(init)); return true; } static bool AstDecodeGlobalSection(AstDecodeContext& c) { uint32_t sectionStart, sectionSize; if (!c.d.startSection(SectionId::Global, §ionStart, §ionSize, "global")) return false; if (sectionStart == Decoder::NotStarted) return true; uint32_t numGlobals; if (!c.d.readVarU32(&numGlobals)) return c.d.fail("expected number of globals"); uint32_t numImported = c.globalDescs().length(); for (uint32_t i = 0; i < numGlobals; i++) { auto* global = new(c.lifo) AstGlobal; if (!AstDecodeGlobal(c, i + numImported, global)) return false; if (!c.module().append(global)) return false; } if (!c.d.finishSection(sectionStart, sectionSize, "global")) return false; return true; } static bool AstDecodeExport(AstDecodeContext& c, AstExport** export_) { AstName fieldName; if (!AstDecodeName(c, &fieldName)) return c.d.fail("expected export name"); uint32_t kindValue; if (!c.d.readVarU32(&kindValue)) return c.d.fail("expected export kind"); uint32_t index; if (!c.d.readVarU32(&index)) return c.d.fail("expected export internal index"); *export_ = new(c.lifo) AstExport(fieldName, DefinitionKind(kindValue), AstRef(index)); if (!*export_) return false; return true; } static bool AstDecodeExportSection(AstDecodeContext& c) { uint32_t sectionStart, sectionSize; if (!c.d.startSection(SectionId::Export, §ionStart, §ionSize, "export")) return false; if (sectionStart == Decoder::NotStarted) return true; uint32_t numExports; if (!c.d.readVarU32(&numExports)) return c.d.fail("failed to read number of exports"); if (numExports > MaxExports) return c.d.fail("too many exports"); for (uint32_t i = 0; i < numExports; i++) { AstExport* export_ = nullptr; if (!AstDecodeExport(c, &export_)) return false; if (!c.module().append(export_)) return false; } if (!c.d.finishSection(sectionStart, sectionSize, "export")) return false; return true; } static bool AstDecodeFunctionBody(AstDecodeContext &c, uint32_t funcDefIndex, AstFunc** func) { uint32_t offset = c.d.currentOffset(); uint32_t bodySize; if (!c.d.readVarU32(&bodySize)) return c.d.fail("expected number of function body bytes"); if (c.d.bytesRemain() < bodySize) return c.d.fail("function body length too big"); const uint8_t* bodyBegin = c.d.currentPosition(); const uint8_t* bodyEnd = bodyBegin + bodySize; AstDecodeOpIter iter(c.d); uint32_t sigIndex = c.funcDefSigs()[funcDefIndex]; const AstSig* sig = c.module().sigs()[sigIndex]; AstValTypeVector vars(c.lifo); AstNameVector localsNames(c.lifo); AstExprVector body(c.lifo); ValTypeVector locals; if (!locals.appendAll(sig->args())) return false; if (!DecodeLocalEntries(c.d, ModuleKind::Wasm, &locals)) return c.d.fail("failed decoding local entries"); c.startFunction(&iter, &locals, sig->ret()); AstName funcName; if (!GenerateName(c, AstName(u"func"), c.module().numFuncImports() + funcDefIndex, &funcName)) return false; uint32_t numParams = sig->args().length(); uint32_t numLocals = locals.length(); for (uint32_t i = numParams; i < numLocals; i++) { if (!vars.append(locals[i])) return false; } for (uint32_t i = 0; i < numLocals; i++) { AstName varName; if (!GenerateName(c, AstName(u"var"), i, &varName)) return false; if (!localsNames.append(varName)) return false; } if (!c.iter().readFunctionStart(sig->ret())) return false; if (!c.depths().append(c.exprs().length())) return false; while (c.d.currentPosition() < bodyEnd) { if (!AstDecodeExpr(c)) return false; const AstDecodeStackItem& item = c.top(); if (!item.expr) { // Op::End was found c.popBack(); break; } } for (auto i = c.exprs().begin() + c.depths().back(), e = c.exprs().end(); i != e; ++i) { if (!body.append(i->expr)) return false; } c.exprs().shrinkTo(c.depths().popCopy()); if (!c.iter().readFunctionEnd()) return false; c.endFunction(); if (c.d.currentPosition() != bodyEnd) return c.d.fail("function body length mismatch"); AstRef sigRef; if (!GenerateRef(c, AstName(u"type"), sigIndex, &sigRef)) return false; *func = new(c.lifo) AstFunc(funcName, sigRef, Move(vars), Move(localsNames), Move(body)); if (!*func) return false; (*func)->setOffset(offset); return true; } static bool AstDecodeCodeSection(AstDecodeContext &c) { uint32_t sectionStart, sectionSize; if (!c.d.startSection(SectionId::Code, §ionStart, §ionSize, "code")) return false; if (sectionStart == Decoder::NotStarted) { if (c.funcDefSigs().length() != 0) return c.d.fail("expected function bodies"); return false; } uint32_t numFuncBodies; if (!c.d.readVarU32(&numFuncBodies)) return c.d.fail("expected function body count"); if (numFuncBodies != c.funcDefSigs().length()) return c.d.fail("function body count does not match function signature count"); for (uint32_t funcDefIndex = 0; funcDefIndex < numFuncBodies; funcDefIndex++) { AstFunc* func; if (!AstDecodeFunctionBody(c, funcDefIndex, &func)) return false; if (!c.module().append(func)) return false; } if (!c.d.finishSection(sectionStart, sectionSize, "code")) return false; return true; } // Number of bytes to display in a single fragment of a data section (per line). static const size_t WRAP_DATA_BYTES = 30; static bool AstDecodeDataSection(AstDecodeContext &c) { DataSegmentVector segments; bool hasMemory = c.module().hasMemory(); MOZ_ASSERT(c.module().memories().length() <= 1, "at most one memory in MVP"); uint32_t memByteLength = hasMemory ? c.module().memories()[0].limits.initial : 0; if (!DecodeDataSection(c.d, hasMemory, memByteLength, c.globalDescs(), &segments)) return false; for (DataSegment& s : segments) { const uint8_t* src = c.d.begin() + s.bytecodeOffset; char16_t* buffer = static_cast(c.lifo.alloc(s.length * sizeof(char16_t))); for (size_t i = 0; i < s.length; i++) buffer[i] = src[i]; AstExpr* offset = ToAstExpr(c, s.offset); if (!offset) return false; AstNameVector fragments(c.lifo); for (size_t start = 0; start < s.length; start += WRAP_DATA_BYTES) { AstName name(buffer + start, Min(WRAP_DATA_BYTES, s.length - start)); if (!fragments.append(name)) return false; } AstDataSegment* segment = new(c.lifo) AstDataSegment(offset, Move(fragments)); if (!segment || !c.module().append(segment)) return false; } return true; } static bool AstDecodeElemSection(AstDecodeContext &c) { uint32_t sectionStart, sectionSize; if (!c.d.startSection(SectionId::Elem, §ionStart, §ionSize, "elem")) return false; if (sectionStart == Decoder::NotStarted) return true; uint32_t numElems; if (!c.d.readVarU32(&numElems)) return c.d.fail("failed to read number of table elements"); for (uint32_t i = 0; i < numElems; i++) { uint32_t tableIndex; if (!c.d.readVarU32(&tableIndex)) return c.d.fail("expected table index for element"); if (tableIndex != 0) return c.d.fail("non-zero table index for element"); AstExpr* offset; if (!AstDecodeInitializerExpression(c, ValType::I32, &offset)) return false; uint32_t count; if (!c.d.readVarU32(&count)) return c.d.fail("expected element count"); AstRefVector elems(c.lifo); if (!elems.resize(count)) return false; for (uint32_t i = 0; i < count; i++) { uint32_t index; if (!c.d.readVarU32(&index)) return c.d.fail("expected element index"); elems[i] = AstRef(index); } AstElemSegment* segment = new(c.lifo) AstElemSegment(offset, Move(elems)); if (!segment || !c.module().append(segment)) return false; } if (!c.d.finishSection(sectionStart, sectionSize, "elem")) return false; return true; } static bool AstDecodeStartSection(AstDecodeContext &c) { uint32_t sectionStart, sectionSize; if (!c.d.startSection(SectionId::Start, §ionStart, §ionSize, "start")) return false; if (sectionStart == Decoder::NotStarted) return true; uint32_t funcIndex; if (!c.d.readVarU32(&funcIndex)) return c.d.fail("failed to read start func index"); AstRef funcRef; if (!GenerateRef(c, AstName(u"func"), funcIndex, &funcRef)) return false; c.module().setStartFunc(AstStartFunc(funcRef)); if (!c.d.finishSection(sectionStart, sectionSize, "start")) return false; return true; } bool wasm::BinaryToAst(JSContext* cx, const uint8_t* bytes, uint32_t length, LifoAlloc& lifo, AstModule** module) { AstModule* result = new(lifo) AstModule(lifo); if (!result->init()) return false; UniqueChars error; Decoder d(bytes, bytes + length, &error); AstDecodeContext c(cx, lifo, d, *result, true); SigWithIdVector sigs; if (!DecodePreamble(d) || !AstDecodeTypeSection(c, &sigs) || !AstDecodeImportSection(c, sigs) || !AstDecodeFunctionSection(c, sigs) || !AstDecodeTableSection(c) || !AstDecodeMemorySection(c) || !AstDecodeGlobalSection(c) || !AstDecodeExportSection(c) || !AstDecodeStartSection(c) || !AstDecodeElemSection(c) || !AstDecodeCodeSection(c) || !AstDecodeDataSection(c) || !DecodeUnknownSections(c.d)) { if (error) { JS_ReportErrorNumberASCII(c.cx, GetErrorMessage, nullptr, JSMSG_WASM_COMPILE_ERROR, error.get()); return false; } ReportOutOfMemory(c.cx); return false; } MOZ_ASSERT(!error, "unreported error in decoding"); *module = result; return true; }