diff --git a/debuggers/gdb/printers/qt.py b/debuggers/gdb/printers/qt.py index c5d2454de3..d21d84dd8b 100644 --- a/debuggers/gdb/printers/qt.py +++ b/debuggers/gdb/printers/qt.py @@ -1,678 +1,680 @@ # -*- coding: iso-8859-1 -*- # Pretty-printers for Qt 4 and Qt 5. # Copyright (C) 2009 Niko Sams # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import gdb import itertools import re import time from helper import * class QStringPrinter: def __init__(self, val): self.val = val def to_string(self): size = self.val['d']['size'] ret = "" # The QString object might be not yet initialized. In this case size is a bogus value # and the following 2 lines might throw memory access error. Hence the try/catch. try: isQt4 = has_field(self.val['d'], 'data') # Qt4 has d->data, Qt5 doesn't. if isQt4: dataAsCharPointer = self.val['d']['data'].cast(gdb.lookup_type("char").pointer()) else: dataAsCharPointer = (self.val['d'] + 1).cast(gdb.lookup_type("char").pointer()) ret = dataAsCharPointer.string(encoding = 'UTF-16', length = size * 2) except Exception: # swallow the exception and return empty string pass return ret def display_hint (self): return 'string' class QByteArrayPrinter: def __init__(self, val): self.val = val # Qt4 has 'data', Qt5 doesn't self.isQt4 = has_field(self.val['d'], 'data') class _iterator(Iterator): def __init__(self, data, size): self.data = data self.size = size self.count = 0 def __iter__(self): return self def __next__(self): if self.count >= self.size: raise StopIteration count = self.count self.count = self.count + 1 return ('[%d]' % count, self.data[count]) def stringData(self): if self.isQt4: return self.val['d']['data'] else: return self.val['d'].cast(gdb.lookup_type("char").const().pointer()) + self.val['d']['offset'] def children(self): return self._iterator(self.stringData(), self.val['d']['size']) def to_string(self): #todo: handle charset correctly return self.stringData() def display_hint (self): return 'string' class QListPrinter: "Print a QList" class _iterator(Iterator): def __init__(self, nodetype, d): self.nodetype = nodetype self.d = d self.count = 0 - def __iter__(self): - return self - - def __next__(self): - if self.count >= self.d['end'] - self.d['begin']: - raise StopIteration - count = self.count - array = self.d['array'][self.d['begin'] + count] - #from QTypeInfo::isLarge isLarge = self.nodetype.sizeof > gdb.lookup_type('void').pointer().sizeof isPointer = self.nodetype.code == gdb.TYPE_CODE_PTR #unfortunately we can't use QTypeInfo::isStatic as it's all inlined, so use #this list of types that use Q_DECLARE_TYPEINFO(T, Q_MOVABLE_TYPE) #(obviously it won't work for custom types) movableTypes = ['QRect', 'QRectF', 'QString', 'QMargins', 'QLocale', 'QChar', 'QDate', 'QTime', 'QDateTime', 'QVector', 'QRegExpr', 'QPoint', 'QPointF', 'QByteArray', 'QSize', 'QSizeF', 'QBitArray', 'QLine', 'QLineF', 'QModelIndex', 'QPersitentModelIndex', 'QVariant', 'QFileInfo', 'QUrl', 'QXmlStreamAttribute', 'QXmlStreamNamespaceDeclaration', 'QXmlStreamNotationDeclaration', 'QXmlStreamEntityDeclaration', 'QPair'] #this list of types that use Q_DECLARE_TYPEINFO(T, Q_PRIMITIVE_TYPE) (from qglobal.h) primitiveTypes = ['bool', 'char', 'signed char', 'unsigned char', 'short', 'unsigned short', 'int', 'unsigned int', 'long', 'unsigned long', 'long long', 'unsigned long long', 'float', 'double'] if movableTypes.count(self.nodetype.tag) or primitiveTypes.count(str(self.nodetype)): isStatic = False else: - isStatic = not isPointer + isStatic = not isPointer + + self.externalStorage = isLarge or isStatic #see QList::Node::t() - if isLarge or isStatic: #see QList::Node::t() - node = array.cast(gdb.lookup_type('QList<%s>::Node' % self.nodetype).pointer()) + + def __iter__(self): + return self + + def __next__(self): + if self.count >= self.d['end'] - self.d['begin']: + raise StopIteration + count = self.count + array = self.d['array'].address + self.d['begin'] + count + if self.externalStorage: + value = array.cast(gdb.lookup_type('QList<%s>::Node' % self.nodetype).pointer())['v'] else: - node = array.cast(gdb.lookup_type('QList<%s>::Node' % self.nodetype)) + value = array self.count = self.count + 1 - return ('[%d]' % count, node['v'].cast(self.nodetype)) + return ('[%d]' % count, value.cast(self.nodetype.pointer()).dereference()) def __init__(self, val, container, itype): self.val = val self.container = container if itype == None: self.itype = self.val.type.template_argument(0) else: self.itype = gdb.lookup_type(itype) def children(self): return self._iterator(self.itype, self.val['d']) def to_string(self): if self.val['d']['end'] == self.val['d']['begin']: empty = "empty " else: empty = "" return "%s%s<%s>" % ( empty, self.container, self.itype ) class QVectorPrinter: "Print a QVector" class _iterator(Iterator): def __init__(self, nodetype, data, size): self.nodetype = nodetype self.data = data self.size = size self.count = 0 def __iter__(self): return self def __next__(self): if self.count >= self.size: raise StopIteration count = self.count self.count = self.count + 1 return ('[%d]' % count, self.data[count]) def __init__(self, val, container): self.val = val self.container = container self.itype = self.val.type.template_argument(0) def children(self): isQt4 = has_field(self.val['d'], 'p') # Qt4 has 'p', Qt5 doesn't if isQt4: return self._iterator(self.itype, self.val['p']['array'], self.val['p']['size']) else: data = self.val['d'].cast(gdb.lookup_type("char").const().pointer()) + self.val['d']['offset'] return self._iterator(self.itype, data.cast(self.itype.pointer()), self.val['d']['size']) def to_string(self): if self.val['d']['size'] == 0: empty = "empty " else: empty = "" return "%s%s<%s>" % ( empty, self.container, self.itype ) class QLinkedListPrinter: "Print a QLinkedList" class _iterator(Iterator): def __init__(self, nodetype, begin, size): self.nodetype = nodetype self.it = begin self.pos = 0 self.size = size def __iter__(self): return self def __next__(self): if self.pos >= self.size: raise StopIteration pos = self.pos val = self.it['t'] self.it = self.it['n'] self.pos = self.pos + 1 return ('[%d]' % pos, val) def __init__(self, val): self.val = val self.itype = self.val.type.template_argument(0) def children(self): return self._iterator(self.itype, self.val['e']['n'], self.val['d']['size']) def to_string(self): if self.val['d']['size'] == 0: empty = "empty " else: empty = "" return "%sQLinkedList<%s>" % ( empty, self.itype ) class QMapPrinter: "Print a QMap" class _iteratorQt4(Iterator): def __init__(self, val): self.val = val self.ktype = self.val.type.template_argument(0) self.vtype = self.val.type.template_argument(1) self.data_node = self.val['e']['forward'][0] self.count = 0 def __iter__(self): return self def payload (self): if gdb.parse_and_eval: ret = int(gdb.parse_and_eval('QMap<%s, %s>::payload()' % (self.ktype, self.vtype))) if (ret): return ret; #if the inferior function call didn't work, let's try to calculate ourselves #we can't use QMapPayloadNode as it's inlined #as a workaround take the sum of sizeof(members) ret = self.ktype.sizeof ret += self.vtype.sizeof ret += gdb.lookup_type('void').pointer().sizeof #but because of data alignment the value can be higher #so guess it's aliged by sizeof(void*) #TODO: find a real solution for this problem ret += ret % gdb.lookup_type('void').pointer().sizeof #for some reason booleans are different if str(self.vtype) == 'bool': ret += 2 ret -= gdb.lookup_type('void').pointer().sizeof return ret def concrete (self, data_node): node_type = gdb.lookup_type('QMapNode<%s, %s>' % (self.ktype, self.vtype)).pointer() return (data_node.cast(gdb.lookup_type('char').pointer()) - self.payload()).cast(node_type) def __next__(self): if self.data_node == self.val['e']: raise StopIteration node = self.concrete(self.data_node).dereference() if self.count % 2 == 0: item = node['key'] else: item = node['value'] self.data_node = node['forward'][0] result = ('[%d]' % self.count, item) self.count = self.count + 1 return result class _iteratorQt5: def __init__(self, val): realtype = val.type.strip_typedefs() keytype = realtype.template_argument(0) valtype = realtype.template_argument(1) node_type = gdb.lookup_type('QMapData<' + keytype.name + ',' + valtype.name + '>::Node') self.node_p_type = node_type.pointer() self.root = val['d']['header'] self.current = None self.next_is_key = True self.i = -1 # we store the path here to avoid keeping re-fetching # values from the inferior (also, skips the pointer # arithmetic involved in using the parent pointer) self.path = [] def __iter__(self): return self def moveToNextNode(self): if self.current is None: # find the leftmost node if not self.root['left']: return False self.current = self.root while self.current['left']: self.path.append(self.current) self.current = self.current['left'] elif self.current['right']: self.path.append(self.current) self.current = self.current['right'] while self.current['left']: self.path.append(self.current) self.current = self.current['left'] else: last = self.current self.current = self.path.pop() while self.current['right'] == last: last = self.current self.current = self.path.pop() # if there are no more parents, we are at the root if len(self.path) == 0: return False return True def __next__(self): if self.next_is_key: if not self.moveToNextNode(): raise StopIteration self.current_typed = self.current.reinterpret_cast(self.node_p_type) self.next_is_key = False self.i += 1 return ('key' + str(self.i), self.current_typed['key']) else: self.next_is_key = True return ('value' + str(self.i), self.current_typed['value']) def next(self): return self.__next__() def __init__(self, val, container): self.val = val self.container = container def children(self): if self.val['d']['size'] == 0: return [] isQt4 = has_field(self.val, 'e') # Qt4 has 'e', Qt5 doesn't if isQt4: return self._iteratorQt4(self.val) else: return self._iteratorQt5(self.val) def to_string(self): if self.val['d']['size'] == 0: empty = "empty " else: empty = "" return "%s%s<%s, %s>" % ( empty, self.container, self.val.type.template_argument(0), self.val.type.template_argument(1) ) def display_hint (self): return 'map' class QHashPrinter: "Print a QHash" class _iterator(Iterator): def __init__(self, val): self.val = val self.d = self.val['d'] self.ktype = self.val.type.template_argument(0) self.vtype = self.val.type.template_argument(1) self.end_node = self.d.cast(gdb.lookup_type('QHashData::Node').pointer()) self.data_node = self.firstNode() self.count = 0 def __iter__(self): return self def hashNode (self): "Casts the current QHashData::Node to a QHashNode and returns the result. See also QHash::concrete()" return self.data_node.cast(gdb.lookup_type('QHashNode<%s, %s>' % (self.ktype, self.vtype)).pointer()) def firstNode (self): "Get the first node, See QHashData::firstNode()." e = self.d.cast(gdb.lookup_type('QHashData::Node').pointer()) #print "QHashData::firstNode() e %s" % e bucketNum = 0 bucket = self.d['buckets'][bucketNum] #print "QHashData::firstNode() *bucket %s" % bucket n = self.d['numBuckets'] #print "QHashData::firstNode() n %s" % n while n: #print "QHashData::firstNode() in while, n %s" % n; if bucket != e: #print "QHashData::firstNode() in while, return *bucket %s" % bucket return bucket bucketNum += 1 bucket = self.d['buckets'][bucketNum] #print "QHashData::firstNode() in while, new bucket %s" % bucket n -= 1 #print "QHashData::firstNode() return e %s" % e return e def nextNode (self, node): "Get the nextNode after the current, see also QHashData::nextNode()." #print "******************************** nextNode" #print "nextNode: node %s" % node next = node['next'].cast(gdb.lookup_type('QHashData::Node').pointer()) e = next #print "nextNode: next %s" % next if next['next']: #print "nextNode: return next" return next #print "nextNode: node->h %s" % node['h'] #print "nextNode: numBuckets %s" % self.d['numBuckets'] start = (node['h'] % self.d['numBuckets']) + 1 bucketNum = start #print "nextNode: start %s" % start bucket = self.d['buckets'][start] #print "nextNode: bucket %s" % bucket n = self.d['numBuckets'] - start #print "nextNode: n %s" % n while n: #print "nextNode: in while; n %s" % n #print "nextNode: in while; e %s" % e #print "nextNode: in while; *bucket %s" % bucket if bucket != e: #print "nextNode: in while; return bucket %s" % bucket return bucket bucketNum += 1 bucket = self.d['buckets'][bucketNum] n -= 1 #print "nextNode: return e %s" % e return e def __next__(self): "GDB iteration, first call returns key, second value and then jumps to the next hash node." if self.data_node == self.end_node: raise StopIteration node = self.hashNode() if self.count % 2 == 0: item = node['key'] else: item = node['value'] self.data_node = self.nextNode(self.data_node) self.count = self.count + 1 return ('[%d]' % self.count, item) def __init__(self, val, container): self.val = val self.container = container def children(self): return self._iterator(self.val) def to_string(self): if self.val['d']['size'] == 0: empty = "empty " else: empty = "" return "%s%s<%s, %s>" % ( empty, self.container, self.val.type.template_argument(0), self.val.type.template_argument(1) ) def display_hint (self): return 'map' class QDatePrinter: def __init__(self, val): self.val = val def to_string(self): julianDay = self.val['jd'] if julianDay == 0: return "invalid QDate" # Copied from Qt sources if julianDay >= 2299161: # Gregorian calendar starting from October 15, 1582 # This algorithm is from Henry F. Fliegel and Thomas C. Van Flandern ell = julianDay + 68569; n = (4 * ell) / 146097; ell = ell - (146097 * n + 3) / 4; i = (4000 * (ell + 1)) / 1461001; ell = ell - (1461 * i) / 4 + 31; j = (80 * ell) / 2447; d = ell - (2447 * j) / 80; ell = j / 11; m = j + 2 - (12 * ell); y = 100 * (n - 49) + i + ell; else: # Julian calendar until October 4, 1582 # Algorithm from Frequently Asked Questions about Calendars by Claus Toendering julianDay += 32082; dd = (4 * julianDay + 3) / 1461; ee = julianDay - (1461 * dd) / 4; mm = ((5 * ee) + 2) / 153; d = ee - (153 * mm + 2) / 5 + 1; m = mm + 3 - 12 * (mm / 10); y = dd - 4800 + (mm / 10); if y <= 0: --y; return "%d-%02d-%02d" % (y, m, d) class QTimePrinter: def __init__(self, val): self.val = val def to_string(self): ds = self.val['mds'] if ds == -1: return "invalid QTime" MSECS_PER_HOUR = 3600000 SECS_PER_MIN = 60 MSECS_PER_MIN = 60000 hour = ds / MSECS_PER_HOUR minute = (ds % MSECS_PER_HOUR) / MSECS_PER_MIN second = (ds / 1000)%SECS_PER_MIN msec = ds % 1000 return "%02d:%02d:%02d.%03d" % (hour, minute, second, msec) class QDateTimePrinter: def __init__(self, val): self.val = val def to_string(self): return time.ctime(gdb.parse_and_eval("reinterpret_cast(%s)->toTime_t()" % self.val.address)) class QUrlPrinter: def __init__(self, val): self.val = val def to_string(self): try: return gdb.parse_and_eval("reinterpret_cast(%s)->toString((QUrl::FormattingOptions)QUrl::PrettyDecoded)" % self.val.address) except: pass # TODO: the below code is only valid for Qt 4 try: return self.val['d']['encodedOriginal'] except RuntimeError: #if no debug information is avaliable for Qt, try guessing the correct address for encodedOriginal #problem with this is that if QUrlPrivate members get changed, this fails offset = gdb.lookup_type('int').sizeof offset += offset % gdb.lookup_type('void').pointer().sizeof #alignment offset += gdb.lookup_type('QString').sizeof * 6 offset += gdb.lookup_type('QByteArray').sizeof encodedOriginal = self.val['d'].cast(gdb.lookup_type('char').pointer()); encodedOriginal += offset encodedOriginal = encodedOriginal.cast(gdb.lookup_type('QByteArray').pointer()).dereference(); encodedOriginal = encodedOriginal['d']['data'].string() return encodedOriginal class QSetPrinter: "Print a QSet" def __init__(self, val): self.val = val class _iterator(Iterator): def __init__(self, hashIterator): self.hashIterator = hashIterator self.count = 0 def __iter__(self): return self def __next__(self): if self.hashIterator.data_node == self.hashIterator.end_node: raise StopIteration node = self.hashIterator.hashNode() item = node['key'] self.hashIterator.data_node = self.hashIterator.nextNode(self.hashIterator.data_node) self.count = self.count + 1 return ('[%d]' % (self.count-1), item) def children(self): hashPrinter = QHashPrinter(self.val['q_hash'], None) hashIterator = hashPrinter._iterator(self.val['q_hash']) return self._iterator(hashIterator) def to_string(self): if self.val['q_hash']['d']['size'] == 0: empty = "empty " else: empty = "" return "%sQSet<%s>" % ( empty , self.val.type.template_argument(0) ) class QCharPrinter: def __init__(self, val): self.val = val def to_string(self): return unichr(self.val['ucs']) def display_hint (self): return 'string' class QUuidPrinter: def __init__(self, val): self.val = val def to_string(self): return "QUuid({%x-%x-%x-%x%x-%x%x%x%x%x%x})" % (int(self.val['data1']), int(self.val['data2']), int(self.val['data3']), int(self.val['data4'][0]), int(self.val['data4'][1]), int(self.val['data4'][2]), int(self.val['data4'][3]), int(self.val['data4'][4]), int(self.val['data4'][5]), int(self.val['data4'][6]), int(self.val['data4'][7])) def display_hint (self): return 'string' pretty_printers_dict = {} def register_qt_printers (obj): if obj == None: obj = gdb obj.pretty_printers.append(FunctionLookup(gdb, pretty_printers_dict)) def build_dictionary (): pretty_printers_dict[re.compile('^QString$')] = lambda val: QStringPrinter(val) pretty_printers_dict[re.compile('^QByteArray$')] = lambda val: QByteArrayPrinter(val) pretty_printers_dict[re.compile('^QList<.*>$')] = lambda val: QListPrinter(val, 'QList', None) pretty_printers_dict[re.compile('^QStringList$')] = lambda val: QListPrinter(val, 'QStringList', 'QString') pretty_printers_dict[re.compile('^QQueue')] = lambda val: QListPrinter(val, 'QQueue', None) pretty_printers_dict[re.compile('^QVector<.*>$')] = lambda val: QVectorPrinter(val, 'QVector') pretty_printers_dict[re.compile('^QStack<.*>$')] = lambda val: QVectorPrinter(val, 'QStack') pretty_printers_dict[re.compile('^QLinkedList<.*>$')] = lambda val: QLinkedListPrinter(val) pretty_printers_dict[re.compile('^QMap<.*>$')] = lambda val: QMapPrinter(val, 'QMap') pretty_printers_dict[re.compile('^QMultiMap<.*>$')] = lambda val: QMapPrinter(val, 'QMultiMap') pretty_printers_dict[re.compile('^QHash<.*>$')] = lambda val: QHashPrinter(val, 'QHash') pretty_printers_dict[re.compile('^QMultiHash<.*>$')] = lambda val: QHashPrinter(val, 'QMultiHash') pretty_printers_dict[re.compile('^QDate$')] = lambda val: QDatePrinter(val) pretty_printers_dict[re.compile('^QTime$')] = lambda val: QTimePrinter(val) pretty_printers_dict[re.compile('^QDateTime$')] = lambda val: QDateTimePrinter(val) pretty_printers_dict[re.compile('^QUrl$')] = lambda val: QUrlPrinter(val) pretty_printers_dict[re.compile('^QSet<.*>$')] = lambda val: QSetPrinter(val) pretty_printers_dict[re.compile('^QChar$')] = lambda val: QCharPrinter(val) pretty_printers_dict[re.compile('^QUuid')] = lambda val: QUuidPrinter(val) build_dictionary () diff --git a/debuggers/gdb/printers/tests/qdate.cpp b/debuggers/gdb/printers/tests/qdate.cpp index 3081487aae..cd5846371a 100644 --- a/debuggers/gdb/printers/tests/qdate.cpp +++ b/debuggers/gdb/printers/tests/qdate.cpp @@ -1,6 +1,7 @@ #include int main() { QDate d(2010, 1, 20); + Q_UNUSED(d.toString()); // prevent compiler optimizing away the unused value return 0; } diff --git a/debuggers/gdb/printers/tests/qtime.cpp b/debuggers/gdb/printers/tests/qtime.cpp index 530745ada2..8cb1b0a58f 100644 --- a/debuggers/gdb/printers/tests/qtime.cpp +++ b/debuggers/gdb/printers/tests/qtime.cpp @@ -1,6 +1,7 @@ #include int main() { QTime t(15, 30, 10, 123); + Q_UNUSED(t.toString()); // prevent compiler optimizing away the unused object return 0; } diff --git a/debuggers/gdb/printers/tests/qtprinters.cpp b/debuggers/gdb/printers/tests/qtprinters.cpp index fbe9de7cac..5e191633d2 100644 --- a/debuggers/gdb/printers/tests/qtprinters.cpp +++ b/debuggers/gdb/printers/tests/qtprinters.cpp @@ -1,455 +1,458 @@ /* Copyright 2010 Niko Sams This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "qtprinters.h" #include #include #include #include #include #include "qtprintersconfig.h" const QString BINARY_PATH(PRINTER_BIN_DIR); namespace GDBDebugger { class GdbProcess : private QProcess { public: GdbProcess(const QString &program) : QProcess() { setProcessChannelMode(MergedChannels); // don't attempt to load .gdbinit in home (may cause unexpected results) QProcess::start("gdb", (QStringList() << "-nx" << (BINARY_PATH + '/' + program))); const bool started = waitForStarted(); if (!started) { qDebug() << "Failed to start 'gdb' executable:" << errorString(); Q_ASSERT(false); return; } QByteArray prompt = waitForPrompt(); QVERIFY(!prompt.contains("No such file or directory")); execute("set confirm off"); execute("set print pretty on"); QList p; QDir printersDir = QFileInfo(__FILE__).dir(); printersDir.cdUp(); // go up to get to the main printers directory p << "python" << "import sys" << "sys.path.insert(0, '"+printersDir.path().toLatin1()+"')" << "from qt import register_qt_printers" << "register_qt_printers (None)" << "from kde import register_kde_printers" << "register_kde_printers (None)" << "end"; foreach (const QByteArray &i, p) { write(i + "\n"); } waitForPrompt(); } ~GdbProcess() { write("q\n"); waitForFinished(); } QByteArray waitForPrompt() { QByteArray output; while (!output.endsWith("(gdb) ")) { Q_ASSERT(state() == QProcess::Running); waitForReadyRead(); QByteArray l = readAll(); //qDebug() << l; output.append(l); } output.chop(7); //remove (gdb) prompt if (output.contains("Traceback") || output.contains("Exception")) { qDebug() << output; QTest::qFail("Unexpected Python Exception", __FILE__, __LINE__); } return output; } QByteArray execute(const QByteArray &cmd) { write(cmd + "\n"); - return waitForPrompt(); + auto out = waitForPrompt(); + qDebug() << cmd << " = " << out; + return out; } }; void QtPrintersTest::testQString() { GdbProcess gdb("qstring"); gdb.execute("break qstring.cpp:5"); gdb.execute("run"); QVERIFY(gdb.execute("print s").contains("\"test string\"")); gdb.execute("next"); QVERIFY(gdb.execute("print s").contains("\"test stringx\"")); } void QtPrintersTest::testQByteArray() { GdbProcess gdb("qbytearray"); gdb.execute("break qbytearray.cpp:5"); gdb.execute("run"); QByteArray out = gdb.execute("print ba"); QVERIFY(out.contains("\"test byte array\"")); QVERIFY(out.contains("[0] = 116 't'")); QVERIFY(out.contains("[4] = 32 ' '")); gdb.execute("next"); QVERIFY(gdb.execute("print ba").contains("\"test byte arrayx\"")); } void QtPrintersTest::testQListContainer_data() { QTest::addColumn("container"); QTest::newRow("QList") << "QList"; QTest::newRow("QQueue") << "QQueue"; QTest::newRow("QVector") << "QVector"; QTest::newRow("QStack") << "QStack"; QTest::newRow("QLinkedList") << "QLinkedList"; QTest::newRow("QSet") << "QSet"; } void QtPrintersTest::testQListContainer() { QFETCH(QString, container); GdbProcess gdb("qlistcontainer"); gdb.execute("break main"); gdb.execute("run"); gdb.execute(QString("break 'doStuff<%1>()'").arg(container).toLocal8Bit()); gdb.execute("cont"); { // - gdb.execute("next"); + gdb.execute("break qlistcontainer.cpp:34"); + gdb.execute("cont"); QByteArray out = gdb.execute("print intList"); QVERIFY(out.contains(QString("empty %1").arg(container).toLocal8Bit())); gdb.execute("next"); out = gdb.execute("print intList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); if (container != "QSet") { QVERIFY(out.contains("[0] = 10")); QVERIFY(out.contains("[1] = 20")); QVERIFY(!out.contains("[2] = 30")); } else { // QSet order is undefined QVERIFY(out.contains("] = 10")); QVERIFY(out.contains("] = 20")); QVERIFY(!out.contains("] = 30")); } gdb.execute("next"); out = gdb.execute("print intList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); if (container != "QSet") { QVERIFY(out.contains("[0] = 10")); QVERIFY(out.contains("[1] = 20")); QVERIFY(out.contains("[2] = 30")); } else { // QSet order is undefined QVERIFY(out.contains("] = 10")); QVERIFY(out.contains("] = 20")); QVERIFY(out.contains("] = 30")); } } { // gdb.execute("next"); QByteArray out = gdb.execute("print stringList"); QVERIFY(out.contains(QString("empty %1").arg(container).toLocal8Bit())); gdb.execute("next"); out = gdb.execute("print stringList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); if (container != "QSet") { QVERIFY(out.contains("[0] = \"a\"")); QVERIFY(out.contains("[1] = \"bc\"")); QVERIFY(!out.contains("[2] = \"d\"")); } else { // QSet order is undefined QVERIFY(out.contains("] = \"a\"")); QVERIFY(out.contains("] = \"bc\"")); QVERIFY(!out.contains("] = \"d\"")); } gdb.execute("next"); out = gdb.execute("print stringList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); if (container != "QSet") { QVERIFY(out.contains("[0] = \"a\"")); QVERIFY(out.contains("[1] = \"bc\"")); QVERIFY(out.contains("[2] = \"d\"")); } else { // QSet order is undefined QVERIFY(out.contains("] = \"a\"")); QVERIFY(out.contains("] = \"bc\"")); QVERIFY(out.contains("] = \"d\"")); } } { // gdb.execute("next"); QByteArray out = gdb.execute("print structList"); QVERIFY(out.contains(QString("empty %1").arg(container).toLocal8Bit())); gdb.execute("next"); out = gdb.execute("print structList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); QVERIFY(out.contains("[0] = {")); QVERIFY(out.contains("a = \"a\"")); QVERIFY(out.contains("b = \"b\"")); QVERIFY(out.contains("c = 100")); QVERIFY(out.contains("d = -200")); gdb.execute("next"); out = gdb.execute("print structList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); QVERIFY(out.contains("[1] = {")); } { // gdb.execute("next"); QByteArray out = gdb.execute("print pointerList"); QVERIFY(out.contains(QString("empty %1").arg(container).toLocal8Bit())); gdb.execute("next"); out = gdb.execute("print pointerList"); QVERIFY(out.contains(QString("%1").arg(container).toLocal8Bit())); QVERIFY(out.contains("[0] = 0x")); QVERIFY(out.contains("[1] = 0x")); QVERIFY(!out.contains("[2] = 0x")); gdb.execute("next"); out = gdb.execute("print pointerList"); QVERIFY(out.contains("[0] = 0x")); QVERIFY(out.contains("[1] = 0x")); QVERIFY(out.contains("[2] = 0x")); gdb.execute("next"); } { // > gdb.execute("next"); QByteArray out = gdb.execute("print pairList"); QVERIFY(out.contains(QString("empty %1>").arg(container).toLocal8Bit())); gdb.execute("next"); out = gdb.execute("print pairList"); QVERIFY(out.contains(QString("%1>").arg(container).toLocal8Bit())); if (container != "QSet") { QVERIFY(out.contains("[0] = {\n first = 1, \n second = 2\n }")); QVERIFY(out.contains("[1] = {\n first = 2, \n second = 3\n }")); } else { // order is undefined in QSet QVERIFY(out.contains("] = {\n first = 1, \n second = 2\n }")); QVERIFY(out.contains("] = {\n first = 2, \n second = 3\n }")); } QVERIFY(!out.contains("[2] = ")); gdb.execute("next"); out = gdb.execute("print pairList"); if (container != "QSet") { QVERIFY(out.contains("[0] = {\n first = 1, \n second = 2\n }")); QVERIFY(out.contains("[1] = {\n first = 2, \n second = 3\n }")); QVERIFY(out.contains("[2] = {\n first = 4, \n second = 5\n }")); } else { // order is undefined in QSet QVERIFY(out.contains("] = {\n first = 1, \n second = 2\n }")); QVERIFY(out.contains("] = {\n first = 2, \n second = 3\n }")); QVERIFY(out.contains("] = {\n first = 4, \n second = 5\n }")); } } } void QtPrintersTest::testQMapInt() { GdbProcess gdb("qmapint"); gdb.execute("break qmapint.cpp:7"); gdb.execute("run"); QByteArray out = gdb.execute("print m"); QVERIFY(out.contains("QMap")); QVERIFY(out.contains("[10] = 100")); QVERIFY(out.contains("[20] = 200")); gdb.execute("next"); out = gdb.execute("print m"); QVERIFY(out.contains("[30] = 300")); } void QtPrintersTest::testQMapString() { GdbProcess gdb("qmapstring"); gdb.execute("break qmapstring.cpp:8"); gdb.execute("run"); QByteArray out = gdb.execute("print m"); QVERIFY(out.contains("QMap")); QVERIFY(out.contains("[\"10\"] = \"100\"")); QVERIFY(out.contains("[\"20\"] = \"200\"")); gdb.execute("next"); out = gdb.execute("print m"); QVERIFY(out.contains("[\"30\"] = \"300\"")); } void QtPrintersTest::testQMapStringBool() { GdbProcess gdb("qmapstringbool"); gdb.execute("break qmapstringbool.cpp:8"); gdb.execute("run"); QByteArray out = gdb.execute("print m"); QVERIFY(out.contains("QMap")); QVERIFY(out.contains("[\"10\"] = true")); QVERIFY(out.contains("[\"20\"] = false")); gdb.execute("next"); out = gdb.execute("print m"); QVERIFY(out.contains("[\"30\"] = true")); } void QtPrintersTest::testQDate() { GdbProcess gdb("qdate"); - gdb.execute("break qdate.cpp:5"); + gdb.execute("break qdate.cpp:6"); gdb.execute("run"); QByteArray out = gdb.execute("print d"); QVERIFY(out.contains("2010-01-20")); } void QtPrintersTest::testQTime() { GdbProcess gdb("qtime"); - gdb.execute("break qtime.cpp:5"); + gdb.execute("break qtime.cpp:6"); gdb.execute("run"); QByteArray out = gdb.execute("print t"); QVERIFY(out.contains("15:30:10.123")); } void QtPrintersTest::testQDateTime() { GdbProcess gdb("qdatetime"); gdb.execute("break qdatetime.cpp:5"); gdb.execute("run"); QByteArray out = gdb.execute("print dt"); QVERIFY(out.contains("Wed Jan 20 15:31:13 2010")); } void QtPrintersTest::testQUrl() { GdbProcess gdb("qurl"); gdb.execute("break qurl.cpp:5"); gdb.execute("run"); QByteArray out = gdb.execute("print u"); QVERIFY(out.contains("http://www.kdevelop.org/foo")); } void QtPrintersTest::testQHashInt() { GdbProcess gdb("qhashint"); gdb.execute("break qhashint.cpp:7"); gdb.execute("run"); QByteArray out = gdb.execute("print h"); QVERIFY(out.contains("[10] = 100")); QVERIFY(out.contains("[20] = 200")); gdb.execute("next"); out = gdb.execute("print h"); QVERIFY(out.contains("[30] = 300")); } void QtPrintersTest::testQHashString() { GdbProcess gdb("qhashstring"); gdb.execute("break qhashstring.cpp:8"); gdb.execute("run"); QByteArray out = gdb.execute("print h"); QVERIFY(out.contains("[\"10\"] = \"100\"")); QVERIFY(out.contains("[\"20\"] = \"200\"")); gdb.execute("next"); out = gdb.execute("print h"); QVERIFY(out.contains("[\"30\"] = \"300\"")); } void QtPrintersTest::testQSetInt() { GdbProcess gdb("qsetint"); gdb.execute("break qsetint.cpp:7"); gdb.execute("run"); QByteArray out = gdb.execute("print s"); QVERIFY(out.contains("] = 10")); QVERIFY(out.contains("] = 20")); gdb.execute("next"); out = gdb.execute("print s"); QVERIFY(out.contains("] = 30")); } void QtPrintersTest::testQSetString() { GdbProcess gdb("qsetstring"); gdb.execute("break qsetstring.cpp:8"); gdb.execute("run"); QByteArray out = gdb.execute("print s"); QVERIFY(out.contains("] = \"10\"")); QVERIFY(out.contains("] = \"20\"")); gdb.execute("next"); out = gdb.execute("print s"); QVERIFY(out.contains("] = \"30\"")); } void QtPrintersTest::testQChar() { GdbProcess gdb("qchar"); gdb.execute("break qchar.cpp:5"); gdb.execute("run"); QVERIFY(gdb.execute("print c").contains("\"k\"")); } void QtPrintersTest::testQListPOD() { GdbProcess gdb("qlistpod"); gdb.execute("break qlistpod.cpp:31"); gdb.execute("run"); QVERIFY(gdb.execute("print b").contains("false")); QVERIFY(gdb.execute("print c").contains("50")); QVERIFY(gdb.execute("print uc").contains("50")); QVERIFY(gdb.execute("print s").contains("50")); QVERIFY(gdb.execute("print us").contains("50")); QVERIFY(gdb.execute("print i").contains("50")); QVERIFY(gdb.execute("print ui").contains("50")); QVERIFY(gdb.execute("print l").contains("50")); QVERIFY(gdb.execute("print ul").contains("50")); QVERIFY(gdb.execute("print i64").contains("50")); QVERIFY(gdb.execute("print ui64").contains("50")); QVERIFY(gdb.execute("print f").contains("50")); QVERIFY(gdb.execute("print d").contains("50")); } void QtPrintersTest::testQUuid() { GdbProcess gdb("quuid"); gdb.execute("break quuid.cpp:4"); gdb.execute("run"); QByteArray data = gdb.execute("print id"); QVERIFY(data.contains("{9ec3b70b-d105-42bf-b3b4-656e44d2e223}")); } void QtPrintersTest::testKTextEditorTypes() { GdbProcess gdb("ktexteditortypes"); gdb.execute("break ktexteditortypes.cpp:9"); gdb.execute("run"); QByteArray data = gdb.execute("print cursor"); QCOMPARE(data, QByteArray("$1 = [1, 1]")); data = gdb.execute("print range"); QCOMPARE(data, QByteArray("$2 = [(1, 1) -> (2, 2)]")); } void QtPrintersTest::testKDevelopTypes() { GdbProcess gdb("kdeveloptypes"); gdb.execute("break kdeveloptypes.cpp:12"); gdb.execute("run"); QVERIFY(gdb.execute("print path1").contains("(\"tmp\", \"foo\")")); QVERIFY(gdb.execute("print path2").contains("(\"http://www.test.com\", \"tmp\", \"asdf.txt\")")); } } QTEST_MAIN(GDBDebugger::QtPrintersTest) diff --git a/debuggers/gdb/printers/tests/quuid.cpp b/debuggers/gdb/printers/tests/quuid.cpp index 69b25140fc..574300cbfc 100644 --- a/debuggers/gdb/printers/tests/quuid.cpp +++ b/debuggers/gdb/printers/tests/quuid.cpp @@ -1,5 +1,6 @@ #include int main() { QUuid id("{9ec3b70b-d105-42bf-b3b4-656e44d2e223}"); + Q_UNUSED(id.toString()); // prevent compiler optimizing away unused object return 0; } diff --git a/file_templates/testing/cpp_gtest/cpp_gtest.desktop b/file_templates/testing/cpp_gtest/cpp_gtest.desktop index d3294e372b..ed5b66db27 100644 --- a/file_templates/testing/cpp_gtest/cpp_gtest.desktop +++ b/file_templates/testing/cpp_gtest/cpp_gtest.desktop @@ -1,80 +1,82 @@ [General] Name=Google Test +Name[sl]=Google Test Comment=A unit test using the Google Test library +Comment[sl]=Preizkus enote z uporabo knjižnice Google Test Category=C++/Basic Language=C++ Language[bs]=C++ Language[ca]=C++ Language[ca@valencia]=C++ Language[cs]=C++ Language[da]=C++ Language[de]=C++ Language[el]=C++ Language[en_GB]=C++ Language[es]=C++ Language[et]=C++ Language[fi]=C++ Language[fr]=C++ Language[ga]=C++ Language[gl]=C++ Language[hu]=C++ Language[it]=C++ Language[kk]=C++ Language[mr]=C++ Language[nb]=C++ Language[nds]=C++ Language[nl]=C++ Language[nn]=C++ Language[pl]=C++ Language[pt]=C++ Language[pt_BR]=C++ Language[ru]=C++ Language[se]=C++ Language[sk]=C++ Language[sl]=C++ Language[sv]=C++ Language[tr]=C++ Language[ug]=C++ Language[uk]=C++ Language[x-test]=xxC++xx Language[zh_CN]=C++ Language[zh_TW]=C++ Type=Test Files=Implementation [Implementation] Name=Implementation Name[bs]=Implementacija Name[ca]=Implementació Name[ca@valencia]=Implementació Name[cs]=Implementace Name[da]=Implementering Name[de]=Implementation Name[el]=Υλοποίηση Name[en_GB]=Implementation Name[es]=Implementación Name[et]=Teostus Name[fi]=Toteutus Name[fr]=Implémentation Name[gl]=Implementación Name[hu]=Megvalósítás Name[it]=Implementazione Name[kk]=Іске асыруы Name[nb]=Implementering Name[nds]=Ümsetten Name[nl]=Implementatie Name[pl]=Implementacja Name[pt]=Implementação Name[pt_BR]=Implementação Name[ru]=Реализация Name[sk]=Implementácia Name[sl]=Izvedba Name[sv]=Implementering Name[tr]=Gerçekleme Name[ug]=ئەمەلگە ئاشۇرۇش Name[uk]=Реалізація Name[x-test]=xxImplementationxx Name[zh_CN]=实现 Name[zh_TW]=實作 File=class.cpp OutputFile={{ name }}_test.cpp diff --git a/languages/clang/util/clangutils.cpp b/languages/clang/util/clangutils.cpp index f08e93eef9..0f02670e25 100644 --- a/languages/clang/util/clangutils.cpp +++ b/languages/clang/util/clangutils.cpp @@ -1,395 +1,401 @@ /* * Copyright 2014 Kevin Funk * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "clangutils.h" #include "../util/clangdebug.h" #include "../util/clangtypes.h" #include "../duchain/cursorkindtraits.h" #include #include #include #include #include #include using namespace KDevelop; CXCursor ClangUtils::getCXCursor(int line, int column, const CXTranslationUnit& unit, const CXFile& file) { if (!file) { clangDebug() << "getCXCursor couldn't find file: " << clang_getFileName(file); return clang_getNullCursor(); } CXSourceLocation location = clang_getLocation(unit, file, line + 1, column + 1); if (clang_equalLocations(clang_getNullLocation(), location)) { clangDebug() << "getCXCursor given invalid position " << line << ", " << column << " for file " << clang_getFileName(file); return clang_getNullCursor(); } return clang_getCursor(unit, location); } KTextEditor::Range ClangUtils::rangeForIncludePathSpec(const QString& line, const KTextEditor::Range& originalRange) { static const QRegularExpression pattern(QStringLiteral("^\\s*#include")); if (!line.contains(pattern)) { return KTextEditor::Range::invalid(); } KTextEditor::Range range = originalRange; int pos = 0; char term_char = 0; for (; pos < line.size(); ++pos) { if (line[pos] == QLatin1Char('"') || line[pos] == QLatin1Char('<')) { term_char = line[pos] == QLatin1Char('"') ? '"' : '>'; range.setStart({ range.start().line(), ++pos }); break; } } for (; pos < line.size(); ++pos) { if (line[pos] == QLatin1Char('\\')) { ++pos; continue; } else if(line[pos] == QLatin1Char(term_char)) { range.setEnd({ range.start().line(), pos }); break; } } return range; } namespace { struct FunctionInfo { KTextEditor::Range range; QString fileName; CXTranslationUnit unit; QStringList stringParts; }; CXChildVisitResult paramVisitor(CXCursor cursor, CXCursor /*parent*/, CXClientData data) { //Ignore the type of the parameter CXCursorKind kind = clang_getCursorKind(cursor); if (kind == CXCursor_TypeRef || kind == CXCursor_TemplateRef || kind == CXCursor_NamespaceRef) { return CXChildVisit_Continue; } FunctionInfo *info = static_cast(data); ClangRange range(clang_getCursorExtent(cursor)); CXFile file; clang_getFileLocation(clang_getCursorLocation(cursor),&file,nullptr,nullptr,nullptr); if (!file) { clangDebug() << "Couldn't find file associated with default parameter cursor!"; //We keep going, because getting an error because we accidentally duplicated //a default parameter is better than deleting a default parameter } QString fileName = ClangString(clang_getFileName(file)).toString(); //Clang doesn't make a distinction between the default arguments being in //the declaration or definition, and the default arguments don't have lexical //parents. So this range check is the only thing that really works. if ((info->fileName.isEmpty() || fileName == info->fileName) && info->range.contains(range.toRange())) { const ClangTokens tokens(info->unit, range.range()); for (CXToken token : tokens) { info->stringParts.append(ClangString(clang_getTokenSpelling(info->unit, token)).toString()); } } return CXChildVisit_Continue; } } QVector ClangUtils::getDefaultArguments(CXCursor cursor, DefaultArgumentsMode mode) { if (!CursorKindTraits::isFunction(clang_getCursorKind(cursor))) { return QVector(); } int numArgs = clang_Cursor_getNumArguments(cursor); QVector arguments(mode == FixedSize ? numArgs : 0); QString fileName; CXFile file; clang_getFileLocation(clang_getCursorLocation(cursor),&file,nullptr,nullptr,nullptr); if (!file) { clangDebug() << "Couldn't find file associated with default parameter cursor!"; //The empty string serves as a wildcard string, because it's better to //duplicate a default parameter than delete one } else { fileName = ClangString(clang_getFileName(file)).toString(); } FunctionInfo info{ClangRange(clang_getCursorExtent(cursor)).toRange(), fileName, clang_Cursor_getTranslationUnit(cursor), QStringList()}; for (int i = 0; i < numArgs; i++) { CXCursor arg = clang_Cursor_getArgument(cursor, i); info.stringParts.clear(); clang_visitChildren(arg, paramVisitor, &info); //Clang includes the equal sign sometimes, but not other times. if (!info.stringParts.isEmpty() && info.stringParts.first() == QLatin1String("=")) { info.stringParts.removeFirst(); } //Clang seems to include the , or ) at the end of the param, so delete that if (!info.stringParts.isEmpty() && (info.stringParts.last() == QLatin1String(",") || info.stringParts.last() == QLatin1String(")"))) { info.stringParts.removeLast(); } const QString result = info.stringParts.join(QString()); if (mode == FixedSize) { arguments.replace(i, result); } else if (!result.isEmpty()) { arguments << result; } } return arguments; } bool ClangUtils::isScopeKind(CXCursorKind kind) { return kind == CXCursor_Namespace || kind == CXCursor_StructDecl || kind == CXCursor_UnionDecl || kind == CXCursor_ClassDecl || kind == CXCursor_ClassTemplate || kind == CXCursor_ClassTemplatePartialSpecialization; } QString ClangUtils::getScope(CXCursor cursor, CXCursor context) { QStringList scope; if (clang_Cursor_isNull(context)) { context = clang_getCursorLexicalParent(cursor); } context = clang_getCanonicalCursor(context); CXCursor search = clang_getCursorSemanticParent(cursor); while (isScopeKind(clang_getCursorKind(search)) && !clang_equalCursors(search, context)) { scope.prepend(ClangString(clang_getCursorDisplayName(search)).toString()); search = clang_getCursorSemanticParent(search); } return scope.join(QStringLiteral("::")); } QString ClangUtils::getCursorSignature(CXCursor cursor, const QString& scope, const QVector& defaultArgs) { CXCursorKind kind = clang_getCursorKind(cursor); //Get the return type QString ret; ret.reserve(128); QTextStream stream(&ret); if (kind != CXCursor_Constructor && kind != CXCursor_Destructor) { stream << ClangString(clang_getTypeSpelling(clang_getCursorResultType(cursor))).toString() << ' '; } //Build the function name, with scope and parameters if (!scope.isEmpty()) { stream << scope << "::"; } QString functionName = ClangString(clang_getCursorSpelling(cursor)).toString(); if (functionName.contains(QLatin1Char('<'))) { stream << functionName.left(functionName.indexOf(QLatin1Char('<'))); } else { stream << functionName; } //Add the parameters and such stream << '('; int numArgs = clang_Cursor_getNumArguments(cursor); for (int i = 0; i < numArgs; i++) { CXCursor arg = clang_Cursor_getArgument(cursor, i); //Clang formats pointer types as "t *x" and reference types as "t &x", while //KDevelop formats them as "t* x" and "t& x". Make that adjustment. const QString type = ClangString(clang_getTypeSpelling(clang_getCursorType(arg))).toString(); if (type.endsWith(QLatin1String(" *")) || type.endsWith(QLatin1String(" &"))) { stream << type.left(type.length() - 2) << type.at(type.length() - 1); } else { stream << type; } const QString id = ClangString(clang_getCursorDisplayName(arg)).toString(); if (!id.isEmpty()) { stream << ' ' << id; } if (i < defaultArgs.count() && !defaultArgs.at(i).isEmpty()) { stream << " = " << defaultArgs.at(i); } if (i < numArgs - 1) { stream << ", "; } } if (clang_Cursor_isVariadic(cursor)) { if (numArgs > 0) { stream << ", "; } stream << "..."; } stream << ')'; if (clang_CXXMethod_isConst(cursor)) { stream << " const"; } return ret; } QStringList ClangUtils::templateArgumentTypes(CXCursor cursor) { CXType typeList = clang_getCursorType(cursor); int templateArgCount = clang_Type_getNumTemplateArguments(typeList); QStringList types; types.reserve(templateArgCount); for (int i = 0; i < templateArgCount; ++i) { ClangString clangString(clang_getTypeSpelling(clang_Type_getTemplateArgumentAsType(typeList, i))); types.append(clangString.toString()); } return types; } QByteArray ClangUtils::getRawContents(CXTranslationUnit unit, CXSourceRange range) { const auto rangeStart = clang_getRangeStart(range); const auto rangeEnd = clang_getRangeEnd(range); unsigned int start, end; clang_getFileLocation(rangeStart, nullptr, nullptr, nullptr, &start); clang_getFileLocation(rangeEnd, nullptr, nullptr, nullptr, &end); QByteArray result; const ClangTokens tokens(unit, range); for (CXToken token : tokens) { const auto location = ClangLocation(clang_getTokenLocation(unit, token)); unsigned int offset; clang_getFileLocation(location, nullptr, nullptr, nullptr, &offset); - Q_ASSERT(offset >= start); + Q_ASSERT(offset >= start); // TODO: Sometimes hit, see bug 357585 + if (offset < start) + return {}; + const int fillCharacters = offset - start - result.size(); Q_ASSERT(fillCharacters >= 0); + if (fillCharacters < 0) + return {}; + result.append(QByteArray(fillCharacters, ' ')); const auto spelling = clang_getTokenSpelling(unit, token); result.append(clang_getCString(spelling)); clang_disposeString(spelling); } // Clang always appends the full range of the last token, even if this exceeds the end of the requested range. // Fix this. result.chop(result.size() - (end - start)); return result; } bool ClangUtils::isExplicitlyDefaultedOrDeleted(CXCursor cursor) { if (clang_getCursorAvailability(cursor) == CXAvailability_NotAvailable) { return true; } // TODO: expose clang::FunctionDecl::isExplicitlyDefaulted() in libclang // For symmetry we should probably also expose clang::FunctionDecl::isDeleted() auto declCursor = clang_getCanonicalCursor(cursor); CXTranslationUnit tu = clang_Cursor_getTranslationUnit(declCursor); ClangTokens tokens(tu, clang_getCursorExtent(declCursor)); bool lastTokenWasDeleteOrDefault = false; for (auto it = tokens.rbegin(), end = tokens.rend(); it != end; ++it) { CXToken token = *it; auto kind = clang_getTokenKind(token); switch (kind) { case CXToken_Comment: break; case CXToken_Identifier: case CXToken_Literal: lastTokenWasDeleteOrDefault = false; break; case CXToken_Punctuation: { ClangString spelling(clang_getTokenSpelling(tu, token)); const char* spellingCStr = spelling.c_str(); if (strcmp(spellingCStr, ")") == 0) { // a closing parent means we have reached the end of the function parameter list // therefore this function can't be explicitly deleted/defaulted return false; } else if (strcmp(spellingCStr, "=") == 0) { if (lastTokenWasDeleteOrDefault) { return true; } #if CINDEX_VERSION_MINOR < 31 // HACK: on old clang versions, we don't get the default/delete // so there, assume the function is defaulted or deleted // when the last token is an equal sign if (it == tokens.rbegin()) { return true; } #endif } lastTokenWasDeleteOrDefault = false; break; } case CXToken_Keyword: { ClangString spelling(clang_getTokenSpelling(tu, token)); const char* spellingCStr = spelling.c_str(); if (strcmp(spellingCStr, "default") == 0 #if CINDEX_VERSION_MINOR < 31 || strcmp(spellingCStr, "delete") == 0 #endif ) { lastTokenWasDeleteOrDefault = true; } else { lastTokenWasDeleteOrDefault = false; } break; } } } return false; } ClangUtils::SpecialQtAttributes ClangUtils::specialQtAttributes(CXCursor cursor) { // check for our injected attributes to detect Qt signals and slots // see also the contents of wrappedQtHeaders/QtCore/qobjectdefs.h SpecialQtAttributes qtAttribute = NoQtAttribute; if (cursor.kind == CXCursor_CXXMethod) { clang_visitChildren(cursor, [] (CXCursor cursor, CXCursor /*parent*/, CXClientData data) -> CXChildVisitResult { if (cursor.kind == CXCursor_AnnotateAttr) { auto retVal = static_cast(data); ClangString attribute(clang_getCursorDisplayName(cursor)); if (attribute.c_str() == QByteArrayLiteral("qt_signal")) { *retVal = QtSignalAttribute; } else if (attribute.c_str() == QByteArrayLiteral("qt_slot")) { *retVal = QtSlotAttribute; } } return CXChildVisit_Break; }, &qtAttribute); } return qtAttribute; } diff --git a/projectmanagers/custom-buildsystem/configwidget.cpp b/projectmanagers/custom-buildsystem/configwidget.cpp index 31052f773b..7c383b37cd 100644 --- a/projectmanagers/custom-buildsystem/configwidget.cpp +++ b/projectmanagers/custom-buildsystem/configwidget.cpp @@ -1,156 +1,158 @@ /************************************************************************ * KDevelop4 Custom Buildsystem Support * * * * Copyright 2010 Andreas Pakulat * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 or version 3 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, but * * WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * * General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, see . * ************************************************************************/ #include "configwidget.h" #include #include #include #include #include #include "ui_configwidget.h" #include #include using namespace KDevelop; ConfigWidget::ConfigWidget( QWidget* parent ) : QWidget ( parent ), ui( new Ui::ConfigWidget ) { ui->setupUi( this ); ui->buildAction->insertItem( CustomBuildSystemTool::Build, i18n("Build"), QVariant() ); ui->buildAction->insertItem( CustomBuildSystemTool::Configure, i18n("Configure"), QVariant() ); ui->buildAction->insertItem( CustomBuildSystemTool::Install, i18n("Install"), QVariant() ); ui->buildAction->insertItem( CustomBuildSystemTool::Clean, i18n("Clean"), QVariant() ); ui->buildAction->insertItem( CustomBuildSystemTool::Prune, i18n("Prune"), QVariant() ); connect( ui->buildAction, static_cast(&KComboBox::activated), this, &ConfigWidget::changeAction ); connect( ui->enableAction, &QCheckBox::toggled, this, &ConfigWidget::toggleActionEnablement ); connect( ui->actionArguments, &QLineEdit::textEdited, this, &ConfigWidget::actionArgumentsEdited ); connect( ui->actionEnvironment, &EnvironmentSelectionWidget::currentProfileChanged, this, &ConfigWidget::actionEnvironmentChanged ); + connect( ui->buildDir, &KUrlRequester::urlSelected, this, static_cast(&ConfigWidget::changed) ); + connect( ui->buildDir->lineEdit(), &KLineEdit::textEdited, this, static_cast(&ConfigWidget::changed) ); connect( ui->actionExecutable, &KUrlRequester::urlSelected, this, static_cast(&ConfigWidget::actionExecutableChanged) ); connect( ui->actionExecutable->lineEdit(), &KLineEdit::textEdited, this, static_cast(&ConfigWidget::actionExecutableChanged) ); } CustomBuildSystemConfig ConfigWidget::config() const { CustomBuildSystemConfig c; c.buildDir = ui->buildDir->url(); c.tools = m_tools; return c; } void ConfigWidget::loadConfig( CustomBuildSystemConfig cfg ) { bool b = blockSignals( true ); clear(); ui->buildDir->setUrl( cfg.buildDir ); m_tools = cfg.tools; blockSignals( b ); changeAction( ui->buildAction->currentIndex() ); m_tools = cfg.tools; } void ConfigWidget::setTool(const CustomBuildSystemTool& tool) { bool b = ui->enableAction->blockSignals( true ); ui->enableAction->setChecked( tool.enabled ); ui->enableAction->blockSignals( b ); ui->actionArguments->setText( tool.arguments ); ui->actionArguments->setEnabled( tool.enabled ); ui->actionExecutable->setUrl( tool.executable ); ui->actionExecutable->setEnabled( tool.enabled ); ui->actionEnvironment->setCurrentProfile( tool.envGrp ); ui->actionEnvironment->setEnabled( tool.enabled ); ui->execLabel->setEnabled( tool.enabled ); ui->argLabel->setEnabled( tool.enabled ); ui->envLabel->setEnabled( tool.enabled ); } void ConfigWidget::changeAction( int idx ) { if (idx < 0 || idx >= m_tools.count() ) { CustomBuildSystemTool emptyTool; emptyTool.type = CustomBuildSystemTool::Build; emptyTool.enabled = false; setTool(emptyTool); } else { CustomBuildSystemTool& selectedTool = m_tools[idx]; setTool(selectedTool); } } void ConfigWidget::toggleActionEnablement( bool enable ) { applyChange([=] (CustomBuildSystemTool* tool) { tool->enabled = enable; }); } void ConfigWidget::actionArgumentsEdited( const QString& txt ) { applyChange([=] (CustomBuildSystemTool* tool) { tool->arguments = txt; }); } void ConfigWidget::actionEnvironmentChanged(const QString& profile) { applyChange([=] (CustomBuildSystemTool* tool) { tool->envGrp = profile; }); } void ConfigWidget::actionExecutableChanged( const QUrl &url ) { applyChange([=] (CustomBuildSystemTool* tool) { tool->executable = url; }); } void ConfigWidget::actionExecutableChanged(const QString& txt ) { applyChange([=] (CustomBuildSystemTool* tool) { tool->executable = QUrl::fromLocalFile(txt); }); } void ConfigWidget::clear() { ui->buildAction->setCurrentIndex( int( CustomBuildSystemTool::Build ) ); changeAction( ui->buildAction->currentIndex() ); ui->buildDir->setText({}); } template void ConfigWidget::applyChange(F toolChanger) { const auto idx = ui->buildAction->currentIndex(); if (idx < 0 || idx >= m_tools.count()) { // happens for the empty tool return; } toolChanger(&m_tools[idx]); emit changed(); }