| /* |
| * Copyright 2016 WebAssembly Community Group participants |
| * |
| * 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 "src/validator.h" |
| |
| #include <cassert> |
| #include <cinttypes> |
| #include <cstdarg> |
| #include <cstdio> |
| |
| #include "config.h" |
| |
| #include "src/binary-reader.h" |
| #include "src/cast.h" |
| #include "src/expr-visitor.h" |
| #include "src/ir.h" |
| #include "src/type-checker.h" |
| |
| namespace wabt { |
| |
| namespace { |
| |
| class Validator : public ExprVisitor::Delegate { |
| public: |
| WABT_DISALLOW_COPY_AND_ASSIGN(Validator); |
| Validator(Errors*, const Script*, const ValidateOptions& options); |
| |
| Result CheckModule(const Module* module); |
| Result CheckScript(const Script* script); |
| Result CheckAllFuncSignatures(const Module* module); |
| |
| // Implements ExprVisitor::Delegate. |
| Result OnBinaryExpr(BinaryExpr*) override; |
| Result BeginBlockExpr(BlockExpr*) override; |
| Result EndBlockExpr(BlockExpr*) override; |
| Result OnBrExpr(BrExpr*) override; |
| Result OnBrIfExpr(BrIfExpr*) override; |
| Result OnBrOnExnExpr(BrOnExnExpr*) override; |
| Result OnBrTableExpr(BrTableExpr*) override; |
| Result OnCallExpr(CallExpr*) override; |
| Result OnCallIndirectExpr(CallIndirectExpr*) override; |
| Result OnCompareExpr(CompareExpr*) override; |
| Result OnConstExpr(ConstExpr*) override; |
| Result OnConvertExpr(ConvertExpr*) override; |
| Result OnDropExpr(DropExpr*) override; |
| Result OnGlobalGetExpr(GlobalGetExpr*) override; |
| Result OnGlobalSetExpr(GlobalSetExpr*) override; |
| Result BeginIfExpr(IfExpr*) override; |
| Result AfterIfTrueExpr(IfExpr*) override; |
| Result EndIfExpr(IfExpr*) override; |
| Result OnLoadExpr(LoadExpr*) override; |
| Result OnLocalGetExpr(LocalGetExpr*) override; |
| Result OnLocalSetExpr(LocalSetExpr*) override; |
| Result OnLocalTeeExpr(LocalTeeExpr*) override; |
| Result BeginLoopExpr(LoopExpr*) override; |
| Result EndLoopExpr(LoopExpr*) override; |
| Result OnMemoryCopyExpr(MemoryCopyExpr*) override; |
| Result OnDataDropExpr(DataDropExpr*) override; |
| Result OnMemoryFillExpr(MemoryFillExpr*) override; |
| Result OnMemoryGrowExpr(MemoryGrowExpr*) override; |
| Result OnMemoryInitExpr(MemoryInitExpr*) override; |
| Result OnMemorySizeExpr(MemorySizeExpr*) override; |
| Result OnTableCopyExpr(TableCopyExpr*) override; |
| Result OnElemDropExpr(ElemDropExpr*) override; |
| Result OnTableInitExpr(TableInitExpr*) override; |
| Result OnTableGetExpr(TableGetExpr*) override; |
| Result OnTableSetExpr(TableSetExpr*) override; |
| Result OnTableGrowExpr(TableGrowExpr*) override; |
| Result OnTableSizeExpr(TableSizeExpr*) override; |
| Result OnRefFuncExpr(RefFuncExpr*) override; |
| Result OnRefNullExpr(RefNullExpr*) override; |
| Result OnRefIsNullExpr(RefIsNullExpr*) override; |
| Result OnNopExpr(NopExpr*) override; |
| Result OnReturnExpr(ReturnExpr*) override; |
| Result OnReturnCallExpr(ReturnCallExpr*) override; |
| Result OnReturnCallIndirectExpr(ReturnCallIndirectExpr*) override; |
| Result OnSelectExpr(SelectExpr*) override; |
| Result OnStoreExpr(StoreExpr*) override; |
| Result OnUnaryExpr(UnaryExpr*) override; |
| Result OnUnreachableExpr(UnreachableExpr*) override; |
| Result BeginTryExpr(TryExpr*) override; |
| Result OnCatchExpr(TryExpr*) override; |
| Result EndTryExpr(TryExpr*) override; |
| Result OnThrowExpr(ThrowExpr*) override; |
| Result OnRethrowExpr(RethrowExpr*) override; |
| Result OnAtomicWaitExpr(AtomicWaitExpr*) override; |
| Result OnAtomicNotifyExpr(AtomicNotifyExpr*) override; |
| Result OnAtomicLoadExpr(AtomicLoadExpr*) override; |
| Result OnAtomicStoreExpr(AtomicStoreExpr*) override; |
| Result OnAtomicRmwExpr(AtomicRmwExpr*) override; |
| Result OnAtomicRmwCmpxchgExpr(AtomicRmwCmpxchgExpr*) override; |
| Result OnTernaryExpr(TernaryExpr*) override; |
| Result OnSimdLaneOpExpr(SimdLaneOpExpr*) override; |
| Result OnSimdShuffleOpExpr(SimdShuffleOpExpr*) override; |
| Result OnLoadSplatExpr(LoadSplatExpr*) override; |
| |
| private: |
| struct ActionResult { |
| enum class Kind { |
| Error, |
| Types, |
| Type, |
| } kind; |
| |
| union { |
| const TypeVector* types; |
| Type type; |
| }; |
| }; |
| |
| void WABT_PRINTF_FORMAT(3, 4) |
| PrintError(const Location* loc, const char* fmt, ...); |
| void OnTypecheckerError(const char* msg); |
| Result CheckVar(Index max_index, |
| const Var* var, |
| const char* desc, |
| Index* out_index); |
| Result CheckFuncVar(const Var* var, const Func** out_func); |
| Result CheckGlobalVar(const Var* var, |
| const Global** out_global, |
| Index* out_global_index); |
| Type GetGlobalVarTypeOrAny(const Var* var); |
| Result CheckFuncTypeVar(const Var* var, const FuncType** out_func_type); |
| Result CheckTableVar(const Var* var, const Table** out_table); |
| Result CheckMemoryVar(const Var* var, const Memory** out_memory); |
| Result CheckDataSegmentVar(const Var* var); |
| Result CheckElemSegmentVar(const Var* var); |
| Result CheckLocalVar(const Var* var, Type* out_type); |
| Type GetLocalVarTypeOrAny(const Var* var); |
| void CheckAlign(const Location* loc, |
| Address alignment, |
| Address natural_alignment); |
| void CheckAtomicAlign(const Location* loc, |
| Address alignment, |
| Address natural_alignment); |
| void CheckType(const Location* loc, |
| Type actual, |
| Type expected, |
| const char* desc); |
| void CheckTypeIndex(const Location* loc, |
| Type actual, |
| Type expected, |
| const char* desc, |
| Index index, |
| const char* index_kind); |
| void CheckTypes(const Location* loc, |
| const TypeVector& actual, |
| const TypeVector& expected, |
| const char* desc, |
| const char* index_kind); |
| void CheckConstTypes(const Location* loc, |
| const TypeVector& actual, |
| const ConstVector& expected, |
| const char* desc); |
| void CheckConstType(const Location* loc, |
| Type actual, |
| const ConstVector& expected, |
| const char* desc); |
| void CheckAssertReturnNanType(const Location* loc, |
| Type actual, |
| const char* desc); |
| void CheckExprList(const Location* loc, const ExprList& exprs); |
| bool CheckHasMemory(const Location* loc, Opcode opcode); |
| bool CheckHasTable(const Location* loc, Opcode opcode, Index index = 0); |
| void CheckHasSharedMemory(const Location* loc, Opcode opcode); |
| void CheckBlockDeclaration(const Location* loc, |
| Opcode opcode, |
| const BlockDeclaration* decl); |
| template <typename T> |
| void CheckAtomicExpr(const T* expr, Result (TypeChecker::*func)(Opcode)); |
| void CheckFuncSignature(const Location* loc, const FuncDeclaration& decl); |
| class CheckFuncSignatureExprVisitorDelegate; |
| |
| void CheckFunc(const Location* loc, const Func* func); |
| void PrintConstExprError(const Location* loc, const char* desc); |
| void CheckConstInitExpr(const Location* loc, |
| const ExprList& expr, |
| Type expected_type, |
| const char* desc); |
| void CheckGlobal(const Location* loc, const Global* global); |
| void CheckLimits(const Location* loc, |
| const Limits* limits, |
| uint64_t absolute_max, |
| const char* desc); |
| void CheckTable(const Location* loc, const Table* table); |
| void CheckElemSegments(const Module* module); |
| void CheckMemory(const Location* loc, const Memory* memory); |
| void CheckDataSegments(const Module* module); |
| void CheckImport(const Location* loc, const Import* import); |
| void CheckExport(const Location* loc, const Export* export_); |
| |
| void CheckDuplicateExportBindings(const Module* module); |
| const TypeVector* CheckInvoke(const InvokeAction* action); |
| Result CheckGet(const GetAction* action, Type* out_type); |
| ActionResult CheckAction(const Action* action); |
| void CheckAssertReturnNanCommand(const Action* action); |
| void CheckCommand(const Command* command); |
| |
| void CheckEvent(const Location* loc, const Event* Event); |
| Result CheckEventVar(const Var* var, const Event** out_event); |
| |
| const ValidateOptions& options_; |
| Errors* errors_ = nullptr; |
| const Script* script_ = nullptr; |
| const Module* current_module_ = nullptr; |
| const Func* current_func_ = nullptr; |
| Index current_table_index_ = 0; |
| Index current_memory_index_ = 0; |
| Index current_global_index_ = 0; |
| Index num_imported_globals_ = 0; |
| Index current_event_index_ = 0; |
| TypeChecker typechecker_; |
| // Cached for access by OnTypecheckerError. |
| const Location* expr_loc_ = nullptr; |
| Result result_ = Result::Ok; |
| }; |
| |
| Validator::Validator(Errors* errors, |
| const Script* script, |
| const ValidateOptions& options) |
| : options_(options), errors_(errors), script_(script) { |
| typechecker_.set_error_callback( |
| [this](const char* msg) { OnTypecheckerError(msg); }); |
| } |
| |
| void Validator::PrintError(const Location* loc, const char* format, ...) { |
| result_ = Result::Error; |
| WABT_SNPRINTF_ALLOCA(buffer, length, format); |
| errors_->emplace_back(ErrorLevel::Error, *loc, buffer); |
| } |
| |
| void Validator::OnTypecheckerError(const char* msg) { |
| PrintError(expr_loc_, "%s", msg); |
| } |
| |
| static bool is_power_of_two(uint32_t x) { |
| return x && ((x & (x - 1)) == 0); |
| } |
| |
| static Address get_opcode_natural_alignment(Opcode opcode) { |
| Address memory_size = opcode.GetMemorySize(); |
| assert(memory_size != 0); |
| return memory_size; |
| } |
| |
| Result Validator::CheckVar(Index max_index, |
| const Var* var, |
| const char* desc, |
| Index* out_index) { |
| if (var->index() < max_index) { |
| if (out_index) { |
| *out_index = var->index(); |
| } |
| return Result::Ok; |
| } |
| PrintError(&var->loc, "%s variable out of range (max %" PRIindex ")", desc, |
| max_index); |
| return Result::Error; |
| } |
| |
| Result Validator::CheckFuncVar(const Var* var, const Func** out_func) { |
| Index index; |
| CHECK_RESULT( |
| CheckVar(current_module_->funcs.size(), var, "function", &index)); |
| if (out_func) { |
| *out_func = current_module_->funcs[index]; |
| } |
| return Result::Ok; |
| } |
| |
| Result Validator::CheckGlobalVar(const Var* var, |
| const Global** out_global, |
| Index* out_global_index) { |
| Index index; |
| CHECK_RESULT( |
| CheckVar(current_module_->globals.size(), var, "global", &index)); |
| if (out_global) { |
| *out_global = current_module_->globals[index]; |
| } |
| if (out_global_index) { |
| *out_global_index = index; |
| } |
| return Result::Ok; |
| } |
| |
| Type Validator::GetGlobalVarTypeOrAny(const Var* var) { |
| const Global* global; |
| if (Succeeded(CheckGlobalVar(var, &global, nullptr))) { |
| return global->type; |
| } |
| return Type::Any; |
| } |
| |
| Result Validator::CheckFuncTypeVar(const Var* var, |
| const FuncType** out_func_type) { |
| Index index; |
| CHECK_RESULT(CheckVar(current_module_->func_types.size(), var, |
| "function type", &index)); |
| if (out_func_type) { |
| *out_func_type = current_module_->func_types[index]; |
| } |
| return Result::Ok; |
| } |
| |
| Result Validator::CheckTableVar(const Var* var, const Table** out_table) { |
| Index index; |
| CHECK_RESULT(CheckVar(current_module_->tables.size(), var, "table", &index)); |
| if (out_table) { |
| *out_table = current_module_->tables[index]; |
| } |
| return Result::Ok; |
| } |
| |
| Result Validator::CheckMemoryVar(const Var* var, const Memory** out_memory) { |
| Index index; |
| CHECK_RESULT( |
| CheckVar(current_module_->memories.size(), var, "memory", &index)); |
| if (out_memory) { |
| *out_memory = current_module_->memories[index]; |
| } |
| return Result::Ok; |
| } |
| |
| Result Validator::CheckDataSegmentVar(const Var* var) { |
| Index index; |
| CHECK_RESULT(CheckVar(current_module_->data_segments.size(), var, |
| "data_segment", &index)); |
| return Result::Ok; |
| } |
| |
| Result Validator::CheckElemSegmentVar(const Var* var) { |
| Index index; |
| CHECK_RESULT(CheckVar(current_module_->elem_segments.size(), var, |
| "elem_segment", &index)); |
| return Result::Ok; |
| } |
| |
| Result Validator::CheckLocalVar(const Var* var, Type* out_type) { |
| const Func* func = current_func_; |
| Index max_index = func->GetNumParamsAndLocals(); |
| Index index = func->GetLocalIndex(*var); |
| if (index < max_index) { |
| if (out_type) { |
| Index num_params = func->GetNumParams(); |
| if (index < num_params) { |
| *out_type = func->GetParamType(index); |
| } else { |
| *out_type = current_func_->local_types[index - num_params]; |
| } |
| } |
| return Result::Ok; |
| } |
| |
| if (var->is_name()) { |
| PrintError(&var->loc, "undefined local variable \"%s\"", |
| var->name().c_str()); |
| } else { |
| PrintError(&var->loc, "local variable out of range (max %" PRIindex ")", |
| max_index); |
| } |
| return Result::Error; |
| } |
| |
| Type Validator::GetLocalVarTypeOrAny(const Var* var) { |
| Type type = Type::Any; |
| CheckLocalVar(var, &type); |
| return type; |
| } |
| |
| void Validator::CheckAlign(const Location* loc, |
| Address alignment, |
| Address natural_alignment) { |
| if (alignment != WABT_USE_NATURAL_ALIGNMENT) { |
| if (!is_power_of_two(alignment)) { |
| PrintError(loc, "alignment must be power-of-two"); |
| } |
| if (alignment > natural_alignment) { |
| PrintError(loc, |
| "alignment must not be larger than natural alignment (%u)", |
| natural_alignment); |
| } |
| } |
| } |
| |
| void Validator::CheckAtomicAlign(const Location* loc, |
| Address alignment, |
| Address natural_alignment) { |
| if (alignment != WABT_USE_NATURAL_ALIGNMENT) { |
| if (!is_power_of_two(alignment)) { |
| PrintError(loc, "alignment must be power-of-two"); |
| } |
| if (alignment != natural_alignment) { |
| PrintError(loc, "alignment must be equal to natural alignment (%u)", |
| natural_alignment); |
| } |
| } |
| } |
| |
| void Validator::CheckType(const Location* loc, |
| Type actual, |
| Type expected, |
| const char* desc) { |
| if (Failed(TypeChecker::CheckType(actual, expected))) { |
| PrintError(loc, "type mismatch at %s. got %s, expected %s", desc, |
| GetTypeName(actual), GetTypeName(expected)); |
| } |
| } |
| |
| void Validator::CheckTypeIndex(const Location* loc, |
| Type actual, |
| Type expected, |
| const char* desc, |
| Index index, |
| const char* index_kind) { |
| if (Failed(TypeChecker::CheckType(actual, expected))) { |
| PrintError( |
| loc, "type mismatch for %s %" PRIindex " of %s. got %s, expected %s", |
| index_kind, index, desc, GetTypeName(actual), GetTypeName(expected)); |
| } |
| } |
| |
| void Validator::CheckTypes(const Location* loc, |
| const TypeVector& actual, |
| const TypeVector& expected, |
| const char* desc, |
| const char* index_kind) { |
| if (actual.size() == expected.size()) { |
| for (size_t i = 0; i < actual.size(); ++i) { |
| CheckTypeIndex(loc, actual[i], expected[i], desc, i, index_kind); |
| } |
| } else { |
| PrintError(loc, "expected %" PRIzd " %ss, got %" PRIzd, expected.size(), |
| index_kind, actual.size()); |
| } |
| } |
| |
| void Validator::CheckConstTypes(const Location* loc, |
| const TypeVector& actual, |
| const ConstVector& expected, |
| const char* desc) { |
| if (actual.size() == expected.size()) { |
| for (size_t i = 0; i < actual.size(); ++i) { |
| CheckTypeIndex(loc, actual[i], expected[i].type, desc, i, "result"); |
| } |
| } else { |
| PrintError(loc, "expected %" PRIzd " results, got %" PRIzd, expected.size(), |
| actual.size()); |
| } |
| } |
| |
| void Validator::CheckConstType(const Location* loc, |
| Type actual, |
| const ConstVector& expected, |
| const char* desc) { |
| TypeVector actual_types; |
| if (actual != Type::Void) { |
| actual_types.push_back(actual); |
| } |
| CheckConstTypes(loc, actual_types, expected, desc); |
| } |
| |
| void Validator::CheckAssertReturnNanType(const Location* loc, |
| Type actual, |
| const char* desc) { |
| // When using assert_return_nan, the result can be either a f32 or f64 type |
| // so we special case it here. |
| if (actual != Type::F32 && actual != Type::F64) { |
| PrintError(loc, "type mismatch at %s. got %s, expected f32 or f64", desc, |
| GetTypeName(actual)); |
| } |
| } |
| |
| void Validator::CheckExprList(const Location* loc, const ExprList& exprs) { |
| ExprVisitor visitor(this); |
| // TODO(binji): Add const-visitors. |
| visitor.VisitExprList(const_cast<ExprList&>(exprs)); |
| } |
| |
| bool Validator::CheckHasMemory(const Location* loc, Opcode opcode) { |
| if (current_module_->memories.size() == 0) { |
| PrintError(loc, "%s requires an imported or defined memory.", |
| opcode.GetName()); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool Validator::CheckHasTable(const Location* loc, Opcode opcode, Index index) { |
| if (current_module_->tables.size() <= index) { |
| PrintError(loc, "%s requires table %d to be an imported or defined table.", |
| opcode.GetName(), index); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void Validator::CheckHasSharedMemory(const Location* loc, Opcode opcode) { |
| if (CheckHasMemory(loc, opcode)) { |
| Memory* memory = current_module_->memories[0]; |
| if (!memory->page_limits.is_shared) { |
| PrintError(loc, "%s requires memory to be shared.", opcode.GetName()); |
| } |
| } |
| } |
| |
| void Validator::CheckBlockDeclaration(const Location* loc, |
| Opcode opcode, |
| const BlockDeclaration* decl) { |
| if (decl->sig.GetNumParams() > 0 && |
| !options_.features.multi_value_enabled()) { |
| PrintError(loc, "%s params not currently supported.", opcode.GetName()); |
| } |
| if (decl->sig.GetNumResults() > 1 && |
| !options_.features.multi_value_enabled()) { |
| PrintError(loc, "multiple %s results not currently supported.", |
| opcode.GetName()); |
| } |
| if (decl->has_func_type) { |
| const FuncType* func_type; |
| if (Succeeded(CheckFuncTypeVar(&decl->type_var, &func_type))) { |
| CheckTypes(loc, decl->sig.result_types, func_type->sig.result_types, |
| opcode.GetName(), "result"); |
| CheckTypes(loc, decl->sig.param_types, func_type->sig.param_types, |
| opcode.GetName(), "argument"); |
| } |
| } |
| } |
| |
| template <typename T> |
| void Validator::CheckAtomicExpr(const T* expr, |
| Result (TypeChecker::*func)(Opcode)) { |
| CheckHasSharedMemory(&expr->loc, expr->opcode); |
| CheckAtomicAlign(&expr->loc, expr->align, |
| get_opcode_natural_alignment(expr->opcode)); |
| (typechecker_.*func)(expr->opcode); |
| } |
| |
| Result Validator::OnBinaryExpr(BinaryExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnBinary(expr->opcode); |
| return Result::Ok; |
| } |
| |
| Result Validator::BeginBlockExpr(BlockExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckBlockDeclaration(&expr->loc, Opcode::Block, &expr->block.decl); |
| typechecker_.OnBlock(expr->block.decl.sig.param_types, |
| expr->block.decl.sig.result_types); |
| return Result::Ok; |
| } |
| |
| Result Validator::EndBlockExpr(BlockExpr* expr) { |
| expr_loc_ = &expr->block.end_loc; |
| typechecker_.OnEnd(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnBrExpr(BrExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnBr(expr->var.index()); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnBrIfExpr(BrIfExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnBrIf(expr->var.index()); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnBrOnExnExpr(BrOnExnExpr* expr) { |
| expr_loc_ = &expr->loc; |
| const Event* event; |
| if (Succeeded(CheckEventVar(&expr->event_var, &event))) { |
| typechecker_.OnBrOnExn(expr->label_var.index(), |
| event->decl.sig.param_types); |
| } |
| return Result::Ok; |
| } |
| |
| Result Validator::OnBrTableExpr(BrTableExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.BeginBrTable(); |
| for (const Var& var : expr->targets) { |
| typechecker_.OnBrTableTarget(var.index()); |
| } |
| typechecker_.OnBrTableTarget(expr->default_target.index()); |
| typechecker_.EndBrTable(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnCallExpr(CallExpr* expr) { |
| expr_loc_ = &expr->loc; |
| const Func* callee; |
| if (Succeeded(CheckFuncVar(&expr->var, &callee))) { |
| typechecker_.OnCall(callee->decl.sig.param_types, |
| callee->decl.sig.result_types); |
| } |
| return Result::Ok; |
| } |
| |
| Result Validator::OnCallIndirectExpr(CallIndirectExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasTable(&expr->loc, Opcode::CallIndirect, expr->table.index()); |
| CheckFuncSignature(&expr->loc, expr->decl); |
| typechecker_.OnCallIndirect(expr->decl.sig.param_types, |
| expr->decl.sig.result_types); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnCompareExpr(CompareExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnCompare(expr->opcode); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnConstExpr(ConstExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnConst(expr->const_.type); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnConvertExpr(ConvertExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnConvert(expr->opcode); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnDropExpr(DropExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnDrop(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnGlobalGetExpr(GlobalGetExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnGlobalGet(GetGlobalVarTypeOrAny(&expr->var)); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnGlobalSetExpr(GlobalSetExpr* expr) { |
| expr_loc_ = &expr->loc; |
| Type type = Type::Any; |
| const Global* global; |
| Index global_index; |
| if (Succeeded(CheckGlobalVar(&expr->var, &global, &global_index))) { |
| if (!global->mutable_) { |
| PrintError(&expr->loc, |
| "can't global.set on immutable global at index %" PRIindex ".", |
| global_index); |
| } |
| type = global->type; |
| } |
| typechecker_.OnGlobalSet(type); |
| return Result::Ok; |
| } |
| |
| Result Validator::BeginIfExpr(IfExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckBlockDeclaration(&expr->loc, Opcode::If, &expr->true_.decl); |
| typechecker_.OnIf(expr->true_.decl.sig.param_types, |
| expr->true_.decl.sig.result_types); |
| return Result::Ok; |
| } |
| |
| Result Validator::AfterIfTrueExpr(IfExpr* expr) { |
| if (!expr->false_.empty()) { |
| typechecker_.OnElse(); |
| } |
| return Result::Ok; |
| } |
| |
| Result Validator::EndIfExpr(IfExpr* expr) { |
| expr_loc_ = |
| expr->false_.empty() ? &expr->true_.end_loc : &expr->false_end_loc; |
| typechecker_.OnEnd(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnLoadExpr(LoadExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasMemory(&expr->loc, expr->opcode); |
| CheckAlign(&expr->loc, expr->align, |
| get_opcode_natural_alignment(expr->opcode)); |
| typechecker_.OnLoad(expr->opcode); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnLocalGetExpr(LocalGetExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnLocalGet(GetLocalVarTypeOrAny(&expr->var)); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnLocalSetExpr(LocalSetExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnLocalSet(GetLocalVarTypeOrAny(&expr->var)); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnLocalTeeExpr(LocalTeeExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnLocalTee(GetLocalVarTypeOrAny(&expr->var)); |
| return Result::Ok; |
| } |
| |
| Result Validator::BeginLoopExpr(LoopExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckBlockDeclaration(&expr->loc, Opcode::Loop, &expr->block.decl); |
| typechecker_.OnLoop(expr->block.decl.sig.param_types, |
| expr->block.decl.sig.result_types); |
| return Result::Ok; |
| } |
| |
| Result Validator::EndLoopExpr(LoopExpr* expr) { |
| expr_loc_ = &expr->block.end_loc; |
| typechecker_.OnEnd(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnMemoryCopyExpr(MemoryCopyExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasMemory(&expr->loc, Opcode::MemoryCopy); |
| typechecker_.OnMemoryCopy(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnDataDropExpr(DataDropExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasMemory(&expr->loc, Opcode::DataDrop); |
| CheckDataSegmentVar(&expr->var); |
| typechecker_.OnDataDrop(expr->var.index()); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnMemoryFillExpr(MemoryFillExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasMemory(&expr->loc, Opcode::MemoryFill); |
| typechecker_.OnMemoryFill(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnMemoryGrowExpr(MemoryGrowExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasMemory(&expr->loc, Opcode::MemoryGrow); |
| typechecker_.OnMemoryGrow(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnMemoryInitExpr(MemoryInitExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasMemory(&expr->loc, Opcode::MemoryInit); |
| CheckDataSegmentVar(&expr->var); |
| typechecker_.OnMemoryInit(expr->var.index()); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnMemorySizeExpr(MemorySizeExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasMemory(&expr->loc, Opcode::MemorySize); |
| typechecker_.OnMemorySize(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnTableCopyExpr(TableCopyExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasTable(&expr->loc, Opcode::TableCopy); |
| typechecker_.OnTableCopy(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnElemDropExpr(ElemDropExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasTable(&expr->loc, Opcode::ElemDrop); |
| CheckElemSegmentVar(&expr->var); |
| typechecker_.OnElemDrop(expr->var.index()); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnTableInitExpr(TableInitExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnTableInit(expr->table_index.index(), |
| expr->segment_index.index()); |
| if (!CheckHasTable(&expr->loc, Opcode::TableInit)) |
| return Result::Ok; |
| CheckTableVar(&expr->table_index, nullptr); |
| CheckElemSegmentVar(&expr->segment_index); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnTableGetExpr(TableGetExpr* expr) { |
| const Table* table; |
| CheckTableVar(&expr->var, &table); |
| expr_loc_ = &expr->loc; |
| CheckHasTable(&expr->loc, Opcode::TableGet, expr->var.index()); |
| typechecker_.OnTableGet(table->elem_type); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnTableSetExpr(TableSetExpr* expr) { |
| const Table* table; |
| CheckTableVar(&expr->var, &table); |
| expr_loc_ = &expr->loc; |
| CheckHasTable(&expr->loc, Opcode::TableSet, expr->var.index()); |
| typechecker_.OnTableSet(table->elem_type); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnTableGrowExpr(TableGrowExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasTable(&expr->loc, Opcode::TableGrow, expr->var.index()); |
| typechecker_.OnTableGrow(expr->var.index()); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnTableSizeExpr(TableSizeExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasTable(&expr->loc, Opcode::TableSize, expr->var.index()); |
| typechecker_.OnTableSize(expr->var.index()); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnRefFuncExpr(RefFuncExpr* expr) { |
| expr_loc_ = &expr->loc; |
| const Func* callee; |
| if (Succeeded(CheckFuncVar(&expr->var, &callee))) { |
| typechecker_.OnRefFuncExpr(expr->var.index()); |
| } |
| return Result::Ok; |
| } |
| |
| Result Validator::OnRefNullExpr(RefNullExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnRefNullExpr(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnRefIsNullExpr(RefIsNullExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnRefIsNullExpr(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnNopExpr(NopExpr* expr) { |
| expr_loc_ = &expr->loc; |
| return Result::Ok; |
| } |
| |
| Result Validator::OnReturnExpr(ReturnExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnReturn(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnReturnCallExpr(ReturnCallExpr* expr) { |
| expr_loc_ = &expr->loc; |
| const Func* callee; |
| if (Succeeded(CheckFuncVar(&expr->var, &callee))) { |
| typechecker_.OnReturnCall(callee->decl.sig.param_types, |
| callee->decl.sig.result_types); |
| } |
| return Result::Ok; |
| } |
| |
| Result Validator::OnReturnCallIndirectExpr(ReturnCallIndirectExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasTable(&expr->loc, Opcode::ReturnCallIndirect, expr->table.index()); |
| CheckFuncSignature(&expr->loc, expr->decl); |
| typechecker_.OnReturnCallIndirect(expr->decl.sig.param_types, |
| expr->decl.sig.result_types); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnSelectExpr(SelectExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnSelect(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnStoreExpr(StoreExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasMemory(&expr->loc, expr->opcode); |
| CheckAlign(&expr->loc, expr->align, |
| get_opcode_natural_alignment(expr->opcode)); |
| typechecker_.OnStore(expr->opcode); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnUnaryExpr(UnaryExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnUnary(expr->opcode); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnUnreachableExpr(UnreachableExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnUnreachable(); |
| return Result::Ok; |
| } |
| |
| Result Validator::BeginTryExpr(TryExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckBlockDeclaration(&expr->loc, Opcode::Try, &expr->block.decl); |
| typechecker_.OnTry(expr->block.decl.sig.param_types, |
| expr->block.decl.sig.result_types); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnCatchExpr(TryExpr* expr) { |
| typechecker_.OnCatch(); |
| return Result::Ok; |
| } |
| |
| Result Validator::EndTryExpr(TryExpr* expr) { |
| expr_loc_ = &expr->block.end_loc; |
| typechecker_.OnEnd(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnThrowExpr(ThrowExpr* expr) { |
| expr_loc_ = &expr->loc; |
| const Event* event; |
| if (Succeeded(CheckEventVar(&expr->var, &event))) { |
| typechecker_.OnThrow(event->decl.sig.param_types); |
| } |
| return Result::Ok; |
| } |
| |
| Result Validator::OnRethrowExpr(RethrowExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnRethrow(); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnAtomicWaitExpr(AtomicWaitExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckAtomicExpr(expr, &TypeChecker::OnAtomicWait); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnAtomicNotifyExpr(AtomicNotifyExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckAtomicExpr(expr, &TypeChecker::OnAtomicNotify); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnAtomicLoadExpr(AtomicLoadExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckAtomicExpr(expr, &TypeChecker::OnAtomicLoad); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnAtomicStoreExpr(AtomicStoreExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckAtomicExpr(expr, &TypeChecker::OnAtomicStore); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnAtomicRmwExpr(AtomicRmwExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckAtomicExpr(expr, &TypeChecker::OnAtomicRmw); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnAtomicRmwCmpxchgExpr(AtomicRmwCmpxchgExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckAtomicExpr(expr, &TypeChecker::OnAtomicRmwCmpxchg); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnTernaryExpr(TernaryExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnTernary(expr->opcode); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnSimdLaneOpExpr(SimdLaneOpExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnSimdLaneOp(expr->opcode, expr->val); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnSimdShuffleOpExpr(SimdShuffleOpExpr* expr) { |
| expr_loc_ = &expr->loc; |
| typechecker_.OnSimdShuffleOp(expr->opcode, expr->val); |
| return Result::Ok; |
| } |
| |
| Result Validator::OnLoadSplatExpr(LoadSplatExpr* expr) { |
| expr_loc_ = &expr->loc; |
| CheckHasMemory(&expr->loc, expr->opcode); |
| CheckAlign(&expr->loc, expr->align, |
| get_opcode_natural_alignment(expr->opcode)); |
| typechecker_.OnLoad(expr->opcode); |
| return Result::Ok; |
| } |
| |
| void Validator::CheckFuncSignature(const Location* loc, |
| const FuncDeclaration& decl) { |
| if (decl.has_func_type) { |
| const FuncType* func_type; |
| if (Succeeded(CheckFuncTypeVar(&decl.type_var, &func_type))) { |
| CheckTypes(loc, decl.sig.result_types, func_type->sig.result_types, |
| "function", "result"); |
| CheckTypes(loc, decl.sig.param_types, func_type->sig.param_types, |
| "function", "argument"); |
| } |
| } |
| } |
| |
| void Validator::CheckFunc(const Location* loc, const Func* func) { |
| current_func_ = func; |
| CheckFuncSignature(loc, func->decl); |
| if (!options_.features.multi_value_enabled() && func->GetNumResults() > 1) { |
| PrintError(loc, "multiple result values not currently supported."); |
| // Don't run any other checks, the won't test the result_type properly. |
| return; |
| } |
| |
| expr_loc_ = loc; |
| typechecker_.BeginFunction(func->decl.sig.result_types); |
| CheckExprList(loc, func->exprs); |
| typechecker_.EndFunction(); |
| current_func_ = nullptr; |
| } |
| |
| void Validator::PrintConstExprError(const Location* loc, const char* desc) { |
| PrintError(loc, |
| "invalid %s, must be a constant expression; either *.const or " |
| "global.get.", |
| desc); |
| } |
| |
| void Validator::CheckConstInitExpr(const Location* loc, |
| const ExprList& exprs, |
| Type expected_type, |
| const char* desc) { |
| Type type = Type::Void; |
| if (!exprs.empty()) { |
| if (exprs.size() > 1) { |
| PrintConstExprError(loc, desc); |
| return; |
| } |
| |
| const Expr* expr = &exprs.front(); |
| loc = &expr->loc; |
| |
| switch (expr->type()) { |
| case ExprType::Const: |
| type = cast<ConstExpr>(expr)->const_.type; |
| break; |
| |
| case ExprType::GlobalGet: { |
| const Global* ref_global = nullptr; |
| Index ref_global_index; |
| if (Failed(CheckGlobalVar(&cast<GlobalGetExpr>(expr)->var, &ref_global, |
| &ref_global_index))) { |
| return; |
| } |
| |
| type = ref_global->type; |
| if (ref_global_index >= num_imported_globals_) { |
| PrintError( |
| loc, |
| "initializer expression can only reference an imported global"); |
| } |
| |
| if (ref_global->mutable_) { |
| PrintError( |
| loc, "initializer expression cannot reference a mutable global"); |
| } |
| break; |
| } |
| |
| case ExprType::RefFunc: |
| type = Type::Funcref; |
| break; |
| |
| case ExprType::RefNull: |
| type = Type::Nullref; |
| break; |
| |
| default: |
| PrintConstExprError(loc, desc); |
| return; |
| } |
| } |
| |
| CheckType(loc, type, expected_type, desc); |
| } |
| |
| void Validator::CheckGlobal(const Location* loc, const Global* global) { |
| CheckConstInitExpr(loc, global->init_expr, global->type, |
| "global initializer expression"); |
| } |
| |
| void Validator::CheckLimits(const Location* loc, |
| const Limits* limits, |
| uint64_t absolute_max, |
| const char* desc) { |
| if (limits->initial > absolute_max) { |
| PrintError(loc, "initial %s (%" PRIu64 ") must be <= (%" PRIu64 ")", desc, |
| limits->initial, absolute_max); |
| } |
| |
| if (limits->has_max) { |
| if (limits->max > absolute_max) { |
| PrintError(loc, "max %s (%" PRIu64 ") must be <= (%" PRIu64 ")", desc, |
| limits->max, absolute_max); |
| } |
| |
| if (limits->max < limits->initial) { |
| PrintError(loc, |
| "max %s (%" PRIu64 ") must be >= initial %s (%" PRIu64 ")", |
| desc, limits->max, desc, limits->initial); |
| } |
| } |
| } |
| |
| void Validator::CheckTable(const Location* loc, const Table* table) { |
| if (current_table_index_ == 1 && |
| !options_.features.reference_types_enabled()) { |
| PrintError(loc, "only one table allowed"); |
| } |
| CheckLimits(loc, &table->elem_limits, UINT32_MAX, "elems"); |
| |
| if (table->elem_limits.is_shared) { |
| PrintError(loc, "tables may not be shared"); |
| } |
| if (table->elem_type == Type::Anyref && |
| !options_.features.reference_types_enabled()) { |
| PrintError(loc, "tables must have anyref type"); |
| } |
| if (table->elem_type != Type::Anyref && table->elem_type != Type::Funcref) { |
| PrintError(loc, "tables must have anyref or funcref type"); |
| } |
| } |
| |
| void Validator::CheckElemSegments(const Module* module) { |
| for (const ModuleField& field : module->fields) { |
| if (auto elem_segment_field = dyn_cast<ElemSegmentModuleField>(&field)) { |
| auto&& elem_segment = elem_segment_field->elem_segment; |
| for (const ElemExpr& elem_expr : elem_segment.elem_exprs) { |
| if (elem_expr.kind == ElemExprKind::RefFunc) { |
| CheckFuncVar(&elem_expr.var, nullptr); |
| } |
| } |
| |
| if (elem_segment.is_passive()) { |
| continue; |
| } |
| if (Failed(CheckTableVar(&elem_segment.table_var, nullptr))) { |
| continue; |
| } |
| CheckConstInitExpr(&field.loc, elem_segment.offset, Type::I32, |
| "elem segment offset"); |
| } |
| } |
| } |
| |
| void Validator::CheckMemory(const Location* loc, const Memory* memory) { |
| if (current_memory_index_ == 1) { |
| PrintError(loc, "only one memory block allowed"); |
| } |
| CheckLimits(loc, &memory->page_limits, WABT_MAX_PAGES, "pages"); |
| |
| if (memory->page_limits.is_shared) { |
| if (!options_.features.threads_enabled()) { |
| PrintError(loc, "memories may not be shared"); |
| } else if (!memory->page_limits.has_max) { |
| PrintError(loc, "shared memories must have max sizes"); |
| } |
| } |
| } |
| |
| void Validator::CheckDataSegments(const Module* module) { |
| for (const ModuleField& field : module->fields) { |
| if (auto data_segment_field = dyn_cast<DataSegmentModuleField>(&field)) { |
| auto&& data_segment = data_segment_field->data_segment; |
| const Memory* memory; |
| if (data_segment.is_passive()) { |
| continue; |
| } |
| if (Failed(CheckMemoryVar(&data_segment.memory_var, &memory))) { |
| continue; |
| } |
| CheckConstInitExpr(&field.loc, data_segment.offset, Type::I32, |
| "data segment offset"); |
| } |
| } |
| } |
| |
| void Validator::CheckImport(const Location* loc, const Import* import) { |
| switch (import->kind()) { |
| case ExternalKind::Event: |
| ++current_event_index_; |
| CheckEvent(loc, &cast<EventImport>(import)->event); |
| break; |
| |
| case ExternalKind::Func: { |
| auto* func_import = cast<FuncImport>(import); |
| if (func_import->func.decl.has_func_type) { |
| CheckFuncTypeVar(&func_import->func.decl.type_var, nullptr); |
| } |
| break; |
| } |
| |
| case ExternalKind::Table: |
| CheckTable(loc, &cast<TableImport>(import)->table); |
| ++current_table_index_; |
| break; |
| |
| case ExternalKind::Memory: |
| CheckMemory(loc, &cast<MemoryImport>(import)->memory); |
| ++current_memory_index_; |
| break; |
| |
| case ExternalKind::Global: { |
| auto* global_import = cast<GlobalImport>(import); |
| if (global_import->global.mutable_ && |
| !options_.features.mutable_globals_enabled()) { |
| PrintError(loc, "mutable globals cannot be imported"); |
| } |
| ++num_imported_globals_; |
| ++current_global_index_; |
| break; |
| } |
| } |
| } |
| |
| void Validator::CheckExport(const Location* loc, const Export* export_) { |
| switch (export_->kind) { |
| case ExternalKind::Event: |
| CheckEventVar(&export_->var, nullptr); |
| break; |
| case ExternalKind::Func: |
| CheckFuncVar(&export_->var, nullptr); |
| break; |
| case ExternalKind::Table: |
| CheckTableVar(&export_->var, nullptr); |
| break; |
| case ExternalKind::Memory: |
| CheckMemoryVar(&export_->var, nullptr); |
| break; |
| case ExternalKind::Global: { |
| const Global* global; |
| if (Succeeded(CheckGlobalVar(&export_->var, &global, nullptr))) { |
| if (global->mutable_ && !options_.features.mutable_globals_enabled()) { |
| PrintError(&export_->var.loc, "mutable globals cannot be exported"); |
| } |
| } |
| break; |
| } |
| } |
| } |
| |
| void Validator::CheckDuplicateExportBindings(const Module* module) { |
| module->export_bindings.FindDuplicates( |
| [this](const BindingHash::value_type& a, |
| const BindingHash::value_type& b) { |
| // Choose the location that is later in the file. |
| const Location& a_loc = a.second.loc; |
| const Location& b_loc = b.second.loc; |
| const Location& loc = a_loc.line > b_loc.line ? a_loc : b_loc; |
| PrintError(&loc, "redefinition of export \"%s\"", a.first.c_str()); |
| }); |
| } |
| |
| Result Validator::CheckModule(const Module* module) { |
| bool seen_start = false; |
| |
| current_module_ = module; |
| current_table_index_ = 0; |
| current_memory_index_ = 0; |
| current_global_index_ = 0; |
| num_imported_globals_ = 0; |
| current_event_index_ = 0; |
| |
| for (const ModuleField& field : module->fields) { |
| switch (field.type()) { |
| case ModuleFieldType::Event: |
| ++current_event_index_; |
| CheckEvent(&field.loc, &cast<EventModuleField>(&field)->event); |
| break; |
| |
| case ModuleFieldType::Func: |
| CheckFunc(&field.loc, &cast<FuncModuleField>(&field)->func); |
| break; |
| |
| case ModuleFieldType::Global: |
| CheckGlobal(&field.loc, &cast<GlobalModuleField>(&field)->global); |
| current_global_index_++; |
| break; |
| |
| case ModuleFieldType::Import: |
| CheckImport(&field.loc, cast<ImportModuleField>(&field)->import.get()); |
| break; |
| |
| case ModuleFieldType::Export: |
| CheckExport(&field.loc, &cast<ExportModuleField>(&field)->export_); |
| break; |
| |
| case ModuleFieldType::Table: |
| CheckTable(&field.loc, &cast<TableModuleField>(&field)->table); |
| current_table_index_++; |
| break; |
| |
| case ModuleFieldType::ElemSegment: |
| // Checked below. |
| break; |
| |
| case ModuleFieldType::Memory: |
| CheckMemory(&field.loc, &cast<MemoryModuleField>(&field)->memory); |
| current_memory_index_++; |
| break; |
| |
| case ModuleFieldType::DataSegment: |
| // Checked below. |
| break; |
| |
| case ModuleFieldType::FuncType: |
| break; |
| |
| case ModuleFieldType::Start: { |
| if (seen_start) { |
| PrintError(&field.loc, "only one start function allowed"); |
| } |
| |
| const Func* start_func = nullptr; |
| CheckFuncVar(&cast<StartModuleField>(&field)->start, &start_func); |
| if (start_func) { |
| if (start_func->GetNumParams() != 0) { |
| PrintError(&field.loc, "start function must be nullary"); |
| } |
| |
| if (start_func->GetNumResults() != 0) { |
| PrintError(&field.loc, "start function must not return anything"); |
| } |
| } |
| seen_start = true; |
| break; |
| } |
| } |
| } |
| |
| CheckElemSegments(module); |
| CheckDataSegments(module); |
| CheckDuplicateExportBindings(module); |
| |
| return result_; |
| } |
| |
| // Returns the result type of the invoked function, checked by the caller; |
| // returning nullptr means that another error occured first, so the result type |
| // should be ignored. |
| const TypeVector* Validator::CheckInvoke(const InvokeAction* action) { |
| const Module* module = script_->GetModule(action->module_var); |
| if (!module) { |
| PrintError(&action->loc, "unknown module"); |
| return nullptr; |
| } |
| |
| const Export* export_ = module->GetExport(action->name); |
| if (!export_) { |
| PrintError(&action->loc, "unknown function export \"%s\"", |
| action->name.c_str()); |
| return nullptr; |
| } |
| |
| const Func* func = module->GetFunc(export_->var); |
| if (!func) { |
| // This error will have already been reported, just skip it. |
| return nullptr; |
| } |
| |
| size_t actual_args = action->args.size(); |
| size_t expected_args = func->GetNumParams(); |
| if (expected_args != actual_args) { |
| PrintError(&action->loc, |
| "too %s parameters to function. got %" PRIzd |
| ", expected %" PRIzd, |
| actual_args > expected_args ? "many" : "few", actual_args, |
| expected_args); |
| return nullptr; |
| } |
| for (size_t i = 0; i < actual_args; ++i) { |
| const Const* const_ = &action->args[i]; |
| CheckTypeIndex(&const_->loc, const_->type, func->GetParamType(i), "invoke", |
| i, "argument"); |
| } |
| |
| return &func->decl.sig.result_types; |
| } |
| |
| Result Validator::CheckGet(const GetAction* action, Type* out_type) { |
| const Module* module = script_->GetModule(action->module_var); |
| if (!module) { |
| PrintError(&action->loc, "unknown module"); |
| return Result::Error; |
| } |
| |
| const Export* export_ = module->GetExport(action->name); |
| if (!export_) { |
| PrintError(&action->loc, "unknown global export \"%s\"", |
| action->name.c_str()); |
| return Result::Error; |
| } |
| |
| const Global* global = module->GetGlobal(export_->var); |
| if (!global) { |
| // This error will have already been reported, just skip it. |
| return Result::Error; |
| } |
| |
| *out_type = global->type; |
| return Result::Ok; |
| } |
| |
| Result Validator::CheckEventVar(const Var* var, const Event** out_event) { |
| Index index; |
| CHECK_RESULT(CheckVar(current_module_->events.size(), var, "event", &index)); |
| if (out_event) { |
| *out_event = current_module_->events[index]; |
| } |
| return Result::Ok; |
| } |
| |
| void Validator::CheckEvent(const Location* loc, const Event* event) { |
| CheckFuncSignature(loc, event->decl); |
| if (event->decl.sig.GetNumResults() > 0) { |
| PrintError(loc, "Event signature must have 0 results."); |
| } |
| } |
| |
| Validator::ActionResult Validator::CheckAction(const Action* action) { |
| ActionResult result; |
| ZeroMemory(result); |
| |
| switch (action->type()) { |
| case ActionType::Invoke: |
| result.types = CheckInvoke(cast<InvokeAction>(action)); |
| result.kind = |
| result.types ? ActionResult::Kind::Types : ActionResult::Kind::Error; |
| break; |
| |
| case ActionType::Get: |
| if (Succeeded(CheckGet(cast<GetAction>(action), &result.type))) { |
| result.kind = ActionResult::Kind::Type; |
| } else { |
| result.kind = ActionResult::Kind::Error; |
| } |
| break; |
| } |
| |
| return result; |
| } |
| |
| void Validator::CheckAssertReturnNanCommand(const Action* action) { |
| ActionResult result = CheckAction(action); |
| |
| // A valid result type will either be f32 or f64; convert a Types result into |
| // a Type result, so it is easier to check below. Type::Any is used to |
| // specify a type that should not be checked (because an earlier error |
| // occurred). |
| if (result.kind == ActionResult::Kind::Types) { |
| if (result.types->size() == 1) { |
| result.kind = ActionResult::Kind::Type; |
| result.type = (*result.types)[0]; |
| } else { |
| PrintError(&action->loc, "expected 1 result, got %" PRIzd, |
| result.types->size()); |
| result.type = Type::Any; |
| } |
| } |
| |
| if (result.kind == ActionResult::Kind::Type && result.type != Type::Any) { |
| CheckAssertReturnNanType(&action->loc, result.type, "action"); |
| } |
| } |
| |
| void Validator::CheckCommand(const Command* command) { |
| switch (command->type) { |
| case CommandType::Module: |
| CheckModule(&cast<ModuleCommand>(command)->module); |
| break; |
| |
| case CommandType::Action: |
| // Ignore result type. |
| CheckAction(cast<ActionCommand>(command)->action.get()); |
| break; |
| |
| case CommandType::Register: |
| case CommandType::AssertMalformed: |
| case CommandType::AssertInvalid: |
| case CommandType::AssertUnlinkable: |
| case CommandType::AssertUninstantiable: |
| // Ignore. |
| break; |
| |
| case CommandType::AssertReturnFunc: { |
| auto* cmd = cast<AssertReturnFuncCommand>(command); |
| const Action* action = cmd->action.get(); |
| CheckAction(action); |
| break; |
| } |
| |
| case CommandType::AssertReturn: { |
| auto* assert_return_command = cast<AssertReturnCommand>(command); |
| const Action* action = assert_return_command->action.get(); |
| ActionResult result = CheckAction(action); |
| switch (result.kind) { |
| case ActionResult::Kind::Types: |
| CheckConstTypes(&action->loc, *result.types, |
| assert_return_command->expected, "action"); |
| break; |
| |
| case ActionResult::Kind::Type: |
| CheckConstType(&action->loc, result.type, |
| assert_return_command->expected, "action"); |
| break; |
| |
| case ActionResult::Kind::Error: |
| // Error occurred, don't do any further checks. |
| break; |
| } |
| break; |
| } |
| |
| case CommandType::AssertReturnCanonicalNan: |
| CheckAssertReturnNanCommand( |
| cast<AssertReturnCanonicalNanCommand>(command)->action.get()); |
| break; |
| |
| case CommandType::AssertReturnArithmeticNan: |
| CheckAssertReturnNanCommand( |
| cast<AssertReturnArithmeticNanCommand>(command)->action.get()); |
| break; |
| |
| case CommandType::AssertTrap: |
| // ignore result type. |
| CheckAction(cast<AssertTrapCommand>(command)->action.get()); |
| break; |
| case CommandType::AssertExhaustion: |
| // ignore result type. |
| CheckAction(cast<AssertExhaustionCommand>(command)->action.get()); |
| break; |
| } |
| } |
| |
| Result Validator::CheckScript(const Script* script) { |
| for (const std::unique_ptr<Command>& command : script->commands) |
| CheckCommand(command.get()); |
| return result_; |
| } |
| |
| class Validator::CheckFuncSignatureExprVisitorDelegate |
| : public ExprVisitor::DelegateNop { |
| public: |
| explicit CheckFuncSignatureExprVisitorDelegate(Validator* validator) |
| : validator_(validator) {} |
| |
| Result BeginBlockExpr(BlockExpr* expr) override { |
| validator_->CheckBlockDeclaration(&expr->loc, Opcode::Block, |
| &expr->block.decl); |
| return Result::Ok; |
| } |
| |
| Result OnCallIndirectExpr(CallIndirectExpr* expr) override { |
| validator_->CheckFuncSignature(&expr->loc, expr->decl); |
| return Result::Ok; |
| } |
| |
| Result OnReturnCallIndirectExpr(ReturnCallIndirectExpr* expr) override { |
| validator_->CheckFuncSignature(&expr->loc, expr->decl); |
| return Result::Ok; |
| } |
| |
| Result BeginIfExpr(IfExpr* expr) override { |
| validator_->CheckBlockDeclaration(&expr->loc, Opcode::If, |
| &expr->true_.decl); |
| return Result::Ok; |
| } |
| |
| Result BeginLoopExpr(LoopExpr* expr) override { |
| validator_->CheckBlockDeclaration(&expr->loc, Opcode::Loop, |
| &expr->block.decl); |
| return Result::Ok; |
| } |
| |
| Result BeginTryExpr(TryExpr* expr) override { |
| validator_->CheckBlockDeclaration(&expr->loc, Opcode::Try, |
| &expr->block.decl); |
| return Result::Ok; |
| } |
| |
| private: |
| Validator* validator_; |
| }; |
| |
| Result Validator::CheckAllFuncSignatures(const Module* module) { |
| current_module_ = module; |
| for (const ModuleField& field : module->fields) { |
| switch (field.type()) { |
| case ModuleFieldType::Func: { |
| auto func_field = cast<FuncModuleField>(&field); |
| CheckFuncSignature(&field.loc, func_field->func.decl); |
| CheckFuncSignatureExprVisitorDelegate delegate(this); |
| ExprVisitor visitor(&delegate); |
| // TODO(binji): would rather not do a const_cast here, but the visitor |
| // is non-const only. |
| visitor.VisitFunc(const_cast<Func*>(&func_field->func)); |
| break; |
| } |
| |
| default: |
| break; |
| } |
| } |
| return result_; |
| } |
| |
| } // end anonymous namespace |
| |
| Result ValidateScript(const Script* script, |
| Errors* errors, |
| const ValidateOptions& options) { |
| Validator validator(errors, script, options); |
| |
| return validator.CheckScript(script); |
| } |
| |
| Result ValidateModule(const Module* module, |
| Errors* errors, |
| const ValidateOptions& options) { |
| Validator validator(errors, nullptr, options); |
| |
| return validator.CheckModule(module); |
| } |
| |
| Result ValidateFuncSignatures(const Module* module, |
| Errors* errors, |
| const ValidateOptions& options) { |
| Validator validator(errors, nullptr, options); |
| |
| return validator.CheckAllFuncSignatures(module); |
| } |
| |
| } // namespace wabt |