Index: clang/include/clang/Analysis/PathDiagnosticConverterDiagnosticConsumer.h =================================================================== --- /dev/null +++ clang/include/clang/Analysis/PathDiagnosticConverterDiagnosticConsumer.h @@ -0,0 +1,62 @@ +//===--- PathDiagnosticConverterDiagnosticConsumer.h ------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines a Clang DiagnosticConsumer that converts Clang diagnostics +// into PathDiagnostics. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_ANALYSIS_PATHDIAGNOSTICCONVERTERDIAGNOSTICCONSUMER_H +#define LLVM_CLANG_ANALYSIS_PATHDIAGNOSTICCONVERTERDIAGNOSTICCONSUMER_H + +#include "clang/Analysis/PathDiagnostic.h" +#include "clang/Analysis/PathDiagnosticConsumers.h" + +namespace clang { +namespace ento { + +class PathDiagnosticConverterDiagnosticConsumer : public DiagnosticConsumer { +public: + struct BugTypeInfo { + StringRef CheckName; + StringRef BugType; + StringRef BugCategory; + }; + + using BugTypeInfoProvider = std::function; + +private: + PathDiagnosticConsumers &PathConsumers; + BugTypeInfoProvider BTIProvider; + bool ShouldDisplayNotesAsEvents; + + std::map> + PartialPDs; + + void flushPartialDiagnostic(); + +public: + PathDiagnosticConverterDiagnosticConsumer( + PathDiagnosticConsumers &PathConsumers, BugTypeInfoProvider &&BTIProvider, + bool ShouldDisplayNotesAsEvents) + : PathConsumers(PathConsumers), BTIProvider(std::move(BTIProvider)), + ShouldDisplayNotesAsEvents(ShouldDisplayNotesAsEvents) {} + + void HandleDiagnostic(DiagnosticsEngine::Level DiagLevel, + const Diagnostic &Info) override; + + /// Inform the consumer that the last diagnostic has been sent. This is + /// necessary because the consumer does not know whether more notes are + /// going to be attached to the last warning. + void finalize() { flushPartialDiagnostic(); } +}; + +} // end 'ento' namespace +} // end 'clang' namespace + +#endif Index: clang/lib/Analysis/CMakeLists.txt =================================================================== --- clang/lib/Analysis/CMakeLists.txt +++ clang/lib/Analysis/CMakeLists.txt @@ -23,6 +23,7 @@ LiveVariables.cpp ObjCNoReturn.cpp PathDiagnostic.cpp + PathDiagnosticConverterDiagnosticConsumer.cpp PlistPathDiagnosticConsumer.cpp PlistHTMLPathDiagnosticConsumer.cpp PostOrderCFGView.cpp Index: clang/lib/Analysis/PathDiagnosticConverterDiagnosticConsumer.cpp =================================================================== --- /dev/null +++ clang/lib/Analysis/PathDiagnosticConverterDiagnosticConsumer.cpp @@ -0,0 +1,79 @@ +//===--- PathDiagnosticConverterDiagnosticConsumer.cpp ----------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines the PathDiagnosticConverterDiagnosticConsumer object +// which converts regular Clang diagnostics into PathDiagnostics. +// +//===----------------------------------------------------------------------===// + +#include + +using namespace clang; +using namespace ento; + +void PathDiagnosticConverterDiagnosticConsumer::flushPartialDiagnostic() { + if (PartialPDs.empty()) + return; + + for (PathDiagnosticConsumer *Consumer : PathConsumers) { + Consumer->HandlePathDiagnostic(std::move(PartialPDs[Consumer])); + } + + PartialPDs.clear(); +} + +void PathDiagnosticConverterDiagnosticConsumer::HandleDiagnostic( + DiagnosticsEngine::Level DiagLevel, const Diagnostic &Info) { + // Count warnings/errors. + DiagnosticConsumer::HandleDiagnostic(DiagLevel, Info); + + llvm::SmallString<128> Msg; + Info.FormatDiagnostic(Msg); + Msg[0] = toupper(Msg[0]); + std::string CleanMsg = Msg.str().substr(0, Msg.str().find('[') - 1).str(); + + SourceLocation Loc = Info.getLocation(); + const SourceManager &SM = Info.getSourceManager(); + + PathDiagnosticLocation PDLoc(Loc, SM); + + // Notes do not come in attached to their respective warnings but are fed + // to us independently. The good news is they always follow their respective + // warnings and come in in the right order so we can automatically re-attach + // them. + if (DiagLevel == DiagnosticsEngine::Note) { + for (PathDiagnosticConsumer *Consumer : PathConsumers) { + PathDiagnosticPieceRef Piece; + if (ShouldDisplayNotesAsEvents) { + Piece = std::make_shared(PDLoc, Msg); + } else { + Piece = std::make_shared(PDLoc, Msg); + } + PartialPDs[Consumer]->getMutablePieces().push_back(Piece); + } + } else { + // No more incoming notes. The current path diagnostic is complete. + flushPartialDiagnostic(); + + BugTypeInfo BTI = BTIProvider(Info); + + for (PathDiagnosticConsumer *Consumer : PathConsumers) { + auto PD = std::make_unique( + BTI.CheckName, /*DeclWithIssue=*/nullptr, BTI.BugType, + CleanMsg, CleanMsg, BTI.BugCategory, PDLoc, /*DeclToUnique=*/nullptr, + std::make_unique()); + + PathDiagnosticPieceRef Piece = + std::make_shared(PDLoc, CleanMsg); + + PD->setEndOfPath(std::move(Piece)); + + PartialPDs[Consumer] = std::move(PD); + } + } +} Index: clang/unittests/Analysis/CMakeLists.txt =================================================================== --- clang/unittests/Analysis/CMakeLists.txt +++ clang/unittests/Analysis/CMakeLists.txt @@ -8,6 +8,7 @@ CFGTest.cpp CloneDetectionTest.cpp ExprMutationAnalyzerTest.cpp + PathDiagnosticConverterDiagnosticConsumer.cpp ) clang_target_link_libraries(ClangAnalysisTests Index: clang/unittests/Analysis/PathDiagnosticConverterDiagnosticConsumer.cpp =================================================================== --- /dev/null +++ clang/unittests/Analysis/PathDiagnosticConverterDiagnosticConsumer.cpp @@ -0,0 +1,157 @@ +//===- unittests/Analysis/CFGTest.cpp - CFG tests -------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "clang/Analysis/PathDiagnosticConverterDiagnosticConsumer.h" +#include "clang/Frontend/CompilerInstance.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +using namespace clang; +using namespace ento; +using namespace llvm; + +namespace { +class TestPathDiagnosticConsumer : public PathDiagnosticConsumer { +public: + struct ExpectedPieceTy { + SourceLocation Loc; + std::string Text; + }; + struct ExpectedDiagTy { + SourceLocation Loc; + std::string VerboseDescription; + std::string ShortDescription; + std::string CheckerName; + std::string BugType; + std::string Category; + std::vector Pieces; + }; + typedef std::vector ExpectedDiagsTy; + +private: + ExpectedDiagsTy ExpectedDiags; + +public: + TestPathDiagnosticConsumer(ExpectedDiagsTy ExpectedDiags) + : ExpectedDiags(ExpectedDiags) + {} + + virtual StringRef getName() const override { return "test"; } + + virtual void FlushDiagnosticsImpl(std::vector &Diags, + FilesMade *filesMade) override { + EXPECT_EQ(Diags.size(), ExpectedDiags.size()); + for (size_t I = 0, E = Diags.size(); I < E; ++I) { + const PathDiagnostic &Diag = *Diags[I]; + const ExpectedDiagTy &ExpectedDiag = ExpectedDiags[I]; + + EXPECT_EQ(Diag.getLocation().asLocation(), ExpectedDiag.Loc); + EXPECT_EQ(Diag.getVerboseDescription(), ExpectedDiag.VerboseDescription); + EXPECT_EQ(Diag.getShortDescription(), ExpectedDiag.ShortDescription); + EXPECT_EQ(Diag.getCheckerName(), ExpectedDiag.CheckerName); + EXPECT_EQ(Diag.getBugType(), ExpectedDiag.BugType); + EXPECT_EQ(Diag.getCategory(), ExpectedDiag.Category); + + const PathPieces &Pieces = Diag.path; + EXPECT_EQ(Pieces.size(), ExpectedDiag.Pieces.size()); + size_t PieceI = 0; + for (const auto &Piece: Pieces) { + const ExpectedPieceTy &ExpectedPiece = ExpectedDiag.Pieces[PieceI]; + EXPECT_EQ(Piece->getLocation().asLocation(), ExpectedPiece.Loc); + EXPECT_EQ(Piece->getString(), ExpectedPiece.Text); + ++PieceI; + } + } + } +}; + +PathDiagnosticConverterDiagnosticConsumer::BugTypeInfo bugTypeInfoProvider( + const Diagnostic &) { + return PathDiagnosticConverterDiagnosticConsumer::BugTypeInfo{ + "test check name", "test bug type", "test bug category"}; +} + +class PathDiagnosticConverterDiagnosticConsumerTestConsumer + : public ASTConsumer { + SourceManager &SM; + + void performTest(const Decl *D) { + TestPathDiagnosticConsumer::ExpectedDiagsTy ExpectedDiags{ + TestPathDiagnosticConsumer::ExpectedDiagTy{ + D->getBeginLoc(), + "Test warning", // Auto-capitalization! + "Test warning", // Auto-capitalization! + "test check name", + "test bug type", + "test bug category", + {TestPathDiagnosticConsumer::ExpectedPieceTy{ + D->getBeginLoc(), + "Test warning", // Auto-capitalization! + }, + TestPathDiagnosticConsumer::ExpectedPieceTy{ + D->getEndLoc(), + "Test note" // Auto-capitalization! + }}}}; + + TestPathDiagnosticConsumer TestPDC {ExpectedDiags}; + PathDiagnosticConsumers PathConsumers = {&TestPDC}; + + llvm::IntrusiveRefCntPtr DiagEngine = + new DiagnosticsEngine(new DiagnosticIDs, new DiagnosticOptions); + PathDiagnosticConverterDiagnosticConsumer DiagConsumer( + PathConsumers, bugTypeInfoProvider, + /*ShouldDisplayNotesAsEvents=*/true); + DiagEngine->setClient(&DiagConsumer, /*ShouldOwnClient=*/false); + DiagEngine->setSourceManager(&SM); + + unsigned WarningDiagID = + DiagEngine->getCustomDiagID(DiagnosticsEngine::Warning, "test warning"); + DiagEngine->Report(D->getBeginLoc(), WarningDiagID); + + unsigned NoteDiagID = + DiagEngine->getCustomDiagID(DiagnosticsEngine::Note, "test note"); + DiagEngine->Report(D->getEndLoc(), NoteDiagID); + + // The last diagnostic must be flushed manually! + DiagConsumer.finalize(); + + PathDiagnosticConsumer::FilesMade FM; + TestPDC.FlushDiagnostics(&FM); + } + +public: + PathDiagnosticConverterDiagnosticConsumerTestConsumer(CompilerInstance &CI) + : SM(CI.getSourceManager()) {} + + bool HandleTopLevelDecl(DeclGroupRef DG) override { + for (auto *D : DG) + performTest(D); + + return true; + } +}; + +class PathDiagnosticConverterDiagnosticConsumerTestAction + : public ASTFrontendAction { +public: + std::unique_ptr CreateASTConsumer(CompilerInstance &CI, + StringRef File) override { + return std::make_unique< + PathDiagnosticConverterDiagnosticConsumerTestConsumer>(CI); + } +}; + +// Constructing a CFG for a range-based for over a dependent type fails (but +// should not crash). +TEST(PathDiagnosticConverter, ConsumeWarning) { + EXPECT_TRUE(tooling::runToolOnCode( + std::make_unique(), + "void foo() {}", "input.c")); +} + +} // end of anonymous namespace