// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "symbolgroupnode.h" #include "symbolgroup.h" #include "symbolgroupvalue.h" #include "stringutils.h" #include "containers.h" #include "extensioncontext.h" #include typedef std::vector::size_type VectorIndexType; typedef std::vector StringVector; enum { BufSize = 2048 }; static inline void indentStream(std::ostream &str, unsigned depth) { for (unsigned d = 0; d < depth; ++d) str << " "; } static inline void debugNodeFlags(std::ostream &str, unsigned f) { if (!f) return; str << " node-flags=" << f; if (f & SymbolGroupNode::Uninitialized) str << " UNINITIALIZED"; if (f & SymbolGroupNode::SimpleDumperNotApplicable) str << " DumperNotApplicable"; if (f & SymbolGroupNode::SimpleDumperOk) str << " DumperOk"; if (f & SymbolGroupNode::SimpleDumperFailed) str << " DumperFailed"; if (f & SymbolGroupNode::ExpandedByRequest) str << " ExpandedByRequest"; if (f & SymbolGroupNode::AdditionalSymbol) str << " AdditionalSymbol"; if (f & SymbolGroupNode::Obscured) str << " Obscured"; if (f & SymbolGroupNode::ComplexDumperOk) str << " ComplexDumperOk"; if (f & SymbolGroupNode::WatchNode) str << " WatchNode"; str << ' '; } // Some helper to conveniently dump flags to a stream struct DebugNodeFlags { DebugNodeFlags(unsigned f) : m_f(f) {} const unsigned m_f; }; inline std::ostream &operator<<(std::ostream &str, const DebugNodeFlags &f) { debugNodeFlags(str, f.m_f); return str; } /*! \class AbstractSymbolGroupNode \brief The AbstractSymbolGroupNode class is an abstract base class for a node of SymbolGroup providing the child list interface. \ingroup qtcreatorcdbext */ AbstractSymbolGroupNode::AbstractSymbolGroupNode(const std::string &name, const std::string &iname) : m_name(name) , m_iname(iname) { } AbstractSymbolGroupNode::~AbstractSymbolGroupNode() { } std::string AbstractSymbolGroupNode::absoluteFullIName() const { std::string rc = iName(); for (const AbstractSymbolGroupNode *p = m_parent; p; p = p->m_parent) { rc.insert(0, 1, SymbolGroupNodeVisitor::iNamePathSeparator); rc.insert(0, p->iName()); } return rc; } AbstractSymbolGroupNode *AbstractSymbolGroupNode::childAt(unsigned i) const { const AbstractSymbolGroupNodePtrVector &c = children(); return i < c.size() ? c.at(i) : static_cast(0); } unsigned AbstractSymbolGroupNode::indexByIName(const char *n) const { const AbstractSymbolGroupNodePtrVector &c = children(); const VectorIndexType size = c.size(); for (VectorIndexType i = 0; i < size; ++i) if ( c.at(i)->iName() == n ) return unsigned(i); return unsigned(-1); } AbstractSymbolGroupNode *AbstractSymbolGroupNode::childByIName(const char *n) const { const unsigned index = indexByIName(n); if (index != unsigned(-1)) return children().at(index); return 0; } unsigned AbstractSymbolGroupNode::indexOf(const AbstractSymbolGroupNode *n) const { const AbstractSymbolGroupNodePtrVector::const_iterator it = std::find(children().begin(), children().end(), n); return it != children().end() ? unsigned(it - children().begin()) : unsigned(-1); } bool AbstractSymbolGroupNode::accept(SymbolGroupNodeVisitor &visitor, const std::string &parentIname, unsigned child, unsigned depth) { // If we happen to be the root node, just skip over const bool invisibleRoot = !m_parent; const unsigned childDepth = invisibleRoot ? 0 : depth + 1; std::string fullIname = parentIname; if (!fullIname.empty()) fullIname.push_back(SymbolGroupNodeVisitor::iNamePathSeparator); fullIname += m_iname; const SymbolGroupNodeVisitor::VisitResult vr = invisibleRoot ? SymbolGroupNodeVisitor::VisitContinue : visitor.visit(this, fullIname, child, depth); switch (vr) { case SymbolGroupNodeVisitor::VisitStop: return true; case SymbolGroupNodeVisitor::VisitSkipChildren: break; case SymbolGroupNodeVisitor::VisitContinue: { AbstractSymbolGroupNodePtrVector c = children(); if (visitor.sortChildrenAlphabetically() && !resolveReference()->testFlags(SymbolGroupNode::PreSortedChildren)) { std::sort(c.begin(), c.end(), [](AbstractSymbolGroupNode *a, AbstractSymbolGroupNode *b) { return a->name() < b->name(); }); } const unsigned childCount = unsigned(c.size()); for (unsigned i = 0; i < childCount; ++i) if (c.at(i)->accept(visitor, fullIname, i, childDepth)) return true; if (!invisibleRoot) visitor.childrenVisited(this, depth); } break; } return false; } void AbstractSymbolGroupNode::debug(std::ostream &str, const std::string &visitingFullIname, unsigned /* verbosity */, unsigned depth) const { indentStream(str, 2 * depth); str << "AbstractSymbolGroupNode " << visitingFullIname << " with " << children().size() << " children\n"; } void AbstractSymbolGroupNode::dumpBasicData(std::ostream &str, const std::string &aName, const std::string &aFullIname, const std::string &type /* = "" */, const std::string &expression /* = "" */) { str << "iname=\"" << aFullIname << "\",name=\"" << aName << '"'; if (!type.empty()) str << ",type=\"" << type << '"'; if (WatchesSymbolGroup::isWatchIname(aFullIname)) str << ",exp=\"" << aName << '"'; else if (!expression.empty()) str << ",exp=\"" << expression << '"'; } void AbstractSymbolGroupNode::setParent(AbstractSymbolGroupNode *n) { if (m_parent) dprintf("Internal error: Attempt to change non-null parent of %s", m_name.c_str()); m_parent = n; } /*! \class BaseSymbolGroupNode \brief The BaseSymbolGroupNode class is the base class for a node of SymbolGroup with a flat list of children. \ingroup qtcreatorcdbext */ BaseSymbolGroupNode::BaseSymbolGroupNode(const std::string &name, const std::string &iname) : AbstractSymbolGroupNode(name, iname) { } BaseSymbolGroupNode::~BaseSymbolGroupNode() { removeChildren(); } void BaseSymbolGroupNode::removeChildAt(unsigned n) { if (VectorIndexType(n) >= m_children.size()) return; const AbstractSymbolGroupNodePtrVector::iterator it = m_children.begin() + n; delete *it; m_children.erase(it, it + 1); } void BaseSymbolGroupNode::removeChildren() { if (!m_children.empty()) { const AbstractSymbolGroupNodePtrVectorIterator end = m_children.end(); for (AbstractSymbolGroupNodePtrVectorIterator it = m_children.begin(); it != end; ++it) delete *it; m_children.clear(); } } void BaseSymbolGroupNode::addChild(AbstractSymbolGroupNode *c) { c->setParent(this); m_children.push_back(c); } // ----------- Helpers: Stream DEBUG_SYMBOL_PARAMETERS std::ostream &operator<<(std::ostream &str, const DEBUG_SYMBOL_PARAMETERS ¶meters) { str << "parent="; if (parameters.ParentSymbol == DEBUG_ANY_ID) str << "DEBUG_ANY_ID"; else str << parameters.ParentSymbol ; if (parameters.Flags != 0 && parameters.Flags != 1) str << " flags=" << parameters.Flags; // Detailed flags: if (parameters.Flags & DEBUG_SYMBOL_EXPANDED) str << " EXPANDED"; if (parameters.Flags & DEBUG_SYMBOL_READ_ONLY) str << " READONLY"; if (parameters.Flags & DEBUG_SYMBOL_IS_ARRAY) str << " ARRAY"; if (parameters.Flags & DEBUG_SYMBOL_IS_FLOAT) str << " FLOAT"; if (parameters.Flags & DEBUG_SYMBOL_IS_ARGUMENT) str << " ARGUMENT"; if (parameters.Flags & DEBUG_SYMBOL_IS_LOCAL) str << " LOCAL"; str << " typeId=" << parameters.TypeId; if (parameters.SubElements) str << " subElements=" << parameters.SubElements; return str; } /*! \class DumpParameters \brief The DumpParameters class contains all parameters for GDBMI dumping of a symbol group in one class. The debugging engine passes maps of type names/inames to special integer values indicating hex/dec, etc. \ingroup qtcreatorcdbext */ // typeformats: decode hex-encoded name, value pairs: // '414A=2,...' -> map of "AB:2". DumpParameters::FormatMap DumpParameters::decodeFormatArgument(const std::string &f, bool isHex) { extern const char *stdStringTypeC; extern const char *stdWStringTypeC; extern const char *stdWStringWCharTypeC; FormatMap rc; const std::string::size_type size = f.size(); // Split 'hexname=4,' for (std::string::size_type pos = 0; pos < size ; ) { // Cut out key const std::string::size_type equalsPos = f.find('=', pos); if (equalsPos == std::string::npos) return rc; const std::string name = isHex ? stringFromHex(f.c_str() + pos, f.c_str() + equalsPos) : f.substr(pos, equalsPos - pos); // Search for number const std::string::size_type numberPos = equalsPos + 1; std::string::size_type nextPos = f.find(',', numberPos); if (nextPos == std::string::npos) nextPos = size; std::string format = f.substr(numberPos, nextPos - numberPos); if (name == "std::basic_string") { // Python dumper naming convention for types rc.insert(FormatMap::value_type(stdStringTypeC, format)); rc.insert(FormatMap::value_type(stdWStringTypeC, format)); rc.insert(FormatMap::value_type(stdWStringWCharTypeC, format)); } else { rc.insert(FormatMap::value_type(name, format)); } pos = nextPos + 1; } return rc; } std::string DumpParameters::format(const std::string &type, const std::string &iname) const { if (!individualFormats.empty()) { const FormatMap::const_iterator iit = individualFormats.find(iname); if (iit != individualFormats.end()) return iit->second; } if (!typeFormats.empty()) { // Strip 'struct' / 'class' prefixes and pointer types // when called with a raw cdb types. std::string stripped = SymbolGroupValue::stripClassPrefixes(type); if (stripped != type) stripped = SymbolGroupValue::stripPointerType(stripped); const FormatMap::const_iterator tit = typeFormats.find(stripped); if (tit != typeFormats.end()) return tit->second; } return std::string(); } /* Recode arrays/pointers of char*, wchar_t according to users * specification. Handles char formats for 'char *', '0x834478 "hallo.."' * and 'wchar_t *', '0x834478 "hallo.."', 'wchar_t[56] "hallo"', etc. * This is done by retrieving the address (from the pointer value for * pointers, using passed in-address for arrays) and the length * (in characters excluding \0) * of the CDB output, converting it to memory size, fetching the data * from memory, and recoding it using the encoding * defined in watchutils.cpp. * As a special case, if there is no user-defined format and the * CDB output contains '?'/'.' (CDB version?) indicating non-printable * characters, switch to a suitable type such that the watchmodel * formatting options trigger. * This is split into a check step that returns a struct containing * an allocated buffer with the raw data/size and recommended format * (or 0 if recoding is not applicable) and the actual recoding step. * Step 1) is used by std::string dumpers to potentially reformat * the arrays. */ DumpParameterRecodeResult DumpParameters::checkRecode(const std::string &type, const std::string &iname, const std::wstring &value, const SymbolGroupValueContext &ctx, ULONG64 address, const DumpParameters *dp /* =0 */) { enum ReformatType { ReformatNone, ReformatPointer, ReformatArray }; DumpParameterRecodeResult result; if (SymbolGroupValue::verbose > 2) { DebugPrint debugPrint; debugPrint << '>' << __FUNCTION__ << ' ' << iname << '/' << type; if (dp) debugPrint << " option format: " << dp->format(type, iname); } // We basically handle char formats for 'char *', '0x834478 "hallo.."' // and 'wchar_t *', '0x834478 "hallo.."' // Determine address and length from the pointer value output, // read the raw memory and recode if that is possible. if (type.empty() || value.empty()) return result; const std::wstring::size_type quote2 = value.size() - 1; if (value.at(quote2) != L'"') return result; ReformatType reformatType = ReformatNone; switch (type.at(type.size() - 1)) { case '*': reformatType = ReformatPointer; if (value.compare(0, 2, L"0x") != 0) return result; break; case ']': reformatType = ReformatArray; break; default: return result; } // Check for a reformattable type (do not trigger for a 'std::string *'). if (type.compare(0, 4, "char") == 0 || type.compare(0, 13, "unsigned char") == 0) { } else if (type.compare(0, 7, "wchar_t", 0, 7) == 0 || type.compare(0, 14, "unsigned short") == 0) { result.isWide = true; } else { return result; } // Empty string? const std::wstring::size_type quote1 = value.find(L'"', 2); if (quote1 == std::wstring::npos || quote2 == quote1) return result; const std::wstring::size_type length = quote2 - quote1 - 1; if (!length) return result; // Choose format if (dp) result.recommendedFormat = dp->format(type, iname); // The user did not specify any format, still, there are '?'/'.' // (indicating non-printable) in what the debugger prints. // Reformat in this case. If there are no '?'-> all happy. if (result.recommendedFormat.empty()) result.recommendedFormat = "latin1"; // Get address from value if it is a pointer. if (reformatType == ReformatPointer) { address = 0; if (!integerFromWString(value.substr(0, quote1 - 1), &address) || !address) return result; } // Get real size (excluding 0) if this is for example a wchar_t *. // Make fit to 2/4 character boundaries. const std::string elementType = reformatType == ReformatPointer ? SymbolGroupValue::stripPointerType(type) : SymbolGroupValue::stripArrayType(type); const unsigned elementSize = SymbolGroupValue::sizeOf(elementType.c_str()); if (!elementSize) return result; result.size = length * elementSize; result.buffer = new unsigned char[result.size]; std::fill(result.buffer, result.buffer + result.size, 0); ULONG obtained = 0; if (FAILED(ctx.dataspaces->ReadVirtual(address, result.buffer, ULONG(result.size), &obtained))) { delete [] result.buffer; DebugPrint() << __FUNCTION__ << " ReadVirtual() failed to read " << result.size << " bytes from 0x" << std::hex << address << std::dec << " for " << iname << '.'; result = DumpParameterRecodeResult(); } if (SymbolGroupValue::verbose > 2) DebugPrint() << '<' << __FUNCTION__ << ' ' << iname << " format=" << result.recommendedFormat << " size=" << result.size << " data=" << dumpMemory(result.buffer, result.size); return result; } bool DumpParameters::recode(const std::string &type, const std::string &iname, const SymbolGroupValueContext &ctx, ULONG64 address, std::wstring *value, std::string *encoding) const { const DumpParameterRecodeResult check = checkRecode(type, iname, *value, ctx, address, this); if (!check.buffer) return false; *value = dataToHexW(check.buffer, check.buffer + check.size); *encoding = check.recommendedFormat; delete [] check.buffer; return true; } std::ostream &operator<<(std::ostream &os, const DumpParameters &d) { if (d.dumpFlags & DumpParameters::DumpHumanReadable) os << ", human-readable"; if (d.dumpFlags & DumpParameters::DumpComplexDumpers) os << ", complex dumpers"; if (!d.typeFormats.empty()) { os << ", type formats: "; DumpParameters::FormatMap::const_iterator cend = d.typeFormats.end(); for (DumpParameters::FormatMap::const_iterator it = d.typeFormats.begin(); it != cend; ++it) os << ' ' << it->first << ':' << it->second; os << '\n'; } if (!d.individualFormats.empty()) { os << ", individual formats: "; DumpParameters::FormatMap::const_iterator cend = d.individualFormats.end(); for (DumpParameters::FormatMap::const_iterator it = d.individualFormats.begin(); it != cend; ++it) os << ' ' << it->first << ':' << it->second; os << '\n'; } return os; } // --------- ErrorSymbolGroupNode ErrorSymbolGroupNode::ErrorSymbolGroupNode(const std::string &name, const std::string &iname) : BaseSymbolGroupNode(name, iname) { } int ErrorSymbolGroupNode::dump(std::ostream &str, const std::string &fullIname, const DumpParameters &, const SymbolGroupValueContext &) { dumpBasicData(str, name(), fullIname, "", std::string()); str << ",valueencoded=\"0\",value=\"\",valueenabled=\"false\",valueeditable=\"false\""; return 0; } void ErrorSymbolGroupNode::debug(std::ostream &os, const std::string &visitingFullIname, unsigned , unsigned depth) const { indentStream(os, 2 * depth); os << "ErrorSymbolGroupNode '" << name() << "','" << iName() << "', '" << visitingFullIname << "'\n"; } /*! \class SymbolGroupNode \brief The SymbolGroupNode class represents a \e real node within a symbol group, identified by its index in IDebugSymbolGroup. Provides accessors for fixed-up symbol group value and a dumping facility consisting of: \list \li 'Simple' dumping done when running the DumpVisitor. This produces one line of formatted output shown for the class. These values values are always displayed, while still allowing for expansion of the structure in the debugger. It also pre-determines some information for complex dumping (type, container). \li 'Complex' dumping: Obscures the symbol group children by fake children, for example container children, to be run when calling SymbolGroup::dump with an iname. The fake children are appended to the child list (other children are just marked as obscured for GDBMI dumping so that SymbolGroupValue expressions still work as before). \endlist The dumping is mostly based on SymbolGroupValue expressions. in the debugger. Evaluating those dumpers might expand symbol nodes, but those are not marked as 'ExpandedByRequest'. This stops the dump recursion to prevent outputting data that were not explicitly expanded by the watch handler. \ingroup qtcreatorcdbext */ SymbolGroupNode::SymbolGroupNode(SymbolGroup *symbolGroup, ULONG index, const std::string &module, const std::string &name, const std::string &iname) : BaseSymbolGroupNode(name, iname) , m_symbolGroup(symbolGroup) , m_module(module) , m_index(index) { memset(&m_parameters, 0, sizeof(DEBUG_SYMBOL_PARAMETERS)); m_parameters.ParentSymbol = DEBUG_ANY_ID; } SymbolGroupNode::~SymbolGroupNode() { delete m_memory; } const SymbolGroupNode *SymbolGroupNode::symbolGroupNodeParent() const { if (const AbstractSymbolGroupNode *p = parent()) return p->asSymbolGroupNode(); return 0; } SymbolGroupNode *SymbolGroupNode::symbolGroupNodeParent() { if (AbstractSymbolGroupNode *p = parent()) return p->asSymbolGroupNode(); return 0; } bool SymbolGroupNode::isArrayElement() const { if (const SymbolGroupNode *p = symbolGroupNodeParent()) return (p->m_parameters.Flags & DEBUG_SYMBOL_IS_ARRAY) != 0; return false; } // Notify about expansion of a node: // Adapt our index and those of our children if we are behind it. // Return true if a modification was required to be able to terminate the // recursion. bool SymbolGroupNode::notifyIndexesMoved(ULONG index, bool inserted, ULONG offset) { typedef AbstractSymbolGroupNodePtrVector::const_reverse_iterator ReverseIt; if (SymbolGroupValue::verbose > 2) DebugPrint() << "notifyIndexesMoved: #" << this->index() << " '" << name() << "' index=" << index << " insert=" << inserted << " offset=" << offset; // Looping backwards over the children. If a subtree has no modifications, // (meaning all other indexes are smaller) we can stop. const ReverseIt rend = children().rend(); for (ReverseIt it = children().rbegin(); it != rend; ++it) { if (SymbolGroupNode *c = (*it)->asSymbolGroupNode()) if (!c->notifyIndexesMoved(index, inserted, offset)) return false; } // Correct our own + parent index if applicable. if (m_index == DEBUG_ANY_ID || m_index < index) return false; if (inserted) m_index += offset; else m_index -= offset; if (m_parameters.ParentSymbol != DEBUG_ANY_ID && m_parameters.ParentSymbol >= index) { if (inserted) m_parameters.ParentSymbol += offset; else m_parameters.ParentSymbol -= offset; } return true; } static inline void fixNameAndIname(unsigned &unnamedId, unsigned &templateId, std::string &iname, std::string &name) { if (name.empty() || name == "__formal") { const std::string number = toString(unnamedId++); name = "'; iname = "unnamed#" + number; } else { // Fix names: fix complicated template base names // Long template base classes 'std::tree_base' -> 'std::tree<>' // for nice display std::string::size_type templatePos = name.find('<'); if (templatePos != std::string::npos) { name.erase(templatePos + 1, name.size() - templatePos - 1); name.push_back('>'); } // Fix array iname "[0]" -> "0" for sorting to work correctly if (!iname.empty() && iname.at(0) == '[') { const std::string::size_type last = iname.size() - 1; if (iname.at(last) == ']') { iname.erase(last, 1); iname.erase(0, 1); return; } } // Fix inames: arrays and long, complicated template base names // Long template base classes 'std::tree_base 'tree@t1', // usable as identifier and command line parameter templatePos = iname.find('<'); if (templatePos != std::string::npos) { iname.erase(templatePos, iname.size() - templatePos); if (iname.compare(0, 5, "std::") == 0) iname.erase(0, 5); iname.append("@t"); iname.append(toString(templateId++)); } } } static inline void addShadowCount(const StringVector::size_type count, std::string &iname, std::string &name) { const std::string number = toString(count); name += " empty()) return; unsigned unnamedId = 1; unsigned templateId = 1; /* 1) Fix name="__formal", which occurs when someone writes "void foo(int /* x * /)..." * 2) Fix array inames for sorting: "[6]" -> name="[6]",iname="6" * 3) For toplevels: Fix shadowed variables in the order the debugger expects them: \code int x; // Occurrence (1), should be reported as name="x "/iname="x#1" if (true) int x = 5; (2) // Occurrence (2), should be reported as name="x"/iname="x" \endcode */ const ExtensionContext::CdbVersion cdbVersion = ExtensionContext::instance().cdbVersion(); const bool reverseOrder = cdbVersion.major == 6 && cdbVersion.minor < 10; if (reverseOrder) { StringVector::reverse_iterator nameIt = names->rbegin(); const StringVector::reverse_iterator namesEnd = names->rend(); StringVector::reverse_iterator iNameIt = inames->rbegin(); for (; nameIt != namesEnd ; ++nameIt, ++iNameIt) { std::string &name = *nameIt; std::string &iname = *iNameIt; fixNameAndIname(unnamedId, templateId, iname, name); if (isTopLevel) { if (const StringVector::size_type count = std::count(nameIt + 1, namesEnd, name)) addShadowCount(count, iname, name); } } } else { StringVector::iterator nameIt = names->begin(); const StringVector::iterator namesEnd = names->end(); StringVector::iterator iNameIt = inames->begin(); for (; nameIt != namesEnd ; ++nameIt, ++iNameIt) { std::string &name = *nameIt; std::string &iname = *iNameIt; fixNameAndIname(unnamedId, templateId, iname, name); if (isTopLevel) { if (const StringVector::size_type count = std::count(nameIt + 1, namesEnd, name)) addShadowCount(count, iname, name); } } } } // Index: Index of symbol, parameterOffset: Looking only at a part of the symbol array, offset void SymbolGroupNode::parseParameters(VectorIndexType index, VectorIndexType parameterOffset, const SymbolGroup::SymbolParameterVector &vec) { static char buf[BufSize]; const bool isTopLevel = index == DEBUG_ANY_ID; if (isTopLevel) { m_parameters.Flags |= DEBUG_SYMBOL_EXPANDED; } else { m_parameters = vec.at(index - parameterOffset); if (m_parameters.SubElements == 0 || !(m_parameters.Flags & DEBUG_SYMBOL_EXPANDED)) return; // No children } if (m_parameters.SubElements > 1) reserveChildren(m_parameters.SubElements); const VectorIndexType size = vec.size(); // Scan the top level elements StringVector names; names.reserve(size); // Pass 1) Determine names. We need the complete set first in order to do some corrections. const VectorIndexType startIndex = isTopLevel ? 0 : index + 1; for (VectorIndexType pos = startIndex - parameterOffset; pos < size ; ++pos) { if (vec.at(pos).ParentSymbol == index) { const VectorIndexType symbolGroupIndex = pos + parameterOffset; if (FAILED(m_symbolGroup->debugSymbolGroup()->GetSymbolName(ULONG(symbolGroupIndex), buf, BufSize, NULL))) buf[0] = '\0'; names.push_back(std::string(buf)); } } // 2) Fix names StringVector inames = names; fixNames(isTopLevel, &names, &inames); // Pass 3): Add nodes with fixed names StringVector::size_type nameIndex = 0; for (VectorIndexType pos = startIndex - parameterOffset; pos < size ; ++pos) { if (vec.at(pos).ParentSymbol == index) { const VectorIndexType symbolGroupIndex = pos + parameterOffset; SymbolGroupNode *child = new SymbolGroupNode(m_symbolGroup, ULONG(symbolGroupIndex), m_module, names.at(nameIndex), inames.at(nameIndex)); child->parseParameters(symbolGroupIndex, parameterOffset, vec); addChild(child); nameIndex++; } } if (isTopLevel) { m_parameters.SubElements = ULONG(children().size()); } else { int delta = int(m_parameters.SubElements - children().size()); if (delta != 0) { m_symbolGroup->root()->notifyIndexesMoved(m_index + m_parameters.SubElements, delta < 0, abs(delta)); m_parameters.SubElements = ULONG(children().size()); } } } SymbolGroupNode *SymbolGroupNode::create(SymbolGroup *sg, const std::string &module, const std::string &name, const SymbolGroup::SymbolParameterVector &vec) { SymbolGroupNode *rc = new SymbolGroupNode(sg, DEBUG_ANY_ID, module, name, name); rc->parseParameters(DEBUG_ANY_ID, 0, vec); return rc; } // Fix some oddities in CDB values static inline bool isHexDigit(wchar_t c) { return (c >= L'0' && c <= L'9') || (c >= L'a' && c <= L'f') || (c >= L'A' && c <= L'F'); } static void fixValue(const std::string &type, std::wstring *value) { // Pointers/Unsigned integers: fix '0x00000000`00000AD bla' ... to "0xAD bla" const bool isHexNumber = value->size() > 3 && value->compare(0, 2, L"0x") == 0 && isHexDigit(value->at(2)); if (isHexNumber) { // Remove dumb 64bit separator const size_t separator = value->find(L'`'); if (separator != std::wstring::npos) value->erase(separator, 1); const std::string::size_type firstNonNullDigit = value->find_first_not_of(L"0", 2); // No on-null digits: plain null ptr. if (firstNonNullDigit == std::string::npos || value->at(firstNonNullDigit) == ' ') { *value = L"0x0"; } else { // Strip if (firstNonNullDigit > 2) value->erase(2, firstNonNullDigit - 2); } } // Strip a vtable "0x13f37b7c8 module!Class::`vftable'" to a plain pointer. if (SymbolGroupValue::isVTableType(type)) { const std::wstring::size_type blankPos = value->find(L' ', 2); if (blankPos != std::wstring::npos) value->erase(blankPos, value->size() - blankPos); return; } // Pointers: fix '0x00000000`00000AD class bla' ... to "0xAD", but leave // 'const char *' values as is ('0x00000000`00000AD "hallo"). if (!type.empty() && type.at(type.size() - 1) == L'*') { // Strip ' Class bla" std::wstring::size_type classPos = value->find(L" struct", 2); if (classPos == std::string::npos) classPos = value->find(L" class", 2); if (classPos != std::string::npos) value->erase(classPos, value->size() - classPos); return; } // unsigned hex ints that are not pointers: Convert to decimal as not to confuse watch model: if (isHexNumber) { ULONG64 uv; std::wistringstream str(*value); str >> std::hex >> uv; if (!str.fail()) { *value = toWString(uv); return; } } // Integers: fix '0n10' -> '10' if (value->size() >= 3 && value->compare(0, 2, L"0n") == 0 && (isdigit(value->at(2)) || value->at(2) == L'-')) { value->erase(0, 2); return; } // Fix long class names on std containers 'class std::tree<...>' -> 'class std::tree<>' if (value->compare(0, 6, L"class ") == 0 || value->compare(0, 7, L"struct ") == 0) { const std::string::size_type openTemplate = value->find(L'<'); if (openTemplate != std::string::npos) { value->erase(openTemplate + 1, value->size() - openTemplate - 2); return; } } } std::string SymbolGroupNode::type() const { static char buf[BufSize]; const HRESULT hr = m_symbolGroup->debugSymbolGroup()->GetSymbolTypeName(m_index, buf, BufSize, NULL); return SUCCEEDED(hr) ? std::string(buf) : std::string(); } unsigned SymbolGroupNode::size() const { DEBUG_SYMBOL_ENTRY entry; if (SUCCEEDED(m_symbolGroup->debugSymbolGroup()->GetSymbolEntryInformation(m_index, &entry))) return entry.Size; return 0; } ULONG64 SymbolGroupNode::address() const { ULONG64 address = 0; const HRESULT hr = m_symbolGroup->debugSymbolGroup()->GetSymbolOffset(m_index, &address); if (SUCCEEDED(hr)) return address; return 0; } std::wstring SymbolGroupNode::symbolGroupRawValue() const { // Determine size and return allocated buffer const ULONG maxValueSize = 262144; ULONG obtainedSize = 0; HRESULT hr = m_symbolGroup->debugSymbolGroup()->GetSymbolValueTextWide(m_index, NULL, maxValueSize, &obtainedSize); if (FAILED(hr)) return std::wstring(); if (obtainedSize > maxValueSize) obtainedSize = maxValueSize; wchar_t *buffer = new wchar_t[obtainedSize]; hr = m_symbolGroup->debugSymbolGroup()->GetSymbolValueTextWide(m_index, buffer, obtainedSize, &obtainedSize); if (FAILED(hr)) // Whoops, should not happen buffer[0] = 0; const std::wstring rc(buffer); delete [] buffer; return rc; } std::wstring SymbolGroupNode::symbolGroupFixedValue() const { std::wstring value = symbolGroupRawValue(); fixValue(type(), &value); return value; } // A quick check if symbol is valid by checking for inaccessible value bool SymbolGroupNode::isMemoryAccessible() const { static const char notAccessibleValueC[] = ""; char buffer[sizeof(notAccessibleValueC)]; ULONG obtained = 0; if (FAILED(symbolGroup()->debugSymbolGroup()->GetSymbolValueText(m_index, buffer, sizeof(notAccessibleValueC), &obtained))) return false; if (obtained < sizeof(notAccessibleValueC)) return true; return strcmp(buffer, notAccessibleValueC) != 0; } // Complex dumpers: Get container/fake children void SymbolGroupNode::runComplexDumpers(const SymbolGroupValueContext &ctx) { if (symbolGroupDebug) { DebugPrint dp; dp << "SymbolGroupNode::runComplexDumpers " << name() << '/' << absoluteFullIName() << ' ' << m_index << ' ' << DebugNodeFlags(flags()) << " type: "; formatKnownTypeFlags(dp, static_cast(m_dumperType)); if (m_dumperSpecialInfo) dp << std::hex << std::showbase << " Special " << m_dumperSpecialInfo; } if ((testFlags(ComplexDumperOk) || !testFlags(SimpleDumperOk))) return; const bool isContainer = (m_dumperType & KT_ContainerType) != 0; const bool otherDumper = (m_dumperType & KT_HasComplexDumper) != 0; if (!isContainer && !otherDumper) return; if (isContainer && m_dumperContainerSize <= 0) return; addFlags(ComplexDumperOk); const AbstractSymbolGroupNodePtrVector ctChildren = otherDumper ? dumpComplexType(this, m_dumperType, m_dumperSpecialInfo, ctx) : containerChildren(this, m_dumperType, m_dumperContainerSize, ctx); m_dumperContainerSize = int(ctChildren.size()); // Just in case... if (ctChildren.empty()) return; // Mark current children as obscured. We cannot show both currently // as this would upset the numerical sorting of the watch model AbstractSymbolGroupNodePtrVectorConstIterator cend = children().end(); for (AbstractSymbolGroupNodePtrVectorConstIterator it = children().begin(); it != cend; ++it) (*it)->addFlags(Obscured); // Add children and mark them as referenced by us. cend = ctChildren.end(); for (AbstractSymbolGroupNodePtrVectorConstIterator it = ctChildren.begin(); it != cend; ++it) addChild(*it); } // Run dumpers, format simple in-line dumper value and retrieve fake children bool SymbolGroupNode::runSimpleDumpers(const SymbolGroupValueContext &ctx) { if (symbolGroupDebug) DebugPrint() << ">SymbolGroupNode::runSimpleDumpers " << name() << '/' << absoluteFullIName() << ' ' << m_index << DebugNodeFlags(flags()); if (testFlags(Uninitialized)) return false; if (testFlags(SimpleDumperOk)) return true; if (testFlags(SimpleDumperMask)) return false; addFlags(dumpSimpleType(this , ctx, &m_dumperValue, &m_dumperValueEncoding, &m_dumperType, &m_dumperContainerSize, &m_dumperSpecialInfo, &m_memory)); if (symbolGroupDebug) DebugPrint() << ""; if (runSimpleDumpers(ctx)) { *encoding = m_dumperValueEncoding; return m_dumperValue; } return symbolGroupFixedValue(); } int SymbolGroupNode::dump(std::ostream &str, const std::string &visitingFullIname, const DumpParameters &p, const SymbolGroupValueContext &ctx) { return dumpNode(str, name(), visitingFullIname, p, ctx); } // Return a watch expression basically as "*(type *)(address)" static inline std::string watchExpression(ULONG64 address, const std::string &typeIn, int /* kType */, const std::string &module) { std::string type = SymbolGroupValue::stripClassPrefixes(typeIn); // Try to make watch expressions faster by at least qualifying // templates with the local module. We cannot do expensive type // lookup here. // We could insert a placeholder here for non-POD types indicating // that a type lookup should be done when inserting watches? if (!module.empty() && type.find('>') != std::string::npos) { type.insert(0, 1, '!'); type.insert(0, module); } if (SymbolGroupValue::isArrayType(type)) type = SymbolGroupValue::stripArrayType(type); std::ostringstream str; str << "*(" << SymbolGroupValue::pointerType(type) << ')' << std::hex << std::showbase << address; return str.str(); } int SymbolGroupNode::dumpNode(std::ostream &str, const std::string &aName, const std::string &aFullIName, const DumpParameters &dumpParameters, const SymbolGroupValueContext &ctx) { const std::string t = type(); const ULONG64 addr = address(); // Use name as watchExpression in case evaluation failed (watch group item // names are the expression). const std::string watchExp = t.empty() ? aName : watchExpression(addr, t, m_dumperType, m_module); SymbolGroupNode::dumpBasicData(str, aName, aFullIName, t, watchExp); std::string encoding; std::wstring value = simpleDumpValue(ctx, &encoding); if (addr) { str << std::hex << std::showbase << ",address=\"" << addr << '"'; if (SymbolGroupValue::isPointerType(t)) { std::string::size_type pointerPos = value.rfind(L"0x"); if (pointerPos != std::string::npos) { ULONG64 referencedAddr = 0; std::wistringstream istr(value.substr(pointerPos + 2u)); istr >> std::hex >> referencedAddr; if (referencedAddr) str << ",origaddr=\"" << referencedAddr << '"'; } } str << std::noshowbase << std::dec; } const ULONG s = size(); if (s) str << ",size=\"" << s << '"'; const bool uninitialized = flags() & Uninitialized; bool valueEditable = !uninitialized; bool valueEnabled = !uninitialized; if (encoding.size()) { str << ",valueencoded=\"" << encoding << "\",value=\"" << gdbmiWStringFormat(value) <<'"'; } else if (dumpParameters.recode(t, aFullIName, ctx, addr, &value, &encoding)) { str << ",valueencoded=\"" << encoding << "\",value=\"" << gdbmiWStringFormat(value) <<'"'; } else { str << ",valueencoded=\"utf16:2:0\",value=\""; hexEncode(str, reinterpret_cast(value.c_str()), value.size() * sizeof(wchar_t)); str << '"'; const std::string &format = dumpParameters.format(t, aFullIName); if (!format.empty()) dumpEditValue(this, ctx, format, str); } // Children: Dump all known non-obscured or subelements unsigned childCountGuess = 0; if (!uninitialized) { if (m_dumperContainerSize > 0) { childCountGuess = m_dumperContainerSize; // See Obscured handling } else { if (children().empty()) childCountGuess = m_parameters.SubElements; // Guess else childCountGuess = unsigned(children().size()); } } // No children..suppose we are editable and enabled. if (m_parameters.Flags & DEBUG_SYMBOL_READ_ONLY) { valueEditable = false; } else { if (childCountGuess != 0 && !(m_dumperType & KT_Editable)) valueEditable = false; } str << ",valueenabled=\"" << (valueEnabled ? "true" : "false") << '"' << ",valueeditable=\"" << (valueEditable ? "true" : "false") << '"'; return childCountGuess; } void SymbolGroupNode::debug(std::ostream &str, const std::string &visitingFullIname, unsigned verbosity, unsigned depth) const { indentStream(str, depth); const std::string fullIname = absoluteFullIName(); str << "AbsIname=" << fullIname << '"'; if (fullIname != visitingFullIname) str << ",VisitIname=\"" <= 0) { str << " ,dumperType=" << m_dumperType; if (m_dumperType & KT_Qt_Type) str << " qt"; if (m_dumperType & KT_STL_Type) str << " STL"; if (m_dumperType & KT_ContainerType) str << " container(" << m_dumperContainerSize << ')'; } if (!testFlags(Uninitialized)) str << " Value=\"" << gdbmiWStringFormat(symbolGroupRawValue()) << '"'; str << '\n'; // Potentially multiline } str << '\n'; } static inline std::string msgCannotCast(const std::string &nodeName, const std::string &fromType, const std::string &toType, const std::string &why) { std::ostringstream str; str << "Cannot cast node '" << nodeName << "' from '" << fromType << "' to '" << toType << "': " << why; return str.str(); } static std::string msgExpandFailed(const std::string &name, const std::string &iname, ULONG index, const std::string &why) { std::ostringstream str; str << "Expansion of '" << name << "'/'" << iname << " (index: " << index << ") failed: " << why; return str.str(); } static std::string msgCollapseFailed(const std::string &name, const std::string &iname, ULONG index, const std::string &why) { std::ostringstream str; str << "Collapsing of '" << name << "'/'" << iname << " (index: " << index << ") failed: " << why; return str.str(); } bool SymbolGroupNode::collapse(std::string *errorMessage) { if (!isExpanded()) return true; SymbolGroupNode *sParent = symbolGroupNodeParent(); if (!sParent) { *errorMessage = msgCollapseFailed(name(), absoluteFullIName(), m_index, "Cannot collapse root."); ExtensionContext::instance().report('X', 0, 0, "Error", "%s", errorMessage->c_str()); return false; } // Get current number of children const ULONG next = nextSymbolIndex(); HRESULT hr = m_symbolGroup->debugSymbolGroup()->ExpandSymbol(m_index, FALSE); if (FAILED(hr)) { *errorMessage = msgCollapseFailed(name(), absoluteFullIName(), m_index, msgDebugEngineComFailed("ExpandSymbol(FALSE)", hr)); ExtensionContext::instance().report('X', 0, 0, "Error", "%s", errorMessage->c_str()); return false; } removeChildren(); if (next) sParent->notifyIndexesMoved(m_index + 1, false, next - m_index - 1); return true; } // Expand! bool SymbolGroupNode::expand(std::string *errorMessage) { if (symbolGroupDebug) DebugPrint() << "SymbolGroupNode::expand " << name() <<'/' << absoluteFullIName() << ' ' << m_index << DebugNodeFlags(flags()); if (isExpanded()) return true; if (!canExpand()) { *errorMessage = msgExpandFailed(name(), absoluteFullIName(), m_index, "No subelements to expand in node."); return false; } if (flags() & Uninitialized) { *errorMessage = msgExpandFailed(name(), absoluteFullIName(), m_index, "Refusing to expand uninitialized node."); return false; } const HRESULT hr = m_symbolGroup->debugSymbolGroup()->ExpandSymbol(m_index, TRUE); if (FAILED(hr)) { *errorMessage = msgExpandFailed(name(), absoluteFullIName(), m_index, msgDebugEngineComFailed("ExpandSymbol", hr)); ExtensionContext::instance().report('X', 0, 0, "Error", "%s", errorMessage->c_str()); return false; } SymbolGroup::SymbolParameterVector parameters; // Retrieve parameters (including self, re-retrieve symbol parameters to get new 'expanded' flag // and corrected SubElement count (might be estimate)) if (!SymbolGroup::getSymbolParameters(m_symbolGroup->debugSymbolGroup(), m_index, m_parameters.SubElements + 1, ¶meters, errorMessage)) { *errorMessage = msgExpandFailed(name(), absoluteFullIName(), m_index, *errorMessage); return false; } // Before inserting children, correct indexes on whole group m_symbolGroup->root()->notifyIndexesMoved(m_index + 1, true, parameters.at(0).SubElements); // Parse parameters, correct our own) and create child nodes. parseParameters(m_index, m_index, parameters); return true; } bool SymbolGroupNode::expandRunComplexDumpers(const SymbolGroupValueContext &ctx, std::string *errorMessage) { if (isExpanded() || testFlags(ComplexDumperOk)) return true; if (!expand(errorMessage)) return false; // Run simple dumpers to obtain type and run complex dumpers if (runSimpleDumpers(ctx) && testFlags(SimpleDumperOk)) runComplexDumpers(ctx); return true; } bool SymbolGroupNode::typeCast(const std::string &desiredType, std::string *errorMessage) { const std::string fromType = type(); if (fromType == desiredType) return true; if (isExpanded()) { *errorMessage = msgCannotCast(absoluteFullIName(), fromType, desiredType, "Already expanded"); return false; } HRESULT hr = m_symbolGroup->debugSymbolGroup()->OutputAsType(m_index, desiredType.c_str()); if (FAILED(hr)) { *errorMessage = msgCannotCast(absoluteFullIName(), fromType, desiredType, msgDebugEngineComFailed("OutputAsType", hr)); return false; } hr = m_symbolGroup->debugSymbolGroup()->GetSymbolParameters(m_index, 1, &m_parameters); if (FAILED(hr)) { // Should never fail *errorMessage = msgCannotCast(absoluteFullIName(), fromType, desiredType, msgDebugEngineComFailed("GetSymbolParameters", hr)); return false; } return true; } // Find the index of the next symbol in the group, that is, right sibling. // Go up the tree if we are that last sibling. 0 indicates none found (last sibling) ULONG SymbolGroupNode::nextSymbolIndex() const { const SymbolGroupNode *sParent = symbolGroupNodeParent(); if (!sParent) return 0; const unsigned myIndex = sParent->indexOf(this); const AbstractSymbolGroupNodePtrVector &siblings = sParent->children(); // Find any 'real' SymbolGroupNode to our right. const unsigned size = unsigned(siblings.size()); for (unsigned i = myIndex + 1; i < size; ++i) if (const SymbolGroupNode *s = siblings.at(i)->asSymbolGroupNode()) return s->index(); return sParent->nextSymbolIndex(); } // Remove self off parent and return the indexes to be shifted or unsigned(-1). bool SymbolGroupNode::removeSelf(SymbolGroupNode *root, std::string *errorMessage) { SymbolGroupNode *sParent = symbolGroupNodeParent(); if (m_index == DEBUG_ANY_ID || !sParent) { *errorMessage = "SymbolGroupNode::removeSelf: Internal error 1."; return false; } const unsigned parentPosition = sParent->indexOf(this); if (parentPosition == unsigned(-1)) { *errorMessage = "SymbolGroupNode::removeSelf: Internal error 2."; return false; } // Determine the index for the following symbols to be subtracted by finding the next // 'real' SymbolGroupNode child (right or up the tree-right) and taking its index const ULONG oldIndex = index(); const ULONG nextSymbolIdx = nextSymbolIndex(); const ULONG offset = nextSymbolIdx > oldIndex ? nextSymbolIdx - oldIndex : 0; if (SymbolGroupValue::verbose) DebugPrint() << "SymbolGroupNode::removeSelf #" << index() << " '" << name() << "' at pos=" << parentPosition<< " (" << sParent->children().size() << ") of parent '" << sParent->name() << "' nextIndex=" << nextSymbolIdx << " offset=" << offset << *errorMessage; // Remove from symbol group const HRESULT hr = symbolGroup()->debugSymbolGroup()->RemoveSymbolByIndex(oldIndex); if (FAILED(hr)) { *errorMessage = msgDebugEngineComFailed("RemoveSymbolByIndex", hr); return false; } // Move the following symbol indexes: We no longer exist below here. sParent ->removeChildAt(parentPosition); if (offset) root->notifyIndexesMoved(oldIndex + 1, false, offset); return true; } static inline std::string msgCannotAddSymbol(const std::string &name, const std::string &why) { std::ostringstream str; str << "Cannot add symbol '" << name << "': " << why; return str.str(); } // For root nodes, only: Add a new symbol by name SymbolGroupNode *SymbolGroupNode::addSymbolByName(const std::string &module, const std::string &name, const std::string &displayName, const std::string &iname, std::string *errorMessage) { ULONG index = DEBUG_ANY_ID; // Append char buf[BufSize]; CIDebugSymbolGroup *symbolGroup = m_symbolGroup->debugSymbolGroup(); SymbolParameterVector indexParameters(1, DEBUG_SYMBOL_PARAMETERS()); static const char separator = '.'; std::string::size_type partStart = 0; std::string::size_type partEnd = name.find(separator); std::string namePart = name.substr(0, partEnd); HRESULT hr = symbolGroup->AddSymbol(namePart.c_str(), &index); if (FAILED(hr)) { *errorMessage = msgCannotAddSymbol(namePart, msgDebugEngineComFailed("AddSymbol", hr)); ExtensionContext::instance().report('X', 0, 0, "Error", "%s", errorMessage->c_str()); return 0; } do { if (index == DEBUG_ANY_ID) { // Occasionally happens for unknown or 'complicated' types *errorMessage = msgCannotAddSymbol(namePart, "DEBUG_ANY_ID was returned as symbol index by AddSymbol."); ExtensionContext::instance().report('X', 0, 0, "Error", "%s", errorMessage->c_str()); return 0; } hr = symbolGroup->GetSymbolParameters(index, 1, &(*indexParameters.begin())); if (FAILED(hr)) { // Should never fail std::ostringstream str; str << "Cannot retrieve 1 symbol parameter entry at " << index << ": " << msgDebugEngineComFailed("GetSymbolParameters", hr); *errorMessage = msgCannotAddSymbol(namePart, str.str()); return 0; } if (partEnd == std::string::npos) break; partStart = partEnd + 1; partEnd = name.find(separator, partStart); namePart = name.substr(partStart, partEnd == std::string::npos ? partEnd : partEnd - partStart); hr = symbolGroup->ExpandSymbol(index, TRUE); if (FAILED(hr)) { std::ostringstream str; str << "Cannot expand " << namePart.c_str() << ": " << msgDebugEngineComFailed("ExpandSymbol", hr); *errorMessage = msgCannotAddSymbol(name, str.str()); return 0; } ULONG childCount = indexParameters.at(0).SubElements; SymbolParameterVector childParameters(childCount + 1, DEBUG_SYMBOL_PARAMETERS()); SymbolGroup::getSymbolParameters(symbolGroup, index, childCount + 1, &childParameters, errorMessage); const VectorIndexType size = childParameters.size(); ULONG newIndex = DEBUG_ANY_ID; for (VectorIndexType pos = 1; pos < size ; ++pos) { if (childParameters.at(pos).ParentSymbol != index) continue; if (FAILED(symbolGroup->GetSymbolName(ULONG(index + pos), buf, BufSize, NULL))) buf[0] = '\0'; if (!namePart.compare(buf)) { newIndex = index + (ULONG)pos; break; } } index = newIndex; } while (true); SymbolGroupNode *node = new SymbolGroupNode(m_symbolGroup, index, module, displayName.empty() ? name : displayName, iname.empty() ? name : iname); node->parseParameters(0, 0, indexParameters); node->addFlags(AdditionalSymbol); addChild(node); return node; } std::string SymbolGroupNode::msgAssignError(const std::string &nodeName, const std::string &value, const std::string &why) { std::ostringstream str; str << "Unable to assign '" << value << "' to '" << nodeName << "': " << why; return str.str(); } // Simple type bool SymbolGroupNode::assign(const std::string &value, std::string *errorMessage /* = 0 */) { const HRESULT hr = m_symbolGroup->debugSymbolGroup()->WriteSymbol(m_index, value.c_str()); if (FAILED(hr)) { if (errorMessage) *errorMessage = SymbolGroupNode::msgAssignError(name(), value, msgDebugEngineComFailed("WriteSymbol", hr)); return false; } return true; } // Utility returning a pair ('[42]','42') as name/iname pair // for a node representing an array index typedef std::pair StringStringPair; static inline StringStringPair arrayIndexNameIname(int index) { StringStringPair rc(std::string(), toString(index)); rc.first = std::string(1, '['); rc.first += rc.second; rc.first.push_back(']'); return rc; } /*! \class ReferenceSymbolGroupNode \brief The ReferenceSymbolGroupNode class is an artificial node referencing another (real) SymbolGroupNode (added symbol or symbol from within an expanded linked list structure). Forwards the dumping to the referenced node using its own name. \ingroup qtcreatorcdbext */ ReferenceSymbolGroupNode::ReferenceSymbolGroupNode(const std::string &name, const std::string &iname, SymbolGroupNode *referencedNode) : AbstractSymbolGroupNode(name, iname), m_referencedNode(referencedNode) { } // Convenience to create a node name name='[1]', iname='1' for arrays ReferenceSymbolGroupNode *ReferenceSymbolGroupNode::createArrayNode(int index, SymbolGroupNode *referencedNode) { const StringStringPair nameIname = arrayIndexNameIname(index); return new ReferenceSymbolGroupNode(nameIname.first, nameIname.second, referencedNode); } int ReferenceSymbolGroupNode::dump(std::ostream &str, const std::string &visitingFullIname, const DumpParameters &p, const SymbolGroupValueContext &ctx) { // Let the referenced node dump with our iname/name return m_referencedNode->dumpNode(str, name(), visitingFullIname, p, ctx); } void ReferenceSymbolGroupNode::debug(std::ostream &str, const std::string &visitingFullIname, unsigned verbosity, unsigned depth) const { indentStream(str, 2 * depth); str << "Node " << name() << '/' << visitingFullIname << " referencing\n"; m_referencedNode->debug(str, visitingFullIname, verbosity, depth); } /*! \class MapNodeSymbolGroupNode \brief The MapNodeSymbolGroupNode class is a [fake] map node with a fake array index and key/value entries consisting of ReferenceSymbolGroupNode. \ingroup qtcreatorcdbext */ MapNodeSymbolGroupNode::MapNodeSymbolGroupNode(const std::string &name, const std::string &iname, ULONG64 address, const std::string &type, AbstractSymbolGroupNode *key, AbstractSymbolGroupNode *value) : BaseSymbolGroupNode(name, iname), m_address(address), m_type(type) { addChild(key); addChild(value); } MapNodeSymbolGroupNode *MapNodeSymbolGroupNode::create(int index, ULONG64 address, const std::string &type, SymbolGroupNode *key, SymbolGroupNode *value) { const StringStringPair nameIname = arrayIndexNameIname(index); const std::string keyName = "key"; ReferenceSymbolGroupNode *keyRN = new ReferenceSymbolGroupNode(keyName, keyName, key); const std::string valueName = "value"; ReferenceSymbolGroupNode *valueRN = new ReferenceSymbolGroupNode(valueName, valueName, value); return new MapNodeSymbolGroupNode(nameIname.first, nameIname.second, address, type, keyRN, valueRN); } int MapNodeSymbolGroupNode::dump(std::ostream &str, const std::string &visitingFullIname, const DumpParameters &, const SymbolGroupValueContext &) { SymbolGroupNode::dumpBasicData(str, name(), visitingFullIname); if (m_address) str << ",address=\"0x" << std::hex << m_address << '"'; str << ",type=\"" << m_type << "\",valueencoded=\"0\",value=\"\",valueenabled=\"false\"" ",valueeditable=\"false\""; return 2; } void MapNodeSymbolGroupNode::debug(std::ostream &os, const std::string &visitingFullIname, unsigned /* verbosity */, unsigned depth) const { indentStream(os, 2 * depth); os << "MapNode " << name() << '/' << visitingFullIname << '\n'; } /*! \class SymbolGroupNodeVisitor \brief The SymbolGroupNodeVisitor class is a visitor that iterates over the nodes and builds the full iname path ('local.foo.bar') that is required for GDBMI dumping. The full name depends on the path on which a node was reached for referenced nodes (a linked list element can be reached via array index or by expanding the whole structure). visit() is not called for the (invisible) root node, but starting with the root's children with depth=0. Return VisitStop from visit() to terminate the recursion. \ingroup qtcreatorcdbext */ // "local.vi" -> "local" std::string SymbolGroupNodeVisitor::parentIname(const std::string &iname) { const std::string::size_type lastSep = iname.rfind(SymbolGroupNodeVisitor::iNamePathSeparator); return lastSep == std::string::npos ? std::string() : iname.substr(0, lastSep); } /*! \class DebugSymbolGroupNodeVisitor \brief The DebugSymbolGroupNodeVisitor class is a debug output visitor. \ingroup qtcreatorcdbext */ DebugSymbolGroupNodeVisitor::DebugSymbolGroupNodeVisitor(std::ostream &os, unsigned verbosity) : m_os(os), m_verbosity(verbosity) { } SymbolGroupNodeVisitor::VisitResult DebugSymbolGroupNodeVisitor::visit(AbstractSymbolGroupNode *node, const std::string &aFullIname, unsigned /* child */, unsigned depth) { node->debug(m_os, aFullIname, m_verbosity, depth); return VisitContinue; } /*! \class DebugFilterSymbolGroupNodeVisitor \brief The DebugFilterSymbolGroupNodeVisitor class is a debug filtering output visitor. \ingroup qtcreatorcdbext */ DebugFilterSymbolGroupNodeVisitor::DebugFilterSymbolGroupNodeVisitor(std::ostream &os, const std::string &filter, const unsigned verbosity) : DebugSymbolGroupNodeVisitor(os, verbosity), m_filter(filter) { } SymbolGroupNodeVisitor::VisitResult DebugFilterSymbolGroupNodeVisitor::visit(AbstractSymbolGroupNode *node, const std::string &fullIname, unsigned child, unsigned depth) { if (fullIname.find(m_filter) == std::string::npos && node->name().find(m_filter) == std::string::npos) return SymbolGroupNodeVisitor::VisitContinue; return DebugSymbolGroupNodeVisitor::visit(node, fullIname, child, depth); } /*! \class DumpSymbolGroupNodeVisitor \brief The DumpSymbolGroupNodeVisitor class is a GDBMI dump output visitor used to report locals values back to the debugging engine. \ingroup qtcreatorcdbext */ DumpSymbolGroupNodeVisitor::DumpSymbolGroupNodeVisitor(std::ostream &os, const SymbolGroupValueContext &context, const DumpParameters ¶meters) : m_os(os) , m_context(context) , m_parameters(parameters) { } SymbolGroupNodeVisitor::VisitResult DumpSymbolGroupNodeVisitor::visit(AbstractSymbolGroupNode *node, const std::string &fullIname, unsigned /* child */, unsigned depth) { // Show container children only, no additional symbol below root (unless it is a watch node). if (node->testFlags(SymbolGroupNode::Obscured)) return VisitSkipChildren; if (node->testFlags(SymbolGroupNode::AdditionalSymbol) && !node->testFlags(SymbolGroupNode::WatchNode)) return VisitSkipChildren; // Recurse to children only if expanded by explicit watchmodel request // and initialized. bool visitChildren = true; // Report only one level for Qt Creator. // Visit children of a SymbolGroupNode only if not expanded by its dumpers. if (const SymbolGroupNode *realNode = node->resolveReference()->asSymbolGroupNode()) { if (!realNode->isExpanded() || realNode->testFlags(SymbolGroupNode::Uninitialized) || !realNode->testFlags(SymbolGroupNode::ExpandedByRequest)) { visitChildren = false; } } // Comma between same level children given obscured children if (depth == m_lastDepth) m_os << ','; else m_lastDepth = depth; if (m_parameters.humanReadable()) { m_os << '\n'; indentStream(m_os, depth * 2); } m_os << '{'; const int childCount = node->dump(m_os, fullIname, m_parameters, m_context); m_os << ",numchild=\"" << childCount << '"'; if (!childCount) visitChildren = false; if (visitChildren) { // open children array m_os << ",children=["; } else { // No children, close array. m_os << '}'; } if (m_parameters.humanReadable()) m_os << '\n'; return visitChildren ? VisitContinue : VisitSkipChildren; } void DumpSymbolGroupNodeVisitor::childrenVisited(const AbstractSymbolGroupNode *n, unsigned) { m_os << "]}"; // Close children array and self if (m_parameters.humanReadable()) m_os << " /* end of '" << n->absoluteFullIName() << "' */\n"; }