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 "clang/AST/ASTContext.h"
11 #include "clang/AST/Type.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
13 #include "llvm/ADT/APSInt.h"
14 #include "llvm/ADT/SmallString.h"
15 #include "llvm/ADT/SmallVector.h"
16 
17 #include <cstdint>
18 
19 using namespace clang::ast_matchers;
20 
21 namespace clang {
22 namespace tidy {
23 namespace cppcoreguidelines {
24 
25 NarrowingConversionsCheck::NarrowingConversionsCheck(StringRef Name,
26                                                      ClangTidyContext *Context)
27     : ClangTidyCheck(Name, Context),
28       WarnOnFloatingPointNarrowingConversion(
29           Options.get("WarnOnFloatingPointNarrowingConversion", true)),
30       PedanticMode(Options.get("PedanticMode", false)) {}
31 
32 void NarrowingConversionsCheck::storeOptions(
33     ClangTidyOptions::OptionMap &Opts) {
34   Options.store(Opts, "WarnOnFloatingPointNarrowingConversion",
35                 WarnOnFloatingPointNarrowingConversion);
36   Options.store(Opts, "PedanticMode", PedanticMode);
37 }
38 
39 void NarrowingConversionsCheck::registerMatchers(MatchFinder *Finder) {
40   // ceil() and floor() are guaranteed to return integers, even though the type
41   // is not integral.
42   const auto IsCeilFloorCallExpr = expr(callExpr(callee(functionDecl(
43       hasAnyName("::ceil", "::std::ceil", "::floor", "::std::floor")))));
44 
45   // Casts:
46   //   i = 0.5;
47   //   void f(int); f(0.5);
48   Finder->addMatcher(
49       traverse(
50           ast_type_traits::TK_AsIs,
51           implicitCastExpr(hasImplicitDestinationType(builtinType()),
52                            hasSourceExpression(hasType(builtinType())),
53                            unless(hasSourceExpression(IsCeilFloorCallExpr)),
54                            unless(hasParent(castExpr())),
55                            unless(isInTemplateInstantiation()))
56               .bind("cast")),
57       this);
58 
59   // Binary operators:
60   //   i += 0.5;
61   Finder->addMatcher(binaryOperator(isAssignmentOperator(),
62                                     hasLHS(expr(hasType(builtinType()))),
63                                     hasRHS(expr(hasType(builtinType()))),
64                                     unless(hasRHS(IsCeilFloorCallExpr)),
65                                     unless(isInTemplateInstantiation()),
66                                     // The `=` case generates an implicit cast
67                                     // which is covered by the previous matcher.
68                                     unless(hasOperatorName("=")))
69                          .bind("binary_op"),
70                      this);
71 }
72 
73 static const BuiltinType *getBuiltinType(const Expr &E) {
74   return E.getType().getCanonicalType().getTypePtr()->getAs<BuiltinType>();
75 }
76 
77 static QualType getUnqualifiedType(const Expr &E) {
78   return E.getType().getUnqualifiedType();
79 }
80 
81 static APValue getConstantExprValue(const ASTContext &Ctx, const Expr &E) {
82   if (auto IntegerConstant = E.getIntegerConstantExpr(Ctx))
83     return APValue(*IntegerConstant);
84   APValue Constant;
85   if (Ctx.getLangOpts().CPlusPlus && E.isCXX11ConstantExpr(Ctx, &Constant))
86     return Constant;
87   return {};
88 }
89 
90 static bool getIntegerConstantExprValue(const ASTContext &Context,
91                                         const Expr &E, llvm::APSInt &Value) {
92   APValue Constant = getConstantExprValue(Context, E);
93   if (!Constant.isInt())
94     return false;
95   Value = Constant.getInt();
96   return true;
97 }
98 
99 static bool getFloatingConstantExprValue(const ASTContext &Context,
100                                          const Expr &E, llvm::APFloat &Value) {
101   APValue Constant = getConstantExprValue(Context, E);
102   if (!Constant.isFloat())
103     return false;
104   Value = Constant.getFloat();
105   return true;
106 }
107 
108 namespace {
109 
110 struct IntegerRange {
111   bool Contains(const IntegerRange &From) const {
112     return llvm::APSInt::compareValues(Lower, From.Lower) <= 0 &&
113            llvm::APSInt::compareValues(Upper, From.Upper) >= 0;
114   }
115 
116   bool Contains(const llvm::APSInt &Value) const {
117     return llvm::APSInt::compareValues(Lower, Value) <= 0 &&
118            llvm::APSInt::compareValues(Upper, Value) >= 0;
119   }
120 
121   llvm::APSInt Lower;
122   llvm::APSInt Upper;
123 };
124 
125 } // namespace
126 
127 static IntegerRange createFromType(const ASTContext &Context,
128                                    const BuiltinType &T) {
129   if (T.isFloatingPoint()) {
130     unsigned PrecisionBits = llvm::APFloatBase::semanticsPrecision(
131         Context.getFloatTypeSemantics(T.desugar()));
132     // Contrary to two's complement integer, floating point values are
133     // symmetric and have the same number of positive and negative values.
134     // The range of valid integers for a floating point value is:
135     // [-2^PrecisionBits, 2^PrecisionBits]
136 
137     // Values are created with PrecisionBits plus two bits:
138     // - One to express the missing negative value of 2's complement
139     //   representation.
140     // - One for the sign.
141     llvm::APSInt UpperValue(PrecisionBits + 2, /*isUnsigned*/ false);
142     UpperValue.setBit(PrecisionBits);
143     llvm::APSInt LowerValue(PrecisionBits + 2, /*isUnsigned*/ false);
144     LowerValue.setBit(PrecisionBits);
145     LowerValue.setSignBit();
146     return {LowerValue, UpperValue};
147   }
148   assert(T.isInteger() && "Unexpected builtin type");
149   uint64_t TypeSize = Context.getTypeSize(&T);
150   bool IsUnsignedInteger = T.isUnsignedInteger();
151   return {llvm::APSInt::getMinValue(TypeSize, IsUnsignedInteger),
152           llvm::APSInt::getMaxValue(TypeSize, IsUnsignedInteger)};
153 }
154 
155 static bool isWideEnoughToHold(const ASTContext &Context,
156                                const BuiltinType &FromType,
157                                const BuiltinType &ToType) {
158   IntegerRange FromIntegerRange = createFromType(Context, FromType);
159   IntegerRange ToIntegerRange = createFromType(Context, ToType);
160   return ToIntegerRange.Contains(FromIntegerRange);
161 }
162 
163 static bool isWideEnoughToHold(const ASTContext &Context,
164                                const llvm::APSInt &IntegerConstant,
165                                const BuiltinType &ToType) {
166   IntegerRange ToIntegerRange = createFromType(Context, ToType);
167   return ToIntegerRange.Contains(IntegerConstant);
168 }
169 
170 static llvm::SmallString<64> getValueAsString(const llvm::APSInt &Value,
171                                               uint64_t HexBits) {
172   llvm::SmallString<64> Str;
173   Value.toString(Str, 10);
174   if (HexBits > 0) {
175     Str.append(" (0x");
176     llvm::SmallString<32> HexValue;
177     Value.toStringUnsigned(HexValue, 16);
178     for (size_t I = HexValue.size(); I < (HexBits / 4); ++I)
179       Str.append("0");
180     Str.append(HexValue);
181     Str.append(")");
182   }
183   return Str;
184 }
185 
186 void NarrowingConversionsCheck::diagNarrowType(SourceLocation SourceLoc,
187                                                const Expr &Lhs,
188                                                const Expr &Rhs) {
189   diag(SourceLoc, "narrowing conversion from %0 to %1")
190       << getUnqualifiedType(Rhs) << getUnqualifiedType(Lhs);
191 }
192 
193 void NarrowingConversionsCheck::diagNarrowTypeToSignedInt(
194     SourceLocation SourceLoc, const Expr &Lhs, const Expr &Rhs) {
195   diag(SourceLoc, "narrowing conversion from %0 to signed type %1 is "
196                   "implementation-defined")
197       << getUnqualifiedType(Rhs) << getUnqualifiedType(Lhs);
198 }
199 
200 void NarrowingConversionsCheck::diagNarrowIntegerConstant(
201     SourceLocation SourceLoc, const Expr &Lhs, const Expr &Rhs,
202     const llvm::APSInt &Value) {
203   diag(SourceLoc,
204        "narrowing conversion from constant value %0 of type %1 to %2")
205       << getValueAsString(Value, /*NoHex*/ 0) << getUnqualifiedType(Rhs)
206       << getUnqualifiedType(Lhs);
207 }
208 
209 void NarrowingConversionsCheck::diagNarrowIntegerConstantToSignedInt(
210     SourceLocation SourceLoc, const Expr &Lhs, const Expr &Rhs,
211     const llvm::APSInt &Value, const uint64_t HexBits) {
212   diag(SourceLoc, "narrowing conversion from constant value %0 of type %1 "
213                   "to signed type %2 is implementation-defined")
214       << getValueAsString(Value, HexBits) << getUnqualifiedType(Rhs)
215       << getUnqualifiedType(Lhs);
216 }
217 
218 void NarrowingConversionsCheck::diagNarrowConstant(SourceLocation SourceLoc,
219                                                    const Expr &Lhs,
220                                                    const Expr &Rhs) {
221   diag(SourceLoc, "narrowing conversion from constant %0 to %1")
222       << getUnqualifiedType(Rhs) << getUnqualifiedType(Lhs);
223 }
224 
225 void NarrowingConversionsCheck::diagConstantCast(SourceLocation SourceLoc,
226                                                  const Expr &Lhs,
227                                                  const Expr &Rhs) {
228   diag(SourceLoc, "constant value should be of type of type %0 instead of %1")
229       << getUnqualifiedType(Lhs) << getUnqualifiedType(Rhs);
230 }
231 
232 void NarrowingConversionsCheck::diagNarrowTypeOrConstant(
233     const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
234     const Expr &Rhs) {
235   APValue Constant = getConstantExprValue(Context, Rhs);
236   if (Constant.isInt())
237     return diagNarrowIntegerConstant(SourceLoc, Lhs, Rhs, Constant.getInt());
238   if (Constant.isFloat())
239     return diagNarrowConstant(SourceLoc, Lhs, Rhs);
240   return diagNarrowType(SourceLoc, Lhs, Rhs);
241 }
242 
243 void NarrowingConversionsCheck::handleIntegralCast(const ASTContext &Context,
244                                                    SourceLocation SourceLoc,
245                                                    const Expr &Lhs,
246                                                    const Expr &Rhs) {
247   const BuiltinType *ToType = getBuiltinType(Lhs);
248   // From [conv.integral]p7.3.8:
249   // Conversions to unsigned integer is well defined so no warning is issued.
250   // "The resulting value is the smallest unsigned value equal to the source
251   // value modulo 2^n where n is the number of bits used to represent the
252   // destination type."
253   if (ToType->isUnsignedInteger())
254     return;
255   const BuiltinType *FromType = getBuiltinType(Rhs);
256   llvm::APSInt IntegerConstant;
257   if (getIntegerConstantExprValue(Context, Rhs, IntegerConstant)) {
258     if (!isWideEnoughToHold(Context, IntegerConstant, *ToType))
259       diagNarrowIntegerConstantToSignedInt(SourceLoc, Lhs, Rhs, IntegerConstant,
260                                            Context.getTypeSize(FromType));
261     return;
262   }
263   if (!isWideEnoughToHold(Context, *FromType, *ToType))
264     diagNarrowTypeToSignedInt(SourceLoc, Lhs, Rhs);
265 }
266 
267 void NarrowingConversionsCheck::handleIntegralToBoolean(
268     const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
269     const Expr &Rhs) {
270   // Conversion from Integral to Bool value is well defined.
271 
272   // We keep this function (even if it is empty) to make sure that
273   // handleImplicitCast and handleBinaryOperator are symmetric in their behavior
274   // and handle the same cases.
275 }
276 
277 void NarrowingConversionsCheck::handleIntegralToFloating(
278     const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
279     const Expr &Rhs) {
280   const BuiltinType *ToType = getBuiltinType(Lhs);
281   llvm::APSInt IntegerConstant;
282   if (getIntegerConstantExprValue(Context, Rhs, IntegerConstant)) {
283     if (!isWideEnoughToHold(Context, IntegerConstant, *ToType))
284       diagNarrowIntegerConstant(SourceLoc, Lhs, Rhs, IntegerConstant);
285     return;
286   }
287   const BuiltinType *FromType = getBuiltinType(Rhs);
288   if (!isWideEnoughToHold(Context, *FromType, *ToType))
289     diagNarrowType(SourceLoc, Lhs, Rhs);
290 }
291 
292 void NarrowingConversionsCheck::handleFloatingToIntegral(
293     const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
294     const Expr &Rhs) {
295   llvm::APFloat FloatConstant(0.0);
296 
297   // We always warn when Rhs is non-constexpr.
298   if (!getFloatingConstantExprValue(Context, Rhs, FloatConstant))
299     return diagNarrowType(SourceLoc, Lhs, Rhs);
300 
301   QualType DestType = Lhs.getType();
302   unsigned DestWidth = Context.getIntWidth(DestType);
303   bool DestSigned = DestType->isSignedIntegerOrEnumerationType();
304   llvm::APSInt Result = llvm::APSInt(DestWidth, !DestSigned);
305   bool IsExact = false;
306   bool Overflows = FloatConstant.convertToInteger(
307                        Result, llvm::APFloat::rmTowardZero, &IsExact) &
308                    llvm::APFloat::opInvalidOp;
309   // We warn iff the constant floating point value is not exactly representable.
310   if (Overflows || !IsExact)
311     return diagNarrowConstant(SourceLoc, Lhs, Rhs);
312 
313   if (PedanticMode)
314     return diagConstantCast(SourceLoc, Lhs, Rhs);
315 }
316 
317 void NarrowingConversionsCheck::handleFloatingToBoolean(
318     const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
319     const Expr &Rhs) {
320   return diagNarrowTypeOrConstant(Context, SourceLoc, Lhs, Rhs);
321 }
322 
323 void NarrowingConversionsCheck::handleBooleanToSignedIntegral(
324     const ASTContext &Context, SourceLocation SourceLoc, const Expr &Lhs,
325     const Expr &Rhs) {
326   // Conversion from Bool to SignedIntegral value is well defined.
327 
328   // We keep this function (even if it is empty) to make sure that
329   // handleImplicitCast and handleBinaryOperator are symmetric in their behavior
330   // and handle the same cases.
331 }
332 
333 void NarrowingConversionsCheck::handleFloatingCast(const ASTContext &Context,
334                                                    SourceLocation SourceLoc,
335                                                    const Expr &Lhs,
336                                                    const Expr &Rhs) {
337   if (WarnOnFloatingPointNarrowingConversion) {
338     const BuiltinType *ToType = getBuiltinType(Lhs);
339     APValue Constant = getConstantExprValue(Context, Rhs);
340     if (Constant.isFloat()) {
341       // From [dcl.init.list]p7.2:
342       // Floating point constant narrowing only takes place when the value is
343       // not within destination range. We convert the value to the destination
344       // type and check if the resulting value is infinity.
345       llvm::APFloat Tmp = Constant.getFloat();
346       bool UnusedLosesInfo;
347       Tmp.convert(Context.getFloatTypeSemantics(ToType->desugar()),
348                   llvm::APFloatBase::rmNearestTiesToEven, &UnusedLosesInfo);
349       if (Tmp.isInfinity())
350         diagNarrowConstant(SourceLoc, Lhs, Rhs);
351       return;
352     }
353     const BuiltinType *FromType = getBuiltinType(Rhs);
354     if (ToType->getKind() < FromType->getKind())
355       diagNarrowType(SourceLoc, Lhs, Rhs);
356   }
357 }
358 
359 void NarrowingConversionsCheck::handleBinaryOperator(const ASTContext &Context,
360                                                      SourceLocation SourceLoc,
361                                                      const Expr &Lhs,
362                                                      const Expr &Rhs) {
363   assert(!Lhs.isInstantiationDependent() && !Rhs.isInstantiationDependent() &&
364          "Dependent types must be check before calling this function");
365   const BuiltinType *LhsType = getBuiltinType(Lhs);
366   const BuiltinType *RhsType = getBuiltinType(Rhs);
367   if (RhsType == nullptr || LhsType == nullptr)
368     return;
369   if (RhsType->getKind() == BuiltinType::Bool && LhsType->isSignedInteger())
370     return handleBooleanToSignedIntegral(Context, SourceLoc, Lhs, Rhs);
371   if (RhsType->isInteger() && LhsType->getKind() == BuiltinType::Bool)
372     return handleIntegralToBoolean(Context, SourceLoc, Lhs, Rhs);
373   if (RhsType->isInteger() && LhsType->isFloatingPoint())
374     return handleIntegralToFloating(Context, SourceLoc, Lhs, Rhs);
375   if (RhsType->isInteger() && LhsType->isInteger())
376     return handleIntegralCast(Context, SourceLoc, Lhs, Rhs);
377   if (RhsType->isFloatingPoint() && LhsType->getKind() == BuiltinType::Bool)
378     return handleFloatingToBoolean(Context, SourceLoc, Lhs, Rhs);
379   if (RhsType->isFloatingPoint() && LhsType->isInteger())
380     return handleFloatingToIntegral(Context, SourceLoc, Lhs, Rhs);
381   if (RhsType->isFloatingPoint() && LhsType->isFloatingPoint())
382     return handleFloatingCast(Context, SourceLoc, Lhs, Rhs);
383 }
384 
385 bool NarrowingConversionsCheck::handleConditionalOperator(
386     const ASTContext &Context, const Expr &Lhs, const Expr &Rhs) {
387   if (const auto *CO = llvm::dyn_cast<ConditionalOperator>(&Rhs)) {
388     // We have an expression like so: `output = cond ? lhs : rhs`
389     // From the point of view of narrowing conversion we treat it as two
390     // expressions `output = lhs` and `output = rhs`.
391     handleBinaryOperator(Context, CO->getLHS()->getExprLoc(), Lhs,
392                          *CO->getLHS());
393     handleBinaryOperator(Context, CO->getRHS()->getExprLoc(), Lhs,
394                          *CO->getRHS());
395     return true;
396   }
397   return false;
398 }
399 
400 void NarrowingConversionsCheck::handleImplicitCast(
401     const ASTContext &Context, const ImplicitCastExpr &Cast) {
402   if (Cast.getExprLoc().isMacroID())
403     return;
404   const Expr &Lhs = Cast;
405   const Expr &Rhs = *Cast.getSubExpr();
406   if (Lhs.isInstantiationDependent() || Rhs.isInstantiationDependent())
407     return;
408   if (handleConditionalOperator(Context, Lhs, Rhs))
409     return;
410   SourceLocation SourceLoc = Lhs.getExprLoc();
411   switch (Cast.getCastKind()) {
412   case CK_BooleanToSignedIntegral:
413     return handleBooleanToSignedIntegral(Context, SourceLoc, Lhs, Rhs);
414   case CK_IntegralToBoolean:
415     return handleIntegralToBoolean(Context, SourceLoc, Lhs, Rhs);
416   case CK_IntegralToFloating:
417     return handleIntegralToFloating(Context, SourceLoc, Lhs, Rhs);
418   case CK_IntegralCast:
419     return handleIntegralCast(Context, SourceLoc, Lhs, Rhs);
420   case CK_FloatingToBoolean:
421     return handleFloatingToBoolean(Context, SourceLoc, Lhs, Rhs);
422   case CK_FloatingToIntegral:
423     return handleFloatingToIntegral(Context, SourceLoc, Lhs, Rhs);
424   case CK_FloatingCast:
425     return handleFloatingCast(Context, SourceLoc, Lhs, Rhs);
426   default:
427     break;
428   }
429 }
430 
431 void NarrowingConversionsCheck::handleBinaryOperator(const ASTContext &Context,
432                                                      const BinaryOperator &Op) {
433   if (Op.getBeginLoc().isMacroID())
434     return;
435   const Expr &Lhs = *Op.getLHS();
436   const Expr &Rhs = *Op.getRHS();
437   if (Lhs.isInstantiationDependent() || Rhs.isInstantiationDependent())
438     return;
439   if (handleConditionalOperator(Context, Lhs, Rhs))
440     return;
441   handleBinaryOperator(Context, Rhs.getBeginLoc(), Lhs, Rhs);
442 }
443 
444 void NarrowingConversionsCheck::check(const MatchFinder::MatchResult &Result) {
445   if (const auto *Op = Result.Nodes.getNodeAs<BinaryOperator>("binary_op"))
446     return handleBinaryOperator(*Result.Context, *Op);
447   if (const auto *Cast = Result.Nodes.getNodeAs<ImplicitCastExpr>("cast"))
448     return handleImplicitCast(*Result.Context, *Cast);
449   llvm_unreachable("must be binary operator or cast expression");
450 }
451 } // namespace cppcoreguidelines
452 } // namespace tidy
453 } // namespace clang
454