Path: blob/main/contrib/llvm-project/clang/lib/Frontend/VerifyDiagnosticConsumer.cpp
35233 views
//===- VerifyDiagnosticConsumer.cpp - Verifying Diagnostic Client ---------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// This is a concrete diagnostic client, which buffers the diagnostic messages.9//10//===----------------------------------------------------------------------===//1112#include "clang/Frontend/VerifyDiagnosticConsumer.h"13#include "clang/Basic/CharInfo.h"14#include "clang/Basic/Diagnostic.h"15#include "clang/Basic/DiagnosticOptions.h"16#include "clang/Basic/FileManager.h"17#include "clang/Basic/LLVM.h"18#include "clang/Basic/SourceLocation.h"19#include "clang/Basic/SourceManager.h"20#include "clang/Basic/TokenKinds.h"21#include "clang/Frontend/FrontendDiagnostic.h"22#include "clang/Frontend/TextDiagnosticBuffer.h"23#include "clang/Lex/HeaderSearch.h"24#include "clang/Lex/Lexer.h"25#include "clang/Lex/PPCallbacks.h"26#include "clang/Lex/Preprocessor.h"27#include "clang/Lex/Token.h"28#include "llvm/ADT/STLExtras.h"29#include "llvm/ADT/SmallPtrSet.h"30#include "llvm/ADT/SmallString.h"31#include "llvm/ADT/StringRef.h"32#include "llvm/ADT/Twine.h"33#include "llvm/Support/ErrorHandling.h"34#include "llvm/Support/Regex.h"35#include "llvm/Support/raw_ostream.h"36#include <algorithm>37#include <cassert>38#include <cstddef>39#include <cstring>40#include <iterator>41#include <memory>42#include <string>43#include <utility>44#include <vector>4546using namespace clang;4748using Directive = VerifyDiagnosticConsumer::Directive;49using DirectiveList = VerifyDiagnosticConsumer::DirectiveList;50using ExpectedData = VerifyDiagnosticConsumer::ExpectedData;5152#ifndef NDEBUG5354namespace {5556class VerifyFileTracker : public PPCallbacks {57VerifyDiagnosticConsumer &Verify;58SourceManager &SM;5960public:61VerifyFileTracker(VerifyDiagnosticConsumer &Verify, SourceManager &SM)62: Verify(Verify), SM(SM) {}6364/// Hook into the preprocessor and update the list of parsed65/// files when the preprocessor indicates a new file is entered.66void FileChanged(SourceLocation Loc, FileChangeReason Reason,67SrcMgr::CharacteristicKind FileType,68FileID PrevFID) override {69Verify.UpdateParsedFileStatus(SM, SM.getFileID(Loc),70VerifyDiagnosticConsumer::IsParsed);71}72};7374} // namespace7576#endif7778//===----------------------------------------------------------------------===//79// Checking diagnostics implementation.80//===----------------------------------------------------------------------===//8182using DiagList = TextDiagnosticBuffer::DiagList;83using const_diag_iterator = TextDiagnosticBuffer::const_iterator;8485namespace {8687/// StandardDirective - Directive with string matching.88class StandardDirective : public Directive {89public:90StandardDirective(SourceLocation DirectiveLoc, SourceLocation DiagnosticLoc,91bool MatchAnyFileAndLine, bool MatchAnyLine, StringRef Text,92unsigned Min, unsigned Max)93: Directive(DirectiveLoc, DiagnosticLoc, MatchAnyFileAndLine,94MatchAnyLine, Text, Min, Max) {}9596bool isValid(std::string &Error) override {97// all strings are considered valid; even empty ones98return true;99}100101bool match(StringRef S) override { return S.contains(Text); }102};103104/// RegexDirective - Directive with regular-expression matching.105class RegexDirective : public Directive {106public:107RegexDirective(SourceLocation DirectiveLoc, SourceLocation DiagnosticLoc,108bool MatchAnyFileAndLine, bool MatchAnyLine, StringRef Text,109unsigned Min, unsigned Max, StringRef RegexStr)110: Directive(DirectiveLoc, DiagnosticLoc, MatchAnyFileAndLine,111MatchAnyLine, Text, Min, Max),112Regex(RegexStr) {}113114bool isValid(std::string &Error) override {115return Regex.isValid(Error);116}117118bool match(StringRef S) override {119return Regex.match(S);120}121122private:123llvm::Regex Regex;124};125126class ParseHelper127{128public:129ParseHelper(StringRef S)130: Begin(S.begin()), End(S.end()), C(Begin), P(Begin) {}131132// Return true if string literal is next.133bool Next(StringRef S) {134P = C;135PEnd = C + S.size();136if (PEnd > End)137return false;138return memcmp(P, S.data(), S.size()) == 0;139}140141// Return true if number is next.142// Output N only if number is next.143bool Next(unsigned &N) {144unsigned TMP = 0;145P = C;146PEnd = P;147for (; PEnd < End && *PEnd >= '0' && *PEnd <= '9'; ++PEnd) {148TMP *= 10;149TMP += *PEnd - '0';150}151if (PEnd == C)152return false;153N = TMP;154return true;155}156157// Return true if a marker is next.158// A marker is the longest match for /#[A-Za-z0-9_-]+/.159bool NextMarker() {160P = C;161if (P == End || *P != '#')162return false;163PEnd = P;164++PEnd;165while ((isAlphanumeric(*PEnd) || *PEnd == '-' || *PEnd == '_') &&166PEnd < End)167++PEnd;168return PEnd > P + 1;169}170171// Return true if string literal S is matched in content.172// When true, P marks begin-position of the match, and calling Advance sets C173// to end-position of the match.174// If S is the empty string, then search for any letter instead (makes sense175// with FinishDirectiveToken=true).176// If EnsureStartOfWord, then skip matches that don't start a new word.177// If FinishDirectiveToken, then assume the match is the start of a comment178// directive for -verify, and extend the match to include the entire first179// token of that directive.180bool Search(StringRef S, bool EnsureStartOfWord = false,181bool FinishDirectiveToken = false) {182do {183if (!S.empty()) {184P = std::search(C, End, S.begin(), S.end());185PEnd = P + S.size();186}187else {188P = C;189while (P != End && !isLetter(*P))190++P;191PEnd = P + 1;192}193if (P == End)194break;195// If not start of word but required, skip and search again.196if (EnsureStartOfWord197// Check if string literal starts a new word.198&& !(P == Begin || isWhitespace(P[-1])199// Or it could be preceded by the start of a comment.200|| (P > (Begin + 1) && (P[-1] == '/' || P[-1] == '*')201&& P[-2] == '/')))202continue;203if (FinishDirectiveToken) {204while (PEnd != End && (isAlphanumeric(*PEnd)205|| *PEnd == '-' || *PEnd == '_'))206++PEnd;207// Put back trailing digits and hyphens to be parsed later as a count208// or count range. Because -verify prefixes must start with letters,209// we know the actual directive we found starts with a letter, so210// we won't put back the entire directive word and thus record an empty211// string.212assert(isLetter(*P) && "-verify prefix must start with a letter");213while (isDigit(PEnd[-1]) || PEnd[-1] == '-')214--PEnd;215}216return true;217} while (Advance());218return false;219}220221// Return true if a CloseBrace that closes the OpenBrace at the current nest222// level is found. When true, P marks begin-position of CloseBrace.223bool SearchClosingBrace(StringRef OpenBrace, StringRef CloseBrace) {224unsigned Depth = 1;225P = C;226while (P < End) {227StringRef S(P, End - P);228if (S.starts_with(OpenBrace)) {229++Depth;230P += OpenBrace.size();231} else if (S.starts_with(CloseBrace)) {232--Depth;233if (Depth == 0) {234PEnd = P + CloseBrace.size();235return true;236}237P += CloseBrace.size();238} else {239++P;240}241}242return false;243}244245// Advance 1-past previous next/search.246// Behavior is undefined if previous next/search failed.247bool Advance() {248C = PEnd;249return C < End;250}251252// Return the text matched by the previous next/search.253// Behavior is undefined if previous next/search failed.254StringRef Match() { return StringRef(P, PEnd - P); }255256// Skip zero or more whitespace.257void SkipWhitespace() {258for (; C < End && isWhitespace(*C); ++C)259;260}261262// Return true if EOF reached.263bool Done() {264return !(C < End);265}266267// Beginning of expected content.268const char * const Begin;269270// End of expected content (1-past).271const char * const End;272273// Position of next char in content.274const char *C;275276// Previous next/search subject start.277const char *P;278279private:280// Previous next/search subject end (1-past).281const char *PEnd = nullptr;282};283284// The information necessary to create a directive.285struct UnattachedDirective {286DirectiveList *DL = nullptr;287bool RegexKind = false;288SourceLocation DirectivePos, ContentBegin;289std::string Text;290unsigned Min = 1, Max = 1;291};292293// Attach the specified directive to the line of code indicated by294// \p ExpectedLoc.295void attachDirective(DiagnosticsEngine &Diags, const UnattachedDirective &UD,296SourceLocation ExpectedLoc,297bool MatchAnyFileAndLine = false,298bool MatchAnyLine = false) {299// Construct new directive.300std::unique_ptr<Directive> D = Directive::create(301UD.RegexKind, UD.DirectivePos, ExpectedLoc, MatchAnyFileAndLine,302MatchAnyLine, UD.Text, UD.Min, UD.Max);303304std::string Error;305if (!D->isValid(Error)) {306Diags.Report(UD.ContentBegin, diag::err_verify_invalid_content)307<< (UD.RegexKind ? "regex" : "string") << Error;308}309310UD.DL->push_back(std::move(D));311}312313} // anonymous314315// Tracker for markers in the input files. A marker is a comment of the form316//317// n = 123; // #123318//319// ... that can be referred to by a later expected-* directive:320//321// // expected-error@#123 {{undeclared identifier 'n'}}322//323// Marker declarations must be at the start of a comment or preceded by324// whitespace to distinguish them from uses of markers in directives.325class VerifyDiagnosticConsumer::MarkerTracker {326DiagnosticsEngine &Diags;327328struct Marker {329SourceLocation DefLoc;330SourceLocation RedefLoc;331SourceLocation UseLoc;332};333llvm::StringMap<Marker> Markers;334335// Directives that couldn't be created yet because they name an unknown336// marker.337llvm::StringMap<llvm::SmallVector<UnattachedDirective, 2>> DeferredDirectives;338339public:340MarkerTracker(DiagnosticsEngine &Diags) : Diags(Diags) {}341342// Register a marker.343void addMarker(StringRef MarkerName, SourceLocation Pos) {344auto InsertResult = Markers.insert(345{MarkerName, Marker{Pos, SourceLocation(), SourceLocation()}});346347Marker &M = InsertResult.first->second;348if (!InsertResult.second) {349// Marker was redefined.350M.RedefLoc = Pos;351} else {352// First definition: build any deferred directives.353auto Deferred = DeferredDirectives.find(MarkerName);354if (Deferred != DeferredDirectives.end()) {355for (auto &UD : Deferred->second) {356if (M.UseLoc.isInvalid())357M.UseLoc = UD.DirectivePos;358attachDirective(Diags, UD, Pos);359}360DeferredDirectives.erase(Deferred);361}362}363}364365// Register a directive at the specified marker.366void addDirective(StringRef MarkerName, const UnattachedDirective &UD) {367auto MarkerIt = Markers.find(MarkerName);368if (MarkerIt != Markers.end()) {369Marker &M = MarkerIt->second;370if (M.UseLoc.isInvalid())371M.UseLoc = UD.DirectivePos;372return attachDirective(Diags, UD, M.DefLoc);373}374DeferredDirectives[MarkerName].push_back(UD);375}376377// Ensure we have no remaining deferred directives, and no378// multiply-defined-and-used markers.379void finalize() {380for (auto &MarkerInfo : Markers) {381StringRef Name = MarkerInfo.first();382Marker &M = MarkerInfo.second;383if (M.RedefLoc.isValid() && M.UseLoc.isValid()) {384Diags.Report(M.UseLoc, diag::err_verify_ambiguous_marker) << Name;385Diags.Report(M.DefLoc, diag::note_verify_ambiguous_marker) << Name;386Diags.Report(M.RedefLoc, diag::note_verify_ambiguous_marker) << Name;387}388}389390for (auto &DeferredPair : DeferredDirectives) {391Diags.Report(DeferredPair.second.front().DirectivePos,392diag::err_verify_no_such_marker)393<< DeferredPair.first();394}395}396};397398static std::string DetailedErrorString(const DiagnosticsEngine &Diags) {399if (Diags.getDiagnosticOptions().VerifyPrefixes.empty())400return "expected";401return *Diags.getDiagnosticOptions().VerifyPrefixes.begin();402}403404/// ParseDirective - Go through the comment and see if it indicates expected405/// diagnostics. If so, then put them in the appropriate directive list.406///407/// Returns true if any valid directives were found.408static bool ParseDirective(StringRef S, ExpectedData *ED, SourceManager &SM,409Preprocessor *PP, SourceLocation Pos,410VerifyDiagnosticConsumer::DirectiveStatus &Status,411VerifyDiagnosticConsumer::MarkerTracker &Markers) {412DiagnosticsEngine &Diags = PP ? PP->getDiagnostics() : SM.getDiagnostics();413414// First, scan the comment looking for markers.415for (ParseHelper PH(S); !PH.Done();) {416if (!PH.Search("#", true))417break;418PH.C = PH.P;419if (!PH.NextMarker()) {420PH.Next("#");421PH.Advance();422continue;423}424PH.Advance();425Markers.addMarker(PH.Match(), Pos);426}427428// A single comment may contain multiple directives.429bool FoundDirective = false;430for (ParseHelper PH(S); !PH.Done();) {431// Search for the initial directive token.432// If one prefix, save time by searching only for its directives.433// Otherwise, search for any potential directive token and check it later.434const auto &Prefixes = Diags.getDiagnosticOptions().VerifyPrefixes;435if (!(Prefixes.size() == 1 ? PH.Search(*Prefixes.begin(), true, true)436: PH.Search("", true, true)))437break;438439StringRef DToken = PH.Match();440PH.Advance();441442// Default directive kind.443UnattachedDirective D;444const char *KindStr = "string";445446// Parse the initial directive token in reverse so we can easily determine447// its exact actual prefix. If we were to parse it from the front instead,448// it would be harder to determine where the prefix ends because there449// might be multiple matching -verify prefixes because some might prefix450// others.451452// Regex in initial directive token: -re453if (DToken.consume_back("-re")) {454D.RegexKind = true;455KindStr = "regex";456}457458// Type in initial directive token: -{error|warning|note|no-diagnostics}459bool NoDiag = false;460StringRef DType;461if (DToken.ends_with(DType = "-error"))462D.DL = ED ? &ED->Errors : nullptr;463else if (DToken.ends_with(DType = "-warning"))464D.DL = ED ? &ED->Warnings : nullptr;465else if (DToken.ends_with(DType = "-remark"))466D.DL = ED ? &ED->Remarks : nullptr;467else if (DToken.ends_with(DType = "-note"))468D.DL = ED ? &ED->Notes : nullptr;469else if (DToken.ends_with(DType = "-no-diagnostics")) {470NoDiag = true;471if (D.RegexKind)472continue;473} else474continue;475DToken = DToken.substr(0, DToken.size()-DType.size());476477// What's left in DToken is the actual prefix. That might not be a -verify478// prefix even if there is only one -verify prefix (for example, the full479// DToken is foo-bar-warning, but foo is the only -verify prefix).480if (!std::binary_search(Prefixes.begin(), Prefixes.end(), DToken))481continue;482483if (NoDiag) {484if (Status == VerifyDiagnosticConsumer::HasOtherExpectedDirectives)485Diags.Report(Pos, diag::err_verify_invalid_no_diags)486<< DetailedErrorString(Diags) << /*IsExpectedNoDiagnostics=*/true;487else488Status = VerifyDiagnosticConsumer::HasExpectedNoDiagnostics;489continue;490}491if (Status == VerifyDiagnosticConsumer::HasExpectedNoDiagnostics) {492Diags.Report(Pos, diag::err_verify_invalid_no_diags)493<< DetailedErrorString(Diags) << /*IsExpectedNoDiagnostics=*/false;494continue;495}496Status = VerifyDiagnosticConsumer::HasOtherExpectedDirectives;497498// If a directive has been found but we're not interested499// in storing the directive information, return now.500if (!D.DL)501return true;502503// Next optional token: @504SourceLocation ExpectedLoc;505StringRef Marker;506bool MatchAnyFileAndLine = false;507bool MatchAnyLine = false;508if (!PH.Next("@")) {509ExpectedLoc = Pos;510} else {511PH.Advance();512unsigned Line = 0;513bool FoundPlus = PH.Next("+");514if (FoundPlus || PH.Next("-")) {515// Relative to current line.516PH.Advance();517bool Invalid = false;518unsigned ExpectedLine = SM.getSpellingLineNumber(Pos, &Invalid);519if (!Invalid && PH.Next(Line) && (FoundPlus || Line < ExpectedLine)) {520if (FoundPlus) ExpectedLine += Line;521else ExpectedLine -= Line;522ExpectedLoc = SM.translateLineCol(SM.getFileID(Pos), ExpectedLine, 1);523}524} else if (PH.Next(Line)) {525// Absolute line number.526if (Line > 0)527ExpectedLoc = SM.translateLineCol(SM.getFileID(Pos), Line, 1);528} else if (PH.NextMarker()) {529Marker = PH.Match();530} else if (PP && PH.Search(":")) {531// Specific source file.532StringRef Filename(PH.C, PH.P-PH.C);533PH.Advance();534535if (Filename == "*") {536MatchAnyFileAndLine = true;537if (!PH.Next("*")) {538Diags.Report(Pos.getLocWithOffset(PH.C - PH.Begin),539diag::err_verify_missing_line)540<< "'*'";541continue;542}543MatchAnyLine = true;544ExpectedLoc = SourceLocation();545} else {546// Lookup file via Preprocessor, like a #include.547OptionalFileEntryRef File =548PP->LookupFile(Pos, Filename, false, nullptr, nullptr, nullptr,549nullptr, nullptr, nullptr, nullptr, nullptr);550if (!File) {551Diags.Report(Pos.getLocWithOffset(PH.C - PH.Begin),552diag::err_verify_missing_file)553<< Filename << KindStr;554continue;555}556557FileID FID = SM.translateFile(*File);558if (FID.isInvalid())559FID = SM.createFileID(*File, Pos, SrcMgr::C_User);560561if (PH.Next(Line) && Line > 0)562ExpectedLoc = SM.translateLineCol(FID, Line, 1);563else if (PH.Next("*")) {564MatchAnyLine = true;565ExpectedLoc = SM.translateLineCol(FID, 1, 1);566}567}568} else if (PH.Next("*")) {569MatchAnyLine = true;570ExpectedLoc = SourceLocation();571}572573if (ExpectedLoc.isInvalid() && !MatchAnyLine && Marker.empty()) {574Diags.Report(Pos.getLocWithOffset(PH.C-PH.Begin),575diag::err_verify_missing_line) << KindStr;576continue;577}578PH.Advance();579}580581// Skip optional whitespace.582PH.SkipWhitespace();583584// Next optional token: positive integer or a '+'.585if (PH.Next(D.Min)) {586PH.Advance();587// A positive integer can be followed by a '+' meaning min588// or more, or by a '-' meaning a range from min to max.589if (PH.Next("+")) {590D.Max = Directive::MaxCount;591PH.Advance();592} else if (PH.Next("-")) {593PH.Advance();594if (!PH.Next(D.Max) || D.Max < D.Min) {595Diags.Report(Pos.getLocWithOffset(PH.C-PH.Begin),596diag::err_verify_invalid_range) << KindStr;597continue;598}599PH.Advance();600} else {601D.Max = D.Min;602}603} else if (PH.Next("+")) {604// '+' on its own means "1 or more".605D.Max = Directive::MaxCount;606PH.Advance();607}608609// Skip optional whitespace.610PH.SkipWhitespace();611612// Next token: {{613if (!PH.Next("{{")) {614Diags.Report(Pos.getLocWithOffset(PH.C-PH.Begin),615diag::err_verify_missing_start) << KindStr;616continue;617}618llvm::SmallString<8> CloseBrace("}}");619const char *const DelimBegin = PH.C;620PH.Advance();621// Count the number of opening braces for `string` kinds622for (; !D.RegexKind && PH.Next("{"); PH.Advance())623CloseBrace += '}';624const char* const ContentBegin = PH.C; // mark content begin625// Search for closing brace626StringRef OpenBrace(DelimBegin, ContentBegin - DelimBegin);627if (!PH.SearchClosingBrace(OpenBrace, CloseBrace)) {628Diags.Report(Pos.getLocWithOffset(PH.C - PH.Begin),629diag::err_verify_missing_end)630<< KindStr << CloseBrace;631continue;632}633const char* const ContentEnd = PH.P; // mark content end634PH.Advance();635636D.DirectivePos = Pos;637D.ContentBegin = Pos.getLocWithOffset(ContentBegin - PH.Begin);638639// Build directive text; convert \n to newlines.640StringRef NewlineStr = "\\n";641StringRef Content(ContentBegin, ContentEnd-ContentBegin);642size_t CPos = 0;643size_t FPos;644while ((FPos = Content.find(NewlineStr, CPos)) != StringRef::npos) {645D.Text += Content.substr(CPos, FPos-CPos);646D.Text += '\n';647CPos = FPos + NewlineStr.size();648}649if (D.Text.empty())650D.Text.assign(ContentBegin, ContentEnd);651652// Check that regex directives contain at least one regex.653if (D.RegexKind && D.Text.find("{{") == StringRef::npos) {654Diags.Report(D.ContentBegin, diag::err_verify_missing_regex) << D.Text;655return false;656}657658if (Marker.empty())659attachDirective(Diags, D, ExpectedLoc, MatchAnyFileAndLine, MatchAnyLine);660else661Markers.addDirective(Marker, D);662FoundDirective = true;663}664665return FoundDirective;666}667668VerifyDiagnosticConsumer::VerifyDiagnosticConsumer(DiagnosticsEngine &Diags_)669: Diags(Diags_), PrimaryClient(Diags.getClient()),670PrimaryClientOwner(Diags.takeClient()),671Buffer(new TextDiagnosticBuffer()), Markers(new MarkerTracker(Diags)),672Status(HasNoDirectives) {673if (Diags.hasSourceManager())674setSourceManager(Diags.getSourceManager());675}676677VerifyDiagnosticConsumer::~VerifyDiagnosticConsumer() {678assert(!ActiveSourceFiles && "Incomplete parsing of source files!");679assert(!CurrentPreprocessor && "CurrentPreprocessor should be invalid!");680SrcManager = nullptr;681CheckDiagnostics();682assert(!Diags.ownsClient() &&683"The VerifyDiagnosticConsumer takes over ownership of the client!");684}685686// DiagnosticConsumer interface.687688void VerifyDiagnosticConsumer::BeginSourceFile(const LangOptions &LangOpts,689const Preprocessor *PP) {690// Attach comment handler on first invocation.691if (++ActiveSourceFiles == 1) {692if (PP) {693CurrentPreprocessor = PP;694this->LangOpts = &LangOpts;695setSourceManager(PP->getSourceManager());696const_cast<Preprocessor *>(PP)->addCommentHandler(this);697#ifndef NDEBUG698// Debug build tracks parsed files.699const_cast<Preprocessor *>(PP)->addPPCallbacks(700std::make_unique<VerifyFileTracker>(*this, *SrcManager));701#endif702}703}704705assert((!PP || CurrentPreprocessor == PP) && "Preprocessor changed!");706PrimaryClient->BeginSourceFile(LangOpts, PP);707}708709void VerifyDiagnosticConsumer::EndSourceFile() {710assert(ActiveSourceFiles && "No active source files!");711PrimaryClient->EndSourceFile();712713// Detach comment handler once last active source file completed.714if (--ActiveSourceFiles == 0) {715if (CurrentPreprocessor)716const_cast<Preprocessor *>(CurrentPreprocessor)->717removeCommentHandler(this);718719// Diagnose any used-but-not-defined markers.720Markers->finalize();721722// Check diagnostics once last file completed.723CheckDiagnostics();724CurrentPreprocessor = nullptr;725LangOpts = nullptr;726}727}728729void VerifyDiagnosticConsumer::HandleDiagnostic(730DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) {731if (Info.hasSourceManager()) {732// If this diagnostic is for a different source manager, ignore it.733if (SrcManager && &Info.getSourceManager() != SrcManager)734return;735736setSourceManager(Info.getSourceManager());737}738739#ifndef NDEBUG740// Debug build tracks unparsed files for possible741// unparsed expected-* directives.742if (SrcManager) {743SourceLocation Loc = Info.getLocation();744if (Loc.isValid()) {745ParsedStatus PS = IsUnparsed;746747Loc = SrcManager->getExpansionLoc(Loc);748FileID FID = SrcManager->getFileID(Loc);749750auto FE = SrcManager->getFileEntryRefForID(FID);751if (FE && CurrentPreprocessor && SrcManager->isLoadedFileID(FID)) {752// If the file is a modules header file it shall not be parsed753// for expected-* directives.754HeaderSearch &HS = CurrentPreprocessor->getHeaderSearchInfo();755if (HS.findModuleForHeader(*FE))756PS = IsUnparsedNoDirectives;757}758759UpdateParsedFileStatus(*SrcManager, FID, PS);760}761}762#endif763764// Send the diagnostic to the buffer, we will check it once we reach the end765// of the source file (or are destructed).766Buffer->HandleDiagnostic(DiagLevel, Info);767}768769/// HandleComment - Hook into the preprocessor and extract comments containing770/// expected errors and warnings.771bool VerifyDiagnosticConsumer::HandleComment(Preprocessor &PP,772SourceRange Comment) {773SourceManager &SM = PP.getSourceManager();774775// If this comment is for a different source manager, ignore it.776if (SrcManager && &SM != SrcManager)777return false;778779SourceLocation CommentBegin = Comment.getBegin();780781const char *CommentRaw = SM.getCharacterData(CommentBegin);782StringRef C(CommentRaw, SM.getCharacterData(Comment.getEnd()) - CommentRaw);783784if (C.empty())785return false;786787// Fold any "\<EOL>" sequences788size_t loc = C.find('\\');789if (loc == StringRef::npos) {790ParseDirective(C, &ED, SM, &PP, CommentBegin, Status, *Markers);791return false;792}793794std::string C2;795C2.reserve(C.size());796797for (size_t last = 0;; loc = C.find('\\', last)) {798if (loc == StringRef::npos || loc == C.size()) {799C2 += C.substr(last);800break;801}802C2 += C.substr(last, loc-last);803last = loc + 1;804805if (C[last] == '\n' || C[last] == '\r') {806++last;807808// Escape \r\n or \n\r, but not \n\n.809if (last < C.size())810if (C[last] == '\n' || C[last] == '\r')811if (C[last] != C[last-1])812++last;813} else {814// This was just a normal backslash.815C2 += '\\';816}817}818819if (!C2.empty())820ParseDirective(C2, &ED, SM, &PP, CommentBegin, Status, *Markers);821return false;822}823824#ifndef NDEBUG825/// Lex the specified source file to determine whether it contains826/// any expected-* directives. As a Lexer is used rather than a full-blown827/// Preprocessor, directives inside skipped #if blocks will still be found.828///829/// \return true if any directives were found.830static bool findDirectives(SourceManager &SM, FileID FID,831const LangOptions &LangOpts) {832// Create a raw lexer to pull all the comments out of FID.833if (FID.isInvalid())834return false;835836// Create a lexer to lex all the tokens of the main file in raw mode.837llvm::MemoryBufferRef FromFile = SM.getBufferOrFake(FID);838Lexer RawLex(FID, FromFile, SM, LangOpts);839840// Return comments as tokens, this is how we find expected diagnostics.841RawLex.SetCommentRetentionState(true);842843Token Tok;844Tok.setKind(tok::comment);845VerifyDiagnosticConsumer::DirectiveStatus Status =846VerifyDiagnosticConsumer::HasNoDirectives;847while (Tok.isNot(tok::eof)) {848RawLex.LexFromRawLexer(Tok);849if (!Tok.is(tok::comment)) continue;850851std::string Comment = RawLex.getSpelling(Tok, SM, LangOpts);852if (Comment.empty()) continue;853854// We don't care about tracking markers for this phase.855VerifyDiagnosticConsumer::MarkerTracker Markers(SM.getDiagnostics());856857// Find first directive.858if (ParseDirective(Comment, nullptr, SM, nullptr, Tok.getLocation(),859Status, Markers))860return true;861}862return false;863}864#endif // !NDEBUG865866/// Takes a list of diagnostics that have been generated but not matched867/// by an expected-* directive and produces a diagnostic to the user from this.868static unsigned PrintUnexpected(DiagnosticsEngine &Diags, SourceManager *SourceMgr,869const_diag_iterator diag_begin,870const_diag_iterator diag_end,871const char *Kind) {872if (diag_begin == diag_end) return 0;873874SmallString<256> Fmt;875llvm::raw_svector_ostream OS(Fmt);876for (const_diag_iterator I = diag_begin, E = diag_end; I != E; ++I) {877if (I->first.isInvalid() || !SourceMgr)878OS << "\n (frontend)";879else {880OS << "\n ";881if (OptionalFileEntryRef File =882SourceMgr->getFileEntryRefForID(SourceMgr->getFileID(I->first)))883OS << " File " << File->getName();884OS << " Line " << SourceMgr->getPresumedLineNumber(I->first);885}886OS << ": " << I->second;887}888889std::string Prefix = *Diags.getDiagnosticOptions().VerifyPrefixes.begin();890std::string KindStr = Prefix + "-" + Kind;891Diags.Report(diag::err_verify_inconsistent_diags).setForceEmit()892<< KindStr << /*Unexpected=*/true << OS.str();893return std::distance(diag_begin, diag_end);894}895896/// Takes a list of diagnostics that were expected to have been generated897/// but were not and produces a diagnostic to the user from this.898static unsigned PrintExpected(DiagnosticsEngine &Diags,899SourceManager &SourceMgr,900std::vector<Directive *> &DL, const char *Kind) {901if (DL.empty())902return 0;903904SmallString<256> Fmt;905llvm::raw_svector_ostream OS(Fmt);906for (const auto *D : DL) {907if (D->DiagnosticLoc.isInvalid() || D->MatchAnyFileAndLine)908OS << "\n File *";909else910OS << "\n File " << SourceMgr.getFilename(D->DiagnosticLoc);911if (D->MatchAnyLine)912OS << " Line *";913else914OS << " Line " << SourceMgr.getPresumedLineNumber(D->DiagnosticLoc);915if (D->DirectiveLoc != D->DiagnosticLoc)916OS << " (directive at "917<< SourceMgr.getFilename(D->DirectiveLoc) << ':'918<< SourceMgr.getPresumedLineNumber(D->DirectiveLoc) << ')';919OS << ": " << D->Text;920}921922std::string Prefix = *Diags.getDiagnosticOptions().VerifyPrefixes.begin();923std::string KindStr = Prefix + "-" + Kind;924Diags.Report(diag::err_verify_inconsistent_diags).setForceEmit()925<< KindStr << /*Unexpected=*/false << OS.str();926return DL.size();927}928929/// Determine whether two source locations come from the same file.930static bool IsFromSameFile(SourceManager &SM, SourceLocation DirectiveLoc,931SourceLocation DiagnosticLoc) {932while (DiagnosticLoc.isMacroID())933DiagnosticLoc = SM.getImmediateMacroCallerLoc(DiagnosticLoc);934935if (SM.isWrittenInSameFile(DirectiveLoc, DiagnosticLoc))936return true;937938const FileEntry *DiagFile = SM.getFileEntryForID(SM.getFileID(DiagnosticLoc));939if (!DiagFile && SM.isWrittenInMainFile(DirectiveLoc))940return true;941942return (DiagFile == SM.getFileEntryForID(SM.getFileID(DirectiveLoc)));943}944945/// CheckLists - Compare expected to seen diagnostic lists and return the946/// the difference between them.947static unsigned CheckLists(DiagnosticsEngine &Diags, SourceManager &SourceMgr,948const char *Label,949DirectiveList &Left,950const_diag_iterator d2_begin,951const_diag_iterator d2_end,952bool IgnoreUnexpected) {953std::vector<Directive *> LeftOnly;954DiagList Right(d2_begin, d2_end);955956for (auto &Owner : Left) {957Directive &D = *Owner;958unsigned LineNo1 = SourceMgr.getPresumedLineNumber(D.DiagnosticLoc);959960for (unsigned i = 0; i < D.Max; ++i) {961DiagList::iterator II, IE;962for (II = Right.begin(), IE = Right.end(); II != IE; ++II) {963if (!D.MatchAnyLine) {964unsigned LineNo2 = SourceMgr.getPresumedLineNumber(II->first);965if (LineNo1 != LineNo2)966continue;967}968969if (!D.DiagnosticLoc.isInvalid() && !D.MatchAnyFileAndLine &&970!IsFromSameFile(SourceMgr, D.DiagnosticLoc, II->first))971continue;972973const std::string &RightText = II->second;974if (D.match(RightText))975break;976}977if (II == IE) {978// Not found.979if (i >= D.Min) break;980LeftOnly.push_back(&D);981} else {982// Found. The same cannot be found twice.983Right.erase(II);984}985}986}987// Now all that's left in Right are those that were not matched.988unsigned num = PrintExpected(Diags, SourceMgr, LeftOnly, Label);989if (!IgnoreUnexpected)990num += PrintUnexpected(Diags, &SourceMgr, Right.begin(), Right.end(), Label);991return num;992}993994/// CheckResults - This compares the expected results to those that995/// were actually reported. It emits any discrepencies. Return "true" if there996/// were problems. Return "false" otherwise.997static unsigned CheckResults(DiagnosticsEngine &Diags, SourceManager &SourceMgr,998const TextDiagnosticBuffer &Buffer,999ExpectedData &ED) {1000// We want to capture the delta between what was expected and what was1001// seen.1002//1003// Expected \ Seen - set expected but not seen1004// Seen \ Expected - set seen but not expected1005unsigned NumProblems = 0;10061007const DiagnosticLevelMask DiagMask =1008Diags.getDiagnosticOptions().getVerifyIgnoreUnexpected();10091010// See if there are error mismatches.1011NumProblems += CheckLists(Diags, SourceMgr, "error", ED.Errors,1012Buffer.err_begin(), Buffer.err_end(),1013bool(DiagnosticLevelMask::Error & DiagMask));10141015// See if there are warning mismatches.1016NumProblems += CheckLists(Diags, SourceMgr, "warning", ED.Warnings,1017Buffer.warn_begin(), Buffer.warn_end(),1018bool(DiagnosticLevelMask::Warning & DiagMask));10191020// See if there are remark mismatches.1021NumProblems += CheckLists(Diags, SourceMgr, "remark", ED.Remarks,1022Buffer.remark_begin(), Buffer.remark_end(),1023bool(DiagnosticLevelMask::Remark & DiagMask));10241025// See if there are note mismatches.1026NumProblems += CheckLists(Diags, SourceMgr, "note", ED.Notes,1027Buffer.note_begin(), Buffer.note_end(),1028bool(DiagnosticLevelMask::Note & DiagMask));10291030return NumProblems;1031}10321033void VerifyDiagnosticConsumer::UpdateParsedFileStatus(SourceManager &SM,1034FileID FID,1035ParsedStatus PS) {1036// Check SourceManager hasn't changed.1037setSourceManager(SM);10381039#ifndef NDEBUG1040if (FID.isInvalid())1041return;10421043OptionalFileEntryRef FE = SM.getFileEntryRefForID(FID);10441045if (PS == IsParsed) {1046// Move the FileID from the unparsed set to the parsed set.1047UnparsedFiles.erase(FID);1048ParsedFiles.insert(std::make_pair(FID, FE ? &FE->getFileEntry() : nullptr));1049} else if (!ParsedFiles.count(FID) && !UnparsedFiles.count(FID)) {1050// Add the FileID to the unparsed set if we haven't seen it before.10511052// Check for directives.1053bool FoundDirectives;1054if (PS == IsUnparsedNoDirectives)1055FoundDirectives = false;1056else1057FoundDirectives = !LangOpts || findDirectives(SM, FID, *LangOpts);10581059// Add the FileID to the unparsed set.1060UnparsedFiles.insert(std::make_pair(FID,1061UnparsedFileStatus(FE, FoundDirectives)));1062}1063#endif1064}10651066void VerifyDiagnosticConsumer::CheckDiagnostics() {1067// Ensure any diagnostics go to the primary client.1068DiagnosticConsumer *CurClient = Diags.getClient();1069std::unique_ptr<DiagnosticConsumer> Owner = Diags.takeClient();1070Diags.setClient(PrimaryClient, false);10711072#ifndef NDEBUG1073// In a debug build, scan through any files that may have been missed1074// during parsing and issue a fatal error if directives are contained1075// within these files. If a fatal error occurs, this suggests that1076// this file is being parsed separately from the main file, in which1077// case consider moving the directives to the correct place, if this1078// is applicable.1079if (!UnparsedFiles.empty()) {1080// Generate a cache of parsed FileEntry pointers for alias lookups.1081llvm::SmallPtrSet<const FileEntry *, 8> ParsedFileCache;1082for (const auto &I : ParsedFiles)1083if (const FileEntry *FE = I.second)1084ParsedFileCache.insert(FE);10851086// Iterate through list of unparsed files.1087for (const auto &I : UnparsedFiles) {1088const UnparsedFileStatus &Status = I.second;1089OptionalFileEntryRef FE = Status.getFile();10901091// Skip files that have been parsed via an alias.1092if (FE && ParsedFileCache.count(*FE))1093continue;10941095// Report a fatal error if this file contained directives.1096if (Status.foundDirectives()) {1097llvm::report_fatal_error("-verify directives found after rather"1098" than during normal parsing of " +1099(FE ? FE->getName() : "(unknown)"));1100}1101}11021103// UnparsedFiles has been processed now, so clear it.1104UnparsedFiles.clear();1105}1106#endif // !NDEBUG11071108if (SrcManager) {1109// Produce an error if no expected-* directives could be found in the1110// source file(s) processed.1111if (Status == HasNoDirectives) {1112Diags.Report(diag::err_verify_no_directives).setForceEmit()1113<< DetailedErrorString(Diags);1114++NumErrors;1115Status = HasNoDirectivesReported;1116}11171118// Check that the expected diagnostics occurred.1119NumErrors += CheckResults(Diags, *SrcManager, *Buffer, ED);1120} else {1121const DiagnosticLevelMask DiagMask =1122~Diags.getDiagnosticOptions().getVerifyIgnoreUnexpected();1123if (bool(DiagnosticLevelMask::Error & DiagMask))1124NumErrors += PrintUnexpected(Diags, nullptr, Buffer->err_begin(),1125Buffer->err_end(), "error");1126if (bool(DiagnosticLevelMask::Warning & DiagMask))1127NumErrors += PrintUnexpected(Diags, nullptr, Buffer->warn_begin(),1128Buffer->warn_end(), "warn");1129if (bool(DiagnosticLevelMask::Remark & DiagMask))1130NumErrors += PrintUnexpected(Diags, nullptr, Buffer->remark_begin(),1131Buffer->remark_end(), "remark");1132if (bool(DiagnosticLevelMask::Note & DiagMask))1133NumErrors += PrintUnexpected(Diags, nullptr, Buffer->note_begin(),1134Buffer->note_end(), "note");1135}11361137Diags.setClient(CurClient, Owner.release() != nullptr);11381139// Reset the buffer, we have processed all the diagnostics in it.1140Buffer.reset(new TextDiagnosticBuffer());1141ED.Reset();1142}11431144std::unique_ptr<Directive> Directive::create(bool RegexKind,1145SourceLocation DirectiveLoc,1146SourceLocation DiagnosticLoc,1147bool MatchAnyFileAndLine,1148bool MatchAnyLine, StringRef Text,1149unsigned Min, unsigned Max) {1150if (!RegexKind)1151return std::make_unique<StandardDirective>(DirectiveLoc, DiagnosticLoc,1152MatchAnyFileAndLine,1153MatchAnyLine, Text, Min, Max);11541155// Parse the directive into a regular expression.1156std::string RegexStr;1157StringRef S = Text;1158while (!S.empty()) {1159if (S.consume_front("{{")) {1160size_t RegexMatchLength = S.find("}}");1161assert(RegexMatchLength != StringRef::npos);1162// Append the regex, enclosed in parentheses.1163RegexStr += "(";1164RegexStr.append(S.data(), RegexMatchLength);1165RegexStr += ")";1166S = S.drop_front(RegexMatchLength + 2);1167} else {1168size_t VerbatimMatchLength = S.find("{{");1169if (VerbatimMatchLength == StringRef::npos)1170VerbatimMatchLength = S.size();1171// Escape and append the fixed string.1172RegexStr += llvm::Regex::escape(S.substr(0, VerbatimMatchLength));1173S = S.drop_front(VerbatimMatchLength);1174}1175}11761177return std::make_unique<RegexDirective>(DirectiveLoc, DiagnosticLoc,1178MatchAnyFileAndLine, MatchAnyLine,1179Text, Min, Max, RegexStr);1180}118111821183