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

Provided by KDAB

Privacy Policy
Update your C++ knowledge – Modern C++11/14/17 Training
Find out more

source code of clang/unittests/Basic/DiagnosticTest.cpp