//===----------------------------------------------------------------------===//
#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"
namespace clang {
namespace tidy {
namespace cppcoreguidelines {
+namespace {
+auto hasAnyListedName(const std::string &Names) {
+ const std::vector<std::string> NameList =
+ utils::options::parseStringList(Names);
+ return hasAnyName(std::vector<StringRef>(NameList.begin(), NameList.end()));
+}
+} // namespace
NarrowingConversionsCheck::NarrowingConversionsCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
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, "WarnOnFloatingPointNarrowingConversion",
WarnOnFloatingPointNarrowingConversion);
+ Options.store(Opts, "WarnWithinTemplateInstantiation",
+ WarnWithinTemplateInstantiation);
+ Options.store(Opts, "WarnOnEquivalentBitWidth", WarnOnEquivalentBitWidth);
+ Options.store(Opts, "IgnoreConversionFromTypes", IgnoreConversionFromTypes);
Options.store(Opts, "PedanticMode", PedanticMode);
}
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(hasAnyListedName(IgnoreConversionFromTypes)));
+
+ // `IsConversionFromIgnoredType` will ignore narrowing calls from those types,
+ // but not expressions that are promoted to `int64` due to 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()))));
+
// Casts:
// i = 0.5;
// void f(int); f(0.5);
hasUnqualifiedDesugaredType(builtinType()))),
unless(hasSourceExpression(IsCeilFloorCallExpr)),
unless(hasParent(castExpr())),
- unless(isInTemplateInstantiation()))
+ WarnWithinTemplateInstantiation
+ ? stmt()
+ : stmt(unless(isInTemplateInstantiation())),
+ IgnoreConversionFromTypes.empty()
+ ? castExpr()
+ : castExpr(unless(hasSourceExpression(
+ IsIgnoredTypeTwoLevelsDeep))))
.bind("cast")),
this);
hasLHS(expr(hasType(hasUnqualifiedDesugaredType(builtinType())))),
hasRHS(expr(hasType(hasUnqualifiedDesugaredType(builtinType())))),
unless(hasRHS(IsCeilFloorCallExpr)),
- unless(isInTemplateInstantiation()),
+ 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("=")))
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))
--- /dev/null
+// RUN: %check_clang_tidy -check-suffix=DEFAULT %s \
+// RUN: cppcoreguidelines-narrowing-conversions %t -- \
+// RUN: -config='{CheckOptions: [ \
+// RUN: ]}'
+
+// RUN: %check_clang_tidy -check-suffix=IGNORED %s \
+// RUN: cppcoreguidelines-narrowing-conversions %t -- \
+// RUN: -config='{CheckOptions: [ \
+// RUN: {key: cppcoreguidelines-narrowing-conversions.IgnoreConversionFromTypes, value: "global_size_t;nested_size_type"} \
+// RUN: ]}'
+
+// We use global_size_t instead of 'size_t' because windows predefines size_t.
+typedef long long global_size_t;
+
+struct vector {
+ typedef long long nested_size_type;
+
+ global_size_t size() const { return 0; }
+};
+
+void narrowing_global_size_t() {
+ int i;
+ global_size_t j;
+ i = j;
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:7: warning: narrowing conversion from 'global_size_t' (aka 'long long') to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ // IGNORED: Warning is disabled with IgnoreConversionFromTypes=global_size_t.
+}
+
+void narrowing_size_type() {
+ int i;
+ vector::nested_size_type j;
+ i = j;
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:7: warning: narrowing conversion from 'vector::nested_size_type' (aka 'long long') to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ // IGNORED: Warning is disabled with IgnoreConversionFromTypes=nested_size_type.
+}
+
+void narrowing_size_method() {
+ vector v;
+ int i, j;
+ i = v.size();
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:7: warning: narrowing conversion from 'global_size_t' (aka 'long long') to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ // IGNORED: Warning is disabled with IgnoreConversionFromTypes=global_size_t.
+
+ i = j + v.size();
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:7: warning: narrowing conversion from 'long long' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ // IGNORED: Warning is disabled with IgnoreConversionFromTypes=global_size_t.
+}
+
+void narrowing_size_method_binary_expr() {
+ int i;
+ int j;
+ vector v;
+ i = j + v.size();
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:7: warning: narrowing conversion from 'long long' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ // IGNORED: Warning is disabled with IgnoreConversionFromTypes=global_size_t.
+}
+
+void narrowing_size_method_binary_op() {
+ int i, j;
+ vector v;
+ i += v.size();
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:8: warning: narrowing conversion from 'global_size_t' (aka 'long long') to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ // IGNORED: Warning is disabled with IgnoreConversionFromTypes=global_size_t.
+
+ i += j + v.size();
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:8: warning: narrowing conversion from 'long long' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ // IGNORED: Warning is disabled with IgnoreConversionFromTypes=global_size_t.
+}
+
+void most_narrowing_is_not_ok() {
+ int i;
+ long long j;
+ i = j;
+ // CHECK-MESSAGES-DEFAULT: :[[@LINE-1]]:7: warning: narrowing conversion from 'long long' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+ // CHECK-MESSAGES-IGNORED: :[[@LINE-2]]:7: warning: narrowing conversion from 'long long' to signed type 'int' is implementation-defined [cppcoreguidelines-narrowing-conversions]
+}