227 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "MisplacedWideningCastCheck.h"10#include "clang/AST/ASTContext.h"11#include "clang/ASTMatchers/ASTMatchFinder.h"12 13using namespace clang::ast_matchers;14 15namespace clang::tidy::bugprone {16 17MisplacedWideningCastCheck::MisplacedWideningCastCheck(18 StringRef Name, ClangTidyContext *Context)19 : ClangTidyCheck(Name, Context),20 CheckImplicitCasts(Options.get("CheckImplicitCasts", false)) {}21 22void MisplacedWideningCastCheck::storeOptions(23 ClangTidyOptions::OptionMap &Opts) {24 Options.store(Opts, "CheckImplicitCasts", CheckImplicitCasts);25}26 27void MisplacedWideningCastCheck::registerMatchers(MatchFinder *Finder) {28 const auto Calc =29 expr(anyOf(binaryOperator(hasAnyOperatorName("+", "-", "*", "<<")),30 unaryOperator(hasOperatorName("~"))),31 hasType(isInteger()))32 .bind("Calc");33 34 const auto ExplicitCast = explicitCastExpr(hasDestinationType(isInteger()),35 has(ignoringParenImpCasts(Calc)));36 const auto ImplicitCast =37 implicitCastExpr(hasImplicitDestinationType(isInteger()),38 has(ignoringParenImpCasts(Calc)));39 const auto Cast =40 traverse(TK_AsIs, expr(anyOf(ExplicitCast, ImplicitCast)).bind("Cast"));41 42 Finder->addMatcher(varDecl(hasInitializer(Cast)), this);43 Finder->addMatcher(returnStmt(hasReturnValue(Cast)), this);44 Finder->addMatcher(callExpr(hasAnyArgument(Cast)), this);45 Finder->addMatcher(binaryOperator(hasOperatorName("="), hasRHS(Cast)), this);46 Finder->addMatcher(47 binaryOperator(isComparisonOperator(), hasEitherOperand(Cast)), this);48}49 50static unsigned getMaxCalculationWidth(const ASTContext &Context,51 const Expr *E) {52 E = E->IgnoreParenImpCasts();53 54 if (const auto *Bop = dyn_cast<BinaryOperator>(E)) {55 const unsigned LHSWidth = getMaxCalculationWidth(Context, Bop->getLHS());56 const unsigned RHSWidth = getMaxCalculationWidth(Context, Bop->getRHS());57 if (Bop->getOpcode() == BO_Mul)58 return LHSWidth + RHSWidth;59 if (Bop->getOpcode() == BO_Add)60 return std::max(LHSWidth, RHSWidth) + 1;61 if (Bop->getOpcode() == BO_Rem) {62 Expr::EvalResult Result;63 if (Bop->getRHS()->EvaluateAsInt(Result, Context))64 return Result.Val.getInt().getActiveBits();65 } else if (Bop->getOpcode() == BO_Shl) {66 Expr::EvalResult Result;67 if (Bop->getRHS()->EvaluateAsInt(Result, Context)) {68 // We don't handle negative values and large values well. It is assumed69 // that compiler warnings are written for such values so the user will70 // fix that.71 return LHSWidth + Result.Val.getInt().getExtValue();72 }73 74 // Unknown bitcount, assume there is truncation.75 return 1024U;76 }77 } else if (const auto *Uop = dyn_cast<UnaryOperator>(E)) {78 // There is truncation when ~ is used.79 if (Uop->getOpcode() == UO_Not)80 return 1024U;81 82 const QualType T = Uop->getType();83 return T->isIntegerType() ? Context.getIntWidth(T) : 1024U;84 } else if (const auto *I = dyn_cast<IntegerLiteral>(E)) {85 return I->getValue().getActiveBits();86 }87 88 return Context.getIntWidth(E->getType());89}90 91static int relativeIntSizes(BuiltinType::Kind Kind) {92 switch (Kind) {93 case BuiltinType::UChar:94 return 1;95 case BuiltinType::SChar:96 return 1;97 case BuiltinType::Char_U:98 return 1;99 case BuiltinType::Char_S:100 return 1;101 case BuiltinType::UShort:102 return 2;103 case BuiltinType::Short:104 return 2;105 case BuiltinType::UInt:106 return 3;107 case BuiltinType::Int:108 return 3;109 case BuiltinType::ULong:110 return 4;111 case BuiltinType::Long:112 return 4;113 case BuiltinType::ULongLong:114 return 5;115 case BuiltinType::LongLong:116 return 5;117 case BuiltinType::UInt128:118 return 6;119 case BuiltinType::Int128:120 return 6;121 default:122 return 0;123 }124}125 126static int relativeCharSizes(BuiltinType::Kind Kind) {127 switch (Kind) {128 case BuiltinType::UChar:129 return 1;130 case BuiltinType::SChar:131 return 1;132 case BuiltinType::Char_U:133 return 1;134 case BuiltinType::Char_S:135 return 1;136 case BuiltinType::Char16:137 return 2;138 case BuiltinType::Char32:139 return 3;140 default:141 return 0;142 }143}144 145static int relativeCharSizesW(BuiltinType::Kind Kind) {146 switch (Kind) {147 case BuiltinType::UChar:148 return 1;149 case BuiltinType::SChar:150 return 1;151 case BuiltinType::Char_U:152 return 1;153 case BuiltinType::Char_S:154 return 1;155 case BuiltinType::WChar_U:156 return 2;157 case BuiltinType::WChar_S:158 return 2;159 default:160 return 0;161 }162}163 164static bool isFirstWider(BuiltinType::Kind First, BuiltinType::Kind Second) {165 int FirstSize = 0, SecondSize = 0;166 if ((FirstSize = relativeIntSizes(First)) != 0 &&167 (SecondSize = relativeIntSizes(Second)) != 0)168 return FirstSize > SecondSize;169 if ((FirstSize = relativeCharSizes(First)) != 0 &&170 (SecondSize = relativeCharSizes(Second)) != 0)171 return FirstSize > SecondSize;172 if ((FirstSize = relativeCharSizesW(First)) != 0 &&173 (SecondSize = relativeCharSizesW(Second)) != 0)174 return FirstSize > SecondSize;175 return false;176}177 178void MisplacedWideningCastCheck::check(const MatchFinder::MatchResult &Result) {179 const auto *Cast = Result.Nodes.getNodeAs<CastExpr>("Cast");180 if (!CheckImplicitCasts && isa<ImplicitCastExpr>(Cast))181 return;182 if (Cast->getBeginLoc().isMacroID())183 return;184 185 const auto *Calc = Result.Nodes.getNodeAs<Expr>("Calc");186 if (Calc->getBeginLoc().isMacroID())187 return;188 189 if (Cast->isTypeDependent() || Cast->isValueDependent() ||190 Calc->isTypeDependent() || Calc->isValueDependent())191 return;192 193 const ASTContext &Context = *Result.Context;194 195 const QualType CastType = Cast->getType();196 const QualType CalcType = Calc->getType();197 198 // Explicit truncation using cast.199 if (Context.getIntWidth(CastType) < Context.getIntWidth(CalcType))200 return;201 202 // If CalcType and CastType have same size then there is no real danger, but203 // there can be a portability problem.204 205 if (Context.getIntWidth(CastType) == Context.getIntWidth(CalcType)) {206 const auto *CastBuiltinType =207 dyn_cast<BuiltinType>(CastType->getUnqualifiedDesugaredType());208 const auto *CalcBuiltinType =209 dyn_cast<BuiltinType>(CalcType->getUnqualifiedDesugaredType());210 if (!CastBuiltinType || !CalcBuiltinType)211 return;212 if (!isFirstWider(CastBuiltinType->getKind(), CalcBuiltinType->getKind()))213 return;214 }215 216 // Don't write a warning if we can easily see that the result is not217 // truncated.218 if (Context.getIntWidth(CalcType) >= getMaxCalculationWidth(Context, Calc))219 return;220 221 diag(Cast->getBeginLoc(), "either cast from %0 to %1 is ineffective, or "222 "there is loss of precision before the conversion")223 << CalcType << CastType;224}225 226} // namespace clang::tidy::bugprone227