| 1 | //===--- ImplicitWideningOfMultiplicationResultCheck.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 "ImplicitWideningOfMultiplicationResultCheck.h" |
| 10 | #include "clang/AST/ASTContext.h" |
| 11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 12 | #include "clang/ASTMatchers/ASTMatchersMacros.h" |
| 13 | #include "clang/Lex/Lexer.h" |
| 14 | #include <optional> |
| 15 | |
| 16 | using namespace clang::ast_matchers; |
| 17 | |
| 18 | namespace clang::tidy::bugprone { |
| 19 | |
| 20 | namespace { |
| 21 | AST_MATCHER(ImplicitCastExpr, isPartOfExplicitCast) { |
| 22 | return Node.isPartOfExplicitCast(); |
| 23 | } |
| 24 | AST_MATCHER(Expr, containsErrors) { return Node.containsErrors(); } |
| 25 | } // namespace |
| 26 | |
| 27 | static const Expr *getLHSOfMulBinOp(const Expr *E) { |
| 28 | assert(E == E->IgnoreParens() && "Already skipped all parens!" ); |
| 29 | // Is this: long r = int(x) * int(y); ? |
| 30 | // FIXME: shall we skip brackets/casts/etc? |
| 31 | const auto *BO = dyn_cast<BinaryOperator>(Val: E); |
| 32 | if (!BO || BO->getOpcode() != BO_Mul) |
| 33 | // FIXME: what about: long r = int(x) + (int(y) * int(z)); ? |
| 34 | return nullptr; |
| 35 | return BO->getLHS()->IgnoreParens(); |
| 36 | } |
| 37 | |
| 38 | ImplicitWideningOfMultiplicationResultCheck:: |
| 39 | ImplicitWideningOfMultiplicationResultCheck(StringRef Name, |
| 40 | ClangTidyContext *Context) |
| 41 | : ClangTidyCheck(Name, Context), |
| 42 | UseCXXStaticCastsInCppSources( |
| 43 | Options.get(LocalName: "UseCXXStaticCastsInCppSources" , Default: true)), |
| 44 | UseCXXHeadersInCppSources(Options.get(LocalName: "UseCXXHeadersInCppSources" , Default: true)), |
| 45 | IgnoreConstantIntExpr(Options.get(LocalName: "IgnoreConstantIntExpr" , Default: false)), |
| 46 | IncludeInserter(Options.getLocalOrGlobal(LocalName: "IncludeStyle" , |
| 47 | Default: utils::IncludeSorter::IS_LLVM), |
| 48 | areDiagsSelfContained()) {} |
| 49 | |
| 50 | void ImplicitWideningOfMultiplicationResultCheck::registerPPCallbacks( |
| 51 | const SourceManager &SM, Preprocessor *PP, Preprocessor *ModuleExpanderPP) { |
| 52 | IncludeInserter.registerPreprocessor(PP); |
| 53 | } |
| 54 | |
| 55 | void ImplicitWideningOfMultiplicationResultCheck::storeOptions( |
| 56 | ClangTidyOptions::OptionMap &Opts) { |
| 57 | Options.store(Options&: Opts, LocalName: "UseCXXStaticCastsInCppSources" , |
| 58 | Value: UseCXXStaticCastsInCppSources); |
| 59 | Options.store(Options&: Opts, LocalName: "UseCXXHeadersInCppSources" , Value: UseCXXHeadersInCppSources); |
| 60 | Options.store(Options&: Opts, LocalName: "IgnoreConstantIntExpr" , Value: IgnoreConstantIntExpr); |
| 61 | Options.store(Options&: Opts, LocalName: "IncludeStyle" , Value: IncludeInserter.getStyle()); |
| 62 | } |
| 63 | |
| 64 | std::optional<FixItHint> |
| 65 | ImplicitWideningOfMultiplicationResultCheck::( |
| 66 | SourceLocation File) { |
| 67 | return IncludeInserter.createIncludeInsertion( |
| 68 | FileID: Result->SourceManager->getFileID(SpellingLoc: File), |
| 69 | Header: ShouldUseCXXHeader ? "<cstddef>" : "<stddef.h>" ); |
| 70 | } |
| 71 | |
| 72 | void ImplicitWideningOfMultiplicationResultCheck::handleImplicitCastExpr( |
| 73 | const ImplicitCastExpr *ICE) { |
| 74 | ASTContext *Context = Result->Context; |
| 75 | |
| 76 | const Expr *E = ICE->getSubExpr()->IgnoreParens(); |
| 77 | QualType Ty = ICE->getType(); |
| 78 | QualType ETy = E->getType(); |
| 79 | |
| 80 | assert(!ETy->isDependentType() && !Ty->isDependentType() && |
| 81 | "Don't expect to ever get here in template Context." ); |
| 82 | |
| 83 | // This must be a widening cast. Else we do not care. |
| 84 | unsigned SrcWidth = Context->getIntWidth(T: ETy); |
| 85 | unsigned TgtWidth = Context->getIntWidth(T: Ty); |
| 86 | if (TgtWidth <= SrcWidth) |
| 87 | return; |
| 88 | |
| 89 | // Is the expression a compile-time constexpr that we know can fit in the |
| 90 | // source type? |
| 91 | if (IgnoreConstantIntExpr && ETy->isIntegerType() && |
| 92 | !ETy->isUnsignedIntegerType()) { |
| 93 | if (const auto ConstExprResult = E->getIntegerConstantExpr(*Context)) { |
| 94 | const auto TypeSize = Context->getTypeSize(T: ETy); |
| 95 | llvm::APSInt WidenedResult = ConstExprResult->extOrTrunc(TypeSize); |
| 96 | if (WidenedResult <= llvm::APSInt::getMaxValue(numBits: TypeSize, Unsigned: false) && |
| 97 | WidenedResult >= llvm::APSInt::getMinValue(numBits: TypeSize, Unsigned: false)) |
| 98 | return; |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | // Does the index expression look like it might be unintentionally computed |
| 103 | // in a narrower-than-wanted type? |
| 104 | const Expr *LHS = getLHSOfMulBinOp(E); |
| 105 | if (!LHS) |
| 106 | return; |
| 107 | |
| 108 | // Ok, looks like we should diagnose this. |
| 109 | diag(E->getBeginLoc(), "performing an implicit widening conversion to type " |
| 110 | "%0 of a multiplication performed in type %1" ) |
| 111 | << Ty << E->getType(); |
| 112 | |
| 113 | { |
| 114 | auto Diag = diag(E->getBeginLoc(), |
| 115 | "make conversion explicit to silence this warning" , |
| 116 | DiagnosticIDs::Note) |
| 117 | << E->getSourceRange(); |
| 118 | const SourceLocation EndLoc = Lexer::getLocForEndOfToken( |
| 119 | Loc: E->getEndLoc(), Offset: 0, SM: *Result->SourceManager, LangOpts: getLangOpts()); |
| 120 | if (ShouldUseCXXStaticCast) |
| 121 | Diag << FixItHint::CreateInsertion( |
| 122 | InsertionLoc: E->getBeginLoc(), Code: "static_cast<" + Ty.getAsString() + ">(" ) |
| 123 | << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: ")" ); |
| 124 | else |
| 125 | Diag << FixItHint::CreateInsertion(InsertionLoc: E->getBeginLoc(), |
| 126 | Code: "(" + Ty.getAsString() + ")(" ) |
| 127 | << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: ")" ); |
| 128 | Diag << includeStddefHeader(File: E->getBeginLoc()); |
| 129 | } |
| 130 | |
| 131 | QualType WideExprTy; |
| 132 | // Get Ty of the same signedness as ExprTy, because we only want to suggest |
| 133 | // to widen the computation, but not change it's signedness domain. |
| 134 | if (Ty->isSignedIntegerType() == ETy->isSignedIntegerType()) |
| 135 | WideExprTy = Ty; |
| 136 | else if (Ty->isSignedIntegerType()) { |
| 137 | assert(ETy->isUnsignedIntegerType() && |
| 138 | "Expected source type to be signed." ); |
| 139 | WideExprTy = Context->getCorrespondingUnsignedType(T: Ty); |
| 140 | } else { |
| 141 | assert(Ty->isUnsignedIntegerType() && |
| 142 | "Expected target type to be unsigned." ); |
| 143 | assert(ETy->isSignedIntegerType() && |
| 144 | "Expected source type to be unsigned." ); |
| 145 | WideExprTy = Context->getCorrespondingSignedType(T: Ty); |
| 146 | } |
| 147 | |
| 148 | { |
| 149 | auto Diag = diag(E->getBeginLoc(), "perform multiplication in a wider type" , |
| 150 | DiagnosticIDs::Note) |
| 151 | << LHS->getSourceRange(); |
| 152 | |
| 153 | if (ShouldUseCXXStaticCast) |
| 154 | Diag << FixItHint::CreateInsertion(InsertionLoc: LHS->getBeginLoc(), |
| 155 | Code: "static_cast<" + |
| 156 | WideExprTy.getAsString() + ">(" ) |
| 157 | << FixItHint::CreateInsertion( |
| 158 | InsertionLoc: Lexer::getLocForEndOfToken(Loc: LHS->getEndLoc(), Offset: 0, |
| 159 | SM: *Result->SourceManager, |
| 160 | LangOpts: getLangOpts()), |
| 161 | Code: ")" ); |
| 162 | else |
| 163 | Diag << FixItHint::CreateInsertion(InsertionLoc: LHS->getBeginLoc(), |
| 164 | Code: "(" + WideExprTy.getAsString() + ")" ); |
| 165 | Diag << includeStddefHeader(File: LHS->getBeginLoc()); |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | void ImplicitWideningOfMultiplicationResultCheck::handlePointerOffsetting( |
| 170 | const Expr *E) { |
| 171 | ASTContext *Context = Result->Context; |
| 172 | |
| 173 | // We are looking for a pointer offset operation, |
| 174 | // with one hand being a pointer, and another one being an offset. |
| 175 | const Expr *PointerExpr = nullptr, *IndexExpr = nullptr; |
| 176 | if (const auto *BO = dyn_cast<BinaryOperator>(Val: E)) { |
| 177 | PointerExpr = BO->getLHS(); |
| 178 | IndexExpr = BO->getRHS(); |
| 179 | } else if (const auto *ASE = dyn_cast<ArraySubscriptExpr>(Val: E)) { |
| 180 | PointerExpr = ASE->getLHS(); |
| 181 | IndexExpr = ASE->getRHS(); |
| 182 | } else |
| 183 | return; |
| 184 | |
| 185 | if (IndexExpr->getType()->isPointerType()) |
| 186 | std::swap(a&: PointerExpr, b&: IndexExpr); |
| 187 | |
| 188 | if (!PointerExpr->getType()->isPointerType() || |
| 189 | IndexExpr->getType()->isPointerType()) |
| 190 | return; |
| 191 | |
| 192 | IndexExpr = IndexExpr->IgnoreParens(); |
| 193 | |
| 194 | QualType IndexExprType = IndexExpr->getType(); |
| 195 | |
| 196 | // If the index expression's type is not known (i.e. we are in a template), |
| 197 | // we can't do anything here. |
| 198 | if (IndexExprType->isDependentType()) |
| 199 | return; |
| 200 | |
| 201 | QualType SSizeTy = Context->getPointerDiffType(); |
| 202 | QualType USizeTy = Context->getSizeType(); |
| 203 | QualType SizeTy = IndexExprType->isSignedIntegerType() ? SSizeTy : USizeTy; |
| 204 | // FIXME: is there a way to actually get the QualType for size_t/ptrdiff_t? |
| 205 | // Note that SizeTy.getAsString() will be unsigned long/..., NOT size_t! |
| 206 | StringRef TyAsString = |
| 207 | IndexExprType->isSignedIntegerType() ? "ptrdiff_t" : "size_t" ; |
| 208 | |
| 209 | // So, is size_t actually wider than the result of the multiplication? |
| 210 | if (Context->getIntWidth(T: IndexExprType) >= Context->getIntWidth(T: SizeTy)) |
| 211 | return; |
| 212 | |
| 213 | // Does the index expression look like it might be unintentionally computed |
| 214 | // in a narrower-than-wanted type? |
| 215 | const Expr *LHS = getLHSOfMulBinOp(E: IndexExpr); |
| 216 | if (!LHS) |
| 217 | return; |
| 218 | |
| 219 | // Ok, looks like we should diagnose this. |
| 220 | diag(E->getBeginLoc(), |
| 221 | "result of multiplication in type %0 is used as a pointer offset after " |
| 222 | "an implicit widening conversion to type '%1'" ) |
| 223 | << IndexExprType << TyAsString; |
| 224 | |
| 225 | { |
| 226 | auto Diag = diag(IndexExpr->getBeginLoc(), |
| 227 | "make conversion explicit to silence this warning" , |
| 228 | DiagnosticIDs::Note) |
| 229 | << IndexExpr->getSourceRange(); |
| 230 | const SourceLocation EndLoc = Lexer::getLocForEndOfToken( |
| 231 | Loc: IndexExpr->getEndLoc(), Offset: 0, SM: *Result->SourceManager, LangOpts: getLangOpts()); |
| 232 | if (ShouldUseCXXStaticCast) |
| 233 | Diag << FixItHint::CreateInsertion( |
| 234 | InsertionLoc: IndexExpr->getBeginLoc(), |
| 235 | Code: (Twine("static_cast<" ) + TyAsString + ">(" ).str()) |
| 236 | << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: ")" ); |
| 237 | else |
| 238 | Diag << FixItHint::CreateInsertion(InsertionLoc: IndexExpr->getBeginLoc(), |
| 239 | Code: (Twine("(" ) + TyAsString + ")(" ).str()) |
| 240 | << FixItHint::CreateInsertion(InsertionLoc: EndLoc, Code: ")" ); |
| 241 | Diag << includeStddefHeader(File: IndexExpr->getBeginLoc()); |
| 242 | } |
| 243 | |
| 244 | { |
| 245 | auto Diag = |
| 246 | diag(IndexExpr->getBeginLoc(), "perform multiplication in a wider type" , |
| 247 | DiagnosticIDs::Note) |
| 248 | << LHS->getSourceRange(); |
| 249 | |
| 250 | if (ShouldUseCXXStaticCast) |
| 251 | Diag << FixItHint::CreateInsertion( |
| 252 | InsertionLoc: LHS->getBeginLoc(), |
| 253 | Code: (Twine("static_cast<" ) + TyAsString + ">(" ).str()) |
| 254 | << FixItHint::CreateInsertion( |
| 255 | InsertionLoc: Lexer::getLocForEndOfToken(Loc: IndexExpr->getEndLoc(), Offset: 0, |
| 256 | SM: *Result->SourceManager, |
| 257 | LangOpts: getLangOpts()), |
| 258 | Code: ")" ); |
| 259 | else |
| 260 | Diag << FixItHint::CreateInsertion(InsertionLoc: LHS->getBeginLoc(), |
| 261 | Code: (Twine("(" ) + TyAsString + ")" ).str()); |
| 262 | Diag << includeStddefHeader(File: LHS->getBeginLoc()); |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | void ImplicitWideningOfMultiplicationResultCheck::registerMatchers( |
| 267 | MatchFinder *Finder) { |
| 268 | Finder->addMatcher(NodeMatch: implicitCastExpr(unless(anyOf(containsErrors(), |
| 269 | isInTemplateInstantiation(), |
| 270 | isPartOfExplicitCast())), |
| 271 | hasCastKind(Kind: CK_IntegralCast)) |
| 272 | .bind(ID: "x" ), |
| 273 | Action: this); |
| 274 | Finder->addMatcher( |
| 275 | NodeMatch: arraySubscriptExpr(unless(isInTemplateInstantiation())).bind(ID: "x" ), Action: this); |
| 276 | Finder->addMatcher(NodeMatch: binaryOperator(unless(isInTemplateInstantiation()), |
| 277 | hasType(InnerMatcher: isAnyPointer()), |
| 278 | hasAnyOperatorName("+" , "-" , "+=" , "-=" )) |
| 279 | .bind(ID: "x" ), |
| 280 | Action: this); |
| 281 | } |
| 282 | |
| 283 | void ImplicitWideningOfMultiplicationResultCheck::check( |
| 284 | const MatchFinder::MatchResult &Result) { |
| 285 | this->Result = &Result; |
| 286 | ShouldUseCXXStaticCast = |
| 287 | UseCXXStaticCastsInCppSources && Result.Context->getLangOpts().CPlusPlus; |
| 288 | ShouldUseCXXHeader = |
| 289 | UseCXXHeadersInCppSources && Result.Context->getLangOpts().CPlusPlus; |
| 290 | |
| 291 | if (const auto *MatchedDecl = Result.Nodes.getNodeAs<ImplicitCastExpr>(ID: "x" )) |
| 292 | handleImplicitCastExpr(ICE: MatchedDecl); |
| 293 | else if (const auto *MatchedDecl = |
| 294 | Result.Nodes.getNodeAs<ArraySubscriptExpr>(ID: "x" )) |
| 295 | handlePointerOffsetting(MatchedDecl); |
| 296 | else if (const auto *MatchedDecl = |
| 297 | Result.Nodes.getNodeAs<BinaryOperator>(ID: "x" )) |
| 298 | handlePointerOffsetting(MatchedDecl); |
| 299 | } |
| 300 | |
| 301 | } // namespace clang::tidy::bugprone |
| 302 | |