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