1 | //===--- MisplacedOperatorInStrlenInAllocCheck.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 "MisplacedOperatorInStrlenInAllocCheck.h" |
10 | #include "clang/AST/ASTContext.h" |
11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
12 | #include "clang/Lex/Lexer.h" |
13 | |
14 | using namespace clang::ast_matchers; |
15 | |
16 | namespace clang::tidy::bugprone { |
17 | |
18 | void MisplacedOperatorInStrlenInAllocCheck::registerMatchers( |
19 | MatchFinder *Finder) { |
20 | const auto StrLenFunc = functionDecl(hasAnyName( |
21 | "::strlen" , "::std::strlen" , "::strnlen" , "::std::strnlen" , "::strnlen_s" , |
22 | "::std::strnlen_s" , "::wcslen" , "::std::wcslen" , "::wcsnlen" , |
23 | "::std::wcsnlen" , "::wcsnlen_s" , "std::wcsnlen_s" )); |
24 | |
25 | const auto BadUse = |
26 | callExpr(callee(InnerMatcher: StrLenFunc), |
27 | hasAnyArgument(InnerMatcher: ignoringImpCasts( |
28 | InnerMatcher: binaryOperator( |
29 | hasOperatorName(Name: "+" ), |
30 | hasRHS(InnerMatcher: ignoringParenImpCasts(InnerMatcher: integerLiteral(equals(Value: 1))))) |
31 | .bind(ID: "BinOp" )))) |
32 | .bind(ID: "StrLen" ); |
33 | |
34 | const auto BadArg = anyOf( |
35 | allOf(unless(binaryOperator( |
36 | hasOperatorName(Name: "+" ), hasLHS(InnerMatcher: BadUse), |
37 | hasRHS(InnerMatcher: ignoringParenImpCasts(InnerMatcher: integerLiteral(equals(Value: 1)))))), |
38 | hasDescendant(BadUse)), |
39 | BadUse); |
40 | |
41 | const auto Alloc0Func = functionDecl( |
42 | hasAnyName("::malloc" , "std::malloc" , "::alloca" , "std::alloca" )); |
43 | const auto Alloc1Func = functionDecl( |
44 | hasAnyName("::calloc" , "std::calloc" , "::realloc" , "std::realloc" )); |
45 | |
46 | const auto Alloc0FuncPtr = |
47 | varDecl(hasType(InnerMatcher: isConstQualified()), |
48 | hasInitializer(InnerMatcher: ignoringParenImpCasts( |
49 | InnerMatcher: declRefExpr(hasDeclaration(InnerMatcher: Alloc0Func))))); |
50 | const auto Alloc1FuncPtr = |
51 | varDecl(hasType(InnerMatcher: isConstQualified()), |
52 | hasInitializer(InnerMatcher: ignoringParenImpCasts( |
53 | InnerMatcher: declRefExpr(hasDeclaration(InnerMatcher: Alloc1Func))))); |
54 | |
55 | Finder->addMatcher( |
56 | NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: callExpr(callee(InnerMatcher: decl(anyOf(Alloc0Func, Alloc0FuncPtr))), |
57 | hasArgument(N: 0, InnerMatcher: BadArg)) |
58 | .bind(ID: "Alloc" )), |
59 | Action: this); |
60 | Finder->addMatcher( |
61 | NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: callExpr(callee(InnerMatcher: decl(anyOf(Alloc1Func, Alloc1FuncPtr))), |
62 | hasArgument(N: 1, InnerMatcher: BadArg)) |
63 | .bind(ID: "Alloc" )), |
64 | Action: this); |
65 | Finder->addMatcher( |
66 | NodeMatch: traverse(TK: TK_AsIs, |
67 | InnerMatcher: cxxNewExpr(isArray(), hasArraySize(InnerMatcher: BadArg)).bind(ID: "Alloc" )), |
68 | Action: this); |
69 | } |
70 | |
71 | void MisplacedOperatorInStrlenInAllocCheck::check( |
72 | const MatchFinder::MatchResult &Result) { |
73 | const Expr *Alloc = Result.Nodes.getNodeAs<CallExpr>(ID: "Alloc" ); |
74 | if (!Alloc) |
75 | Alloc = Result.Nodes.getNodeAs<CXXNewExpr>(ID: "Alloc" ); |
76 | assert(Alloc && "Matched node bound by 'Alloc' should be either 'CallExpr'" |
77 | " or 'CXXNewExpr'" ); |
78 | |
79 | const auto *StrLen = Result.Nodes.getNodeAs<CallExpr>(ID: "StrLen" ); |
80 | const auto *BinOp = Result.Nodes.getNodeAs<BinaryOperator>(ID: "BinOp" ); |
81 | |
82 | const StringRef StrLenText = Lexer::getSourceText( |
83 | Range: CharSourceRange::getTokenRange(StrLen->getSourceRange()), |
84 | SM: *Result.SourceManager, LangOpts: getLangOpts()); |
85 | const StringRef Arg0Text = Lexer::getSourceText( |
86 | Range: CharSourceRange::getTokenRange(StrLen->getArg(Arg: 0)->getSourceRange()), |
87 | SM: *Result.SourceManager, LangOpts: getLangOpts()); |
88 | const StringRef StrLenBegin = StrLenText.substr(Start: 0, N: StrLenText.find(Str: Arg0Text)); |
89 | const StringRef StrLenEnd = StrLenText.substr( |
90 | Start: StrLenText.find(Str: Arg0Text) + Arg0Text.size(), N: StrLenText.size()); |
91 | |
92 | const StringRef LHSText = Lexer::getSourceText( |
93 | Range: CharSourceRange::getTokenRange(BinOp->getLHS()->getSourceRange()), |
94 | SM: *Result.SourceManager, LangOpts: getLangOpts()); |
95 | const StringRef RHSText = Lexer::getSourceText( |
96 | Range: CharSourceRange::getTokenRange(BinOp->getRHS()->getSourceRange()), |
97 | SM: *Result.SourceManager, LangOpts: getLangOpts()); |
98 | |
99 | auto Hint = FixItHint::CreateReplacement( |
100 | StrLen->getSourceRange(), |
101 | (StrLenBegin + LHSText + StrLenEnd + " + " + RHSText).str()); |
102 | |
103 | diag(Alloc->getBeginLoc(), |
104 | "addition operator is applied to the argument of %0 instead of its " |
105 | "result" ) |
106 | << StrLen->getDirectCallee()->getName() << Hint; |
107 | } |
108 | |
109 | } // namespace clang::tidy::bugprone |
110 | |