
Fix https://llvm.org/PR49498. The check notices 2 sides of a conditional operator have types with a different constness and so tries to examine the implicit cast. As one side is infinity, the float narrowing detection sees when its casted to a double(which it already was) it thinks the result is out of range. I've fixed this by just disregarding expressions where the builtin type(without quals) match as no conversion would take place. However this has opened a can of worms. Currenty `float a = std::numeric_limits<double>::infinity();` is marked as narrowing. Whats more suspicious is `double a = std::numeric_limits<float>::infinity();` is also marked as narrowing. It could be argued `double inf -> float inf` is narrowing, but `float inf -> double inf` definitely isnt. Reviewed By: carlosgalvezp Differential Revision: https://reviews.llvm.org/D98416
600 lines
25 KiB
C++
600 lines
25 KiB
C++
//===--- 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/SmallString.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
|
|
#include <cstdint>
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang::tidy::cppcoreguidelines {
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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")))));
|
|
|
|
// 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 = hasType(namedDecl(
|
|
hasAnyName(utils::options::parseStringList(IgnoreConversionFromTypes))));
|
|
|
|
// `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;
|
|
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::cppcoreguidelines
|