1 //===--- StringIntegerAssignmentCheck.cpp - clang-tidy---------------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "StringIntegerAssignmentCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 #include "clang/Lex/Lexer.h"
14 using namespace clang::ast_matchers
;
16 namespace clang::tidy::bugprone
{
18 void StringIntegerAssignmentCheck::registerMatchers(MatchFinder
*Finder
) {
21 hasAnyOverloadedOperatorName("=", "+="),
22 callee(cxxMethodDecl(ofClass(classTemplateSpecializationDecl(
23 hasName("::std::basic_string"),
24 hasTemplateArgument(0, refersToType(hasCanonicalType(
25 qualType().bind("type")))))))),
29 expr(hasType(isInteger()), unless(hasType(isAnyCharacter())),
30 // Ignore calls to tolower/toupper (see PR27723).
31 unless(callExpr(callee(functionDecl(
32 hasAnyName("tolower", "std::tolower", "toupper",
34 // Do not warn if assigning e.g. `CodePoint` to
35 // `basic_string<CodePoint>`
36 unless(hasType(qualType(
37 hasCanonicalType(equalsBoundNode("type"))))))
39 unless(isInTemplateInstantiation())),
43 class CharExpressionDetector
{
45 CharExpressionDetector(QualType CharType
, const ASTContext
&Ctx
)
46 : CharType(CharType
), Ctx(Ctx
) {}
48 bool isLikelyCharExpression(const Expr
*E
) const {
52 if (const auto *BinOp
= dyn_cast
<BinaryOperator
>(E
)) {
53 const auto *LHS
= BinOp
->getLHS()->IgnoreParenImpCasts();
54 const auto *RHS
= BinOp
->getRHS()->IgnoreParenImpCasts();
55 // Handle both directions, e.g. `'a' + (i % 26)` and `(i % 26) + 'a'`.
56 if (BinOp
->isAdditiveOp() || BinOp
->isBitwiseOp())
57 return handleBinaryOp(BinOp
->getOpcode(), LHS
, RHS
) ||
58 handleBinaryOp(BinOp
->getOpcode(), RHS
, LHS
);
59 // Except in the case of '%'.
60 if (BinOp
->getOpcode() == BO_Rem
)
61 return handleBinaryOp(BinOp
->getOpcode(), LHS
, RHS
);
65 // Ternary where at least one branch is a likely char expression, e.g.
67 if (const auto *CondOp
= dyn_cast
<AbstractConditionalOperator
>(E
))
68 return isLikelyCharExpression(
69 CondOp
->getFalseExpr()->IgnoreParenImpCasts()) ||
70 isLikelyCharExpression(
71 CondOp
->getTrueExpr()->IgnoreParenImpCasts());
76 bool handleBinaryOp(clang::BinaryOperatorKind Opcode
, const Expr
*const LHS
,
77 const Expr
*const RHS
) const {
78 // <char_expr> <op> <char_expr> (c++ integer promotion rules make this an
81 if (isCharTyped(LHS
) && isCharTyped(RHS
))
84 // <expr> & <char_valued_constant> or <expr> % <char_valued_constant>, e.g.
86 if ((Opcode
== BO_And
|| Opcode
== BO_Rem
) && isCharValuedConstant(RHS
))
89 // <char_expr> | <char_valued_constant>, e.g.
91 if (Opcode
== BO_Or
&& isCharTyped(LHS
) && isCharValuedConstant(RHS
))
94 // <char_constant> + <likely_char_expr>, e.g.
97 return isCharConstant(LHS
) && isLikelyCharExpression(RHS
);
102 // Returns true if `E` is an character constant.
103 bool isCharConstant(const Expr
*E
) const {
104 return isCharTyped(E
) && isCharValuedConstant(E
);
107 // Returns true if `E` is an integer constant which fits in `CharType`.
108 bool isCharValuedConstant(const Expr
*E
) const {
109 if (E
->isInstantiationDependent())
111 Expr::EvalResult EvalResult
;
112 if (!E
->EvaluateAsInt(EvalResult
, Ctx
, Expr::SE_AllowSideEffects
))
114 return EvalResult
.Val
.getInt().getActiveBits() <= Ctx
.getTypeSize(CharType
);
117 // Returns true if `E` has the right character type.
118 bool isCharTyped(const Expr
*E
) const {
119 return E
->getType().getCanonicalType().getTypePtr() ==
120 CharType
.getTypePtr();
123 const QualType CharType
;
124 const ASTContext
&Ctx
;
127 void StringIntegerAssignmentCheck::check(
128 const MatchFinder::MatchResult
&Result
) {
129 const auto *Argument
= Result
.Nodes
.getNodeAs
<Expr
>("expr");
130 const auto CharType
=
131 Result
.Nodes
.getNodeAs
<QualType
>("type")->getCanonicalType();
132 SourceLocation Loc
= Argument
->getBeginLoc();
134 // Try to detect a few common expressions to reduce false positives.
135 if (CharExpressionDetector(CharType
, *Result
.Context
)
136 .isLikelyCharExpression(Argument
))
140 diag(Loc
, "an integer is interpreted as a character code when assigning "
141 "it to a string; if this is intended, cast the integer to the "
142 "appropriate character type; if you want a string "
143 "representation, use the appropriate conversion facility");
148 bool IsWideCharType
= CharType
->isWideCharType();
149 if (!CharType
->isCharType() && !IsWideCharType
)
151 bool IsOneDigit
= false;
152 bool IsLiteral
= false;
153 if (const auto *Literal
= dyn_cast
<IntegerLiteral
>(Argument
)) {
154 IsOneDigit
= Literal
->getValue().getLimitedValue() < 10;
158 SourceLocation EndLoc
= Lexer::getLocForEndOfToken(
159 Argument
->getEndLoc(), 0, *Result
.SourceManager
, getLangOpts());
161 Diag
<< FixItHint::CreateInsertion(Loc
, IsWideCharType
? "L'" : "'")
162 << FixItHint::CreateInsertion(EndLoc
, "'");
166 Diag
<< FixItHint::CreateInsertion(Loc
, IsWideCharType
? "L\"" : "\"")
167 << FixItHint::CreateInsertion(EndLoc
, "\"");
171 if (getLangOpts().CPlusPlus11
) {
172 Diag
<< FixItHint::CreateInsertion(Loc
, IsWideCharType
? "std::to_wstring("
174 << FixItHint::CreateInsertion(EndLoc
, ")");
178 } // namespace clang::tidy::bugprone