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   if (!getLangOpts().CPlusPlus)
22     return;
23   Finder->addMatcher(
24       cxxOperatorCallExpr(
25           anyOf(hasOverloadedOperatorName("="),
26                 hasOverloadedOperatorName("+=")),
27           callee(cxxMethodDecl(ofClass(classTemplateSpecializationDecl(
28               hasName("::std::basic_string"),
29               hasTemplateArgument(0, refersToType(hasCanonicalType(
30                                          qualType().bind("type")))))))),
31           hasArgument(
32               1,
33               ignoringImpCasts(
34                   expr(hasType(isInteger()), unless(hasType(isAnyCharacter())),
35                        // Ignore calls to tolower/toupper (see PR27723).
36                        unless(callExpr(callee(functionDecl(
37                            hasAnyName("tolower", "std::tolower", "toupper",
38                                       "std::toupper"))))),
39                        // Do not warn if assigning e.g. `CodePoint` to
40                        // `basic_string<CodePoint>`
41                        unless(hasType(qualType(
42                            hasCanonicalType(equalsBoundNode("type"))))))
43                       .bind("expr"))),
44           unless(isInTemplateInstantiation())),
45       this);
46 }
47 
48 static bool isLikelyCharExpression(const Expr *Argument,
49                                    const ASTContext &Ctx) {
50   const auto *BinOp = dyn_cast<BinaryOperator>(Argument);
51   if (!BinOp)
52     return false;
53   const auto *LHS = BinOp->getLHS()->IgnoreParenImpCasts();
54   const auto *RHS = BinOp->getRHS()->IgnoreParenImpCasts();
55   // <expr> & <mask>, mask is a compile time constant.
56   Expr::EvalResult RHSVal;
57   if (BinOp->getOpcode() == BO_And &&
58       (RHS->EvaluateAsInt(RHSVal, Ctx, Expr::SE_AllowSideEffects) ||
59        LHS->EvaluateAsInt(RHSVal, Ctx, Expr::SE_AllowSideEffects)))
60     return true;
61   // <char literal> + (<expr> % <mod>), where <base> is a char literal.
62   const auto IsCharPlusModExpr = [](const Expr *L, const Expr *R) {
63     const auto *ROp = dyn_cast<BinaryOperator>(R);
64     return ROp && ROp->getOpcode() == BO_Rem && isa<CharacterLiteral>(L);
65   };
66   if (BinOp->getOpcode() == BO_Add) {
67     if (IsCharPlusModExpr(LHS, RHS) || IsCharPlusModExpr(RHS, LHS))
68       return true;
69   }
70   return false;
71 }
72 
73 void StringIntegerAssignmentCheck::check(
74     const MatchFinder::MatchResult &Result) {
75   const auto *Argument = Result.Nodes.getNodeAs<Expr>("expr");
76   SourceLocation Loc = Argument->getBeginLoc();
77 
78   // Try to detect a few common expressions to reduce false positives.
79   if (isLikelyCharExpression(Argument, *Result.Context))
80     return;
81 
82   auto Diag =
83       diag(Loc, "an integer is interpreted as a character code when assigning "
84                 "it to a string; if this is intended, cast the integer to the "
85                 "appropriate character type; if you want a string "
86                 "representation, use the appropriate conversion facility");
87 
88   if (Loc.isMacroID())
89     return;
90 
91   auto CharType = *Result.Nodes.getNodeAs<QualType>("type");
92   bool IsWideCharType = CharType->isWideCharType();
93   if (!CharType->isCharType() && !IsWideCharType)
94     return;
95   bool IsOneDigit = false;
96   bool IsLiteral = false;
97   if (const auto *Literal = dyn_cast<IntegerLiteral>(Argument)) {
98     IsOneDigit = Literal->getValue().getLimitedValue() < 10;
99     IsLiteral = true;
100   }
101 
102   SourceLocation EndLoc = Lexer::getLocForEndOfToken(
103       Argument->getEndLoc(), 0, *Result.SourceManager, getLangOpts());
104   if (IsOneDigit) {
105     Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L'" : "'")
106          << FixItHint::CreateInsertion(EndLoc, "'");
107     return;
108   }
109   if (IsLiteral) {
110     Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "L\"" : "\"")
111          << FixItHint::CreateInsertion(EndLoc, "\"");
112     return;
113   }
114 
115   if (getLangOpts().CPlusPlus11) {
116     Diag << FixItHint::CreateInsertion(Loc, IsWideCharType ? "std::to_wstring("
117                                                            : "std::to_string(")
118          << FixItHint::CreateInsertion(EndLoc, ")");
119   }
120 }
121 
122 } // namespace bugprone
123 } // namespace tidy
124 } // namespace clang
125