VerifyDiagnosticConsumer.cpp revision 7c304f56eecbd03db7d222a05dfcd593750d50d3
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/Basic/FileManager.h" 15#include "clang/Frontend/VerifyDiagnosticConsumer.h" 16#include "clang/Frontend/FrontendDiagnostic.h" 17#include "clang/Frontend/TextDiagnosticBuffer.h" 18#include "clang/Lex/HeaderSearch.h" 19#include "clang/Lex/Preprocessor.h" 20#include "llvm/ADT/SmallString.h" 21#include "llvm/Support/Regex.h" 22#include "llvm/Support/raw_ostream.h" 23 24using namespace clang; 25typedef VerifyDiagnosticConsumer::Directive Directive; 26typedef VerifyDiagnosticConsumer::DirectiveList DirectiveList; 27typedef VerifyDiagnosticConsumer::ExpectedData ExpectedData; 28 29VerifyDiagnosticConsumer::VerifyDiagnosticConsumer(DiagnosticsEngine &_Diags) 30 : Diags(_Diags), 31 PrimaryClient(Diags.getClient()), OwnsPrimaryClient(Diags.ownsClient()), 32 Buffer(new TextDiagnosticBuffer()), CurrentPreprocessor(0), 33 ActiveSourceFiles(0) 34{ 35 Diags.takeClient(); 36} 37 38VerifyDiagnosticConsumer::~VerifyDiagnosticConsumer() { 39 assert(!ActiveSourceFiles && "Incomplete parsing of source files!"); 40 assert(!CurrentPreprocessor && "CurrentPreprocessor should be invalid!"); 41 CheckDiagnostics(); 42 Diags.takeClient(); 43 if (OwnsPrimaryClient) 44 delete PrimaryClient; 45} 46 47#ifndef NDEBUG 48namespace { 49class VerifyFileTracker : public PPCallbacks { 50 typedef VerifyDiagnosticConsumer::FilesParsedForDirectivesSet ListType; 51 ListType &FilesList; 52 SourceManager &SM; 53 54public: 55 VerifyFileTracker(ListType &FilesList, SourceManager &SM) 56 : FilesList(FilesList), SM(SM) { } 57 58 /// \brief Hook into the preprocessor and update the list of parsed 59 /// files when the preprocessor indicates a new file is entered. 60 virtual void FileChanged(SourceLocation Loc, FileChangeReason Reason, 61 SrcMgr::CharacteristicKind FileType, 62 FileID PrevFID) { 63 if (const FileEntry *E = SM.getFileEntryForID(SM.getFileID(Loc))) 64 FilesList.insert(E); 65 } 66}; 67} // End anonymous namespace. 68#endif 69 70// DiagnosticConsumer interface. 71 72void VerifyDiagnosticConsumer::BeginSourceFile(const LangOptions &LangOpts, 73 const Preprocessor *PP) { 74 // Attach comment handler on first invocation. 75 if (++ActiveSourceFiles == 1) { 76 if (PP) { 77 CurrentPreprocessor = PP; 78 const_cast<Preprocessor*>(PP)->addCommentHandler(this); 79#ifndef NDEBUG 80 VerifyFileTracker *V = new VerifyFileTracker(FilesParsedForDirectives, 81 PP->getSourceManager()); 82 const_cast<Preprocessor*>(PP)->addPPCallbacks(V); 83#endif 84 } 85 } 86 87 assert((!PP || CurrentPreprocessor == PP) && "Preprocessor changed!"); 88 PrimaryClient->BeginSourceFile(LangOpts, PP); 89} 90 91void VerifyDiagnosticConsumer::EndSourceFile() { 92 assert(ActiveSourceFiles && "No active source files!"); 93 PrimaryClient->EndSourceFile(); 94 95 // Detach comment handler once last active source file completed. 96 if (--ActiveSourceFiles == 0) { 97 if (CurrentPreprocessor) 98 const_cast<Preprocessor*>(CurrentPreprocessor)->removeCommentHandler(this); 99 100 // Check diagnostics once last file completed. 101 CheckDiagnostics(); 102 CurrentPreprocessor = 0; 103 } 104} 105 106void VerifyDiagnosticConsumer::HandleDiagnostic( 107 DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { 108#ifndef NDEBUG 109 if (Info.hasSourceManager()) { 110 FileID FID = Info.getSourceManager().getFileID(Info.getLocation()); 111 if (!FID.isInvalid()) 112 FilesWithDiagnostics.insert(FID); 113 } 114#endif 115 // Send the diagnostic to the buffer, we will check it once we reach the end 116 // of the source file (or are destructed). 117 Buffer->HandleDiagnostic(DiagLevel, Info); 118} 119 120//===----------------------------------------------------------------------===// 121// Checking diagnostics implementation. 122//===----------------------------------------------------------------------===// 123 124typedef TextDiagnosticBuffer::DiagList DiagList; 125typedef TextDiagnosticBuffer::const_iterator const_diag_iterator; 126 127namespace { 128 129/// StandardDirective - Directive with string matching. 130/// 131class StandardDirective : public Directive { 132public: 133 StandardDirective(SourceLocation DirectiveLoc, SourceLocation DiagnosticLoc, 134 StringRef Text, unsigned Min, unsigned Max) 135 : Directive(DirectiveLoc, DiagnosticLoc, Text, Min, Max) { } 136 137 virtual bool isValid(std::string &Error) { 138 // all strings are considered valid; even empty ones 139 return true; 140 } 141 142 virtual bool match(StringRef S) { 143 return S.find(Text) != StringRef::npos; 144 } 145}; 146 147/// RegexDirective - Directive with regular-expression matching. 148/// 149class RegexDirective : public Directive { 150public: 151 RegexDirective(SourceLocation DirectiveLoc, SourceLocation DiagnosticLoc, 152 StringRef Text, unsigned Min, unsigned Max) 153 : Directive(DirectiveLoc, DiagnosticLoc, Text, Min, Max), Regex(Text) { } 154 155 virtual bool isValid(std::string &Error) { 156 if (Regex.isValid(Error)) 157 return true; 158 return false; 159 } 160 161 virtual bool match(StringRef S) { 162 return Regex.match(S); 163 } 164 165private: 166 llvm::Regex Regex; 167}; 168 169class ParseHelper 170{ 171public: 172 ParseHelper(StringRef S) 173 : Begin(S.begin()), End(S.end()), C(Begin), P(Begin), PEnd(NULL) { } 174 175 // Return true if string literal is next. 176 bool Next(StringRef S) { 177 P = C; 178 PEnd = C + S.size(); 179 if (PEnd > End) 180 return false; 181 return !memcmp(P, S.data(), S.size()); 182 } 183 184 // Return true if number is next. 185 // Output N only if number is next. 186 bool Next(unsigned &N) { 187 unsigned TMP = 0; 188 P = C; 189 for (; P < End && P[0] >= '0' && P[0] <= '9'; ++P) { 190 TMP *= 10; 191 TMP += P[0] - '0'; 192 } 193 if (P == C) 194 return false; 195 PEnd = P; 196 N = TMP; 197 return true; 198 } 199 200 // Return true if string literal is found. 201 // When true, P marks begin-position of S in content. 202 bool Search(StringRef S) { 203 P = std::search(C, End, S.begin(), S.end()); 204 PEnd = P + S.size(); 205 return P != End; 206 } 207 208 // Advance 1-past previous next/search. 209 // Behavior is undefined if previous next/search failed. 210 bool Advance() { 211 C = PEnd; 212 return C < End; 213 } 214 215 // Skip zero or more whitespace. 216 void SkipWhitespace() { 217 for (; C < End && isspace(*C); ++C) 218 ; 219 } 220 221 // Return true if EOF reached. 222 bool Done() { 223 return !(C < End); 224 } 225 226 const char * const Begin; // beginning of expected content 227 const char * const End; // end of expected content (1-past) 228 const char *C; // position of next char in content 229 const char *P; 230 231private: 232 const char *PEnd; // previous next/search subject end (1-past) 233}; 234 235} // namespace anonymous 236 237/// ParseDirective - Go through the comment and see if it indicates expected 238/// diagnostics. If so, then put them in the appropriate directive list. 239/// 240/// Returns true if any valid directives were found. 241static bool ParseDirective(StringRef S, ExpectedData *ED, SourceManager &SM, 242 SourceLocation Pos, DiagnosticsEngine &Diags) { 243 // A single comment may contain multiple directives. 244 bool FoundDirective = false; 245 for (ParseHelper PH(S); !PH.Done();) { 246 // Search for token: expected 247 if (!PH.Search("expected")) 248 break; 249 PH.Advance(); 250 251 // Next token: - 252 if (!PH.Next("-")) 253 continue; 254 PH.Advance(); 255 256 // Next token: { error | warning | note } 257 DirectiveList* DL = NULL; 258 if (PH.Next("error")) 259 DL = ED ? &ED->Errors : NULL; 260 else if (PH.Next("warning")) 261 DL = ED ? &ED->Warnings : NULL; 262 else if (PH.Next("note")) 263 DL = ED ? &ED->Notes : NULL; 264 else 265 continue; 266 PH.Advance(); 267 268 // If a directive has been found but we're not interested 269 // in storing the directive information, return now. 270 if (!DL) 271 return true; 272 273 // Default directive kind. 274 bool RegexKind = false; 275 const char* KindStr = "string"; 276 277 // Next optional token: - 278 if (PH.Next("-re")) { 279 PH.Advance(); 280 RegexKind = true; 281 KindStr = "regex"; 282 } 283 284 // Next optional token: @ 285 SourceLocation ExpectedLoc; 286 if (!PH.Next("@")) { 287 ExpectedLoc = Pos; 288 } else { 289 PH.Advance(); 290 unsigned Line = 0; 291 bool FoundPlus = PH.Next("+"); 292 if (FoundPlus || PH.Next("-")) { 293 // Relative to current line. 294 PH.Advance(); 295 bool Invalid = false; 296 unsigned ExpectedLine = SM.getSpellingLineNumber(Pos, &Invalid); 297 if (!Invalid && PH.Next(Line) && (FoundPlus || Line < ExpectedLine)) { 298 if (FoundPlus) ExpectedLine += Line; 299 else ExpectedLine -= Line; 300 ExpectedLoc = SM.translateLineCol(SM.getFileID(Pos), ExpectedLine, 1); 301 } 302 } else { 303 // Absolute line number. 304 if (PH.Next(Line) && Line > 0) 305 ExpectedLoc = SM.translateLineCol(SM.getFileID(Pos), Line, 1); 306 } 307 308 if (ExpectedLoc.isInvalid()) { 309 Diags.Report(Pos.getLocWithOffset(PH.C-PH.Begin), 310 diag::err_verify_missing_line) << KindStr; 311 continue; 312 } 313 PH.Advance(); 314 } 315 316 // Skip optional whitespace. 317 PH.SkipWhitespace(); 318 319 // Next optional token: positive integer or a '+'. 320 unsigned Min = 1; 321 unsigned Max = 1; 322 if (PH.Next(Min)) { 323 PH.Advance(); 324 // A positive integer can be followed by a '+' meaning min 325 // or more, or by a '-' meaning a range from min to max. 326 if (PH.Next("+")) { 327 Max = Directive::MaxCount; 328 PH.Advance(); 329 } else if (PH.Next("-")) { 330 PH.Advance(); 331 if (!PH.Next(Max) || Max < Min) { 332 Diags.Report(Pos.getLocWithOffset(PH.C-PH.Begin), 333 diag::err_verify_invalid_range) << KindStr; 334 continue; 335 } 336 PH.Advance(); 337 } else { 338 Max = Min; 339 } 340 } else if (PH.Next("+")) { 341 // '+' on its own means "1 or more". 342 Max = Directive::MaxCount; 343 PH.Advance(); 344 } 345 346 // Skip optional whitespace. 347 PH.SkipWhitespace(); 348 349 // Next token: {{ 350 if (!PH.Next("{{")) { 351 Diags.Report(Pos.getLocWithOffset(PH.C-PH.Begin), 352 diag::err_verify_missing_start) << KindStr; 353 continue; 354 } 355 PH.Advance(); 356 const char* const ContentBegin = PH.C; // mark content begin 357 358 // Search for token: }} 359 if (!PH.Search("}}")) { 360 Diags.Report(Pos.getLocWithOffset(PH.C-PH.Begin), 361 diag::err_verify_missing_end) << KindStr; 362 continue; 363 } 364 const char* const ContentEnd = PH.P; // mark content end 365 PH.Advance(); 366 367 // Build directive text; convert \n to newlines. 368 std::string Text; 369 StringRef NewlineStr = "\\n"; 370 StringRef Content(ContentBegin, ContentEnd-ContentBegin); 371 size_t CPos = 0; 372 size_t FPos; 373 while ((FPos = Content.find(NewlineStr, CPos)) != StringRef::npos) { 374 Text += Content.substr(CPos, FPos-CPos); 375 Text += '\n'; 376 CPos = FPos + NewlineStr.size(); 377 } 378 if (Text.empty()) 379 Text.assign(ContentBegin, ContentEnd); 380 381 // Construct new directive. 382 Directive *D = Directive::create(RegexKind, Pos, ExpectedLoc, Text, 383 Min, Max); 384 std::string Error; 385 if (D->isValid(Error)) { 386 DL->push_back(D); 387 FoundDirective = true; 388 } else { 389 Diags.Report(Pos.getLocWithOffset(ContentBegin-PH.Begin), 390 diag::err_verify_invalid_content) 391 << KindStr << Error; 392 } 393 } 394 395 return FoundDirective; 396} 397 398/// HandleComment - Hook into the preprocessor and extract comments containing 399/// expected errors and warnings. 400bool VerifyDiagnosticConsumer::HandleComment(Preprocessor &PP, 401 SourceRange Comment) { 402 SourceManager &SM = PP.getSourceManager(); 403 SourceLocation CommentBegin = Comment.getBegin(); 404 405 const char *CommentRaw = SM.getCharacterData(CommentBegin); 406 StringRef C(CommentRaw, SM.getCharacterData(Comment.getEnd()) - CommentRaw); 407 408 if (C.empty()) 409 return false; 410 411 // Fold any "\<EOL>" sequences 412 size_t loc = C.find('\\'); 413 if (loc == StringRef::npos) { 414 ParseDirective(C, &ED, SM, CommentBegin, PP.getDiagnostics()); 415 return false; 416 } 417 418 std::string C2; 419 C2.reserve(C.size()); 420 421 for (size_t last = 0;; loc = C.find('\\', last)) { 422 if (loc == StringRef::npos || loc == C.size()) { 423 C2 += C.substr(last); 424 break; 425 } 426 C2 += C.substr(last, loc-last); 427 last = loc + 1; 428 429 if (C[last] == '\n' || C[last] == '\r') { 430 ++last; 431 432 // Escape \r\n or \n\r, but not \n\n. 433 if (last < C.size()) 434 if (C[last] == '\n' || C[last] == '\r') 435 if (C[last] != C[last-1]) 436 ++last; 437 } else { 438 // This was just a normal backslash. 439 C2 += '\\'; 440 } 441 } 442 443 if (!C2.empty()) 444 ParseDirective(C2, &ED, SM, CommentBegin, PP.getDiagnostics()); 445 return false; 446} 447 448#ifndef NDEBUG 449/// \brief Lex the specified source file to determine whether it contains 450/// any expected-* directives. As a Lexer is used rather than a full-blown 451/// Preprocessor, directives inside skipped #if blocks will still be found. 452/// 453/// \return true if any directives were found. 454static bool findDirectives(const Preprocessor &PP, FileID FID) { 455 // Create a raw lexer to pull all the comments out of FID. 456 if (FID.isInvalid()) 457 return false; 458 459 SourceManager& SM = PP.getSourceManager(); 460 // Create a lexer to lex all the tokens of the main file in raw mode. 461 const llvm::MemoryBuffer *FromFile = SM.getBuffer(FID); 462 Lexer RawLex(FID, FromFile, SM, PP.getLangOpts()); 463 464 // Return comments as tokens, this is how we find expected diagnostics. 465 RawLex.SetCommentRetentionState(true); 466 467 Token Tok; 468 Tok.setKind(tok::comment); 469 bool Found = false; 470 while (Tok.isNot(tok::eof)) { 471 RawLex.Lex(Tok); 472 if (!Tok.is(tok::comment)) continue; 473 474 std::string Comment = PP.getSpelling(Tok); 475 if (Comment.empty()) continue; 476 477 // Find all expected errors/warnings/notes. 478 Found |= ParseDirective(Comment, 0, SM, Tok.getLocation(), 479 PP.getDiagnostics()); 480 } 481 return Found; 482} 483#endif // !NDEBUG 484 485/// \brief Takes a list of diagnostics that have been generated but not matched 486/// by an expected-* directive and produces a diagnostic to the user from this. 487static unsigned PrintUnexpected(DiagnosticsEngine &Diags, SourceManager *SourceMgr, 488 const_diag_iterator diag_begin, 489 const_diag_iterator diag_end, 490 const char *Kind) { 491 if (diag_begin == diag_end) return 0; 492 493 SmallString<256> Fmt; 494 llvm::raw_svector_ostream OS(Fmt); 495 for (const_diag_iterator I = diag_begin, E = diag_end; I != E; ++I) { 496 if (I->first.isInvalid() || !SourceMgr) 497 OS << "\n (frontend)"; 498 else 499 OS << "\n Line " << SourceMgr->getPresumedLineNumber(I->first); 500 OS << ": " << I->second; 501 } 502 503 Diags.Report(diag::err_verify_inconsistent_diags).setForceEmit() 504 << Kind << /*Unexpected=*/true << OS.str(); 505 return std::distance(diag_begin, diag_end); 506} 507 508/// \brief Takes a list of diagnostics that were expected to have been generated 509/// but were not and produces a diagnostic to the user from this. 510static unsigned PrintExpected(DiagnosticsEngine &Diags, SourceManager &SourceMgr, 511 DirectiveList &DL, const char *Kind) { 512 if (DL.empty()) 513 return 0; 514 515 SmallString<256> Fmt; 516 llvm::raw_svector_ostream OS(Fmt); 517 for (DirectiveList::iterator I = DL.begin(), E = DL.end(); I != E; ++I) { 518 Directive &D = **I; 519 OS << "\n Line " << SourceMgr.getPresumedLineNumber(D.DiagnosticLoc); 520 if (D.DirectiveLoc != D.DiagnosticLoc) 521 OS << " (directive at " 522 << SourceMgr.getFilename(D.DirectiveLoc) << ":" 523 << SourceMgr.getPresumedLineNumber(D.DirectiveLoc) << ")"; 524 OS << ": " << D.Text; 525 } 526 527 Diags.Report(diag::err_verify_inconsistent_diags).setForceEmit() 528 << Kind << /*Unexpected=*/false << OS.str(); 529 return DL.size(); 530} 531 532/// CheckLists - Compare expected to seen diagnostic lists and return the 533/// the difference between them. 534/// 535static unsigned CheckLists(DiagnosticsEngine &Diags, SourceManager &SourceMgr, 536 const char *Label, 537 DirectiveList &Left, 538 const_diag_iterator d2_begin, 539 const_diag_iterator d2_end) { 540 DirectiveList LeftOnly; 541 DiagList Right(d2_begin, d2_end); 542 543 for (DirectiveList::iterator I = Left.begin(), E = Left.end(); I != E; ++I) { 544 Directive& D = **I; 545 unsigned LineNo1 = SourceMgr.getPresumedLineNumber(D.DiagnosticLoc); 546 547 for (unsigned i = 0; i < D.Max; ++i) { 548 DiagList::iterator II, IE; 549 for (II = Right.begin(), IE = Right.end(); II != IE; ++II) { 550 unsigned LineNo2 = SourceMgr.getPresumedLineNumber(II->first); 551 if (LineNo1 != LineNo2) 552 continue; 553 554 const std::string &RightText = II->second; 555 if (D.match(RightText)) 556 break; 557 } 558 if (II == IE) { 559 // Not found. 560 if (i >= D.Min) break; 561 LeftOnly.push_back(*I); 562 } else { 563 // Found. The same cannot be found twice. 564 Right.erase(II); 565 } 566 } 567 } 568 // Now all that's left in Right are those that were not matched. 569 unsigned num = PrintExpected(Diags, SourceMgr, LeftOnly, Label); 570 num += PrintUnexpected(Diags, &SourceMgr, Right.begin(), Right.end(), Label); 571 return num; 572} 573 574/// CheckResults - This compares the expected results to those that 575/// were actually reported. It emits any discrepencies. Return "true" if there 576/// were problems. Return "false" otherwise. 577/// 578static unsigned CheckResults(DiagnosticsEngine &Diags, SourceManager &SourceMgr, 579 const TextDiagnosticBuffer &Buffer, 580 ExpectedData &ED) { 581 // We want to capture the delta between what was expected and what was 582 // seen. 583 // 584 // Expected \ Seen - set expected but not seen 585 // Seen \ Expected - set seen but not expected 586 unsigned NumProblems = 0; 587 588 // See if there are error mismatches. 589 NumProblems += CheckLists(Diags, SourceMgr, "error", ED.Errors, 590 Buffer.err_begin(), Buffer.err_end()); 591 592 // See if there are warning mismatches. 593 NumProblems += CheckLists(Diags, SourceMgr, "warning", ED.Warnings, 594 Buffer.warn_begin(), Buffer.warn_end()); 595 596 // See if there are note mismatches. 597 NumProblems += CheckLists(Diags, SourceMgr, "note", ED.Notes, 598 Buffer.note_begin(), Buffer.note_end()); 599 600 return NumProblems; 601} 602 603void VerifyDiagnosticConsumer::CheckDiagnostics() { 604 // Ensure any diagnostics go to the primary client. 605 bool OwnsCurClient = Diags.ownsClient(); 606 DiagnosticConsumer *CurClient = Diags.takeClient(); 607 Diags.setClient(PrimaryClient, false); 608 609 // If we have a preprocessor, scan the source for expected diagnostic 610 // markers. If not then any diagnostics are unexpected. 611 if (CurrentPreprocessor) { 612 SourceManager &SM = CurrentPreprocessor->getSourceManager(); 613 614#ifndef NDEBUG 615 // In a debug build, scan through any files that may have been missed 616 // during parsing and issue a fatal error if directives are contained 617 // within these files. If a fatal error occurs, this suggests that 618 // this file is being parsed separately from the main file. 619 HeaderSearch &HS = CurrentPreprocessor->getHeaderSearchInfo(); 620 for (FilesWithDiagnosticsSet::iterator I = FilesWithDiagnostics.begin(), 621 End = FilesWithDiagnostics.end(); 622 I != End; ++I) { 623 const FileEntry *E = SM.getFileEntryForID(*I); 624 // Don't check files already parsed or those handled as modules. 625 if (E && (FilesParsedForDirectives.count(E) 626 || HS.findModuleForHeader(E))) 627 continue; 628 629 if (findDirectives(*CurrentPreprocessor, *I)) 630 llvm::report_fatal_error(Twine("-verify directives found after rather" 631 " than during normal parsing of ", 632 StringRef(E ? E->getName() : "(unknown)"))); 633 } 634#endif 635 636 // Check that the expected diagnostics occurred. 637 NumErrors += CheckResults(Diags, SM, *Buffer, ED); 638 } else { 639 NumErrors += (PrintUnexpected(Diags, 0, Buffer->err_begin(), 640 Buffer->err_end(), "error") + 641 PrintUnexpected(Diags, 0, Buffer->warn_begin(), 642 Buffer->warn_end(), "warn") + 643 PrintUnexpected(Diags, 0, Buffer->note_begin(), 644 Buffer->note_end(), "note")); 645 } 646 647 Diags.takeClient(); 648 Diags.setClient(CurClient, OwnsCurClient); 649 650 // Reset the buffer, we have processed all the diagnostics in it. 651 Buffer.reset(new TextDiagnosticBuffer()); 652 ED.Errors.clear(); 653 ED.Warnings.clear(); 654 ED.Notes.clear(); 655} 656 657DiagnosticConsumer * 658VerifyDiagnosticConsumer::clone(DiagnosticsEngine &Diags) const { 659 if (!Diags.getClient()) 660 Diags.setClient(PrimaryClient->clone(Diags)); 661 662 return new VerifyDiagnosticConsumer(Diags); 663} 664 665Directive *Directive::create(bool RegexKind, SourceLocation DirectiveLoc, 666 SourceLocation DiagnosticLoc, StringRef Text, 667 unsigned Min, unsigned Max) { 668 if (RegexKind) 669 return new RegexDirective(DirectiveLoc, DiagnosticLoc, Text, Min, Max); 670 return new StandardDirective(DirectiveLoc, DiagnosticLoc, Text, Min, Max); 671} 672