xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CallDescription.cpp (revision 0fca6ea1d4eea4c934cfff25ac9ee8ad6fe95583)
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.
readRequiredParams(MaybeCount RequiredArgs,MaybeCount RequiredParams)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 
CallDescription(Mode MatchAs,ArrayRef<StringRef> QualifiedName,MaybeCount RequiredArgs,MaybeCount RequiredParams)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 
matches(const CallEvent & Call) const51 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 
matchesAsWritten(const CallExpr & CE) const63 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 
matchNameOnly(const NamedDecl * ND) const71 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 
matchQualifiedNameParts(const Decl * D) const87 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 
matchesImpl(const FunctionDecl * FD,size_t ArgCount,size_t ParamCount) const113 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 
CallDescriptionSet(std::initializer_list<CallDescription> && List)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 
contains(const CallEvent & Call) const163 bool ento::CallDescriptionSet::contains(const CallEvent &Call) const {
164   return static_cast<bool>(Impl.lookup(Call));
165 }
166 
containsAsWritten(const CallExpr & CE) const167 bool ento::CallDescriptionSet::containsAsWritten(const CallExpr &CE) const {
168   return static_cast<bool>(Impl.lookupAsWritten(CE));
169 }
170