# Copyright (C) 2016 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0

import os
import codecs
import functools
import glob
import struct
import sys
import re
import time
import inspect
from utils import DisplayFormat, TypeCode

try:
    # That's only used in native combined debugging right now, so
    # we do not need to hard fail in cases of partial python installation
    # that will never use this.
    import json
except:
    print("Python module json not found. "
          "Native combined debugging might not work.")
    pass

try:
    # That fails on some QNX via Windows installations
    import base64
    def hexencode_(s):
        return base64.b16encode(s).decode('utf8')
except:
    def hexencode_(s):
        return ''.join(["%x" % c for c in s])

toInteger = int

class ReportItem():
    """
    Helper structure to keep temporary 'best' information about a value
    or a type scheduled to be reported. This might get overridden be
    subsequent better guesses during a putItem() run.
    """

    def __init__(self, value=None, encoding=None, priority=-100, length=None):
        self.value = value
        self.priority = priority
        self.encoding = encoding
        self.length = length

    def __str__(self):
        return 'Item(value: %s, encoding: %s, priority: %s, length: %s)' \
            % (self.value, self.encoding, self.priority, self.length)


class Children():
    def __init__(self, d, numChild=1, childType=None, childNumChild=None,
                 maxNumChild=None, addrBase=None, addrStep=None):
        self.d = d
        self.numChild = numChild
        self.childNumChild = childNumChild
        self.maxNumChild = maxNumChild
        if childType is None:
            self.childType = None
        else:
            self.childType = childType.name
            if not self.d.isCli:
                self.d.putField('childtype', self.childType)
            if childNumChild is not None:
                self.d.putField('childnumchild', childNumChild)
                self.childNumChild = childNumChild
        if addrBase is not None and addrStep is not None:
            self.d.put('addrbase="0x%x",addrstep="%d",' % (addrBase, addrStep))

    def __enter__(self):
        self.savedChildType = self.d.currentChildType
        self.savedChildNumChild = self.d.currentChildNumChild
        self.savedNumChild = self.d.currentNumChild
        self.savedMaxNumChild = self.d.currentMaxNumChild
        self.d.currentChildType = self.childType
        self.d.currentChildNumChild = self.childNumChild
        self.d.currentNumChild = self.numChild
        self.d.currentMaxNumChild = self.maxNumChild
        self.d.put(self.d.childrenPrefix)

    def __exit__(self, exType, exValue, exTraceBack):
        if exType is not None:
            if self.d.passExceptions:
                self.d.showException('CHILDREN', exType, exValue, exTraceBack)
            self.d.putSpecialValue('notaccessible')
            self.d.putNumChild(0)
        if self.d.currentMaxNumChild is not None:
            if self.d.currentMaxNumChild < self.d.currentNumChild:
                self.d.put('{name="<load more>",value="",type="",numchild="1"},')
        self.d.currentChildType = self.savedChildType
        self.d.currentChildNumChild = self.savedChildNumChild
        self.d.currentNumChild = self.savedNumChild
        self.d.currentMaxNumChild = self.savedMaxNumChild
        if self.d.isCli:
            self.d.put('\n' + '   ' * self.d.indent)
        self.d.put(self.d.childrenSuffix)
        return True


class SubItem():
    def __init__(self, d, component):
        self.d = d
        self.name = component
        self.iname = None

    def __enter__(self):
        self.d.enterSubItem(self)

    def __exit__(self, exType, exValue, exTraceBack):
        return self.d.exitSubItem(self, exType, exValue, exTraceBack)


class TopLevelItem(SubItem):
    def __init__(self, d, iname):
        self.d = d
        self.iname = iname
        self.name = None


class UnnamedSubItem(SubItem):
    def __init__(self, d, component):
        self.d = d
        self.iname = '%s.%s' % (self.d.currentIName, component)
        self.name = None


class DumperBase():
    @staticmethod
    def warn(message):
        print('bridgemessage={msg="%s"}' % message.replace('"', "'").replace('\\', '\\\\'))

    #@staticmethod
    def showException(self, msg, exType, exValue, exTraceback):
        self.warn('**** CAUGHT EXCEPTION: %s ****' % msg)
        try:
            import traceback
            for frame_desc in traceback.format_exception(exType, exValue, exTraceback):
                for line in frame_desc.split('\n'):
                    self.warn(line)
        except:
            pass

    def dump_location(self):
        import traceback
        from io import StringIO
        io = StringIO()
        traceback.print_stack(file=io)
        data = io.getvalue()
        self.warn('LOCATION:')
        for line in data.split('\n')[:-3]:
            self.warn(line)

    def __init__(self):
        self.isCdb = False
        self.isGdb = False
        self.isLldb = False
        self.isCli = False
        self.isDebugBuild = None

        # Later set, or not set:
        self.stringCutOff = 10000
        self.displayStringLimit = 100
        self.useTimeStamps = False

        self.output = []
        self.typesReported = {}
        self.typesToReport = {}
        self.qtNamespaceToReport = None
        self.qtCustomEventFunc = 0
        self.qtCustomEventPltFunc = 0
        self.qtPropertyFunc = 0
        self.passExceptions = False
        self.isTesting = False
        self.qtLoaded = False

        self.isBigEndian = False
        self.packCode = '<'
        self.byteorder = 'little'

        self.resetCaches()
        self.resetStats()

        self.childrenPrefix = 'children=['
        self.childrenSuffix = '],'

        self.dumpermodules = []

        # These are sticky for the session
        self.qtversion = None
        self.qtversionAtLeast6 = None
        self.qtnamespace = None

        self.init_type_cache()

        try:
            # Fails in the piping case
            self.dumpermodules = [
                os.path.splitext(os.path.basename(p))[0] for p in
                glob.glob(os.path.join(os.path.dirname(__file__), '*types.py'))
            ]
        except:
            pass

        # These values are never used, but the variables need to have
        # some value base for the swapping logic in Children.__enter__()
        # and Children.__exit__().
        self.currentIName = None
        self.currentValue = None
        self.currentType = None
        self.currentNumChild = None
        self.currentMaxNumChild = None
        self.currentPrintsAddress = True
        self.currentChildType = None
        self.currentChildNumChild = None
        self.register_known_simple_types()

    def setVariableFetchingOptions(self, args):
        self.last_args = args
        self.resultVarName = args.get('resultvarname', '')
        self.expandedINames = args.get('expanded', {})
        self.stringCutOff = int(args.get('stringcutoff', 10000))
        self.displayStringLimit = int(args.get('displaystringlimit', 100))
        self.typeformats = args.get('typeformats', {})
        self.formats = args.get('formats', {})
        self.watchers = args.get('watchers', {})
        self.useDynamicType = int(args.get('dyntype', '0'))
        self.useFancy = int(args.get('fancy', '0'))
        self.allowInferiorCalls = int(args.get('allowinferiorcalls', '0'))
        self.forceQtNamespace = int(args.get('forcens', '0'))
        self.passExceptions = int(args.get('passexceptions', '0'))
        self.isTesting = int(args.get('testing', '0'))
        self.showQObjectNames = int(args.get('qobjectnames', '1'))
        self.nativeMixed = int(args.get('nativemixed', '0'))
        self.autoDerefPointers = int(args.get('autoderef', '0'))
        self.useTimeStamps = int(args.get('timestamps', '0'))
        self.partialVariable = args.get('partialvar', '')
        self.uninitialized = args.get('uninitialized', [])

        self.uninitialized = list(map(lambda x: self.hexdecode(x), self.uninitialized))

        if self.qtversion is None:
            self.qtversion = args.get('qtversion', None)
            if self.qtversion == 0:
                self.qtversion = None
        if self.qtnamespace is None:
            self.qtnamespace = args.get('qtnamespace', None)

        #self.warn('NAMESPACE: "%s"' % self.qtNamespace())
        #self.warn('EXPANDED INAMES: %s' % self.expandedINames)
        #self.warn('WATCHERS: %s' % self.watchers)

    # Call this with 'py theDumper.profile1() from Creator
    def profile(self):
        '''Internal profiling'''
        import cProfile
        import visualize
        profiler = cProfile.Profile()
        profiler.enable()
        self.profiled_command()
        profiler.disable()
        visualize.profile_visualize(profiler.getstats())

    def profiled_command(self):
        args = self.last_args
        args['partialvar'] = ''
        self.fetchVariables(args)

    def extractQtVersion(self):
        # can be overridden in bridges
        pass

    def qtVersion(self):
        if self.qtversion:
            return self.qtversion

        #self.warn("ACCESSING UNKNOWN QT VERSION")
        self.qtversion = self.extractQtVersion()
        if self.qtversion:
            return self.qtversion

        #self.warn("EXTRACTING QT VERSION FAILED. GUESSING NOW.")
        if self.qtversionAtLeast6 is None or self.qtversionAtLeast6 is True:
            return 0x060602
        return 0x050f00

    def qtVersionAtLeast(self, version):
        # A hack to cover most of the changes from Qt 5 to 6
        if version == 0x60000 and self.qtversionAtLeast6 is not None:
            return self.qtversionAtLeast6
        return self.qtVersion() >= version

    def qtVersionPing(self, typeid, size_for_qt5=-1):
        # To be called from places where the type size is sufficient
        # to distinguish Qt 5.x and 6.x
        if size_for_qt5 == -1:
           size_for_qt5 = self.ptrSize()
        test_size = self.type_size(typeid)
        self.setQtVersionAtLeast6(test_size > size_for_qt5)

    def setQtVersionAtLeast6(self, is6):
        if self.qtversionAtLeast6 is None:
            #self.warn("SETTING Qt VERSION AT LEAST 6 TO %s" % is6)
            self.qtversionAtLeast6 = is6
            self.register_known_qt_types()
        #else:
        #   self.warn("QT VERSION ALREADY KNOWN")

    def qtNamespace(self):
        return '' if self.qtnamespace is None else self.qtnamespace

    def resetPerStepCaches(self):
        self.perStepCache = {}
        pass

    def resetCaches(self):
        # This is a cache mapping from 'type name' to 'display alternatives'.
        self.qqFormats = {'QVariant (QVariantMap)': [DisplayFormat.CompactMap]}

        # This is a cache of all known dumpers.
        self.qqDumpers = {}    # Direct type match
        self.qqDumpersEx = {}  # Using regexp

        # This is a cache of all dumpers that support writing.
        self.qqEditable = {}

        # This keeps canonical forms of the typenames, without array indices etc.
        self.cachedFormats = {}

        # Maps type names to static metaobjects. If a type is known
        # to not be QObject derived, it contains a 0 value.
        self.knownStaticMetaObjects = {}

        # A dictionary to serve as a per debugging step cache.
        # Cleared on each step over / into / continue.
        self.perStepCache = {}

        # A dictionary to serve as a general cache throughout the whole
        # debug session.
        self.generalCache = {}

        self.counts = {}
        self.timings = []
        self.expandableINames = set({})

    def resetStats(self):
        # Timing collection
        self.timings = []
        pass

    def dumpStats(self):
        msg = [self.counts, self.timings]
        self.resetStats()
        return msg

    def bump(self, key):
        if key in self.counts:
            self.counts[key] += 1
        else:
            self.counts[key] = 1

    def childRange(self):
        if self.currentMaxNumChild is None:
            return range(0, self.currentNumChild)
        return range(min(self.currentMaxNumChild, self.currentNumChild))

    def maxArrayCount(self):
        if self.currentIName in self.expandedINames:
            return self.expandedINames[self.currentIName]
        return 100

    def enterSubItem(self, item):
        if self.useTimeStamps:
            item.startTime = time.time()
        if not item.iname:
            item.iname = '%s.%s' % (self.currentIName, item.name)
        if not self.isCli:
            self.put('{')
            if isinstance(item.name, str):
                self.putField('name', item.name)
        else:
            self.indent += 1
            self.put('\n' + '   ' * self.indent)
            if isinstance(item.name, str):
                self.put(item.name + ' = ')
        item.savedIName = self.currentIName
        item.savedValue = self.currentValue
        item.savedType = self.currentType
        self.currentIName = item.iname
        self.currentValue = ReportItem()
        self.currentType = ReportItem()

    def exitSubItem(self, item, exType, exValue, exTraceBack):
        #self.warn('CURRENT VALUE: %s: %s %s' %
        # (self.currentIName, self.currentValue, self.currentType))
        if exType is not None:
            if self.passExceptions:
                self.showException('SUBITEM', exType, exValue, exTraceBack)
            self.putSpecialValue('notaccessible')
            self.putNumChild(0)
        if not self.isCli:
            try:
                if self.currentType.value:
                    typename = self.currentType.value
                    if len(typename) > 0 and typename != self.currentChildType:
                        self.putField('type', typename)
                if self.currentValue.value is None:
                    self.put('value="",encoding="notaccessible",numchild="0",')
                else:
                    if self.currentValue.encoding is not None:
                        self.put('valueencoded="%s",' % self.currentValue.encoding)
                    if self.currentValue.length:
                        self.put('valuelen="%s",' % self.currentValue.length)
                    self.put('value="%s",' % self.currentValue.value)
            except:
                pass
            if self.useTimeStamps:
                self.put('time="%s",' % (time.time() - item.startTime))
            self.put('},')
        else:
            self.indent -= 1
            try:
                if self.currentType.value:
                    typename = self.currentType.value
                    self.put('<%s> = {' % typename)

                if self.currentValue.value is None:
                    self.put('<not accessible>')
                else:
                    value = self.currentValue.value
                    if self.currentValue.encoding == 'latin1':
                        value = self.hexdecode(value)
                    elif self.currentValue.encoding == 'utf8':
                        value = self.hexdecode(value)
                    elif self.currentValue.encoding == 'utf16':
                        b = bytes(bytearray.fromhex(value))
                        value = codecs.decode(b, 'utf-16')
                    self.put('"%s"' % value)
                    if self.currentValue.length:
                        self.put('...')

                if self.currentType.value:
                    self.put('}')
            except:
                pass
        self.currentIName = item.savedIName
        self.currentValue = item.savedValue
        self.currentType = item.savedType
        return True

    def stripForFormat(self, typename):
        if not isinstance(typename, str):
            raise RuntimeError('Expected string in stripForFormat(), got %s' % type(typename))
        if typename in self.cachedFormats:
            return self.cachedFormats[typename]
        stripped = ''
        inArray = 0
        for c in typename:
            if c == '<':
                break
            if c == ' ':
                continue
            if c == '[':
                inArray += 1
            elif c == ']':
                inArray -= 1
            if inArray and ord(c) >= 48 and ord(c) <= 57:
                continue
            stripped += c
        self.cachedFormats[typename] = stripped
        return stripped

    def templateArgument(self, typeobj, index):
        return self.type_template_argument(typeobj.typeid, index)

    def intType(self):
        return self.type_for_int

    def charType(self):
        return self.type_for_char

    def ptrSize(self):
        result = self.lookupType('void*').size()
        self.ptrSize = lambda: result
        return result

    def lookupType(self, typename):
        if not isinstance(typename, str):
            raise RuntimeError('ARG ERROR FOR lookupType, got %s' % type(typename))

        typeid = self.typeid_for_string(typename)
        native_type = self.type_nativetype_cache.get(typeid)
        if native_type is None:
            native_type = self.lookupNativeType(typename)
            if native_type is None:
                #sCANNOT DETERMINE SIZE FOR TYelf.dump_location()
                #self.dump_location()
                self.warn("TYPEIDS: %s" % self.typeid_cache)
                self.warn("COULD NOT FIND TYPE '%s'" % typename)
                return None

        self.type_nativetype_cache[typeid] = native_type
        typeid = self.from_native_type(native_type)
        if typeid == 0:
            return None
        return self.Type(self, typeid)

    def register_type(self, name, code, size, enc=None):
        typeid = self.typeid_for_string(name)
        self.type_code_cache[typeid] = code
        self.type_size_cache[typeid] = size
        self.type_alignment_cache[typeid] = size
        if enc is not None:
            self.type_encoding_cache[typeid] = enc
        return typeid

    def register_int(self, name, size, enc=None):
        typeid = self.typeid_for_string(name)
        self.type_code_cache[typeid] = TypeCode.Integral
        self.type_size_cache[typeid] = size
        self.type_alignment_cache[typeid] = size
        if enc is not None:
            self.type_encoding_cache[typeid] = enc
        return typeid

    def register_enum(self, name, size):
        typeid = self.typeid_for_string(name)
        self.type_code_cache[typeid] = TypeCode.Enum
        self.type_size_cache[typeid] = size
        self.type_alignment_cache[typeid] = size
        return typeid

    def register_typedef(self, name, target_typeid):
        typeid = self.typeid_for_string(name)
        self.type_code_cache[typeid] = TypeCode.Typedef
        self.type_target_cache[typeid] = target_typeid
        self.type_size_cache[typeid] = self.type_size_cache[target_typeid]
        self.type_alignment_cache[typeid] = self.type_alignment_cache[target_typeid]
        return typeid

    def register_struct(self, name, p5=0, p6=0, s=0, qobject_based=False):
        # p5 = n  -> n * ptrsize for Qt 5
        # p6 = n  -> n * ptrsize for Qt 6
        if self.qtversionAtLeast6 is None:
            self.warn("TOO EARLY TO GUESS QT VERSION")
            size = 8 * p6 + s
        elif self.qtversionAtLeast6 is True:
            size = 8 * p6 + s
        else:
            size = 8 * p5 + s
        typeid = self.typeid_for_string(name)
        self.type_code_cache[typeid] = TypeCode.Struct
        self.type_size_cache[typeid] = size
        self.type_qobject_based_cache[typeid] = qobject_based
        self.type_alignment_cache[typeid] = 8
        return typeid

    def register_known_simple_types(self):
        typeid = 0
        self.typeid_cache[''] = typeid
        self.type_code_cache[typeid] = TypeCode.Void
        self.type_name_cache[typeid] = '<Error>'
        self.type_size_cache[typeid] = 1

        typeid_char = self.register_int('char', 1, 'uint:1')
        self.type_for_char = self.Type(self, typeid_char)
        self.register_int('signed char', 1, 'int:1')
        self.register_int('unsigned char', 1, 'uint:1')
        self.register_int('bool', 1, 'uint:1')
        self.register_int('char8_t', 1, 'uint:1')
        self.register_int('int8_t', 1, 'int:1')
        self.register_int('uint8_t', 1, 'uint:1')
        self.register_int('qint8', 1, 'int:1')
        self.register_int('quint8', 1, 'uint:1')

        self.register_int('short', 2, 'int:2')
        self.register_int('short int', 2, 'int:2')
        self.register_int('signed short', 2, 'int:2')
        self.register_int('signed short int', 2, 'int:2')
        typeid_unsigned_short = \
        self.register_int('unsigned short', 2, 'uint:2')
        self.register_int('unsigned short int', 2, 'uint:2')
        self.register_int('char16_t', 2, 'uint:2')
        self.register_int('int16_t', 2, 'int:2')
        self.register_int('uint16_t', 2, 'uint:2')
        self.register_int('qint16', 2, 'int:2')
        self.register_int('quint16', 2, 'uint:2')

        typeid_int = self.register_type('int', 4, 'int:4')
        self.type_for_int = self.Type(self, typeid_int)
        self.register_int('int', 4, 'int:4')
        self.register_int('signed int', 4, 'int:4')
        self.register_int('unsigned int', 4, 'uint:4')
        self.register_int('char32_t', 4, 'int:4')
        self.register_int('int32_t', 4, 'int:4')
        self.register_int('uint32_t', 4, 'uint:4')
        self.register_int('qint32', 4, 'int:4')
        self.register_int('quint32', 4, 'uint:4')

        self.register_int('long long', 8, 'int:8')
        self.register_int('signed long long', 8, 'int:8')
        self.register_int('unsigned long long', 8, 'uint:8')
        self.register_int('int64_t', 8, 'int:8')
        self.register_int('uint64_t', 8, 'uint:8')
        self.register_int('qint64', 8, 'int:8')
        self.register_int('quint64', 8, 'uint:8')

        self.register_type('float', TypeCode.Float, 4, 'float:4')
        typeid_double = self.register_type('double', TypeCode.Float, 8, 'float:8')
        self.register_typedef('qreal', typeid_double)

        typeid_qchar = self.register_type('@QChar', TypeCode.Struct, 2, 'uint:2')
        #self.type_fields_cache[typeid_qchar] = [
        #    self.Field(name='ucs', typeid=typeid_unsigned_short, bitsize=16, bitpos=0)]

        self.register_enum('@Qt::ItemDataRole', 4)

    def register_known_qt_types(self):
        #self.warn("REGISTERING KNOWN QT TYPES NOW")
        self.register_struct('@QObject', p5=2, p6=2, qobject_based=True)
        self.register_struct('@QObjectPrivate', p5=10, p6=10) # FIXME: Not exact

        self.register_struct('@QByteArray', p5=1, p6=3)
        self.register_struct('@QString', p5=1, p6=3)
        self.register_struct('@QStandardItemData', p5=3, p6=5)
        self.register_struct('@QVariant', p5=2, p6=4)
        self.register_struct('@QXmlAttributes::Attribute', p5=4, p6=12)

        self.register_struct('@QList<@QObject*>', p5=1, p6=3)
        self.register_struct('@QList<@QStandardItemData>', p5=1, p6=3)
        self.register_struct('@QList<@QRect>', p5=1, p6=3)

        typeid_string_list = self.register_struct('@QList<@QString>', p5=1, p6=3)
        self.register_typedef('@QStringList', typeid_string_list)

        typeid_var_list = self.register_struct('@QList<@QVariant>', p5=1, p6=3)
        self.register_typedef('@QVariantList', typeid_var_list)

        typeid_var_map = self.register_struct('@QMap<@QString, @QVariant>', p5=1, p6=1)
        self.register_typedef('@QVariantMap', typeid_var_map)

        typeid_var_hash = self.register_struct('@QHash<@QString, @QVariant>', p5=1, p6=1)
        self.register_typedef('@QVariantHash', typeid_var_hash)

        self.register_struct('@QPoint', s=8)
        self.register_struct('@QPointF', s=16)
        self.register_struct('@QLine', s=16)
        self.register_struct('@QLineF', s=32)

        # FIXME: Comment out for production, see [MARK_A]
        name1 = 'std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>'
        self.register_struct(name1, p6=4)

    def nativeDynamicType(self, address, baseType):
        return baseType  # Override in backends.

    def fill_template_parameters_manually(self, typeid):
        typename = self.type_name(typeid)
        # Undo id mangling for template typedefs. Relevant for QPair.
        if typename.endswith('}'):
            typename = typename[typename.find('{') + 1 : -1]

        if not typename.endswith('>'):
            return

        targs = []

        def push(inner):
            # Handle local struct definitions like QList<main(int, char**)::SomeStruct>
            inner = inner.strip()[::-1]
            p = inner.find(')::')
            if p > -1:
                inner = inner[p + 3:].strip()
            if inner.startswith('const '):
                inner = inner[6:].strip()
            if inner.endswith(' const'):
                inner = inner[:-6].strip()
            #self.warn("FOUND: %s" % inner)
            targs.append(inner)

        #self.warn("SPLITTING %s" % typename)
        level = 0
        inner = ''
        for c in typename[::-1]:  # Reversed...
            #self.warn("C: %s" % c)
            if c == '>':
                if level > 0:
                    inner += c
                level += 1
            elif c == '<':
                level -= 1
                if level > 0:
                    inner += c
                else:
                    push(inner)
                    inner = ''
                    break
            elif c == ',':
                #self.warn('c: %s level: %s' % (c, level))
                if level == 1:
                    push(inner)
                    inner = ''
                else:
                    inner += c
            else:
                inner += c

        #self.warn("TARGS: %s %s" % (typename, targs))
        idx = 0
        for item in targs[::-1]:
            if len(item) == 0:
                continue
            if item == "false": # Triggered in StdTuple dumper
                self.type_template_arguments_cache[(typeid, idx)] = False
            elif item == "true":
                self.type_template_arguments_cache[(typeid, idx)] = True
            else:
                c = ord(item[0])
                if c in (45, 46) or (c >= 48 and c < 58):  # '-', '.' or digit.
                    if '.' in item:
                        self.type_template_arguments_cache[(typeid, idx)] = float(item)
                    else:
                        if item.endswith('l'):
                            item = item[:-1]
                        if item.endswith('u'):
                            item = item[:-1]
                        val = int(item)
                        if val > 0x80000000:
                            val -= 0x100000000
                        self.type_template_arguments_cache[(typeid, idx)] = val
                else:
                    targ = self.Type(self, self.create_typeid_from_name(item))
                    self.type_template_arguments_cache[(typeid, idx)] = targ
            idx += 1
            #self.warn('MANUAL: %s %s' % (type_name, targs))

    # Hex decoding operating on str, return str.
    @staticmethod
    def hexdecode(s, encoding='utf8'):
        return bytes.fromhex(s).decode(encoding)

    # Hex encoding operating on str or bytes, return str.
    @staticmethod
    def hexencode(s):
        if s is None:
            s = ''
        if isinstance(s, str):
            s = s.encode('utf8')
        return hexencode_(s)

    def isQt3Support(self):
        # assume no Qt 3 support by default
        return False

    # Clamps length to limit.
    def computeLimit(self, length, limit=0):
        if limit == 0:
            limit = self.displayStringLimit
        if limit is None or length <= limit:
            return length
        return limit

    def vectorData(self, value):
        if self.qtVersionAtLeast(0x060000):
            data, length, alloc = self.qArrayData(value)
        elif self.qtVersionAtLeast(0x050000):
            vector_data_ptr = self.extractPointer(value)
            if self.ptrSize() == 4:
                (ref, length, alloc, offset) = self.split('IIIp', vector_data_ptr)
            else:
                (ref, length, alloc, pad, offset) = self.split('IIIIp', vector_data_ptr)
            alloc = alloc & 0x7ffffff
            data = vector_data_ptr + offset
        else:
            vector_data_ptr = self.extractPointer(value)
            (ref, alloc, length) = self.split('III', vector_data_ptr)
            data = vector_data_ptr + 16
        self.check(0 <= length and length <= alloc and alloc <= 1000 * 1000 * 1000)
        return data, length

    def qArrayData(self, value):
        if self.qtVersionAtLeast(0x60000):
            dd, data, length = self.split('ppp', value)
            if dd:
                _, _, alloc = self.split('iip', dd)
            else: # fromRawData
                alloc = length
            return data, length, alloc
        return self.qArrayDataHelper(self.extractPointer(value))

    def qArrayDataHelper(self, array_data_ptr):
        # array_data_ptr is what is e.g. stored in a QByteArray's d_ptr.
        if self.qtVersionAtLeast(0x050000):
            # QTypedArray:
            # - QtPrivate::RefCount ref
            # - int length
            # - uint alloc : 31, capacityReserved : 1
            # - qptrdiff offset
            (ref, length, alloc, offset) = self.split('IIpp', array_data_ptr)
            alloc = alloc & 0x7ffffff
            data = array_data_ptr + offset
            if self.ptrSize() == 4:
                data = data & 0xffffffff
            else:
                data = data & 0xffffffffffffffff
        elif self.qtVersionAtLeast(0x040000):
            # Data:
            # - QBasicAtomicInt ref;
            # - int alloc, length;
            # - [padding]
            # - char *data;
            if self.ptrSize() == 4:
                (ref, alloc, length, data) = self.split('IIIp', array_data_ptr)
            else:
                (ref, alloc, length, pad, data) = self.split('IIIIp', array_data_ptr)
        else:
            # Data:
            # - QShared count;
            # - QChar *unicode
            # - char *ascii
            # - uint len: 30
            (dummy, dummy, dummy, length) = self.split('IIIp', array_data_ptr)
            length = self.extractInt(array_data_ptr + 3 * self.ptrSize()) & 0x3ffffff
            alloc = length  # pretend.
            data = self.extract_pointer_at_address(array_data_ptr + self.ptrSize())
        return data, length, alloc

    def encodeStringHelper(self, value, limit):
        data, length, alloc = self.qArrayData(value)
        if alloc != 0:
            self.check(0 <= length and length <= alloc and alloc <= 100 * 1000 * 1000)
        shown = self.computeLimit(2 * length, 2 * limit)
        return length, self.readMemory(data, shown)

    def encodeByteArrayHelper(self, value, limit):
        data, length, alloc = self.qArrayData(value)
        if alloc != 0:
            self.check(0 <= length and length <= alloc and alloc <= 100 * 1000 * 1000)
        shown = self.computeLimit(length, limit)
        return length, self.readMemory(data, shown)

    def putCharArrayValue(self, data, length, charSize,
                          displayFormat=DisplayFormat.Automatic):
        shown = self.computeLimit(length, self.displayStringLimit)
        mem = self.readMemory(data, shown * charSize)
        if charSize == 1:
            if displayFormat in (DisplayFormat.Latin1String, DisplayFormat.SeparateLatin1String):
                encodingType = 'latin1'
            else:
                encodingType = 'utf8'
            #childType = 'char'
        elif charSize == 2:
            encodingType = 'utf16'
            #childType = 'short'
        else:
            encodingType = 'ucs4'
            #childType = 'int'

        self.putValue(mem, encodingType, length=length)

        if displayFormat in (
                DisplayFormat.SeparateLatin1String,
                DisplayFormat.SeparateUtf8String,
                DisplayFormat.Separate):
            shown = self.computeLimit(length, 100000)
            self.putDisplay(encodingType + ':separate', self.readMemory(data, shown))

    def putCharArrayHelper(self, data, size, charType,
                           displayFormat=DisplayFormat.Automatic,
                           makeExpandable=True):
        charSize = charType.size()
        self.putCharArrayValue(data, size, charSize, displayFormat)

        if makeExpandable:
            self.putNumChild(size)
            if self.isExpanded():
                with Children(self):
                    for i in range(size):
                        self.putSubItem(size, self.createValueFromAddress(data + i * charSize, charType))

    def readMemory(self, addr, size):
        return self.hexencode(bytes(self.readRawMemory(addr, size)))

    def encodeByteArray(self, value, limit=0):
        _, data = self.encodeByteArrayHelper(value, limit)
        return data

    def putByteArrayValue(self, value):
        length, data = self.encodeByteArrayHelper(value, self.displayStringLimit)
        self.putValue(data, 'latin1', length=length)

    def encodeString(self, value, limit=0):
        _, data = self.encodeStringHelper(value, limit)
        return data

    def encodedUtf16ToUtf8(self, s):
        return ''.join([chr(int(s[i:i + 2], 16)) for i in range(0, len(s), 4)])

    def encodeStringUtf8(self, value, limit=0):
        return self.encodedUtf16ToUtf8(self.encodeString(value, limit))

    def stringData(self, value): # -> (data, size, alloc)
        return self.qArrayData(value)

    def putStringValue(self, value):
        length, data = self.encodeStringHelper(value, self.displayStringLimit)
        self.putValue(data, 'utf16', length=length)

    def putPtrItem(self, name, value):
        with SubItem(self, name):
            self.putValue('0x%x' % value)
            self.putType('void*')

    def putIntItem(self, name, value):
        with SubItem(self, name):
            if isinstance(value, self.Value):
                self.putValue(value.display())
            else:
                self.putValue(value)
            self.putType('int')

    def putEnumItem(self, name, ival, typish):
        val = self.Value(self)
        val.ldata = ival
        val.typeid = self.create_typeid(typish)
        with SubItem(self, name):
            self.putItem(val)

    def putBoolItem(self, name, value):
        with SubItem(self, name):
            self.putValue(value)
            self.putType('bool')

    def putPairItem(self, index, pair, keyName='first', valueName='second'):
        with SubItem(self, index):
            self.putPairContents(index, pair, keyName, valueName)

    def putPairContents(self, index, pair, kname, vname):
        with Children(self):
            first, second = pair if isinstance(pair, tuple) else pair.members(False)
            key = self.putSubItem(kname, first)
            value = self.putSubItem(vname, second)
        if self.isCli:
            self.putEmptyValue()
        else:
            if index is not None:
                self.putField('keyprefix', '[%s] ' % index)
            self.putField('key', key.value)
            if key.encoding is not None:
                self.putField('keyencoded', key.encoding)
            self.putValue(value.value, value.encoding)

    def putEnumValue(self, ival, vals):
        nice = vals.get(ival, None)
        display = ('%d' % ival) if nice is None else ('%s (%d)' % (nice, ival))
        self.putValue(display)

    def putCallItem(self, name, rettype, value, func, *args):
        with SubItem(self, name):
            try:
                result = self.callHelper(rettype, value, func, args)
            except Exception as error:
                if self.passExceptions:
                    raise error
                children = [('error', error)]
                self.putSpecialValue("notcallable", children=children)
            else:
                if result is None:
                    self.putSpecialValue("notcallable")
                else:
                    self.putItem(result)

    def call(self, rettype, value, func, *args):
        return self.callHelper(rettype, value, func, args)

    def putAddress(self, address):
        if address is not None and not self.isCli:
            self.put('address="0x%x",' % address)

    def putPlainChildren(self, value, dumpBase=True):
        self.putExpandable()
        if self.isExpanded():
            self.putEmptyValue(-99)
            with Children(self):
                self.putFields(value, dumpBase)

    def putNamedChildren(self, values, names):
        self.putEmptyValue(-99)
        self.putExpandable()
        if self.isExpanded():
            with Children(self):
                for n, v in zip(names, values):
                    self.putSubItem(n, v)

    def prettySymbolByAddress(self, address):
        return '0x%x' % address

    def putSymbolValue(self, address):
        self.putValue(self.prettySymbolByAddress(address))

    def putVTableChildren(self, value, itemCount):
        p = self.value_as_address(value)
        entry_typeid = self.create_pointer_typeid(self.create_typeid('void'))
        for i in range(itemCount):
            deref = self.extract_pointer_at_address(p)
            if deref == 0:
                itemCount = i
                break
            with SubItem(self, i):
                val = self.Value(self)
                val.ldata = deref
                val.typeid = entry_typeid
                self.putItem(val)
                p += self.ptrSize()
        return itemCount

    def putFields(self, value, dumpBase=True):
        baseIndex = 0
        for item in value.members(True):
            if item.name is not None:
                if (item.name.startswith('_vptr.')
                        or item.name.startswith('_vptr$')
                        or item.name.startswith('__vfptr')):
                    with SubItem(self, '[vptr]'):
                        # int (**)(void)
                        self.putType(' ')
                        self.putSortGroup(20)
                        self.putValue(item.name)
                        n = 100
                        if self.isExpanded():
                            with Children(self):
                                n = self.putVTableChildren(item, n)
                        self.putNumChild(n)
                    continue

            if item.isBaseClass and dumpBase:
                baseIndex += 1
                # We cannot use nativeField.name as part of the iname as
                # it might contain spaces and other strange characters.
                with UnnamedSubItem(self, "@%d" % baseIndex):
                    self.putField('iname', self.currentIName)
                    self.putField('name', '[%s]' % item.name)
                    if not self.isCli:
                        self.putSortGroup(1000 - baseIndex)
                        self.putAddress(item.address())
                    self.putItem(item)
                continue

            with SubItem(self, item.name):
                self.putItem(item)

    def putExpandable(self):
        self.putNumChild(1)
        self.expandableINames.add(self.currentIName)
        if self.isCli:
            self.putValue('{...}', -99)

    def putMembersItem(self, value, sortorder=10):
        with SubItem(self, '[members]'):
            self.putSortGroup(sortorder)
            self.putPlainChildren(value)

    def put(self, stuff):
        self.output.append(stuff)

    def takeOutput(self):
        res = ''.join(self.output)
        self.output = []
        return res

    def check(self, exp):
        if not exp:
            self.warn('Check failed: %s' % exp)
            #self.dump_location()
            raise RuntimeError('Check failed: %s' % exp)

    def check_typeid(self, typeid):
        if not isinstance(typeid, int):
            raise RuntimeError('WRONG TYPE FOR TYPEID: %s %s' % (str(typeid), type(typeid)))

    def checkRef(self, ref):
        # Assume there aren't a million references to any object.
        self.check(ref >= -1)
        self.check(ref < 1000000)

    def checkIntType(self, thing):
        if not isinstance(thing, int):
            raise RuntimeError('Expected an integral value, got %s' % type(thing))

    def readToFirstZero(self, base, typesize, maximum):
        self.checkIntType(base)
        self.checkIntType(typesize)
        self.checkIntType(maximum)

        code = self.packCode + (None, 'b', 'H', None, 'I')[typesize]
        #blob = self.readRawMemory(base, 1)
        blob = bytes()
        while maximum > 1:
            try:
                blob = self.readRawMemory(base, maximum)
                break
            except:
                maximum = int(maximum / 2)
                self.warn('REDUCING READING MAXIMUM TO %s' % maximum)

        #self.warn('BASE: 0x%x TSIZE: %s MAX: %s' % (base, typesize, maximum))
        for i in range(0, maximum, typesize):
            t = struct.unpack_from(code, blob, i)[0]
            if t == 0:
                return 0, i, self.hexencode(blob[:i])

        # Real end is unknown.
        return -1, maximum, self.hexencode(blob[:maximum])

    def encodeCArray(self, p, typesize, limit):
        length, shown, blob = self.readToFirstZero(p, typesize, limit)
        return length, blob

    def putItemCount(self, count, maximum=1000000000):
        # This needs to override the default value, so don't use 'put' directly.
        if count > maximum:
            self.putSpecialValue('minimumitemcount', maximum)
        else:
            self.putSpecialValue('itemcount', count)
        self.putNumChild(count)

    def resultToMi(self, value):
        if isinstance(value, bool):
            return '"%d"' % int(value)
        if isinstance(value, dict):
            return '{' + ','.join(['%s=%s' % (k, self.resultToMi(v))
                                   for (k, v) in list(value.items())]) + '}'
        if isinstance(value, list):
            return '[' + ','.join([self.resultToMi(k)
                                   for k in value]) + ']'
        return '"%s"' % value

    def variablesToMi(self, value, prefix):
        if isinstance(value, bool):
            return '"%d"' % int(value)
        if isinstance(value, dict):
            pairs = []
            for (k, v) in list(value.items()):
                if k == 'iname':
                    if v.startswith('.'):
                        v = '"%s%s"' % (prefix, v)
                    else:
                        v = '"%s"' % v
                else:
                    v = self.variablesToMi(v, prefix)
                pairs.append('%s=%s' % (k, v))
            return '{' + ','.join(pairs) + '}'
        if isinstance(value, list):
            index = 0
            pairs = []
            for item in value:
                if item.get('type', '') == 'function':
                    continue
                name = item.get('name', '')
                if len(name) == 0:
                    name = str(index)
                    index += 1
                pairs.append((name, self.variablesToMi(item, prefix)))
            pairs.sort(key=lambda pair: pair[0])
            return '[' + ','.join([pair[1] for pair in pairs]) + ']'
        return '"%s"' % value

    def filterPrefix(self, prefix, items):
        return [i[len(prefix):] for i in items if i.startswith(prefix)]

    def tryFetchInterpreterVariables(self, args):
        if not int(args.get('nativemixed', 0)):
            return (False, '')
        context = args.get('context', '')
        if not len(context):
            return (False, '')

        expanded = args.get('expanded')
        args['expanded'] = self.filterPrefix('local', expanded)

        res = self.sendInterpreterRequest('variables', args)
        if not res:
            return (False, '')

        reslist = []
        for item in res.get('variables', {}):
            if 'iname' not in item:
                item['iname'] = '.' + item.get('name')
            reslist.append(self.variablesToMi(item, 'local'))

        watchers = args.get('watchers', None)
        if watchers:
            toevaluate = []
            name2expr = {}
            seq = 0
            for watcher in watchers:
                expr = self.hexdecode(watcher.get('exp'))
                name = str(seq)
                toevaluate.append({'name': name, 'expression': expr})
                name2expr[name] = expr
                seq += 1
            args['expressions'] = toevaluate

            args['expanded'] = self.filterPrefix('watch', expanded)
            del args['watchers']
            res = self.sendInterpreterRequest('expressions', args)

            if res:
                for item in res.get('expressions', {}):
                    name = item.get('name')
                    iname = 'watch.' + name
                    expr = name2expr.get(name)
                    item['iname'] = iname
                    item['wname'] = self.hexencode(expr)
                    item['exp'] = expr
                    reslist.append(self.variablesToMi(item, 'watch'))

        return (True, 'data=[%s]' % ','.join(reslist))

    def putField(self, name, value):
        self.put('%s="%s",' % (name, value))

    def putType(self, typish, priority=0):
        # Higher priority values override lower ones.
        if priority >= self.currentType.priority:
            if isinstance(typish, str):
                self.currentType.value = typish
            elif isinstance(typish, int):
                self.currentType.value = self.type_name(typish)
            elif isinstance(typish, self.Type):
                self.currentType.value = typish.name
            else:
                self.currentType.value = str(type(typish))
            self.currentType.priority = priority

    def putValue(self, value, encoding=None, priority=0, length=None):
        # Higher priority values override lower ones.
        # length = None indicates all data is available in value,
        # otherwise it's the true length.
        if priority >= self.currentValue.priority:
            self.currentValue = ReportItem(value, encoding, priority, length)

    def putSpecialValue(self, encoding, value='', children=None):
        self.putValue(value, encoding)
        if children is not None:
            self.putExpandable()
            if self.isExpanded():
                with Children(self):
                    for name, value in children:
                        with SubItem(self, name):
                            self.putValue(str(value).replace('"', '$'))

    def putEmptyValue(self, priority=-10):
        if priority >= self.currentValue.priority:
            self.currentValue = ReportItem('', None, priority, None)

    def putName(self, name):
        self.putField('name', name)

    def putBetterType(self, typish):
        if isinstance(typish, ReportItem):
            self.currentType.value = typish.value
        elif isinstance(typish, str):
            self.currentType.value = typish.replace('@', self.qtNamespace())
        else:
            self.currentType.value = typish.name
        self.currentType.priority += 1

    def putNoType(self):
        # FIXME: replace with something that does not need special handling
        # in SubItem.__exit__().
        self.putBetterType(' ')

    def putInaccessible(self):
        #self.putBetterType(' ')
        self.putNumChild(0)
        self.currentValue.value = None

    def putNamedSubItem(self, component, value, name):
        with SubItem(self, component):
            self.putName(name)
            self.putItem(value)

    def isExpanded(self):
        #self.warn('IS EXPANDED: %s in %s: %s' % (self.currentIName,
        #    self.expandedINames, self.currentIName in self.expandedINames))
        return self.currentIName in self.expandedINames

    def mangleName(self, typename):
        return '_ZN%sE' % ''.join(map(lambda x: '%d%s' % (len(x), x),
                                      typename.split('::')))

    def arrayItemCountFromTypeName(self, typename, fallbackMax=1):
        itemCount = typename[typename.find('[') + 1:typename.find(']')]
        return int(itemCount) if itemCount else fallbackMax

    def putCStyleArray(self, value):
        arrayType = value.type
        innerType = arrayType.target()
        #self.warn("ARRAY TYPE: %s" % arrayType)
        #self.warn("INNER TYPE: %s" % innerType)

        address = value.address()
        if address:
            self.putValue('@0x%x' % address, priority=-1)
        else:
            self.putEmptyValue()
        self.putType(arrayType)

        displayFormat = self.currentItemFormat()
        arrayByteSize = arrayType.size()
        n = self.arrayItemCountFromTypeName(value.type.name, 100)
        if arrayByteSize == 0:
            # This should not happen. But it does, see QTCREATORBUG-14755.
            # GDB/GCC produce sizeof == 0 for QProcess arr[3]
            # And in the Nim string dumper.
            arrayByteSize = n * innerType.size()
        elif not self.isCdb:
            # Do not check the inner type size for cdb since this requires a potentially expensive
            # type lookup
            n = arrayByteSize // innerType.size()

        p = value.address()
        if displayFormat != DisplayFormat.Raw and p:
            if innerType.name in (
                'char',
                'int8_t',
                'qint8',
                'wchar_t',
                'unsigned char',
                'uint8_t',
                'quint8',
                'signed char',
                'CHAR',
                'WCHAR',
                'char8_t',
                'char16_t',
                'char32_t'
            ):
                self.putCharArrayHelper(p, n, innerType, self.currentItemFormat(),
                                        makeExpandable=False)
            else:
                self.tryPutSimpleFormattedPointer(p, arrayType, innerType,
                                                  displayFormat, arrayByteSize)
        self.putNumChild(n)

        if self.isExpanded():
            self.putArrayData(p, n, innerType)

        self.putPlotDataHelper(p, n, innerType)

    def cleanAddress(self, addr):
        if addr is None:
            return '<no address>'
        return '0x%x' % int(hex(addr), 16)

    def stripNamespaceFromType(self, typename):
        ns = self.qtNamespace()
        if len(ns) > 0 and typename.startswith(ns):
            typename = typename[len(ns):]
        # self.warn( 'stripping %s' % typename )
        lvl = 0
        pos = None
        stripChunks = []
        sz = len(typename)
        for index in range(0, sz):
            s = typename[index]
            if s == '<':
                lvl += 1
                if lvl == 1:
                    pos = index
                continue
            elif s == '>':
                lvl -= 1
                if lvl < 0:
                    raise RuntimeError("Unbalanced '<' in type, @index %d" % index)
                if lvl == 0:
                    stripChunks.append((pos, index + 1))
        if lvl != 0:
            raise RuntimeError("unbalanced at end of type name")
        for (f, l) in reversed(stripChunks):
            typename = typename[:f] + typename[l:]
        return typename

    def tryPutPrettyItem(self, typename, value):
        value.check()
        if self.useFancy and self.currentItemFormat() != DisplayFormat.Raw:
            self.putType(typename)

            nsStrippedType = self.stripNamespaceFromType(typename)\
                .replace('::', '__')

            # Strip leading 'struct' for C structs
            if nsStrippedType.startswith('struct '):
                nsStrippedType = nsStrippedType[7:]

            #self.warn('STRIPPED: %s' % nsStrippedType)
            # The following block is only needed for D.
            if nsStrippedType.startswith('_A'):
                # DMD v2.058 encodes string[] as _Array_uns long long.
                # With spaces.
                if nsStrippedType.startswith('_Array_'):
                    qdump_Array(self, value)
                    return True
                if nsStrippedType.startswith('_AArray_'):
                    qdump_AArray(self, value)
                    return True

            dumper = self.qqDumpers.get(nsStrippedType)
            #self.warn('DUMPER: %s' % dumper)
            if dumper is not None:
                dumper(self, value)
                return True

            for pattern in self.qqDumpersEx.keys():
                dumper = self.qqDumpersEx[pattern]
                if re.match(pattern, nsStrippedType):
                    dumper(self, value)
                    return True

        return False

    def putSimpleCharArray(self, base, length=None):
        if length is None:
            length, shown, data = self.readToFirstZero(base, 1, self.displayStringLimit)
        else:
            shown = self.computeLimit(length)
            data = self.readMemory(base, shown)
        self.putValue(data, 'latin1', length=length)

    def putDisplay(self, editFormat, value):
        self.putField('editformat', editFormat)
        self.putField('editvalue', value)

    # This is shared by pointer and array formatting.
    def tryPutSimpleFormattedPointer(self, ptr, typename, innerType, displayFormat, limit):
        if displayFormat == DisplayFormat.Automatic:
            if self.isCdb or innerType.code is not TypeCode.Typedef:
                targetType = innerType
            else:
                targetType = innerType.target()

            if targetType.name in ('char', 'signed char', 'unsigned char', 'uint8_t', 'CHAR'):
                # Use UTF-8 as default for char *.
                self.putType(typename)
                (length, shown, data) = self.readToFirstZero(ptr, 1, limit)
                self.putValue(data, 'utf8', length=length)
                if self.isExpanded():
                    self.putArrayData(ptr, shown, innerType)
                return True

            if targetType.name in ('wchar_t', 'WCHAR'):
                self.putType(typename)
                charSize = self.lookupType('wchar_t').size()
                (length, data) = self.encodeCArray(ptr, charSize, limit)
                if charSize == 2:
                    self.putValue(data, 'utf16', length=length)
                else:
                    self.putValue(data, 'ucs4', length=length)
                return True

        if displayFormat == DisplayFormat.Latin1String:
            self.putType(typename)
            (length, data) = self.encodeCArray(ptr, 1, limit)
            self.putValue(data, 'latin1', length=length)
            return True

        if displayFormat == DisplayFormat.SeparateLatin1String:
            self.putType(typename)
            (length, data) = self.encodeCArray(ptr, 1, limit)
            self.putValue(data, 'latin1', length=length)
            self.putDisplay('latin1:separate', data)
            return True

        if displayFormat == DisplayFormat.Utf8String:
            self.putType(typename)
            (length, data) = self.encodeCArray(ptr, 1, limit)
            self.putValue(data, 'utf8', length=length)
            return True

        if displayFormat == DisplayFormat.SeparateUtf8String:
            self.putType(typename)
            (length, data) = self.encodeCArray(ptr, 1, limit)
            self.putValue(data, 'utf8', length=length)
            self.putDisplay('utf8:separate', data)
            return True

        if displayFormat == DisplayFormat.Local8BitString:
            self.putType(typename)
            (length, data) = self.encodeCArray(ptr, 1, limit)
            self.putValue(data, 'local8bit', length=length)
            return True

        if displayFormat == DisplayFormat.Utf16String:
            self.putType(typename)
            (length, data) = self.encodeCArray(ptr, 2, limit)
            self.putValue(data, 'utf16', length=length)
            return True

        if displayFormat == DisplayFormat.Ucs4String:
            self.putType(typename)
            (length, data) = self.encodeCArray(ptr, 4, limit)
            self.putValue(data, 'ucs4', length=length)
            return True

        return False

    def putDerefedPointer(self, value):
        derefValue = self.value_dereference(value)
        innerType = value.type.target()
        self.putType(innerType)
        savedCurrentChildType = self.currentChildType
        self.currentChildType = innerType.name
        derefValue.name = '*'
        derefValue.autoDerefCount = value.autoDerefCount + 1

        if derefValue.type.code != TypeCode.Pointer:
            self.putField('autoderefcount', '{}'.format(derefValue.autoDerefCount))

        self.putItem(derefValue)
        self.currentChildType = savedCurrentChildType

    def putFormattedPointer(self, value):
        self.putOriginalAddress(value.address())
        #self.warn("PUT FORMATTED: %s" % value)
        pointer = self.value_as_address(value)
        self.putAddress(pointer)
        #self.warn('POINTER: 0x%x' % pointer)
        if pointer == 0:
            #self.warn('NULL POINTER')
            self.putType(value.typeid)
            self.putValue('0x0')
            return

        typename = self.type_name(value.typeid)

        try:
            self.readRawMemory(pointer, 1)
        except:
            # Failure to dereference a pointer should at least
            # show the value of a pointer.
            #self.warn('BAD POINTER: %s' % value)
            self.putValue('0x%x' % pointer)
            self.putType(typename)
            return

        if self.currentIName.endswith('.this'):
            self.putDerefedPointer(value)
            return

        displayFormat = self.currentItemFormat(typename)
        innerType = value.type.target()

        if innerType.name == 'void':
            #self.warn('VOID POINTER: %s' % displayFormat)
            self.putType(typename)
            self.putSymbolValue(pointer)
            return

        if displayFormat == DisplayFormat.Raw:
            # Explicitly requested bald pointer.
            #self.warn('RAW')
            self.putType(typename)
            self.putValue('0x%x' % pointer)
            self.putExpandable()
            if self.currentIName in self.expandedINames:
                with Children(self):
                    with SubItem(self, '*'):
                        self.putItem(value.dereference())
            return

        limit = self.displayStringLimit
        if displayFormat in (DisplayFormat.SeparateLatin1String, DisplayFormat.SeparateUtf8String):
            limit = 1000000
        if self.tryPutSimpleFormattedPointer(pointer, typename,
                                             innerType, displayFormat, limit):
            self.putExpandable()
            return

        if DisplayFormat.Array10 <= displayFormat and displayFormat <= DisplayFormat.Array10000:
            n = (10, 100, 1000, 10000)[displayFormat - DisplayFormat.Array10]
            self.putType(typename)
            self.putItemCount(n)
            self.putArrayData(self.value_as_address(value), n, innerType)
            return

        if innerType.code == TypeCode.Function:
            # A function pointer.
            self.putSymbolValue(pointer)
            self.putType(typename)
            return

        #self.warn('AUTODEREF: %s' % self.autoDerefPointers)
        #self.warn('INAME: %s' % self.currentIName)
        #self.warn('INNER: %s' % innerType.name)
        if self.autoDerefPointers:
            # Generic pointer type with AutomaticFormat, but never dereference char types:
            if innerType.name not in (
                'char',
                'signed char',
                'int8_t',
                'qint8',
                'unsigned char',
                'uint8_t',
                'quint8',
                'wchar_t',
                'CHAR',
                'WCHAR',
                'char8_t',
                'char16_t',
                'char32_t'
            ):
                self.putDerefedPointer(value)
                return

        #self.warn('GENERIC PLAIN POINTER: %s' % value.type)
        #self.warn('ADDR PLAIN POINTER: 0x%x' % value.laddress)
        self.putType(typename)
        self.putSymbolValue(pointer)
        self.putExpandable()
        if self.currentIName in self.expandedINames:
            with Children(self):
                with SubItem(self, '*'):
                    self.putItem(value.dereference())

    def putOriginalAddress(self, address):
        if address is not None:
            self.put('origaddr="0x%x",' % address)

    def wantQObjectNames(self):
        return self.showQObjectNames and self.qtLoaded

    def fetchInternalFunctions(self):
        # Overrridden
        pass

    def putQObjectNameValue(self, value):
        is_qobject_based = self.type_qobject_based_cache.get(value.typeid, None)
        if is_qobject_based == False:
            #self.warn("SKIP TEST OBJNAME: %s" % self.type_name(value.typeid))
            return

        #self.warn("TEST OBJNAME: %s" % self.type_name(value.typeid))
        self.fetchInternalFunctions()

        try:
            # dd = value['d_ptr']['d'] is just behind the vtable.
            (vtable, dd) = self.split('pp', value)
            if not self.couldBeQObjectVTable(vtable):
                return False

            intSize = 4
            ptrSize = self.ptrSize()
            if self.qtVersionAtLeast(0x060000):
                # Size of QObjectData: 9 pointer + 2 int
                #   - vtable
                #   - QObject *q_ptr;
                #   - QObject *parent;
                #   - QObjectList children;
                #   - uint isWidget : 1; etc...
                #   - int postedEvents;
                #   - QDynamicMetaObjectData *metaObject;
                #   - QBindingStorage bindingStorage;
                extra = self.extract_pointer_at_address(dd + 9 * ptrSize + 2 * intSize)
                if extra == 0:
                    return False

                # Offset of objectName in ExtraData: 12 pointer
                #   - QList<QByteArray> propertyNames;
                #   - QList<QVariant> propertyValues;
                #   - QVector<int> runningTimers;
                #   - QList<QPointer<QObject> > eventFilters;
                #   - QString objectName
                objectNameAddress = extra + 12 * ptrSize
            elif self.qtVersionAtLeast(0x050000):
                # Size of QObjectData: 5 pointer + 2 int
                #   - vtable
                #   - QObject *q_ptr;
                #   - QObject *parent;
                #   - QObjectList children;
                #   - uint isWidget : 1; etc...
                #   - int postedEvents;
                #   - QDynamicMetaObjectData *metaObject;
                extra = self.extract_pointer_at_address(dd + 5 * ptrSize + 2 * intSize)
                if extra == 0:
                    return False

                # Offset of objectName in ExtraData: 6 pointer
                #   - QVector<QObjectUserData *> userData; only #ifndef QT_NO_USERDATA
                #   - QList<QByteArray> propertyNames;
                #   - QList<QVariant> propertyValues;
                #   - QVector<int> runningTimers;
                #   - QList<QPointer<QObject> > eventFilters;
                #   - QString objectName
                objectNameAddress = extra + 5 * ptrSize
            else:
                # Size of QObjectData: 5 pointer + 2 int
                #  - vtable
                #   - QObject *q_ptr;
                #   - QObject *parent;
                #   - QObjectList children;
                #   - uint isWidget : 1; etc..
                #   - int postedEvents;
                #   - QMetaObject *metaObject;

                # Offset of objectName in QObjectPrivate: 5 pointer + 2 int
                #   - [QObjectData base]
                #   - QString objectName
                objectNameAddress = dd + 5 * ptrSize + 2 * intSize


            data, size, alloc = self.qArrayData(objectNameAddress)

            # Object names are short, and GDB can crash on to big chunks.
            # Since this here is a convenience feature only, limit it.
            if size <= 0 or size > 80:
                return False

            raw = self.readMemory(data, 2 * size)
            self.putValue(raw, 'utf16', 1)
            return True

        except:
            #    warn('NO QOBJECT: %s' % value.type)
            return False

    def couldBePointer(self, p):
        if self.ptrSize() == 4:
            return p > 100000 and (p & 0x3 == 0)
        else:
            return p > 100000 and (p & 0x7 == 0) and (p < 0x7fffffffffff)

    def couldBeVTableEntry(self, p):
        if self.ptrSize() == 4:
            return p > 100000 and (p & 0x1 == 0)
        else:
            return p > 100000 and (p & 0x1 == 0) and (p < 0x7fffffffffff)

    def couldBeQObjectPointer(self, objectPtr):
        try:
            vtablePtr, dd = self.split('pp', objectPtr)
        except:
            self.bump('nostruct-1')
            return False

        try:
            dvtablePtr, qptr, parentPtr = self.split('ppp', dd)
        except:
            self.bump('nostruct-2')
            return False
        # Check d_ptr.d.q_ptr == objectPtr
        if qptr != objectPtr:
            self.bump('q_ptr')
            return False

        return self.couldBeQObjectVTable(vtablePtr)

    def couldBeQObjectVTable(self, vtablePtr):
        def getJumpAddress_x86(dumper, address):
            relativeJumpCode = 0xe9
            jumpCode = 0xff
            try:
                data = dumper.readRawMemory(address, 6)
            except:
                return 0
            primaryOpcode = data[0]
            if primaryOpcode == relativeJumpCode:
                # relative jump on 32 and 64 bit with a 32bit offset
                offset = int.from_bytes(data[1:5], byteorder=self.byteorder)
                return address + 5 + offset
            if primaryOpcode == jumpCode:
                if data[1] != 0x25:  # check for known extended opcode
                    return 0
                # 0xff25 is a relative jump on 64bit and an absolute jump on 32 bit
                if self.ptrSize() == 8:
                    offset = int.from_bytes(data[2:6], byteorder=self.byteorder)
                    return address + 6 + offset
                else:
                    return int.from_bytes(data[2:6], byteorder=self.byteorder)
            return 0

        # Do not try to extract a function pointer if there are no values to compare with
        if self.qtCustomEventFunc == 0 and self.qtCustomEventPltFunc == 0:
            return False

        try:
            customEventOffset = 8 if self.isMsvcTarget() else 9
            customEventFunc = self.extract_pointer_at_address(vtablePtr + customEventOffset * self.ptrSize())
        except:
            self.bump('nostruct-3')
            return False

        if self.isWindowsTarget():
            if customEventFunc in (self.qtCustomEventFunc, self.qtCustomEventPltFunc):
                return True
            # The vtable may point to a function that is just calling the customEvent function
            customEventFunc = getJumpAddress_x86(self, customEventFunc)
            if customEventFunc in (self.qtCustomEventFunc, self.qtCustomEventPltFunc):
                return True
            customEventFunc = self.extract_pointer_at_address(customEventFunc)
            if customEventFunc in (self.qtCustomEventFunc, self.qtCustomEventPltFunc):
                return True
            # If the object is defined in another module there may be another level of indirection
            customEventFunc = getJumpAddress_x86(self, customEventFunc)

        return customEventFunc in (self.qtCustomEventFunc, self.qtCustomEventPltFunc)

#    def extractQObjectProperty(objectPtr):
#        vtablePtr = self.extract_pointer_at_address(objectPtr)
#        metaObjectFunc = self.extract_pointer_at_address(vtablePtr)
#        cmd = '((void*(*)(void*))0x%x)((void*)0x%x)' % (metaObjectFunc, objectPtr)
#        try:
#            #self.warn('MO CMD: %s' % cmd)
#            res = self.parseAndEvaluate(cmd)
#            #self.warn('MO RES: %s' % res)
#            self.bump('successfulMetaObjectCall')
#            return self.value_as_address(res)
#        except:
#            self.bump('failedMetaObjectCall')
#            #self.warn('COULD NOT EXECUTE: %s' % cmd)
#        return 0

    def extractMetaObjectPtr(self, objectPtr, typeobj):
        """ objectPtr - address of *potential* instance of QObject derived class
            typeobj - type of *objectPtr if known, None otherwise. """

        if objectPtr is not None:
            self.checkIntType(objectPtr)

        def extractMetaObjectPtrFromAddress():
            #return 0
            # FIXME: Calling 'works' but seems to impact memory contents(!)
            # in relevant places. One symptom is that object name
            # contents 'vanishes' as the reported size of the string
            # gets zeroed out(?).
            # Try vtable, metaObject() is the first entry.
            vtablePtr = self.extract_pointer_at_address(objectPtr)
            metaObjectFunc = self.extract_pointer_at_address(vtablePtr)
            cmd = '((void*(*)(void*))0x%x)((void*)0x%x)' % (metaObjectFunc, objectPtr)
            try:
                #self.warn('MO CMD: %s' % cmd)
                res = self.parseAndEvaluate(cmd)
                #self.warn('MO RES: %s' % res)
                self.bump('successfulMetaObjectCall')
                return self.value_as_address(res)
            except:
                self.bump('failedMetaObjectCall')
                #self.warn('COULD NOT EXECUTE: %s' % cmd)
            return 0

        def extractStaticMetaObjectFromTypeHelper(someTypeObj):
            if someTypeObj.isSimpleType():
                return 0

            typename = someTypeObj.name
            isQObjectProper = typename == self.qtNamespace() + 'QObject'

            # No templates for now.
            if typename.find('<') >= 0:
                return 0

            result = self.findStaticMetaObject(someTypeObj)

            # We need to distinguish Q_OBJECT from Q_GADGET:
            # a Q_OBJECT SMO has a non-null superdata (unless it's QObject itself),
            # a Q_GADGET SMO has a null superdata (hopefully)
            if result and not isQObjectProper:
                if self.qtVersionAtLeast(0x60000) and self.isWindowsTarget():
                    (direct, indirect) = self.split('pp', result)
                    # since Qt 6 there is an additional indirect super data getter on windows
                    if direct == 0 and indirect == 0:
                        # This looks like a Q_GADGET
                        return 0
                else:
                    if self.extractPointer(result) == 0:
                        # This looks like a Q_GADGET
                        return 0

            return result

        def extractStaticMetaObjectPtrFromType(someTypeObj):
            if someTypeObj is None:
                return 0
            someTypeName = someTypeObj.name
            self.bump('metaObjectFromType')
            known = self.knownStaticMetaObjects.get(someTypeName, None)
            if known is not None:  # Is 0 or the static metaobject.
                return known

            result = 0
            #try:
            result = extractStaticMetaObjectFromTypeHelper(someTypeObj)
            #except RuntimeError as error:
            #    warn('METAOBJECT EXTRACTION FAILED: %s' % error)
            #except:
            #    warn('METAOBJECT EXTRACTION FAILED FOR UNKNOWN REASON')

            #if not result:
            #    base = someTypeObj.firstBase()
            #    if base is not None and base != someTypeObj: # sanity check
            #        result = extractStaticMetaObjectPtrFromType(base)

            if result:
                self.knownStaticMetaObjects[someTypeName] = result
            return result

        if not self.useFancy:
            return 0

        ptrSize = self.ptrSize()

        typename = typeobj.name
        result = self.knownStaticMetaObjects.get(typename, None)
        if result is not None:  # Is 0 or the static metaobject.
            self.bump('typecached')
            #self.warn('CACHED RESULT: %s %s 0x%x' % (self.currentIName, typename, result))
            return result

        if not self.couldBeQObjectPointer(objectPtr):
            self.bump('cannotBeQObject')
            #self.warn('DOES NOT LOOK LIKE A QOBJECT: %s' % self.currentIName)
            return 0

        metaObjectPtr = 0
        if not metaObjectPtr:
            # measured: 3 ms (example had one level of inheritance)
            #with self.timer('metaObjectType-' + self.currentIName):
            metaObjectPtr = extractStaticMetaObjectPtrFromType(typeobj)

        if not metaObjectPtr and not self.isWindowsTarget():
            # measured: 200 ms (example had one level of inheritance)
            #with self.timer('metaObjectCall-' + self.currentIName):
            metaObjectPtr = extractMetaObjectPtrFromAddress()

        #if metaObjectPtr:
        #    self.bump('foundMetaObject')
        #    self.8;

        return metaObjectPtr

    def extractCString(self, addr):
        result = bytearray()
        while True:
            d = bytes(self.readRawMemory(addr, 1))
            if d[0] == 0:
                break
            result += d
            addr += 1
        return result

    def listData(self, value, check=True):
        if self.qtVersionAtLeast(0x60000):
            dd, data, size = self.split('ppi', value)
            return data, size

        base = self.extractPointer(value)
        (ref, alloc, begin, end) = self.split('IIII', base)
        array = base + 16
        if not self.qtVersionAtLeast(0x50000):
            array += self.ptrSize()
        size = end - begin

        if check:
            self.check(begin >= 0 and end >= 0 and end <= 1000 * 1000 * 1000)
            size = end - begin
            self.check(size >= 0)

        stepSize = self.ptrSize()
        data = array + begin * stepSize
        return data, size

    def putTypedPointer(self, name, addr, typename):
        """ Prints a typed pointer, expandable if the type can be resolved,
            and without children otherwise """
        with SubItem(self, name):
            self.putAddress(addr)
            self.putValue('@0x%x' % addr)
            typeObj = self.lookupType(typename)
            if typeObj:
                self.putType(typeObj)
                self.putExpandable()
                if self.isExpanded():
                    with Children(self):
                        self.putFields(self.createValueFromAddress(addr, typeObj))
            else:
                self.putType(typename)

    # This is called is when a QObject derived class is expanded
    def tryPutQObjectGuts(self, value):
        metaObjectPtr = self.extractMetaObjectPtr(value.address(), value.type)
        if metaObjectPtr:
            self.putQObjectGutsHelper(value, value.address(),
                                      -1, metaObjectPtr, 'QObject')

    def metaString(self, metaObjectPtr, index, revision):
        ptrSize = self.ptrSize()
        stringdataOffset = ptrSize
        if self.isWindowsTarget() and self.qtVersionAtLeast(0x060000):
            stringdataOffset += ptrSize # indirect super data member
        stringdata = self.extract_pointer_at_address(int(metaObjectPtr) + stringdataOffset)

        def unpack_string(base, size):
            try:
                s = struct.unpack_from('%ds' % size, self.readRawMemory(base, size))[0]
                return s.decode('utf8')
            except:
                return '<not available>'

        if revision >= 9:  # Qt 6.
            pos, size = self.split('II', stringdata + 8 * index)
            return unpack_string(stringdata + pos, size)

        if revision >= 7:  # Qt 5.
            byteArrayDataSize = 24 if ptrSize == 8 else 16
            literal = stringdata + int(index) * byteArrayDataSize
            base, size, _ = self.qArrayDataHelper(literal)
            return unpack_string(base, size)

        ldata = stringdata + index
        return self.extractCString(ldata).decode('utf8')

    def putSortGroup(self, sortorder):
        if not self.isCli:
            self.putField('sortgroup', sortorder)

    def putQMetaStuff(self, value, origType):
        if self.qtVersionAtLeast(0x060000):
            metaObjectPtr, handle = value.split('pp')
        else:
            metaObjectPtr, handle = value.split('pI')
        if metaObjectPtr != 0:
            if self.qtVersionAtLeast(0x060000):
                if handle == 0:
                    self.putEmptyValue()
                    return
                revision = 9
                name, alias, flags, keyCount, data = self.split('IIIII', handle)
                index = name
            elif self.qtVersionAtLeast(0x050000):
                revision = 7
                dataPtr = self.extract_pointer_at_address(metaObjectPtr + 2 * self.ptrSize())
                index = self.extractInt(dataPtr + 4 * handle)
            else:
                revision = 6
                dataPtr = self.extract_pointer_at_address(metaObjectPtr + 2 * self.ptrSize())
                index = self.extractInt(dataPtr + 4 * handle)
            #self.putValue("index: %s rev: %s" % (index, revision))
            name = self.metaString(metaObjectPtr, index, revision)
            self.putValue(name)
            self.putExpandable()
            if self.isExpanded():
                with Children(self):
                    self.putFields(value)
                    self.putQObjectGutsHelper(0, 0, handle, metaObjectPtr, origType)
        else:
            self.putEmptyValue()
            if self.isExpanded():
                with Children(self):
                    self.putFields(value)

    # basically all meta things go through this here.
    # qobject and qobjectPtr are non-null  if coming from a real structure display
    # qobject == 0, qobjectPtr != 0 is possible for builds without QObject debug info
    #   if qobject == 0, properties and d-ptr cannot be shown.
    # handle is what's store in QMetaMethod etc, pass -1 for QObject/QMetaObject
    # itself metaObjectPtr needs to point to a valid QMetaObject.
    def putQObjectGutsHelper(self, qobject, qobjectPtr, handle, metaObjectPtr, origType):
        ptrSize = self.ptrSize()

        def putt(name, value, typename=' '):
            with SubItem(self, name):
                self.putValue(value)
                self.putType(typename)

        def extractSuperDataPtr(someMetaObjectPtr):
            #return someMetaObjectPtr['d']['superdata']
            return self.extract_pointer_at_address(someMetaObjectPtr)

        def extractDataPtr(someMetaObjectPtr):
            # dataPtr = metaObjectPtr['d']['data']
            if self.qtVersionAtLeast(0x60000) and self.isWindowsTarget():
                offset = 3
            else:
                offset = 2
            return self.extract_pointer_at_address(someMetaObjectPtr + offset * ptrSize)

        isQMetaObject = origType == 'QMetaObject'
        isQObject = origType == 'QObject'

        #self.warn('OBJECT GUTS: %s 0x%x ' % (self.currentIName, metaObjectPtr))
        dataPtr = extractDataPtr(metaObjectPtr)
        #self.warn('DATA PTRS: %s 0x%x ' % (self.currentIName, dataPtr))
        (revision, classname,
            classinfo, classinfo2,
            methodCount, methods,
            propertyCount, properties,
            enumCount, enums,
            constructorCount, constructors,
            flags, signalCount) = self.split('I' * 14, dataPtr)

        largestStringIndex = -1
        for i in range(methodCount):
            t = self.split('IIIII', dataPtr + 56 + i * 20)
            if largestStringIndex < t[0]:
                largestStringIndex = t[0]

        ns = self.qtNamespace()
        extraData = 0
        if qobjectPtr:
            dd = self.extract_pointer_at_address(qobjectPtr + ptrSize)
            if self.qtVersionAtLeast(0x60000):
                (dvtablePtr, qptr, parent, children, bindingStorageData, bindingStatus,
                    flags, postedEvents, dynMetaObjectPtr, # Up to here QObjectData.
                    extraData, threadDataPtr, connectionListsPtr,
                    sendersPtr, currentSenderPtr) \
                    = self.split('pp{@QObject*}{@QList<@QObject *>}ppIIp' + 'ppppp', dd)
            elif self.qtVersionAtLeast(0x50000):
                (dvtablePtr, qptr, parent, children, flags, postedEvents,
                    dynMetaObjectPtr,  # Up to here QObjectData.
                    extraData, threadDataPtr, connectionListsPtr,
                    sendersPtr, currentSenderPtr) \
                    = self.split('pp{@QObject*}{@QList<@QObject *>}IIp' + 'ppppp', dd)
            else:
                (dvtablePtr, qptr, parent, children, flags, postedEvents,
                    dynMetaObjectPtr,  # Up to here QObjectData
                    objectName, extraData, threadDataPtr, connectionListsPtr,
                    sendersPtr, currentSenderPtr) \
                    = self.split('pp{@QObject*}{@QList<@QObject *>}IIp' + 'pppppp', dd)

            with SubItem(self, '[parent]'):
                if not self.isCli:
                    self.putSortGroup(9)
                self.putItem(parent)

            with SubItem(self, '[children]'):
                if not self.isCli:
                    self.putSortGroup(8)

                dvtablePtr, qptr, parentPtr, children = self.split('ppp{@QList<@QObject *>}', dd)
                self.putItem(children)

        if isQMetaObject:
            with SubItem(self, '[strings]'):
                if not self.isCli:
                    self.putSortGroup(2)
                if largestStringIndex > 0:
                    self.putSpecialValue('minimumitemcount', largestStringIndex)
                    self.putExpandable()
                    if self.isExpanded():
                        with Children(self, largestStringIndex + 1):
                            for i in self.childRange():
                                with SubItem(self, i):
                                    s = self.metaString(metaObjectPtr, i, revision)
                                    self.putValue(self.hexencode(s), 'latin1')
                else:
                    self.putValue(' ')

        if isQMetaObject:
            with SubItem(self, '[raw]'):
                self.putSortGroup(1)
                self.putEmptyValue()
                self.putExpandable()
                if self.isExpanded():
                    with Children(self):
                        putt('revision', revision)
                        putt('classname', classname)
                        putt('classinfo', classinfo)
                        putt('methods', '%d %d' % (methodCount, methods))
                        putt('properties', '%d %d' % (propertyCount, properties))
                        putt('enums/sets', '%d %d' % (enumCount, enums))
                        putt('constructors', '%d %d' % (constructorCount, constructors))
                        putt('flags', flags)
                        putt('signalCount', signalCount)
                        for i in range(methodCount):
                            t = self.split('IIIII', dataPtr + 56 + i * 20)
                            putt('method %d' % i, '%s %s %s %s %s' % t)

        if isQObject:
            with SubItem(self, '[extra]'):
                self.putSortGroup(1)
                self.putEmptyValue()
                self.putExpandable()
                if self.isExpanded():
                    with Children(self):
                        if extraData:
                            self.putTypedPointer('[extraData]', extraData,
                                                 ns + 'QObjectPrivate::ExtraData')

                        with SubItem(self, '[metaObject]'):
                            self.putAddress(metaObjectPtr)
                            self.putExpandable()
                            if self.isExpanded():
                                with Children(self):
                                    self.putQObjectGutsHelper(
                                        0, 0, -1, metaObjectPtr, 'QMetaObject')

                        if False:
                            with SubItem(self, '[connections]'):
                                if connectionListsPtr:
                                    typename = '@QObjectConnectionListVector'
                                    self.putItem(self.createValueFromAddress(connectionListsPtr,
typename))
                                else:
                                    self.putItemCount(0)

                        if False:
                            with SubItem(self, '[signals]'):
                                self.putItemCount(signalCount)
                                if self.isExpanded():
                                    with Children(self):
                                        j = -1
                                        for i in range(signalCount):
                                            t = self.split('IIIII', dataPtr + 56 + 20 * i)
                                            flags = t[4]
                                            if flags != 0x06:
                                                continue
                                            j += 1
                                            with SubItem(self, j):
                                                name = self.metaString(
                                                    metaObjectPtr, t[0], revision)
                                                self.putType(' ')
                                                self.putValue(name)
                                                self.putExpandable()
                                                with Children(self):
                                                    putt('[nameindex]', t[0])
                                                    #putt('[type]', 'signal')
                                                    putt('[argc]', t[1])
                                                    putt('[parameter]', t[2])
                                                    putt('[tag]', t[3])
                                                    putt('[flags]', t[4])
                                                    putt('[localindex]', str(i))
                                                    putt('[globalindex]', str(globalOffset + i))
                                                    #self.putQObjectConnections(dd)

        if isQMetaObject or isQObject:
            with SubItem(self, '[properties]'):
                self.putSortGroup(5)
                if self.isExpanded():
                    dynamicPropertyCount = 0
                    with Children(self):
                        # Static properties.
                        for i in range(propertyCount):
                            if self.qtVersionAtLeast(0x60000):
                                t = self.split('IIIII', dataPtr + properties * 4 + 20 * i)
                            else:
                                t = self.split('III', dataPtr + properties * 4 + 12 * i)
                            name = self.metaString(metaObjectPtr, t[0], revision)
                            if qobject and self.qtPropertyFunc:
                                    # LLDB doesn't like calling it on a derived class, possibly
                                    # due to type information living in a different shared object.
                                    #base = self.createValueFromAddress(qobjectPtr, '@QObject')
                                    #self.warn("CALL FUNC: 0x%x" % self.qtPropertyFunc)
                                cmd = '((QVariant(*)(void*,char*))0x%x)((void*)0x%x,"%s")' \
                                    % (self.qtPropertyFunc, qobjectPtr, name)
                                try:
                                        #self.warn('PROP CMD: %s' % cmd)
                                    res = self.parseAndEvaluate(cmd)
                                    #self.warn('PROP RES: %s' % res)
                                except:
                                    self.bump('failedMetaObjectCall')
                                    putt(name, ' ')
                                    continue
                                    #self.warn('COULD NOT EXECUTE: %s' % cmd)
                                #self.putCallItem(name, '@QVariant', base, 'property', '"' + name + '"')
                                if res is None:
                                    self.bump('failedMetaObjectCall2')
                                    putt(name, ' ')
                                    continue
                                self.putSubItem(name, res)
                            else:
                                putt(name, ' ')

                        # Dynamic properties.
                        if extraData:
                            def list6Generator(addr, inner_typeid):
                                data, size = self.listData(addr)
                                inner_size = self.type_size(inner_typeid)
                                for i in range(size):
                                    yield self.createValueFromAddress(data, inner_typeid)
                                    data += inner_size

                            def list5Generator(addr, inner_typeid):
                                data, size = self.listData(addr)
                                for i in range(size):
                                    yield self.createValueFromAddress(data, inner_typeid)
                                    data += ptrSize

                            def vectorGenerator(addr, inner_typeid):
                                data, size = self.vectorData(addr)
                                inner_size = self.type_size(inner_typeid)
                                for i in range(size):
                                    yield self.createValueFromAddress(data, inner_typeid)
                                    data += inner_size

                            variant_typeid = self.cheap_typeid_from_name('@QVariant')
                            if self.qtVersionAtLeast(0x60000):
                                values = vectorGenerator(extraData + 3 * ptrSize, variant_typeid)
                            elif self.qtVersionAtLeast(0x50600):
                                values = vectorGenerator(extraData + 2 * ptrSize, variant_typeid)
                            elif self.qtVersionAtLeast(0x50000):
                                values = list5Generator(extraData + 2 * ptrSize, variant_typeid)
                            else:
                                variantptr_typeid = self.cheap_typeid_from_name('@QVariant')
                                values = list5Generator(extraData + 2 * ptrSize, variantptr_typeid)

                            bytearray_typeid = self.cheap_typeid_from_name('@QByteArray')
                            if self.qtVersionAtLeast(0x60000):
                                names = list6Generator(extraData, bytearray_typeid)
                            else:
                                names = list5Generator(extraData + ptrSize, bytearray_typeid)

                            for (k, v) in zip(names, values):
                                with SubItem(self, propertyCount + dynamicPropertyCount):
                                    if not self.isCli:
                                        self.putField('key', self.encodeByteArray(k))
                                        self.putField('keyencoded', 'latin1:1:0')
                                    self.putItem(v)
                                    dynamicPropertyCount += 1
                    self.putItemCount(propertyCount + dynamicPropertyCount)
                else:
                    # We need a handle to [x] for the user to expand the item
                    # before we know whether there are actual children. Counting
                    # them is too expensive.
                    self.putSpecialValue('minimumitemcount', propertyCount)
                    self.putExpandable()

        superDataPtr = extractSuperDataPtr(metaObjectPtr)

        globalOffset = 0
        superDataIterator = superDataPtr
        while superDataIterator:
            sdata = extractDataPtr(superDataIterator)
            globalOffset += self.extractInt(sdata + 16)  # methodCount member
            superDataIterator = extractSuperDataPtr(superDataIterator)

        if isQMetaObject or isQObject:
            with SubItem(self, '[methods]'):
                self.putSortGroup(3)
                self.putItemCount(methodCount)
                if self.isExpanded():
                    with Children(self):
                        for i in range(methodCount):
                            t = self.split('IIIII', dataPtr + 56 + 20 * i)
                            name = self.metaString(metaObjectPtr, t[0], revision)
                            with SubItem(self, i):
                                self.putValue(name)
                                self.putType(' ')
                                self.putNumChild(1)
                                isSignal = False
                                flags = t[4]
                                if flags == 0x06:
                                    typ = 'signal'
                                    isSignal = True
                                elif flags == 0x0a:
                                    typ = 'slot'
                                elif flags == 0x0a:
                                    typ = 'invokable'
                                else:
                                    typ = '<unknown>'
                                with Children(self):
                                    putt('[nameindex]', t[0])
                                    putt('[type]', typ)
                                    putt('[argc]', t[1])
                                    putt('[parameter]', t[2])
                                    putt('[tag]', t[3])
                                    putt('[flags]', t[4])
                                    putt('[localindex]', str(i))
                                    putt('[globalindex]', str(globalOffset + i))

        if isQObject:
            with SubItem(self, '[d]'):
                self.putItem(self.createValueFromAddress(dd, '@QObjectPrivate'))
                self.putSortGroup(15)

        if isQMetaObject:
            with SubItem(self, '[superdata]'):
                self.putSortGroup(12)
                if superDataPtr:
                    self.putType('@QMetaObject')
                    self.putAddress(superDataPtr)
                    self.putExpandable()
                    if self.isExpanded():
                        with Children(self):
                            self.putQObjectGutsHelper(0, 0, -1, superDataPtr, 'QMetaObject')
                else:
                    self.putType('@QMetaObject *')
                    self.putValue('0x0')

        if handle >= 0:
            localIndex = int((handle - methods) / 5)
            with SubItem(self, '[localindex]'):
                self.putSortGroup(12)
                self.putValue(localIndex)
            with SubItem(self, '[globalindex]'):
                self.putSortGroup(11)
                self.putValue(globalOffset + localIndex)

    def putQObjectConnections(self, dd):
        with SubItem(self, '[connections]'):
            ptrSize = self.ptrSize()
            self.putNoType()
            privateType = self.create_typeid_from_name('@QObjectPrivate')
            d_ptr = dd.cast(privateType.pointer()).dereference()
            connections = d_ptr['connectionLists']
            if self.value_as_integer(connections) == 0:
                self.putItemCount(0)
            else:
                connections = connections.dereference()
                #connections = connections.cast(connections.type.firstBase())
                self.putSpecialValue('minimumitemcount', 0)
                self.putExpandable()
            if self.isExpanded():
                pp = 0
                with Children(self):
                    innerType = connections.type[0]
                    # Should check:  innerType == ns::QObjectPrivate::ConnectionList
                    data, size = self.vectorData(connections)
                    connection_typeid = self.create_typeid_from_name('@QObjectPrivate::Connection')
                    connection_ptr_typeid = self.create_pointer_typeid(connection_typeid)
                    for i in range(size):
                        first = self.extract_pointer_at_address(data + i * 2 * ptrSize)
                        while first:
                            val = self.Value(self)
                            val.ldata = first
                            val.typeid = connection_typeid
                            self.putSubItem('%s' % pp, val)
                            first = self.extract_pointer_at_address(first + 3 * ptrSize)
                            # We need to enforce some upper limit.
                            pp += 1
                            if pp > 1000:
                                break

    def currentItemFormat(self, typename=None):
        displayFormat = self.formats.get(self.currentIName, DisplayFormat.Automatic)
        if displayFormat == DisplayFormat.Automatic:
            if typename is None:
                typename = self.currentType.value
            needle = None if typename is None else self.stripForFormat(typename)
            displayFormat = self.typeformats.get(needle, DisplayFormat.Automatic)
        return displayFormat

    def putSubItem(self, component, value):  # -> ReportItem
        if not isinstance(value, self.Value):
            raise RuntimeError('WRONG VALUE TYPE IN putSubItem: %s' % type(value))
        res = None
        with SubItem(self, component):
            self.putItem(value)
            res = self.currentValue
        return res  # The 'short' display.

    def putArrayData(self, base, n, inner_typish, childNumChild=None):
        self.checkIntType(base)
        self.checkIntType(n)
        inner_typeid = self.typeid_for_typish(inner_typish)
        inner_size = self.type_size(inner_typeid)
        self.putNumChild(n)
        #self.warn('ADDRESS: 0x%x INNERSIZE: %s INNERTYPE: %s' % (base, inner_size, inner_typeid))
        enc = self.type_encoding_cache.get(inner_typeid, None)
        maxNumChild = self.maxArrayCount()
        if enc:
            self.put('childtype="%s",' % self.type_name(inner_typeid))
            self.put('addrbase="0x%x",' % base)
            self.put('addrstep="0x%x",' % inner_size)
            self.put('arrayencoding="%s",' % enc)
            self.put('endian="%s",' % self.packCode)
            if n > maxNumChild:
                self.put('childrenelided="%s",' % n)
                n = maxNumChild
            self.put('arraydata="')
            self.put(self.readMemory(base, n * inner_size))
            self.put('",')
        else:
            innerType = self.Type(self, inner_typeid)
            with Children(self, n, innerType, childNumChild, maxNumChild,
                          addrBase=base, addrStep=inner_size):
                for i in self.childRange():
                    self.putSubItem(i, self.createValueFromAddress(base + i * inner_size, innerType))

    def putArrayItem(self, name, addr, n, typename):
        self.checkIntType(addr)
        self.checkIntType(n)
        with SubItem(self, name):
            self.putEmptyValue()
            self.putType('%s [%d]' % (typename, n))
            self.putArrayData(addr, n, self.lookupType(typename))
            self.putAddress(addr)

    def putPlotDataHelper(self, base, n, innerType, maxNumChild=1000 * 1000):
        if n > maxNumChild:
            self.putField('plotelided', n)  # FIXME: Act on that in frontend
            n = maxNumChild
        if self.currentItemFormat() == DisplayFormat.ArrayPlot and innerType.isSimpleType():
            enc = innerType.simpleEncoding()
            if enc:
                self.putField('editencoding', enc)
                self.putDisplay('plotdata:separate',
                                self.readMemory(base, n * innerType.size()))

    def putPlotData(self, base, n, innerType, maxNumChild=1000 * 1000):
        self.putPlotDataHelper(base, n, innerType, maxNumChild=maxNumChild)
        if self.isExpanded():
            self.putArrayData(base, n, innerType)

    def putSpecialArgv(self, value):
        """
        Special handling for char** argv.
        """
        ptr_size = self.ptrSize()
        n = 0
        argv = self.value_as_address(value)
        # argv is 0 for "optimized out" cases. Or contains rubbish.
        try:
            if argv:
                p = argv
                while self.extract_pointer_at_address(p) and n <= 100:
                    p += ptr_size
                    n += 1
        except:
            pass

        with TopLevelItem(self, 'local.argv'):
            self.put('iname="local.argv",name="argv",')
            self.putItemCount(n, 100)
            self.putType('char **')
            if self.currentIName in self.expandedINames:
                p = value
                with Children(self, n):
                    for i in range(n):
                        self.putSubItem(i, p.dereference())
                        p += 1

    def extract_pointer_at_address(self, address):
        blob = self.value_data_from_address(address, self.ptrSize())
        return int.from_bytes(blob, byteorder=self.byteorder)

    def value_extract_integer(self, value, size, signed):
        if isinstance(value.lvalue, int):
            return value.lvalue
        if isinstance(value.ldata, int):
            return value.ldata
        #with self.dumper.timer('extractInt'):
        value.check()
        blob = self.value_data(value, size)
        return int.from_bytes(blob, byteorder=self.byteorder, signed=signed)

    def value_extract_something(self, valuish, size, signed=False):
        if isinstance(valuish, int):
            blob = self.value_data_from_address(valuish, size)
        elif isinstance(valuish, self.Value):
            blob = self.value_data(valuish, size)
        else:
            raise RuntimeError('CANT EXTRACT FROM %s' % type(valuish))
        res = int.from_bytes(blob, byteorder=self.byteorder, signed=signed)
        #self.warn("EXTRACTED %s SIZE %s FROM %s" % (res, size, blob))
        return res

    def extractPointer(self, value):
        return self.value_extract_something(value, self.ptrSize())

    def extractInt64(self, value):
        return self.value_extract_something(value, 8, True)

    def extractUInt64(self, value):
        return self.value_extract_something(value, 8)

    def extractInt(self, value):
        return self.value_extract_something(value, 4, True)

    def extractUInt(self, value):
        return self.value_extract_something(value, 4)

    def extractShort(self, value):
        return self.value_extract_something(value, 2, True)

    def extractUShort(self, value):
        return self.value_extract_something(value, 2)

    def extractByte(self, value):
        return self.value_extract_something(value, 1)

    # Parses a..b and  a.(s).b
    def parseRange(self, exp):

        # Search for the first unbalanced delimiter in s
        def searchUnbalanced(s, upwards):
            paran = 0
            bracket = 0
            if upwards:
                open_p, close_p, open_b, close_b = '(', ')', '[', ']'
            else:
                open_p, close_p, open_b, close_b = ')', '(', ']', '['
            for i in range(len(s)):
                c = s[i]
                if c == open_p:
                    paran += 1
                elif c == open_b:
                    bracket += 1
                elif c == close_p:
                    paran -= 1
                    if paran < 0:
                        return i
                elif c == close_b:
                    bracket -= 1
                    if bracket < 0:
                        return i
            return len(s)

        match = re.search(r'(\.)(\(.+?\))?(\.)', exp)
        if match:
            s = match.group(2)
            left_e = match.start(1)
            left_s = 1 + left_e - searchUnbalanced(exp[left_e::-1], False)
            right_s = match.end(3)
            right_e = right_s + searchUnbalanced(exp[right_s:], True)
            template = exp[:left_s] + '%s' + exp[right_e:]

            a = exp[left_s:left_e]
            b = exp[right_s:right_e]

            try:
                # Allow integral expressions.
                ss = self.value_as_integer(self.parseAndEvaluate(s[1:len(s) - 1])) if s else 1
                aa = self.value_as_integer(self.parseAndEvaluate(a))
                bb = self.value_as_integer(self.parseAndEvaluate(b))
                if aa < bb and ss > 0:
                    return True, aa, ss, bb + 1, template
            except:
                pass
        return False, 0, 1, 1, exp

    def putNumChild(self, numchild):
        if numchild != self.currentChildNumChild:
            self.putField('numchild', numchild)

    def handleLocals(self, variables):
        #self.warn('VARIABLES: %s' % variables)
        shadowed = {}
        for value in variables:
            if value.name == 'argv':
                if value.type.code == TypeCode.Pointer:
                    target = value.type.target()
                    if target.code == TypeCode.Pointer:
                        if target.target().name == 'char':
                            self.putSpecialArgv(value)
                            continue

            name = value.name
            if name in shadowed:
                level = shadowed[name]
                shadowed[name] = level + 1
                name += '@%d' % level
            else:
                shadowed[name] = 1
            # A 'normal' local variable or parameter.
            iname = value.iname if hasattr(value, 'iname') else 'local.' + name
            with TopLevelItem(self, iname):
                #with self.timer('all-' + iname):
                self.putField('iname', iname)
                self.putField('name', name)
                self.putItem(value)

    def handleWatches(self, args):
        #with self.timer('watches'):
        for watcher in args.get('watchers', []):
            iname = watcher['iname']
            exp = self.hexdecode(watcher['exp'])
            self.handleWatch(exp, exp, iname)

    def handleWatch(self, origexp, exp, iname):
        exp = str(exp).strip()
        escapedExp = self.hexencode(exp)
        #self.warn('HANDLING WATCH %s -> %s, INAME: "%s"' % (origexp, exp, iname))

        # Grouped items separated by semicolon.
        if exp.find(';') >= 0:
            exps = exp.split(';')
            n = len(exps)
            with TopLevelItem(self, iname):
                self.putField('iname', iname)
                #self.putField('wname', escapedExp)
                self.putField('name', exp)
                self.putField('exp', exp)
                self.putItemCount(n)
                self.putNoType()
            for i in range(n):
                self.handleWatch(exps[i], exps[i], '%s.%d' % (iname, i))
            return

        # Special array index: e.g a[1..199] or a[1.(3).199] for stride 3.
        isRange, begin, step, end, template = self.parseRange(exp)
        if isRange:
            #self.warn('RANGE: %s %s %s in %s' % (begin, step, end, template))
            r = range(begin, end, step)
            n = len(r)
            with TopLevelItem(self, iname):
                self.putField('iname', iname)
                #self.putField('wname', escapedExp)
                self.putField('name', exp)
                self.putField('exp', exp)
                self.putItemCount(n)
                self.putNoType()
                with Children(self, n):
                    for i in r:
                        e = template % i
                        self.handleWatch(e, e, '%s.%s' % (iname, i))
            return

            # Fall back to less special syntax
            #return self.handleWatch(origexp, exp, iname)

        with TopLevelItem(self, iname):
            self.putField('iname', iname)
            self.putField('wname', escapedExp)
            try:
                value = self.parseAndEvaluate(exp)
                self.putItem(value)
            except Exception:
                self.currentType.value = ' '
                self.currentValue.value = '<no such value>'
                self.currentChildNumChild = -1
                self.currentNumChild = 0
                self.putNumChild(0)

    def registerDumper(self, funcname, function):
        try:
            if funcname.startswith('qdump__'):
                typename = funcname[7:]
                spec = inspect.getfullargspec(function)
                if len(spec.args) == 2:
                    self.qqDumpers[typename] = function
                elif len(spec.args) == 3 and len(spec.defaults) == 1:
                    self.qqDumpersEx[spec.defaults[0]] = function
                self.qqFormats[typename] = self.qqFormats.get(typename, [])
            elif funcname.startswith('qform__'):
                typename = funcname[7:]
                try:
                    self.qqFormats[typename] = function()
                except:
                    self.qqFormats[typename] = []
            elif funcname.startswith('qedit__'):
                typename = funcname[7:]
                try:
                    self.qqEditable[typename] = function
                except:
                    pass
        except:
            pass

    def setupDumpers(self, _={}):
        self.resetCaches()

        for mod in self.dumpermodules:
            try:
                m = __import__(mod)
                dic = m.__dict__
                for name in dic.keys():
                    item = dic[name]
                    self.registerDumper(name, item)
            except Exception as e:
                print('Failed to load dumper module: %s (%s)' % (mod, e))

        msg = 'dumpers=['
        for key, value in self.qqFormats.items():
            editable = ',editable="true"' if key in self.qqEditable else ''
            formats = (',formats=\"%s\"' % str(value)[1:-1]) if len(value) else ''
            msg += '{type="%s"%s%s},' % (key, editable, formats)
        msg += '],'
        v = 10000 * sys.version_info[0] + 100 * sys.version_info[1] + sys.version_info[2]
        msg += 'python="%d"' % v
        return msg

    def reloadDumpers(self, args):
        for mod in self.dumpermodules:
            m = sys.modules[mod]
            import importlib
            importlib.reload(m)
        self.setupDumpers(args)

    def loadDumpers(self, args):
        msg = self.setupDumpers()
        self.reportResult(msg, args)

    def addDumperModule(self, args):
        path = args['path']
        (head, tail) = os.path.split(path)
        sys.path.insert(1, head)
        self.dumpermodules.append(os.path.splitext(tail)[0])

    def extractQStringFromQDataStream(self, buf, offset):
        """ Read a QString from the stream """
        length = struct.unpack_from('!I', buf, offset)[0]
        offset += 4
        string = buf[offset:offset + length].decode('utf-16be')
        return (string, offset + length)

    def extractQByteArrayFromQDataStream(self, buf, offset):
        """ Read a QByteArray from the stream """
        length = struct.unpack_from('!I', buf, offset)[0]
        offset += 4
        string = buf[offset:offset + length].decode('latin1')
        return (string, offset + length)

    def extractIntFromQDataStream(self, buf, offset):
        """ Read an int from the stream """
        value = struct.unpack_from('!I', buf, offset)[0]
        return (value, offset + 4)

    def handleInterpreterMessage(self):
        """ Return True if inferior stopped """
        resdict = self.fetchInterpreterResult()
        return resdict.get('event') == 'break'

    def reportInterpreterResult(self, resdict, args):
        print('interpreterresult=%s,token="%s"'
              % (self.resultToMi(resdict), args.get('token', -1)))

    def reportInterpreterAsync(self, resdict, asyncclass):
        print('interpreterasync=%s,asyncclass="%s"'
              % (self.resultToMi(resdict), asyncclass))

    def removeInterpreterBreakpoint(self, args):
        res = self.sendInterpreterRequest('removebreakpoint', {'id': args['id']})
        return res

    def insertInterpreterBreakpoint(self, args):
        args['condition'] = self.hexdecode(args.get('condition', ''))
        # Will fail if the service is not yet up and running.
        response = self.sendInterpreterRequest('setbreakpoint', args)
        resdict = args.copy()
        bp = None if response is None else response.get('breakpoint', None)
        if bp:
            resdict['number'] = bp
            resdict['pending'] = 0
        else:
            self.createResolvePendingBreakpointsHookBreakpoint(args)
            resdict['number'] = -1
            resdict['pending'] = 1
            resdict['warning'] = 'Direct interpreter breakpoint insertion failed.'
        self.reportInterpreterResult(resdict, args)

    def resolvePendingInterpreterBreakpoint(self, args):
        self.parseAndEvaluate('qt_qmlDebugEnableService("NativeQmlDebugger")')
        response = self.sendInterpreterRequest('setbreakpoint', args)
        bp = None if response is None else response.get('breakpoint', None)
        resdict = args.copy()
        if bp:
            resdict['number'] = bp
            resdict['pending'] = 0
        else:
            resdict['number'] = -1
            resdict['pending'] = 0
            resdict['error'] = 'Pending interpreter breakpoint insertion failed.'
        self.reportInterpreterAsync(resdict, 'breakpointmodified')

    def fetchInterpreterResult(self):
        buf = self.parseAndEvaluate('qt_qmlDebugMessageBuffer')
        size = self.parseAndEvaluate('qt_qmlDebugMessageLength')
        msg = self.hexdecode(self.readMemory(buf, size))
        # msg is a sequence of 'servicename<space>msglen<space>msg' items.
        resdict = {}  # Native payload.
        while len(msg):
            pos0 = msg.index(' ')  # End of service name
            pos1 = msg.index(' ', pos0 + 1)  # End of message length
            service = msg[0:pos0]
            msglen = int(msg[pos0 + 1:pos1])
            msgend = pos1 + 1 + msglen
            payload = msg[pos1 + 1:msgend]
            msg = msg[msgend:]
            if service == 'NativeQmlDebugger':
                try:
                    resdict = json.loads(payload)
                    continue
                except:
                    self.warn('Cannot parse native payload: %s' % payload)
            else:
                print('interpreteralien=%s'
                      % {'service': service, 'payload': self.hexencode(payload)})
        try:
            expr = 'qt_qmlDebugClearBuffer()'
            res = self.parseAndEvaluate(expr)
        except RuntimeError as error:
            self.warn('Cleaning buffer failed: %s: %s' % (expr, error))

        return resdict

    def sendInterpreterRequest(self, command, args={}):
        encoded = json.dumps({'command': command, 'arguments': args})
        hexdata = self.hexencode(encoded)
        expr = 'qt_qmlDebugSendDataToService("NativeQmlDebugger","%s")' % hexdata
        try:
            res = self.parseAndEvaluate(expr)
        except RuntimeError as error:
            self.warn('Interpreter command failed: %s: %s' % (encoded, error))
            return {}
        except AttributeError as error:
            # Happens with LLDB and 'None' current thread.
            self.warn('Interpreter command failed: %s: %s' % (encoded, error))
            return {}
        if not res:
            self.warn('Interpreter command failed: %s ' % encoded)
            return {}
        return self.fetchInterpreterResult()

    def executeStep(self, args):
        if self.nativeMixed:
            response = self.sendInterpreterRequest('stepin', args)
        self.doContinue()

    def executeStepOut(self, args):
        if self.nativeMixed:
            response = self.sendInterpreterRequest('stepout', args)
        self.doContinue()

    def executeNext(self, args):
        if self.nativeMixed:
            response = self.sendInterpreterRequest('stepover', args)
        self.doContinue()

    def executeContinue(self, args):
        if self.nativeMixed:
            response = self.sendInterpreterRequest('continue', args)
        self.doContinue()

    def doInsertInterpreterBreakpoint(self, args, wasPending):
        #self.warn('DO INSERT INTERPRETER BREAKPOINT, WAS PENDING: %s' % wasPending)
        # Will fail if the service is not yet up and running.
        response = self.sendInterpreterRequest('setbreakpoint', args)
        bp = None if response is None else response.get('breakpoint', None)
        if wasPending:
            if not bp:
                self.reportInterpreterResult({'bpnr': -1, 'pending': 1,
                                              'error': 'Pending interpreter breakpoint insertion failed.'}, args)
                return
        else:
            if not bp:
                self.reportInterpreterResult({'bpnr': -1, 'pending': 1,
                                              'warning': 'Direct interpreter breakpoint insertion failed.'}, args)
                self.createResolvePendingBreakpointsHookBreakpoint(args)
                return
        self.reportInterpreterResult({'bpnr': bp, 'pending': 0}, args)

    def isInternalInterpreterFrame(self, functionName):
        if functionName is None:
            return False
        if functionName.startswith('qt_v4'):
            return True
        return functionName.startswith(self.qtNamespace() + 'QV4::')

    # Hack to avoid QDate* dumper timeouts with GDB 7.4 on 32 bit
    # due to misaligned %ebx in SSE calls (qstring.cpp:findChar)
    def canCallLocale(self):
        return True

    def isReportableInterpreterFrame(self, functionName):
        return functionName and functionName.find('QV4::Moth::VME::exec') >= 0

    def extractInterpreterStack(self):
        return self.sendInterpreterRequest('backtrace', {'limit': 10})

    def putItems(self, count, generator, maxNumChild=10000):
        self.putItemCount(count)
        if self.isExpanded():
            with Children(self, count, maxNumChild=maxNumChild):
                for i, val in zip(self.childRange(), generator):
                    self.putSubItem(i, val)

    def putItem(self, value):
        #self.warn('PUT ITEM: %s' % value.stringify())
        #self.dump_location()
        #self.addToCache(typeobj)  # Fill type cache

        if not value.lIsInScope:
            self.putSpecialValue('optimizedout')
            self.putNumChild(0)
            return

        if not isinstance(value, self.Value):
            raise RuntimeError('WRONG TYPE IN putItem: %s' % type(self.Value))

        typeid = value.typeid
        typecode = self.type_code(typeid)
        typename = self.type_name(typeid)

        # Try on possibly typedefed type first.
        if self.tryPutPrettyItem(typename, value):
            if typecode == TypeCode.Pointer:
                self.putOriginalAddress(value.address())
            else:
                self.putAddress(value.address())
            return

        if typecode == TypeCode.Typedef:
            #self.warn('TYPEDEF VALUE: %s' % value.stringify())
            self.putItem(value.detypedef())
            self.putBetterType(typename)
            return

        if typecode == TypeCode.Pointer:
            self.putFormattedPointer(value)
            if value.summary and self.useFancy:
                self.putValue(self.hexencode(value.summary), 'utf8:1:0')
            return

        self.putAddress(value.address())
        if value.size is not None:
            self.putField('size', value.size // 8)

        if typecode == TypeCode.Function:
            #self.warn('FUNCTION VALUE: %s' % value)
            self.putType(typeid)
            self.putSymbolValue(self.value_as_address(value))
            self.putNumChild(0)
            return

        if typecode == TypeCode.Enum:
            #self.warn('ENUM VALUE: %s' % value.stringify())
            self.putType(typename)
            self.putValue(value.display())
            self.putNumChild(0)
            return

        if typecode == TypeCode.Array:
            #self.warn('ARRAY VALUE: %s' % value)
            self.putCStyleArray(value)
            return

        if typecode == TypeCode.Bitfield:
            #self.warn('BITFIELD VALUE: %s %d %s' % (value.name, value.lvalue, typename))
            self.putNumChild(0)
            #dd = typeobj.target().enumDisplay
            #self.putValue(str(value.lvalue) if dd is None else dd(
            #    value.lvalue, value.laddress, '%d'))
            self.putValue(self.value_as_integer(value))
            self.putType(typename)
            return

        if typecode == TypeCode.Integral:
            #self.warn('INTEGER: %s %s' % (value.name, value))
            self.putNumChild(0)
            self.putValue(self.value_as_integer(value))
            self.putType(typename)
            return

        if typecode == TypeCode.Float:
            #self.warn('FLOAT VALUE: %s' % value)
            self.putValue(value.value())
            self.putNumChild(0)
            self.putType(typename)
            return

        if typecode in (TypeCode.Reference, TypeCode.RValueReference):
            #self.warn('REFERENCE VALUE: %s' % value)
            val = value.dereference()
            if val.laddress != 0:
                self.putItem(val)
            else:
                self.putSpecialValue('nullreference')
            self.putBetterType(typename)
            return

        if typecode == TypeCode.Complex:
            self.putType(typeid)
            self.putValue(value.display())
            self.putNumChild(0)
            return

        if typecode == TypeCode.FortranString:
            self.putValue(self.hexencode(value.data()), 'latin1')
            self.putNumChild(0)
            self.putType(typeid)

        if typename.endswith('[]'):
            # D arrays, gdc compiled.
            n = value['length']
            base = value['ptr']
            self.putType(typename)
            self.putItemCount(n)
            if self.isExpanded():
                self.putArrayData(self.value_as_address(base), n, base.type.target())
            return

        #self.warn('SOME VALUE: %s' % value)
        #self.warn('GENERIC STRUCT: %s' % typeid)
        #self.warn('INAME: %s ' % self.currentIName)
        #self.warn('INAMES: %s ' % self.expandedINames)
        #self.warn('EXPANDED: %s ' % (self.currentIName in self.expandedINames))
        self.putType(typename)

        if value.summary is not None and self.useFancy:
            self.putValue(self.hexencode(value.summary), 'utf8:1:0')
            self.putNumChild(0)
            return

        self.putExpandable()
        self.putEmptyValue()
        #self.warn('STRUCT GUTS: %s  ADDRESS: 0x%x ' % (value.name, value.address()))
        if self.wantQObjectNames():
            #with self.timer(self.currentIName):
            self.putQObjectNameValue(value)
        if self.isExpanded():
            if not self.isCli:
                self.putField('sortable', 1)
            with Children(self, 1, childType=None):
                self.putFields(value)
                if self.wantQObjectNames():
                    self.tryPutQObjectGuts(value)

    def symbolAddress(self, symbolName):
        res = self.parseAndEvaluate('(void *)&' + symbolName)
        return None if res is None else self.value_as_address(res)

    def qtHookDataSymbolName(self):
        return 'qtHookData'

    def qtTypeInfoVersion(self):
        addr = self.symbolAddress(self.qtHookDataSymbolName())
        if addr:
            # Only available with Qt 5.3+
            (hookVersion, x, x, x, x, x, tiVersion) = self.split('ppppppp', addr)
            #self.warn('HOOK: %s TI: %s' % (hookVersion, tiVersion))
            if hookVersion >= 3:
                self.qtTypeInfoVersion = lambda: tiVersion
                return tiVersion
        return None

    def qtDeclarativeHookDataSymbolName(self):
        return 'qtDeclarativeHookData'

    def qtDeclarativeTypeInfoVersion(self):
        addr = self.symbolAddress(self.qtDeclarativeHookDataSymbolName())
        if addr:
            # Only available with Qt 5.6+
            (hookVersion, x, tiVersion) = self.split('ppp', addr)
            if hookVersion >= 1:
                self.qtTypeInfoVersion = lambda: tiVersion
                return tiVersion
        return None

    def addToCache(self, typeobj):
        typename = typeobj.name
        if typename in self.typesReported:
            return
        self.typesReported[typename] = True
        self.typesToReport[typename] = typeobj

    class Value():
        def __init__(self, dumper):
            # This can be helpful to track down from where a Value was created
            #self._stack = inspect.stack()
            self.dumper = dumper
            self.name = None
            self.typeid = None
            self.code = None
            self.size = None
            self.ldata = None        # Target address in case of references and pointers.
            self.laddress = None     # Own address.
            self.lvalue = None
            self.lIsInScope = True
            self.ldisplay = None
            self.summary = None      # Always hexencoded UTF-8.
            self.isBaseClass = None
            self.nativeValue = None
            self.autoDerefCount = 0

        def copy(self):
            val = self.dumper.Value(self.dumper)
            val.dumper = self.dumper
            val.name = self.name
            val.typeid = self.typeid
            val.code = self.code = None
            val.size = self.size
            val.ldata = self.ldata
            val.laddress = self.laddress
            val.lvalue = self.lvalue
            val.lIsInScope = self.lIsInScope
            val.ldisplay = self.ldisplay
            val.summary = self.summary
            val.nativeValue = self.nativeValue
            return val

        @property
        def type(self):
            return self.dumper.Type(self.dumper, self.typeid)

        def check(self):
            #if self.typeid is not None and not isinstance(self.typeid, int):
            #    raise RuntimeError('INCONSISTENT TYPE: %s' % type(self.typeid))
            #if self.laddress is not None and not isinstance(self.laddress, int):
            #    raise RuntimeError('INCONSISTENT ADDRESS: %s' % type(self.laddress))
            pass

        def __str__(self):
            #raise RuntimeError('Not implemented')
            return self.stringify()

        def __int__(self):
            return self.dumper.value_as_integer(self)

        def stringify(self):
            addr = 'None' if self.laddress is None else ('0x%x' % self.laddress)
            if isinstance(self.ldata, int):
                data = str(self.ldata)
            else:
                data = self.dumper.hexencode(self.ldata)
            return "Value(name='%s',typeid=%s, type=%s,data=%s,address=%s)" \
                % (self.name, self.typeid, self.type.name, data, addr)

        def displayEnum(self, form='%d'):
            return self.dumper.value_display_enum(self, form)

        def display(self):
            if self.ldisplay is not None:
                return self.ldisplay
            simple = self.value()
            if simple is not None:
                return str(simple)
            #if self.ldata is not None:
            #    return self.ldata.encode('hex')
            if self.laddress is not None:
                return 'value of type %s at address 0x%x' % (self.type.name, self.laddress)
            return '<unknown data>'

        def pointer(self):
            return self.dumper.value_as_address(self)

        def as_address(self):
            return self.dumper.value_as_address(self)

        def integer(self):
            return self.dumper.value_as_integer(self)

        def floatingPoint(self):
            return self.dumper.value_as_floating_point(self)

        def value(self):
            return self.dumper.value_display(self)

        def extractPointer(self):
            return self.dumper.value_extract_something(self, self.dumper.ptrSize())

        def hasMember(self, name):
            return self.dumper.value_member_by_name(self, name) is not None

        def __getitem__(self, indexish):
            return self.dumper.value_member_by_indexish(self, indexish)

        def members(self, include_bases):
            return self.dumper.value_members(self, include_bases)

        def __add__(self, other):
            return self.dumper.value_plus_something(self, other)

        def __sub__(self, other):
            return self.dumper.value_minus_something(self, other)

        def dereference(self):
            return self.dumper.value_dereference(self)

        def detypedef(self):
            return self.dumper.value_detypedef(self)

        def cast(self, typish):
            return self.dumper.value_cast(self, typish)

        def address(self):
            self.check()
            return self.laddress

        def data(self):
            return self.dumper.value_data(self, self.dumper.type_size(self.typeid))

        def to(self, pattern):
            return self.split(pattern)[0]

        def split(self, pattern):
            return self.dumper.value_split(self, pattern)

    def checkPointer(self, p, align=1):
        ptr = p if isinstance(p, int) else p.pointer()
        self.readRawMemory(ptr, 1)

    def splitArrayType(self, type_name):
        #  "foo[2][3][4]" ->  ("foo", "[3][4]", 2)
        pos1 = len(type_name)
        # In case there are more dimensions we need the inner one.
        while True:
            pos1 = type_name.rfind('[', 0, pos1 - 1)
            pos2 = type_name.find(']', pos1)
            if type_name[pos1 - 1] != ']':
                break

        item_count = type_name[pos1 + 1:pos2]
        return (type_name[0:pos1].strip(), type_name[pos2 + 1:].strip(), int(item_count))

    def registerTypeAlias(self, existing_type_id, alias_id):
        #self.warn('REGISTER ALIAS %s FOR %s' % (aliasId, existingTypeId))
        self.type_alias[alias_id] = existing_type_id

    def init_type_cache(self):
        self.type_name_cache = {}
        self.type_fields_cache = {}
        self.type_alignment_cache = {}
        self.type_bitsize_cache = {}
        self.type_size_cache = {}
        self.type_target_cache = {}
        self.type_template_arguments_cache = {}
        self.type_code_cache = {}
        self.type_enum_display_cache = {}
        self.type_module_name_cache = {}
        self.type_nativetype_cache = {}
        self.type_modulename_cache = {}
        self.type_encoding_cache = {}
        self.type_qobject_based_cache = {}
        self.typeid_cache = {}   # internal typename -> id
        self.typeid_current = 100
        self.typeid_from_typekey = {}   # typename -> id

    def dump_type_cache(self):
        self.warn('NAME: %s' % self.type_name_cache)
        self.warn('CODE: %s' % self.type_code_cache)
        #self.warn('FIELDS: %s' % self.type_fields_cache)
        self.warn('SIZE: %s' % self.type_size_cache)
        self.warn('TARGS: %s' % self.type_template_arguments_cache)
        self.warn('BITSIZE: %s' % self.type_bitsize_cache)
        self.warn('TARGET: %s' % self.type_target_cache)
        #self.warn('NATIVETYPE: %s' % self.type_nativetype_cache)

    def dump_typeid(self, typeid):
        self.warn('  NAME: %s' % self.type_name_cache.get(typeid, None))
        self.warn('  CODE: %s' % self.type_code_cache.get(typeid, None))
        #self.warn('  FIELDS: %s' % self.type_fields_cache.get(typeid, None))
        self.warn('  SIZE: %s' % self.type_size_cache.get(typeid, None))
        self.warn('  TARGS: %s' % self.type_template_arguments_cache.get(typeid, None))
        self.warn('  BITSIZE: %s' % self.type_bitsize_cache.get(typeid, None))
        self.warn('  TARGET: %s' % self.type_target_cache.get(typeid, None))
        #self.warn('  NATIVETYPE: %s' % self.type_nativetype_cache.get(typeid, None))

    def typeid_for_typish(self, typish):
        if isinstance(typish, int):
            return typish
        if isinstance(typish, str):
            return self.typeid_for_string(typish)
        if isinstance(typish, self.Type):
            return typish.typeid
        self.warn('NO TYPE FOR TYPISH: %s' % str(typish))
        return 0

    def sanitize_type_name(self, typeid_str):
        if not ' ' in  typeid_str:
            # FIXME: This uses self.qtNamespace() too early.
            #typeid_arr.append(self.qtNamespace())
            return typeid_str.replace('@', '')
        typeid_arr = []
        last_char_was_space = False
        for c in typeid_str:
            if c == '@' in typeid_str:
                # FIXME: This uses self.qtNamespace() too early.
                #typeid_arr.append(self.qtNamespace())
                pass
            elif c == ' ':
                last_char_was_space = True
            elif c in '&*<>,':
                last_char_was_space = False
                typeid_arr.append(c)
            else:
                if last_char_was_space:
                    typeid_arr.append(' ')
                    last_char_was_space = False
                typeid_arr.append(c)
        #self.warn("SANITIZE: '%s' TO '%s'" % (typeid_str, ''.join(typeid_arr)))
        return ''.join(typeid_arr)

    def typeid_for_string(self, typeid_str, type_name=None):
        #typeid = self.typeid_cache.get(typeid_str, None)
        #if typeid is not None:
        #    return typeid
        sane_typeid_str = self.sanitize_type_name(typeid_str)
        typeid = self.typeid_cache.get(sane_typeid_str, None)
        if typeid is not None:
            return typeid

        self.typeid_current += 1
        if type_name is None:
            type_name = sane_typeid_str
        typeid = self.typeid_current
        self.typeid_cache[typeid_str] = typeid
        self.typeid_cache[sane_typeid_str] = typeid
        self.type_name_cache[typeid] = type_name
        #if typeid == 103:
        #self.warn("CREATED TYPE: %d %s" % (typeid, sane_typeid_str))
        #if typeid == 135: self.dump_location()
        return typeid

    class Type():
        __slots__ = ['dumper', 'typeid']
        def __init__(self, dumper, typeid):
            self.dumper = dumper
            self.typeid = typeid

        def __str__(self):
            return self.dumper.type_stringify(self.typeid)

        @property
        def name(self):
            return self.dumper.type_name(self.typeid)

        @property
        def code(self):
            return self.dumper.type_code(self.typeid)

        def bitsize(self):
            return self.dumper.type_bitsize(self.typeid)

        def size(self):
            return self.dumper.type_size(self.typeid)

        def target(self):
            return self.dumper.Type(self.dumper, self.dumper.type_target(self.typeid))

        @property
        def moduleName(self):
            return self.dumper.type_modulename_cache.get(self.typeid, None)

        def __getitem__(self, index):
            if isinstance(index, int):
                return self.dumper.type_template_argument(self.typeid, index)
            raise RuntimeError('CANNOT INDEX TYPE')

        def check(self):
            #if self.tdata.name is None:
            #    raise RuntimeError('TYPE WITHOUT NAME: %s' % self.typeid)
            pass

        def dereference(self):
            return self.dumper.Type(self.dumper, self.dumper.type_dereference(self.typeid))

        def templateArguments(self):
            return self.dumper.type_template_arguments(self.typeid)

        def templateArgument(self, index):
            return self.dumper.type_template_argument(self.typeid, index)

        def isSimpleType(self):
            return self.code in (TypeCode.Integral, TypeCode.Float, TypeCode.Enum)

        def alignment(self):
            return self.dumper.type_alignment(self.typeid)

        def pointer(self):
            return self.dumper.Type(self.dumper, self.dumper.create_pointer_typeid(self.typeid))

        def stripTypedefs(self):
            return self.dumper.Type(self.dumper, self.dumper.type_target(self.typeid))

        def isMovableType(self):
            if self.code in (TypeCode.Pointer, TypeCode.Integral, TypeCode.Float):
                return True
            strippedName = self.dumper.stripNamespaceFromType(self.name)
            if strippedName in (
                    'QBrush', 'QBitArray', 'QByteArray', 'QCustomTypeInfo',
                    'QChar', 'QDate', 'QDateTime', 'QFileInfo', 'QFixed',
                    'QFixedPoint', 'QFixedSize', 'QHashDummyValue', 'QIcon',
                    'QImage', 'QLine', 'QLineF', 'QLatin1Char', 'QLocale',
                    'QMatrix', 'QModelIndex', 'QPoint', 'QPointF', 'QPen',
                    'QPersistentModelIndex', 'QResourceRoot', 'QRect', 'QRectF',
                    'QRegExp', 'QSize', 'QSizeF', 'QString', 'QTime', 'QTextBlock',
                    'QUrl', 'QVariant',
                    'QXmlStreamAttribute', 'QXmlStreamNamespaceDeclaration',
                    'QXmlStreamNotationDeclaration', 'QXmlStreamEntityDeclaration'
            ):
                return True
            if strippedName == 'QStringList':
                return self.dumper.qtVersionAtLeast(0x050000)
            if strippedName == 'QList':
                return self.dumper.qtVersionAtLeast(0x050600)
            return False

    class Field:
        __slots__ = ['name', 'typeid', 'bitsize', 'bitpos', 'is_struct', 'is_artificial', 'is_base_class']

        def __init__(self, name=None, typeid=None, bitsize=None, bitpos=None,
                    extractor=None, is_struct=False, is_artificial=False, is_base_class=False):
            self.name = name
            self.typeid = typeid
            self.bitsize = bitsize
            self.bitpos = bitpos
            self.is_struct = is_struct
            self.is_base_class = is_base_class


    def ptrCode(self):
        return 'I' if self.ptrSize() == 4 else 'Q'

    def fromPointerData(self, bytes_value):
        return struct.unpack(self.packCode + self.ptrCode(), bytes_value)

    def createPointerValue(self, target_address, target_typish):
        if not isinstance(target_address, int):
            raise RuntimeError('Expected integral address value in createPointerValue(), got %s'
                               % type(target_typish))
        val = self.Value(self)
        val.ldata = target_address
        val.typeid = self.create_pointer_typeid(self.create_typeid(target_typish))
        return val
        #target_typeid = self.create_typeid(target_typish)
        #if self.useDynamicType:
        #    target_typeid = self.dynamic_typeid_at_address(target_typeid, target_address)
        #val.typeid = self.create_pointer_typeid(target_typeid)
        #return val

    def createPointerType(self, target_typish):
        typeid = self.create_pointer_typeid(self.typeid_for_typish(target_typish))
        return self.Type(self, typeid)

    def create_pointer_typeid(self, target_typeid):
        name = self.type_name(target_typeid) + ' *'
        typeid = self.typeid_for_string(name)
        self.type_size_cache[typeid] = self.ptrSize()
        self.type_alignment_cache[typeid] = self.ptrSize()
        self.type_code_cache[typeid] = TypeCode.Pointer
        self.type_target_cache[typeid] = target_typeid
        return typeid

    def create_reference_typeid(self, target_typeid):
        type_name = self.type_name_cache[target_typeid] + ' &'
        typeid = self.typeid_for_string(type_name)
        self.type_code_cache[typeid] = TypeCode.Reference
        self.type_target_cache[typeid] = target_typeid
        #self.type_size_cache[typeid] = self.ptrSize()  # Needed for Gdb13393 test.
        return typeid

    def create_rvalue_reference_typeid(self, target_typeid):
        type_name = self.type_name_cache[target_typeid] + ' &&'
        typeid = self.typeid_for_string(type_name)
        self.type_code_cache[typeid] = TypeCode.RValueReference
        self.type_target_cache[typeid] = target_typeid
        return typeid

    def create_array_typeid(self, target_typeid, count):
        target_type_name = self.type_name(target_typeid)
        if target_type_name.endswith(']'):
            (prefix, suffix, inner_count) = self.splitArrayType(target_type_name)
            type_name = '%s[%d][%d]%s' % (prefix, count, inner_count, suffix)
        else:
            type_name = '%s[%d]' % (target_type_name, count)
        typeid = self.typeid_for_string(type_name)
        self.type_code_cache[typeid] = TypeCode.Array
        self.type_target_cache[typeid] = target_typeid
        self.type_size_cache[typeid] = self.type_size(target_typeid) * count
        self.type_alignment_cache[typeid] = self.type_alignment_cache.get(target_typeid, None)
        return typeid

    def create_bitfield_typeid(self, target_typeid, bitsize):
        target_typename = self.type_name(target_typeid)
        typeid = self.typeid_for_string('%s:%d' % (target_typename, bitsize))
        self.type_name_cache[typeid] = '%s : %d' % (target_typename, bitsize)
        self.type_code_cache[typeid] = TypeCode.Bitfield
        self.type_target_cache[typeid] = target_typeid
        self.type_bitsize_cache[typeid] = bitsize
        return typeid

    def create_typedefed_typeid(self, target_typeid, type_name, type_key):
        typeid = self.typeid_for_string(type_key, type_name)
        # Happens for C-style struct in GDB: typedef { int x; } struct S1;
        if target_typeid == typeid:
            return target_typeid
        self.type_code_cache[typeid] = TypeCode.Typedef
        self.type_target_cache[typeid] = target_typeid
        size = self.type_size_cache.get(target_typeid, None)
        if size is not None:
            self.type_size_cache[typeid] = size
        return typeid

    def createType(self, typish, size=None):
        return self.Type(self, self.create_typeid(typish, size))

    def create_typeid(self, typish, size=None):
        if isinstance(typish, int):
            return typish
        if isinstance(typish, self.Type):
            return typish.typeid
        if isinstance(typish, str):
            return self.create_typeid_from_name(typish)
        raise RuntimeError('NEED TYPE, NOT %s' % type(typish))

    def cheap_typeid_from_name(self, typename_):
        ns = self.qtNamespace()
        typename = typename_.replace('@', ns)
        return self.cheap_typeid_from_name_nons(typename)

    def cheap_typeid_from_name_nons(self, typename):
        if typename in self.typeid_cache:
            return self.typeid_for_string(typename)

        if typename.startswith('QList<') or typename.startswith('QVector<'):
            typeid = self.typeid_for_string(typename)
            if typeid:
                size = 3 * self.ptrSize() if self.qtVersionAtLeast(0x060000) else self.ptrSize()
                self.type_code_cache[typeid] = TypeCode.Struct
                self.type_size_cache[typeid] = size
                return typeid

        if typename.endswith('*'):
            inner_typeid = self.cheap_typeid_from_name_nons(typename[0:-1])
            if inner_typeid != 0:
                return self.create_pointer_typeid(inner_typeid)

        return 0

    def create_typeid_from_name(self, typename_, size=None):
        ns = self.qtNamespace()
        typename = typename_.replace('@', ns)

        if typename in self.typeid_cache:
            return self.typeid_for_string(typename)

        # This triggers for boost::variant<int, std::string> due to the mis-encoding
        # of the second template parameter.   [MARK_A]
        knownType = self.lookupType(typename)
        #self.warn('KNOWN: %s FOR %s' % (knownType, typename))
        if knownType is not None:
            #self.warn('USE FROM NATIVE')
            #self.dump_location()
            return knownType.typeid

        #self.warn('FAKING: %s SIZE: %s' % (typename, size))
        typeid = self.typeid_for_string(typename)
        if size is not None:
            self.type_size_cache[typeid] = size
            self.type_code_cache[typeid] = TypeCode.Struct
        if typename.endswith('*'):
            self.type_code_cache[typeid] = TypeCode.Pointer
            self.type_size_cache[typeid] = self.ptrSize()
            self.type_target_cache[typeid] = self.typeid_for_string(typename[:-1].strip())

        #self.dump_location()
        #self.warn('CREATED TYPE: %s' % typeid)
        return typeid

    def createValueFromAddress(self, address, typish):
        val = self.Value(self)
        val.typeid = self.create_typeid(typish)
        #self.warn('CREATING %s AT 0x%x' % (val.type.name, address))
        val.laddress = address
        if self.useDynamicType:
            val.typeid = self.dynamic_typeid_at_address(val.typeid, address)
        return val

    def createValueFromData(self, data, typish):
        val = self.Value(self)
        val.typeid = self.create_typeid(typish)
        #self.warn('CREATING %s WITH DATA %s' % (val.type.name, self.hexencode(data)))
        val.ldata = data
        val.check()
        return val

    def createValue(self, datish, typish):
        if isinstance(datish, int):  # Used as address.
            return self.createValueFromAddress(datish, typish)
        if isinstance(datish, bytes):
            return self.createValueFromData(datish, typish)
        raise RuntimeError('EXPECTING ADDRESS OR BYTES, GOT %s' % type(datish))

    def createProxyValue(self, proxy_data, type_name):
        typeid = self.typeid_for_string(type_name)
        self.type_code_cache[typeid] = TypeCode.Struct
        val = self.Value(self)
        val.typeid = typeid
        val.ldata = proxy_data
        return val

    class StructBuilder():
        def __init__(self, dumper):
            self.dumper = dumper
            self.pattern = ''
            self.current_size = 0
            self.fields = []
            self.autoPadNext = False
            self.maxAlign = 1

        def add_field(self, field_size, field_code=None, field_is_struct=False,
                      field_name=None, field_typeid=0, field_align=1):

            if field_code is None:
                field_code = '%ss' % field_size

            #self.dumper.warn("FIELD SIZE: %s %s %s " % (field_name, field_size, str(field_align)))

            if self.autoPadNext:
                padding = (field_align - self.current_size) % field_align
                #self.warn('AUTO PADDING AT %s BITS BY %s BYTES' % (self.current_size, padding))
                field = self.dumper.Field(self.dumper, bitpos=self.current_size * 8,
                                          bitsize=padding * 8)
                self.pattern += '%ds' % padding
                self.current_size += padding
                self.fields.append(field)
                self.autoPadNext = False

            if field_align > self.maxAlign:
                self.maxAlign = field_align

            #self.warn("MAX ALIGN: %s" % self.maxAlign)

            field = self.dumper.Field(name=field_name, typeid=field_typeid,
                                      is_struct=field_is_struct, bitpos=self.current_size *8,
                                      bitsize=field_size * 8)

            self.pattern += field_code
            self.current_size += field_size
            self.fields.append(field)

    def describe_struct_member(self, typename):
        typename = typename.replace('@', self.qtNamespace())

        typeid = self.cheap_typeid_from_name_nons(typename)
        if typeid:
            size = self.type_size(typeid)
            if size is not None:
                return size, typeid

        typeobj = self.lookupType(typename)
        #self.warn("LOOKUP FIELD TYPE: %s TYPEOBJ: %s" % (typename, typeobj))
        if typeobj is not None:
            typeid = typeobj.typeid
            size = self.type_size(typeid)
            if size is not None:
                return size, typeid

        self.warn("UNKNOWN EMBEDDED TYPE: %s" % typename)
        return 0, 0

    @functools.lru_cache(maxsize = None)
    def describeStruct(self, pattern):
        ptrSize = self.ptrSize()
        builder = self.StructBuilder(self)
        n = None
        typename = ''
        readingTypeName = False
        #self.warn("PATTERN: %s" % pattern)
        for c in pattern:
            #self.warn("PAT CODE: %s %s" % (c, str(n)))
            if readingTypeName:
                if c == '}':
                    readingTypeName = False
                    n, field_typeid = self.describe_struct_member(typename)

                    field_align = self.type_alignment(field_typeid)
                    builder.add_field(n,
                                      field_is_struct=True,
                                      field_typeid=field_typeid,
                                      field_align=field_align)
                    typename = None
                    n = None
                else:
                    typename += c
            elif c == 't':  # size_t
                builder.add_field(ptrSize, self.ptrCode(), field_align=ptrSize)
            elif c == 'p':  # Pointer as int
                builder.add_field(ptrSize, self.ptrCode(), field_align=ptrSize)
            elif c == 'P':  # Pointer as Value
                builder.add_field(ptrSize, '%ss' % ptrSize, field_align=ptrSize)
            elif c in ('d'):
                builder.add_field(8, c, field_align=ptrSize)  # field_type = 'double' ?
            elif c in ('q', 'Q'):
                builder.add_field(8, c, field_align=ptrSize)
            elif c in ('i', 'I', 'f'):
                builder.add_field(4, c, field_align=4)
            elif c in ('h', 'H'):
                builder.add_field(2, c, field_align=2)
            elif c in ('b', 'B', 'c'):
                builder.add_field(1, c, field_align=1)
            elif c >= '0' and c <= '9':
                if n is None:
                    n = ''
                n += c
            elif c == 's':
                builder.add_field(int(n), field_align=1)
                n = None
            elif c == '{':
                readingTypeName = True
                typename = ''
            elif c == '@':
                if n is None:
                    # Automatic padding depending on next item
                    builder.autoPadNext = True
                else:
                    # Explicit padding.
                    padding = (int(n) - builder.current_size) % int(n)
                    field = self.Field(self)
                    builder.pattern += '%ds' % padding
                    builder.current_size += padding
                    builder.fields.append(field)
                    n = None
            else:
                raise RuntimeError('UNKNOWN STRUCT CODE: %s' % c)
        pp = builder.pattern
        size = builder.current_size
        fields = builder.fields
        tailPad = (builder.maxAlign - size) % builder.maxAlign
        size += tailPad
        #self.warn("FIELDS: %s" % ((pp, size, fields),))
        return (pp, size, fields)

    def type_stringify(self, typeid):
        return 'Type(id="%s",name="%s",bsize=%s,code=%s)'% (
            str(typeid),
            self.type_name_cache.get(typeid, '?'),
            self.type_bitsize_cache.get(typeid, '?'),
            self.type_code_cache.get(typeid, '?'))

    def type_name(self, typeid):
        name = self.type_name_cache.get(typeid, None)
        if name is None:
            self.dump_type_cache()
            self.check_typeid(typeid)
            raise RuntimeError('UNNAMED TYPE: %d' % typeid)
        return name

    def type_code(self, typeid):
        # This does not seem to be needed for GDB and LLDB
        if not typeid in self.type_code_cache:
            typename = self.type_name_cache.get(typeid, None)
            if typename is None:
                raise RuntimeError('NAME/ID ERROR FOR %s' % typeid)
            #self.warn("EMERGENCY LOOKUP: %s "  % typename)
            typeobj = self.lookupType(typename)
            if typeobj is None:
                #self.warn("EMERGENCY LOOKUP FAILED: %s "  % typeid)
                #self.dump_type_cache()
                return TypeCode.Struct
            #self.warn("EMERGENCY LOOKUP SUCCEEDED: %s "  % typeid)
            typeid = typeobj.typeid
        return self.type_code_cache[typeid]

    def type_bitpos(self, typeid):
        return self.type_bitpos_cache[typeid]

    def type_target(self, typeid):
        return self.type_target_cache.get(typeid, None)
        targetid = self.type_target_cache.get(typeid, None)
        if not targetid in self.type_code_cache:
            typename = self.type_name_cache.get(targetid, None)
            if typename is None:
                raise RuntimeError('NAME/ID ERROR FOR TARGET %s' % targetid)
            typeobj = self.lookupType(typename)
            if typeobj is None:
                #self.warn("EMERGENCY LOOKUP FAILED FOR %s %s "  % (typename, typeid))
                #self.dump_type_cache()
                return 0 # Void type id
        return targetid

    def type_template_arguments(self, typeid):
        targs = []
        #self.dump_type_cache()
        #self.warn('TRY TEMPLATE ARGS FOR %s' % typeid)
        for index in range(0, 100):
            targ = self.type_template_argument(typeid, index)
            #self.warn('INDEX %s %s' % (index, targ))
            if targ is None:
                break
            targs.append(targ)
        #self.warn('TARGS %s' % targs)
        return targs

    def nativeTemplateParameter(self, typeid, index, nativeType):
        return None

    def type_template_argument(self, typeid, index):
        targ = self.type_template_arguments_cache.get((typeid, index), None)
        if targ is not None:
            return targ

        native_type = self.type_nativetype_cache.get(typeid, None)
        if native_type is not None:
            targ = self.nativeTemplateParameter(typeid, index, native_type)
            if targ is not None:
                self.type_template_arguments_cache[(typeid, index)] = targ
                return targ

        # FIXME: The block below is apparently not needed anymore in the GDB
        # and LLDB cases, so removing also doesn't bring performance. But it
        # is at least potentially one source of type lookups.
        #typename = self.type_name(typeid)
        #self.dump_type_cache()
        #self.warn('TEMPLATE ARGS FOR %s %s' % (typeid, typename))
        #typeobj = self.lookupType(typename)
        #if typeobj is not None:
        #    #self.warn('  FOUNT NATIVE %s %s, %s' % (typeid, typeobj, native_type))
        #    native_type = self.type_nativetype_cache.get(typeobj.typeid, None)
        #    #targ = self.type_template_argument(typeobj.typeid, index)
        #    targ = self.nativeTemplateParameter(typeobj.typeid, index, native_type)
        #    if targ is not None:
        #        self.type_template_arguments_cache[(typeid, index)] = targ
        #        return targ

        # Native lookups didn't help. Happens for 'wrong' placement of 'const'
        # etc. with LLDB or template parameter packs with gcc in boost::variant
        # 13.2.0. But not all is lost:
        self.fill_template_parameters_manually(typeid)
        targ = self.type_template_arguments_cache.get((typeid, index), None)
        return targ

    def type_alignment(self, typeid):
        alignment = self.type_alignment_cache.get(typeid, None)
        if alignment is not None:
            return alignment

        code = self.type_code_cache.get(typeid, None)
        if code in (TypeCode.Typedef, TypeCode.Array):
            alignment = self.type_alignment(self.type_target_cache[typeid])
        elif code in (TypeCode.Integral, TypeCode.Float, TypeCode.Enum):
            name = self.type_name(typeid)
            if name in ('double', 'long long', 'unsigned long long'):
                # Crude approximation.
                alignment =  8 if self.isWindowsTarget() else self.ptrSize()
            else:
                alignment = self.type_size(typeid)
        elif code in (TypeCode.Pointer, TypeCode.Reference, TypeCode.RValueReference):
            alignment = self.ptrSize()
        else:
            alignment = self.nativeStructAlignment(self.type_nativetype(typeid))
            #self.warn("GUESSING ALIGNMENT %s FOR TYPEID %s" % (alignment, typeid))
        self.type_alignment_cache[typeid] = alignment
        return alignment


    def type_nativetype(self, typeid):
        native_type = self.type_nativetype_cache.get(typeid, None)
        if native_type is not None:
            return native_type

        typename = self.type_name(typeid)
        native_type = self.lookupNativeType(typename)
        # Also cache unsuccessful attempts
        self.type_nativetype_cache[typeid] = native_type

        return native_type


    def type_size(self, typeid):
        self.check_typeid(typeid)
        size = self.type_size_cache.get(typeid, None)
        if size is not None:
            return size

        nativeType = self.type_nativetype(typeid)
        if self.isCdb:
            size = nativeType.bitsize() // 8
        else:
            if not self.type_size_cache.get(typeid):
                self.from_native_type(nativeType)
            size = self.type_size_cache.get(typeid, None)

        if size is not None:
            self.type_size_cache[typeid] = size
        else:
            self.dump_type_cache()
            self.warn("CANNOT DETERMINE SIZE FOR TYPE %s" % str(typeid))

        return size

    def type_bitsize(self, typeid):
        bitsize = self.type_bitsize_cache.get(typeid, None)
        if bitsize is None:
            bitsize = 8 * self.type_size(typeid)
            self.type_bitsize_cache[typeid] = bitsize
        return bitsize

    def dynamic_typeid_at_address(self, base_typeid, address):
        #with self.dumper.timer('dynamic_typeid_at_address %s 0x%s' % (self.name, address)):
        type_code = self.type_code_cache.get(base_typeid, None)
        if type_code != TypeCode.Struct:
           #self.dump_type_cache()
           #self.warn('SHORT CUT FOR BASE ID: %d TC: %s' % (base_typeid, type_code))
           return base_typeid

        # This turned out to be expensive.
        #try:
        #    vtbl = self.extract_pointer_at_address(address)
        #except:
        #    return base_typeid
        ##self.warn('VTBL: 0x%x' % vtbl)
        #if not self.couldBePointer(vtbl):
        #    return base_typeid
        #self.warn("DYN TYPE FOR %s %s" % (base_typeid, self.type_name(base_typeid)))

        return self.nativeDynamicType(address, base_typeid)

    # This is the generic version for synthetic values.
    # The native backends replace it in their fromNativeValue()
    # implementations.
    def value_members(self, value, include_bases):
        #self.warn("LISTING MEMBERS OF %s" % value)
        #self.warn("LISTING MEMBERS OF TYPE %s %s" % (value.typeid, self.type_name(value.typeid)))
        typeid = value.typeid

        members = self.type_fields_cache.get(typeid, None)
        if members is not None:
            return members

        members = []
        native_type = self.type_nativetype_cache.get(typeid, None)
        if native_type is None:
            native_type = self.lookupNativeType(self.type_name(typeid))
        if not native_type is None:
            members = self.nativeListMembers(value, native_type, include_bases)
            #self.warn("FIELDS 2: %s" % ', '.join(str(f) for f in members))
        else:
            self.warn("NO NATIVE TYPE FIELDS FOR: %s" % typeid)

        #self.warn("GOT MEMBERS: %s" % ', '.join(str(f.name) for f in members))
        return members

    def value_member_by_field(self, value, field):
        #self.warn("EXTRACTING MEMBER '%s' OF %s AT OFFSET %s" % (field.name, field.typeid, field.bitpos))
        val = self.Value(self)
        val.typeid = field.typeid
        val.name = field.name
        val.isBaseClass = field.is_base_class
        #self.warn('CREATING %s WITH DATA %s' % (val.type.name, self.hexencode(data)))
        field_offset = field.bitpos // 8
        if value.laddress is not None:
            val.laddress = value.laddress + field_offset
        field_size = (field.bitsize + 7) // 8
        blob = self.value_data(value, field_offset + field_size)
        val.ldata = blob[field_offset:field_offset + field_size]
        #self.dump_location()
        return val

    def value_member_by_name(self, value, name):
        #field = self.type_fields_cache.get((value.typeid, name), None)
        #if field is not None:
        #    return self.value_member_by_field(value, field)

        #self.dump_location()
        #self.warn("WANT MEMBER  '%s' OF  '%s'" % (name, value))
        #value.check()
        value_typecode = self.type_code(value.typeid)
        if value_typecode == TypeCode.Typedef:
            return self.value_member_by_name(self.value_detypedef(value), name)
        if value_typecode in (TypeCode.Pointer, TypeCode.Reference, TypeCode.RValueReference):
            res = self.value_member_by_name(self.value_dereference(value), name)
            if res is not None:
                return res
        if value_typecode == TypeCode.Struct:
            #self.warn('SEARCHING FOR MEMBER: %s IN %s' % (name, value.type.name))
            members = self.value_members(value, True)
            #self.warn('MEMBERS: %s' % ', '.join(str(m.name) for m in members))
            bases = []
            for member in members:
                #self.warn('CHECKING FIELD %s' % member.name)
                if member.type.code == TypeCode.Typedef:
                    member = member.detypedef()
                if member.name == name:
                    #self.warn('FOUND MEMBER 1: %s IN %s' % (name, value.type.name))
                    return member
                if member.isBaseClass:
                    bases.append(member)
            if self.isCdb:
                for base in bases:
                    # self.warn("CHECKING BASE CLASS '%s' for '%s'" % (base.type.name, name))
                    res = self.value_member_by_name(base, name)
                    if res is not None:
                        # self.warn('FOUND MEMBER 2: %s IN %s' % (name, value.type.name))
                        return res
            else:
                for member in members:
                    if member.type.code == TypeCode.Typedef:
                        member = member.detypedef()
                    if member.name == name:  # Could be base class.
                        return member
                    if member.type.code == TypeCode.Struct:
                        res = self.value_member_by_name(member, name)
                        if res is not None:
                            #self.warn('FOUND MEMBER 2: %s IN %s' % (name, value.type.name))
                            return res

            #self.warn('DID NOT FIND MEMBER: %s IN %s' % (name, value.type.name))
            #self.dump_location()
        return None

    def value_member_by_indexish(self, value, indexish):
        #self.warn('GET ITEM %s %s' % (self, indexish))
        #value.check()
        value_typecode = self.type_code(value.typeid)
        if isinstance(indexish, str):
            if value_typecode == TypeCode.Pointer:
                #self.warn('GET ITEM %s DEREFERENCE TO %s' % (value, value.dereference()))
                return value.dereference().__getitem__(indexish)
            res = self.value_member_by_name(value, indexish)
            if res is None:
                raise RuntimeError('No member named %s in type %s'
                                   % (indexish, value.type.name))
            return res
        if isinstance(indexish, int):
            if value_typecode == TypeCode.Array:
                addr = value.laddress + int(indexish) * value.type.target().size()
                return self.createValueFromAddress(addr, value.type.target())
            if value_typecode == TypeCode.Pointer:
                addr = value.pointer() + int(indexish) * value.type.target().size()
                return self.createValueFromAddress(addr, value.type.target())
            return self.value_members(value, False)[indexish]
        raise RuntimeError('BAD INDEX TYPE %s' % type(indexish))

    def value_extract_bits(self, value, bitpos, bitsize):
        value_size = self.type_size(value.typeid)
        ldata = bytes(self.value_data(value, value_size))
        bdata = ''.join([format(x, '0>8b')[::-1] for x in ldata])
        fdata = bdata[bitpos : bitpos + bitsize]
        fdata = fdata[::-1]
        return int(fdata, 2)

    def value_display_enum(self, value, form='%d'):
        size = value.type.size()
        intval = self.value_extract_integer(value, size, False)
        dd = self.type_enum_display_cache.get(value.typeid, None)
        if dd is None:
            return str(intval)
        return dd(intval, value.laddress, form)

    def value_as_address(self, value):
        return self.value_extract_integer(value, self.ptrSize(), False)

    def value_as_integer(self, value):
        if isinstance(value.ldata, int):
            return value.ldata
        type_name = self.type_name(value.typeid)
        signed = type_name != 'unsigned' \
            and not type_name.startswith('unsigned ') \
            and  type_name.find(' unsigned ') == -1
        size = value.type.size()
        return self.value_extract_integer(value, size, signed)

    def value_as_floating_point(self, value):
        if value.nativeValue is not None and not self.isCdb:
            return str(value.nativeValue)
        if self.type_code(value.typeid) == TypeCode.Typedef:
            return self.value_as_floating_point(self.value_detypedef(value))
        if value.type.size() == 8:
            blob = self.value_data(value, 8)
            return struct.unpack_from(self.packCode + 'd', blob, 0)[0]
        if value.type.size() == 4:
            blob = self.value_data(value, 4)
            return struct.unpack_from(self.packCode + 'f', blob, 0)[0]
        # Fall back in case we don't have a nativeValue at hand.
        # FIXME: This assumes Intel's 80bit extended floats. Which might
        # be wrong.
        l, h = value.split('QQ')
        if True:  # 80 bit floats
            sign = (h >> 15) & 1
            exp = (h & 0x7fff)
            fraction = l
            bit63 = (l >> 63) & 1
            #self.warn("SIGN: %s  EXP: %s  H: 0x%x L: 0x%x" % (sign, exp, h, l))
            if exp == 0:
                if bit63 == 0:
                    if l == 0:
                        res = '-0' if sign else '0'
                    else:
                        res = (-1)**sign * l * 2**(-16382)  # subnormal
                else:
                    res = 'pseudodenormal'
            elif exp == 0x7fff:
                res = 'special'
            else:
                res = (-1)**sign * l * 2**(exp - 16383 - 63)
        else:  # 128 bits
            sign = h >> 63
            exp = (h >> 48) & 0x7fff
            fraction = h & (2**48 - 1)
            #self.warn("SIGN: %s  EXP: %s  FRAC: %s  H: 0x%x L: 0x%x" % (sign, exp, fraction, h, l))
            if exp == 0:
                if fraction == 0:
                    res = -0.0 if sign else 0.0
                else:
                    res = (-1)**sign * fraction / 2**48 * 2**(-62)  # subnormal
            elif exp == 0x7fff:
                res = ('-inf' if sign else 'inf') if fraction == 0 else 'nan'
            else:
                res = (-1)**sign * (1 + fraction / 2**48) * 2**(exp - 63)
        return res

    def value_data(self, value, size):
        if value.ldata is not None:
            return value.ldata[:size]
        if value.laddress is not None:
            return self.value_data_from_address(value.laddress, size)
        raise RuntimeError('CANNOT CONVERT TO BYTES: %s' % value)

    def value_data_from_address(self, address, size):
        if not isinstance(address, int):
            raise RuntimeError('ADDRESS WRONG TYPE: %s' % type(address))
        if not isinstance(size, int):
            raise RuntimeError('SIZE WRONG TYPE: %s' % type(size))
        if size <= 0:
            raise RuntimeError('SIZE WRONG VALUE: %s' % size)
        res = self.readRawMemory(address, size)
        if len(res) > 0:
            return res
        raise RuntimeError('CANNOT READ %d BYTES FROM ADDRESS: %s' % (size, address))

    def value_display(self, value):
        type_code = self.type_code(value.typeid)
        if type_code == TypeCode.Enum:
            return self.value_display_enum(value)
        if type_code == TypeCode.Typedef:
            return self.value_display(self.value_detypedef(value))
        if type_code == TypeCode.Integral:
            return self.value_as_integer(value)
        if type_code == TypeCode.Bitfield:
            return self.value_as_integer(value)
        if type_code == TypeCode.Float:
            return self.value_as_floating_point(value)
        if type_code == TypeCode.Pointer:
            return self.value_as_address(value)
        return None

    def value_detypedef(self, value):
        #value.check()
        #if value.type.code != TypeCode.Typedef:
        #    raise RuntimeError("WRONG")
        val = value.copy()
        val.typeid = self.type_target(value.typeid)
        #self.warn("DETYPEDEF FROM: %s" % self)
        #self.warn("DETYPEDEF TO: %s" % val)
        return val

    def split(self, pattern, value_or_address):
        if isinstance(value_or_address, self.Value):
            return self.value_split(value_or_address, pattern)
        if isinstance(value_or_address, int):
            val = self.Value(self)
            val.laddress = value_or_address
            return self.value_split(val, pattern)
        raise RuntimeError('CANNOT EXTRACT STRUCT FROM %s' % type(value_or_address))

    def value_split(self, value, pattern):
        #self.warn('EXTRACT STRUCT FROM: %s' % self.type)
        (pp, size, fields) = self.describeStruct(pattern)
        #self.warn('SIZE: %s ' % size)

        blob = self.value_data(value, size)
        address = value.laddress

        parts = struct.unpack_from(self.packCode + pp, blob)

        def fix_struct(field, part):
            #self.warn('STRUCT MEMBER: %s' % type(part))
            if field.is_struct:
                res = self.Value(self)
                res.typeid = field.typeid
                res.ldata = part
                if address is not None:
                    res.laddress = address + field.bitpos // 8
                return res
            return part

        if len(fields) != len(parts):
            raise RuntimeError('STRUCT ERROR: %s %s' % (fields, parts))
        return tuple(map(fix_struct, fields, parts))

    def type_dereference(self, typeid):
        if self.type_code(typeid) == TypeCode.Typedef:
            return self.type_dereference(self.type_target(typeid))
        return self.type_target(typeid)

    def type_strip_typedefs(self, typeid):
        if self.type_code(typeid) == TypeCode.Typedef:
            return self.type_strip_typedefs(self.type_target(typeid))
        return typeid

    def value_dereference(self, value):
        value.check()
        #if value.type.code == TypeCode.Typedef:
        #    return self.value_dereference(self.value_detypedef(value))
        val = self.Value(self)
        if value.type.code in (TypeCode.Reference, TypeCode.RValueReference):
            val.summary = value.summary
            if value.nativeValue is None:
                val.laddress = value.pointer()
                if val.laddress is None and value.laddress is not None:
                    val.laddress = value.laddress
                val.typeid = self.type_dereference(value.typeid)
                if self.useDynamicType:
                    val.typeid = self.nativeDynamicType(val.laddress, val.typeid)
            else:
                val = self.nativeValueDereferenceReference(value)
        elif value.type.code == TypeCode.Pointer:
            try:
                val = self.nativeValueDereferencePointer(value)
            except:
                val.laddress = value.pointer()
                val.typeid = self.type_dereference(value.typeid)
                if self.useDynamicType:
                    val.typeid = self.nativeDynamicType(val.laddress, val.typeid)
        else:
            raise RuntimeError("WRONG: %s" % value.type.code)

        return val

    def value_cast(self, value, typish):
        value.check()
        val = self.Value(self)
        val.laddress = value.laddress
        val.ldata = value.ldata
        val.typeid = self.create_typeid(typish)
        return val

    def value_plus_something(self, value, other):
        value.check()
        if isinstance(other, int):
            stripped = self.type_strip_typedefs(value.typeid)
            if self.type_code(stripped) == TypeCode.Pointer:
                item_size = self.type_size(self.type_dereference(stripped))
                address = self.value_as_address(value) + item_size * other
                val = self.Value(self)
                val.laddress = None
                val.ldata = address
                val.typeid = value.typeid
                return val
        raise RuntimeError('BAD DATA TO ADD TO: %s %s' % (value.type, other))

    def value_minus_something(self, value, other):
        value.check()
        if other.type.name == value.type.name:
            stripped = self.type_strip_typedefs(value.typeid)
            if self.type_code(stripped.code) == TypeCode.Pointer:
                item_size = self.type_size(self.type_dereference(stripped))
                return (value.pointer() - other.pointer()) // item_size
        raise RuntimeError('BAD DATA TO SUB TO: %s %s' % (value.type, other))

