1//===-- ClangSACheckersEmitter.cpp - Generate SA checkers tables ----------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9// This tablegen backend emits Clang Static Analyzer checkers tables.
10//
11//===----------------------------------------------------------------------===//
12
13#include "TableGenBackends.h"
14#include "llvm/ADT/StringMap.h"
15#include "llvm/TableGen/Error.h"
16#include "llvm/TableGen/Record.h"
17#include "llvm/TableGen/TableGenBackend.h"
18#include <string>
19
20using namespace llvm;
21
22//===----------------------------------------------------------------------===//
23// Static Analyzer Checkers Tables generation
24//===----------------------------------------------------------------------===//
25
26static std::string getPackageFullName(const Record *R, StringRef Sep = ".");
27
28static std::string getParentPackageFullName(const Record *R,
29 StringRef Sep = ".") {
30 if (const DefInit *DI = dyn_cast<DefInit>(Val: R->getValueInit(FieldName: "ParentPackage")))
31 return getPackageFullName(R: DI->getDef(), Sep);
32 return "";
33}
34
35static std::string getPackageFullName(const Record *R, StringRef Sep) {
36 std::string name = getParentPackageFullName(R, Sep);
37 if (!name.empty())
38 name += Sep;
39 assert(!R->getValueAsString("PackageName").empty());
40 name += R->getValueAsString(FieldName: "PackageName");
41 return name;
42}
43
44static std::string getCheckerFullName(const Record *R, StringRef Sep = ".") {
45 std::string name = getParentPackageFullName(R, Sep);
46 if (!name.empty())
47 name += Sep;
48 assert(!R->getValueAsString("CheckerName").empty());
49 name += R->getValueAsString(FieldName: "CheckerName");
50 return name;
51}
52
53static StringRef getStringValue(const Record &R, StringRef field) {
54 if (const StringInit *SI = dyn_cast<StringInit>(Val: R.getValueInit(FieldName: field)))
55 return SI->getValue();
56 return "";
57}
58
59// Calculates the integer value representing the BitsInit object
60static inline uint64_t getValueFromBitsInit(const BitsInit *B, const Record &R) {
61 assert(B->getNumBits() <= sizeof(uint64_t) * 8 && "BitInits' too long!");
62
63 uint64_t Value = 0;
64 for (unsigned i = 0, e = B->getNumBits(); i != e; ++i) {
65 const auto *Bit = dyn_cast<BitInit>(Val: B->getBit(Bit: i));
66 if (Bit)
67 Value |= uint64_t(Bit->getValue()) << i;
68 else
69 PrintFatalError(ErrorLoc: R.getLoc(),
70 Msg: "missing Documentation for " + getCheckerFullName(R: &R));
71 }
72 return Value;
73}
74
75static std::string getCheckerDocs(const Record &R) {
76 const BitsInit *BI = R.getValueAsBitsInit(FieldName: "Documentation");
77 if (!BI)
78 PrintFatalError(ErrorLoc: R.getLoc(), Msg: "missing Documentation<...> member for " +
79 getCheckerFullName(R: &R));
80
81 // Ignore 'Documentation<NotDocumented>' checkers.
82 if (getValueFromBitsInit(B: BI, R) == 0)
83 return "";
84
85 std::string CheckerFullName = StringRef(getCheckerFullName(R: &R, Sep: "-")).lower();
86 return (Twine("https://clang.llvm.org/docs/analyzer/checkers.html#") +
87 CheckerFullName)
88 .str();
89}
90
91/// Retrieves the type from a CmdOptionTypeEnum typed Record object. Note that
92/// the class itself has to be modified for adding a new option type in
93/// CheckerBase.td.
94static StringRef getCheckerOptionType(const Record &R) {
95 if (const BitsInit *BI = R.getValueAsBitsInit(FieldName: "Type")) {
96 switch(getValueFromBitsInit(B: BI, R)) {
97 case 0:
98 return "int";
99 case 1:
100 return "string";
101 case 2:
102 return "bool";
103 }
104 }
105 PrintFatalError(ErrorLoc: R.getLoc(),
106 Msg: "unable to parse command line option type for "
107 + getCheckerFullName(R: &R));
108 return "";
109}
110
111static StringRef getDevelopmentStage(const Record &R) {
112 if (const BitsInit *BI = R.getValueAsBitsInit(FieldName: "DevelopmentStage")) {
113 switch(getValueFromBitsInit(B: BI, R)) {
114 case 0:
115 return "alpha";
116 case 1:
117 return "released";
118 }
119 }
120
121 PrintFatalError(ErrorLoc: R.getLoc(),
122 Msg: "unable to parse command line option type for "
123 + getCheckerFullName(R: &R));
124 return "";
125}
126
127static bool isHidden(const Record *R) {
128 if (R->getValueAsBit(FieldName: "Hidden"))
129 return true;
130
131 // Not declared as hidden, check the parent package if it is hidden.
132 if (const DefInit *DI = dyn_cast<DefInit>(Val: R->getValueInit(FieldName: "ParentPackage")))
133 return isHidden(R: DI->getDef());
134
135 return false;
136}
137
138static void printChecker(raw_ostream &OS, const Record &R) {
139 OS << "CHECKER(" << "\"";
140 OS.write_escaped(Str: getCheckerFullName(R: &R)) << "\", ";
141 OS << R.getName() << ", ";
142 OS << "\"";
143 OS.write_escaped(Str: getStringValue(R, field: "HelpText")) << "\", ";
144 OS << "\"";
145 OS.write_escaped(Str: getCheckerDocs(R));
146 OS << "\", ";
147
148 if (!isHidden(R: &R))
149 OS << "false";
150 else
151 OS << "true";
152
153 OS << ")\n";
154}
155
156static void printOption(raw_ostream &OS, StringRef FullName, const Record &R) {
157 OS << "\"";
158 OS.write_escaped(Str: getCheckerOptionType(R)) << "\", \"";
159 OS.write_escaped(Str: FullName) << "\", ";
160 OS << '\"' << getStringValue(R, field: "CmdFlag") << "\", ";
161 OS << '\"';
162 OS.write_escaped(Str: getStringValue(R, field: "Desc")) << "\", ";
163 OS << '\"';
164 OS.write_escaped(Str: getStringValue(R, field: "DefaultVal")) << "\", ";
165 OS << '\"';
166 OS << getDevelopmentStage(R) << "\", ";
167
168 if (!R.getValueAsBit(FieldName: "Hidden"))
169 OS << "false";
170 else
171 OS << "true";
172}
173
174void clang::EmitClangSACheckers(const RecordKeeper &Records, raw_ostream &OS) {
175 ArrayRef<const Record *> checkers =
176 Records.getAllDerivedDefinitions(ClassName: "Checker");
177 ArrayRef<const Record *> packages =
178 Records.getAllDerivedDefinitions(ClassName: "Package");
179
180 OS << "// This file is automatically generated. Do not edit this file by "
181 "hand.\n";
182
183 // Emit packages.
184 //
185 // PACKAGE(PACKAGENAME)
186 // - PACKAGENAME: The name of the package.
187 OS << "\n"
188 "#ifdef GET_PACKAGES\n";
189 {
190 StringMap<const Record *> sortedPackages;
191 for (const Record *Package : packages)
192 sortedPackages[getPackageFullName(R: Package)] = Package;
193
194 for (const auto &[_, R] : sortedPackages) {
195 OS << "PACKAGE(" << "\"";
196 OS.write_escaped(Str: getPackageFullName(R)) << '\"';
197 OS << ")\n";
198 }
199 }
200 OS << "#endif // GET_PACKAGES\n"
201 "\n";
202
203 // Emit a package option.
204 //
205 // PACKAGE_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL,
206 // DEVELOPMENT_STATUS, IS_HIDDEN)
207 // - TYPE: Type of the option, whether it's integer or boolean etc.
208 // This is important for validating user input. Note that
209 // it's a string, rather than an actual type: since we can
210 // load checkers runtime, we can't use template hackery for
211 // sorting this out compile-time.
212 // - FULLNAME: Name of the package.
213 // - CMDFLAG: Name of the option.
214 // - DESC: The description of the option.
215 // - DEFAULT_VAL: The default value for this option as a string.
216 // - DEVELOPMENT_STATUS: "released" or "alpha".
217 // - IS_HIDDEN: Boolean (true or false) marking if the option is hidden.
218 //
219 // The full option can be specified in the command like this:
220 // -analyzer-config FULLNAME:CMDFLAG=VALUE
221 OS << "\n"
222 "#ifdef GET_PACKAGE_OPTIONS\n";
223 for (const Record *Package : packages) {
224 if (Package->isValueUnset(FieldName: "PackageOptions"))
225 continue;
226
227 for (const Record *PackageOpt :
228 Package->getValueAsListOfDefs(FieldName: "PackageOptions")) {
229 OS << "PACKAGE_OPTION(";
230 printOption(OS, FullName: getPackageFullName(R: Package), R: *PackageOpt);
231 OS << ")\n";
232 }
233 }
234 OS << "#endif // GET_PACKAGE_OPTIONS\n"
235 "\n";
236
237 // Emit checkers.
238 //
239 // CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN)
240 // - FULLNAME: The full name of the checker, including packages, e.g.:
241 // alpha.cplusplus.UninitializedObject
242 // - CLASS: The common ending of the name of the register... and
243 // shouldRegister... functions. Traditionally coincides with the
244 // name of the class that implements the checker, but can be
245 // anything else e.g. in checker families.
246 // - HELPTEXT: The description of the checker.
247 // - DOC_URI: If the checker is NotDocumented, an empty string; if the
248 // checker HasDocumentation, the concatenation of the fixed URL
249 // https://clang.llvm.org/docs/analyzer/checkers.html# and
250 // a html id derived from the full name by writing '-' instead
251 // of '.' and lowercasing.
252 // - IS_HIDDEN: Boolean (true or false) marking if the checker is hidden.
253
254 OS << "\n"
255 "#ifdef GET_CHECKERS\n"
256 "\n";
257 for (const Record *checker : checkers)
258 printChecker(OS, R: *checker);
259
260 OS << "\n"
261 "#endif // GET_CHECKERS\n"
262 "\n";
263
264 // Emit dependencies.
265 //
266 // CHECKER_DEPENDENCY(FULLNAME, DEPENDENCY)
267 // - FULLNAME: The full name of the checker that depends on another checker.
268 // - DEPENDENCY: The full name of the checker FULLNAME depends on.
269 OS << "\n"
270 "#ifdef GET_CHECKER_DEPENDENCIES\n";
271 for (const Record *Checker : checkers) {
272 if (Checker->isValueUnset(FieldName: "Dependencies"))
273 continue;
274
275 for (const Record *Dependency :
276 Checker->getValueAsListOfDefs(FieldName: "Dependencies")) {
277 OS << "CHECKER_DEPENDENCY(";
278 OS << '\"';
279 OS.write_escaped(Str: getCheckerFullName(R: Checker)) << "\", ";
280 OS << '\"';
281 OS.write_escaped(Str: getCheckerFullName(R: Dependency)) << '\"';
282 OS << ")\n";
283 }
284 }
285 OS << "\n"
286 "#endif // GET_CHECKER_DEPENDENCIES\n";
287
288 // Emit weak dependencies.
289 //
290 // CHECKER_DEPENDENCY(FULLNAME, DEPENDENCY)
291 // - FULLNAME: The full name of the checker that is supposed to be
292 // registered first.
293 // - DEPENDENCY: The full name of the checker FULLNAME weak depends on.
294 OS << "\n"
295 "#ifdef GET_CHECKER_WEAK_DEPENDENCIES\n";
296 for (const Record *Checker : checkers) {
297 if (Checker->isValueUnset(FieldName: "WeakDependencies"))
298 continue;
299
300 for (const Record *Dependency :
301 Checker->getValueAsListOfDefs(FieldName: "WeakDependencies")) {
302 OS << "CHECKER_WEAK_DEPENDENCY(";
303 OS << '\"';
304 OS.write_escaped(Str: getCheckerFullName(R: Checker)) << "\", ";
305 OS << '\"';
306 OS.write_escaped(Str: getCheckerFullName(R: Dependency)) << '\"';
307 OS << ")\n";
308 }
309 }
310 OS << "\n"
311 "#endif // GET_CHECKER_WEAK_DEPENDENCIES\n";
312
313 // Emit a package option.
314 //
315 // CHECKER_OPTION(TYPE, FULLNAME, CMDFLAG, DESC, DEFAULT_VAL,
316 // DEVELOPMENT_STATUS, IS_HIDDEN)
317 // - TYPE: Type of the option, whether it's integer or boolean etc.
318 // This is important for validating user input. Note that
319 // it's a string, rather than an actual type: since we can
320 // load checkers runtime, we can't use template hackery for
321 // sorting this out compile-time.
322 // - FULLNAME: Name of the checker.
323 // - CMDFLAG: Name of the option.
324 // - DESC: The description of the option.
325 // - DEFAULT_VAL: The default value for this option as a string.
326 // - DEVELOPMENT_STATUS: "released" or "alpha".
327 // - IS_HIDDEN: Boolean (true or false) marking if the option is hidden.
328 //
329 // The full option can be specified in the command like this:
330 // -analyzer-config FULLNAME:CMDFLAG=VALUE
331 OS << "\n"
332 "#ifdef GET_CHECKER_OPTIONS\n";
333 for (const Record *Checker : checkers) {
334 if (Checker->isValueUnset(FieldName: "CheckerOptions"))
335 continue;
336
337 for (const Record *CheckerOpt :
338 Checker->getValueAsListOfDefs(FieldName: "CheckerOptions")) {
339 OS << "CHECKER_OPTION(";
340 printOption(OS, FullName: getCheckerFullName(R: Checker), R: *CheckerOpt);
341 OS << ")\n";
342 }
343 }
344 OS << "#endif // GET_CHECKER_OPTIONS\n"
345 "\n";
346}
347