Files
qt-creator/src/libs/valgrind/callgrind/callgrindparser.cpp
2011-05-10 15:21:41 +02:00

681 lines
20 KiB
C++

/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (info@qt.nokia.com)
**
**
** GNU Lesser General Public License Usage
**
** 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, Nokia gives you certain additional
** rights. These rights are described in the Nokia Qt LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
** Other Usage
**
** Alternatively, this file may be used in accordance with the terms and
** conditions contained in a signed written agreement between you and Nokia.
**
** If you have questions regarding the use of this file, please contact
** Nokia at info@qt.nokia.com.
**
**************************************************************************/
#include "callgrindparser.h"
#include "callgrindparsedata.h"
#include "callgrindfunctioncall.h"
#include "callgrindcostitem.h"
#include "callgrindfunction.h"
#include <utils/qtcassert.h>
#include <QtCore/QHash>
#include <QtCore/QVector>
#include <QtCore/QStringList>
#include <QtCore/QDebug>
// #define DEBUG_PARSER
namespace {
static void skipSpace(const char **current, const char *end)
{
const char *b = *current;
while (b < end) {
if (*b == ' ' || *b == '\t')
b++;
else
break;
}
*current = b;
}
// set *ok to true if at least one digit was parsed; "garbage" after the number is not considered
// an error.
// *current is moved to one char after the last digit
static qint64 parseDecimal(const char **current, const char *end, bool *ok)
{
const char *b = *current;
bool parsedDigit = false;
qint64 ret = 0;
while (b < end) {
const char c = *b;
if (c >= '0' && c <= '9') {
b++;
ret *= 10;
ret += c - '0';
parsedDigit = true;
} else {
break;
}
}
*ok = parsedDigit;
*current = b;
return ret;
}
//like parseDecimal, but for 0xabcd-style hex encoding (without the leading 0x)
static qint64 parseHex(const char **current, const char *end, bool *ok)
{
const char *b = *current;
bool parsedDigit = false;
qint64 ret = 0;
while (b < end) {
char c = *b;
if (c >= '0' && c <= '9')
c &= 0x0f;
else if (c >= 'a' && c <= 'f')
c = c - 'a' + 10;
else
break;
b++;
ret <<= 4;
ret += c;
parsedDigit = true;
}
*ok = parsedDigit;
*current = b;
return ret;
}
static quint64 parseAddr(const char **current, const char *end, bool *ok)
{
if (**current == '0' && *(*current + 1) == 'x') {
*current += 2;
return parseHex(current, end, ok);
} else {
return parseDecimal(current, end, ok);
}
}
// this function expects that *current already points one past the opening parenthesis
static int parseNameShorthand(const char **current, const char *end)
{
bool ok;
int ret = parseDecimal(current, end, &ok);
if (ok) {
if (**current == ')') {
(*current)++;
return ret;
}
}
return -1; // invalid
}
}
namespace Valgrind {
namespace Callgrind {
class Parser::Private
{
Parser *const q;
public:
explicit Private(Parser *qq)
: q(qq),
addressValuesCount(0),
costValuesCount(0),
data(0),
currentFunction(0),
lastObject(-1),
lastFile(-1),
currentDifferingFile(-1),
isParsingFunctionCall(false),
callsCount(0)
{
}
~Private()
{
delete data;
}
void parse(QIODevice *device);
void parseHeader(QIODevice *device);
typedef QPair<qint64, QString> NamePair;
NamePair parseName(const char *begin, const char *end);
void dispatchLine(const QByteArray &line);
void parseCostItem(const char *begin, const char *end);
void parseFunction(const char *begin, const char *end);
void parseSourceFile(const char *begin, const char *end);
void parseDifferingSourceFile(const char *begin, const char *end);
void parseObjectFile(const char *begin, const char *end);
void parseCalls(const char *begin, const char *end);
void parseCalledFunction(const char *begin, const char *end);
void parseCalledSourceFile(const char *begin, const char *end);
void parseCalledObjectFile(const char *begin, const char *end);
int addressValuesCount;
int costValuesCount;
ParseData *data;
Function *currentFunction;
qint64 lastObject;
qint64 lastFile;
qint64 currentDifferingFile;
bool isParsingFunctionCall;
quint64 callsCount;
struct CallData {
CallData()
: calledFunction(-1)
, calledObject(-1)
, calledFile(-1)
, call(0)
{
}
qint64 calledFunction;
qint64 calledObject;
qint64 calledFile;
FunctionCall *call;
};
CallData currentCallData;
QVector<quint64> callDestinations;
// we can only resolve callees after parsing the whole file so save that data here for now
QVector<CallData> pendingCallees;
// id(s) for the ??? file
QVector<quint64> unknownFiles;
// functions which call themselves
QSet<Function *> recursiveFunctions;
};
void Parser::Private::parse(QIODevice *device)
{
// be sure to clean up existing data before re-allocating
// the callee might not have taken the parse data
delete data;
data = 0;
data = new ParseData;
parseHeader(device);
while (!device->atEnd()) {
QByteArray line = device->readLine();
// empty lines actually have no meaning - only fn= starts a new function
if (line.length() > 1)
dispatchLine(line);
}
// build fast lookup of functions by their nameId
QHash<qint64, QList<const Function *> > functionLookup;
foreach(const Function *function, data->functions()) {
functionLookup[function->nameId()].append(function);
}
// functions that need to accumulate their calees
QSet<Function *> pendingFunctions;
foreach(const CallData &callData, pendingCallees) {
Function *calledFunction = 0;
QTC_ASSERT(callData.call, continue);
QTC_ASSERT(callData.call->caller(), continue);
foreach(const Function *function, functionLookup.value(callData.calledFunction)) {
QTC_ASSERT(function->nameId() == callData.calledFunction, continue);
if (function->objectId() == callData.calledObject
&& function->fileId() == callData.calledFile)
{
calledFunction = const_cast<Function *>(function);
break;
}
}
#ifdef DEBUG_PARSER
if (!calledFunction) {
qDebug() << unknownFiles;
qDebug() << "could not find called function:" << data->stringForFunctionCompression(callData.calledFunction) << callData.calledFunction;
qDebug() << "caller is:" << callData.call->caller()->name() << callData.call->caller()->nameId();
qDebug() << "called file:" << callData.calledFile << "object:" << callData.calledObject;
qDebug() << data->stringForFileCompression(callData.calledFile) << data->stringForObjectCompression(callData.calledObject);
foreach(const Function *function, functionLookup.value(callData.calledFunction)) {
qDebug() << "available function file:" << function->fileId() << function->file() << "object:" << function->objectId() << function->object();
}
}
#endif
QTC_ASSERT(calledFunction, continue)
callData.call->setCallee(calledFunction);
calledFunction->addIncomingCall(callData.call);
Function *caller = const_cast<Function *>(callData.call->caller());
caller->addOutgoingCall(callData.call);
pendingFunctions.insert(caller);
}
pendingCallees.clear();
// lookup done
// now accumulate callees
foreach(Function *func, pendingFunctions)
func->finalize();
q->parserDataReady(); // emit
}
inline QString getValue(const QByteArray &line, const int prefixLength)
{
// we are not interested in the trailing newline
// TODO: \r\n ?
return QString::fromLatin1(line.mid(prefixLength, line.length() - 1 - prefixLength).constData());
}
void Parser::Private::parseHeader(QIODevice *device)
{
QTC_ASSERT(device->isOpen(), return);
QTC_ASSERT(device->isReadable(), return);
// parse expected headers until we hit the first non-empty line
while (!device->atEnd()) {
QByteArray line = device->readLine();
// now that we're done checking if we're done (heh) with the header, parse the address
// and cost column descriptions. speed is unimportant here.
if (line.startsWith("positions: ")) {
QString values = getValue(line, 11);
data->setPositions(values.split(QLatin1Char(' '), QString::SkipEmptyParts));
addressValuesCount = data->positions().count();
} else if (line.startsWith("events: ")) {
QString values = getValue(line, 8);
data->setEvents(values.split(QLatin1Char(' '), QString::SkipEmptyParts));
costValuesCount = data->events().count();
} else if (line.startsWith("version: ")) {
QString value = getValue(line, 9);
data->setVersion(value.toInt());
} else if (line.startsWith("creator: ")) {
QString value = getValue(line, 9);
data->setCreator(value);
} else if (line.startsWith("pid: ")) {
QString value = getValue(line, 5);
data->setPid(value.toULongLong());
} else if (line.startsWith("cmd: ")) {
QString value = getValue(line, 5);
data->setCommand(value);
} else if (line.startsWith("part: ")) {
QString value = getValue(line, 6);
data->setPart(value.toUInt());
} else if (line.startsWith("desc: ")) {
QString value = getValue(line, 6);
data->addDescription(value);
} else if (line.startsWith("summary: ")) {
QString values = getValue(line, 9);
uint i = 0;
foreach (const QString &value, values.split(QLatin1Char(' '), QString::SkipEmptyParts))
data->setTotalCost(i++, value.toULongLong());
} else if (!line.trimmed().isEmpty()) {
// handle line and exit parseHeader
dispatchLine(line);
return;
}
}
}
Parser::Private::NamePair Parser::Private::parseName(const char *begin, const char *end)
{
const char *current = begin;
qint64 nameShorthand = -1;
if (*current == '(') {
current++;
if ((nameShorthand = parseNameShorthand(&current, end)) == -1)
return qMakePair(qint64(-1), QString()); // error
}
skipSpace(&current, end);
return qMakePair(nameShorthand, QString::fromUtf8(QByteArray(current, end - current)));
}
/*
* fl means source file
* ob means object file
* fn means function
* fe, fi means different source file
* cfi or cfl means called source file
* cob means called object file
* cfn means called function
*/
void Parser::Private::dispatchLine(const QByteArray &line)
{
const char *const begin = line.constData();
const char *const end = begin + line.length() - 1; // we're not interested in the '\n'
const char *current = begin;
// shortest possible line is "1 1" - a cost item line
QTC_ASSERT(end - begin >= 3, return);
const char first = *begin;
if ((first >= '0' && first <= '9') || first == '+' || first == '*' || first =='-') {
parseCostItem(begin, end);
if (isParsingFunctionCall)
isParsingFunctionCall = false;
return;
}
QTC_ASSERT(!isParsingFunctionCall, return);
current++;
const char second = *current++;
const char third = *current++;
// current now points to the fourth char...
if (first == 'c') {
// information about a callee
const char fourth = *current++;
// current now points to the fifth char...
switch (second) {
// comments show the shortest possible line for every case
case 'a':
{
// "calls=1 1", length 9
QTC_ASSERT(end - begin >= 9, return);
const char fifth = *current++;
const char sixth = *current++;
if (third == 'l' && fourth == 'l' && fifth == 's' && sixth == '=')
parseCalls(current, end);
break;
}
case 'f':
QTC_ASSERT(end - begin >= 5, return);
// "cfi=a" / "cfl=a", length 5
// "cfn=a", length 5
if (fourth == '=') {
if (third == 'i' || third == 'l')
parseCalledSourceFile(current, end);
else if (third == 'n')
parseCalledFunction(current, end);
}
break;
case 'o':
QTC_ASSERT(end - begin >= 5, return);
// "cob=a", length 5
if (third == 'b' && fourth == '=')
parseCalledObjectFile(current, end);
break;
default:
break;
}
} else {
// information about this function
// shortest possible line is always four chars here, of the type "fl=a"
QTC_ASSERT(end - begin >= 4, return);
if (third == '=') {
if (first == 'f') {
if (second == 'l')
parseSourceFile(current, end);
else if (second == 'n')
parseFunction(current, end);
else if (second == 'i' || second == 'e')
parseDifferingSourceFile(current, end);
} else if (first == 'o' && second == 'b') {
parseObjectFile(current, end);
}
}
}
}
void Parser::Private::parseCostItem(const char *begin, const char *end)
{
QTC_ASSERT(currentFunction, return);
bool ok;
const char *current = begin;
CostItem *costItem = new CostItem(data);
QTC_ASSERT(currentDifferingFile == -1 || currentDifferingFile != currentFunction->fileId(), return);
costItem->setDifferingFile(currentDifferingFile);
FunctionCall *call = 0;
if (isParsingFunctionCall) {
call = new FunctionCall;
call->setCaller(currentFunction);
currentCallData.call = call;
costItem->setCall(call);
call->setCalls(callsCount);
callsCount = 0;
call->setDestinations(callDestinations);
callDestinations.clear();
if (currentCallData.calledFile == -1) {
currentCallData.calledFile = currentDifferingFile != -1 ? currentDifferingFile : lastFile;
//HACK: workaround issue where sometimes fi=??? lines are prepended to function calls
if (unknownFiles.contains(currentCallData.calledFile))
currentCallData.calledFile = lastFile;
}
if (currentCallData.calledObject == -1)
currentCallData.calledObject = lastObject;
if (currentCallData.calledFunction == currentFunction->nameId() &&
currentCallData.calledFile == currentFunction->fileId() &&
currentCallData.calledObject == currentFunction->objectId() )
{
// recursive call,
recursiveFunctions << currentFunction;
}
pendingCallees.append(currentCallData);
currentCallData = CallData();
}
const CostItem *lastCostItem = 0;
if (!currentFunction->costItems().isEmpty())
lastCostItem = currentFunction->costItems().last();
// parse positions ("where")
for (int i = 0; i < addressValuesCount; ++i) {
char c = *current;
// TODO overflow checks
quint64 position = 0;
if (c == '*') {
// leave the old value unchanged
current++;
QTC_ASSERT(lastCostItem, continue);
position = lastCostItem->position(i);
} else {
if (c == '+' || c == '-')
current++;
quint64 addr = parseAddr(&current, end, &ok);
if (!ok)
break; /// TODO: error reporting
if (c == '+') {
QTC_ASSERT(lastCostItem, continue);
position = lastCostItem->position(i) + addr;
} else if (c == '-') {
QTC_ASSERT(lastCostItem, continue);
position = lastCostItem->position(i) - addr;
} else
position = addr;
}
costItem->setPosition(i, position);
skipSpace(&current, end);
}
// parse events ("what")
for (int i = 0; i < costValuesCount; ++i) {
quint64 parsedCost = parseDecimal(&current, end, &ok);
if (!ok)
break; /// TODO: error reporting
costItem->setCost(i, parsedCost);
skipSpace(&current, end);
}
if (call) {
call->setCosts(costItem->costs());
}
currentFunction->addCostItem(costItem);
}
void Parser::Private::parseSourceFile(const char *begin, const char *end)
{
NamePair name = parseName(begin, end);
if (!name.second.isEmpty()) {
data->addCompressedFile(name.second, name.first);
if (name.second == QLatin1String("???"))
unknownFiles << name.first;
}
lastFile = name.first;
currentDifferingFile = -1;
}
void Parser::Private::parseFunction(const char *begin, const char *end)
{
currentFunction = new Function(data);
currentFunction->setFile(lastFile);
currentFunction->setObject(lastObject);
data->addFunction(currentFunction);
NamePair name = parseName(begin, end);
if (!name.second.isEmpty())
data->addCompressedFunction(name.second, name.first);
currentFunction->setName(name.first);
}
void Parser::Private::parseDifferingSourceFile(const char *begin, const char *end)
{
NamePair name = parseName(begin, end);
if (!name.second.isEmpty()) {
data->addCompressedFile(name.second, name.first);
if (name.second == QLatin1String("???"))
unknownFiles << name.first;
}
if (name.first == currentFunction->fileId())
currentDifferingFile = -1;
else
currentDifferingFile = name.first;
}
void Parser::Private::parseObjectFile(const char *begin, const char *end)
{
NamePair name = parseName(begin, end);
if (!name.second.isEmpty())
data->addCompressedObject(name.second, name.first);
lastObject = name.first;
}
void Parser::Private::parseCalls(const char *begin, const char *end)
{
const char *current = begin;
bool ok;
callsCount = parseDecimal(&current, end, &ok);
skipSpace(&current, end);
callDestinations.fill(0, addressValuesCount);
for(int i = 0; i < addressValuesCount; ++i) {
callDestinations[i] = parseAddr(&current, end, &ok);
if (!ok)
break; // TODO error handling?
skipSpace(&current, end);
}
isParsingFunctionCall = true;
}
void Parser::Private::parseCalledFunction(const char *begin, const char *end)
{
NamePair name = parseName(begin, end);
if (!name.second.isEmpty())
data->addCompressedFunction(name.second, name.first);
currentCallData.calledFunction = name.first;
}
void Parser::Private::parseCalledSourceFile(const char *begin, const char *end)
{
NamePair name = parseName(begin, end);
if (!name.second.isEmpty()) {
data->addCompressedFile(name.second, name.first);
if (name.second == QLatin1String("???"))
unknownFiles << name.first;
}
currentCallData.calledFile = name.first;
}
void Parser::Private::parseCalledObjectFile(const char *begin, const char *end)
{
NamePair name = parseName(begin, end);
if (!name.second.isEmpty())
data->addCompressedObject(name.second, name.first);
currentCallData.calledObject = name.first;
}
//BEGIN Parser
void Parser::parse(QIODevice *device)
{
d->parse(device);
}
Parser::Parser(QObject *parent)
: QObject(parent),
d(new Private(this))
{
}
Parser::~Parser()
{
delete d;
}
ParseData *Parser::takeData()
{
ParseData *data = d->data;
d->data = 0;
return data;
}
} //Callgrind
} //Valgrind