Revision control
Copy as Markdown
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "tracepointgen.h"
#include "parser.h"
#include <qtextstream.h>
#include <qregularexpression.h>
#include <qfileinfo.h>
static void removeOffsetRange(qsizetype begin, qsizetype end, QList<LineNumber> &offsets)
{
qsizetype count = end - begin;
qsizetype i = 0;
DEBUGPRINTF2(printf("tracepointgen: removeOffsetRange: %llu %llu\n", begin, end));
while (i < offsets.size()) {
LineNumber &cur = offsets[i];
if (begin > cur.end) {
i++;
} else if (begin >= cur.begin && begin <= cur.end) {
cur.end = begin;
i++;
} else if (begin < cur.begin && end > cur.end) {
offsets.remove(i);
DEBUGPRINTF2(printf("tracepointgen: removeOffsetRange: %llu, %llu, %d\n", cur.begin, cur.end, cur.line));
} else if (end >= cur.begin && end <= cur.end) {
cur.begin = begin;
cur.end -= count;
i++;
} else if (end < cur.begin) {
cur.begin -= count;
cur.end -= count;
i++;
}
}
}
static bool findSpaceRange(const QString &data, qsizetype &offset, qsizetype &end) {
qsizetype cur = data.indexOf(QLatin1Char(' '), offset);
if (cur >= 0) {
qsizetype i = cur + 1;
while (data.constData()[i] == QLatin1Char(' ')) i++;
if (i - cur > 1) {
offset = cur;
end = i - 1;
return true;
}
cur = data.indexOf(QLatin1Char(' '), cur + 1);
}
return false;
}
static void simplifyData(QString &data, QList<LineNumber> &offsets)
{
qsizetype offset = data.indexOf(QStringLiteral("//"));
while (offset >= 0) {
qsizetype endOfLine = data.indexOf(QLatin1Char('\n'), offset);
if (endOfLine == -1)
endOfLine = data.length();
removeOffsetRange(offset, endOfLine, offsets);
data.remove(offset, endOfLine - offset);
offset = data.indexOf(QStringLiteral("//"), offset);
}
offset = data.indexOf(QStringLiteral("/*"));
while (offset >= 0) {
qsizetype endOfComment = data.indexOf(QStringLiteral("*/"), offset);
if (endOfComment == -1)
break;
removeOffsetRange(offset, endOfComment + 2, offsets);
data.remove(offset, endOfComment - offset + 2);
offset = data.indexOf(QStringLiteral("/*"), offset);
}
offset = 0;
qsizetype end = 0;
data.replace(QLatin1Char('\n'), QLatin1Char(' '));
while (findSpaceRange(data, offset, end)) {
removeOffsetRange(offset, end, offsets);
data.remove(offset, end - offset);
}
}
static void simplifyData(QString &data)
{
qsizetype offset = data.indexOf(QStringLiteral("//"));
while (offset >= 0) {
qsizetype endOfLine = data.indexOf(QLatin1Char('\n'), offset);
if (endOfLine == -1)
endOfLine = data.length();
data.remove(offset, endOfLine - offset);
offset = data.indexOf(QStringLiteral("//"), offset);
}
offset = data.indexOf(QStringLiteral("/*"));
while (offset >= 0) {
qsizetype endOfComment = data.indexOf(QStringLiteral("*/"), offset);
if (endOfComment == -1)
break;
data.remove(offset, endOfComment - offset + 2);
offset = data.indexOf(QStringLiteral("/*"), offset);
}
offset = 0;
qsizetype end = 0;
while (findSpaceRange(data, offset, end))
data.remove(offset, end - offset);
}
static QString preprocessMetadata(const QString &in)
{
DEBUGPRINTF(printf("in: %s\n", qPrintable(in)));
QList<QString> lines = in.split(QLatin1Char('\\'));
QString out;
for (int i = 0; i < lines.size(); i++) {
QString l = lines.at(i).simplified();
DEBUGPRINTF(printf("line: %s\n", qPrintable(l)));
if (l.length() < 2)
continue;
if (l.startsWith(QStringLiteral("\"")))
l = l.right(l.length() - 1);
if (l.endsWith(QStringLiteral("\"")))
l = l.left(l.length() - 1);
l = l.simplified();
if (l.length() > 1) {
if (out.size() > 0)
out.append(QLatin1Char('\n'));
out.append(l);
}
}
DEBUGPRINTF(printf("out: %s\n", qPrintable(out)));
return out;
}
int Parser::lineNumber(qsizetype offset) const
{
DEBUGPRINTF(printf("tracepointgen: lineNumber: offset %llu, line count: %llu\n", offset , m_offsets.size()));
for (const auto line : m_offsets) {
DEBUGPRINTF(printf("tracepointgen: lineNumber: %llu %llu %d\n", line.begin, line.end, line.line));
if (offset >= line.begin && offset <= line.end)
return line.line;
}
return 0;
}
void Parser::parseParamReplace(const QString &data, qsizetype offset, const QString &name)
{
Replace rep;
qsizetype beginBrace = data.indexOf(QLatin1Char('('), offset);
qsizetype endBrace = data.indexOf(QLatin1Char(')'), beginBrace);
QString params = data.mid(beginBrace + 1, endBrace - beginBrace -1);
int punc = params.indexOf(QLatin1Char(','));
if (punc < 0)
panic("Syntax error in Q_TRACE_PARAM_REPLACE at file %s, line %llu", qPrintable(name), lineNumber(offset));
rep.in = params.left(punc).simplified();
rep.out = params.right(params.length() - punc - 1).simplified();
if (rep.in.endsWith(QLatin1Char('*')) || rep.out.endsWith(QLatin1Char(']')))
rep.out.append(QLatin1Char(' '));
DEBUGPRINTF(printf("tracepointgen: replace: %s with %s\n", qPrintable(rep.in), qPrintable(rep.out)));
m_replaces.push_back(rep);
}
void Parser::parseInstrument(const QString &data, qsizetype offset)
{
qsizetype beginOfProvider = data.indexOf(QLatin1Char('('), offset);
qsizetype endOfProvider = data.indexOf(QLatin1Char(')'), beginOfProvider);
Function func;
QString provider = data.mid(beginOfProvider + 1, endOfProvider - beginOfProvider - 1).simplified();
if (provider != m_provider)
return;
qsizetype classMarker = data.indexOf(QStringLiteral("::"), endOfProvider);
qsizetype beginOfFunctionMarker = data.indexOf(QLatin1Char('{'), classMarker);
QString begin = data.mid(endOfProvider + 1, classMarker - endOfProvider - 1);
QString end = data.mid(classMarker + 2, beginOfFunctionMarker - classMarker - 2);
int spaceIndex = begin.lastIndexOf(QLatin1Char(' '));
if (spaceIndex == -1)
func.className = begin;
else
func.className = begin.mid(spaceIndex + 1, begin.length() - spaceIndex - 1);
qsizetype braceIndex = end.indexOf(QLatin1Char('('));
spaceIndex = end.indexOf(QLatin1Char(' '));
if (spaceIndex < braceIndex)
func.functionName = end.left(spaceIndex).simplified();
else
func.functionName = end.left(braceIndex).simplified();
qsizetype lastBraceIndex = end.lastIndexOf(QLatin1Char(')'));
func.functionParameters = end.mid(braceIndex + 1, lastBraceIndex - braceIndex - 1).simplified();
DEBUGPRINTF(printf("tracepointgen: %s(%s)\n", qPrintable(func.functionName), qPrintable(func.functionParameters)));
m_functions.push_back(func);
}
void Parser::parsePoint(const QString &data, qsizetype offset)
{
qsizetype beginOfProvider = data.indexOf(QLatin1Char('('), offset);
qsizetype endOfProvider = data.indexOf(QLatin1Char(','), beginOfProvider);
Point point;
QString provider = data.mid(beginOfProvider + 1, endOfProvider - beginOfProvider - 1).simplified();
if (provider != m_provider)
return;
qsizetype endOfPoint = data.indexOf(QLatin1Char(','), endOfProvider + 1);
qsizetype endOfPoint2 = data.indexOf(QLatin1Char(')'), endOfProvider + 1);
bool params = true;
if (endOfPoint == -1 || endOfPoint2 < endOfPoint) {
endOfPoint = endOfPoint2;
params = false;
}
point.name = data.mid(endOfProvider + 1, endOfPoint - endOfProvider - 1).simplified();
if (params) {
int endOfParams = data.indexOf(QLatin1Char(')'), endOfPoint);
point.parameters = data.mid(endOfPoint + 1, endOfParams - endOfPoint - 1).simplified();
}
DEBUGPRINTF(printf("tracepointgen: %s(%s)\n", qPrintable(point.name), qPrintable(point.parameters)));
m_points.push_back(point);
}
void Parser::parsePrefix(const QString &data, qsizetype offset)
{
qsizetype beginOfProvider = data.indexOf(QLatin1Char('('), offset);
qsizetype endOfProvider = data.indexOf(QLatin1Char(','), beginOfProvider);
QString prefix;
QString provider = data.mid(beginOfProvider + 1, endOfProvider - beginOfProvider - 1).simplified();
if (provider != m_provider)
return;
qsizetype endOfPoint = data.indexOf(QLatin1Char(')'), endOfProvider + 1);
prefix = data.mid(endOfProvider + 1, endOfPoint - endOfProvider - 1).simplified();
DEBUGPRINTF(printf("tracepointgen: prefix: %s\n", qPrintable(prefix)));
if (!m_prefixes.contains(prefix))
m_prefixes.push_back(preprocessMetadata(prefix));
}
QStringList Parser::findEnumValues(const QString &name, const QStringList &includes)
{
QStringList split = name.split(QStringLiteral("::"));
QString enumName = split.last();
DEBUGPRINTF(printf("searching for %s\n", qPrintable(name)));
QStringList ret;
for (const QString &filename : includes) {
QFile input(filename);
if (!input.open(QIODevice::ReadOnly | QIODevice::Text)) {
DEBUGPRINTF(printf("Cannot open '%s' for reading: %s\n",
qPrintable(filename), qPrintable(input.errorString())));
return ret;
}
QString data;
QTextStream stream(&input);
while (!stream.atEnd()) {
QString line = stream.readLine().trimmed();
data += line + QLatin1Char('\n');
}
simplifyData(data);
int pos = 0;
bool valid = true;
for (int i = 0; i < split.size() - 1; i++) {
QRegularExpression macro(QStringLiteral("(struct|class|namespace) +([A-Za-z0-9_]*)? +([A-Za-z0-9]*;?)"));
QRegularExpressionMatchIterator m = macro.globalMatch(data);
bool found = false;
while (m.hasNext() && !found) {
QRegularExpressionMatch match = m.next();
QString n = match.captured(2);
if (!n.endsWith(QLatin1Char(';')) && n == split[i] && match.capturedStart(2) > pos) {
pos = match.capturedStart(2);
found = true;
break;
}
if (match.hasCaptured(3)) {
n = match.captured(3);
if (!n.endsWith(QLatin1Char(';')) && n == split[i] && match.capturedStart(3) > pos) {
pos = match.capturedStart(3);
found = true;
break;
}
}
}
if (!found) {
valid = false;
break;
}
}
if (valid) {
QRegularExpression macro(QStringLiteral("enum\\s+(?:struct|class)?\\s*+([A-Za-z0-9_]*)"));
QRegularExpressionMatchIterator m = macro.globalMatch(data);
while (m.hasNext()) {
QRegularExpressionMatch match = m.next();
if (match.capturedStart() < pos)
continue;
QString n = match.captured(1);
if (n == enumName) {
DEBUGPRINTF(printf("Found enum: %s\n", qPrintable(n)));
int begin = data.indexOf(QLatin1Char('{'), match.capturedEnd());
int end = data.indexOf(QLatin1Char('}'), begin);
QString block = data.mid(begin + 1, end - begin - 1);
const QStringList enums = block.split(QLatin1Char('\n'));
for (const auto &e : enums) {
const auto trimmed = e.trimmed();
if (!trimmed.isEmpty() && !trimmed.startsWith(QLatin1Char('#')))
ret << trimmed;
}
return ret;
}
}
}
}
return ret;
}
struct EnumNameValue
{
QString name;
QString valueStr;
int value;
};
static QList<EnumNameValue> enumsToValues(const QStringList &values)
{
int cur = 0;
QList<EnumNameValue> ret;
for (const QString &value : values) {
EnumNameValue r;
if (value.contains(QLatin1Char('='))) {
size_t offset = value.indexOf(QLatin1Char('='));
r.name = value.left(offset).trimmed();
QString val = value.right(value.length() - offset - 1).trimmed();
if (val.endsWith(QLatin1Char(',')))
val = val.left(val.length() - 1);
bool valid = false;
int integer = val.toInt(&valid);
if (!valid)
integer = val.toInt(&valid, 16);
if (valid) {
cur = r.value = integer;
ret << r;
} else {
auto iter = std::find_if(ret.begin(), ret.end(), [&val](const EnumNameValue &elem){
return elem.name == val;
});
if (iter != ret.end()) {
cur = r.value = iter->value;
ret << r;
} else {
DEBUGPRINTF(printf("Invalid value: %s %s\n", qPrintable(r.name), qPrintable(value)));
}
}
} else {
if (value.endsWith(QLatin1Char(',')))
r.name = value.left(value.length() - 1);
else
r.name = value;
r.value = ++cur;
ret << r;
}
}
return ret;
}
void Parser::parseMetadata(const QString &data, qsizetype offset, const QStringList &includes)
{
qsizetype beginOfProvider = data.indexOf(QLatin1Char('('), offset);
qsizetype endOfProvider = data.indexOf(QLatin1Char(','), beginOfProvider);
QString metadata;
QString provider = data.mid(beginOfProvider + 1, endOfProvider - beginOfProvider - 1).simplified();
if (provider != m_provider)
return;
qsizetype endOfPoint = data.indexOf(QLatin1Char(')'), endOfProvider + 1);
metadata = data.mid(endOfProvider + 1, endOfPoint - endOfProvider - 1).simplified();
DEBUGPRINTF(printf("tracepointgen: metadata: %s", qPrintable(metadata)));
QString preprocessed = preprocessMetadata(metadata);
DEBUGPRINTF2(printf("preprocessed %s\n", qPrintable(preprocessed)));
QRegularExpression macro(QStringLiteral("([A-Z]*) ?{ ?([A-Za-z0-9=_,. ]*) ?} ?([A-Za-z0-9_:]*) ?;"));
QRegularExpressionMatchIterator i = macro.globalMatch(preprocessed);
qsizetype prev = 0;
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QString values = match.captured(2).trimmed();
int cur = match.capturedStart();
if (cur > prev)
m_metadata.append(preprocessed.mid(prev, cur - prev));
prev = match.capturedEnd() + 1;
DEBUGPRINTF2(printf("values: %s\n", qPrintable(values)));
if (values.isEmpty() || values.startsWith(QStringLiteral("AUTO"))) {
values.replace(QLatin1Char('\n'), QLatin1Char(' '));
QStringList ranges;
if (values.contains(QStringLiteral("RANGE"))) {
QRegularExpression rangeMacro(QStringLiteral("RANGE +([A-Za-z0-9_]*) +... +([A-Za-z0-9_]*)"));
QRegularExpressionMatchIterator r = rangeMacro.globalMatch(values);
while (r.hasNext()) {
QRegularExpressionMatch rm = r.next();
ranges << rm.captured(1);
ranges << rm.captured(2);
DEBUGPRINTF2(printf("range: %s ... %s\n", qPrintable(rm.captured(1)), qPrintable(rm.captured(2))));
}
}
const auto enumOrFlag = match.captured(1);
const auto name = match.captured(3);
const bool flags = enumOrFlag == QStringLiteral("FLAGS");
QStringList values = findEnumValues(name, includes);
if (values.isEmpty()) {
if (flags && name.endsWith(QLatin1Char('s')))
values = findEnumValues(name.left(name.length() - 1), includes);
if (values.isEmpty())
panic("Unable to find values for %s\n", qPrintable(name));
}
if (!values.isEmpty()) {
auto moreValues = enumsToValues(values);
if (ranges.size()) {
for (int i = 0; i < ranges.size() / 2; i++) {
bool rangeFound = false;
for (auto &v : moreValues) {
if (v.name == ranges[2 * i]) {
rangeFound = true;
QString rangeEnd = ranges[2 * i + 1];
auto iter = std::find_if(moreValues.begin(), moreValues.end(), [&rangeEnd](const EnumNameValue &elem){
return elem.name == rangeEnd;
});
if (iter != moreValues.end())
v.valueStr = QStringLiteral("RANGE(%1, %2 ... %3)").arg(v.name).arg(v.value).arg(iter->value);
else
panic("Unable to find range end: %s\n", qPrintable(rangeEnd));
break;
}
}
if (rangeFound == false)
panic("Unable to find range begin: %s\n", qPrintable(ranges[2 * i]));
}
}
std::sort(moreValues.begin(), moreValues.end(), [](const EnumNameValue &a, const EnumNameValue &b) {
return a.value < b.value;
});
values.clear();
int prevValue = std::as_const(moreValues).front().value;
for (const auto &v : std::as_const(moreValues)) {
QString a;
if (v.valueStr.isNull()) {
if (v.value == prevValue + 1 && !flags)
a = v.name;
else
a = QStringLiteral("%1 = %2").arg(v.name).arg(v.value);
prevValue = v.value;
} else {
a = v.valueStr;
}
values << a;
}
metadata = QStringLiteral("%1 {\n %2 \n} %3;").arg(enumOrFlag).arg(values.join(QStringLiteral(",\n"))).arg(name);
if (!m_metadata.contains(metadata))
m_metadata.append(metadata);
}
} else {
if (!m_metadata.contains(match.captured()))
m_metadata.append(match.captured());
}
}
if (prev < preprocessed.length())
m_metadata.append(preprocessed.mid(prev, preprocessed.length() - prev));
}
QString Parser::resolveInclude(const QString &filename)
{
QFileInfo info(filename);
if (info.exists())
return info.absoluteFilePath();
for (const QString &sp : std::as_const(m_includeDirs)) {
info = QFileInfo(sp + QLatin1Char('/') + filename);
if (info.exists())
return info.absoluteFilePath();
}
return {};
}
void Parser::addIncludesRecursive(const QString &filename, QList<QString> &includes)
{
QFileInfo info(filename);
DEBUGPRINTF(printf("check include: %s\n", qPrintable(filename)));
QFile input(filename);
if (!input.open(QIODevice::ReadOnly | QIODevice::Text)) {
DEBUGPRINTF(printf("Cannot open '%s' for reading: %s\n",
qPrintable(filename), qPrintable(input.errorString())));
return;
}
QString data;
QTextStream stream(&input);
while (!stream.atEnd()) {
QString line = stream.readLine().trimmed();
data += line + QLatin1Char(QLatin1Char('\n'));
}
QRegularExpression includeMacro(QStringLiteral("#include [\"<]([A-Za-z0-9_.:/-]*.h)[\">]"));
QRegularExpressionMatchIterator i = includeMacro.globalMatch(data);
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QString filename = match.captured(1);
QString rinc = filename;
if (filename.startsWith(QStringLiteral("../"))) {
QFileInfo info2(info.absolutePath() + QLatin1Char('/') + filename);
if (!info2.exists()) {
DEBUGPRINTF(printf("unable to find %s\n", qPrintable(filename)));
continue;
}
rinc = info2.absoluteFilePath();
filename = info2.fileName();
}
// only search possible qt headers
if (QFileInfo(filename).baseName().startsWith(QLatin1Char('q'), Qt::CaseInsensitive)) {
QString resolved = resolveInclude(rinc);
if (!resolved.isEmpty() && !includes.contains(resolved)) {
includes.push_back(resolved);
addIncludesRecursive(resolved, includes);
}
}
}
}
void Parser::parse(QIODevice &input, const QString &name)
{
QString data;
QTextStream stream(&input);
int lineNumber = 1;
qsizetype prev = 0;
while (!stream.atEnd()) {
QString line = stream.readLine().trimmed();
m_offsets.push_back({prev, prev + line.length(), lineNumber++});
prev += line.length() + 1;
data += line + QLatin1Char(QLatin1Char('\n'));
}
simplifyData(data, m_offsets);
QStringList includes;
QRegularExpression includeMacro(QStringLiteral("#include [\"<]([A-Za-z0-9_.:/-]*.h)[\">]"));
QRegularExpressionMatchIterator i = includeMacro.globalMatch(data);
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
const QString filename = match.captured(1);
// only search possible qt headers
if (filename.startsWith(QLatin1Char('q'), Qt::CaseInsensitive)) {
const QString resolved = resolveInclude(filename);
if (!resolved.isEmpty() && !includes.contains(resolved)) {
includes.push_back(resolved);
addIncludesRecursive(resolved, includes);
}
}
}
QRegularExpression traceMacro(QStringLiteral("Q_TRACE_([A-Z_]*)"));
i = traceMacro.globalMatch(data);
while (i.hasNext()) {
QRegularExpressionMatch match = i.next();
QString macroType = match.captured(1);
if (macroType == QStringLiteral("PARAM_REPLACE"))
parseParamReplace(data, match.capturedEnd(), name);
else if (macroType == QStringLiteral("INSTRUMENT"))
parseInstrument(data, match.capturedEnd());
else if (macroType == QStringLiteral("POINT"))
parsePoint(data, match.capturedEnd());
else if (macroType == QStringLiteral("PREFIX"))
parsePrefix(data, match.capturedEnd());
else if (macroType == QStringLiteral("METADATA"))
parseMetadata(data, match.capturedEnd(), includes);
}
for (auto &func : m_functions) {
for (auto &rep : m_replaces)
func.functionParameters.replace(rep.in, rep.out);
}
}
void Parser::write(QIODevice &input) const
{
QTextStream out(&input);
if (m_prefixes.size() > 0) {
out << QStringLiteral("{\n");
for (const auto &prefix : m_prefixes)
out << prefix << "\n";
out << QStringLiteral("}\n");
}
for (const auto &m : m_metadata)
out << m << "\n";
for (const auto &func : m_functions) {
out << func.className << "_" << func.functionName << "_entry(" << func.functionParameters << ")\n";
out << func.className << "_" << func.functionName << "_exit()\n";
}
for (const auto &point : m_points)
out << point.name << "(" << point.parameters << ")\n";
}