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