//=- ClangSACheckersEmitter.cpp - Generate Clang SA checkers tables -*- C++ -*-
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This tablegen backend emits Clang Static Analyzer checkers tables.
//
//===----------------------------------------------------------------------===//

#include "llvm/ADT/StringMap.h"
#include "llvm/TableGen/Error.h"
#include "llvm/TableGen/Record.h"
#include "llvm/TableGen/TableGenBackend.h"
#include <map>
#include <string>

using namespace llvm;

//===----------------------------------------------------------------------===//
// Static Analyzer Checkers Tables generation
//===----------------------------------------------------------------------===//

static std::string getPackageFullName(const Record *R);

static std::string getParentPackageFullName(const Record *R) {
  std::string name;
  if (DefInit *DI = dyn_cast<DefInit>(R->getValueInit("ParentPackage")))
    name = getPackageFullName(DI->getDef());
  return name;
}

static std::string getPackageFullName(const Record *R) {
  std::string name = getParentPackageFullName(R);
  if (!name.empty())
    name += ".";
  assert(!R->getValueAsString("PackageName").empty());
  name += R->getValueAsString("PackageName");
  return name;
}

static std::string getCheckerFullName(const Record *R) {
  std::string name = getParentPackageFullName(R);
  if (!name.empty())
    name += ".";
  assert(!R->getValueAsString("CheckerName").empty());
  name += R->getValueAsString("CheckerName");
  return name;
}

static std::string getStringValue(const Record &R, StringRef field) {
  if (StringInit *SI = dyn_cast<StringInit>(R.getValueInit(field)))
    return SI->getValue();
  return std::string();
}

// Calculates the integer value representing the BitsInit object
static inline uint64_t getValueFromBitsInit(const BitsInit *B, const Record &R) {
  assert(B->getNumBits() <= sizeof(uint64_t) * 8 && "BitInits' too long!");

  uint64_t Value = 0;
  for (unsigned i = 0, e = B->getNumBits(); i != e; ++i) {
    const auto *Bit = dyn_cast<BitInit>(B->getBit(i));
    if (Bit)
      Value |= uint64_t(Bit->getValue()) << i;
    else
      PrintFatalError(R.getLoc(),
                      "missing Documentation for " + getCheckerFullName(&R));
  }
  return Value;
}

static std::string getCheckerDocs(const Record &R) {
  StringRef LandingPage;
  if (BitsInit *BI = R.getValueAsBitsInit("Documentation")) {
    uint64_t V = getValueFromBitsInit(BI, R);
    if (V == 1)
      LandingPage = "available_checks.html";
    else if (V == 2)
      LandingPage = "alpha_checks.html";
  }
  
  if (LandingPage.empty())
    return "";

  return (llvm::Twine("https://clang-analyzer.llvm.org/") + LandingPage + "#" +
          getCheckerFullName(&R))
      .str();
}

static void printChecker(llvm::raw_ostream &OS, const Record &R) {
    OS << "CHECKER(" << "\"";
    OS.write_escaped(getCheckerFullName(&R)) << "\", ";
    OS << R.getName() << ", ";
    OS << "\"";
    OS.write_escaped(getStringValue(R, "HelpText")) << "\", ";
    OS << "\"";
    OS.write_escaped(getCheckerDocs(R));
    OS << "\"";
}

namespace clang {
void EmitClangSACheckers(RecordKeeper &Records, raw_ostream &OS) {
  std::vector<Record*> checkers = Records.getAllDerivedDefinitions("Checker");
  std::vector<Record*> packages = Records.getAllDerivedDefinitions("Package");

  using SortedRecords = llvm::StringMap<const Record *>;

  OS << "// This file is automatically generated. Do not edit this file by "
        "hand.\n";

  // Emit packages.
  //
  // PACKAGE(PACKAGENAME)
  //   - PACKAGENAME: The name of the package.
  OS << "\n"
        "#ifdef GET_PACKAGES\n";
  {
    SortedRecords sortedPackages;
    for (unsigned i = 0, e = packages.size(); i != e; ++i)
      sortedPackages[getPackageFullName(packages[i])] = packages[i];
  
    for (SortedRecords::iterator
           I = sortedPackages.begin(), E = sortedPackages.end(); I != E; ++I) {
      const Record &R = *I->second;
  
      OS << "PACKAGE(" << "\"";
      OS.write_escaped(getPackageFullName(&R)) << '\"';
      OS << ")\n";
    }
  }
  OS << "#endif // GET_PACKAGES\n"
        "\n";

  // Emit checkers.
  //
  // CHECKER(FULLNAME, CLASS, HELPTEXT)
  //   - FULLNAME: The full name of the checker, including packages, e.g.:
  //               alpha.cplusplus.UninitializedObject
  //   - CLASS: The name of the checker, with "Checker" appended, e.g.:
  //            UninitializedObjectChecker
  //   - HELPTEXT: The description of the checker.
  OS << "\n"
        "#ifdef GET_CHECKERS\n"
        "\n";
  for (const Record *checker : checkers) {
    printChecker(OS, *checker);
    OS << ")\n";
  }
  OS << "\n"
        "#endif // GET_CHECKERS\n"
        "\n";

  // Emit dependencies.
  //
  // CHECKER_DEPENDENCY(FULLNAME, DEPENDENCY)
  //   - FULLNAME: The full name of the checker that depends on another checker.
  //   - DEPENDENCY: The full name of the checker FULLNAME depends on.
  OS << "\n"
        "#ifdef GET_CHECKER_DEPENDENCIES\n";
  for (const Record *checker : checkers) {
    if (checker->isValueUnset("Dependencies"))
      continue;

    for (const Record *Dependency :
                            checker->getValueAsListOfDefs("Dependencies")) {
      OS << "CHECKER_DEPENDENCY(";
      OS << '\"';
      OS.write_escaped(getCheckerFullName(checker)) << "\", ";
      OS << '\"';
      OS.write_escaped(getCheckerFullName(Dependency)) << '\"';
      OS << ")\n";
    }
  }
  OS << "\n"
        "#endif // GET_CHECKER_DEPENDENCIES\n";
}
} // end namespace clang
