#include "NarrowingConversionsCheck.h"
#include "clang/AST/ASTContext.h"
+#include "clang/AST/Type.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "llvm/ADT/APSInt.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/ADT/SmallVector.h"
+
+#include <cstdint>
using namespace clang::ast_matchers;
namespace tidy {
namespace cppcoreguidelines {
-// FIXME: Check double -> float truncation. Pay attention to casts:
+NarrowingConversionsCheck::NarrowingConversionsCheck(StringRef Name,
+ ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ WarnOnFloatingPointNarrowingConversion(
+ Options.get("WarnOnFloatingPointNarrowingConversion", 1)),
+ PedanticMode(Options.get("PedanticMode", 0)) {}
+
void NarrowingConversionsCheck::registerMatchers(MatchFinder *Finder) {
// ceil() and floor() are guaranteed to return integers, even though the type
// is not integral.
- const auto IsCeilFloorCall = callExpr(callee(functionDecl(
- hasAnyName("::ceil", "::std::ceil", "::floor", "::std::floor"))));
-
- const auto IsFloatExpr =
- expr(hasType(realFloatingPointType()), unless(IsCeilFloorCall));
+ const auto IsCeilFloorCallExpr = expr(callExpr(callee(functionDecl(
+ hasAnyName("::ceil", "::std::ceil", "::floor", "::std::floor")))));
- // 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(hasImplicitDestinationType(builtinType()),
+ hasSourceExpression(hasType(builtinType())),
+ unless(hasSourceExpression(IsCeilFloorCallExpr)),
+ 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"),
- this);
+ Finder->addMatcher(binaryOperator(isAssignmentOperator(),
+ hasLHS(expr(hasType(builtinType()))),
+ hasRHS(expr(hasType(builtinType()))),
+ unless(hasRHS(IsCeilFloorCallExpr)),
+ unless(isInTemplateInstantiation()),
+ // The `=` case generates an implicit cast
+ // which is covered by the previous matcher.
+ unless(hasOperatorName("=")))
+ .bind("binary_op"),
+ this);
}
-void NarrowingConversionsCheck::check(const MatchFinder::MatchResult &Result) {
- if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>("op")) {
- if (Op->getBeginLoc().isMacroID())
+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) {
+ llvm::APSInt IntegerConstant;
+ if (E.isIntegerConstantExpr(IntegerConstant, 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);
+}
+
+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;
+}
+
+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) {
+ 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);
+ 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) {
+ 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 (!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);
+
+ // We always warn when Rhs is non-constexpr.
+ if (!getFloatingConstantExprValue(Context, Rhs, FloatConstant))
+ return diagNarrowType(SourceLoc, Lhs, Rhs);
+
+ QualType DestType = Lhs.getType();
+ 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;
+ // We warn iff the constant floating point value is not exactly representable.
+ if (Overflows || !IsExact)
+ return diagNarrowConstant(SourceLoc, Lhs, Rhs);
+
+ if (PedanticMode)
+ return diagConstantCast(SourceLoc, Lhs, Rhs);
+}
+
+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;
+ Tmp.convert(Context.getFloatTypeSemantics(ToType->desugar()),
+ llvm::APFloatBase::rmNearestTiesToEven, &UnusedLosesInfo);
+ if (Tmp.isInfinity())
+ diagNarrowConstant(SourceLoc, Lhs, Rhs);
return;
- diag(Op->getOperatorLoc(), "narrowing conversion from %0 to %1")
- << Op->getRHS()->getType() << Op->getLHS()->getType();
+ }
+ 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 (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;
}
- const auto *Cast = Result.Nodes.getNodeAs<ImplicitCastExpr>("cast");
- if (Cast->getBeginLoc().isMacroID())
+ return false;
+}
+
+void NarrowingConversionsCheck::handleImplicitCast(
+ const ASTContext &Context, const ImplicitCastExpr &Cast) {
+ if (Cast.getExprLoc().isMacroID())
return;
- diag(Cast->getExprLoc(), "narrowing conversion from %0 to %1")
- << Cast->getSubExpr()->getType() << Cast->getType();
+ const Expr &Lhs = Cast;
+ const Expr &Rhs = *Cast.getSubExpr();
+ if (Lhs.isInstantiationDependent() || Rhs.isInstantiationDependent())
+ 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 cppcoreguidelines
-// RUN: %check_clang_tidy %s cppcoreguidelines-narrowing-conversions %t
+// RUN: %check_clang_tidy %s cppcoreguidelines-narrowing-conversions %t \
+// RUN: -config="{CheckOptions: [ \
+// RUN: {key: "cppcoreguidelines-narrowing-conversions.WarnOnFloatingPointNarrowingConversion", value: 0}, \
+// RUN: ]}" \
+// RUN: -- -target x86_64-unknown-linux -fsigned-char
float ceil(float);
namespace std {
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_fp_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]
i = 0.5f;
- // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'float' to 'int' [cppcoreguidelines-narrowing-conversions]
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from constant 'float' to 'int' [cppcoreguidelines-narrowing-conversions]
i = static_cast<float>(d);
// 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) {
- int i = 0;
+ i += d;
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'double' to 'int' [cppcoreguidelines-narrowing-conversions]
i += 0.5;
- // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'double' to 'int' [cppcoreguidelines-narrowing-conversions]
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from constant 'double' to 'int' [cppcoreguidelines-narrowing-conversions]
i += 0.5f;
- // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'float' to 'int' [cppcoreguidelines-narrowing-conversions]
- i += d;
- // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'double' to 'int' [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.
- i += 2.0;
- // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'double' to 'int' [cppcoreguidelines-narrowing-conversions]
- i += 2.0f;
- // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'float' to 'int' [cppcoreguidelines-narrowing-conversions]
-
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from constant 'float' to 'int' [cppcoreguidelines-narrowing-conversions]
i *= 0.5f;
- // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'float' to 'int' [cppcoreguidelines-narrowing-conversions]
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from constant 'float' to 'int' [cppcoreguidelines-narrowing-conversions]
i /= 0.5f;
- // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'float' to 'int' [cppcoreguidelines-narrowing-conversions]
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from constant 'float' to 'int' [cppcoreguidelines-narrowing-conversions]
i += (double)0.5f;
- // CHECK-MESSAGES: :[[@LINE-1]]:5: warning: narrowing conversion from 'double' to 'int' [cppcoreguidelines-narrowing-conversions]
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from constant 'double' to 'int' [cppcoreguidelines-narrowing-conversions]
+ i += 2.0;
+ i += 2.0f;
+}
+
+double operator"" _double(unsigned long long);
+
+float narrow_double_to_float_return() {
+ return 0.5; // [dcl.init.list] 7.2 : in-range fp constant to narrower float is not a narrowing.
+}
+
+void narrow_double_to_float_ok(double d) {
+ float f;
+ f = d;
+ f = 15_double;
+}
+
+void narrow_fp_constants() {
+ float f;
+ f = 0.5; // [dcl.init.list] 7.2 : in-range fp constant to narrower float is not a narrowing.
+
+ f = __builtin_huge_valf(); // max float is not narrowing.
+ f = -__builtin_huge_valf(); // -max float is not narrowing.
+ f = __builtin_inff(); // float infinity is not narrowing.
+ f = __builtin_nanf("0"); // float NaN is not narrowing.
+
+ f = __builtin_huge_val(); // max double is not within-range of float.
+ f = -__builtin_huge_val(); // -max double is not within-range of float.
+ f = __builtin_inf(); // double infinity is not within-range of float.
+ f = __builtin_nan("0"); // double NaN is not narrowing.
+}
+
+void narrow_double_to_float_not_ok_binary_ops(double d) {
+ float f;
+ f += 0.5; // [dcl.init.list] 7.2 : in-range fp constant to narrower float is not a narrowing.
+ f += 2.0; // [dcl.init.list] 7.2 : in-range fp constant to narrower float is not a narrowing.
+ f *= 0.5; // [dcl.init.list] 7.2 : in-range fp constant to narrower float is not a narrowing.
+ f /= 0.5; // [dcl.init.list] 7.2 : in-range fp constant to narrower float is not a narrowing.
+ f += (double)0.5f; // [dcl.init.list] 7.2 : in-range fp constant to narrower float is not a narrowing.
+ f += d; // We do not warn about floating point narrowing by default.
+}
+
+void narrow_fp_constant_to_bool_not_ok() {
+ bool b1 = 1.0;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: narrowing conversion from constant 'double' to 'bool' [cppcoreguidelines-narrowing-conversions]
+ bool b2 = 1.0f;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: narrowing conversion from constant 'float' to 'bool' [cppcoreguidelines-narrowing-conversions]
+}
+
+void narrow_integer_to_floating() {
+ {
+ long long ll; // 64 bits
+ float f = ll; // doesn't fit in 24 bits
+ // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: narrowing conversion from 'long long' to 'float' [cppcoreguidelines-narrowing-conversions]
+ double d = ll; // doesn't fit in 53 bits.
+ // CHECK-MESSAGES: :[[@LINE-1]]:16: warning: narrowing conversion from 'long long' to 'double' [cppcoreguidelines-narrowing-conversions]
+ }
+ {
+ int i; // 32 bits
+ float f = i; // doesn't fit in 24 bits
+ // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: narrowing conversion from 'int' to 'float' [cppcoreguidelines-narrowing-conversions]
+ double d = i; // fits in 53 bits.
+ }
+ {
+ short n1, n2;
+ float f = n1 + n2; // 'n1 + n2' is of type 'int' because of integer rules
+ // CHECK-MESSAGES: :[[@LINE-1]]:15: warning: narrowing conversion from 'int' to 'float' [cppcoreguidelines-narrowing-conversions]
+ }
+ {
+ short s; // 16 bits
+ float f = s; // fits in 24 bits
+ double d = s; // fits in 53 bits.
+ }
+}
+
+void narrow_integer_to_unsigned_integer_is_ok() {
+ char c;
+ short s;
+ int i;
+ long l;
+ long long ll;
+
+ unsigned char uc;
+ unsigned short us;
+ unsigned int ui;
+ unsigned long ul;
+ unsigned long long ull;
+
+ ui = c;
+ uc = s;
+ uc = i;
+ uc = l;
+ uc = ll;
+
+ uc = uc;
+ uc = us;
+ uc = ui;
+ uc = ul;
+ uc = ull;
+}
+
+void narrow_integer_to_signed_integer_is_not_ok() {
+ char c;
+ short s;
+ int i;
+ long l;
+ long long ll;
+
+ unsigned char uc;
+ unsigned short us;
+ unsigned int ui;
+ unsigned long ul;
+ unsigned long long ull;
+
+ c = c;
+ c = s;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'short' to signed type 'char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ c = i;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'int' to signed type 'char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ c = l;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'long' to signed type 'char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ c = ll;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'long long' to signed type 'char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+
+ c = uc;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'unsigned char' to signed type 'char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ c = us;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'unsigned short' to signed type 'char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ c = ui;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'unsigned int' to signed type 'char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ c = ul;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'unsigned long' to signed type 'char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ c = ull;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'unsigned long long' to signed type 'char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+
+ i = c;
+ i = s;
+ i = i;
+ i = l;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'long' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ i = ll;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'long long' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+
+ i = uc;
+ i = us;
+ i = ui;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'unsigned int' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ i = ul;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'unsigned long' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ i = ull;
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'unsigned long long' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+
+ ll = c;
+ ll = s;
+ ll = i;
+ ll = l;
+ ll = ll;
+
+ ll = uc;
+ ll = us;
+ ll = ui;
+ ll = ul;
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'unsigned long' to signed type 'long long' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ ll = ull;
+ // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'unsigned long long' to signed type 'long long' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+}
+
+void narrow_constant_to_unsigned_integer_is_ok() {
+ unsigned char uc1 = 0;
+ unsigned char uc2 = 255;
+ unsigned char uc3 = -1; // unsigned dst type is well defined.
+ unsigned char uc4 = 256; // unsigned dst type is well defined.
+ unsigned short us1 = 0;
+ unsigned short us2 = 65535;
+ unsigned short us3 = -1; // unsigned dst type is well defined.
+ unsigned short us4 = 65536; // unsigned dst type is well defined.
+}
+
+void narrow_constant_to_signed_integer_is_not_ok() {
+ char c1 = -128;
+ char c2 = 127;
+ char c3 = -129;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: narrowing conversion from constant value -129 (0xFFFFFF7F) of type 'int' to signed type 'char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ char c4 = 128;
+ // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: narrowing conversion from constant value 128 (0x00000080) of type 'int' to signed type 'char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+
+ short s1 = -32768;
+ short s2 = 32767;
+ short s3 = -32769;
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: narrowing conversion from constant value -32769 (0xFFFF7FFF) of type 'int' to signed type 'short' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ short s4 = 32768;
+ // CHECK-MESSAGES: :[[@LINE-1]]:14: warning: narrowing conversion from constant value 32768 (0x00008000) of type 'int' to signed type 'short' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+}
+
+void narrow_conditional_operator_contant_to_unsigned_is_ok(bool b) {
+ // conversion to unsigned dst type is well defined.
+ unsigned char c1 = b ? 1 : 0;
+ unsigned char c2 = b ? 1 : 256;
+ unsigned char c3 = b ? -1 : 0;
+}
+
+void narrow_conditional_operator_contant_to_signed_is_not_ok(bool b) {
+ char uc1 = b ? 1 : 0;
+ char uc2 = b ? 1 : 128;
+ // CHECK-MESSAGES: :[[@LINE-1]]:22: warning: narrowing conversion from constant value 128 (0x00000080) of type 'int' to signed type 'char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ char uc3 = b ? -129 : 0;
+ // CHECK-MESSAGES: :[[@LINE-1]]:18: warning: narrowing conversion from constant value -129 (0xFFFFFF7F) of type 'int' to signed type 'char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ unsigned long long ysize;
+ long long mirror = b ? -1 : ysize - 1;
+ // CHECK-MESSAGES: :[[@LINE-1]]:26: warning: narrowing conversion from constant value 18446744073709551615 (0xFFFFFFFFFFFFFFFF) of type 'unsigned long long' to signed type 'long long' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ // CHECK-MESSAGES: :[[@LINE-2]]:37: warning: narrowing conversion from 'unsigned long long' to signed type 'long long' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+}
+
+void narrow_constant_to_floating_point() {
+ float f_ok = 1ULL << 24; // fits in 24 bits mantissa.
+ float f_not_ok = (1ULL << 24) + 1ULL; // doesn't fit in 24 bits mantissa.
+ // CHECK-MESSAGES: :[[@LINE-1]]:20: warning: narrowing conversion from constant value 16777217 of type 'unsigned long long' to 'float' [cppcoreguidelines-narrowing-conversions]
+ double d_ok = 1ULL << 53; // fits in 53 bits mantissa.
+ double d_not_ok = (1ULL << 53) + 1ULL; // doesn't fit in 53 bits mantissa.
+ // CHECK-MESSAGES: :[[@LINE-1]]:21: warning: narrowing conversion from constant value 9007199254740993 of type 'unsigned long long' to 'double' [cppcoreguidelines-narrowing-conversions]
+}
+
+void casting_integer_to_bool_is_ok() {
+ int i;
+ while (i) {
+ }
+ for (; i;) {
+ }
+ if (i) {
+ }
+}
+
+void casting_float_to_bool_is_not_ok() {
+ float f;
+ while (f) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: narrowing conversion from 'float' to 'bool' [cppcoreguidelines-narrowing-conversions]
+ }
+ for (; f;) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:10: warning: narrowing conversion from 'float' to 'bool' [cppcoreguidelines-narrowing-conversions]
+ }
+ if (f) {
+ // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'float' to 'bool' [cppcoreguidelines-narrowing-conversions]
+ }
+}
+
+void legitimate_comparison_do_not_warn(unsigned long long size) {
+ for (int i = 0; i < size; ++i) {
+ }
}
void ok(double d) {
void template_context() {
f(1, 2);
+ f(1, .5f);
f(1, .5);
+ f(1, .5l);
}
#define DERP(i, j) (i += j)
void macro_context() {
int i = 0;
DERP(i, 2);
+ DERP(i, .5f);
DERP(i, .5);
+ DERP(i, .5l);
}
-} // namespace floats
+} // namespace floats