398 lines · cpp
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-exception6//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 warning32namespace 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(DiagnosticIDs::create(), 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 FatalErrorOccurred64 // (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 intended78TEST(DiagnosticTest, fatalsAsError) {79 for (unsigned FatalsAsError = 0; FatalsAsError != 2; ++FatalsAsError) {80 DiagnosticOptions DiagOpts;81 DiagnosticsEngine Diags(DiagnosticIDs::create(), 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 FatalErrorOccurred89 // (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 suppressing98 // after fatal errors.99 EXPECT_EQ(Diags.getNumWarnings(), FatalsAsError);100 }101}102 103TEST(DiagnosticTest, tooManyErrorsIsAlwaysFatal) {104 DiagnosticOptions DiagOpts;105 DiagnosticsEngine Diags(DiagnosticIDs::create(), DiagOpts,106 new IgnoringDiagConsumer());107 Diags.setFatalsAsError(true);108 109 // Report a fatal_too_many_errors diagnostic to ensure that still110 // 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 intended120TEST(DiagnosticTest, softReset) {121 DiagnosticOptions DiagOpts;122 DiagnosticsEngine Diags(DiagnosticIDs::create(), DiagOpts,123 new IgnoringDiagConsumer());124 125 unsigned numWarnings = 0U, numErrors = 0U;126 127 Diags.Reset(true);128 // Check For ErrorOccurred and TrapNumErrorsOccurred129 EXPECT_FALSE(Diags.hasErrorOccurred());130 EXPECT_FALSE(Diags.hasFatalErrorOccurred());131 EXPECT_FALSE(Diags.hasUncompilableErrorOccurred());132 // Check for UnrecoverableErrorOccurred and TrapNumUnrecoverableErrorsOccurred133 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 reset139 DiagnosticsTestHelper(Diags);140 141 EXPECT_TRUE(Diags.isLastDiagnosticIgnored());142}143 144TEST(DiagnosticTest, diagnosticError) {145 DiagnosticOptions DiagOpts;146 DiagnosticsEngine Diags(DiagnosticIDs::create(), 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(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(20, 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(DiagnosticIDs::create(), 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(StoredDiagnostic(level, Info));178 }179 };180 181 CaptureDiagnosticConsumer CaptureConsumer;182 Diags.setClient(&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(CaptureConsumer.StoredDiags.front());188}189 190class SuppressionMappingTest : public testing::Test {191public:192 SuppressionMappingTest() {193 Diags.setClient(&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{DiagnosticIDs::create(), DiagOpts};201 202 llvm::ArrayRef<StoredDiagnostic> diags() {203 return CaptureConsumer.StoredDiags;204 }205 206 SourceLocation locForFile(llvm::StringRef FileName) {207 auto Buf = MemoryBuffer::getMemBuffer("", FileName);208 SourceManager &SM = Diags.getSourceManager();209 FileID FooID = SM.createFileID(std::move(Buf));210 return SM.getLocForStartOfFile(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(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, Diags.getDiagnosticOptions(), *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("foo.txt", /*ModificationTime=*/{},247 llvm::MemoryBuffer::getMemBuffer("asdf", "foo.txt"));248 clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *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("foo.txt", /*ModificationTime=*/{},259 llvm::MemoryBuffer::getMemBuffer("[non-existing-warning]"));260 clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *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("foo.txt", /*ModificationTime=*/{},271 llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));272 clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);273 EXPECT_THAT(diags(), IsEmpty());274 275 SourceLocation FooLoc = locForFile("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("foo.txt", /*ModificationTime=*/{},287 llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));288 clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *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, LastMatchWins) {298 llvm::StringLiteral SuppressionMappingFile = R"(299 [unused]300 src:*clang/*301 src:*clang/lib/Sema/*=emit302 src:*clang/lib/Sema/foo*)";303 Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";304 FS->addFile("foo.txt", /*ModificationTime=*/{},305 llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));306 clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *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, LongShortMatch) {318 llvm::StringLiteral SuppressionMappingFile = R"(319 [unused]320 src:*test/*321 src:*lld/*=emit)";322 Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";323 FS->addFile("foo.txt", /*ModificationTime=*/{},324 llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));325 clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);326 EXPECT_THAT(diags(), IsEmpty());327 328 EXPECT_TRUE(Diags.isSuppressedViaMapping(diag::warn_unused_function,329 locForFile("test/t1.cpp")));330 EXPECT_FALSE(Diags.isSuppressedViaMapping(diag::warn_unused_function,331 locForFile("lld/test/t2.cpp")));332}333 334TEST_F(SuppressionMappingTest, ShortLongMatch) {335 llvm::StringLiteral SuppressionMappingFile = R"(336 [unused]337 src:*lld/*=emit338 src:*test/*)";339 Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";340 FS->addFile("foo.txt", /*ModificationTime=*/{},341 llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));342 clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);343 EXPECT_THAT(diags(), IsEmpty());344 345 EXPECT_TRUE(Diags.isSuppressedViaMapping(diag::warn_unused_function,346 locForFile("test/t1.cpp")));347 EXPECT_TRUE(Diags.isSuppressedViaMapping(diag::warn_unused_function,348 locForFile("lld/test/t2.cpp")));349}350 351TEST_F(SuppressionMappingTest, IsIgnored) {352 llvm::StringLiteral SuppressionMappingFile = R"(353 [unused]354 src:*clang/*)";355 Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";356 Diags.getDiagnosticOptions().Warnings = {"unused"};357 FS->addFile("foo.txt", /*ModificationTime=*/{},358 llvm::MemoryBuffer::getMemBuffer(SuppressionMappingFile));359 clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);360 ASSERT_THAT(diags(), IsEmpty());361 362 SourceManager &SM = Diags.getSourceManager();363 auto ClangID =364 SM.createFileID(llvm::MemoryBuffer::getMemBuffer("", "clang/foo.h"));365 auto NonClangID =366 SM.createFileID(llvm::MemoryBuffer::getMemBuffer("", "llvm/foo.h"));367 auto PresumedClangID =368 SM.createFileID(llvm::MemoryBuffer::getMemBuffer("", "llvm/foo2.h"));369 // Add a line directive to point into clang/foo.h370 SM.AddLineNote(SM.getLocForStartOfFile(PresumedClangID), 42,371 SM.getLineTableFilenameID("clang/foo.h"), false, false,372 clang::SrcMgr::C_User);373 374 EXPECT_TRUE(Diags.isIgnored(diag::warn_unused_function,375 SM.getLocForStartOfFile(ClangID)));376 EXPECT_FALSE(Diags.isIgnored(diag::warn_unused_function,377 SM.getLocForStartOfFile(NonClangID)));378 EXPECT_TRUE(Diags.isIgnored(diag::warn_unused_function,379 SM.getLocForStartOfFile(PresumedClangID)));380 381 // Pretend we have a clang-diagnostic pragma to enforce the warning. Make sure382 // suppressing mapping doesn't take over.383 Diags.setSeverity(diag::warn_unused_function, diag::Severity::Error,384 SM.getLocForStartOfFile(ClangID));385 EXPECT_FALSE(Diags.isIgnored(diag::warn_unused_function,386 SM.getLocForStartOfFile(ClangID)));387}388 389TEST_F(SuppressionMappingTest, ParsingRespectsOtherWarningOpts) {390 Diags.getDiagnosticOptions().DiagnosticSuppressionMappingsFile = "foo.txt";391 FS->addFile("foo.txt", /*ModificationTime=*/{},392 llvm::MemoryBuffer::getMemBuffer("[non-existing-warning]"));393 Diags.getDiagnosticOptions().Warnings.push_back("no-unknown-warning-option");394 clang::ProcessWarningOptions(Diags, Diags.getDiagnosticOptions(), *FS);395 EXPECT_THAT(diags(), IsEmpty());396}397} // namespace398