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