| // 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 |