]> granicus.if.org Git - clang/commitdiff
Add VerifyDiagnosticsClient, to replace old -verify.
authorDaniel Dunbar <daniel@zuster.org>
Sat, 14 Nov 2009 03:23:19 +0000 (03:23 +0000)
committerDaniel Dunbar <daniel@zuster.org>
Sat, 14 Nov 2009 03:23:19 +0000 (03:23 +0000)
 - This reimplements -verify as just another DiagnosticClient, which buffers the diagnostics and checks them when the source file is complete. There are some hacks to make this work, but they are all internal, and this exposes a better external interface.

 - This also tweaks a few things:
   o Errors are now just regular diagnostics.
   o Frontend diagnostics are now caught (for example, errors in command line arguments), although there isn't yet a way to specify that they are expected. That would be nice though.

 - Not yet used.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@88748 91177308-0d34-0410-b5e6-96231b3b80d8

include/clang/Basic/DiagnosticFrontendKinds.td
include/clang/Frontend/CompilerInstance.h
include/clang/Frontend/VerifyDiagnosticsClient.h [new file with mode: 0644]
lib/Frontend/CMakeLists.txt
lib/Frontend/VerifyDiagnosticsClient.cpp [new file with mode: 0644]

index 130628b5d4db2e52f36407242e9de71d59dff637..220efe11221c3f7c2522dd3874aa4da201178e57 100644 (file)
@@ -27,6 +27,15 @@ def err_fe_incompatible_options : Error<
 def err_fe_no_fixit_and_codegen : Error<
     "FIX-ITs cannot be applied when generating code">;
 
+def err_verify_bogus_characters : Error<
+    "bogus characters before '{{' in expected string">;
+def err_verify_missing_start : Error<
+    "cannot find start ('{{') of expected string">;
+def err_verify_missing_end : Error<
+    "cannot find end ('}}') of expected string">;
+def err_verify_inconsistent_diags : Error<
+    "'%0' diagnostics %select{expected|seen}1 but not %select{seen|expected}1: %2">;
+
 def note_fixit_applied : Note<"FIX-IT applied suggested code changes">;
 def note_fixit_in_macro : Note<
     "FIX-IT unable to apply suggested code changes in a macro">;
index 5e347061ccd7c7ffa5e4c76b4ab903585c4cec0f..ecfd509acf589fc68235d79b2b250dd67d208ead 100644 (file)
@@ -210,7 +210,10 @@ public:
   /// instance takes ownership of \arg Value.
   void setDiagnostics(Diagnostic *Value);
 
-  DiagnosticClient &getDiagnosticClient() const;
+  DiagnosticClient &getDiagnosticClient() const {
+    assert(Target && "Compiler instance has no diagnostic client!");
+    return *DiagClient;
+  }
 
   /// takeDiagnosticClient - Remove the current diagnostics client and give
   /// ownership to the caller.
diff --git a/include/clang/Frontend/VerifyDiagnosticsClient.h b/include/clang/Frontend/VerifyDiagnosticsClient.h
new file mode 100644 (file)
index 0000000..3b841cb
--- /dev/null
@@ -0,0 +1,83 @@
+//===-- VerifyDiagnosticsClient.h - Verifying Diagnostic Client -*- C++ -*-===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_CLANG_FRONTEND_VERIFYDIAGNOSTICSCLIENT_H
+#define LLVM_CLANG_FRONTEND_VERIFYDIAGNOSTICSCLIENT_H
+
+#include "clang/Basic/Diagnostic.h"
+#include "llvm/ADT/OwningPtr.h"
+
+namespace clang {
+
+class Diagnostic;
+class SourceMgr;
+class TextDiagnosticBuffer;
+
+/// VerifyDiagnosticsClient - Create a diagnostic client which will use markers
+/// in the input source to check that all the emitted diagnostics match those
+/// expected.
+///
+/// USING THE DIAGNOSTIC CHECKER:
+///
+/// Indicating that a line expects an error or a warning is simple. Put a
+/// comment on the line that has the diagnostic, use "expected-{error,warning}"
+/// to tag if it's an expected error or warning, and place the expected text
+/// between {{ and }} markers. The full text doesn't have to be included, only
+/// enough to ensure that the correct diagnostic was emitted.
+///
+/// Here's an example:
+///
+///   int A = B; // expected-error {{use of undeclared identifier 'B'}}
+///
+/// You can place as many diagnostics on one line as you wish. To make the code
+/// more readable, you can use slash-newline to separate out the diagnostics.
+///
+/// The simple syntax above allows each specification to match exactly one
+/// error.  You can use the extended syntax to customize this. The extended
+/// syntax is "expected-<type> <n> {{diag text}}", where <type> is one of
+/// "error", "warning" or "note", and <n> is a positive integer. This allows the
+/// diagnostic to appear as many times as specified. Example:
+///
+///   void f(); // expected-note 2 {{previous declaration is here}}
+///
+class VerifyDiagnosticsClient : public DiagnosticClient {
+public:
+  llvm::OwningPtr<DiagnosticClient> PrimaryClient;
+  llvm::OwningPtr<TextDiagnosticBuffer> Buffer;
+  Preprocessor *CurrentPreprocessor;
+  Diagnostic *Diags;
+  SourceManager *SourceMgr;
+  bool NumErrors;
+
+private:
+  void CheckDiagnostics();
+
+public:
+  /// Create a new verifying diagnostic client, which will issue errors to \arg
+  /// PrimaryClient when a diagnostic does not match what is expected (as
+  /// indicated in the source file). The verifying diagnostic client takes
+  /// ownership of \arg PrimaryClient.
+  VerifyDiagnosticsClient(DiagnosticClient *PrimaryClient);
+  ~VerifyDiagnosticsClient();
+
+  virtual void BeginSourceFile(const LangOptions &LangOpts,
+                               const Preprocessor *PP);
+
+  virtual void EndSourceFile();
+                               
+  virtual void HandleDiagnostic(Diagnostic::Level DiagLevel,
+                                const DiagnosticInfo &Info);
+
+  /// HadErrors - Check if there were any mismatches in expected diagnostics.
+  bool HadErrors();
+};
+
+} // end namspace clang
+
+#endif
index 676b067193ebc56abf80b5925c9b3c86acc6a999..13e55a4e16ffe1001b003680db7146f70db0fd44 100644 (file)
@@ -35,6 +35,7 @@ add_clang_library(clangFrontend
   TextDiagnosticBuffer.cpp
   TextDiagnosticPrinter.cpp
   TypeXML.cpp
+  VerifyDiagnosticsClient.cpp
   Warnings.cpp
   )
 
diff --git a/lib/Frontend/VerifyDiagnosticsClient.cpp b/lib/Frontend/VerifyDiagnosticsClient.cpp
new file mode 100644 (file)
index 0000000..abcca09
--- /dev/null
@@ -0,0 +1,340 @@
+//===--- VerifyDiagnosticsClient.cpp - Verifying Diagnostic Client --------===//
+//
+//                     The LLVM Compiler Infrastructure
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+//
+// This is a concrete diagnostic client, which buffers the diagnostic messages.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/Frontend/VerifyDiagnosticsClient.h"
+#include "clang/Frontend/FrontendDiagnostic.h"
+#include "clang/Frontend/TextDiagnosticBuffer.h"
+#include "clang/Lex/Preprocessor.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Support/raw_ostream.h"
+using namespace clang;
+
+VerifyDiagnosticsClient::VerifyDiagnosticsClient(DiagnosticClient *_Primary)
+  : PrimaryClient(_Primary), Buffer(new TextDiagnosticBuffer()),
+    CurrentPreprocessor(0), Diags(0), SourceMgr(0), NumErrors(0) {
+}
+
+VerifyDiagnosticsClient::~VerifyDiagnosticsClient() {
+  CheckDiagnostics();
+}
+
+// DiagnosticClient interface.
+
+void VerifyDiagnosticsClient::BeginSourceFile(const LangOptions &LangOpts,
+                                             const Preprocessor *PP) {
+  // FIXME: Const hack, we screw up the preprocessor but in practice its ok
+  // because it doesn't get reused. It would be better if we could make a copy
+  // though.
+  CurrentPreprocessor = const_cast<Preprocessor*>(PP);
+  
+  // FIXME: HACK. Remember the source manager, and just expect that its lifetime
+  // exceeds when we need it.
+  SourceMgr = PP ? &PP->getSourceManager() : 0;
+
+  // FIXME: HACK. Remember the diagnostic engine, and just expect that its
+  // lifetime exceeds when we need it.
+  Diags = PP ? &PP->getDiagnostics() : 0;
+
+  PrimaryClient->BeginSourceFile(LangOpts, PP);
+}
+
+void VerifyDiagnosticsClient::EndSourceFile() {
+  CheckDiagnostics();
+
+  PrimaryClient->EndSourceFile();
+
+  CurrentPreprocessor = 0;
+}
+                               
+void VerifyDiagnosticsClient::HandleDiagnostic(Diagnostic::Level DiagLevel,
+                                              const DiagnosticInfo &Info) {
+  // Send the diagnostic to the buffer, we will check it once we reach the end
+  // of the source file (or are destructed).
+  Buffer->HandleDiagnostic(DiagLevel, Info);
+}
+
+// FIXME: It would be nice to just get this from the primary diagnostic client
+// or something.
+bool VerifyDiagnosticsClient::HadErrors() {
+  CheckDiagnostics();
+
+  return NumErrors != 0;
+}
+
+//===----------------------------------------------------------------------===//
+// Checking diagnostics implementation.
+//===----------------------------------------------------------------------===//
+
+typedef TextDiagnosticBuffer::DiagList DiagList;
+typedef TextDiagnosticBuffer::const_iterator const_diag_iterator;
+
+/// FindDiagnostics - Go through the comment and see if it indicates expected
+/// diagnostics. If so, then put them in a diagnostic list.
+///
+static void FindDiagnostics(const char *CommentStart, unsigned CommentLen,
+                            DiagList &ExpectedDiags,
+                            Preprocessor &PP, SourceLocation Pos,
+                            const char *ExpectedStr) {
+  const char *CommentEnd = CommentStart+CommentLen;
+  unsigned ExpectedStrLen = strlen(ExpectedStr);
+
+  // Find all expected-foo diagnostics in the string and add them to
+  // ExpectedDiags.
+  while (CommentStart != CommentEnd) {
+    CommentStart = std::find(CommentStart, CommentEnd, 'e');
+    if (unsigned(CommentEnd-CommentStart) < ExpectedStrLen) return;
+
+    // If this isn't expected-foo, ignore it.
+    if (memcmp(CommentStart, ExpectedStr, ExpectedStrLen)) {
+      ++CommentStart;
+      continue;
+    }
+
+    CommentStart += ExpectedStrLen;
+
+    // Skip whitespace.
+    while (CommentStart != CommentEnd &&
+           isspace(CommentStart[0]))
+      ++CommentStart;
+
+    // Default, if we find the '{' now, is 1 time.
+    int Times = 1;
+    int Temp = 0;
+    // In extended syntax, there could be a digit now.
+    while (CommentStart != CommentEnd &&
+           CommentStart[0] >= '0' && CommentStart[0] <= '9') {
+      Temp *= 10;
+      Temp += CommentStart[0] - '0';
+      ++CommentStart;
+    }
+    if (Temp > 0)
+      Times = Temp;
+
+    // Skip whitespace again.
+    while (CommentStart != CommentEnd &&
+           isspace(CommentStart[0]))
+      ++CommentStart;
+
+    // We should have a {{ now.
+    if (CommentEnd-CommentStart < 2 ||
+        CommentStart[0] != '{' || CommentStart[1] != '{') {
+      if (std::find(CommentStart, CommentEnd, '{') != CommentEnd)
+        PP.Diag(Pos, diag::err_verify_bogus_characters);
+      else
+        PP.Diag(Pos, diag::err_verify_missing_start);
+      return;
+    }
+    CommentStart += 2;
+
+    // Find the }}.
+    const char *ExpectedEnd = CommentStart;
+    while (1) {
+      ExpectedEnd = std::find(ExpectedEnd, CommentEnd, '}');
+      if (CommentEnd-ExpectedEnd < 2) {
+        PP.Diag(Pos, diag::err_verify_missing_end);
+        return;
+      }
+
+      if (ExpectedEnd[1] == '}')
+        break;
+
+      ++ExpectedEnd;  // Skip over singular }'s
+    }
+
+    std::string Msg(CommentStart, ExpectedEnd);
+    std::string::size_type FindPos;
+    while ((FindPos = Msg.find("\\n")) != std::string::npos)
+      Msg.replace(FindPos, 2, "\n");
+    // Add is possibly multiple times.
+    for (int i = 0; i < Times; ++i)
+      ExpectedDiags.push_back(std::make_pair(Pos, Msg));
+
+    CommentStart = ExpectedEnd;
+  }
+}
+
+/// FindExpectedDiags - Lex the main source file to find all of the
+//   expected errors and warnings.
+static void FindExpectedDiags(Preprocessor &PP,
+                              DiagList &ExpectedErrors,
+                              DiagList &ExpectedWarnings,
+                              DiagList &ExpectedNotes) {
+  // Create a raw lexer to pull all the comments out of the main file.  We don't
+  // want to look in #include'd headers for expected-error strings.
+  FileID FID = PP.getSourceManager().getMainFileID();
+  if (PP.getSourceManager().getMainFileID().isInvalid())
+    return;
+
+  // Create a lexer to lex all the tokens of the main file in raw mode.
+  Lexer RawLex(FID, PP.getSourceManager(), PP.getLangOptions());
+
+  // Return comments as tokens, this is how we find expected diagnostics.
+  RawLex.SetCommentRetentionState(true);
+
+  Token Tok;
+  Tok.setKind(tok::comment);
+  while (Tok.isNot(tok::eof)) {
+    RawLex.Lex(Tok);
+    if (!Tok.is(tok::comment)) continue;
+
+    std::string Comment = PP.getSpelling(Tok);
+    if (Comment.empty()) continue;
+
+    // Find all expected errors.
+    FindDiagnostics(&Comment[0], Comment.size(), ExpectedErrors, PP,
+                    Tok.getLocation(), "expected-error");
+
+    // Find all expected warnings.
+    FindDiagnostics(&Comment[0], Comment.size(), ExpectedWarnings, PP,
+                    Tok.getLocation(), "expected-warning");
+
+    // Find all expected notes.
+    FindDiagnostics(&Comment[0], Comment.size(), ExpectedNotes, PP,
+                    Tok.getLocation(), "expected-note");
+  };
+}
+
+/// PrintProblem - This takes a diagnostic map of the delta between expected and
+/// seen diagnostics. If there's anything in it, then something unexpected
+/// happened. Print the map out in a nice format and return "true". If the map
+/// is empty and we're not going to print things, then return "false".
+///
+static unsigned PrintProblem(Diagnostic &Diags, SourceManager &SourceMgr,
+                             const_diag_iterator diag_begin,
+                             const_diag_iterator diag_end,
+                             const char *Kind, bool Expected) {
+  if (diag_begin == diag_end) return 0;
+
+  llvm::SmallString<256> Fmt;
+  llvm::raw_svector_ostream OS(Fmt);
+  for (const_diag_iterator I = diag_begin, E = diag_end; I != E; ++I) {
+    if (I->first.isInvalid())
+      OS << "\n  (frontend)";
+    else
+      OS << "\n  Line " << SourceMgr.getInstantiationLineNumber(I->first);
+    OS << ": " << I->second;
+  }
+
+  Diags.Report(diag::err_verify_inconsistent_diags)
+    << Kind << Expected << OS.str();
+  return std::distance(diag_begin, diag_end);
+}
+
+/// CompareDiagLists - Compare two diagnostic lists and return the difference
+/// between them.
+///
+static unsigned CompareDiagLists(Diagnostic &Diags,
+                                 SourceManager &SourceMgr,
+                                 const_diag_iterator d1_begin,
+                                 const_diag_iterator d1_end,
+                                 const_diag_iterator d2_begin,
+                                 const_diag_iterator d2_end,
+                                 const char *Label) {
+  DiagList LeftOnly;
+  DiagList Left(d1_begin, d1_end);
+  DiagList Right(d2_begin, d2_end);
+
+  for (const_diag_iterator I = Left.begin(), E = Left.end(); I != E; ++I) {
+    unsigned LineNo1 = SourceMgr.getInstantiationLineNumber(I->first);
+    const std::string &Diag1 = I->second;
+
+    DiagList::iterator II, IE;
+    for (II = Right.begin(), IE = Right.end(); II != IE; ++II) {
+      unsigned LineNo2 = SourceMgr.getInstantiationLineNumber(II->first);
+      if (LineNo1 != LineNo2) continue;
+
+      const std::string &Diag2 = II->second;
+      if (Diag2.find(Diag1) != std::string::npos ||
+          Diag1.find(Diag2) != std::string::npos) {
+        break;
+      }
+    }
+    if (II == IE) {
+      // Not found.
+      LeftOnly.push_back(*I);
+    } else {
+      // Found. The same cannot be found twice.
+      Right.erase(II);
+    }
+  }
+  // Now all that's left in Right are those that were not matched.
+
+  return (PrintProblem(Diags, SourceMgr,
+                      LeftOnly.begin(), LeftOnly.end(), Label, false) +
+          PrintProblem(Diags, SourceMgr,
+                       Right.begin(), Right.end(), Label, true));
+}
+
+/// CheckResults - This compares the expected results to those that
+/// were actually reported. It emits any discrepencies. Return "true" if there
+/// were problems. Return "false" otherwise.
+///
+static unsigned CheckResults(Diagnostic &Diags, SourceManager &SourceMgr,
+                             const TextDiagnosticBuffer &Buffer,
+                             const DiagList &ExpectedErrors,
+                             const DiagList &ExpectedWarnings,
+                             const DiagList &ExpectedNotes) {
+  // We want to capture the delta between what was expected and what was
+  // seen.
+  //
+  //   Expected \ Seen - set expected but not seen
+  //   Seen \ Expected - set seen but not expected
+  unsigned NumProblems = 0;
+
+  // See if there are error mismatches.
+  NumProblems += CompareDiagLists(Diags, SourceMgr,
+                                  ExpectedErrors.begin(), ExpectedErrors.end(),
+                                  Buffer.err_begin(), Buffer.err_end(),
+                                  "error");
+  
+  // See if there are warning mismatches.
+  NumProblems += CompareDiagLists(Diags, SourceMgr,
+                                  ExpectedWarnings.begin(),
+                                  ExpectedWarnings.end(),
+                                  Buffer.warn_begin(), Buffer.warn_end(),
+                                  "warning");
+
+  // See if there are note mismatches.
+  NumProblems += CompareDiagLists(Diags, SourceMgr,
+                                  ExpectedNotes.begin(),
+                                  ExpectedNotes.end(),
+                                  Buffer.note_begin(), Buffer.note_end(),
+                                  "note");
+
+  return NumProblems;
+}
+
+
+void VerifyDiagnosticsClient::CheckDiagnostics() {
+  DiagList ExpectedErrors, ExpectedWarnings, ExpectedNotes;
+
+  // Ensure any diagnostics go to the primary client.
+  DiagnosticClient *CurClient = Diags->getClient();
+  Diags->setClient(PrimaryClient.get());
+
+  // If we have a preprocessor, scan the source for expected diagnostic
+  // markers. If not then any diagnostics are unexpected.
+  if (CurrentPreprocessor) {
+    FindExpectedDiags(*CurrentPreprocessor, ExpectedErrors, ExpectedWarnings,
+                      ExpectedNotes);
+  }
+
+  // Check that the expected diagnostics occurred.
+  NumErrors += CheckResults(*Diags, *SourceMgr, *Buffer,
+                            ExpectedErrors, ExpectedWarnings, ExpectedNotes);
+
+  Diags->setClient(CurClient);
+
+  // Reset the buffer, we have processed all the diagnostics in it.
+  Buffer.reset(new TextDiagnosticBuffer());
+}