xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Core/CallDescription.cpp (revision 700637cbb5e582861067a11aaca4d053546871d2)
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