VerifyDiagnosticConsumer.cpp revision 2fe9b7fb07dff15dd15dd8755a9a9e6de0fe46fc
1//===---- VerifyDiagnosticConsumer.cpp - Verifying Diagnostic Client ------===//
2//
3//                     The LLVM Compiler Infrastructure
4//
5// This file is distributed under the University of Illinois Open Source
6// License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// This is a concrete diagnostic client, which buffers the diagnostic messages.
11//
12//===----------------------------------------------------------------------===//
13
14#include "clang/Frontend/VerifyDiagnosticConsumer.h"
15#include "clang/Frontend/FrontendDiagnostic.h"
16#include "clang/Frontend/TextDiagnosticBuffer.h"
17#include "clang/Lex/Preprocessor.h"
18#include "llvm/ADT/SmallString.h"
19#include "llvm/Support/Regex.h"
20#include "llvm/Support/raw_ostream.h"
21using namespace clang;
22
23VerifyDiagnosticConsumer::VerifyDiagnosticConsumer(DiagnosticsEngine &_Diags)
24  : Diags(_Diags), PrimaryClient(Diags.getClient()),
25    OwnsPrimaryClient(Diags.ownsClient()),
26    Buffer(new TextDiagnosticBuffer()), CurrentPreprocessor(0)
27{
28  Diags.takeClient();
29}
30
31VerifyDiagnosticConsumer::~VerifyDiagnosticConsumer() {
32  CheckDiagnostics();
33  Diags.takeClient();
34  if (OwnsPrimaryClient)
35    delete PrimaryClient;
36}
37
38// DiagnosticConsumer interface.
39
40void VerifyDiagnosticConsumer::BeginSourceFile(const LangOptions &LangOpts,
41                                             const Preprocessor *PP) {
42  // FIXME: Const hack, we screw up the preprocessor but in practice its ok
43  // because it doesn't get reused. It would be better if we could make a copy
44  // though.
45  CurrentPreprocessor = const_cast<Preprocessor*>(PP);
46
47  PrimaryClient->BeginSourceFile(LangOpts, PP);
48}
49
50void VerifyDiagnosticConsumer::EndSourceFile() {
51  CheckDiagnostics();
52
53  PrimaryClient->EndSourceFile();
54
55  CurrentPreprocessor = 0;
56}
57
58void VerifyDiagnosticConsumer::HandleDiagnostic(
59      DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) {
60  if (FirstErrorFID.isInvalid() && Info.hasSourceManager()) {
61    const SourceManager &SM = Info.getSourceManager();
62    FirstErrorFID = SM.getFileID(Info.getLocation());
63  }
64  // Send the diagnostic to the buffer, we will check it once we reach the end
65  // of the source file (or are destructed).
66  Buffer->HandleDiagnostic(DiagLevel, Info);
67}
68
69//===----------------------------------------------------------------------===//
70// Checking diagnostics implementation.
71//===----------------------------------------------------------------------===//
72
73typedef TextDiagnosticBuffer::DiagList DiagList;
74typedef TextDiagnosticBuffer::const_iterator const_diag_iterator;
75
76namespace {
77
78/// Directive - Abstract class representing a parsed verify directive.
79///
80class Directive {
81public:
82  static Directive* Create(bool RegexKind, const SourceLocation &Location,
83                           const std::string &Text, unsigned Count);
84public:
85  SourceLocation Location;
86  const std::string Text;
87  unsigned Count;
88
89  virtual ~Directive() { }
90
91  // Returns true if directive text is valid.
92  // Otherwise returns false and populates E.
93  virtual bool isValid(std::string &Error) = 0;
94
95  // Returns true on match.
96  virtual bool Match(const std::string &S) = 0;
97
98protected:
99  Directive(const SourceLocation &Location, const std::string &Text,
100            unsigned Count)
101    : Location(Location), Text(Text), Count(Count) { }
102
103private:
104  Directive(const Directive&); // DO NOT IMPLEMENT
105  void operator=(const Directive&); // DO NOT IMPLEMENT
106};
107
108/// StandardDirective - Directive with string matching.
109///
110class StandardDirective : public Directive {
111public:
112  StandardDirective(const SourceLocation &Location, const std::string &Text,
113                    unsigned Count)
114    : Directive(Location, Text, Count) { }
115
116  virtual bool isValid(std::string &Error) {
117    // all strings are considered valid; even empty ones
118    return true;
119  }
120
121  virtual bool Match(const std::string &S) {
122    return S.find(Text) != std::string::npos;
123  }
124};
125
126/// RegexDirective - Directive with regular-expression matching.
127///
128class RegexDirective : public Directive {
129public:
130  RegexDirective(const SourceLocation &Location, const std::string &Text,
131                 unsigned Count)
132    : Directive(Location, Text, Count), Regex(Text) { }
133
134  virtual bool isValid(std::string &Error) {
135    if (Regex.isValid(Error))
136      return true;
137    return false;
138  }
139
140  virtual bool Match(const std::string &S) {
141    return Regex.match(S);
142  }
143
144private:
145  llvm::Regex Regex;
146};
147
148typedef std::vector<Directive*> DirectiveList;
149
150/// ExpectedData - owns directive objects and deletes on destructor.
151///
152struct ExpectedData {
153  DirectiveList Errors;
154  DirectiveList Warnings;
155  DirectiveList Notes;
156
157  ~ExpectedData() {
158    DirectiveList* Lists[] = { &Errors, &Warnings, &Notes, 0 };
159    for (DirectiveList **PL = Lists; *PL; ++PL) {
160      DirectiveList * const L = *PL;
161      for (DirectiveList::iterator I = L->begin(), E = L->end(); I != E; ++I)
162        delete *I;
163    }
164  }
165};
166
167class ParseHelper
168{
169public:
170  ParseHelper(const char *Begin, const char *End)
171    : Begin(Begin), End(End), C(Begin), P(Begin), PEnd(NULL) { }
172
173  // Return true if string literal is next.
174  bool Next(StringRef S) {
175    P = C;
176    PEnd = C + S.size();
177    if (PEnd > End)
178      return false;
179    return !memcmp(P, S.data(), S.size());
180  }
181
182  // Return true if number is next.
183  // Output N only if number is next.
184  bool Next(unsigned &N) {
185    unsigned TMP = 0;
186    P = C;
187    for (; P < End && P[0] >= '0' && P[0] <= '9'; ++P) {
188      TMP *= 10;
189      TMP += P[0] - '0';
190    }
191    if (P == C)
192      return false;
193    PEnd = P;
194    N = TMP;
195    return true;
196  }
197
198  // Return true if string literal is found.
199  // When true, P marks begin-position of S in content.
200  bool Search(StringRef S) {
201    P = std::search(C, End, S.begin(), S.end());
202    PEnd = P + S.size();
203    return P != End;
204  }
205
206  // Advance 1-past previous next/search.
207  // Behavior is undefined if previous next/search failed.
208  bool Advance() {
209    C = PEnd;
210    return C < End;
211  }
212
213  // Skip zero or more whitespace.
214  void SkipWhitespace() {
215    for (; C < End && isspace(*C); ++C)
216      ;
217  }
218
219  // Return true if EOF reached.
220  bool Done() {
221    return !(C < End);
222  }
223
224  const char * const Begin; // beginning of expected content
225  const char * const End;   // end of expected content (1-past)
226  const char *C;            // position of next char in content
227  const char *P;
228
229private:
230  const char *PEnd; // previous next/search subject end (1-past)
231};
232
233} // namespace anonymous
234
235/// ParseDirective - Go through the comment and see if it indicates expected
236/// diagnostics. If so, then put them in the appropriate directive list.
237///
238static void ParseDirective(const char *CommentStart, unsigned CommentLen,
239                           ExpectedData &ED, Preprocessor &PP,
240                           SourceLocation Pos) {
241  // A single comment may contain multiple directives.
242  for (ParseHelper PH(CommentStart, CommentStart+CommentLen); !PH.Done();) {
243    // search for token: expected
244    if (!PH.Search("expected"))
245      break;
246    PH.Advance();
247
248    // next token: -
249    if (!PH.Next("-"))
250      continue;
251    PH.Advance();
252
253    // next token: { error | warning | note }
254    DirectiveList* DL = NULL;
255    if (PH.Next("error"))
256      DL = &ED.Errors;
257    else if (PH.Next("warning"))
258      DL = &ED.Warnings;
259    else if (PH.Next("note"))
260      DL = &ED.Notes;
261    else
262      continue;
263    PH.Advance();
264
265    // default directive kind
266    bool RegexKind = false;
267    const char* KindStr = "string";
268
269    // next optional token: -
270    if (PH.Next("-re")) {
271      PH.Advance();
272      RegexKind = true;
273      KindStr = "regex";
274    }
275
276    // skip optional whitespace
277    PH.SkipWhitespace();
278
279    // next optional token: positive integer
280    unsigned Count = 1;
281    if (PH.Next(Count))
282      PH.Advance();
283
284    // skip optional whitespace
285    PH.SkipWhitespace();
286
287    // next token: {{
288    if (!PH.Next("{{")) {
289      PP.Diag(Pos.getLocWithOffset(PH.C-PH.Begin),
290              diag::err_verify_missing_start) << KindStr;
291      continue;
292    }
293    PH.Advance();
294    const char* const ContentBegin = PH.C; // mark content begin
295
296    // search for token: }}
297    if (!PH.Search("}}")) {
298      PP.Diag(Pos.getLocWithOffset(PH.C-PH.Begin),
299              diag::err_verify_missing_end) << KindStr;
300      continue;
301    }
302    const char* const ContentEnd = PH.P; // mark content end
303    PH.Advance();
304
305    // build directive text; convert \n to newlines
306    std::string Text;
307    StringRef NewlineStr = "\\n";
308    StringRef Content(ContentBegin, ContentEnd-ContentBegin);
309    size_t CPos = 0;
310    size_t FPos;
311    while ((FPos = Content.find(NewlineStr, CPos)) != StringRef::npos) {
312      Text += Content.substr(CPos, FPos-CPos);
313      Text += '\n';
314      CPos = FPos + NewlineStr.size();
315    }
316    if (Text.empty())
317      Text.assign(ContentBegin, ContentEnd);
318
319    // construct new directive
320    Directive *D = Directive::Create(RegexKind, Pos, Text, Count);
321    std::string Error;
322    if (D->isValid(Error))
323      DL->push_back(D);
324    else {
325      PP.Diag(Pos.getLocWithOffset(ContentBegin-PH.Begin),
326              diag::err_verify_invalid_content)
327        << KindStr << Error;
328    }
329  }
330}
331
332/// FindExpectedDiags - Lex the main source file to find all of the
333//   expected errors and warnings.
334static void FindExpectedDiags(Preprocessor &PP, ExpectedData &ED, FileID FID) {
335  // Create a raw lexer to pull all the comments out of FID.
336  if (FID.isInvalid())
337    return;
338
339  SourceManager& SM = PP.getSourceManager();
340  // Create a lexer to lex all the tokens of the main file in raw mode.
341  const llvm::MemoryBuffer *FromFile = SM.getBuffer(FID);
342  Lexer RawLex(FID, FromFile, SM, PP.getLangOptions());
343
344  // Return comments as tokens, this is how we find expected diagnostics.
345  RawLex.SetCommentRetentionState(true);
346
347  Token Tok;
348  Tok.setKind(tok::comment);
349  while (Tok.isNot(tok::eof)) {
350    RawLex.Lex(Tok);
351    if (!Tok.is(tok::comment)) continue;
352
353    std::string Comment = PP.getSpelling(Tok);
354    if (Comment.empty()) continue;
355
356    // Find all expected errors/warnings/notes.
357    ParseDirective(&Comment[0], Comment.size(), ED, PP, Tok.getLocation());
358  };
359}
360
361/// PrintProblem - This takes a diagnostic map of the delta between expected and
362/// seen diagnostics. If there's anything in it, then something unexpected
363/// happened. Print the map out in a nice format and return "true". If the map
364/// is empty and we're not going to print things, then return "false".
365///
366static unsigned PrintProblem(DiagnosticsEngine &Diags, SourceManager *SourceMgr,
367                             const_diag_iterator diag_begin,
368                             const_diag_iterator diag_end,
369                             const char *Kind, bool Expected) {
370  if (diag_begin == diag_end) return 0;
371
372  llvm::SmallString<256> Fmt;
373  llvm::raw_svector_ostream OS(Fmt);
374  for (const_diag_iterator I = diag_begin, E = diag_end; I != E; ++I) {
375    if (I->first.isInvalid() || !SourceMgr)
376      OS << "\n  (frontend)";
377    else
378      OS << "\n  Line " << SourceMgr->getPresumedLineNumber(I->first);
379    OS << ": " << I->second;
380  }
381
382  Diags.Report(diag::err_verify_inconsistent_diags)
383    << Kind << !Expected << OS.str();
384  return std::distance(diag_begin, diag_end);
385}
386
387static unsigned PrintProblem(DiagnosticsEngine &Diags, SourceManager *SourceMgr,
388                             DirectiveList &DL, const char *Kind,
389                             bool Expected) {
390  if (DL.empty())
391    return 0;
392
393  llvm::SmallString<256> Fmt;
394  llvm::raw_svector_ostream OS(Fmt);
395  for (DirectiveList::iterator I = DL.begin(), E = DL.end(); I != E; ++I) {
396    Directive& D = **I;
397    if (D.Location.isInvalid() || !SourceMgr)
398      OS << "\n  (frontend)";
399    else
400      OS << "\n  Line " << SourceMgr->getPresumedLineNumber(D.Location);
401    OS << ": " << D.Text;
402  }
403
404  Diags.Report(diag::err_verify_inconsistent_diags)
405    << Kind << !Expected << OS.str();
406  return DL.size();
407}
408
409/// CheckLists - Compare expected to seen diagnostic lists and return the
410/// the difference between them.
411///
412static unsigned CheckLists(DiagnosticsEngine &Diags, SourceManager &SourceMgr,
413                           const char *Label,
414                           DirectiveList &Left,
415                           const_diag_iterator d2_begin,
416                           const_diag_iterator d2_end) {
417  DirectiveList LeftOnly;
418  DiagList Right(d2_begin, d2_end);
419
420  for (DirectiveList::iterator I = Left.begin(), E = Left.end(); I != E; ++I) {
421    Directive& D = **I;
422    unsigned LineNo1 = SourceMgr.getPresumedLineNumber(D.Location);
423
424    for (unsigned i = 0; i < D.Count; ++i) {
425      DiagList::iterator II, IE;
426      for (II = Right.begin(), IE = Right.end(); II != IE; ++II) {
427        unsigned LineNo2 = SourceMgr.getPresumedLineNumber(II->first);
428        if (LineNo1 != LineNo2)
429          continue;
430
431        const std::string &RightText = II->second;
432        if (D.Match(RightText))
433          break;
434      }
435      if (II == IE) {
436        // Not found.
437        LeftOnly.push_back(*I);
438      } else {
439        // Found. The same cannot be found twice.
440        Right.erase(II);
441      }
442    }
443  }
444  // Now all that's left in Right are those that were not matched.
445
446  return (PrintProblem(Diags, &SourceMgr, LeftOnly, Label, true) +
447          PrintProblem(Diags, &SourceMgr, Right.begin(), Right.end(),
448                       Label, false));
449}
450
451/// CheckResults - This compares the expected results to those that
452/// were actually reported. It emits any discrepencies. Return "true" if there
453/// were problems. Return "false" otherwise.
454///
455static unsigned CheckResults(DiagnosticsEngine &Diags, SourceManager &SourceMgr,
456                             const TextDiagnosticBuffer &Buffer,
457                             ExpectedData &ED) {
458  // We want to capture the delta between what was expected and what was
459  // seen.
460  //
461  //   Expected \ Seen - set expected but not seen
462  //   Seen \ Expected - set seen but not expected
463  unsigned NumProblems = 0;
464
465  // See if there are error mismatches.
466  NumProblems += CheckLists(Diags, SourceMgr, "error", ED.Errors,
467                            Buffer.err_begin(), Buffer.err_end());
468
469  // See if there are warning mismatches.
470  NumProblems += CheckLists(Diags, SourceMgr, "warning", ED.Warnings,
471                            Buffer.warn_begin(), Buffer.warn_end());
472
473  // See if there are note mismatches.
474  NumProblems += CheckLists(Diags, SourceMgr, "note", ED.Notes,
475                            Buffer.note_begin(), Buffer.note_end());
476
477  return NumProblems;
478}
479
480void VerifyDiagnosticConsumer::CheckDiagnostics() {
481  ExpectedData ED;
482
483  // Ensure any diagnostics go to the primary client.
484  bool OwnsCurClient = Diags.ownsClient();
485  DiagnosticConsumer *CurClient = Diags.takeClient();
486  Diags.setClient(PrimaryClient, false);
487
488  // If we have a preprocessor, scan the source for expected diagnostic
489  // markers. If not then any diagnostics are unexpected.
490  if (CurrentPreprocessor) {
491    SourceManager &SM = CurrentPreprocessor->getSourceManager();
492    // Extract expected-error strings from main file.
493    FindExpectedDiags(*CurrentPreprocessor, ED, SM.getMainFileID());
494    // Only check for expectations in other diagnostic locations
495    // if they are not the main file (via ID or FileEntry) - the main
496    // file has already been looked at, and its expectations must not
497    // be added twice.
498    if (!FirstErrorFID.isInvalid() && FirstErrorFID != SM.getMainFileID()
499        && (!SM.getFileEntryForID(FirstErrorFID)
500            || (SM.getFileEntryForID(FirstErrorFID) !=
501                SM.getFileEntryForID(SM.getMainFileID())))) {
502      FindExpectedDiags(*CurrentPreprocessor, ED, FirstErrorFID);
503      FirstErrorFID = FileID();
504    }
505
506    // Check that the expected diagnostics occurred.
507    NumErrors += CheckResults(Diags, SM, *Buffer, ED);
508  } else {
509    NumErrors += (PrintProblem(Diags, 0,
510                               Buffer->err_begin(), Buffer->err_end(),
511                               "error", false) +
512                  PrintProblem(Diags, 0,
513                               Buffer->warn_begin(), Buffer->warn_end(),
514                               "warn", false) +
515                  PrintProblem(Diags, 0,
516                               Buffer->note_begin(), Buffer->note_end(),
517                               "note", false));
518  }
519
520  Diags.takeClient();
521  Diags.setClient(CurClient, OwnsCurClient);
522
523  // Reset the buffer, we have processed all the diagnostics in it.
524  Buffer.reset(new TextDiagnosticBuffer());
525}
526
527DiagnosticConsumer *
528VerifyDiagnosticConsumer::clone(DiagnosticsEngine &Diags) const {
529  if (!Diags.getClient())
530    Diags.setClient(PrimaryClient->clone(Diags));
531
532  return new VerifyDiagnosticConsumer(Diags);
533}
534
535Directive* Directive::Create(bool RegexKind, const SourceLocation &Location,
536                             const std::string &Text, unsigned Count) {
537  if (RegexKind)
538    return new RegexDirective(Location, Text, Count);
539  return new StandardDirective(Location, Text, Count);
540}
541