[clang][ssaf] Add CallGraph summary and extractor (#188753)
rdar://170258016
This commit is contained in:
parent
0ef10d62d7
commit
d08ebbe8eb
@ -0,0 +1,53 @@
|
||||
//===- CallGraphSummary.h ---------------------------------------*- 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_CALLGRAPH_CALLGRAPHSUMMARY_H
|
||||
#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_CALLGRAPH_CALLGRAPHSUMMARY_H
|
||||
|
||||
#include "clang/ScalableStaticAnalysisFramework/Core/Model/EntityId.h"
|
||||
#include "clang/ScalableStaticAnalysisFramework/Core/Model/SummaryName.h"
|
||||
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/EntitySummary.h"
|
||||
#include <set>
|
||||
|
||||
namespace clang::ssaf {
|
||||
|
||||
/// Summary of direct call-graph edges for a single function entity.
|
||||
///
|
||||
/// Represents a function definition, and information about its callees.
|
||||
///
|
||||
/// \bug Indirect calls (e.g. function pointers) are not represented.
|
||||
/// \bug ObjCMessageExprs are not represented.
|
||||
/// \bug Primary template functions are not represented.
|
||||
struct CallGraphSummary final : public EntitySummary {
|
||||
struct Location {
|
||||
std::string File;
|
||||
unsigned Line;
|
||||
unsigned Column;
|
||||
};
|
||||
|
||||
SummaryName getSummaryName() const override {
|
||||
return SummaryName("CallGraph");
|
||||
}
|
||||
|
||||
/// Represents the location of the function.
|
||||
Location Definition = {};
|
||||
|
||||
/// The set of direct callees of this function.
|
||||
std::set<EntityId> DirectCallees;
|
||||
|
||||
/// The set of virtual callees of this function.
|
||||
std::set<EntityId> VirtualCallees;
|
||||
|
||||
/// A human-readable name of the function.
|
||||
/// This is not guaranteed to be accurate or unique.
|
||||
std::string PrettyName;
|
||||
};
|
||||
|
||||
} // namespace clang::ssaf
|
||||
|
||||
#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_ANALYSES_CALLGRAPH_CALLGRAPHSUMMARY_H
|
||||
@ -20,6 +20,8 @@
|
||||
#ifndef LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_SSAFBUILTINFORCELINKER_H
|
||||
#define LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_SSAFBUILTINFORCELINKER_H
|
||||
|
||||
// TODO: Move these to the `clang::ssaf` namespace.
|
||||
|
||||
// This anchor is used to force the linker to link the JSONFormat registration.
|
||||
extern volatile int SSAFJSONFormatAnchorSource;
|
||||
[[maybe_unused]] static int SSAFJSONFormatAnchorDestination =
|
||||
@ -30,4 +32,9 @@ extern volatile int SSAFAnalysisRegistryAnchorSource;
|
||||
[[maybe_unused]] static int SSAFAnalysisRegistryAnchorDestination =
|
||||
SSAFAnalysisRegistryAnchorSource;
|
||||
|
||||
// This anchor is used to force the linker to link the CallGraphExtractor.
|
||||
extern volatile int CallGraphExtractorAnchorSource;
|
||||
[[maybe_unused]] static int CallGraphExtractorAnchorDestination =
|
||||
CallGraphExtractorAnchorSource;
|
||||
|
||||
#endif // LLVM_CLANG_SCALABLESTATICANALYSISFRAMEWORK_SSAFBUILTINFORCELINKER_H
|
||||
|
||||
@ -110,6 +110,7 @@ add_clang_library(clangDriver
|
||||
clangBasic
|
||||
clangDependencyScanning
|
||||
clangFrontend
|
||||
clangScalableStaticAnalysisFrameworkAnalyses
|
||||
clangScalableStaticAnalysisFrameworkCore
|
||||
clangScalableStaticAnalysisFrameworkFrontend
|
||||
clangSerialization
|
||||
|
||||
@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
|
||||
)
|
||||
|
||||
set(link_libs
|
||||
clangScalableStaticAnalysisFrameworkAnalyses
|
||||
clangScalableStaticAnalysisFrameworkCore
|
||||
clangScalableStaticAnalysisFrameworkFrontend
|
||||
clangBasic
|
||||
|
||||
@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS
|
||||
)
|
||||
|
||||
add_clang_library(clangScalableStaticAnalysisFrameworkAnalyses
|
||||
CallGraph/CallGraphExtractor.cpp
|
||||
UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp
|
||||
|
||||
LINK_LIBS
|
||||
|
||||
@ -0,0 +1,106 @@
|
||||
//===- CallGraphExtractor.cpp - Call Graph Summary Extractor --------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/AST/DeclCXX.h"
|
||||
#include "clang/AST/DeclObjC.h"
|
||||
#include "clang/Analysis/AnalysisDeclContext.h"
|
||||
#include "clang/Analysis/CallGraph.h"
|
||||
#include "clang/Basic/SourceManager.h"
|
||||
#include "clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h"
|
||||
#include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
|
||||
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h"
|
||||
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryBuilder.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include <memory>
|
||||
|
||||
using namespace clang;
|
||||
using namespace ssaf;
|
||||
|
||||
namespace {
|
||||
class CallGraphExtractor final : public TUSummaryExtractor {
|
||||
public:
|
||||
using TUSummaryExtractor::TUSummaryExtractor;
|
||||
|
||||
private:
|
||||
void HandleTranslationUnit(ASTContext &Ctx) override;
|
||||
|
||||
void handleCallGraphNode(const ASTContext &Ctx, const CallGraphNode *N);
|
||||
};
|
||||
} // namespace
|
||||
|
||||
void CallGraphExtractor::HandleTranslationUnit(ASTContext &Ctx) {
|
||||
CallGraph CG;
|
||||
CG.addToCallGraph(
|
||||
const_cast<TranslationUnitDecl *>(Ctx.getTranslationUnitDecl()));
|
||||
|
||||
for (const auto &N : llvm::make_second_range(CG)) {
|
||||
if (N && N->getDecl() && N->getDefinition())
|
||||
handleCallGraphNode(Ctx, N.get());
|
||||
}
|
||||
}
|
||||
|
||||
void CallGraphExtractor::handleCallGraphNode(const ASTContext &Ctx,
|
||||
const CallGraphNode *N) {
|
||||
const FunctionDecl *Definition = N->getDefinition();
|
||||
|
||||
// FIXME: `clang::CallGraph` does not create entries for primary templates.
|
||||
assert(!Definition->isTemplated());
|
||||
|
||||
auto CallerName = getEntityName(Definition);
|
||||
if (!CallerName)
|
||||
return;
|
||||
|
||||
auto FnSummary = std::make_unique<CallGraphSummary>();
|
||||
|
||||
PresumedLoc Loc =
|
||||
Ctx.getSourceManager().getPresumedLoc(Definition->getLocation());
|
||||
FnSummary->Definition.File = Loc.getFilename();
|
||||
FnSummary->Definition.Line = Loc.getLine();
|
||||
FnSummary->Definition.Column = Loc.getColumn();
|
||||
FnSummary->PrettyName = AnalysisDeclContext::getFunctionName(Definition);
|
||||
|
||||
for (const auto &Record : N->callees()) {
|
||||
const Decl *CalleeDecl = Record.Callee->getDecl();
|
||||
|
||||
// FIXME: `clang::CallGraph` does not consider indirect calls, thus this is
|
||||
// never null.
|
||||
assert(CalleeDecl);
|
||||
|
||||
// FIXME: `clang::CallGraph` does not consider ObjCMessageExprs as calls.
|
||||
// Consequently, they don't appear as a Callee.
|
||||
assert(!isa<ObjCMethodDecl>(CalleeDecl));
|
||||
|
||||
// FIXME: `clang::CallGraph` does not create entries for primary templates.
|
||||
assert(!CalleeDecl->isTemplated());
|
||||
|
||||
auto CalleeName = getEntityName(CalleeDecl);
|
||||
if (!CalleeName)
|
||||
continue;
|
||||
|
||||
EntityId CalleeId = SummaryBuilder.addEntity(*CalleeName);
|
||||
if (const auto *MD = dyn_cast_or_null<CXXMethodDecl>(CalleeDecl);
|
||||
MD && MD->isVirtual()) {
|
||||
FnSummary->VirtualCallees.insert(CalleeId);
|
||||
continue;
|
||||
}
|
||||
FnSummary->DirectCallees.insert(CalleeId);
|
||||
}
|
||||
|
||||
EntityId CallerId = SummaryBuilder.addEntity(*CallerName);
|
||||
SummaryBuilder.addSummary(CallerId, std::move(FnSummary));
|
||||
}
|
||||
|
||||
static TUSummaryExtractorRegistry::Add<CallGraphExtractor>
|
||||
RegisterExtractor("CallGraph", "Extracts static call-graph information");
|
||||
|
||||
// This anchor is used to force the linker to link in the generated object file
|
||||
// and thus register the CallGraphExtractor.
|
||||
// NOLINTNEXTLINE(misc-use-internal-linkage)
|
||||
volatile int CallGraphExtractorAnchorSource = 0;
|
||||
@ -10,6 +10,7 @@ add_clang_tool(clang-ssaf-format
|
||||
clang_target_link_libraries(clang-ssaf-format
|
||||
PRIVATE
|
||||
clangBasic
|
||||
clangScalableStaticAnalysisFrameworkAnalyses
|
||||
clangScalableStaticAnalysisFrameworkCore
|
||||
clangScalableStaticAnalysisFrameworkTool
|
||||
)
|
||||
|
||||
@ -10,6 +10,7 @@ add_clang_tool(clang-ssaf-linker
|
||||
clang_target_link_libraries(clang-ssaf-linker
|
||||
PRIVATE
|
||||
clangBasic
|
||||
clangScalableStaticAnalysisFrameworkAnalyses
|
||||
clangScalableStaticAnalysisFrameworkCore
|
||||
clangScalableStaticAnalysisFrameworkTool
|
||||
)
|
||||
|
||||
@ -0,0 +1,470 @@
|
||||
//===- CallGraphExtractorTest.cpp -----------------------------------------===//
|
||||
//
|
||||
// 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
|
||||
//
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#include "TestFixture.h"
|
||||
#include "clang/AST/ASTContext.h"
|
||||
#include "clang/AST/Decl.h"
|
||||
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
||||
#include "clang/ASTMatchers/ASTMatchers.h"
|
||||
#include "clang/ScalableStaticAnalysisFramework/Analyses/CallGraph/CallGraphSummary.h"
|
||||
#include "clang/ScalableStaticAnalysisFramework/Core/ASTEntityMapping.h"
|
||||
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/ExtractorRegistry.h"
|
||||
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummary.h"
|
||||
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummaryBuilder.h"
|
||||
#include "clang/Tooling/Tooling.h"
|
||||
#include "llvm/ADT/ArrayRef.h"
|
||||
#include "llvm/ADT/STLExtras.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include "llvm/Testing/Support/Error.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include <cassert>
|
||||
|
||||
using namespace clang;
|
||||
using namespace ssaf;
|
||||
|
||||
namespace {
|
||||
AST_MATCHER(FunctionDecl, isPrimaryTemplate) {
|
||||
return Node.getDescribedFunctionTemplate() != nullptr;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
static llvm::Expected<const NamedDecl *> findDecl(ASTContext &Ctx,
|
||||
StringRef FnName) {
|
||||
using namespace ast_matchers;
|
||||
auto Matcher =
|
||||
functionDecl(hasName(FnName), unless(isPrimaryTemplate())).bind("decl");
|
||||
auto Matches = match(Matcher, Ctx);
|
||||
if (Matches.empty())
|
||||
return llvm::createStringError("No definition was found with name '" +
|
||||
FnName + "'");
|
||||
auto *ND = Matches[0].template getNodeAs<NamedDecl>("decl");
|
||||
assert(ND);
|
||||
return cast<NamedDecl>(ND->getCanonicalDecl());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PrintTo overload for readable failure messages.
|
||||
// Must live in the same namespace as Location (clang::ssaf) for ADL.
|
||||
// ============================================================================
|
||||
|
||||
namespace clang::ssaf {
|
||||
void PrintTo(const CallGraphSummary::Location &Loc, std::ostream *OS) {
|
||||
*OS << Loc.File << ":" << Loc.Line << ":" << Loc.Column;
|
||||
}
|
||||
void PrintTo(const CallGraphSummary &S, std::ostream *OS) {
|
||||
*OS << "CallGraphSummary { PrettyName: '" << S.PrettyName << "'"
|
||||
<< ", Definition: ";
|
||||
PrintTo(S.Definition, OS);
|
||||
*OS << ", DirectCallees: " << S.DirectCallees.size()
|
||||
<< ", VirtualCallees: " << S.VirtualCallees.size() << " }";
|
||||
}
|
||||
} // namespace clang::ssaf
|
||||
|
||||
namespace {
|
||||
|
||||
MATCHER_P3(DefinedAt, File, Line, Column,
|
||||
std::string(negation ? "is not" : "is") + " defined at " +
|
||||
std::string(File) + ":" + testing::PrintToString(Line) + ":" +
|
||||
testing::PrintToString(Column)) {
|
||||
const auto &D = arg.Definition;
|
||||
if (D.File != File || D.Line != Line || D.Column != Column) {
|
||||
*result_listener << "defined at " << D.File << ":" << D.Line << ":"
|
||||
<< D.Column;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MATCHER_P(HasPrettyName, Name,
|
||||
std::string(negation ? "doesn't have" : "has") + " pretty name '" +
|
||||
testing::PrintToString(Name) + "'") {
|
||||
if (arg.PrettyName != std::string(Name)) {
|
||||
*result_listener << "has pretty name '" << arg.PrettyName << "'";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MATCHER(HasNoDirectCallees,
|
||||
std::string(negation ? "has" : "has no") + " direct callees") {
|
||||
if (!arg.DirectCallees.empty()) {
|
||||
*result_listener << "has " << arg.DirectCallees.size()
|
||||
<< " direct callee(s)";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
MATCHER(HasNoVirtualCallees,
|
||||
std::string(negation ? "has" : "has no") + " virtual callees") {
|
||||
if (!arg.VirtualCallees.empty()) {
|
||||
*result_listener << "has " << arg.VirtualCallees.size()
|
||||
<< " virtual callee(s)";
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename... Matchers> auto hasSummaryThat(const Matchers &...Ms) {
|
||||
using namespace testing;
|
||||
return llvm::HasValue(Pointee(AllOf(std::move(Ms)...)));
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Test fixture
|
||||
// ============================================================================
|
||||
|
||||
struct CallGraphExtractorTest : ssaf::TestFixture {
|
||||
TUSummary Summary =
|
||||
BuildNamespace(BuildNamespaceKind::CompilationUnit, "Mock.cpp");
|
||||
TUSummaryBuilder Builder = TUSummaryBuilder(Summary);
|
||||
|
||||
/// Creates the AST and extractor, then extracts the summaries from the AST.
|
||||
/// This will update the \c AST \c Builder and \c Summary data members.
|
||||
void runExtractor(StringRef Code, ArrayRef<std::string> Args = {}) {
|
||||
AST = tooling::buildASTFromCodeWithArgs(Code, Args);
|
||||
auto Consumer = makeTUSummaryExtractor("CallGraph", Builder);
|
||||
Consumer->HandleTranslationUnit(AST->getASTContext());
|
||||
}
|
||||
|
||||
/// Tries to find the \c CallGraphSummary for the \p FnName function.
|
||||
llvm::Expected<const CallGraphSummary *>
|
||||
findSummary(llvm::StringRef FnName) const;
|
||||
|
||||
/// Matcher factory: matches a summary whose direct callees are exactly the
|
||||
/// given set of function names (resolved to USRs via the entity table).
|
||||
/// Uses \c testing::ResultOf to transform the summary's EntityId set into
|
||||
/// USR strings before comparing with \c testing::ContainerEq.
|
||||
auto hasDirectCallees(llvm::ArrayRef<StringRef> Names)
|
||||
-> testing::Matcher<const CallGraphSummary &> {
|
||||
auto MaybeUSRs = asUSRs(Names);
|
||||
if (!MaybeUSRs) {
|
||||
ADD_FAILURE() << "Failed to resolve callee names to USRs: "
|
||||
<< llvm::toString(MaybeUSRs.takeError());
|
||||
return testing::An<const CallGraphSummary &>();
|
||||
}
|
||||
std::set<std::string> ExpectedUSRs = std::move(*MaybeUSRs);
|
||||
return testing::ResultOf(
|
||||
"direct callees",
|
||||
[this](const CallGraphSummary &S) {
|
||||
return getUSRsForCallees(S.DirectCallees);
|
||||
},
|
||||
testing::ContainerEq(ExpectedUSRs));
|
||||
}
|
||||
|
||||
/// Matcher factory: same as \c hasDirectCallees but for virtual callees.
|
||||
auto hasVirtualCallees(llvm::ArrayRef<StringRef> Names)
|
||||
-> testing::Matcher<const CallGraphSummary &> {
|
||||
auto MaybeUSRs = asUSRs(Names);
|
||||
if (!MaybeUSRs) {
|
||||
ADD_FAILURE() << "Failed to resolve callee names to USRs: "
|
||||
<< llvm::toString(MaybeUSRs.takeError());
|
||||
return testing::A<const CallGraphSummary &>();
|
||||
}
|
||||
std::set<std::string> ExpectedUSRs = std::move(*MaybeUSRs);
|
||||
return testing::ResultOf(
|
||||
"virtual callees",
|
||||
[this](const CallGraphSummary &S) {
|
||||
return getUSRsForCallees(S.VirtualCallees);
|
||||
},
|
||||
testing::ContainerEq(ExpectedUSRs));
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<ASTUnit> AST;
|
||||
|
||||
std::set<std::string>
|
||||
getUSRsForCallees(const std::set<EntityId> &Callees) const;
|
||||
|
||||
/// Looks up the Decls for \p FnNames, and then transforms those into USRs.
|
||||
llvm::Expected<std::set<std::string>>
|
||||
asUSRs(llvm::ArrayRef<StringRef> FnNames);
|
||||
};
|
||||
|
||||
llvm::Expected<const CallGraphSummary *>
|
||||
CallGraphExtractorTest::findSummary(llvm::StringRef FnName) const {
|
||||
auto MaybeDecl = findDecl(AST->getASTContext(), FnName);
|
||||
if (!MaybeDecl)
|
||||
return MaybeDecl.takeError();
|
||||
|
||||
std::optional<EntityName> EntName = getEntityName(*MaybeDecl);
|
||||
if (!EntName.has_value()) {
|
||||
return llvm::createStringError("Failed to create an entity name for '" +
|
||||
FnName + "'");
|
||||
}
|
||||
|
||||
const auto &EntitiesTable = getEntities(getIdTable(Summary));
|
||||
auto It = EntitiesTable.find(EntName.value());
|
||||
if (It == EntitiesTable.end()) {
|
||||
return llvm::createStringError(
|
||||
"No entity ID was present in the entity table for '" + FnName + "'");
|
||||
}
|
||||
EntityId ID = It->second;
|
||||
auto &Data = getData(Summary);
|
||||
auto SummaryIt = Data.find(SummaryName("CallGraph"));
|
||||
if (SummaryIt == Data.end())
|
||||
return llvm::createStringError("There is no 'CallGraph' summary");
|
||||
auto EntityIt = SummaryIt->second.find(ID);
|
||||
if (EntityIt == SummaryIt->second.end()) {
|
||||
return llvm::createStringError(
|
||||
"There is no 'CallGraph' summary for entity ID " +
|
||||
std::to_string(getIndex(ID)) + " aka. '" + FnName + "'");
|
||||
}
|
||||
return static_cast<const CallGraphSummary *>(EntityIt->second.get());
|
||||
}
|
||||
|
||||
std::set<std::string> CallGraphExtractorTest::getUSRsForCallees(
|
||||
const std::set<EntityId> &Callees) const {
|
||||
std::set<std::string> USRs;
|
||||
|
||||
auto GatherCalleeUSRs = [&](const EntityName &Name, EntityId Id) {
|
||||
if (llvm::is_contained(Callees, Id))
|
||||
USRs.insert(TestFixture::getUSR(Name));
|
||||
};
|
||||
TestFixture::getIdTable(Summary).forEach(GatherCalleeUSRs);
|
||||
assert(Callees.size() == USRs.size());
|
||||
return USRs;
|
||||
}
|
||||
|
||||
llvm::Expected<std::set<std::string>>
|
||||
CallGraphExtractorTest::asUSRs(llvm::ArrayRef<StringRef> FnNames) {
|
||||
std::set<std::string> USRs;
|
||||
ASTContext &Ctx = AST->getASTContext();
|
||||
for (StringRef FnName : FnNames) {
|
||||
auto MaybeDecl = findDecl(Ctx, FnName);
|
||||
if (!MaybeDecl)
|
||||
return MaybeDecl.takeError();
|
||||
std::optional<EntityName> Name = getEntityName(MaybeDecl.get());
|
||||
if (!Name.has_value()) {
|
||||
return llvm::createStringError("Failed to get the USR of '" + FnName +
|
||||
"'");
|
||||
}
|
||||
USRs.insert(getUSR(Name.value()));
|
||||
}
|
||||
assert(USRs.size() == FnNames.size());
|
||||
return USRs;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Tests
|
||||
// ============================================================================
|
||||
|
||||
TEST_F(CallGraphExtractorTest, SimpleFunctionCalls) {
|
||||
runExtractor(R"cpp(
|
||||
void a();
|
||||
void b();
|
||||
void calls_a_and_b(bool coin) {
|
||||
if (coin)
|
||||
a();
|
||||
else
|
||||
b();
|
||||
}
|
||||
)cpp");
|
||||
|
||||
ASSERT_THAT_EXPECTED(
|
||||
findSummary("calls_a_and_b"),
|
||||
hasSummaryThat(hasDirectCallees({"a", "b"}), HasNoVirtualCallees()));
|
||||
}
|
||||
|
||||
TEST_F(CallGraphExtractorTest, NoCallees) {
|
||||
runExtractor(R"cpp(
|
||||
void leaf() {}
|
||||
)cpp");
|
||||
|
||||
ASSERT_THAT_EXPECTED(
|
||||
findSummary("leaf"),
|
||||
hasSummaryThat(HasNoDirectCallees(), HasNoVirtualCallees()));
|
||||
}
|
||||
|
||||
TEST_F(CallGraphExtractorTest, TransitiveCalls) {
|
||||
runExtractor(R"cpp(
|
||||
void c() { /*empty*/ }
|
||||
void b() { c(); }
|
||||
void a() { b(); }
|
||||
)cpp");
|
||||
|
||||
// a calls b (not c — we only record direct callees).
|
||||
ASSERT_THAT_EXPECTED(findSummary("a"), hasSummaryThat(hasDirectCallees({"b"}),
|
||||
HasNoVirtualCallees()));
|
||||
|
||||
// b calls c.
|
||||
ASSERT_THAT_EXPECTED(findSummary("b"), hasSummaryThat(hasDirectCallees({"c"}),
|
||||
HasNoVirtualCallees()));
|
||||
|
||||
// c calls nothing.
|
||||
ASSERT_THAT_EXPECTED(findSummary("c"), hasSummaryThat(HasNoDirectCallees(),
|
||||
HasNoVirtualCallees()));
|
||||
}
|
||||
|
||||
TEST_F(CallGraphExtractorTest, VirtualCallsAreImprecise) {
|
||||
runExtractor(R"cpp(
|
||||
struct Base {
|
||||
virtual void virt();
|
||||
};
|
||||
struct Derived : Base {
|
||||
void virt() override;
|
||||
};
|
||||
void caller(Base &Obj) {
|
||||
Obj.virt();
|
||||
}
|
||||
)cpp");
|
||||
|
||||
ASSERT_THAT_EXPECTED(
|
||||
findSummary("caller"),
|
||||
hasSummaryThat(HasNoDirectCallees(), hasVirtualCallees({"Base::virt"})));
|
||||
}
|
||||
|
||||
TEST_F(CallGraphExtractorTest, MixedDirectAndVirtualCalls) {
|
||||
runExtractor(R"cpp(
|
||||
void direct_target();
|
||||
struct Base {
|
||||
virtual void virt();
|
||||
};
|
||||
void caller(Base &Obj) {
|
||||
direct_target();
|
||||
Obj.virt();
|
||||
}
|
||||
)cpp");
|
||||
|
||||
ASSERT_THAT_EXPECTED(findSummary("caller"),
|
||||
hasSummaryThat(hasDirectCallees({"direct_target"}),
|
||||
hasVirtualCallees({"Base::virt"})));
|
||||
}
|
||||
|
||||
TEST_F(CallGraphExtractorTest, DeclarationsOnlyNoSummary) {
|
||||
runExtractor(R"cpp(
|
||||
void declared_only();
|
||||
)cpp");
|
||||
|
||||
// No summary for functions without definitions.
|
||||
EXPECT_FALSE(llvm::is_contained(getData(Summary), SummaryName("CallGraph")));
|
||||
}
|
||||
|
||||
TEST_F(CallGraphExtractorTest, DuplicateCallees) {
|
||||
runExtractor(R"cpp(
|
||||
void target();
|
||||
void caller() {
|
||||
target();
|
||||
target();
|
||||
target();
|
||||
}
|
||||
)cpp");
|
||||
|
||||
// Despite three calls, there's only one unique callee.
|
||||
ASSERT_THAT_EXPECTED(
|
||||
findSummary("caller"),
|
||||
hasSummaryThat(hasDirectCallees({"target"}), HasNoVirtualCallees()));
|
||||
}
|
||||
|
||||
TEST_F(CallGraphExtractorTest, NonVirtualMethodCalls) {
|
||||
runExtractor(R"cpp(
|
||||
struct S {
|
||||
void method();
|
||||
};
|
||||
void caller() {
|
||||
S s;
|
||||
s.method();
|
||||
}
|
||||
)cpp");
|
||||
|
||||
ASSERT_THAT_EXPECTED(
|
||||
findSummary("caller"),
|
||||
hasSummaryThat(hasDirectCallees({"method"}), HasNoVirtualCallees()));
|
||||
}
|
||||
|
||||
TEST_F(CallGraphExtractorTest, StaticMethodCalls) {
|
||||
runExtractor(R"cpp(
|
||||
struct S {
|
||||
static void staticMethod();
|
||||
};
|
||||
void caller() {
|
||||
S::staticMethod();
|
||||
}
|
||||
)cpp");
|
||||
|
||||
ASSERT_THAT_EXPECTED(findSummary("caller"),
|
||||
hasSummaryThat(hasDirectCallees({"staticMethod"}),
|
||||
HasNoVirtualCallees()));
|
||||
}
|
||||
|
||||
TEST_F(CallGraphExtractorTest, FunctionPtrCall) {
|
||||
runExtractor(R"cpp(
|
||||
void caller(int (&fptr)()) {
|
||||
fptr();
|
||||
}
|
||||
)cpp");
|
||||
|
||||
ASSERT_THAT_EXPECTED(
|
||||
findSummary("caller"),
|
||||
hasSummaryThat(HasNoDirectCallees(), HasNoVirtualCallees()));
|
||||
}
|
||||
|
||||
TEST_F(CallGraphExtractorTest, ObjCMessageExprs) {
|
||||
runExtractor(R"cpp(
|
||||
@interface NSString
|
||||
- (id)stringByAppendingString:(id)str;
|
||||
@end
|
||||
|
||||
void caller(void) {
|
||||
id msg = [@"Hello" stringByAppendingString:@", World!"];
|
||||
}
|
||||
)cpp",
|
||||
{"-x", "objective-c"});
|
||||
|
||||
ASSERT_THAT_EXPECTED(
|
||||
findSummary("caller"),
|
||||
hasSummaryThat(HasNoDirectCallees(), HasNoVirtualCallees()));
|
||||
}
|
||||
|
||||
TEST_F(CallGraphExtractorTest, DefinitionLocation) {
|
||||
runExtractor(R"cpp(
|
||||
void callee_with_def() {}
|
||||
void callee_without_def();
|
||||
void caller(int n) {
|
||||
if (n == 0) return;
|
||||
callee_with_def();
|
||||
callee_without_def();
|
||||
caller(n - 1);
|
||||
}
|
||||
)cpp");
|
||||
|
||||
ASSERT_THAT_EXPECTED(
|
||||
findSummary("caller"),
|
||||
hasSummaryThat(
|
||||
hasDirectCallees({"caller", "callee_with_def", "callee_without_def"}),
|
||||
HasNoVirtualCallees(), DefinedAt("input.cc", 4U, 10U)));
|
||||
|
||||
ASSERT_THAT_EXPECTED(findSummary("callee_with_def"),
|
||||
hasSummaryThat(HasNoDirectCallees(),
|
||||
HasNoVirtualCallees(),
|
||||
DefinedAt("input.cc", 2U, 10U)));
|
||||
}
|
||||
|
||||
TEST_F(CallGraphExtractorTest, PrettyName) {
|
||||
runExtractor(R"cpp(
|
||||
template <class T, int N>
|
||||
void templated_function(int *) {}
|
||||
void caller(int n) {
|
||||
templated_function<struct TypeTag, 404>(&n);
|
||||
}
|
||||
)cpp");
|
||||
|
||||
ASSERT_THAT_EXPECTED(findSummary("caller"),
|
||||
hasSummaryThat(hasDirectCallees({"templated_function"}),
|
||||
HasNoVirtualCallees(),
|
||||
HasPrettyName("caller(int)")));
|
||||
|
||||
// FIXME: The template arguments are not spelled here.
|
||||
ASSERT_THAT_EXPECTED(
|
||||
findSummary("templated_function"),
|
||||
hasSummaryThat(HasNoDirectCallees(), HasNoVirtualCallees(),
|
||||
HasPrettyName("templated_function(int *)")));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@ -1,4 +1,5 @@
|
||||
add_distinct_clang_unittest(ClangScalableAnalysisTests
|
||||
Analyses/CallGraph/CallGraphExtractorTest.cpp
|
||||
Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp
|
||||
ASTEntityMappingTest.cpp
|
||||
BuildNamespaceTest.cpp
|
||||
|
||||
@ -12,6 +12,7 @@
|
||||
#include "clang/ScalableStaticAnalysisFramework/Core/TUSummary/TUSummary.h"
|
||||
#include "clang/Tooling/Tooling.h"
|
||||
#include "llvm/ADT/StringRef.h"
|
||||
#include "gmock/gmock.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include <memory>
|
||||
|
||||
@ -38,11 +39,8 @@ TEST(SummaryExtractorRegistryTest, EnumeratingRegistryEntries) {
|
||||
EXPECT_TRUE(Inserted);
|
||||
}
|
||||
|
||||
EXPECT_EQ(ActualNames, (std::set<llvm::StringRef>{
|
||||
"MockSummaryExtractor1",
|
||||
"MockSummaryExtractor2",
|
||||
"NoOpExtractor",
|
||||
}));
|
||||
EXPECT_THAT(ActualNames, testing::IsSupersetOf({"MockSummaryExtractor1",
|
||||
"MockSummaryExtractor2"}));
|
||||
}
|
||||
|
||||
TEST(SummaryExtractorRegistryTest, InstantiatingExtractor1) {
|
||||
|
||||
@ -17,6 +17,7 @@ static_library("Driver") {
|
||||
"//clang/lib/DependencyScanning",
|
||||
"//clang/lib/Frontend",
|
||||
"//clang/lib/Options",
|
||||
"//clang/lib/ScalableStaticAnalysisFramework/Analyses",
|
||||
"//clang/lib/ScalableStaticAnalysisFramework/Core",
|
||||
"//clang/lib/ScalableStaticAnalysisFramework/Frontend",
|
||||
"//llvm/include/llvm/Config:llvm-config",
|
||||
|
||||
@ -12,6 +12,7 @@ static_library("FrontendTool") {
|
||||
"//clang/lib/Frontend",
|
||||
"//clang/lib/Frontend/Rewrite",
|
||||
"//clang/lib/Options",
|
||||
"//clang/lib/ScalableStaticAnalysisFramework/Analyses",
|
||||
"//clang/lib/ScalableStaticAnalysisFramework/Core",
|
||||
"//clang/lib/ScalableStaticAnalysisFramework/Frontend",
|
||||
"//llvm/lib/Option",
|
||||
|
||||
@ -8,5 +8,8 @@ static_library("Analyses") {
|
||||
"//clang/lib/ScalableStaticAnalysisFramework/Core",
|
||||
"//llvm/lib/Support",
|
||||
]
|
||||
sources = [ "UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp" ]
|
||||
sources = [
|
||||
"CallGraph/CallGraphExtractor.cpp",
|
||||
"UnsafeBufferUsage/UnsafeBufferUsageExtractor.cpp",
|
||||
]
|
||||
}
|
||||
|
||||
@ -18,8 +18,9 @@ unittest("ClangScalableAnalysisTests") {
|
||||
]
|
||||
include_dirs = [ "." ]
|
||||
sources = [
|
||||
"ASTEntityMappingTest.cpp",
|
||||
"Analyses/CallGraph/CallGraphExtractorTest.cpp",
|
||||
"Analyses/UnsafeBufferUsage/UnsafeBufferUsageTest.cpp",
|
||||
"ASTEntityMappingTest.cpp",
|
||||
"BuildNamespaceTest.cpp",
|
||||
"EntityIdTableTest.cpp",
|
||||
"EntityIdTest.cpp",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user