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 | |