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