blob: 7a5d5212786d2ca921dceb855d1642d2c85da03b [file]
// Copyright 2026 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/client/fuchsia_async_rust_task_provider.h"
#include <algorithm>
#include <string_view>
#include "src/developer/debug/shared/message_loop.h"
#include "src/developer/debug/shared/string_util.h"
#include "src/developer/debug/zxdb/client/frame.h"
#include "src/developer/debug/zxdb/client/session.h"
#include "src/developer/debug/zxdb/common/file_util.h"
#include "src/developer/debug/zxdb/common/join_callbacks.h"
#include "src/developer/debug/zxdb/common/string_util.h"
#include "src/developer/debug/zxdb/expr/cast.h"
#include "src/developer/debug/zxdb/expr/expr.h"
#include "src/developer/debug/zxdb/expr/found_member.h"
#include "src/developer/debug/zxdb/expr/resolve_collection.h"
#include "src/developer/debug/zxdb/expr/resolve_ptr_ref.h"
#include "src/developer/debug/zxdb/expr/resolve_variant.h"
#include "src/developer/debug/zxdb/symbols/collection.h"
#include "src/developer/debug/zxdb/symbols/data_member.h"
#include "src/developer/debug/zxdb/symbols/function.h"
#include "src/developer/debug/zxdb/symbols/identifier.h"
#include "src/developer/debug/zxdb/symbols/modified_type.h"
#include "src/developer/debug/zxdb/symbols/template_parameter.h"
#include "src/lib/fxl/memory/ref_ptr.h"
#include "src/lib/fxl/strings/split_string.h"
namespace zxdb {
namespace {
// Concrete implementation of AsyncTask for fuchsia-async Rust.
class FuchsiaAsyncRustTask : public AsyncTask {
public:
FuchsiaAsyncRustTask(Session* session, uint64_t id, Location location, Identifier identifier,
std::string state, AsyncTask::Type type)
: AsyncTask(session),
id_(id),
location_(std::move(location)),
identifier_(std::move(identifier)),
state_(std::move(state)),
type_(type) {}
uint64_t GetId() const override { return id_; }
AsyncTask::Type GetType() const override { return type_; }
const Location& GetLocation() const override { return location_; }
const Identifier& GetIdentifier() const override { return identifier_; }
std::string GetState() const override { return state_; }
const std::vector<NamedValue>& GetValues() const override { return values_; }
std::vector<AsyncTask::Ref> GetChildren() const override {
std::vector<AsyncTask::Ref> ret;
ret.reserve(children_.size());
for (const auto& child : children_) {
ret.push_back(*child);
}
return ret;
}
void AddChild(std::unique_ptr<AsyncTask> child) { children_.push_back(std::move(child)); }
void AddNamedValue(std::optional<std::string> name, ExprValue value) {
values_.push_back({.name = std::move(name), .value = std::move(value)});
}
private:
uint64_t id_;
Location location_;
Identifier identifier_;
std::string state_;
AsyncTask::Type type_;
std::vector<NamedValue> values_;
std::vector<std::unique_ptr<AsyncTask>> children_;
};
std::string_view StripTemplate(std::string_view type_name) {
return type_name.substr(0, type_name.find('<'));
}
bool IsAsyncFunctionOrBlock(Type* type) {
if (type->GetIdentifier().components().empty())
return false;
return debug::StringStartsWith(type->GetIdentifier().components().back().name(), "{async_");
}
Err MakeError(std::string msg, const Err& err = Err()) {
if (err.has_error())
msg += ": " + err.msg();
return Err(msg);
}
Identifier MakeIdentifier(std::string_view qualified_name) {
Identifier identifier;
auto components =
fxl::SplitStringCopy(qualified_name, "::", fxl::WhiteSpaceHandling::kTrimWhitespace,
fxl::SplitResult::kSplitWantNonEmpty);
for (const auto& component : components) {
identifier.AppendComponent(IdentifierComponent(component));
}
return identifier;
}
// Forward declarations for recursive fetching
void FetchFuture(Session* session, const fxl::RefPtr<EvalContext>& context, const ExprValue& future,
const std::string& awaiter_file_name,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb);
// We don't have the facilities here to access the pretty-printing system, which is how we would
// typically fetch the string value. Since we can't do that, we manually extract the data and length
// and fetch the memory manually.
void FormatRustString(const fxl::RefPtr<EvalContext>& context, const ExprValue& value,
fit::callback<void(std::string)> cb) {
// A Rust String is { vec: { buf: { ptr: { pointer: <addr> }, ... }, len: <len> } }
ErrOrValue len_val = ResolveNonstaticMember(context, value, {"vec", "len"});
ErrOrValue ptr_val =
ResolveNonstaticMember(context, value, {"vec", "buf", "inner", "ptr", "pointer", "pointer"});
if (len_val.has_error() || ptr_val.has_error()) {
cb("<error>");
return;
}
uint64_t len = 0;
uint64_t addr = 0;
len_val.value().PromoteTo64(&len);
ptr_val.value().PromoteTo64(&addr);
if (len == 0) {
cb("");
return;
}
context->GetDataProvider()->GetMemoryAsync(
addr, static_cast<uint32_t>(len),
[len, cb = std::move(cb)](const Err& err, std::vector<uint8_t> data) mutable {
if (err.has_error()) {
cb("<error>");
return;
}
// We should always get back the exact size we requested even if the implementation read
// more than we asked for.
FX_DCHECK(data.size() == len);
cb(std::string(reinterpret_cast<const char*>(data.data()), len));
});
}
void FetchAsyncFunctionOrBlock(Session* session, const fxl::RefPtr<EvalContext>& context,
const ExprValue& future,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
fxl::RefPtr<DataMember> member;
ErrOrValue variant_val = ResolveSingleVariantValue(context, future, &member);
if (variant_val.has_error()) {
cb(nullptr);
return;
}
ExprValue value = variant_val.take_value();
Identifier ident = value.type()->GetIdentifier();
std::string state = ident.components()[ident.components().size() - 1].name();
// Trim off the async state from the end of the type identifier.
ident.components().resize(ident.components().size() - 2);
// Don't display suspend states.
if (debug::StringStartsWith(state, "Suspend")) {
state = "";
}
Location loc;
if (member->decl_line().is_valid()) {
// Create a location with the file line.
loc = Location(0, member->decl_line(), 0, SymbolContext::ForRelativeAddresses(), {});
}
auto task = std::make_unique<FuchsiaAsyncRustTask>(session, 0, loc, ident, state,
AsyncTask::Type::kFunction);
std::optional<ExprValue> awaitee;
std::map<std::string, ExprValue> values;
if (const Collection* coll = value.type()->As<Collection>()) {
for (const auto& lazy_member : coll->data_members()) {
const DataMember* member = lazy_member.Get()->As<DataMember>();
if (!member || member->artificial() || member->is_external())
continue;
std::string name = member->GetAssignedName();
ErrOrValue val = ResolveNonstaticMember(context, value, FoundMember(coll, member));
if (val.has_error()) {
continue;
} else if (name == "__awaitee") {
awaitee = val.take_value();
} else {
// For some reason Rust could repeat the same field twice.
values.try_emplace(name, val.take_value());
}
}
}
for (auto [name, val] : values) {
task->AddNamedValue(name, std::move(val));
}
if (awaitee) {
std::string filename;
if (loc.file_line().is_valid()) {
filename = ExtractLastFileComponent(loc.file_line().file());
}
FetchFuture(
session, context, *awaitee, filename,
[task = std::move(task), cb = std::move(cb)](std::unique_ptr<AsyncTask> child) mutable {
if (child)
task->AddChild(std::move(child));
cb(std::move(task));
});
} else {
cb(std::move(task));
}
}
void FetchSelectJoin(Session* session, const fxl::RefPtr<EvalContext>& context,
const ExprValue& future, const std::string& name,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
ErrOrValue err_or_f = ResolveNonstaticMember(context, future, {"f"});
if (err_or_f.has_error())
return cb(nullptr);
ExprValue f = err_or_f.take_value();
const Collection* f_coll = f.type()->As<Collection>();
if (!f_coll)
return cb(nullptr);
auto task = std::make_unique<FuchsiaAsyncRustTask>(session, 0, Location(), MakeIdentifier(name),
"", AsyncTask::Type::kOther);
auto joiner = fxl::MakeRefCounted<JoinCallbacks<std::unique_ptr<AsyncTask>>>();
for (const auto& lazy_member : f_coll->data_members()) {
const DataMember* member = lazy_member.Get()->As<DataMember>();
if (!member || member->artificial() || member->is_external())
continue;
ErrOrValue member_val = ResolveNonstaticMember(context, f, FoundMember(f_coll, member));
if (member_val.ok()) {
FetchFuture(session, context, member_val.take_value(), "", joiner->AddCallback());
}
}
joiner->Ready([task = std::move(task),
cb = std::move(cb)](std::vector<std::unique_ptr<AsyncTask>> children) mutable {
for (auto& child : children) {
if (child)
task->AddChild(std::move(child));
}
cb(std::move(task));
});
}
void FetchMember(Session* session, const fxl::RefPtr<EvalContext>& context, const ExprValue& value,
const std::vector<std::string>& names,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
ErrOrValue val(value);
for (const auto& name : names) {
val = ResolveNonstaticMember(context, val.value(), {name});
if (val.has_error())
return cb(nullptr);
}
FetchFuture(session, context, val.value(), "", std::move(cb));
}
void FetchPin(Session* session, const fxl::RefPtr<EvalContext>& context, const ExprValue& pin,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
ErrOrValue pointer = ResolveNonstaticMember(context, pin, {"__pointer"});
if (pointer.has_error())
pointer = ResolveNonstaticMember(context, pin, {"pointer"});
if (pointer.ok())
return FetchFuture(session, context, pointer.value(), "", std::move(cb));
}
void FetchTask(Session* session, const fxl::RefPtr<EvalContext>& context, const ExprValue& task,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
ErrOrValue task_handle_opt = ResolveNonstaticMember(context, task, {"__0", "task"});
if (task_handle_opt.ok()) {
ErrOrValue task_handle = ResolveSingleVariantValue(context, task_handle_opt.value());
if (task_handle.ok() && task_handle.value().type()->GetAssignedName() == "Some") {
// Some(AtomicFutureHandle) -> AtomicFutureHandle -> NonNull -> pointer
ErrOrValue ptr =
ResolveNonstaticMember(context, task_handle.value(), {"__0", "__0", "pointer"});
uint64_t task_id = 0;
if (ptr.ok() && ptr.value().PromoteTo64(&task_id).ok()) {
auto task = std::make_unique<FuchsiaAsyncRustTask>(
session, task_id, Location(), MakeIdentifier("fuchsia_async::Task"),
"id = " + to_hex_string(task_id), AsyncTask::Type::kTask);
debug::MessageLoop::Current()->PostTask(
FROM_HERE,
[task = std::move(task), cb = std::move(cb)]() mutable { cb(std::move(task)); });
return;
}
}
}
FetchMember(session, context, task, {"__0"}, std::move(cb));
}
void FetchJoinHandle(Session* session, const fxl::RefPtr<EvalContext>& context,
const ExprValue& join_handle,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
ErrOrValue task_handle_opt = ResolveNonstaticMember(context, join_handle, {"task"});
if (task_handle_opt.ok()) {
ErrOrValue task_handle = ResolveSingleVariantValue(context, task_handle_opt.value());
if (task_handle.ok() && task_handle.value().type()->GetAssignedName() == "Some") {
ErrOrValue ptr =
ResolveNonstaticMember(context, task_handle.value(), {"__0", "__0", "pointer"});
uint64_t task_id = 0;
if (ptr.ok() && ptr.value().PromoteTo64(&task_id).ok()) {
auto task = std::make_unique<FuchsiaAsyncRustTask>(
session, task_id, Location(), MakeIdentifier("fuchsia_async::JoinHandle"),
"id = " + to_hex_string(task_id), AsyncTask::Type::kFuture);
debug::MessageLoop::Current()->PostTask(
FROM_HERE,
[task = std::move(task), cb = std::move(cb)]() mutable { cb(std::move(task)); });
return;
}
}
}
FetchMember(session, context, join_handle, {"task"}, std::move(cb));
}
void FetchScopeJoin(Session* session, const fxl::RefPtr<EvalContext>& context,
const ExprValue& scope_join,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
ErrOrValue arc_inner_ptr =
ResolveNonstaticMember(context, scope_join, {"scope", "inner", "inner", "ptr", "pointer"});
uint64_t addr = 0;
if (arc_inner_ptr.ok()) {
arc_inner_ptr.value().PromoteTo64(&addr);
}
auto task = std::make_unique<FuchsiaAsyncRustTask>(session, 0, Location(),
MakeIdentifier("fuchsia_async::scope::Join"),
to_hex_string(addr), AsyncTask::Type::kOther);
debug::MessageLoop::Current()->PostTask(
FROM_HERE, [task = std::move(task), cb = std::move(cb)]() mutable { cb(std::move(task)); });
}
void FetchFuse(Session* session, const fxl::RefPtr<EvalContext>& context, const ExprValue& fuse,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
ErrOrValue inner = ResolveNonstaticMember(context, fuse, {"inner"});
if (inner.ok()) {
ErrOrValue some = ResolveSingleVariantValue(context, inner.value());
if (some.ok() && some.value().type()->GetAssignedName() != "None") {
FetchMember(session, context, some.value(), {"__0"}, std::move(cb));
return;
}
}
cb(nullptr);
}
void FetchMap(Session* session, const fxl::RefPtr<EvalContext>& context, const ExprValue& map,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
ErrOrValue val = ResolveSingleVariantValue(context, map);
if (val.has_error()) {
cb(nullptr);
return;
}
val = ResolveNonstaticMember(context, val.value(), {"future"});
if (val.has_error()) {
cb(nullptr);
return;
}
FetchFuture(session, context, val.value(), "", std::move(cb));
}
void FetchMapDebug(Session* session, const fxl::RefPtr<EvalContext>& context,
const ExprValue& debug_map, fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
ErrOrValue map = ResolveNonstaticMember(context, debug_map, {"inner"});
if (map.has_error()) {
cb(nullptr);
return;
}
FetchMap(session, context, map.value(), std::move(cb));
}
void FetchMaybeDone(Session* session, const fxl::RefPtr<EvalContext>& context,
const ExprValue& maybe_done,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
ErrOrValue some = ResolveSingleVariantValue(context, maybe_done);
if (some.ok() && some.value().type()->GetAssignedName() == "Future") {
FetchMember(session, context, some.value(), {"__0"}, std::move(cb));
} else {
cb(nullptr);
}
}
void FetchTraceFuture(Session* session, const fxl::RefPtr<EvalContext>& context,
const ExprValue& trace_future,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
ErrOrValue future = ResolveNonstaticMember(context, trace_future, {"future"});
if (future.has_error()) {
cb(nullptr);
return;
}
ErrOrValue trace_category = ResolveNonstaticMember(context, trace_future, {"category"});
if (trace_category.has_error()) {
cb(nullptr);
return;
}
ErrOrValue trace_name = ResolveNonstaticMember(context, trace_future, {"name"});
if (trace_name.has_error()) {
cb(nullptr);
return;
}
auto fut = std::make_unique<FuchsiaAsyncRustTask>(session, 0, Location(),
MakeIdentifier("fuchsia_trace::TraceFuture"),
"", AsyncTask::Type::kFuture);
fut->AddNamedValue(std::nullopt, trace_category.take_value());
fut->AddNamedValue(std::nullopt, trace_name.take_value());
FetchFuture(
session, context, future.take_value(), "",
[task = std::move(fut), cb = std::move(cb)](std::unique_ptr<AsyncTask> child) mutable {
task->AddChild(std::move(child));
cb(std::move(task));
});
}
void FetchVfsRequestListener(Session* session, const fxl::RefPtr<EvalContext>& context,
const ExprValue& request_listener,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
ErrOrValue state = ResolveNonstaticMember(context, request_listener, {"state"});
if (state.has_error()) {
cb(nullptr);
return;
}
ErrOrValue variant = ResolveSingleVariantValue(context, state.value());
if (variant.has_error()) {
cb(nullptr);
return;
}
std::string variant_name = variant.value().type()->GetAssignedName();
auto fut = std::make_unique<FuchsiaAsyncRustTask>(
session, 0, Location(), MakeIdentifier("vfs::request_handler::RequestListener"), variant_name,
AsyncTask::Type::kFuture);
if (variant_name == "PollStream") {
ErrOrValue stream = ResolveNonstaticMember(context, request_listener, {"stream"});
if (stream.has_error()) {
cb(nullptr);
return;
}
fut->AddNamedValue(stream.value().type()->GetFullName(), stream.take_value());
cb(std::move(fut));
} else if (variant_name == "RequestFuture" || variant_name == "CloseFuture") {
ErrOrValue future = ResolveNonstaticMember(context, variant.value(), {"__0"});
if (future.has_error()) {
cb(nullptr);
return;
}
FetchFuture(
session, context, future.take_value(), "",
[fut = std::move(fut), cb = std::move(cb)](std::unique_ptr<AsyncTask> child) mutable {
fut->AddChild(std::move(child));
cb(std::move(fut));
});
}
}
void FetchFuture(Session* session, const fxl::RefPtr<EvalContext>& context, const ExprValue& future,
const std::string& awaiter_file_name,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
std::string_view type = StripTemplate(future.type()->GetFullName());
// Resolve pointers first. A pointer could be either non-dyn or dyn, raw or boxed.
//
// A non-dyn pointer (raw or boxed) is a ModifiedType.
// A dyn raw pointer is a Collection and has a name "*mut dyn ..." or "*mut (dyn ... + ...)".
// A dyn boxed pointer has the same layout but with a name "alloc::boxed::Box<(dyn ... + ...)>".
if (future.type()->As<ModifiedType>() || debug::StringStartsWith(type, "*mut ") ||
type == "alloc::boxed::Box") {
ResolvePointer(context, future, [session, context, cb = std::move(cb)](ErrOrValue val) mutable {
if (val.has_error()) {
cb(nullptr);
} else {
FetchFuture(session, context, val.value(), "", std::move(cb));
}
});
return;
}
if (IsAsyncFunctionOrBlock(future.type())) {
FetchAsyncFunctionOrBlock(session, context, future, std::move(cb));
return;
}
if (type == "core::pin::Pin") {
FetchPin(session, context, future, std::move(cb));
return;
}
if (type == "core::mem::maybe_dangling::MaybeDangling" ||
type == "futures_util::future::future::WrappedFuture") {
FetchMember(session, context, future, {"__0"}, std::move(cb));
return;
}
if (type == "fuchsia_async::runtime::fuchsia::task::Task") {
FetchTask(session, context, future, std::move(cb));
return;
}
if (type == "fuchsia_async::runtime::fuchsia::task::JoinHandle") {
FetchJoinHandle(session, context, future, std::move(cb));
return;
}
if (type == "futures_util::future::future::fuse::Fuse") {
FetchFuse(session, context, future, std::move(cb));
return;
}
if (type == "futures_util::future::maybe_done::MaybeDone") {
FetchMaybeDone(session, context, future, std::move(cb));
return;
}
if (type == "futures_util::future::future::Then") {
FetchMember(session, context, future, {"inner", "__0", "f"}, std::move(cb));
return;
}
if (type == "futures_util::future::future::Map") { // only appears in debug mode.
FetchMapDebug(session, context, future, std::move(cb));
return;
}
if (type == "futures_util::future::future::map::Map") {
FetchMap(session, context, future, std::move(cb));
return;
}
if (type == "futures_util::future::future::remote_handle::Remote") {
FetchMember(session, context, future, {"future", "future", "__0"}, std::move(cb));
return;
}
if (type == "vfs::execution_scope::TaskRunner") {
FetchMember(session, context, future, {"task"}, std::move(cb));
return;
}
if (type == "starnix_core::task::kernel_threads::WrappedFuture") {
FetchMember(session, context, future, {"__0"}, std::move(cb));
return;
}
if (type == "fuchsia_async::runtime::fuchsia::executor::scope::Join") {
FetchScopeJoin(session, context, future, std::move(cb));
return;
}
if (type == "fxfs::future_with_guard::FutureWithGuard") {
FetchMember(session, context, future, {"future"}, std::move(cb));
return;
}
if (type == "fuchsia_trace::TraceFuture") {
FetchTraceFuture(session, context, future, std::move(cb));
return;
}
if (type == "vfs::request_handler::RequestListener") {
FetchVfsRequestListener(session, context, future, std::move(cb));
return;
}
// NOTE: `select!` and `join!` macro expand to PollFn. It'll be useful if we could describe it.
// However, PollFn could encode an arbitrary function so there's a chance we're doing very wrong.
// To be more accurate, we also check the filename of the awaiter. `select!` will be expanded
// from select_mod.rs, and `join!` will be expanded from `join_mod.rs`.
if (type == "futures_util::future::poll_fn::PollFn") {
if (awaiter_file_name == "select_mod.rs") {
FetchSelectJoin(session, context, future, "select!", std::move(cb));
return;
}
if (awaiter_file_name == "join_mod.rs") {
FetchSelectJoin(session, context, future, "join!", std::move(cb));
return;
}
}
// Generic task object.
auto task = std::make_unique<FuchsiaAsyncRustTask>(session, 0, Location(), MakeIdentifier(type),
"", AsyncTask::Type::kOther);
task->AddNamedValue(std::nullopt, future);
debug::MessageLoop::Current()->PostTask(
FROM_HERE, [task = std::move(task), cb = std::move(cb)]() mutable { cb(std::move(task)); });
}
template <typename Val>
void IterateHashMap(const fxl::RefPtr<EvalContext>& context, const ExprValue& hashmap,
fit::function<void(const ExprValue&, fit::callback<void(Val)>)> each_cb,
fit::callback<void(ErrOr<std::vector<Val>>)> done_cb) {
if (StripTemplate(hashmap.type()->GetFullName()) != "hashbrown::map::HashMap") {
return done_cb(MakeError("Expect a HashMap, got " + hashmap.type()->GetFullName()));
}
ErrOrValue raw_table = ResolveNonstaticMember(context, hashmap, {"table"});
if (raw_table.has_error())
return done_cb(MakeError("Invalid HashMap (1)", raw_table.err()));
const Collection* raw_table_coll = raw_table.value().type()->As<Collection>();
if (!raw_table_coll || raw_table_coll->template_params().empty())
return done_cb(MakeError("Invalid HashMap (2)"));
fxl::RefPtr<Type> tuple_type;
if (auto param = raw_table_coll->template_params()[0].Get()->As<TemplateParameter>())
tuple_type = RefPtrTo(param->type().Get()->As<Type>());
if (!tuple_type)
return done_cb(MakeError("Invalid HashMap (3)"));
ErrOrValue bucket_mask_res =
ResolveNonstaticMember(context, raw_table.value(), {"table", "bucket_mask"});
if (bucket_mask_res.has_error())
return done_cb(MakeError("Invalid HashMap (4)", bucket_mask_res.err()));
uint64_t bucket_mask = 0;
bucket_mask_res.value().PromoteTo64(&bucket_mask);
ErrOrValue ctrl_res =
ResolveNonstaticMember(context, raw_table.value(), {"table", "ctrl", "pointer"});
if (ctrl_res.has_error())
return done_cb(MakeError("Invalid HashMap (6)", ctrl_res.err()));
uint64_t ctrl = 0;
ctrl_res.value().PromoteTo64(&ctrl);
if (!ctrl) {
// If ctrl is null, the map is empty.
return done_cb(std::vector<Val>{});
}
uint64_t capacity = bucket_mask + 1;
uint64_t total_buckets_size = tuple_type->byte_size() * capacity;
context->GetDataProvider()->GetMemoryAsync(
uint64_t(ctrl - total_buckets_size), uint32_t(total_buckets_size + capacity),
[=, done_cb = std::move(done_cb), each_cb = std::move(each_cb)](
const Err& err, std::vector<uint8_t> data) mutable {
if (err.has_error()) {
return done_cb(MakeError("Invalid HashMap (8)", err));
}
auto joiner = fxl::MakeRefCounted<JoinCallbacks<Val>>();
for (size_t idx = 0; idx < capacity; idx++) {
if ((data[total_buckets_size + idx] & 0x80))
continue;
uint8_t* slot = &data[total_buckets_size - (idx + 1) * tuple_type->byte_size()];
ExprValue tuple(tuple_type, {slot, slot + tuple_type->byte_size()},
ExprValueSource(ctrl - (idx + 1) * tuple_type->byte_size()));
each_cb(tuple, joiner->AddCallback());
}
joiner->Ready([done_cb = std::move(done_cb)](std::vector<Val> val) mutable {
done_cb(std::move(val));
});
});
}
void FetchScopeTasks(Session* session, const fxl::RefPtr<EvalContext>& context,
const ExprValue& scope_state,
fit::callback<void(std::vector<std::unique_ptr<AsyncTask>>)> cb);
void FetchTaskFromHashSet(Session* session, const fxl::RefPtr<EvalContext>& context,
const ExprValue& tuple,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
// Some(AtomicFutureHandle) -> AtomicFutureHandle -> NonNull
ErrOrValue meta_ptr = ResolveNonstaticMember(context, tuple, {"__0", "__0", "pointer"});
if (meta_ptr.has_error()) {
cb(nullptr);
return;
}
ResolvePointer(
context, meta_ptr.value(),
[session, context, meta_ptr, cb = std::move(cb)](ErrOrValue meta) mutable {
if (meta.has_error()) {
cb(nullptr);
return;
}
uint64_t task_id = 0;
if (auto err = meta_ptr.value().PromoteTo64(&task_id); err.has_error()) {
cb(nullptr);
return;
}
ErrOrValue state = ResolveNonstaticMember(context, meta.value(), {"state"});
if (state.has_error()) {
cb(nullptr);
return;
}
uint64_t state_value;
if (auto err = state.value().PromoteTo64(&state_value); err.has_error()) {
cb(nullptr);
return;
}
// If bit 61 is set then the future is DONE.
if (state_value & (1ull << 61)) {
auto task = std::make_unique<FuchsiaAsyncRustTask>(
session, task_id, Location(), Identifier(), "Finished", AsyncTask::Type::kTask);
debug::MessageLoop::Current()->PostTask(
FROM_HERE,
[task = std::move(task), cb = std::move(cb)]() mutable { cb(std::move(task)); });
return;
}
// Read the drop vtable ptr.
ErrOrValue vtable_ptr = ResolveNonstaticMember(context, meta.value(), {"vtable"});
if (vtable_ptr.has_error()) {
cb(nullptr);
return;
}
ResolvePointer(
context, vtable_ptr.value(),
[session, context, meta_ptr, task_id, cb = std::move(cb)](ErrOrValue vtable) mutable {
if (vtable.has_error()) {
cb(nullptr);
return;
}
ErrOrValue drop_fn = ResolveNonstaticMember(context, vtable.value(), {"drop"});
if (drop_fn.has_error()) {
cb(nullptr);
return;
}
uint64_t drop_fn_value;
if (auto err = drop_fn.value().PromoteTo64(&drop_fn_value); err.has_error()) {
cb(nullptr);
return;
}
Location loc = context->GetLocationForAddress(drop_fn_value);
if (!loc.symbol()) {
cb(nullptr);
return;
}
const Function* func = loc.symbol().Get()->As<Function>();
if (!func || func->template_params().empty()) {
cb(nullptr);
return;
}
auto derived = fxl::MakeRefCounted<ModifiedType>(DwarfTag::kPointerType,
func->parent().GetCached());
CastExprValue(
context, CastType::kRust, meta_ptr.value(), derived, ExprValueSource(),
[session, context, task_id,
cb = std::move(cb)](ErrOrValue atomic_future_ptr) mutable {
if (atomic_future_ptr.has_error()) {
cb(nullptr);
return;
}
ResolvePointer(
context, atomic_future_ptr.value(),
[session, context, task_id,
cb = std::move(cb)](ErrOrValue atomic_future) mutable {
if (atomic_future.has_error()) {
cb(nullptr);
return;
}
ErrOrValue future = ResolveNonstaticMember(context, atomic_future.value(),
{"future", "future", "value"});
if (future.has_error()) {
cb(nullptr);
return;
}
FetchFuture(
session, context, future.value(), "",
[session, task_id,
cb = std::move(cb)](std::unique_ptr<AsyncTask> task) mutable {
if (task) {
auto wrapper = std::make_unique<FuchsiaAsyncRustTask>(
session, task_id, task->GetLocation(), Identifier("Task"),
"id = " + to_hex_string(task_id), AsyncTask::Type::kTask);
wrapper->AddChild(std::move(task));
debug::MessageLoop::Current()->PostTask(
FROM_HERE,
[wrapper = std::move(wrapper), cb = std::move(cb)]() mutable {
cb(std::move(wrapper));
});
} else {
cb(nullptr);
}
});
});
});
});
});
}
void ResolveScopeHandle(const fxl::RefPtr<EvalContext>& context, const ExprValue& handle,
fit::callback<void(ErrOrValue)> cb) {
// ScopeHandle has "inner" (Arc<ScopeInner>).
ErrOrValue arc_ptr = ResolveNonstaticMember(context, handle, {"inner", "ptr", "pointer"});
if (arc_ptr.has_error())
arc_ptr = ResolveNonstaticMember(context, handle, {"inner", "inner", "ptr", "pointer"});
if (arc_ptr.has_error())
return cb(arc_ptr.err());
ResolvePointer(
context, arc_ptr.value(), [context, cb = std::move(cb)](ErrOrValue arc_inner) mutable {
if (arc_inner.has_error())
return cb(arc_inner.err());
// ArcInner<T> has a field "data" of type T (ScopeInner).
ErrOrValue scope_inner = ResolveNonstaticMember(context, arc_inner.value(), {"data"});
if (scope_inner.has_error())
scope_inner = arc_inner;
// Condition<T> has a field "__0" (Arc<Mutex<Inner<T>>>).
ErrOrValue cond = ResolveNonstaticMember(context, scope_inner.value(), {"state"});
if (cond.has_error())
return cb(cond.err());
ErrOrValue cond_arc_ptr =
ResolveNonstaticMember(context, cond.value(), {"__0", "ptr", "pointer"});
if (cond_arc_ptr.has_error())
cond_arc_ptr =
ResolveNonstaticMember(context, cond.value(), {"__0", "inner", "ptr", "pointer"});
if (cond_arc_ptr.has_error())
return cb(cond_arc_ptr.err());
ResolvePointer(context, cond_arc_ptr.value(),
[context, cb = std::move(cb)](ErrOrValue cond_arc_inner) mutable {
if (cond_arc_inner.has_error())
return cb(cond_arc_inner.err());
// ArcInner<Mutex<Inner<ScopeState>>>
// data is Mutex<Inner<ScopeState>>
// Mutex.data is Inner<ScopeState>
// Inner.data is ScopeState
// We try various paths to be robust to UnsafeCell wrappers etc.
ErrOrValue child_state = ResolveNonstaticMember(
context, cond_arc_inner.value(), {"data", "data", "value", "data"});
if (child_state.has_error())
child_state = ResolveNonstaticMember(context, cond_arc_inner.value(),
{"data", "data", "data"});
if (child_state.has_error())
child_state = ResolveNonstaticMember(context, cond_arc_inner.value(),
{"data", "data"});
if (child_state.has_error())
child_state =
ResolveNonstaticMember(context, cond_arc_inner.value(), {"data"});
cb(std::move(child_state));
});
});
}
void FetchScopeTasks(Session* session, const fxl::RefPtr<EvalContext>& context,
const ExprValue& scope_state,
fit::callback<void(std::vector<std::unique_ptr<AsyncTask>>)> cb) {
// Try to find the tasks HashSet. It might be in "all_tasks" or "all_tasks.base".
ErrOrValue tasks_member = ResolveNonstaticMember(context, scope_state, {"all_tasks"});
if (tasks_member.ok()) {
ErrOrValue base = ResolveNonstaticMember(context, tasks_member.value(), {"base"});
if (base.ok())
tasks_member = base;
}
auto joiner = fxl::MakeRefCounted<JoinCallbacks<std::vector<std::unique_ptr<AsyncTask>>>>();
auto tasks_cb = joiner->AddCallback();
if (tasks_member.has_error()) {
tasks_cb({});
} else {
// A HashSet usually wraps a HashMap in a field called "map".
ErrOrValue map = ResolveNonstaticMember(context, tasks_member.value(), {"map"});
ExprValue tasks_map = map.ok() ? map.value() : tasks_member.value();
IterateHashMap<std::unique_ptr<AsyncTask>>(
context, tasks_map,
[session, context](const ExprValue& tuple,
fit::callback<void(std::unique_ptr<AsyncTask>)> cb) {
FetchTaskFromHashSet(session, context, tuple, std::move(cb));
},
[tasks_cb =
std::move(tasks_cb)](ErrOr<std::vector<std::unique_ptr<AsyncTask>>> result) mutable {
std::vector<std::unique_ptr<AsyncTask>> tasks;
if (result.ok()) {
for (auto& t : result.value()) {
if (t)
tasks.push_back(std::move(t));
}
}
tasks_cb(std::move(tasks));
});
}
auto children_cb = joiner->AddCallback();
// Try to find the children HashSet. It might be in "children" or "children.base".
ErrOrValue children_member = ResolveNonstaticMember(context, scope_state, {"children"});
if (children_member.ok()) {
ErrOrValue base = ResolveNonstaticMember(context, children_member.value(), {"base"});
if (base.ok())
children_member = base;
}
if (children_member.has_error()) {
children_cb({});
} else {
ErrOrValue map = ResolveNonstaticMember(context, children_member.value(), {"map"});
ExprValue children_map = map.ok() ? map.value() : children_member.value();
IterateHashMap<std::vector<std::unique_ptr<AsyncTask>>>(
context, children_map,
[session, context](const ExprValue& tuple,
fit::callback<void(std::vector<std::unique_ptr<AsyncTask>>)> cb) {
// HashSet entry for WeakScopeHandle. Try to find the pointer.
ErrOrValue weak_handle = ResolveNonstaticMember(context, tuple, {"__0"});
if (weak_handle.has_error())
weak_handle = ErrOrValue(tuple);
// WeakScopeHandle has "inner" (Weak<ScopeInner>).
// Weak has "ptr" (NonNull). NonNull has "pointer".
ErrOrValue arc_inner_ptr =
ResolveNonstaticMember(context, weak_handle.value(), {"inner", "ptr", "pointer"});
if (arc_inner_ptr.has_error())
arc_inner_ptr =
ResolveNonstaticMember(context, weak_handle.value(), {"inner", "pointer"});
if (arc_inner_ptr.has_error()) {
cb({});
return;
}
ResolvePointer(
context, arc_inner_ptr.value(),
[session, context, arc_inner_ptr, cb = std::move(cb)](ErrOrValue arc_inner) mutable {
if (arc_inner.has_error()) {
cb({});
return;
}
// ArcInner<T> has a field "data" of type T.
ErrOrValue scope_inner =
ResolveNonstaticMember(context, arc_inner.value(), {"data"});
if (scope_inner.has_error()) {
cb({});
return;
}
ErrOrValue scope_name_opt =
ResolveNonstaticMember(context, scope_inner.value(), {"name"});
auto on_name_ready = [session, context, scope_inner, arc_inner_ptr,
cb = std::move(cb)](std::string scope_name) mutable {
ErrOrValue mutex_ptr = ResolveNonstaticMember(context, scope_inner.value(),
{"state", "__0", "ptr", "pointer"});
if (mutex_ptr.has_error()) {
cb({});
return;
}
ResolvePointer(
context, mutex_ptr.value(),
[session, context, scope_name, arc_inner_ptr,
cb = std::move(cb)](ErrOrValue mutex) mutable {
if (mutex.has_error()) {
cb({});
return;
}
ErrOrValue child_state = ResolveNonstaticMember(
context, mutex.value(), {"data", "data", "value", "data"});
if (child_state.has_error()) {
cb({});
return;
}
FetchScopeTasks(
session, context, child_state.value(),
[session, scope_name, arc_inner_ptr, cb = std::move(cb)](
std::vector<std::unique_ptr<AsyncTask>> tasks) mutable {
auto scope = std::make_unique<FuchsiaAsyncRustTask>(
session, 0, Location(), Identifier("Scope"), scope_name,
AsyncTask::Type::kScope);
scope->AddNamedValue(std::nullopt, arc_inner_ptr.take_value());
for (auto& t : tasks) {
scope->AddChild(std::move(t));
}
std::vector<std::unique_ptr<AsyncTask>> res;
res.push_back(std::move(scope));
debug::MessageLoop::Current()->PostTask(
FROM_HERE, [res = std::move(res), cb = std::move(cb)]() mutable {
cb(std::move(res));
});
});
});
};
if (scope_name_opt.ok()) {
FormatRustString(context, scope_name_opt.value(), std::move(on_name_ready));
} else {
on_name_ready("<unknown>");
}
});
},
[children_cb = std::move(children_cb)](
ErrOr<std::vector<std::vector<std::unique_ptr<AsyncTask>>>> result) mutable {
std::vector<std::unique_ptr<AsyncTask>> all_children_tasks;
if (result.ok()) {
for (auto& tasks : result.value()) {
for (auto& t : tasks) {
if (t)
all_children_tasks.push_back(std::move(t));
}
}
}
children_cb(std::move(all_children_tasks));
});
}
joiner->Ready(
[cb = std::move(cb)](std::vector<std::vector<std::unique_ptr<AsyncTask>>> results) mutable {
std::vector<std::unique_ptr<AsyncTask>> final_tasks;
for (auto& tasks : results) {
for (auto& t : tasks) {
final_tasks.push_back(std::move(t));
}
}
std::ranges::sort(final_tasks, [](const auto& lhs, const auto& rhs) {
return lhs->GetLocation().address() < rhs->GetLocation().address();
});
cb(std::move(final_tasks));
});
}
std::optional<std::string> GetScopeExpressionForExecutorFrame(
std::string_view executor_frame_name) {
// This keeps the mapping of function signatures to expression strings that we expect to be able
// to find in order to get at the executor's root scope, which is where all subsequent scopes,
// tasks, and futures will be posted. The root scope is not actually anything the user cares
// about, and has precisely the same lifetime as the executor itself.
//
// The key is a vector of strings, the first of which will be treated as a prefix match. Any
// subsequent strings will be checked for containment only. All elements of the vector must
static const std::vector<std::pair<std::vector<std::string_view>, std::string_view>>
kExecutorFrameNameToScopeExpr = {
// Single threaded executor, found on the main thread.
{{"fuchsia_async::runtime::fuchsia::executor::local::LocalExecutor::run"},
"self.ehandle.root_scope"},
// Multithreaded executor, found on the main thread.
{{"fuchsia_async::runtime::fuchsia::executor::send::SendExecutor::run"},
"self.root_scope"},
// Multithreaded executor, found on a worker thread.
{{"fuchsia_async::runtime::fuchsia::executor::send", "create_worker_threads::{closure"},
"root_scope"},
};
for (const auto& [func_parts, expr] : kExecutorFrameNameToScopeExpr) {
size_t i = 0;
for (; i < func_parts.size(); i++) {
if ((i == 0 && !debug::StringStartsWith(executor_frame_name, func_parts[i])) ||
(i > 0 && !debug::StringContains(executor_frame_name, func_parts[i]))) {
break;
}
}
// Return the first entry that matched all elements.
if (i == func_parts.size()) {
return std::string(expr);
}
}
return std::nullopt;
}
} // namespace
FuchsiaAsyncRustTaskProvider::FuchsiaAsyncRustTaskProvider() = default;
FuchsiaAsyncRustTaskProvider::~FuchsiaAsyncRustTaskProvider() = default;
bool FuchsiaAsyncRustTaskProvider::CanHandle(Frame* frame) const {
if (!frame->GetLocation().symbol().is_valid())
return false;
std::string func_name(StripTemplate(frame->GetLocation().symbol().Get()->GetFullName()));
return GetScopeExpressionForExecutorFrame(func_name) != std::nullopt;
}
void FuchsiaAsyncRustTaskProvider::GetTasks(
Frame* frame, fit::callback<void(const Err&, std::vector<std::unique_ptr<AsyncTask>>)> cb) {
FX_DCHECK(frame);
std::string func_name(StripTemplate(frame->GetLocation().symbol().Get()->GetFullName()));
auto maybe_expr = GetScopeExpressionForExecutorFrame(func_name);
if (maybe_expr == std::nullopt) {
debug::MessageLoop::Current()->PostTask(FROM_HERE, [cb = std::move(cb)]() mutable {
cb(Err("No matching function signature to find async executor."), {});
});
return;
}
auto context = frame->GetEvalContext();
EvalExpression(
*maybe_expr, context, false,
[session = frame->session(), context, cb = std::move(cb)](ErrOrValue value) mutable {
if (value.has_error()) {
cb(value.err(), {});
} else {
ResolveScopeHandle(
context, value.value(),
[session, context, cb = std::move(cb)](ErrOrValue state) mutable {
if (state.has_error()) {
cb(state.err(), {});
} else {
FetchScopeTasks(
session, context, state.value(),
[cb = std::move(cb)](std::vector<std::unique_ptr<AsyncTask>> tasks) mutable {
cb(Err(), std::move(tasks));
});
}
});
}
});
}
} // namespace zxdb