//===- CallDescription.cpp - function/method call matching --*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // /// \file This file defines a generic mechanism for matching for function and /// method calls of C, C++, and Objective-C languages. Instances of these /// classes are frequently used together with the CallEvent classes. // //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" #include "clang/AST/Decl.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "llvm/ADT/ArrayRef.h" #include #include using namespace llvm; using namespace clang; using MaybeCount = std::optional; // A constructor helper. static MaybeCount readRequiredParams(MaybeCount RequiredArgs, MaybeCount RequiredParams) { if (RequiredParams) return RequiredParams; if (RequiredArgs) return RequiredArgs; return std::nullopt; } ento::CallDescription::CallDescription(Mode MatchAs, ArrayRef QualifiedName, MaybeCount RequiredArgs /*= None*/, MaybeCount RequiredParams /*= None*/) : RequiredArgs(RequiredArgs), RequiredParams(readRequiredParams(RequiredArgs, RequiredParams)), MatchAs(MatchAs) { assert(!QualifiedName.empty()); this->QualifiedName.reserve(QualifiedName.size()); llvm::transform(QualifiedName, std::back_inserter(this->QualifiedName), [](StringRef From) { return From.str(); }); } bool ento::CallDescription::matches(const CallEvent &Call) const { // FIXME: Add ObjC Message support. if (Call.getKind() == CE_ObjCMessage) return false; const auto *FD = dyn_cast_or_null(Call.getDecl()); if (!FD) return false; return matchesImpl(FD, Call.getNumArgs(), Call.parameters().size()); } bool ento::CallDescription::matchesAsWritten(const CallExpr &CE) const { const auto *FD = dyn_cast_or_null(CE.getCalleeDecl()); if (!FD) return false; return matchesImpl(FD, CE.getNumArgs(), FD->param_size()); } bool ento::CallDescription::matchNameOnly(const NamedDecl *ND) const { DeclarationName Name = ND->getDeclName(); if (const auto *NameII = Name.getAsIdentifierInfo()) { if (!II) II = &ND->getASTContext().Idents.get(getFunctionName()); return NameII == *II; // Fast case. } // Fallback to the slow stringification and comparison for: // C++ overloaded operators, constructors, destructors, etc. // FIXME This comparison is way SLOWER than comparing pointers. // At some point in the future, we should compare FunctionDecl pointers. return Name.getAsString() == getFunctionName(); } bool ento::CallDescription::matchQualifiedNameParts(const Decl *D) const { const auto FindNextNamespaceOrRecord = [](const DeclContext *Ctx) -> const DeclContext * { while (Ctx && !isa(Ctx)) Ctx = Ctx->getParent(); return Ctx; }; auto QualifierPartsIt = begin_qualified_name_parts(); const auto QualifierPartsEndIt = end_qualified_name_parts(); // Match namespace and record names. Skip unrelated names if they don't // match. const DeclContext *Ctx = FindNextNamespaceOrRecord(D->getDeclContext()); for (; Ctx && QualifierPartsIt != QualifierPartsEndIt; Ctx = FindNextNamespaceOrRecord(Ctx->getParent())) { // If not matched just continue and try matching for the next one. if (cast(Ctx)->getName() != *QualifierPartsIt) continue; ++QualifierPartsIt; } // We matched if we consumed all expected qualifier segments. return QualifierPartsIt == QualifierPartsEndIt; } bool ento::CallDescription::matchesImpl(const FunctionDecl *FD, size_t ArgCount, size_t ParamCount) const { if (!FD) return false; const bool isMethod = isa(FD); if (MatchAs == Mode::SimpleFunc && isMethod) return false; if (MatchAs == Mode::CXXMethod && !isMethod) return false; if (MatchAs == Mode::CLibraryMaybeHardened) { // In addition to accepting FOO() with CLibrary rules, we also want to // accept calls to __FOO_chk() and __builtin___FOO_chk(). if (CheckerContext::isCLibraryFunction(FD) && CheckerContext::isHardenedVariantOf(FD, getFunctionName())) { // Check that the actual argument/parameter counts are greater or equal // to the required counts. (Setting a requirement to std::nullopt matches // anything, so in that case value_or ensures that the value is compared // with itself.) return (RequiredArgs.value_or(ArgCount) <= ArgCount && RequiredParams.value_or(ParamCount) <= ParamCount); } } if (RequiredArgs.value_or(ArgCount) != ArgCount || RequiredParams.value_or(ParamCount) != ParamCount) return false; if (MatchAs == Mode::CLibrary || MatchAs == Mode::CLibraryMaybeHardened) return CheckerContext::isCLibraryFunction(FD, getFunctionName()); if (!matchNameOnly(FD)) return false; if (!hasQualifiedNameParts()) return true; return matchQualifiedNameParts(FD); } ento::CallDescriptionSet::CallDescriptionSet( std::initializer_list &&List) { Impl.LinearMap.reserve(List.size()); for (const CallDescription &CD : List) Impl.LinearMap.push_back({CD, /*unused*/ true}); } bool ento::CallDescriptionSet::contains(const CallEvent &Call) const { return static_cast(Impl.lookup(Call)); } bool ento::CallDescriptionSet::containsAsWritten(const CallExpr &CE) const { return static_cast(Impl.lookupAsWritten(CE)); }