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