
debugging optimized code. Adds new methods on Function/SBFunction to query whether a given function is optimized. Adds a new function.is-optimized format entity and changes the default frame-format to append "[opt]" if the function was built with optimization. The only indication that a binary was built with optimization that we have right now is the presence of the DW_AT_APPLE_optimized attribute (DW_FORM_flag value 1) in the DW_TAG_compile_unit. The absence of this flag may mean that the compile_unit was not compiled with optimization, or it may mean that the producer does not generate this attribute. Currently this only works for dSYM debugging. When we create the CompileUnit with dwarf-in-.o-file debugging we don't have the attribute value yet so it's not set. I need to find the flag value when we do start to read the .o file DWARF and set the CompileUnit's status at that point - but haven't done it yet. I'm also going to add a mechanism for issuing warnings to users such that they're only issued once in a debug session and there is away for users to suppress these warnings altogether via .lldbinit file settings. But I want to get this changeset committed now that it's at a useful state. <rdar://problem/19281172> llvm-svn: 243508
2564 lines
98 KiB
C++
2564 lines
98 KiB
C++
//===-- FormatEntity.cpp ----------------------------------------*- C++ -*-===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is distributed under the University of Illinois Open Source
|
|
// License. See LICENSE.TXT for details.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/ADT/StringRef.h"
|
|
|
|
#include "lldb/Core/FormatEntity.h"
|
|
|
|
#include "lldb/Core/Address.h"
|
|
#include "lldb/Core/Debugger.h"
|
|
#include "lldb/Core/Language.h"
|
|
#include "lldb/Core/Module.h"
|
|
#include "lldb/Core/Stream.h"
|
|
#include "lldb/Core/StreamString.h"
|
|
#include "lldb/Core/ValueObject.h"
|
|
#include "lldb/Core/ValueObjectVariable.h"
|
|
#include "lldb/DataFormatters/DataVisualization.h"
|
|
#include "lldb/DataFormatters/FormatManager.h"
|
|
#include "lldb/Expression/ClangExpressionVariable.h"
|
|
#include "lldb/Host/FileSpec.h"
|
|
#include "lldb/Interpreter/CommandInterpreter.h"
|
|
#include "lldb/Symbol/Block.h"
|
|
#include "lldb/Symbol/CompileUnit.h"
|
|
#include "lldb/Symbol/Function.h"
|
|
#include "lldb/Symbol/LineEntry.h"
|
|
#include "lldb/Symbol/Symbol.h"
|
|
#include "lldb/Symbol/VariableList.h"
|
|
#include "lldb/Target/ExecutionContext.h"
|
|
#include "lldb/Target/Process.h"
|
|
#include "lldb/Target/RegisterContext.h"
|
|
#include "lldb/Target/SectionLoadList.h"
|
|
#include "lldb/Target/StackFrame.h"
|
|
#include "lldb/Target/StopInfo.h"
|
|
#include "lldb/Target/Target.h"
|
|
#include "lldb/Target/Thread.h"
|
|
#include "lldb/Utility/AnsiTerminal.h"
|
|
|
|
using namespace lldb;
|
|
using namespace lldb_private;
|
|
|
|
|
|
enum FileKind
|
|
{
|
|
FileError = 0,
|
|
Basename,
|
|
Dirname,
|
|
Fullpath
|
|
};
|
|
|
|
#define ENTRY(n,t,f) { n, NULL, FormatEntity::Entry::Type::t, FormatEntity::Entry::FormatType::f, 0,0,NULL, false}
|
|
#define ENTRY_VALUE(n,t,f,v) { n, NULL, FormatEntity::Entry::Type::t, FormatEntity::Entry::FormatType::f, v,0,NULL, false}
|
|
#define ENTRY_CHILDREN(n,t,f,c) { n, NULL, FormatEntity::Entry::Type::t, FormatEntity::Entry::FormatType::f, 0,llvm::array_lengthof(c),c, false}
|
|
#define ENTRY_CHILDREN_KEEP_SEP(n,t,f,c) { n, NULL, FormatEntity::Entry::Type::t, FormatEntity::Entry::FormatType::f, 0,llvm::array_lengthof(c),c, true}
|
|
#define ENTRY_STRING(n,s) { n, s, FormatEntity::Entry::Type::InsertString, FormatEntity::Entry::FormatType::None, 0,0, NULL, false}
|
|
static FormatEntity::Entry::Definition g_string_entry[] =
|
|
{
|
|
ENTRY("*", ParentString, None)
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_addr_entries[] =
|
|
{
|
|
ENTRY ("load", AddressLoad , UInt64),
|
|
ENTRY ("file", AddressFile , UInt64),
|
|
ENTRY ("load", AddressLoadOrFile, UInt64),
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_file_child_entries[] =
|
|
{
|
|
ENTRY_VALUE("basename", ParentNumber, CString, FileKind::Basename),
|
|
ENTRY_VALUE("dirname", ParentNumber, CString, FileKind::Dirname),
|
|
ENTRY_VALUE("fullpath", ParentNumber, CString, FileKind::Fullpath)
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_frame_child_entries[] =
|
|
{
|
|
|
|
ENTRY ("index", FrameIndex , UInt32),
|
|
ENTRY ("pc" , FrameRegisterPC , UInt64),
|
|
ENTRY ("fp" , FrameRegisterFP , UInt64),
|
|
ENTRY ("sp" , FrameRegisterSP , UInt64),
|
|
ENTRY ("flags", FrameRegisterFlags, UInt64),
|
|
ENTRY_CHILDREN ("reg", FrameRegisterByName, UInt64, g_string_entry),
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_function_child_entries[] =
|
|
{
|
|
ENTRY ("id" , FunctionID , UInt64),
|
|
ENTRY ("name" , FunctionName , CString),
|
|
ENTRY ("name-without-args" , FunctionNameNoArgs , CString),
|
|
ENTRY ("name-with-args" , FunctionNameWithArgs , CString),
|
|
ENTRY ("addr-offset" , FunctionAddrOffset , UInt64),
|
|
ENTRY ("concrete-only-addr-offset-no-padding", FunctionAddrOffsetConcrete, UInt64),
|
|
ENTRY ("line-offset" , FunctionLineOffset , UInt64),
|
|
ENTRY ("pc-offset" , FunctionPCOffset , UInt64),
|
|
ENTRY ("initial-function" , FunctionInitial , None),
|
|
ENTRY ("changed" , FunctionChanged , None),
|
|
ENTRY ("is-optimized" , FunctionIsOptimized , None)
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_line_child_entries[] =
|
|
{
|
|
ENTRY_CHILDREN("file", LineEntryFile , None , g_file_child_entries),
|
|
ENTRY("number" , LineEntryLineNumber , UInt32),
|
|
ENTRY("start-addr" , LineEntryStartAddress, UInt64),
|
|
ENTRY("end-addr" , LineEntryEndAddress , UInt64),
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_module_child_entries[] =
|
|
{
|
|
ENTRY_CHILDREN("file", ModuleFile, None, g_file_child_entries),
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_process_child_entries[] =
|
|
{
|
|
ENTRY ( "id" , ProcessID , UInt64 ),
|
|
ENTRY_VALUE ( "name" , ProcessFile , CString , FileKind::Basename),
|
|
ENTRY_CHILDREN ( "file" , ProcessFile , None , g_file_child_entries),
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_svar_child_entries[] =
|
|
{
|
|
ENTRY ( "*" , ParentString , None)
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_var_child_entries[] =
|
|
{
|
|
ENTRY ( "*" , ParentString , None)
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_thread_child_entries[] =
|
|
{
|
|
ENTRY ( "id" , ThreadID , UInt64 ),
|
|
ENTRY ( "protocol_id" , ThreadProtocolID , UInt64 ),
|
|
ENTRY ( "index" , ThreadIndexID , UInt32 ),
|
|
ENTRY_CHILDREN ( "info" , ThreadInfo , None , g_string_entry),
|
|
ENTRY ( "queue" , ThreadQueue , CString ),
|
|
ENTRY ( "name" , ThreadName , CString ),
|
|
ENTRY ( "stop-reason" , ThreadStopReason , CString ),
|
|
ENTRY ( "return-value" , ThreadReturnValue , CString ),
|
|
ENTRY ( "completed-expression", ThreadCompletedExpression , CString ),
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_target_child_entries[] =
|
|
{
|
|
ENTRY ( "arch" , TargetArch , CString ),
|
|
};
|
|
|
|
#define _TO_STR2(_val) #_val
|
|
#define _TO_STR(_val) _TO_STR2(_val)
|
|
|
|
static FormatEntity::Entry::Definition g_ansi_fg_entries[] =
|
|
{
|
|
ENTRY_STRING ("black" , ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_BLACK) ANSI_ESC_END),
|
|
ENTRY_STRING ("red" , ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_RED) ANSI_ESC_END),
|
|
ENTRY_STRING ("green" , ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_GREEN) ANSI_ESC_END),
|
|
ENTRY_STRING ("yellow" , ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_YELLOW) ANSI_ESC_END),
|
|
ENTRY_STRING ("blue" , ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_BLUE) ANSI_ESC_END),
|
|
ENTRY_STRING ("purple" , ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_PURPLE) ANSI_ESC_END),
|
|
ENTRY_STRING ("cyan" , ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_CYAN) ANSI_ESC_END),
|
|
ENTRY_STRING ("white" , ANSI_ESC_START _TO_STR(ANSI_FG_COLOR_WHITE) ANSI_ESC_END),
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_ansi_bg_entries[] =
|
|
{
|
|
ENTRY_STRING ("black" , ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_BLACK) ANSI_ESC_END),
|
|
ENTRY_STRING ("red" , ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_RED) ANSI_ESC_END),
|
|
ENTRY_STRING ("green" , ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_GREEN) ANSI_ESC_END),
|
|
ENTRY_STRING ("yellow" , ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_YELLOW) ANSI_ESC_END),
|
|
ENTRY_STRING ("blue" , ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_BLUE) ANSI_ESC_END),
|
|
ENTRY_STRING ("purple" , ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_PURPLE) ANSI_ESC_END),
|
|
ENTRY_STRING ("cyan" , ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_CYAN) ANSI_ESC_END),
|
|
ENTRY_STRING ("white" , ANSI_ESC_START _TO_STR(ANSI_BG_COLOR_WHITE) ANSI_ESC_END),
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_ansi_entries[] =
|
|
{
|
|
ENTRY_CHILDREN ( "fg" , Invalid , None , g_ansi_fg_entries),
|
|
ENTRY_CHILDREN ( "bg" , Invalid , None , g_ansi_bg_entries),
|
|
ENTRY_STRING ( "normal" , ANSI_ESC_START _TO_STR(ANSI_CTRL_NORMAL) ANSI_ESC_END),
|
|
ENTRY_STRING ( "bold" , ANSI_ESC_START _TO_STR(ANSI_CTRL_BOLD) ANSI_ESC_END),
|
|
ENTRY_STRING ( "faint" , ANSI_ESC_START _TO_STR(ANSI_CTRL_FAINT) ANSI_ESC_END),
|
|
ENTRY_STRING ( "italic" , ANSI_ESC_START _TO_STR(ANSI_CTRL_ITALIC) ANSI_ESC_END),
|
|
ENTRY_STRING ( "underline" , ANSI_ESC_START _TO_STR(ANSI_CTRL_UNDERLINE) ANSI_ESC_END),
|
|
ENTRY_STRING ( "slow-blink" , ANSI_ESC_START _TO_STR(ANSI_CTRL_SLOW_BLINK) ANSI_ESC_END),
|
|
ENTRY_STRING ( "fast-blink" , ANSI_ESC_START _TO_STR(ANSI_CTRL_FAST_BLINK) ANSI_ESC_END),
|
|
ENTRY_STRING ( "negative" , ANSI_ESC_START _TO_STR(ANSI_CTRL_IMAGE_NEGATIVE) ANSI_ESC_END),
|
|
ENTRY_STRING ( "conceal" , ANSI_ESC_START _TO_STR(ANSI_CTRL_CONCEAL) ANSI_ESC_END),
|
|
ENTRY_STRING ( "crossed-out" , ANSI_ESC_START _TO_STR(ANSI_CTRL_CROSSED_OUT) ANSI_ESC_END),
|
|
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_script_child_entries[] =
|
|
{
|
|
ENTRY ( "frame" , ScriptFrame , None),
|
|
ENTRY ( "process" , ScriptProcess , None),
|
|
ENTRY ( "target" , ScriptTarget , None),
|
|
ENTRY ( "thread" , ScriptThread , None),
|
|
ENTRY ( "var" , ScriptVariable , None),
|
|
ENTRY ( "svar" , ScriptVariableSynthetic , None),
|
|
ENTRY ( "thread" , ScriptThread , None),
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_top_level_entries[] =
|
|
{
|
|
ENTRY_CHILDREN ("addr" , AddressLoadOrFile , UInt64 , g_addr_entries),
|
|
ENTRY ("addr-file-or-load" , AddressLoadOrFile , UInt64 ),
|
|
ENTRY_CHILDREN ("ansi" , Invalid , None , g_ansi_entries),
|
|
ENTRY ("current-pc-arrow" , CurrentPCArrow , CString ),
|
|
ENTRY_CHILDREN ("file" , File , CString , g_file_child_entries),
|
|
ENTRY ("language" , Lang , CString),
|
|
ENTRY_CHILDREN ("frame" , Invalid , None , g_frame_child_entries),
|
|
ENTRY_CHILDREN ("function" , Invalid , None , g_function_child_entries),
|
|
ENTRY_CHILDREN ("line" , Invalid , None , g_line_child_entries),
|
|
ENTRY_CHILDREN ("module" , Invalid , None , g_module_child_entries),
|
|
ENTRY_CHILDREN ("process" , Invalid , None , g_process_child_entries),
|
|
ENTRY_CHILDREN ("script" , Invalid , None , g_script_child_entries),
|
|
ENTRY_CHILDREN_KEEP_SEP ("svar" , VariableSynthetic , None , g_svar_child_entries),
|
|
ENTRY_CHILDREN ("thread" , Invalid , None , g_thread_child_entries),
|
|
ENTRY_CHILDREN ("target" , Invalid , None , g_target_child_entries),
|
|
ENTRY_CHILDREN_KEEP_SEP ("var" , Variable , None , g_var_child_entries),
|
|
};
|
|
|
|
static FormatEntity::Entry::Definition g_root = ENTRY_CHILDREN ("<root>", Root, None, g_top_level_entries);
|
|
|
|
|
|
FormatEntity::Entry::Entry (llvm::StringRef s) :
|
|
string (s.data(), s.size()),
|
|
printf_format (),
|
|
children (),
|
|
definition (NULL),
|
|
type (Type::String),
|
|
fmt (lldb::eFormatDefault),
|
|
number (0),
|
|
deref (false)
|
|
{
|
|
}
|
|
|
|
FormatEntity::Entry::Entry (char ch) :
|
|
string (1, ch),
|
|
printf_format (),
|
|
children (),
|
|
definition (NULL),
|
|
type (Type::String),
|
|
fmt (lldb::eFormatDefault),
|
|
number (0),
|
|
deref (false)
|
|
{
|
|
}
|
|
|
|
void
|
|
FormatEntity::Entry::AppendChar (char ch)
|
|
{
|
|
if (children.empty() || children.back().type != Entry::Type::String)
|
|
children.push_back(Entry(ch));
|
|
else
|
|
children.back().string.append(1, ch);
|
|
}
|
|
|
|
void
|
|
FormatEntity::Entry::AppendText (const llvm::StringRef &s)
|
|
{
|
|
if (children.empty() || children.back().type != Entry::Type::String)
|
|
children.push_back(Entry(s));
|
|
else
|
|
children.back().string.append(s.data(), s.size());
|
|
}
|
|
|
|
void
|
|
FormatEntity::Entry::AppendText (const char *cstr)
|
|
{
|
|
return AppendText (llvm::StringRef(cstr));
|
|
}
|
|
|
|
|
|
Error
|
|
FormatEntity::Parse (const llvm::StringRef &format_str, Entry &entry)
|
|
{
|
|
entry.Clear();
|
|
entry.type = Entry::Type::Root;
|
|
llvm::StringRef modifiable_format (format_str);
|
|
return ParseInternal (modifiable_format, entry, 0);
|
|
}
|
|
|
|
#define ENUM_TO_CSTR(eee) case FormatEntity::Entry::Type::eee: return #eee
|
|
|
|
const char *
|
|
FormatEntity::Entry::TypeToCString (Type t)
|
|
{
|
|
switch (t)
|
|
{
|
|
ENUM_TO_CSTR(Invalid);
|
|
ENUM_TO_CSTR(ParentNumber);
|
|
ENUM_TO_CSTR(ParentString);
|
|
ENUM_TO_CSTR(InsertString);
|
|
ENUM_TO_CSTR(Root);
|
|
ENUM_TO_CSTR(String);
|
|
ENUM_TO_CSTR(Scope);
|
|
ENUM_TO_CSTR(Variable);
|
|
ENUM_TO_CSTR(VariableSynthetic);
|
|
ENUM_TO_CSTR(ScriptVariable);
|
|
ENUM_TO_CSTR(ScriptVariableSynthetic);
|
|
ENUM_TO_CSTR(AddressLoad);
|
|
ENUM_TO_CSTR(AddressFile);
|
|
ENUM_TO_CSTR(AddressLoadOrFile);
|
|
ENUM_TO_CSTR(ProcessID);
|
|
ENUM_TO_CSTR(ProcessFile);
|
|
ENUM_TO_CSTR(ScriptProcess);
|
|
ENUM_TO_CSTR(ThreadID);
|
|
ENUM_TO_CSTR(ThreadProtocolID);
|
|
ENUM_TO_CSTR(ThreadIndexID);
|
|
ENUM_TO_CSTR(ThreadName);
|
|
ENUM_TO_CSTR(ThreadQueue);
|
|
ENUM_TO_CSTR(ThreadStopReason);
|
|
ENUM_TO_CSTR(ThreadReturnValue);
|
|
ENUM_TO_CSTR(ThreadCompletedExpression);
|
|
ENUM_TO_CSTR(ScriptThread);
|
|
ENUM_TO_CSTR(ThreadInfo);
|
|
ENUM_TO_CSTR(TargetArch);
|
|
ENUM_TO_CSTR(ScriptTarget);
|
|
ENUM_TO_CSTR(ModuleFile);
|
|
ENUM_TO_CSTR(File);
|
|
ENUM_TO_CSTR(Lang);
|
|
ENUM_TO_CSTR(FrameIndex);
|
|
ENUM_TO_CSTR(FrameRegisterPC);
|
|
ENUM_TO_CSTR(FrameRegisterSP);
|
|
ENUM_TO_CSTR(FrameRegisterFP);
|
|
ENUM_TO_CSTR(FrameRegisterFlags);
|
|
ENUM_TO_CSTR(FrameRegisterByName);
|
|
ENUM_TO_CSTR(ScriptFrame);
|
|
ENUM_TO_CSTR(FunctionID);
|
|
ENUM_TO_CSTR(FunctionDidChange);
|
|
ENUM_TO_CSTR(FunctionInitialFunction);
|
|
ENUM_TO_CSTR(FunctionName);
|
|
ENUM_TO_CSTR(FunctionNameWithArgs);
|
|
ENUM_TO_CSTR(FunctionNameNoArgs);
|
|
ENUM_TO_CSTR(FunctionAddrOffset);
|
|
ENUM_TO_CSTR(FunctionAddrOffsetConcrete);
|
|
ENUM_TO_CSTR(FunctionLineOffset);
|
|
ENUM_TO_CSTR(FunctionPCOffset);
|
|
ENUM_TO_CSTR(FunctionInitial);
|
|
ENUM_TO_CSTR(FunctionChanged);
|
|
ENUM_TO_CSTR(FunctionIsOptimized);
|
|
ENUM_TO_CSTR(LineEntryFile);
|
|
ENUM_TO_CSTR(LineEntryLineNumber);
|
|
ENUM_TO_CSTR(LineEntryStartAddress);
|
|
ENUM_TO_CSTR(LineEntryEndAddress);
|
|
ENUM_TO_CSTR(CurrentPCArrow);
|
|
}
|
|
return "???";
|
|
}
|
|
|
|
#undef ENUM_TO_CSTR
|
|
|
|
void
|
|
FormatEntity::Entry::Dump (Stream &s, int depth) const
|
|
{
|
|
s.Printf ("%*.*s%-20s: ", depth * 2, depth * 2, "", TypeToCString(type));
|
|
if (fmt != eFormatDefault)
|
|
s.Printf ("lldb-format = %s, ", FormatManager::GetFormatAsCString (fmt));
|
|
if (!string.empty())
|
|
s.Printf ("string = \"%s\"", string.c_str());
|
|
if (!printf_format.empty())
|
|
s.Printf ("printf_format = \"%s\"", printf_format.c_str());
|
|
if (number != 0)
|
|
s.Printf ("number = %" PRIu64 " (0x%" PRIx64 "), ", number, number);
|
|
if (deref)
|
|
s.Printf ("deref = true, ");
|
|
s.EOL();
|
|
for (const auto &child : children)
|
|
{
|
|
child.Dump(s, depth + 1);
|
|
}
|
|
}
|
|
|
|
|
|
template <typename T>
|
|
static bool RunScriptFormatKeyword(Stream &s,
|
|
const SymbolContext *sc,
|
|
const ExecutionContext *exe_ctx,
|
|
T t,
|
|
const char *script_function_name)
|
|
{
|
|
Target *target = Target::GetTargetFromContexts (exe_ctx, sc);
|
|
|
|
if (target)
|
|
{
|
|
ScriptInterpreter *script_interpreter = target->GetDebugger().GetCommandInterpreter().GetScriptInterpreter();
|
|
if (script_interpreter)
|
|
{
|
|
Error error;
|
|
std::string script_output;
|
|
|
|
if (script_interpreter->RunScriptFormatKeyword(script_function_name, t, script_output, error) && error.Success())
|
|
{
|
|
s.Printf("%s", script_output.c_str());
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
s.Printf("<error: %s>",error.AsCString());
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
DumpAddress (Stream &s,
|
|
const SymbolContext *sc,
|
|
const ExecutionContext *exe_ctx,
|
|
const Address &addr,
|
|
bool print_file_addr_or_load_addr)
|
|
{
|
|
Target *target = Target::GetTargetFromContexts (exe_ctx, sc);
|
|
addr_t vaddr = LLDB_INVALID_ADDRESS;
|
|
if (exe_ctx && !target->GetSectionLoadList().IsEmpty())
|
|
vaddr = addr.GetLoadAddress (target);
|
|
if (vaddr == LLDB_INVALID_ADDRESS)
|
|
vaddr = addr.GetFileAddress ();
|
|
|
|
if (vaddr != LLDB_INVALID_ADDRESS)
|
|
{
|
|
int addr_width = 0;
|
|
if (exe_ctx && target)
|
|
{
|
|
addr_width = target->GetArchitecture().GetAddressByteSize() * 2;
|
|
}
|
|
if (addr_width == 0)
|
|
addr_width = 16;
|
|
if (print_file_addr_or_load_addr)
|
|
{
|
|
ExecutionContextScope *exe_scope = NULL;
|
|
if (exe_ctx)
|
|
exe_scope = exe_ctx->GetBestExecutionContextScope();
|
|
addr.Dump (&s, exe_scope, Address::DumpStyleLoadAddress, Address::DumpStyleModuleWithFileAddress, 0);
|
|
}
|
|
else
|
|
{
|
|
s.Printf("0x%*.*" PRIx64, addr_width, addr_width, vaddr);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
DumpAddressOffsetFromFunction (Stream &s,
|
|
const SymbolContext *sc,
|
|
const ExecutionContext *exe_ctx,
|
|
const Address &format_addr,
|
|
bool concrete_only,
|
|
bool no_padding,
|
|
bool print_zero_offsets)
|
|
{
|
|
if (format_addr.IsValid())
|
|
{
|
|
Address func_addr;
|
|
|
|
if (sc)
|
|
{
|
|
if (sc->function)
|
|
{
|
|
func_addr = sc->function->GetAddressRange().GetBaseAddress();
|
|
if (sc->block && !concrete_only)
|
|
{
|
|
// Check to make sure we aren't in an inline
|
|
// function. If we are, use the inline block
|
|
// range that contains "format_addr" since
|
|
// blocks can be discontiguous.
|
|
Block *inline_block = sc->block->GetContainingInlinedBlock ();
|
|
AddressRange inline_range;
|
|
if (inline_block && inline_block->GetRangeContainingAddress (format_addr, inline_range))
|
|
func_addr = inline_range.GetBaseAddress();
|
|
}
|
|
}
|
|
else if (sc->symbol && sc->symbol->ValueIsAddress())
|
|
func_addr = sc->symbol->GetAddressRef();
|
|
}
|
|
|
|
if (func_addr.IsValid())
|
|
{
|
|
const char *addr_offset_padding = no_padding ? "" : " ";
|
|
|
|
if (func_addr.GetSection() == format_addr.GetSection())
|
|
{
|
|
addr_t func_file_addr = func_addr.GetFileAddress();
|
|
addr_t addr_file_addr = format_addr.GetFileAddress();
|
|
if (addr_file_addr > func_file_addr
|
|
|| (addr_file_addr == func_file_addr && print_zero_offsets))
|
|
{
|
|
s.Printf("%s+%s%" PRIu64, addr_offset_padding, addr_offset_padding, addr_file_addr - func_file_addr);
|
|
}
|
|
else if (addr_file_addr < func_file_addr)
|
|
{
|
|
s.Printf("%s-%s%" PRIu64, addr_offset_padding, addr_offset_padding, func_file_addr - addr_file_addr);
|
|
}
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Target *target = Target::GetTargetFromContexts (exe_ctx, sc);
|
|
if (target)
|
|
{
|
|
addr_t func_load_addr = func_addr.GetLoadAddress (target);
|
|
addr_t addr_load_addr = format_addr.GetLoadAddress (target);
|
|
if (addr_load_addr > func_load_addr
|
|
|| (addr_load_addr == func_load_addr && print_zero_offsets))
|
|
{
|
|
s.Printf("%s+%s%" PRIu64, addr_offset_padding, addr_offset_padding, addr_load_addr - func_load_addr);
|
|
}
|
|
else if (addr_load_addr < func_load_addr)
|
|
{
|
|
s.Printf("%s-%s%" PRIu64, addr_offset_padding, addr_offset_padding, func_load_addr - addr_load_addr);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
ScanBracketedRange (llvm::StringRef subpath,
|
|
size_t& close_bracket_index,
|
|
const char*& var_name_final_if_array_range,
|
|
int64_t& index_lower,
|
|
int64_t& index_higher)
|
|
{
|
|
Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_TYPES));
|
|
close_bracket_index = llvm::StringRef::npos;
|
|
const size_t open_bracket_index = subpath.find('[');
|
|
if (open_bracket_index == llvm::StringRef::npos)
|
|
{
|
|
if (log)
|
|
log->Printf("[ScanBracketedRange] no bracketed range, skipping entirely");
|
|
return false;
|
|
}
|
|
|
|
close_bracket_index = subpath.find(']', open_bracket_index + 1);
|
|
|
|
if (close_bracket_index == llvm::StringRef::npos)
|
|
{
|
|
if (log)
|
|
log->Printf("[ScanBracketedRange] no bracketed range, skipping entirely");
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
var_name_final_if_array_range = subpath.data() + open_bracket_index;
|
|
|
|
if (close_bracket_index - open_bracket_index == 1)
|
|
{
|
|
if (log)
|
|
log->Printf("[ScanBracketedRange] '[]' detected.. going from 0 to end of data");
|
|
index_lower = 0;
|
|
}
|
|
else
|
|
{
|
|
const size_t separator_index = subpath.find('-', open_bracket_index + 1);
|
|
|
|
if (separator_index == llvm::StringRef::npos)
|
|
{
|
|
const char *index_lower_cstr = subpath.data() + open_bracket_index + 1;
|
|
index_lower = ::strtoul (index_lower_cstr, NULL, 0);
|
|
index_higher = index_lower;
|
|
if (log)
|
|
log->Printf("[ScanBracketedRange] [%" PRId64 "] detected, high index is same", index_lower);
|
|
}
|
|
else
|
|
{
|
|
const char *index_lower_cstr = subpath.data() + open_bracket_index + 1;
|
|
const char *index_higher_cstr = subpath.data() + separator_index + 1;
|
|
index_lower = ::strtoul (index_lower_cstr, NULL, 0);
|
|
index_higher = ::strtoul (index_higher_cstr, NULL, 0);
|
|
if (log)
|
|
log->Printf("[ScanBracketedRange] [%" PRId64 "-%" PRId64 "] detected", index_lower, index_higher);
|
|
}
|
|
if (index_lower > index_higher && index_higher > 0)
|
|
{
|
|
if (log)
|
|
log->Printf("[ScanBracketedRange] swapping indices");
|
|
const int64_t temp = index_lower;
|
|
index_lower = index_higher;
|
|
index_higher = temp;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
DumpFile (Stream &s, const FileSpec &file, FileKind file_kind)
|
|
{
|
|
switch (file_kind)
|
|
{
|
|
case FileKind::FileError:
|
|
break;
|
|
|
|
case FileKind::Basename:
|
|
if (file.GetFilename())
|
|
{
|
|
s << file.GetFilename();
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case FileKind::Dirname:
|
|
if (file.GetDirectory())
|
|
{
|
|
s << file.GetDirectory();
|
|
return true;
|
|
}
|
|
break;
|
|
|
|
case FileKind::Fullpath:
|
|
if (file)
|
|
{
|
|
s << file;
|
|
return true;
|
|
}
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
DumpRegister (Stream &s,
|
|
StackFrame *frame,
|
|
RegisterKind reg_kind,
|
|
uint32_t reg_num,
|
|
Format format)
|
|
|
|
{
|
|
if (frame)
|
|
{
|
|
RegisterContext *reg_ctx = frame->GetRegisterContext().get();
|
|
|
|
if (reg_ctx)
|
|
{
|
|
const uint32_t lldb_reg_num = reg_ctx->ConvertRegisterKindToRegisterNumber(reg_kind, reg_num);
|
|
if (lldb_reg_num != LLDB_INVALID_REGNUM)
|
|
{
|
|
const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoAtIndex (lldb_reg_num);
|
|
if (reg_info)
|
|
{
|
|
RegisterValue reg_value;
|
|
if (reg_ctx->ReadRegister (reg_info, reg_value))
|
|
{
|
|
reg_value.Dump(&s, reg_info, false, false, format);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static ValueObjectSP
|
|
ExpandIndexedExpression (ValueObject* valobj,
|
|
size_t index,
|
|
StackFrame* frame,
|
|
bool deref_pointer)
|
|
{
|
|
Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_TYPES));
|
|
const char* ptr_deref_format = "[%d]";
|
|
std::string ptr_deref_buffer(10,0);
|
|
::sprintf(&ptr_deref_buffer[0], ptr_deref_format, index);
|
|
if (log)
|
|
log->Printf("[ExpandIndexedExpression] name to deref: %s",ptr_deref_buffer.c_str());
|
|
const char* first_unparsed;
|
|
ValueObject::GetValueForExpressionPathOptions options;
|
|
ValueObject::ExpressionPathEndResultType final_value_type;
|
|
ValueObject::ExpressionPathScanEndReason reason_to_stop;
|
|
ValueObject::ExpressionPathAftermath what_next = (deref_pointer ? ValueObject::eExpressionPathAftermathDereference : ValueObject::eExpressionPathAftermathNothing);
|
|
ValueObjectSP item = valobj->GetValueForExpressionPath (ptr_deref_buffer.c_str(),
|
|
&first_unparsed,
|
|
&reason_to_stop,
|
|
&final_value_type,
|
|
options,
|
|
&what_next);
|
|
if (!item)
|
|
{
|
|
if (log)
|
|
log->Printf("[ExpandIndexedExpression] ERROR: unparsed portion = %s, why stopping = %d,"
|
|
" final_value_type %d",
|
|
first_unparsed, reason_to_stop, final_value_type);
|
|
}
|
|
else
|
|
{
|
|
if (log)
|
|
log->Printf("[ExpandIndexedExpression] ALL RIGHT: unparsed portion = %s, why stopping = %d,"
|
|
" final_value_type %d",
|
|
first_unparsed, reason_to_stop, final_value_type);
|
|
}
|
|
return item;
|
|
}
|
|
|
|
static char
|
|
ConvertValueObjectStyleToChar(ValueObject::ValueObjectRepresentationStyle style)
|
|
{
|
|
switch (style)
|
|
{
|
|
case ValueObject::eValueObjectRepresentationStyleLanguageSpecific: return '@';
|
|
case ValueObject::eValueObjectRepresentationStyleValue: return 'V';
|
|
case ValueObject::eValueObjectRepresentationStyleLocation: return 'L';
|
|
case ValueObject::eValueObjectRepresentationStyleSummary: return 'S';
|
|
case ValueObject::eValueObjectRepresentationStyleChildrenCount: return '#';
|
|
case ValueObject::eValueObjectRepresentationStyleType: return 'T';
|
|
case ValueObject::eValueObjectRepresentationStyleName: return 'N';
|
|
case ValueObject::eValueObjectRepresentationStyleExpressionPath: return '>';
|
|
}
|
|
return '\0';
|
|
}
|
|
|
|
static bool
|
|
DumpValue (Stream &s,
|
|
const SymbolContext *sc,
|
|
const ExecutionContext *exe_ctx,
|
|
const FormatEntity::Entry &entry,
|
|
ValueObject *valobj)
|
|
{
|
|
if (valobj == NULL)
|
|
return false;
|
|
|
|
Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_TYPES));
|
|
Format custom_format = eFormatInvalid;
|
|
ValueObject::ValueObjectRepresentationStyle val_obj_display = entry.string.empty() ? ValueObject::eValueObjectRepresentationStyleValue : ValueObject::eValueObjectRepresentationStyleSummary;
|
|
|
|
bool do_deref_pointer = entry.deref;
|
|
bool is_script = false;
|
|
switch (entry.type)
|
|
{
|
|
case FormatEntity::Entry::Type::ScriptVariable:
|
|
is_script = true;
|
|
break;
|
|
|
|
case FormatEntity::Entry::Type::Variable:
|
|
custom_format = entry.fmt;
|
|
val_obj_display = (ValueObject::ValueObjectRepresentationStyle)entry.number;
|
|
break;
|
|
|
|
case FormatEntity::Entry::Type::ScriptVariableSynthetic:
|
|
is_script = true;
|
|
// Fall through
|
|
case FormatEntity::Entry::Type::VariableSynthetic:
|
|
custom_format = entry.fmt;
|
|
val_obj_display = (ValueObject::ValueObjectRepresentationStyle)entry.number;
|
|
if (!valobj->IsSynthetic())
|
|
{
|
|
valobj = valobj->GetSyntheticValue().get();
|
|
if (valobj == nullptr)
|
|
return false;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
if (valobj == NULL)
|
|
return false;
|
|
|
|
ValueObject::ExpressionPathAftermath what_next = (do_deref_pointer ?
|
|
ValueObject::eExpressionPathAftermathDereference : ValueObject::eExpressionPathAftermathNothing);
|
|
ValueObject::GetValueForExpressionPathOptions options;
|
|
options.DontCheckDotVsArrowSyntax().DoAllowBitfieldSyntax().DoAllowFragileIVar().SetSyntheticChildrenTraversal(ValueObject::GetValueForExpressionPathOptions::SyntheticChildrenTraversal::Both);
|
|
ValueObject* target = NULL;
|
|
const char* var_name_final_if_array_range = NULL;
|
|
size_t close_bracket_index = llvm::StringRef::npos;
|
|
int64_t index_lower = -1;
|
|
int64_t index_higher = -1;
|
|
bool is_array_range = false;
|
|
const char* first_unparsed;
|
|
bool was_plain_var = false;
|
|
bool was_var_format = false;
|
|
bool was_var_indexed = false;
|
|
ValueObject::ExpressionPathScanEndReason reason_to_stop = ValueObject::eExpressionPathScanEndReasonEndOfString;
|
|
ValueObject::ExpressionPathEndResultType final_value_type = ValueObject::eExpressionPathEndResultTypePlain;
|
|
|
|
if (is_script)
|
|
{
|
|
return RunScriptFormatKeyword (s, sc, exe_ctx, valobj, entry.string.c_str());
|
|
}
|
|
|
|
llvm::StringRef subpath (entry.string);
|
|
// simplest case ${var}, just print valobj's value
|
|
if (entry.string.empty())
|
|
{
|
|
if (entry.printf_format.empty() && entry.fmt == eFormatDefault && entry.number == ValueObject::eValueObjectRepresentationStyleValue)
|
|
was_plain_var = true;
|
|
else
|
|
was_var_format = true;
|
|
target = valobj;
|
|
}
|
|
else // this is ${var.something} or multiple .something nested
|
|
{
|
|
if (entry.string[0] == '[')
|
|
was_var_indexed = true;
|
|
ScanBracketedRange (subpath,
|
|
close_bracket_index,
|
|
var_name_final_if_array_range,
|
|
index_lower,
|
|
index_higher);
|
|
|
|
Error error;
|
|
|
|
const std::string &expr_path = entry.string;
|
|
|
|
if (log)
|
|
log->Printf("[Debugger::FormatPrompt] symbol to expand: %s",expr_path.c_str());
|
|
|
|
target = valobj->GetValueForExpressionPath(expr_path.c_str(),
|
|
&first_unparsed,
|
|
&reason_to_stop,
|
|
&final_value_type,
|
|
options,
|
|
&what_next).get();
|
|
|
|
if (!target)
|
|
{
|
|
if (log)
|
|
log->Printf("[Debugger::FormatPrompt] ERROR: unparsed portion = %s, why stopping = %d,"
|
|
" final_value_type %d",
|
|
first_unparsed, reason_to_stop, final_value_type);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if (log)
|
|
log->Printf("[Debugger::FormatPrompt] ALL RIGHT: unparsed portion = %s, why stopping = %d,"
|
|
" final_value_type %d",
|
|
first_unparsed, reason_to_stop, final_value_type);
|
|
target = target->GetQualifiedRepresentationIfAvailable(target->GetDynamicValueType(), true).get();
|
|
}
|
|
}
|
|
|
|
|
|
is_array_range = (final_value_type == ValueObject::eExpressionPathEndResultTypeBoundedRange ||
|
|
final_value_type == ValueObject::eExpressionPathEndResultTypeUnboundedRange);
|
|
|
|
do_deref_pointer = (what_next == ValueObject::eExpressionPathAftermathDereference);
|
|
|
|
if (do_deref_pointer && !is_array_range)
|
|
{
|
|
// I have not deref-ed yet, let's do it
|
|
// this happens when we are not going through GetValueForVariableExpressionPath
|
|
// to get to the target ValueObject
|
|
Error error;
|
|
target = target->Dereference(error).get();
|
|
if (error.Fail())
|
|
{
|
|
if (log)
|
|
log->Printf("[Debugger::FormatPrompt] ERROR: %s\n", error.AsCString("unknown")); \
|
|
return false;
|
|
}
|
|
do_deref_pointer = false;
|
|
}
|
|
|
|
if (!target)
|
|
{
|
|
if (log)
|
|
log->Printf("[Debugger::FormatPrompt] could not calculate target for prompt expression");
|
|
return false;
|
|
}
|
|
|
|
// we do not want to use the summary for a bitfield of type T:n
|
|
// if we were originally dealing with just a T - that would get
|
|
// us into an endless recursion
|
|
if (target->IsBitfield() && was_var_indexed)
|
|
{
|
|
// TODO: check for a (T:n)-specific summary - we should still obey that
|
|
StreamString bitfield_name;
|
|
bitfield_name.Printf("%s:%d", target->GetTypeName().AsCString(), target->GetBitfieldBitSize());
|
|
lldb::TypeNameSpecifierImplSP type_sp(new TypeNameSpecifierImpl(bitfield_name.GetData(),false));
|
|
if (val_obj_display == ValueObject::eValueObjectRepresentationStyleSummary && !DataVisualization::GetSummaryForType(type_sp))
|
|
val_obj_display = ValueObject::eValueObjectRepresentationStyleValue;
|
|
}
|
|
|
|
// TODO use flags for these
|
|
const uint32_t type_info_flags = target->GetClangType().GetTypeInfo(NULL);
|
|
bool is_array = (type_info_flags & eTypeIsArray) != 0;
|
|
bool is_pointer = (type_info_flags & eTypeIsPointer) != 0;
|
|
bool is_aggregate = target->GetClangType().IsAggregateType();
|
|
|
|
if ((is_array || is_pointer) && (!is_array_range) && val_obj_display == ValueObject::eValueObjectRepresentationStyleValue) // this should be wrong, but there are some exceptions
|
|
{
|
|
StreamString str_temp;
|
|
if (log)
|
|
log->Printf("[Debugger::FormatPrompt] I am into array || pointer && !range");
|
|
|
|
if (target->HasSpecialPrintableRepresentation(val_obj_display, custom_format))
|
|
{
|
|
// try to use the special cases
|
|
bool success = target->DumpPrintableRepresentation(str_temp,
|
|
val_obj_display,
|
|
custom_format);
|
|
if (log)
|
|
log->Printf("[Debugger::FormatPrompt] special cases did%s match", success ? "" : "n't");
|
|
|
|
// should not happen
|
|
if (success)
|
|
s << str_temp.GetData();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (was_plain_var) // if ${var}
|
|
{
|
|
s << target->GetTypeName() << " @ " << target->GetLocationAsCString();
|
|
}
|
|
else if (is_pointer) // if pointer, value is the address stored
|
|
{
|
|
target->DumpPrintableRepresentation (s,
|
|
val_obj_display,
|
|
custom_format,
|
|
ValueObject::ePrintableRepresentationSpecialCasesDisable);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// if directly trying to print ${var}, and this is an aggregate, display a nice
|
|
// type @ location message
|
|
if (is_aggregate && was_plain_var)
|
|
{
|
|
s << target->GetTypeName() << " @ " << target->GetLocationAsCString();
|
|
return true;
|
|
}
|
|
|
|
// if directly trying to print ${var%V}, and this is an aggregate, do not let the user do it
|
|
if (is_aggregate && ((was_var_format && val_obj_display == ValueObject::eValueObjectRepresentationStyleValue)))
|
|
{
|
|
s << "<invalid use of aggregate type>";
|
|
return true;
|
|
}
|
|
|
|
if (!is_array_range)
|
|
{
|
|
if (log)
|
|
log->Printf("[Debugger::FormatPrompt] dumping ordinary printable output");
|
|
return target->DumpPrintableRepresentation(s,val_obj_display, custom_format);
|
|
}
|
|
else
|
|
{
|
|
if (log)
|
|
log->Printf("[Debugger::FormatPrompt] checking if I can handle as array");
|
|
if (!is_array && !is_pointer)
|
|
return false;
|
|
if (log)
|
|
log->Printf("[Debugger::FormatPrompt] handle as array");
|
|
StreamString special_directions_stream;
|
|
llvm::StringRef special_directions;
|
|
if (close_bracket_index != llvm::StringRef::npos && subpath.size() > close_bracket_index)
|
|
{
|
|
ConstString additional_data (subpath.drop_front(close_bracket_index+1));
|
|
special_directions_stream.Printf("${%svar%s",
|
|
do_deref_pointer ? "*" : "",
|
|
additional_data.GetCString());
|
|
|
|
if (entry.fmt != eFormatDefault)
|
|
{
|
|
const char format_char = FormatManager::GetFormatAsFormatChar(entry.fmt);
|
|
if (format_char != '\0')
|
|
special_directions_stream.Printf("%%%c", format_char);
|
|
else
|
|
{
|
|
const char *format_cstr = FormatManager::GetFormatAsCString(entry.fmt);
|
|
special_directions_stream.Printf("%%%s", format_cstr);
|
|
}
|
|
}
|
|
else if (entry.number != 0)
|
|
{
|
|
const char style_char = ConvertValueObjectStyleToChar ((ValueObject::ValueObjectRepresentationStyle)entry.number);
|
|
if (style_char)
|
|
special_directions_stream.Printf("%%%c", style_char);
|
|
}
|
|
special_directions_stream.PutChar('}');
|
|
special_directions = llvm::StringRef(special_directions_stream.GetString());
|
|
}
|
|
|
|
// let us display items index_lower thru index_higher of this array
|
|
s.PutChar('[');
|
|
|
|
if (index_higher < 0)
|
|
index_higher = valobj->GetNumChildren() - 1;
|
|
|
|
uint32_t max_num_children = target->GetTargetSP()->GetMaximumNumberOfChildrenToDisplay();
|
|
|
|
bool success = true;
|
|
for (int64_t index = index_lower;index<=index_higher; ++index)
|
|
{
|
|
ValueObject* item = ExpandIndexedExpression (target,
|
|
index,
|
|
exe_ctx->GetFramePtr(),
|
|
false).get();
|
|
|
|
if (!item)
|
|
{
|
|
if (log)
|
|
log->Printf("[Debugger::FormatPrompt] ERROR in getting child item at index %" PRId64, index);
|
|
}
|
|
else
|
|
{
|
|
if (log)
|
|
log->Printf("[Debugger::FormatPrompt] special_directions for child item: %s",special_directions.data() ? special_directions.data() : "");
|
|
}
|
|
|
|
if (special_directions.empty())
|
|
{
|
|
success &= item->DumpPrintableRepresentation(s,val_obj_display, custom_format);
|
|
}
|
|
else
|
|
{
|
|
success &= FormatEntity::FormatStringRef(special_directions, s, sc, exe_ctx, NULL, item, false, false);
|
|
}
|
|
|
|
if (--max_num_children == 0)
|
|
{
|
|
s.PutCString(", ...");
|
|
break;
|
|
}
|
|
|
|
if (index < index_higher)
|
|
s.PutChar(',');
|
|
}
|
|
s.PutChar(']');
|
|
return success;
|
|
}
|
|
|
|
}
|
|
|
|
static bool
|
|
DumpRegister (Stream &s,
|
|
StackFrame *frame,
|
|
const char *reg_name,
|
|
Format format)
|
|
|
|
{
|
|
if (frame)
|
|
{
|
|
RegisterContext *reg_ctx = frame->GetRegisterContext().get();
|
|
|
|
if (reg_ctx)
|
|
{
|
|
const RegisterInfo *reg_info = reg_ctx->GetRegisterInfoByName(reg_name);
|
|
if (reg_info)
|
|
{
|
|
RegisterValue reg_value;
|
|
if (reg_ctx->ReadRegister (reg_info, reg_value))
|
|
{
|
|
reg_value.Dump(&s, reg_info, false, false, format);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
FormatThreadExtendedInfoRecurse(const FormatEntity::Entry &entry,
|
|
const StructuredData::ObjectSP &thread_info_dictionary,
|
|
const SymbolContext *sc,
|
|
const ExecutionContext *exe_ctx,
|
|
Stream &s)
|
|
{
|
|
llvm::StringRef path(entry.string);
|
|
|
|
StructuredData::ObjectSP value = thread_info_dictionary->GetObjectForDotSeparatedPath (path);
|
|
|
|
if (value.get())
|
|
{
|
|
if (value->GetType() == StructuredData::Type::eTypeInteger)
|
|
{
|
|
const char *token_format = "0x%4.4" PRIx64;
|
|
if (!entry.printf_format.empty())
|
|
token_format = entry.printf_format.c_str();
|
|
s.Printf(token_format, value->GetAsInteger()->GetValue());
|
|
return true;
|
|
}
|
|
else if (value->GetType() == StructuredData::Type::eTypeFloat)
|
|
{
|
|
s.Printf ("%f", value->GetAsFloat()->GetValue());
|
|
return true;
|
|
}
|
|
else if (value->GetType() == StructuredData::Type::eTypeString)
|
|
{
|
|
s.Printf("%s", value->GetAsString()->GetValue().c_str());
|
|
return true;
|
|
}
|
|
else if (value->GetType() == StructuredData::Type::eTypeArray)
|
|
{
|
|
if (value->GetAsArray()->GetSize() > 0)
|
|
{
|
|
s.Printf ("%zu", value->GetAsArray()->GetSize());
|
|
return true;
|
|
}
|
|
}
|
|
else if (value->GetType() == StructuredData::Type::eTypeDictionary)
|
|
{
|
|
s.Printf ("%zu", value->GetAsDictionary()->GetKeys()->GetAsArray()->GetSize());
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
static inline bool
|
|
IsToken(const char *var_name_begin, const char *var)
|
|
{
|
|
return (::strncmp (var_name_begin, var, strlen(var)) == 0);
|
|
}
|
|
|
|
bool
|
|
FormatEntity::FormatStringRef (const llvm::StringRef &format_str,
|
|
Stream &s,
|
|
const SymbolContext *sc,
|
|
const ExecutionContext *exe_ctx,
|
|
const Address *addr,
|
|
ValueObject* valobj,
|
|
bool function_changed,
|
|
bool initial_function)
|
|
{
|
|
if (!format_str.empty())
|
|
{
|
|
FormatEntity::Entry root;
|
|
Error error = FormatEntity::Parse(format_str, root);
|
|
if (error.Success())
|
|
{
|
|
return FormatEntity::Format (root,
|
|
s,
|
|
sc,
|
|
exe_ctx,
|
|
addr,
|
|
valobj,
|
|
function_changed,
|
|
initial_function);
|
|
}
|
|
}
|
|
return false;
|
|
|
|
}
|
|
bool
|
|
FormatEntity::FormatCString (const char *format,
|
|
Stream &s,
|
|
const SymbolContext *sc,
|
|
const ExecutionContext *exe_ctx,
|
|
const Address *addr,
|
|
ValueObject* valobj,
|
|
bool function_changed,
|
|
bool initial_function)
|
|
{
|
|
if (format && format[0])
|
|
{
|
|
FormatEntity::Entry root;
|
|
llvm::StringRef format_str(format);
|
|
Error error = FormatEntity::Parse(format_str, root);
|
|
if (error.Success())
|
|
{
|
|
return FormatEntity::Format (root,
|
|
s,
|
|
sc,
|
|
exe_ctx,
|
|
addr,
|
|
valobj,
|
|
function_changed,
|
|
initial_function);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
FormatEntity::Format (const Entry &entry,
|
|
Stream &s,
|
|
const SymbolContext *sc,
|
|
const ExecutionContext *exe_ctx,
|
|
const Address *addr,
|
|
ValueObject* valobj,
|
|
bool function_changed,
|
|
bool initial_function)
|
|
{
|
|
switch (entry.type)
|
|
{
|
|
case Entry::Type::Invalid:
|
|
case Entry::Type::ParentNumber: // Only used for FormatEntity::Entry::Definition encoding
|
|
case Entry::Type::ParentString: // Only used for FormatEntity::Entry::Definition encoding
|
|
case Entry::Type::InsertString: // Only used for FormatEntity::Entry::Definition encoding
|
|
return false;
|
|
|
|
case Entry::Type::Root:
|
|
for (const auto &child : entry.children)
|
|
{
|
|
if (Format (child,
|
|
s,
|
|
sc,
|
|
exe_ctx,
|
|
addr,
|
|
valobj,
|
|
function_changed,
|
|
initial_function) == false)
|
|
{
|
|
return false; // If any item of root fails, then the formatting fails
|
|
}
|
|
}
|
|
return true; // Only return true if all items succeeded
|
|
|
|
case Entry::Type::String:
|
|
s.PutCString(entry.string.c_str());
|
|
return true;
|
|
|
|
case Entry::Type::Scope:
|
|
{
|
|
StreamString scope_stream;
|
|
bool success = false;
|
|
for (const auto &child : entry.children)
|
|
{
|
|
success = Format (child, scope_stream, sc, exe_ctx, addr, valobj, function_changed, initial_function);
|
|
if (!success)
|
|
break;
|
|
}
|
|
// Only if all items in a scope succeed, then do we
|
|
// print the output into the main stream
|
|
if (success)
|
|
s.Write(scope_stream.GetString().data(), scope_stream.GetString().size());
|
|
}
|
|
return true; // Scopes always successfully print themselves
|
|
|
|
case Entry::Type::Variable:
|
|
case Entry::Type::VariableSynthetic:
|
|
case Entry::Type::ScriptVariable:
|
|
case Entry::Type::ScriptVariableSynthetic:
|
|
if (DumpValue(s, sc, exe_ctx, entry, valobj))
|
|
return true;
|
|
return false;
|
|
|
|
case Entry::Type::AddressFile:
|
|
case Entry::Type::AddressLoad:
|
|
case Entry::Type::AddressLoadOrFile:
|
|
if (addr && addr->IsValid() && DumpAddress(s, sc, exe_ctx, *addr, entry.type == Entry::Type::AddressLoadOrFile))
|
|
return true;
|
|
return false;
|
|
|
|
case Entry::Type::ProcessID:
|
|
if (exe_ctx)
|
|
{
|
|
Process *process = exe_ctx->GetProcessPtr();
|
|
if (process)
|
|
{
|
|
const char *format = "%" PRIu64;
|
|
if (!entry.printf_format.empty())
|
|
format = entry.printf_format.c_str();
|
|
s.Printf(format, process->GetID());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ProcessFile:
|
|
if (exe_ctx)
|
|
{
|
|
Process *process = exe_ctx->GetProcessPtr();
|
|
if (process)
|
|
{
|
|
Module *exe_module = process->GetTarget().GetExecutableModulePointer();
|
|
if (exe_module)
|
|
{
|
|
if (DumpFile(s, exe_module->GetFileSpec(), (FileKind)entry.number))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ScriptProcess:
|
|
if (exe_ctx)
|
|
{
|
|
Process *process = exe_ctx->GetProcessPtr();
|
|
if (process)
|
|
return RunScriptFormatKeyword (s, sc, exe_ctx, process, entry.string.c_str());
|
|
}
|
|
return false;
|
|
|
|
|
|
case Entry::Type::ThreadID:
|
|
if (exe_ctx)
|
|
{
|
|
Thread *thread = exe_ctx->GetThreadPtr();
|
|
if (thread)
|
|
{
|
|
const char *format = "0x%4.4" PRIx64;
|
|
if (!entry.printf_format.empty())
|
|
{
|
|
// Watch for the special "tid" format...
|
|
if (entry.printf_format == "tid")
|
|
{
|
|
Target &target = thread->GetProcess()->GetTarget();
|
|
ArchSpec arch (target.GetArchitecture ());
|
|
llvm::Triple::OSType ostype = arch.IsValid() ? arch.GetTriple().getOS() : llvm::Triple::UnknownOS;
|
|
if ((ostype == llvm::Triple::FreeBSD) || (ostype == llvm::Triple::Linux))
|
|
{
|
|
format = "%" PRIu64;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
format = entry.printf_format.c_str();
|
|
}
|
|
}
|
|
s.Printf(format, thread->GetID());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ThreadProtocolID:
|
|
if (exe_ctx)
|
|
{
|
|
Thread *thread = exe_ctx->GetThreadPtr();
|
|
if (thread)
|
|
{
|
|
const char *format = "0x%4.4" PRIx64;
|
|
if (!entry.printf_format.empty())
|
|
format = entry.printf_format.c_str();
|
|
s.Printf(format, thread->GetProtocolID());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ThreadIndexID:
|
|
if (exe_ctx)
|
|
{
|
|
Thread *thread = exe_ctx->GetThreadPtr();
|
|
if (thread)
|
|
{
|
|
const char *format = "%" PRIu32;
|
|
if (!entry.printf_format.empty())
|
|
format = entry.printf_format.c_str();
|
|
s.Printf(format, thread->GetIndexID());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ThreadName:
|
|
if (exe_ctx)
|
|
{
|
|
Thread *thread = exe_ctx->GetThreadPtr();
|
|
if (thread)
|
|
{
|
|
const char *cstr = thread->GetName();
|
|
if (cstr && cstr[0])
|
|
{
|
|
s.PutCString(cstr);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ThreadQueue:
|
|
if (exe_ctx)
|
|
{
|
|
Thread *thread = exe_ctx->GetThreadPtr();
|
|
if (thread)
|
|
{
|
|
const char *cstr = thread->GetQueueName();
|
|
if (cstr && cstr[0])
|
|
{
|
|
s.PutCString(cstr);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ThreadStopReason:
|
|
if (exe_ctx)
|
|
{
|
|
Thread *thread = exe_ctx->GetThreadPtr();
|
|
if (thread)
|
|
{
|
|
StopInfoSP stop_info_sp = thread->GetStopInfo ();
|
|
if (stop_info_sp && stop_info_sp->IsValid())
|
|
{
|
|
const char *cstr = stop_info_sp->GetDescription();
|
|
if (cstr && cstr[0])
|
|
{
|
|
s.PutCString(cstr);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ThreadReturnValue:
|
|
if (exe_ctx)
|
|
{
|
|
Thread *thread = exe_ctx->GetThreadPtr();
|
|
if (thread)
|
|
{
|
|
StopInfoSP stop_info_sp = thread->GetStopInfo ();
|
|
if (stop_info_sp && stop_info_sp->IsValid())
|
|
{
|
|
ValueObjectSP return_valobj_sp = StopInfo::GetReturnValueObject (stop_info_sp);
|
|
if (return_valobj_sp)
|
|
{
|
|
return_valobj_sp->Dump(s);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ThreadCompletedExpression:
|
|
if (exe_ctx)
|
|
{
|
|
Thread *thread = exe_ctx->GetThreadPtr();
|
|
if (thread)
|
|
{
|
|
StopInfoSP stop_info_sp = thread->GetStopInfo ();
|
|
if (stop_info_sp && stop_info_sp->IsValid())
|
|
{
|
|
ClangExpressionVariableSP expression_var_sp = StopInfo::GetExpressionVariable (stop_info_sp);
|
|
if (expression_var_sp && expression_var_sp->GetValueObject())
|
|
{
|
|
expression_var_sp->GetValueObject()->Dump(s);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ScriptThread:
|
|
if (exe_ctx)
|
|
{
|
|
Thread *thread = exe_ctx->GetThreadPtr();
|
|
if (thread)
|
|
return RunScriptFormatKeyword (s, sc, exe_ctx, thread, entry.string.c_str());
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ThreadInfo:
|
|
if (exe_ctx)
|
|
{
|
|
Thread *thread = exe_ctx->GetThreadPtr();
|
|
if (thread)
|
|
{
|
|
StructuredData::ObjectSP object_sp = thread->GetExtendedInfo();
|
|
if (object_sp && object_sp->GetType() == StructuredData::Type::eTypeDictionary)
|
|
{
|
|
if (FormatThreadExtendedInfoRecurse (entry, object_sp, sc, exe_ctx, s))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::TargetArch:
|
|
if (exe_ctx)
|
|
{
|
|
Target *target = exe_ctx->GetTargetPtr();
|
|
if (target)
|
|
{
|
|
const ArchSpec &arch = target->GetArchitecture ();
|
|
if (arch.IsValid())
|
|
{
|
|
s.PutCString (arch.GetArchitectureName());
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ScriptTarget:
|
|
if (exe_ctx)
|
|
{
|
|
Target *target = exe_ctx->GetTargetPtr();
|
|
if (target)
|
|
return RunScriptFormatKeyword (s, sc, exe_ctx, target, entry.string.c_str());
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ModuleFile:
|
|
if (sc)
|
|
{
|
|
Module *module = sc->module_sp.get();
|
|
if (module)
|
|
{
|
|
if (DumpFile(s, module->GetFileSpec(), (FileKind)entry.number))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::File:
|
|
if (sc)
|
|
{
|
|
CompileUnit *cu = sc->comp_unit;
|
|
if (cu)
|
|
{
|
|
// CompileUnit is a FileSpec
|
|
if (DumpFile(s, *cu, (FileKind)entry.number))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::Lang:
|
|
if (sc)
|
|
{
|
|
CompileUnit *cu = sc->comp_unit;
|
|
if (cu)
|
|
{
|
|
Language lang(cu->GetLanguage());
|
|
const char *lang_name = lang.AsCString();
|
|
if (lang_name)
|
|
{
|
|
s.PutCString(lang_name);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::FrameIndex:
|
|
if (exe_ctx)
|
|
{
|
|
StackFrame *frame = exe_ctx->GetFramePtr();
|
|
if (frame)
|
|
{
|
|
const char *format = "%" PRIu32;
|
|
if (!entry.printf_format.empty())
|
|
format = entry.printf_format.c_str();
|
|
s.Printf(format, frame->GetFrameIndex());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::FrameRegisterPC:
|
|
if (exe_ctx)
|
|
{
|
|
StackFrame *frame = exe_ctx->GetFramePtr();
|
|
if (frame)
|
|
{
|
|
const Address &pc_addr = frame->GetFrameCodeAddress();
|
|
if (pc_addr.IsValid())
|
|
{
|
|
if (DumpAddress(s, sc, exe_ctx, pc_addr, false))
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::FrameRegisterSP:
|
|
if (exe_ctx)
|
|
{
|
|
StackFrame *frame = exe_ctx->GetFramePtr();
|
|
if (frame)
|
|
{
|
|
if (DumpRegister (s, frame, eRegisterKindGeneric, LLDB_REGNUM_GENERIC_SP, (lldb::Format)entry.number))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::FrameRegisterFP:
|
|
if (exe_ctx)
|
|
{
|
|
StackFrame *frame = exe_ctx->GetFramePtr();
|
|
if (frame)
|
|
{
|
|
if (DumpRegister (s, frame, eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FP, (lldb::Format)entry.number))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::FrameRegisterFlags:
|
|
if (exe_ctx)
|
|
{
|
|
StackFrame *frame = exe_ctx->GetFramePtr();
|
|
if (frame)
|
|
{
|
|
if (DumpRegister (s, frame, eRegisterKindGeneric, LLDB_REGNUM_GENERIC_FLAGS, (lldb::Format)entry.number))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
|
|
case Entry::Type::FrameRegisterByName:
|
|
if (exe_ctx)
|
|
{
|
|
StackFrame *frame = exe_ctx->GetFramePtr();
|
|
if (frame)
|
|
{
|
|
if (DumpRegister (s, frame, entry.string.c_str(), (lldb::Format)entry.number))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::ScriptFrame:
|
|
if (exe_ctx)
|
|
{
|
|
StackFrame *frame = exe_ctx->GetFramePtr();
|
|
if (frame)
|
|
return RunScriptFormatKeyword (s, sc, exe_ctx, frame, entry.string.c_str());
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::FunctionID:
|
|
if (sc)
|
|
{
|
|
if (sc->function)
|
|
{
|
|
s.Printf("function{0x%8.8" PRIx64 "}", sc->function->GetID());
|
|
return true;
|
|
}
|
|
else if (sc->symbol)
|
|
{
|
|
s.Printf("symbol[%u]", sc->symbol->GetID());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::FunctionDidChange:
|
|
return function_changed;
|
|
|
|
case Entry::Type::FunctionInitialFunction:
|
|
return initial_function;
|
|
|
|
case Entry::Type::FunctionName:
|
|
{
|
|
const char *name = NULL;
|
|
if (sc->function)
|
|
name = sc->function->GetName().AsCString (NULL);
|
|
else if (sc->symbol)
|
|
name = sc->symbol->GetName().AsCString (NULL);
|
|
if (name)
|
|
{
|
|
s.PutCString(name);
|
|
|
|
if (sc->block)
|
|
{
|
|
Block *inline_block = sc->block->GetContainingInlinedBlock ();
|
|
if (inline_block)
|
|
{
|
|
const InlineFunctionInfo *inline_info = sc->block->GetInlinedFunctionInfo();
|
|
if (inline_info)
|
|
{
|
|
s.PutCString(" [inlined] ");
|
|
inline_info->GetName(sc->function->GetLanguage()).Dump(&s);
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::FunctionNameNoArgs:
|
|
{
|
|
ConstString name;
|
|
if (sc->function)
|
|
name = sc->function->GetNameNoArguments();
|
|
else if (sc->symbol)
|
|
name = sc->symbol->GetNameNoArguments();
|
|
if (name)
|
|
{
|
|
s.PutCString(name.GetCString());
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::FunctionNameWithArgs:
|
|
{
|
|
// Print the function name with arguments in it
|
|
if (sc->function)
|
|
{
|
|
ExecutionContextScope *exe_scope = exe_ctx ? exe_ctx->GetBestExecutionContextScope() : NULL;
|
|
const char *cstr = sc->function->GetName().AsCString (NULL);
|
|
if (cstr)
|
|
{
|
|
const InlineFunctionInfo *inline_info = NULL;
|
|
VariableListSP variable_list_sp;
|
|
bool get_function_vars = true;
|
|
if (sc->block)
|
|
{
|
|
Block *inline_block = sc->block->GetContainingInlinedBlock ();
|
|
|
|
if (inline_block)
|
|
{
|
|
get_function_vars = false;
|
|
inline_info = sc->block->GetInlinedFunctionInfo();
|
|
if (inline_info)
|
|
variable_list_sp = inline_block->GetBlockVariableList (true);
|
|
}
|
|
}
|
|
|
|
if (get_function_vars)
|
|
{
|
|
variable_list_sp = sc->function->GetBlock(true).GetBlockVariableList (true);
|
|
}
|
|
|
|
if (inline_info)
|
|
{
|
|
s.PutCString (cstr);
|
|
s.PutCString (" [inlined] ");
|
|
cstr = inline_info->GetName(sc->function->GetLanguage()).GetCString();
|
|
}
|
|
|
|
VariableList args;
|
|
if (variable_list_sp)
|
|
variable_list_sp->AppendVariablesWithScope(eValueTypeVariableArgument, args);
|
|
if (args.GetSize() > 0)
|
|
{
|
|
const char *open_paren = strchr (cstr, '(');
|
|
const char *close_paren = nullptr;
|
|
const char *generic = strchr(cstr, '<');
|
|
// if before the arguments list begins there is a template sign
|
|
// then scan to the end of the generic args before you try to find
|
|
// the arguments list
|
|
if (generic && open_paren && generic < open_paren)
|
|
{
|
|
int generic_depth = 1;
|
|
++generic;
|
|
for (;
|
|
*generic && generic_depth > 0;
|
|
generic++)
|
|
{
|
|
if (*generic == '<')
|
|
generic_depth++;
|
|
if (*generic == '>')
|
|
generic_depth--;
|
|
}
|
|
if (*generic)
|
|
open_paren = strchr(generic, '(');
|
|
else
|
|
open_paren = nullptr;
|
|
}
|
|
if (open_paren)
|
|
{
|
|
if (IsToken (open_paren, "(anonymous namespace)"))
|
|
{
|
|
open_paren = strchr (open_paren + strlen("(anonymous namespace)"), '(');
|
|
if (open_paren)
|
|
close_paren = strchr (open_paren, ')');
|
|
}
|
|
else
|
|
close_paren = strchr (open_paren, ')');
|
|
}
|
|
|
|
if (open_paren)
|
|
s.Write(cstr, open_paren - cstr + 1);
|
|
else
|
|
{
|
|
s.PutCString (cstr);
|
|
s.PutChar ('(');
|
|
}
|
|
const size_t num_args = args.GetSize();
|
|
for (size_t arg_idx = 0; arg_idx < num_args; ++arg_idx)
|
|
{
|
|
std::string buffer;
|
|
|
|
VariableSP var_sp (args.GetVariableAtIndex (arg_idx));
|
|
ValueObjectSP var_value_sp (ValueObjectVariable::Create (exe_scope, var_sp));
|
|
const char *var_representation = nullptr;
|
|
const char *var_name = var_value_sp->GetName().GetCString();
|
|
if (var_value_sp->GetClangType().IsAggregateType() &&
|
|
DataVisualization::ShouldPrintAsOneLiner(*var_value_sp.get()))
|
|
{
|
|
static StringSummaryFormat format(TypeSummaryImpl::Flags()
|
|
.SetHideItemNames(false)
|
|
.SetShowMembersOneLiner(true),
|
|
"");
|
|
format.FormatObject(var_value_sp.get(), buffer, TypeSummaryOptions());
|
|
var_representation = buffer.c_str();
|
|
}
|
|
else
|
|
var_representation = var_value_sp->GetValueAsCString();
|
|
if (arg_idx > 0)
|
|
s.PutCString (", ");
|
|
if (var_value_sp->GetError().Success())
|
|
{
|
|
if (var_representation)
|
|
s.Printf ("%s=%s", var_name, var_representation);
|
|
else
|
|
s.Printf ("%s=%s at %s", var_name, var_value_sp->GetTypeName().GetCString(), var_value_sp->GetLocationAsCString());
|
|
}
|
|
else
|
|
s.Printf ("%s=<unavailable>", var_name);
|
|
}
|
|
|
|
if (close_paren)
|
|
s.PutCString (close_paren);
|
|
else
|
|
s.PutChar(')');
|
|
|
|
}
|
|
else
|
|
{
|
|
s.PutCString(cstr);
|
|
}
|
|
return true;
|
|
}
|
|
}
|
|
else if (sc->symbol)
|
|
{
|
|
const char *cstr = sc->symbol->GetName().AsCString (NULL);
|
|
if (cstr)
|
|
{
|
|
s.PutCString(cstr);
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::FunctionAddrOffset:
|
|
if (addr)
|
|
{
|
|
if (DumpAddressOffsetFromFunction (s, sc, exe_ctx, *addr, false, false, false))
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::FunctionAddrOffsetConcrete:
|
|
if (addr)
|
|
{
|
|
if (DumpAddressOffsetFromFunction (s, sc, exe_ctx, *addr, true, true, true))
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::FunctionLineOffset:
|
|
if (DumpAddressOffsetFromFunction (s, sc, exe_ctx, sc->line_entry.range.GetBaseAddress(), false, false, false))
|
|
return true;
|
|
return false;
|
|
|
|
case Entry::Type::FunctionPCOffset:
|
|
if (exe_ctx)
|
|
{
|
|
StackFrame *frame = exe_ctx->GetFramePtr();
|
|
if (frame)
|
|
{
|
|
if (DumpAddressOffsetFromFunction (s, sc, exe_ctx, frame->GetFrameCodeAddress(), false, false, false))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::FunctionChanged:
|
|
return function_changed == true;
|
|
|
|
case Entry::Type::FunctionIsOptimized:
|
|
{
|
|
bool is_optimized = false;
|
|
if (sc->function && sc->function->GetIsOptimized())
|
|
{
|
|
is_optimized = true;
|
|
}
|
|
return is_optimized;
|
|
}
|
|
|
|
case Entry::Type::FunctionInitial:
|
|
return initial_function == true;
|
|
|
|
case Entry::Type::LineEntryFile:
|
|
if (sc && sc->line_entry.IsValid())
|
|
{
|
|
Module *module = sc->module_sp.get();
|
|
if (module)
|
|
{
|
|
if (DumpFile(s, sc->line_entry.file, (FileKind)entry.number))
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::LineEntryLineNumber:
|
|
if (sc && sc->line_entry.IsValid())
|
|
{
|
|
const char *format = "%" PRIu32;
|
|
if (!entry.printf_format.empty())
|
|
format = entry.printf_format.c_str();
|
|
s.Printf(format, sc->line_entry.line);
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::LineEntryStartAddress:
|
|
case Entry::Type::LineEntryEndAddress:
|
|
if (sc && sc->line_entry.range.GetBaseAddress().IsValid())
|
|
{
|
|
Address addr = sc->line_entry.range.GetBaseAddress();
|
|
|
|
if (entry.type == Entry::Type::LineEntryEndAddress)
|
|
addr.Slide(sc->line_entry.range.GetByteSize());
|
|
if (DumpAddress(s, sc, exe_ctx, addr, false))
|
|
return true;
|
|
}
|
|
return false;
|
|
|
|
case Entry::Type::CurrentPCArrow:
|
|
if (addr && exe_ctx && exe_ctx->GetFramePtr())
|
|
{
|
|
RegisterContextSP reg_ctx = exe_ctx->GetFramePtr()->GetRegisterContextSP();
|
|
if (reg_ctx.get())
|
|
{
|
|
addr_t pc_loadaddr = reg_ctx->GetPC();
|
|
if (pc_loadaddr != LLDB_INVALID_ADDRESS)
|
|
{
|
|
Address pc;
|
|
pc.SetLoadAddress (pc_loadaddr, exe_ctx->GetTargetPtr());
|
|
if (pc == *addr)
|
|
{
|
|
s.Printf ("-> ");
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
s.Printf(" ");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool
|
|
DumpCommaSeparatedChildEntryNames (Stream &s, const FormatEntity::Entry::Definition *parent)
|
|
{
|
|
if (parent->children)
|
|
{
|
|
const size_t n = parent->num_children;
|
|
for (size_t i=0; i<n; ++i)
|
|
{
|
|
if (i > 0)
|
|
s.PutCString(", ");
|
|
s.Printf ("\"%s\"", parent->children[i].name);
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
static Error
|
|
ParseEntry (const llvm::StringRef &format_str,
|
|
const FormatEntity::Entry::Definition *parent,
|
|
FormatEntity::Entry &entry)
|
|
{
|
|
Error error;
|
|
|
|
const size_t sep_pos = format_str.find_first_of(".[:");
|
|
const char sep_char = (sep_pos == llvm::StringRef::npos) ? '\0' : format_str[sep_pos];
|
|
llvm::StringRef key = format_str.substr(0, sep_pos);
|
|
|
|
const size_t n = parent->num_children;
|
|
for (size_t i=0; i<n; ++i)
|
|
{
|
|
const FormatEntity::Entry::Definition *entry_def = parent->children + i;
|
|
if (key.equals(entry_def->name) || entry_def->name[0] == '*')
|
|
{
|
|
llvm::StringRef value;
|
|
if (sep_char)
|
|
value = format_str.substr(sep_pos + (entry_def->keep_separator ? 0 : 1));
|
|
switch (entry_def->type)
|
|
{
|
|
case FormatEntity::Entry::Type::ParentString:
|
|
entry.string = format_str.str();
|
|
return error; // Success
|
|
|
|
case FormatEntity::Entry::Type::ParentNumber:
|
|
entry.number = entry_def->data;
|
|
return error; // Success
|
|
|
|
case FormatEntity::Entry::Type::InsertString:
|
|
entry.type = entry_def->type;
|
|
entry.string = entry_def->string;
|
|
return error; // Success
|
|
|
|
default:
|
|
entry.type = entry_def->type;
|
|
break;
|
|
}
|
|
|
|
if (value.empty())
|
|
{
|
|
if (entry_def->type == FormatEntity::Entry::Type::Invalid)
|
|
{
|
|
if (entry_def->children)
|
|
{
|
|
StreamString error_strm;
|
|
error_strm.Printf("'%s' can't be specified on its own, you must access one of its children: ", entry_def->name);
|
|
DumpCommaSeparatedChildEntryNames (error_strm, entry_def);
|
|
error.SetErrorStringWithFormat("%s", error_strm.GetString().c_str());
|
|
}
|
|
else if (sep_char == ':')
|
|
{
|
|
// Any value whose separator is a with a ':' means this value has a string argument
|
|
// that needs to be stored in the entry (like "${script.var:}").
|
|
// In this case the string value is the empty string which is ok.
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorStringWithFormat("%s", "invalid entry definitions");
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (entry_def->children)
|
|
{
|
|
error = ParseEntry (value, entry_def, entry);
|
|
}
|
|
else if (sep_char == ':')
|
|
{
|
|
// Any value whose separator is a with a ':' means this value has a string argument
|
|
// that needs to be stored in the entry (like "${script.var:modulename.function}")
|
|
entry.string = value.str();
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorStringWithFormat("'%s' followed by '%s' but it has no children",
|
|
key.str().c_str(),
|
|
value.str().c_str());
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
}
|
|
StreamString error_strm;
|
|
if (parent->type == FormatEntity::Entry::Type::Root)
|
|
error_strm.Printf("invalid top level item '%s'. Valid top level items are: ", key.str().c_str());
|
|
else
|
|
error_strm.Printf("invalid member '%s' in '%s'. Valid members are: ", key.str().c_str(), parent->name);
|
|
DumpCommaSeparatedChildEntryNames (error_strm, parent);
|
|
error.SetErrorStringWithFormat("%s", error_strm.GetString().c_str());
|
|
return error;
|
|
}
|
|
|
|
|
|
static const FormatEntity::Entry::Definition *
|
|
FindEntry (const llvm::StringRef &format_str, const FormatEntity::Entry::Definition *parent, llvm::StringRef &remainder)
|
|
{
|
|
Error error;
|
|
|
|
std::pair<llvm::StringRef, llvm::StringRef> p = format_str.split('.');
|
|
const size_t n = parent->num_children;
|
|
for (size_t i=0; i<n; ++i)
|
|
{
|
|
const FormatEntity::Entry::Definition *entry_def = parent->children + i;
|
|
if (p.first.equals(entry_def->name) || entry_def->name[0] == '*')
|
|
{
|
|
if (p.second.empty())
|
|
{
|
|
if (format_str.back() == '.')
|
|
remainder = format_str.drop_front(format_str.size() - 1);
|
|
else
|
|
remainder = llvm::StringRef(); // Exact match
|
|
return entry_def;
|
|
}
|
|
else
|
|
{
|
|
if (entry_def->children)
|
|
{
|
|
return FindEntry (p.second, entry_def, remainder);
|
|
}
|
|
else
|
|
{
|
|
remainder = p.second;
|
|
return entry_def;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
remainder = format_str;
|
|
return parent;
|
|
}
|
|
|
|
Error
|
|
FormatEntity::ParseInternal (llvm::StringRef &format, Entry &parent_entry, uint32_t depth)
|
|
{
|
|
Error error;
|
|
while (!format.empty() && error.Success())
|
|
{
|
|
const size_t non_special_chars = format.find_first_of("${}\\");
|
|
|
|
if (non_special_chars == llvm::StringRef::npos)
|
|
{
|
|
// No special characters, just string bytes so add them and we are done
|
|
parent_entry.AppendText(format);
|
|
return error;
|
|
}
|
|
|
|
if (non_special_chars > 0)
|
|
{
|
|
// We have a special character, so add all characters before these as a plain string
|
|
parent_entry.AppendText(format.substr(0,non_special_chars));
|
|
format = format.drop_front(non_special_chars);
|
|
}
|
|
|
|
switch (format[0])
|
|
{
|
|
case '\0':
|
|
return error;
|
|
|
|
case '{':
|
|
{
|
|
format = format.drop_front(); // Skip the '{'
|
|
Entry scope_entry(Entry::Type::Scope);
|
|
error = FormatEntity::ParseInternal (format, scope_entry, depth+1);
|
|
if (error.Fail())
|
|
return error;
|
|
parent_entry.AppendEntry(std::move(scope_entry));
|
|
}
|
|
break;
|
|
|
|
case '}':
|
|
if (depth == 0)
|
|
error.SetErrorString("unmatched '}' character");
|
|
else
|
|
format = format.drop_front(); // Skip the '}' as we are at the end of the scope
|
|
return error;
|
|
|
|
case '\\':
|
|
{
|
|
format = format.drop_front(); // Skip the '\' character
|
|
if (format.empty())
|
|
{
|
|
error.SetErrorString("'\\' character was not followed by another character");
|
|
return error;
|
|
}
|
|
|
|
const char desens_char = format[0];
|
|
format = format.drop_front(); // Skip the desensitized char character
|
|
switch (desens_char)
|
|
{
|
|
case 'a': parent_entry.AppendChar('\a'); break;
|
|
case 'b': parent_entry.AppendChar('\b'); break;
|
|
case 'f': parent_entry.AppendChar('\f'); break;
|
|
case 'n': parent_entry.AppendChar('\n'); break;
|
|
case 'r': parent_entry.AppendChar('\r'); break;
|
|
case 't': parent_entry.AppendChar('\t'); break;
|
|
case 'v': parent_entry.AppendChar('\v'); break;
|
|
case '\'': parent_entry.AppendChar('\''); break;
|
|
case '\\': parent_entry.AppendChar('\\'); break;
|
|
case '0':
|
|
// 1 to 3 octal chars
|
|
{
|
|
// Make a string that can hold onto the initial zero char,
|
|
// up to 3 octal digits, and a terminating NULL.
|
|
char oct_str[5] = { 0, 0, 0, 0, 0 };
|
|
|
|
int i;
|
|
for (i=0; (format[i] >= '0' && format[i] <= '7') && i<4; ++i)
|
|
oct_str[i] = format[i];
|
|
|
|
// We don't want to consume the last octal character since
|
|
// the main for loop will do this for us, so we advance p by
|
|
// one less than i (even if i is zero)
|
|
format = format.drop_front(i);
|
|
unsigned long octal_value = ::strtoul (oct_str, NULL, 8);
|
|
if (octal_value <= UINT8_MAX)
|
|
{
|
|
parent_entry.AppendChar((char)octal_value);
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorString("octal number is larger than a single byte");
|
|
return error;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 'x':
|
|
// hex number in the format
|
|
if (isxdigit(format[0]))
|
|
{
|
|
// Make a string that can hold onto two hex chars plus a
|
|
// NULL terminator
|
|
char hex_str[3] = { 0,0,0 };
|
|
hex_str[0] = format[0];
|
|
|
|
format = format.drop_front();
|
|
|
|
if (isxdigit(format[0]))
|
|
{
|
|
hex_str[1] = format[0];
|
|
format = format.drop_front();
|
|
}
|
|
|
|
unsigned long hex_value = strtoul (hex_str, NULL, 16);
|
|
if (hex_value <= UINT8_MAX)
|
|
{
|
|
parent_entry.AppendChar((char)hex_value);
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorString("hex number is larger than a single byte");
|
|
return error;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
parent_entry.AppendChar(desens_char);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
// Just desensitize any other character by just printing what
|
|
// came after the '\'
|
|
parent_entry.AppendChar(desens_char);
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case '$':
|
|
if (format.size() == 1)
|
|
{
|
|
// '$' at the end of a format string, just print the '$'
|
|
parent_entry.AppendText("$");
|
|
}
|
|
else
|
|
{
|
|
format = format.drop_front(); // Skip the '$'
|
|
|
|
if (format[0] == '{')
|
|
{
|
|
format = format.drop_front(); // Skip the '{'
|
|
|
|
llvm::StringRef variable, variable_format;
|
|
error = FormatEntity::ExtractVariableInfo (format, variable, variable_format);
|
|
if (error.Fail())
|
|
return error;
|
|
bool verify_is_thread_id = false;
|
|
Entry entry;
|
|
if (!variable_format.empty())
|
|
{
|
|
entry.printf_format = variable_format.str();
|
|
|
|
// If the format contains a '%' we are going to assume this is
|
|
// a printf style format. So if you want to format your thread ID
|
|
// using "0x%llx" you can use:
|
|
// ${thread.id%0x%llx}
|
|
//
|
|
// If there is no '%' in the format, then it is assumed to be a
|
|
// LLDB format name, or one of the extended formats specified in
|
|
// the switch statement below.
|
|
|
|
if (entry.printf_format.find('%') == std::string::npos)
|
|
{
|
|
bool clear_printf = false;
|
|
|
|
if (FormatManager::GetFormatFromCString(entry.printf_format.c_str(),
|
|
false,
|
|
entry.fmt))
|
|
{
|
|
// We have an LLDB format, so clear the printf format
|
|
clear_printf = true;
|
|
}
|
|
else if (entry.printf_format.size() == 1)
|
|
{
|
|
switch (entry.printf_format[0])
|
|
{
|
|
case '@': // if this is an @ sign, print ObjC description
|
|
entry.number = ValueObject::eValueObjectRepresentationStyleLanguageSpecific;
|
|
clear_printf = true;
|
|
break;
|
|
case 'V': // if this is a V, print the value using the default format
|
|
entry.number = ValueObject::eValueObjectRepresentationStyleValue;
|
|
clear_printf = true;
|
|
break;
|
|
case 'L': // if this is an L, print the location of the value
|
|
entry.number = ValueObject::eValueObjectRepresentationStyleLocation;
|
|
clear_printf = true;
|
|
break;
|
|
case 'S': // if this is an S, print the summary after all
|
|
entry.number = ValueObject::eValueObjectRepresentationStyleSummary;
|
|
clear_printf = true;
|
|
break;
|
|
case '#': // if this is a '#', print the number of children
|
|
entry.number = ValueObject::eValueObjectRepresentationStyleChildrenCount;
|
|
clear_printf = true;
|
|
break;
|
|
case 'T': // if this is a 'T', print the type
|
|
entry.number = ValueObject::eValueObjectRepresentationStyleType;
|
|
clear_printf = true;
|
|
break;
|
|
case 'N': // if this is a 'N', print the name
|
|
entry.number = ValueObject::eValueObjectRepresentationStyleName;
|
|
clear_printf = true;
|
|
break;
|
|
case '>': // if this is a '>', print the expression path
|
|
entry.number = ValueObject::eValueObjectRepresentationStyleExpressionPath;
|
|
clear_printf = true;
|
|
break;
|
|
default:
|
|
error.SetErrorStringWithFormat("invalid format: '%s'", entry.printf_format.c_str());
|
|
return error;
|
|
}
|
|
}
|
|
else if (FormatManager::GetFormatFromCString(entry.printf_format.c_str(),
|
|
true,
|
|
entry.fmt))
|
|
{
|
|
clear_printf = true;
|
|
}
|
|
else if (entry.printf_format == "tid")
|
|
{
|
|
verify_is_thread_id = true;
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorStringWithFormat("invalid format: '%s'", entry.printf_format.c_str());
|
|
return error;
|
|
}
|
|
|
|
// Our format string turned out to not be a printf style format
|
|
// so lets clear the string
|
|
if (clear_printf)
|
|
entry.printf_format.clear();
|
|
}
|
|
}
|
|
|
|
// Check for dereferences
|
|
if (variable[0] == '*')
|
|
{
|
|
entry.deref = true;
|
|
variable = variable.drop_front();
|
|
}
|
|
|
|
error = ParseEntry (variable, &g_root, entry);
|
|
if (error.Fail())
|
|
return error;
|
|
|
|
if (verify_is_thread_id)
|
|
{
|
|
if (entry.type != Entry::Type::ThreadID &&
|
|
entry.type != Entry::Type::ThreadProtocolID)
|
|
{
|
|
error.SetErrorString("the 'tid' format can only be used on ${thread.id} and ${thread.protocol_id}");
|
|
}
|
|
}
|
|
|
|
switch (entry.type)
|
|
{
|
|
case Entry::Type::Variable:
|
|
case Entry::Type::VariableSynthetic:
|
|
if (entry.number == 0)
|
|
{
|
|
if (entry.string.empty())
|
|
entry.number = ValueObject::eValueObjectRepresentationStyleValue;
|
|
else
|
|
entry.number = ValueObject::eValueObjectRepresentationStyleSummary;
|
|
}
|
|
break;
|
|
default:
|
|
// Make sure someone didn't try to dereference anything but ${var} or ${svar}
|
|
if (entry.deref)
|
|
{
|
|
error.SetErrorStringWithFormat("${%s} can't be dereferenced, only ${var} and ${svar} can.", variable.str().c_str());
|
|
return error;
|
|
}
|
|
}
|
|
// Check if this entry just wants to insert a constant string
|
|
// value into the parent_entry, if so, insert the string with
|
|
// AppendText, else append the entry to the parent_entry.
|
|
if (entry.type == Entry::Type::InsertString)
|
|
parent_entry.AppendText(entry.string.c_str());
|
|
else
|
|
parent_entry.AppendEntry(std::move(entry));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return error;
|
|
}
|
|
|
|
|
|
Error
|
|
FormatEntity::ExtractVariableInfo (llvm::StringRef &format_str, llvm::StringRef &variable_name, llvm::StringRef &variable_format)
|
|
{
|
|
Error error;
|
|
variable_name = llvm::StringRef();
|
|
variable_format = llvm::StringRef();
|
|
|
|
const size_t paren_pos = format_str.find_first_of('}');
|
|
if (paren_pos != llvm::StringRef::npos)
|
|
{
|
|
const size_t percent_pos = format_str.find_first_of('%');
|
|
if (percent_pos < paren_pos)
|
|
{
|
|
if (percent_pos > 0)
|
|
{
|
|
if (percent_pos > 1)
|
|
variable_name = format_str.substr(0, percent_pos);
|
|
variable_format = format_str.substr(percent_pos + 1, paren_pos - (percent_pos + 1));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
variable_name = format_str.substr(0, paren_pos);
|
|
}
|
|
// Strip off elements and the formatting and the trailing '}'
|
|
format_str = format_str.substr(paren_pos + 1);
|
|
}
|
|
else
|
|
{
|
|
error.SetErrorStringWithFormat("missing terminating '}' character for '${%s'", format_str.str().c_str());
|
|
}
|
|
return error;
|
|
}
|
|
|
|
bool
|
|
FormatEntity::FormatFileSpec (const FileSpec &file_spec, Stream &s, llvm::StringRef variable_name, llvm::StringRef variable_format)
|
|
{
|
|
if (variable_name.empty() || variable_name.equals(".fullpath"))
|
|
{
|
|
file_spec.Dump(&s);
|
|
return true;
|
|
}
|
|
else if (variable_name.equals(".basename"))
|
|
{
|
|
s.PutCString(file_spec.GetFilename().AsCString(""));
|
|
return true;
|
|
}
|
|
else if (variable_name.equals(".dirname"))
|
|
{
|
|
s.PutCString(file_spec.GetFilename().AsCString(""));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static std::string
|
|
MakeMatch (const llvm::StringRef &prefix, const char *suffix)
|
|
{
|
|
std::string match(prefix.str());
|
|
match.append(suffix);
|
|
return match;
|
|
}
|
|
|
|
static void
|
|
AddMatches (const FormatEntity::Entry::Definition *def,
|
|
const llvm::StringRef &prefix,
|
|
const llvm::StringRef &match_prefix,
|
|
StringList &matches)
|
|
{
|
|
const size_t n = def->num_children;
|
|
if (n > 0)
|
|
{
|
|
for (size_t i=0; i<n; ++i)
|
|
{
|
|
std::string match = prefix.str();
|
|
if (match_prefix.empty())
|
|
matches.AppendString(MakeMatch (prefix, def->children[i].name));
|
|
else if (strncmp(def->children[i].name, match_prefix.data(), match_prefix.size()) == 0)
|
|
matches.AppendString(MakeMatch (prefix, def->children[i].name + match_prefix.size()));
|
|
}
|
|
}
|
|
}
|
|
size_t
|
|
FormatEntity::AutoComplete (const char *s,
|
|
int match_start_point,
|
|
int max_return_elements,
|
|
bool &word_complete,
|
|
StringList &matches)
|
|
{
|
|
word_complete = false;
|
|
llvm::StringRef str(s + match_start_point);
|
|
matches.Clear();
|
|
|
|
const size_t dollar_pos = str.rfind('$');
|
|
if (dollar_pos != llvm::StringRef::npos)
|
|
{
|
|
// Hitting TAB after $ at the end of the string add a "{"
|
|
if (dollar_pos == str.size() - 1)
|
|
{
|
|
std::string match = str.str();
|
|
match.append("{");
|
|
matches.AppendString(std::move(match));
|
|
}
|
|
else if (str[dollar_pos + 1] == '{')
|
|
{
|
|
const size_t close_pos = str.find('}', dollar_pos + 2);
|
|
if (close_pos == llvm::StringRef::npos)
|
|
{
|
|
const size_t format_pos = str.find('%', dollar_pos + 2);
|
|
if (format_pos == llvm::StringRef::npos)
|
|
{
|
|
llvm::StringRef partial_variable (str.substr(dollar_pos + 2));
|
|
if (partial_variable.empty())
|
|
{
|
|
// Suggest all top level entites as we are just past "${"
|
|
AddMatches(&g_root, str, llvm::StringRef(), matches);
|
|
}
|
|
else
|
|
{
|
|
// We have a partially specified variable, find it
|
|
llvm::StringRef remainder;
|
|
const FormatEntity::Entry::Definition* entry_def = FindEntry (partial_variable, &g_root, remainder);
|
|
if (entry_def)
|
|
{
|
|
const size_t n = entry_def->num_children;
|
|
|
|
if (remainder.empty())
|
|
{
|
|
// Exact match
|
|
if (n > 0)
|
|
{
|
|
// "${thread.info" <TAB>
|
|
matches.AppendString(MakeMatch(str, "."));
|
|
}
|
|
else
|
|
{
|
|
// "${thread.id" <TAB>
|
|
matches.AppendString(MakeMatch (str, "}"));
|
|
word_complete = true;
|
|
}
|
|
}
|
|
else if (remainder.equals("."))
|
|
{
|
|
// "${thread." <TAB>
|
|
AddMatches(entry_def, str, llvm::StringRef(), matches);
|
|
}
|
|
else
|
|
{
|
|
// We have a partial match
|
|
// "${thre" <TAB>
|
|
AddMatches(entry_def, str, remainder, matches);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return matches.GetSize();
|
|
}
|