ClangSACheckersEmitter.cpp had some comments that described the structure of its output, which would have been helpful, but became out of sync with the code. This commit updates them to ensure they are consistent with `CheckerRegistry.cpp`.
347 lines
12 KiB
C++
347 lines
12 KiB
C++
//===-- ClangSACheckersEmitter.cpp - Generate SA checkers tables ----------===//
|
|
//
|
|
// 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 tablegen backend emits Clang Static Analyzer checkers tables.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "TableGenBackends.h"
|
|
#include "llvm/ADT/StringMap.h"
|
|
#include "llvm/TableGen/Error.h"
|
|
#include "llvm/TableGen/Record.h"
|
|
#include "llvm/TableGen/TableGenBackend.h"
|
|
#include <string>
|
|
|
|
using namespace llvm;
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Static Analyzer Checkers Tables generation
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
static std::string getPackageFullName(const Record *R, StringRef Sep = ".");
|
|
|
|
static std::string getParentPackageFullName(const Record *R,
|
|
StringRef Sep = ".") {
|
|
if (const DefInit *DI = dyn_cast<DefInit>(R->getValueInit("ParentPackage")))
|
|
return getPackageFullName(DI->getDef(), Sep);
|
|
return "";
|
|
}
|
|
|
|
static std::string getPackageFullName(const Record *R, StringRef Sep) {
|
|
std::string name = getParentPackageFullName(R, Sep);
|
|
if (!name.empty())
|
|
name += Sep;
|
|
assert(!R->getValueAsString("PackageName").empty());
|
|
name += R->getValueAsString("PackageName");
|
|
return name;
|
|
}
|
|
|
|
static std::string getCheckerFullName(const Record *R, StringRef Sep = ".") {
|
|
std::string name = getParentPackageFullName(R, Sep);
|
|
if (!name.empty())
|
|
name += Sep;
|
|
assert(!R->getValueAsString("CheckerName").empty());
|
|
name += R->getValueAsString("CheckerName");
|
|
return name;
|
|
}
|
|
|
|
static StringRef getStringValue(const Record &R, StringRef field) {
|
|
if (const StringInit *SI = dyn_cast<StringInit>(R.getValueInit(field)))
|
|
return SI->getValue();
|
|
return "";
|
|
}
|
|
|
|
// 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) {
|
|
const BitsInit *BI = R.getValueAsBitsInit("Documentation");
|
|
if (!BI)
|
|
PrintFatalError(R.getLoc(), "missing Documentation<...> member for " +
|
|
getCheckerFullName(&R));
|
|
|
|
// Ignore 'Documentation<NotDocumented>' checkers.
|
|
if (getValueFromBitsInit(BI, R) == 0)
|
|
return "";
|
|
|
|
std::string CheckerFullName = StringRef(getCheckerFullName(&R, "-")).lower();
|
|
return (Twine("https://clang.llvm.org/docs/analyzer/checkers.html#") +
|
|
CheckerFullName)
|
|
.str();
|
|
}
|
|
|
|
/// Retrieves the type from a CmdOptionTypeEnum typed Record object. Note that
|
|
/// the class itself has to be modified for adding a new option type in
|
|
/// CheckerBase.td.
|
|
static StringRef getCheckerOptionType(const Record &R) {
|
|
if (const BitsInit *BI = R.getValueAsBitsInit("Type")) {
|
|
switch(getValueFromBitsInit(BI, R)) {
|
|
case 0:
|
|
return "int";
|
|
case 1:
|
|
return "string";
|
|
case 2:
|
|
return "bool";
|
|
}
|
|
}
|
|
PrintFatalError(R.getLoc(),
|
|
"unable to parse command line option type for "
|
|
+ getCheckerFullName(&R));
|
|
return "";
|
|
}
|
|
|
|
static StringRef getDevelopmentStage(const Record &R) {
|
|
if (const BitsInit *BI = R.getValueAsBitsInit("DevelopmentStage")) {
|
|
switch(getValueFromBitsInit(BI, R)) {
|
|
case 0:
|
|
return "alpha";
|
|
case 1:
|
|
return "released";
|
|
}
|
|
}
|
|
|
|
PrintFatalError(R.getLoc(),
|
|
"unable to parse command line option type for "
|
|
+ getCheckerFullName(&R));
|
|
return "";
|
|
}
|
|
|
|
static bool isHidden(const Record *R) {
|
|
if (R->getValueAsBit("Hidden"))
|
|
return true;
|
|
|
|
// Not declared as hidden, check the parent package if it is hidden.
|
|
if (const DefInit *DI = dyn_cast<DefInit>(R->getValueInit("ParentPackage")))
|
|
return isHidden(DI->getDef());
|
|
|
|
return false;
|
|
}
|
|
|
|
static void printChecker(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 << "\", ";
|
|
|
|
if (!isHidden(&R))
|
|
OS << "false";
|
|
else
|
|
OS << "true";
|
|
|
|
OS << ")\n";
|
|
}
|
|
|
|
static void printOption(raw_ostream &OS, StringRef FullName, const Record &R) {
|
|
OS << "\"";
|
|
OS.write_escaped(getCheckerOptionType(R)) << "\", \"";
|
|
OS.write_escaped(FullName) << "\", ";
|
|
OS << '\"' << getStringValue(R, "CmdFlag") << "\", ";
|
|
OS << '\"';
|
|
OS.write_escaped(getStringValue(R, "Desc")) << "\", ";
|
|
OS << '\"';
|
|
OS.write_escaped(getStringValue(R, "DefaultVal")) << "\", ";
|
|
OS << '\"';
|
|
OS << getDevelopmentStage(R) << "\", ";
|
|
|
|
if (!R.getValueAsBit("Hidden"))
|
|
OS << "false";
|
|
else
|
|
OS << "true";
|
|
}
|
|
|
|
void clang::EmitClangSACheckers(const RecordKeeper &Records, raw_ostream &OS) {
|
|
ArrayRef<const Record *> checkers =
|
|
Records.getAllDerivedDefinitions("Checker");
|
|
ArrayRef<const Record *> packages =
|
|
Records.getAllDerivedDefinitions("Package");
|
|
|
|
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";
|
|
{
|
|
StringMap<const Record *> sortedPackages;
|
|
for (const Record *Package : packages)
|
|
sortedPackages[getPackageFullName(Package)] = Package;
|
|
|
|
for (const auto &[_, R] : sortedPackages) {
|
|
OS << "PACKAGE(" << "\"";
|
|
OS.write_escaped(getPackageFullName(R)) << '\"';
|
|
OS << ")\n";
|
|
}
|
|
}
|
|
OS << "#endif // GET_PACKAGES\n"
|
|
"\n";
|
|
|
|
// Emit a package option.
|
|
//
|
|
// PACKAGE_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL,
|
|
// DEVELOPMENT_STATUS, IS_HIDDEN)
|
|
// - TYPE: Type of the option, whether it's integer or boolean etc.
|
|
// This is important for validating user input. Note that
|
|
// it's a string, rather than an actual type: since we can
|
|
// load checkers runtime, we can't use template hackery for
|
|
// sorting this out compile-time.
|
|
// - FULLNAME: Name of the package.
|
|
// - CMDFLAG: Name of the option.
|
|
// - DESC: The description of the option.
|
|
// - DEFAULT_VAL: The default value for this option as a string.
|
|
// - DEVELOPMENT_STATUS: "released" or "alpha".
|
|
// - IS_HIDDEN: Boolean (true or false) marking if the option is hidden.
|
|
//
|
|
// The full option can be specified in the command like this:
|
|
// -analyzer-config FULLNAME:CMDFLAG=VALUE
|
|
OS << "\n"
|
|
"#ifdef GET_PACKAGE_OPTIONS\n";
|
|
for (const Record *Package : packages) {
|
|
if (Package->isValueUnset("PackageOptions"))
|
|
continue;
|
|
|
|
for (const Record *PackageOpt :
|
|
Package->getValueAsListOfDefs("PackageOptions")) {
|
|
OS << "PACKAGE_OPTION(";
|
|
printOption(OS, getPackageFullName(Package), *PackageOpt);
|
|
OS << ")\n";
|
|
}
|
|
}
|
|
OS << "#endif // GET_PACKAGE_OPTIONS\n"
|
|
"\n";
|
|
|
|
// Emit checkers.
|
|
//
|
|
// CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN)
|
|
// - FULLNAME: The full name of the checker, including packages, e.g.:
|
|
// alpha.cplusplus.UninitializedObject
|
|
// - CLASS: The common ending of the name of the register... and
|
|
// shouldRegister... functions. Traditionally coincides with the
|
|
// name of the class that implements the checker, but can be
|
|
// anything else e.g. in checker families.
|
|
// - HELPTEXT: The description of the checker.
|
|
// - DOC_URI: If the checker is NotDocumented, an empty string; if the
|
|
// checker HasDocumentation, the concatenation of the fixed URL
|
|
// https://clang.llvm.org/docs/analyzer/checkers.html# and
|
|
// a html id derived from the full name by writing '-' instead
|
|
// of '.' and lowercasing.
|
|
// - IS_HIDDEN: Boolean (true or false) marking if the checker is hidden.
|
|
|
|
OS << "\n"
|
|
"#ifdef GET_CHECKERS\n"
|
|
"\n";
|
|
for (const Record *checker : checkers)
|
|
printChecker(OS, *checker);
|
|
|
|
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";
|
|
|
|
// Emit weak dependencies.
|
|
//
|
|
// CHECKER_DEPENDENCY(FULLNAME, DEPENDENCY)
|
|
// - FULLNAME: The full name of the checker that is supposed to be
|
|
// registered first.
|
|
// - DEPENDENCY: The full name of the checker FULLNAME weak depends on.
|
|
OS << "\n"
|
|
"#ifdef GET_CHECKER_WEAK_DEPENDENCIES\n";
|
|
for (const Record *Checker : checkers) {
|
|
if (Checker->isValueUnset("WeakDependencies"))
|
|
continue;
|
|
|
|
for (const Record *Dependency :
|
|
Checker->getValueAsListOfDefs("WeakDependencies")) {
|
|
OS << "CHECKER_WEAK_DEPENDENCY(";
|
|
OS << '\"';
|
|
OS.write_escaped(getCheckerFullName(Checker)) << "\", ";
|
|
OS << '\"';
|
|
OS.write_escaped(getCheckerFullName(Dependency)) << '\"';
|
|
OS << ")\n";
|
|
}
|
|
}
|
|
OS << "\n"
|
|
"#endif // GET_CHECKER_WEAK_DEPENDENCIES\n";
|
|
|
|
// Emit a package option.
|
|
//
|
|
// CHECKER_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL,
|
|
// DEVELOPMENT_STATUS, IS_HIDDEN)
|
|
// - TYPE: Type of the option, whether it's integer or boolean etc.
|
|
// This is important for validating user input. Note that
|
|
// it's a string, rather than an actual type: since we can
|
|
// load checkers runtime, we can't use template hackery for
|
|
// sorting this out compile-time.
|
|
// - FULLNAME: Name of the checker.
|
|
// - CMDFLAG: Name of the option.
|
|
// - DESC: The description of the option.
|
|
// - DEFAULT_VAL: The default value for this option as a string.
|
|
// - DEVELOPMENT_STATUS: "released" or "alpha".
|
|
// - IS_HIDDEN: Boolean (true or false) marking if the option is hidden.
|
|
//
|
|
// The full option can be specified in the command like this:
|
|
// -analyzer-config FULLNAME:CMDFLAG=VALUE
|
|
OS << "\n"
|
|
"#ifdef GET_CHECKER_OPTIONS\n";
|
|
for (const Record *Checker : checkers) {
|
|
if (Checker->isValueUnset("CheckerOptions"))
|
|
continue;
|
|
|
|
for (const Record *CheckerOpt :
|
|
Checker->getValueAsListOfDefs("CheckerOptions")) {
|
|
OS << "CHECKER_OPTION(";
|
|
printOption(OS, getCheckerFullName(Checker), *CheckerOpt);
|
|
OS << ")\n";
|
|
}
|
|
}
|
|
OS << "#endif // GET_CHECKER_OPTIONS\n"
|
|
"\n";
|
|
}
|