1//===--- SignedCharMisuseCheck.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 "SignedCharMisuseCheck.h"
10#include "../utils/OptionsUtils.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13
14using namespace clang::ast_matchers;
15using namespace clang::ast_matchers::internal;
16
17namespace clang::tidy::bugprone {
18
19static constexpr int UnsignedASCIIUpperBound = 127;
20
21SignedCharMisuseCheck::SignedCharMisuseCheck(StringRef Name,
22 ClangTidyContext *Context)
23 : ClangTidyCheck(Name, Context),
24 CharTypdefsToIgnoreList(Options.get(LocalName: "CharTypdefsToIgnore", Default: "")),
25 DiagnoseSignedUnsignedCharComparisons(
26 Options.get(LocalName: "DiagnoseSignedUnsignedCharComparisons", Default: true)) {}
27
28void SignedCharMisuseCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
29 Options.store(Options&: Opts, LocalName: "CharTypdefsToIgnore", Value: CharTypdefsToIgnoreList);
30 Options.store(Options&: Opts, LocalName: "DiagnoseSignedUnsignedCharComparisons",
31 Value: DiagnoseSignedUnsignedCharComparisons);
32}
33
34// Create a matcher for char -> integer cast.
35BindableMatcher<clang::Stmt> SignedCharMisuseCheck::charCastExpression(
36 bool IsSigned, const Matcher<clang::QualType> &IntegerType,
37 const std::string &CastBindName) const {
38 // We can ignore typedefs which are some kind of integer types
39 // (e.g. typedef char sal_Int8). In this case, we don't need to
40 // worry about the misinterpretation of char values.
41 const auto IntTypedef = qualType(hasDeclaration(InnerMatcher: typedefDecl(
42 hasAnyName(utils::options::parseStringList(Option: CharTypdefsToIgnoreList)))));
43
44 auto CharTypeExpr = expr();
45 if (IsSigned) {
46 CharTypeExpr = expr(hasType(
47 InnerMatcher: qualType(isAnyCharacter(), isSignedInteger(), unless(IntTypedef))));
48 } else {
49 CharTypeExpr = expr(hasType(InnerMatcher: qualType(
50 isAnyCharacter(), unless(isSignedInteger()), unless(IntTypedef))));
51 }
52
53 const auto ImplicitCastExpr =
54 implicitCastExpr(hasSourceExpression(InnerMatcher: CharTypeExpr),
55 hasImplicitDestinationType(InnerMatcher: IntegerType))
56 .bind(ID: CastBindName);
57
58 const auto CStyleCastExpr = cStyleCastExpr(has(ImplicitCastExpr));
59 const auto StaticCastExpr = cxxStaticCastExpr(has(ImplicitCastExpr));
60 const auto FunctionalCastExpr = cxxFunctionalCastExpr(has(ImplicitCastExpr));
61
62 // We catch any type of casts to an integer. We need to have these cast
63 // expressions explicitly to catch only those casts which are direct children
64 // of the checked expressions. (e.g. assignment, declaration).
65 return traverse(TK: TK_AsIs, InnerMatcher: expr(anyOf(ImplicitCastExpr, CStyleCastExpr,
66 StaticCastExpr, FunctionalCastExpr)));
67}
68
69void SignedCharMisuseCheck::registerMatchers(MatchFinder *Finder) {
70 const auto IntegerType =
71 qualType(isInteger(), unless(isAnyCharacter()), unless(booleanType()))
72 .bind(ID: "integerType");
73 const auto SignedCharCastExpr =
74 charCastExpression(IsSigned: true, IntegerType, CastBindName: "signedCastExpression");
75 const auto UnSignedCharCastExpr =
76 charCastExpression(IsSigned: false, IntegerType, CastBindName: "unsignedCastExpression");
77
78 // Catch assignments with signed char -> integer conversion.
79 const auto AssignmentOperatorExpr =
80 expr(binaryOperator(hasOperatorName(Name: "="), hasLHS(InnerMatcher: hasType(InnerMatcher: IntegerType)),
81 hasRHS(InnerMatcher: SignedCharCastExpr)));
82
83 Finder->addMatcher(NodeMatch: AssignmentOperatorExpr, Action: this);
84
85 // Catch declarations with signed char -> integer conversion.
86 const auto Declaration = varDecl(isDefinition(), hasType(InnerMatcher: IntegerType),
87 hasInitializer(InnerMatcher: SignedCharCastExpr));
88
89 Finder->addMatcher(NodeMatch: Declaration, Action: this);
90
91 if (DiagnoseSignedUnsignedCharComparisons) {
92 // Catch signed char/unsigned char comparison.
93 const auto CompareOperator =
94 expr(binaryOperator(hasAnyOperatorName("==", "!="),
95 anyOf(allOf(hasLHS(InnerMatcher: SignedCharCastExpr),
96 hasRHS(InnerMatcher: UnSignedCharCastExpr)),
97 allOf(hasLHS(InnerMatcher: UnSignedCharCastExpr),
98 hasRHS(InnerMatcher: SignedCharCastExpr)))))
99 .bind(ID: "comparison");
100
101 Finder->addMatcher(NodeMatch: CompareOperator, Action: this);
102 }
103
104 // Catch array subscripts with signed char -> integer conversion.
105 // Matcher for C arrays.
106 const auto CArraySubscript =
107 arraySubscriptExpr(hasIndex(InnerMatcher: SignedCharCastExpr)).bind(ID: "arraySubscript");
108
109 Finder->addMatcher(NodeMatch: CArraySubscript, Action: this);
110
111 // Matcher for std arrays.
112 const auto STDArraySubscript =
113 cxxOperatorCallExpr(
114 hasOverloadedOperatorName(Name: "[]"),
115 hasArgument(N: 0, InnerMatcher: hasType(InnerMatcher: cxxRecordDecl(hasName(Name: "::std::array")))),
116 hasArgument(N: 1, InnerMatcher: SignedCharCastExpr))
117 .bind(ID: "arraySubscript");
118
119 Finder->addMatcher(NodeMatch: STDArraySubscript, Action: this);
120}
121
122void SignedCharMisuseCheck::check(const MatchFinder::MatchResult &Result) {
123 const auto *SignedCastExpression =
124 Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "signedCastExpression");
125 const auto *IntegerType = Result.Nodes.getNodeAs<QualType>(ID: "integerType");
126 assert(SignedCastExpression);
127 assert(IntegerType);
128
129 // Ignore the match if we know that the signed char's value is not negative.
130 // The potential misinterpretation happens for negative values only.
131 Expr::EvalResult EVResult;
132 if (!SignedCastExpression->isValueDependent() &&
133 SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
134 *Result.Context)) {
135 llvm::APSInt Value = EVResult.Val.getInt();
136 if (Value.isNonNegative())
137 return;
138 }
139
140 if (const auto *Comparison = Result.Nodes.getNodeAs<Expr>(ID: "comparison")) {
141 const auto *UnSignedCastExpression =
142 Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "unsignedCastExpression");
143
144 // We can ignore the ASCII value range also for unsigned char.
145 Expr::EvalResult EVResult;
146 if (!UnSignedCastExpression->isValueDependent() &&
147 UnSignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
148 *Result.Context)) {
149 llvm::APSInt Value = EVResult.Val.getInt();
150 if (Value <= UnsignedASCIIUpperBound)
151 return;
152 }
153
154 diag(Comparison->getBeginLoc(),
155 "comparison between 'signed char' and 'unsigned char'");
156 } else if (Result.Nodes.getNodeAs<Expr>(ID: "arraySubscript")) {
157 diag(Loc: SignedCastExpression->getBeginLoc(),
158 Description: "'signed char' to %0 conversion in array subscript; "
159 "consider casting to 'unsigned char' first.")
160 << *IntegerType;
161 } else {
162 diag(Loc: SignedCastExpression->getBeginLoc(),
163 Description: "'signed char' to %0 conversion; "
164 "consider casting to 'unsigned char' first.")
165 << *IntegerType;
166 }
167}
168
169} // namespace clang::tidy::bugprone
170

source code of clang-tools-extra/clang-tidy/bugprone/SignedCharMisuseCheck.cpp