blob: dd85f437a0d727ffc3178f1f7ac2e58c010fbb81 [file] [log] [blame] [edit]
/*
* 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