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

source code of clang-tools-extra/clang-tidy/modernize/UseNullptrCheck.cpp