blob: 11dc51879ea90f5463c7b0f4bb64c90d4f3366f4 [file] [log] [blame] [edit]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/developer/debug/zxdb/expr/resolve_function.h"
#include "src/developer/debug/shared/logging/logging.h"
#include "src/developer/debug/zxdb/common/err.h"
#include "src/developer/debug/zxdb/common/err_or.h"
#include "src/developer/debug/zxdb/common/join_callbacks.h"
#include "src/developer/debug/zxdb/expr/cast.h"
#include "src/developer/debug/zxdb/expr/eval_context.h"
#include "src/developer/debug/zxdb/expr/find_name.h"
#include "src/developer/debug/zxdb/expr/found_name.h"
#include "src/developer/debug/zxdb/symbols/base_type.h"
#include "src/developer/debug/zxdb/symbols/dwarf_tag.h"
#include "src/developer/debug/zxdb/symbols/function.h"
#include "src/developer/debug/zxdb/symbols/function_call_info.h"
#include "src/developer/debug/zxdb/symbols/input_location.h"
#include "src/developer/debug/zxdb/symbols/process_symbols.h"
#include "src/developer/debug/zxdb/symbols/target_symbols.h"
#include "src/developer/debug/zxdb/symbols/type.h"
#include "src/developer/debug/zxdb/symbols/variable.h"
#include "src/lib/fxl/memory/ref_ptr.h"
namespace zxdb {
namespace {
bool TypeIsPointerOrArray(const Type* type) {
return DwarfTagIsPointerOrReference(type->tag()) || !!type->As<ArrayType>();
}
bool ImplicitTypeCastIsSupported(const Type* desired, const Type* given) {
// Only cast to a pointer if the given type is another pointer or an array (implying a pointer to
// the first element of the array). We will not implicitly convert an integer to a pointer (a user
// can manually cast it themselves if that's what they really want). CastExprValue below will do
// more robust checking of the pointed to types that we do not need to replicate here, but will
// allow any random integer to be cast to a pointer, which we don't want unless the user casts it
// themselves.
if (DwarfTagIsPointerOrReference(desired->tag())) {
if (!TypeIsPointerOrArray(given)) {
return false;
}
}
return true;
}
// Validate the given arguments against the types the function expects. We do some simple coercion
// and casting to get the right types from what the user gave us. If all of the attempts fail the
// callback will be issued reentrantly with an appropriate error. If the argument requires a full
// cast that cannot be done synchronously, then an asynchronous cast will be kicked off. Preserving
// the order of the arguments is important.
void CollectArguments(const fxl::RefPtr<EvalContext>& eval_context, std::string_view fn_name,
const std::vector<LazySymbol>& expected_types,
const std::vector<ExprValue>& given_args,
fit::callback<void(ErrOr<std::vector<ExprValue>>)> cb) {
if (expected_types.size() != given_args.size()) {
return cb(
Err("Function expects %zu arguments, got %zu", expected_types.size(), given_args.size()));
}
std::vector<ExprValue> results;
results.resize(given_args.size());
std::map<size_t, ExprValue> arg_map;
// This will collect the casted to types from below.
auto joiner = fxl::MakeRefCounted<JoinCallbacks<ErrOrValue>>();
for (size_t i = 0; i < expected_types.size(); i++) {
const Variable* var = expected_types[i].Get()->As<Variable>();
// This is the type the function expects. The corresponding ExprValue in |given_args| should
// either match this type or be castable to this type.
fxl::RefPtr<Type> desired_type = eval_context->GetConcreteType(var->type());
if (!desired_type) {
joiner->Abandon();
return cb(Err("Could not determine type of %s", var->GetAssignedName().c_str()));
}
if (desired_type->As<BaseType>()) {
if (desired_type->As<BaseType>()->base_type() == BaseType::kBaseTypeFloat) {
joiner->Abandon();
return cb(Err("Floating point arguments are not yet supported!"));
}
}
// Make sure the given parameter is supported.
if (given_args[i].type()->As<BaseType>() &&
given_args[i].type()->As<BaseType>()->base_type() == BaseType::kBaseTypeFloat) {
joiner->Abandon();
return cb(Err("Floating point arguments are not supported yet."));
}
// This is a bit tricky. The debugger can be helpful by forcing the given argument into the type
// the function expects, but if we are _too_ helpful, then we could end up calling a function
// with an argument that it was not expecting and produce a bogus result, or cause the program
// to crash. CastExprValue is powerful and will be more lenient than we can allow, so we have
// some restrictions to apply in ImplicitTypeCastIsSupported. For passing arrays as pointers, we
// can construct a new ExprValue with the address of the first element of the array.
// CastExprValue will refuse to do this, so it is done manually here.
if (DwarfTagIsPointerOrReference(desired_type->tag()) &&
given_args[i].type()->As<ArrayType>() &&
given_args[i].source().type() == ExprValueSource::Type::kMemory) {
arg_map[i] =
ExprValue(given_args[i].source().address(), desired_type, given_args[i].source());
} else if (ImplicitTypeCastIsSupported(desired_type.get(), given_args[i].type())) {
CastExprValue(eval_context, CastType::kImplicit, given_args[i], desired_type,
given_args[i].source(),
[&arg_map, i, cb = joiner->AddCallback()](ErrOrValue result) mutable {
if (result.ok())
arg_map[i] = result.take_value();
cb(result);
});
} else {
joiner->Abandon();
return cb(Err("Refusing to implicitly cast from %s to %s, try casting explicitly.",
given_args[i].type()->GetFullName().c_str(),
desired_type->GetFullName().c_str()));
}
}
joiner->Ready(
[results, arg_map, cb = std::move(cb)](const std::vector<ErrOrValue>& cast_results) mutable {
// If any of the casts failed, we cannot continue.
if (auto err_it = std::find_if(cast_results.begin(), cast_results.end(),
[](const ErrOrValue& result) { return result.has_error(); });
err_it != cast_results.end()) {
return cb(err_it->err());
}
for (auto& [i, expr_value] : arg_map) {
results[i] = std::move(expr_value);
}
cb(results);
});
// return results;
}
} // namespace
void ResolveFunction(const fxl::RefPtr<EvalContext>& eval_context, const ParsedIdentifier& fn_name,
const std::vector<ExprValue>& params,
fit::callback<void(ErrOr<FunctionCallInfo>)> cb) {
// FindName should be able to find the identifier in any context, regardless of where the current
// execution is. This includes any namespaces that the function might live in.
std::vector<FoundName> found_names;
FindNameOptions opts(FindNameOptions::kNoKinds);
opts.find_functions = true;
eval_context->FindName(opts, fn_name, &found_names);
if (found_names.size() > 1) {
// TODO(https://fxbug.dev/42132103): Handle overloads.
return cb(Err(fn_name.GetFullName() +
" resolved to multiple locations. Overloaded functions are not supported yet."));
}
auto found = found_names[0];
if (!found.function()) {
// Print a more helpful error message if something was found but it wasn't a
// function.
if (found.is_found()) {
return cb(Err(std::string("Identifier was not function: ") + found.GetName().GetFullName() +
" " + FoundName::KindToString(found.kind())));
}
return cb(Err(fn_name.GetFullName() + " was not found in this context."));
}
auto fn_ref = found.function();
// None of the locations actually resolved to the function or the function was inlined, return an
// error.
if (!fn_ref) {
return cb(Err("Error casting symbol " + fn_name.GetDebugName() + " to function."));
} else if (fn_ref->is_inline()) {
return cb(
Err(fn_ref->GetFullName() + " has been inlined, and cannot be called from the debugger."));
}
FunctionCallInfo call_info;
call_info.fn = fn_ref;
CollectArguments(
eval_context, fn_ref->GetAssignedName(), fn_ref->parameters(), params,
[call_info, cb = std::move(cb)](ErrOr<std::vector<ExprValue>> err_or_args) mutable {
if (err_or_args.has_error()) {
return cb(err_or_args.err());
}
call_info.parameters = err_or_args.take_value();
cb(call_info);
});
}
} // namespace zxdb