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