1//===--- NarrowingConversionsCheck.cpp - clang-tidy------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "NarrowingConversionsCheck.h"
10#include "../utils/OptionsUtils.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/AST/Expr.h"
13#include "clang/AST/Type.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
15#include "clang/ASTMatchers/ASTMatchers.h"
16#include "llvm/ADT/APSInt.h"
17#include "llvm/ADT/STLExtras.h"
18#include "llvm/ADT/SmallString.h"
19#include "llvm/ADT/SmallVector.h"
20
21#include <cstdint>
22
23using namespace clang::ast_matchers;
24
25namespace clang::tidy::cppcoreguidelines {
26
27namespace {
28
29AST_MATCHER_P(QualType, hasAnyType, std::vector<StringRef>, Names) {
30 if (Names.empty())
31 return false;
32
33 std::string Name = Node.getLocalUnqualifiedType().getAsString();
34 return llvm::any_of(Range: Names, P: [&Name](StringRef Ref) { return Ref == Name; });
35}
36
37AST_MATCHER(FieldDecl, hasIntBitwidth) {
38 assert(Node.isBitField());
39 const ASTContext &Ctx = Node.getASTContext();
40 unsigned IntBitWidth = Ctx.getIntWidth(T: Ctx.IntTy);
41 unsigned CurrentBitWidth = Node.getBitWidthValue(Ctx);
42 return IntBitWidth == CurrentBitWidth;
43}
44
45} // namespace
46
47NarrowingConversionsCheck::NarrowingConversionsCheck(StringRef Name,
48 ClangTidyContext *Context)
49 : ClangTidyCheck(Name, Context),
50 WarnOnIntegerNarrowingConversion(
51 Options.get(LocalName: "WarnOnIntegerNarrowingConversion", Default: true)),
52 WarnOnIntegerToFloatingPointNarrowingConversion(
53 Options.get(LocalName: "WarnOnIntegerToFloatingPointNarrowingConversion", Default: true)),
54 WarnOnFloatingPointNarrowingConversion(
55 Options.get(LocalName: "WarnOnFloatingPointNarrowingConversion", Default: true)),
56 WarnWithinTemplateInstantiation(
57 Options.get(LocalName: "WarnWithinTemplateInstantiation", Default: false)),
58 WarnOnEquivalentBitWidth(Options.get(LocalName: "WarnOnEquivalentBitWidth", Default: true)),
59 IgnoreConversionFromTypes(Options.get(LocalName: "IgnoreConversionFromTypes", Default: "")),
60 PedanticMode(Options.get(LocalName: "PedanticMode", Default: false)) {}
61
62void NarrowingConversionsCheck::storeOptions(
63 ClangTidyOptions::OptionMap &Opts) {
64 Options.store(Options&: Opts, LocalName: "WarnOnIntegerNarrowingConversion",
65 Value: WarnOnIntegerNarrowingConversion);
66 Options.store(Options&: Opts, LocalName: "WarnOnIntegerToFloatingPointNarrowingConversion",
67 Value: WarnOnIntegerToFloatingPointNarrowingConversion);
68 Options.store(Options&: Opts, LocalName: "WarnOnFloatingPointNarrowingConversion",
69 Value: WarnOnFloatingPointNarrowingConversion);
70 Options.store(Options&: Opts, LocalName: "WarnWithinTemplateInstantiation",
71 Value: WarnWithinTemplateInstantiation);
72 Options.store(Options&: Opts, LocalName: "WarnOnEquivalentBitWidth", Value: WarnOnEquivalentBitWidth);
73 Options.store(Options&: Opts, LocalName: "IgnoreConversionFromTypes", Value: IgnoreConversionFromTypes);
74 Options.store(Options&: Opts, LocalName: "PedanticMode", Value: PedanticMode);
75}
76
77void NarrowingConversionsCheck::registerMatchers(MatchFinder *Finder) {
78 // ceil() and floor() are guaranteed to return integers, even though the type
79 // is not integral.
80 const auto IsCeilFloorCallExpr = expr(callExpr(callee(InnerMatcher: functionDecl(
81 hasAnyName("::ceil", "::std::ceil", "::floor", "::std::floor")))));
82
83 std::vector<StringRef> IgnoreConversionFromTypesVec =
84 utils::options::parseStringList(Option: IgnoreConversionFromTypes);
85
86 // We may want to exclude other types from the checks, such as `size_type`
87 // and `difference_type`. These are often used to count elements, represented
88 // in 64 bits and assigned to `int`. Rarely are people counting >2B elements.
89 const auto IsConversionFromIgnoredType =
90 anyOf(hasType(InnerMatcher: namedDecl(hasAnyName(IgnoreConversionFromTypesVec))),
91 allOf(unless(hasType(InnerMatcher: namedDecl())),
92 hasType(InnerMatcher: qualType(hasAnyType(Names: IgnoreConversionFromTypesVec)))));
93
94 // `IsConversionFromIgnoredType` will ignore narrowing calls from those types,
95 // but not expressions that are promoted to an ignored type as a result of a
96 // binary expression with one of those types.
97 // For example, it will continue to reject:
98 // `int narrowed = int_value + container.size()`.
99 // We attempt to address common incidents of compound expressions with
100 // `IsIgnoredTypeTwoLevelsDeep`, allowing binary expressions that have one
101 // operand of the ignored types and the other operand of another integer type.
102 const auto IsIgnoredTypeTwoLevelsDeep =
103 anyOf(IsConversionFromIgnoredType,
104 binaryOperator(hasOperands(Matcher1: IsConversionFromIgnoredType,
105 Matcher2: hasType(InnerMatcher: isInteger()))));
106
107 // Bitfields are special. Due to integral promotion [conv.prom/5] bitfield
108 // member access expressions are frequently wrapped by an implicit cast to
109 // `int` if that type can represent all the values of the bitfield.
110 //
111 // Consider these examples:
112 // struct SmallBitfield { unsigned int id : 4; };
113 // x.id & 1; (case-1)
114 // x.id & 1u; (case-2)
115 // x.id << 1u; (case-3)
116 // (unsigned)x.id << 1; (case-4)
117 //
118 // Due to the promotion rules, we would get a warning for case-1. It's
119 // debatable how useful this is, but the user at least has a convenient way of
120 // //fixing// it by adding the `u` unsigned-suffix to the literal as
121 // demonstrated by case-2. However, this won't work for shift operators like
122 // the one in case-3. In case of a normal binary operator, both operands
123 // contribute to the result type. However, the type of the shift expression is
124 // the promoted type of the left operand. One could still suppress this
125 // superfluous warning by explicitly casting the bitfield member access as
126 // case-4 demonstrates, but why? The compiler already knew that the value from
127 // the member access should safely fit into an `int`, why do we have this
128 // warning in the first place? So, hereby we suppress this specific scenario.
129 //
130 // Note that the bitshift operation might invoke unspecified/undefined
131 // behavior, but that's another topic, this checker is about detecting
132 // conversion-related defects.
133 //
134 // Example AST for `x.id << 1`:
135 // BinaryOperator 'int' '<<'
136 // |-ImplicitCastExpr 'int' <IntegralCast>
137 // | `-ImplicitCastExpr 'unsigned int' <LValueToRValue>
138 // | `-MemberExpr 'unsigned int' lvalue bitfield .id
139 // | `-DeclRefExpr 'SmallBitfield' lvalue ParmVar 'x' 'SmallBitfield'
140 // `-IntegerLiteral 'int' 1
141 const auto ImplicitIntWidenedBitfieldValue = implicitCastExpr(
142 hasCastKind(Kind: CK_IntegralCast), hasType(InnerMatcher: asString(Name: "int")),
143 has(castExpr(hasCastKind(Kind: CK_LValueToRValue),
144 has(ignoringParens(InnerMatcher: memberExpr(hasDeclaration(
145 InnerMatcher: fieldDecl(isBitField(), unless(hasIntBitwidth())))))))));
146
147 // Casts:
148 // i = 0.5;
149 // void f(int); f(0.5);
150 Finder->addMatcher(
151 NodeMatch: traverse(TK: TK_AsIs, InnerMatcher: implicitCastExpr(
152 hasImplicitDestinationType(
153 InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: builtinType())),
154 hasSourceExpression(InnerMatcher: hasType(
155 InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: builtinType()))),
156 unless(hasSourceExpression(InnerMatcher: IsCeilFloorCallExpr)),
157 unless(hasParent(castExpr())),
158 WarnWithinTemplateInstantiation
159 ? stmt()
160 : stmt(unless(isInTemplateInstantiation())),
161 IgnoreConversionFromTypes.empty()
162 ? castExpr()
163 : castExpr(unless(hasSourceExpression(
164 InnerMatcher: IsIgnoredTypeTwoLevelsDeep))),
165 unless(ImplicitIntWidenedBitfieldValue))
166 .bind(ID: "cast")),
167 Action: this);
168
169 // Binary operators:
170 // i += 0.5;
171 Finder->addMatcher(
172 NodeMatch: binaryOperator(
173 isAssignmentOperator(),
174 hasLHS(InnerMatcher: expr(hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: builtinType())))),
175 hasRHS(InnerMatcher: expr(hasType(InnerMatcher: hasUnqualifiedDesugaredType(InnerMatcher: builtinType())))),
176 unless(hasRHS(InnerMatcher: IsCeilFloorCallExpr)),
177 WarnWithinTemplateInstantiation
178 ? binaryOperator()
179 : binaryOperator(unless(isInTemplateInstantiation())),
180 IgnoreConversionFromTypes.empty()
181 ? binaryOperator()
182 : binaryOperator(unless(hasRHS(InnerMatcher: IsIgnoredTypeTwoLevelsDeep))),
183 // The `=` case generates an implicit cast
184 // which is covered by the previous matcher.
185 unless(hasOperatorName(Name: "=")))
186 .bind(ID: "binary_op"),
187 Action: this);
188}
189
190static const BuiltinType *getBuiltinType(const Expr &E) {
191 return E.getType().getCanonicalType().getTypePtr()->getAs<BuiltinType>();
192}
193
194static QualType getUnqualifiedType(const Expr &E) {
195 return E.getType().getUnqualifiedType();
196}
197
198static APValue getConstantExprValue(const ASTContext &Ctx, const Expr &E) {
199 if (auto IntegerConstant = E.getIntegerConstantExpr(Ctx))
200 return APValue(*IntegerConstant);
201 APValue Constant;
202 if (Ctx.getLangOpts().CPlusPlus && E.isCXX11ConstantExpr(Ctx, Result: &Constant))
203 return Constant;
204 return {};
205}
206
207static bool getIntegerConstantExprValue(const ASTContext &Context,
208 const Expr &E, llvm::APSInt &Value) {
209 APValue Constant = getConstantExprValue(Ctx: Context, E);
210 if (!Constant.isInt())
211 return false;
212 Value = Constant.getInt();
213 return true;
214}
215
216static bool getFloatingConstantExprValue(const ASTContext &Context,
217 const Expr &E, llvm::APFloat &Value) {
218 APValue Constant = getConstantExprValue(Ctx: Context, E);
219 if (!Constant.isFloat())
220 return false;
221 Value = Constant.getFloat();
222 return true;
223}
224
225namespace {
226
227struct IntegerRange {
228 bool contains(const IntegerRange &From) const {
229 return llvm::APSInt::compareValues(I1: Lower, I2: From.Lower) <= 0 &&
230 llvm::APSInt::compareValues(I1: Upper, I2: From.Upper) >= 0;
231 }
232
233 bool contains(const llvm::APSInt &Value) const {
234 return llvm::APSInt::compareValues(I1: Lower, I2: Value) <= 0 &&
235 llvm::APSInt::compareValues(I1: Upper, I2: Value) >= 0;
236 }
237
238 llvm::APSInt Lower;
239 llvm::APSInt Upper;
240};
241
242} // namespace
243
244static IntegerRange createFromType(const ASTContext &Context,
245 const BuiltinType &T) {
246 if (T.isFloatingPoint()) {
247 unsigned PrecisionBits = llvm::APFloatBase::semanticsPrecision(
248 Context.getFloatTypeSemantics(T: T.desugar()));
249 // Contrary to two's complement integer, floating point values are
250 // symmetric and have the same number of positive and negative values.
251 // The range of valid integers for a floating point value is:
252 // [-2^PrecisionBits, 2^PrecisionBits]
253
254 // Values are created with PrecisionBits plus two bits:
255 // - One to express the missing negative value of 2's complement
256 // representation.
257 // - One for the sign.
258 llvm::APSInt UpperValue(PrecisionBits + 2, /*isUnsigned*/ false);
259 UpperValue.setBit(PrecisionBits);
260 llvm::APSInt LowerValue(PrecisionBits + 2, /*isUnsigned*/ false);
261 LowerValue.setBit(PrecisionBits);
262 LowerValue.setSignBit();
263 return {.Lower: LowerValue, .Upper: UpperValue};
264 }
265 assert(T.isInteger() && "Unexpected builtin type");
266 uint64_t TypeSize = Context.getTypeSize(&T);
267 bool IsUnsignedInteger = T.isUnsignedInteger();
268 return {.Lower: llvm::APSInt::getMinValue(numBits: TypeSize, Unsigned: IsUnsignedInteger),
269 .Upper: llvm::APSInt::getMaxValue(numBits: TypeSize, Unsigned: IsUnsignedInteger)};
270}
271
272static bool isWideEnoughToHold(const ASTContext &Context,
273 const BuiltinType &FromType,
274 const BuiltinType &ToType) {
275 IntegerRange FromIntegerRange = createFromType(Context, T: FromType);
276 IntegerRange ToIntegerRange = createFromType(Context, T: ToType);
277 return ToIntegerRange.contains(From: FromIntegerRange);
278}
279
280static bool isWideEnoughToHold(const ASTContext &Context,
281 const llvm::APSInt &IntegerConstant,
282 const BuiltinType &ToType) {
283 IntegerRange ToIntegerRange = createFromType(Context, T: ToType);
284 return ToIntegerRange.contains(Value: IntegerConstant);
285}
286
287// Returns true iff the floating point constant can be losslessly represented
288// by an integer in the given destination type. eg. 2.0 can be accurately
289// represented by an int32_t, but neither 2^33 nor 2.001 can.
290static bool isFloatExactlyRepresentable(const ASTContext &Context,
291 const llvm::APFloat &FloatConstant,
292 const QualType &DestType) {
293 unsigned DestWidth = Context.getIntWidth(T: DestType);
294 bool DestSigned = DestType->isSignedIntegerOrEnumerationType();
295 llvm::APSInt Result = llvm::APSInt(DestWidth, !DestSigned);
296 bool IsExact = false;
297 bool Overflows = FloatConstant.convertToInteger(
298 Result, RM: llvm::APFloat::rmTowardZero, IsExact: &IsExact) &
299 llvm::APFloat::opInvalidOp;
300 return !Overflows && IsExact;
301}
302
303static llvm::SmallString<64> getValueAsString(const llvm::APSInt &Value,
304 uint64_t HexBits) {
305 llvm::SmallString<64> Str;
306 Value.toString(Str, Radix: 10);
307 if (HexBits > 0) {
308 Str.append(RHS: " (0x");
309 llvm::SmallString<32> HexValue;
310 Value.toStringUnsigned(Str&: HexValue, Radix: 16);
311 for (size_t I = HexValue.size(); I < (HexBits / 4); ++I)
312 Str.append(RHS: "0");
313 Str.append(RHS: HexValue);
314 Str.append(RHS: ")");
315 }
316 return Str;
317}
318
319bool NarrowingConversionsCheck::isWarningInhibitedByEquivalentSize(
320 const ASTContext &Context, const BuiltinType &FromType,
321 const BuiltinType &ToType) const {
322 // With this option, we don't warn on conversions that have equivalent width
323 // in bits. eg. uint32 <-> int32.
324 if (!WarnOnEquivalentBitWidth) {
325 uint64_t FromTypeSize = Context.getTypeSize(&FromType);
326 uint64_t ToTypeSize = Context.getTypeSize(&ToType);
327 if (FromTypeSize == ToTypeSize) {
328 return true;
329 }
330 }
331 return false;
332}
333
334void NarrowingConversionsCheck::diagNarrowType(SourceLocation SourceLoc,
335 const Expr &Lhs,
336 const Expr &Rhs) {
337 diag(Loc: SourceLoc, Description: "narrowing conversion from %0 to %1")
338 << getUnqualifiedType(E: Rhs) << getUnqualifiedType(E: Lhs);
339}
340
341void NarrowingConversionsCheck::diagNarrowTypeToSignedInt(
342 SourceLocation SourceLoc, const Expr &Lhs, const Expr &Rhs) {
343 diag(Loc: SourceLoc, Description: "narrowing conversion from %0 to signed type %1 is "
344 "implementation-defined")
345 << getUnqualifiedType(E: Rhs) << getUnqualifiedType(E: Lhs);
346}
347
348void NarrowingConversionsCheck::diagNarrowIntegerConstant(
349 SourceLocation SourceLoc, const Expr &Lhs, const Expr &Rhs,
350 const llvm::APSInt &Value) {
351 diag(Loc: SourceLoc,
352 Description: "narrowing conversion from constant value %0 of type %1 to %2")
353 << getValueAsString(Value, /*NoHex*/ HexBits: 0) << getUnqualifiedType(E: Rhs)
354 << getUnqualifiedType(E: Lhs);
355}
356
357void NarrowingConversionsCheck::diagNarrowIntegerConstantToSignedInt(
358 SourceLocation SourceLoc, const Expr &Lhs, const Expr &Rhs,
359 const llvm::APSInt &Value, const uint64_t HexBits) {
360 diag(Loc: SourceLoc, Description: "narrowing conversion from constant value %0 of type %1 "
361 "to signed type %2 is implementation-defined")
362 << getValueAsString(Value, HexBits) << getUnqualifiedType(E: Rhs)
363 << getUnqualifiedType(E: Lhs);
364}
365
366void NarrowingConversionsCheck::diagNarrowConstant(SourceLocation SourceLoc,
367 const Expr &Lhs,
368 const Expr &Rhs) {
369 diag(Loc: SourceLoc, Description: "narrowing conversion from constant %0 to %1")
370 << getUnqualifiedType(E: Rhs) << getUnqualifiedType(E: Lhs);
371}
372
373void NarrowingConversionsCheck::diagConstantCast(SourceLocation SourceLoc,
374 const Expr &Lhs,
375 const Expr &Rhs) {
376 diag(Loc: SourceLoc, Description: "constant value should be of type of type %0 instead of %1")
377 << getUnqualifiedType(E: Lhs) << getUnqualifiedType(E: Rhs);
378}
379
380void NarrowingConversionsCheck::diagNarrowTypeOrConstant(
381 const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
382 const Expr &Rhs) {
383 APValue Constant = getConstantExprValue(Ctx: Context, E: Rhs);
384 if (Constant.isInt())
385 return diagNarrowIntegerConstant(SourceLoc, Lhs, Rhs, Value: Constant.getInt());
386 if (Constant.isFloat())
387 return diagNarrowConstant(SourceLoc, Lhs, Rhs);
388 return diagNarrowType(SourceLoc, Lhs, Rhs);
389}
390
391void NarrowingConversionsCheck::handleIntegralCast(const ASTContext &Context,
392 SourceLocation SourceLoc,
393 const Expr &Lhs,
394 const Expr &Rhs) {
395 if (WarnOnIntegerNarrowingConversion) {
396 const BuiltinType *ToType = getBuiltinType(E: Lhs);
397 // From [conv.integral]p7.3.8:
398 // Conversions to unsigned integer is well defined so no warning is issued.
399 // "The resulting value is the smallest unsigned value equal to the source
400 // value modulo 2^n where n is the number of bits used to represent the
401 // destination type."
402 if (ToType->isUnsignedInteger())
403 return;
404 const BuiltinType *FromType = getBuiltinType(E: Rhs);
405
406 // With this option, we don't warn on conversions that have equivalent width
407 // in bits. eg. uint32 <-> int32.
408 if (!WarnOnEquivalentBitWidth) {
409 uint64_t FromTypeSize = Context.getTypeSize(FromType);
410 uint64_t ToTypeSize = Context.getTypeSize(ToType);
411 if (FromTypeSize == ToTypeSize)
412 return;
413 }
414
415 llvm::APSInt IntegerConstant;
416 if (getIntegerConstantExprValue(Context, E: Rhs, Value&: IntegerConstant)) {
417 if (!isWideEnoughToHold(Context, IntegerConstant, ToType: *ToType))
418 diagNarrowIntegerConstantToSignedInt(SourceLoc, Lhs, Rhs,
419 Value: IntegerConstant,
420 HexBits: Context.getTypeSize(FromType));
421 return;
422 }
423 if (!isWideEnoughToHold(Context, FromType: *FromType, ToType: *ToType))
424 diagNarrowTypeToSignedInt(SourceLoc, Lhs, Rhs);
425 }
426}
427
428void NarrowingConversionsCheck::handleIntegralToBoolean(
429 const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
430 const Expr &Rhs) {
431 // Conversion from Integral to Bool value is well defined.
432
433 // We keep this function (even if it is empty) to make sure that
434 // handleImplicitCast and handleBinaryOperator are symmetric in their behavior
435 // and handle the same cases.
436}
437
438void NarrowingConversionsCheck::handleIntegralToFloating(
439 const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
440 const Expr &Rhs) {
441 if (WarnOnIntegerToFloatingPointNarrowingConversion) {
442 const BuiltinType *ToType = getBuiltinType(E: Lhs);
443 llvm::APSInt IntegerConstant;
444 if (getIntegerConstantExprValue(Context, E: Rhs, Value&: IntegerConstant)) {
445 if (!isWideEnoughToHold(Context, IntegerConstant, ToType: *ToType))
446 diagNarrowIntegerConstant(SourceLoc, Lhs, Rhs, Value: IntegerConstant);
447 return;
448 }
449
450 const BuiltinType *FromType = getBuiltinType(E: Rhs);
451 if (isWarningInhibitedByEquivalentSize(Context, FromType: *FromType, ToType: *ToType))
452 return;
453 if (!isWideEnoughToHold(Context, FromType: *FromType, ToType: *ToType))
454 diagNarrowType(SourceLoc, Lhs, Rhs);
455 }
456}
457
458void NarrowingConversionsCheck::handleFloatingToIntegral(
459 const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
460 const Expr &Rhs) {
461 llvm::APFloat FloatConstant(0.0);
462 if (getFloatingConstantExprValue(Context, E: Rhs, Value&: FloatConstant)) {
463 if (!isFloatExactlyRepresentable(Context, FloatConstant, DestType: Lhs.getType()))
464 return diagNarrowConstant(SourceLoc, Lhs, Rhs);
465
466 if (PedanticMode)
467 return diagConstantCast(SourceLoc, Lhs, Rhs);
468
469 return;
470 }
471
472 const BuiltinType *FromType = getBuiltinType(E: Rhs);
473 const BuiltinType *ToType = getBuiltinType(E: Lhs);
474 if (isWarningInhibitedByEquivalentSize(Context, FromType: *FromType, ToType: *ToType))
475 return;
476 diagNarrowType(SourceLoc, Lhs, Rhs); // Assumed always lossy.
477}
478
479void NarrowingConversionsCheck::handleFloatingToBoolean(
480 const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
481 const Expr &Rhs) {
482 return diagNarrowTypeOrConstant(Context, SourceLoc, Lhs, Rhs);
483}
484
485void NarrowingConversionsCheck::handleBooleanToSignedIntegral(
486 const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
487 const Expr &Rhs) {
488 // Conversion from Bool to SignedIntegral value is well defined.
489
490 // We keep this function (even if it is empty) to make sure that
491 // handleImplicitCast and handleBinaryOperator are symmetric in their behavior
492 // and handle the same cases.
493}
494
495void NarrowingConversionsCheck::handleFloatingCast(const ASTContext &Context,
496 SourceLocation SourceLoc,
497 const Expr &Lhs,
498 const Expr &Rhs) {
499 if (WarnOnFloatingPointNarrowingConversion) {
500 const BuiltinType *ToType = getBuiltinType(E: Lhs);
501 APValue Constant = getConstantExprValue(Ctx: Context, E: Rhs);
502 if (Constant.isFloat()) {
503 // From [dcl.init.list]p7.2:
504 // Floating point constant narrowing only takes place when the value is
505 // not within destination range. We convert the value to the destination
506 // type and check if the resulting value is infinity.
507 llvm::APFloat Tmp = Constant.getFloat();
508 bool UnusedLosesInfo = false;
509 Tmp.convert(ToSemantics: Context.getFloatTypeSemantics(T: ToType->desugar()),
510 RM: llvm::APFloatBase::rmNearestTiesToEven, losesInfo: &UnusedLosesInfo);
511 if (Tmp.isInfinity())
512 diagNarrowConstant(SourceLoc, Lhs, Rhs);
513 return;
514 }
515 const BuiltinType *FromType = getBuiltinType(E: Rhs);
516 if (ToType->getKind() < FromType->getKind())
517 diagNarrowType(SourceLoc, Lhs, Rhs);
518 }
519}
520
521void NarrowingConversionsCheck::handleBinaryOperator(const ASTContext &Context,
522 SourceLocation SourceLoc,
523 const Expr &Lhs,
524 const Expr &Rhs) {
525 assert(!Lhs.isInstantiationDependent() && !Rhs.isInstantiationDependent() &&
526 "Dependent types must be check before calling this function");
527 const BuiltinType *LhsType = getBuiltinType(E: Lhs);
528 const BuiltinType *RhsType = getBuiltinType(E: Rhs);
529 if (RhsType == nullptr || LhsType == nullptr)
530 return;
531 if (LhsType == RhsType)
532 return;
533 if (RhsType->getKind() == BuiltinType::Bool && LhsType->isSignedInteger())
534 return handleBooleanToSignedIntegral(Context, SourceLoc, Lhs, Rhs);
535 if (RhsType->isInteger() && LhsType->getKind() == BuiltinType::Bool)
536 return handleIntegralToBoolean(Context, SourceLoc, Lhs, Rhs);
537 if (RhsType->isInteger() && LhsType->isFloatingPoint())
538 return handleIntegralToFloating(Context, SourceLoc, Lhs, Rhs);
539 if (RhsType->isInteger() && LhsType->isInteger())
540 return handleIntegralCast(Context, SourceLoc, Lhs, Rhs);
541 if (RhsType->isFloatingPoint() && LhsType->getKind() == BuiltinType::Bool)
542 return handleFloatingToBoolean(Context, SourceLoc, Lhs, Rhs);
543 if (RhsType->isFloatingPoint() && LhsType->isInteger())
544 return handleFloatingToIntegral(Context, SourceLoc, Lhs, Rhs);
545 if (RhsType->isFloatingPoint() && LhsType->isFloatingPoint())
546 return handleFloatingCast(Context, SourceLoc, Lhs, Rhs);
547}
548
549bool NarrowingConversionsCheck::handleConditionalOperator(
550 const ASTContext &Context, const Expr &Lhs, const Expr &Rhs) {
551 if (const auto *CO = llvm::dyn_cast<ConditionalOperator>(Val: &Rhs)) {
552 // We have an expression like so: `output = cond ? lhs : rhs`
553 // From the point of view of narrowing conversion we treat it as two
554 // expressions `output = lhs` and `output = rhs`.
555 handleBinaryOperator(Context, SourceLoc: CO->getLHS()->getExprLoc(), Lhs,
556 Rhs: *CO->getLHS());
557 handleBinaryOperator(Context, SourceLoc: CO->getRHS()->getExprLoc(), Lhs,
558 Rhs: *CO->getRHS());
559 return true;
560 }
561 return false;
562}
563
564void NarrowingConversionsCheck::handleImplicitCast(
565 const ASTContext &Context, const ImplicitCastExpr &Cast) {
566 if (Cast.getExprLoc().isMacroID())
567 return;
568 const Expr &Lhs = Cast;
569 const Expr &Rhs = *Cast.getSubExpr();
570 if (Lhs.isInstantiationDependent() || Rhs.isInstantiationDependent())
571 return;
572 if (getBuiltinType(E: Lhs) == getBuiltinType(E: Rhs))
573 return;
574 if (handleConditionalOperator(Context, Lhs, Rhs))
575 return;
576 SourceLocation SourceLoc = Lhs.getExprLoc();
577 switch (Cast.getCastKind()) {
578 case CK_BooleanToSignedIntegral:
579 return handleBooleanToSignedIntegral(Context, SourceLoc, Lhs, Rhs);
580 case CK_IntegralToBoolean:
581 return handleIntegralToBoolean(Context, SourceLoc, Lhs, Rhs);
582 case CK_IntegralToFloating:
583 return handleIntegralToFloating(Context, SourceLoc, Lhs, Rhs);
584 case CK_IntegralCast:
585 return handleIntegralCast(Context, SourceLoc, Lhs, Rhs);
586 case CK_FloatingToBoolean:
587 return handleFloatingToBoolean(Context, SourceLoc, Lhs, Rhs);
588 case CK_FloatingToIntegral:
589 return handleFloatingToIntegral(Context, SourceLoc, Lhs, Rhs);
590 case CK_FloatingCast:
591 return handleFloatingCast(Context, SourceLoc, Lhs, Rhs);
592 default:
593 break;
594 }
595}
596
597void NarrowingConversionsCheck::handleBinaryOperator(const ASTContext &Context,
598 const BinaryOperator &Op) {
599 if (Op.getBeginLoc().isMacroID())
600 return;
601 const Expr &Lhs = *Op.getLHS();
602 const Expr &Rhs = *Op.getRHS();
603 if (Lhs.isInstantiationDependent() || Rhs.isInstantiationDependent())
604 return;
605 if (handleConditionalOperator(Context, Lhs, Rhs))
606 return;
607 handleBinaryOperator(Context, Rhs.getBeginLoc(), Lhs, Rhs);
608}
609
610void NarrowingConversionsCheck::check(const MatchFinder::MatchResult &Result) {
611 if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>(ID: "binary_op"))
612 return handleBinaryOperator(Context: *Result.Context, Op: *Op);
613 if (const auto *Cast = Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "cast"))
614 return handleImplicitCast(Context: *Result.Context, Cast: *Cast);
615 llvm_unreachable("must be binary operator or cast expression");
616}
617} // namespace clang::tidy::cppcoreguidelines
618

source code of clang-tools-extra/clang-tidy/cppcoreguidelines/NarrowingConversionsCheck.cpp