From 5e80284153ac7785619d4018813afaec8ea832f1 Mon Sep 17 00:00:00 2001 From: guochao2 Date: Thu, 16 Apr 2026 17:27:35 +0800 Subject: [PATCH] add more qt type suport.(by claude) --- examples/main.cpp | 118 ++++++- lldb_qt_formatters/__init__.py | 85 ++++- lldb_qt_formatters/datetime.py | 31 ++ lldb_qt_formatters/geometry.py | 43 +++ lldb_qt_formatters/misc.py | 123 +++++++ lldb_qt_formatters/qbytearray.py | 28 ++ lldb_qt_formatters/qhash.py | 125 ++++++++ lldb_qt_formatters/qjson.py | 512 ++++++++++++++++++++++++++++++ lldb_qt_formatters/qlinkedlist.py | 70 ++++ lldb_qt_formatters/qlist.py | 72 +++++ lldb_qt_formatters/qvariant.py | 157 +++++++++ lldb_qt_formatters/qvector.py | 44 +++ test/conftest.py | 2 +- 13 files changed, 1405 insertions(+), 5 deletions(-) create mode 100644 lldb_qt_formatters/datetime.py create mode 100644 lldb_qt_formatters/geometry.py create mode 100644 lldb_qt_formatters/misc.py create mode 100644 lldb_qt_formatters/qbytearray.py create mode 100644 lldb_qt_formatters/qhash.py create mode 100644 lldb_qt_formatters/qjson.py create mode 100644 lldb_qt_formatters/qlinkedlist.py create mode 100644 lldb_qt_formatters/qlist.py create mode 100644 lldb_qt_formatters/qvariant.py create mode 100644 lldb_qt_formatters/qvector.py diff --git a/examples/main.cpp b/examples/main.cpp index 326ffd0..49fc655 100755 --- a/examples/main.cpp +++ b/examples/main.cpp @@ -1,15 +1,127 @@ +#include +#include +#include +#include +#include +#include #include +#include +#include +#include +#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include int main() { + // Existing auto hello = QString("Hello World"); - auto demosthenes = QString("Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι"); + auto demosthenes = QString("Οὐχὶ ταὐτὰ παρίσταταί μοι γιγνώσκειν, ὦ ἄνδρες ᾿Αθηναῖοι"); - auto map = QMap{ + auto map = QMap{ {"one", 1}, {"forty-two", 42}, {"1.21 gigawatts", 1210000} }; + // QByteArray + auto byteArray = QByteArray("Hello Bytes"); + + // Geometry + auto point = QPoint(10, 20); + auto pointF = QPointF(1.5, 2.5); + auto size = QSize(800, 600); + auto sizeF = QSizeF(12.5, 34.75); + auto rect = QRect(0, 0, 99, 49); + auto rectF = QRectF(1.0, 2.0, 100.5, 50.25); + auto line = QLine(QPoint(0, 0), QPoint(10, 20)); + auto lineF = QLineF(QPointF(0.0, 0.0), QPointF(10.5, 20.5)); + + // Date/Time + auto date = QDate(2024, 1, 15); + auto time = QTime(14, 30, 25, 123); + + // QVector + auto vec = QVector{1, 2, 3, 4, 5}; + + // QList + auto list = QList{"alpha", "beta", "gamma"}; + auto stringList = QStringList{"one", "two", "three"}; + + // QHash + auto hash = QHash{{"one", 1}, {"two", 2}, {"three", 3}}; + + // QSet + auto set = QSet{10, 20, 30}; + + // QLinkedList + auto linkedList = QLinkedList{100, 200, 300}; + + // QVariant + auto variantInt = QVariant(42); + auto variantStr = QVariant(QString("variant string")); + auto variantBool = QVariant(true); + auto variantDouble = QVariant(3.14); + + // QPair + auto pair = QPair(1, "hello"); + + // QUuid + auto uuid = QUuid("{67c8770b-44f1-410a-ab9a-f9b5446f13ee}"); + + // QVarLengthArray + auto varArray = QVarLengthArray{7, 8, 9}; + + // QChar + auto ch = QChar('A'); + + // QJsonValue + auto jsonNull = QJsonValue(); + auto jsonBool = QJsonValue(true); + auto jsonDouble = QJsonValue(3.14); + auto jsonString = QJsonValue(QString("hello json")); + + // QJsonObject + auto jsonObj = QJsonObject{{"name", "test"}, {"value", 42}}; + + // QJsonArray + auto jsonArr = QJsonArray{1, 2, 3}; + + // Nested: QJsonObject containing QJsonArray and QJsonObject + auto jsonNested = QJsonObject{ + {"title", "nested test"}, + {"tags", QJsonArray{"alpha", "beta", "gamma"}}, + {"metadata", QJsonObject{{"version", 2}, {"draft", true}}}, + {"score", 9.5} + }; + + // Nested: QJsonArray containing mixed types including objects and arrays + auto jsonMixed = QJsonArray{ + 42, + "hello", + true, + QJsonValue(), + QJsonObject{{"x", 1}, {"y", 2}}, + QJsonArray{10, 20, 30} + }; + + // Deep nesting: 3 levels + auto jsonDeep = QJsonObject{ + {"level1", QJsonObject{ + {"level2", QJsonObject{ + {"level3", "deep value"} + }} + }} + }; + return 0; -} \ No newline at end of file +} + diff --git a/lldb_qt_formatters/__init__.py b/lldb_qt_formatters/__init__.py index b3433c0..9aff7cd 100755 --- a/lldb_qt_formatters/__init__.py +++ b/lldb_qt_formatters/__init__.py @@ -1,14 +1,97 @@ # We need to import the formatters like this - no other method will work from .qstring import * from .qmap import * +from .qbytearray import * +from .geometry import * +from .datetime import * +from .qvector import * +from .qlist import * +from .qhash import * +from .qlinkedlist import * +from .qvariant import * +from .misc import * +from .qjson import * def __lldb_init_module(debugger, internal_dict): init_commands = [ + # === Existing: QString, QMap === 'type summary add --python-function lldb_qt_formatters.qstring.format_summary "QString"', 'type summary add --summary-string "\{${var.key}: ${var.value}\}" -x "QMapNode<.+>$"', # noqa: W605 'type synthetic add --python-class lldb_qt_formatters.qmap.SyntheticChildrenProvider -x "QMap<.+>$"', - 'type summary add --expand --python-function lldb_qt_formatters.qmap.format_summary -x "QMap<.+>$"' + 'type summary add --expand --python-function lldb_qt_formatters.qmap.format_summary -x "QMap<.+>$"', + + # === QByteArray === + 'type summary add --python-function lldb_qt_formatters.qbytearray.format_summary "QByteArray"', + + # === Geometry types === + 'type summary add --python-function lldb_qt_formatters.geometry.format_qpoint_summary "QPoint"', + 'type summary add --python-function lldb_qt_formatters.geometry.format_qpoint_summary "QPointF"', + 'type summary add --python-function lldb_qt_formatters.geometry.format_qsize_summary "QSize"', + 'type summary add --python-function lldb_qt_formatters.geometry.format_qsize_summary "QSizeF"', + 'type summary add --python-function lldb_qt_formatters.geometry.format_qrect_summary "QRect"', + 'type summary add --python-function lldb_qt_formatters.geometry.format_qrectf_summary "QRectF"', + 'type summary add --python-function lldb_qt_formatters.geometry.format_qline_summary "QLine"', + 'type summary add --python-function lldb_qt_formatters.geometry.format_qline_summary "QLineF"', + + # === Date/Time === + 'type summary add --python-function lldb_qt_formatters.datetime.format_qdate_summary "QDate"', + 'type summary add --python-function lldb_qt_formatters.datetime.format_qtime_summary "QTime"', + + # === QVector / QStack === + 'type synthetic add --python-class lldb_qt_formatters.qvector.SyntheticChildrenProvider -x "QVector<.+>$"', + 'type summary add --expand --python-function lldb_qt_formatters.qvector.format_summary -x "QVector<.+>$"', + 'type synthetic add --python-class lldb_qt_formatters.qvector.SyntheticChildrenProvider -x "QStack<.+>$"', + 'type summary add --expand --python-function lldb_qt_formatters.qvector.format_summary -x "QStack<.+>$"', + + # === QList / QQueue / QStringList === + 'type synthetic add --python-class lldb_qt_formatters.qlist.SyntheticChildrenProvider -x "QList<.+>$"', + 'type summary add --expand --python-function lldb_qt_formatters.qlist.format_summary -x "QList<.+>$"', + 'type synthetic add --python-class lldb_qt_formatters.qlist.SyntheticChildrenProvider -x "QQueue<.+>$"', + 'type summary add --expand --python-function lldb_qt_formatters.qlist.format_summary -x "QQueue<.+>$"', + 'type synthetic add --python-class lldb_qt_formatters.qlist.SyntheticChildrenProvider "QStringList"', + 'type summary add --expand --python-function lldb_qt_formatters.qlist.format_summary "QStringList"', + + # === QHash / QMultiHash === + 'type summary add --summary-string "\{${var.key}: ${var.value}\}" -x "QHashNode<.+>$"', # noqa: W605 + 'type synthetic add --python-class lldb_qt_formatters.qhash.SyntheticChildrenProvider -x "QHash<.+>$"', + 'type summary add --expand --python-function lldb_qt_formatters.qhash.format_summary -x "QHash<.+>$"', + 'type synthetic add --python-class lldb_qt_formatters.qhash.SyntheticChildrenProvider -x "QMultiHash<.+>$"', + 'type summary add --expand --python-function lldb_qt_formatters.qhash.format_summary -x "QMultiHash<.+>$"', + + # === QSet === + 'type synthetic add --python-class lldb_qt_formatters.qhash.QSetSyntheticChildrenProvider -x "QSet<.+>$"', + 'type summary add --expand --python-function lldb_qt_formatters.qhash.format_set_summary -x "QSet<.+>$"', + + # === QLinkedList === + 'type synthetic add --python-class lldb_qt_formatters.qlinkedlist.SyntheticChildrenProvider -x "QLinkedList<.+>$"', + 'type summary add --expand --python-function lldb_qt_formatters.qlinkedlist.format_summary -x "QLinkedList<.+>$"', + + # === QVariant === + 'type summary add --python-function lldb_qt_formatters.qvariant.format_summary "QVariant"', + + # === Miscellaneous types === + 'type summary add --python-function lldb_qt_formatters.misc.format_qchar_summary "QChar"', + 'type summary add --python-function lldb_qt_formatters.misc.format_qpair_summary -x "QPair<.+>$"', + 'type summary add --python-function lldb_qt_formatters.misc.format_quuid_summary "QUuid"', + + # === QVarLengthArray === + 'type synthetic add --python-class lldb_qt_formatters.misc.QVarLengthArraySyntheticProvider -x "QVarLengthArray<.+>$"', + 'type summary add --expand --python-function lldb_qt_formatters.misc.format_qvarlengtharray_summary -x "QVarLengthArray<.+>$"', + + # === Smart pointers === + 'type summary add --python-function lldb_qt_formatters.misc.format_qsharedpointer_summary -x "QSharedPointer<.+>$"', + 'type summary add --python-function lldb_qt_formatters.misc.format_qweakpointer_summary -x "QWeakPointer<.+>$"', + 'type summary add --python-function lldb_qt_formatters.misc.format_qscopedpointer_summary -x "QScopedPointer<.+>$"', + 'type summary add --python-function lldb_qt_formatters.misc.format_qpointer_summary -x "QPointer<.+>$"', + + # === QJson types === + 'type synthetic add --python-class lldb_qt_formatters.qjson.QJsonValueSyntheticProvider "QJsonValue"', + 'type summary add --expand --python-function lldb_qt_formatters.qjson.format_jsonvalue_summary "QJsonValue"', + 'type synthetic add --python-class lldb_qt_formatters.qjson.QJsonObjectSyntheticProvider "QJsonObject"', + 'type summary add --expand --python-function lldb_qt_formatters.qjson.format_jsonobject_summary "QJsonObject"', + 'type synthetic add --python-class lldb_qt_formatters.qjson.QJsonArraySyntheticProvider "QJsonArray"', + 'type summary add --expand --python-function lldb_qt_formatters.qjson.format_jsonarray_summary "QJsonArray"', ] for command in init_commands: debugger.HandleCommand(command) diff --git a/lldb_qt_formatters/datetime.py b/lldb_qt_formatters/datetime.py new file mode 100644 index 0000000..5fd9325 --- /dev/null +++ b/lldb_qt_formatters/datetime.py @@ -0,0 +1,31 @@ +from lldb import SBValue + + +def format_qdate_summary(valobj: SBValue, internal_dict, options): + jd = valobj.GetChildMemberWithName("jd").GetValueAsSigned() + if jd < 0: + return "Invalid" + + # Julian Day Number to Gregorian calendar conversion + a = jd + 32044 + b = (4 * a + 3) // 146097 + c = a - (146097 * b) // 4 + d = (4 * c + 3) // 1461 + e = c - (1461 * d) // 4 + m = (5 * e + 2) // 153 + day = e - (153 * m + 2) // 5 + 1 + month = m + 3 - 12 * (m // 10) + year = 100 * b + d - 4800 + m // 10 + return f"{year:04d}-{month:02d}-{day:02d}" + + +def format_qtime_summary(valobj: SBValue, internal_dict, options): + mds = valobj.GetChildMemberWithName("mds").GetValueAsSigned() + if mds < 0: + return "Invalid" + + hours = mds // 3600000 + minutes = (mds % 3600000) // 60000 + seconds = (mds // 1000) % 60 + ms = mds % 1000 + return f"{hours:02d}:{minutes:02d}:{seconds:02d}.{ms:03d}" diff --git a/lldb_qt_formatters/geometry.py b/lldb_qt_formatters/geometry.py new file mode 100644 index 0000000..2962f40 --- /dev/null +++ b/lldb_qt_formatters/geometry.py @@ -0,0 +1,43 @@ +from lldb import SBValue + + +def format_qpoint_summary(valobj: SBValue, internal_dict, options): + x = valobj.GetChildMemberWithName("xp") + y = valobj.GetChildMemberWithName("yp") + return f"{{ x = {x.GetValue()}, y = {y.GetValue()} }}" + + +def format_qsize_summary(valobj: SBValue, internal_dict, options): + w = valobj.GetChildMemberWithName("wd") + h = valobj.GetChildMemberWithName("ht") + return f"{{ width = {w.GetValue()}, height = {h.GetValue()} }}" + + +def format_qrect_summary(valobj: SBValue, internal_dict, options): + x1 = valobj.GetChildMemberWithName("x1").GetValueAsSigned() + y1 = valobj.GetChildMemberWithName("y1").GetValueAsSigned() + x2 = valobj.GetChildMemberWithName("x2").GetValueAsSigned() + y2 = valobj.GetChildMemberWithName("y2").GetValueAsSigned() + width = x2 - x1 + 1 + height = y2 - y1 + 1 + return f"{{ x = {x1}, y = {y1}, width = {width}, height = {height} }}" + + +def format_qrectf_summary(valobj: SBValue, internal_dict, options): + x = valobj.GetChildMemberWithName("xp") + y = valobj.GetChildMemberWithName("yp") + w = valobj.GetChildMemberWithName("w") + h = valobj.GetChildMemberWithName("h") + return f"{{ x = {x.GetValue()}, y = {y.GetValue()}, width = {w.GetValue()}, height = {h.GetValue()} }}" + + +def _format_point(pt: SBValue): + x = pt.GetChildMemberWithName("xp") + y = pt.GetChildMemberWithName("yp") + return f"({x.GetValue()}, {y.GetValue()})" + + +def format_qline_summary(valobj: SBValue, internal_dict, options): + pt1 = valobj.GetChildMemberWithName("pt1") + pt2 = valobj.GetChildMemberWithName("pt2") + return f"{{ start = {_format_point(pt1)}, end = {_format_point(pt2)} }}" diff --git a/lldb_qt_formatters/misc.py b/lldb_qt_formatters/misc.py new file mode 100644 index 0000000..3ed07d9 --- /dev/null +++ b/lldb_qt_formatters/misc.py @@ -0,0 +1,123 @@ +from lldb import SBError, SBValue + + +def format_qchar_summary(valobj: SBValue, internal_dict, options): + ucs = valobj.GetChildMemberWithName("ucs").GetValueAsUnsigned() + try: + ch = chr(ucs) + return f"'{ch}' (U+{ucs:04X})" + except (ValueError, OverflowError): + return f"U+{ucs:04X}" + + +def format_qpair_summary(valobj: SBValue, internal_dict, options): + first = valobj.GetChildMemberWithName("first") + second = valobj.GetChildMemberWithName("second") + return f"({first.GetValue()}, {second.GetValue()})" + + +def format_quuid_summary(valobj: SBValue, internal_dict, options): + data1 = valobj.GetChildMemberWithName("data1").GetValueAsUnsigned() + data2 = valobj.GetChildMemberWithName("data2").GetValueAsUnsigned() + data3 = valobj.GetChildMemberWithName("data3").GetValueAsUnsigned() + + data4_bytes = [] + data4 = valobj.GetChildMemberWithName("data4") + for i in range(8): + byte_val = data4.GetChildAtIndex(i).GetValueAsUnsigned() + data4_bytes.append(byte_val) + + part4 = f"{data4_bytes[0]:02x}{data4_bytes[1]:02x}" + part5 = "".join(f"{b:02x}" for b in data4_bytes[2:]) + + return f"{{{data1:08x}-{data2:04x}-{data3:04x}-{part4}-{part5}}}" + + +def format_qsharedpointer_summary(valobj: SBValue, internal_dict, options): + value = valobj.GetChildMemberWithName("value") + if value.GetValueAsUnsigned() == 0: + return "(null)" + d = valobj.GetChildMemberWithName("d") + if d.GetValueAsUnsigned() == 0: + return "(null)" + d_deref = d.deref + strong = d_deref.GetChildMemberWithName("strongref") + strong_val = strong.GetChildMemberWithName("_q_value").GetValueAsSigned() + weak = d_deref.GetChildMemberWithName("weakref") + weak_val = weak.GetChildMemberWithName("_q_value").GetValueAsSigned() + addr = value.GetValueAsUnsigned() + return f"0x{addr:x} (strong={strong_val}, weak={weak_val})" + + +def format_qweakpointer_summary(valobj: SBValue, internal_dict, options): + d = valobj.GetChildMemberWithName("d") + value = valobj.GetChildMemberWithName("value") + if d.GetValueAsUnsigned() == 0 or value.GetValueAsUnsigned() == 0: + return "(null)" + d_deref = d.deref + strong = d_deref.GetChildMemberWithName("strongref") + strong_val = strong.GetChildMemberWithName("_q_value").GetValueAsSigned() + if strong_val == 0: + return "(expired)" + weak = d_deref.GetChildMemberWithName("weakref") + weak_val = weak.GetChildMemberWithName("_q_value").GetValueAsSigned() + addr = value.GetValueAsUnsigned() + return f"0x{addr:x} (strong={strong_val}, weak={weak_val})" + + +def format_qscopedpointer_summary(valobj: SBValue, internal_dict, options): + d = valobj.GetChildMemberWithName("d") + addr = d.GetValueAsUnsigned() + if addr == 0: + return "(null)" + return f"0x{addr:x}" + + +def format_qpointer_summary(valobj: SBValue, internal_dict, options): + wp = valobj.GetChildMemberWithName("wp") + d = wp.GetChildMemberWithName("d") + value = wp.GetChildMemberWithName("value") + if d.GetValueAsUnsigned() == 0 or value.GetValueAsUnsigned() == 0: + return "(null)" + d_deref = d.deref + strong = d_deref.GetChildMemberWithName("strongref") + strong_val = strong.GetChildMemberWithName("_q_value").GetValueAsSigned() + if strong_val == 0: + return "(null)" + addr = value.GetValueAsUnsigned() + return f"0x{addr:x}" + + +def format_qvarlengtharray_summary(valobj: SBValue, internal_dict, options): + s = valobj.GetChildMemberWithName("s").GetValueAsSigned() + return f"Size: {s}" + + +class QVarLengthArraySyntheticProvider: + def __init__(self, valobj, internal_dict): + self.valobj = valobj + self.element_type = valobj.type.GetUnqualifiedType().GetTemplateArgumentType(0) + + def update(self): + self._size = self.valobj.GetChildMemberWithName("s").GetValueAsSigned() + self._ptr = self.valobj.GetChildMemberWithName("ptr").GetValueAsUnsigned() + + def num_children(self): + return self._size + + def has_children(self): + return self._size > 0 + + @staticmethod + def get_child_index(name: str): + index = name.lstrip("[").rstrip("]") + return int(index) + + def get_child_at_index(self, index): + if index < 0 or index >= self._size: + return None + element_size = self.element_type.GetByteSize() + addr = self._ptr + index * element_size + return self.valobj.CreateValueFromAddress( + f"[{index}]", addr, self.element_type + ) diff --git a/lldb_qt_formatters/qbytearray.py b/lldb_qt_formatters/qbytearray.py new file mode 100644 index 0000000..bbe2037 --- /dev/null +++ b/lldb_qt_formatters/qbytearray.py @@ -0,0 +1,28 @@ +from lldb import SBError, SBValue + + +def format_summary(valobj: SBValue, internal_dict, options): + if not valobj.process: + return "Unknown" + + data_member = valobj.GetChildMemberWithName("d") + array_member = data_member.deref + + offset_in_bytes = array_member.GetChildMemberWithName("offset").GetValueAsUnsigned() + size = array_member.GetChildMemberWithName("size").GetValueAsUnsigned() + address = data_member.GetValueAsUnsigned() + + if size == 0: + return '""' + + error = SBError() + content = valobj.process.ReadMemory(address + offset_in_bytes, size, error) + if error.Fail(): + return "Unknown" + + try: + text = bytearray(content).decode("utf-8") + except UnicodeDecodeError: + text = bytearray(content).decode("latin-1") + + return f'"{text}"' diff --git a/lldb_qt_formatters/qhash.py b/lldb_qt_formatters/qhash.py new file mode 100644 index 0000000..08abdf2 --- /dev/null +++ b/lldb_qt_formatters/qhash.py @@ -0,0 +1,125 @@ +import lldb +from lldb import SBValue + + +def format_summary(valobj: SBValue, internal_dict, options): + provider = SyntheticChildrenProvider(valobj.GetNonSyntheticValue(), internal_dict) + return f"Size: {provider.size()}" + + +def format_set_summary(valobj: SBValue, internal_dict, options): + provider = QSetSyntheticChildrenProvider( + valobj.GetNonSyntheticValue(), internal_dict + ) + return f"Size: {provider.size()}" + + +def _derived_node_type(hash_obj: SBValue): + hash_type = hash_obj.type.GetUnqualifiedType() + key_type = hash_type.GetTemplateArgumentType(0) + value_type = hash_type.GetTemplateArgumentType(1) + node_name = f"QHashNode<{key_type.name}, {value_type.name}>" + return hash_obj.target.FindFirstType(node_name) + + +class SyntheticChildrenProvider: + def __init__(self, valobj, internal_dict): + self.valobj = valobj + self.node_type = _derived_node_type(self.valobj) + + def size(self): + d_ptr = self.valobj.GetChildMemberWithName("d") + d = d_ptr.deref + return d.GetChildMemberWithName("size").GetValueAsSigned() + + def update(self): + d_ptr = self.valobj.GetChildMemberWithName("d") + d = d_ptr.deref + self._size = d.GetChildMemberWithName("size").GetValueAsSigned() + num_buckets = d.GetChildMemberWithName("numBuckets").GetValueAsSigned() + buckets = d.GetChildMemberWithName("buckets") + + self._nodes = [] + if self._size == 0: + return + + ptr_size = self.valobj.target.GetAddressByteSize() + buckets_addr = buckets.GetValueAsUnsigned() + error = lldb.SBError() + + for i in range(num_buckets): + bucket_slot = buckets_addr + i * ptr_size + node_ptr = self.valobj.process.ReadPointerFromMemory( + bucket_slot, error + ) + while node_ptr != 0: + # The first field of QHashData::Node is 'next' + next_ptr = self.valobj.process.ReadPointerFromMemory( + node_ptr, error + ) + if next_ptr != 0: + self._nodes.append(node_ptr) + node_ptr = next_ptr + if len(self._nodes) >= self._size: + break + + def num_children(self): + return self._size + + def has_children(self): + return self._size > 0 + + @staticmethod + def get_child_index(name: str): + index = name.lstrip("[").rstrip("]") + return int(index) + + def get_child_at_index(self, index): + if index < 0 or index >= len(self._nodes): + return None + node_addr = self._nodes[index] + return self.valobj.CreateValueFromAddress( + f"[{index}]", node_addr, self.node_type + ) + + +class QSetSyntheticChildrenProvider: + """Synthetic provider for QSet which wraps QHash.""" + + def __init__(self, valobj, internal_dict): + self.valobj = valobj + self._q_hash = valobj.GetChildMemberWithName("q_hash") + self._hash_provider = SyntheticChildrenProvider( + self._q_hash, internal_dict + ) + + def size(self): + return self._hash_provider.size() + + def update(self): + self._q_hash = self.valobj.GetChildMemberWithName("q_hash") + self._hash_provider.valobj = self._q_hash + self._hash_provider.node_type = _derived_node_type(self._q_hash) + self._hash_provider.update() + self._size = self._hash_provider._size + + def num_children(self): + return self._size + + def has_children(self): + return self._size > 0 + + @staticmethod + def get_child_index(name: str): + index = name.lstrip("[").rstrip("]") + return int(index) + + def get_child_at_index(self, index): + child = self._hash_provider.get_child_at_index(index) + if child is None: + return None + # Extract just the key from the QHashNode + key = child.GetChildMemberWithName("key") + return key.CreateValueFromData( + f"[{index}]", key.GetData(), key.type + ) diff --git a/lldb_qt_formatters/qjson.py b/lldb_qt_formatters/qjson.py new file mode 100644 index 0000000..b3dda20 --- /dev/null +++ b/lldb_qt_formatters/qjson.py @@ -0,0 +1,512 @@ +import struct + +import lldb +from lldb import SBError, SBValue + + +# QCborValue::Type enum values (Qt 5.15) +_CBOR_INTEGER = 0x00 # 0 +_CBOR_STRING = 0x60 # 96 +_CBOR_ARRAY = 0x80 # 128 +_CBOR_MAP = 0xa0 # 160 +_CBOR_FALSE = 0x114 # 276 = 0x100 + 20 +_CBOR_TRUE = 0x115 # 277 = 0x100 + 21 +_CBOR_NULL = 0x116 # 278 = 0x100 + 22 +_CBOR_UNDEFINED = 0x117 # 279 = 0x100 + 23 +_CBOR_DOUBLE = 0x202 # 514 + +# QtCbor::Element::ValueFlag +_FLAG_IS_CONTAINER = 0x0001 +_FLAG_HAS_BYTE_DATA = 0x0002 +_FLAG_STRING_IS_UTF16 = 0x0004 +_FLAG_STRING_IS_ASCII = 0x0008 + +_ELEMENT_SIZE = 16 # Q_STATIC_ASSERT(sizeof(Element) == 16) +_BYTEDATA_HDR = 4 # sizeof(QtCbor::ByteData) == sizeof(int) + + +# ------------------------------------------------------------------ +# Raw-memory helpers – independent of QCborContainerPrivate debug info +# ------------------------------------------------------------------ +# +# QCborContainerPrivate layout (inherits QSharedData): +# offset 0 : QAtomicInt ref [4] +# offset 4 : int usedData [4] +# offset 8 : QByteArray data [ptr_size] (only member is `d` ptr) +# offset 8+ptr_size : QVector elements [ptr_size] +# +# QArrayData (used by both QByteArray and QVector): +# offset 0 : ref (int) [4] +# offset 4 : size (int) [4] +# offset 8 : alloc (uint) [4] +# offset 12: capacityReserved [4] +# offset 16: offset (qptrdiff) [ptr_size] +# + +def _get_container_ptr(valobj, member_name): + """Get QCborContainerPrivate* from an ESDP member of valobj.""" + esdp = valobj.GetChildMemberWithName(member_name) + raw_ptr = esdp.GetChildMemberWithName("d") + return raw_ptr.GetValueAsUnsigned() + + +def _read_ptr(process, addr, ptr_size): + """Read a pointer from target memory.""" + error = SBError() + if ptr_size == 8: + raw = process.ReadMemory(addr, 8, error) + if error.Fail(): + return 0 + return struct.unpack("data at the given element offset.""" + if not (elem_flags & _FLAG_HAS_BYTE_DATA) or data_base == 0: + return None + + error = SBError() + bd_addr = data_base + elem_value + + str_len = _read_i32(process, bd_addr) + if str_len <= 0: + return "" + + raw = process.ReadMemory(bd_addr + _BYTEDATA_HDR, str_len, error) + if error.Fail(): + return None + + if elem_flags & _FLAG_STRING_IS_UTF16: + return bytearray(raw).decode("utf-16-le") + elif elem_flags & _FLAG_STRING_IS_ASCII: + return bytearray(raw).decode("latin-1") + return bytearray(raw).decode("utf-8") + + +def _format_element(process, data_base, val, typ, flags): + """One-line display for any Element.""" + if typ == _CBOR_NULL: + return "null" + if typ == _CBOR_TRUE: + return "true" + if typ == _CBOR_FALSE: + return "false" + if typ == _CBOR_INTEGER: + return str(val) + if typ == _CBOR_DOUBLE: + dbl = struct.unpack(" 0 + + def get_child_index(self, name: str): + if hasattr(self, '_name_to_idx') and name in self._name_to_idx: + return self._name_to_idx[name] + try: + return int(name.lstrip("[").rstrip("]")) + except ValueError: + return -1 + + def get_child_at_index(self, index): + if 0 <= index < len(self._children): + return self._children[index] + return None + + +# ------------------------------------------------------------------ +# QJsonArray +# ------------------------------------------------------------------ + +def format_jsonarray_summary(valobj: SBValue, internal_dict, options): + try: + valobj = valobj.GetNonSyntheticValue() + process = valobj.process + ptr_size = valobj.target.GetAddressByteSize() + addr = _get_container_ptr(valobj, "a") + _, count, _ = _container_info(process, addr, ptr_size) + return f"Size: {count}" + except Exception: + return "QJsonArray(?)" + + +class QJsonArraySyntheticProvider: + def __init__(self, valobj, internal_dict): + self.valobj = valobj + self._children = [] + self._name_to_idx = {} + + def update(self): + self._children = [] + self._name_to_idx = {} + try: + process = self.valobj.process + ptr_size = self.valobj.target.GetAddressByteSize() + container_addr = _get_container_ptr(self.valobj, "a") + if container_addr == 0: + return + data_base, elem_count, elem_base = _container_info( + process, container_addr, ptr_size + ) + for i in range(elem_count): + child = _build_child( + self.valobj, f"[{i}]", i, elem_base, + data_base, container_addr + ) + self._children.append(child) + except Exception: + self._children = [] + + def num_children(self): + return len(self._children) + + def has_children(self): + return len(self._children) > 0 + + def get_child_index(self, name: str): + if hasattr(self, '_name_to_idx') and name in self._name_to_idx: + return self._name_to_idx[name] + try: + return int(name.lstrip("[").rstrip("]")) + except ValueError: + return -1 + + def get_child_at_index(self, index): + if 0 <= index < len(self._children): + return self._children[index] + return None + + +# ------------------------------------------------------------------ +# QJsonObject +# ------------------------------------------------------------------ + +def format_jsonobject_summary(valobj: SBValue, internal_dict, options): + try: + valobj = valobj.GetNonSyntheticValue() + process = valobj.process + ptr_size = valobj.target.GetAddressByteSize() + addr = _get_container_ptr(valobj, "o") + _, count, _ = _container_info(process, addr, ptr_size) + return f"Size: {count // 2}" + except Exception: + return "QJsonObject(?)" + + +class QJsonObjectSyntheticProvider: + def __init__(self, valobj, internal_dict): + self.valobj = valobj + self._children = [] + self._name_to_idx = {} + + def update(self): + self._children = [] + self._name_to_idx = {} + try: + process = self.valobj.process + ptr_size = self.valobj.target.GetAddressByteSize() + container_addr = _get_container_ptr(self.valobj, "o") + if container_addr == 0: + return + data_base, elem_count, elem_base = _container_info( + process, container_addr, ptr_size + ) + pairs = elem_count // 2 + for i in range(pairs): + ki = i * 2 + vi = ki + 1 + kval, ktyp, kflags = _read_element( + process, elem_base, ki + ) + key_str = f"[{i}]" + if ktyp == _CBOR_STRING: + s = _read_string(process, data_base, kval, kflags) + if s: + key_str = s + self._name_to_idx[key_str] = i + child = _build_child( + self.valobj, key_str, vi, elem_base, + data_base, container_addr + ) + self._children.append(child) + except Exception: + self._children = [] + + def num_children(self): + return len(self._children) + + def has_children(self): + return len(self._children) > 0 + + def get_child_index(self, name: str): + if hasattr(self, '_name_to_idx') and name in self._name_to_idx: + return self._name_to_idx[name] + try: + return int(name.lstrip("[").rstrip("]")) + except ValueError: + return -1 + + def get_child_at_index(self, index): + if 0 <= index < len(self._children): + return self._children[index] + return None diff --git a/lldb_qt_formatters/qlinkedlist.py b/lldb_qt_formatters/qlinkedlist.py new file mode 100644 index 0000000..eb9dae4 --- /dev/null +++ b/lldb_qt_formatters/qlinkedlist.py @@ -0,0 +1,70 @@ +import lldb +from lldb import SBValue + + +def format_summary(valobj: SBValue, internal_dict, options): + provider = SyntheticChildrenProvider(valobj.GetNonSyntheticValue(), internal_dict) + return f"Size: {provider.size()}" + + +class SyntheticChildrenProvider: + def __init__(self, valobj, internal_dict): + self.valobj = valobj + self.element_type = valobj.type.GetUnqualifiedType().GetTemplateArgumentType(0) + self._node_type_name = f"QLinkedListNode<{self.element_type.name}>" + self._node_type = valobj.target.FindFirstType(self._node_type_name) + + def size(self): + d_ptr = self.valobj.GetChildMemberWithName("d") + d = d_ptr.deref + return d.GetChildMemberWithName("size").GetValueAsSigned() + + def update(self): + d_ptr = self.valobj.GetChildMemberWithName("d") + d = d_ptr.deref + self._size = d.GetChildMemberWithName("size").GetValueAsSigned() + + self._nodes = [] + if self._size == 0: + return + + # d->n is the first real node (the head's next) + head_n = d.GetChildMemberWithName("n") + node_ptr = head_n.GetValueAsUnsigned() + + error = lldb.SBError() + ptr_size = self.valobj.target.GetAddressByteSize() + + for _ in range(self._size): + if node_ptr == 0: + break + self._nodes.append(node_ptr) + # Read the 'n' (next) field - first member of the node + next_ptr = self.valobj.process.ReadPointerFromMemory( + node_ptr, error + ) + node_ptr = next_ptr + + def num_children(self): + return self._size + + def has_children(self): + return self._size > 0 + + @staticmethod + def get_child_index(name: str): + index = name.lstrip("[").rstrip("]") + return int(index) + + def get_child_at_index(self, index): + if index < 0 or index >= len(self._nodes): + return None + node_addr = self._nodes[index] + node = self.valobj.CreateValueFromAddress( + f"node_{index}", node_addr, self._node_type + ) + # Return the 't' (value) member of the node + value = node.GetChildMemberWithName("t") + return value.CreateValueFromData( + f"[{index}]", value.GetData(), value.type + ) diff --git a/lldb_qt_formatters/qlist.py b/lldb_qt_formatters/qlist.py new file mode 100644 index 0000000..baf2311 --- /dev/null +++ b/lldb_qt_formatters/qlist.py @@ -0,0 +1,72 @@ +import lldb +from lldb import SBValue + + +def format_summary(valobj: SBValue, internal_dict, options): + provider = SyntheticChildrenProvider(valobj.GetNonSyntheticValue(), internal_dict) + return f"Size: {provider.size()}" + + +class SyntheticChildrenProvider: + def __init__(self, valobj, internal_dict): + self.valobj = valobj + self.element_type = self._get_element_type() + ptr_size = valobj.target.GetAddressByteSize() + # QList stores values directly in the void* slot when sizeof(T) <= sizeof(void*) + # and the type is trivially relocatable. For safety, we use size comparison. + self._is_large = self.element_type.GetByteSize() > ptr_size + + def _get_element_type(self): + type_name = self.valobj.type.GetUnqualifiedType().name + if type_name == "QStringList": + return self.valobj.target.FindFirstType("QString") + return self.valobj.type.GetUnqualifiedType().GetTemplateArgumentType(0) + + def size(self): + d_ptr = self.valobj.GetChildMemberWithName("d") + d = d_ptr.deref + begin = d.GetChildMemberWithName("begin").GetValueAsSigned() + end = d.GetChildMemberWithName("end").GetValueAsSigned() + return end - begin + + def update(self): + d_ptr = self.valobj.GetChildMemberWithName("d") + d = d_ptr.deref + self._begin = d.GetChildMemberWithName("begin").GetValueAsSigned() + end = d.GetChildMemberWithName("end").GetValueAsSigned() + self._size = end - self._begin + self._array = d.GetChildMemberWithName("array") + + def num_children(self): + return self._size + + def has_children(self): + return self._size > 0 + + @staticmethod + def get_child_index(name: str): + index = name.lstrip("[").rstrip("]") + return int(index) + + def get_child_at_index(self, index): + if index < 0 or index >= self._size: + return None + + ptr_size = self.valobj.target.GetAddressByteSize() + array_addr = self._array.GetValueAsUnsigned() + slot_addr = array_addr + (self._begin + index) * ptr_size + + if self._is_large: + # Slot contains a pointer to the value (Node->v pattern) + error = lldb.SBError() + ptr = self.valobj.process.ReadPointerFromMemory(slot_addr, error) + if error.Fail(): + return None + return self.valobj.CreateValueFromAddress( + f"[{index}]", ptr, self.element_type + ) + else: + # Value stored directly in the slot + return self.valobj.CreateValueFromAddress( + f"[{index}]", slot_addr, self.element_type + ) diff --git a/lldb_qt_formatters/qvariant.py b/lldb_qt_formatters/qvariant.py new file mode 100644 index 0000000..d52e078 --- /dev/null +++ b/lldb_qt_formatters/qvariant.py @@ -0,0 +1,157 @@ +from lldb import SBError, SBValue + + +# QMetaType::Type enum values (Qt5) +_METATYPE_NAMES = { + 0: "UnknownType", + 1: "Bool", + 2: "Int", + 3: "UInt", + 4: "LongLong", + 5: "ULongLong", + 6: "Double", + 7: "QChar", + 8: "QVariantMap", + 9: "QVariantList", + 10: "QString", + 11: "QStringList", + 12: "QByteArray", + 13: "QBitArray", + 14: "QDate", + 15: "QTime", + 16: "QDateTime", + 17: "QUrl", + 18: "QLocale", + 19: "QRect", + 20: "QRectF", + 21: "QSize", + 22: "QSizeF", + 23: "QLine", + 24: "QLineF", + 25: "QPoint", + 26: "QPointF", + 27: "QRegExp", + 28: "QRegularExpression", + 29: "QVariantHash", + 30: "QEasingCurve", + 31: "QUuid", + 32: "QModelIndex", + 33: "LastCoreType", + 64: "QFont", + 65: "QPixmap", + 66: "QBrush", + 67: "QColor", + 68: "QPalette", + 69: "QIcon", + 70: "QImage", + 71: "QPolygon", + 72: "QRegion", + 73: "QBitmap", + 74: "QCursor", + 75: "QKeySequence", + 76: "QPen", + 77: "QTextLength", + 78: "QTextFormat", + 79: "QMatrix", + 80: "QTransform", + 81: "QMatrix4x4", + 82: "QVector2D", + 83: "QVector3D", + 84: "QVector4D", + 85: "QQuaternion", + 86: "QPolygonF", +} + + +def _read_inline_value(d_member, type_id): + """Read a value stored inline in QVariant::Private::Data union.""" + data = d_member.GetChildMemberWithName("data") + + if type_id == 1: # Bool + val = data.GetChildMemberWithName("b") + return "true" if val.GetValueAsUnsigned() else "false" + elif type_id == 2: # Int + val = data.GetChildMemberWithName("i") + return str(val.GetValueAsSigned()) + elif type_id == 3: # UInt + val = data.GetChildMemberWithName("u") + return str(val.GetValueAsUnsigned()) + elif type_id == 4: # LongLong + val = data.GetChildMemberWithName("ll") + return str(val.GetValueAsSigned()) + elif type_id == 5: # ULongLong + val = data.GetChildMemberWithName("ull") + return str(val.GetValueAsUnsigned()) + elif type_id == 6: # Double + val = data.GetChildMemberWithName("d") + return val.GetValue() + elif type_id == 7: # QChar + val = data.GetChildMemberWithName("c") + ucs = val.GetValueAsUnsigned() + try: + return f"'{chr(ucs)}'" + except (ValueError, OverflowError): + return f"U+{ucs:04X}" + + return None + + +def _read_string_from_qvariant(valobj, d_member): + """Read a QString stored in a QVariant.""" + data = d_member.GetChildMemberWithName("data") + is_shared = d_member.GetChildMemberWithName("is_shared").GetValueAsUnsigned() + + if is_shared: + shared = data.GetChildMemberWithName("shared") + ptr = shared.GetChildMemberWithName("ptr") + str_addr = ptr.GetValueAsUnsigned() + else: + str_addr = data.GetChildMemberWithName("ptr").load_addr + + if str_addr == 0: + return '""' + + qstring_type = valobj.target.FindFirstType("QString") + if not qstring_type.IsValid(): + return None + + qstr = valobj.CreateValueFromAddress("tmp", str_addr, qstring_type) + d_ptr = qstr.GetChildMemberWithName("d") + arr = d_ptr.deref + offset = arr.GetChildMemberWithName("offset").GetValueAsUnsigned() + size = arr.GetChildMemberWithName("size").GetValueAsUnsigned() + + if size == 0: + return '""' + + error = SBError() + content = valobj.process.ReadMemory( + d_ptr.GetValueAsUnsigned() + offset, size * 2, error + ) + if error.Fail(): + return None + + return f'"{bytearray(content).decode("utf-16")}"' + + +def format_summary(valobj: SBValue, internal_dict, options): + d_member = valobj.GetChildMemberWithName("d") + type_id = d_member.GetChildMemberWithName("type").GetValueAsUnsigned() + + if type_id == 0: # UnknownType + return "Invalid" + + type_name = _METATYPE_NAMES.get(type_id, f"Type({type_id})") + + # Inline primitive types + inline_val = _read_inline_value(d_member, type_id) + if inline_val is not None: + return f"({type_name}) {inline_val}" + + # QString + if type_id == 10: + str_val = _read_string_from_qvariant(valobj, d_member) + if str_val: + return f"({type_name}) {str_val}" + + return f"({type_name})" diff --git a/lldb_qt_formatters/qvector.py b/lldb_qt_formatters/qvector.py new file mode 100644 index 0000000..2c5305c --- /dev/null +++ b/lldb_qt_formatters/qvector.py @@ -0,0 +1,44 @@ +from lldb import SBValue + + +def format_summary(valobj: SBValue, internal_dict, options): + provider = SyntheticChildrenProvider(valobj.GetNonSyntheticValue(), internal_dict) + return f"Size: {provider.size()}" + + +class SyntheticChildrenProvider: + def __init__(self, valobj, internal_dict): + self.valobj = valobj + self.element_type = valobj.type.GetUnqualifiedType().GetTemplateArgumentType(0) + + def size(self): + d_ptr = self.valobj.GetChildMemberWithName("d") + d = d_ptr.deref + return d.GetChildMemberWithName("size").GetValueAsSigned() + + def update(self): + d_ptr = self.valobj.GetChildMemberWithName("d") + d = d_ptr.deref + self._size = d.GetChildMemberWithName("size").GetValueAsSigned() + offset = d.GetChildMemberWithName("offset").GetValueAsUnsigned() + self._base_addr = d_ptr.GetValueAsUnsigned() + offset + + def num_children(self): + return self._size + + def has_children(self): + return self._size > 0 + + @staticmethod + def get_child_index(name: str): + index = name.lstrip("[").rstrip("]") + return int(index) + + def get_child_at_index(self, index): + if index < 0 or index >= self._size: + return None + element_size = self.element_type.GetByteSize() + addr = self._base_addr + index * element_size + return self.valobj.CreateValueFromAddress( + f"[{index}]", addr, self.element_type + ) diff --git a/test/conftest.py b/test/conftest.py index 7569b74..d43739f 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -28,7 +28,7 @@ def exe(tmp_path_factory): def lldb(request, exe): preamble = [ "command script import lldb_qt_formatters", - "breakpoint set --file main.cpp --line 14", + "breakpoint set --file main.cpp --line 125", "run" ] marker = request.node.get_closest_marker("lldb_script")