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