1 | //===- VerifyDiagnosticConsumer.cpp - Verifying Diagnostic Client ---------===// |
---|---|
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // This is a concrete diagnostic client, which buffers the diagnostic messages. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | |
13 | #include "clang/Frontend/VerifyDiagnosticConsumer.h" |
14 | #include "clang/Basic/CharInfo.h" |
15 | #include "clang/Basic/Diagnostic.h" |
16 | #include "clang/Basic/DiagnosticOptions.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> |
45 | |
46 | using namespace clang; |
47 | |
48 | using Directive = VerifyDiagnosticConsumer::Directive; |
49 | using DirectiveList = VerifyDiagnosticConsumer::DirectiveList; |
50 | using ExpectedData = VerifyDiagnosticConsumer::ExpectedData; |
51 | |
52 | #ifndef NDEBUG |
53 | |
54 | namespace { |
55 | |
56 | class VerifyFileTracker : public PPCallbacks { |
57 | VerifyDiagnosticConsumer &Verify; |
58 | SourceManager &SM; |
59 | |
60 | public: |
61 | VerifyFileTracker(VerifyDiagnosticConsumer &Verify, SourceManager &SM) |
62 | : Verify(Verify), SM(SM) {} |
63 | |
64 | /// Hook into the preprocessor and update the list of parsed |
65 | /// files when the preprocessor indicates a new file is entered. |
66 | void FileChanged(SourceLocation Loc, FileChangeReason Reason, |
67 | SrcMgr::CharacteristicKind FileType, |
68 | FileID PrevFID) override { |
69 | Verify.UpdateParsedFileStatus(SM, FID: SM.getFileID(SpellingLoc: Loc), |
70 | PS: VerifyDiagnosticConsumer::IsParsed); |
71 | } |
72 | }; |
73 | |
74 | } // namespace |
75 | |
76 | #endif |
77 | |
78 | //===----------------------------------------------------------------------===// |
79 | // Checking diagnostics implementation. |
80 | //===----------------------------------------------------------------------===// |
81 | |
82 | using DiagList = TextDiagnosticBuffer::DiagList; |
83 | using const_diag_iterator = TextDiagnosticBuffer::const_iterator; |
84 | |
85 | namespace { |
86 | |
87 | /// StandardDirective - Directive with string matching. |
88 | class StandardDirective : public Directive { |
89 | public: |
90 | StandardDirective(SourceLocation DirectiveLoc, SourceLocation DiagnosticLoc, |
91 | StringRef Spelling, bool MatchAnyFileAndLine, |
92 | bool MatchAnyLine, StringRef Text, unsigned Min, |
93 | unsigned Max) |
94 | : Directive(DirectiveLoc, DiagnosticLoc, Spelling, MatchAnyFileAndLine, |
95 | MatchAnyLine, Text, Min, Max) {} |
96 | |
97 | bool isValid(std::string &Error) override { |
98 | // all strings are considered valid; even empty ones |
99 | return true; |
100 | } |
101 | |
102 | bool match(StringRef S) override { return S.contains(Other: Text); } |
103 | }; |
104 | |
105 | /// RegexDirective - Directive with regular-expression matching. |
106 | class RegexDirective : public Directive { |
107 | public: |
108 | RegexDirective(SourceLocation DirectiveLoc, SourceLocation DiagnosticLoc, |
109 | StringRef Spelling, bool MatchAnyFileAndLine, |
110 | bool MatchAnyLine, StringRef Text, unsigned Min, unsigned Max, |
111 | StringRef RegexStr) |
112 | : Directive(DirectiveLoc, DiagnosticLoc, Spelling, MatchAnyFileAndLine, |
113 | MatchAnyLine, Text, Min, Max), |
114 | Regex(RegexStr) {} |
115 | |
116 | bool isValid(std::string &Error) override { |
117 | return Regex.isValid(Error); |
118 | } |
119 | |
120 | bool match(StringRef S) override { |
121 | return Regex.match(String: S); |
122 | } |
123 | |
124 | private: |
125 | llvm::Regex Regex; |
126 | }; |
127 | |
128 | class ParseHelper |
129 | { |
130 | public: |
131 | ParseHelper(StringRef S) |
132 | : Begin(S.begin()), End(S.end()), C(Begin), P(Begin) {} |
133 | |
134 | // Return true if string literal is next. |
135 | bool Next(StringRef S) { |
136 | P = C; |
137 | PEnd = C + S.size(); |
138 | if (PEnd > End) |
139 | return false; |
140 | return memcmp(s1: P, s2: S.data(), n: S.size()) == 0; |
141 | } |
142 | |
143 | // Return true if number is next. |
144 | // Output N only if number is next. |
145 | bool Next(unsigned &N) { |
146 | unsigned TMP = 0; |
147 | P = C; |
148 | PEnd = P; |
149 | for (; PEnd < End && *PEnd >= '0' && *PEnd <= '9'; ++PEnd) { |
150 | TMP *= 10; |
151 | TMP += *PEnd - '0'; |
152 | } |
153 | if (PEnd == C) |
154 | return false; |
155 | N = TMP; |
156 | return true; |
157 | } |
158 | |
159 | // Return true if a marker is next. |
160 | // A marker is the longest match for /#[A-Za-z0-9_-]+/. |
161 | bool NextMarker() { |
162 | P = C; |
163 | if (P == End || *P != '#') |
164 | return false; |
165 | PEnd = P; |
166 | ++PEnd; |
167 | while ((isAlphanumeric(c: *PEnd) || *PEnd == '-' || *PEnd == '_') && |
168 | PEnd < End) |
169 | ++PEnd; |
170 | return PEnd > P + 1; |
171 | } |
172 | |
173 | // Return true if string literal S is matched in content. |
174 | // When true, P marks begin-position of the match, and calling Advance sets C |
175 | // to end-position of the match. |
176 | // If S is the empty string, then search for any letter instead (makes sense |
177 | // with FinishDirectiveToken=true). |
178 | // If EnsureStartOfWord, then skip matches that don't start a new word. |
179 | // If FinishDirectiveToken, then assume the match is the start of a comment |
180 | // directive for -verify, and extend the match to include the entire first |
181 | // token of that directive. |
182 | bool Search(StringRef S, bool EnsureStartOfWord = false, |
183 | bool FinishDirectiveToken = false) { |
184 | do { |
185 | if (!S.empty()) { |
186 | P = std::search(first1: C, last1: End, first2: S.begin(), last2: S.end()); |
187 | PEnd = P + S.size(); |
188 | } |
189 | else { |
190 | P = C; |
191 | while (P != End && !isLetter(c: *P)) |
192 | ++P; |
193 | PEnd = P + 1; |
194 | } |
195 | if (P == End) |
196 | break; |
197 | // If not start of word but required, skip and search again. |
198 | if (EnsureStartOfWord |
199 | // Check if string literal starts a new word. |
200 | && !(P == Begin || isWhitespace(c: P[-1]) |
201 | // Or it could be preceded by the start of a comment. |
202 | || (P > (Begin + 1) && (P[-1] == '/' || P[-1] == '*') |
203 | && P[-2] == '/'))) |
204 | continue; |
205 | if (FinishDirectiveToken) { |
206 | while (PEnd != End && (isAlphanumeric(c: *PEnd) |
207 | || *PEnd == '-' || *PEnd == '_')) |
208 | ++PEnd; |
209 | // Put back trailing digits and hyphens to be parsed later as a count |
210 | // or count range. Because -verify prefixes must start with letters, |
211 | // we know the actual directive we found starts with a letter, so |
212 | // we won't put back the entire directive word and thus record an empty |
213 | // string. |
214 | assert(isLetter(*P) && "-verify prefix must start with a letter"); |
215 | while (isDigit(c: PEnd[-1]) || PEnd[-1] == '-') |
216 | --PEnd; |
217 | } |
218 | return true; |
219 | } while (Advance()); |
220 | return false; |
221 | } |
222 | |
223 | // Return true if a CloseBrace that closes the OpenBrace at the current nest |
224 | // level is found. When true, P marks begin-position of CloseBrace. |
225 | bool SearchClosingBrace(StringRef OpenBrace, StringRef CloseBrace) { |
226 | unsigned Depth = 1; |
227 | P = C; |
228 | while (P < End) { |
229 | StringRef S(P, End - P); |
230 | if (S.starts_with(Prefix: OpenBrace)) { |
231 | ++Depth; |
232 | P += OpenBrace.size(); |
233 | } else if (S.starts_with(Prefix: CloseBrace)) { |
234 | --Depth; |
235 | if (Depth == 0) { |
236 | PEnd = P + CloseBrace.size(); |
237 | return true; |
238 | } |
239 | P += CloseBrace.size(); |
240 | } else { |
241 | ++P; |
242 | } |
243 | } |
244 | return false; |
245 | } |
246 | |
247 | // Advance 1-past previous next/search. |
248 | // Behavior is undefined if previous next/search failed. |
249 | bool Advance() { |
250 | C = PEnd; |
251 | return C < End; |
252 | } |
253 | |
254 | // Return the text matched by the previous next/search. |
255 | // Behavior is undefined if previous next/search failed. |
256 | StringRef Match() { return StringRef(P, PEnd - P); } |
257 | |
258 | // Skip zero or more whitespace. |
259 | void SkipWhitespace() { |
260 | for (; C < End && isWhitespace(c: *C); ++C) |
261 | ; |
262 | } |
263 | |
264 | // Return true if EOF reached. |
265 | bool Done() { |
266 | return !(C < End); |
267 | } |
268 | |
269 | // Beginning of expected content. |
270 | const char * const Begin; |
271 | |
272 | // End of expected content (1-past). |
273 | const char * const End; |
274 | |
275 | // Position of next char in content. |
276 | const char *C; |
277 | |
278 | // Previous next/search subject start. |
279 | const char *P; |
280 | |
281 | private: |
282 | // Previous next/search subject end (1-past). |
283 | const char *PEnd = nullptr; |
284 | }; |
285 | |
286 | // The information necessary to create a directive. |
287 | struct UnattachedDirective { |
288 | DirectiveList *DL = nullptr; |
289 | std::string Spelling; |
290 | bool RegexKind = false; |
291 | SourceLocation DirectivePos, ContentBegin; |
292 | std::string Text; |
293 | unsigned Min = 1, Max = 1; |
294 | }; |
295 | |
296 | // Attach the specified directive to the line of code indicated by |
297 | // \p ExpectedLoc. |
298 | void attachDirective(DiagnosticsEngine &Diags, const UnattachedDirective &UD, |
299 | SourceLocation ExpectedLoc, |
300 | bool MatchAnyFileAndLine = false, |
301 | bool MatchAnyLine = false) { |
302 | // Construct new directive. |
303 | std::unique_ptr<Directive> D = Directive::create( |
304 | RegexKind: UD.RegexKind, DirectiveLoc: UD.DirectivePos, DiagnosticLoc: ExpectedLoc, Spelling: UD.Spelling, |
305 | MatchAnyFileAndLine, MatchAnyLine, Text: UD.Text, Min: UD.Min, Max: UD.Max); |
306 | |
307 | std::string Error; |
308 | if (!D->isValid(Error)) { |
309 | Diags.Report(UD.ContentBegin, diag::err_verify_invalid_content) |
310 | << (UD.RegexKind ? "regex": "string") << Error; |
311 | } |
312 | |
313 | UD.DL->push_back(x: std::move(D)); |
314 | } |
315 | |
316 | } // anonymous |
317 | |
318 | // Tracker for markers in the input files. A marker is a comment of the form |
319 | // |
320 | // n = 123; // #123 |
321 | // |
322 | // ... that can be referred to by a later expected-* directive: |
323 | // |
324 | // // expected-error@#123 {{undeclared identifier 'n'}} |
325 | // |
326 | // Marker declarations must be at the start of a comment or preceded by |
327 | // whitespace to distinguish them from uses of markers in directives. |
328 | class VerifyDiagnosticConsumer::MarkerTracker { |
329 | DiagnosticsEngine &Diags; |
330 | |
331 | struct Marker { |
332 | SourceLocation DefLoc; |
333 | SourceLocation RedefLoc; |
334 | SourceLocation UseLoc; |
335 | }; |
336 | llvm::StringMap<Marker> Markers; |
337 | |
338 | // Directives that couldn't be created yet because they name an unknown |
339 | // marker. |
340 | llvm::StringMap<llvm::SmallVector<UnattachedDirective, 2>> DeferredDirectives; |
341 | |
342 | public: |
343 | MarkerTracker(DiagnosticsEngine &Diags) : Diags(Diags) {} |
344 | |
345 | // Register a marker. |
346 | void addMarker(StringRef MarkerName, SourceLocation Pos) { |
347 | auto InsertResult = Markers.insert( |
348 | KV: {MarkerName, Marker{.DefLoc: Pos, .RedefLoc: SourceLocation(), .UseLoc: SourceLocation()}}); |
349 | |
350 | Marker &M = InsertResult.first->second; |
351 | if (!InsertResult.second) { |
352 | // Marker was redefined. |
353 | M.RedefLoc = Pos; |
354 | } else { |
355 | // First definition: build any deferred directives. |
356 | auto Deferred = DeferredDirectives.find(Key: MarkerName); |
357 | if (Deferred != DeferredDirectives.end()) { |
358 | for (auto &UD : Deferred->second) { |
359 | if (M.UseLoc.isInvalid()) |
360 | M.UseLoc = UD.DirectivePos; |
361 | attachDirective(Diags, UD, ExpectedLoc: Pos); |
362 | } |
363 | DeferredDirectives.erase(I: Deferred); |
364 | } |
365 | } |
366 | } |
367 | |
368 | // Register a directive at the specified marker. |
369 | void addDirective(StringRef MarkerName, const UnattachedDirective &UD) { |
370 | auto MarkerIt = Markers.find(Key: MarkerName); |
371 | if (MarkerIt != Markers.end()) { |
372 | Marker &M = MarkerIt->second; |
373 | if (M.UseLoc.isInvalid()) |
374 | M.UseLoc = UD.DirectivePos; |
375 | return attachDirective(Diags, UD, ExpectedLoc: M.DefLoc); |
376 | } |
377 | DeferredDirectives[MarkerName].push_back(Elt: UD); |
378 | } |
379 | |
380 | // Ensure we have no remaining deferred directives, and no |
381 | // multiply-defined-and-used markers. |
382 | void finalize() { |
383 | for (auto &MarkerInfo : Markers) { |
384 | StringRef Name = MarkerInfo.first(); |
385 | Marker &M = MarkerInfo.second; |
386 | if (M.RedefLoc.isValid() && M.UseLoc.isValid()) { |
387 | Diags.Report(M.UseLoc, diag::err_verify_ambiguous_marker) << Name; |
388 | Diags.Report(M.DefLoc, diag::note_verify_ambiguous_marker) << Name; |
389 | Diags.Report(M.RedefLoc, diag::note_verify_ambiguous_marker) << Name; |
390 | } |
391 | } |
392 | |
393 | for (auto &DeferredPair : DeferredDirectives) { |
394 | Diags.Report(DeferredPair.second.front().DirectivePos, |
395 | diag::err_verify_no_such_marker) |
396 | << DeferredPair.first(); |
397 | } |
398 | } |
399 | }; |
400 | |
401 | static std::string DetailedErrorString(const DiagnosticsEngine &Diags) { |
402 | if (Diags.getDiagnosticOptions().VerifyPrefixes.empty()) |
403 | return "expected"; |
404 | return *Diags.getDiagnosticOptions().VerifyPrefixes.begin(); |
405 | } |
406 | |
407 | /// ParseDirective - Go through the comment and see if it indicates expected |
408 | /// diagnostics. If so, then put them in the appropriate directive list. |
409 | /// |
410 | /// Returns true if any valid directives were found. |
411 | static bool ParseDirective(StringRef S, ExpectedData *ED, SourceManager &SM, |
412 | Preprocessor *PP, SourceLocation Pos, |
413 | VerifyDiagnosticConsumer::ParsingState &State, |
414 | VerifyDiagnosticConsumer::MarkerTracker &Markers) { |
415 | DiagnosticsEngine &Diags = PP ? PP->getDiagnostics() : SM.getDiagnostics(); |
416 | |
417 | // First, scan the comment looking for markers. |
418 | for (ParseHelper PH(S); !PH.Done();) { |
419 | if (!PH.Search(S: "#", EnsureStartOfWord: true)) |
420 | break; |
421 | PH.C = PH.P; |
422 | if (!PH.NextMarker()) { |
423 | PH.Next(S: "#"); |
424 | PH.Advance(); |
425 | continue; |
426 | } |
427 | PH.Advance(); |
428 | Markers.addMarker(MarkerName: PH.Match(), Pos); |
429 | } |
430 | |
431 | // A single comment may contain multiple directives. |
432 | bool FoundDirective = false; |
433 | for (ParseHelper PH(S); !PH.Done();) { |
434 | // Search for the initial directive token. |
435 | // If one prefix, save time by searching only for its directives. |
436 | // Otherwise, search for any potential directive token and check it later. |
437 | const auto &Prefixes = Diags.getDiagnosticOptions().VerifyPrefixes; |
438 | if (!(Prefixes.size() == 1 ? PH.Search(S: *Prefixes.begin(), EnsureStartOfWord: true, FinishDirectiveToken: true) |
439 | : PH.Search(S: "", EnsureStartOfWord: true, FinishDirectiveToken: true))) |
440 | break; |
441 | |
442 | StringRef DToken = PH.Match(); |
443 | PH.Advance(); |
444 | |
445 | UnattachedDirective D; |
446 | D.Spelling = DToken; |
447 | // Default directive kind. |
448 | const char *KindStr = "string"; |
449 | |
450 | // Parse the initial directive token in reverse so we can easily determine |
451 | // its exact actual prefix. If we were to parse it from the front instead, |
452 | // it would be harder to determine where the prefix ends because there |
453 | // might be multiple matching -verify prefixes because some might prefix |
454 | // others. |
455 | |
456 | // Regex in initial directive token: -re |
457 | if (DToken.consume_back(Suffix: "-re")) { |
458 | D.RegexKind = true; |
459 | KindStr = "regex"; |
460 | } |
461 | |
462 | // Type in initial directive token: -{error|warning|note|no-diagnostics} |
463 | bool NoDiag = false; |
464 | StringRef DType; |
465 | if (DToken.ends_with(Suffix: DType = "-error")) |
466 | D.DL = ED ? &ED->Errors : nullptr; |
467 | else if (DToken.ends_with(Suffix: DType = "-warning")) |
468 | D.DL = ED ? &ED->Warnings : nullptr; |
469 | else if (DToken.ends_with(Suffix: DType = "-remark")) |
470 | D.DL = ED ? &ED->Remarks : nullptr; |
471 | else if (DToken.ends_with(Suffix: DType = "-note")) |
472 | D.DL = ED ? &ED->Notes : nullptr; |
473 | else if (DToken.ends_with(Suffix: DType = "-no-diagnostics")) { |
474 | NoDiag = true; |
475 | if (D.RegexKind) |
476 | continue; |
477 | } else |
478 | continue; |
479 | DToken = DToken.substr(Start: 0, N: DToken.size()-DType.size()); |
480 | |
481 | // What's left in DToken is the actual prefix. That might not be a -verify |
482 | // prefix even if there is only one -verify prefix (for example, the full |
483 | // DToken is foo-bar-warning, but foo is the only -verify prefix). |
484 | if (!llvm::binary_search(Range: Prefixes, Value&: DToken)) |
485 | continue; |
486 | |
487 | if (NoDiag) { |
488 | if (State.Status == |
489 | VerifyDiagnosticConsumer::HasOtherExpectedDirectives) { |
490 | Diags.Report(Pos, diag::err_verify_invalid_no_diags) |
491 | << D.Spelling << /*IsExpectedNoDiagnostics=*/true; |
492 | } else if (State.Status != |
493 | VerifyDiagnosticConsumer::HasExpectedNoDiagnostics) { |
494 | State.Status = VerifyDiagnosticConsumer::HasExpectedNoDiagnostics; |
495 | State.FirstNoDiagnosticsDirective = D.Spelling; |
496 | } |
497 | continue; |
498 | } |
499 | if (State.Status == VerifyDiagnosticConsumer::HasExpectedNoDiagnostics) { |
500 | Diags.Report(Pos, diag::err_verify_invalid_no_diags) |
501 | << D.Spelling << /*IsExpectedNoDiagnostics=*/false |
502 | << State.FirstNoDiagnosticsDirective; |
503 | continue; |
504 | } |
505 | State.Status = VerifyDiagnosticConsumer::HasOtherExpectedDirectives; |
506 | |
507 | // If a directive has been found but we're not interested |
508 | // in storing the directive information, return now. |
509 | if (!D.DL) |
510 | return true; |
511 | |
512 | // Next optional token: @ |
513 | SourceLocation ExpectedLoc; |
514 | StringRef Marker; |
515 | bool MatchAnyFileAndLine = false; |
516 | bool MatchAnyLine = false; |
517 | if (!PH.Next(S: "@")) { |
518 | ExpectedLoc = Pos; |
519 | } else { |
520 | PH.Advance(); |
521 | unsigned Line = 0; |
522 | bool FoundPlus = PH.Next(S: "+"); |
523 | if (FoundPlus || PH.Next(S: "-")) { |
524 | // Relative to current line. |
525 | PH.Advance(); |
526 | bool Invalid = false; |
527 | unsigned ExpectedLine = SM.getSpellingLineNumber(Loc: Pos, Invalid: &Invalid); |
528 | if (!Invalid && PH.Next(N&: Line) && (FoundPlus || Line < ExpectedLine)) { |
529 | if (FoundPlus) ExpectedLine += Line; |
530 | else ExpectedLine -= Line; |
531 | ExpectedLoc = SM.translateLineCol(FID: SM.getFileID(SpellingLoc: Pos), Line: ExpectedLine, Col: 1); |
532 | } |
533 | } else if (PH.Next(N&: Line)) { |
534 | // Absolute line number. |
535 | if (Line > 0) |
536 | ExpectedLoc = SM.translateLineCol(FID: SM.getFileID(SpellingLoc: Pos), Line, Col: 1); |
537 | } else if (PH.NextMarker()) { |
538 | Marker = PH.Match(); |
539 | } else if (PP && PH.Search(S: ":")) { |
540 | // Specific source file. |
541 | StringRef Filename(PH.C, PH.P-PH.C); |
542 | PH.Advance(); |
543 | |
544 | if (Filename == "*") { |
545 | MatchAnyFileAndLine = true; |
546 | if (!PH.Next(S: "*")) { |
547 | Diags.Report(Pos.getLocWithOffset(Offset: PH.C - PH.Begin), |
548 | diag::err_verify_missing_line) |
549 | << "'*'"; |
550 | continue; |
551 | } |
552 | MatchAnyLine = true; |
553 | ExpectedLoc = SourceLocation(); |
554 | } else { |
555 | // Lookup file via Preprocessor, like a #include. |
556 | OptionalFileEntryRef File = |
557 | PP->LookupFile(FilenameLoc: Pos, Filename, isAngled: false, FromDir: nullptr, FromFile: nullptr, CurDir: nullptr, |
558 | SearchPath: nullptr, RelativePath: nullptr, SuggestedModule: nullptr, IsMapped: nullptr, IsFrameworkFound: nullptr); |
559 | if (!File) { |
560 | Diags.Report(Pos.getLocWithOffset(Offset: PH.C - PH.Begin), |
561 | diag::err_verify_missing_file) |
562 | << Filename << KindStr; |
563 | continue; |
564 | } |
565 | |
566 | FileID FID = SM.translateFile(SourceFile: *File); |
567 | if (FID.isInvalid()) |
568 | FID = SM.createFileID(SourceFile: *File, IncludePos: Pos, FileCharacter: SrcMgr::C_User); |
569 | |
570 | if (PH.Next(N&: Line) && Line > 0) |
571 | ExpectedLoc = SM.translateLineCol(FID, Line, Col: 1); |
572 | else if (PH.Next(S: "*")) { |
573 | MatchAnyLine = true; |
574 | ExpectedLoc = SM.translateLineCol(FID, Line: 1, Col: 1); |
575 | } |
576 | } |
577 | } else if (PH.Next(S: "*")) { |
578 | MatchAnyLine = true; |
579 | ExpectedLoc = SourceLocation(); |
580 | } |
581 | |
582 | if (ExpectedLoc.isInvalid() && !MatchAnyLine && Marker.empty()) { |
583 | Diags.Report(Pos.getLocWithOffset(Offset: PH.C-PH.Begin), |
584 | diag::err_verify_missing_line) << KindStr; |
585 | continue; |
586 | } |
587 | PH.Advance(); |
588 | } |
589 | |
590 | // Skip optional whitespace. |
591 | PH.SkipWhitespace(); |
592 | |
593 | // Next optional token: positive integer or a '+'. |
594 | if (PH.Next(N&: D.Min)) { |
595 | PH.Advance(); |
596 | // A positive integer can be followed by a '+' meaning min |
597 | // or more, or by a '-' meaning a range from min to max. |
598 | if (PH.Next(S: "+")) { |
599 | D.Max = Directive::MaxCount; |
600 | PH.Advance(); |
601 | } else if (PH.Next(S: "-")) { |
602 | PH.Advance(); |
603 | if (!PH.Next(N&: D.Max) || D.Max < D.Min) { |
604 | Diags.Report(Pos.getLocWithOffset(Offset: PH.C-PH.Begin), |
605 | diag::err_verify_invalid_range) << KindStr; |
606 | continue; |
607 | } |
608 | PH.Advance(); |
609 | } else { |
610 | D.Max = D.Min; |
611 | } |
612 | } else if (PH.Next(S: "+")) { |
613 | // '+' on its own means "1 or more". |
614 | D.Max = Directive::MaxCount; |
615 | PH.Advance(); |
616 | } |
617 | |
618 | // Skip optional whitespace. |
619 | PH.SkipWhitespace(); |
620 | |
621 | // Next token: {{ |
622 | if (!PH.Next(S: "{{")) { |
623 | Diags.Report(Pos.getLocWithOffset(Offset: PH.C-PH.Begin), |
624 | diag::err_verify_missing_start) << KindStr; |
625 | continue; |
626 | } |
627 | llvm::SmallString<8> CloseBrace("}}"); |
628 | const char *const DelimBegin = PH.C; |
629 | PH.Advance(); |
630 | // Count the number of opening braces for `string` kinds |
631 | for (; !D.RegexKind && PH.Next(S: "{"); PH.Advance()) |
632 | CloseBrace += '}'; |
633 | const char* const ContentBegin = PH.C; // mark content begin |
634 | // Search for closing brace |
635 | StringRef OpenBrace(DelimBegin, ContentBegin - DelimBegin); |
636 | if (!PH.SearchClosingBrace(OpenBrace, CloseBrace)) { |
637 | Diags.Report(Pos.getLocWithOffset(Offset: PH.C - PH.Begin), |
638 | diag::err_verify_missing_end) |
639 | << KindStr << CloseBrace; |
640 | continue; |
641 | } |
642 | const char* const ContentEnd = PH.P; // mark content end |
643 | PH.Advance(); |
644 | |
645 | D.DirectivePos = Pos; |
646 | D.ContentBegin = Pos.getLocWithOffset(Offset: ContentBegin - PH.Begin); |
647 | |
648 | // Build directive text; convert \n to newlines. |
649 | StringRef NewlineStr = "\\n"; |
650 | StringRef Content(ContentBegin, ContentEnd-ContentBegin); |
651 | size_t CPos = 0; |
652 | size_t FPos; |
653 | while ((FPos = Content.find(Str: NewlineStr, From: CPos)) != StringRef::npos) { |
654 | D.Text += Content.substr(Start: CPos, N: FPos-CPos); |
655 | D.Text += '\n'; |
656 | CPos = FPos + NewlineStr.size(); |
657 | } |
658 | if (D.Text.empty()) |
659 | D.Text.assign(first: ContentBegin, last: ContentEnd); |
660 | |
661 | // Check that regex directives contain at least one regex. |
662 | if (D.RegexKind && D.Text.find(s: "{{") == StringRef::npos) { |
663 | Diags.Report(D.ContentBegin, diag::err_verify_missing_regex) << D.Text; |
664 | return false; |
665 | } |
666 | |
667 | if (Marker.empty()) |
668 | attachDirective(Diags, UD: D, ExpectedLoc, MatchAnyFileAndLine, MatchAnyLine); |
669 | else |
670 | Markers.addDirective(MarkerName: Marker, UD: D); |
671 | FoundDirective = true; |
672 | } |
673 | |
674 | return FoundDirective; |
675 | } |
676 | |
677 | VerifyDiagnosticConsumer::VerifyDiagnosticConsumer(DiagnosticsEngine &Diags_) |
678 | : Diags(Diags_), PrimaryClient(Diags.getClient()), |
679 | PrimaryClientOwner(Diags.takeClient()), |
680 | Buffer(new TextDiagnosticBuffer()), Markers(new MarkerTracker(Diags)), |
681 | State{.Status: HasNoDirectives, .FirstNoDiagnosticsDirective: {}} { |
682 | if (Diags.hasSourceManager()) |
683 | setSourceManager(Diags.getSourceManager()); |
684 | } |
685 | |
686 | VerifyDiagnosticConsumer::~VerifyDiagnosticConsumer() { |
687 | assert(!ActiveSourceFiles && "Incomplete parsing of source files!"); |
688 | assert(!CurrentPreprocessor && "CurrentPreprocessor should be invalid!"); |
689 | SrcManager = nullptr; |
690 | CheckDiagnostics(); |
691 | assert(!Diags.ownsClient() && |
692 | "The VerifyDiagnosticConsumer takes over ownership of the client!"); |
693 | } |
694 | |
695 | // DiagnosticConsumer interface. |
696 | |
697 | void VerifyDiagnosticConsumer::BeginSourceFile(const LangOptions &LangOpts, |
698 | const Preprocessor *PP) { |
699 | // Attach comment handler on first invocation. |
700 | if (++ActiveSourceFiles == 1) { |
701 | if (PP) { |
702 | CurrentPreprocessor = PP; |
703 | this->LangOpts = &LangOpts; |
704 | setSourceManager(PP->getSourceManager()); |
705 | const_cast<Preprocessor *>(PP)->addCommentHandler(Handler: this); |
706 | #ifndef NDEBUG |
707 | // Debug build tracks parsed files. |
708 | const_cast<Preprocessor *>(PP)->addPPCallbacks( |
709 | C: std::make_unique<VerifyFileTracker>(args&: *this, args&: *SrcManager)); |
710 | #endif |
711 | } |
712 | } |
713 | |
714 | assert((!PP || CurrentPreprocessor == PP) && "Preprocessor changed!"); |
715 | PrimaryClient->BeginSourceFile(LangOpts, PP); |
716 | } |
717 | |
718 | void VerifyDiagnosticConsumer::EndSourceFile() { |
719 | assert(ActiveSourceFiles && "No active source files!"); |
720 | PrimaryClient->EndSourceFile(); |
721 | |
722 | // Detach comment handler once last active source file completed. |
723 | if (--ActiveSourceFiles == 0) { |
724 | if (CurrentPreprocessor) |
725 | const_cast<Preprocessor *>(CurrentPreprocessor)-> |
726 | removeCommentHandler(Handler: this); |
727 | |
728 | // Diagnose any used-but-not-defined markers. |
729 | Markers->finalize(); |
730 | |
731 | // Check diagnostics once last file completed. |
732 | CheckDiagnostics(); |
733 | CurrentPreprocessor = nullptr; |
734 | LangOpts = nullptr; |
735 | } |
736 | } |
737 | |
738 | void VerifyDiagnosticConsumer::HandleDiagnostic( |
739 | DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { |
740 | if (Info.hasSourceManager()) { |
741 | // If this diagnostic is for a different source manager, ignore it. |
742 | if (SrcManager && &Info.getSourceManager() != SrcManager) |
743 | return; |
744 | |
745 | setSourceManager(Info.getSourceManager()); |
746 | } |
747 | |
748 | #ifndef NDEBUG |
749 | // Debug build tracks unparsed files for possible |
750 | // unparsed expected-* directives. |
751 | if (SrcManager) { |
752 | SourceLocation Loc = Info.getLocation(); |
753 | if (Loc.isValid()) { |
754 | ParsedStatus PS = IsUnparsed; |
755 | |
756 | Loc = SrcManager->getExpansionLoc(Loc); |
757 | FileID FID = SrcManager->getFileID(SpellingLoc: Loc); |
758 | |
759 | auto FE = SrcManager->getFileEntryRefForID(FID); |
760 | if (FE && CurrentPreprocessor && SrcManager->isLoadedFileID(FID)) { |
761 | // If the file is a modules header file it shall not be parsed |
762 | // for expected-* directives. |
763 | HeaderSearch &HS = CurrentPreprocessor->getHeaderSearchInfo(); |
764 | if (HS.findModuleForHeader(File: *FE)) |
765 | PS = IsUnparsedNoDirectives; |
766 | } |
767 | |
768 | UpdateParsedFileStatus(SM&: *SrcManager, FID, PS); |
769 | } |
770 | } |
771 | #endif |
772 | |
773 | // Send the diagnostic to the buffer, we will check it once we reach the end |
774 | // of the source file (or are destructed). |
775 | Buffer->HandleDiagnostic(DiagLevel, Info); |
776 | } |
777 | |
778 | /// HandleComment - Hook into the preprocessor and extract comments containing |
779 | /// expected errors and warnings. |
780 | bool VerifyDiagnosticConsumer::HandleComment(Preprocessor &PP, |
781 | SourceRange Comment) { |
782 | SourceManager &SM = PP.getSourceManager(); |
783 | |
784 | // If this comment is for a different source manager, ignore it. |
785 | if (SrcManager && &SM != SrcManager) |
786 | return false; |
787 | |
788 | SourceLocation CommentBegin = Comment.getBegin(); |
789 | |
790 | const char *CommentRaw = SM.getCharacterData(SL: CommentBegin); |
791 | StringRef C(CommentRaw, SM.getCharacterData(SL: Comment.getEnd()) - CommentRaw); |
792 | |
793 | if (C.empty()) |
794 | return false; |
795 | |
796 | // Fold any "\<EOL>" sequences |
797 | size_t loc = C.find(C: '\\'); |
798 | if (loc == StringRef::npos) { |
799 | ParseDirective(S: C, ED: &ED, SM, PP: &PP, Pos: CommentBegin, State, Markers&: *Markers); |
800 | return false; |
801 | } |
802 | |
803 | std::string C2; |
804 | C2.reserve(res: C.size()); |
805 | |
806 | for (size_t last = 0;; loc = C.find(C: '\\', From: last)) { |
807 | if (loc == StringRef::npos || loc == C.size()) { |
808 | C2 += C.substr(Start: last); |
809 | break; |
810 | } |
811 | C2 += C.substr(Start: last, N: loc-last); |
812 | last = loc + 1; |
813 | |
814 | if (last < C.size() && (C[last] == '\n' || C[last] == '\r')) { |
815 | ++last; |
816 | |
817 | // Escape \r\n or \n\r, but not \n\n. |
818 | if (last < C.size()) |
819 | if (C[last] == '\n' || C[last] == '\r') |
820 | if (C[last] != C[last-1]) |
821 | ++last; |
822 | } else { |
823 | // This was just a normal backslash. |
824 | C2 += '\\'; |
825 | } |
826 | } |
827 | |
828 | if (!C2.empty()) |
829 | ParseDirective(S: C2, ED: &ED, SM, PP: &PP, Pos: CommentBegin, State, Markers&: *Markers); |
830 | return false; |
831 | } |
832 | |
833 | #ifndef NDEBUG |
834 | /// Lex the specified source file to determine whether it contains |
835 | /// any expected-* directives. As a Lexer is used rather than a full-blown |
836 | /// Preprocessor, directives inside skipped #if blocks will still be found. |
837 | /// |
838 | /// \return true if any directives were found. |
839 | static bool findDirectives(SourceManager &SM, FileID FID, |
840 | const LangOptions &LangOpts) { |
841 | // Create a raw lexer to pull all the comments out of FID. |
842 | if (FID.isInvalid()) |
843 | return false; |
844 | |
845 | // Create a lexer to lex all the tokens of the main file in raw mode. |
846 | llvm::MemoryBufferRef FromFile = SM.getBufferOrFake(FID); |
847 | Lexer RawLex(FID, FromFile, SM, LangOpts); |
848 | |
849 | // Return comments as tokens, this is how we find expected diagnostics. |
850 | RawLex.SetCommentRetentionState(true); |
851 | |
852 | Token Tok; |
853 | Tok.setKind(tok::comment); |
854 | VerifyDiagnosticConsumer::ParsingState State = { |
855 | .Status: VerifyDiagnosticConsumer::HasNoDirectives, .FirstNoDiagnosticsDirective: {}}; |
856 | while (Tok.isNot(K: tok::eof)) { |
857 | RawLex.LexFromRawLexer(Result&: Tok); |
858 | if (!Tok.is(K: tok::comment)) continue; |
859 | |
860 | std::string Comment = RawLex.getSpelling(Tok, SourceMgr: SM, LangOpts); |
861 | if (Comment.empty()) continue; |
862 | |
863 | // We don't care about tracking markers for this phase. |
864 | VerifyDiagnosticConsumer::MarkerTracker Markers(SM.getDiagnostics()); |
865 | |
866 | // Find first directive. |
867 | if (ParseDirective(S: Comment, ED: nullptr, SM, PP: nullptr, Pos: Tok.getLocation(), State, |
868 | Markers)) |
869 | return true; |
870 | } |
871 | return false; |
872 | } |
873 | #endif // !NDEBUG |
874 | |
875 | /// Takes a list of diagnostics that have been generated but not matched |
876 | /// by an expected-* directive and produces a diagnostic to the user from this. |
877 | static unsigned PrintUnexpected(DiagnosticsEngine &Diags, SourceManager *SourceMgr, |
878 | const_diag_iterator diag_begin, |
879 | const_diag_iterator diag_end, |
880 | const char *Kind) { |
881 | if (diag_begin == diag_end) return 0; |
882 | |
883 | SmallString<256> Fmt; |
884 | llvm::raw_svector_ostream OS(Fmt); |
885 | for (const_diag_iterator I = diag_begin, E = diag_end; I != E; ++I) { |
886 | if (I->first.isInvalid() || !SourceMgr) |
887 | OS << "\n (frontend)"; |
888 | else { |
889 | OS << "\n "; |
890 | if (OptionalFileEntryRef File = |
891 | SourceMgr->getFileEntryRefForID(FID: SourceMgr->getFileID(SpellingLoc: I->first))) |
892 | OS << " File "<< File->getName(); |
893 | OS << " Line "<< SourceMgr->getPresumedLineNumber(Loc: I->first); |
894 | } |
895 | OS << ": "<< I->second; |
896 | } |
897 | |
898 | const bool IsSinglePrefix = |
899 | Diags.getDiagnosticOptions().VerifyPrefixes.size() == 1; |
900 | std::string Prefix = *Diags.getDiagnosticOptions().VerifyPrefixes.begin(); |
901 | Diags.Report(diag::err_verify_inconsistent_diags).setForceEmit() |
902 | << IsSinglePrefix << Prefix << Kind << /*Unexpected=*/true << OS.str(); |
903 | return std::distance(first: diag_begin, last: diag_end); |
904 | } |
905 | |
906 | /// Takes a list of diagnostics that were expected to have been generated |
907 | /// but were not and produces a diagnostic to the user from this. |
908 | static unsigned PrintExpected(DiagnosticsEngine &Diags, |
909 | SourceManager &SourceMgr, |
910 | std::vector<Directive *> &DL, const char *Kind) { |
911 | if (DL.empty()) |
912 | return 0; |
913 | |
914 | const bool IsSinglePrefix = |
915 | Diags.getDiagnosticOptions().VerifyPrefixes.size() == 1; |
916 | |
917 | SmallString<256> Fmt; |
918 | llvm::raw_svector_ostream OS(Fmt); |
919 | for (const auto *D : DL) { |
920 | if (D->DiagnosticLoc.isInvalid() || D->MatchAnyFileAndLine) |
921 | OS << "\n File *"; |
922 | else |
923 | OS << "\n File "<< SourceMgr.getFilename(SpellingLoc: D->DiagnosticLoc); |
924 | if (D->MatchAnyLine) |
925 | OS << " Line *"; |
926 | else |
927 | OS << " Line "<< SourceMgr.getPresumedLineNumber(Loc: D->DiagnosticLoc); |
928 | if (D->DirectiveLoc != D->DiagnosticLoc) |
929 | OS << " (directive at " |
930 | << SourceMgr.getFilename(SpellingLoc: D->DirectiveLoc) << ':' |
931 | << SourceMgr.getPresumedLineNumber(Loc: D->DirectiveLoc) << ')'; |
932 | if (!IsSinglePrefix) |
933 | OS << " \'"<< D->Spelling << '\''; |
934 | OS << ": "<< D->Text; |
935 | } |
936 | |
937 | std::string Prefix = *Diags.getDiagnosticOptions().VerifyPrefixes.begin(); |
938 | Diags.Report(diag::err_verify_inconsistent_diags).setForceEmit() |
939 | << IsSinglePrefix << Prefix << Kind << /*Unexpected=*/false << OS.str(); |
940 | return DL.size(); |
941 | } |
942 | |
943 | /// Determine whether two source locations come from the same file. |
944 | static bool IsFromSameFile(SourceManager &SM, SourceLocation DirectiveLoc, |
945 | SourceLocation DiagnosticLoc) { |
946 | while (DiagnosticLoc.isMacroID()) |
947 | DiagnosticLoc = SM.getImmediateMacroCallerLoc(Loc: DiagnosticLoc); |
948 | |
949 | if (SM.isWrittenInSameFile(Loc1: DirectiveLoc, Loc2: DiagnosticLoc)) |
950 | return true; |
951 | |
952 | const FileEntry *DiagFile = SM.getFileEntryForID(FID: SM.getFileID(SpellingLoc: DiagnosticLoc)); |
953 | if (!DiagFile && SM.isWrittenInMainFile(Loc: DirectiveLoc)) |
954 | return true; |
955 | |
956 | return (DiagFile == SM.getFileEntryForID(FID: SM.getFileID(SpellingLoc: DirectiveLoc))); |
957 | } |
958 | |
959 | /// CheckLists - Compare expected to seen diagnostic lists and return the |
960 | /// the difference between them. |
961 | static unsigned CheckLists(DiagnosticsEngine &Diags, SourceManager &SourceMgr, |
962 | const char *Label, |
963 | DirectiveList &Left, |
964 | const_diag_iterator d2_begin, |
965 | const_diag_iterator d2_end, |
966 | bool IgnoreUnexpected) { |
967 | std::vector<Directive *> LeftOnly; |
968 | DiagList Right(d2_begin, d2_end); |
969 | |
970 | for (auto &Owner : Left) { |
971 | Directive &D = *Owner; |
972 | unsigned LineNo1 = SourceMgr.getPresumedLineNumber(Loc: D.DiagnosticLoc); |
973 | |
974 | for (unsigned i = 0; i < D.Max; ++i) { |
975 | DiagList::iterator II, IE; |
976 | for (II = Right.begin(), IE = Right.end(); II != IE; ++II) { |
977 | if (!D.MatchAnyLine) { |
978 | unsigned LineNo2 = SourceMgr.getPresumedLineNumber(Loc: II->first); |
979 | if (LineNo1 != LineNo2) |
980 | continue; |
981 | } |
982 | |
983 | if (!D.DiagnosticLoc.isInvalid() && !D.MatchAnyFileAndLine && |
984 | !IsFromSameFile(SM&: SourceMgr, DirectiveLoc: D.DiagnosticLoc, DiagnosticLoc: II->first)) |
985 | continue; |
986 | |
987 | const std::string &RightText = II->second; |
988 | if (D.match(S: RightText)) |
989 | break; |
990 | } |
991 | if (II == IE) { |
992 | // Not found. |
993 | if (i >= D.Min) break; |
994 | LeftOnly.push_back(x: &D); |
995 | } else { |
996 | // Found. The same cannot be found twice. |
997 | Right.erase(position: II); |
998 | } |
999 | } |
1000 | } |
1001 | // Now all that's left in Right are those that were not matched. |
1002 | unsigned num = PrintExpected(Diags, SourceMgr, DL&: LeftOnly, Kind: Label); |
1003 | if (!IgnoreUnexpected) |
1004 | num += PrintUnexpected(Diags, SourceMgr: &SourceMgr, diag_begin: Right.begin(), diag_end: Right.end(), Kind: Label); |
1005 | return num; |
1006 | } |
1007 | |
1008 | /// CheckResults - This compares the expected results to those that |
1009 | /// were actually reported. It emits any discrepencies. Return "true" if there |
1010 | /// were problems. Return "false" otherwise. |
1011 | static unsigned CheckResults(DiagnosticsEngine &Diags, SourceManager &SourceMgr, |
1012 | const TextDiagnosticBuffer &Buffer, |
1013 | ExpectedData &ED) { |
1014 | // We want to capture the delta between what was expected and what was |
1015 | // seen. |
1016 | // |
1017 | // Expected \ Seen - set expected but not seen |
1018 | // Seen \ Expected - set seen but not expected |
1019 | unsigned NumProblems = 0; |
1020 | |
1021 | const DiagnosticLevelMask DiagMask = |
1022 | Diags.getDiagnosticOptions().getVerifyIgnoreUnexpected(); |
1023 | |
1024 | // See if there are error mismatches. |
1025 | NumProblems += CheckLists(Diags, SourceMgr, Label: "error", Left&: ED.Errors, |
1026 | d2_begin: Buffer.err_begin(), d2_end: Buffer.err_end(), |
1027 | IgnoreUnexpected: bool(DiagnosticLevelMask::Error & DiagMask)); |
1028 | |
1029 | // See if there are warning mismatches. |
1030 | NumProblems += CheckLists(Diags, SourceMgr, Label: "warning", Left&: ED.Warnings, |
1031 | d2_begin: Buffer.warn_begin(), d2_end: Buffer.warn_end(), |
1032 | IgnoreUnexpected: bool(DiagnosticLevelMask::Warning & DiagMask)); |
1033 | |
1034 | // See if there are remark mismatches. |
1035 | NumProblems += CheckLists(Diags, SourceMgr, Label: "remark", Left&: ED.Remarks, |
1036 | d2_begin: Buffer.remark_begin(), d2_end: Buffer.remark_end(), |
1037 | IgnoreUnexpected: bool(DiagnosticLevelMask::Remark & DiagMask)); |
1038 | |
1039 | // See if there are note mismatches. |
1040 | NumProblems += CheckLists(Diags, SourceMgr, Label: "note", Left&: ED.Notes, |
1041 | d2_begin: Buffer.note_begin(), d2_end: Buffer.note_end(), |
1042 | IgnoreUnexpected: bool(DiagnosticLevelMask::Note & DiagMask)); |
1043 | |
1044 | return NumProblems; |
1045 | } |
1046 | |
1047 | void VerifyDiagnosticConsumer::UpdateParsedFileStatus(SourceManager &SM, |
1048 | FileID FID, |
1049 | ParsedStatus PS) { |
1050 | // Check SourceManager hasn't changed. |
1051 | setSourceManager(SM); |
1052 | |
1053 | #ifndef NDEBUG |
1054 | if (FID.isInvalid()) |
1055 | return; |
1056 | |
1057 | OptionalFileEntryRef FE = SM.getFileEntryRefForID(FID); |
1058 | |
1059 | if (PS == IsParsed) { |
1060 | // Move the FileID from the unparsed set to the parsed set. |
1061 | UnparsedFiles.erase(Val: FID); |
1062 | ParsedFiles.insert(KV: std::make_pair(x&: FID, y: FE ? &FE->getFileEntry() : nullptr)); |
1063 | } else if (!ParsedFiles.count(Val: FID) && !UnparsedFiles.count(Val: FID)) { |
1064 | // Add the FileID to the unparsed set if we haven't seen it before. |
1065 | |
1066 | // Check for directives. |
1067 | bool FoundDirectives; |
1068 | if (PS == IsUnparsedNoDirectives) |
1069 | FoundDirectives = false; |
1070 | else |
1071 | FoundDirectives = !LangOpts || findDirectives(SM, FID, LangOpts: *LangOpts); |
1072 | |
1073 | // Add the FileID to the unparsed set. |
1074 | UnparsedFiles.insert(KV: std::make_pair(x&: FID, |
1075 | y: UnparsedFileStatus(FE, FoundDirectives))); |
1076 | } |
1077 | #endif |
1078 | } |
1079 | |
1080 | void VerifyDiagnosticConsumer::CheckDiagnostics() { |
1081 | // Ensure any diagnostics go to the primary client. |
1082 | DiagnosticConsumer *CurClient = Diags.getClient(); |
1083 | std::unique_ptr<DiagnosticConsumer> Owner = Diags.takeClient(); |
1084 | Diags.setClient(client: PrimaryClient, ShouldOwnClient: false); |
1085 | |
1086 | #ifndef NDEBUG |
1087 | // In a debug build, scan through any files that may have been missed |
1088 | // during parsing and issue a fatal error if directives are contained |
1089 | // within these files. If a fatal error occurs, this suggests that |
1090 | // this file is being parsed separately from the main file, in which |
1091 | // case consider moving the directives to the correct place, if this |
1092 | // is applicable. |
1093 | if (!UnparsedFiles.empty()) { |
1094 | // Generate a cache of parsed FileEntry pointers for alias lookups. |
1095 | llvm::SmallPtrSet<const FileEntry *, 8> ParsedFileCache; |
1096 | for (const auto &I : ParsedFiles) |
1097 | if (const FileEntry *FE = I.second) |
1098 | ParsedFileCache.insert(Ptr: FE); |
1099 | |
1100 | // Iterate through list of unparsed files. |
1101 | for (const auto &I : UnparsedFiles) { |
1102 | const UnparsedFileStatus &Status = I.second; |
1103 | OptionalFileEntryRef FE = Status.getFile(); |
1104 | |
1105 | // Skip files that have been parsed via an alias. |
1106 | if (FE && ParsedFileCache.count(Ptr: *FE)) |
1107 | continue; |
1108 | |
1109 | // Report a fatal error if this file contained directives. |
1110 | if (Status.foundDirectives()) { |
1111 | llvm::report_fatal_error(reason: "-verify directives found after rather" |
1112 | " than during normal parsing of "+ |
1113 | (FE ? FE->getName() : "(unknown)")); |
1114 | } |
1115 | } |
1116 | |
1117 | // UnparsedFiles has been processed now, so clear it. |
1118 | UnparsedFiles.clear(); |
1119 | } |
1120 | #endif // !NDEBUG |
1121 | |
1122 | if (SrcManager) { |
1123 | // Produce an error if no expected-* directives could be found in the |
1124 | // source file(s) processed. |
1125 | if (State.Status == HasNoDirectives) { |
1126 | Diags.Report(diag::err_verify_no_directives).setForceEmit() |
1127 | << DetailedErrorString(Diags); |
1128 | ++NumErrors; |
1129 | State.Status = HasNoDirectivesReported; |
1130 | } |
1131 | |
1132 | // Check that the expected diagnostics occurred. |
1133 | NumErrors += CheckResults(Diags, SourceMgr&: *SrcManager, Buffer: *Buffer, ED); |
1134 | } else { |
1135 | const DiagnosticLevelMask DiagMask = |
1136 | ~Diags.getDiagnosticOptions().getVerifyIgnoreUnexpected(); |
1137 | if (bool(DiagnosticLevelMask::Error & DiagMask)) |
1138 | NumErrors += PrintUnexpected(Diags, SourceMgr: nullptr, diag_begin: Buffer->err_begin(), |
1139 | diag_end: Buffer->err_end(), Kind: "error"); |
1140 | if (bool(DiagnosticLevelMask::Warning & DiagMask)) |
1141 | NumErrors += PrintUnexpected(Diags, SourceMgr: nullptr, diag_begin: Buffer->warn_begin(), |
1142 | diag_end: Buffer->warn_end(), Kind: "warn"); |
1143 | if (bool(DiagnosticLevelMask::Remark & DiagMask)) |
1144 | NumErrors += PrintUnexpected(Diags, SourceMgr: nullptr, diag_begin: Buffer->remark_begin(), |
1145 | diag_end: Buffer->remark_end(), Kind: "remark"); |
1146 | if (bool(DiagnosticLevelMask::Note & DiagMask)) |
1147 | NumErrors += PrintUnexpected(Diags, SourceMgr: nullptr, diag_begin: Buffer->note_begin(), |
1148 | diag_end: Buffer->note_end(), Kind: "note"); |
1149 | } |
1150 | |
1151 | Diags.setClient(client: CurClient, ShouldOwnClient: Owner.release() != nullptr); |
1152 | |
1153 | // Reset the buffer, we have processed all the diagnostics in it. |
1154 | Buffer.reset(p: new TextDiagnosticBuffer()); |
1155 | ED.Reset(); |
1156 | } |
1157 | |
1158 | std::unique_ptr<Directive> |
1159 | Directive::create(bool RegexKind, SourceLocation DirectiveLoc, |
1160 | SourceLocation DiagnosticLoc, StringRef Spelling, |
1161 | bool MatchAnyFileAndLine, bool MatchAnyLine, StringRef Text, |
1162 | unsigned Min, unsigned Max) { |
1163 | if (!RegexKind) |
1164 | return std::make_unique<StandardDirective>(args&: DirectiveLoc, args&: DiagnosticLoc, |
1165 | args&: Spelling, args&: MatchAnyFileAndLine, |
1166 | args&: MatchAnyLine, args&: Text, args&: Min, args&: Max); |
1167 | |
1168 | // Parse the directive into a regular expression. |
1169 | std::string RegexStr; |
1170 | StringRef S = Text; |
1171 | while (!S.empty()) { |
1172 | if (S.consume_front(Prefix: "{{")) { |
1173 | size_t RegexMatchLength = S.find(Str: "}}"); |
1174 | assert(RegexMatchLength != StringRef::npos); |
1175 | // Append the regex, enclosed in parentheses. |
1176 | RegexStr += "("; |
1177 | RegexStr.append(s: S.data(), n: RegexMatchLength); |
1178 | RegexStr += ")"; |
1179 | S = S.drop_front(N: RegexMatchLength + 2); |
1180 | } else { |
1181 | size_t VerbatimMatchLength = S.find(Str: "{{"); |
1182 | if (VerbatimMatchLength == StringRef::npos) |
1183 | VerbatimMatchLength = S.size(); |
1184 | // Escape and append the fixed string. |
1185 | RegexStr += llvm::Regex::escape(String: S.substr(Start: 0, N: VerbatimMatchLength)); |
1186 | S = S.drop_front(N: VerbatimMatchLength); |
1187 | } |
1188 | } |
1189 | |
1190 | return std::make_unique<RegexDirective>(args&: DirectiveLoc, args&: DiagnosticLoc, args&: Spelling, |
1191 | args&: MatchAnyFileAndLine, args&: MatchAnyLine, |
1192 | args&: Text, args&: Min, args&: Max, args&: RegexStr); |
1193 | } |
1194 |
Definitions
- VerifyFileTracker
- VerifyFileTracker
- FileChanged
- StandardDirective
- StandardDirective
- isValid
- match
- RegexDirective
- RegexDirective
- isValid
- match
- ParseHelper
- ParseHelper
- Next
- Next
- NextMarker
- Search
- SearchClosingBrace
- Advance
- Match
- SkipWhitespace
- Done
- UnattachedDirective
- attachDirective
- MarkerTracker
- Marker
- MarkerTracker
- addMarker
- addDirective
- finalize
- DetailedErrorString
- ParseDirective
- VerifyDiagnosticConsumer
- ~VerifyDiagnosticConsumer
- BeginSourceFile
- EndSourceFile
- HandleDiagnostic
- HandleComment
- findDirectives
- PrintUnexpected
- PrintExpected
- IsFromSameFile
- CheckLists
- CheckResults
- UpdateParsedFileStatus
- CheckDiagnostics
Learn to use CMake with our Intro Training
Find out more