blob: 348fc18d38dea43d5b1511e62f13cee60f0b87e4 [file] [log] [blame]
/*
* Copyright (C) 2016-2019 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "WasmValidate.h"
#if ENABLE(WEBASSEMBLY)
#include "JSCJSValueInlines.h"
#include "WasmFunctionParser.h"
#include "WasmSignature.h"
#include <wtf/CommaPrinter.h>
namespace JSC { namespace Wasm {
class Validate {
public:
class ControlData {
public:
ControlData() = default;
ControlData(BlockType type, BlockSignature signature)
: m_blockType(type)
, m_signature(signature)
{
}
void dump(PrintStream& out) const
{
switch (blockType()) {
case BlockType::If:
out.print("If: ");
break;
case BlockType::Block:
out.print("Block: ");
break;
case BlockType::Loop:
out.print("Loop: ");
break;
case BlockType::TopLevel:
out.print("TopLevel: ");
break;
}
const Signature& sig = *signature();
out.print(sig);
}
BlockType blockType() const { return m_blockType; }
BlockSignature signature() const { return m_signature; }
unsigned branchTargetArity() const { return blockType() == BlockType::Loop ? signature()->argumentCount() : signature()->returnCount(); }
Type branchTargetType(unsigned i) const
{
ASSERT(i < branchTargetArity());
return blockType() == BlockType::Loop ? signature()->argument(i) : signature()->returnType(i);
}
private:
BlockType m_blockType;
BlockSignature m_signature;
};
typedef String ErrorType;
typedef Unexpected<ErrorType> UnexpectedResult;
typedef Expected<void, ErrorType> Result;
using ExpressionType = Type;
using ExpressionList = Vector<ExpressionType, 1>;
using Stack = ExpressionList;
typedef ControlData ControlType;
typedef ExpressionList ResultList;
typedef FunctionParser<Validate>::ControlEntry ControlEntry;
static constexpr ExpressionType emptyExpression() { return Void; }
Stack createStack() { return Stack(); }
template <typename ...Args>
NEVER_INLINE UnexpectedResult WARN_UNUSED_RETURN fail(const Args&... args) const
{
using namespace FailureHelper; // See ADL comment in WasmParser.h.
if (UNLIKELY(!ASSERT_DISABLED && Options::crashOnFailedWebAssemblyValidate()))
WTFBreakpointTrap();
StringPrintStream out;
out.print("WebAssembly.Module doesn't validate: "_s, args...);
return UnexpectedResult(out.toString());
}
#define WASM_VALIDATOR_FAIL_IF(condition, ...) do { \
if (UNLIKELY(condition)) \
return fail(__VA_ARGS__); \
} while (0)
Result WARN_UNUSED_RETURN addArguments(const Signature&);
Result WARN_UNUSED_RETURN addLocal(Type, uint32_t);
ExpressionType addConstant(Type type, uint64_t) { return type; }
// References
Result WARN_UNUSED_RETURN addRefIsNull(ExpressionType& value, ExpressionType& result);
Result WARN_UNUSED_RETURN addRefFunc(uint32_t index, ExpressionType& result);
// Tables
Result WARN_UNUSED_RETURN addTableGet(unsigned, ExpressionType& index, ExpressionType& result);
Result WARN_UNUSED_RETURN addTableSet(unsigned, ExpressionType& index, ExpressionType& value);
Result WARN_UNUSED_RETURN addTableSize(unsigned, ExpressionType& result);
Result WARN_UNUSED_RETURN addTableGrow(unsigned, ExpressionType& fill, ExpressionType& delta, ExpressionType& result);
Result WARN_UNUSED_RETURN addTableFill(unsigned, ExpressionType& offset, ExpressionType& fill, ExpressionType& count);
// Locals
Result WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
Result WARN_UNUSED_RETURN setLocal(uint32_t index, ExpressionType value);
// Globals
Result WARN_UNUSED_RETURN getGlobal(uint32_t index, ExpressionType& result);
Result WARN_UNUSED_RETURN setGlobal(uint32_t index, ExpressionType value);
// Memory
Result WARN_UNUSED_RETURN load(LoadOpType, ExpressionType pointer, ExpressionType& result, uint32_t offset);
Result WARN_UNUSED_RETURN store(StoreOpType, ExpressionType pointer, ExpressionType value, uint32_t offset);
// Basic operators
template<OpType>
Result WARN_UNUSED_RETURN addOp(ExpressionType arg, ExpressionType& result);
template<OpType>
Result WARN_UNUSED_RETURN addOp(ExpressionType left, ExpressionType right, ExpressionType& result);
Result WARN_UNUSED_RETURN addSelect(ExpressionType condition, ExpressionType nonZero, ExpressionType zero, ExpressionType& result);
// Control flow
ControlData WARN_UNUSED_RETURN addTopLevel(BlockSignature);
Result WARN_UNUSED_RETURN addBlock(BlockSignature, Stack& enclosingStack, ControlType& newBlock, Stack& newStack);
Result WARN_UNUSED_RETURN addLoop(BlockSignature, Stack& enclosingStack, ControlType& block, Stack& newStack, uint32_t loopIndex);
Result WARN_UNUSED_RETURN addIf(ExpressionType condition, BlockSignature, Stack& enclosingStack, ControlType& result, Stack& newStack);
Result WARN_UNUSED_RETURN addElse(ControlData&, const Stack&);
Result WARN_UNUSED_RETURN addElseToUnreachable(ControlData&);
Result WARN_UNUSED_RETURN addReturn(ControlData& topLevel, const Stack& returnValues);
Result WARN_UNUSED_RETURN addBranch(ControlData&, ExpressionType condition, const Stack& expressionStack);
Result WARN_UNUSED_RETURN addSwitch(ExpressionType condition, const Vector<ControlData*>& targets, ControlData& defaultTarget, const Stack& expressionStack);
Result WARN_UNUSED_RETURN endBlock(ControlEntry&, Stack& expressionStack);
Result WARN_UNUSED_RETURN addEndToUnreachable(ControlEntry&);
Result WARN_UNUSED_RETURN addGrowMemory(ExpressionType delta, ExpressionType& result);
Result WARN_UNUSED_RETURN addCurrentMemory(ExpressionType& result);
Result WARN_UNUSED_RETURN addUnreachable() { return { }; }
// Calls
Result WARN_UNUSED_RETURN addCall(unsigned calleeIndex, const Signature&, const Vector<ExpressionType>& args, ResultList&);
Result WARN_UNUSED_RETURN addCallIndirect(unsigned tableIndex, const Signature&, const Vector<ExpressionType>& args, ResultList&);
bool hasMemory() const { return !!m_module.memory; }
Validate(const ModuleInformation& module)
: m_module(module)
{
}
void dump(const Vector<ControlEntry>&, const Stack*);
void setParser(FunctionParser<Validate>*) { }
private:
Result WARN_UNUSED_RETURN unify(const Stack&, const ControlData&);
Result WARN_UNUSED_RETURN checkBranchTarget(ControlData& target, const Stack& expressionStack);
Vector<Type> m_locals;
const ModuleInformation& m_module;
};
auto Validate::addArguments(const Signature& signature) -> Result
{
for (size_t i = 0; i < signature.argumentCount(); ++i)
WASM_FAIL_IF_HELPER_FAILS(addLocal(signature.argument(i), 1));
return { };
}
auto Validate::addTableGet(unsigned tableIndex, ExpressionType& index, ExpressionType& result) -> Result
{
WASM_VALIDATOR_FAIL_IF(tableIndex >= m_module.tableCount(), "table index ", tableIndex, " is invalid, limit is ", m_module.tableCount());
result = m_module.tables[tableIndex].wasmType();
WASM_VALIDATOR_FAIL_IF(Type::I32 != index, "table.get index to type ", makeString(index), " expected ", makeString(Type::I32));
return { };
}
auto Validate::addTableSet(unsigned tableIndex, ExpressionType& index, ExpressionType& value) -> Result
{
WASM_VALIDATOR_FAIL_IF(tableIndex >= m_module.tableCount(), "table index ", tableIndex, " is invalid, limit is ", m_module.tableCount());
auto type = m_module.tables[tableIndex].wasmType();
WASM_VALIDATOR_FAIL_IF(Type::I32 != index, "table.set index to type ", index, " expected ", Type::I32);
WASM_VALIDATOR_FAIL_IF(!isSubtype(value, type), "table.set value to type ", value, " expected ", type);
RELEASE_ASSERT(m_module.tables[tableIndex].type() == TableElementType::Anyref || m_module.tables[tableIndex].type() == TableElementType::Funcref);
return { };
}
auto Validate::addTableSize(unsigned tableIndex, ExpressionType& result) -> Result
{
result = Type::I32;
WASM_VALIDATOR_FAIL_IF(tableIndex >= m_module.tableCount(), "table index ", tableIndex, " is invalid, limit is ", m_module.tableCount());
return { };
}
auto Validate::addTableGrow(unsigned tableIndex, ExpressionType& fill, ExpressionType& delta, ExpressionType& result) -> Result
{
result = Type::I32;
WASM_VALIDATOR_FAIL_IF(tableIndex >= m_module.tableCount(), "table index ", tableIndex, " is invalid, limit is ", m_module.tableCount());
WASM_VALIDATOR_FAIL_IF(!isSubtype(fill, m_module.tables[tableIndex].wasmType()), "table.grow expects fill value of type ", m_module.tables[tableIndex].wasmType(), " got ", fill);
WASM_VALIDATOR_FAIL_IF(Type::I32 != delta, "table.grow expects an i32 delta value, got ", makeString(delta));
return { };
}
auto Validate::addTableFill(unsigned tableIndex, ExpressionType& offset, ExpressionType& fill, ExpressionType& count) -> Result
{
WASM_VALIDATOR_FAIL_IF(tableIndex >= m_module.tableCount(), "table index ", tableIndex, " is invalid, limit is ", m_module.tableCount());
WASM_VALIDATOR_FAIL_IF(!isSubtype(fill, m_module.tables[tableIndex].wasmType()), "table.fill expects fill value of type ", m_module.tables[tableIndex].wasmType(), " got ", fill);
WASM_VALIDATOR_FAIL_IF(Type::I32 != offset, "table.fill expects an i32 offset value, got ", makeString(offset));
WASM_VALIDATOR_FAIL_IF(Type::I32 != count, "table.fill expects an i32 count value, got ", makeString(count));
return { };
}
auto Validate::addRefIsNull(ExpressionType& value, ExpressionType& result) -> Result
{
result = Type::I32;
WASM_VALIDATOR_FAIL_IF(!isSubtype(value, Type::Anyref), "ref.is_null to type ", makeString(value), " expected ", makeString(Type::Anyref));
return { };
}
auto Validate::addRefFunc(uint32_t index, ExpressionType& result) -> Result
{
result = Type::Funcref;
WASM_VALIDATOR_FAIL_IF(index >= m_module.functionIndexSpaceSize(), "ref.func index ", index, " is too large, max is ", m_module.functionIndexSpaceSize());
m_module.addReferencedFunction(index);
return { };
}
auto Validate::addLocal(Type type, uint32_t count) -> Result
{
size_t newSize = m_locals.size() + count;
ASSERT(!(CheckedUint32(count) + m_locals.size()).hasOverflowed());
ASSERT(newSize <= maxFunctionLocals);
WASM_VALIDATOR_FAIL_IF(!m_locals.tryReserveCapacity(newSize), "can't allocate memory for ", newSize, " locals");
for (uint32_t i = 0; i < count; ++i)
m_locals.uncheckedAppend(type);
return { };
}
auto Validate::getLocal(uint32_t index, ExpressionType& result) -> Result
{
WASM_VALIDATOR_FAIL_IF(index >= m_locals.size(), "attempt to use unknown local ", index, " last one is ", m_locals.size());
result = m_locals[index];
return { };
}
auto Validate::setLocal(uint32_t index, ExpressionType value) -> Result
{
ExpressionType localType;
WASM_FAIL_IF_HELPER_FAILS(getLocal(index, localType));
WASM_VALIDATOR_FAIL_IF(!isSubtype(value, localType), "set_local to type ", value, " expected ", localType);
return { };
}
auto Validate::getGlobal(uint32_t index, ExpressionType& result) -> Result
{
WASM_VALIDATOR_FAIL_IF(index >= m_module.globals.size(), "get_global ", index, " of unknown global, limit is ", m_module.globals.size());
result = m_module.globals[index].type;
ASSERT(isValueType(result));
return { };
}
auto Validate::setGlobal(uint32_t index, ExpressionType value) -> Result
{
WASM_VALIDATOR_FAIL_IF(index >= m_module.globals.size(), "set_global ", index, " of unknown global, limit is ", m_module.globals.size());
WASM_VALIDATOR_FAIL_IF(m_module.globals[index].mutability == Global::Immutable, "set_global ", index, " is immutable");
ExpressionType globalType = m_module.globals[index].type;
ASSERT(isValueType(globalType));
WASM_VALIDATOR_FAIL_IF(globalType != value, "set_global ", index, " with type ", globalType, " with a variable of type ", value);
return { };
}
Validate::ControlType Validate::addTopLevel(BlockSignature signature)
{
return ControlData(BlockType::TopLevel, signature);
}
static Validate::Stack splitStack(BlockSignature signature, Validate::Stack& stack)
{
Validate::Stack result;
result.reserveInitialCapacity(signature->argumentCount());
ASSERT(stack.size() >= signature->argumentCount());
unsigned offset = stack.size() - signature->argumentCount();
for (unsigned i = 0; i < signature->argumentCount(); ++i)
result.uncheckedAppend(stack[i + offset]);
stack.shrink(offset);
return result;
}
auto Validate::addBlock(BlockSignature signature, Stack& enclosingStack, ControlType& newBlock, Stack& newStack) -> Result
{
WASM_VALIDATOR_FAIL_IF(enclosingStack.size() < signature->argumentCount(), "Too few values on stack for block. Block expects ", signature->argumentCount(), ", but only ", enclosingStack.size(), " were present. Block has signature: ", *signature);
newStack = splitStack(signature, enclosingStack);
newBlock = ControlData(BlockType::Block, signature);
for (unsigned i = 0; i < signature->argumentCount(); ++i)
WASM_VALIDATOR_FAIL_IF(!isSubtype(newStack[i], signature->argument(i)), "Loop expects the argument at index", i, " to be a subtype of ", signature->argument(i), " but argument has type ", newStack[i]);
return { };
}
auto Validate::addLoop(BlockSignature signature, Stack& enclosingStack, ControlType& loop, Stack& newStack, uint32_t) -> Result
{
WASM_VALIDATOR_FAIL_IF(enclosingStack.size() < signature->argumentCount(), "Too few values on stack for loop block. Loop expects ", signature->argumentCount(), ", but only ", enclosingStack.size(), " were present. Loop has signature: ", *signature);
newStack = splitStack(signature, enclosingStack);
loop = ControlData(BlockType::Loop, signature);
for (unsigned i = 0; i < signature->argumentCount(); ++i)
WASM_VALIDATOR_FAIL_IF(!isSubtype(newStack[i], signature->argument(i)), "Loop expects the argument at index", i, " to be a subtype of ", signature->argument(i), " but argument has type ", newStack[i]);
return { };
}
auto Validate::addSelect(ExpressionType condition, ExpressionType nonZero, ExpressionType zero, ExpressionType& result) -> Result
{
WASM_VALIDATOR_FAIL_IF(condition != I32, "select condition must be i32, got ", condition);
WASM_VALIDATOR_FAIL_IF(nonZero != zero, "select result types must match, got ", nonZero, " and ", zero);
result = zero;
return { };
}
auto Validate::addIf(ExpressionType condition, BlockSignature signature, Stack& enclosingStack, ControlType& result, Stack& newStack) -> Result
{
WASM_VALIDATOR_FAIL_IF(condition != I32, "if condition must be i32, got ", makeString(condition));
WASM_VALIDATOR_FAIL_IF(enclosingStack.size() < signature->argumentCount(), "Too few arguments on stack for if block. If expects ", signature->argumentCount(), ", but only ", enclosingStack.size(), " were present. If block has signature: ", *signature);
newStack = splitStack(signature, enclosingStack);
result = ControlData(BlockType::If, signature);
for (unsigned i = 0; i < signature->argumentCount(); ++i)
WASM_VALIDATOR_FAIL_IF(!isSubtype(newStack[i], signature->argument(i)), "Loop expects the argument at index", i, " to be a subtype of ", signature->argument(i), " but argument has type ", newStack[i]);
return { };
}
auto Validate::addElse(ControlType& current, const Stack& values) -> Result
{
WASM_FAIL_IF_HELPER_FAILS(unify(values, current));
return addElseToUnreachable(current);
}
auto Validate::addElseToUnreachable(ControlType& current) -> Result
{
WASM_VALIDATOR_FAIL_IF(current.blockType() != BlockType::If, "else block isn't associated to an if");
current = ControlData(BlockType::Block, current.signature());
return { };
}
auto Validate::addReturn(ControlType& topLevel, const ExpressionList& returnValues) -> Result
{
ASSERT(topLevel.blockType() == BlockType::TopLevel);
return checkBranchTarget(topLevel, returnValues);
}
auto Validate::checkBranchTarget(ControlType& target, const Stack& expressionStack) -> Result
{
if (!target.branchTargetArity())
return { };
WASM_VALIDATOR_FAIL_IF(expressionStack.size() < target.branchTargetArity(), target.blockType() == BlockType::TopLevel ? "branch out of function" : "branch to block", " on expression stack of size ", expressionStack.size(), ", but block, ", target , " expects ", target.branchTargetArity(), " values");
unsigned expressionStackOffset = expressionStack.size() - target.branchTargetArity();
for (unsigned i = 0; i < target.branchTargetArity(); ++i)
WASM_VALIDATOR_FAIL_IF(!isSubtype(target.branchTargetType(i), expressionStack[expressionStackOffset + i]), "branch's stack type is not a subtype of block's type branch target type. Stack value has type", expressionStack[expressionStackOffset + i], " but branch target expects a value with subtype of ", target.branchTargetType(i), " at index ", i);
return { };
}
auto Validate::addBranch(ControlType& target, ExpressionType condition, const Stack& stack) -> Result
{
// Void means this is an unconditional branch.
WASM_VALIDATOR_FAIL_IF(condition != Void && condition != I32, "conditional branch with non-i32 condition ", condition);
return checkBranchTarget(target, stack);
}
auto Validate::addSwitch(ExpressionType condition, const Vector<ControlData*>& targets, ControlData& defaultTarget, const Stack& expressionStack) -> Result
{
WASM_VALIDATOR_FAIL_IF(condition != I32, "br_table with non-i32 condition ", condition);
for (unsigned i = 0; i < targets.size(); ++i) {
auto* target = targets[i];
WASM_VALIDATOR_FAIL_IF(defaultTarget.branchTargetArity() != target->branchTargetArity(), "br_table target type size mismatch. Default has size: ", defaultTarget.branchTargetArity(), "but target: ", i, " has size: ", target->branchTargetArity());
for (unsigned type = 0; type < defaultTarget.branchTargetArity(); ++type)
WASM_VALIDATOR_FAIL_IF(!isSubtype(defaultTarget.branchTargetType(type), target->branchTargetType(type)), "br_table target type mismatch at offset ", type, " expected: ", defaultTarget.branchTargetType(type), " but saw: ", target->branchTargetType(type), " when targeting block", *target);
}
return checkBranchTarget(defaultTarget, expressionStack);
}
auto Validate::addGrowMemory(ExpressionType delta, ExpressionType& result) -> Result
{
WASM_VALIDATOR_FAIL_IF(delta != I32, "grow_memory with non-i32 delta argument has type: ", delta);
result = I32;
return { };
}
auto Validate::addCurrentMemory(ExpressionType& result) -> Result
{
result = I32;
return { };
}
auto Validate::endBlock(ControlEntry& entry, Stack& stack) -> Result
{
WASM_FAIL_IF_HELPER_FAILS(unify(stack, entry.controlData));
return addEndToUnreachable(entry);
}
auto Validate::addEndToUnreachable(ControlEntry& entry) -> Result
{
auto block = entry.controlData;
RELEASE_ASSERT_WITH_SECURITY_IMPLICATION(block.blockType() != BlockType::If);
for (unsigned i = 0; i < block.signature()->returnCount(); ++i)
entry.enclosedExpressionStack.append(block.signature()->returnType(i));
return { };
}
auto Validate::addCall(unsigned, const Signature& signature, const Vector<ExpressionType>& args, ResultList& results) -> Result
{
RELEASE_ASSERT(signature.argumentCount() == args.size());
for (unsigned i = 0; i < args.size(); ++i)
WASM_VALIDATOR_FAIL_IF(!isSubtype(args[i], signature.argument(i)), "argument type mismatch in call, got ", args[i], ", expected ", signature.argument(i));
for (unsigned i = 0; i < signature.returnCount(); ++i)
results.append(signature.returnType(i));
return { };
}
auto Validate::addCallIndirect(unsigned tableIndex, const Signature& signature, const Vector<ExpressionType>& args, ResultList& results) -> Result
{
RELEASE_ASSERT(tableIndex < m_module.tableCount());
RELEASE_ASSERT(m_module.tables[tableIndex].type() == TableElementType::Funcref);
const auto argumentCount = signature.argumentCount();
RELEASE_ASSERT(argumentCount == args.size() - 1);
for (unsigned i = 0; i < argumentCount; ++i)
WASM_VALIDATOR_FAIL_IF(!isSubtype(args[i], signature.argument(i)), "argument type mismatch in call_indirect, got ", args[i], ", expected ", signature.argument(i));
WASM_VALIDATOR_FAIL_IF(args.last() != I32, "non-i32 call_indirect index ", args.last());
for (unsigned i = 0; i < signature.returnCount(); ++i)
results.append(signature.returnType(i));
return { };
}
auto Validate::unify(const Stack& values, const ControlType& block) -> Result
{
WASM_VALIDATOR_FAIL_IF(block.signature()->returnCount() != values.size(), " block with type: ", *block.signature(), " returns: ", block.signature()->returnCount(), " but stack has: ", values.size(), " values");
for (unsigned i = 0; i < block.signature()->returnCount(); ++i)
WASM_VALIDATOR_FAIL_IF(!isSubtype(values[i], block.signature()->returnType(i)), "control flow returns with unexpected type. ", values[i], " is not a subtype of ", block.signature()->returnType(i));
// WASM_VALIDATOR_FAIL_IF(values.size() != 1, "block with type: ", makeString(*block.signature()), " ends with a stack containing more than one value");
return { };
}
static void dumpExpressionStack(const CommaPrinter& comma, const Validate::Stack& expressionStack)
{
dataLog(comma, " ExpressionStack:");
for (const auto& expression : expressionStack)
dataLog(comma, makeString(expression));
}
void Validate::dump(const Vector<ControlEntry>& controlStack, const Stack* expressionStack)
{
for (size_t i = controlStack.size(); i--;) {
dataLog(" ", controlStack[i].controlData);
CommaPrinter comma(", ", "");
dumpExpressionStack(comma, *expressionStack);
expressionStack = &controlStack[i].enclosedExpressionStack;
dataLogLn();
}
dataLogLn();
}
Expected<void, String> validateFunction(const FunctionData& function, const Signature& signature, const ModuleInformation& module)
{
Validate context(module);
FunctionParser<Validate> validator(context, function.data.data(), function.data.size(), signature, module);
WASM_FAIL_IF_HELPER_FAILS(validator.parse());
return { };
}
} } // namespace JSC::Wasm
#include "WasmValidateInlines.h"
#endif // ENABLE(WEBASSEMBLY)