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