
Since CallDescriptions can only be matched against CallEvents that are created during symbolic execution, it was not possible to use it in syntactic-only contexts. For example, even though InnerPointerChecker can check with its set of CallDescriptions whether a function call is interested during analysis, its unable to check without hassle whether a non-analyzer piece of code also calls such a function. The patch adds the ability to use CallDescriptions in syntactic contexts as well. While we already have that in Signature, we still want to leverage the ability to use dynamic information when we have it (function pointers, for example). This could be done with Signature as well (StdLibraryFunctionsChecker does it), but it makes it even less of a drop-in replacement. Differential Revision: https://reviews.llvm.org/D119004
169 lines
6.1 KiB
C++
169 lines
6.1 KiB
C++
//===- 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 "llvm/ADT/Optional.h"
|
|
#include <iterator>
|
|
|
|
using namespace llvm;
|
|
using namespace clang;
|
|
|
|
using MaybeCount = Optional<unsigned>;
|
|
|
|
// A constructor helper.
|
|
static MaybeCount readRequiredParams(MaybeCount RequiredArgs,
|
|
MaybeCount RequiredParams) {
|
|
if (RequiredParams)
|
|
return RequiredParams;
|
|
if (RequiredArgs)
|
|
return RequiredArgs;
|
|
return None;
|
|
}
|
|
|
|
ento::CallDescription::CallDescription(CallDescriptionFlags Flags,
|
|
ArrayRef<const char *> QualifiedName,
|
|
MaybeCount RequiredArgs /*= None*/,
|
|
MaybeCount RequiredParams /*= None*/)
|
|
: RequiredArgs(RequiredArgs),
|
|
RequiredParams(readRequiredParams(RequiredArgs, RequiredParams)),
|
|
Flags(Flags) {
|
|
assert(!QualifiedName.empty());
|
|
this->QualifiedName.reserve(QualifiedName.size());
|
|
llvm::copy(QualifiedName, std::back_inserter(this->QualifiedName));
|
|
}
|
|
|
|
/// Construct a CallDescription with default flags.
|
|
ento::CallDescription::CallDescription(ArrayRef<const char *> QualifiedName,
|
|
MaybeCount RequiredArgs /*= None*/,
|
|
MaybeCount RequiredParams /*= None*/)
|
|
: CallDescription(CDF_None, QualifiedName, RequiredArgs, RequiredParams) {}
|
|
|
|
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<FunctionDecl>(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<FunctionDecl>(CE.getCalleeDecl());
|
|
if (!FD)
|
|
return false;
|
|
|
|
return matchesImpl(FD, CE.getNumArgs(), FD->param_size());
|
|
}
|
|
|
|
bool ento::CallDescription::matchesImpl(const FunctionDecl *Callee,
|
|
size_t ArgCount,
|
|
size_t ParamCount) const {
|
|
const auto *FD = Callee;
|
|
if (!FD)
|
|
return false;
|
|
|
|
if (Flags & CDF_MaybeBuiltin) {
|
|
return CheckerContext::isCLibraryFunction(FD, getFunctionName()) &&
|
|
(!RequiredArgs || *RequiredArgs <= ArgCount) &&
|
|
(!RequiredParams || *RequiredParams <= ParamCount);
|
|
}
|
|
|
|
if (!II.hasValue()) {
|
|
II = &FD->getASTContext().Idents.get(getFunctionName());
|
|
}
|
|
|
|
const auto MatchNameOnly = [](const CallDescription &CD,
|
|
const NamedDecl *ND) -> bool {
|
|
DeclarationName Name = ND->getDeclName();
|
|
if (const auto *II = Name.getAsIdentifierInfo())
|
|
return II == CD.II.getValue(); // 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() == CD.getFunctionName();
|
|
};
|
|
|
|
const auto ExactMatchArgAndParamCounts =
|
|
[](size_t ArgCount, size_t ParamCount,
|
|
const CallDescription &CD) -> bool {
|
|
const bool ArgsMatch = !CD.RequiredArgs || *CD.RequiredArgs == ArgCount;
|
|
const bool ParamsMatch =
|
|
!CD.RequiredParams || *CD.RequiredParams == ParamCount;
|
|
return ArgsMatch && ParamsMatch;
|
|
};
|
|
|
|
const auto MatchQualifiedNameParts = [](const CallDescription &CD,
|
|
const Decl *D) -> bool {
|
|
const auto FindNextNamespaceOrRecord =
|
|
[](const DeclContext *Ctx) -> const DeclContext * {
|
|
while (Ctx && !isa<NamespaceDecl, RecordDecl>(Ctx))
|
|
Ctx = Ctx->getParent();
|
|
return Ctx;
|
|
};
|
|
|
|
auto QualifierPartsIt = CD.begin_qualified_name_parts();
|
|
const auto QualifierPartsEndIt = CD.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<NamedDecl>(Ctx)->getName() != *QualifierPartsIt)
|
|
continue;
|
|
++QualifierPartsIt;
|
|
}
|
|
|
|
// We matched if we consumed all expected qualifier segments.
|
|
return QualifierPartsIt == QualifierPartsEndIt;
|
|
};
|
|
|
|
// Let's start matching...
|
|
if (!ExactMatchArgAndParamCounts(ArgCount, ParamCount, *this))
|
|
return false;
|
|
|
|
if (!MatchNameOnly(*this, FD))
|
|
return false;
|
|
|
|
if (!hasQualifiedNameParts())
|
|
return true;
|
|
|
|
return MatchQualifiedNameParts(*this, FD);
|
|
}
|
|
|
|
ento::CallDescriptionSet::CallDescriptionSet(
|
|
std::initializer_list<CallDescription> &&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<bool>(Impl.lookup(Call));
|
|
}
|
|
|
|
bool ento::CallDescriptionSet::containsAsWritten(const CallExpr &CE) const {
|
|
return static_cast<bool>(Impl.lookupAsWritten(CE));
|
|
}
|