xref: /openbsd-src/gnu/llvm/clang/lib/StaticAnalyzer/Core/CallDescription.cpp (revision 12c855180aad702bbcca06e0398d774beeafb155)
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(CallDescriptionFlags Flags,ArrayRef<StringRef> QualifiedName,MaybeCount RequiredArgs,MaybeCount RequiredParams)38 ento::CallDescription::CallDescription(CallDescriptionFlags Flags,
39                                        ArrayRef<StringRef> QualifiedName,
40                                        MaybeCount RequiredArgs /*= None*/,
41                                        MaybeCount RequiredParams /*= None*/)
42     : RequiredArgs(RequiredArgs),
43       RequiredParams(readRequiredParams(RequiredArgs, RequiredParams)),
44       Flags(Flags) {
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 /// Construct a CallDescription with default flags.
CallDescription(ArrayRef<StringRef> QualifiedName,MaybeCount RequiredArgs,MaybeCount RequiredParams)52 ento::CallDescription::CallDescription(ArrayRef<StringRef> QualifiedName,
53                                        MaybeCount RequiredArgs /*= None*/,
54                                        MaybeCount RequiredParams /*= None*/)
55     : CallDescription(CDF_None, QualifiedName, RequiredArgs, RequiredParams) {}
56 
matches(const CallEvent & Call) const57 bool ento::CallDescription::matches(const CallEvent &Call) const {
58   // FIXME: Add ObjC Message support.
59   if (Call.getKind() == CE_ObjCMessage)
60     return false;
61 
62   const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
63   if (!FD)
64     return false;
65 
66   return matchesImpl(FD, Call.getNumArgs(), Call.parameters().size());
67 }
68 
matchesAsWritten(const CallExpr & CE) const69 bool ento::CallDescription::matchesAsWritten(const CallExpr &CE) const {
70   const auto *FD = dyn_cast_or_null<FunctionDecl>(CE.getCalleeDecl());
71   if (!FD)
72     return false;
73 
74   return matchesImpl(FD, CE.getNumArgs(), FD->param_size());
75 }
76 
matchesImpl(const FunctionDecl * Callee,size_t ArgCount,size_t ParamCount) const77 bool ento::CallDescription::matchesImpl(const FunctionDecl *Callee,
78                                         size_t ArgCount,
79                                         size_t ParamCount) const {
80   const auto *FD = Callee;
81   if (!FD)
82     return false;
83 
84   if (Flags & CDF_MaybeBuiltin) {
85     return CheckerContext::isCLibraryFunction(FD, getFunctionName()) &&
86            (!RequiredArgs || *RequiredArgs <= ArgCount) &&
87            (!RequiredParams || *RequiredParams <= ParamCount);
88   }
89 
90   if (!II) {
91     II = &FD->getASTContext().Idents.get(getFunctionName());
92   }
93 
94   const auto MatchNameOnly = [](const CallDescription &CD,
95                                 const NamedDecl *ND) -> bool {
96     DeclarationName Name = ND->getDeclName();
97     if (const auto *II = Name.getAsIdentifierInfo())
98       return II == *CD.II; // Fast case.
99 
100     // Fallback to the slow stringification and comparison for:
101     // C++ overloaded operators, constructors, destructors, etc.
102     // FIXME This comparison is way SLOWER than comparing pointers.
103     // At some point in the future, we should compare FunctionDecl pointers.
104     return Name.getAsString() == CD.getFunctionName();
105   };
106 
107   const auto ExactMatchArgAndParamCounts =
108       [](size_t ArgCount, size_t ParamCount,
109          const CallDescription &CD) -> bool {
110     const bool ArgsMatch = !CD.RequiredArgs || *CD.RequiredArgs == ArgCount;
111     const bool ParamsMatch =
112         !CD.RequiredParams || *CD.RequiredParams == ParamCount;
113     return ArgsMatch && ParamsMatch;
114   };
115 
116   const auto MatchQualifiedNameParts = [](const CallDescription &CD,
117                                           const Decl *D) -> bool {
118     const auto FindNextNamespaceOrRecord =
119         [](const DeclContext *Ctx) -> const DeclContext * {
120       while (Ctx && !isa<NamespaceDecl, RecordDecl>(Ctx))
121         Ctx = Ctx->getParent();
122       return Ctx;
123     };
124 
125     auto QualifierPartsIt = CD.begin_qualified_name_parts();
126     const auto QualifierPartsEndIt = CD.end_qualified_name_parts();
127 
128     // Match namespace and record names. Skip unrelated names if they don't
129     // match.
130     const DeclContext *Ctx = FindNextNamespaceOrRecord(D->getDeclContext());
131     for (; Ctx && QualifierPartsIt != QualifierPartsEndIt;
132          Ctx = FindNextNamespaceOrRecord(Ctx->getParent())) {
133       // If not matched just continue and try matching for the next one.
134       if (cast<NamedDecl>(Ctx)->getName() != *QualifierPartsIt)
135         continue;
136       ++QualifierPartsIt;
137     }
138 
139     // We matched if we consumed all expected qualifier segments.
140     return QualifierPartsIt == QualifierPartsEndIt;
141   };
142 
143   // Let's start matching...
144   if (!ExactMatchArgAndParamCounts(ArgCount, ParamCount, *this))
145     return false;
146 
147   if (!MatchNameOnly(*this, FD))
148     return false;
149 
150   if (!hasQualifiedNameParts())
151     return true;
152 
153   return MatchQualifiedNameParts(*this, 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