forked from qt-creator/qt-creator
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:
@@ -55,6 +55,7 @@
|
||||
#include "shared/cdbsymbolpathlisteditor.h"
|
||||
#include "shared/hostutils.h"
|
||||
#include "procinterrupt.h"
|
||||
#include "sourceutils.h"
|
||||
|
||||
#include <TranslationUnit.h>
|
||||
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -117,6 +117,8 @@ QtcPlugin {
|
||||
"sourcefileshandler.h",
|
||||
"sourcefileswindow.cpp",
|
||||
"sourcefileswindow.h",
|
||||
"sourceutils.cpp",
|
||||
"sourceutils.h",
|
||||
"stackframe.cpp",
|
||||
"stackframe.h",
|
||||
"stackhandler.cpp",
|
||||
|
||||
@@ -62,6 +62,7 @@
|
||||
#include "localsandexpressionswindow.h"
|
||||
#include "loadcoredialog.h"
|
||||
#include "hostutils.h"
|
||||
#include "sourceutils.h"
|
||||
|
||||
#include "snapshothandler.h"
|
||||
#include "threadshandler.h"
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include "debuggerprotocol.h"
|
||||
#include "debuggerstartparameters.h"
|
||||
#include "debuggerstringutils.h"
|
||||
#include "sourceutils.h"
|
||||
#include "stackhandler.h"
|
||||
#include "watchhandler.h"
|
||||
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "moduleshandler.h"
|
||||
#include "registerhandler.h"
|
||||
#include "stackhandler.h"
|
||||
#include "sourceutils.h"
|
||||
#include "watchhandler.h"
|
||||
#include "watchutils.h"
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
431
src/plugins/debugger/sourceutils.cpp
Normal file
431
src/plugins/debugger/sourceutils.cpp
Normal file
@@ -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
|
||||
60
src/plugins/debugger/sourceutils.h
Normal file
60
src/plugins/debugger/sourceutils.h
Normal file
@@ -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
|
||||
@@ -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>
|
||||
|
||||
////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user