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

View File

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

View File

@@ -175,24 +175,6 @@ def importPlainDumpers(args):
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
@@ -214,7 +196,7 @@ class Dumper(DumperBase):
self.interpreterBreakpointResolvers = []
def prepare(self, args):
self.output = ''
self.output = []
self.setVariableFetchingOptions(args)
def fromFrameValue(self, nativeValue):
@@ -690,7 +672,7 @@ class Dumper(DumperBase):
safePrint(res)
return
self.output += 'data=['
self.put('data=[')
partialVar = args.get('partialvar', '')
isPartial = len(partialVar) > 0
@@ -713,27 +695,26 @@ class Dumper(DumperBase):
self.handleLocals(variables)
self.handleWatches(args)
self.output += '],typeinfo=['
self.put('],typeinfo=[')
for name in self.typesToReport.keys():
typeobj = self.typesToReport[name]
# Happens e.g. for '(anonymous namespace)::InsertDefOperation'
#if not typeobj is None:
# self.output.append('{name="%s",size="%s"}'
# % (self.hexencode(name), typeobj.sizeof))
self.output += ']'
# self.put('{name="%s",size="%s"}' % (self.hexencode(name), typeobj.sizeof))
self.put(']')
self.typesToReport = {}
if self.forceQtNamespace:
self.qtNamespaceToReport = self.qtNamespace()
if self.qtNamespaceToReport:
self.output += ',qtnamespace="%s"' % self.qtNamespaceToReport
self.put(',qtnamespace="%s"' % self.qtNamespaceToReport)
self.qtNamespaceToReport = None
self.output += ',partial="%d"' % isPartial
self.output += ',counts=%s' % self.counts
self.output += ',timings=%s' % self.timings
self.reportResult(self.output, args)
self.put(',partial="%d"' % isPartial)
self.put(',counts=%s' % self.counts)
self.put(',timings=%s' % self.timings)
self.reportResult(''.join(self.output), args)
def parseAndEvaluate(self, exp):
val = self.nativeParseAndEvaluate(exp)
@@ -1321,7 +1302,7 @@ class Dumper(DumperBase):
limit = 10000
self.prepare(args)
self.output = ''
self.output = []
i = 0
if extraQml:
@@ -1375,58 +1356,60 @@ class Dumper(DumperBase):
frame = gdb.newest_frame()
self.currentCallContext = None
self.output = []
self.put('stack={frames=[')
while i < limit and frame:
with OutputSaver(self):
name = frame.name()
functionName = '??' if name is None else name
fileName = ''
objfile = ''
symtab = ''
pc = frame.pc()
sal = frame.find_sal()
line = -1
if sal:
line = sal.line
symtab = sal.symtab
if symtab is not None:
objfile = fromNativePath(symtab.objfile.filename)
fullname = symtab.fullname()
if fullname is None:
fileName = ''
else:
fileName = fromNativePath(fullname)
name = frame.name()
functionName = '??' if name is None else name
fileName = ''
objfile = ''
symtab = ''
pc = frame.pc()
sal = frame.find_sal()
line = -1
if sal:
line = sal.line
symtab = sal.symtab
if symtab is not None:
objfile = fromNativePath(symtab.objfile.filename)
fullname = symtab.fullname()
if fullname is None:
fileName = ''
else:
fileName = fromNativePath(fullname)
if self.nativeMixed and functionName == 'qt_qmlDebugMessageAvailable':
interpreterStack = self.extractInterpreterStack()
#print('EXTRACTED INTEPRETER STACK: %s' % interpreterStack)
for interpreterFrame in interpreterStack.get('frames', []):
function = interpreterFrame.get('function', '')
fileName = interpreterFrame.get('file', '')
language = interpreterFrame.get('language', '')
lineNumber = interpreterFrame.get('line', 0)
context = interpreterFrame.get('context', 0)
if self.nativeMixed and functionName == 'qt_qmlDebugMessageAvailable':
interpreterStack = self.extractInterpreterStack()
#print('EXTRACTED INTEPRETER STACK: %s' % interpreterStack)
for interpreterFrame in interpreterStack.get('frames', []):
function = interpreterFrame.get('function', '')
fileName = interpreterFrame.get('file', '')
language = interpreterFrame.get('language', '')
lineNumber = interpreterFrame.get('line', 0)
context = interpreterFrame.get('context', 0)
self.put(('frame={function="%s",file="%s",'
'line="%s",language="%s",context="%s"}')
% (function, self.hexencode(fileName), lineNumber, language, context))
self.put(('frame={function="%s",file="%s",'
'line="%s",language="%s",context="%s"}')
% (function, self.hexencode(fileName), lineNumber, language, context))
if False and self.isInternalInterpreterFrame(functionName):
frame = frame.older()
self.put(('frame={address="0x%x",function="%s",'
'file="%s",line="%s",'
'module="%s",language="c",usable="0"}') %
(pc, functionName, fileName, line, objfile))
i += 1
frame = frame.older()
continue
if False and self.isInternalInterpreterFrame(functionName):
frame = frame.older()
self.put(('frame={address="0x%x",function="%s",'
'file="%s",line="%s",'
'module="%s",language="c",usable="0"}') %
(pc, functionName, fileName, line, objfile))
i += 1
frame = frame.older()
continue
self.put(('frame={level="%s",address="0x%x",function="%s",'
'file="%s",line="%s",module="%s",language="c"}') %
(i, pc, functionName, fileName, line, objfile))
self.put(('frame={level="%s",address="0x%x",function="%s",'
'file="%s",line="%s",module="%s",language="c"}') %
(i, pc, functionName, fileName, line, objfile))
frame = frame.older()
i += 1
self.reportResult('stack={frames=[' + self.output + ']}', args)
self.put(']}')
self.reportResult(self.takeOutput(), args)
def createResolvePendingBreakpointsHookBreakpoint(self, args):
class Resolver(gdb.Breakpoint):
@@ -1512,6 +1495,7 @@ class Dumper(DumperBase):
onHit=self.tracepointHit,
onExpression=lambda tp, expr, val: self.tracepointExpression(tp, expr, val, args))
self.reportResult("tracepoint=%s" % self.resultToMi(tp.dicts()), args)
class CliDumper(Dumper):
def __init__(self):
Dumper.__init__(self)
@@ -1559,16 +1543,18 @@ class CliDumper(Dumper):
self.expandableINames = set()
self.prepare(args)
self.output = name + ' = '
self.output = []
self.put(name + ' = ')
value = self.parseAndEvaluate(name)
with TopLevelItem(self, name):
self.putItem(value)
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'
return (self.output
return (self.takeOutput()
+ '\n\nDrill down:\n '
+ '\n '.join(pattern % x for x in self.expandableINames)
+ '\n')

View File

@@ -1270,7 +1270,7 @@ class Dumper(DumperBase):
self.reportResult('error="No frame"', args)
return
self.output = ''
self.output = []
isPartial = len(self.partialVariable) > 0
self.currentIName = 'local'
@@ -1323,7 +1323,7 @@ class Dumper(DumperBase):
self.handleWatches(args)
self.put('],partial="%d"' % isPartial)
self.reportResult(self.output, args)
self.reportResult(self.takeOutput(), args)
def fetchRegisters(self, args=None):
@@ -2101,7 +2101,7 @@ class SummaryDumper(Dumper, LogMixin):
self.dumpermodules = ['qttypes']
self.loadDumpers({})
self.output = ''
self.output = []
def report(self, stuff):
return # Don't mess up lldb output
@@ -2123,12 +2123,12 @@ class SummaryDumper(Dumper, LogMixin):
self.expandedINames = [value.name] if expanded else []
savedOutput = self.output
self.output = ''
self.output = []
with TopLevelItem(self, value.name):
self.putItem(value)
# 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.expandedINames = oldExpanded