[cfe-commits] r88748 - in /cfe/trunk: include/clang/Basic/DiagnosticFrontendKinds.td include/clang/Frontend/CompilerInstance.h include/clang/Frontend/VerifyDiagnosticsClient.h lib/Frontend/CMakeLists.txt lib/Frontend/VerifyDiagnosticsClient.cpp

Daniel Dunbar daniel at zuster.org
Fri Nov 13 19:23:19 PST 2009


Author: ddunbar
Date: Fri Nov 13 21:23:19 2009
New Revision: 88748

URL: http://llvm.org/viewvc/llvm-project?rev=88748&view=rev
Log:
Add VerifyDiagnosticsClient, to replace old -verify.
 - 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.

Added:
    cfe/trunk/include/clang/Frontend/VerifyDiagnosticsClient.h
    cfe/trunk/lib/Frontend/VerifyDiagnosticsClient.cpp
Modified:
    cfe/trunk/include/clang/Basic/DiagnosticFrontendKinds.td
    cfe/trunk/include/clang/Frontend/CompilerInstance.h
    cfe/trunk/lib/Frontend/CMakeLists.txt

Modified: cfe/trunk/include/clang/Basic/DiagnosticFrontendKinds.td
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Basic/DiagnosticFrontendKinds.td?rev=88748&r1=88747&r2=88748&view=diff

==============================================================================
--- cfe/trunk/include/clang/Basic/DiagnosticFrontendKinds.td (original)
+++ cfe/trunk/include/clang/Basic/DiagnosticFrontendKinds.td Fri Nov 13 21:23:19 2009
@@ -27,6 +27,15 @@
 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">;

Modified: cfe/trunk/include/clang/Frontend/CompilerInstance.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Frontend/CompilerInstance.h?rev=88748&r1=88747&r2=88748&view=diff

==============================================================================
--- cfe/trunk/include/clang/Frontend/CompilerInstance.h (original)
+++ cfe/trunk/include/clang/Frontend/CompilerInstance.h Fri Nov 13 21:23:19 2009
@@ -210,7 +210,10 @@
   /// 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.

Added: cfe/trunk/include/clang/Frontend/VerifyDiagnosticsClient.h
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/Frontend/VerifyDiagnosticsClient.h?rev=88748&view=auto

==============================================================================
--- cfe/trunk/include/clang/Frontend/VerifyDiagnosticsClient.h (added)
+++ cfe/trunk/include/clang/Frontend/VerifyDiagnosticsClient.h Fri Nov 13 21:23:19 2009
@@ -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

Modified: cfe/trunk/lib/Frontend/CMakeLists.txt
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Frontend/CMakeLists.txt?rev=88748&r1=88747&r2=88748&view=diff

==============================================================================
--- cfe/trunk/lib/Frontend/CMakeLists.txt (original)
+++ cfe/trunk/lib/Frontend/CMakeLists.txt Fri Nov 13 21:23:19 2009
@@ -35,6 +35,7 @@
   TextDiagnosticBuffer.cpp
   TextDiagnosticPrinter.cpp
   TypeXML.cpp
+  VerifyDiagnosticsClient.cpp
   Warnings.cpp
   )
 

Added: cfe/trunk/lib/Frontend/VerifyDiagnosticsClient.cpp
URL: http://llvm.org/viewvc/llvm-project/cfe/trunk/lib/Frontend/VerifyDiagnosticsClient.cpp?rev=88748&view=auto

==============================================================================
--- cfe/trunk/lib/Frontend/VerifyDiagnosticsClient.cpp (added)
+++ cfe/trunk/lib/Frontend/VerifyDiagnosticsClient.cpp Fri Nov 13 21:23:19 2009
@@ -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());
+}





More information about the cfe-commits mailing list