Data formatters now cache themselves. This commit provides a new formatter cache mechanism. Upon resolving a formatter (summary or synthetic), LLDB remembers the resolution for later faster retrieval. Also moved the data formatters subsystem from the core to its own group and folder for easier management, and done some code reorganization. The ObjC runtime v1 now returns a class name if asked for the dynamic type of an object. This is required for formatters caching to work with the v1 runtime. Lastly, this commit disposes of the old hack where ValueObjects had to remember whether they were queried for formatters with their static or dynamic type. Now the ValueObjectDynamicValue class works well enough that we can use its dynamic value setting for the same purpose. llvm-svn: 173728
682 lines
20 KiB
C++
682 lines
20 KiB
C++
//===-- FormatNavigator.h ----------------------------------------*- C++ -*-===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef lldb_FormatNavigator_h_
|
|
#define lldb_FormatNavigator_h_
|
|
|
|
// C Includes
|
|
// C++ Includes
|
|
|
|
// Other libraries and framework includes
|
|
#include "clang/AST/DeclCXX.h"
|
|
#include "clang/AST/Type.h"
|
|
#include "clang/AST/DeclObjC.h"
|
|
|
|
// Project includes
|
|
#include "lldb/lldb-public.h"
|
|
|
|
#include "lldb/Core/Log.h"
|
|
#include "lldb/Core/RegularExpression.h"
|
|
#include "lldb/Core/ValueObject.h"
|
|
|
|
#include "lldb/DataFormatters/FormatClasses.h"
|
|
|
|
#include "lldb/Symbol/ClangASTContext.h"
|
|
|
|
#include "lldb/Target/ObjCLanguageRuntime.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/StackFrame.h"
|
|
#include "lldb/Target/TargetList.h"
|
|
|
|
using lldb::LogSP;
|
|
|
|
namespace lldb_private {
|
|
|
|
// this file (and its. cpp) contain the low-level implementation of LLDB Data Visualization
|
|
// class DataVisualization is the high-level front-end of this feature
|
|
// clients should refer to that class as the entry-point into the data formatters
|
|
// unless they have a good reason to bypass it and prefer to use this file's objects directly
|
|
class IFormatChangeListener
|
|
{
|
|
public:
|
|
virtual void
|
|
Changed () = 0;
|
|
|
|
virtual
|
|
~IFormatChangeListener () {}
|
|
|
|
virtual uint32_t
|
|
GetCurrentRevision () = 0;
|
|
|
|
};
|
|
|
|
static inline bool
|
|
IsWhitespace (char c)
|
|
{
|
|
return ( (c == ' ') || (c == '\t') || (c == '\v') || (c == '\f') );
|
|
}
|
|
|
|
static inline bool
|
|
HasPrefix (const char* str1, const char* str2)
|
|
{
|
|
return ( ::strstr(str1, str2) == str1 );
|
|
}
|
|
|
|
// if the user tries to add formatters for, say, "struct Foo"
|
|
// those will not match any type because of the way we strip qualifiers from typenames
|
|
// this method looks for the case where the user is adding a "class","struct","enum" or "union" Foo
|
|
// and strips the unnecessary qualifier
|
|
static ConstString
|
|
GetValidTypeName_Impl (const ConstString& type)
|
|
{
|
|
int strip_len = 0;
|
|
|
|
if (type == false)
|
|
return type;
|
|
|
|
const char* type_cstr = type.AsCString();
|
|
|
|
if ( HasPrefix(type_cstr, "class ") )
|
|
strip_len = 6;
|
|
else if ( HasPrefix(type_cstr, "enum ") )
|
|
strip_len = 5;
|
|
else if ( HasPrefix(type_cstr, "struct ") )
|
|
strip_len = 7;
|
|
else if ( HasPrefix(type_cstr, "union ") )
|
|
strip_len = 6;
|
|
|
|
if (strip_len == 0)
|
|
return type;
|
|
|
|
type_cstr += strip_len;
|
|
while (IsWhitespace(*type_cstr) && ++type_cstr)
|
|
;
|
|
|
|
return ConstString(type_cstr);
|
|
}
|
|
|
|
template<typename KeyType, typename ValueType>
|
|
class FormatNavigator;
|
|
|
|
template<typename KeyType, typename ValueType>
|
|
class FormatMap
|
|
{
|
|
public:
|
|
|
|
typedef typename ValueType::SharedPointer ValueSP;
|
|
typedef std::map<KeyType, ValueSP> MapType;
|
|
typedef typename MapType::iterator MapIterator;
|
|
typedef bool(*CallbackType)(void*, KeyType, const ValueSP&);
|
|
|
|
FormatMap(IFormatChangeListener* lst) :
|
|
m_map(),
|
|
m_map_mutex(Mutex::eMutexTypeRecursive),
|
|
listener(lst)
|
|
{
|
|
}
|
|
|
|
void
|
|
Add(KeyType name,
|
|
const ValueSP& entry)
|
|
{
|
|
if (listener)
|
|
entry->GetRevision() = listener->GetCurrentRevision();
|
|
else
|
|
entry->GetRevision() = 0;
|
|
|
|
Mutex::Locker locker(m_map_mutex);
|
|
m_map[name] = entry;
|
|
if (listener)
|
|
listener->Changed();
|
|
}
|
|
|
|
bool
|
|
Delete (KeyType name)
|
|
{
|
|
Mutex::Locker locker(m_map_mutex);
|
|
MapIterator iter = m_map.find(name);
|
|
if (iter == m_map.end())
|
|
return false;
|
|
m_map.erase(name);
|
|
if (listener)
|
|
listener->Changed();
|
|
return true;
|
|
}
|
|
|
|
void
|
|
Clear ()
|
|
{
|
|
Mutex::Locker locker(m_map_mutex);
|
|
m_map.clear();
|
|
if (listener)
|
|
listener->Changed();
|
|
}
|
|
|
|
bool
|
|
Get(KeyType name,
|
|
ValueSP& entry)
|
|
{
|
|
Mutex::Locker locker(m_map_mutex);
|
|
MapIterator iter = m_map.find(name);
|
|
if (iter == m_map.end())
|
|
return false;
|
|
entry = iter->second;
|
|
return true;
|
|
}
|
|
|
|
void
|
|
LoopThrough (CallbackType callback, void* param)
|
|
{
|
|
if (callback)
|
|
{
|
|
Mutex::Locker locker(m_map_mutex);
|
|
MapIterator pos, end = m_map.end();
|
|
for (pos = m_map.begin(); pos != end; pos++)
|
|
{
|
|
KeyType type = pos->first;
|
|
if (!callback(param, type, pos->second))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t
|
|
GetCount ()
|
|
{
|
|
return m_map.size();
|
|
}
|
|
|
|
ValueSP
|
|
GetValueAtIndex (size_t index)
|
|
{
|
|
Mutex::Locker locker(m_map_mutex);
|
|
MapIterator iter = m_map.begin();
|
|
MapIterator end = m_map.end();
|
|
while (index > 0)
|
|
{
|
|
iter++;
|
|
index--;
|
|
if (end == iter)
|
|
return ValueSP();
|
|
}
|
|
return iter->second;
|
|
}
|
|
|
|
KeyType
|
|
GetKeyAtIndex (size_t index)
|
|
{
|
|
Mutex::Locker locker(m_map_mutex);
|
|
MapIterator iter = m_map.begin();
|
|
MapIterator end = m_map.end();
|
|
while (index > 0)
|
|
{
|
|
iter++;
|
|
index--;
|
|
if (end == iter)
|
|
return KeyType();
|
|
}
|
|
return iter->first;
|
|
}
|
|
|
|
protected:
|
|
MapType m_map;
|
|
Mutex m_map_mutex;
|
|
IFormatChangeListener* listener;
|
|
|
|
MapType&
|
|
map ()
|
|
{
|
|
return m_map;
|
|
}
|
|
|
|
Mutex&
|
|
mutex ()
|
|
{
|
|
return m_map_mutex;
|
|
}
|
|
|
|
friend class FormatNavigator<KeyType, ValueType>;
|
|
friend class FormatManager;
|
|
|
|
};
|
|
|
|
template<typename KeyType, typename ValueType>
|
|
class FormatNavigator
|
|
{
|
|
protected:
|
|
typedef FormatMap<KeyType,ValueType> BackEndType;
|
|
|
|
public:
|
|
typedef typename BackEndType::MapType MapType;
|
|
typedef typename MapType::iterator MapIterator;
|
|
typedef typename MapType::key_type MapKeyType;
|
|
typedef typename MapType::mapped_type MapValueType;
|
|
typedef typename BackEndType::CallbackType CallbackType;
|
|
#ifdef _LIBCPP_VERSION
|
|
typedef typename std::shared_ptr<FormatNavigator<KeyType, ValueType> > SharedPointer;
|
|
#else
|
|
typedef typename std::tr1::shared_ptr<FormatNavigator<KeyType, ValueType> > SharedPointer;
|
|
#endif
|
|
|
|
friend class TypeCategoryImpl;
|
|
|
|
FormatNavigator(std::string name,
|
|
IFormatChangeListener* lst) :
|
|
m_format_map(lst),
|
|
m_name(name),
|
|
m_id_cs(ConstString("id"))
|
|
{
|
|
}
|
|
|
|
void
|
|
Add (const MapKeyType &type, const MapValueType& entry)
|
|
{
|
|
Add_Impl(type, entry, (KeyType*)NULL);
|
|
}
|
|
|
|
bool
|
|
Delete (ConstString type)
|
|
{
|
|
return Delete_Impl(type, (KeyType*)NULL);
|
|
}
|
|
|
|
bool
|
|
Get(ValueObject& valobj,
|
|
MapValueType& entry,
|
|
lldb::DynamicValueType use_dynamic,
|
|
uint32_t* why = NULL)
|
|
{
|
|
uint32_t value = lldb_private::eFormatterChoiceCriterionDirectChoice;
|
|
clang::QualType type = clang::QualType::getFromOpaquePtr(valobj.GetClangType());
|
|
bool ret = Get(valobj, type, entry, use_dynamic, value);
|
|
if (ret)
|
|
entry = MapValueType(entry);
|
|
else
|
|
entry = MapValueType();
|
|
if (why)
|
|
*why = value;
|
|
return ret;
|
|
}
|
|
|
|
bool
|
|
Get (ConstString type, MapValueType& entry)
|
|
{
|
|
return Get_Impl(type, entry, (KeyType*)NULL);
|
|
}
|
|
|
|
bool
|
|
GetExact (ConstString type, MapValueType& entry)
|
|
{
|
|
return GetExact_Impl(type, entry, (KeyType*)NULL);
|
|
}
|
|
|
|
MapValueType
|
|
GetAtIndex (size_t index)
|
|
{
|
|
return m_format_map.GetValueAtIndex(index);
|
|
}
|
|
|
|
lldb::TypeNameSpecifierImplSP
|
|
GetTypeNameSpecifierAtIndex (size_t index)
|
|
{
|
|
return GetTypeNameSpecifierAtIndex_Impl(index, (KeyType*)NULL);
|
|
}
|
|
|
|
void
|
|
Clear ()
|
|
{
|
|
m_format_map.Clear();
|
|
}
|
|
|
|
void
|
|
LoopThrough (CallbackType callback, void* param)
|
|
{
|
|
m_format_map.LoopThrough(callback,param);
|
|
}
|
|
|
|
uint32_t
|
|
GetCount ()
|
|
{
|
|
return m_format_map.GetCount();
|
|
}
|
|
|
|
protected:
|
|
|
|
BackEndType m_format_map;
|
|
|
|
std::string m_name;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(FormatNavigator);
|
|
|
|
ConstString m_id_cs;
|
|
|
|
void
|
|
Add_Impl (const MapKeyType &type, const MapValueType& entry, lldb::RegularExpressionSP *dummy)
|
|
{
|
|
m_format_map.Add(type,entry);
|
|
}
|
|
|
|
void Add_Impl (const ConstString &type, const MapValueType& entry, ConstString *dummy)
|
|
{
|
|
m_format_map.Add(GetValidTypeName_Impl(type), entry);
|
|
}
|
|
|
|
bool
|
|
Delete_Impl (ConstString type, ConstString *dummy)
|
|
{
|
|
return m_format_map.Delete(type);
|
|
}
|
|
|
|
bool
|
|
Delete_Impl (ConstString type, lldb::RegularExpressionSP *dummy)
|
|
{
|
|
Mutex& x_mutex = m_format_map.mutex();
|
|
lldb_private::Mutex::Locker locker(x_mutex);
|
|
MapIterator pos, end = m_format_map.map().end();
|
|
for (pos = m_format_map.map().begin(); pos != end; pos++)
|
|
{
|
|
lldb::RegularExpressionSP regex = pos->first;
|
|
if ( ::strcmp(type.AsCString(),regex->GetText()) == 0)
|
|
{
|
|
m_format_map.map().erase(pos);
|
|
if (m_format_map.listener)
|
|
m_format_map.listener->Changed();
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
Get_Impl (ConstString type, MapValueType& entry, ConstString *dummy)
|
|
{
|
|
return m_format_map.Get(type, entry);
|
|
}
|
|
|
|
bool
|
|
GetExact_Impl (ConstString type, MapValueType& entry, ConstString *dummy)
|
|
{
|
|
return Get_Impl(type,entry, (KeyType*)0);
|
|
}
|
|
|
|
lldb::TypeNameSpecifierImplSP
|
|
GetTypeNameSpecifierAtIndex_Impl (size_t index, ConstString *dummy)
|
|
{
|
|
ConstString key = m_format_map.GetKeyAtIndex(index);
|
|
if (key)
|
|
return lldb::TypeNameSpecifierImplSP(new TypeNameSpecifierImpl(key.AsCString(),
|
|
false));
|
|
else
|
|
return lldb::TypeNameSpecifierImplSP();
|
|
}
|
|
|
|
lldb::TypeNameSpecifierImplSP
|
|
GetTypeNameSpecifierAtIndex_Impl (size_t index, lldb::RegularExpressionSP *dummy)
|
|
{
|
|
lldb::RegularExpressionSP regex = m_format_map.GetKeyAtIndex(index);
|
|
if (regex.get() == NULL)
|
|
return lldb::TypeNameSpecifierImplSP();
|
|
return lldb::TypeNameSpecifierImplSP(new TypeNameSpecifierImpl(regex->GetText(),
|
|
true));
|
|
}
|
|
|
|
bool
|
|
Get_Impl (ConstString key, MapValueType& value, lldb::RegularExpressionSP *dummy)
|
|
{
|
|
const char* key_cstr = key.AsCString();
|
|
if (!key_cstr)
|
|
return false;
|
|
Mutex& x_mutex = m_format_map.mutex();
|
|
lldb_private::Mutex::Locker locker(x_mutex);
|
|
MapIterator pos, end = m_format_map.map().end();
|
|
for (pos = m_format_map.map().begin(); pos != end; pos++)
|
|
{
|
|
lldb::RegularExpressionSP regex = pos->first;
|
|
if (regex->Execute(key_cstr))
|
|
{
|
|
value = pos->second;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
GetExact_Impl (ConstString key, MapValueType& value, lldb::RegularExpressionSP *dummy)
|
|
{
|
|
Mutex& x_mutex = m_format_map.mutex();
|
|
lldb_private::Mutex::Locker locker(x_mutex);
|
|
MapIterator pos, end = m_format_map.map().end();
|
|
for (pos = m_format_map.map().begin(); pos != end; pos++)
|
|
{
|
|
lldb::RegularExpressionSP regex = pos->first;
|
|
if (strcmp(regex->GetText(),key.AsCString()) == 0)
|
|
{
|
|
value = pos->second;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
Get_BitfieldMatch (ValueObject& valobj,
|
|
ConstString typeName,
|
|
MapValueType& entry,
|
|
uint32_t& reason)
|
|
{
|
|
LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_TYPES));
|
|
// for bitfields, append size to the typename so one can custom format them
|
|
StreamString sstring;
|
|
sstring.Printf("%s:%d",typeName.AsCString(),valobj.GetBitfieldBitSize());
|
|
ConstString bitfieldname = ConstString(sstring.GetData());
|
|
if (log)
|
|
log->Printf("[Get_BitfieldMatch] appended bitfield info, final result is %s", bitfieldname.GetCString());
|
|
if (Get(bitfieldname, entry))
|
|
{
|
|
if (log)
|
|
log->Printf("[Get_BitfieldMatch] bitfield direct match found, returning");
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
reason |= lldb_private::eFormatterChoiceCriterionStrippedBitField;
|
|
if (log)
|
|
log->Printf("[Get_BitfieldMatch] no bitfield direct match");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool Get_ObjC (ValueObject& valobj,
|
|
MapValueType& entry)
|
|
{
|
|
LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_TYPES));
|
|
lldb::ProcessSP process_sp = valobj.GetProcessSP();
|
|
ObjCLanguageRuntime* runtime = process_sp->GetObjCLanguageRuntime();
|
|
if (runtime == NULL)
|
|
{
|
|
if (log)
|
|
log->Printf("[Get_ObjC] no valid ObjC runtime, skipping dynamic");
|
|
return false;
|
|
}
|
|
ObjCLanguageRuntime::ClassDescriptorSP objc_class_sp (runtime->GetClassDescriptor(valobj));
|
|
if (!objc_class_sp)
|
|
{
|
|
if (log)
|
|
log->Printf("[Get_ObjC] invalid ISA, skipping dynamic");
|
|
return false;
|
|
}
|
|
ConstString name (objc_class_sp->GetClassName());
|
|
if (log)
|
|
log->Printf("[Get_ObjC] dynamic type inferred is %s - looking for direct dynamic match", name.GetCString());
|
|
if (Get(name, entry))
|
|
{
|
|
if (log)
|
|
log->Printf("[Get_ObjC] direct dynamic match found, returning");
|
|
return true;
|
|
}
|
|
if (log)
|
|
log->Printf("[Get_ObjC] no dynamic match");
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
Get_Impl (ValueObject& valobj,
|
|
clang::QualType type,
|
|
MapValueType& entry,
|
|
lldb::DynamicValueType use_dynamic,
|
|
uint32_t& reason)
|
|
{
|
|
LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_TYPES));
|
|
|
|
if (type.isNull())
|
|
{
|
|
if (log)
|
|
log->Printf("[Get_Impl] type is NULL, returning");
|
|
return false;
|
|
}
|
|
|
|
type.removeLocalConst(); type.removeLocalVolatile(); type.removeLocalRestrict();
|
|
const clang::Type* typePtr = type.getTypePtrOrNull();
|
|
if (!typePtr)
|
|
{
|
|
if (log)
|
|
log->Printf("[Get_Impl] type is NULL, returning");
|
|
return false;
|
|
}
|
|
ConstString typeName(ClangASTType::GetTypeNameForQualType(valobj.GetClangAST(), type).c_str());
|
|
|
|
if (valobj.GetBitfieldBitSize() > 0)
|
|
{
|
|
if (Get_BitfieldMatch(valobj, typeName, entry, reason))
|
|
return true;
|
|
}
|
|
|
|
if (log)
|
|
log->Printf("[Get_Impl] trying to get %s for VO name %s of type %s",
|
|
m_name.c_str(),
|
|
valobj.GetName().AsCString(),
|
|
typeName.AsCString());
|
|
|
|
if (Get(typeName, entry))
|
|
{
|
|
if (log)
|
|
log->Printf("[Get] direct match found, returning");
|
|
return true;
|
|
}
|
|
if (log)
|
|
log->Printf("[Get_Impl] no direct match");
|
|
|
|
// strip pointers and references and see if that helps
|
|
if (typePtr->isReferenceType())
|
|
{
|
|
if (log)
|
|
log->Printf("[Get_Impl] stripping reference");
|
|
if (Get_Impl(valobj,type.getNonReferenceType(),entry, use_dynamic, reason) && !entry->SkipsReferences())
|
|
{
|
|
reason |= lldb_private::eFormatterChoiceCriterionStrippedPointerReference;
|
|
return true;
|
|
}
|
|
}
|
|
else if (typePtr->isPointerType())
|
|
{
|
|
if (log)
|
|
log->Printf("[Get_Impl] stripping pointer");
|
|
clang::QualType pointee = typePtr->getPointeeType();
|
|
if (Get_Impl(valobj, pointee, entry, use_dynamic, reason) && !entry->SkipsPointers())
|
|
{
|
|
reason |= lldb_private::eFormatterChoiceCriterionStrippedPointerReference;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool canBeObjCDynamic = ClangASTContext::IsPossibleDynamicType (valobj.GetClangAST(),
|
|
type.getAsOpaquePtr(),
|
|
NULL,
|
|
false, // no C++
|
|
true); // yes ObjC
|
|
|
|
if (canBeObjCDynamic)
|
|
{
|
|
if (use_dynamic != lldb::eNoDynamicValues)
|
|
{
|
|
if (log)
|
|
log->Printf("[Get_Impl] allowed to figure out dynamic ObjC type");
|
|
if (Get_ObjC(valobj,entry))
|
|
{
|
|
reason |= lldb_private::eFormatterChoiceCriterionDynamicObjCDiscovery;
|
|
return true;
|
|
}
|
|
}
|
|
if (log)
|
|
log->Printf("[Get_Impl] dynamic disabled or failed - stripping ObjC pointer");
|
|
clang::QualType pointee = typePtr->getPointeeType();
|
|
if (Get_Impl(valobj, pointee, entry, use_dynamic, reason) && !entry->SkipsPointers())
|
|
{
|
|
reason |= lldb_private::eFormatterChoiceCriterionStrippedPointerReference;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// try to strip typedef chains
|
|
const clang::TypedefType* type_tdef = type->getAs<clang::TypedefType>();
|
|
if (type_tdef)
|
|
{
|
|
if (log)
|
|
log->Printf("[Get_Impl] stripping typedef");
|
|
if ((Get_Impl(valobj, type_tdef->getDecl()->getUnderlyingType(), entry, use_dynamic, reason)) && entry->Cascades())
|
|
{
|
|
reason |= lldb_private::eFormatterChoiceCriterionNavigatedTypedefs;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// out of luck here
|
|
return false;
|
|
}
|
|
|
|
// we are separately passing in valobj and type because the valobj is fixed (and is used for ObjC discovery and bitfield size)
|
|
// but the type can change (e.g. stripping pointers, ...)
|
|
bool Get (ValueObject& valobj,
|
|
clang::QualType type,
|
|
MapValueType& entry,
|
|
lldb::DynamicValueType use_dynamic,
|
|
uint32_t& reason)
|
|
{
|
|
LogSP log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_TYPES));
|
|
|
|
if (Get_Impl (valobj,type,entry,use_dynamic,reason))
|
|
return true;
|
|
|
|
// if all else fails, go to static type
|
|
if (valobj.IsDynamic())
|
|
{
|
|
if (log)
|
|
log->Printf("[Get] going to static value");
|
|
lldb::ValueObjectSP static_value_sp(valobj.GetStaticValue());
|
|
if (static_value_sp)
|
|
{
|
|
if (log)
|
|
log->Printf("[Get] has a static value - actually use it");
|
|
if (Get(*static_value_sp.get(), clang::QualType::getFromOpaquePtr(static_value_sp->GetClangType()) , entry, use_dynamic, reason))
|
|
{
|
|
reason |= lldb_private::eFormatterChoiceCriterionWentToStaticValue;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
};
|
|
|
|
} // namespace lldb_private
|
|
|
|
#endif // lldb_FormatNavigator_h_
|