| //===-- 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"; | |
| } |