WebAssembly: validate load / store alignment
[WebKit-https.git] / Source / JavaScriptCore / wasm / WasmFunctionParser.h
index c4147c5..316bf53 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 Apple Inc. All rights reserved.
+ * Copyright (C) 2016-2017 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -35,635 +35,610 @@ namespace JSC { namespace Wasm {
 enum class BlockType {
     If,
     Block,
-    Loop
+    Loop,
+    TopLevel
 };
 
 template<typename Context>
-class FunctionParser : public Parser {
+class FunctionParser : public Parser<void> {
 public:
     typedef typename Context::ExpressionType ExpressionType;
     typedef typename Context::ControlType ControlType;
     typedef typename Context::ExpressionList ExpressionList;
 
-    FunctionParser(Context&, const uint8_t* functionStart, size_t functionLength, const Signature*, const ImmutableFunctionIndexSpace&, const ModuleInformation&);
+    FunctionParser(Context&, const uint8_t* functionStart, size_t functionLength, const Signature&, const ModuleInformation&);
 
-    bool WARN_UNUSED_RETURN parse();
+    Result WARN_UNUSED_RETURN parse();
 
     struct ControlEntry {
         ExpressionList enclosedExpressionStack;
         ControlType controlData;
     };
 
+    OpType currentOpcode() const { return m_currentOpcode; }
+    size_t currentOpcodeStartingOffset() const { return m_currentOpcodeStartingOffset; }
+
 private:
     static const bool verbose = false;
 
-    bool WARN_UNUSED_RETURN parseBody();
-    bool WARN_UNUSED_RETURN parseExpression(OpType);
-    bool WARN_UNUSED_RETURN parseUnreachableExpression(OpType);
-    bool WARN_UNUSED_RETURN addReturn();
-    bool WARN_UNUSED_RETURN unifyControl(Vector<ExpressionType>&, unsigned level);
+    PartialResult WARN_UNUSED_RETURN parseBody();
+    PartialResult WARN_UNUSED_RETURN parseExpression();
+    PartialResult WARN_UNUSED_RETURN parseUnreachableExpression();
+    PartialResult WARN_UNUSED_RETURN unifyControl(Vector<ExpressionType>&, unsigned level);
 
-    bool WARN_UNUSED_RETURN popExpressionStack(ExpressionType& result);
+#define WASM_TRY_POP_EXPRESSION_STACK_INTO(result, what) do {                               \
+        WASM_PARSER_FAIL_IF(m_expressionStack.isEmpty(), "can't pop empty stack in " what); \
+        result = m_expressionStack.takeLast();                                              \
+    } while (0)
 
     template<OpType>
-    bool WARN_UNUSED_RETURN unaryCase();
+    PartialResult WARN_UNUSED_RETURN unaryCase();
 
     template<OpType>
-    bool WARN_UNUSED_RETURN binaryCase();
+    PartialResult WARN_UNUSED_RETURN binaryCase();
 
-    bool setErrorMessage(String&& message)
-    {
-        m_context.setErrorMessage(WTFMove(message));
-        return false;
-    }
+#define WASM_TRY_ADD_TO_CONTEXT(add_expression) WASM_FAIL_IF_HELPER_FAILS(m_context.add_expression)
+
+    // FIXME add a macro as above for WASM_TRY_APPEND_TO_CONTROL_STACK https://bugs.webkit.org/show_bug.cgi?id=165862
 
     Context& m_context;
     ExpressionList m_expressionStack;
     Vector<ControlEntry> m_controlStack;
-    const Signature* m_signature;
-    const ImmutableFunctionIndexSpace& m_functionIndexSpace;
+    const Signature& m_signature;
     const ModuleInformation& m_info;
+
+    OpType m_currentOpcode;
+    size_t m_currentOpcodeStartingOffset { 0 };
+
     unsigned m_unreachableBlocks { 0 };
 };
 
 template<typename Context>
-FunctionParser<Context>::FunctionParser(Context& context, const uint8_t* functionStart, size_t functionLength, const Signature* signature, const ImmutableFunctionIndexSpace& functionIndexSpace, const ModuleInformation& info)
+FunctionParser<Context>::FunctionParser(Context& context, const uint8_t* functionStart, size_t functionLength, const Signature& signature, const ModuleInformation& info)
     : Parser(functionStart, functionLength)
     , m_context(context)
     , m_signature(signature)
-    , m_functionIndexSpace(functionIndexSpace)
     , m_info(info)
 {
     if (verbose)
         dataLogLn("Parsing function starting at: ", (uintptr_t)functionStart, " of length: ", functionLength);
+    m_context.setParser(this);
 }
 
 template<typename Context>
-bool FunctionParser<Context>::parse()
+auto FunctionParser<Context>::parse() -> Result
 {
-    if (!m_context.addArguments(m_signature->arguments))
-        return false;
-
     uint32_t localCount;
-    if (!parseVarUInt32(localCount))
-        return false;
+
+    WASM_PARSER_FAIL_IF(!m_context.addArguments(m_signature), "can't add ", m_signature.argumentCount(), " arguments to Function");
+    WASM_PARSER_FAIL_IF(!parseVarUInt32(localCount), "can't get local count");
+    WASM_PARSER_FAIL_IF(localCount == std::numeric_limits<uint32_t>::max(), "Function section's local count is too big ", localCount);
 
     for (uint32_t i = 0; i < localCount; ++i) {
         uint32_t numberOfLocals;
-        if (!parseVarUInt32(numberOfLocals))
-            return false;
-
         Type typeOfLocal;
-        if (!parseValueType(typeOfLocal))
-            return false;
 
-        if (!m_context.addLocal(typeOfLocal, numberOfLocals))
-            return false;
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(numberOfLocals), "can't get Function's number of locals in group ", i);
+        WASM_PARSER_FAIL_IF(numberOfLocals == std::numeric_limits<uint32_t>::max(), "Function section's ", i, "th local group count is too big ", numberOfLocals);
+        WASM_PARSER_FAIL_IF(!parseValueType(typeOfLocal), "can't get Function local's type in group ", i);
+        WASM_TRY_ADD_TO_CONTEXT(addLocal(typeOfLocal, numberOfLocals));
     }
 
-    return parseBody();
+    WASM_FAIL_IF_HELPER_FAILS(parseBody());
+
+    return { };
 }
 
 template<typename Context>
-bool FunctionParser<Context>::parseBody()
+auto FunctionParser<Context>::parseBody() -> PartialResult
 {
-    while (true) {
-        uint8_t op;
-        if (!parseUInt8(op) || !isValidOpType(op)) {
-            if (verbose)
-                WTF::dataLogLn("attempted to decode invalid op: ", RawPointer(reinterpret_cast<void*>(op)), " at offset: ", RawPointer(reinterpret_cast<void*>(m_offset)));
-            return false;
-        }
+    m_controlStack.append({ ExpressionList(), m_context.addTopLevel(m_signature.returnType()) });
+    uint8_t op;
+    while (m_controlStack.size()) {
+        m_currentOpcodeStartingOffset = m_offset;
+        WASM_PARSER_FAIL_IF(!parseUInt8(op), "can't decode opcode");
+        WASM_PARSER_FAIL_IF(!isValidOpType(op), "invalid opcode ", op);
 
-        if (verbose) {
-            dataLogLn("processing op (", m_unreachableBlocks, "): ",  RawPointer(reinterpret_cast<void*>(op)), " at offset: ", RawPointer(reinterpret_cast<void*>(m_offset)));
-            m_context.dump(m_controlStack, m_expressionStack);
-        }
+        m_currentOpcode = static_cast<OpType>(op);
 
-        if (op == OpType::End && !m_controlStack.size())
-            return m_unreachableBlocks ? true : addReturn();
-
-        if (m_unreachableBlocks) {
-            if (!parseUnreachableExpression(static_cast<OpType>(op))) {
-                if (verbose)
-                    dataLogLn("failed to process unreachable op:", op);
-                return false;
-            }
-        } else if (!parseExpression(static_cast<OpType>(op))) {
-            if (verbose)
-                dataLogLn("failed to process op:", op);
-            return false;
+        if (verbose) {
+            dataLogLn("processing op (", m_unreachableBlocks, "): ",  RawPointer(reinterpret_cast<void*>(op)), ", ", makeString(static_cast<OpType>(op)), " at offset: ", RawPointer(reinterpret_cast<void*>(m_offset)));
+            m_context.dump(m_controlStack, &m_expressionStack);
         }
 
+        if (m_unreachableBlocks)
+            WASM_FAIL_IF_HELPER_FAILS(parseUnreachableExpression());
+        else
+            WASM_FAIL_IF_HELPER_FAILS(parseExpression());
     }
 
-    RELEASE_ASSERT_NOT_REACHED();
-}
-
-template<typename Context>
-bool FunctionParser<Context>::addReturn()
-{
-    ExpressionList returnValues;
-    if (m_signature->returnType != Void) {
-        ExpressionType returnValue;
-        if (!popExpressionStack(returnValue))
-            return false;
-        returnValues.append(returnValue);
-    }
-
-    m_unreachableBlocks = 1;
-    return m_context.addReturn(returnValues);
+    ASSERT(op == OpType::End);
+    return { };
 }
 
 template<typename Context>
 template<OpType op>
-bool FunctionParser<Context>::binaryCase()
+auto FunctionParser<Context>::binaryCase() -> PartialResult
 {
     ExpressionType right;
-    if (!popExpressionStack(right))
-        return false;
-
     ExpressionType left;
-    if (!popExpressionStack(left))
-        return false;
-
     ExpressionType result;
-    if (!m_context.template addOp<op>(left, right, result))
-        return false;
+
+    WASM_TRY_POP_EXPRESSION_STACK_INTO(right, "binary right");
+    WASM_TRY_POP_EXPRESSION_STACK_INTO(left, "binary left");
+    WASM_TRY_ADD_TO_CONTEXT(template addOp<op>(left, right, result));
+
     m_expressionStack.append(result);
-    return true;
+    return { };
 }
 
 template<typename Context>
 template<OpType op>
-bool FunctionParser<Context>::unaryCase()
+auto FunctionParser<Context>::unaryCase() -> PartialResult
 {
     ExpressionType value;
-    if (!popExpressionStack(value))
-        return false;
-
     ExpressionType result;
-    if (!m_context.template addOp<op>(value, result))
-        return false;
+
+    WASM_TRY_POP_EXPRESSION_STACK_INTO(value, "unary");
+    WASM_TRY_ADD_TO_CONTEXT(template addOp<op>(value, result));
+
     m_expressionStack.append(result);
-    return true;
+    return { };
 }
 
 template<typename Context>
-bool FunctionParser<Context>::parseExpression(OpType op)
+auto FunctionParser<Context>::parseExpression() -> PartialResult
 {
-    switch (op) {
+    switch (m_currentOpcode) {
 #define CREATE_CASE(name, id, b3op, inc) case OpType::name: return binaryCase<OpType::name>();
-    FOR_EACH_WASM_SIMPLE_BINARY_OP(CREATE_CASE)
+    FOR_EACH_WASM_BINARY_OP(CREATE_CASE)
 #undef CREATE_CASE
 
-    case OpType::F32ConvertUI64: return unaryCase<OpType::F32ConvertUI64>();
-    case OpType::F64ConvertUI64: return unaryCase<OpType::F64ConvertUI64>();
-    case OpType::F32Nearest: return unaryCase<OpType::F32Nearest>();
-    case OpType::F64Nearest: return unaryCase<OpType::F64Nearest>();
-    case OpType::F32Trunc: return unaryCase<OpType::F32Trunc>();
-    case OpType::F64Trunc: return unaryCase<OpType::F64Trunc>();
-    case OpType::I32Ctz: return unaryCase<OpType::I32Ctz>();
-    case OpType::I64Ctz: return unaryCase<OpType::I64Ctz>();
-    case OpType::I32Popcnt: return unaryCase<OpType::I32Popcnt>();
-    case OpType::I64Popcnt: return unaryCase<OpType::I64Popcnt>();
-    case OpType::I32TruncSF32: return unaryCase<OpType::I32TruncSF32>();
-    case OpType::I32TruncUF32: return unaryCase<OpType::I32TruncUF32>();
-    case OpType::I32TruncSF64: return unaryCase<OpType::I32TruncSF64>();
-    case OpType::I32TruncUF64: return unaryCase<OpType::I32TruncUF64>();
-    case OpType::I64TruncSF32: return unaryCase<OpType::I64TruncSF32>();
-    case OpType::I64TruncUF32: return unaryCase<OpType::I64TruncUF32>();
-    case OpType::I64TruncSF64: return unaryCase<OpType::I64TruncSF64>();
-    case OpType::I64TruncUF64: return unaryCase<OpType::I64TruncUF64>();
 #define CREATE_CASE(name, id, b3op, inc) case OpType::name: return unaryCase<OpType::name>();
-    FOR_EACH_WASM_SIMPLE_UNARY_OP(CREATE_CASE)
+    FOR_EACH_WASM_UNARY_OP(CREATE_CASE)
 #undef CREATE_CASE
 
-    case OpType::Select: {
+    case Select: {
         ExpressionType condition;
-        if (!popExpressionStack(condition))
-            return false;
-
         ExpressionType zero;
-        if (!popExpressionStack(zero))
-            return false;
-
         ExpressionType nonZero;
-        if (!popExpressionStack(nonZero))
-            return false;
+
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(condition, "select condition");
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(zero, "select zero");
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(nonZero, "select non-zero");
 
         ExpressionType result;
-        if (!m_context.addSelect(condition, nonZero, zero, result))
-            return false;
+        WASM_TRY_ADD_TO_CONTEXT(addSelect(condition, nonZero, zero, result));
 
         m_expressionStack.append(result);
-        return true;
+        return { };
     }
 
 #define CREATE_CASE(name, id, b3op, inc) case OpType::name:
     FOR_EACH_WASM_MEMORY_LOAD_OP(CREATE_CASE) {
         uint32_t alignment;
-        if (!parseVarUInt32(alignment))
-            return false;
-
         uint32_t offset;
-        if (!parseVarUInt32(offset))
-            return false;
-
         ExpressionType pointer;
-        if (!popExpressionStack(pointer))
-            return false;
-
         ExpressionType result;
-        if (!m_context.load(static_cast<LoadOpType>(op), pointer, result, offset))
-            return false;
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(alignment), "can't get load alignment");
+        WASM_PARSER_FAIL_IF(alignment > memoryLog2Alignment(m_currentOpcode), "alignment cannot exceed load's natural alignment");
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(offset), "can't get load offset");
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(pointer, "load pointer");
+        WASM_TRY_ADD_TO_CONTEXT(load(static_cast<LoadOpType>(m_currentOpcode), pointer, result, offset));
         m_expressionStack.append(result);
-        return true;
+        return { };
     }
 
     FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) {
         uint32_t alignment;
-        if (!parseVarUInt32(alignment))
-            return false;
-
         uint32_t offset;
-        if (!parseVarUInt32(offset))
-            return false;
-
         ExpressionType value;
-        if (!popExpressionStack(value))
-            return false;
-
         ExpressionType pointer;
-        if (!popExpressionStack(pointer))
-            return false;
-
-        return m_context.store(static_cast<StoreOpType>(op), pointer, value, offset);
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(alignment), "can't get store alignment");
+        WASM_PARSER_FAIL_IF(alignment > memoryLog2Alignment(m_currentOpcode), "alignment cannot exceed store's natural alignment");
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(offset), "can't get store offset");
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(value, "store value");
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(pointer, "store pointer");
+        WASM_TRY_ADD_TO_CONTEXT(store(static_cast<StoreOpType>(m_currentOpcode), pointer, value, offset));
+        return { };
     }
 #undef CREATE_CASE
 
-    case OpType::F32Const:
-    case OpType::I32Const: {
+    case F32Const: {
         uint32_t constant;
-        if (!parseVarUInt32(constant))
-            return false;
+        WASM_PARSER_FAIL_IF(!parseUInt32(constant), "can't parse 32-bit floating-point constant");
+        m_expressionStack.append(m_context.addConstant(F32, constant));
+        return { };
+    }
+
+    case I32Const: {
+        int32_t constant;
+        WASM_PARSER_FAIL_IF(!parseVarInt32(constant), "can't parse 32-bit constant");
         m_expressionStack.append(m_context.addConstant(I32, constant));
-        return true;
+        return { };
     }
 
-    case OpType::F64Const:
-    case OpType::I64Const: {
+    case F64Const: {
         uint64_t constant;
-        if (!parseVarUInt64(constant))
-            return false;
+        WASM_PARSER_FAIL_IF(!parseUInt64(constant), "can't parse 64-bit floating-point constant");
+        m_expressionStack.append(m_context.addConstant(F64, constant));
+        return { };
+    }
+
+    case I64Const: {
+        int64_t constant;
+        WASM_PARSER_FAIL_IF(!parseVarInt64(constant), "can't parse 64-bit constant");
         m_expressionStack.append(m_context.addConstant(I64, constant));
-        return true;
+        return { };
     }
 
-    case OpType::GetLocal: {
+    case GetLocal: {
         uint32_t index;
-        if (!parseVarUInt32(index))
-            return false;
         ExpressionType result;
-        if (!m_context.getLocal(index, result))
-            return false;
-
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(index), "can't get index for get_local");
+        WASM_TRY_ADD_TO_CONTEXT(getLocal(index, result));
         m_expressionStack.append(result);
-        return true;
+        return { };
     }
 
-    case OpType::SetLocal: {
+    case SetLocal: {
         uint32_t index;
-        if (!parseVarUInt32(index))
-            return false;
         ExpressionType value;
-        if (!popExpressionStack(value))
-            return false;
-        return m_context.setLocal(index, value);
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(index), "can't get index for set_local");
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(value, "set_local");
+        WASM_TRY_ADD_TO_CONTEXT(setLocal(index, value));
+        return { };
     }
 
-    case OpType::TeeLocal: {
+    case TeeLocal: {
         uint32_t index;
-        if (!parseVarUInt32(index))
-            return false;
-        if (!m_expressionStack.size())
-            return false;
-        return m_context.setLocal(index, m_expressionStack.last());
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(index), "can't get index for tee_local");
+        WASM_PARSER_FAIL_IF(m_expressionStack.isEmpty(), "can't tee_local on empty expression stack");
+        WASM_TRY_ADD_TO_CONTEXT(setLocal(index, m_expressionStack.last()));
+        return { };
     }
 
-    case OpType::GetGlobal: {
+    case GetGlobal: {
         uint32_t index;
-        if (!parseVarUInt32(index))
-            return false;
         ExpressionType result;
-        if (!m_context.getGlobal(index, result))
-            return false;
-
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(index), "can't get get_global's index");
+        WASM_TRY_ADD_TO_CONTEXT(getGlobal(index, result));
         m_expressionStack.append(result);
-        return true;
+        return { };
     }
 
-    case OpType::SetGlobal: {
+    case SetGlobal: {
         uint32_t index;
-        if (!parseVarUInt32(index))
-            return false;
         ExpressionType value;
-        if (!popExpressionStack(value))
-            return false;
-        return m_context.setGlobal(index, value);
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(index), "can't get set_global's index");
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(value, "set_global value");
+        WASM_TRY_ADD_TO_CONTEXT(setGlobal(index, value));
+        return { };
     }
 
-    case OpType::Call: {
+    case Call: {
         uint32_t functionIndex;
-        if (!parseVarUInt32(functionIndex))
-            return false;
-
-        if (functionIndex >= m_functionIndexSpace.size)
-            return false;
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(functionIndex), "can't parse call's function index");
+        WASM_PARSER_FAIL_IF(functionIndex >= m_info.functionIndexSpaceSize(), "call function index ", functionIndex, " exceeds function index space ", m_info.functionIndexSpaceSize());
 
-        const Signature* calleeSignature = m_functionIndexSpace.buffer.get()[functionIndex].signature;
+        SignatureIndex calleeSignatureIndex = m_info.signatureIndexFromFunctionIndexSpace(functionIndex);
+        const Signature& calleeSignature = SignatureInformation::get(calleeSignatureIndex);
+        WASM_PARSER_FAIL_IF(calleeSignature.argumentCount() > m_expressionStack.size(), "call function index ", functionIndex, " has ", calleeSignature.argumentCount(), " arguments, but the expression stack currently holds ", m_expressionStack.size(), " values");
 
-        if (calleeSignature->arguments.size() > m_expressionStack.size())
-            return false;
-
-        size_t firstArgumentIndex = m_expressionStack.size() - calleeSignature->arguments.size();
+        size_t firstArgumentIndex = m_expressionStack.size() - calleeSignature.argumentCount();
         Vector<ExpressionType> args;
-        args.reserveInitialCapacity(calleeSignature->arguments.size());
-        for (unsigned i = firstArgumentIndex; i < m_expressionStack.size(); ++i)
-            args.append(m_expressionStack[i]);
+        WASM_PARSER_FAIL_IF(!args.tryReserveCapacity(calleeSignature.argumentCount()), "can't allocate enough memory for call's ", calleeSignature.argumentCount(), " arguments");
+        for (size_t i = firstArgumentIndex; i < m_expressionStack.size(); ++i)
+            args.uncheckedAppend(m_expressionStack[i]);
         m_expressionStack.shrink(firstArgumentIndex);
 
         ExpressionType result = Context::emptyExpression;
-        if (!m_context.addCall(functionIndex, calleeSignature, args, result))
-            return false;
+        WASM_TRY_ADD_TO_CONTEXT(addCall(functionIndex, calleeSignature, args, result));
 
         if (result != Context::emptyExpression)
             m_expressionStack.append(result);
 
-        return true;
+        return { };
     }
 
-    case OpType::CallIndirect: {
-        if (!m_info.tableInformation)
-            return setErrorMessage("call_indirect is only valid when a table is defined or imported");
+    case CallIndirect: {
         uint32_t signatureIndex;
-        if (!parseVarUInt32(signatureIndex))
-            return false;
-
         uint8_t reserved;
-        if (!parseVarUInt1(reserved))
-            return false;
-
-        if (reserved != 0)
-            return setErrorMessage("call_indirect 'reserved' varuint1 must be 0x0");
-
-        if (m_info.signatures.size() <= signatureIndex)
-            return setErrorMessage("Tried to use a signature outside the range of valid signatures");
+        WASM_PARSER_FAIL_IF(!m_info.tableInformation, "call_indirect is only valid when a table is defined or imported");
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(signatureIndex), "can't get call_indirect's signature index");
+        WASM_PARSER_FAIL_IF(!parseVarUInt1(reserved), "can't get call_indirect's reserved byte");
+        WASM_PARSER_FAIL_IF(reserved, "call_indirect's 'reserved' varuint1 must be 0x0");
+        WASM_PARSER_FAIL_IF(m_info.usedSignatures.size() <= signatureIndex, "call_indirect's signature index ", signatureIndex, " exceeds known signatures ", m_info.usedSignatures.size());
 
-        const Signature* calleeSignature = &m_info.signatures[signatureIndex];
-        size_t argumentCount = calleeSignature->arguments.size() + 1; // Add the callee's index.
-        if (argumentCount > m_expressionStack.size())
-            return setErrorMessage("Not enough values on the stack for call_indirect");
+        const Signature& calleeSignature = m_info.usedSignatures[signatureIndex].get();
+        size_t argumentCount = calleeSignature.argumentCount() + 1; // Add the callee's index.
+        WASM_PARSER_FAIL_IF(argumentCount > m_expressionStack.size(), "call_indirect expects ", argumentCount, " arguments, but the expression stack currently holds ", m_expressionStack.size(), " values");
 
         Vector<ExpressionType> args;
-        if (!args.tryReserveCapacity(argumentCount))
-            return setErrorMessage("Out of memory");
-
+        WASM_PARSER_FAIL_IF(!args.tryReserveCapacity(argumentCount), "can't allocate enough memory for ", argumentCount, " call_indirect arguments");
         size_t firstArgumentIndex = m_expressionStack.size() - argumentCount;
-        for (unsigned i = firstArgumentIndex; i < m_expressionStack.size(); ++i)
+        for (size_t i = firstArgumentIndex; i < m_expressionStack.size(); ++i)
             args.uncheckedAppend(m_expressionStack[i]);
         m_expressionStack.shrink(firstArgumentIndex);
 
         ExpressionType result = Context::emptyExpression;
-        if (!m_context.addCallIndirect(calleeSignature, args, result))
-            return false;
+        WASM_TRY_ADD_TO_CONTEXT(addCallIndirect(calleeSignature, args, result));
 
         if (result != Context::emptyExpression)
             m_expressionStack.append(result);
 
-        return true;
+        return { };
     }
 
-    case OpType::Block: {
+    case Block: {
         Type inlineSignature;
-        if (!parseResultType(inlineSignature))
-            return false;
-
+        WASM_PARSER_FAIL_IF(!parseResultType(inlineSignature), "can't get block's inline signature");
         m_controlStack.append({ WTFMove(m_expressionStack), m_context.addBlock(inlineSignature) });
         m_expressionStack = ExpressionList();
-        return true;
+        return { };
     }
 
-    case OpType::Loop: {
+    case Loop: {
         Type inlineSignature;
-        if (!parseResultType(inlineSignature))
-            return false;
-
+        WASM_PARSER_FAIL_IF(!parseResultType(inlineSignature), "can't get loop's inline signature");
         m_controlStack.append({ WTFMove(m_expressionStack), m_context.addLoop(inlineSignature) });
         m_expressionStack = ExpressionList();
-        return true;
+        return { };
     }
 
-    case OpType::If: {
+    case If: {
         Type inlineSignature;
-        if (!parseResultType(inlineSignature))
-            return false;
-
         ExpressionType condition;
-        if (!popExpressionStack(condition))
-            return false;
-
         ControlType control;
-        if (!m_context.addIf(condition, inlineSignature, control))
-            return false;
-
+        WASM_PARSER_FAIL_IF(!parseResultType(inlineSignature), "can't get if's inline signature");
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(condition, "if condition");
+        WASM_TRY_ADD_TO_CONTEXT(addIf(condition, inlineSignature, control));
         m_controlStack.append({ WTFMove(m_expressionStack), control });
         m_expressionStack = ExpressionList();
-        return true;
+        return { };
     }
 
-    case OpType::Else: {
-        if (!m_controlStack.size()) {
-            setErrorMessage("Attempted to use else block at the top-level of a function");
-            return false;
-        }
-
-        if (!m_context.addElse(m_controlStack.last().controlData, m_expressionStack))
-            return false;
+    case Else: {
+        WASM_PARSER_FAIL_IF(m_controlStack.size() == 1, "can't use else block at the top-level of a function");
+        WASM_TRY_ADD_TO_CONTEXT(addElse(m_controlStack.last().controlData, m_expressionStack));
         m_expressionStack.shrink(0);
-        return true;
+        return { };
     }
 
-    case OpType::Br:
-    case OpType::BrIf: {
+    case Br:
+    case BrIf: {
         uint32_t target;
-        if (!parseVarUInt32(target) || target >= m_controlStack.size())
-            return false;
-
         ExpressionType condition = Context::emptyExpression;
-        if (op == OpType::BrIf) {
-            if (!popExpressionStack(condition))
-                return false;
-        } else
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(target), "can't get br / br_if's target");
+        WASM_PARSER_FAIL_IF(target >= m_controlStack.size(), "br / br_if's target ", target, " exceeds control stack size ", m_controlStack.size());
+        if (m_currentOpcode == BrIf)
+            WASM_TRY_POP_EXPRESSION_STACK_INTO(condition, "br / br_if condition");
+        else
             m_unreachableBlocks = 1;
 
         ControlType& data = m_controlStack[m_controlStack.size() - 1 - target].controlData;
 
-        return m_context.addBranch(data, condition, m_expressionStack);
+        WASM_TRY_ADD_TO_CONTEXT(addBranch(data, condition, m_expressionStack));
+        return { };
     }
 
-    case OpType::BrTable: {
+    case BrTable: {
         uint32_t numberOfTargets;
-        if (!parseVarUInt32(numberOfTargets))
-            return false;
-
+        uint32_t defaultTarget;
+        ExpressionType condition;
         Vector<ControlType*> targets;
-        if (!targets.tryReserveCapacity(numberOfTargets))
-            return false;
 
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(numberOfTargets), "can't get the number of targets for br_table");
+        WASM_PARSER_FAIL_IF(numberOfTargets == std::numeric_limits<uint32_t>::max(), "br_table's number of targets is too big ", numberOfTargets);
+
+        WASM_PARSER_FAIL_IF(!targets.tryReserveCapacity(numberOfTargets), "can't allocate memory for ", numberOfTargets, " br_table targets");
         for (uint32_t i = 0; i < numberOfTargets; ++i) {
             uint32_t target;
-            if (!parseVarUInt32(target))
-                return false;
-
-            if (target >= m_controlStack.size())
-                return false;
-
+            WASM_PARSER_FAIL_IF(!parseVarUInt32(target), "can't get ", i, "th target for br_table");
+            WASM_PARSER_FAIL_IF(target >= m_controlStack.size(), "br_table's ", i, "th target ", target, " exceeds control stack size ", m_controlStack.size());
             targets.uncheckedAppend(&m_controlStack[m_controlStack.size() - 1 - target].controlData);
         }
 
-        uint32_t defaultTarget;
-        if (!parseVarUInt32(defaultTarget))
-            return false;
-
-        if (defaultTarget >= m_controlStack.size())
-            return false;
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(defaultTarget), "can't get default target for br_table");
+        WASM_PARSER_FAIL_IF(defaultTarget >= m_controlStack.size(), "br_table's default target ", defaultTarget, " exceeds control stack size ", m_controlStack.size());
 
-        ExpressionType condition;
-        if (!popExpressionStack(condition))
-            return false;
-        
-        if (!m_context.addSwitch(condition, targets, m_controlStack[m_controlStack.size() - 1 - defaultTarget].controlData, m_expressionStack))
-            return false;
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(condition, "br_table condition");
+        WASM_TRY_ADD_TO_CONTEXT(addSwitch(condition, targets, m_controlStack[m_controlStack.size() - 1 - defaultTarget].controlData, m_expressionStack));
 
         m_unreachableBlocks = 1;
-        return true;
+        return { };
     }
 
-    case OpType::Return: {
-        return addReturn();
+    case Return: {
+        ExpressionList returnValues;
+        if (m_signature.returnType() != Void) {
+            ExpressionType returnValue;
+            WASM_TRY_POP_EXPRESSION_STACK_INTO(returnValue, "return");
+            returnValues.append(returnValue);
+        }
+
+        WASM_TRY_ADD_TO_CONTEXT(addReturn(m_controlStack[0].controlData, returnValues));
+        m_unreachableBlocks = 1;
+        return { };
     }
 
-    case OpType::End: {
+    case End: {
         ControlEntry data = m_controlStack.takeLast();
         // FIXME: This is a little weird in that it will modify the expressionStack for the result of the block.
         // That's a little too effectful for me but I don't have a better API right now.
         // see: https://bugs.webkit.org/show_bug.cgi?id=164353
-        if (!m_context.endBlock(data, m_expressionStack))
-            return false;
+        WASM_TRY_ADD_TO_CONTEXT(endBlock(data, m_expressionStack));
         m_expressionStack.swap(data.enclosedExpressionStack);
-        return true;
+        return { };
     }
 
-    case OpType::Unreachable: {
-        m_context.addUnreachable();
+    case Unreachable: {
+        WASM_TRY_ADD_TO_CONTEXT(addUnreachable());
         m_unreachableBlocks = 1;
-        return true;
+        return { };
     }
 
-    case OpType::Drop: {
-        if (!m_expressionStack.size()) {
-            setErrorMessage("Attempted to drop an expression from an empty stack.");
-            return false;
-        }
+    case Drop: {
+        WASM_PARSER_FAIL_IF(!m_expressionStack.size(), "can't drop on empty stack");
         m_expressionStack.takeLast();
-        return true;
+        return { };
+    }
+
+    case Nop: {
+        return { };
     }
 
-    case OpType::Nop: {
-        return true;
+    case GrowMemory: {
+        WASM_PARSER_FAIL_IF(!m_info.memory, "grow_memory is only valid if a memory is defined or imported");
+
+        uint8_t reserved;
+        WASM_PARSER_FAIL_IF(!parseVarUInt1(reserved), "can't parse reserved varUint1 for grow_memory");
+        WASM_PARSER_FAIL_IF(reserved != 0, "reserved varUint1 for grow_memory must be zero");
+
+        ExpressionType delta;
+        WASM_TRY_POP_EXPRESSION_STACK_INTO(delta, "expect an i32 argument to grow_memory on the stack");
+
+        ExpressionType result;
+        WASM_TRY_ADD_TO_CONTEXT(addGrowMemory(delta, result));
+        m_expressionStack.append(result);
+
+        return { };
     }
 
-    case OpType::GrowMemory:
-    case OpType::CurrentMemory:
-        // FIXME: Not yet implemented.
-        return false;
+    case CurrentMemory: {
+        WASM_PARSER_FAIL_IF(!m_info.memory, "current_memory is only valid if a memory is defined or imported");
+
+        uint8_t reserved;
+        WASM_PARSER_FAIL_IF(!parseVarUInt1(reserved), "can't parse reserved varUint1 for current_memory");
+        WASM_PARSER_FAIL_IF(reserved != 0, "reserved varUint1 for current_memory must be zero");
+
+        ExpressionType result;
+        WASM_TRY_ADD_TO_CONTEXT(addCurrentMemory(result));
+        m_expressionStack.append(result);
+
+        return { };
+    }
     }
 
     ASSERT_NOT_REACHED();
+    return { };
 }
 
+// FIXME: We should try to use the same decoder function for both unreachable and reachable code. https://bugs.webkit.org/show_bug.cgi?id=165965
 template<typename Context>
-bool FunctionParser<Context>::parseUnreachableExpression(OpType op)
+auto FunctionParser<Context>::parseUnreachableExpression() -> PartialResult
 {
     ASSERT(m_unreachableBlocks);
-    switch (op) {
-    case OpType::Else: {
+#define CREATE_CASE(name, id, b3op, inc) case OpType::name:
+    switch (m_currentOpcode) {
+    case Else: {
         if (m_unreachableBlocks > 1)
-            return true;
+            return { };
 
         ControlEntry& data = m_controlStack.last();
         m_unreachableBlocks = 0;
-        if (!m_context.addElseToUnreachable(data.controlData))
-            return false;
+        WASM_TRY_ADD_TO_CONTEXT(addElseToUnreachable(data.controlData));
         m_expressionStack.shrink(0);
-        return true;
+        return { };
     }
 
-    case OpType::End: {
+    case End: {
         if (m_unreachableBlocks == 1) {
             ControlEntry data = m_controlStack.takeLast();
-            if (!m_context.addEndToUnreachable(data))
-                return false;
+            WASM_TRY_ADD_TO_CONTEXT(addEndToUnreachable(data));
             m_expressionStack.swap(data.enclosedExpressionStack);
         }
         m_unreachableBlocks--;
-        return true;
+        return { };
     }
 
-    case OpType::Loop:
-    case OpType::If:
-    case OpType::Block: {
+    case Loop:
+    case If:
+    case Block: {
         m_unreachableBlocks++;
-        return true;
+        Type unused;
+        WASM_PARSER_FAIL_IF(!parseResultType(unused), "can't get inline type for ", m_currentOpcode, " in unreachable context");
+        return { };
+    }
+
+    case BrTable: {
+        uint32_t numberOfTargets;
+        uint32_t unused;
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(numberOfTargets), "can't get the number of targets for br_table in unreachable context");
+        WASM_PARSER_FAIL_IF(numberOfTargets == std::numeric_limits<uint32_t>::max(), "br_table's number of targets is too big ", numberOfTargets);
+
+        for (uint32_t i = 0; i < numberOfTargets; ++i)
+            WASM_PARSER_FAIL_IF(!parseVarUInt32(unused), "can't get ", i, "th target for br_table in unreachable context");
+
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(unused), "can't get default target for br_table in unreachable context");
+        return { };
+    }
+
+    case CallIndirect: {
+        uint32_t unused;
+        uint8_t unused2;
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(unused), "can't get call_indirect's signature index in unreachable context");
+        WASM_PARSER_FAIL_IF(!parseVarUInt1(unused2), "can't get call_indirect's reserved byte in unreachable context");
+        return { };
+    }
+
+    case F32Const: {
+        uint32_t unused;
+        WASM_PARSER_FAIL_IF(!parseUInt32(unused), "can't parse 32-bit floating-point constant");
+        return { };
+    }
+
+    case F64Const: {
+        uint64_t constant;
+        WASM_PARSER_FAIL_IF(!parseUInt64(constant), "can't parse 64-bit floating-point constant");
+        return { };
     }
 
     // two immediate cases
-    case OpType::Br:
-    case OpType::BrIf: {
+    FOR_EACH_WASM_MEMORY_LOAD_OP(CREATE_CASE)
+    FOR_EACH_WASM_MEMORY_STORE_OP(CREATE_CASE) {
         uint32_t unused;
-        if (!parseVarUInt32(unused))
-            return false;
-        return parseVarUInt32(unused);
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(unused), "can't get first immediate for ", m_currentOpcode, " in unreachable context");
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(unused), "can't get second immediate for ", m_currentOpcode, " in unreachable context");
+        return { };
     }
 
     // one immediate cases
-    case OpType::F32Const:
-    case OpType::I32Const:
-    case OpType::F64Const:
-    case OpType::I64Const:
-    case OpType::SetLocal:
-    case OpType::GetLocal: {
+    case I32Const:
+    case I64Const:
+    case SetLocal:
+    case GetLocal:
+    case TeeLocal:
+    case GetGlobal:
+    case SetGlobal:
+    case Br:
+    case BrIf:
+    case Call: {
         uint32_t unused;
-        return parseVarUInt32(unused);
+        WASM_PARSER_FAIL_IF(!parseVarUInt32(unused), "can't get immediate for ", m_currentOpcode, " in unreachable context");
+        return { };
     }
 
-    default:
-        break;
+    case GrowMemory:
+    case CurrentMemory: {
+        uint8_t reserved;
+        WASM_PARSER_FAIL_IF(!parseVarUInt1(reserved), "can't parse reserved varUint1 for grow_memory/current_memory");
+        return { };
     }
-    return true;
-}
 
-template<typename Context>
-bool FunctionParser<Context>::popExpressionStack(ExpressionType& result)
-{
-    if (m_expressionStack.size()) {
-        result = m_expressionStack.takeLast();
-        return true;
+    // no immediate cases
+    FOR_EACH_WASM_BINARY_OP(CREATE_CASE)
+    FOR_EACH_WASM_UNARY_OP(CREATE_CASE)
+    case Unreachable:
+    case Nop:
+    case Return:
+    case Select:
+    case Drop: {
+        return { };
     }
-
-    setErrorMessage("Attempted to use a stack value when none existed");
-    return false;
+    }
+#undef CREATE_CASE
+    RELEASE_ASSERT_NOT_REACHED();
 }
 
 } } // namespace JSC::Wasm