clang-tools 22.0.0git
SignedCharMisuseCheck.cpp
Go to the documentation of this file.
1//===----------------------------------------------------------------------===//
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
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
22 ClangTidyContext *Context)
23 : ClangTidyCheck(Name, Context),
24 CharTypdefsToIgnoreList(Options.get("CharTypdefsToIgnore", "")),
25 DiagnoseSignedUnsignedCharComparisons(
26 Options.get("DiagnoseSignedUnsignedCharComparisons", true)) {}
27
29 Options.store(Opts, "CharTypdefsToIgnore", CharTypdefsToIgnoreList);
30 Options.store(Opts, "DiagnoseSignedUnsignedCharComparisons",
31 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(typedefDecl(
42 hasAnyName(utils::options::parseStringList(CharTypdefsToIgnoreList)))));
43
44 auto CharTypeExpr = expr();
45 if (IsSigned) {
46 CharTypeExpr = expr(hasType(
47 qualType(isAnyCharacter(), isSignedInteger(), unless(IntTypedef))));
48 } else {
49 CharTypeExpr = expr(hasType(qualType(
50 isAnyCharacter(), unless(isSignedInteger()), unless(IntTypedef))));
51 }
52
53 const auto ImplicitCastExpr =
54 implicitCastExpr(hasSourceExpression(CharTypeExpr),
55 hasImplicitDestinationType(IntegerType))
56 .bind(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_AsIs, 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("integerType");
73 const auto SignedCharCastExpr =
74 charCastExpression(true, IntegerType, "signedCastExpression");
75 const auto UnSignedCharCastExpr =
76 charCastExpression(false, IntegerType, "unsignedCastExpression");
77 const bool IsC23 = getLangOpts().C23;
78
79 // Catch assignments with signed char -> integer conversion. Ignore false
80 // positives on C23 enums with the fixed underlying type of signed char.
81 const auto AssignmentOperatorExpr =
82 expr(binaryOperator(hasOperatorName("="), hasLHS(hasType(IntegerType)),
83 hasRHS(SignedCharCastExpr)),
84 IsC23 ? unless(binaryOperator(
85 hasLHS(hasType(hasCanonicalType(enumType())))))
86 : Matcher<Stmt>(anything()));
87
88 Finder->addMatcher(AssignmentOperatorExpr, this);
89
90 // Catch declarations with signed char -> integer conversion. Ignore false
91 // positives on C23 enums with the fixed underlying type of signed char.
92 const auto Declaration = varDecl(
93 isDefinition(), hasType(IntegerType), hasInitializer(SignedCharCastExpr),
94 IsC23 ? unless(hasType(hasCanonicalType(enumType())))
95 : Matcher<VarDecl>(anything()));
96
97 Finder->addMatcher(Declaration, this);
98
99 if (DiagnoseSignedUnsignedCharComparisons) {
100 // Catch signed char/unsigned char comparison.
101 const auto CompareOperator =
102 expr(binaryOperator(hasAnyOperatorName("==", "!="),
103 anyOf(allOf(hasLHS(SignedCharCastExpr),
104 hasRHS(UnSignedCharCastExpr)),
105 allOf(hasLHS(UnSignedCharCastExpr),
106 hasRHS(SignedCharCastExpr)))))
107 .bind("comparison");
108
109 Finder->addMatcher(CompareOperator, this);
110 }
111
112 // Catch array subscripts with signed char -> integer conversion.
113 // Matcher for C arrays.
114 const auto CArraySubscript =
115 arraySubscriptExpr(hasIndex(SignedCharCastExpr)).bind("arraySubscript");
116
117 Finder->addMatcher(CArraySubscript, this);
118
119 // Matcher for std arrays.
120 const auto STDArraySubscript =
121 cxxOperatorCallExpr(
122 hasOverloadedOperatorName("[]"),
123 hasArgument(0, hasType(cxxRecordDecl(hasName("::std::array")))),
124 hasArgument(1, SignedCharCastExpr))
125 .bind("arraySubscript");
126
127 Finder->addMatcher(STDArraySubscript, this);
128}
129
130void SignedCharMisuseCheck::check(const MatchFinder::MatchResult &Result) {
131 const auto *SignedCastExpression =
132 Result.Nodes.getNodeAs<ImplicitCastExpr>("signedCastExpression");
133 const auto *IntegerType = Result.Nodes.getNodeAs<QualType>("integerType");
134 assert(SignedCastExpression);
135 assert(IntegerType);
136
137 // Ignore the match if we know that the signed char's value is not negative.
138 // The potential misinterpretation happens for negative values only.
139 Expr::EvalResult EVResult;
140 if (!SignedCastExpression->isValueDependent() &&
141 SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
142 *Result.Context)) {
143 llvm::APSInt Value = EVResult.Val.getInt();
144 if (Value.isNonNegative())
145 return;
146 }
147
148 if (const auto *Comparison = Result.Nodes.getNodeAs<Expr>("comparison")) {
149 const auto *UnSignedCastExpression =
150 Result.Nodes.getNodeAs<ImplicitCastExpr>("unsignedCastExpression");
151
152 // We can ignore the ASCII value range also for unsigned char.
153 Expr::EvalResult EVResult;
154 if (!UnSignedCastExpression->isValueDependent() &&
155 UnSignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
156 *Result.Context)) {
157 llvm::APSInt Value = EVResult.Val.getInt();
158 if (Value <= UnsignedASCIIUpperBound)
159 return;
160 }
161
162 diag(Comparison->getBeginLoc(),
163 "comparison between 'signed char' and 'unsigned char'");
164 } else if (Result.Nodes.getNodeAs<Expr>("arraySubscript")) {
165 diag(SignedCastExpression->getBeginLoc(),
166 "'signed char' to %0 conversion in array subscript; "
167 "consider casting to 'unsigned char' first.")
168 << *IntegerType;
169 } else {
170 diag(SignedCastExpression->getBeginLoc(),
171 "'signed char' to %0 conversion; "
172 "consider casting to 'unsigned char' first.")
173 << *IntegerType;
174 }
175}
176
177} // namespace clang::tidy::bugprone
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
SignedCharMisuseCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
static constexpr int UnsignedASCIIUpperBound
std::vector< StringRef > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
llvm::StringMap< ClangTidyValue > OptionMap