diff options
Diffstat (limited to 'clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp')
-rw-r--r-- | clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp | 617 |
1 files changed, 617 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp new file mode 100644 index 0000000..a950704 --- /dev/null +++ b/clang-tools-extra/clang-tidy/bugprone/NarrowingConversionsCheck.cpp @@ -0,0 +1,617 @@ +//===--- NarrowingConversionsCheck.cpp - clang-tidy------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "NarrowingConversionsCheck.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Expr.h" +#include "clang/AST/Type.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/SmallVector.h" + +#include <cstdint> + +using namespace clang::ast_matchers; + +namespace clang::tidy::bugprone { + +namespace { + +AST_MATCHER_P(QualType, hasAnyType, std::vector<StringRef>, Names) { + if (Names.empty()) + return false; + + std::string Name = Node.getLocalUnqualifiedType().getAsString(); + return llvm::any_of(Names, [&Name](StringRef Ref) { return Ref == Name; }); +} + +AST_MATCHER(FieldDecl, hasIntBitwidth) { + assert(Node.isBitField()); + const ASTContext &Ctx = Node.getASTContext(); + unsigned IntBitWidth = Ctx.getIntWidth(Ctx.IntTy); + unsigned CurrentBitWidth = Node.getBitWidthValue(Ctx); + return IntBitWidth == CurrentBitWidth; +} + +} // namespace + +NarrowingConversionsCheck::NarrowingConversionsCheck(StringRef Name, + ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + WarnOnIntegerNarrowingConversion( + Options.get("WarnOnIntegerNarrowingConversion", true)), + WarnOnIntegerToFloatingPointNarrowingConversion( + Options.get("WarnOnIntegerToFloatingPointNarrowingConversion", true)), + WarnOnFloatingPointNarrowingConversion( + Options.get("WarnOnFloatingPointNarrowingConversion", true)), + WarnWithinTemplateInstantiation( + Options.get("WarnWithinTemplateInstantiation", false)), + WarnOnEquivalentBitWidth(Options.get("WarnOnEquivalentBitWidth", true)), + IgnoreConversionFromTypes(Options.get("IgnoreConversionFromTypes", "")), + PedanticMode(Options.get("PedanticMode", false)) {} + +void NarrowingConversionsCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "WarnOnIntegerNarrowingConversion", + WarnOnIntegerNarrowingConversion); + Options.store(Opts, "WarnOnIntegerToFloatingPointNarrowingConversion", + WarnOnIntegerToFloatingPointNarrowingConversion); + Options.store(Opts, "WarnOnFloatingPointNarrowingConversion", + WarnOnFloatingPointNarrowingConversion); + Options.store(Opts, "WarnWithinTemplateInstantiation", + WarnWithinTemplateInstantiation); + Options.store(Opts, "WarnOnEquivalentBitWidth", WarnOnEquivalentBitWidth); + Options.store(Opts, "IgnoreConversionFromTypes", IgnoreConversionFromTypes); + Options.store(Opts, "PedanticMode", PedanticMode); +} + +void NarrowingConversionsCheck::registerMatchers(MatchFinder *Finder) { + // ceil() and floor() are guaranteed to return integers, even though the type + // is not integral. + const auto IsCeilFloorCallExpr = expr(callExpr(callee(functionDecl( + hasAnyName("::ceil", "::std::ceil", "::floor", "::std::floor"))))); + + std::vector<StringRef> IgnoreConversionFromTypesVec = + utils::options::parseStringList(IgnoreConversionFromTypes); + + // We may want to exclude other types from the checks, such as `size_type` + // and `difference_type`. These are often used to count elements, represented + // in 64 bits and assigned to `int`. Rarely are people counting >2B elements. + const auto IsConversionFromIgnoredType = + anyOf(hasType(namedDecl(hasAnyName(IgnoreConversionFromTypesVec))), + allOf(unless(hasType(namedDecl())), + hasType(qualType(hasAnyType(IgnoreConversionFromTypesVec))))); + + // `IsConversionFromIgnoredType` will ignore narrowing calls from those types, + // but not expressions that are promoted to an ignored type as a result of a + // binary expression with one of those types. + // For example, it will continue to reject: + // `int narrowed = int_value + container.size()`. + // We attempt to address common incidents of compound expressions with + // `IsIgnoredTypeTwoLevelsDeep`, allowing binary expressions that have one + // operand of the ignored types and the other operand of another integer type. + const auto IsIgnoredTypeTwoLevelsDeep = + anyOf(IsConversionFromIgnoredType, + binaryOperator(hasOperands(IsConversionFromIgnoredType, + hasType(isInteger())))); + + // Bitfields are special. Due to integral promotion [conv.prom/5] bitfield + // member access expressions are frequently wrapped by an implicit cast to + // `int` if that type can represent all the values of the bitfield. + // + // Consider these examples: + // struct SmallBitfield { unsigned int id : 4; }; + // x.id & 1; (case-1) + // x.id & 1u; (case-2) + // x.id << 1u; (case-3) + // (unsigned)x.id << 1; (case-4) + // + // Due to the promotion rules, we would get a warning for case-1. It's + // debatable how useful this is, but the user at least has a convenient way of + // //fixing// it by adding the `u` unsigned-suffix to the literal as + // demonstrated by case-2. However, this won't work for shift operators like + // the one in case-3. In case of a normal binary operator, both operands + // contribute to the result type. However, the type of the shift expression is + // the promoted type of the left operand. One could still suppress this + // superfluous warning by explicitly casting the bitfield member access as + // case-4 demonstrates, but why? The compiler already knew that the value from + // the member access should safely fit into an `int`, why do we have this + // warning in the first place? So, hereby we suppress this specific scenario. + // + // Note that the bitshift operation might invoke unspecified/undefined + // behavior, but that's another topic, this checker is about detecting + // conversion-related defects. + // + // Example AST for `x.id << 1`: + // BinaryOperator 'int' '<<' + // |-ImplicitCastExpr 'int' <IntegralCast> + // | `-ImplicitCastExpr 'unsigned int' <LValueToRValue> + // | `-MemberExpr 'unsigned int' lvalue bitfield .id + // | `-DeclRefExpr 'SmallBitfield' lvalue ParmVar 'x' 'SmallBitfield' + // `-IntegerLiteral 'int' 1 + const auto ImplicitIntWidenedBitfieldValue = implicitCastExpr( + hasCastKind(CK_IntegralCast), hasType(asString("int")), + has(castExpr(hasCastKind(CK_LValueToRValue), + has(ignoringParens(memberExpr(hasDeclaration( + fieldDecl(isBitField(), unless(hasIntBitwidth()))))))))); + + // Casts: + // i = 0.5; + // void f(int); f(0.5); + Finder->addMatcher( + traverse(TK_AsIs, implicitCastExpr( + hasImplicitDestinationType( + hasUnqualifiedDesugaredType(builtinType())), + hasSourceExpression(hasType( + hasUnqualifiedDesugaredType(builtinType()))), + unless(hasSourceExpression(IsCeilFloorCallExpr)), + unless(hasParent(castExpr())), + WarnWithinTemplateInstantiation + ? stmt() + : stmt(unless(isInTemplateInstantiation())), + IgnoreConversionFromTypes.empty() + ? castExpr() + : castExpr(unless(hasSourceExpression( + IsIgnoredTypeTwoLevelsDeep))), + unless(ImplicitIntWidenedBitfieldValue)) + .bind("cast")), + this); + + // Binary operators: + // i += 0.5; + Finder->addMatcher( + binaryOperator( + isAssignmentOperator(), + hasLHS(expr(hasType(hasUnqualifiedDesugaredType(builtinType())))), + hasRHS(expr(hasType(hasUnqualifiedDesugaredType(builtinType())))), + unless(hasRHS(IsCeilFloorCallExpr)), + WarnWithinTemplateInstantiation + ? binaryOperator() + : binaryOperator(unless(isInTemplateInstantiation())), + IgnoreConversionFromTypes.empty() + ? binaryOperator() + : binaryOperator(unless(hasRHS(IsIgnoredTypeTwoLevelsDeep))), + // The `=` case generates an implicit cast + // which is covered by the previous matcher. + unless(hasOperatorName("="))) + .bind("binary_op"), + this); +} + +static const BuiltinType *getBuiltinType(const Expr &E) { + return E.getType().getCanonicalType().getTypePtr()->getAs<BuiltinType>(); +} + +static QualType getUnqualifiedType(const Expr &E) { + return E.getType().getUnqualifiedType(); +} + +static APValue getConstantExprValue(const ASTContext &Ctx, const Expr &E) { + if (auto IntegerConstant = E.getIntegerConstantExpr(Ctx)) + return APValue(*IntegerConstant); + APValue Constant; + if (Ctx.getLangOpts().CPlusPlus && E.isCXX11ConstantExpr(Ctx, &Constant)) + return Constant; + return {}; +} + +static bool getIntegerConstantExprValue(const ASTContext &Context, + const Expr &E, llvm::APSInt &Value) { + APValue Constant = getConstantExprValue(Context, E); + if (!Constant.isInt()) + return false; + Value = Constant.getInt(); + return true; +} + +static bool getFloatingConstantExprValue(const ASTContext &Context, + const Expr &E, llvm::APFloat &Value) { + APValue Constant = getConstantExprValue(Context, E); + if (!Constant.isFloat()) + return false; + Value = Constant.getFloat(); + return true; +} + +namespace { + +struct IntegerRange { + bool contains(const IntegerRange &From) const { + return llvm::APSInt::compareValues(Lower, From.Lower) <= 0 && + llvm::APSInt::compareValues(Upper, From.Upper) >= 0; + } + + bool contains(const llvm::APSInt &Value) const { + return llvm::APSInt::compareValues(Lower, Value) <= 0 && + llvm::APSInt::compareValues(Upper, Value) >= 0; + } + + llvm::APSInt Lower; + llvm::APSInt Upper; +}; + +} // namespace + +static IntegerRange createFromType(const ASTContext &Context, + const BuiltinType &T) { + if (T.isFloatingPoint()) { + unsigned PrecisionBits = llvm::APFloatBase::semanticsPrecision( + Context.getFloatTypeSemantics(T.desugar())); + // Contrary to two's complement integer, floating point values are + // symmetric and have the same number of positive and negative values. + // The range of valid integers for a floating point value is: + // [-2^PrecisionBits, 2^PrecisionBits] + + // Values are created with PrecisionBits plus two bits: + // - One to express the missing negative value of 2's complement + // representation. + // - One for the sign. + llvm::APSInt UpperValue(PrecisionBits + 2, /*isUnsigned*/ false); + UpperValue.setBit(PrecisionBits); + llvm::APSInt LowerValue(PrecisionBits + 2, /*isUnsigned*/ false); + LowerValue.setBit(PrecisionBits); + LowerValue.setSignBit(); + return {LowerValue, UpperValue}; + } + assert(T.isInteger() && "Unexpected builtin type"); + uint64_t TypeSize = Context.getTypeSize(&T); + bool IsUnsignedInteger = T.isUnsignedInteger(); + return {llvm::APSInt::getMinValue(TypeSize, IsUnsignedInteger), + llvm::APSInt::getMaxValue(TypeSize, IsUnsignedInteger)}; +} + +static bool isWideEnoughToHold(const ASTContext &Context, + const BuiltinType &FromType, + const BuiltinType &ToType) { + IntegerRange FromIntegerRange = createFromType(Context, FromType); + IntegerRange ToIntegerRange = createFromType(Context, ToType); + return ToIntegerRange.contains(FromIntegerRange); +} + +static bool isWideEnoughToHold(const ASTContext &Context, + const llvm::APSInt &IntegerConstant, + const BuiltinType &ToType) { + IntegerRange ToIntegerRange = createFromType(Context, ToType); + return ToIntegerRange.contains(IntegerConstant); +} + +// Returns true iff the floating point constant can be losslessly represented +// by an integer in the given destination type. eg. 2.0 can be accurately +// represented by an int32_t, but neither 2^33 nor 2.001 can. +static bool isFloatExactlyRepresentable(const ASTContext &Context, + const llvm::APFloat &FloatConstant, + const QualType &DestType) { + unsigned DestWidth = Context.getIntWidth(DestType); + bool DestSigned = DestType->isSignedIntegerOrEnumerationType(); + llvm::APSInt Result = llvm::APSInt(DestWidth, !DestSigned); + bool IsExact = false; + bool Overflows = FloatConstant.convertToInteger( + Result, llvm::APFloat::rmTowardZero, &IsExact) & + llvm::APFloat::opInvalidOp; + return !Overflows && IsExact; +} + +static llvm::SmallString<64> getValueAsString(const llvm::APSInt &Value, + uint64_t HexBits) { + llvm::SmallString<64> Str; + Value.toString(Str, 10); + if (HexBits > 0) { + Str.append(" (0x"); + llvm::SmallString<32> HexValue; + Value.toStringUnsigned(HexValue, 16); + for (size_t I = HexValue.size(); I < (HexBits / 4); ++I) + Str.append("0"); + Str.append(HexValue); + Str.append(")"); + } + return Str; +} + +bool NarrowingConversionsCheck::isWarningInhibitedByEquivalentSize( + const ASTContext &Context, const BuiltinType &FromType, + const BuiltinType &ToType) const { + // With this option, we don't warn on conversions that have equivalent width + // in bits. eg. uint32 <-> int32. + if (!WarnOnEquivalentBitWidth) { + uint64_t FromTypeSize = Context.getTypeSize(&FromType); + uint64_t ToTypeSize = Context.getTypeSize(&ToType); + if (FromTypeSize == ToTypeSize) { + return true; + } + } + return false; +} + +void NarrowingConversionsCheck::diagNarrowType(SourceLocation SourceLoc, + const Expr &Lhs, + const Expr &Rhs) { + diag(SourceLoc, "narrowing conversion from %0 to %1") + << getUnqualifiedType(Rhs) << getUnqualifiedType(Lhs); +} + +void NarrowingConversionsCheck::diagNarrowTypeToSignedInt( + SourceLocation SourceLoc, const Expr &Lhs, const Expr &Rhs) { + diag(SourceLoc, "narrowing conversion from %0 to signed type %1 is " + "implementation-defined") + << getUnqualifiedType(Rhs) << getUnqualifiedType(Lhs); +} + +void NarrowingConversionsCheck::diagNarrowIntegerConstant( + SourceLocation SourceLoc, const Expr &Lhs, const Expr &Rhs, + const llvm::APSInt &Value) { + diag(SourceLoc, + "narrowing conversion from constant value %0 of type %1 to %2") + << getValueAsString(Value, /*NoHex*/ 0) << getUnqualifiedType(Rhs) + << getUnqualifiedType(Lhs); +} + +void NarrowingConversionsCheck::diagNarrowIntegerConstantToSignedInt( + SourceLocation SourceLoc, const Expr &Lhs, const Expr &Rhs, + const llvm::APSInt &Value, const uint64_t HexBits) { + diag(SourceLoc, "narrowing conversion from constant value %0 of type %1 " + "to signed type %2 is implementation-defined") + << getValueAsString(Value, HexBits) << getUnqualifiedType(Rhs) + << getUnqualifiedType(Lhs); +} + +void NarrowingConversionsCheck::diagNarrowConstant(SourceLocation SourceLoc, + const Expr &Lhs, + const Expr &Rhs) { + diag(SourceLoc, "narrowing conversion from constant %0 to %1") + << getUnqualifiedType(Rhs) << getUnqualifiedType(Lhs); +} + +void NarrowingConversionsCheck::diagConstantCast(SourceLocation SourceLoc, + const Expr &Lhs, + const Expr &Rhs) { + diag(SourceLoc, "constant value should be of type of type %0 instead of %1") + << getUnqualifiedType(Lhs) << getUnqualifiedType(Rhs); +} + +void NarrowingConversionsCheck::diagNarrowTypeOrConstant( + const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs, + const Expr &Rhs) { + APValue Constant = getConstantExprValue(Context, Rhs); + if (Constant.isInt()) + return diagNarrowIntegerConstant(SourceLoc, Lhs, Rhs, Constant.getInt()); + if (Constant.isFloat()) + return diagNarrowConstant(SourceLoc, Lhs, Rhs); + return diagNarrowType(SourceLoc, Lhs, Rhs); +} + +void NarrowingConversionsCheck::handleIntegralCast(const ASTContext &Context, + SourceLocation SourceLoc, + const Expr &Lhs, + const Expr &Rhs) { + if (WarnOnIntegerNarrowingConversion) { + const BuiltinType *ToType = getBuiltinType(Lhs); + // From [conv.integral]p7.3.8: + // Conversions to unsigned integer is well defined so no warning is issued. + // "The resulting value is the smallest unsigned value equal to the source + // value modulo 2^n where n is the number of bits used to represent the + // destination type." + if (ToType->isUnsignedInteger()) + return; + const BuiltinType *FromType = getBuiltinType(Rhs); + + // With this option, we don't warn on conversions that have equivalent width + // in bits. eg. uint32 <-> int32. + if (!WarnOnEquivalentBitWidth) { + uint64_t FromTypeSize = Context.getTypeSize(FromType); + uint64_t ToTypeSize = Context.getTypeSize(ToType); + if (FromTypeSize == ToTypeSize) + return; + } + + llvm::APSInt IntegerConstant; + if (getIntegerConstantExprValue(Context, Rhs, IntegerConstant)) { + if (!isWideEnoughToHold(Context, IntegerConstant, *ToType)) + diagNarrowIntegerConstantToSignedInt(SourceLoc, Lhs, Rhs, + IntegerConstant, + Context.getTypeSize(FromType)); + return; + } + if (!isWideEnoughToHold(Context, *FromType, *ToType)) + diagNarrowTypeToSignedInt(SourceLoc, Lhs, Rhs); + } +} + +void NarrowingConversionsCheck::handleIntegralToBoolean( + const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs, + const Expr &Rhs) { + // Conversion from Integral to Bool value is well defined. + + // We keep this function (even if it is empty) to make sure that + // handleImplicitCast and handleBinaryOperator are symmetric in their behavior + // and handle the same cases. +} + +void NarrowingConversionsCheck::handleIntegralToFloating( + const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs, + const Expr &Rhs) { + if (WarnOnIntegerToFloatingPointNarrowingConversion) { + const BuiltinType *ToType = getBuiltinType(Lhs); + llvm::APSInt IntegerConstant; + if (getIntegerConstantExprValue(Context, Rhs, IntegerConstant)) { + if (!isWideEnoughToHold(Context, IntegerConstant, *ToType)) + diagNarrowIntegerConstant(SourceLoc, Lhs, Rhs, IntegerConstant); + return; + } + + const BuiltinType *FromType = getBuiltinType(Rhs); + if (isWarningInhibitedByEquivalentSize(Context, *FromType, *ToType)) + return; + if (!isWideEnoughToHold(Context, *FromType, *ToType)) + diagNarrowType(SourceLoc, Lhs, Rhs); + } +} + +void NarrowingConversionsCheck::handleFloatingToIntegral( + const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs, + const Expr &Rhs) { + llvm::APFloat FloatConstant(0.0); + if (getFloatingConstantExprValue(Context, Rhs, FloatConstant)) { + if (!isFloatExactlyRepresentable(Context, FloatConstant, Lhs.getType())) + return diagNarrowConstant(SourceLoc, Lhs, Rhs); + + if (PedanticMode) + return diagConstantCast(SourceLoc, Lhs, Rhs); + + return; + } + + const BuiltinType *FromType = getBuiltinType(Rhs); + const BuiltinType *ToType = getBuiltinType(Lhs); + if (isWarningInhibitedByEquivalentSize(Context, *FromType, *ToType)) + return; + diagNarrowType(SourceLoc, Lhs, Rhs); // Assumed always lossy. +} + +void NarrowingConversionsCheck::handleFloatingToBoolean( + const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs, + const Expr &Rhs) { + return diagNarrowTypeOrConstant(Context, SourceLoc, Lhs, Rhs); +} + +void NarrowingConversionsCheck::handleBooleanToSignedIntegral( + const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs, + const Expr &Rhs) { + // Conversion from Bool to SignedIntegral value is well defined. + + // We keep this function (even if it is empty) to make sure that + // handleImplicitCast and handleBinaryOperator are symmetric in their behavior + // and handle the same cases. +} + +void NarrowingConversionsCheck::handleFloatingCast(const ASTContext &Context, + SourceLocation SourceLoc, + const Expr &Lhs, + const Expr &Rhs) { + if (WarnOnFloatingPointNarrowingConversion) { + const BuiltinType *ToType = getBuiltinType(Lhs); + APValue Constant = getConstantExprValue(Context, Rhs); + if (Constant.isFloat()) { + // From [dcl.init.list]p7.2: + // Floating point constant narrowing only takes place when the value is + // not within destination range. We convert the value to the destination + // type and check if the resulting value is infinity. + llvm::APFloat Tmp = Constant.getFloat(); + bool UnusedLosesInfo = false; + Tmp.convert(Context.getFloatTypeSemantics(ToType->desugar()), + llvm::APFloatBase::rmNearestTiesToEven, &UnusedLosesInfo); + if (Tmp.isInfinity()) + diagNarrowConstant(SourceLoc, Lhs, Rhs); + return; + } + const BuiltinType *FromType = getBuiltinType(Rhs); + if (ToType->getKind() < FromType->getKind()) + diagNarrowType(SourceLoc, Lhs, Rhs); + } +} + +void NarrowingConversionsCheck::handleBinaryOperator(const ASTContext &Context, + SourceLocation SourceLoc, + const Expr &Lhs, + const Expr &Rhs) { + assert(!Lhs.isInstantiationDependent() && !Rhs.isInstantiationDependent() && + "Dependent types must be check before calling this function"); + const BuiltinType *LhsType = getBuiltinType(Lhs); + const BuiltinType *RhsType = getBuiltinType(Rhs); + if (RhsType == nullptr || LhsType == nullptr) + return; + if (LhsType == RhsType) + return; + if (RhsType->getKind() == BuiltinType::Bool && LhsType->isSignedInteger()) + return handleBooleanToSignedIntegral(Context, SourceLoc, Lhs, Rhs); + if (RhsType->isInteger() && LhsType->getKind() == BuiltinType::Bool) + return handleIntegralToBoolean(Context, SourceLoc, Lhs, Rhs); + if (RhsType->isInteger() && LhsType->isFloatingPoint()) + return handleIntegralToFloating(Context, SourceLoc, Lhs, Rhs); + if (RhsType->isInteger() && LhsType->isInteger()) + return handleIntegralCast(Context, SourceLoc, Lhs, Rhs); + if (RhsType->isFloatingPoint() && LhsType->getKind() == BuiltinType::Bool) + return handleFloatingToBoolean(Context, SourceLoc, Lhs, Rhs); + if (RhsType->isFloatingPoint() && LhsType->isInteger()) + return handleFloatingToIntegral(Context, SourceLoc, Lhs, Rhs); + if (RhsType->isFloatingPoint() && LhsType->isFloatingPoint()) + return handleFloatingCast(Context, SourceLoc, Lhs, Rhs); +} + +bool NarrowingConversionsCheck::handleConditionalOperator( + const ASTContext &Context, const Expr &Lhs, const Expr &Rhs) { + if (const auto *CO = llvm::dyn_cast<ConditionalOperator>(&Rhs)) { + // We have an expression like so: `output = cond ? lhs : rhs` + // From the point of view of narrowing conversion we treat it as two + // expressions `output = lhs` and `output = rhs`. + handleBinaryOperator(Context, CO->getLHS()->getExprLoc(), Lhs, + *CO->getLHS()); + handleBinaryOperator(Context, CO->getRHS()->getExprLoc(), Lhs, + *CO->getRHS()); + return true; + } + return false; +} + +void NarrowingConversionsCheck::handleImplicitCast( + const ASTContext &Context, const ImplicitCastExpr &Cast) { + if (Cast.getExprLoc().isMacroID()) + return; + const Expr &Lhs = Cast; + const Expr &Rhs = *Cast.getSubExpr(); + if (Lhs.isInstantiationDependent() || Rhs.isInstantiationDependent()) + return; + if (getBuiltinType(Lhs) == getBuiltinType(Rhs)) + return; + if (handleConditionalOperator(Context, Lhs, Rhs)) + return; + SourceLocation SourceLoc = Lhs.getExprLoc(); + switch (Cast.getCastKind()) { + case CK_BooleanToSignedIntegral: + return handleBooleanToSignedIntegral(Context, SourceLoc, Lhs, Rhs); + case CK_IntegralToBoolean: + return handleIntegralToBoolean(Context, SourceLoc, Lhs, Rhs); + case CK_IntegralToFloating: + return handleIntegralToFloating(Context, SourceLoc, Lhs, Rhs); + case CK_IntegralCast: + return handleIntegralCast(Context, SourceLoc, Lhs, Rhs); + case CK_FloatingToBoolean: + return handleFloatingToBoolean(Context, SourceLoc, Lhs, Rhs); + case CK_FloatingToIntegral: + return handleFloatingToIntegral(Context, SourceLoc, Lhs, Rhs); + case CK_FloatingCast: + return handleFloatingCast(Context, SourceLoc, Lhs, Rhs); + default: + break; + } +} + +void NarrowingConversionsCheck::handleBinaryOperator(const ASTContext &Context, + const BinaryOperator &Op) { + if (Op.getBeginLoc().isMacroID()) + return; + const Expr &Lhs = *Op.getLHS(); + const Expr &Rhs = *Op.getRHS(); + if (Lhs.isInstantiationDependent() || Rhs.isInstantiationDependent()) + return; + if (handleConditionalOperator(Context, Lhs, Rhs)) + return; + handleBinaryOperator(Context, Rhs.getBeginLoc(), Lhs, Rhs); +} + +void NarrowingConversionsCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>("binary_op")) + return handleBinaryOperator(*Result.Context, *Op); + if (const auto *Cast = Result.Nodes.getNodeAs<ImplicitCastExpr>("cast")) + return handleImplicitCast(*Result.Context, *Cast); + llvm_unreachable("must be binary operator or cast expression"); +} +} // namespace clang::tidy::bugprone |