blob: f4de011f32fc9afcbab5e4fd6153bf138d92ef0c [file] [log] [blame]
// Copyright 2018 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.
#pragma once
#include <stdint.h>
#include <functional>
#include <memory>
#include <vector>
#include "garnet/bin/zxdb/console/output_buffer.h"
#include "garnet/bin/zxdb/expr/format_expr_value_options.h"
#include "lib/fxl/memory/ref_counted.h"
#include "lib/fxl/memory/ref_ptr.h"
#include "lib/fxl/memory/weak_ptr.h"
namespace zxdb {
class Collection;
class Enumeration;
class Err;
class ExprValue;
class Location;
class MemberPtr;
class OutputBuffer;
class SymbolContext;
class SymbolDataProvider;
class SymbolVariableResolver;
class Type;
class Value;
class Variable;
// Manages formatting of variables and ExprValues (the results of expressions).
// Since formatting is asynchronous this can be tricky. This class manages a
// set of output operations interleaved with synchronously and asynchronously
// formatted values.
//
// When all requested formatting is complete, the callback will be issued with
// the concatenated result.
//
// When all output is done being appended, call Complete() to schedule the
// final callback.
//
// In common usage the helper can actually be owned by the callback to keep it
// alive during processing and automatically delete it when done:
//
// auto helper = fxl::MakeRefCounted<FormatValue>();
// helper->Append(...);
// // IMPORTANT: Do not move helper into this call or the RefPtr can get
// // cleared before invoking the call!
// helper->Complete([helper](OutputBuffer out) { UseIt(out); });
class FormatValue : public fxl::RefCountedThreadSafe<FormatValue> {
public:
// Abstruct interface for looking up information about a process.
class ProcessContext {
public:
virtual ~ProcessContext() = default;
// Given an address in the process, returns the (symbolized if possible)
// Location for that address.
virtual Location GetLocationForAddress(uint64_t address) const = 0;
};
using Callback = std::function<void(OutputBuffer)>;
// Construct with fxl::MakeRefCounted<FormatValue>().
void AppendValue(fxl::RefPtr<SymbolDataProvider> data_provider,
const ExprValue value,
const FormatExprValueOptions& options);
// The data provider normally comes from the frame where you want to evaluate
// the variable in. This will prepend "<name> = " to the value of the
// variable.
void AppendVariable(const SymbolContext& symbol_context,
fxl::RefPtr<SymbolDataProvider> data_provider,
const Variable* var,
const FormatExprValueOptions& options);
void Append(std::string str);
void Append(OutputBuffer out);
// Call after all data has been appended.
//
// This needs to be a separate call since not all output is asynchronous, and
// we don't want to call a callback before everything is complete, or not at
// all.
void Complete(Callback callback);
private:
FRIEND_REF_COUNTED_THREAD_SAFE(FormatValue);
FRIEND_MAKE_REF_COUNTED(FormatValue);
enum class NodeType { kGeneric, kVariable, kBaseClass };
// Output is multilevel and each level can be asynchronous (a struct can
// include another struct which can include an array, etc.).
//
// As we build up the formatted output, each composite type
// (struct/class/array) adds a new node with its contents as children.
// Asynchronous operations can fill in the buffers of these nodes, and when
// all output is complete, the tree can be flattened to produce the final
// result.
//
// REFACTORING IN PROGRESS...
//
// Originally OutputNode just held an OutputBuffer in a hierarchical way
// that can be referenced externally as an OutputKey. Strings were filled in
// by the value formatter as it went.
//
// We would like to move to a design where value formatting is split in two
// parts: (1) computing a tree of values, (2) converting that tree to text.
// The first part should be in the client or expr directory, while the text
// formatting should be in the console directory.
//
// With this split, we can drive other types of UIs without duplicating the
// complex type deduction and stringification logic. Even with the console
// output only, this design would let us do smarter wrapping since the
// complete result is known at text-generation time.
//
// This class will morph into the tree node. Currently it is a bit of a
// hybrid where it can store a higher level concept (given by NodeType) with
// an optional name, or it can just be random text.
struct OutputNode {
// Optional.
std::string name;
NodeType type = NodeType::kGeneric;
OutputBuffer buffer; // Only used when there are no children.
// Used for sanity checking. This is set when waiting on async resolution
// on a given node, and cleared when async resolution is complete. It
// makes sure we don't miss or double-set anything.
bool pending = false;
// The children must be heap-allocated because the pointers (in the form of
// an OutputKey) will be passed around across vector resizes.
std::vector<std::unique_ptr<OutputNode>> child;
};
// Identifies an output buffer to write asynchronously to.
//
// This is actually an OutputNode*. The tricky thing is that the pointers
// are all owned by the FormatValue class. If the class goes out of scope
// the pointers will be invalidated, but they may still be referenced by
// in-progress callbacks.
//
// To avoid the temptation to write to the OutputBuffer directly from these
// callbacks, or to forget to check for completion, this type requires a
// cast. OutputKeyComplete does the cast and checks whether all callbacks are
// complete. By being on the object itself, it forces asynchronous callbacks
// to resolve their weak back-pointers first.
//
// No code should ever case this other than the functions that manipulate the
// keys (AppendToOutputKey, AsyncAppend, OutputKeyComplete).
using OutputKey = intptr_t;
explicit FormatValue(std::unique_ptr<ProcessContext> process_context);
~FormatValue();
// Formats the given expression value to the output buffer. The variant that
// takes an Err will do an error check before printing the value, and will
// output the appropriate error message instead if there is one. It will
// modify the error message to be appropriate as a replacement for a value.
// output the appropriate error message instead if there is one.
//
// When set, suppress_type_printing will suppress the use of
// options.always_show_types for this item only (but not nested items). This
// is designed to be used when called recursively and the type has already
// been printed.
void FormatExprValue(fxl::RefPtr<SymbolDataProvider> data_provider,
const ExprValue& value,
const FormatExprValueOptions& options,
bool suppress_type_printing, OutputKey output_key);
void FormatExprValue(fxl::RefPtr<SymbolDataProvider> data_provider,
const Err& err, const ExprValue& value,
const FormatExprValueOptions& options,
bool suppress_type_printing, OutputKey output_key);
// Asynchronously formats the given type.
//
// The known_elt_count can be -1 if the array size is not statically known.
void FormatCollection(fxl::RefPtr<SymbolDataProvider> data_provider,
const Collection* coll, const ExprValue& value,
const FormatExprValueOptions& options,
OutputKey output_key);
void FormatString(fxl::RefPtr<SymbolDataProvider> data_provider,
const ExprValue& value, const Type* array_value_type,
int known_elt_count, const FormatExprValueOptions& options,
OutputKey output_key);
// Checks array and string types and formats the value accordingly. Returns
// true if it was an array or string type that was handled, false if it
// was anything else.
bool TryFormatArrayOrString(fxl::RefPtr<SymbolDataProvider> data_provider,
const Type* type, const ExprValue& value,
const FormatExprValueOptions& options,
OutputKey output_key);
// Array and string format helpers.
void FormatCharPointer(fxl::RefPtr<SymbolDataProvider> data_provider,
const Type* type, const ExprValue& value,
const FormatExprValueOptions& options,
OutputKey output_key);
void FormatCharArray(const uint8_t* data, size_t length, bool truncated,
OutputKey output_key);
void FormatArray(fxl::RefPtr<SymbolDataProvider> data_provider,
const ExprValue& value, int elt_count,
const FormatExprValueOptions& options, OutputKey output_key);
// Dispatcher for all numeric types. This handles formatting overrides.
void FormatNumeric(const ExprValue& value,
const FormatExprValueOptions& options, OutputBuffer* out);
// Simpler synchronous outputs.
void FormatBoolean(const ExprValue& value, OutputBuffer* out);
void FormatEnum(const ExprValue& value, const Enumeration* enum_type,
const FormatExprValueOptions& options, OutputBuffer* out);
void FormatFloat(const ExprValue& value, OutputBuffer* out);
void FormatSignedInt(const ExprValue& value, OutputBuffer* out);
void FormatUnsignedInt(const ExprValue& value,
const FormatExprValueOptions& options,
OutputBuffer* out);
void FormatChar(const ExprValue& value, OutputBuffer* out);
void FormatPointer(const ExprValue& value,
const FormatExprValueOptions& options, OutputBuffer* out);
void FormatReference(fxl::RefPtr<SymbolDataProvider> data_provider,
const ExprValue& value,
const FormatExprValueOptions& options,
OutputKey output_key);
void FormatFunctionPointer(const ExprValue& value,
const FormatExprValueOptions& options,
OutputBuffer* out);
void FormatMemberPtr(const ExprValue& value, const MemberPtr* type,
const FormatExprValueOptions& options,
OutputBuffer* out);
OutputKey GetRootOutputKey();
// Appends a child node to the output key without opening an async
// transaction.
void AppendToOutputKey(OutputKey output_key, OutputBuffer buffer);
// An asynchronous version of AppendToOutputKey. The returned key is a
// sub-key for use in later appending. Call OutputKeyComplete when this is
// done.
OutputKey AsyncAppend(OutputKey parent);
OutputKey AsyncAppend(NodeType type, std::string name, OutputKey parent);
// Marks the given output key complete. The variant that takes an output
// buffer is a shorthand for appending the contents and marking it complete.
// This will check for completion and issue the callback if everything has
// been resolved.
void OutputKeyComplete(OutputKey key);
void OutputKeyComplete(OutputKey key, OutputBuffer contents);
// Issues the pending callback if necessary. The callback may delete |this|
// so the caller should immediately return after calling.
void CheckPendingResolution();
// Recursively walks the OutputNode tree to produce the final output in
// the given output buffer. The sources are moved from so this is
// destructive.
void RecursiveCollectOutput(OutputNode* node, OutputBuffer* out);
std::unique_ptr<ProcessContext> process_context_;
Callback complete_callback_;
std::vector<OutputBuffer> buffers_;
std::vector<std::unique_ptr<SymbolVariableResolver>> resolvers_;
// The root of the output.
OutputNode root_;
int pending_resolution_ = 0;
fxl::WeakPtrFactory<FormatValue> weak_factory_;
};
} // namespace zxdb