Index: clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.h =================================================================== --- clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.h +++ clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.h @@ -28,6 +28,13 @@ : ClangTidyCheck(Name, Context) {} void registerMatchers(ast_matchers::MatchFinder *Finder) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + void checkBinaryOperator(const ASTContext *const Context, + const BinaryOperator &Op); + + void checkCastOperator(const ASTContext *const Context, + const ImplicitCastExpr &Cast); }; } // namespace cppcoreguidelines Index: clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp =================================================================== --- clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp +++ clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp @@ -27,42 +27,68 @@ const auto IsFloatExpr = expr(hasType(realFloatingPointType()), unless(IsCeilFloorCall)); - // casts: + // Casts: // i = 0.5; // void f(int); f(0.5); - Finder->addMatcher(implicitCastExpr(hasImplicitDestinationType(isInteger()), - hasSourceExpression(IsFloatExpr), - unless(hasParent(castExpr())), - unless(isInTemplateInstantiation())) - .bind("cast"), - this); + Finder->addMatcher( + implicitCastExpr( + anyOf(hasImplicitDestinationType(isInteger()), + hasImplicitDestinationType(realFloatingPointType())), + hasSourceExpression(IsFloatExpr), unless(hasParent(castExpr())), + unless(isInTemplateInstantiation())) + .bind("cast"), + this); // Binary operators: // i += 0.5; Finder->addMatcher( - binaryOperator(isAssignmentOperator(), - // The `=` case generates an implicit cast which is covered - // by the previous matcher. - unless(hasOperatorName("=")), - hasLHS(hasType(isInteger())), hasRHS(IsFloatExpr), - unless(isInTemplateInstantiation())) - .bind("op"), + binaryOperator( + isAssignmentOperator(), + // The `=` case generates an implicit cast which is covered + // by the previous matcher. + unless(hasOperatorName("=")), + hasLHS(anyOf(hasType(isInteger()), hasType(realFloatingPointType()))), + hasRHS(IsFloatExpr), unless(isInTemplateInstantiation())) + .bind("binary_op"), this); } -void NarrowingConversionsCheck::check(const MatchFinder::MatchResult &Result) { - if (const auto *Op = Result.Nodes.getNodeAs("op")) { - if (Op->getBeginLoc().isMacroID()) - return; - diag(Op->getOperatorLoc(), "narrowing conversion from %0 to %1") - << Op->getRHS()->getType() << Op->getLHS()->getType(); +static bool isNarrower(const ASTContext *const Context, const QualType Lhs, + const QualType Rhs) { + assert(Lhs->isRealType()); // Either integer or floating point. + assert(Rhs->isFloatingType()); // Floating point only. + return Lhs->isIntegerType() || + (Context->getTypeSize(Rhs) > Context->getTypeSize(Lhs)); +} + +void NarrowingConversionsCheck::checkBinaryOperator( + const ASTContext *const Context, const BinaryOperator &Op) { + if (Op.getBeginLoc().isMacroID()) return; - } - const auto *Cast = Result.Nodes.getNodeAs("cast"); - if (Cast->getBeginLoc().isMacroID()) + const auto LhsType = Op.getLHS()->getType(); + const auto RhsType = Op.getRHS()->getType(); + if (isNarrower(Context, LhsType, RhsType)) + diag(Op.getOperatorLoc(), "narrowing conversion from %0 to %1") + << RhsType << LhsType; +} + +void NarrowingConversionsCheck::checkCastOperator( + const ASTContext *const Context, const ImplicitCastExpr &Cast) { + if (Cast.getBeginLoc().isMacroID()) return; - diag(Cast->getExprLoc(), "narrowing conversion from %0 to %1") - << Cast->getSubExpr()->getType() << Cast->getType(); + const auto LhsType = Cast.getType(); + const auto RhsType = Cast.getSubExpr()->getType(); + if (isNarrower(Context, LhsType, RhsType)) + diag(Cast.getExprLoc(), "narrowing conversion from %0 to %1") + << RhsType << LhsType; +} + +void NarrowingConversionsCheck::check(const MatchFinder::MatchResult &Result) { + if (const auto *Op = Result.Nodes.getNodeAs("binary_op")) + return checkBinaryOperator(Result.Context, *Op); + if (const auto *Cast = Result.Nodes.getNodeAs("cast")) + return checkCastOperator(Result.Context, *Cast); + llvm_unreachable("must be binary operator or cast expression"); } } // namespace cppcoreguidelines Index: docs/clang-tidy/checks/cppcoreguidelines-narrowing-conversions.rst =================================================================== --- docs/clang-tidy/checks/cppcoreguidelines-narrowing-conversions.rst +++ docs/clang-tidy/checks/cppcoreguidelines-narrowing-conversions.rst @@ -10,13 +10,15 @@ This rule is part of the "Expressions and statements" profile of the C++ Core Guidelines, corresponding to rule ES.46. See -https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Res-narrowing. +https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#es46-avoid-lossy-narrowing-truncating-arithmetic-conversions. -We enforce only part of the guideline, more specifically, we flag: - - All floating-point to integer conversions that are not marked by an explicit - cast (c-style or ``static_cast``). For example: ``int i = 0; i += 0.1;``, - ``void f(int); f(0.1);``, - - All applications of binary operators where the left-hand-side is an integer - and the right-hand-size is a floating-point. For example: - ``int i; i+= 0.1;``. +A narrowing conversion is currently defined as a conversion from: + - a floating-point to an integer (e.g. ``double`` to ``int``), + - a floating-point to a narrower floating-point (e.g. ``double`` to ``float``). +This check will flag: + - All narrowing conversions that are not marked by an explicit cast (c-style or + ``static_cast``). For example: ``int i = 0; i += 0.1;``, + ``void f(int); f(0.1);``, + - All applications of binary operators with a narrowing conversions. + For example: ``int i; i+= 0.1;``. Index: test/clang-tidy/cppcoreguidelines-narrowing-conversions.cpp =================================================================== --- test/clang-tidy/cppcoreguidelines-narrowing-conversions.cpp +++ test/clang-tidy/cppcoreguidelines-narrowing-conversions.cpp @@ -9,12 +9,12 @@ namespace floats { struct ConvertsToFloat { - operator float() const { return 0.5; } + operator float() const { return 0.5f; } }; -float operator "" _Pa(unsigned long long); +float operator"" _float(unsigned long long); -void not_ok(double d) { +void narrow_double_to_int_not_ok(double d) { int i = 0; i = d; // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'double' to 'int' [cppcoreguidelines-narrowing-conversions] @@ -24,11 +24,11 @@ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'float' to 'int' [cppcoreguidelines-narrowing-conversions] i = ConvertsToFloat(); // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'float' to 'int' [cppcoreguidelines-narrowing-conversions] - i = 15_Pa; + i = 15_float; // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'float' to 'int' [cppcoreguidelines-narrowing-conversions] } -void not_ok_binary_ops(double d) { +void narrow_double_to_int_not_ok_binary_ops(double d) { int i = 0; i += 0.5; // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'double' to 'int' [cppcoreguidelines-narrowing-conversions] @@ -52,6 +52,43 @@ // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'double' to 'int' [cppcoreguidelines-narrowing-conversions] } +double operator"" _double(unsigned long long); + +float narrow_double_to_float_return() { + return 0.5; + // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: narrowing conversion from 'double' to 'float' [cppcoreguidelines-narrowing-conversions] +} + +void narrow_double_to_float_not_ok(double d) { + float f = 0; + f = d; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'double' to 'float' [cppcoreguidelines-narrowing-conversions] + f = 0.5; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'double' to 'float' [cppcoreguidelines-narrowing-conversions] + f = 15_double; + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'double' to 'float' [cppcoreguidelines-narrowing-conversions] +} + +void narrow_double_to_float_not_ok_binary_ops(double d) { + float f = 0; + f += 0.5; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'double' to 'float' [cppcoreguidelines-narrowing-conversions] + f += d; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'double' to 'float' [cppcoreguidelines-narrowing-conversions] + // We warn on the following even though it's not dangerous because there is no + // reason to use a double literal here. + // TODO(courbet): Provide an automatic fix. + f += 2.0; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'double' to 'float' [cppcoreguidelines-narrowing-conversions] + + f *= 0.5; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'double' to 'float' [cppcoreguidelines-narrowing-conversions] + f /= 0.5; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'double' to 'float' [cppcoreguidelines-narrowing-conversions] + f += (double)0.5f; + // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'double' to 'float' [cppcoreguidelines-narrowing-conversions] +} + void ok(double d) { int i = 0; i = 1; @@ -89,7 +126,9 @@ void template_context() { f(1, 2); + f(1, .5f); f(1, .5); + f(1, .5l); } #define DERP(i, j) (i += j) @@ -97,7 +136,9 @@ void macro_context() { int i = 0; DERP(i, 2); + DERP(i, .5f); DERP(i, .5); + DERP(i, .5l); } -} // namespace floats +} // namespace floats