| //===--- MisplacedWideningCastCheck.cpp - clang-tidy-----------------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "MisplacedWideningCastCheck.h" |
| #include "../utils/Matchers.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| |
| using namespace clang::ast_matchers; |
| |
| namespace clang { |
| namespace tidy { |
| namespace bugprone { |
| |
| MisplacedWideningCastCheck::MisplacedWideningCastCheck( |
| StringRef Name, ClangTidyContext *Context) |
| : ClangTidyCheck(Name, Context), |
| CheckImplicitCasts(Options.get("CheckImplicitCasts", false)) {} |
| |
| void MisplacedWideningCastCheck::storeOptions( |
| ClangTidyOptions::OptionMap &Opts) { |
| Options.store(Opts, "CheckImplicitCasts", CheckImplicitCasts); |
| } |
| |
| void MisplacedWideningCastCheck::registerMatchers(MatchFinder *Finder) { |
| const auto Calc = |
| expr(anyOf(binaryOperator( |
| anyOf(hasOperatorName("+"), hasOperatorName("-"), |
| hasOperatorName("*"), hasOperatorName("<<"))), |
| unaryOperator(hasOperatorName("~"))), |
| hasType(isInteger())) |
| .bind("Calc"); |
| |
| const auto ExplicitCast = explicitCastExpr(hasDestinationType(isInteger()), |
| has(ignoringParenImpCasts(Calc))); |
| const auto ImplicitCast = |
| implicitCastExpr(hasImplicitDestinationType(isInteger()), |
| has(ignoringParenImpCasts(Calc))); |
| const auto Cast = expr(anyOf(ExplicitCast, ImplicitCast)).bind("Cast"); |
| |
| Finder->addMatcher(varDecl(hasInitializer(Cast)), this); |
| Finder->addMatcher(returnStmt(hasReturnValue(Cast)), this); |
| Finder->addMatcher(callExpr(hasAnyArgument(Cast)), this); |
| Finder->addMatcher(binaryOperator(hasOperatorName("="), hasRHS(Cast)), this); |
| Finder->addMatcher( |
| binaryOperator(matchers::isComparisonOperator(), hasEitherOperand(Cast)), |
| this); |
| } |
| |
| static unsigned getMaxCalculationWidth(const ASTContext &Context, |
| const Expr *E) { |
| E = E->IgnoreParenImpCasts(); |
| |
| if (const auto *Bop = dyn_cast<BinaryOperator>(E)) { |
| unsigned LHSWidth = getMaxCalculationWidth(Context, Bop->getLHS()); |
| unsigned RHSWidth = getMaxCalculationWidth(Context, Bop->getRHS()); |
| if (Bop->getOpcode() == BO_Mul) |
| return LHSWidth + RHSWidth; |
| if (Bop->getOpcode() == BO_Add) |
| return std::max(LHSWidth, RHSWidth) + 1; |
| if (Bop->getOpcode() == BO_Rem) { |
| llvm::APSInt Val; |
| if (Bop->getRHS()->EvaluateAsInt(Val, Context)) |
| return Val.getActiveBits(); |
| } else if (Bop->getOpcode() == BO_Shl) { |
| llvm::APSInt Bits; |
| if (Bop->getRHS()->EvaluateAsInt(Bits, Context)) { |
| // We don't handle negative values and large values well. It is assumed |
| // that compiler warnings are written for such values so the user will |
| // fix that. |
| return LHSWidth + Bits.getExtValue(); |
| } |
| |
| // Unknown bitcount, assume there is truncation. |
| return 1024U; |
| } |
| } else if (const auto *Uop = dyn_cast<UnaryOperator>(E)) { |
| // There is truncation when ~ is used. |
| if (Uop->getOpcode() == UO_Not) |
| return 1024U; |
| |
| QualType T = Uop->getType(); |
| return T->isIntegerType() ? Context.getIntWidth(T) : 1024U; |
| } else if (const auto *I = dyn_cast<IntegerLiteral>(E)) { |
| return I->getValue().getActiveBits(); |
| } |
| |
| return Context.getIntWidth(E->getType()); |
| } |
| |
| static int relativeIntSizes(BuiltinType::Kind Kind) { |
| switch (Kind) { |
| case BuiltinType::UChar: |
| return 1; |
| case BuiltinType::SChar: |
| return 1; |
| case BuiltinType::Char_U: |
| return 1; |
| case BuiltinType::Char_S: |
| return 1; |
| case BuiltinType::UShort: |
| return 2; |
| case BuiltinType::Short: |
| return 2; |
| case BuiltinType::UInt: |
| return 3; |
| case BuiltinType::Int: |
| return 3; |
| case BuiltinType::ULong: |
| return 4; |
| case BuiltinType::Long: |
| return 4; |
| case BuiltinType::ULongLong: |
| return 5; |
| case BuiltinType::LongLong: |
| return 5; |
| case BuiltinType::UInt128: |
| return 6; |
| case BuiltinType::Int128: |
| return 6; |
| default: |
| return 0; |
| } |
| } |
| |
| static int relativeCharSizes(BuiltinType::Kind Kind) { |
| switch (Kind) { |
| case BuiltinType::UChar: |
| return 1; |
| case BuiltinType::SChar: |
| return 1; |
| case BuiltinType::Char_U: |
| return 1; |
| case BuiltinType::Char_S: |
| return 1; |
| case BuiltinType::Char16: |
| return 2; |
| case BuiltinType::Char32: |
| return 3; |
| default: |
| return 0; |
| } |
| } |
| |
| static int relativeCharSizesW(BuiltinType::Kind Kind) { |
| switch (Kind) { |
| case BuiltinType::UChar: |
| return 1; |
| case BuiltinType::SChar: |
| return 1; |
| case BuiltinType::Char_U: |
| return 1; |
| case BuiltinType::Char_S: |
| return 1; |
| case BuiltinType::WChar_U: |
| return 2; |
| case BuiltinType::WChar_S: |
| return 2; |
| default: |
| return 0; |
| } |
| } |
| |
| static bool isFirstWider(BuiltinType::Kind First, BuiltinType::Kind Second) { |
| int FirstSize, SecondSize; |
| if ((FirstSize = relativeIntSizes(First)) != 0 && |
| (SecondSize = relativeIntSizes(Second)) != 0) |
| return FirstSize > SecondSize; |
| if ((FirstSize = relativeCharSizes(First)) != 0 && |
| (SecondSize = relativeCharSizes(Second)) != 0) |
| return FirstSize > SecondSize; |
| if ((FirstSize = relativeCharSizesW(First)) != 0 && |
| (SecondSize = relativeCharSizesW(Second)) != 0) |
| return FirstSize > SecondSize; |
| return false; |
| } |
| |
| void MisplacedWideningCastCheck::check(const MatchFinder::MatchResult &Result) { |
| const auto *Cast = Result.Nodes.getNodeAs<CastExpr>("Cast"); |
| if (!CheckImplicitCasts && isa<ImplicitCastExpr>(Cast)) |
| return; |
| if (Cast->getLocStart().isMacroID()) |
| return; |
| |
| const auto *Calc = Result.Nodes.getNodeAs<Expr>("Calc"); |
| if (Calc->getLocStart().isMacroID()) |
| return; |
| |
| if (Cast->isTypeDependent() || Cast->isValueDependent() || |
| Calc->isTypeDependent() || Calc->isValueDependent()) |
| return; |
| |
| ASTContext &Context = *Result.Context; |
| |
| QualType CastType = Cast->getType(); |
| QualType CalcType = Calc->getType(); |
| |
| // Explicit truncation using cast. |
| if (Context.getIntWidth(CastType) < Context.getIntWidth(CalcType)) |
| return; |
| |
| // If CalcType and CastType have same size then there is no real danger, but |
| // there can be a portability problem. |
| |
| if (Context.getIntWidth(CastType) == Context.getIntWidth(CalcType)) { |
| const auto *CastBuiltinType = |
| dyn_cast<BuiltinType>(CastType->getUnqualifiedDesugaredType()); |
| const auto *CalcBuiltinType = |
| dyn_cast<BuiltinType>(CalcType->getUnqualifiedDesugaredType()); |
| if (CastBuiltinType && CalcBuiltinType && |
| !isFirstWider(CastBuiltinType->getKind(), CalcBuiltinType->getKind())) |
| return; |
| } |
| |
| // Don't write a warning if we can easily see that the result is not |
| // truncated. |
| if (Context.getIntWidth(CalcType) >= getMaxCalculationWidth(Context, Calc)) |
| return; |
| |
| diag(Cast->getLocStart(), "either cast from %0 to %1 is ineffective, or " |
| "there is loss of precision before the conversion") |
| << CalcType << CastType; |
| } |
| |
| } // namespace bugprone |
| } // namespace tidy |
| } // namespace clang |