clang-tools 24.0.0git
UseNullptrCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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#include "UseNullptrCheck.h"
10#include "../utils/Matchers.h"
12#include "clang/AST/ASTContext.h"
13#include "clang/AST/RecursiveASTVisitor.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
15#include "clang/Lex/Lexer.h"
16
17using namespace clang;
18using namespace clang::ast_matchers;
19using namespace llvm;
20
21namespace clang::tidy::modernize {
22namespace {
23
24AST_MATCHER(Type, sugaredNullptrType) {
25 const Type *DesugaredType = Node.getUnqualifiedDesugaredType();
26 if (const auto *BT = dyn_cast<BuiltinType>(DesugaredType))
27 return BT->getKind() == BuiltinType::NullPtr;
28 return false;
29}
30
31AST_MATCHER(DecltypeType, decltypeTypeNullptrLiteral) {
32 if (const Expr *E = Node.getUnderlyingExpr())
33 return isa<CXXNullPtrLiteralExpr>(E->IgnoreParens());
34 return false;
35}
36
37} // namespace
38
39static constexpr char CastSequence[] = "sequence";
40
41static constexpr char DefaultIgnoredTypes[] = "std::_CmpUnspecifiedParam;"
42 "^std::__cmp_cat::__unspec;"
43 "^std::__cmp_cat::__literal_zero";
44
45/// Create a matcher that finds implicit casts as well as the head of a
46/// sequence of zero or more nested explicit casts that have an implicit cast
47/// to null within.
48/// Finding sequences of explicit casts is necessary so that an entire sequence
49/// can be replaced instead of just the inner-most implicit cast.
50///
51/// TODO/NOTE: The second "anyOf" below discards matches on a substituted type,
52/// since we don't know if that would _always_ be a pointer type for all other
53/// specializations, unless the expression was "__null", in which case we assume
54/// that all specializations are expected to be for pointer types. Ideally this
55/// would check for the "NULL" macro instead, but that'd be harder to express.
56/// In practice, "NULL" is often defined as "__null", and this is a useful
57/// condition.
58void UseNullptrCheck::registerMatchers(MatchFinder *Finder) {
59 auto ImplicitCastToNull = implicitCastExpr(
60 anyOf(hasCastKind(CK_NullToPointer), hasCastKind(CK_NullToMemberPointer)),
61 anyOf(hasSourceExpression(gnuNullExpr()),
62 unless(hasImplicitDestinationType(
63 qualType(substTemplateTypeParmType())))),
64 unless(hasSourceExpression(hasType(sugaredNullptrType()))),
65 unless(hasImplicitDestinationType(
66 qualType(matchers::matchesAnyListedTypeName(IgnoredTypes)))));
67
68 const auto IsOrHasDescendant = [](const auto &InnerMatcher) {
69 return anyOf(InnerMatcher, hasDescendant(InnerMatcher));
70 };
71
72 Finder->addMatcher(
73 castExpr(anyOf(ImplicitCastToNull,
74 explicitCastExpr(hasDescendant(ImplicitCastToNull))),
75 unless(hasAncestor(explicitCastExpr())),
76 unless(hasAncestor(cxxRewrittenBinaryOperator())))
77 .bind(CastSequence),
78 this);
79
80 Finder->addMatcher(
81 cxxRewrittenBinaryOperator(
82 // Match rewritten operators, but verify (in the check method)
83 // that if an implicit cast is found, it is not from another
84 // nested rewritten operator.
85 expr().bind("matchBinopOperands"),
86 hasEitherOperand(IsOrHasDescendant(
87 implicitCastExpr(ImplicitCastToNull,
88 hasAncestor(cxxRewrittenBinaryOperator().bind(
89 "checkBinopOperands")))
90 .bind(CastSequence))),
91 // Skip defaulted comparison operators.
92 unless(hasAncestor(functionDecl(isDefaulted())))),
93 this);
94
95 if (UseNullptrT)
96 Finder->addMatcher(typeLoc(loc(decltypeType(decltypeTypeNullptrLiteral())))
97 .bind("matchDecltypeNullptr"),
98 this);
99}
100
101static bool isReplaceableRange(SourceLocation StartLoc, SourceLocation EndLoc,
102 const SourceManager &SM) {
103 return SM.isWrittenInSameFile(StartLoc, EndLoc);
104}
105
106/// Replaces the provided range with the text "nullptr", but only if
107/// the start and end location are both in main file.
108/// Returns true if and only if a replacement was made.
109static void replaceWithNullptr(ClangTidyCheck &Check, const SourceManager &SM,
110 SourceLocation StartLoc, SourceLocation EndLoc) {
111 const CharSourceRange Range(SourceRange(StartLoc, EndLoc), true);
112 // Add a space if nullptr follows an alphanumeric character. This happens
113 // whenever there is an c-style explicit cast to nullptr not surrounded by
114 // parentheses and right beside a return statement.
115 const SourceLocation PreviousLocation = StartLoc.getLocWithOffset(-1);
116 const bool NeedsSpace =
117 isAlphanumeric(*SM.getCharacterData(PreviousLocation));
118 Check.diag(Range.getBegin(), "use nullptr") << FixItHint::CreateReplacement(
119 Range, NeedsSpace ? " nullptr" : "nullptr");
120}
121
122/// Returns the name of the outermost macro.
123///
124/// Given
125/// \code
126/// #define MY_NULL NULL
127/// \endcode
128/// If \p Loc points to NULL, this function will return the name MY_NULL.
129static StringRef getOutermostMacroName(SourceLocation Loc,
130 const SourceManager &SM,
131 const LangOptions &LO) {
132 assert(Loc.isMacroID());
133 SourceLocation OutermostMacroLoc;
134
135 while (Loc.isMacroID()) {
136 OutermostMacroLoc = Loc;
137 Loc = SM.getImmediateMacroCallerLoc(Loc);
138 }
139
140 return Lexer::getImmediateMacroName(OutermostMacroLoc, SM, LO);
141}
142
143namespace {
144
145/// RecursiveASTVisitor for ensuring all nodes rooted at a given AST
146/// subtree that have file-level source locations corresponding to a macro
147/// argument have implicit NullTo(Member)Pointer nodes as ancestors.
148class MacroArgUsageVisitor : public RecursiveASTVisitor<MacroArgUsageVisitor> {
149public:
150 MacroArgUsageVisitor(SourceLocation CastLoc, const SourceManager &SM)
151 : CastLoc(CastLoc), SM(SM) {
152 assert(CastLoc.isFileID());
153 }
154
155 bool TraverseStmt(Stmt *S) {
156 const bool VisitedPreviously = Visited;
157
158 if (!RecursiveASTVisitor<MacroArgUsageVisitor>::TraverseStmt(S))
159 return false;
160
161 // The point at which VisitedPreviously is false and Visited is true is the
162 // root of a subtree containing nodes whose locations match CastLoc. It's
163 // at this point we test that the Implicit NullTo(Member)Pointer cast was
164 // found or not.
165 if (!VisitedPreviously) {
166 if (Visited && !CastFound) {
167 // Found nodes with matching SourceLocations but didn't come across a
168 // cast. This is an invalid macro arg use. Can stop traversal
169 // completely now.
170 InvalidFound = true;
171 return false;
172 }
173 // Reset state as we unwind back up the tree.
174 CastFound = false;
175 Visited = false;
176 }
177 return true;
178 }
179
180 bool VisitStmt(Stmt *S) {
181 if (SM.getFileLoc(S->getBeginLoc()) != CastLoc)
182 return true;
183 Visited = true;
184
185 const ImplicitCastExpr *Cast = dyn_cast<ImplicitCastExpr>(S);
186 if (Cast && (Cast->getCastKind() == CK_NullToPointer ||
187 Cast->getCastKind() == CK_NullToMemberPointer))
188 CastFound = true;
189
190 return true;
191 }
192
193 bool TraverseInitListExpr(InitListExpr *S) {
194 // Only go through the semantic form of the InitListExpr, because
195 // ImplicitCast might not appear in the syntactic form, and this results in
196 // finding usages of the macro argument that don't have a ImplicitCast as an
197 // ancestor (thus invalidating the replacement) when they actually have.
198 return RecursiveASTVisitor<MacroArgUsageVisitor>::
199 TraverseSynOrSemInitListExpr(
200 S->isSemanticForm() ? S : S->getSemanticForm());
201 }
202
203 bool foundInvalid() const { return InvalidFound; }
204
205private:
206 SourceLocation CastLoc;
207 const SourceManager &SM;
208
209 bool Visited = false;
210 bool CastFound = false;
211 bool InvalidFound = false;
212};
213
214/// Looks for implicit casts as well as sequences of 0 or more explicit
215/// casts with an implicit null-to-pointer cast within.
216///
217/// The matcher this visitor is used with will find a single implicit cast or a
218/// top-most explicit cast (i.e. it has no explicit casts as an ancestor) where
219/// an implicit cast is nested within. However, there is no guarantee that only
220/// explicit casts exist between the found top-most explicit cast and the
221/// possibly more than one nested implicit cast. This visitor finds all cast
222/// sequences with an implicit cast to null within and creates a replacement
223/// leaving the outermost explicit cast unchanged to avoid introducing
224/// ambiguities.
225class CastSequenceVisitor : public RecursiveASTVisitor<CastSequenceVisitor> {
226public:
227 CastSequenceVisitor(ASTContext &Context, ArrayRef<StringRef> NullMacros,
228 ClangTidyCheck &Check)
229 : SM(Context.getSourceManager()), Context(Context),
230 NullMacros(NullMacros), Check(Check) {}
231
232 bool TraverseStmt(Stmt *S) {
233 // Stop traversing down the tree if requested.
234 if (PruneSubtree) {
235 PruneSubtree = false;
236 return true;
237 }
238 return RecursiveASTVisitor<CastSequenceVisitor>::TraverseStmt(S);
239 }
240
241 // Only VisitStmt is overridden as we shouldn't find other base AST types
242 // within a cast expression.
243 bool VisitStmt(Stmt *S) {
244 auto *C = dyn_cast<CastExpr>(S);
245 // Catch the castExpr inside cxxDefaultArgExpr.
246 if (auto *E = dyn_cast<CXXDefaultArgExpr>(S)) {
247 C = dyn_cast<CastExpr>(E->getExpr());
248 FirstSubExpr = nullptr;
249 }
250 if (!C) {
251 FirstSubExpr = nullptr;
252 return true;
253 }
254
255 auto *CastSubExpr = C->getSubExpr()->IgnoreParens();
256 // Ignore cast expressions which cast nullptr literal.
257 if (isa<CXXNullPtrLiteralExpr>(CastSubExpr))
258 return true;
259
260 if (!FirstSubExpr)
261 FirstSubExpr = CastSubExpr;
262
263 if (C->getCastKind() != CK_NullToPointer &&
264 C->getCastKind() != CK_NullToMemberPointer) {
265 return true;
266 }
267
268 SourceLocation StartLoc = FirstSubExpr->getBeginLoc();
269 SourceLocation EndLoc = FirstSubExpr->getEndLoc();
270
271 // If the location comes from a macro arg expansion, *all* uses of that
272 // arg must be checked to result in NullTo(Member)Pointer casts.
273 //
274 // If the location comes from a macro body expansion, check to see if its
275 // coming from one of the allowed 'NULL' macros.
276 if (SM.isMacroArgExpansion(StartLoc) && SM.isMacroArgExpansion(EndLoc)) {
277 const SourceLocation FileLocStart = SM.getFileLoc(StartLoc),
278 FileLocEnd = SM.getFileLoc(EndLoc);
279 SourceLocation ImmediateMacroArgLoc, MacroLoc;
280 // Skip NULL macros used in macro.
281 if (!getMacroAndArgLocations(StartLoc, ImmediateMacroArgLoc, MacroLoc) ||
282 ImmediateMacroArgLoc != FileLocStart)
283 return skipSubTree();
284
285 if (isReplaceableRange(FileLocStart, FileLocEnd, SM) &&
286 allArgUsesValid(C)) {
287 replaceWithNullptr(Check, SM, FileLocStart, FileLocEnd);
288 }
289 return true;
290 }
291
292 if (SM.isMacroBodyExpansion(StartLoc) && SM.isMacroBodyExpansion(EndLoc)) {
293 const StringRef OutermostMacroName =
294 getOutermostMacroName(StartLoc, SM, Context.getLangOpts());
295
296 // Check to see if the user wants to replace the macro being expanded.
297 if (!llvm::is_contained(NullMacros, OutermostMacroName))
298 return skipSubTree();
299
300 StartLoc = SM.getFileLoc(StartLoc);
301 EndLoc = SM.getFileLoc(EndLoc);
302 }
303
304 if (!isReplaceableRange(StartLoc, EndLoc, SM))
305 return skipSubTree();
306 replaceWithNullptr(Check, SM, StartLoc, EndLoc);
307
308 return true;
309 }
310
311private:
312 bool skipSubTree() {
313 PruneSubtree = true;
314 return true;
315 }
316
317 /// Tests that all expansions of a macro arg, one of which expands to
318 /// result in \p CE, yield NullTo(Member)Pointer casts.
319 bool allArgUsesValid(const CastExpr *CE) {
320 const SourceLocation CastLoc = CE->getBeginLoc();
321
322 // Step 1: Get location of macro arg and location of the macro the arg was
323 // provided to.
324 SourceLocation ArgLoc, MacroLoc;
325 if (!getMacroAndArgLocations(CastLoc, ArgLoc, MacroLoc))
326 return false;
327
328 // Step 2: Find the first ancestor that doesn't expand from this macro.
329 DynTypedNode ContainingAncestor;
330 if (!findContainingAncestor(DynTypedNode::create<Stmt>(*CE), MacroLoc,
331 ContainingAncestor))
332 return false;
333
334 // Step 3:
335 // Visit children of this containing parent looking for the least-descended
336 // nodes of the containing parent which are macro arg expansions that expand
337 // from the given arg location.
338 // Visitor needs: arg loc.
339 MacroArgUsageVisitor ArgUsageVisitor(SM.getFileLoc(CastLoc), SM);
340 if (const auto *D = ContainingAncestor.get<Decl>())
341 ArgUsageVisitor.TraverseDecl(const_cast<Decl *>(D));
342 else if (const auto *S = ContainingAncestor.get<Stmt>())
343 ArgUsageVisitor.TraverseStmt(const_cast<Stmt *>(S));
344 else
345 llvm_unreachable("Unhandled ContainingAncestor node type");
346
347 return !ArgUsageVisitor.foundInvalid();
348 }
349
350 /// Given the SourceLocation for a macro arg expansion, finds the
351 /// non-macro SourceLocation of the macro the arg was passed to and the
352 /// non-macro SourceLocation of the argument in the arg list to that macro.
353 /// These results are returned via \c MacroLoc and \c ArgLoc respectively.
354 /// These values are undefined if the return value is false.
355 ///
356 /// \returns false if one of the returned SourceLocations would be a
357 /// SourceLocation pointing within the definition of another macro.
358 bool getMacroAndArgLocations(SourceLocation Loc, SourceLocation &ArgLoc,
359 SourceLocation &MacroLoc) {
360 assert(Loc.isMacroID() && "Only reasonable to call this on macros");
361
362 ArgLoc = Loc;
363
364 // Find the location of the immediate macro expansion.
365 while (true) {
366 const std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(ArgLoc);
367 const SrcMgr::SLocEntry *E = &SM.getSLocEntry(LocInfo.first);
368 const SrcMgr::ExpansionInfo &Expansion = E->getExpansion();
369
370 const SourceLocation OldArgLoc = ArgLoc;
371 ArgLoc = Expansion.getExpansionLocStart();
372 if (!Expansion.isMacroArgExpansion()) {
373 if (!MacroLoc.isFileID())
374 return false;
375
376 const StringRef Name =
377 Lexer::getImmediateMacroName(OldArgLoc, SM, Context.getLangOpts());
378 return llvm::is_contained(NullMacros, Name);
379 }
380
381 MacroLoc = SM.getExpansionRange(ArgLoc).getBegin();
382
383 ArgLoc = Expansion.getSpellingLoc().getLocWithOffset(LocInfo.second);
384 if (ArgLoc.isFileID())
385 return true;
386
387 // If spelling location resides in the same FileID as macro expansion
388 // location, it means there is no inner macro.
389 const FileID MacroFID = SM.getFileID(MacroLoc);
390 if (SM.isInFileID(ArgLoc, MacroFID)) {
391 // Don't transform this case. If the characters that caused the
392 // null-conversion come from within a macro, they can't be changed.
393 return false;
394 }
395 }
396
397 llvm_unreachable("getMacroAndArgLocations");
398 }
399
400 /// Tests if TestMacroLoc is found while recursively unravelling
401 /// expansions starting at TestLoc. TestMacroLoc.isFileID() must be true.
402 /// Implementation is very similar to getMacroAndArgLocations() except in this
403 /// case, it's not assumed that TestLoc is expanded from a macro argument.
404 /// While unravelling expansions macro arguments are handled as with
405 /// getMacroAndArgLocations() but in this function macro body expansions are
406 /// also handled.
407 ///
408 /// False means either:
409 /// - TestLoc is not from a macro expansion.
410 /// - TestLoc is from a different macro expansion.
411 bool expandsFrom(SourceLocation TestLoc, SourceLocation TestMacroLoc) {
412 if (TestLoc.isFileID())
413 return false;
414
415 SourceLocation Loc = TestLoc, MacroLoc;
416
417 while (true) {
418 const std::pair<FileID, unsigned> LocInfo = SM.getDecomposedLoc(Loc);
419 const SrcMgr::SLocEntry *E = &SM.getSLocEntry(LocInfo.first);
420 const SrcMgr::ExpansionInfo &Expansion = E->getExpansion();
421
422 Loc = Expansion.getExpansionLocStart();
423
424 if (!Expansion.isMacroArgExpansion()) {
425 if (Loc.isFileID())
426 return Loc == TestMacroLoc;
427 // Since Loc is still a macro ID and it's not an argument expansion, we
428 // don't need to do the work of handling an argument expansion. Simply
429 // keep recursively expanding until we hit a FileID or a macro arg
430 // expansion or a macro arg expansion.
431 continue;
432 }
433
434 MacroLoc = SM.getImmediateExpansionRange(Loc).getBegin();
435 if (MacroLoc.isFileID() && MacroLoc == TestMacroLoc) {
436 // Match made.
437 return true;
438 }
439
440 Loc = Expansion.getSpellingLoc().getLocWithOffset(LocInfo.second);
441 if (Loc.isFileID()) {
442 // If we made it this far without finding a match, there is no match to
443 // be made.
444 return false;
445 }
446 }
447
448 llvm_unreachable("expandsFrom");
449 }
450
451 /// Given a starting point \c Start in the AST, find an ancestor that
452 /// doesn't expand from the macro called at file location \c MacroLoc.
453 ///
454 /// \pre MacroLoc.isFileID()
455 /// \returns true if such an ancestor was found, false otherwise.
456 bool findContainingAncestor(DynTypedNode Start, SourceLocation MacroLoc,
457 DynTypedNode &Result) {
458 // Below we're only following the first parent back up the AST. This should
459 // be fine since for the statements we care about there should only be one
460 // parent, except for the case specified below.
461
462 assert(MacroLoc.isFileID());
463
464 while (true) {
465 const auto &Parents = Context.getParents(Start);
466 if (Parents.empty())
467 return false;
468 if (Parents.size() > 1) {
469 // If there are more than one parents, don't do the replacement unless
470 // they are InitListsExpr (semantic and syntactic form). In this case we
471 // can choose any one here, and the ASTVisitor will take care of
472 // traversing the right one.
473 for (const auto &Parent : Parents)
474 if (!Parent.get<InitListExpr>())
475 return false;
476 }
477
478 const DynTypedNode &Parent = Parents[0];
479
480 SourceLocation Loc;
481 if (const auto *D = Parent.get<Decl>())
482 Loc = D->getBeginLoc();
483 else if (const auto *S = Parent.get<Stmt>())
484 Loc = S->getBeginLoc();
485
486 // TypeLoc and NestedNameSpecifierLoc are members of the parent map. Skip
487 // them and keep going up.
488 if (Loc.isValid() && !expandsFrom(Loc, MacroLoc)) {
489 Result = Parent;
490 return true;
491 }
492
493 Start = Parent;
494 }
495
496 llvm_unreachable("findContainingAncestor");
497 }
498
499 SourceManager &SM;
500 ASTContext &Context;
501 ArrayRef<StringRef> NullMacros;
502 ClangTidyCheck &Check;
503 Expr *FirstSubExpr = nullptr;
504 bool PruneSubtree = false;
505};
506
507} // namespace
508
510 : ClangTidyCheck(Name, Context),
511 NullMacrosStr(Options.get("NullMacros", "NULL")),
512 IgnoredTypes(utils::options::parseStringList(
513 Options.get("IgnoredTypes", DefaultIgnoredTypes))),
514 UseNullptrT(Options.get("UseNullptrT", true)),
515 IncludeInserter(Options.getLocalOrGlobal("IncludeStyle",
516 utils::IncludeSorter::IS_LLVM),
517 areDiagsSelfContained()) {
518 NullMacrosStr.split(NullMacros, ",");
519}
520
521void UseNullptrCheck::registerPPCallbacks(const SourceManager &SM,
522 Preprocessor *PP,
523 Preprocessor *ModuleExpanderPP) {
524 IncludeInserter.registerPreprocessor(PP);
525}
526
528 Options.store(Opts, "NullMacros", NullMacrosStr);
529 Options.store(Opts, "IgnoredTypes",
531 Options.store(Opts, "IncludeStyle", IncludeInserter.getStyle());
532 Options.store(Opts, "UseNullptrT", UseNullptrT);
533}
534
535void UseNullptrCheck::check(const MatchFinder::MatchResult &Result) {
536 if (const auto *MatchedTypeLoc =
537 Result.Nodes.getNodeAs<TypeLoc>("matchDecltypeNullptr")) {
538 diag(MatchedTypeLoc->getBeginLoc(), "use std::nullptr_t instead")
539 << IncludeInserter.createIncludeInsertion(
540 Result.SourceManager->getFileID(MatchedTypeLoc->getBeginLoc()),
541 "<cstddef>")
542 << FixItHint::CreateReplacement(MatchedTypeLoc->getSourceRange(),
543 "std::nullptr_t");
544 return;
545 }
546
547 const auto *NullCast = Result.Nodes.getNodeAs<CastExpr>(CastSequence);
548 assert(NullCast && "Bad Callback. No node provided");
549
550 if (Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>(
551 "matchBinopOperands") !=
552 Result.Nodes.getNodeAs<CXXRewrittenBinaryOperator>("checkBinopOperands"))
553 return;
554
555 // Given an implicit null-ptr cast or an explicit cast with an implicit
556 // null-to-pointer cast within use CastSequenceVisitor to identify sequences
557 // of explicit casts that can be converted into 'nullptr'.
558 CastSequenceVisitor(*Result.Context, NullMacros, *this)
559 .TraverseStmt(const_cast<CastExpr *>(NullCast));
560}
561
562} // namespace clang::tidy::modernize
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
UseNullptrCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Create a matcher that finds implicit casts as well as the head of a sequence of zero or more nested e...
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void registerPPCallbacks(const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
@ Type
An inlay hint that for a type annotation.
Definition Protocol.h:1745
inline ::clang::ast_matchers::internal::Matcher< QualType > matchesAnyListedTypeName(llvm::ArrayRef< StringRef > NameList, bool CanonicalTypes)
AST_MATCHER(BinaryOperator, isRelationalOperator)
static StringRef getOutermostMacroName(SourceLocation Loc, const SourceManager &SM, const LangOptions &LO)
Returns the name of the outermost macro.
static constexpr char DefaultIgnoredTypes[]
static constexpr char CastSequence[]
static void replaceWithNullptr(ClangTidyCheck &Check, const SourceManager &SM, SourceLocation StartLoc, SourceLocation EndLoc)
Replaces the provided range with the text "nullptr", but only if the start and end location are both ...
static bool isReplaceableRange(SourceLocation StartLoc, SourceLocation EndLoc, const SourceManager &SM)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Some operations such as code completion produce a set of candidates.
Definition Generators.h:150
llvm::StringMap< ClangTidyValue > OptionMap