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