diff --git a/src/plugins/debugger/cdb/cdbengine.cpp b/src/plugins/debugger/cdb/cdbengine.cpp index 550d0360a7c7e668fcfd54b5c8413f9a001b0f6c..903a991caf3afd65446e9bd3ee64c621ada6bb00 100644 --- a/src/plugins/debugger/cdb/cdbengine.cpp +++ b/src/plugins/debugger/cdb/cdbengine.cpp @@ -55,6 +55,7 @@ #include "shared/cdbsymbolpathlisteditor.h" #include "shared/hostutils.h" #include "procinterrupt.h" +#include "sourceutils.h" #include <TranslationUnit.h> diff --git a/src/plugins/debugger/debugger.pro b/src/plugins/debugger/debugger.pro index eb9e101e716cfa9eadc1127dd9cfc8d829f7d29e..0ca196b27fe00f305894698487ddac3511648118 100644 --- a/src/plugins/debugger/debugger.pro +++ b/src/plugins/debugger/debugger.pro @@ -57,6 +57,7 @@ HEADERS += \ sourceagent.h \ sourcefileshandler.h \ sourcefileswindow.h \ + sourceutils.h \ stackframe.h \ stackhandler.h \ stackwindow.h \ @@ -107,6 +108,7 @@ SOURCES += \ sourceagent.cpp \ sourcefileshandler.cpp \ sourcefileswindow.cpp \ + sourceutils.cpp \ stackhandler.cpp \ stackwindow.cpp \ threadshandler.cpp \ diff --git a/src/plugins/debugger/debugger.qbs b/src/plugins/debugger/debugger.qbs index 1cbbd2c4824064369125c7c882c20f7373f38687..d87a42171f2eb6a2b28acf47f0aed768dcae3cf6 100644 --- a/src/plugins/debugger/debugger.qbs +++ b/src/plugins/debugger/debugger.qbs @@ -117,6 +117,8 @@ QtcPlugin { "sourcefileshandler.h", "sourcefileswindow.cpp", "sourcefileswindow.h", + "sourceutils.cpp", + "sourceutils.h", "stackframe.cpp", "stackframe.h", "stackhandler.cpp", diff --git a/src/plugins/debugger/debuggerplugin.cpp b/src/plugins/debugger/debuggerplugin.cpp index a1e155a376b3225641a33712a3f9555321ea317e..a2d76a2bba679a69899541e9bd5437b452b7442c 100644 --- a/src/plugins/debugger/debuggerplugin.cpp +++ b/src/plugins/debugger/debuggerplugin.cpp @@ -62,6 +62,7 @@ #include "localsandexpressionswindow.h" #include "loadcoredialog.h" #include "hostutils.h" +#include "sourceutils.h" #include "snapshothandler.h" #include "threadshandler.h" diff --git a/src/plugins/debugger/gdb/classicgdbengine.cpp b/src/plugins/debugger/gdb/classicgdbengine.cpp index a6634d8226e2c5df58643331db9e21c58068d193..957a5b6bb322b0ace2bf6dff3ffcecfaeebf5a2f 100644 --- a/src/plugins/debugger/gdb/classicgdbengine.cpp +++ b/src/plugins/debugger/gdb/classicgdbengine.cpp @@ -34,6 +34,7 @@ #include "debuggerprotocol.h" #include "debuggerstartparameters.h" #include "debuggerstringutils.h" +#include "sourceutils.h" #include "stackhandler.h" #include "watchhandler.h" diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp index 2d97fbcda298b2a609438018b6cf18a2825a4e5d..fef90b55a1441d4fceedcc850962b8066e1f3956 100644 --- a/src/plugins/debugger/gdb/gdbengine.cpp +++ b/src/plugins/debugger/gdb/gdbengine.cpp @@ -51,6 +51,7 @@ #include "disassembleragent.h" #include "gdboptionspage.h" #include "memoryagent.h" +#include "sourceutils.h" #include "watchutils.h" #include "breakhandler.h" @@ -5448,6 +5449,14 @@ void GdbEngine::interruptLocalInferior(qint64 pid) } } +QByteArray GdbEngine::dotEscape(QByteArray str) +{ + str.replace(' ', '.'); + str.replace('\\', '.'); + str.replace('/', '.'); + return str; +} + // // Factory // diff --git a/src/plugins/debugger/gdb/gdbengine.h b/src/plugins/debugger/gdb/gdbengine.h index 6cd6597438e12487516aedca389d03aa55a17c8a..c9f932f89caaaf3de6468e6d064c9c279c4fa6bf 100644 --- a/src/plugins/debugger/gdb/gdbengine.h +++ b/src/plugins/debugger/gdb/gdbengine.h @@ -720,6 +720,7 @@ protected: static QString msgInferiorSetupOk(); static QString msgInferiorRunOk(); static QString msgConnectRemoteServerFailed(const QString &why); + static QByteArray dotEscape(QByteArray str); protected: enum DumperHandling diff --git a/src/plugins/debugger/pdb/pdbengine.cpp b/src/plugins/debugger/pdb/pdbengine.cpp index ca78dcff62c10ca63edd03a72f9cf36918c3a14e..b87b27ce2d1ef42b99a2a476c1fd11622509a67a 100644 --- a/src/plugins/debugger/pdb/pdbengine.cpp +++ b/src/plugins/debugger/pdb/pdbengine.cpp @@ -42,6 +42,7 @@ #include "moduleshandler.h" #include "registerhandler.h" #include "stackhandler.h" +#include "sourceutils.h" #include "watchhandler.h" #include "watchutils.h" diff --git a/src/plugins/debugger/script/scriptengine.cpp b/src/plugins/debugger/script/scriptengine.cpp index b8a8a8ff82306d716ae839a2fbfece482a9d8527..f88bd8f35e29f103ee6da398babfdf82a89f29c2 100644 --- a/src/plugins/debugger/script/scriptengine.cpp +++ b/src/plugins/debugger/script/scriptengine.cpp @@ -37,6 +37,7 @@ #include "debuggerstringutils.h" #include "moduleshandler.h" #include "registerhandler.h" +#include "sourceutils.h" #include "stackhandler.h" #include "watchhandler.h" #include "watchutils.h" diff --git a/src/plugins/debugger/sourceutils.cpp b/src/plugins/debugger/sourceutils.cpp new file mode 100644 index 0000000000000000000000000000000000000000..22dc8e34d3e30fcfebfadc380ada7d39642fde4f --- /dev/null +++ b/src/plugins/debugger/sourceutils.cpp @@ -0,0 +1,431 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#include "sourceutils.h" + +#include "debuggerprotocol.h" +#include "debuggerstringutils.h" +#include "watchdata.h" +#include "watchutils.h" + +#include <utils/qtcassert.h> + +#include <coreplugin/idocument.h> + +#include <texteditor/basetexteditor.h> +#include <texteditor/basetextmark.h> +#include <texteditor/itexteditor.h> +#include <texteditor/texteditorconstants.h> +#include <coreplugin/editormanager/editormanager.h> + +#include <cpptools/cpptoolsconstants.h> +#include <cpptools/abstracteditorsupport.h> + +#include <cpptools/ModelManagerInterface.h> +#include <cplusplus/ExpressionUnderCursor.h> +#include <cplusplus/Overview.h> +#include <Symbols.h> +#include <Scope.h> + +#include <extensionsystem/pluginmanager.h> + +#include <QCoreApplication> +#include <QDateTime> +#include <QDebug> +#include <QHash> +#include <QStringList> +#include <QTextStream> +#include <QTime> + +#include <QTextCursor> +#include <QPlainTextEdit> + +#include <string.h> +#include <ctype.h> + +enum { debug = 0 }; + +// Debug helpers for code model. @todo: Move to some CppTools library? +namespace CPlusPlus { + +static void debugCppSymbolRecursion(QTextStream &str, const Overview &o, + const Symbol &s, bool doRecurse = true, + int recursion = 0) +{ + for (int i = 0; i < recursion; i++) + str << " "; + str << "Symbol: " << o.prettyName(s.name()) << " at line " << s.line(); + if (s.isFunction()) + str << " function"; + if (s.isClass()) + str << " class"; + if (s.isDeclaration()) + str << " declaration"; + if (s.isBlock()) + str << " block"; + if (doRecurse && s.isScope()) { + const Scope *scoped = s.asScope(); + const int size = scoped->memberCount(); + str << " scoped symbol of " << size << '\n'; + for (int m = 0; m < size; m++) + debugCppSymbolRecursion(str, o, *scoped->memberAt(m), true, recursion + 1); + } else { + str << '\n'; + } +} + +QDebug operator<<(QDebug d, const Symbol &s) +{ + QString output; + CPlusPlus::Overview o; + QTextStream str(&output); + debugCppSymbolRecursion(str, o, s, true, 0); + d.nospace() << output; + return d; +} + +QDebug operator<<(QDebug d, const Scope &scope) +{ + QString output; + Overview o; + QTextStream str(&output); + const int size = scope.memberCount(); + str << "Scope of " << size; + if (scope.isNamespace()) + str << " namespace"; + if (scope.isClass()) + str << " class"; + if (scope.isEnum()) + str << " enum"; + if (scope.isBlock()) + str << " block"; + if (scope.isFunction()) + str << " function"; + if (scope.isFunction()) + str << " prototype"; +#if 0 // ### port me + if (const Symbol *owner = &scope) { + str << " owner: "; + debugCppSymbolRecursion(str, o, *owner, false, 0); + } else { + str << " 0-owner\n"; + } +#endif + for (int s = 0; s < size; s++) + debugCppSymbolRecursion(str, o, *scope.memberAt(s), true, 2); + d.nospace() << output; + return d; +} +} // namespace CPlusPlus + +namespace Debugger { +namespace Internal { + +/* getUninitializedVariables(): Get variables that are not initialized + * at a certain line of a function from the code model to be able to + * indicate them as not in scope in the locals view. + * Find document + function in the code model, do a double check and + * collect declarative symbols that are in the function past or on + * the current line. blockRecursion() recurses up the scopes + * and collect symbols declared past or on the current line. + * Recursion goes up from the innermost scope, keeping a map + * of occurrences seen, to be able to derive the names of + * shadowed variables as the debugger sees them: +\code +int x; // Occurrence (1), should be reported as "x <shadowed 1>" +if (true) { + int x = 5; (2) // Occurrence (2), should be reported as "x" +} +\endcode + */ + +typedef QHash<QString, int> SeenHash; + +static void blockRecursion(const CPlusPlus::Overview &overview, + const CPlusPlus::Scope *scope, + unsigned line, + QStringList *uninitializedVariables, + SeenHash *seenHash, + int level = 0) +{ + // Go backwards in case someone has identical variables in the same scope. + // Fixme: loop variables or similar are currently seen in the outer scope + for (int s = scope->memberCount() - 1; s >= 0; --s){ + const CPlusPlus::Symbol *symbol = scope->memberAt(s); + if (symbol->isDeclaration()) { + // Find out about shadowed symbols by bookkeeping + // the already seen occurrences in a hash. + const QString name = overview.prettyName(symbol->name()); + SeenHash::iterator it = seenHash->find(name); + if (it == seenHash->end()) + it = seenHash->insert(name, 0); + else + ++(it.value()); + // Is the declaration on or past the current line, that is, + // the variable not initialized. + if (symbol->line() >= line) + uninitializedVariables->push_back(WatchData::shadowedName(name, it.value())); + } + } + // Next block scope. + if (const CPlusPlus::Scope *enclosingScope = scope->enclosingBlock()) + blockRecursion(overview, enclosingScope, line, uninitializedVariables, seenHash, level + 1); +} + +// Inline helper with integer error return codes. +static inline +int getUninitializedVariablesI(const CPlusPlus::Snapshot &snapshot, + const QString &functionName, + const QString &file, + int line, + QStringList *uninitializedVariables) +{ + uninitializedVariables->clear(); + // Find document + if (snapshot.isEmpty() || functionName.isEmpty() || file.isEmpty() || line < 1) + return 1; + const CPlusPlus::Snapshot::const_iterator docIt = snapshot.find(file); + if (docIt == snapshot.end()) + return 2; + const CPlusPlus::Document::Ptr doc = docIt.value(); + // Look at symbol at line and find its function. Either it is the + // function itself or some expression/variable. + const CPlusPlus::Symbol *symbolAtLine = doc->lastVisibleSymbolAt(line, 0); + if (!symbolAtLine) + return 4; + // First figure out the function to do a safety name check + // and the innermost scope at cursor position + const CPlusPlus::Function *function = 0; + const CPlusPlus::Scope *innerMostScope = 0; + if (symbolAtLine->isFunction()) { + function = symbolAtLine->asFunction(); + if (function->memberCount() == 1) // Skip over function block + if (CPlusPlus::Block *block = function->memberAt(0)->asBlock()) + innerMostScope = block; + } else { + if (const CPlusPlus::Scope *functionScope = symbolAtLine->enclosingFunction()) { + function = functionScope->asFunction(); + innerMostScope = symbolAtLine->isBlock() ? + symbolAtLine->asBlock() : + symbolAtLine->enclosingBlock(); + } + } + if (!function || !innerMostScope) + return 7; + // Compare function names with a bit off fuzz, + // skipping modules from a CDB symbol "lib!foo" or namespaces + // that the code model does not show at this point + CPlusPlus::Overview overview; + const QString name = overview.prettyName(function->name()); + if (!functionName.endsWith(name)) + return 11; + if (functionName.size() > name.size()) { + const char previousChar = functionName.at(functionName.size() - name.size() - 1).toLatin1(); + if (previousChar != ':' && previousChar != '!' ) + return 11; + } + // Starting from the innermost block scope, collect declarations. + SeenHash seenHash; + blockRecursion(overview, innerMostScope, line, uninitializedVariables, &seenHash); + return 0; +} + +bool getUninitializedVariables(const CPlusPlus::Snapshot &snapshot, + const QString &function, + const QString &file, + int line, + QStringList *uninitializedVariables) +{ + const int rc = getUninitializedVariablesI(snapshot, function, file, line, uninitializedVariables); + if (debug) { + QString msg; + QTextStream str(&msg); + str << "getUninitializedVariables() " << function << ' ' << file << ':' << line + << " returns (int) " << rc << " '" + << uninitializedVariables->join(QString(QLatin1Char(','))) << '\''; + if (rc) + str << " of " << snapshot.size() << " documents"; + qDebug() << msg; + } + return rc == 0; +} + +//QByteArray gdbQuoteTypes(const QByteArray &type) +//{ +// // gdb does not understand sizeof(Core::IDocument*). +// // "sizeof('Core::IDocument*')" is also not acceptable, +// // it needs to be "sizeof('Core::IDocument'*)" +// // +// // We never will have a perfect solution here (even if we had a full blown +// // C++ parser as we do not have information on what is a type and what is +// // a variable name. So "a<b>::c" could either be two comparisons of values +// // 'a', 'b' and '::c', or a nested type 'c' in a template 'a<b>'. We +// // assume here it is the latter. +// //return type; + +// // (*('myns::QPointer<myns::QObject>*'*)0x684060)" is not acceptable +// // (*('myns::QPointer<myns::QObject>'**)0x684060)" is acceptable +// if (isPointerType(type)) +// return gdbQuoteTypes(stripPointerType(type)) + '*'; + +// QByteArray accu; +// QByteArray result; +// int templateLevel = 0; + +// const char colon = ':'; +// const char singleQuote = '\''; +// const char lessThan = '<'; +// const char greaterThan = '>'; +// for (int i = 0; i != type.size(); ++i) { +// const char c = type.at(i); +// if (isLetterOrNumber(c) || c == '_' || c == colon || c == ' ') { +// accu += c; +// } else if (c == lessThan) { +// ++templateLevel; +// accu += c; +// } else if (c == greaterThan) { +// --templateLevel; +// accu += c; +// } else if (templateLevel > 0) { +// accu += c; +// } else { +// if (accu.contains(colon) || accu.contains(lessThan)) +// result += singleQuote + accu + singleQuote; +// else +// result += accu; +// accu.clear(); +// result += c; +// } +// } +// if (accu.contains(colon) || accu.contains(lessThan)) +// result += singleQuote + accu + singleQuote; +// else +// result += accu; +// //qDebug() << "GDB_QUOTING" << type << " TO " << result; + +// return result; +//} + +// Utilities to decode string data returned by the dumper helpers. + + +// Editor tooltip support +bool isCppEditor(Core::IEditor *editor) +{ + using namespace CppTools::Constants; + const Core::IDocument *document= editor->document(); + if (!document) + return false; + const QByteArray mimeType = document->mimeType().toLatin1(); + return mimeType == C_SOURCE_MIMETYPE + || mimeType == CPP_SOURCE_MIMETYPE + || mimeType == CPP_HEADER_MIMETYPE + || mimeType == OBJECTIVE_CPP_SOURCE_MIMETYPE; +} + +// Return the Cpp expression, and, if desired, the function +QString cppExpressionAt(TextEditor::ITextEditor *editor, int pos, + int *line, int *column, QString *function /* = 0 */) +{ + using namespace CppTools; + using namespace CPlusPlus; + *line = *column = 0; + if (function) + function->clear(); + + const QPlainTextEdit *plaintext = qobject_cast<QPlainTextEdit*>(editor->widget()); + if (!plaintext) + return QString(); + + QString expr = plaintext->textCursor().selectedText(); + CppModelManagerInterface *modelManager = CppModelManagerInterface::instance(); + if (expr.isEmpty() && modelManager) { + QTextCursor tc(plaintext->document()); + tc.setPosition(pos); + + const QChar ch = editor->characterAt(pos); + if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) + tc.movePosition(QTextCursor::EndOfWord); + + // Fetch the expression's code. + CPlusPlus::ExpressionUnderCursor expressionUnderCursor; + expr = expressionUnderCursor(tc); + *column = tc.positionInBlock(); + *line = tc.blockNumber(); + } else { + const QTextCursor tc = plaintext->textCursor(); + *column = tc.positionInBlock(); + *line = tc.blockNumber(); + } + + if (function && !expr.isEmpty()) + if (const Core::IDocument *document= editor->document()) + if (modelManager) + *function = AbstractEditorSupport::functionAt(modelManager, + document->fileName(), *line, *column); + + return expr; +} + +// Ensure an expression can be added as side-effect +// free debugger expression. +QString fixCppExpression(const QString &expIn) +{ + QString exp = expIn.trimmed();; + // Extract the first identifier, everything else is considered + // too dangerous. + int pos1 = 0, pos2 = exp.size(); + bool inId = false; + for (int i = 0; i != exp.size(); ++i) { + const QChar c = exp.at(i); + const bool isIdChar = c.isLetterOrNumber() || c.unicode() == '_'; + if (inId && !isIdChar) { + pos2 = i; + break; + } + if (!inId && isIdChar) { + inId = true; + pos1 = i; + } + } + exp = exp.mid(pos1, pos2 - pos1); + return removeObviousSideEffects(exp); +} + +QString cppFunctionAt(const QString &fileName, int line) +{ + using namespace CppTools; + using namespace CPlusPlus; + CppModelManagerInterface *modelManager = CppModelManagerInterface::instance(); + return AbstractEditorSupport::functionAt(modelManager, + fileName, line, 1); +} + +} // namespace Internal +} // namespace Debugger diff --git a/src/plugins/debugger/sourceutils.h b/src/plugins/debugger/sourceutils.h new file mode 100644 index 0000000000000000000000000000000000000000..12bea32f198a13e49414fea233ecd916b88389fb --- /dev/null +++ b/src/plugins/debugger/sourceutils.h @@ -0,0 +1,60 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +****************************************************************************/ + +#ifndef SOURCE_UTILS_H +#define SOURCE_UTILS_H + +#include <QSet> +#include <QString> + +namespace TextEditor { class ITextEditor; } +namespace Core { class IEditor; } +namespace CPlusPlus { class Snapshot; } + +namespace Debugger { +namespace Internal { + +// Editor tooltip support +bool isCppEditor(Core::IEditor *editor); +QString cppExpressionAt(TextEditor::ITextEditor *editor, int pos, + int *line, int *column, QString *function = 0); +QString fixCppExpression(const QString &exp); +QString cppFunctionAt(const QString &fileName, int line); + +// Get variables that are not initialized at a certain line +// of a function from the code model. Shadowed variables will +// be reported using the debugger naming conventions '<shadowed n>' +bool getUninitializedVariables(const CPlusPlus::Snapshot &snapshot, + const QString &function, const QString &file, int line, + QStringList *uninitializedVariables); + +} // namespace Internal +} // namespace Debugger + +#endif // SOURCE_UTILS_H diff --git a/src/plugins/debugger/watchdata.cpp b/src/plugins/debugger/watchdata.cpp index 866b31209cdc5b692769f45f5e3eeb2c3a30198b..90234453922a93d44d89f74b77f43f44c2c08e9e 100644 --- a/src/plugins/debugger/watchdata.cpp +++ b/src/plugins/debugger/watchdata.cpp @@ -27,11 +27,14 @@ ** ****************************************************************************/ +// NOTE: Don't add dependencies to other files. +// This is used in the debugger auto-tests. + #include "watchdata.h" #include "watchutils.h" -#include <QTextStream> #include <QTextDocument> +#include <QTextStream> #include <QDebug> //////////////////////////////////////////////////////////////////// diff --git a/src/plugins/debugger/watchutils.cpp b/src/plugins/debugger/watchutils.cpp index ad079eb27a5e5e0b7ac15a9f7fdf86c28c5745bc..162c91b22d6edf7373642f2e947678bd753c1369 100644 --- a/src/plugins/debugger/watchutils.cpp +++ b/src/plugins/debugger/watchutils.cpp @@ -27,135 +27,44 @@ ** ****************************************************************************/ +// NOTE: Don't add dependencies to other files. +// This is used in the debugger auto-tests. + #include "watchutils.h" #include "watchdata.h" #include "debuggerprotocol.h" -#include "debuggerstringutils.h" - -#include <utils/qtcassert.h> - -#include <coreplugin/idocument.h> - -#include <texteditor/basetexteditor.h> -#include <texteditor/basetextmark.h> -#include <texteditor/itexteditor.h> -#include <texteditor/texteditorconstants.h> -#include <coreplugin/editormanager/editormanager.h> - -#include <cpptools/cpptoolsconstants.h> -#include <cpptools/abstracteditorsupport.h> - -#include <cpptools/ModelManagerInterface.h> -#include <cplusplus/ExpressionUnderCursor.h> -#include <cplusplus/Overview.h> -#include <Symbols.h> -#include <Scope.h> - -#include <extensionsystem/pluginmanager.h> -#include <QCoreApplication> -#include <QDateTime> #include <QDebug> -#include <QHash> -#include <QStringList> -#include <QTextStream> -#include <QTime> - -#include <QTextCursor> -#include <QPlainTextEdit> #include <string.h> #include <ctype.h> enum { debug = 0 }; -// Debug helpers for code model. @todo: Move to some CppTools library? -namespace CPlusPlus { +namespace Debugger { +namespace Internal { -static void debugCppSymbolRecursion(QTextStream &str, const Overview &o, - const Symbol &s, bool doRecurse = true, - int recursion = 0) +QString removeObviousSideEffects(const QString &expIn) { - for (int i = 0; i < recursion; i++) - str << " "; - str << "Symbol: " << o.prettyName(s.name()) << " at line " << s.line(); - if (s.isFunction()) - str << " function"; - if (s.isClass()) - str << " class"; - if (s.isDeclaration()) - str << " declaration"; - if (s.isBlock()) - str << " block"; - if (doRecurse && s.isScope()) { - const Scope *scoped = s.asScope(); - const int size = scoped->memberCount(); - str << " scoped symbol of " << size << '\n'; - for (int m = 0; m < size; m++) - debugCppSymbolRecursion(str, o, *scoped->memberAt(m), true, recursion + 1); - } else { - str << '\n'; - } -} + QString exp = expIn.trimmed(); + if (exp.isEmpty() || exp.startsWith(QLatin1Char('#')) || !hasLetterOrNumber(exp) || isKeyWord(exp)) + return QString(); -QDebug operator<<(QDebug d, const Symbol &s) -{ - QString output; - CPlusPlus::Overview o; - QTextStream str(&output); - debugCppSymbolRecursion(str, o, s, true, 0); - d.nospace() << output; - return d; -} + if (exp.startsWith(QLatin1Char('"')) && exp.endsWith(QLatin1Char('"'))) + return QString(); -QDebug operator<<(QDebug d, const Scope &scope) -{ - QString output; - Overview o; - QTextStream str(&output); - const int size = scope.memberCount(); - str << "Scope of " << size; - if (scope.isNamespace()) - str << " namespace"; - if (scope.isClass()) - str << " class"; - if (scope.isEnum()) - str << " enum"; - if (scope.isBlock()) - str << " block"; - if (scope.isFunction()) - str << " function"; - if (scope.isFunction()) - str << " prototype"; -#if 0 // ### port me - if (const Symbol *owner = &scope) { - str << " owner: "; - debugCppSymbolRecursion(str, o, *owner, false, 0); - } else { - str << " 0-owner\n"; - } -#endif - for (int s = 0; s < size; s++) - debugCppSymbolRecursion(str, o, *scope.memberAt(s), true, 2); - d.nospace() << output; - return d; -} -} // namespace CPlusPlus + if (exp.startsWith(QLatin1String("++")) || exp.startsWith(QLatin1String("--"))) + exp.remove(0, 2); -namespace Debugger { -namespace Internal { + if (exp.endsWith(QLatin1String("++")) || exp.endsWith(QLatin1String("--"))) + exp.truncate(exp.size() - 2); -QByteArray dotEscape(QByteArray str) -{ - str.replace(' ', '.'); - str.replace('\\', '.'); - str.replace('/', '.'); - return str; -} + if (exp.startsWith(QLatin1Char('<')) || exp.startsWith(QLatin1Char('['))) + return QString(); -QString currentTime() -{ - return QTime::currentTime().toString(QLatin1String("hh:mm:ss.zzz")); + if (hasSideEffects(exp) || exp.isEmpty()) + return QString(); + return exp; } bool isSkippableFunction(const QString &funcName, const QString &fileName) @@ -269,8 +178,9 @@ bool hasSideEffects(const QString &exp) bool isKeyWord(const QString &exp) { - // FIXME: incomplete - QTC_ASSERT(!exp.isEmpty(), return false); + // FIXME: incomplete. + if (!exp.isEmpty()) + return false; switch (exp.at(0).toLatin1()) { case 'a': return exp == QLatin1String("auto"); @@ -361,135 +271,6 @@ QString formatToolTipAddress(quint64 a) return QLatin1String("0x") + rc; } -/* getUninitializedVariables(): Get variables that are not initialized - * at a certain line of a function from the code model to be able to - * indicate them as not in scope in the locals view. - * Find document + function in the code model, do a double check and - * collect declarative symbols that are in the function past or on - * the current line. blockRecursion() recurses up the scopes - * and collect symbols declared past or on the current line. - * Recursion goes up from the innermost scope, keeping a map - * of occurrences seen, to be able to derive the names of - * shadowed variables as the debugger sees them: -\code -int x; // Occurrence (1), should be reported as "x <shadowed 1>" -if (true) { - int x = 5; (2) // Occurrence (2), should be reported as "x" -} -\endcode - */ - -typedef QHash<QString, int> SeenHash; - -static void blockRecursion(const CPlusPlus::Overview &overview, - const CPlusPlus::Scope *scope, - unsigned line, - QStringList *uninitializedVariables, - SeenHash *seenHash, - int level = 0) -{ - // Go backwards in case someone has identical variables in the same scope. - // Fixme: loop variables or similar are currently seen in the outer scope - for (int s = scope->memberCount() - 1; s >= 0; --s){ - const CPlusPlus::Symbol *symbol = scope->memberAt(s); - if (symbol->isDeclaration()) { - // Find out about shadowed symbols by bookkeeping - // the already seen occurrences in a hash. - const QString name = overview.prettyName(symbol->name()); - SeenHash::iterator it = seenHash->find(name); - if (it == seenHash->end()) - it = seenHash->insert(name, 0); - else - ++(it.value()); - // Is the declaration on or past the current line, that is, - // the variable not initialized. - if (symbol->line() >= line) - uninitializedVariables->push_back(WatchData::shadowedName(name, it.value())); - } - } - // Next block scope. - if (const CPlusPlus::Scope *enclosingScope = scope->enclosingBlock()) - blockRecursion(overview, enclosingScope, line, uninitializedVariables, seenHash, level + 1); -} - -// Inline helper with integer error return codes. -static inline -int getUninitializedVariablesI(const CPlusPlus::Snapshot &snapshot, - const QString &functionName, - const QString &file, - int line, - QStringList *uninitializedVariables) -{ - uninitializedVariables->clear(); - // Find document - if (snapshot.isEmpty() || functionName.isEmpty() || file.isEmpty() || line < 1) - return 1; - const CPlusPlus::Snapshot::const_iterator docIt = snapshot.find(file); - if (docIt == snapshot.end()) - return 2; - const CPlusPlus::Document::Ptr doc = docIt.value(); - // Look at symbol at line and find its function. Either it is the - // function itself or some expression/variable. - const CPlusPlus::Symbol *symbolAtLine = doc->lastVisibleSymbolAt(line, 0); - if (!symbolAtLine) - return 4; - // First figure out the function to do a safety name check - // and the innermost scope at cursor position - const CPlusPlus::Function *function = 0; - const CPlusPlus::Scope *innerMostScope = 0; - if (symbolAtLine->isFunction()) { - function = symbolAtLine->asFunction(); - if (function->memberCount() == 1) // Skip over function block - if (CPlusPlus::Block *block = function->memberAt(0)->asBlock()) - innerMostScope = block; - } else { - if (const CPlusPlus::Scope *functionScope = symbolAtLine->enclosingFunction()) { - function = functionScope->asFunction(); - innerMostScope = symbolAtLine->isBlock() ? - symbolAtLine->asBlock() : - symbolAtLine->enclosingBlock(); - } - } - if (!function || !innerMostScope) - return 7; - // Compare function names with a bit off fuzz, - // skipping modules from a CDB symbol "lib!foo" or namespaces - // that the code model does not show at this point - CPlusPlus::Overview overview; - const QString name = overview.prettyName(function->name()); - if (!functionName.endsWith(name)) - return 11; - if (functionName.size() > name.size()) { - const char previousChar = functionName.at(functionName.size() - name.size() - 1).toLatin1(); - if (previousChar != ':' && previousChar != '!' ) - return 11; - } - // Starting from the innermost block scope, collect declarations. - SeenHash seenHash; - blockRecursion(overview, innerMostScope, line, uninitializedVariables, &seenHash); - return 0; -} - -bool getUninitializedVariables(const CPlusPlus::Snapshot &snapshot, - const QString &function, - const QString &file, - int line, - QStringList *uninitializedVariables) -{ - const int rc = getUninitializedVariablesI(snapshot, function, file, line, uninitializedVariables); - if (debug) { - QString msg; - QTextStream str(&msg); - str << "getUninitializedVariables() " << function << ' ' << file << ':' << line - << " returns (int) " << rc << " '" - << uninitializedVariables->join(QString(QLatin1Char(','))) << '\''; - if (rc) - str << " of " << snapshot.size() << " documents"; - qDebug() << msg; - } - return rc == 0; -} - QByteArray gdbQuoteTypes(const QByteArray &type) { // gdb does not understand sizeof(Core::IDocument*). @@ -609,121 +390,6 @@ void decodeArray(QList<WatchData> *list, const WatchData &tmplate, } } -// Editor tooltip support -bool isCppEditor(Core::IEditor *editor) -{ - using namespace CppTools::Constants; - const Core::IDocument *document= editor->document(); - if (!document) - return false; - const QByteArray mimeType = document->mimeType().toLatin1(); - return mimeType == C_SOURCE_MIMETYPE - || mimeType == CPP_SOURCE_MIMETYPE - || mimeType == CPP_HEADER_MIMETYPE - || mimeType == OBJECTIVE_CPP_SOURCE_MIMETYPE; -} - -// Return the Cpp expression, and, if desired, the function -QString cppExpressionAt(TextEditor::ITextEditor *editor, int pos, - int *line, int *column, QString *function /* = 0 */) -{ - using namespace CppTools; - using namespace CPlusPlus; - *line = *column = 0; - if (function) - function->clear(); - - const QPlainTextEdit *plaintext = qobject_cast<QPlainTextEdit*>(editor->widget()); - if (!plaintext) - return QString(); - - QString expr = plaintext->textCursor().selectedText(); - CppModelManagerInterface *modelManager = CppModelManagerInterface::instance(); - if (expr.isEmpty() && modelManager) { - QTextCursor tc(plaintext->document()); - tc.setPosition(pos); - - const QChar ch = editor->characterAt(pos); - if (ch.isLetterOrNumber() || ch == QLatin1Char('_')) - tc.movePosition(QTextCursor::EndOfWord); - - // Fetch the expression's code. - CPlusPlus::ExpressionUnderCursor expressionUnderCursor; - expr = expressionUnderCursor(tc); - *column = tc.positionInBlock(); - *line = tc.blockNumber(); - } else { - const QTextCursor tc = plaintext->textCursor(); - *column = tc.positionInBlock(); - *line = tc.blockNumber(); - } - - if (function && !expr.isEmpty()) - if (const Core::IDocument *document= editor->document()) - if (modelManager) - *function = AbstractEditorSupport::functionAt(modelManager, - document->fileName(), *line, *column); - - return expr; -} - -// Ensure an expression can be added as side-effect -// free debugger expression. -QString fixCppExpression(const QString &expIn) -{ - QString exp = expIn.trimmed();; - // Extract the first identifier, everything else is considered - // too dangerous. - int pos1 = 0, pos2 = exp.size(); - bool inId = false; - for (int i = 0; i != exp.size(); ++i) { - const QChar c = exp.at(i); - const bool isIdChar = c.isLetterOrNumber() || c.unicode() == '_'; - if (inId && !isIdChar) { - pos2 = i; - break; - } - if (!inId && isIdChar) { - inId = true; - pos1 = i; - } - } - exp = exp.mid(pos1, pos2 - pos1); - return removeObviousSideEffects(exp); -} - -QString removeObviousSideEffects(const QString &expIn) -{ - QString exp = expIn.trimmed(); - if (exp.isEmpty() || exp.startsWith(QLatin1Char('#')) || !hasLetterOrNumber(exp) || isKeyWord(exp)) - return QString(); - - if (exp.startsWith(QLatin1Char('"')) && exp.endsWith(QLatin1Char('"'))) - return QString(); - - if (exp.startsWith(QLatin1String("++")) || exp.startsWith(QLatin1String("--"))) - exp.remove(0, 2); - - if (exp.endsWith(QLatin1String("++")) || exp.endsWith(QLatin1String("--"))) - exp.truncate(exp.size() - 2); - - if (exp.startsWith(QLatin1Char('<')) || exp.startsWith(QLatin1Char('['))) - return QString(); - - if (hasSideEffects(exp) || exp.isEmpty()) - return QString(); - return exp; -} - -QString cppFunctionAt(const QString &fileName, int line) -{ - using namespace CppTools; - using namespace CPlusPlus; - CppModelManagerInterface *modelManager = CppModelManagerInterface::instance(); - return AbstractEditorSupport::functionAt(modelManager, - fileName, line, 1); -} - ////////////////////////////////////////////////////////////////////// // // GdbMi interaction @@ -830,7 +496,7 @@ void setWatchDataType(WatchData &data, const GdbMi &item) void setWatchDataDisplayedType(WatchData &data, const GdbMi &item) { if (item.isValid()) - data.displayedType = _(item.data()); + data.displayedType = QString::fromLatin1(item.data()); } void parseWatchData(const QSet<QByteArray> &expandedINames, @@ -897,7 +563,7 @@ void parseWatchData(const QSet<QByteArray> &expandedINames, data1.sortId = i; GdbMi name = child.findChild("name"); if (name.isValid()) - data1.name = _(name.data()); + data1.name = QString::fromLatin1(name.data()); else data1.name = QString::number(i); GdbMi iname = child.findChild("iname"); @@ -920,7 +586,7 @@ void parseWatchData(const QSet<QByteArray> &expandedINames, QString skey = decodeData(key, encoding); if (skey.size() > 13) { skey = skey.left(12); - skey += _("..."); + skey += QLatin1String("..."); } //data1.name += " (" + skey + ")"; data1.name = skey; @@ -930,6 +596,5 @@ void parseWatchData(const QSet<QByteArray> &expandedINames, } } - } // namespace Internal } // namespace Debugger diff --git a/src/plugins/debugger/watchutils.h b/src/plugins/debugger/watchutils.h index aef4ad1b42fc8a1504364119926c40c8ecdebc86..8ab67f3b77eeb0fcf4dbe4fcfcda89fa92c95d9d 100644 --- a/src/plugins/debugger/watchutils.h +++ b/src/plugins/debugger/watchutils.h @@ -30,29 +30,18 @@ #ifndef WATCHUTILS_H #define WATCHUTILS_H +// NOTE: Don't add dependencies to other files. +// This is used in the debugger auto-tests. + #include <QSet> #include <QString> -namespace TextEditor { - class ITextEditor; -} - -namespace Core { - class IEditor; -} - -namespace CPlusPlus { - class Snapshot; -} - namespace Debugger { namespace Internal { class WatchData; class GdbMi; -QByteArray dotEscape(QByteArray str); -QString currentTime(); bool isSkippableFunction(const QString &funcName, const QString &fileName); bool isLeavableFunction(const QString &funcName, const QString &fileName); @@ -69,25 +58,12 @@ bool isIntOrFloatType(const QByteArray &type); bool isIntType(const QByteArray &type); QString formatToolTipAddress(quint64 a); - -// Editor tooltip support -bool isCppEditor(Core::IEditor *editor); -QString cppExpressionAt(TextEditor::ITextEditor *editor, int pos, - int *line, int *column, QString *function = 0); QString removeObviousSideEffects(const QString &exp); -QString fixCppExpression(const QString &exp); -QString cppFunctionAt(const QString &fileName, int line); + // Decode string data as returned by the dumper helpers. void decodeArray(WatchData *list, const WatchData &tmplate, const QByteArray &rawData, int encoding); -// Get variables that are not initialized at a certain line -// of a function from the code model. Shadowed variables will -// be reported using the debugger naming conventions '<shadowed n>' -bool getUninitializedVariables(const CPlusPlus::Snapshot &snapshot, - const QString &function, const QString &file, int line, - QStringList *uninitializedVariables); - // // GdbMi interaction