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