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