| /* |
| * 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 "resolve-names.h" |
| |
| #include <assert.h> |
| #include <stdio.h> |
| |
| #include "ir.h" |
| #include "wast-parser-lexer-shared.h" |
| |
| namespace wabt { |
| |
| namespace { |
| |
| typedef Label* LabelPtr; |
| |
| struct Context { |
| Context(); |
| |
| SourceErrorHandler* error_handler = nullptr; |
| WastLexer* lexer = nullptr; |
| Script* script = nullptr; |
| Module* current_module = nullptr; |
| Func* current_func = nullptr; |
| ExprVisitor visitor; |
| std::vector<Label*> labels; |
| Result result = Result::Ok; |
| }; |
| |
| Context::Context() { |
| WABT_ZERO_MEMORY(visitor); |
| } |
| |
| } // 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 push_label(Context* ctx, Label* label) { |
| ctx->labels.push_back(label); |
| } |
| |
| static void pop_label(Context* ctx) { |
| ctx->labels.pop_back(); |
| } |
| |
| struct FindDuplicateBindingContext { |
| Context* ctx; |
| const char* desc; |
| }; |
| |
| static void on_duplicate_binding(const BindingHash::value_type& a, |
| const BindingHash::value_type& b, |
| void* user_data) { |
| FindDuplicateBindingContext* fdbc = |
| static_cast<FindDuplicateBindingContext*>(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(fdbc->ctx, &loc, "redefinition of %s \"%s\"", fdbc->desc, |
| a.first.c_str()); |
| } |
| |
| static void check_duplicate_bindings(Context* ctx, |
| const BindingHash* bindings, |
| const char* desc) { |
| FindDuplicateBindingContext fdbc; |
| fdbc.ctx = ctx; |
| fdbc.desc = desc; |
| bindings->find_duplicates(on_duplicate_binding, &fdbc); |
| } |
| |
| static void resolve_label_var(Context* ctx, Var* var) { |
| if (var->type == VarType::Name) { |
| for (int i = ctx->labels.size() - 1; i >= 0; --i) { |
| Label* label = ctx->labels[i]; |
| if (string_slices_are_equal(label, &var->name)) { |
| destroy_string_slice(&var->name); |
| var->type = VarType::Index; |
| var->index = ctx->labels.size() - i - 1; |
| return; |
| } |
| } |
| print_error(ctx, &var->loc, |
| "undefined label variable \"" PRIstringslice "\"", |
| WABT_PRINTF_STRING_SLICE_ARG(var->name)); |
| } |
| } |
| |
| static void resolve_var(Context* ctx, |
| const BindingHash* bindings, |
| Var* var, |
| const char* desc) { |
| if (var->type == VarType::Name) { |
| int index = get_index_from_var(bindings, var); |
| if (index == -1) { |
| print_error(ctx, &var->loc, |
| "undefined %s variable \"" PRIstringslice "\"", desc, |
| WABT_PRINTF_STRING_SLICE_ARG(var->name)); |
| return; |
| } |
| |
| destroy_string_slice(&var->name); |
| var->index = index; |
| var->type = VarType::Index; |
| } |
| } |
| |
| static void resolve_func_var(Context* ctx, Var* var) { |
| resolve_var(ctx, &ctx->current_module->func_bindings, var, "function"); |
| } |
| |
| static void resolve_global_var(Context* ctx, Var* var) { |
| resolve_var(ctx, &ctx->current_module->global_bindings, var, "global"); |
| } |
| |
| static void resolve_func_type_var(Context* ctx, Var* var) { |
| resolve_var(ctx, &ctx->current_module->func_type_bindings, var, |
| "function type"); |
| } |
| |
| static void resolve_table_var(Context* ctx, Var* var) { |
| resolve_var(ctx, &ctx->current_module->table_bindings, var, "table"); |
| } |
| |
| static void resolve_memory_var(Context* ctx, Var* var) { |
| resolve_var(ctx, &ctx->current_module->memory_bindings, var, "memory"); |
| } |
| |
| static void resolve_local_var(Context* ctx, Var* var) { |
| if (var->type == VarType::Name) { |
| int index = get_local_index_by_var(ctx->current_func, var); |
| if (index == -1) { |
| print_error(ctx, &var->loc, |
| "undefined local variable \"" PRIstringslice "\"", |
| WABT_PRINTF_STRING_SLICE_ARG(var->name)); |
| return; |
| } |
| |
| destroy_string_slice(&var->name); |
| var->index = index; |
| var->type = VarType::Index; |
| } |
| } |
| |
| static Result begin_block_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| push_label(ctx, &expr->block->label); |
| return Result::Ok; |
| } |
| |
| static Result end_block_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| pop_label(ctx); |
| return Result::Ok; |
| } |
| |
| static Result begin_loop_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| push_label(ctx, &expr->loop->label); |
| return Result::Ok; |
| } |
| |
| static Result end_loop_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| pop_label(ctx); |
| return Result::Ok; |
| } |
| |
| static Result on_br_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| resolve_label_var(ctx, &expr->br.var); |
| return Result::Ok; |
| } |
| |
| static Result on_br_if_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| resolve_label_var(ctx, &expr->br_if.var); |
| return Result::Ok; |
| } |
| |
| static Result on_br_table_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| for (Var& target: *expr->br_table.targets) |
| resolve_label_var(ctx, &target); |
| resolve_label_var(ctx, &expr->br_table.default_target); |
| return Result::Ok; |
| } |
| |
| static Result on_call_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| resolve_func_var(ctx, &expr->call.var); |
| return Result::Ok; |
| } |
| |
| static Result on_call_indirect_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| resolve_func_type_var(ctx, &expr->call_indirect.var); |
| return Result::Ok; |
| } |
| |
| static Result on_get_global_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| resolve_global_var(ctx, &expr->get_global.var); |
| return Result::Ok; |
| } |
| |
| static Result on_get_local_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| resolve_local_var(ctx, &expr->get_local.var); |
| return Result::Ok; |
| } |
| |
| static Result begin_if_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| push_label(ctx, &expr->if_.true_->label); |
| return Result::Ok; |
| } |
| |
| static Result end_if_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| pop_label(ctx); |
| return Result::Ok; |
| } |
| |
| static Result on_set_global_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| resolve_global_var(ctx, &expr->set_global.var); |
| return Result::Ok; |
| } |
| |
| static Result on_set_local_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| resolve_local_var(ctx, &expr->set_local.var); |
| return Result::Ok; |
| } |
| |
| static Result on_tee_local_expr(Expr* expr, void* user_data) { |
| Context* ctx = static_cast<Context*>(user_data); |
| resolve_local_var(ctx, &expr->tee_local.var); |
| return Result::Ok; |
| } |
| |
| static void visit_func(Context* ctx, Func* func) { |
| ctx->current_func = func; |
| if (decl_has_func_type(&func->decl)) |
| resolve_func_type_var(ctx, &func->decl.type_var); |
| |
| check_duplicate_bindings(ctx, &func->param_bindings, "parameter"); |
| check_duplicate_bindings(ctx, &func->local_bindings, "local"); |
| |
| visit_func(func, &ctx->visitor); |
| ctx->current_func = nullptr; |
| } |
| |
| static void visit_export(Context* ctx, Export* export_) { |
| switch (export_->kind) { |
| case ExternalKind::Func: |
| resolve_func_var(ctx, &export_->var); |
| break; |
| |
| case ExternalKind::Table: |
| resolve_table_var(ctx, &export_->var); |
| break; |
| |
| case ExternalKind::Memory: |
| resolve_memory_var(ctx, &export_->var); |
| break; |
| |
| case ExternalKind::Global: |
| resolve_global_var(ctx, &export_->var); |
| break; |
| } |
| } |
| |
| static void visit_global(Context* ctx, Global* global) { |
| visit_expr_list(global->init_expr, &ctx->visitor); |
| } |
| |
| static void visit_elem_segment(Context* ctx, ElemSegment* segment) { |
| resolve_table_var(ctx, &segment->table_var); |
| visit_expr_list(segment->offset, &ctx->visitor); |
| for (Var& var: segment->vars) |
| resolve_func_var(ctx, &var); |
| } |
| |
| static void visit_data_segment(Context* ctx, DataSegment* segment) { |
| resolve_memory_var(ctx, &segment->memory_var); |
| visit_expr_list(segment->offset, &ctx->visitor); |
| } |
| |
| static void visit_module(Context* ctx, Module* module) { |
| ctx->current_module = module; |
| check_duplicate_bindings(ctx, &module->func_bindings, "function"); |
| check_duplicate_bindings(ctx, &module->global_bindings, "global"); |
| check_duplicate_bindings(ctx, &module->func_type_bindings, "function type"); |
| check_duplicate_bindings(ctx, &module->table_bindings, "table"); |
| check_duplicate_bindings(ctx, &module->memory_bindings, "memory"); |
| |
| for (Func* func : module->funcs) |
| visit_func(ctx, func); |
| for (Export* export_ : module->exports) |
| visit_export(ctx, export_); |
| for (Global* global : module->globals) |
| visit_global(ctx, global); |
| for (ElemSegment* elem_segment : module->elem_segments) |
| visit_elem_segment(ctx, elem_segment); |
| for (DataSegment* data_segment : module->data_segments) |
| visit_data_segment(ctx, data_segment); |
| if (module->start) |
| resolve_func_var(ctx, module->start); |
| ctx->current_module = nullptr; |
| } |
| |
| static void visit_raw_module(Context* ctx, RawModule* raw_module) { |
| if (raw_module->type == RawModuleType::Text) |
| visit_module(ctx, raw_module->text); |
| } |
| |
| static void visit_command(Context* ctx, Command* command) { |
| switch (command->type) { |
| case CommandType::Module: |
| visit_module(ctx, command->module); |
| break; |
| |
| case CommandType::Action: |
| case CommandType::AssertReturn: |
| case CommandType::AssertReturnCanonicalNan: |
| case CommandType::AssertReturnArithmeticNan: |
| case CommandType::AssertTrap: |
| case CommandType::AssertExhaustion: |
| case CommandType::Register: |
| /* Don't resolve a module_var, since it doesn't really behave like other |
| * vars. You can't reference a module by index. */ |
| break; |
| |
| case CommandType::AssertMalformed: |
| /* Malformed modules should not be text; the whole point of this |
| * assertion is to test for malformed binary modules. */ |
| break; |
| |
| case CommandType::AssertInvalid: { |
| /* The module may be invalid because the names cannot be resolved; we |
| * don't want to print errors or fail if that's the case, but we still |
| * should try to resolve names when possible. */ |
| SourceErrorHandlerNop new_error_handler; |
| |
| Context new_ctx; |
| new_ctx.error_handler = &new_error_handler; |
| new_ctx.lexer = ctx->lexer; |
| new_ctx.visitor = ctx->visitor; |
| new_ctx.visitor.user_data = &new_ctx; |
| new_ctx.result = Result::Ok; |
| |
| visit_raw_module(&new_ctx, command->assert_invalid.module); |
| if (WABT_FAILED(new_ctx.result)) { |
| command->type = CommandType::AssertInvalidNonBinary; |
| } |
| break; |
| } |
| |
| case CommandType::AssertInvalidNonBinary: |
| /* The only reason a module would be "non-binary" is if the names cannot |
| * be resolved. So we assume name resolution has already been tried and |
| * failed, so skip it. */ |
| break; |
| |
| case CommandType::AssertUnlinkable: |
| visit_raw_module(ctx, command->assert_unlinkable.module); |
| break; |
| |
| case CommandType::AssertUninstantiable: |
| visit_raw_module(ctx, command->assert_uninstantiable.module); |
| break; |
| } |
| } |
| |
| static void visit_script(Context* ctx, Script* script) { |
| for (const std::unique_ptr<Command>& command: script->commands) |
| visit_command(ctx, command.get()); |
| } |
| |
| static void init_context(Context* ctx, |
| WastLexer* lexer, |
| Script* script, |
| SourceErrorHandler* error_handler) { |
| ctx->lexer = lexer; |
| ctx->error_handler = error_handler; |
| ctx->result = Result::Ok; |
| ctx->script = script; |
| ctx->visitor.user_data = ctx; |
| ctx->visitor.begin_block_expr = begin_block_expr; |
| ctx->visitor.end_block_expr = end_block_expr; |
| ctx->visitor.begin_loop_expr = begin_loop_expr; |
| ctx->visitor.end_loop_expr = end_loop_expr; |
| ctx->visitor.on_br_expr = on_br_expr; |
| ctx->visitor.on_br_if_expr = on_br_if_expr; |
| ctx->visitor.on_br_table_expr = on_br_table_expr; |
| ctx->visitor.on_call_expr = on_call_expr; |
| ctx->visitor.on_call_indirect_expr = on_call_indirect_expr; |
| ctx->visitor.on_get_global_expr = on_get_global_expr; |
| ctx->visitor.on_get_local_expr = on_get_local_expr; |
| ctx->visitor.begin_if_expr = begin_if_expr; |
| ctx->visitor.end_if_expr = end_if_expr; |
| ctx->visitor.on_set_global_expr = on_set_global_expr; |
| ctx->visitor.on_set_local_expr = on_set_local_expr; |
| ctx->visitor.on_tee_local_expr = on_tee_local_expr; |
| } |
| |
| Result resolve_names_module(WastLexer* lexer, |
| Module* module, |
| SourceErrorHandler* error_handler) { |
| Context ctx; |
| init_context(&ctx, lexer, nullptr, error_handler); |
| visit_module(&ctx, module); |
| return ctx.result; |
| } |
| |
| Result resolve_names_script(WastLexer* lexer, |
| Script* script, |
| SourceErrorHandler* error_handler) { |
| Context ctx; |
| init_context(&ctx, lexer, script, error_handler); |
| visit_script(&ctx, script); |
| return ctx.result; |
| } |
| |
| } // namespace wabt |