xref: /llvm-project/clang/unittests/Basic/DiagnosticTest.cpp (revision d74214cc8c03159e5d1f1168a09368cf3b23fd5f)
1 //===- unittests/Basic/DiagnosticTest.cpp -- Diagnostic engine tests ------===//
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 #include "clang/Basic/Diagnostic.h"
10 #include "clang/Basic/DiagnosticError.h"
11 #include "clang/Basic/DiagnosticIDs.h"
12 #include "clang/Basic/DiagnosticLex.h"
13 #include "clang/Basic/DiagnosticSema.h"
14 #include "clang/Basic/FileManager.h"
15 #include "clang/Basic/SourceLocation.h"
16 #include "clang/Basic/SourceManager.h"
17 #include "llvm/ADT/ArrayRef.h"
18 #include "llvm/ADT/IntrusiveRefCntPtr.h"
19 #include "llvm/ADT/StringRef.h"
20 #include "llvm/Support/MemoryBuffer.h"
21 #include "llvm/Support/VirtualFileSystem.h"
22 #include "gmock/gmock.h"
23 #include "gtest/gtest.h"
24 #include <memory>
25 #include <optional>
26 #include <vector>
27 
28 using namespace llvm;
29 using namespace clang;
30 
31 // Declare DiagnosticsTestHelper to avoid GCC warning
32 namespace clang {
33 void DiagnosticsTestHelper(DiagnosticsEngine &diag);
34 }
35 
36 void clang::DiagnosticsTestHelper(DiagnosticsEngine &diag) {
37   EXPECT_FALSE(diag.DiagStates.empty());
38   EXPECT_TRUE(diag.DiagStatesByLoc.empty());
39   EXPECT_TRUE(diag.DiagStateOnPushStack.empty());
40 }
41 
42 namespace {
43 using testing::AllOf;
44 using testing::ElementsAre;
45 using testing::IsEmpty;
46 
47 // Check that DiagnosticErrorTrap works with SuppressAllDiagnostics.
48 TEST(DiagnosticTest, suppressAndTrap) {
49   DiagnosticsEngine Diags(new DiagnosticIDs(),
50                           new DiagnosticOptions,
51                           new IgnoringDiagConsumer());
52   Diags.setSuppressAllDiagnostics(true);
53 
54   {
55     DiagnosticErrorTrap trap(Diags);
56 
57     // Diag that would set UncompilableErrorOccurred and ErrorOccurred.
58     Diags.Report(diag::err_target_unknown_triple) << "unknown";
59 
60     // Diag that would set UnrecoverableErrorOccurred and ErrorOccurred.
61     Diags.Report(diag::err_cannot_open_file) << "file" << "error";
62 
63     // Diag that would set FatalErrorOccurred
64     // (via non-note following a fatal error).
65     Diags.Report(diag::warn_mt_message) << "warning";
66 
67     EXPECT_TRUE(trap.hasErrorOccurred());
68     EXPECT_TRUE(trap.hasUnrecoverableErrorOccurred());
69   }
70 
71   EXPECT_FALSE(Diags.hasErrorOccurred());
72   EXPECT_FALSE(Diags.hasFatalErrorOccurred());
73   EXPECT_FALSE(Diags.hasUncompilableErrorOccurred());
74   EXPECT_FALSE(Diags.hasUnrecoverableErrorOccurred());
75 }
76 
77 // Check that FatalsAsError works as intended
78 TEST(DiagnosticTest, fatalsAsError) {
79   for (unsigned FatalsAsError = 0; FatalsAsError != 2; ++FatalsAsError) {
80     DiagnosticsEngine Diags(new DiagnosticIDs(),
81                             new DiagnosticOptions,
82                             new IgnoringDiagConsumer());
83     Diags.setFatalsAsError(FatalsAsError);
84 
85     // Diag that would set UnrecoverableErrorOccurred and ErrorOccurred.
86     Diags.Report(diag::err_cannot_open_file) << "file" << "error";
87 
88     // Diag that would set FatalErrorOccurred
89     // (via non-note following a fatal error).
90     Diags.Report(diag::warn_mt_message) << "warning";
91 
92     EXPECT_TRUE(Diags.hasErrorOccurred());
93     EXPECT_EQ(Diags.hasFatalErrorOccurred(), FatalsAsError ? 0u : 1u);
94     EXPECT_TRUE(Diags.hasUncompilableErrorOccurred());
95     EXPECT_TRUE(Diags.hasUnrecoverableErrorOccurred());
96 
97     // The warning should be emitted and counted only if we're not suppressing
98     // after fatal errors.
99     EXPECT_EQ(Diags.getNumWarnings(), FatalsAsError);
100   }
101 }
102 
103 TEST(DiagnosticTest, tooManyErrorsIsAlwaysFatal) {
104   DiagnosticsEngine Diags(new DiagnosticIDs(), new DiagnosticOptions,
105                           new IgnoringDiagConsumer());
106   Diags.setFatalsAsError(true);
107 
108   // Report a fatal_too_many_errors diagnostic to ensure that still
109   // acts as a fatal error despite downgrading fatal errors to errors.
110   Diags.Report(diag::fatal_too_many_errors);
111   EXPECT_TRUE(Diags.hasFatalErrorOccurred());
112 
113   // Ensure that the severity of that diagnostic is really "fatal".
114   EXPECT_EQ(Diags.getDiagnosticLevel(diag::fatal_too_many_errors, {}),
115             DiagnosticsEngine::Level::Fatal);
116 }
117 
118 // Check that soft RESET works as intended
119 TEST(DiagnosticTest, softReset) {
120   DiagnosticsEngine Diags(new DiagnosticIDs(), new DiagnosticOptions,
121                           new IgnoringDiagConsumer());
122 
123   unsigned numWarnings = 0U, numErrors = 0U;
124 
125   Diags.Reset(true);
126   // Check For ErrorOccurred and TrapNumErrorsOccurred
127   EXPECT_FALSE(Diags.hasErrorOccurred());
128   EXPECT_FALSE(Diags.hasFatalErrorOccurred());
129   EXPECT_FALSE(Diags.hasUncompilableErrorOccurred());
130   // Check for UnrecoverableErrorOccurred and TrapNumUnrecoverableErrorsOccurred
131   EXPECT_FALSE(Diags.hasUnrecoverableErrorOccurred());
132 
133   EXPECT_EQ(Diags.getNumWarnings(), numWarnings);
134   EXPECT_EQ(Diags.getNumErrors(), numErrors);
135 
136   // Check for private variables of DiagnosticsEngine differentiating soft reset
137   DiagnosticsTestHelper(Diags);
138 
139   EXPECT_TRUE(Diags.isLastDiagnosticIgnored());
140 }
141 
142 TEST(DiagnosticTest, diagnosticError) {
143   DiagnosticsEngine Diags(new DiagnosticIDs(), new DiagnosticOptions,
144                           new IgnoringDiagConsumer());
145   PartialDiagnostic::DiagStorageAllocator Alloc;
146   llvm::Expected<std::pair<int, int>> Value = DiagnosticError::create(
147       SourceLocation(), PartialDiagnostic(diag::err_cannot_open_file, Alloc)
148                             << "file"
149                             << "error");
150   ASSERT_TRUE(!Value);
151   llvm::Error Err = Value.takeError();
152   std::optional<PartialDiagnosticAt> ErrDiag = DiagnosticError::take(Err);
153   llvm::cantFail(std::move(Err));
154   ASSERT_FALSE(!ErrDiag);
155   EXPECT_EQ(ErrDiag->first, SourceLocation());
156   EXPECT_EQ(ErrDiag->second.getDiagID(), diag::err_cannot_open_file);
157 
158   Value = std::make_pair(20, 1);
159   ASSERT_FALSE(!Value);
160   EXPECT_EQ(*Value, std::make_pair(20, 1));
161   EXPECT_EQ(Value->first, 20);
162 }
163 
164 TEST(DiagnosticTest, storedDiagEmptyWarning) {
165   DiagnosticsEngine Diags(new DiagnosticIDs(), new DiagnosticOptions);
166 
167   class CaptureDiagnosticConsumer : public DiagnosticConsumer {
168   public:
169     SmallVector<StoredDiagnostic> StoredDiags;
170 
171     void HandleDiagnostic(DiagnosticsEngine::Level level,
172                           const Diagnostic &Info) override {
173       StoredDiags.push_back(StoredDiagnostic(level, Info));
174     }
175   };
176 
177   CaptureDiagnosticConsumer CaptureConsumer;
178   Diags.setClient(&CaptureConsumer, /*ShouldOwnClient=*/false);
179   Diags.Report(diag::pp_hash_warning) << "";
180   ASSERT_TRUE(CaptureConsumer.StoredDiags.size() == 1);
181 
182   // Make sure an empty warning can round-trip with \c StoredDiagnostic.
183   Diags.Report(CaptureConsumer.StoredDiags.front());
184 }
185 
186 class SuppressionMappingTest : public testing::Test {
187 public:
188   SuppressionMappingTest() {
189     Diags.setClient(&CaptureConsumer, /*ShouldOwnClient=*/false);
190   }
191 
192 protected:
193   llvm::IntrusiveRefCntPtr<llvm::vfs::InMemoryFileSystem> FS =
194       llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
195   DiagnosticsEngine Diags{new DiagnosticIDs(), new DiagnosticOptions};
196 
197   llvm::ArrayRef<StoredDiagnostic> diags() {
198     return CaptureConsumer.StoredDiags;
199   }
200 
201   SourceLocation locForFile(llvm::StringRef FileName) {
202     auto Buf = MemoryBuffer::getMemBuffer("", FileName);
203     SourceManager &SM = Diags.getSourceManager();
204     FileID FooID = SM.createFileID(std::move(Buf));
205     return SM.getLocForStartOfFile(FooID);
206   }
207 
208 private:
209   FileManager FM{{}, FS};
210   SourceManager SM{Diags, FM};
211 
212   class CaptureDiagnosticConsumer : public DiagnosticConsumer {
213   public:
214     std::vector<StoredDiagnostic> StoredDiags;
215 
216     void HandleDiagnostic(DiagnosticsEngine::Level level,
217                           const Diagnostic &Info) override {
218       StoredDiags.push_back(StoredDiagnostic(level, Info));
219     }
220   };
221   CaptureDiagnosticConsumer CaptureConsumer;
222 };
223 
224 MATCHER_P(WithMessage, Msg, "has diagnostic message") {
225   return arg.getMessage() == Msg;
226 }
227 MATCHER(IsError, "has error severity") {
228   return arg.getLevel() == DiagnosticsEngine::Level::Error;
229 }
230 
231 TEST_F(SuppressionMappingTest, MissingMappingFile) {
232   Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
233   clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
234   EXPECT_THAT(diags(), ElementsAre(AllOf(
235                            WithMessage("no such file or directory: 'foo.txt'"),
236                            IsError())));
237 }
238 
239 TEST_F(SuppressionMappingTest, MalformedFile) {
240   Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
241   FS->addFile("foo.txt", /*ModificationTime=*/{},
242               llvm::MemoryBuffer::getMemBuffer("asdf", "foo.txt"));
243   clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
244   EXPECT_THAT(diags(),
245               ElementsAre(AllOf(
246                   WithMessage("failed to process suppression mapping file "
247                               "'foo.txt': malformed line 1: 'asdf'"),
248                   IsError())));
249 }
250 
251 TEST_F(SuppressionMappingTest, UnknownDiagName) {
252   Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
253   FS->addFile("foo.txt", /*ModificationTime=*/{},
254               llvm::MemoryBuffer::getMemBuffer("[non-existing-warning]"));
255   clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
256   EXPECT_THAT(diags(), ElementsAre(WithMessage(
257                            "unknown warning option 'non-existing-warning'")));
258 }
259 
260 TEST_F(SuppressionMappingTest, SuppressesGroup) {
261   llvm::StringLiteral SuppressionMappingFile = R"(
262   [unused]
263   src:*)";
264   Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
265   FS->addFile("foo.txt", /*ModificationTime=*/{},
266               llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));
267   clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
268   EXPECT_THAT(diags(), IsEmpty());
269 
270   SourceLocation FooLoc = locForFile("foo.cpp");
271   EXPECT_TRUE(Diags.isSuppressedViaMapping(diag::warn_unused_function, FooLoc));
272   EXPECT_FALSE(Diags.isSuppressedViaMapping(diag::warn_deprecated, FooLoc));
273 }
274 
275 TEST_F(SuppressionMappingTest, EmitCategoryIsExcluded) {
276   llvm::StringLiteral SuppressionMappingFile = R"(
277   [unused]
278   src:*
279   src:*foo.cpp=emit)";
280   Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
281   FS->addFile("foo.txt", /*ModificationTime=*/{},
282               llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));
283   clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
284   EXPECT_THAT(diags(), IsEmpty());
285 
286   EXPECT_TRUE(Diags.isSuppressedViaMapping(diag::warn_unused_function,
287                                            locForFile("bar.cpp")));
288   EXPECT_FALSE(Diags.isSuppressedViaMapping(diag::warn_unused_function,
289                                             locForFile("foo.cpp")));
290 }
291 
292 TEST_F(SuppressionMappingTest, LongestMatchWins) {
293   llvm::StringLiteral SuppressionMappingFile = R"(
294   [unused]
295   src:*clang/*
296   src:*clang/lib/Sema/*=emit
297   src:*clang/lib/Sema/foo*)";
298   Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
299   FS->addFile("foo.txt", /*ModificationTime=*/{},
300               llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));
301   clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
302   EXPECT_THAT(diags(), IsEmpty());
303 
304   EXPECT_TRUE(Diags.isSuppressedViaMapping(
305       diag::warn_unused_function, locForFile("clang/lib/Basic/foo.h")));
306   EXPECT_FALSE(Diags.isSuppressedViaMapping(
307       diag::warn_unused_function, locForFile("clang/lib/Sema/bar.h")));
308   EXPECT_TRUE(Diags.isSuppressedViaMapping(diag::warn_unused_function,
309                                            locForFile("clang/lib/Sema/foo.h")));
310 }
311 
312 TEST_F(SuppressionMappingTest, IsIgnored) {
313   llvm::StringLiteral SuppressionMappingFile = R"(
314   [unused]
315   src:*clang/*)";
316   Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";
317   Diags.getDiagnosticOptions().Warnings = {"unused"};
318   FS->addFile("foo.txt", /*ModificationTime=*/{},
319               llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));
320   clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);
321   ASSERT_THAT(diags(), IsEmpty());
322 
323   SourceManager &SM = Diags.getSourceManager();
324   auto ClangID =
325       SM.createFileID(llvm::MemoryBuffer::getMemBuffer("", "clang/foo.h"));
326   auto NonClangID =
327       SM.createFileID(llvm::MemoryBuffer::getMemBuffer("", "llvm/foo.h"));
328   auto PresumedClangID =
329       SM.createFileID(llvm::MemoryBuffer::getMemBuffer("", "llvm/foo2.h"));
330   // Add a line directive to point into clang/foo.h
331   SM.AddLineNote(SM.getLocForStartOfFile(PresumedClangID), 42,
332                  SM.getLineTableFilenameID("clang/foo.h"), false, false,
333                  clang::SrcMgr::C_User);
334 
335   EXPECT_TRUE(Diags.isIgnored(diag::warn_unused_function,
336                               SM.getLocForStartOfFile(ClangID)));
337   EXPECT_FALSE(Diags.isIgnored(diag::warn_unused_function,
338                                SM.getLocForStartOfFile(NonClangID)));
339   EXPECT_TRUE(Diags.isIgnored(diag::warn_unused_function,
340                               SM.getLocForStartOfFile(PresumedClangID)));
341 
342   // Pretend we have a clang-diagnostic pragma to enforce the warning. Make sure
343   // suppressing mapping doesn't take over.
344   Diags.setSeverity(diag::warn_unused_function, diag::Severity::Error,
345                     SM.getLocForStartOfFile(ClangID));
346   EXPECT_FALSE(Diags.isIgnored(diag::warn_unused_function,
347                                SM.getLocForStartOfFile(ClangID)));
348 }
349 } // namespace
350