1 //===--- StringIntegerAssignmentCheck.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 "StringIntegerAssignmentCheck.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 {
17 namespace tidy {
18 namespace bugprone {
19 
20 void StringIntegerAssignmentCheck::registerMatchers(MatchFinder *Finder) {
21   Finder->addMatcher(
22       cxxOperatorCallExpr(
23           anyOf(hasOverloadedOperatorName("="),
24                 hasOverloadedOperatorName("+=")),
25           callee(cxxMethodDecl(ofClass(classTemplateSpecializationDecl(
26               hasName("::std::basic_string"),
27               hasTemplateArgument(0, refersToType(hasCanonicalType(
28                                          qualType().bind("type")))))))),
29           hasArgument(
30               1,
31               ignoringImpCasts(
32                   expr(hasType(isInteger()), unless(hasType(isAnyCharacter())),
33                        // Ignore calls to tolower/toupper (see PR27723).
34                        unless(callExpr(callee(functionDecl(
35                            hasAnyName("tolower", "std::tolower", "toupper",
36                                       "std::toupper"))))),
37                        // Do not warn if assigning e.g. `CodePoint` to
38                        // `basic_string<CodePoint>`
39                        unless(hasType(qualType(
40                            hasCanonicalType(equalsBoundNode("type"))))))
41                       .bind("expr"))),
42           unless(isInTemplateInstantiation())),
43       this);
44 }
45 
46 class CharExpressionDetector {
47 public:
48   CharExpressionDetector(QualType CharType, const ASTContext &Ctx)
49       : CharType(CharType), Ctx(Ctx) {}
50 
51   bool isLikelyCharExpression(const Expr *E) const {
52     if (isCharTyped(E))
53       return true;
54 
55     if (const auto *BinOp = dyn_cast<BinaryOperator>(E)) {
56       const auto *LHS = BinOp->getLHS()->IgnoreParenImpCasts();
57       const auto *RHS = BinOp->getRHS()->IgnoreParenImpCasts();
58       // Handle both directions, e.g. `'a' + (i % 26)` and `(i % 26) + 'a'`.
59       if (BinOp->isAdditiveOp() || BinOp->isBitwiseOp())
60         return handleBinaryOp(BinOp->getOpcode(), LHS, RHS) ||
61                handleBinaryOp(BinOp->getOpcode(), RHS, LHS);
62       // Except in the case of '%'.
63       if (BinOp->getOpcode() == BO_Rem)
64         return handleBinaryOp(BinOp->getOpcode(), LHS, RHS);
65       return false;
66     }
67 
68     // Ternary where at least one branch is a likely char expression, e.g.
69     //    i < 265 ? i : ' '
70     if (const auto *CondOp = dyn_cast<AbstractConditionalOperator>(E))
71       return isLikelyCharExpression(
72                  CondOp->getFalseExpr()->IgnoreParenImpCasts()) ||
73              isLikelyCharExpression(
74                  CondOp->getTrueExpr()->IgnoreParenImpCasts());
75     return false;
76   }
77 
78 private:
79   bool handleBinaryOp(clang::BinaryOperatorKind Opcode, const Expr *const LHS,
80                       const Expr *const RHS) const {
81     // <char_expr> <op> <char_expr> (c++ integer promotion rules make this an
82     // int), e.g.
83     //    'a' + c
84     if (isCharTyped(LHS) && isCharTyped(RHS))
85       return true;
86 
87     // <expr> & <char_valued_constant> or <expr> % <char_valued_constant>, e.g.
88     //    i & 0xff
89     if ((Opcode == BO_And || Opcode == BO_Rem) && isCharValuedConstant(RHS))
90       return true;
91 
92     // <char_expr> | <char_valued_constant>, e.g.
93     //    c | 0x80
94     if (Opcode == BO_Or && isCharTyped(LHS) && isCharValuedConstant(RHS))
95       return true;
96 
97     // <char_constant> + <likely_char_expr>, e.g.
98     //    'a' + (i % 26)
99     if (Opcode == BO_Add)
100       return isCharConstant(LHS) && isLikelyCharExpression(RHS);
101 
102     return false;
103   }
104 
105   // Returns true if `E` is an character constant.
106   bool isCharConstant(const Expr *E) const {
107     return isCharTyped(E) && isCharValuedConstant(E);
108   };
109 
110   // Returns true if `E` is an integer constant which fits in `CharType`.
111   bool isCharValuedConstant(const Expr *E) const {
112     if (E->isInstantiationDependent())
113       return false;
114     Expr::EvalResult EvalResult;
115     if (!E->EvaluateAsInt(EvalResult, Ctx, Expr::SE_AllowSideEffects))
116       return false;
117     return EvalResult.Val.getInt().getActiveBits() <= Ctx.getTypeSize(CharType);
118   };
119 
120   // Returns true if `E` has the right character type.
121   bool isCharTyped(const Expr *E) const {
122     return E->getType().getCanonicalType().getTypePtr() ==
123            CharType.getTypePtr();
124   };
125 
126   const QualType CharType;
127   const ASTContext &Ctx;
128 };
129 
130 void StringIntegerAssignmentCheck::check(
131     const MatchFinder::MatchResult &Result) {
132   const auto *Argument = Result.Nodes.getNodeAs<Expr>("expr");
133   const auto CharType =
134       Result.Nodes.getNodeAs<QualType>("type")->getCanonicalType();
135   SourceLocation Loc = Argument->getBeginLoc();
136 
137   // Try to detect a few common expressions to reduce false positives.
138   if (CharExpressionDetector(CharType, *Result.Context)
139           .isLikelyCharExpression(Argument))
140     return;
141 
142   auto Diag =
143       diag(Loc, "an integer is interpreted as a character code when assigning "
144                 "it to a string; if this is intended, cast the integer to the "
145                 "appropriate character type; if you want a string "
146                 "representation, use the appropriate conversion facility");
147 
148   if (Loc.isMacroID())
149     return;
150 
151   bool IsWideCharType = CharType->isWideCharType();
152   if (!CharType->isCharType() && !IsWideCharType)
153     return;
154   bool IsOneDigit = false;
155   bool IsLiteral = false;
156   if (const auto *Literal = dyn_cast<IntegerLiteral>(Argument)) {
157     IsOneDigit = Literal->getValue().getLimitedValue() < 10;
158     IsLiteral = true;
159   }
160 
161   SourceLocation EndLoc = Lexer::getLocForEndOfToken(
162       Argument->getEndLoc(), 0, *Result.SourceManager, getLangOpts());
163   if (IsOneDigit) {
164     Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L'" : "'")
165          << FixItHint::CreateInsertion(EndLoc, "'");
166     return;
167   }
168   if (IsLiteral) {
169     Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L\"" : "\"")
170          << FixItHint::CreateInsertion(EndLoc, "\"");
171     return;
172   }
173 
174   if (getLangOpts().CPlusPlus11) {
175     Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "std::to_wstring("
176                                                            : "std::to_string(")
177          << FixItHint::CreateInsertion(EndLoc, ")");
178   }
179 }
180 
181 } // namespace bugprone
182 } // namespace tidy
183 } // namespace clang
184