llvm-project/lldb/source/Core/FormatEntity.cpp

2512 lines
97 KiB
C++
Raw Normal View History

Get rid of Debugger::FormatPrompt() and replace it with the new FormatEntity class. Why? Debugger::FormatPrompt() would run through the format prompt every time and parse it and emit it piece by piece. It also did formatting differently depending on which key/value pair it was parsing. The new code improves on this with the following features: 1 - Allow format strings to be parsed into a FormatEntity::Entry which can contain multiple child FormatEntity::Entry objects. This FormatEntity::Entry is a parsed version of what was previously always done in Debugger::FormatPrompt() so it is more efficient to emit formatted strings using the new parsed FormatEntity::Entry. 2 - Allows errors in format strings to be shown immediately when setting the settings (frame-format, thread-format, disassembly-format 3 - Allows auto completion by implementing a new OptionValueFormatEntity and switching frame-format, thread-format, and disassembly-format settings over to using it. 4 - The FormatEntity::Entry for each of the frame-format, thread-format, disassembly-format settings only replaces the old one if the format parses correctly 5 - Combines all consecutive string values together for efficient output. This means all "${ansi.*}" keys and all desensitized characters like "\n" "\t" "\0721" "\x23" will get combined with their previous strings 6 - ${*.script:} (like "${var.script:mymodule.my_var_function}") have all been switched over to use ${script.*:} "${script.var:mymodule.my_var_function}") to make the format easier to parse as I don't believe anyone was using these format string power user features. 7 - All key values pairs are defined in simple C arrays of entries so it is much easier to add new entries. These changes pave the way for subsequent modifications where we can modify formats to do more (like control the width of value strings can do more and add more functionality more easily like string formatting to control the width, printf formats and more). llvm-svn: 228207
2015-02-05 06:00:53 +08:00
//===-- 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/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/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)
};
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_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(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(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)
{
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->GetAddress();
}
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)
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)
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().DoAllowSyntheticChildren();
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");
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));
StreamString special_directions_stream;
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")
{
bool handled = false;
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))
{
handled = true;
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::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().Dump(&s);
}
}
}
return true;
}
}
return false;
case Entry::Type::FunctionNameNoArgs:
{
ConstString name;
if (sc->function)
name = sc->function->GetMangled().GetName (Mangled::ePreferDemangledWithoutArguments);
else if (sc->symbol)
name = sc->symbol->GetMangled().GetName (Mangled::ePreferDemangledWithoutArguments);
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().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))
return true;
}
return false;
case Entry::Type::FunctionAddrOffsetConcrete:
if (addr)
{
if (DumpAddressOffsetFromFunction (s, sc, exe_ctx, *addr, true, true))
return true;
}
return false;
case Entry::Type::FunctionLineOffset:
if (DumpAddressOffsetFromFunction (s, sc, exe_ctx, sc->line_entry.range.GetBaseAddress(), 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))
return true;
}
}
return false;
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 = std::move(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 = std::move(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 = std::move(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
Get rid of Debugger::FormatPrompt() and replace it with the new FormatEntity class. Why? Debugger::FormatPrompt() would run through the format prompt every time and parse it and emit it piece by piece. It also did formatting differently depending on which key/value pair it was parsing. The new code improves on this with the following features: 1 - Allow format strings to be parsed into a FormatEntity::Entry which can contain multiple child FormatEntity::Entry objects. This FormatEntity::Entry is a parsed version of what was previously always done in Debugger::FormatPrompt() so it is more efficient to emit formatted strings using the new parsed FormatEntity::Entry. 2 - Allows errors in format strings to be shown immediately when setting the settings (frame-format, thread-format, disassembly-format 3 - Allows auto completion by implementing a new OptionValueFormatEntity and switching frame-format, thread-format, and disassembly-format settings over to using it. 4 - The FormatEntity::Entry for each of the frame-format, thread-format, disassembly-format settings only replaces the old one if the format parses correctly 5 - Combines all consecutive string values together for efficient output. This means all "${ansi.*}" keys and all desensitized characters like "\n" "\t" "\0721" "\x23" will get combined with their previous strings 6 - ${*.script:} (like "${var.script:mymodule.my_var_function}") have all been switched over to use ${script.*:} "${script.var:mymodule.my_var_function}") to make the format easier to parse as I don't believe anyone was using these format string power user features. 7 - All key values pairs are defined in simple C arrays of entries so it is much easier to add new entries. These changes pave the way for subsequent modifications where we can modify formats to do more (like control the width of value strings can do more and add more functionality more easily like string formatting to control the width, printf formats and more). llvm-svn: 228207
2015-02-05 06:00:53 +08:00
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 std::move(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 = std::move(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 = std::move(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(std::move(MakeMatch (str, ".")));
}
else
{
// "${thread.id" <TAB>
matches.AppendString(std::move(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();
}