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 <iterator>
20 #include <optional>
21
22 using namespace llvm;
23 using namespace clang;
24
25 using MaybeCount = std::optional<unsigned>;
26
27 // A constructor helper.
readRequiredParams(MaybeCount RequiredArgs,MaybeCount RequiredParams)28 static 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
CallDescription(Mode MatchAs,ArrayRef<StringRef> QualifiedName,MaybeCount RequiredArgs,MaybeCount RequiredParams)37 ento::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
matches(const CallEvent & Call) const50 bool 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
matchesAsWritten(const CallExpr & CE) const62 bool 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
matchNameOnly(const NamedDecl * ND) const70 bool 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
matchQualifiedNameParts(const Decl * D) const86 bool 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't
98 // 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
matchesImpl(const FunctionDecl * FD,size_t ArgCount,size_t ParamCount) const112 bool 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 to
127 // 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 equal
131 // to the required counts. (Setting a requirement to std::nullopt matches
132 // anything, so in that case value_or ensures that the value is compared
133 // 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
CallDescriptionSet(std::initializer_list<CallDescription> && List)155 ento::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
contains(const CallEvent & Call) const162 bool ento::CallDescriptionSet::contains(const CallEvent &Call) const {
163 return static_cast<bool>(Impl.lookup(Call));
164 }
165
containsAsWritten(const CallExpr & CE) const166 bool ento::CallDescriptionSet::containsAsWritten(const CallExpr &CE) const {
167 return static_cast<bool>(Impl.lookupAsWritten(CE));
168 }
169