blob: 6f32fced0e59864e96da157a6c2f8b76e414e72e [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.
#ifndef SRC_DEVELOPER_DEBUG_ZXDB_SYMBOLS_SYMBOL_H_
#define SRC_DEVELOPER_DEBUG_ZXDB_SYMBOLS_SYMBOL_H_
#include <optional>
#include <string>
#include <vector>
#include "src/developer/debug/zxdb/common/ref_ptr_to.h"
#include "src/developer/debug/zxdb/symbols/dwarf_lang.h"
#include "src/developer/debug/zxdb/symbols/dwarf_tag.h"
#include "src/developer/debug/zxdb/symbols/identifier.h"
#include "src/developer/debug/zxdb/symbols/lazy_symbol.h"
#include "src/developer/debug/zxdb/symbols/symbol_context.h"
#include "src/lib/fxl/memory/ref_counted.h"
#include "src/lib/fxl/memory/weak_ptr.h"
namespace zxdb {
class ArrayType;
class BaseType;
class CallSite;
class CallSiteParameter;
class CodeBlock;
class Collection;
class CompileUnit;
class DataMember;
class ElfSymbol;
class Enumeration;
class Function;
class FunctionType;
class InheritedFrom;
class MemberPtr;
class ModifiedType;
class ModuleSymbols;
class Namespace;
class ProcessSymbols;
class TemplateParameter;
class Type;
class Value;
class Variable;
class Variant;
class VariantPart;
// Represents the type of a variable. This is a deserialized version of the various DWARF DIEs
// ("Debug Information Entry" -- a record in the DWARF file) that define types.
//
// SYMBOL MEMORY MODEL
// -------------------
// Symbols are reference counted and have references to other Symbols via a LazySymbol object which
// allows lazy decoding of the DWARF data. These are not cached or re-used so we can get many
// duplicate Symbol objects for the same DIE. Therefore, Symbol object identity is not a way to
// compare two symbols. Even if these were unified, DWARF will often encode the same thing in each
// compilation unit it is needed in, so object identity can never work in DWARF context.
//
// This non-caching behavior is important to prevent reference cycles that would cause memory leaks.
// Not only does each symbol reference its parent, there are complex and almost-arbitrary links
// between DIEs that don't work well with the reference-counting used by symbols.
//
// A downside to this design is that we might decode the same symbol multiple times and end up with
// many copies of the same data, both of which are inefficient.
//
// The main alternative would be to remove reference counting and instead maintain a per-module
// mapping of DIE address to decoded symbols. Then links between Symbol objects can either be DIE
// addresses that are looked up in the module every time they're needed (lets the module free things
// that haven't been used in a while) or object pointers (avoids the intermediate lookup but means
// objects can never be freed without unloading the whole module). This scheme would mean that
// symbols will be freed when the module is removed, which will require weak pointers from the
// expression system.
class Symbol : public fxl::RefCountedThreadSafe<Symbol> {
public:
DwarfTag tag() const { return tag_; }
// The parent symbol.
//
// Normally this is the symbol that contains this one in the symbol file.
//
// In the case of function implementations with separate definitions, this will be the lexical
// parent of the function (for example, a class or namespace) rather than the one containing the
// code. This is how callers can navigate the type tree but it means the parent won't match the
// record in the DWARF file.
//
// For inline functions, it's important to know both the lexical scope which tells you the
// class/namespace of the function being inlined (the parent()) as well as the function it's
// inlined into. Function symbols have a special containing_block() to give the latter.
const UncachedLazySymbol& parent() const { return parent_; }
void set_parent(const UncachedLazySymbol& e) { parent_ = e; }
// Returns the name associated with this symbol. This name comes from the corresponding record in
// the DWARF format (hence "assigned"). It will NOT include namespace and struct qualifiers.
// Anything without a name assigned on the particular DWARF record name will return an empty
// string, even if that thing logically has a name that can be computed (as for ModifiedType).
//
// This default implementation returns a reference to an empty string. Derived classes will
// override as needed.
//
// Most callers will want to use GetFullName().
virtual const std::string& GetAssignedName() const;
// Returns the full user-visible name for this symbol. This will include all namespace and struct
// qualifications, and will include things like const and "*" qualifiers on modified types.
//
// It will not include a global qualifier ("::" at the beginning) because that's not desired in
// most uses. If your use-case cares about controlling this, use GetIdentifier().
//
// This implements caching. Derived classes override ComputeFullName() to control how the full
// name is presented.
//
// See also GetIdentifier().
const std::string& GetFullName() const;
// Returns the name of this symbol as an identifier if possible.
//
// Many symbols have identifier names, this normally includes anything with an assigned name:
// functions, structs, typedefs and base types.
//
// Some things don't have names that can be made into identifiers, this includes modified types
// such as "const Foo*" since the "const" and the "*" don't fit into the normal identifier scheme.
// These types will report an empty Identifier for GetIdentifier().
//
// See also GetFullName(). GetFullName() will work for the modified type cases above since it just
// returns a string, but it's not parseable.
const Identifier& GetIdentifier() const;
// Returns the CompileUnit that this symbol is associated with. Returns null on failure.
fxl::RefPtr<CompileUnit> GetCompileUnit() const;
// Returns the module symbols associated with this symbol object. It can be null if the module
// has been unloaded and there are still dangling references to symbols, and it can also be null
// in some test situations.
virtual fxl::WeakPtr<ModuleSymbols> GetModuleSymbols() const;
// Returns the symbol context for this symbol in the given process. This requires the process so
// it can look up what the module load address is for this symbol's module (the same module can be
// loaded into multiple processes).
//
// The ProcessSymbols can be null. It will be treated as an invalid module (see below).
//
// The module may not be valid. It could have been unloaded while there were dangling symbols,
// or it can be null in some test situations. In these cases the resulting symbol context will
// be a "relative" context -- see SymbolContext::is_relative().
SymbolContext GetSymbolContext(const ProcessSymbols* process_symbols) const;
// Computes and returns the language associated with this symbol. This will be kNone if the
// language is not known or unset.
//
// This requires decoding the compile unit so is not super efficient to get.
DwarfLang GetLanguage() const;
// Manual RTTI.
// Allows templatized conversion to a base class. Both const and non-const variants are supported
// using the protected virtual functions below. This is basically dynamic_cast but we're required
// to avoid compiling with RTTI.
//
// const Collection* c = symbol->As<Collection>();
// if (!c)
// return false;
//
template <typename Derived>
const Derived* As() const;
template <typename Derived>
Derived* As();
#define IMPLEMENT_TEMPLATIZED_AS(DerivedType) \
template <> \
const DerivedType* As<DerivedType>() const { \
return As##DerivedType(); \
} \
template <> \
DerivedType* As<DerivedType>() { \
return const_cast<DerivedType*>(const_cast<const Symbol*>(this)->As##DerivedType()); \
}
IMPLEMENT_TEMPLATIZED_AS(ArrayType)
IMPLEMENT_TEMPLATIZED_AS(BaseType)
IMPLEMENT_TEMPLATIZED_AS(CallSite)
IMPLEMENT_TEMPLATIZED_AS(CallSiteParameter)
IMPLEMENT_TEMPLATIZED_AS(CodeBlock)
IMPLEMENT_TEMPLATIZED_AS(Collection)
IMPLEMENT_TEMPLATIZED_AS(CompileUnit)
IMPLEMENT_TEMPLATIZED_AS(DataMember)
IMPLEMENT_TEMPLATIZED_AS(ElfSymbol)
IMPLEMENT_TEMPLATIZED_AS(Enumeration)
IMPLEMENT_TEMPLATIZED_AS(Function)
IMPLEMENT_TEMPLATIZED_AS(FunctionType)
IMPLEMENT_TEMPLATIZED_AS(InheritedFrom)
IMPLEMENT_TEMPLATIZED_AS(MemberPtr)
IMPLEMENT_TEMPLATIZED_AS(ModifiedType)
IMPLEMENT_TEMPLATIZED_AS(Namespace)
IMPLEMENT_TEMPLATIZED_AS(TemplateParameter)
IMPLEMENT_TEMPLATIZED_AS(Type)
IMPLEMENT_TEMPLATIZED_AS(Value)
IMPLEMENT_TEMPLATIZED_AS(Variable)
IMPLEMENT_TEMPLATIZED_AS(Variant)
IMPLEMENT_TEMPLATIZED_AS(VariantPart)
#undef IMPLEMENT_TEMPLATIZED_AS
protected:
FRIEND_REF_COUNTED_THREAD_SAFE(Symbol);
FRIEND_MAKE_REF_COUNTED(Symbol);
// Construct via fxl::MakeRefCounted.
Symbol();
explicit Symbol(DwarfTag tag);
virtual ~Symbol();
// Manual RTTI. See "As<...>" template above for the public versions (these are protected just
// so callers are consistent).
virtual const ArrayType* AsArrayType() const;
virtual const BaseType* AsBaseType() const;
virtual const CallSite* AsCallSite() const;
virtual const CallSiteParameter* AsCallSiteParameter() const;
virtual const CodeBlock* AsCodeBlock() const;
virtual const Collection* AsCollection() const;
virtual const CompileUnit* AsCompileUnit() const;
virtual const DataMember* AsDataMember() const;
virtual const ElfSymbol* AsElfSymbol() const;
virtual const Enumeration* AsEnumeration() const;
virtual const Function* AsFunction() const;
virtual const FunctionType* AsFunctionType() const;
virtual const InheritedFrom* AsInheritedFrom() const;
virtual const MemberPtr* AsMemberPtr() const;
virtual const ModifiedType* AsModifiedType() const;
virtual const Namespace* AsNamespace() const;
virtual const TemplateParameter* AsTemplateParameter() const;
virtual const Type* AsType() const;
virtual const Value* AsValue() const;
virtual const Variable* AsVariable() const;
virtual const Variant* AsVariant() const;
virtual const VariantPart* AsVariantPart() const;
// Computes the full name and identifier. Used by GetFullName() and GetIdentifier() which add a
// caching layer.
//
// Derived classes should override these to control how the name is presented. The default
// implementation of ComputeIdentifier() returns the scope prefix (namespaces, structs) + the
// assigned name. The default implementation of ComputeFullName() returns the stringified version
// of the identifier.
//
// The returned Identifier should be globally qualified.
virtual std::string ComputeFullName() const;
virtual Identifier ComputeIdentifier() const;
private:
DwarfTag tag_ = DwarfTag::kNone;
// Using the "uncached" version here prevents reference cycles since normally a parent has
// references back to each of its children. By always using the "uncached" one when pointing
// up in the symbol tree, there are no owning references to symbol objects going in the opposite
// direction that can cause reference cycles. The tradeoff is that going up in the tree requires
// decoding the symbol each time at a slight performance penalty.
UncachedLazySymbol parent_;
// Lazily computed full symbol name and identifier name.
mutable std::optional<std::string> full_name_;
mutable std::optional<Identifier> identifier_;
};
} // namespace zxdb
#endif // SRC_DEVELOPER_DEBUG_ZXDB_SYMBOLS_SYMBOL_H_