NoQ created this revision. NoQ added reviewers: vsavchenko, xazax.hun, martong, Szelethus, baloghadamsoftware, Charusso. Herald added subscribers: steakhal, ASDenysPetrov, dkrupp, donat.nagy, mikhail.ramalho, a.sidorin, rnkovacs, szepet, mgorny. NoQ requested review of this revision.
This `clang::DiagnosticConsumer` can take normal clang diagnostics (eg., Sema errors or Clang-Tidy warnings) and turn them into static analyzer's `PathDiagnostic`s which are then fed into `PathDiagnosticConsumer`s. It's an essential component for my clang-tidy/static analyzer cross-integration efforts. The consumer isn't magically aware of information that isn't present in Clang diagnostics but expected in path diagnostics. Such info includes checker name / bug type / category; the consumer expects an external provider to provide such information for every incoming diagnostic. Additionally, `PathDiagnostic`'s declaration-with-issue and uniqueing-declaration are currently set to `nullptr`. I'm slowly thinking about auto-detecting those based on the source location but technically all path diagnostic consumer facilities can function just fine without them (declaration-with-issue is there purely for displaying user-visible names and uniqueing declaration only makes sense for path-sensitive warnings that require exotic uniqueing which will probably never be fed into this consumer). There are also a few features missing here and there, such as fix-it hint support, which i plan to add later. Though technically possible, this consumer is never supposed to consume the output of `TextPathDiagnosticConsumer`; a lot of information is lost that way that can't be recovered. In particular, the proper way to teach clang-tidy to emit path diagnostics while keeping in mind that it already ships with static analyzer integration is to allow the integrated static analyzer to talk to path diagnostic consumers directly; static analyzer warnings should never go through this new consumer. https://reviews.llvm.org/D94476 Files: clang/include/clang/Analysis/PathDiagnosticConverterDiagnosticConsumer.h clang/lib/Analysis/CMakeLists.txt clang/lib/Analysis/PathDiagnosticConverterDiagnosticConsumer.cpp clang/unittests/Analysis/CMakeLists.txt clang/unittests/Analysis/PathDiagnosticConverterDiagnosticConsumer.cpp
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<ExpectedPieceTy> Pieces; + }; + typedef std::vector<ExpectedDiagTy> ExpectedDiagsTy; + +private: + ExpectedDiagsTy ExpectedDiags; + +public: + TestPathDiagnosticConsumer(ExpectedDiagsTy ExpectedDiags) + : ExpectedDiags(ExpectedDiags) + {} + + virtual StringRef getName() const override { return "test"; } + + virtual void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &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<DiagnosticsEngine> 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<ASTConsumer> 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<PathDiagnosticConverterDiagnosticConsumerTestAction>(), + "void foo() {}", "input.c")); +} + +} // end of anonymous namespace 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/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 <clang/Analysis/PathDiagnosticConverterDiagnosticConsumer.h> + +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<PathDiagnosticEventPiece>(PDLoc, Msg); + } else { + Piece = std::make_shared<PathDiagnosticNotePiece>(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<PathDiagnostic>( + BTI.CheckName, /*DeclWithIssue=*/nullptr, BTI.BugType, + CleanMsg, CleanMsg, BTI.BugCategory, PDLoc, /*DeclToUnique=*/nullptr, + std::make_unique<FilesToLineNumsMap>()); + + PathDiagnosticPieceRef Piece = + std::make_shared<PathDiagnosticEventPiece>(PDLoc, CleanMsg); + + PD->setEndOfPath(std::move(Piece)); + + PartialPDs[Consumer] = std::move(PD); + } + } +} 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/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<BugTypeInfo(const Diagnostic &)>; + +private: + PathDiagnosticConsumers &PathConsumers; + BugTypeInfoProvider BTIProvider; + bool ShouldDisplayNotesAsEvents; + + std::map<PathDiagnosticConsumer *, std::unique_ptr<PathDiagnostic>> + 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
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits