| 1 | //===--- SuspiciousMemoryComparisonCheck.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 "SuspiciousMemoryComparisonCheck.h" |
| 10 | #include "clang/AST/ASTContext.h" |
| 11 | #include "clang/ASTMatchers/ASTMatchFinder.h" |
| 12 | #include <optional> |
| 13 | |
| 14 | using namespace clang::ast_matchers; |
| 15 | |
| 16 | namespace clang::tidy::bugprone { |
| 17 | |
| 18 | static std::optional<uint64_t> tryEvaluateSizeExpr(const Expr *SizeExpr, |
| 19 | const ASTContext &Ctx) { |
| 20 | Expr::EvalResult Result; |
| 21 | if (SizeExpr->EvaluateAsRValue(Result, Ctx)) |
| 22 | return Ctx.toBits( |
| 23 | CharSize: CharUnits::fromQuantity(Quantity: Result.Val.getInt().getExtValue())); |
| 24 | return std::nullopt; |
| 25 | } |
| 26 | |
| 27 | void SuspiciousMemoryComparisonCheck::registerMatchers(MatchFinder *Finder) { |
| 28 | Finder->addMatcher( |
| 29 | NodeMatch: callExpr(callee(InnerMatcher: namedDecl( |
| 30 | anyOf(hasName(Name: "::memcmp" ), hasName(Name: "::std::memcmp" )))), |
| 31 | unless(isInstantiationDependent())) |
| 32 | .bind(ID: "call" ), |
| 33 | Action: this); |
| 34 | } |
| 35 | |
| 36 | void SuspiciousMemoryComparisonCheck::check( |
| 37 | const MatchFinder::MatchResult &Result) { |
| 38 | const ASTContext &Ctx = *Result.Context; |
| 39 | const auto *CE = Result.Nodes.getNodeAs<CallExpr>(ID: "call" ); |
| 40 | |
| 41 | const Expr *SizeExpr = CE->getArg(Arg: 2); |
| 42 | assert(SizeExpr != nullptr && "Third argument of memcmp is mandatory." ); |
| 43 | std::optional<uint64_t> ComparedBits = tryEvaluateSizeExpr(SizeExpr, Ctx); |
| 44 | |
| 45 | for (unsigned int ArgIndex = 0; ArgIndex < 2; ++ArgIndex) { |
| 46 | const Expr *ArgExpr = CE->getArg(Arg: ArgIndex); |
| 47 | QualType ArgType = ArgExpr->IgnoreImplicit()->getType(); |
| 48 | const Type *PointeeType = ArgType->getPointeeOrArrayElementType(); |
| 49 | assert(PointeeType != nullptr && "PointeeType should always be available." ); |
| 50 | QualType PointeeQualifiedType(PointeeType, 0); |
| 51 | |
| 52 | if (PointeeType->isRecordType()) { |
| 53 | if (const RecordDecl *RD = |
| 54 | PointeeType->getAsRecordDecl()->getDefinition()) { |
| 55 | if (const auto *CXXDecl = dyn_cast<CXXRecordDecl>(Val: RD)) { |
| 56 | if (!CXXDecl->isStandardLayout()) { |
| 57 | diag(Loc: CE->getBeginLoc(), |
| 58 | Description: "comparing object representation of non-standard-layout type " |
| 59 | "%0; consider using a comparison operator instead" ) |
| 60 | << PointeeQualifiedType; |
| 61 | break; |
| 62 | } |
| 63 | } |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | if (!PointeeType->isIncompleteType()) { |
| 68 | uint64_t PointeeSize = Ctx.getTypeSize(T: PointeeType); |
| 69 | if (ComparedBits && *ComparedBits >= PointeeSize && |
| 70 | !Ctx.hasUniqueObjectRepresentations(Ty: PointeeQualifiedType)) { |
| 71 | diag(Loc: CE->getBeginLoc(), |
| 72 | Description: "comparing object representation of type %0 which does not have a " |
| 73 | "unique object representation; consider comparing %select{the " |
| 74 | "values|the members of the object}1 manually" ) |
| 75 | << PointeeQualifiedType << (PointeeType->isRecordType() ? 1 : 0); |
| 76 | break; |
| 77 | } |
| 78 | } |
| 79 | } |
| 80 | } |
| 81 | |
| 82 | } // namespace clang::tidy::bugprone |
| 83 | |