Skip to content

Commit

Permalink
[clang-tidy] Improving narrowing conversions
Browse files Browse the repository at this point in the history
Summary:
Newly flagged narrowing conversions:
 - integer to narrower signed integer (this is compiler implementation defined),
 - integer - floating point narrowing conversions,
 - floating point - integer narrowing conversions,
 - constants with narrowing conversions (even in ternary operator).

Reviewers: hokein, alexfh, aaron.ballman, JonasToth

Reviewed By: aaron.ballman, JonasToth

Subscribers: lebedev.ri, courbet, nemanjai, xazax.hun, kbarton, cfe-commits

Tags: #clang-tools-extra

Differential Revision: https://reviews.llvm.org/D53488

llvm-svn: 347570
  • Loading branch information
gchatelet committed Nov 26, 2018
1 parent b9e4852 commit 10a7ee7
Show file tree
Hide file tree
Showing 9 changed files with 988 additions and 66 deletions.

Large diffs are not rendered by default.

Expand Up @@ -24,10 +24,76 @@ namespace cppcoreguidelines {
/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-narrowing-conversions.html
class NarrowingConversionsCheck : public ClangTidyCheck {
public:
NarrowingConversionsCheck(StringRef Name, ClangTidyContext *Context)
: ClangTidyCheck(Name, Context) {}
NarrowingConversionsCheck(StringRef Name, ClangTidyContext *Context);

void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;

private:
void diagNarrowType(SourceLocation SourceLoc, const Expr &Lhs,
const Expr &Rhs);

void diagNarrowTypeToSignedInt(SourceLocation SourceLoc, const Expr &Lhs,
const Expr &Rhs);

void diagNarrowIntegerConstant(SourceLocation SourceLoc, const Expr &Lhs,
const Expr &Rhs, const llvm::APSInt &Value);

void diagNarrowIntegerConstantToSignedInt(SourceLocation SourceLoc,
const Expr &Lhs, const Expr &Rhs,
const llvm::APSInt &Value,
const uint64_t HexBits);

void diagNarrowConstant(SourceLocation SourceLoc, const Expr &Lhs,
const Expr &Rhs);

void diagConstantCast(SourceLocation SourceLoc, const Expr &Lhs,
const Expr &Rhs);

void diagNarrowTypeOrConstant(const ASTContext &Context,
SourceLocation SourceLoc, const Expr &Lhs,
const Expr &Rhs);

void handleIntegralCast(const ASTContext &Context, SourceLocation SourceLoc,
const Expr &Lhs, const Expr &Rhs);

void handleIntegralToBoolean(const ASTContext &Context,
SourceLocation SourceLoc, const Expr &Lhs,
const Expr &Rhs);

void handleIntegralToFloating(const ASTContext &Context,
SourceLocation SourceLoc, const Expr &Lhs,
const Expr &Rhs);

void handleFloatingToIntegral(const ASTContext &Context,
SourceLocation SourceLoc, const Expr &Lhs,
const Expr &Rhs);

void handleFloatingToBoolean(const ASTContext &Context,
SourceLocation SourceLoc, const Expr &Lhs,
const Expr &Rhs);

void handleBooleanToSignedIntegral(const ASTContext &Context,
SourceLocation SourceLoc, const Expr &Lhs,
const Expr &Rhs);

void handleFloatingCast(const ASTContext &Context, SourceLocation SourceLoc,
const Expr &Lhs, const Expr &Rhs);

void handleBinaryOperator(const ASTContext &Context, SourceLocation SourceLoc,
const Expr &Lhs, const Expr &Rhs);

bool handleConditionalOperator(const ASTContext &Context, const Expr &Lhs,
const Expr &Rhs);

void handleImplicitCast(const ASTContext &Context,
const ImplicitCastExpr &Cast);

void handleBinaryOperator(const ASTContext &Context,
const BinaryOperator &Op);

const bool WarnOnFloatingPointNarrowingConversion;
const bool PedanticMode;
};

} // namespace cppcoreguidelines
Expand Down
8 changes: 8 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Expand Up @@ -217,6 +217,14 @@ Improvements to clang-tidy
<clang-tidy/checks/readability-redundant-smartptr-get>` check does not warn
about calls inside macros anymore by default.

- The :doc:`cppcoreguidelines-narrowing-conversions
<clang-tidy/checks/cppcoreguidelines-narrowing-conversions>` check now
detects more narrowing conversions:
- integer to narrower signed integer (this is compiler implementation defined),
- integer - floating point narrowing conversions,
- floating point - integer narrowing conversions,
- constants with narrowing conversions (even in ternary operator).

Improvements to include-fixer
-----------------------------

Expand Down
Expand Up @@ -10,13 +10,55 @@ following: ``void MyClass::f(double d) { int_member_ += d; }``.
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;``,
We enforce only part of the guideline, more specifically, we flag narrowing conversions from:
- an integer to a narrower integer (e.g. ``char`` to ``unsigned char``),
- an integer to a narrower floating-point (e.g. ``uint64_t`` to ``float``),
- a floating-point to an integer (e.g. ``double`` to ``int``),
- a floating-point to a narrower floating-point (e.g. ``double`` to ``float``)
if WarnOnFloatingPointNarrowingConversion Option is set.

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 where the left-hand-side is an integer
and the right-hand-size is a floating-point. For example:
``int i; i+= 0.1;``.
- All applications of binary operators with a narrowing conversions.
For example: ``int i; i+= 0.1;``.


Options
-------

.. option:: WarnOnFloatingPointNarrowingConversion

When non-zero, the check will warn on narrowing floating point conversion
(e.g. ``double`` to ``float``). `1` by default.

.. option:: PedanticMode

When non-zero, the check will warn on assigning a floating point constant
to an integer value even if the floating point value is exactly
representable in the destination type (e.g. ``int i = 1.0;``).
`0` by default.

FAQ
---

- What does "narrowing conversion from 'int' to 'float'" mean?

An IEEE754 Floating Point number can represent all integer values in the range
[-2^PrecisionBits, 2^PrecisionBits] where PrecisionBits is the number of bits in
the mantissa.

For ``float`` this would be [-2^23, 2^23], where ``int`` can represent values in
the range [-2^31, 2^31-1].

- What does "implementation-defined" mean?

You may have encountered messages like "narrowing conversion from 'unsigned int'
to signed type 'int' is implementation-defined".
The C/C++ standard does not mandate two’s complement for signed integers, and so
the compiler is free to define what the semantics are for converting an unsigned
integer to signed integer. Clang's implementation uses the two’s complement
format.
@@ -0,0 +1,23 @@
// RUN: %check_clang_tidy %s cppcoreguidelines-narrowing-conversions %t \
// RUN: -- -- -target x86_64-unknown-linux -m32

static_assert(sizeof(int) * 8 == 32, "int is 32-bits");
static_assert(sizeof(long) * 8 == 32, "long is 32-bits");
static_assert(sizeof(long long) * 8 == 64, "long long is 64-bits");

void narrow_integer_to_signed_integer_is_not_ok() {
int i; // i.e. int32_t
long l; // i.e. int32_t
long long ll; // i.e. int64_t

unsigned int ui; // i.e. uint32_t
unsigned long ul; // i.e. uint32_t
unsigned long long ull; // i.e. uint64_t

i = l; // int and long are the same type.
i = ll; // int64_t does not fit in an int32_t
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from 'long long' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
ll = ul; // uint32_t fits into int64_t
ll = ull; // uint64_t does not fit in an int64_t
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'unsigned long long' to signed type 'long long' is implementation-defined [cppcoreguidelines-narrowing-conversions]
}
@@ -0,0 +1,57 @@
// RUN: %check_clang_tidy %s cppcoreguidelines-narrowing-conversions %t \
// RUN: -- -- -target x86_64-unknown-linux -fsigned-char

namespace floats {

void narrow_constant_floating_point_to_int_not_ok(double d) {
int i = 0;
i += 0.5;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from constant 'double' to 'int' [cppcoreguidelines-narrowing-conversions]
i += 0.5f;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from constant 'float' to 'int' [cppcoreguidelines-narrowing-conversions]
i *= 0.5f;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from constant 'float' to 'int' [cppcoreguidelines-narrowing-conversions]
i /= 0.5f;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from constant 'float' to 'int' [cppcoreguidelines-narrowing-conversions]
i += (double)0.5f;
// 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;
}

void narrow_double_to_float_not_ok(double d) {
float f;
f = d;
// 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]
f += d;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'double' to 'float' [cppcoreguidelines-narrowing-conversions]
f = narrow_double_to_float_return();
}

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.
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from constant 'double' to 'float' [cppcoreguidelines-narrowing-conversions]
f = -__builtin_huge_val(); // -max double is not within-range of float.
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from constant 'double' to 'float' [cppcoreguidelines-narrowing-conversions]
f = __builtin_inf(); // double infinity is not within-range of float.
// CHECK-MESSAGES: :[[@LINE-1]]:7: warning: narrowing conversion from constant 'double' to 'float' [cppcoreguidelines-narrowing-conversions]
f = __builtin_nan("0"); // double NaN is not narrowing.
}

} // namespace floats
@@ -0,0 +1,23 @@
// RUN: %check_clang_tidy %s cppcoreguidelines-narrowing-conversions %t \
// RUN: -config="{CheckOptions: [ \
// RUN: {key: "cppcoreguidelines-narrowing-conversions.PedanticMode", value: 1} \
// RUN: ]}" \
// RUN: -- -target x86_64-unknown-linux -fsigned-char

namespace floats {

void triggers_wrong_constant_type_warning(double d) {
int i = 0.0;
// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: constant value should be of type of type 'int' instead of 'double' [cppcoreguidelines-narrowing-conversions]
i += 2.0;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: constant value should be of type of type 'int' instead of 'double' [cppcoreguidelines-narrowing-conversions]
i += 2.0f;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: constant value should be of type of type 'int' instead of 'float' [cppcoreguidelines-narrowing-conversions]
}

void triggers_narrowing_warning_when_overflowing() {
unsigned short us = 65537.0;
// CHECK-MESSAGES: :[[@LINE-1]]:23: warning: narrowing conversion from constant 'double' to 'unsigned short' [cppcoreguidelines-narrowing-conversions]
}

} // namespace floats
@@ -0,0 +1,83 @@
// RUN: %check_clang_tidy %s cppcoreguidelines-narrowing-conversions %t \
// RUN: -- -- -target x86_64-unknown-linux -funsigned-char

void narrow_integer_to_unsigned_integer_is_ok() {
signed char sc;
short s;
int i;
long l;
long long ll;

char c;
unsigned short us;
unsigned int ui;
unsigned long ul;
unsigned long long ull;

ui = sc;
c = s;
c = i;
c = l;
c = ll;

c = c;
c = us;
c = ui;
c = ul;
c = ull;
}

void narrow_integer_to_signed_integer_is_not_ok() {
signed char sc;
short s;
int i;
long l;
long long ll;

char c;
unsigned short us;
unsigned int ui;
unsigned long ul;
unsigned long long ull;

sc = sc;
sc = s;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'short' to signed type 'signed char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
sc = i;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'int' to signed type 'signed char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
sc = l;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'long' to signed type 'signed char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
sc = ll;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'long long' to signed type 'signed char' is implementation-defined [cppcoreguidelines-narrowing-conversions]

sc = c;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'char' to signed type 'signed char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
sc = us;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'unsigned short' to signed type 'signed char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
sc = ui;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'unsigned int' to signed type 'signed char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
sc = ul;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'unsigned long' to signed type 'signed char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
sc = ull;
// CHECK-MESSAGES: :[[@LINE-1]]:8: warning: narrowing conversion from 'unsigned long long' to signed type 'signed char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
}

void narrow_constant_to_unsigned_integer_is_ok() {
char c1 = -128; // unsigned dst type is well defined.
char c2 = 127; // unsigned dst type is well defined.
char c3 = -129; // unsigned dst type is well defined.
char c4 = 128; // unsigned dst type is well defined.
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.
signed char sc = 128;
// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: narrowing conversion from constant value 128 (0x00000080) of type 'int' to signed type 'signed char' is implementation-defined [cppcoreguidelines-narrowing-conversions]
}

void narrow_conditional_operator_contant_to_unsigned_is_ok(bool b) {
// conversion to unsigned char type is well defined.
char c1 = b ? 1 : 0;
char c2 = b ? 1 : 256;
char c3 = b ? -1 : 0;
}

0 comments on commit 10a7ee7

Please sign in to comment.