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