#include "config.h"
#include "WasmValidate.h"
#if ENABLE(WEBASSEMBLY)
#include "WasmFunctionParser.h"
#include <wtf/CommaPrinter.h>
#include <wtf/text/StringBuilder.h>
namespace JSC { namespace Wasm {
class Validate {
public:
class ControlData {
public:
ControlData(BlockType type, Type signature)
: m_blockType(type)
, m_signature(signature)
{
}
ControlData()
{
}
void dump(PrintStream& out) const
{
switch (type()) {
case BlockType::If:
out.print("If: ");
break;
case BlockType::Block:
out.print("Block: ");
break;
case BlockType::Loop:
out.print("Loop: ");
break;
}
}
bool hasNonVoidSignature() const { return m_signature != Void; }
BlockType type() const { return m_blockType; }
Type signature() const { return m_signature; }
private:
BlockType m_blockType;
Type m_signature;
};
typedef String ErrorType;
typedef UnexpectedType<ErrorType> UnexpectedResult;
typedef Expected<void, ErrorType> Result;
typedef Type ExpressionType;
typedef ControlData ControlType;
typedef Vector<ExpressionType, 1> ExpressionList;
typedef FunctionParser<Validate>::ControlEntry ControlEntry;
static const ExpressionType emptyExpression = Void;
template <typename ...Args>
NEVER_INLINE UnexpectedResult WARN_UNUSED_RETURN fail(Args... args) const
{
using namespace FailureHelper; return UnexpectedResult(makeString(ASCIILiteral("WebAssembly.Module doesn't validate: "), makeString(args)...));
}
#define WASM_VALIDATOR_FAIL_IF(condition, ...) do { \
if (UNLIKELY(condition)) \
return fail(__VA_ARGS__); \
} while (0)
Result WARN_UNUSED_RETURN addArguments(const Vector<Type>&);
Result WARN_UNUSED_RETURN addLocal(Type, uint32_t);
ExpressionType addConstant(Type type, uint64_t) { return type; }
Result WARN_UNUSED_RETURN getLocal(uint32_t index, ExpressionType& result);
Result WARN_UNUSED_RETURN setLocal(uint32_t index, ExpressionType value);
Result WARN_UNUSED_RETURN getGlobal(uint32_t index, ExpressionType& result);
Result WARN_UNUSED_RETURN setGlobal(uint32_t index, ExpressionType value);
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);
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);
ControlData WARN_UNUSED_RETURN addBlock(Type signature);
ControlData WARN_UNUSED_RETURN addLoop(Type signature);
Result WARN_UNUSED_RETURN addIf(ExpressionType condition, Type signature, ControlData& result);
Result WARN_UNUSED_RETURN addElse(ControlData&, const ExpressionList&);
Result WARN_UNUSED_RETURN addElseToUnreachable(ControlData&);
Result WARN_UNUSED_RETURN addReturn(const ExpressionList& returnValues);
Result WARN_UNUSED_RETURN addBranch(ControlData&, ExpressionType condition, const ExpressionList& expressionStack);
Result WARN_UNUSED_RETURN addSwitch(ExpressionType condition, const Vector<ControlData*>& targets, ControlData& defaultTarget, const ExpressionList& expressionStack);
Result WARN_UNUSED_RETURN endBlock(ControlEntry&, ExpressionList& expressionStack);
Result WARN_UNUSED_RETURN addEndToUnreachable(ControlEntry&);
Result WARN_UNUSED_RETURN addUnreachable() { return { }; }
Result WARN_UNUSED_RETURN addCall(unsigned calleeIndex, const Signature*, const Vector<ExpressionType>& args, ExpressionType& result);
Result WARN_UNUSED_RETURN addCallIndirect(const Signature*, const Vector<ExpressionType>& args, ExpressionType& result);
bool hasMemory() const { return !!m_module.memory; }
Validate(ExpressionType returnType, const ModuleInformation& module)
: m_returnType(returnType)
, m_module(module)
{
}
void dump(const Vector<ControlEntry>&, const ExpressionList&);
private:
Result unify(Type, Type);
Result unify(const ExpressionList&, const ControlData&);
Result checkBranchTarget(ControlData& target, const ExpressionList& expressionStack);
ExpressionType m_returnType;
Vector<Type> m_locals;
const ModuleInformation& m_module;
};
auto Validate::addArguments(const Vector<Type>& args) -> Result
{
for (Type arg : args)
WASM_FAIL_IF_HELPER_FAILS(addLocal(arg, 1));
return { };
}
auto Validate::addLocal(Type type, uint32_t count) -> Result
{
size_t size = m_locals.size() + count;
WASM_VALIDATOR_FAIL_IF(!m_locals.tryReserveCapacity(size), "can't allocate memory for ", size, " 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(localType != value, "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::addBlock(Type signature)
{
return ControlData(BlockType::Block, signature);
}
Validate::ControlType Validate::addLoop(Type signature)
{
return ControlData(BlockType::Loop, signature);
}
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, Type signature, ControlType& result) -> Result
{
WASM_VALIDATOR_FAIL_IF(condition != I32, "if condition must be i32, got ", condition);
result = ControlData(BlockType::If, signature);
return { };
}
auto Validate::addElse(ControlType& current, const ExpressionList& values) -> Result
{
WASM_FAIL_IF_HELPER_FAILS(unify(values, current));
return addElseToUnreachable(current);
}
auto Validate::addElseToUnreachable(ControlType& current) -> Result
{
WASM_VALIDATOR_FAIL_IF(current.type() != BlockType::If, "else block isn't associated to an if");
current = ControlData(BlockType::Block, current.signature());
return { };
}
auto Validate::addReturn(const ExpressionList& returnValues) -> Result
{
if (m_returnType == Void)
return { };
ASSERT(returnValues.size() == 1);
WASM_VALIDATOR_FAIL_IF(m_returnType != returnValues[0], "return type ", returnValues[0], " doesn't match function's return type ", m_returnType);
return { };
}
auto Validate::checkBranchTarget(ControlType& target, const ExpressionList& expressionStack) -> Result
{
if (target.type() == BlockType::Loop)
return { };
if (target.signature() == Void)
return { };
WASM_VALIDATOR_FAIL_IF(expressionStack.isEmpty(), "branch to block on empty expression stack");
WASM_VALIDATOR_FAIL_IF(target.signature() != expressionStack.last(), "branch's stack type doesn't match block's type");
return { };
}
auto Validate::addBranch(ControlType& target, ExpressionType condition, const ExpressionList& stack) -> Result
{
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 ExpressionList& expressionStack) -> Result
{
WASM_VALIDATOR_FAIL_IF(condition != I32, "br_table with non-i32 condition ", condition);
for (auto target : targets)
WASM_VALIDATOR_FAIL_IF(defaultTarget.signature() != target->signature(), "br_table target type mismatch");
return checkBranchTarget(defaultTarget, expressionStack);
}
auto Validate::endBlock(ControlEntry& entry, ExpressionList& stack) -> Result
{
ControlData& block = entry.controlData;
if (block.signature() == Void)
return { };
WASM_VALIDATOR_FAIL_IF(block.type() == BlockType::If, "If-block had a non-void result type: ", block.signature(), " but had no else-block");
WASM_VALIDATOR_FAIL_IF(stack.isEmpty(), "typed block falls through on empty stack");
WASM_VALIDATOR_FAIL_IF(block.signature() != stack.last(), "block fallthrough doesn't match its declared type");
entry.enclosedExpressionStack.append(block.signature());
return { };
}
auto Validate::addEndToUnreachable(ControlEntry& entry) -> Result
{
auto block = entry.controlData;
if (block.signature() != Void) {
WASM_VALIDATOR_FAIL_IF(block.type() == BlockType::If, "If-block had a non-void result type: ", block.signature(), " but had no else-block");
entry.enclosedExpressionStack.append(block.signature());
}
return { };
}
auto Validate::addCall(unsigned, const Signature* signature, const Vector<ExpressionType>& args, ExpressionType& result) -> Result
{
WASM_VALIDATOR_FAIL_IF(signature->arguments.size() != args.size(), "arity mismatch in call, got ", args.size(), " arguments, expected ", signature->arguments.size());
for (unsigned i = 0; i < args.size(); ++i)
WASM_VALIDATOR_FAIL_IF(args[i] != signature->arguments[i], "argument type mismatch in call, got ", args[i], ", expected ", signature->arguments[i]);
result = signature->returnType;
return { };
}
auto Validate::addCallIndirect(const Signature* signature, const Vector<ExpressionType>& args, ExpressionType& result) -> Result
{
const auto argumentCount = signature->arguments.size();
WASM_VALIDATOR_FAIL_IF(argumentCount != args.size() - 1, "arity mismatch in call_indirect, got ", args.size() - 1, " arguments, expected ", argumentCount);
for (unsigned i = 0; i < argumentCount; ++i)
WASM_VALIDATOR_FAIL_IF(args[i] != signature->arguments[i], "argument type mismatch in call_indirect, got ", args[i], ", expected ", signature->arguments[i]);
WASM_VALIDATOR_FAIL_IF(args.last() != I32, "non-i32 call_indirect index ", args.last());
result = signature->returnType;
return { };
}
auto Validate::unify(const ExpressionList& values, const ControlType& block) -> Result
{
ASSERT(values.size() <= 1);
if (block.signature() == Void)
return { };
WASM_VALIDATOR_FAIL_IF(values.isEmpty(), "non-void block ends with an empty stack");
WASM_VALIDATOR_FAIL_IF(values[0] != block.signature(), "control flow returns with unexpected type");
return { };
}
void Validate::dump(const Vector<ControlEntry>&, const ExpressionList&)
{
}
Expected<void, String> validateFunction(const uint8_t* source, size_t length, const Signature* signature, const ImmutableFunctionIndexSpace& functionIndexSpace, const ModuleInformation& module)
{
Validate context(signature->returnType, module);
FunctionParser<Validate> validator(context, source, length, signature, functionIndexSpace, module);
WASM_FAIL_IF_HELPER_FAILS(validator.parse());
return { };
}
} }
#include "WasmValidateInlines.h"
#endif // ENABLE(WEBASSEMBLY)