Skip to content

Commit

Permalink
Merge pull request #71 from liangti/weak-require
Browse files Browse the repository at this point in the history
Add weak dependencies analysis for include graph validation
  • Loading branch information
dbeer1 authored Jul 1, 2022
2 parents df6179b + 36c81f6 commit 4b1f915
Show file tree
Hide file tree
Showing 6 changed files with 423 additions and 34 deletions.
40 changes: 38 additions & 2 deletions include/clangmetatool/include_graph_dependencies.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

namespace clangmetatool {


/**
* Collect stateless functions to query and and modify the state of
* dependencies of a given `clangmetatool::IncludeGraphData` structure.
Expand All @@ -29,7 +30,7 @@ struct IncludeGraphDependencies {
*/
static std::set<clangmetatool::types::FileUID>
collectAllIncludes(const clangmetatool::collectors::IncludeGraphData* data,
const clangmetatool::types::FileUID &fileFUID);
const clangmetatool::types::FileUID &fileUID);


/**
Expand All @@ -45,7 +46,42 @@ struct IncludeGraphDependencies {
*/
static std::set<clangmetatool::types::FileUID>
liveDependencies(const clangmetatool::collectors::IncludeGraphData *data,
const clangmetatool::types::FileUID &headerFUID);
const clangmetatool::types::FileUID &fileUID);

/*
* A data structure for include graph weak dependencies analysis
* for a specific source file
*
* Key: all headers the file indirectly depends on
* Value: a set of direct included headers that could allow the file to
* access the key header
*
* Example:
* \code{.unparsed}
* { "def1.h": {"a.h", "b.h"},
* "def2.h": {"a.h", "c.h"} }
* \endcode
*
* So that given the example data above it means current analyzing file:
* - depends on definitions from \c "def1.h", \c "def2.h"
* - includes \c "a.h", \c "b.h", \c "c.h"
* - can access \c "def1.h" by \c "a.h" and \c "b.h"
* - can access \c "def2.h" by \c "a.h" and \c "c.h"
*/
typedef std::map<clangmetatool::types::FileUID,
std::set<clangmetatool::types::FileUID>> DirectDependenciesMap;

/**
* Get the live weak dependencies of a header within the given include graph.
*
* Unlike \c "liveDependencies" which returns the first header that leads to a header
* with a needed declaration, the output of this function includes all direct includes
* that have a path to a header with a needed declaration.
*/
static DirectDependenciesMap
liveWeakDependencies(const clangmetatool::collectors::IncludeGraphData *data,
const clangmetatool::types::FileUID &fileUID);

}; // struct IncludeGraphDependencies
} // namespace clangmetatool

Expand Down
100 changes: 84 additions & 16 deletions src/include_graph_dependencies.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ namespace clangmetatool {

namespace {

// Returns a range of edges whose source vertex matches the given file uid
// Returns a range of edges starts with given source file uid
inline std::pair<types::FileGraph::const_iterator,
types::FileGraph::const_iterator>
edge_range_with_source(const collectors::IncludeGraphData *data,
const types::FileUID &sourceFUID) {
edgeRangeStartsWith(const collectors::IncludeGraphData *data,
const types::FileUID &sourceFUID) {
// Exploit an implementation detail of the include graph being an ordered
// set of pairs and how operator<(...) on pairs works.
// The property in use is that operator<(...) on pairs sorts
Expand All @@ -32,10 +32,10 @@ edge_range_with_source(const collectors::IncludeGraphData *data,
// This function depends on the current view of the graph to check an edge
// is important. There are more than one valid solutions to this problem on
// a DAG, affected by the order of traversal and the initial state provided.
bool requires(const collectors::IncludeGraphData *data,
const types::FileUID &from, const types::FileUID &to,
std::set<types::FileGraphEdge> &knownEdges,
std::set<types::FileUID> &knownNodes) {
bool isRequired(const collectors::IncludeGraphData *data,
const types::FileUID &from, const types::FileUID &to,
std::set<types::FileGraphEdge> &knownEdges,
std::set<types::FileUID> &knownNodes) {
std::queue<types::FileUID> filesToProcess;
bool keepEdge = false;

Expand All @@ -62,7 +62,7 @@ bool requires(const collectors::IncludeGraphData *data,
// Find the set of files included by the current file uid
// and set those up for traversal if we haven't seen them already
types::FileGraph::const_iterator rangeBegin, rangeEnd;
std::tie(rangeBegin, rangeEnd) = edge_range_with_source(data, currentFUID);
std::tie(rangeBegin, rangeEnd) = edgeRangeStartsWith(data, currentFUID);

for (auto edgeIt = rangeBegin; edgeIt != rangeEnd; ++edgeIt) {
types::FileUID nextNode;
Expand Down Expand Up @@ -93,14 +93,14 @@ bool IncludeGraphDependencies::decrementUsageRefCount(
std::set<clangmetatool::types::FileUID>
IncludeGraphDependencies::collectAllIncludes(
const clangmetatool::collectors::IncludeGraphData* data,
const types::FileUID &headerFUID)
const types::FileUID &fileUID)
{
types::FileGraph::const_iterator rangeBegin, rangeEnd;
std::tie(rangeBegin, rangeEnd) = edge_range_with_source(data, headerFUID);
std::tie(rangeBegin, rangeEnd) = edgeRangeStartsWith(data, fileUID);

std::set<clangmetatool::types::FileUID> visitedNodes;
std::queue<clangmetatool::types::FileUID> toVisit;
toVisit.push(headerFUID);
toVisit.push(fileUID);
while (!toVisit.empty()) {
auto currentFUID = toVisit.front();
toVisit.pop();
Expand All @@ -109,7 +109,7 @@ IncludeGraphDependencies::collectAllIncludes(
continue;
}
types::FileGraph::const_iterator rangeBegin, rangeEnd;
std::tie(rangeBegin, rangeEnd) = edge_range_with_source(data, currentFUID);
std::tie(rangeBegin, rangeEnd) = edgeRangeStartsWith(data, currentFUID);
for (auto it = rangeBegin; it != rangeEnd; ++it) {
toVisit.push(it->second);
}
Expand All @@ -120,22 +120,90 @@ IncludeGraphDependencies::collectAllIncludes(

std::set<types::FileUID> IncludeGraphDependencies::liveDependencies(
const collectors::IncludeGraphData *data,
const clangmetatool::types::FileUID &headerFUID) {
const clangmetatool::types::FileUID &fileUID) {
std::set<types::FileUID> dependencies;
std::set<types::FileGraphEdge> visitedEdges;
std::set<types::FileUID> visitedNodes;

types::FileGraph::const_iterator rangeBegin, rangeEnd;
std::tie(rangeBegin, rangeEnd) = edge_range_with_source(data, headerFUID);
std::tie(rangeBegin, rangeEnd) = edgeRangeStartsWith(data, fileUID);

for (auto it = rangeBegin; it != rangeEnd; ++it) {
assert(it->first == headerFUID);
assert(it->first == fileUID);
auto &dependency = it->second;
if (requires(data, headerFUID, dependency, visitedEdges, visitedNodes)) {
if (isRequired(data, fileUID, dependency, visitedEdges, visitedNodes)) {
dependencies.insert(dependency);
}
}

return dependencies;
}

/*
* Traverse the include graph for `forNode` start from `rootNode` to all
* accessible node using BFS and update given DirectDependenciesMap.
*
* For any node that `usage_reference_count[{rootNode, toNode}] > 0`, add a record
* `{toNode: [rootNode]}` to depsMap, means that `forNode` needs to access
* resource defined in `toNode` through `rootNode`
*
* Include graph should looks like:
* forNode -> rootNode ... -> toNode
*/
void traverseFor(const types::FileUID &forNode, const types::FileUID &rootNode,
const clangmetatool::collectors::IncludeGraphData *data,
std::set<types::FileUID> &knownNodes,
IncludeGraphDependencies::DirectDependenciesMap& depsMap){
std::queue<types::FileUID> filesToProcess;

filesToProcess.push(rootNode);

while (!filesToProcess.empty()) {
auto toNode = filesToProcess.front();
filesToProcess.pop();

types::FileGraphEdge currentEdge{forNode, toNode};
auto refCountIt = data->usage_reference_count.find(currentEdge);
// the include graph looks like
// forNode -> rootNode -> ... -> toNode
if (refCountIt != data->usage_reference_count.end() &&
refCountIt->second > 0) {
depsMap[toNode].emplace(rootNode);
}

// Find the set of files included by the current file uid
// and set those up for traversal if we haven't seen them already
types::FileGraph::const_iterator rangeBegin, rangeEnd;
std::tie(rangeBegin, rangeEnd) = edgeRangeStartsWith(data, toNode);

for (auto edgeIt = rangeBegin; edgeIt != rangeEnd; ++edgeIt) {
types::FileUID nextNode;
std::tie(std::ignore, nextNode) = *edgeIt;
if (knownNodes.find(nextNode) == knownNodes.end()) {
filesToProcess.push(nextNode);
knownNodes.insert(nextNode);
}
}
}

}

IncludeGraphDependencies::DirectDependenciesMap
IncludeGraphDependencies::liveWeakDependencies(
const clangmetatool::collectors::IncludeGraphData *data,
const clangmetatool::types::FileUID &fileUID){
IncludeGraphDependencies::DirectDependenciesMap depsMap;

types::FileGraph::const_iterator rangeBegin, rangeEnd;
std::tie(rangeBegin, rangeEnd) = edgeRangeStartsWith(data, fileUID);

for (auto it = rangeBegin; it != rangeEnd; ++it) {
assert(it->first == fileUID);
std::set<types::FileUID> knownNodes;
traverseFor(fileUID, it->second, data, knownNodes, depsMap);
}

return depsMap;
}

} // namespace clangmetatool
Loading

0 comments on commit 4b1f915

Please sign in to comment.