Revision control

Copy as Markdown

// 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<QString, QStringList>::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<QString, QString>(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<QString, QString>(oldVTable.at(i),
vTable.at(i)));
break;
case Reimp:
result.reimpMethods.append(QPair<QString, QString>(oldVTable.at(i), vTable.at(i)));
break;
}
}
}
for (QHash<QString, QStringList>::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<QString, int>::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<QString, int>::const_iterator it = oldLib.classSizes.constBegin();
it != oldLib.classSizes.constEnd(); ++it) {
if (!newLib.classSizes.contains(it.key()))
result.removed.append(it.key());
}
return result;
}