| //===- VerifyDiagnosticConsumer.h - Verifying Diagnostic Client -*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #ifndef LLVM_CLANG_FRONTEND_VERIFYDIAGNOSTICCONSUMER_H |
| #define LLVM_CLANG_FRONTEND_VERIFYDIAGNOSTICCONSUMER_H |
| |
| #include "clang/Basic/Diagnostic.h" |
| #include "clang/Basic/LLVM.h" |
| #include "clang/Basic/SourceLocation.h" |
| #include "clang/Lex/Preprocessor.h" |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/PointerIntPair.h" |
| #include "llvm/ADT/StringRef.h" |
| #include <cassert> |
| #include <limits> |
| #include <memory> |
| #include <string> |
| #include <vector> |
| |
| namespace clang { |
| |
| class FileEntry; |
| class LangOptions; |
| class SourceManager; |
| class TextDiagnosticBuffer; |
| |
| /// VerifyDiagnosticConsumer - Create a diagnostic client which will use |
| /// markers in the input source to check that all the emitted diagnostics match |
| /// those expected. |
| /// |
| /// INVOKING THE DIAGNOSTIC CHECKER: |
| /// |
| /// VerifyDiagnosticConsumer is typically invoked via the "-verify" option to |
| /// "clang -cc1". "-verify" is equivalent to "-verify=expected", so all |
| /// diagnostics are typically specified with the prefix "expected". For |
| /// example: |
| /// |
| /// \code |
| /// int A = B; // expected-error {{use of undeclared identifier 'B'}} |
| /// \endcode |
| /// |
| /// Custom prefixes can be specified as a comma-separated sequence. Each |
| /// prefix must start with a letter and contain only alphanumeric characters, |
| /// hyphens, and underscores. For example, given just "-verify=foo,bar", |
| /// the above diagnostic would be ignored, but the following diagnostics would |
| /// be recognized: |
| /// |
| /// \code |
| /// int A = B; // foo-error {{use of undeclared identifier 'B'}} |
| /// int C = D; // bar-error {{use of undeclared identifier 'D'}} |
| /// \endcode |
| /// |
| /// Multiple occurrences accumulate prefixes. For example, |
| /// "-verify -verify=foo,bar -verify=baz" is equivalent to |
| /// "-verify=expected,foo,bar,baz". |
| /// |
| /// SPECIFYING DIAGNOSTICS: |
| /// |
| /// Indicating that a line expects an error or a warning is simple. Put a |
| /// comment on the line that has the diagnostic, use: |
| /// |
| /// \code |
| /// expected-{error,warning,remark,note} |
| /// \endcode |
| /// |
| /// to tag if it's an expected error, remark or warning, and place the expected |
| /// text between {{ and }} markers. The full text doesn't have to be included, |
| /// only enough to ensure that the correct diagnostic was emitted. |
| /// |
| /// Here's an example: |
| /// |
| /// \code |
| /// int A = B; // expected-error {{use of undeclared identifier 'B'}} |
| /// \endcode |
| /// |
| /// You can place as many diagnostics on one line as you wish. To make the code |
| /// more readable, you can use slash-newline to separate out the diagnostics. |
| /// |
| /// Alternatively, it is possible to specify the line on which the diagnostic |
| /// should appear by appending "@<line>" to "expected-<type>", for example: |
| /// |
| /// \code |
| /// #warning some text |
| /// // expected-warning@10 {{some text}} |
| /// \endcode |
| /// |
| /// The line number may be absolute (as above), or relative to the current |
| /// line by prefixing the number with either '+' or '-'. |
| /// |
| /// If the diagnostic is generated in a separate file, for example in a shared |
| /// header file, it may be beneficial to be able to declare the file in which |
| /// the diagnostic will appear, rather than placing the expected-* directive in |
| /// the actual file itself. This can be done using the following syntax: |
| /// |
| /// \code |
| /// // expected-error@path/include.h:15 {{error message}} |
| /// \endcode |
| /// |
| /// The path can be absolute or relative and the same search paths will be used |
| /// as for #include directives. The line number in an external file may be |
| /// substituted with '*' meaning that any line number will match (useful where |
| /// the included file is, for example, a system header where the actual line |
| /// number may change and is not critical). |
| /// |
| /// As an alternative to specifying a fixed line number, the location of a |
| /// diagnostic can instead be indicated by a marker of the form "#<marker>". |
| /// Markers are specified by including them in a comment, and then referenced |
| /// by appending the marker to the diagnostic with "@#<marker>": |
| /// |
| /// \code |
| /// #warning some text // #1 |
| /// // expected-warning@#1 {{some text}} |
| /// \endcode |
| /// |
| /// The name of a marker used in a directive must be unique within the |
| /// compilation. |
| /// |
| /// The simple syntax above allows each specification to match exactly one |
| /// error. You can use the extended syntax to customize this. The extended |
| /// syntax is "expected-<type> <n> {{diag text}}", where \<type> is one of |
| /// "error", "warning" or "note", and \<n> is a positive integer. This allows |
| /// the diagnostic to appear as many times as specified. Example: |
| /// |
| /// \code |
| /// void f(); // expected-note 2 {{previous declaration is here}} |
| /// \endcode |
| /// |
| /// Where the diagnostic is expected to occur a minimum number of times, this |
| /// can be specified by appending a '+' to the number. Example: |
| /// |
| /// \code |
| /// void f(); // expected-note 0+ {{previous declaration is here}} |
| /// void g(); // expected-note 1+ {{previous declaration is here}} |
| /// \endcode |
| /// |
| /// In the first example, the diagnostic becomes optional, i.e. it will be |
| /// swallowed if it occurs, but will not generate an error if it does not |
| /// occur. In the second example, the diagnostic must occur at least once. |
| /// As a short-hand, "one or more" can be specified simply by '+'. Example: |
| /// |
| /// \code |
| /// void g(); // expected-note + {{previous declaration is here}} |
| /// \endcode |
| /// |
| /// A range can also be specified by "<n>-<m>". Example: |
| /// |
| /// \code |
| /// void f(); // expected-note 0-1 {{previous declaration is here}} |
| /// \endcode |
| /// |
| /// In this example, the diagnostic may appear only once, if at all. |
| /// |
| /// Regex matching mode may be selected by appending '-re' to type and |
| /// including regexes wrapped in double curly braces in the directive, such as: |
| /// |
| /// \code |
| /// expected-error-re {{format specifies type 'wchar_t **' (aka '{{.+}}')}} |
| /// \endcode |
| /// |
| /// Examples matching error: "variable has incomplete type 'struct s'" |
| /// |
| /// \code |
| /// // expected-error {{variable has incomplete type 'struct s'}} |
| /// // expected-error {{variable has incomplete type}} |
| /// |
| /// // expected-error-re {{variable has type 'struct {{.}}'}} |
| /// // expected-error-re {{variable has type 'struct {{.*}}'}} |
| /// // expected-error-re {{variable has type 'struct {{(.*)}}'}} |
| /// // expected-error-re {{variable has type 'struct{{[[:space:]](.*)}}'}} |
| /// \endcode |
| /// |
| /// VerifyDiagnosticConsumer expects at least one expected-* directive to |
| /// be found inside the source code. If no diagnostics are expected the |
| /// following directive can be used to indicate this: |
| /// |
| /// \code |
| /// // expected-no-diagnostics |
| /// \endcode |
| /// |
| class VerifyDiagnosticConsumer: public DiagnosticConsumer, |
| public CommentHandler { |
| public: |
| /// Directive - Abstract class representing a parsed verify directive. |
| /// |
| class Directive { |
| public: |
| static std::unique_ptr<Directive> create(bool RegexKind, |
| SourceLocation DirectiveLoc, |
| SourceLocation DiagnosticLoc, |
| bool MatchAnyLine, StringRef Text, |
| unsigned Min, unsigned Max); |
| |
| public: |
| /// Constant representing n or more matches. |
| static const unsigned MaxCount = std::numeric_limits<unsigned>::max(); |
| |
| SourceLocation DirectiveLoc; |
| SourceLocation DiagnosticLoc; |
| const std::string Text; |
| unsigned Min, Max; |
| bool MatchAnyLine; |
| |
| Directive(const Directive &) = delete; |
| Directive &operator=(const Directive &) = delete; |
| virtual ~Directive() = default; |
| |
| // Returns true if directive text is valid. |
| // Otherwise returns false and populates E. |
| virtual bool isValid(std::string &Error) = 0; |
| |
| // Returns true on match. |
| virtual bool match(StringRef S) = 0; |
| |
| protected: |
| Directive(SourceLocation DirectiveLoc, SourceLocation DiagnosticLoc, |
| bool MatchAnyLine, StringRef Text, unsigned Min, unsigned Max) |
| : DirectiveLoc(DirectiveLoc), DiagnosticLoc(DiagnosticLoc), |
| Text(Text), Min(Min), Max(Max), MatchAnyLine(MatchAnyLine) { |
| assert(!DirectiveLoc.isInvalid() && "DirectiveLoc is invalid!"); |
| assert((!DiagnosticLoc.isInvalid() || MatchAnyLine) && |
| "DiagnosticLoc is invalid!"); |
| } |
| }; |
| |
| using DirectiveList = std::vector<std::unique_ptr<Directive>>; |
| |
| /// ExpectedData - owns directive objects and deletes on destructor. |
| struct ExpectedData { |
| DirectiveList Errors; |
| DirectiveList Warnings; |
| DirectiveList Remarks; |
| DirectiveList Notes; |
| |
| void Reset() { |
| Errors.clear(); |
| Warnings.clear(); |
| Remarks.clear(); |
| Notes.clear(); |
| } |
| }; |
| |
| enum DirectiveStatus { |
| HasNoDirectives, |
| HasNoDirectivesReported, |
| HasExpectedNoDiagnostics, |
| HasOtherExpectedDirectives |
| }; |
| |
| class MarkerTracker; |
| |
| private: |
| DiagnosticsEngine &Diags; |
| DiagnosticConsumer *PrimaryClient; |
| std::unique_ptr<DiagnosticConsumer> PrimaryClientOwner; |
| std::unique_ptr<TextDiagnosticBuffer> Buffer; |
| std::unique_ptr<MarkerTracker> Markers; |
| const Preprocessor *CurrentPreprocessor = nullptr; |
| const LangOptions *LangOpts = nullptr; |
| SourceManager *SrcManager = nullptr; |
| unsigned ActiveSourceFiles = 0; |
| DirectiveStatus Status; |
| ExpectedData ED; |
| |
| void CheckDiagnostics(); |
| |
| void setSourceManager(SourceManager &SM) { |
| assert((!SrcManager || SrcManager == &SM) && "SourceManager changed!"); |
| SrcManager = &SM; |
| } |
| |
| // These facilities are used for validation in debug builds. |
| class UnparsedFileStatus { |
| llvm::PointerIntPair<const FileEntry *, 1, bool> Data; |
| |
| public: |
| UnparsedFileStatus(const FileEntry *File, bool FoundDirectives) |
| : Data(File, FoundDirectives) {} |
| |
| const FileEntry *getFile() const { return Data.getPointer(); } |
| bool foundDirectives() const { return Data.getInt(); } |
| }; |
| |
| using ParsedFilesMap = llvm::DenseMap<FileID, const FileEntry *>; |
| using UnparsedFilesMap = llvm::DenseMap<FileID, UnparsedFileStatus>; |
| |
| ParsedFilesMap ParsedFiles; |
| UnparsedFilesMap UnparsedFiles; |
| |
| public: |
| /// Create a new verifying diagnostic client, which will issue errors to |
| /// the currently-attached diagnostic client when a diagnostic does not match |
| /// what is expected (as indicated in the source file). |
| VerifyDiagnosticConsumer(DiagnosticsEngine &Diags); |
| ~VerifyDiagnosticConsumer() override; |
| |
| void BeginSourceFile(const LangOptions &LangOpts, |
| const Preprocessor *PP) override; |
| |
| void EndSourceFile() override; |
| |
| enum ParsedStatus { |
| /// File has been processed via HandleComment. |
| IsParsed, |
| |
| /// File has diagnostics and may have directives. |
| IsUnparsed, |
| |
| /// File has diagnostics but guaranteed no directives. |
| IsUnparsedNoDirectives |
| }; |
| |
| /// Update lists of parsed and unparsed files. |
| void UpdateParsedFileStatus(SourceManager &SM, FileID FID, ParsedStatus PS); |
| |
| bool HandleComment(Preprocessor &PP, SourceRange Comment) override; |
| |
| void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, |
| const Diagnostic &Info) override; |
| }; |
| |
| } // namespace clang |
| |
| #endif // LLVM_CLANG_FRONTEND_VERIFYDIAGNOSTICCONSUMER_H |