Skip to content

[clang-tidy] bugprone-implicit-widening ignores unsigned consts #101073

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,15 @@ void ImplicitWideningOfMultiplicationResultCheck::handleImplicitCastExpr(

// Is the expression a compile-time constexpr that we know can fit in the
// source type?
if (IgnoreConstantIntExpr && ETy->isIntegerType() &&
!ETy->isUnsignedIntegerType()) {
if (const auto ConstExprResult = E->getIntegerConstantExpr(*Context)) {
const auto TypeSize = Context->getTypeSize(ETy);
if (IgnoreConstantIntExpr && ETy->isIntegerType()) {
if (const std::optional<llvm::APSInt> ConstExprResult =
E->getIntegerConstantExpr(*Context, nullptr, true)) {
const uint64_t TypeSize = Context->getTypeSize(ETy);
const bool Unsigned = ETy->isUnsignedIntegerType();

llvm::APSInt WidenedResult = ConstExprResult->extOrTrunc(TypeSize);
if (WidenedResult <= llvm::APSInt::getMaxValue(TypeSize, false) &&
WidenedResult >= llvm::APSInt::getMinValue(TypeSize, false))
if (WidenedResult <= llvm::APSInt::getMaxValue(TypeSize, Unsigned) &&
WidenedResult >= llvm::APSInt::getMinValue(TypeSize, Unsigned))
return;
}
}
Expand Down
5 changes: 5 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,11 @@ Changes in existing checks
<clang-tidy/checks/bugprone/forwarding-reference-overload>` check by fixing
a crash when determining if an ``enable_if[_t]`` was found.

- Improved :doc:`bugprone-implicit-widening-of-multiplication-result
<clang-tidy/checks/bugprone/implicit-widening-of-multiplication-result>` check
by allowing the `IgnoreConstantIntExpr` option to apply to both signed and
unsigned integer types.

- Improved :doc:`bugprone-sizeof-expression
<clang-tidy/checks/bugprone/sizeof-expression>` check to find suspicious
usages of ``sizeof()``, ``alignof()``, and ``offsetof()`` when adding or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,19 @@ Options
.. option:: UseCXXStaticCastsInCppSources

When suggesting fix-its for C++ code, should C++-style ``static_cast<>()``'s
be suggested, or C-style casts. Defaults to ``true``.
be suggested, or C-style casts. Defaults to `true`.

.. option:: UseCXXHeadersInCppSources

When suggesting to include the appropriate header in C++ code,
should ``<cstddef>`` header be suggested, or ``<stddef.h>``.
Defaults to ``true``.
Defaults to `true`.

.. option:: IgnoreConstantIntExpr

If the multiplication operands are compile-time constants (like literals or
are ``constexpr``) and fit within the source expression type, do not emit a
diagnostic or suggested fix. Only considers expressions where the source
expression is a signed integer type. Defaults to ``false``.
diagnostic or suggested fix. Defaults to `false`.

Examples:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ long t5() {
return 1 * min_int;
}

long t6() {
const unsigned int a = 4294967295u; // unsigned int max
return a * 1u;
}

unsigned long n0() {
const int a = 1073741824; // 1/2 of int32_t max
const int b = 3;
Expand All @@ -50,7 +55,11 @@ unsigned long n0() {
// CHECK-MESSAGES: :[[@LINE-4]]:10: note: perform multiplication in a wider type
}

double n1() {
const long a = 100000000;
return a * 400;
long n1() {
const unsigned int a = 4294967295u; // unsigned int max
return a * 3u;
// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: performing an implicit widening conversion to type 'long' of a multiplication performed in type 'unsigned int'
// CHECK-MESSAGES: :[[@LINE-2]]:10: note: make conversion explicit to silence this warning
// CHECK-MESSAGES: static_cast<long>( )
// CHECK-MESSAGES: :[[@LINE-4]]:10: note: perform multiplication in a wider type
}
11 changes: 8 additions & 3 deletions clang/include/clang/AST/Expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -551,11 +551,15 @@ class Expr : public ValueStmt {
/// and fill in Loc (if specified) with the location of the invalid
/// expression.
///
/// If CheckUnsignedOverflow is true, the i-c-e is of an unsigned type, and
/// the i-c-e result cannot fit into the expression type without overflowing,
/// std::nullopt is returned.
///
/// Note: This does not perform the implicit conversions required by C++11
/// [expr.const]p5.
std::optional<llvm::APSInt>
getIntegerConstantExpr(const ASTContext &Ctx,
SourceLocation *Loc = nullptr) const;
getIntegerConstantExpr(const ASTContext &Ctx, SourceLocation *Loc = nullptr,
bool CheckUnsignedOverflow = false) const;
bool isIntegerConstantExpr(const ASTContext &Ctx,
SourceLocation *Loc = nullptr) const;

Expand All @@ -569,7 +573,8 @@ class Expr : public ValueStmt {
/// Note: This does not perform the implicit conversions required by C++11
/// [expr.const]p5.
bool isCXX11ConstantExpr(const ASTContext &Ctx, APValue *Result = nullptr,
SourceLocation *Loc = nullptr) const;
SourceLocation *Loc = nullptr,
bool CheckUnsignedOverflow = false) const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that unsigned overflow is well defined and allowed in constexpr, having an argument to disallow it seems a little off. From what I can tell the implementation here can just hard code the value internally to false and the public API for this function remains unchanged

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that unsigned overflow is well defined and allowed in constexpr, having an argument to disallow it seems a little off.

I agree from the compiler's perspective, but it seems in-line with the purpose of a bugprone clang tidy check: it's a hint that something may warrant investigation to make sure it is intentional.

From what I can tell the implementation here can just hard code the value internally to false and the public API for this function remains unchanged

I'm not seeing how, since this function sets up the EvalInfo that communications the option to the lower-level CheckedIntArithmetic function. It's not just checking if the expression is constant, it's also calculating the result, and the overflow is detected during that calculation.


/// isPotentialConstantExpr - Return true if this function's definition
/// might be usable in a constant expression in C++11, if it were marked
Expand Down
40 changes: 27 additions & 13 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -980,6 +980,12 @@ namespace {
/// is set; this is used when evaluating ICEs in C.
bool CheckingForUndefinedBehavior = false;

/// Whether we're checking for an expression that causing the unsigned
/// integer expression type to overflow and wrap-around back to 0 when
/// evaluating an ICE. If set to true, the ICE evaluation will fail as
/// though the expression could not be calculated at compile time
bool CheckingForUnsignedIntegerOverflow = false;

enum EvaluationMode {
/// Evaluate as a constant expression. Stop if we find that the expression
/// is not a constant expression.
Expand Down Expand Up @@ -2795,24 +2801,28 @@ static bool truncateBitfieldValue(EvalInfo &Info, const Expr *E,

/// Perform the given integer operation, which is known to need at most BitWidth
/// bits, and check for overflow in the original type (if that type was not an
/// unsigned type).
/// unsigned type or EvalInfo.CheckingForUnsignedIntegerOverflow is true).
template<typename Operation>
static bool CheckedIntArithmetic(EvalInfo &Info, const Expr *E,
const APSInt &LHS, const APSInt &RHS,
unsigned BitWidth, Operation Op,
APSInt &Result) {
if (LHS.isUnsigned()) {
if (LHS.isUnsigned() && !Info.CheckingForUnsignedIntegerOverflow) {
Result = Op(LHS, RHS);
return true;
}

APSInt Value(Op(LHS.extend(BitWidth), RHS.extend(BitWidth)), false);
APSInt Value(Op(LHS.extend(BitWidth), RHS.extend(BitWidth)),
LHS.isUnsigned());
Result = Value.trunc(LHS.getBitWidth());
if (Result.extend(BitWidth) != Value) {
if (Result.isUnsigned())
return false;
if (Info.checkingForUndefinedBehavior())
Info.Ctx.getDiagnostics().Report(E->getExprLoc(),
diag::warn_integer_constant_overflow)
<< toString(Result, 10, Result.isSigned(), /*formatAsCLiteral=*/false,
<< toString(Result, 10, Result.isSigned(),
/*formatAsCLiteral=*/false,
/*UpperCase=*/true, /*InsertSeparators=*/true)
<< E->getType() << E->getSourceRange();
return HandleOverflow(Info, E, Value, E->getType());
Expand Down Expand Up @@ -16980,17 +16990,16 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
}

/// Evaluate an expression as a C++11 integral constant expression.
static bool EvaluateCPlusPlus11IntegralConstantExpr(const ASTContext &Ctx,
const Expr *E,
llvm::APSInt *Value,
SourceLocation *Loc) {
static bool EvaluateCPlusPlus11IntegralConstantExpr(
const ASTContext &Ctx, const Expr *E, llvm::APSInt *Value,
SourceLocation *Loc, bool CheckUnsignedOverflow) {
if (!E->getType()->isIntegralOrUnscopedEnumerationType()) {
if (Loc) *Loc = E->getExprLoc();
return false;
}

APValue Result;
if (!E->isCXX11ConstantExpr(Ctx, &Result, Loc))
if (!E->isCXX11ConstantExpr(Ctx, &Result, Loc, CheckUnsignedOverflow))
return false;

if (!Result.isInt()) {
Expand All @@ -17010,7 +17019,8 @@ bool Expr::isIntegerConstantExpr(const ASTContext &Ctx,
ExprTimeTraceScope TimeScope(this, Ctx, "isIntegerConstantExpr");

if (Ctx.getLangOpts().CPlusPlus11)
return EvaluateCPlusPlus11IntegralConstantExpr(Ctx, this, nullptr, Loc);
return EvaluateCPlusPlus11IntegralConstantExpr(Ctx, this, nullptr, Loc,
false);

ICEDiag D = CheckICE(this, Ctx);
if (D.Kind != IK_ICE) {
Expand All @@ -17021,7 +17031,8 @@ bool Expr::isIntegerConstantExpr(const ASTContext &Ctx,
}

std::optional<llvm::APSInt>
Expr::getIntegerConstantExpr(const ASTContext &Ctx, SourceLocation *Loc) const {
Expr::getIntegerConstantExpr(const ASTContext &Ctx, SourceLocation *Loc,
bool CheckUnsignedOverflow) const {
if (isValueDependent()) {
// Expression evaluator can't succeed on a dependent expression.
return std::nullopt;
Expand All @@ -17030,7 +17041,8 @@ Expr::getIntegerConstantExpr(const ASTContext &Ctx, SourceLocation *Loc) const {
APSInt Value;

if (Ctx.getLangOpts().CPlusPlus11) {
if (EvaluateCPlusPlus11IntegralConstantExpr(Ctx, this, &Value, Loc))
if (EvaluateCPlusPlus11IntegralConstantExpr(Ctx, this, &Value, Loc,
CheckUnsignedOverflow))
return Value;
return std::nullopt;
}
Expand Down Expand Up @@ -17061,7 +17073,8 @@ bool Expr::isCXX98IntegralConstantExpr(const ASTContext &Ctx) const {
}

bool Expr::isCXX11ConstantExpr(const ASTContext &Ctx, APValue *Result,
SourceLocation *Loc) const {
SourceLocation *Loc,
bool CheckUnsignedOverflow) const {
assert(!isValueDependent() &&
"Expression evaluator can't be called on a dependent expression.");

Expand All @@ -17074,6 +17087,7 @@ bool Expr::isCXX11ConstantExpr(const ASTContext &Ctx, APValue *Result,
SmallVector<PartialDiagnosticAt, 8> Diags;
Status.Diag = &Diags;
EvalInfo Info(Ctx, Status, EvalInfo::EM_ConstantExpression);
Info.CheckingForUnsignedIntegerOverflow = CheckUnsignedOverflow;

APValue Scratch;
bool IsConstExpr =
Expand Down
38 changes: 38 additions & 0 deletions clang/unittests/AST/ASTExprTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,41 @@ TEST(ASTExpr, InitListIsConstantInitialized) {
(void)FooInit->updateInit(Ctx, 2, Ref);
EXPECT_FALSE(FooInit->isConstantInitializer(Ctx, false));
}

TEST(ASTExpr, NoUnsignedOverflowICE) {
std::unique_ptr<ASTUnit> AST = buildASTFromCode(R"cpp(
unsigned u = 1u * 8u;
)cpp");
ASTContext &Ctx = AST->getASTContext();
const VarDecl *Var = getVariableNode(AST.get(), "u");

const std::optional<llvm::APSInt> ICE =
Var->getInit()->getIntegerConstantExpr(Ctx);
EXPECT_TRUE(ICE.has_value());
EXPECT_TRUE(ICE->isUnsigned());
EXPECT_EQ(8u, *ICE);

const std::optional<llvm::APSInt> ICEOverflowCheck =
Var->getInit()->getIntegerConstantExpr(Ctx, nullptr, true);
EXPECT_TRUE(ICEOverflowCheck.has_value());
EXPECT_TRUE(ICEOverflowCheck->isUnsigned());
EXPECT_EQ(8u, *ICEOverflowCheck);
}

TEST(ASTExpr, CheckForUnsignedOverflowICE) {
std::unique_ptr<ASTUnit> AST = buildASTFromCode(R"cpp(
unsigned u = 1'000'000'000u * 8u;
)cpp");
ASTContext &Ctx = AST->getASTContext();
const VarDecl *Var = getVariableNode(AST.get(), "u");

const std::optional<llvm::APSInt> ICE =
Var->getInit()->getIntegerConstantExpr(Ctx);
EXPECT_TRUE(ICE.has_value());
EXPECT_TRUE(ICE->isUnsigned());
EXPECT_EQ(3'705'032'704u, *ICE);

const std::optional<llvm::APSInt> ICEOverflowCheck =
Var->getInit()->getIntegerConstantExpr(Ctx, nullptr, true);
EXPECT_FALSE(ICEOverflowCheck.has_value());
}
Loading