From b5fd00955f7c76e9600c163fa401ef3f3e845d5f Mon Sep 17 00:00:00 2001
From: Oswald Buddenhagen <oswald.buddenhagen@nokia.com>
Date: Fri, 22 Jan 2010 12:26:14 +0100
Subject: [PATCH] get rid of lots of qfileinfo and qfile calls

partly by removing unnecessary calls, partly by providing minimalistic
reimplementations.
this gives a quite incredible performance boost ...
---
 src/plugins/qt4projectmanager/profilereader.h |  1 +
 src/shared/proparser/ioutils.cpp              | 88 +++++++++++++++++
 src/shared/proparser/ioutils.h                | 59 +++++++++++
 src/shared/proparser/profileevaluator.cpp     | 97 ++++++++++---------
 src/shared/proparser/profileevaluator.h       |  1 +
 src/shared/proparser/proitems.cpp             |  8 +-
 src/shared/proparser/proparser.pri            |  4 +-
 7 files changed, 207 insertions(+), 51 deletions(-)
 create mode 100644 src/shared/proparser/ioutils.cpp
 create mode 100644 src/shared/proparser/ioutils.h

diff --git a/src/plugins/qt4projectmanager/profilereader.h b/src/plugins/qt4projectmanager/profilereader.h
index f77df8d8900..ebbeb85aa07 100644
--- a/src/plugins/qt4projectmanager/profilereader.h
+++ b/src/plugins/qt4projectmanager/profilereader.h
@@ -47,6 +47,7 @@ public:
     ProFileReader(ProFileOption *option);
     ~ProFileReader();
 
+    // fileName is expected to be absolute and cleanPath()ed.
     bool readProFile(const QString &fileName);
     QList<ProFile*> includeFiles() const;
 
diff --git a/src/shared/proparser/ioutils.cpp b/src/shared/proparser/ioutils.cpp
new file mode 100644
index 00000000000..e4a0e365696
--- /dev/null
+++ b/src/shared/proparser/ioutils.cpp
@@ -0,0 +1,88 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "ioutils.h"
+
+#include <QDir>
+#include <QFile>
+
+#ifdef Q_OS_WIN
+#  include <windows.h>
+#else
+#  include <sys/types.h>
+#  include <sys/stat.h>
+#  include <unistd.h>
+#endif
+
+using namespace ProFileEvaluatorInternal;
+
+IoUtils::FileType IoUtils::fileType(const QString &fileName)
+{
+#ifdef Q_OS_WIN
+    DWORD attr = GetFileAttributesW((WCHAR*)fileName.constData());
+    if (attr == INVALID_FILE_ATTRIBUTES)
+        return FileNotFound;
+    return (attr & FILE_ATTRIBUTE_DIRECTORY) ? FileIsDir : FileIsRegular;
+#else
+    struct ::stat st;
+    if (::stat(fileName.toLatin1().constData(), &st)) // latin1 symmetric to the file reader
+        return FileNotFound;
+    return S_ISDIR(st.st_mode) ? FileIsDir : FileIsRegular;
+#endif
+}
+
+bool IoUtils::isRelativePath(const QString &path)
+{
+    if (path.startsWith(QLatin1Char('/')))
+        return false;
+#ifdef Q_OS_WIN
+    if (path.startsWith(QLatin1Char('\\')))
+        return false;
+    // Unlike QFileInfo, this won't accept a relative path with a drive letter.
+    // Such paths result in a royal mess anyway ...
+    if (path.length() >= 3 && path.at(1) == QLatin1Char(':') && path.at(0).isLetter()
+        && (path.at(2) == QLatin1Char('/') || path.at(2) == QLatin1Char('\\')))
+        return false;
+#endif
+    return true;
+}
+
+QStringRef IoUtils::fileName(const QString &fileName)
+{
+    return fileName.midRef(fileName.lastIndexOf(QLatin1Char('/')) + 1);
+}
+
+QString IoUtils::resolvePath(const QString &baseDir, const QString &fileName)
+{
+    if (fileName.isEmpty())
+        return QString();
+    if (isAbsolutePath(fileName))
+        return QDir::cleanPath(fileName);
+    return QDir::cleanPath(baseDir + QLatin1Char('/') + fileName);
+}
diff --git a/src/shared/proparser/ioutils.h b/src/shared/proparser/ioutils.h
new file mode 100644
index 00000000000..4655649f4e7
--- /dev/null
+++ b/src/shared/proparser/ioutils.h
@@ -0,0 +1,59 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, 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.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef IOUTILS_H
+#define IOUTILS_H
+
+#include <QString>
+
+namespace ProFileEvaluatorInternal {
+
+/*!
+  This class provides replacement functionality for QFileInfo, QFile & QDir,
+  as these are abysmally slow.
+*/
+class IoUtils {
+public:
+    enum FileType {
+        FileNotFound = 0,
+        FileIsRegular = 1,
+        FileIsDir = 2
+    };
+
+    static FileType fileType(const QString &fileName);
+    static bool exists(const QString &fileName) { return fileType(fileName) != FileNotFound; }
+    static bool isRelativePath(const QString &fileName);
+    static bool isAbsolutePath(const QString &fileName) { return !isRelativePath(fileName); }
+    static QStringRef fileName(const QString &fileName); // Requires normalized path
+    static QString resolvePath(const QString &baseDir, const QString &fileName);
+};
+
+}
+
+#endif // IOUTILS_H
diff --git a/src/shared/proparser/profileevaluator.cpp b/src/shared/proparser/profileevaluator.cpp
index 755785a9d3b..af97e7fd2f0 100644
--- a/src/shared/proparser/profileevaluator.cpp
+++ b/src/shared/proparser/profileevaluator.cpp
@@ -29,6 +29,7 @@
 
 #include "profileevaluator.h"
 #include "proitems.h"
+#include "ioutils.h"
 
 #include <QtCore/QByteArray>
 #include <QtCore/QDateTime>
@@ -61,6 +62,8 @@
 #define QT_PCLOSE pclose
 #endif
 
+using namespace ProFileEvaluatorInternal;
+
 QT_BEGIN_NAMESPACE
 
 static void refFunctions(QHash<QString, ProBlock *> *defs)
@@ -240,6 +243,8 @@ public:
     QString currentFileName() const;
     QString currentDirectory() const;
     ProFile *currentProFile() const;
+    QString resolvePath(const QString &fileName) const
+        { return IoUtils::resolvePath(currentDirectory(), fileName); }
 
     ProItem::ProItemReturn evaluateConditionalFunction(const QString &function, const QString &arguments);
     ProFile *parsedProFile(const QString &fileName, bool cache,
@@ -328,7 +333,8 @@ bool ProFileEvaluator::Private::read(ProFile *pro)
 {
     QFile file(pro->fileName());
     if (!file.open(QIODevice::ReadOnly)) {
-        errorMessage(format("%1 not readable.").arg(pro->fileName()));
+        if (IoUtils::exists(pro->fileName()))
+            errorMessage(format("%1 not readable.").arg(pro->fileName()));
         return false;
     }
 
@@ -1022,8 +1028,8 @@ ProItem::ProItemReturn ProFileEvaluator::Private::visitBeginProFile(ProFile * pr
                 if (qmake_cache.isEmpty() && !m_outputDir.isEmpty())  { //find it as it has not been specified
                     QDir dir(m_outputDir);
                     forever {
-                        qmake_cache = dir.filePath(QLatin1String(".qmake.cache"));
-                        if (QFile::exists(qmake_cache))
+                        qmake_cache = dir.path() + QLatin1String("/.qmake.cache");
+                        if (IoUtils::exists(qmake_cache))
                             break;
                         if (!dir.cdUp() || dir.isRoot()) {
                             qmake_cache.clear();
@@ -1050,8 +1056,7 @@ ProItem::ProItemReturn ProFileEvaluator::Private::visitBeginProFile(ProFile * pr
                 if (qmakespec.isEmpty()) {
                     foreach (const QString &root, mkspec_roots) {
                         QString mkspec = root + QLatin1String("/default");
-                        QFileInfo default_info(mkspec);
-                        if (default_info.exists() && default_info.isDir()) {
+                        if (IoUtils::fileType(mkspec) == IoUtils::FileIsDir) {
                             qmakespec = mkspec;
                             break;
                         }
@@ -1062,17 +1067,17 @@ ProItem::ProItemReturn ProFileEvaluator::Private::visitBeginProFile(ProFile * pr
                     }
                 }
 
-                if (QDir::isRelativePath(qmakespec)) {
-                    if (QFile::exists(qmakespec + QLatin1String("/qmake.conf"))) {
-                        qmakespec = QFileInfo(qmakespec).absoluteFilePath();
+                if (IoUtils::isRelativePath(qmakespec)) {
+                    if (IoUtils::exists(qmakespec + QLatin1String("/qmake.conf"))) {
+                        qmakespec = currentDirectory() + QLatin1Char('/') + qmakespec;
                     } else if (!m_outputDir.isEmpty()
-                               && QFile::exists(m_outputDir + QLatin1Char('/') + qmakespec
-                                                + QLatin1String("/qmake.conf"))) {
+                               && IoUtils::exists(m_outputDir + QLatin1Char('/') + qmakespec
+                                                  + QLatin1String("/qmake.conf"))) {
                         qmakespec = m_outputDir + QLatin1Char('/') + qmakespec;
                     } else {
                         foreach (const QString &root, mkspec_roots) {
                             QString mkspec = root + QLatin1Char('/') + qmakespec;
-                            if (QFile::exists(mkspec)) {
+                            if (IoUtils::exists(mkspec)) {
                                 qmakespec = mkspec;
                                 goto cool;
                             }
@@ -1260,7 +1265,7 @@ QStringList ProFileEvaluator::Private::qmakeFeaturePaths() const
         while (!specdir.isRoot()) {
             if (!specdir.cdUp() || specdir.isRoot())
                 break;
-            if (QFile::exists(specdir.path() + features_concat)) {
+            if (IoUtils::exists(specdir.path() + features_concat)) {
                 foreach (const QString &concat_it, concat)
                     feature_roots << (specdir.path() + concat_it);
                 break;
@@ -1892,7 +1897,7 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun
                 logMessage(format("fromfile(file, variable) requires two arguments."));
             } else {
                 QHash<QString, QStringList> vars;
-                if (evaluateFileInto(args.at(0), &vars, 0))
+                if (evaluateFileInto(resolvePath(args.at(0)), &vars, 0))
                     ret = vars.value(args.at(1));
             }
             break;
@@ -2036,7 +2041,7 @@ QStringList ProFileEvaluator::Private::evaluateExpandFunction(const QString &fun
                         if (qdir[i] == QLatin1String(".") || qdir[i] == QLatin1String(".."))
                             continue;
                         QString fname = dir + qdir[i];
-                        if (QFileInfo(fname).isDir()) {
+                        if (IoUtils::fileType(fname) == IoUtils::FileIsDir) {
                             if (recursive)
                                 dirs.append(fname);
                         }
@@ -2205,7 +2210,7 @@ ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction(
                 logMessage(format("infile(file, var, [values]) requires two or three arguments."));
             } else {
                 QHash<QString, QStringList> vars;
-                if (!evaluateFileInto(args.at(0), &vars, 0))
+                if (!evaluateFileInto(resolvePath(args.at(0)), &vars, 0))
                     return ProItem::ReturnFalse;
                 if (args.count() == 2)
                     return returnBool(vars.contains(args.at(1)));
@@ -2526,12 +2531,8 @@ ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction(
                 logMessage(format("include(file) requires one, two or three arguments."));
                 return ProItem::ReturnFalse;
             }
-            QString fileName = args.first();
-            // ### this breaks if we have include(c:/reallystupid.pri) but IMHO that's really bad style.
-            QDir currentProPath(currentDirectory());
-            fileName = QDir::cleanPath(currentProPath.absoluteFilePath(fileName));
             State sts = m_sts;
-            bool ok = evaluateFile(fileName);
+            bool ok = evaluateFile(resolvePath(args.first()));
             m_sts = sts;
             return returnBool(ok);
         }
@@ -2595,7 +2596,7 @@ ProItem::ProItemReturn ProFileEvaluator::Private::evaluateConditionalFunction(
             QString file = args.first();
             file = fixPathToLocalOS(file);
 
-            if (QFile::exists(file)) {
+            if (IoUtils::exists(file)) {
                 return ProItem::ReturnTrue;
             }
             //regular expression I guess
@@ -2648,7 +2649,7 @@ QStringList ProFileEvaluator::Private::values(const QString &variableName,
     if (variableName == QLatin1String("_PRO_FILE_"))
         return QStringList(m_profileStack.first()->fileName());
     if (variableName == QLatin1String("_PRO_FILE_PWD_"))
-        return  QStringList(QFileInfo(m_profileStack.first()->fileName()).absolutePath());
+        return QStringList(m_profileStack.first()->directoryName());
     if (variableName == QLatin1String("_QMAKE_CACHE_"))
         return QStringList(m_option->cachefile);
     if (variableName.startsWith(QLatin1String("QMAKE_HOST."))) {
@@ -2763,16 +2764,14 @@ ProFile *ProFileEvaluator::Private::parsedProFile(const QString &fileName, bool
 
 bool ProFileEvaluator::Private::evaluateFile(const QString &fileName)
 {
-    QFileInfo fi(fileName);
-    if (!fi.exists())
+    if (fileName.isEmpty())
         return false;
-    QString fn = QDir::cleanPath(fi.absoluteFilePath());
     foreach (const ProFile *pf, m_profileStack)
-        if (pf->fileName() == fn) {
-            errorMessage(format("circular inclusion of %1").arg(fn));
+        if (pf->fileName() == fileName) {
+            errorMessage(format("circular inclusion of %1").arg(fileName));
             return false;
         }
-    if (ProFile *pro = parsedProFile(fn, true)) {
+    if (ProFile *pro = parsedProFile(fileName, true)) {
         q->aboutToEval(pro);
         bool ok = (pro->Accept(this) == ProItem::ReturnTrue);
         pro->deref();
@@ -2789,12 +2788,12 @@ bool ProFileEvaluator::Private::evaluateFeatureFile(
     if (!fn.endsWith(QLatin1String(".prf")))
         fn += QLatin1String(".prf");
 
-    if (!fileName.contains((ushort)'/') || !QFile::exists(fn)) {
+    if (!fileName.contains((ushort)'/') || !IoUtils::exists(fn)) {
         if (m_option->feature_roots.isEmpty())
             m_option->feature_roots = qmakeFeaturePaths();
         int start_root = 0;
         QString currFn = currentFileName();
-        if (QFileInfo(currFn).fileName() == QFileInfo(fn).fileName()) {
+        if (IoUtils::fileName(currFn) == IoUtils::fileName(fn)) {
             for (int root = 0; root < m_option->feature_roots.size(); ++root)
                 if (m_option->feature_roots.at(root) + fn == currFn) {
                     start_root = root + 1;
@@ -2803,7 +2802,7 @@ bool ProFileEvaluator::Private::evaluateFeatureFile(
         }
         for (int root = start_root; root < m_option->feature_roots.size(); ++root) {
             QString fname = m_option->feature_roots.at(root) + fn;
-            if (QFileInfo(fname).exists()) {
+            if (IoUtils::exists(fname)) {
                 fn = fname;
                 goto cool;
             }
@@ -2817,7 +2816,7 @@ bool ProFileEvaluator::Private::evaluateFeatureFile(
             return true;
         already.append(fn);
     } else {
-        fn = QDir::cleanPath(fn);
+        fn = resolvePath(fn);
     }
 
     if (values) {
@@ -2924,9 +2923,9 @@ QStringList ProFileEvaluator::absolutePathValues(
 {
     QStringList result;
     foreach (const QString &el, values(variable)) {
-        const QFileInfo info = QFileInfo(baseDirectory, el);
-        if (info.isDir())
-            result << QDir::cleanPath(info.absoluteFilePath());
+        QString absEl = IoUtils::resolvePath(baseDirectory, el);
+        if (IoUtils::fileType(absEl) == IoUtils::FileIsDir)
+            result << QDir::cleanPath(absEl);
     }
     return result;
 }
@@ -2937,33 +2936,37 @@ QStringList ProFileEvaluator::absoluteFileValues(
 {
     QStringList result;
     foreach (const QString &el, pro ? values(variable, pro) : values(variable)) {
-        QFileInfo info(el);
-        if (info.isAbsolute()) {
-            if (info.exists()) {
+        QString absEl;
+        if (IoUtils::isAbsolutePath(el)) {
+            if (IoUtils::exists(el)) {
                 result << QDir::cleanPath(el);
                 goto next;
             }
+            absEl = el;
         } else {
             foreach (const QString &dir, searchDirs) {
-                QFileInfo info(dir, el);
-                if (info.isFile()) {
-                    result << QDir::cleanPath(info.filePath());
+                QString fn = dir + QLatin1Char('/') + el;
+                if (IoUtils::exists(fn)) {
+                    result << QDir::cleanPath(fn);
                     goto next;
                 }
             }
             if (baseDirectory.isEmpty())
                 goto next;
-            info = QFileInfo(baseDirectory, el);
+            absEl = baseDirectory + QLatin1Char('/') + el;
         }
         {
-            QFileInfo baseInfo(info.absolutePath());
-            if (baseInfo.exists()) {
-                QString wildcard = info.fileName();
+            absEl = QDir::cleanPath(absEl);
+            int nameOff = absEl.lastIndexOf(QLatin1Char('/'));
+            QString absDir = QString::fromRawData(absEl.constData(), nameOff);
+            if (IoUtils::exists(absDir)) {
+                QString wildcard = QString::fromRawData(absEl.constData() + nameOff + 1,
+                                                        absEl.length() - nameOff - 1);
                 if (wildcard.contains(QLatin1Char('*')) || wildcard.contains(QLatin1Char('?'))) {
-                    QDir theDir(QDir::cleanPath(baseInfo.filePath()));
+                    QDir theDir(absDir);
                     foreach (const QString &fn, theDir.entryList(QStringList(wildcard)))
                         if (fn != QLatin1String(".") && fn != QLatin1String(".."))
-                            result << theDir.absoluteFilePath(fn);
+                            result << absDir + QLatin1Char('/') + fn;
                 } // else if (acceptMissing)
             }
         }
diff --git a/src/shared/proparser/profileevaluator.h b/src/shared/proparser/profileevaluator.h
index 692c086f830..efe3af5e6f5 100644
--- a/src/shared/proparser/profileevaluator.h
+++ b/src/shared/proparser/profileevaluator.h
@@ -90,6 +90,7 @@ public:
     void setConfigCommandLineArguments(const QStringList &addUserConfigCmdArgs, const QStringList &removeUserConfigCmdArgs);
     void setParsePreAndPostFiles(bool on); // Default is true
 
+    // fileName is expected to be absolute and cleanPath()ed.
     // If contents is non-null, it will be used instead of the file's actual content
     ProFile *parsedProFile(const QString &fileName, const QString &contents = QString());
     bool accept(ProFile *pro);
diff --git a/src/shared/proparser/proitems.cpp b/src/shared/proparser/proitems.cpp
index c3dc18f74ae..f39816a2b10 100644
--- a/src/shared/proparser/proitems.cpp
+++ b/src/shared/proparser/proitems.cpp
@@ -260,9 +260,11 @@ ProFile::ProFile(const QString &fileName)
     setBlockKind(ProBlock::ProFileKind);
     m_fileName = fileName;
 
-    QFileInfo fi(fileName);
-    m_displayFileName = fi.fileName();
-    m_directoryName = fi.absolutePath();
+    // If the full name does not outlive the parts, things will go boom ...
+    int nameOff = fileName.lastIndexOf(QLatin1Char('/'));
+    m_displayFileName = QString::fromRawData(fileName.constData() + nameOff + 1,
+                                             fileName.length() - nameOff - 1);
+    m_directoryName = QString::fromRawData(fileName.constData(), nameOff);
 }
 
 ProFile::~ProFile()
diff --git a/src/shared/proparser/proparser.pri b/src/shared/proparser/proparser.pri
index 36cf91b5286..353f1cc18e7 100644
--- a/src/shared/proparser/proparser.pri
+++ b/src/shared/proparser/proparser.pri
@@ -10,11 +10,13 @@ HEADERS += \
         profileevaluator.h \
         proitems.h \
         prowriter.h \
+        ioutils.h \
         $$PWD/../namespace_global.h
 
 SOURCES += \
         profileevaluator.cpp \
         proitems.cpp \
-        prowriter.cpp
+        prowriter.cpp \
+        ioutils.cpp
 
 RESOURCES += proparser.qrc
-- 
GitLab