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