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
23using namespace llvm;
24using namespace clang;
25
26using MaybeCount = std::optional<unsigned>;
27
28// A constructor helper.
29static 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
38ento::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(n: QualifiedName.size());
47 llvm::transform(Range&: QualifiedName, d_first: std::back_inserter(x&: this->QualifiedName),
48 F: [](StringRef From) { return From.str(); });
49}
50
51bool 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>(Val: Call.getDecl());
57 if (!FD)
58 return false;
59
60 return matchesImpl(Callee: FD, ArgCount: Call.getNumArgs(), ParamCount: Call.parameters().size());
61}
62
63bool ento::CallDescription::matchesAsWritten(const CallExpr &CE) const {
64 const auto *FD = dyn_cast_or_null<FunctionDecl>(Val: CE.getCalleeDecl());
65 if (!FD)
66 return false;
67
68 return matchesImpl(Callee: FD, ArgCount: CE.getNumArgs(), ParamCount: FD->param_size());
69}
70
71bool 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(Name: 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
87bool ento::CallDescription::matchQualifiedNameParts(const Decl *D) const {
88 const auto FindNextNamespaceOrRecord =
89 [](const DeclContext *Ctx) -> const DeclContext * {
90 while (Ctx && !isa<NamespaceDecl, RecordDecl>(Val: 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>(Val: 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
113bool 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>(Val: 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, Name: 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(u&: ArgCount) <= ArgCount &&
136 RequiredParams.value_or(u&: ParamCount) <= ParamCount);
137 }
138 }
139
140 if (RequiredArgs.value_or(u&: ArgCount) != ArgCount ||
141 RequiredParams.value_or(u&: ParamCount) != ParamCount)
142 return false;
143
144 if (MatchAs == Mode::CLibrary || MatchAs == Mode::CLibraryMaybeHardened)
145 return CheckerContext::isCLibraryFunction(FD, Name: getFunctionName());
146
147 if (!matchNameOnly(ND: FD))
148 return false;
149
150 if (!hasQualifiedNameParts())
151 return true;
152
153 return matchQualifiedNameParts(D: FD);
154}
155
156ento::CallDescriptionSet::CallDescriptionSet(
157 std::initializer_list<CallDescription> &&List) {
158 Impl.LinearMap.reserve(n: List.size());
159 for (const CallDescription &CD : List)
160 Impl.LinearMap.push_back(x: {CD, /*unused*/ true});
161}
162
163bool ento::CallDescriptionSet::contains(const CallEvent &Call) const {
164 return static_cast<bool>(Impl.lookup(Call));
165}
166
167bool ento::CallDescriptionSet::containsAsWritten(const CallExpr &CE) const {
168 return static_cast<bool>(Impl.lookupAsWritten(Call: CE));
169}
170