169 lines · cpp
1//===- CallDescription.cpp - function/method call matching --*- C++ -*-===//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-exception6//7//===----------------------------------------------------------------------===//8//9/// \file This file defines a generic mechanism for matching for function and10/// method calls of C, C++, and Objective-C languages. Instances of these11/// classes are frequently used together with the CallEvent classes.12//13//===----------------------------------------------------------------------===//14 15#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"16#include "clang/AST/Decl.h"17#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"18#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"19#include <iterator>20#include <optional>21 22using namespace llvm;23using namespace clang;24 25using MaybeCount = std::optional<unsigned>;26 27// A constructor helper.28static MaybeCount readRequiredParams(MaybeCount RequiredArgs,29 MaybeCount RequiredParams) {30 if (RequiredParams)31 return RequiredParams;32 if (RequiredArgs)33 return RequiredArgs;34 return std::nullopt;35}36 37ento::CallDescription::CallDescription(Mode MatchAs,38 ArrayRef<StringRef> QualifiedName,39 MaybeCount RequiredArgs /*= None*/,40 MaybeCount RequiredParams /*= None*/)41 : RequiredArgs(RequiredArgs),42 RequiredParams(readRequiredParams(RequiredArgs, RequiredParams)),43 MatchAs(MatchAs) {44 assert(!QualifiedName.empty());45 this->QualifiedName.reserve(QualifiedName.size());46 llvm::transform(QualifiedName, std::back_inserter(this->QualifiedName),47 [](StringRef From) { return From.str(); });48}49 50bool ento::CallDescription::matches(const CallEvent &Call) const {51 // FIXME: Add ObjC Message support.52 if (Call.getKind() == CE_ObjCMessage)53 return false;54 55 const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());56 if (!FD)57 return false;58 59 return matchesImpl(FD, Call.getNumArgs(), Call.parameters().size());60}61 62bool ento::CallDescription::matchesAsWritten(const CallExpr &CE) const {63 const auto *FD = dyn_cast_or_null<FunctionDecl>(CE.getCalleeDecl());64 if (!FD)65 return false;66 67 return matchesImpl(FD, CE.getNumArgs(), FD->param_size());68}69 70bool ento::CallDescription::matchNameOnly(const NamedDecl *ND) const {71 DeclarationName Name = ND->getDeclName();72 if (const auto *NameII = Name.getAsIdentifierInfo()) {73 if (!II)74 II = &ND->getASTContext().Idents.get(getFunctionName());75 76 return NameII == *II; // Fast case.77 }78 79 // Fallback to the slow stringification and comparison for:80 // C++ overloaded operators, constructors, destructors, etc.81 // FIXME This comparison is way SLOWER than comparing pointers.82 // At some point in the future, we should compare FunctionDecl pointers.83 return Name.getAsString() == getFunctionName();84}85 86bool ento::CallDescription::matchQualifiedNameParts(const Decl *D) const {87 const auto FindNextNamespaceOrRecord =88 [](const DeclContext *Ctx) -> const DeclContext * {89 while (Ctx && !isa<NamespaceDecl, RecordDecl>(Ctx))90 Ctx = Ctx->getParent();91 return Ctx;92 };93 94 auto QualifierPartsIt = begin_qualified_name_parts();95 const auto QualifierPartsEndIt = end_qualified_name_parts();96 97 // Match namespace and record names. Skip unrelated names if they don't98 // match.99 const DeclContext *Ctx = FindNextNamespaceOrRecord(D->getDeclContext());100 for (; Ctx && QualifierPartsIt != QualifierPartsEndIt;101 Ctx = FindNextNamespaceOrRecord(Ctx->getParent())) {102 // If not matched just continue and try matching for the next one.103 if (cast<NamedDecl>(Ctx)->getName() != *QualifierPartsIt)104 continue;105 ++QualifierPartsIt;106 }107 108 // We matched if we consumed all expected qualifier segments.109 return QualifierPartsIt == QualifierPartsEndIt;110}111 112bool ento::CallDescription::matchesImpl(const FunctionDecl *FD, size_t ArgCount,113 size_t ParamCount) const {114 if (!FD)115 return false;116 117 const bool isMethod = isa<CXXMethodDecl>(FD);118 119 if (MatchAs == Mode::SimpleFunc && isMethod)120 return false;121 122 if (MatchAs == Mode::CXXMethod && !isMethod)123 return false;124 125 if (MatchAs == Mode::CLibraryMaybeHardened) {126 // In addition to accepting FOO() with CLibrary rules, we also want to127 // accept calls to __FOO_chk() and __builtin___FOO_chk().128 if (CheckerContext::isCLibraryFunction(FD) &&129 CheckerContext::isHardenedVariantOf(FD, getFunctionName())) {130 // Check that the actual argument/parameter counts are greater or equal131 // to the required counts. (Setting a requirement to std::nullopt matches132 // anything, so in that case value_or ensures that the value is compared133 // with itself.)134 return (RequiredArgs.value_or(ArgCount) <= ArgCount &&135 RequiredParams.value_or(ParamCount) <= ParamCount);136 }137 }138 139 if (RequiredArgs.value_or(ArgCount) != ArgCount ||140 RequiredParams.value_or(ParamCount) != ParamCount)141 return false;142 143 if (MatchAs == Mode::CLibrary || MatchAs == Mode::CLibraryMaybeHardened)144 return CheckerContext::isCLibraryFunction(FD, getFunctionName());145 146 if (!matchNameOnly(FD))147 return false;148 149 if (!hasQualifiedNameParts())150 return true;151 152 return matchQualifiedNameParts(FD);153}154 155ento::CallDescriptionSet::CallDescriptionSet(156 std::initializer_list<CallDescription> &&List) {157 Impl.LinearMap.reserve(List.size());158 for (const CallDescription &CD : List)159 Impl.LinearMap.push_back({CD, /*unused*/ true});160}161 162bool ento::CallDescriptionSet::contains(const CallEvent &Call) const {163 return static_cast<bool>(Impl.lookup(Call));164}165 166bool ento::CallDescriptionSet::containsAsWritten(const CallExpr &CE) const {167 return static_cast<bool>(Impl.lookupAsWritten(CE));168}169