blob: 732ccbc9dd2ae698501c1344e34daf44924d48e9 [file] [log] [blame]
Tamás Zolnai350da402020-01-04 14:05:09 +01001//===--- 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 {
18namespace tidy {
19namespace bugprone {
20
Tamás Zolnai04410c52020-03-14 17:57:02 +010021static constexpr int UnsignedASCIIUpperBound = 127;
22
Tamás Zolnai350da402020-01-04 14:05:09 +010023static Matcher<TypedefDecl> hasAnyListedName(const std::string &Names) {
24 const std::vector<std::string> NameList =
25 utils::options::parseStringList(Names);
26 return hasAnyName(std::vector<StringRef>(NameList.begin(), NameList.end()));
27}
28
29SignedCharMisuseCheck::SignedCharMisuseCheck(StringRef Name,
30 ClangTidyContext *Context)
31 : ClangTidyCheck(Name, Context),
32 CharTypdefsToIgnoreList(Options.get("CharTypdefsToIgnore", "")) {}
33
34void SignedCharMisuseCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
35 Options.store(Opts, "CharTypdefsToIgnore", CharTypdefsToIgnoreList);
36}
37
Tamás Zolnai04410c52020-03-14 17:57:02 +010038// Create a matcher for char -> integer cast.
39BindableMatcher<clang::Stmt> SignedCharMisuseCheck::charCastExpression(
40 bool IsSigned, const Matcher<clang::QualType> &IntegerType,
41 const std::string &CastBindName) const {
Tamás Zolnai350da402020-01-04 14:05:09 +010042 // We can ignore typedefs which are some kind of integer types
43 // (e.g. typedef char sal_Int8). In this case, we don't need to
44 // worry about the misinterpretation of char values.
45 const auto IntTypedef = qualType(
46 hasDeclaration(typedefDecl(hasAnyListedName(CharTypdefsToIgnoreList))));
47
Tamás Zolnai04410c52020-03-14 17:57:02 +010048 auto CharTypeExpr = expr();
49 if (IsSigned) {
50 CharTypeExpr = expr(hasType(
51 qualType(isAnyCharacter(), isSignedInteger(), unless(IntTypedef))));
52 } else {
53 CharTypeExpr = expr(hasType(qualType(
54 isAnyCharacter(), unless(isSignedInteger()), unless(IntTypedef))));
55 }
Tamás Zolnai350da402020-01-04 14:05:09 +010056
Tamás Zolnai350da402020-01-04 14:05:09 +010057 const auto ImplicitCastExpr =
Tamás Zolnai04410c52020-03-14 17:57:02 +010058 implicitCastExpr(hasSourceExpression(CharTypeExpr),
Tamás Zolnai350da402020-01-04 14:05:09 +010059 hasImplicitDestinationType(IntegerType))
Tamás Zolnai04410c52020-03-14 17:57:02 +010060 .bind(CastBindName);
Tamás Zolnai350da402020-01-04 14:05:09 +010061
62 const auto CStyleCastExpr = cStyleCastExpr(has(ImplicitCastExpr));
63 const auto StaticCastExpr = cxxStaticCastExpr(has(ImplicitCastExpr));
64 const auto FunctionalCastExpr = cxxFunctionalCastExpr(has(ImplicitCastExpr));
65
66 // We catch any type of casts to an integer. We need to have these cast
67 // expressions explicitly to catch only those casts which are direct children
Tamás Zolnai04410c52020-03-14 17:57:02 +010068 // of the checked expressions. (e.g. assignment, declaration).
69 return expr(anyOf(ImplicitCastExpr, CStyleCastExpr, StaticCastExpr,
70 FunctionalCastExpr));
71}
Tamás Zolnai350da402020-01-04 14:05:09 +010072
Tamás Zolnai04410c52020-03-14 17:57:02 +010073void SignedCharMisuseCheck::registerMatchers(MatchFinder *Finder) {
74 const auto IntegerType =
75 qualType(isInteger(), unless(isAnyCharacter()), unless(booleanType()))
76 .bind("integerType");
77 const auto SignedCharCastExpr =
78 charCastExpression(true, IntegerType, "signedCastExpression");
79 const auto UnSignedCharCastExpr =
80 charCastExpression(false, IntegerType, "unsignedCastExpression");
81
82 // Catch assignments with singed char -> integer conversion.
83 const auto AssignmentOperatorExpr =
84 expr(binaryOperator(hasOperatorName("="), hasLHS(hasType(IntegerType)),
85 hasRHS(SignedCharCastExpr)));
Tamás Zolnai350da402020-01-04 14:05:09 +010086
87 Finder->addMatcher(AssignmentOperatorExpr, this);
88
Tamás Zolnai04410c52020-03-14 17:57:02 +010089 // Catch declarations with singed char -> integer conversion.
90 const auto Declaration = varDecl(isDefinition(), hasType(IntegerType),
91 hasInitializer(SignedCharCastExpr));
Tamás Zolnai350da402020-01-04 14:05:09 +010092
93 Finder->addMatcher(Declaration, this);
Tamás Zolnai04410c52020-03-14 17:57:02 +010094
95 // Catch signed char/unsigned char comparison.
96 const auto CompareOperator =
97 expr(binaryOperator(hasAnyOperatorName("==", "!="),
98 anyOf(allOf(hasLHS(SignedCharCastExpr),
99 hasRHS(UnSignedCharCastExpr)),
100 allOf(hasLHS(UnSignedCharCastExpr),
101 hasRHS(SignedCharCastExpr)))))
102 .bind("comparison");
103
104 Finder->addMatcher(CompareOperator, this);
Tamás Zolnai350da402020-01-04 14:05:09 +0100105}
106
107void SignedCharMisuseCheck::check(const MatchFinder::MatchResult &Result) {
Tamás Zolnai04410c52020-03-14 17:57:02 +0100108 const auto *SignedCastExpression =
109 Result.Nodes.getNodeAs<ImplicitCastExpr>("signedCastExpression");
Tamás Zolnai350da402020-01-04 14:05:09 +0100110
Tamás Zolnai04410c52020-03-14 17:57:02 +0100111 // Ignore the match if we know that the signed char's value is not negative.
Tamás Zolnai350da402020-01-04 14:05:09 +0100112 // The potential misinterpretation happens for negative values only.
113 Expr::EvalResult EVResult;
Tamás Zolnai04410c52020-03-14 17:57:02 +0100114 if (!SignedCastExpression->isValueDependent() &&
115 SignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
116 *Result.Context)) {
117 llvm::APSInt Value = EVResult.Val.getInt();
118 if (Value.isNonNegative())
Tamás Zolnai350da402020-01-04 14:05:09 +0100119 return;
120 }
121
Tamás Zolnai04410c52020-03-14 17:57:02 +0100122 if (const auto *Comparison = Result.Nodes.getNodeAs<Expr>("comparison")) {
123 const auto *UnSignedCastExpression =
124 Result.Nodes.getNodeAs<ImplicitCastExpr>("unsignedCastExpression");
125
126 // We can ignore the ASCII value range also for unsigned char.
127 Expr::EvalResult EVResult;
128 if (!UnSignedCastExpression->isValueDependent() &&
129 UnSignedCastExpression->getSubExpr()->EvaluateAsInt(EVResult,
130 *Result.Context)) {
131 llvm::APSInt Value = EVResult.Val.getInt();
132 if (Value <= UnsignedASCIIUpperBound)
133 return;
134 }
135
136 diag(Comparison->getBeginLoc(),
137 "comparison between 'signed char' and 'unsigned char'");
138 } else if (const auto *IntegerType =
139 Result.Nodes.getNodeAs<QualType>("integerType")) {
140 diag(SignedCastExpression->getBeginLoc(),
141 "'signed char' to %0 conversion; "
142 "consider casting to 'unsigned char' first.")
143 << *IntegerType;
144 } else
145 llvm_unreachable("Unexpected match");
Tamás Zolnai350da402020-01-04 14:05:09 +0100146}
147
148} // namespace bugprone
149} // namespace tidy
150} // namespace clang