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