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

2428 lines
83 KiB
C++

//===-- FormatEntity.cpp ----------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#include "lldb/Core/FormatEntity.h"
#include "lldb/Core/Address.h"
#include "lldb/Core/AddressRange.h"
#include "lldb/Core/Debugger.h"
#include "lldb/Core/DumpRegisterValue.h"
#include "lldb/Core/Module.h"
#include "lldb/Core/ValueObject.h"
#include "lldb/Core/ValueObjectVariable.h"
#include "lldb/DataFormatters/DataVisualization.h"
#include "lldb/DataFormatters/FormatClasses.h"
#include "lldb/DataFormatters/FormatManager.h"
#include "lldb/DataFormatters/TypeSummary.h"
#include "lldb/Expression/ExpressionVariable.h"
#include "lldb/Interpreter/CommandInterpreter.h"
#include "lldb/Symbol/Block.h"
#include "lldb/Symbol/CompileUnit.h"
#include "lldb/Symbol/CompilerType.h"
#include "lldb/Symbol/Function.h"
#include "lldb/Symbol/LineEntry.h"
#include "lldb/Symbol/Symbol.h"
#include "lldb/Symbol/SymbolContext.h"
#include "lldb/Symbol/VariableList.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/ExecutionContextScope.h"
#include "lldb/Target/Language.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"
#include "lldb/Utility/ArchSpec.h"
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/Log.h"
#include "lldb/Utility/Logging.h"
#include "lldb/Utility/RegisterValue.h"
#include "lldb/Utility/SharingPtr.h"
#include "lldb/Utility/Stream.h"
#include "lldb/Utility/StreamString.h"
#include "lldb/Utility/StringList.h"
#include "lldb/Utility/StructuredData.h"
#include "lldb/lldb-defines.h"
#include "lldb/lldb-forward.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/ADT/Triple.h"
#include "llvm/Support/Compiler.h"
#include <ctype.h>
#include <inttypes.h>
#include <memory>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <type_traits>
#include <utility>
namespace lldb_private {
class ScriptInterpreter;
}
namespace lldb_private {
struct RegisterInfo;
}
using namespace lldb;
using namespace lldb_private;
enum FileKind { FileError = 0, Basename, Dirname, Fullpath };
#define ENTRY(n, t) \
{ n, nullptr, FormatEntity::Entry::Type::t, 0, 0, nullptr, false }
#define ENTRY_VALUE(n, t, v) \
{ n, nullptr, FormatEntity::Entry::Type::t, v, 0, nullptr, false }
#define ENTRY_CHILDREN(n, t, c) \
{ \
n, nullptr, FormatEntity::Entry::Type::t, 0, \
static_cast<uint32_t>(llvm::array_lengthof(c)), c, false \
}
#define ENTRY_CHILDREN_KEEP_SEP(n, t, c) \
{ \
n, nullptr, FormatEntity::Entry::Type::t, 0, \
static_cast<uint32_t>(llvm::array_lengthof(c)), c, true \
}
#define ENTRY_STRING(n, s) \
{ n, s, FormatEntity::Entry::Type::EscapeCode, 0, 0, nullptr, false }
static FormatEntity::Entry::Definition g_string_entry[] = {
ENTRY("*", ParentString)};
static FormatEntity::Entry::Definition g_addr_entries[] = {
ENTRY("load", AddressLoad),
ENTRY("file", AddressFile),
ENTRY("load", AddressLoadOrFile),
};
static FormatEntity::Entry::Definition g_file_child_entries[] = {
ENTRY_VALUE("basename", ParentNumber, FileKind::Basename),
ENTRY_VALUE("dirname", ParentNumber, FileKind::Dirname),
ENTRY_VALUE("fullpath", ParentNumber, FileKind::Fullpath)};
static FormatEntity::Entry::Definition g_frame_child_entries[] = {
ENTRY("index", FrameIndex),
ENTRY("pc", FrameRegisterPC),
ENTRY("fp", FrameRegisterFP),
ENTRY("sp", FrameRegisterSP),
ENTRY("flags", FrameRegisterFlags),
ENTRY("no-debug", FrameNoDebug),
ENTRY_CHILDREN("reg", FrameRegisterByName, g_string_entry),
ENTRY("is-artificial", FrameIsArtificial),
};
static FormatEntity::Entry::Definition g_function_child_entries[] = {
ENTRY("id", FunctionID),
ENTRY("name", FunctionName),
ENTRY("name-without-args", FunctionNameNoArgs),
ENTRY("name-with-args", FunctionNameWithArgs),
ENTRY("addr-offset", FunctionAddrOffset),
ENTRY("concrete-only-addr-offset-no-padding", FunctionAddrOffsetConcrete),
ENTRY("line-offset", FunctionLineOffset),
ENTRY("pc-offset", FunctionPCOffset),
ENTRY("initial-function", FunctionInitial),
ENTRY("changed", FunctionChanged),
ENTRY("is-optimized", FunctionIsOptimized)};
static FormatEntity::Entry::Definition g_line_child_entries[] = {
ENTRY_CHILDREN("file", LineEntryFile, g_file_child_entries),
ENTRY("number", LineEntryLineNumber),
ENTRY("column", LineEntryColumn),
ENTRY("start-addr", LineEntryStartAddress),
ENTRY("end-addr", LineEntryEndAddress),
};
static FormatEntity::Entry::Definition g_module_child_entries[] = {
ENTRY_CHILDREN("file", ModuleFile, g_file_child_entries),
};
static FormatEntity::Entry::Definition g_process_child_entries[] = {
ENTRY("id", ProcessID),
ENTRY_VALUE("name", ProcessFile, FileKind::Basename),
ENTRY_CHILDREN("file", ProcessFile, g_file_child_entries),
};
static FormatEntity::Entry::Definition g_svar_child_entries[] = {
ENTRY("*", ParentString)};
static FormatEntity::Entry::Definition g_var_child_entries[] = {
ENTRY("*", ParentString)};
static FormatEntity::Entry::Definition g_thread_child_entries[] = {
ENTRY("id", ThreadID),
ENTRY("protocol_id", ThreadProtocolID),
ENTRY("index", ThreadIndexID),
ENTRY_CHILDREN("info", ThreadInfo, g_string_entry),
ENTRY("queue", ThreadQueue),
ENTRY("name", ThreadName),
ENTRY("stop-reason", ThreadStopReason),
ENTRY("return-value", ThreadReturnValue),
ENTRY("completed-expression", ThreadCompletedExpression),
};
static FormatEntity::Entry::Definition g_target_child_entries[] = {
ENTRY("arch", TargetArch),
};
#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, g_ansi_fg_entries),
ENTRY_CHILDREN("bg", Invalid, 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), ENTRY("process", ScriptProcess),
ENTRY("target", ScriptTarget), ENTRY("thread", ScriptThread),
ENTRY("var", ScriptVariable), ENTRY("svar", ScriptVariableSynthetic),
ENTRY("thread", ScriptThread),
};
static FormatEntity::Entry::Definition g_top_level_entries[] = {
ENTRY_CHILDREN("addr", AddressLoadOrFile, g_addr_entries),
ENTRY("addr-file-or-load", AddressLoadOrFile),
ENTRY_CHILDREN("ansi", Invalid, g_ansi_entries),
ENTRY("current-pc-arrow", CurrentPCArrow),
ENTRY_CHILDREN("file", File, g_file_child_entries),
ENTRY("language", Lang),
ENTRY_CHILDREN("frame", Invalid, g_frame_child_entries),
ENTRY_CHILDREN("function", Invalid, g_function_child_entries),
ENTRY_CHILDREN("line", Invalid, g_line_child_entries),
ENTRY_CHILDREN("module", Invalid, g_module_child_entries),
ENTRY_CHILDREN("process", Invalid, g_process_child_entries),
ENTRY_CHILDREN("script", Invalid, g_script_child_entries),
ENTRY_CHILDREN_KEEP_SEP("svar", VariableSynthetic, g_svar_child_entries),
ENTRY_CHILDREN("thread", Invalid, g_thread_child_entries),
ENTRY_CHILDREN("target", Invalid, g_target_child_entries),
ENTRY_CHILDREN_KEEP_SEP("var", Variable, g_var_child_entries),
};
static FormatEntity::Entry::Definition g_root =
ENTRY_CHILDREN("<root>", Root, g_top_level_entries);
FormatEntity::Entry::Entry(llvm::StringRef s)
: string(s.data(), s.size()), printf_format(), children(),
definition(nullptr), type(Type::String), fmt(lldb::eFormatDefault),
number(0), deref(false) {}
FormatEntity::Entry::Entry(char ch)
: string(1, ch), printf_format(), children(), definition(nullptr),
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));
}
Status 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(EscapeCode);
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(FrameNoDebug);
ENUM_TO_CSTR(FrameRegisterPC);
ENUM_TO_CSTR(FrameRegisterSP);
ENUM_TO_CSTR(FrameRegisterFP);
ENUM_TO_CSTR(FrameRegisterFlags);
ENUM_TO_CSTR(FrameRegisterByName);
ENUM_TO_CSTR(FrameIsArtificial);
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(LineEntryColumn);
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().GetScriptInterpreter();
if (script_interpreter) {
Status 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 = nullptr;
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_DATAFORMATTERS));
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, nullptr, 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, nullptr, 0);
index_higher = ::strtoul(index_higher_cstr, nullptr, 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)) {
DumpRegisterValue(reg_value, &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_DATAFORMATTERS));
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());
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(), &reason_to_stop, &final_value_type, options,
&what_next);
if (!item) {
if (log)
log->Printf("[ExpandIndexedExpression] ERROR: why stopping = %d,"
" final_value_type %d",
reason_to_stop, final_value_type);
} else {
if (log)
log->Printf("[ExpandIndexedExpression] ALL RIGHT: why stopping = %d,"
" final_value_type %d",
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 == nullptr)
return false;
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_DATAFORMATTERS));
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;
LLVM_FALLTHROUGH;
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 == nullptr)
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 = nullptr;
const char *var_name_final_if_array_range = nullptr;
size_t close_bracket_index = llvm::StringRef::npos;
int64_t index_lower = -1;
int64_t index_higher = -1;
bool is_array_range = false;
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);
Status 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(), &reason_to_stop,
&final_value_type, options, &what_next)
.get();
if (!target) {
if (log)
log->Printf("[Debugger::FormatPrompt] ERROR: why stopping = %d,"
" final_value_type %d",
reason_to_stop, final_value_type);
return false;
} else {
if (log)
log->Printf("[Debugger::FormatPrompt] ALL RIGHT: why stopping = %d,"
" final_value_type %d",
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
Status 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());
auto type_sp = std::make_shared<TypeNameSpecifierImpl>(
bitfield_name.GetString(), 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->GetCompilerType().GetTypeInfo(nullptr);
bool is_array = (type_info_flags & eTypeIsArray) != 0;
bool is_pointer = (type_info_flags & eTypeIsPointer) != 0;
bool is_aggregate = target->GetCompilerType().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.GetString();
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::PrintableRepresentationSpecialCases::eDisable);
}
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, nullptr, 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)) {
DumpRegisterValue(reg_value, &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) {
if (value->GetType() == eStructuredDataTypeInteger) {
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() == eStructuredDataTypeFloat) {
s.Printf("%f", value->GetAsFloat()->GetValue());
return true;
} else if (value->GetType() == eStructuredDataTypeString) {
s.Format("{0}", value->GetAsString()->GetValue());
return true;
} else if (value->GetType() == eStructuredDataTypeArray) {
if (value->GetAsArray()->GetSize() > 0) {
s.Printf("%zu", value->GetAsArray()->GetSize());
return true;
}
} else if (value->GetType() == eStructuredDataTypeDictionary) {
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;
Status 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);
Status 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
return false;
case Entry::Type::EscapeCode:
if (exe_ctx) {
if (Target *target = exe_ctx->GetTargetPtr()) {
Debugger &debugger = target->GetDebugger();
if (debugger.GetUseColor()) {
s.PutCString(entry.string);
}
}
}
// Always return true, so colors being disabled is transparent.
return true;
case Entry::Type::Root:
for (const auto &child : entry.children) {
if (!Format(child, s, sc, exe_ctx, addr, valobj, function_changed,
initial_function)) {
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);
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:
return DumpValue(s, sc, exe_ctx, entry, valobj);
case Entry::Type::AddressFile:
case Entry::Type::AddressLoad:
case Entry::Type::AddressLoadOrFile:
return (addr != nullptr && addr->IsValid() &&
DumpAddress(s, sc, exe_ctx, *addr,
entry.type == Entry::Type::AddressLoadOrFile));
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") {
// TODO(zturner): Rather than hardcoding this to be platform
// specific, it should be controlled by a setting and the default
// value of the setting can be different depending on the platform.
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) ||
(ostype == llvm::Triple::NetBSD)) {
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()) {
ExpressionVariableSP 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() == eStructuredDataTypeDictionary) {
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) {
const char *lang_name =
Language::GetNameForLanguageType(cu->GetLanguage());
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::FrameNoDebug:
if (exe_ctx) {
StackFrame *frame = exe_ctx->GetFramePtr();
if (frame) {
return !frame->HasDebugInformation();
}
}
return true;
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::FrameIsArtificial: {
if (exe_ctx)
if (StackFrame *frame = exe_ctx->GetFramePtr())
return frame->IsArtificial();
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: {
Language *language_plugin = nullptr;
bool language_plugin_handled = false;
StreamString ss;
if (sc->function)
language_plugin = Language::FindPlugin(sc->function->GetLanguage());
else if (sc->symbol)
language_plugin = Language::FindPlugin(sc->symbol->GetLanguage());
if (language_plugin) {
language_plugin_handled = language_plugin->GetFunctionDisplayName(
sc, exe_ctx, Language::FunctionNameRepresentation::eName, ss);
}
if (language_plugin_handled) {
s << ss.GetString();
return true;
} else {
const char *name = nullptr;
if (sc->function)
name = sc->function->GetName().AsCString(nullptr);
else if (sc->symbol)
name = sc->symbol->GetName().AsCString(nullptr);
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: {
Language *language_plugin = nullptr;
bool language_plugin_handled = false;
StreamString ss;
if (sc->function)
language_plugin = Language::FindPlugin(sc->function->GetLanguage());
else if (sc->symbol)
language_plugin = Language::FindPlugin(sc->symbol->GetLanguage());
if (language_plugin) {
language_plugin_handled = language_plugin->GetFunctionDisplayName(
sc, exe_ctx, Language::FunctionNameRepresentation::eNameWithNoArgs,
ss);
}
if (language_plugin_handled) {
s << ss.GetString();
return true;
} else {
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: {
Language *language_plugin = nullptr;
bool language_plugin_handled = false;
StreamString ss;
if (sc->function)
language_plugin = Language::FindPlugin(sc->function->GetLanguage());
else if (sc->symbol)
language_plugin = Language::FindPlugin(sc->symbol->GetLanguage());
if (language_plugin) {
language_plugin_handled = language_plugin->GetFunctionDisplayName(
sc, exe_ctx, Language::FunctionNameRepresentation::eNameWithArgs, ss);
}
if (language_plugin_handled) {
s << ss.GetString();
return true;
} else {
// Print the function name with arguments in it
if (sc->function) {
ExecutionContextScope *exe_scope =
exe_ctx ? exe_ctx->GetBestExecutionContextScope() : nullptr;
const char *cstr = sc->function->GetName().AsCString(nullptr);
if (cstr) {
const InlineFunctionInfo *inline_info = nullptr;
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));
StreamString ss;
llvm::StringRef var_representation;
const char *var_name = var_value_sp->GetName().GetCString();
if (var_value_sp->GetCompilerType().IsValid()) {
if (var_value_sp && exe_scope->CalculateTarget())
var_value_sp =
var_value_sp->GetQualifiedRepresentationIfAvailable(
exe_scope->CalculateTarget()
->TargetProperties::GetPreferDynamicValue(),
exe_scope->CalculateTarget()
->TargetProperties::GetEnableSyntheticValue());
if (var_value_sp->GetCompilerType().IsAggregateType() &&
DataVisualization::ShouldPrintAsOneLiner(*var_value_sp)) {
static StringSummaryFormat format(
TypeSummaryImpl::Flags()
.SetHideItemNames(false)
.SetShowMembersOneLiner(true),
"");
format.FormatObject(var_value_sp.get(), buffer,
TypeSummaryOptions());
var_representation = buffer;
} else
var_value_sp->DumpPrintableRepresentation(
ss,
ValueObject::ValueObjectRepresentationStyle::
eValueObjectRepresentationStyleSummary,
eFormatDefault,
ValueObject::PrintableRepresentationSpecialCases::eAllow,
false);
}
if (!ss.GetString().empty())
var_representation = ss.GetString();
if (arg_idx > 0)
s.PutCString(", ");
if (var_value_sp->GetError().Success()) {
if (!var_representation.empty())
s.Printf("%s=%s", var_name, var_representation.str().c_str());
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(nullptr);
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:
return (DumpAddressOffsetFromFunction(s, sc, exe_ctx,
sc->line_entry.range.GetBaseAddress(),
false, false, 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;
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;
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::LineEntryColumn:
if (sc && sc->line_entry.IsValid() && sc->line_entry.column) {
const char *format = "%" PRIu32;
if (!entry.printf_format.empty())
format = entry.printf_format.c_str();
s.Printf(format, sc->line_entry.column);
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) {
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 Status ParseEntry(const llvm::StringRef &format_str,
const FormatEntity::Entry::Definition *parent,
FormatEntity::Entry &entry) {
Status 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::EscapeCode:
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.GetData());
} 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.GetData());
return error;
}
static const FormatEntity::Entry::Definition *
FindEntry(const llvm::StringRef &format_str,
const FormatEntity::Entry::Definition *parent,
llvm::StringRef &remainder) {
Status 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;
}
Status FormatEntity::ParseInternal(llvm::StringRef &format, Entry &parent_entry,
uint32_t depth) {
Status 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, nullptr, 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, nullptr, 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;
}
}
parent_entry.AppendEntry(std::move(entry));
}
}
break;
}
}
return error;
}
Status FormatEntity::ExtractVariableInfo(llvm::StringRef &format_str,
llvm::StringRef &variable_name,
llvm::StringRef &variable_format) {
Status error;
variable_name = llvm::StringRef();
variable_format = llvm::StringRef();
const size_t paren_pos = format_str.find('}');
if (paren_pos != llvm::StringRef::npos) {
const size_t percent_pos = format_str.find('%');
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(CompletionRequest &request) {
llvm::StringRef str = request.GetCursorArgumentPrefix().str();
request.SetWordComplete(false);
str = str.drop_front(request.GetMatchStartPoint());
const size_t dollar_pos = str.rfind('$');
if (dollar_pos == llvm::StringRef::npos)
return 0;
// Hitting TAB after $ at the end of the string add a "{"
if (dollar_pos == str.size() - 1) {
std::string match = str.str();
match.append("{");
request.AddCompletion(match);
return 1;
}
if (str[dollar_pos + 1] != '{')
return 0;
const size_t close_pos = str.find('}', dollar_pos + 2);
if (close_pos != llvm::StringRef::npos)
return 0;
const size_t format_pos = str.find('%', dollar_pos + 2);
if (format_pos != llvm::StringRef::npos)
return 0;
llvm::StringRef partial_variable(str.substr(dollar_pos + 2));
if (partial_variable.empty()) {
// Suggest all top level entites as we are just past "${"
StringList new_matches;
AddMatches(&g_root, str, llvm::StringRef(), new_matches);
request.AddCompletions(new_matches);
return request.GetNumberOfMatches();
}
// 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)
return 0;
const size_t n = entry_def->num_children;
if (remainder.empty()) {
// Exact match
if (n > 0) {
// "${thread.info" <TAB>
request.AddCompletion(MakeMatch(str, "."));
} else {
// "${thread.id" <TAB>
request.AddCompletion(MakeMatch(str, "}"));
request.SetWordComplete(true);
}
} else if (remainder.equals(".")) {
// "${thread." <TAB>
StringList new_matches;
AddMatches(entry_def, str, llvm::StringRef(), new_matches);
request.AddCompletions(new_matches);
} else {
// We have a partial match
// "${thre" <TAB>
StringList new_matches;
AddMatches(entry_def, str, remainder, new_matches);
request.AddCompletions(new_matches);
}
return request.GetNumberOfMatches();
}