Lex and ignore Microsoft's #pragma warning(...)

Summary:
This fixes PR17145 and avoids unknown pragma warnings.

This change does not attempt to map MSVC warning numbers to clang
warning flags.  Perhaps in the future we will implement a mapping for
some common subset of Microsoft warnings, but for now we don't.

Reviewers: rsmith

CC: cfe-commits

Differential Revision: http://llvm-reviews.chandlerc.com/D1652

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@190726 91177308-0d34-0410-b5e6-96231b3b80d8
diff --git a/include/clang/Basic/DiagnosticLexKinds.td b/include/clang/Basic/DiagnosticLexKinds.td
index f1c8a09..3abbb85 100644
--- a/include/clang/Basic/DiagnosticLexKinds.td
+++ b/include/clang/Basic/DiagnosticLexKinds.td
@@ -408,6 +408,21 @@
    ExtWarn<"pragma include_alias expected include filename">,
    InGroup<UnknownPragmas>;
 
+// - #pragma warning(...)
+def warn_pragma_warning_expected :
+  ExtWarn<"#pragma warning expected '%0'">,
+  InGroup<UnknownPragmas>;
+def warn_pragma_warning_spec_invalid :
+  ExtWarn<"#pragma warning expected 'push', 'pop', 'default', 'disable',"
+          " 'error', 'once', 'suppress', 1, 2, 3, or 4">,
+  InGroup<UnknownPragmas>;
+def warn_pragma_warning_push_level :
+  ExtWarn<"#pragma warning(push, level) requires a level between 1 and 4">,
+  InGroup<UnknownPragmas>;
+def warn_pragma_warning_expected_number :
+  ExtWarn<"#pragma warning expected a warning number">,
+  InGroup<UnknownPragmas>;
+
 def err__Pragma_malformed : Error<
   "_Pragma takes a parenthesized string literal">;
 def err_pragma_message_malformed : Error<
diff --git a/include/clang/Lex/PPCallbacks.h b/include/clang/Lex/PPCallbacks.h
index cb635bc..73cfb0a 100644
--- a/include/clang/Lex/PPCallbacks.h
+++ b/include/clang/Lex/PPCallbacks.h
@@ -217,6 +217,19 @@
                                 diag::Mapping mapping, StringRef Str) {
   }
 
+  /// \brief Callback invoked when a \#pragma warning directive is read.
+  virtual void PragmaWarning(SourceLocation Loc, StringRef WarningSpec,
+                             ArrayRef<int> Ids) {
+  }
+
+  /// \brief Callback invoked when a \#pragma warning(push) directive is read.
+  virtual void PragmaWarningPush(SourceLocation Loc, int Level) {
+  }
+
+  /// \brief Callback invoked when a \#pragma warning(pop) directive is read.
+  virtual void PragmaWarningPop(SourceLocation Loc) {
+  }
+
   /// \brief Called by Preprocessor::HandleMacroExpandedIdentifier when a
   /// macro invocation is found.
   virtual void MacroExpands(const Token &MacroNameTok, const MacroDirective *MD,
@@ -400,6 +413,22 @@
     Second->PragmaDiagnostic(Loc, Namespace, mapping, Str);
   }
 
+  virtual void PragmaWarning(SourceLocation Loc, StringRef WarningSpec,
+                             ArrayRef<int> Ids) {
+    First->PragmaWarning(Loc, WarningSpec, Ids);
+    Second->PragmaWarning(Loc, WarningSpec, Ids);
+  }
+
+  virtual void PragmaWarningPush(SourceLocation Loc, int Level) {
+    First->PragmaWarningPush(Loc, Level);
+    Second->PragmaWarningPush(Loc, Level);
+  }
+
+  virtual void PragmaWarningPop(SourceLocation Loc) {
+    First->PragmaWarningPop(Loc);
+    Second->PragmaWarningPop(Loc);
+  }
+
   virtual void MacroExpands(const Token &MacroNameTok, const MacroDirective *MD,
                             SourceRange Range, const MacroArgs *Args) {
     First->MacroExpands(MacroNameTok, MD, Range, Args);
diff --git a/lib/Frontend/PrintPreprocessedOutput.cpp b/lib/Frontend/PrintPreprocessedOutput.cpp
index e0ec08f..3e45fc7 100644
--- a/lib/Frontend/PrintPreprocessedOutput.cpp
+++ b/lib/Frontend/PrintPreprocessedOutput.cpp
@@ -152,6 +152,10 @@
                                    StringRef Namespace);
   virtual void PragmaDiagnostic(SourceLocation Loc, StringRef Namespace,
                                 diag::Mapping Map, StringRef Str);
+  virtual void PragmaWarning(SourceLocation Loc, StringRef WarningSpec,
+                             ArrayRef<int> Ids);
+  virtual void PragmaWarningPush(SourceLocation Loc, int Level);
+  virtual void PragmaWarningPop(SourceLocation Loc);
 
   bool HandleFirstTokOnLine(Token &Tok);
 
@@ -507,6 +511,36 @@
   setEmittedDirectiveOnThisLine();
 }
 
+void PrintPPOutputPPCallbacks::PragmaWarning(SourceLocation Loc,
+                                             StringRef WarningSpec,
+                                             ArrayRef<int> Ids) {
+  startNewLineIfNeeded();
+  MoveToLine(Loc);
+  OS << "#pragma warning(" << WarningSpec << ':';
+  for (ArrayRef<int>::iterator I = Ids.begin(), E = Ids.end(); I != E; ++I)
+    OS << ' ' << *I;
+  OS << ')';
+  setEmittedDirectiveOnThisLine();
+}
+
+void PrintPPOutputPPCallbacks::PragmaWarningPush(SourceLocation Loc,
+                                                 int Level) {
+  startNewLineIfNeeded();
+  MoveToLine(Loc);
+  OS << "#pragma warning(push";
+  if (Level)
+    OS << ", " << Level;
+  OS << ')';
+  setEmittedDirectiveOnThisLine();
+}
+
+void PrintPPOutputPPCallbacks::PragmaWarningPop(SourceLocation Loc) {
+  startNewLineIfNeeded();
+  MoveToLine(Loc);
+  OS << "#pragma warning(pop)";
+  setEmittedDirectiveOnThisLine();
+}
+
 /// HandleFirstTokOnLine - When emitting a preprocessed file in -E mode, this
 /// is called for the first token on each new line.  If this really is the start
 /// of a new logical line, handle it and return true, otherwise return false.
diff --git a/lib/Lex/Pragma.cpp b/lib/Lex/Pragma.cpp
index 324bbd2..8d3dedc 100644
--- a/lib/Lex/Pragma.cpp
+++ b/lib/Lex/Pragma.cpp
@@ -20,11 +20,15 @@
 #include "clang/Lex/LiteralSupport.h"
 #include "clang/Lex/MacroInfo.h"
 #include "clang/Lex/Preprocessor.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringSwitch.h"
 #include "llvm/Support/CrashRecoveryContext.h"
 #include "llvm/Support/ErrorHandling.h"
 #include <algorithm>
 using namespace clang;
 
+#include "llvm/Support/raw_ostream.h"
+
 // Out-of-line destructor to provide a home for the class.
 PragmaHandler::~PragmaHandler() {
 }
@@ -1003,12 +1007,137 @@
   }
 };
 
+// Returns 0 on failure.
+static unsigned LexSimpleUint(Preprocessor &PP, Token &Tok) {
+  assert(Tok.is(tok::numeric_constant));
+  SmallString<8> IntegerBuffer;
+  bool NumberInvalid = false;
+  StringRef Spelling = PP.getSpelling(Tok, IntegerBuffer, &NumberInvalid);
+  if (NumberInvalid)
+    return 0;
+  NumericLiteralParser Literal(Spelling, Tok.getLocation(), PP);
+  if (Literal.hadError || !Literal.isIntegerLiteral() || Literal.hasUDSuffix())
+    return 0;
+  llvm::APInt APVal(32, 0);
+  if (Literal.GetIntegerValue(APVal))
+    return 0;
+  PP.Lex(Tok);
+  return unsigned(APVal.getLimitedValue(UINT_MAX));
+}
+
+/// "\#pragma warning(...)".  MSVC's diagnostics do not map cleanly to clang's
+/// diagnostics, so we don't really implement this pragma.  We parse it and
+/// ignore it to avoid -Wunknown-pragma warnings.
+struct PragmaWarningHandler : public PragmaHandler {
+  PragmaWarningHandler() : PragmaHandler("warning") {}
+
+  virtual void HandlePragma(Preprocessor &PP, PragmaIntroducerKind Introducer,
+                            Token &Tok) {
+    // Parse things like:
+    // warning(push, 1)
+    // warning(pop)
+    // warning(disable : 1 2 3 ; error 4 5 6 ; suppress 7 8 9)
+    SourceLocation DiagLoc = Tok.getLocation();
+    PPCallbacks *Callbacks = PP.getPPCallbacks();
+
+    PP.Lex(Tok);
+    if (Tok.isNot(tok::l_paren)) {
+      PP.Diag(Tok, diag::warn_pragma_warning_expected) << "(";
+      return;
+    }
+
+    PP.Lex(Tok);
+    IdentifierInfo *II = Tok.getIdentifierInfo();
+    if (!II) {
+      PP.Diag(Tok, diag::warn_pragma_warning_spec_invalid);
+      return;
+    }
+
+    if (II->isStr("push")) {
+      // #pragma warning( push[ ,n ] )
+      unsigned Level = 0;
+      PP.Lex(Tok);
+      if (Tok.is(tok::comma)) {
+        PP.Lex(Tok);
+        if (Tok.is(tok::numeric_constant))
+          Level = LexSimpleUint(PP, Tok);
+        if (Level < 1 || Level > 4) {
+          PP.Diag(Tok, diag::warn_pragma_warning_push_level);
+          return;
+        }
+      }
+      if (Callbacks)
+        Callbacks->PragmaWarningPush(DiagLoc, Level);
+    } else if (II->isStr("pop")) {
+      // #pragma warning( pop )
+      PP.Lex(Tok);
+      if (Callbacks)
+        Callbacks->PragmaWarningPop(DiagLoc);
+    } else {
+      // #pragma warning( warning-specifier : warning-number-list
+      //                  [; warning-specifier : warning-number-list...] )
+      while (true) {
+        II = Tok.getIdentifierInfo();
+        if (!II) {
+          PP.Diag(Tok, diag::warn_pragma_warning_spec_invalid);
+          return;
+        }
+
+        // Figure out which warning specifier this is.
+        StringRef Specifier = II->getName();
+        bool SpecifierValid =
+            llvm::StringSwitch<bool>(Specifier)
+                .Cases("1", "2", "3", "4", true)
+                .Cases("default", "disable", "error", "once", "suppress", true)
+                .Default(false);
+        if (!SpecifierValid) {
+          PP.Diag(Tok, diag::warn_pragma_warning_spec_invalid);
+          return;
+        }
+        PP.Lex(Tok);
+        if (Tok.isNot(tok::colon)) {
+          PP.Diag(Tok, diag::warn_pragma_warning_expected) << ":";
+          return;
+        }
+
+        // Collect the warning ids.
+        SmallVector<int, 4> Ids;
+        PP.Lex(Tok);
+        while (Tok.is(tok::numeric_constant)) {
+          unsigned Id = LexSimpleUint(PP, Tok);
+          if (Id == 0 || Id >= INT_MAX) {
+            PP.Diag(Tok, diag::warn_pragma_warning_expected_number);
+            return;
+          }
+          Ids.push_back(Id);
+        }
+        if (Callbacks)
+          Callbacks->PragmaWarning(DiagLoc, Specifier, Ids);
+
+        // Parse the next specifier if there is a semicolon.
+        if (Tok.isNot(tok::semi))
+          break;
+        PP.Lex(Tok);
+      }
+    }
+
+    if (Tok.isNot(tok::r_paren)) {
+      PP.Diag(Tok, diag::warn_pragma_warning_expected) << ")";
+      return;
+    }
+
+    PP.Lex(Tok);
+    if (Tok.isNot(tok::eod))
+      PP.Diag(Tok, diag::ext_pp_extra_tokens_at_eol) << "pragma warning";
+  }
+};
+
 /// PragmaIncludeAliasHandler - "\#pragma include_alias("...")".
 struct PragmaIncludeAliasHandler : public PragmaHandler {
   PragmaIncludeAliasHandler() : PragmaHandler("include_alias") {}
   virtual void HandlePragma(Preprocessor &PP, PragmaIntroducerKind Introducer,
                             Token &IncludeAliasTok) {
-      PP.HandlePragmaIncludeAlias(IncludeAliasTok);
+    PP.HandlePragmaIncludeAlias(IncludeAliasTok);
   }
 };
 
@@ -1266,6 +1395,7 @@
 
   // MS extensions.
   if (LangOpts.MicrosoftExt) {
+    AddPragmaHandler(new PragmaWarningHandler());
     AddPragmaHandler(new PragmaIncludeAliasHandler());
     AddPragmaHandler(new PragmaRegionHandler("region"));
     AddPragmaHandler(new PragmaRegionHandler("endregion"));
diff --git a/test/Lexer/pragma-operators.cpp b/test/Lexer/pragma-operators.cpp
index 6a5a498..7270f1e 100644
--- a/test/Lexer/pragma-operators.cpp
+++ b/test/Lexer/pragma-operators.cpp
@@ -35,3 +35,25 @@
 // CHECK: #pragma message("\042Hello\042, world!")
 // CHECK: 0;
 int n = pragma_L pragma_u8 pragma_u pragma_U pragma_R pragma_UR pragma_hello 0;
+
+#pragma warning(disable : 1 2L 3U ; error : 4 5 6 ; suppress : 7 8 9)
+// CHECK: #pragma warning(disable: 1 2 3)
+// CHECK: #line [[@LINE-2]]
+// CHECK: #pragma warning(error: 4 5 6)
+// CHECK: #line [[@LINE-4]]
+// CHECK: #pragma warning(suppress: 7 8 9)
+
+#pragma warning(push)
+#pragma warning(push, 1L)
+#pragma warning(push, 4U)
+#pragma warning(push, 0x1)
+#pragma warning(push, 03)
+#pragma warning(push, 0b10)
+#pragma warning(push, 1i8)
+// CHECK: #pragma warning(push)
+// CHECK: #pragma warning(push, 1)
+// CHECK: #pragma warning(push, 4)
+// CHECK: #pragma warning(push, 1)
+// CHECK: #pragma warning(push, 3)
+// CHECK: #pragma warning(push, 2)
+// CHECK: #pragma warning(push, 1)
diff --git a/test/Preprocessor/pragma_microsoft.c b/test/Preprocessor/pragma_microsoft.c
index 26f0a1d..c7bf3c4 100644
--- a/test/Preprocessor/pragma_microsoft.c
+++ b/test/Preprocessor/pragma_microsoft.c
@@ -87,3 +87,26 @@
 // Make sure that empty includes don't work
 #pragma include_alias("", "foo.h")  // expected-error {{empty filename}}
 #pragma include_alias(<foo.h>, <>)  // expected-error {{empty filename}}
+
+// Test that we ignore pragma warning.
+#pragma warning(push)
+#pragma warning(push, 1)
+#pragma warning(disable : 4705)
+#pragma warning(disable : 123 456 789 ; error : 321)
+#pragma warning(once : 321)
+#pragma warning(suppress : 321)
+#pragma warning(default : 321)
+#pragma warning(pop)
+
+#pragma warning  // expected-warning {{expected '('}}
+#pragma warning(   // expected-warning {{expected 'push', 'pop', 'default', 'disable', 'error', 'once', 'suppress', 1, 2, 3, or 4}}
+#pragma warning()   // expected-warning {{expected 'push', 'pop', 'default', 'disable', 'error', 'once', 'suppress', 1, 2, 3, or 4}}
+#pragma warning(push 4)  // expected-warning {{expected ')'}}
+#pragma warning(push  // expected-warning {{expected ')'}}
+#pragma warning(push, 5)  // expected-warning {{requires a level between 1 and 4}}
+#pragma warning(pop, 1)  // expected-warning {{expected ')'}}
+#pragma warning(push, 1) asdf // expected-warning {{extra tokens at end of #pragma warning directive}}
+#pragma warning(disable 4705) // expected-warning {{expected ':'}}
+#pragma warning(disable : 0) // expected-warning {{expected a warning number}}
+#pragma warning(default 321) // expected-warning {{expected ':'}}
+#pragma warning(asdf : 321) // expected-warning {{expected 'push', 'pop'}}
diff --git a/test/Preprocessor/pragma_microsoft.cpp b/test/Preprocessor/pragma_microsoft.cpp
new file mode 100644
index 0000000..5bc1ccc
--- /dev/null
+++ b/test/Preprocessor/pragma_microsoft.cpp
@@ -0,0 +1,3 @@
+// RUN: %clang_cc1 %s -fsyntax-only -std=c++11 -verify -fms-extensions
+
+#pragma warning(push, 4_D) // expected-warning {{requires a level between 1 and 4}}