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