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