1 //===- unittests/Basic/DiagnosticTest.cpp -- Diagnostic engine tests ------===//
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
7 //===----------------------------------------------------------------------===//
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"
27 using namespace clang
;
29 // Declare DiagnosticsTestHelper to avoid GCC warning
31 void DiagnosticsTestHelper(DiagnosticsEngine
&diag
);
34 void clang::DiagnosticsTestHelper(DiagnosticsEngine
&diag
) {
35 EXPECT_FALSE(diag
.DiagStates
.empty());
36 EXPECT_TRUE(diag
.DiagStatesByLoc
.empty());
37 EXPECT_TRUE(diag
.DiagStateOnPushStack
.empty());
42 using testing::ElementsAre
;
43 using testing::IsEmpty
;
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);
53 DiagnosticErrorTrap
trap(Diags
);
55 // Diag that would set UncompilableErrorOccurred and ErrorOccurred.
56 Diags
.Report(diag::err_target_unknown_triple
) << "unknown";
58 // Diag that would set UnrecoverableErrorOccurred and ErrorOccurred.
59 Diags
.Report(diag::err_cannot_open_file
) << "file" << "error";
61 // Diag that would set FatalErrorOccurred
62 // (via non-note following a fatal error).
63 Diags
.Report(diag::warn_mt_message
) << "warning";
65 EXPECT_TRUE(trap
.hasErrorOccurred());
66 EXPECT_TRUE(trap
.hasUnrecoverableErrorOccurred());
69 EXPECT_FALSE(Diags
.hasErrorOccurred());
70 EXPECT_FALSE(Diags
.hasFatalErrorOccurred());
71 EXPECT_FALSE(Diags
.hasUncompilableErrorOccurred());
72 EXPECT_FALSE(Diags
.hasUnrecoverableErrorOccurred());
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
);
83 // Diag that would set UnrecoverableErrorOccurred and ErrorOccurred.
84 Diags
.Report(diag::err_cannot_open_file
) << "file" << "error";
86 // Diag that would set FatalErrorOccurred
87 // (via non-note following a fatal error).
88 Diags
.Report(diag::warn_mt_message
) << "warning";
90 EXPECT_TRUE(Diags
.hasErrorOccurred());
91 EXPECT_EQ(Diags
.hasFatalErrorOccurred(), FatalsAsError
? 0u : 1u);
92 EXPECT_TRUE(Diags
.hasUncompilableErrorOccurred());
93 EXPECT_TRUE(Diags
.hasUnrecoverableErrorOccurred());
95 // The warning should be emitted and counted only if we're not suppressing
96 // after fatal errors.
97 EXPECT_EQ(Diags
.getNumWarnings(), FatalsAsError
);
101 TEST(DiagnosticTest
, tooManyErrorsIsAlwaysFatal
) {
102 DiagnosticsEngine
Diags(new DiagnosticIDs(), new DiagnosticOptions
,
103 new IgnoringDiagConsumer());
104 Diags
.setFatalsAsError(true);
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());
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
);
116 // Check that soft RESET works as intended
117 TEST(DiagnosticTest
, softReset
) {
118 DiagnosticsEngine
Diags(new DiagnosticIDs(), new DiagnosticOptions
,
119 new IgnoringDiagConsumer());
121 unsigned numWarnings
= 0U, numErrors
= 0U;
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());
131 EXPECT_EQ(Diags
.getNumWarnings(), numWarnings
);
132 EXPECT_EQ(Diags
.getNumErrors(), numErrors
);
134 // Check for private variables of DiagnosticsEngine differentiating soft reset
135 DiagnosticsTestHelper(Diags
);
137 EXPECT_TRUE(Diags
.isLastDiagnosticIgnored());
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
)
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
);
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);
162 TEST(DiagnosticTest
, storedDiagEmptyWarning
) {
163 DiagnosticsEngine
Diags(new DiagnosticIDs(), new DiagnosticOptions
);
165 class CaptureDiagnosticConsumer
: public DiagnosticConsumer
{
167 SmallVector
<StoredDiagnostic
> StoredDiags
;
169 void HandleDiagnostic(DiagnosticsEngine::Level level
,
170 const Diagnostic
&Info
) override
{
171 StoredDiags
.push_back(StoredDiagnostic(level
, Info
));
175 CaptureDiagnosticConsumer CaptureConsumer
;
176 Diags
.setClient(&CaptureConsumer
, /*ShouldOwnClient=*/false);
177 Diags
.Report(diag::pp_hash_warning
) << "";
178 ASSERT_TRUE(CaptureConsumer
.StoredDiags
.size() == 1);
180 // Make sure an empty warning can round-trip with \c StoredDiagnostic.
181 Diags
.Report(CaptureConsumer
.StoredDiags
.front());
184 class SuppressionMappingTest
: public testing::Test
{
186 SuppressionMappingTest() {
187 Diags
.setClient(&CaptureConsumer
, /*ShouldOwnClient=*/false);
191 llvm::IntrusiveRefCntPtr
<llvm::vfs::InMemoryFileSystem
> FS
=
192 llvm::makeIntrusiveRefCnt
<llvm::vfs::InMemoryFileSystem
>();
193 DiagnosticsEngine Diags
{new DiagnosticIDs(), new DiagnosticOptions
};
195 llvm::ArrayRef
<StoredDiagnostic
> diags() {
196 return CaptureConsumer
.StoredDiags
;
200 class CaptureDiagnosticConsumer
: public DiagnosticConsumer
{
202 std::vector
<StoredDiagnostic
> StoredDiags
;
204 void HandleDiagnostic(DiagnosticsEngine::Level level
,
205 const Diagnostic
&Info
) override
{
206 StoredDiags
.push_back(StoredDiagnostic(level
, Info
));
209 CaptureDiagnosticConsumer CaptureConsumer
;
212 MATCHER_P(WithMessage
, Msg
, "has diagnostic message") {
213 return arg
.getMessage() == Msg
;
215 MATCHER(IsError
, "has error severity") {
216 return arg
.getLevel() == DiagnosticsEngine::Level::Error
;
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'"),
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
);
234 WithMessage("failed to process suppression mapping file "
235 "'foo.txt': malformed line 1: 'asdf'"),
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'")));
248 TEST_F(SuppressionMappingTest
, SuppressesGroup
) {
249 llvm::StringLiteral SuppressionMappingFile
= R
"(
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());
259 Diags
.isSuppressedViaMapping(diag::warn_unused_function
, "foo.cpp"));
260 EXPECT_FALSE(Diags
.isSuppressedViaMapping(diag::warn_deprecated
, "foo.cpp"));
263 TEST_F(SuppressionMappingTest
, EmitCategoryIsExcluded
) {
264 llvm::StringLiteral SuppressionMappingFile
= R
"(
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());
275 Diags
.isSuppressedViaMapping(diag::warn_unused_function
, "bar.cpp"));
277 Diags
.isSuppressedViaMapping(diag::warn_unused_function
, "foo.cpp"));
280 TEST_F(SuppressionMappingTest
, LongestMatchWins
) {
281 llvm::StringLiteral SuppressionMappingFile
= R
"(
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());
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"));
300 TEST_F(SuppressionMappingTest
, IsIgnored
) {
301 llvm::StringLiteral SuppressionMappingFile
= R
"(
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());
311 FileManager
FM({}, FS
);
312 SourceManager
SM(Diags
, FM
);
315 SM
.createFileID(llvm::MemoryBuffer::getMemBuffer("", "clang/foo.h"));
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
);
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
)));
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
)));