// Copyright (C) 2017 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author Rafael Roquetto // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "provider.h" #include "panic.h" #include #include #include #include #include #include using namespace Qt::StringLiterals; #ifdef TRACEGEN_DEBUG #include static void dumpTracepoint(const Tracepoint &t) { qDebug() << "=== BEGIN TRACEPOINT ==="; qDebug() << "name:" << t.name; qDebug() << "ARGS\n"; int j = 0; for (auto i = t.args.constBegin(); i != t.args.constEnd(); ++i) { qDebug() << "ARG[" << j << "] type:" << i->type; qDebug() << "ARG[" << j << "] name:" << i->name; qDebug() << "ARG[" << j << "] arrayLen:" << i->arrayLen; ++j; } qDebug() << "\nFIELDS\n"; j = 0; for (auto i = t.fields.constBegin(); i != t.fields.constEnd(); ++i) { qDebug() << "FIELD[" << j << "] backend_type" << static_cast(i->backendType); qDebug() << "FIELD[" << j << "] param_type" << i->paramType; qDebug() << "FIELD[" << j << "] name" << i->name; qDebug() << "FIELD[" << j << "] arrayLen" << i->arrayLen; qDebug() << "FIELD[" << j << "] seqLen" << i->seqLen; ++j; } qDebug() << "=== END TRACEPOINT ===\n"; } #endif static inline int arrayLength(const QString &rawType) { /* matches the length of an ordinary array type * Ex: foo[10] yields '10' */ static const QRegularExpression rx(QStringLiteral("\\[([0-9]+)\\]")); auto match = rx.match(rawType); if (!match.hasMatch()) return 0; return match.captured(1).toInt(); } static inline QString sequenceLength(const QString &rawType) { /* matches the identifier pointing to length of a CTF sequence type, which is * a dynamic sized array. * Ex: in qcoreapplication_foo(const char[len], some_string, unsigned int, * len) * it will match the 'len' part of 'const char[len]') */ static const QRegularExpression rx(QStringLiteral("\\[([A-Za-z_][A-Za-z_0-9]*)\\]")); auto match = rx.match(rawType); if (!match.hasMatch()) return QString(); return match.captured(1); } static QString decayArrayToPointer(QString type) { /* decays an array to a pointer, i.e., if 'type' holds int[10], * this function returns 'int *' */ static QRegularExpression rx(QStringLiteral("\\[([^\\]]+)\\]")); return type.replace(rx, QStringLiteral("*")); } static QString removeBraces(QString type) { static const QRegularExpression rx(QStringLiteral("\\[.*\\]")); return type.remove(rx); } #define TYPEDATA_ENTRY(type, backendType) \ { QT_STRINGIFY(type), backendType } static Tracepoint::Field::Type backendType(QString rawType) { static const struct TypeData { const char *type; Tracepoint::Field::Type backendType; } typeTable[] = { TYPEDATA_ENTRY(short_int, Tracepoint::Field::Integer), TYPEDATA_ENTRY(signed_short, Tracepoint::Field::Integer), TYPEDATA_ENTRY(signed_short_int, Tracepoint::Field::Integer), TYPEDATA_ENTRY(unsigned_short, Tracepoint::Field::Integer), TYPEDATA_ENTRY(unsigned_short_int, Tracepoint::Field::Integer), TYPEDATA_ENTRY(unsigned_int, Tracepoint::Field::Integer), TYPEDATA_ENTRY(signed_int, Tracepoint::Field::Integer), TYPEDATA_ENTRY(long_int, Tracepoint::Field::Integer), TYPEDATA_ENTRY(signed_long, Tracepoint::Field::Integer), TYPEDATA_ENTRY(signed_long_int, Tracepoint::Field::Integer), TYPEDATA_ENTRY(unsigned_long, Tracepoint::Field::Integer), TYPEDATA_ENTRY(unsigned_long_int, Tracepoint::Field::Integer), TYPEDATA_ENTRY(long_long, Tracepoint::Field::Integer), TYPEDATA_ENTRY(long_long_int, Tracepoint::Field::Integer), TYPEDATA_ENTRY(signed_long_long, Tracepoint::Field::Integer), TYPEDATA_ENTRY(signed_long_long_int, Tracepoint::Field::Integer), TYPEDATA_ENTRY(unsigned_long_long, Tracepoint::Field::Integer), TYPEDATA_ENTRY(bool, Tracepoint::Field::Boolean), TYPEDATA_ENTRY(int, Tracepoint::Field::Integer), TYPEDATA_ENTRY(signed, Tracepoint::Field::Integer), TYPEDATA_ENTRY(unsigned, Tracepoint::Field::Integer), TYPEDATA_ENTRY(long, Tracepoint::Field::Integer), TYPEDATA_ENTRY(qint64, Tracepoint::Field::Integer), TYPEDATA_ENTRY(char, Tracepoint::Field::Integer), TYPEDATA_ENTRY(intptr_t, Tracepoint::Field::IntegerHex), TYPEDATA_ENTRY(uintptr_t, Tracepoint::Field::IntegerHex), TYPEDATA_ENTRY(std::intptr_t, Tracepoint::Field::IntegerHex), TYPEDATA_ENTRY(std::uintptr_t, Tracepoint::Field::IntegerHex), TYPEDATA_ENTRY(float, Tracepoint::Field::Float), TYPEDATA_ENTRY(double, Tracepoint::Field::Float), TYPEDATA_ENTRY(long double, Tracepoint::Field::Float), TYPEDATA_ENTRY(QString, Tracepoint::Field::QtString), TYPEDATA_ENTRY(QByteArray, Tracepoint::Field::QtByteArray), TYPEDATA_ENTRY(QUrl, Tracepoint::Field::QtUrl), TYPEDATA_ENTRY(QRect, Tracepoint::Field::QtRect), TYPEDATA_ENTRY(QSize, Tracepoint::Field::QtSize), TYPEDATA_ENTRY(QRectF, Tracepoint::Field::QtRectF), TYPEDATA_ENTRY(QSizeF, Tracepoint::Field::QtSizeF) }; auto backendType = [](const QString &rawType) { static const size_t tableSize = sizeof (typeTable) / sizeof (typeTable[0]); for (size_t i = 0; i < tableSize; ++i) { if (rawType == QLatin1StringView(typeTable[i].type)) return typeTable[i]; } TypeData unknown = { nullptr, Tracepoint::Field::Unknown }; return unknown; }; int arrayLen = arrayLength(rawType); if (arrayLen > 0) rawType = removeBraces(rawType); if (!sequenceLength(rawType).isNull()) return Tracepoint::Field::Sequence; static const QRegularExpression constMatch(QStringLiteral("\\bconst\\b")); rawType.remove(constMatch); rawType.remove(u'&'); static const QRegularExpression ptrMatch(QStringLiteral("\\s*\\*\\s*")); rawType.replace(ptrMatch, QStringLiteral("_ptr")); rawType = rawType.trimmed(); rawType.replace(QStringLiteral(" "), QStringLiteral("_")); if (rawType == "char_ptr"_L1) return Tracepoint::Field::String; if (rawType.endsWith("_ptr"_L1)) return Tracepoint::Field::Pointer; TypeData d = backendType(rawType); return d.backendType; } static Tracepoint parseTracepoint(const Provider &provider, const QString &name, const QStringList &args, const QString &fileName, const int lineNumber) { Tracepoint t; t.name = name; auto findEnumeration = [](const QList &enums, const QString &name) { for (const auto &e : enums) { if (e.name == name) return e; } return TraceEnum(); }; auto findFlags = [](const QList &flags, const QString &name) { for (const auto &f : flags) { if (f.name == name) return f; } return TraceFlags(); }; if (args.isEmpty()) return t; auto i = args.constBegin(); auto end = args.constEnd(); int argc = 0; static const QRegularExpression rx(QStringLiteral("^(.*)\\b([A-Za-z_][A-Za-z0-9_]*)$")); while (i != end) { auto match = rx.match(*i); const QString type = match.captured(1).trimmed(); if (type.isNull()) { panic("Missing parameter type for argument %d of %s (%s:%d)", argc, qPrintable(name), qPrintable(fileName), lineNumber); } const QString name = match.captured(2).trimmed(); if (name.isNull()) { panic("Missing parameter name for argument %d of %s (%s:%d)", argc, qPrintable(name), qPrintable(fileName), lineNumber); } int arrayLen = arrayLength(type); Tracepoint::Argument a; a.arrayLen = arrayLen; a.name = name; a.type = decayArrayToPointer(type); t.args << std::move(a); Tracepoint::Field field; const TraceEnum &e = findEnumeration(provider.enumerations, type); const TraceFlags &f = findFlags(provider.flags, type); if (!e.name.isEmpty()) { field.backendType = Tracepoint::Field::EnumeratedType; field.enumValueSize = e.valueSize; } else if (!f.name.isEmpty()) { field.backendType = Tracepoint::Field::FlagType; } else { field.backendType = backendType(type); } field.paramType = removeBraces(type); field.name = name; field.arrayLen = arrayLen; field.seqLen = sequenceLength(type); t.fields << std::move(field); ++i; } return t; } static int minumumValueSize(int min, int max) { if (min < 0) { if (min >= std::numeric_limits::min() && max <= std::numeric_limits::max()) return 8; if (min >= std::numeric_limits::min() && max <= std::numeric_limits::max()) return 16; return 32; } if (max <= std::numeric_limits::max()) return 8; if (max <= std::numeric_limits::max()) return 16; return 32; } static bool isPow2OrZero(quint32 value) { return (value & (value - 1)) == 0; } static quint32 pow2Log2(quint32 v) { return 32 - qCountLeadingZeroBits(v); } Provider parseProvider(const QString &filename) { QFile f(filename); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) panic("Cannot open %s: %s", qPrintable(filename), qPrintable(f.errorString())); QTextStream s(&f); static const QRegularExpression tracedef(QStringLiteral("^([A-Za-z][A-Za-z0-9_]*)\\((.*)\\)$")); static const QRegularExpression enumenddef(QStringLiteral("^} ?([A-Za-z][A-Za-z0-9_:]*);")); static const QRegularExpression enumdef(QStringLiteral("^([A-Za-z][A-Za-z0-9_]*)( *= *([xabcdef0-9]*))?")); static const QRegularExpression rangedef(QStringLiteral("^RANGE\\(([A-Za-z][A-Za-z0-9_]*) ?, ?([0-9]*) ?... ?([0-9]*) ?\\)")); Provider provider; provider.name = QFileInfo(filename).baseName(); bool parsingPrefixText = false; bool parsingEnum = false; bool parsingFlags = false; TraceEnum currentEnum; TraceFlags currentFlags; int currentEnumValue = 0; int minEnumValue = std::numeric_limits::max(); int maxEnumValue = std::numeric_limits::min(); for (int lineNumber = 1; !s.atEnd(); ++lineNumber) { QString line = s.readLine().trimmed(); if (line == "{"_L1) { parsingPrefixText = true; continue; } else if (parsingPrefixText && line == "}"_L1) { parsingPrefixText = false; continue; } else if (parsingPrefixText) { provider.prefixText.append(line); continue; } else if (line == "ENUM {"_L1) { parsingEnum = true; continue; } else if (line == "FLAGS {"_L1) { parsingFlags = true; continue; } else if (line.startsWith("}"_L1) && (parsingEnum || parsingFlags)) { auto match = enumenddef.match(line); if (match.hasMatch()) { if (parsingEnum) { currentEnum.name = match.captured(1); currentEnum.valueSize = minumumValueSize(minEnumValue, maxEnumValue); provider.enumerations.push_back(currentEnum); currentEnum = TraceEnum(); parsingEnum = false; } else { currentFlags.name = match.captured(1); provider.flags.push_back(currentFlags); currentFlags = TraceFlags(); parsingFlags = false; } minEnumValue = std::numeric_limits::max(); maxEnumValue = std::numeric_limits::min(); } else { panic("Syntax error while processing '%s' line %d:\n" " '%s' end of enum/flags does not match", qPrintable(filename), lineNumber, qPrintable(line)); } continue; } if (line.isEmpty() || line.startsWith(u'#')) continue; if (parsingEnum || parsingFlags) { auto m = enumdef.match(line); if (parsingEnum && line.startsWith(QStringLiteral("RANGE"))) { auto m = rangedef.match(line); if (m.hasMatch()) { TraceEnum::EnumValue value; value.name = m.captured(1); value.value = m.captured(2).toInt(); value.range = m.captured(3).toInt(); currentEnumValue = value.range + 1; currentEnum.values.push_back(value); maxEnumValue = qMax(maxEnumValue, value.range); minEnumValue = qMin(minEnumValue, value.value); } } else if (m.hasMatch()) { if (m.hasCaptured(3)) { if (parsingEnum) { TraceEnum::EnumValue value; value.name = m.captured(1); value.value = m.captured(3).toInt(); value.range = 0; currentEnumValue = value.value + 1; currentEnum.values.push_back(value); maxEnumValue = qMax(maxEnumValue, value.value); minEnumValue = qMin(minEnumValue, value.value); } else { TraceFlags::FlagValue value; value.name = m.captured(1); if (m.captured(3).startsWith(QStringLiteral("0x"))) value.value = m.captured(3).toInt(nullptr, 16); else value.value = m.captured(3).toInt(); if (!isPow2OrZero(value.value)) { printf("Warning: '%s' line %d:\n" " '%s' flag value is not power of two.\n", qPrintable(filename), lineNumber, qPrintable(line)); } else { value.value = pow2Log2(value.value); currentFlags.values.push_back(value); } } } else { maxEnumValue = qMax(maxEnumValue, currentEnumValue); minEnumValue = qMin(minEnumValue, currentEnumValue); if (parsingEnum) { currentEnum.values.push_back({m.captured(0), currentEnumValue++, 0}); } else { panic("Syntax error while processing '%s' line %d:\n" " '%s' flags value not set", qPrintable(filename), lineNumber, qPrintable(line)); } } } else { panic("Syntax error while processing '%s' line %d:\n" " '%s' enum/flags does not match", qPrintable(filename), lineNumber, qPrintable(line)); } continue; } auto match = tracedef.match(line); if (match.hasMatch()) { const QString name = match.captured(1); const QString argsString = match.captured(2); const QStringList args = argsString.split(u',', Qt::SkipEmptyParts); provider.tracepoints << parseTracepoint(provider, name, args, filename, lineNumber); } else { panic("Syntax error while processing '%s' line %d:\n" " '%s' does not look like a tracepoint definition", qPrintable(filename), lineNumber, qPrintable(line)); } } if (parsingPrefixText) { panic("Syntax error while processing '%s': " "no closing brace found for prefix text block", qPrintable(filename)); } #ifdef TRACEGEN_DEBUG qDebug() << provider.prefixText; for (auto i = provider.tracepoints.constBegin(); i != provider.tracepoints.constEnd(); ++i) dumpTracepoint(*i); #endif return provider; }