| //===- CallDescription.cpp - function/method call matching --*- C++ -*-===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| /// \file This file defines a generic mechanism for matching for function and |
| /// method calls of C, C++, and Objective-C languages. Instances of these |
| /// classes are frequently used together with the CallEvent classes. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" |
| #include "clang/AST/Decl.h" |
| #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
| #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
| #include "llvm/ADT/ArrayRef.h" |
| #include <iterator> |
| #include <optional> |
| |
| using namespace llvm; |
| using namespace clang; |
| |
| using MaybeCount = std::optional<unsigned>; |
| |
| // A constructor helper. |
| static MaybeCount readRequiredParams(MaybeCount RequiredArgs, |
| MaybeCount RequiredParams) { |
| if (RequiredParams) |
| return RequiredParams; |
| if (RequiredArgs) |
| return RequiredArgs; |
| return std::nullopt; |
| } |
| |
| ento::CallDescription::CallDescription(Mode MatchAs, |
| ArrayRef<StringRef> QualifiedName, |
| MaybeCount RequiredArgs /*= None*/, |
| MaybeCount RequiredParams /*= None*/) |
| : RequiredArgs(RequiredArgs), |
| RequiredParams(readRequiredParams(RequiredArgs, RequiredParams)), |
| MatchAs(MatchAs) { |
| assert(!QualifiedName.empty()); |
| this->QualifiedName.reserve(QualifiedName.size()); |
| llvm::transform(QualifiedName, std::back_inserter(this->QualifiedName), |
| [](StringRef From) { return From.str(); }); |
| } |
| |
| /// Construct a CallDescription with default flags. |
| ento::CallDescription::CallDescription(ArrayRef<StringRef> QualifiedName, |
| MaybeCount RequiredArgs /*= None*/, |
| MaybeCount RequiredParams /*= None*/) |
| : CallDescription(Mode::Unspecified, QualifiedName, RequiredArgs, |
| RequiredParams) {} |
| |
| bool ento::CallDescription::matches(const CallEvent &Call) const { |
| // FIXME: Add ObjC Message support. |
| if (Call.getKind() == CE_ObjCMessage) |
| return false; |
| |
| const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); |
| if (!FD) |
| return false; |
| |
| return matchesImpl(FD, Call.getNumArgs(), Call.parameters().size()); |
| } |
| |
| bool ento::CallDescription::matchesAsWritten(const CallExpr &CE) const { |
| const auto *FD = dyn_cast_or_null<FunctionDecl>(CE.getCalleeDecl()); |
| if (!FD) |
| return false; |
| |
| return matchesImpl(FD, CE.getNumArgs(), FD->param_size()); |
| } |
| |
| bool ento::CallDescription::matchNameOnly(const NamedDecl *ND) const { |
| DeclarationName Name = ND->getDeclName(); |
| if (const auto *NameII = Name.getAsIdentifierInfo()) { |
| if (!II) |
| II = &ND->getASTContext().Idents.get(getFunctionName()); |
| |
| return NameII == *II; // Fast case. |
| } |
| |
| // Fallback to the slow stringification and comparison for: |
| // C++ overloaded operators, constructors, destructors, etc. |
| // FIXME This comparison is way SLOWER than comparing pointers. |
| // At some point in the future, we should compare FunctionDecl pointers. |
| return Name.getAsString() == getFunctionName(); |
| } |
| |
| bool ento::CallDescription::matchQualifiedNameParts(const Decl *D) const { |
| const auto FindNextNamespaceOrRecord = |
| [](const DeclContext *Ctx) -> const DeclContext * { |
| while (Ctx && !isa<NamespaceDecl, RecordDecl>(Ctx)) |
| Ctx = Ctx->getParent(); |
| return Ctx; |
| }; |
| |
| auto QualifierPartsIt = begin_qualified_name_parts(); |
| const auto QualifierPartsEndIt = end_qualified_name_parts(); |
| |
| // Match namespace and record names. Skip unrelated names if they don't |
| // match. |
| const DeclContext *Ctx = FindNextNamespaceOrRecord(D->getDeclContext()); |
| for (; Ctx && QualifierPartsIt != QualifierPartsEndIt; |
| Ctx = FindNextNamespaceOrRecord(Ctx->getParent())) { |
| // If not matched just continue and try matching for the next one. |
| if (cast<NamedDecl>(Ctx)->getName() != *QualifierPartsIt) |
| continue; |
| ++QualifierPartsIt; |
| } |
| |
| // We matched if we consumed all expected qualifier segments. |
| return QualifierPartsIt == QualifierPartsEndIt; |
| } |
| |
| bool ento::CallDescription::matchesImpl(const FunctionDecl *FD, size_t ArgCount, |
| size_t ParamCount) const { |
| if (!FD) |
| return false; |
| |
| const bool isMethod = isa<CXXMethodDecl>(FD); |
| |
| if (MatchAs == Mode::SimpleFunc && isMethod) |
| return false; |
| |
| if (MatchAs == Mode::CXXMethod && !isMethod) |
| return false; |
| |
| if (MatchAs == Mode::CLibraryMaybeHardened) { |
| // In addition to accepting FOO() with CLibrary rules, we also want to |
| // accept calls to __FOO_chk() and __builtin___FOO_chk(). |
| if (CheckerContext::isCLibraryFunction(FD) && |
| CheckerContext::isHardenedVariantOf(FD, getFunctionName())) { |
| // Check that the actual argument/parameter counts are greater or equal |
| // to the required counts. (Setting a requirement to std::nullopt matches |
| // anything, so in that case value_or ensures that the value is compared |
| // with itself.) |
| return (RequiredArgs.value_or(ArgCount) <= ArgCount && |
| RequiredParams.value_or(ParamCount) <= ParamCount); |
| } |
| } |
| |
| if (RequiredArgs.value_or(ArgCount) != ArgCount || |
| RequiredParams.value_or(ParamCount) != ParamCount) |
| return false; |
| |
| if (MatchAs == Mode::CLibrary || MatchAs == Mode::CLibraryMaybeHardened) |
| return CheckerContext::isCLibraryFunction(FD, getFunctionName()); |
| |
| if (!matchNameOnly(FD)) |
| return false; |
| |
| if (!hasQualifiedNameParts()) |
| return true; |
| |
| return matchQualifiedNameParts(FD); |
| } |
| |
| ento::CallDescriptionSet::CallDescriptionSet( |
| std::initializer_list<CallDescription> &&List) { |
| Impl.LinearMap.reserve(List.size()); |
| for (const CallDescription &CD : List) |
| Impl.LinearMap.push_back({CD, /*unused*/ true}); |
| } |
| |
| bool ento::CallDescriptionSet::contains(const CallEvent &Call) const { |
| return static_cast<bool>(Impl.lookup(Call)); |
| } |
| |
| bool ento::CallDescriptionSet::containsAsWritten(const CallExpr &CE) const { |
| return static_cast<bool>(Impl.lookupAsWritten(CE)); |
| } |