Debugger: Avoid quadratic behavior for larger debug output

Task-number: QTCREATORBUG-28111
Change-Id: I0567b6af9f74c2d335d19a52765834ef7cee9449
Reviewed-by: hjk <hjk@qt.io>
Reviewed-by: David Schulz <david.schulz@qt.io>
Reviewed-by: <github-actions-qt-creator@cristianadam.eu>
This commit is contained in:
hjk
2022-11-08 12:42:04 +01:00
parent bacb05e285
commit bb11788a0a
4 changed files with 82 additions and 93 deletions

View File

@@ -353,9 +353,6 @@ class Dumper(DumperBase):
self.ptrSize = lambda: size self.ptrSize = lambda: size
return size return size
def put(self, stuff):
self.output += stuff
def stripQintTypedefs(self, typeName): def stripQintTypedefs(self, typeName):
if typeName.startswith('qint'): if typeName.startswith('qint'):
prefix = '' prefix = ''
@@ -423,7 +420,7 @@ class Dumper(DumperBase):
self.setVariableFetchingOptions(args) self.setVariableFetchingOptions(args)
self.output = '' self.output = []
self.currentIName = 'local' self.currentIName = 'local'
self.put('data=[') self.put('data=[')
@@ -445,10 +442,11 @@ class Dumper(DumperBase):
self.qtNamespaceToReport = self.qtNamespace() self.qtNamespaceToReport = self.qtNamespace()
if self.qtNamespaceToReport: if self.qtNamespaceToReport:
self.output += ',qtnamespace="%s"' % self.qtNamespaceToReport self.put(',qtnamespace="%s"' % self.qtNamespaceToReport)
self.qtNamespaceToReport = None self.qtNamespaceToReport = None
self.reportResult(self.output, args) self.reportResult(''.join(self.output), args)
self.output = []
def report(self, stuff): def report(self, stuff):
sys.stdout.write(stuff + "\n") sys.stdout.write(stuff + "\n")

View File

@@ -112,7 +112,7 @@ class Children():
self.d.currentNumChild = self.savedNumChild self.d.currentNumChild = self.savedNumChild
self.d.currentMaxNumChild = self.savedMaxNumChild self.d.currentMaxNumChild = self.savedMaxNumChild
if self.d.isCli: if self.d.isCli:
self.d.output += '\n' + ' ' * self.d.indent self.d.put('\n' + ' ' * self.d.indent)
self.d.put(self.d.childrenSuffix) self.d.put(self.d.childrenSuffix)
return True return True
@@ -173,7 +173,7 @@ class DumperBase():
self.displayStringLimit = 100 self.displayStringLimit = 100
self.useTimeStamps = False self.useTimeStamps = False
self.output = '' self.output = []
self.typesReported = {} self.typesReported = {}
self.typesToReport = {} self.typesToReport = {}
self.qtNamespaceToReport = None self.qtNamespaceToReport = None
@@ -309,9 +309,9 @@ class DumperBase():
self.putField('name', item.name) self.putField('name', item.name)
else: else:
self.indent += 1 self.indent += 1
self.output += '\n' + ' ' * self.indent self.put('\n' + ' ' * self.indent)
if isinstance(item.name, str): if isinstance(item.name, str):
self.output += item.name + ' = ' self.put(item.name + ' = ')
item.savedIName = self.currentIName item.savedIName = self.currentIName
item.savedValue = self.currentValue item.savedValue = self.currentValue
item.savedType = self.currentType item.savedType = self.currentType
@@ -869,7 +869,12 @@ class DumperBase():
self.putPlainChildren(value) self.putPlainChildren(value)
def put(self, stuff): def put(self, stuff):
self.output += stuff self.output.append(stuff)
def takeOutput(self):
res = '\n'.join(self.output)
self.output = []
return res
def check(self, exp): def check(self, exp):
if not exp: if not exp:

View File

@@ -175,24 +175,6 @@ def importPlainDumpers(args):
registerCommand('importPlainDumpers', importPlainDumpers) registerCommand('importPlainDumpers', importPlainDumpers)
class OutputSaver():
def __init__(self, d):
self.d = d
def __enter__(self):
self.savedOutput = self.d.output
self.d.output = ''
def __exit__(self, exType, exValue, exTraceBack):
if self.d.passExceptions and exType is not None:
self.d.showException('OUTPUTSAVER', exType, exValue, exTraceBack)
self.d.output = self.savedOutput
else:
self.savedOutput += self.d.output
self.d.output = self.savedOutput
return False
####################################################################### #######################################################################
# #
# The Dumper Class # The Dumper Class
@@ -214,7 +196,7 @@ class Dumper(DumperBase):
self.interpreterBreakpointResolvers = [] self.interpreterBreakpointResolvers = []
def prepare(self, args): def prepare(self, args):
self.output = '' self.output = []
self.setVariableFetchingOptions(args) self.setVariableFetchingOptions(args)
def fromFrameValue(self, nativeValue): def fromFrameValue(self, nativeValue):
@@ -690,7 +672,7 @@ class Dumper(DumperBase):
safePrint(res) safePrint(res)
return return
self.output += 'data=[' self.put('data=[')
partialVar = args.get('partialvar', '') partialVar = args.get('partialvar', '')
isPartial = len(partialVar) > 0 isPartial = len(partialVar) > 0
@@ -713,27 +695,26 @@ class Dumper(DumperBase):
self.handleLocals(variables) self.handleLocals(variables)
self.handleWatches(args) self.handleWatches(args)
self.output += '],typeinfo=[' self.put('],typeinfo=[')
for name in self.typesToReport.keys(): for name in self.typesToReport.keys():
typeobj = self.typesToReport[name] typeobj = self.typesToReport[name]
# Happens e.g. for '(anonymous namespace)::InsertDefOperation' # Happens e.g. for '(anonymous namespace)::InsertDefOperation'
#if not typeobj is None: #if not typeobj is None:
# self.output.append('{name="%s",size="%s"}' # self.put('{name="%s",size="%s"}' % (self.hexencode(name), typeobj.sizeof))
# % (self.hexencode(name), typeobj.sizeof)) self.put(']')
self.output += ']'
self.typesToReport = {} self.typesToReport = {}
if self.forceQtNamespace: if self.forceQtNamespace:
self.qtNamespaceToReport = self.qtNamespace() self.qtNamespaceToReport = self.qtNamespace()
if self.qtNamespaceToReport: if self.qtNamespaceToReport:
self.output += ',qtnamespace="%s"' % self.qtNamespaceToReport self.put(',qtnamespace="%s"' % self.qtNamespaceToReport)
self.qtNamespaceToReport = None self.qtNamespaceToReport = None
self.output += ',partial="%d"' % isPartial self.put(',partial="%d"' % isPartial)
self.output += ',counts=%s' % self.counts self.put(',counts=%s' % self.counts)
self.output += ',timings=%s' % self.timings self.put(',timings=%s' % self.timings)
self.reportResult(self.output, args) self.reportResult(''.join(self.output), args)
def parseAndEvaluate(self, exp): def parseAndEvaluate(self, exp):
val = self.nativeParseAndEvaluate(exp) val = self.nativeParseAndEvaluate(exp)
@@ -1321,7 +1302,7 @@ class Dumper(DumperBase):
limit = 10000 limit = 10000
self.prepare(args) self.prepare(args)
self.output = '' self.output = []
i = 0 i = 0
if extraQml: if extraQml:
@@ -1375,8 +1356,9 @@ class Dumper(DumperBase):
frame = gdb.newest_frame() frame = gdb.newest_frame()
self.currentCallContext = None self.currentCallContext = None
self.output = []
self.put('stack={frames=[')
while i < limit and frame: while i < limit and frame:
with OutputSaver(self):
name = frame.name() name = frame.name()
functionName = '??' if name is None else name functionName = '??' if name is None else name
fileName = '' fileName = ''
@@ -1426,7 +1408,8 @@ class Dumper(DumperBase):
frame = frame.older() frame = frame.older()
i += 1 i += 1
self.reportResult('stack={frames=[' + self.output + ']}', args) self.put(']}')
self.reportResult(self.takeOutput(), args)
def createResolvePendingBreakpointsHookBreakpoint(self, args): def createResolvePendingBreakpointsHookBreakpoint(self, args):
class Resolver(gdb.Breakpoint): class Resolver(gdb.Breakpoint):
@@ -1512,6 +1495,7 @@ class Dumper(DumperBase):
onHit=self.tracepointHit, onHit=self.tracepointHit,
onExpression=lambda tp, expr, val: self.tracepointExpression(tp, expr, val, args)) onExpression=lambda tp, expr, val: self.tracepointExpression(tp, expr, val, args))
self.reportResult("tracepoint=%s" % self.resultToMi(tp.dicts()), args) self.reportResult("tracepoint=%s" % self.resultToMi(tp.dicts()), args)
class CliDumper(Dumper): class CliDumper(Dumper):
def __init__(self): def __init__(self):
Dumper.__init__(self) Dumper.__init__(self)
@@ -1559,16 +1543,18 @@ class CliDumper(Dumper):
self.expandableINames = set() self.expandableINames = set()
self.prepare(args) self.prepare(args)
self.output = name + ' = ' self.output = []
self.put(name + ' = ')
value = self.parseAndEvaluate(name) value = self.parseAndEvaluate(name)
with TopLevelItem(self, name): with TopLevelItem(self, name):
self.putItem(value) self.putItem(value)
if not self.expandableINames: if not self.expandableINames:
return self.output + '\n\nNo drill down available.\n' self.put('\n\nNo drill down available.\n')
return self.takeOutput()
pattern = ' pp ' + name + ' ' + '%s' pattern = ' pp ' + name + ' ' + '%s'
return (self.output return (self.takeOutput()
+ '\n\nDrill down:\n ' + '\n\nDrill down:\n '
+ '\n '.join(pattern % x for x in self.expandableINames) + '\n '.join(pattern % x for x in self.expandableINames)
+ '\n') + '\n')

View File

@@ -1270,7 +1270,7 @@ class Dumper(DumperBase):
self.reportResult('error="No frame"', args) self.reportResult('error="No frame"', args)
return return
self.output = '' self.output = []
isPartial = len(self.partialVariable) > 0 isPartial = len(self.partialVariable) > 0
self.currentIName = 'local' self.currentIName = 'local'
@@ -1323,7 +1323,7 @@ class Dumper(DumperBase):
self.handleWatches(args) self.handleWatches(args)
self.put('],partial="%d"' % isPartial) self.put('],partial="%d"' % isPartial)
self.reportResult(self.output, args) self.reportResult(self.takeOutput(), args)
def fetchRegisters(self, args=None): def fetchRegisters(self, args=None):
@@ -2101,7 +2101,7 @@ class SummaryDumper(Dumper, LogMixin):
self.dumpermodules = ['qttypes'] self.dumpermodules = ['qttypes']
self.loadDumpers({}) self.loadDumpers({})
self.output = '' self.output = []
def report(self, stuff): def report(self, stuff):
return # Don't mess up lldb output return # Don't mess up lldb output
@@ -2123,12 +2123,12 @@ class SummaryDumper(Dumper, LogMixin):
self.expandedINames = [value.name] if expanded else [] self.expandedINames = [value.name] if expanded else []
savedOutput = self.output savedOutput = self.output
self.output = '' self.output = []
with TopLevelItem(self, value.name): with TopLevelItem(self, value.name):
self.putItem(value) self.putItem(value)
# FIXME: Hook into putField, etc to build up object instead of parsing MI # FIXME: Hook into putField, etc to build up object instead of parsing MI
response = gdbmiparser.parse_response("^ok,summary=%s" % self.output) response = gdbmiparser.parse_response("^ok,summary=%s" % self.takeOutput()))
self.output = savedOutput self.output = savedOutput
self.expandedINames = oldExpanded self.expandedINames = oldExpanded