Debugger: split editor related code off watchutils.{h,cpp}

This is now in sourceutils.{h,cpp} to make watchutils.{h,cpp}
better acessible to the debugger auto-tests.

Change-Id: Ie87e715bc7018ca190a460c37dfd19bc897059f0
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@digia.com>
This commit is contained in:
hjk
2013-01-24 16:33:17 +01:00
parent fb50278521
commit b6f187d0f2
14 changed files with 546 additions and 392 deletions

View File

@@ -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 {
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 {
QByteArray dotEscape(QByteArray str)
QString removeObviousSideEffects(const QString &expIn)
{
str.replace(' ', '.');
str.replace('\\', '.');
str.replace('/', '.');
return str;
}
QString exp = expIn.trimmed();
if (exp.isEmpty() || exp.startsWith(QLatin1Char('#')) || !hasLetterOrNumber(exp) || isKeyWord(exp))
return QString();
QString currentTime()
{
return QTime::currentTime().toString(QLatin1String("hh:mm:ss.zzz"));
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;
}
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