From 5ff5211a2570e0e894f53bfedb1f1102bde4d58c Mon Sep 17 00:00:00 2001
From: Nikolai Kosjar <nikolai.kosjar@theqtcompany.com>
Date: Mon, 11 Apr 2016 11:53:50 +0200
Subject: [PATCH] Clang: Set -fms-compatibility-version explicitly

Infer the version from the _MSC_FULL_VER macro, so it cannot get out of sync
with that.

Adapt the analyzer to do the same.

Based on

  commit daf08d8702905335e3fc63c629f917e99715b915
  Clang Static Analyzer: Workaround analyzing MSVC2015 projects with clang 3.8.0

Change-Id: I9d34abdbe2c83fe271eadd8d051caad43aca6772
Reviewed-by: Christian Kandeler <christian.kandeler@theqtcompany.com>
---
 src/plugins/clangcodemodel/clangutils.cpp     |  2 +
 .../clangstaticanalyzerruncontrol.cpp         | 82 +++++++++++++------
 .../cpptools/compileroptionsbuilder.cpp       | 80 ++++++++++++++++--
 src/plugins/cpptools/compileroptionsbuilder.h |  2 +
 4 files changed, 132 insertions(+), 34 deletions(-)

diff --git a/src/plugins/clangcodemodel/clangutils.cpp b/src/plugins/clangcodemodel/clangutils.cpp
index 39eeeb31878..7c5c5494935 100644
--- a/src/plugins/clangcodemodel/clangutils.cpp
+++ b/src/plugins/clangcodemodel/clangutils.cpp
@@ -99,6 +99,8 @@ public:
         optionsBuilder.addHeaderPathOptions();
         optionsBuilder.addProjectConfigFileInclude();
 
+        optionsBuilder.addMsvcCompatibilityVersion();
+
         optionsBuilder.addExtraOptions();
 
         return optionsBuilder.options();
diff --git a/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp
index 5a9fcdbbd31..1b2ace47379 100644
--- a/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp
+++ b/src/plugins/clangstaticanalyzer/clangstaticanalyzerruncontrol.cpp
@@ -142,14 +142,6 @@ QStringList inputAndOutputArgumentsRemoved(const QString &inputFile, const QStri
     return newArguments;
 }
 
-static void appendMsCompatibility2015OptionForMsvc2015(QStringList *arguments, bool isMsvc2015)
-{
-    QTC_ASSERT(arguments, return);
-
-    if (isMsvc2015)
-        arguments->append(QLatin1String("-fms-compatibility-version=19"));
-}
-
 static QStringList languageFeatureMacros()
 {
     // Collected with:
@@ -198,19 +190,6 @@ static void undefineCppLanguageFeatureMacrosForMsvc2015(QStringList *arguments,
     }
 }
 
-static QStringList tweakedArguments(const QString &filePath,
-                                    const QStringList &arguments,
-                                    const ExtraToolChainInfo &extraParams)
-{
-    QStringList newArguments = inputAndOutputArgumentsRemoved(filePath, arguments);
-    prependWordWidthArgumentIfNotIncluded(&newArguments, extraParams.wordWidth);
-    prependTargetTripleIfNotIncludedAndNotEmpty(&newArguments, extraParams.targetTriple);
-    appendMsCompatibility2015OptionForMsvc2015(&newArguments, extraParams.isMsvc2015);
-    undefineCppLanguageFeatureMacrosForMsvc2015(&newArguments, extraParams.isMsvc2015);
-
-    return newArguments;
-}
-
 static QString createLanguageOptionMsvc(ProjectFile::Kind fileKind)
 {
     switch (fileKind) {
@@ -252,6 +231,7 @@ public:
 
         optionsBuilder.addToolchainAndProjectDefines();
         optionsBuilder.addHeaderPathOptions();
+        optionsBuilder.addMsvcCompatibilityVersion();
 
         if (type == ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID)
             optionsBuilder.add(QLatin1String("/EHsc")); // clang-cl does not understand exceptions
@@ -260,19 +240,18 @@ public:
 
         QStringList options = optionsBuilder.options();
         prependWordWidthArgumentIfNotIncluded(&options, extraParams.wordWidth);
-        appendMsCompatibility2015OptionForMsvc2015(&options, extraParams.isMsvc2015);
         undefineCppLanguageFeatureMacrosForMsvc2015(&options, extraParams.isMsvc2015);
 
         return options;
     }
 
-private:
     ClangStaticAnalyzerOptionsBuilder(const CppTools::ProjectPart &projectPart)
         : CompilerOptionsBuilder(projectPart)
         , m_isMsvcToolchain(m_projectPart.toolchainType == ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID)
     {
     }
 
+private:
     void addTargetTriple() override
     {
         // For MSVC toolchains we use clang-cl.exe, so there is nothing to do here since
@@ -315,7 +294,31 @@ private:
     bool m_isMsvcToolchain;
 };
 
+static QStringList createMsCompatibilityVersionOption(const ProjectPart &projectPart)
+{
+    ClangStaticAnalyzerOptionsBuilder optionsBuilder(projectPart);
+    optionsBuilder.addMsvcCompatibilityVersion();
+    const QStringList option = optionsBuilder.options();
+
+    return option;
+}
+
+static QStringList tweakedArguments(const ProjectPart &projectPart,
+                                    const QString &filePath,
+                                    const QStringList &arguments,
+                                    const ExtraToolChainInfo &extraParams)
+{
+    QStringList newArguments = inputAndOutputArgumentsRemoved(filePath, arguments);
+    prependWordWidthArgumentIfNotIncluded(&newArguments, extraParams.wordWidth);
+    prependTargetTripleIfNotIncludedAndNotEmpty(&newArguments, extraParams.targetTriple);
+    newArguments.append(createMsCompatibilityVersionOption(projectPart));
+    undefineCppLanguageFeatureMacrosForMsvc2015(&newArguments, extraParams.isMsvc2015);
+
+    return newArguments;
+}
+
 static AnalyzeUnits unitsToAnalyzeFromCompilerCallData(
+            const QHash<QString, ProjectPart::Ptr> &projectFileToProjectPart,
             const ProjectInfo::CompilerCallData &compilerCallData,
             const ExtraToolChainInfo &extraParams)
 {
@@ -324,13 +327,20 @@ static AnalyzeUnits unitsToAnalyzeFromCompilerCallData(
     AnalyzeUnits unitsToAnalyze;
 
     foreach (const ProjectInfo::CompilerCallGroup &compilerCallGroup, compilerCallData) {
+        const ProjectPart::Ptr projectPart
+                = projectFileToProjectPart.value(compilerCallGroup.groupId);
+        QTC_ASSERT(projectPart, continue);
+
         QHashIterator<QString, QList<QStringList> > it(compilerCallGroup.callsPerSourceFile);
         while (it.hasNext()) {
             it.next();
             const QString file = it.key();
             const QList<QStringList> compilerCalls = it.value();
             foreach (const QStringList &options, compilerCalls) {
-                const QStringList arguments = tweakedArguments(file, options, extraParams);
+                const QStringList arguments = tweakedArguments(*projectPart,
+                                                               file,
+                                                               options,
+                                                               extraParams);
                 unitsToAnalyze << AnalyzeUnit(file, arguments);
             }
         }
@@ -367,16 +377,34 @@ static AnalyzeUnits unitsToAnalyzeFromProjectParts(const QList<ProjectPart::Ptr>
     return unitsToAnalyze;
 }
 
+static QHash<QString, ProjectPart::Ptr> generateProjectFileToProjectPartMapping(
+            const QList<ProjectPart::Ptr> &projectParts)
+{
+    QHash<QString, ProjectPart::Ptr> mapping;
+
+    foreach (const ProjectPart::Ptr &projectPart, projectParts) {
+        QTC_ASSERT(projectPart, continue);
+        mapping[projectPart->projectFile] = projectPart;
+    }
+
+    return mapping;
+}
+
 AnalyzeUnits ClangStaticAnalyzerRunControl::sortedUnitsToAnalyze()
 {
     QTC_ASSERT(m_projectInfo.isValid(), return AnalyzeUnits());
 
     AnalyzeUnits units;
     const ProjectInfo::CompilerCallData compilerCallData = m_projectInfo.compilerCallData();
-    if (compilerCallData.isEmpty())
+    if (compilerCallData.isEmpty()) {
         units = unitsToAnalyzeFromProjectParts(m_projectInfo.projectParts(), m_extraToolChainInfo);
-    else
-        units = unitsToAnalyzeFromCompilerCallData(compilerCallData, m_extraToolChainInfo);
+    } else {
+        const QHash<QString, ProjectPart::Ptr> projectFileToProjectPart
+                = generateProjectFileToProjectPartMapping(m_projectInfo.projectParts());
+        units = unitsToAnalyzeFromCompilerCallData(projectFileToProjectPart,
+                                                   compilerCallData,
+                                                   m_extraToolChainInfo);
+    }
 
     Utils::sort(units, [](const AnalyzeUnit &a1, const AnalyzeUnit &a2) -> bool {
         return a1.file < a2.file;
diff --git a/src/plugins/cpptools/compileroptionsbuilder.cpp b/src/plugins/cpptools/compileroptionsbuilder.cpp
index b520babef13..6de6a44d5dc 100644
--- a/src/plugins/cpptools/compileroptionsbuilder.cpp
+++ b/src/plugins/cpptools/compileroptionsbuilder.cpp
@@ -44,16 +44,39 @@ void CompilerOptionsBuilder::add(const QString &option)
     m_options.append(option);
 }
 
-QString CompilerOptionsBuilder::defineLineToDefineOption(const QByteArray &defineLine)
+struct Macro {
+    static Macro fromDefineDirective(const QByteArray &defineDirective);
+    QByteArray toDefineOption(const QByteArray &option) const;
+
+    QByteArray name;
+    QByteArray value;
+};
+
+Macro Macro::fromDefineDirective(const QByteArray &defineDirective)
 {
-    QByteArray str = defineLine.mid(8);
-    int spaceIdx = str.indexOf(' ');
-    const QString option = defineOption();
+    const QByteArray str = defineDirective.mid(8);
+    const int spaceIdx = str.indexOf(' ');
     const bool hasValue = spaceIdx != -1;
-    QString arg = option + QLatin1String(str.left(hasValue ? spaceIdx : str.size()) + '=');
+
+    Macro macro;
+    macro.name = str.left(hasValue ? spaceIdx : str.size());
     if (hasValue)
-        arg += QLatin1String(str.mid(spaceIdx + 1));
-    return arg;
+        macro.value = str.mid(spaceIdx + 1);
+
+    return macro;
+}
+
+QByteArray Macro::toDefineOption(const QByteArray &option) const
+{
+    QByteArray result;
+
+    result.append(option);
+    result.append(name);
+    result.append('=');
+    if (!value.isEmpty())
+        result.append(value);
+
+    return result;
 }
 
 void CompilerOptionsBuilder::addDefine(const QByteArray &defineLine)
@@ -225,11 +248,54 @@ void CompilerOptionsBuilder::addOptionsForLanguage(bool checkForBorlandExtension
     m_options.append(opts);
 }
 
+static QByteArray toMsCompatibilityVersionFormat(const QByteArray &mscFullVer)
+{
+    return mscFullVer.left(2)
+         + QByteArray(".")
+         + mscFullVer.mid(2, 2);
+}
+
+static QByteArray msCompatibilityVersionFromDefines(const QByteArray &defineDirectives)
+{
+    foreach (QByteArray defineDirective, defineDirectives.split('\n')) {
+        if (defineDirective.isEmpty())
+            continue;
+
+        const Macro macro = Macro::fromDefineDirective(defineDirective);
+        if (macro.name == "_MSC_FULL_VER")
+            return toMsCompatibilityVersionFormat(macro.value);
+    }
+
+    return QByteArray();
+}
+
+void CompilerOptionsBuilder::addMsvcCompatibilityVersion()
+{
+    if (m_projectPart.toolchainType == ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID) {
+        const QByteArray defines = m_projectPart.toolchainDefines + m_projectPart.projectDefines;
+        const QByteArray msvcVersion = msCompatibilityVersionFromDefines(defines);
+
+        if (!msvcVersion.isEmpty()) {
+            const QString option = QLatin1String("-fms-compatibility-version=")
+                    + QLatin1String(msvcVersion);
+            m_options.append(option);
+        }
+    }
+}
+
 QString CompilerOptionsBuilder::includeOption() const
 {
     return QLatin1String("-I");
 }
 
+QString CompilerOptionsBuilder::defineLineToDefineOption(const QByteArray &defineLine)
+{
+    const Macro macro = Macro::fromDefineDirective(defineLine);
+    const QByteArray option = macro.toDefineOption(defineOption().toLatin1());
+
+    return QString::fromLatin1(option);
+}
+
 QString CompilerOptionsBuilder::defineOption() const
 {
     return QLatin1String("-D");
diff --git a/src/plugins/cpptools/compileroptionsbuilder.h b/src/plugins/cpptools/compileroptionsbuilder.h
index c259c8cf4dc..e2c38f02258 100644
--- a/src/plugins/cpptools/compileroptionsbuilder.h
+++ b/src/plugins/cpptools/compileroptionsbuilder.h
@@ -51,6 +51,8 @@ public:
     virtual void addLanguageOption(ProjectFile::Kind fileKind);
     virtual void addOptionsForLanguage(bool checkForBorlandExtensions = true);
 
+    void addMsvcCompatibilityVersion();
+
 protected:
     virtual bool excludeDefineLine(const QByteArray &defineLine) const;
     virtual bool excludeHeaderPath(const QString &headerPath) const;
-- 
GitLab