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