// Copyright (C) 2017 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "qbic.h" #include "QtCore/qfile.h" #include "QtCore/qdebug.h" void QBic::addBlacklistedClass(const QString &wildcard) { blackList.append(QRegularExpression(QRegularExpression::anchoredPattern(QRegularExpression::wildcardToRegularExpression(wildcard)))); } void QBic::addBlacklistedClass(const QRegularExpression &expression) { blackList.append(expression); } void QBic::removeBlacklistedClass(const QString &wildcard) { blackList.removeAll(QRegularExpression(QRegularExpression::anchoredPattern(QRegularExpression::wildcardToRegularExpression(wildcard)))); } bool QBic::isBlacklisted(const QString &className) const { // all templates are blacklisted if (className.contains('<')) return true; for (int i = 0; i < blackList.size(); ++i) if (blackList[i].match(className).hasMatch()) return true; return false; } static bool qualifiedTailMatch(const QString &expectedTail, const QString &symbol) { if (symbol == expectedTail) return true; const QString tail = symbol.right(expectedTail.size() - 2); if (!tail.startsWith(QLatin1String("::"))) return false; return tail.endsWith(expectedTail); } static QString innerClassVTableSymbol(const QString &outerClass, const QString &innerClass) { return (QLatin1String("_ZTVN") + QString::number(outerClass.size()) + outerClass + QString::number(innerClass.size()) + innerClass + QLatin1Char('E')); } static QStringList nonVirtualThunkToDestructorSymbols(const QString &className) { const QString symbolTemplate = QString::fromLatin1("%1::_ZThn%2_N%3%4"); const QString candidates[] = { symbolTemplate.arg(className).arg(16).arg(className.size()).arg(className), symbolTemplate.arg(className).arg(32).arg(className.size()).arg(className), symbolTemplate.arg(className).arg(40).arg(className.size()).arg(className) }; QStringList result; for (int i = 0; i <= 1; ++i) { const QString suffix = QString::fromLatin1("D%1Ev").arg(i); for (const QString &candidate : candidates) result << candidate + suffix; } return result; } static void parseClassName(const QString &mangledClassName, QString *className, QString *qualifiedClassName) { const QString outerClassCandidate = mangledClassName.section(QLatin1String("::"), 0, 0); const QString innerClassCandidate = mangledClassName.section(QLatin1String("::"), 1, 1); const QString innerClassVTableSymbolCandidate = innerClassVTableSymbol(outerClassCandidate, innerClassCandidate); const QString qualifiedInnerClassVTableSymbolCandidate = outerClassCandidate + QLatin1String("::") + innerClassCandidate + QLatin1String("::") + innerClassVTableSymbolCandidate; if (mangledClassName == qualifiedInnerClassVTableSymbolCandidate) { *qualifiedClassName = outerClassCandidate + QLatin1String("::") + innerClassCandidate; *className = innerClassCandidate; } else { *qualifiedClassName = *className = outerClassCandidate; } } static bool matchDestructor(const QString &mangledClassName, const QString &symbol) { QString className; QString qualifiedClassName; parseClassName(mangledClassName, &className, &qualifiedClassName); const QString destructor = qualifiedClassName + QLatin1String("::~") + className; const QStringList nonVirtualThunkToDestructorSymbolCandidates = nonVirtualThunkToDestructorSymbols(className); if (qualifiedTailMatch(destructor, symbol)) return true; for (const QString &candidate : nonVirtualThunkToDestructorSymbolCandidates) { if (qualifiedTailMatch(candidate, symbol)) return true; } return false; } static QStringList normalizedVTable(const QStringList &entry) { QStringList normalized; // Extract the class name from lines like these: // QObject::_ZTV7QObject: 14u entries QString className = entry.at(1).section(QLatin1Char(' '), 0, 0); className.chop(1); for (int i = 2; i < entry.size(); ++i) { const QString line = entry.at(i).simplified(); bool isOk = false; int num = line.left(line.indexOf(QLatin1Char(' '))).toInt(&isOk); if (!isOk) { qWarning("unrecognized line: %s", qPrintable(line)); continue; } QString sym = line.mid(line.indexOf(QLatin1Char(' ')) + 1); if (sym.startsWith(QLatin1Char('('))) { if (sym.endsWith(QLatin1Char(')'))) { sym = sym.mid(sym.lastIndexOf('(') + 1); sym.chop(1); } else { sym = sym.mid(sym.lastIndexOf(QLatin1Char(')')) + 1); } } else { sym = sym.left(sym.indexOf(QLatin1Char('('))); } if (sym.startsWith(QLatin1String("& "))) sym.remove(1, 1); // Clear the entry for destructors, as starting with 4.9, gcc intentionally stores null // pointers in the vtable for the destructors of abstract classes. if (matchDestructor(className, sym)) sym = QLatin1String("0"); if (sym.startsWith(QLatin1String("-0")) || sym.startsWith(QLatin1String("0"))) { if (sym.endsWith('u')) sym.chop(1); bool isOk = false; qint64 num = sym.toLongLong(&isOk, 16); if (!isOk) { qWarning("unrecognized token: %s", qPrintable(sym)); continue; } if (sizeof(void*) == 4) sym = QString::number(int(num)); else sym = QString::number(num); } normalized << QString::number(num) + QLatin1Char(' ') + sym; } return normalized; } QBic::Info QBic::parseOutput(const QByteArray &ba) const { Info info; const QStringList source = QString::fromLatin1(ba).split("\n\n"); for (const QString &str : source) { QStringList entry = str.split('\n'); if (entry.size() < 2) continue; if (entry.at(0).startsWith("Class ")) { const QString className = entry.at(0).mid(6); if (isBlacklisted(className)) continue; QRegularExpression rx("size=(\\d+)"); QRegularExpressionMatch match = rx.match(entry.at(1)); if (!match.hasMatch()) { qWarning("Could not parse class information for className %s", className.toLatin1().constData()); continue; } info.classSizes[className] = match.captured(1).toInt(); } else if (entry.at(0).startsWith("Vtable for ")) { const QString className = entry.at(0).mid(11); if (isBlacklisted(className)) continue; info.classVTables[className] = normalizedVTable(entry); } } return info; } QBic::Info QBic::parseFile(const QString &fileName) const { QFile f(fileName); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return Info(); QByteArray ba = f.readAll(); f.close(); return parseOutput(ba); } enum VTableDiffResult { Match, Mismatch, Reimp }; static VTableDiffResult diffVTableEntry(const QString &v1, const QString &v2) { if (v1 == v2) return Match; if (v2.endsWith(QLatin1String("__cxa_pure_virtual"))) return Reimp; if (!v1.contains(QLatin1String("::")) || !v2.contains(QLatin1String("::"))) return Mismatch; const QString sym1 = v1.mid(v1.lastIndexOf(QLatin1String("::")) + 2); const QString sym2 = v2.mid(v2.lastIndexOf(QLatin1String("::")) + 2); if (sym1 == sym2) return Reimp; return Mismatch; } QBic::VTableDiff QBic::diffVTables(const Info &oldLib, const Info &newLib) const { VTableDiff result; for (QHash::const_iterator it = newLib.classVTables.constBegin(); it != newLib.classVTables.constEnd(); ++it) { if (!oldLib.classVTables.contains(it.key())) { result.addedVTables.append(it.key()); continue; } const QStringList oldVTable = oldLib.classVTables.value(it.key()); const QStringList vTable = it.value(); if (vTable.size() != oldVTable.size()) { result.modifiedVTables.append(QPair(it.key(), QLatin1String("size mismatch"))); continue; } for (int i = 0; i < vTable.size(); ++i) { VTableDiffResult diffResult = diffVTableEntry(vTable.at(i), oldVTable.at(i)); switch (diffResult) { case Match: // do nothing break; case Mismatch: result.modifiedVTables.append(QPair(oldVTable.at(i), vTable.at(i))); break; case Reimp: result.reimpMethods.append(QPair(oldVTable.at(i), vTable.at(i))); break; } } } for (QHash::const_iterator it = oldLib.classVTables.constBegin(); it != oldLib.classVTables.constEnd(); ++it) { if (!newLib.classVTables.contains(it.key())) result.removedVTables.append(it.key()); } return result; } QBic::SizeDiff QBic::diffSizes(const Info &oldLib, const Info &newLib) const { QBic::SizeDiff result; for (QHash::const_iterator it = newLib.classSizes.constBegin(); it != newLib.classSizes.constEnd(); ++it) { if (!oldLib.classSizes.contains(it.key())) { result.added.append(it.key()); continue; } int oldSize = oldLib.classSizes.value(it.key()); int newSize = it.value(); if (oldSize != newSize) result.mismatch.append(it.key()); } for (QHash::const_iterator it = oldLib.classSizes.constBegin(); it != oldLib.classSizes.constEnd(); ++it) { if (!newLib.classSizes.contains(it.key())) result.removed.append(it.key()); } return result; }