| /* |
| * 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 "validator.h" |
| #include "config.h" |
| |
| #include <assert.h> |
| #include <inttypes.h> |
| #include <memory.h> |
| #include <stdarg.h> |
| #include <stdio.h> |
| |
| #include "binary-reader.h" |
| #include "type-checker.h" |
| #include "wast-parser-lexer-shared.h" |
| |
| namespace wabt { |
| |
| namespace { |
| |
| enum class ActionResultKind { |
| Error, |
| Types, |
| Type, |
| }; |
| |
| struct ActionResult { |
| ActionResultKind kind; |
| union { |
| const TypeVector* types; |
| Type type; |
| }; |
| }; |
| |
| struct Context { |
| WABT_DISALLOW_COPY_AND_ASSIGN(Context); |
| Context(SourceErrorHandler*, WastLexer*, const Script*); |
| |
| SourceErrorHandler* error_handler = nullptr; |
| WastLexer* lexer = nullptr; |
| const Script* script = nullptr; |
| const Module* current_module = nullptr; |
| const Func* current_func = nullptr; |
| int current_table_index = 0; |
| int current_memory_index = 0; |
| int current_global_index = 0; |
| int num_imported_globals = 0; |
| TypeChecker typechecker; |
| /* Cached for access by on_typechecker_error */ |
| const Location* expr_loc = nullptr; |
| Result result = Result::Ok; |
| }; |
| |
| Context::Context(SourceErrorHandler* error_handler, |
| WastLexer* lexer, |
| const Script* script) |
| : error_handler(error_handler), lexer(lexer), script(script) {} |
| |
| } // namespace |
| |
| static void WABT_PRINTF_FORMAT(3, 4) |
| print_error(Context* ctx, const Location* loc, const char* fmt, ...) { |
| ctx->result = Result::Error; |
| va_list args; |
| va_start(args, fmt); |
| wast_format_error(ctx->error_handler, loc, ctx->lexer, fmt, args); |
| va_end(args); |
| } |
| |
| static void on_typechecker_error(const char* msg, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| print_error(ctx, ctx->expr_loc, "%s", msg); |
| } |
| |
| static bool is_power_of_two(uint32_t x) { |
| return x && ((x & (x - 1)) == 0); |
| } |
| |
| static uint32_t get_opcode_natural_alignment(Opcode opcode) { |
| uint32_t memory_size = get_opcode_memory_size(opcode); |
| assert(memory_size != 0); |
| return memory_size; |
| } |
| |
| static Result check_var(Context* ctx, |
| int max_index, |
| const Var* var, |
| const char* desc, |
| int* out_index) { |
| assert(var->type == VarType::Index); |
| if (var->index >= 0 && var->index < max_index) { |
| if (out_index) |
| *out_index = var->index; |
| return Result::Ok; |
| } |
| print_error(ctx, &var->loc, "%s variable out of range (max %d)", desc, |
| max_index); |
| return Result::Error; |
| } |
| |
| static Result check_func_var(Context* ctx, |
| const Var* var, |
| const Func** out_func) { |
| int index; |
| if (WABT_FAILED(check_var(ctx, ctx->current_module->funcs.size(), var, |
| "function", &index))) { |
| return Result::Error; |
| } |
| |
| if (out_func) |
| *out_func = ctx->current_module->funcs[index]; |
| return Result::Ok; |
| } |
| |
| static Result check_global_var(Context* ctx, |
| const Var* var, |
| const Global** out_global, |
| int* out_global_index) { |
| int index; |
| if (WABT_FAILED(check_var(ctx, ctx->current_module->globals.size(), var, |
| "global", &index))) { |
| return Result::Error; |
| } |
| |
| if (out_global) |
| *out_global = ctx->current_module->globals[index]; |
| if (out_global_index) |
| *out_global_index = index; |
| return Result::Ok; |
| } |
| |
| static Type get_global_var_type_or_any(Context* ctx, const Var* var) { |
| const Global* global; |
| if (WABT_SUCCEEDED(check_global_var(ctx, var, &global, nullptr))) |
| return global->type; |
| return Type::Any; |
| } |
| |
| static Result check_func_type_var(Context* ctx, |
| const Var* var, |
| const FuncType** out_func_type) { |
| int index; |
| if (WABT_FAILED(check_var(ctx, ctx->current_module->func_types.size(), var, |
| "function type", &index))) { |
| return Result::Error; |
| } |
| |
| if (out_func_type) |
| *out_func_type = ctx->current_module->func_types[index]; |
| return Result::Ok; |
| } |
| |
| static Result check_table_var(Context* ctx, |
| const Var* var, |
| const Table** out_table) { |
| int index; |
| if (WABT_FAILED(check_var(ctx, ctx->current_module->tables.size(), var, |
| "table", &index))) { |
| return Result::Error; |
| } |
| |
| if (out_table) |
| *out_table = ctx->current_module->tables[index]; |
| return Result::Ok; |
| } |
| |
| static Result check_memory_var(Context* ctx, |
| const Var* var, |
| const Memory** out_memory) { |
| int index; |
| if (WABT_FAILED(check_var(ctx, ctx->current_module->memories.size(), var, |
| "memory", &index))) { |
| return Result::Error; |
| } |
| |
| if (out_memory) |
| *out_memory = ctx->current_module->memories[index]; |
| return Result::Ok; |
| } |
| |
| static Result check_local_var(Context* ctx, const Var* var, Type* out_type) { |
| const Func* func = ctx->current_func; |
| int max_index = get_num_params_and_locals(func); |
| int index = get_local_index_by_var(func, var); |
| if (index >= 0 && index < max_index) { |
| if (out_type) { |
| int num_params = get_num_params(func); |
| if (index < num_params) { |
| *out_type = get_param_type(func, index); |
| } else { |
| *out_type = ctx->current_func->local_types[index - num_params]; |
| } |
| } |
| return Result::Ok; |
| } |
| |
| if (var->type == VarType::Name) { |
| print_error(ctx, &var->loc, |
| "undefined local variable \"" PRIstringslice "\"", |
| WABT_PRINTF_STRING_SLICE_ARG(var->name)); |
| } else { |
| print_error(ctx, &var->loc, "local variable out of range (max %d)", |
| max_index); |
| } |
| return Result::Error; |
| } |
| |
| static Type get_local_var_type_or_any(Context* ctx, const Var* var) { |
| Type type = Type::Any; |
| check_local_var(ctx, var, &type); |
| return type; |
| } |
| |
| static void check_align(Context* ctx, |
| const Location* loc, |
| uint32_t alignment, |
| uint32_t natural_alignment) { |
| if (alignment != WABT_USE_NATURAL_ALIGNMENT) { |
| if (!is_power_of_two(alignment)) |
| print_error(ctx, loc, "alignment must be power-of-two"); |
| if (alignment > natural_alignment) { |
| print_error(ctx, loc, |
| "alignment must not be larger than natural alignment (%u)", |
| natural_alignment); |
| } |
| } |
| } |
| |
| static void check_offset(Context* ctx, const Location* loc, uint64_t offset) { |
| if (offset > UINT32_MAX) { |
| print_error(ctx, loc, "offset must be less than or equal to 0xffffffff"); |
| } |
| } |
| |
| static void check_type(Context* ctx, |
| const Location* loc, |
| Type actual, |
| Type expected, |
| const char* desc) { |
| if (expected != actual) { |
| print_error(ctx, loc, "type mismatch at %s. got %s, expected %s", desc, |
| get_type_name(actual), get_type_name(expected)); |
| } |
| } |
| |
| static void check_type_index(Context* ctx, |
| const Location* loc, |
| Type actual, |
| Type expected, |
| const char* desc, |
| int index, |
| const char* index_kind) { |
| if (expected != actual && expected != Type::Any && actual != Type::Any) { |
| print_error(ctx, loc, "type mismatch for %s %d of %s. got %s, expected %s", |
| index_kind, index, desc, get_type_name(actual), |
| get_type_name(expected)); |
| } |
| } |
| |
| static void check_types(Context* ctx, |
| 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) { |
| check_type_index(ctx, loc, actual[i], expected[i], desc, i, index_kind); |
| } |
| } else { |
| print_error(ctx, loc, "expected %" PRIzd " %ss, got %" PRIzd, |
| expected.size(), index_kind, actual.size()); |
| } |
| } |
| |
| static void check_const_types(Context* ctx, |
| 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) { |
| check_type_index(ctx, loc, actual[i], expected[i].type, desc, i, |
| "result"); |
| } |
| } else { |
| print_error(ctx, loc, "expected %" PRIzd " results, got %" PRIzd, |
| expected.size(), actual.size()); |
| } |
| } |
| |
| static void check_const_type(Context* ctx, |
| const Location* loc, |
| Type actual, |
| const ConstVector& expected, |
| const char* desc) { |
| TypeVector actual_types; |
| if (actual != Type::Void) |
| actual_types.push_back(actual); |
| check_const_types(ctx, loc, actual_types, expected, desc); |
| } |
| |
| static void check_assert_return_nan_type(Context* ctx, |
| 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) { |
| print_error(ctx, loc, "type mismatch at %s. got %s, expected f32 or f64", |
| desc, get_type_name(actual)); |
| } |
| } |
| |
| static void check_expr(Context* ctx, const Expr* expr); |
| |
| static void check_expr_list(Context* ctx, |
| const Location* loc, |
| const Expr* first) { |
| if (first) { |
| for (const Expr* expr = first; expr; expr = expr->next) |
| check_expr(ctx, expr); |
| } |
| } |
| |
| static void check_has_memory(Context* ctx, const Location* loc, Opcode opcode) { |
| if (ctx->current_module->memories.size() == 0) { |
| print_error(ctx, loc, "%s requires an imported or defined memory.", |
| get_opcode_name(opcode)); |
| } |
| } |
| |
| static void check_expr(Context* ctx, const Expr* expr) { |
| ctx->expr_loc = &expr->loc; |
| |
| switch (expr->type) { |
| case ExprType::Binary: |
| typechecker_on_binary(&ctx->typechecker, expr->binary.opcode); |
| break; |
| |
| case ExprType::Block: |
| typechecker_on_block(&ctx->typechecker, &expr->block->sig); |
| check_expr_list(ctx, &expr->loc, expr->block->first); |
| typechecker_on_end(&ctx->typechecker); |
| break; |
| |
| case ExprType::Br: |
| typechecker_on_br(&ctx->typechecker, expr->br.var.index); |
| break; |
| |
| case ExprType::BrIf: |
| typechecker_on_br_if(&ctx->typechecker, expr->br_if.var.index); |
| break; |
| |
| case ExprType::BrTable: { |
| typechecker_begin_br_table(&ctx->typechecker); |
| for (Var& var: *expr->br_table.targets) { |
| typechecker_on_br_table_target(&ctx->typechecker, var.index); |
| } |
| typechecker_on_br_table_target(&ctx->typechecker, |
| expr->br_table.default_target.index); |
| typechecker_end_br_table(&ctx->typechecker); |
| break; |
| } |
| |
| case ExprType::Call: { |
| const Func* callee; |
| if (WABT_SUCCEEDED(check_func_var(ctx, &expr->call.var, &callee))) { |
| typechecker_on_call(&ctx->typechecker, &callee->decl.sig.param_types, |
| &callee->decl.sig.result_types); |
| } |
| break; |
| } |
| |
| case ExprType::CallIndirect: { |
| const FuncType* func_type; |
| if (ctx->current_module->tables.size() == 0) { |
| print_error(ctx, &expr->loc, |
| "found call_indirect operator, but no table"); |
| } |
| if (WABT_SUCCEEDED( |
| check_func_type_var(ctx, &expr->call_indirect.var, &func_type))) { |
| typechecker_on_call_indirect(&ctx->typechecker, |
| &func_type->sig.param_types, |
| &func_type->sig.result_types); |
| } |
| break; |
| } |
| |
| case ExprType::Compare: |
| typechecker_on_compare(&ctx->typechecker, expr->compare.opcode); |
| break; |
| |
| case ExprType::Const: |
| typechecker_on_const(&ctx->typechecker, expr->const_.type); |
| break; |
| |
| case ExprType::Convert: |
| typechecker_on_convert(&ctx->typechecker, expr->convert.opcode); |
| break; |
| |
| case ExprType::Drop: |
| typechecker_on_drop(&ctx->typechecker); |
| break; |
| |
| case ExprType::GetGlobal: |
| typechecker_on_get_global( |
| &ctx->typechecker, |
| get_global_var_type_or_any(ctx, &expr->get_global.var)); |
| break; |
| |
| case ExprType::GetLocal: |
| typechecker_on_get_local( |
| &ctx->typechecker, |
| get_local_var_type_or_any(ctx, &expr->get_local.var)); |
| break; |
| |
| case ExprType::GrowMemory: |
| check_has_memory(ctx, &expr->loc, Opcode::GrowMemory); |
| typechecker_on_grow_memory(&ctx->typechecker); |
| break; |
| |
| case ExprType::If: |
| typechecker_on_if(&ctx->typechecker, &expr->if_.true_->sig); |
| check_expr_list(ctx, &expr->loc, expr->if_.true_->first); |
| if (expr->if_.false_) { |
| typechecker_on_else(&ctx->typechecker); |
| check_expr_list(ctx, &expr->loc, expr->if_.false_); |
| } |
| typechecker_on_end(&ctx->typechecker); |
| break; |
| |
| case ExprType::Load: |
| check_has_memory(ctx, &expr->loc, expr->load.opcode); |
| check_align(ctx, &expr->loc, expr->load.align, |
| get_opcode_natural_alignment(expr->load.opcode)); |
| check_offset(ctx, &expr->loc, expr->load.offset); |
| typechecker_on_load(&ctx->typechecker, expr->load.opcode); |
| break; |
| |
| case ExprType::Loop: |
| typechecker_on_loop(&ctx->typechecker, &expr->loop->sig); |
| check_expr_list(ctx, &expr->loc, expr->loop->first); |
| typechecker_on_end(&ctx->typechecker); |
| break; |
| |
| case ExprType::CurrentMemory: |
| check_has_memory(ctx, &expr->loc, Opcode::CurrentMemory); |
| typechecker_on_current_memory(&ctx->typechecker); |
| break; |
| |
| case ExprType::Nop: |
| break; |
| |
| case ExprType::Return: |
| typechecker_on_return(&ctx->typechecker); |
| break; |
| |
| case ExprType::Select: |
| typechecker_on_select(&ctx->typechecker); |
| break; |
| |
| case ExprType::SetGlobal: |
| typechecker_on_set_global( |
| &ctx->typechecker, |
| get_global_var_type_or_any(ctx, &expr->set_global.var)); |
| break; |
| |
| case ExprType::SetLocal: |
| typechecker_on_set_local( |
| &ctx->typechecker, |
| get_local_var_type_or_any(ctx, &expr->set_local.var)); |
| break; |
| |
| case ExprType::Store: |
| check_has_memory(ctx, &expr->loc, expr->store.opcode); |
| check_align(ctx, &expr->loc, expr->store.align, |
| get_opcode_natural_alignment(expr->store.opcode)); |
| check_offset(ctx, &expr->loc, expr->store.offset); |
| typechecker_on_store(&ctx->typechecker, expr->store.opcode); |
| break; |
| |
| case ExprType::TeeLocal: |
| typechecker_on_tee_local( |
| &ctx->typechecker, |
| get_local_var_type_or_any(ctx, &expr->tee_local.var)); |
| break; |
| |
| case ExprType::Unary: |
| typechecker_on_unary(&ctx->typechecker, expr->unary.opcode); |
| break; |
| |
| case ExprType::Unreachable: |
| typechecker_on_unreachable(&ctx->typechecker); |
| break; |
| } |
| } |
| |
| static void check_func_signature_matches_func_type(Context* ctx, |
| const Location* loc, |
| const FuncSignature& sig, |
| const FuncType* func_type) { |
| check_types(ctx, loc, sig.result_types, func_type->sig.result_types, |
| "function", "result"); |
| check_types(ctx, loc, sig.param_types, func_type->sig.param_types, "function", |
| "argument"); |
| } |
| |
| static void check_func(Context* ctx, const Location* loc, const Func* func) { |
| ctx->current_func = func; |
| if (get_num_results(func) > 1) { |
| print_error(ctx, loc, "multiple result values not currently supported."); |
| /* don't run any other checks, the won't test the result_type properly */ |
| return; |
| } |
| if (decl_has_func_type(&func->decl)) { |
| const FuncType* func_type; |
| if (WABT_SUCCEEDED( |
| check_func_type_var(ctx, &func->decl.type_var, &func_type))) { |
| check_func_signature_matches_func_type(ctx, loc, func->decl.sig, |
| func_type); |
| } |
| } |
| |
| ctx->expr_loc = loc; |
| typechecker_begin_function(&ctx->typechecker, &func->decl.sig.result_types); |
| check_expr_list(ctx, loc, func->first_expr); |
| typechecker_end_function(&ctx->typechecker); |
| ctx->current_func = nullptr; |
| } |
| |
| static void print_const_expr_error(Context* ctx, |
| const Location* loc, |
| const char* desc) { |
| print_error(ctx, loc, |
| "invalid %s, must be a constant expression; either *.const or " |
| "get_global.", |
| desc); |
| } |
| |
| static void check_const_init_expr(Context* ctx, |
| const Location* loc, |
| const Expr* expr, |
| Type expected_type, |
| const char* desc) { |
| Type type = Type::Void; |
| if (expr) { |
| if (expr->next) { |
| print_const_expr_error(ctx, loc, desc); |
| return; |
| } |
| |
| switch (expr->type) { |
| case ExprType::Const: |
| type = expr->const_.type; |
| break; |
| |
| case ExprType::GetGlobal: { |
| const Global* ref_global = nullptr; |
| int ref_global_index; |
| if (WABT_FAILED(check_global_var(ctx, &expr->get_global.var, |
| &ref_global, &ref_global_index))) { |
| return; |
| } |
| |
| type = ref_global->type; |
| /* globals can only reference previously defined, internal globals */ |
| if (ref_global_index >= ctx->current_global_index) { |
| print_error(ctx, loc, |
| "initializer expression can only reference a previously " |
| "defined global"); |
| } else if (ref_global_index >= ctx->num_imported_globals) { |
| print_error( |
| ctx, loc, |
| "initializer expression can only reference an imported global"); |
| } |
| |
| if (ref_global->mutable_) { |
| print_error( |
| ctx, loc, |
| "initializer expression cannot reference a mutable global"); |
| } |
| break; |
| } |
| |
| default: |
| print_const_expr_error(ctx, loc, desc); |
| return; |
| } |
| } |
| |
| check_type(ctx, expr ? &expr->loc : loc, type, expected_type, desc); |
| } |
| |
| static void check_global(Context* ctx, |
| const Location* loc, |
| const Global* global) { |
| check_const_init_expr(ctx, loc, global->init_expr, global->type, |
| "global initializer expression"); |
| } |
| |
| static void check_limits(Context* ctx, |
| const Location* loc, |
| const Limits* limits, |
| uint64_t absolute_max, |
| const char* desc) { |
| if (limits->initial > absolute_max) { |
| print_error(ctx, loc, "initial %s (%" PRIu64 ") must be <= (%" PRIu64 ")", |
| desc, limits->initial, absolute_max); |
| } |
| |
| if (limits->has_max) { |
| if (limits->max > absolute_max) { |
| print_error(ctx, loc, "max %s (%" PRIu64 ") must be <= (%" PRIu64 ")", |
| desc, limits->max, absolute_max); |
| } |
| |
| if (limits->max < limits->initial) { |
| print_error(ctx, loc, |
| "max %s (%" PRIu64 ") must be >= initial %s (%" PRIu64 ")", |
| desc, limits->max, desc, limits->initial); |
| } |
| } |
| } |
| |
| static void check_table(Context* ctx, const Location* loc, const Table* table) { |
| if (ctx->current_table_index == 1) |
| print_error(ctx, loc, "only one table allowed"); |
| check_limits(ctx, loc, &table->elem_limits, UINT32_MAX, "elems"); |
| } |
| |
| static void check_elem_segments(Context* ctx, const Module* module) { |
| for (ModuleField* field = module->first_field; field; field = field->next) { |
| if (field->type != ModuleFieldType::ElemSegment) |
| continue; |
| |
| ElemSegment* elem_segment = field->elem_segment; |
| const Table* table; |
| if (!WABT_SUCCEEDED(check_table_var(ctx, &elem_segment->table_var, &table))) |
| continue; |
| |
| for (const Var& var: elem_segment->vars) { |
| if (!WABT_SUCCEEDED(check_func_var(ctx, &var, nullptr))) |
| continue; |
| } |
| |
| check_const_init_expr(ctx, &field->loc, elem_segment->offset, Type::I32, |
| "elem segment offset"); |
| } |
| } |
| |
| static void check_memory(Context* ctx, |
| const Location* loc, |
| const Memory* memory) { |
| if (ctx->current_memory_index == 1) |
| print_error(ctx, loc, "only one memory block allowed"); |
| check_limits(ctx, loc, &memory->page_limits, WABT_MAX_PAGES, "pages"); |
| } |
| |
| static void check_data_segments(Context* ctx, const Module* module) { |
| for (ModuleField* field = module->first_field; field; field = field->next) { |
| if (field->type != ModuleFieldType::DataSegment) |
| continue; |
| |
| DataSegment* data_segment = field->data_segment; |
| const Memory* memory; |
| if (!WABT_SUCCEEDED( |
| check_memory_var(ctx, &data_segment->memory_var, &memory))) |
| continue; |
| |
| check_const_init_expr(ctx, &field->loc, data_segment->offset, Type::I32, |
| "data segment offset"); |
| } |
| } |
| |
| static void check_import(Context* ctx, |
| const Location* loc, |
| const Import* import) { |
| switch (import->kind) { |
| case ExternalKind::Func: |
| if (decl_has_func_type(&import->func->decl)) |
| check_func_type_var(ctx, &import->func->decl.type_var, nullptr); |
| break; |
| case ExternalKind::Table: |
| check_table(ctx, loc, import->table); |
| ctx->current_table_index++; |
| break; |
| case ExternalKind::Memory: |
| check_memory(ctx, loc, import->memory); |
| ctx->current_memory_index++; |
| break; |
| case ExternalKind::Global: |
| if (import->global->mutable_) { |
| print_error(ctx, loc, "mutable globals cannot be imported"); |
| } |
| ctx->num_imported_globals++; |
| ctx->current_global_index++; |
| break; |
| } |
| } |
| |
| static void check_export(Context* ctx, const Export* export_) { |
| switch (export_->kind) { |
| case ExternalKind::Func: |
| check_func_var(ctx, &export_->var, nullptr); |
| break; |
| case ExternalKind::Table: |
| check_table_var(ctx, &export_->var, nullptr); |
| break; |
| case ExternalKind::Memory: |
| check_memory_var(ctx, &export_->var, nullptr); |
| break; |
| case ExternalKind::Global: { |
| const Global* global; |
| if (WABT_SUCCEEDED( |
| check_global_var(ctx, &export_->var, &global, nullptr))) { |
| if (global->mutable_) { |
| print_error(ctx, &export_->var.loc, |
| "mutable globals cannot be exported"); |
| } |
| } |
| break; |
| } |
| } |
| } |
| |
| static void on_duplicate_binding(const BindingHash::value_type& a, |
| const BindingHash::value_type& b, |
| void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| /* 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; |
| print_error(ctx, &loc, "redefinition of export \"%s\"", a.first.c_str()); |
| } |
| |
| static void check_duplicate_export_bindings(Context* ctx, |
| const Module* module) { |
| module->export_bindings.find_duplicates(on_duplicate_binding, ctx); |
| } |
| |
| static void check_module(Context* ctx, const Module* module) { |
| bool seen_start = false; |
| |
| ctx->current_module = module; |
| ctx->current_table_index = 0; |
| ctx->current_memory_index = 0; |
| ctx->current_global_index = 0; |
| ctx->num_imported_globals = 0; |
| |
| for (ModuleField* field = module->first_field; field; field = field->next) { |
| switch (field->type) { |
| case ModuleFieldType::Func: |
| check_func(ctx, &field->loc, field->func); |
| break; |
| |
| case ModuleFieldType::Global: |
| check_global(ctx, &field->loc, field->global); |
| ctx->current_global_index++; |
| break; |
| |
| case ModuleFieldType::Import: |
| check_import(ctx, &field->loc, field->import); |
| break; |
| |
| case ModuleFieldType::Export: |
| check_export(ctx, field->export_); |
| break; |
| |
| case ModuleFieldType::Table: |
| check_table(ctx, &field->loc, field->table); |
| ctx->current_table_index++; |
| break; |
| |
| case ModuleFieldType::ElemSegment: |
| /* checked below */ |
| break; |
| |
| case ModuleFieldType::Memory: |
| check_memory(ctx, &field->loc, field->memory); |
| ctx->current_memory_index++; |
| break; |
| |
| case ModuleFieldType::DataSegment: |
| /* checked below */ |
| break; |
| |
| case ModuleFieldType::FuncType: |
| break; |
| |
| case ModuleFieldType::Start: { |
| if (seen_start) { |
| print_error(ctx, &field->loc, "only one start function allowed"); |
| } |
| |
| const Func* start_func = nullptr; |
| check_func_var(ctx, &field->start, &start_func); |
| if (start_func) { |
| if (get_num_params(start_func) != 0) { |
| print_error(ctx, &field->loc, "start function must be nullary"); |
| } |
| |
| if (get_num_results(start_func) != 0) { |
| print_error(ctx, &field->loc, |
| "start function must not return anything"); |
| } |
| } |
| seen_start = true; |
| break; |
| } |
| } |
| } |
| |
| check_elem_segments(ctx, module); |
| check_data_segments(ctx, module); |
| check_duplicate_export_bindings(ctx, module); |
| } |
| |
| /* 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. */ |
| static const TypeVector* check_invoke(Context* ctx, const Action* action) { |
| const ActionInvoke* invoke = action->invoke; |
| const Module* module = get_module_by_var(ctx->script, &action->module_var); |
| if (!module) { |
| print_error(ctx, &action->loc, "unknown module"); |
| return nullptr; |
| } |
| |
| Export* export_ = get_export_by_name(module, &action->name); |
| if (!export_) { |
| print_error(ctx, &action->loc, |
| "unknown function export \"" PRIstringslice "\"", |
| WABT_PRINTF_STRING_SLICE_ARG(action->name)); |
| return nullptr; |
| } |
| |
| Func* func = get_func_by_var(module, &export_->var); |
| if (!func) { |
| /* this error will have already been reported, just skip it */ |
| return nullptr; |
| } |
| |
| size_t actual_args = invoke->args.size(); |
| size_t expected_args = get_num_params(func); |
| if (expected_args != actual_args) { |
| print_error(ctx, &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_ = &invoke->args[i]; |
| check_type_index(ctx, &const_->loc, const_->type, get_param_type(func, i), |
| "invoke", i, "argument"); |
| } |
| |
| return &func->decl.sig.result_types; |
| } |
| |
| static Result check_get(Context* ctx, const Action* action, Type* out_type) { |
| const Module* module = get_module_by_var(ctx->script, &action->module_var); |
| if (!module) { |
| print_error(ctx, &action->loc, "unknown module"); |
| return Result::Error; |
| } |
| |
| Export* export_ = get_export_by_name(module, &action->name); |
| if (!export_) { |
| print_error(ctx, &action->loc, |
| "unknown global export \"" PRIstringslice "\"", |
| WABT_PRINTF_STRING_SLICE_ARG(action->name)); |
| return Result::Error; |
| } |
| |
| Global* global = get_global_by_var(module, &export_->var); |
| if (!global) { |
| /* this error will have already been reported, just skip it */ |
| return Result::Error; |
| } |
| |
| *out_type = global->type; |
| return Result::Ok; |
| } |
| |
| static ActionResult check_action(Context* ctx, const Action* action) { |
| ActionResult result; |
| WABT_ZERO_MEMORY(result); |
| |
| switch (action->type) { |
| case ActionType::Invoke: |
| result.types = check_invoke(ctx, action); |
| result.kind = |
| result.types ? ActionResultKind::Types : ActionResultKind::Error; |
| break; |
| |
| case ActionType::Get: |
| if (WABT_SUCCEEDED(check_get(ctx, action, &result.type))) |
| result.kind = ActionResultKind::Type; |
| else |
| result.kind = ActionResultKind::Error; |
| break; |
| } |
| |
| return result; |
| } |
| |
| static void check_assert_return_nan_command(Context* ctx, |
| const Action* action) { |
| ActionResult result = check_action(ctx, 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 == ActionResultKind::Types) { |
| if (result.types->size() == 1) { |
| result.kind = ActionResultKind::Type; |
| result.type = (*result.types)[0]; |
| } else { |
| print_error(ctx, &action->loc, "expected 1 result, got %" PRIzd, |
| result.types->size()); |
| result.type = Type::Any; |
| } |
| } |
| |
| if (result.kind == ActionResultKind::Type && result.type != Type::Any) |
| check_assert_return_nan_type(ctx, &action->loc, result.type, "action"); |
| } |
| |
| static void check_command(Context* ctx, const Command* command) { |
| switch (command->type) { |
| case CommandType::Module: |
| check_module(ctx, command->module); |
| break; |
| |
| case CommandType::Action: |
| /* ignore result type */ |
| check_action(ctx, command->action); |
| break; |
| |
| case CommandType::Register: |
| case CommandType::AssertMalformed: |
| case CommandType::AssertInvalid: |
| case CommandType::AssertInvalidNonBinary: |
| case CommandType::AssertUnlinkable: |
| case CommandType::AssertUninstantiable: |
| /* ignore */ |
| break; |
| |
| case CommandType::AssertReturn: { |
| const Action* action = command->assert_return.action; |
| ActionResult result = check_action(ctx, action); |
| switch (result.kind) { |
| case ActionResultKind::Types: |
| check_const_types(ctx, &action->loc, *result.types, |
| *command->assert_return.expected, "action"); |
| break; |
| |
| case ActionResultKind::Type: |
| check_const_type(ctx, &action->loc, result.type, |
| *command->assert_return.expected, "action"); |
| break; |
| |
| case ActionResultKind::Error: |
| /* error occurred, don't do any further checks */ |
| break; |
| } |
| break; |
| } |
| |
| case CommandType::AssertReturnCanonicalNan: |
| check_assert_return_nan_command( |
| ctx, command->assert_return_canonical_nan.action); |
| break; |
| |
| case CommandType::AssertReturnArithmeticNan: |
| check_assert_return_nan_command( |
| ctx, command->assert_return_arithmetic_nan.action); |
| break; |
| |
| case CommandType::AssertTrap: |
| case CommandType::AssertExhaustion: |
| /* ignore result type */ |
| check_action(ctx, command->assert_trap.action); |
| break; |
| } |
| } |
| |
| Result validate_script(WastLexer* lexer, |
| const struct Script* script, |
| SourceErrorHandler* error_handler) { |
| Context ctx(error_handler, lexer, script); |
| |
| TypeCheckerErrorHandler tc_error_handler; |
| tc_error_handler.on_error = on_typechecker_error; |
| tc_error_handler.user_data = &ctx; |
| ctx.typechecker.error_handler = &tc_error_handler; |
| |
| for (const std::unique_ptr<Command>& command : script->commands) |
| check_command(&ctx, command.get()); |
| return ctx.result; |
| } |
| |
| } // namespace wabt |