Compiler projects using llvm
//===- unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp ------------===//
//
// 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/PathDiagnostic.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h"
#include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h"
#include "clang/Tooling/Tooling.h"
#include "gtest/gtest.h"

namespace clang {
namespace ento {

class OnlyWarningsDiagConsumer : public PathDiagnosticConsumer {
  llvm::raw_ostream &Output;

public:
  OnlyWarningsDiagConsumer(llvm::raw_ostream &Output) : Output(Output) {}
  void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
                            FilesMade *filesMade) override {
    for (const auto *PD : Diags) {
      Output << PD->getCheckerName() << ": ";
      Output << PD->getShortDescription() << '\n';
    }
  }

  StringRef getName() const override { return "Test"; }
};

class PathDiagConsumer : public PathDiagnosticConsumer {
  llvm::raw_ostream &Output;

public:
  PathDiagConsumer(llvm::raw_ostream &Output) : Output(Output) {}
  void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags,
                            FilesMade *filesMade) override {
    for (const auto *PD : Diags) {
      Output << PD->getCheckerName() << ": ";

      for (PathDiagnosticPieceRef Piece :
           PD->path.flatten(/*ShouldFlattenMacros*/ true)) {
        if (Piece->getKind() != PathDiagnosticPiece::Event)
          continue;
        if (Piece->getString().empty())
          continue;
        // The last event is usually the same as the warning message, skip.
        if (Piece->getString() == PD->getShortDescription())
          continue;

        Output << Piece->getString() << " | ";
      }
      Output << PD->getShortDescription() << '\n';
    }
  }

  StringRef getName() const override { return "Test"; }
};

using AddCheckerFn = void(AnalysisASTConsumer &AnalysisConsumer,
                          AnalyzerOptions &AnOpts);

template <AddCheckerFn Fn1, AddCheckerFn Fn2, AddCheckerFn... Fns>
void addChecker(AnalysisASTConsumer &AnalysisConsumer,
                AnalyzerOptions &AnOpts) {
  Fn1(AnalysisConsumer, AnOpts);
  addChecker<Fn2, Fns...>(AnalysisConsumer, AnOpts);
}

template <AddCheckerFn Fn1>
void addChecker(AnalysisASTConsumer &AnalysisConsumer,
                AnalyzerOptions &AnOpts) {
  Fn1(AnalysisConsumer, AnOpts);
}

template <AddCheckerFn... Fns> class TestAction : public ASTFrontendAction {
  llvm::raw_ostream &DiagsOutput;
  bool OnlyEmitWarnings;

public:
  TestAction(llvm::raw_ostream &DiagsOutput, bool OnlyEmitWarnings)
      : DiagsOutput(DiagsOutput), OnlyEmitWarnings(OnlyEmitWarnings) {}

  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &Compiler,
                                                 StringRef File) override {
    std::unique_ptr<AnalysisASTConsumer> AnalysisConsumer =
        CreateAnalysisConsumer(Compiler);
    if (OnlyEmitWarnings)
      AnalysisConsumer->AddDiagnosticConsumer(
          new OnlyWarningsDiagConsumer(DiagsOutput));
    else
      AnalysisConsumer->AddDiagnosticConsumer(
          new PathDiagConsumer(DiagsOutput));
    addChecker<Fns...>(*AnalysisConsumer, *Compiler.getAnalyzerOpts());
    return std::move(AnalysisConsumer);
  }
};

inline SmallString<80> getCurrentTestNameAsFileName() {
  const ::testing::TestInfo *Info =
      ::testing::UnitTest::GetInstance()->current_test_info();

  SmallString<80> FileName;
  (Twine{Info->name()} + ".cc").toVector(FileName);
  return FileName;
}

template <AddCheckerFn... Fns>
bool runCheckerOnCode(const std::string &Code, std::string &Diags,
                      bool OnlyEmitWarnings = false) {
  const SmallVectorImpl<char> &FileName = getCurrentTestNameAsFileName();
  llvm::raw_string_ostream OS(Diags);
  return tooling::runToolOnCode(
      std::make_unique<TestAction<Fns...>>(OS, OnlyEmitWarnings), Code,
      FileName);
}

template <AddCheckerFn... Fns> bool runCheckerOnCode(const std::string &Code) {
  std::string Diags;
  return runCheckerOnCode<Fns...>(Code, Diags);
}

template <AddCheckerFn... Fns>
bool runCheckerOnCodeWithArgs(const std::string &Code,
                              const std::vector<std::string> &Args,
                              std::string &Diags,
                              bool OnlyEmitWarnings = false) {
  const SmallVectorImpl<char> &FileName = getCurrentTestNameAsFileName();
  llvm::raw_string_ostream OS(Diags);
  return tooling::runToolOnCodeWithArgs(
      std::make_unique<TestAction<Fns...>>(OS, OnlyEmitWarnings), Code, Args,
      FileName);
}

template <AddCheckerFn... Fns>
bool runCheckerOnCodeWithArgs(const std::string &Code,
                              const std::vector<std::string> &Args) {
  std::string Diags;
  return runCheckerOnCodeWithArgs<Fns...>(Code, Args, Diags);
}

} // namespace ento
} // namespace clang