SVFTools 图有关的基类
三个基类
SVFTools
为图提供了三个基类,均为模板类,分别是
G
e
n
e
r
i
c
N
o
d
e
,
G
e
n
e
r
i
c
E
d
g
e
,
G
e
n
e
r
i
c
G
r
a
p
h
GenericNode, GenericEdge, GenericGraph
GenericNode,GenericEdge,GenericGraph,对应节点、边与图类型(图是有向图)。其中边类最为简单,主要包含3个成员:
template<class NodeTy> class GenericEdge{
private:
NodeTy* src; ///< source node 起点,指针类型
NodeTy* dst; ///< destination node 终点,指针类型
GEdgeFlag edgeFlag; ///< edge kind 边种类,就是一个整数
.......
}
此外,还提供了默认的比较函数。
节点类稍微复杂一点,包含4个成员:
template<class NodeTy,class EdgeTy> class GenericNode{
private:
NodeID id; ///< Node ID 节点ID,就是一个整数
GNodeK nodeKind; ///< Node kind 节点种类,就是一个整数
GEdgeSetTy InEdges; ///< all incoming edge of this node 入边的有序集合
GEdgeSetTy OutEdges; ///< all outgoing edge of this node 出边的有序集合
......
}
边和节点类都提供了public函数,可以完全访问这些成员。
图基类也比较简单,主要包含三个成员:
template<class NodeTy,class EdgeTy> class GenericGraph{
protected:
// 节点编号到节点指针的键值对,这是一个hash,而不是有序map
IDToNodeMapTy IDToNodeMap; ///< node map
public:
u32_t edgeNum; ///< total num of node 边数量
u32_t nodeNum; ///< total num of edge 点数量
}
图基类也提供各种函数以访问成员,包括提供相应的迭代器以供遍历。
工具模板
为了在图上实现各种算法,最简单的如遍历算法,需要提供萃取机制。SVFTools
的图基类已经提供了基本实现,如果是自行实现的继承图,做一个简单的偏特化即可。一共有三个模板。
namespace llvm
{
template<class NodeTy,class EdgeTy> struct GraphTraits<SVF::GenericNode<NodeTy,EdgeTy>* >{
};
template<class NodeTy,class EdgeTy>
struct GraphTraits<Inverse<SVF::GenericNode<NodeTy,EdgeTy>* > >{
};
template<class NodeTy,class EdgeTy> struct GraphTraits<SVF::GenericGraph<NodeTy,EdgeTy>* > : public GraphTraits<SVF::GenericNode<NodeTy,EdgeTy>* >{
};
} // End namespace llvm
如果需要输出成dot文件,可以使用llvm
的提供的traits工具。参考后面的自建图的例子。
自建图与SCC
建立一个简单的有向图,并测试强连通分量。
自建图与相关的偏特化
自建点边图子类
建立一个简单的有向图类型,节点只需保存编号信息即可。因此三个子类均无需提供新的成员,边和节点子类只需提供构造函数即可。
图子类需要提供一个
a
d
d
E
d
g
e
addEdge
addEdge函数,因为基类没有类似功能的函数。另外提供了2个函数,用于输出dot文件。最后提供了一个静态方法,用于生成一个6点7边的简单的有向图。
class LxNode;
class LxEdge: public GenericEdge<LxNode>{
public:
LxEdge(LxNode *src, LxNode *dst):GenericEdge(src,dst,0){}
};
class LxNode:public GenericNode<LxNode, LxEdge>{
public:
LxNode(NodeID id):GenericNode(id,0){}
};
class LxGraph:public GenericGraph<LxNode,LxEdge>{
public:
LxGraph():GenericGraph(){}
bool addEdge(LxNode *src, LxNode *dst, LxEdge *edge){
///不考虑边的类型,直接使用addOutgoingEdge添加即可
src->addOutgoingEdge(edge);
dst->addIncomingEdge(edge);
this->incEdgeNum();
return true;
}
/**
* 生成默认的测试用图
* 6个点,7条边,2个SCC
*/
static LxGraph* getDefaultTestGraph(){
LxGraph *g = new LxGraph;
for(int i=1;i<=6;++i){
g->addGNode(i, new LxNode(i));
}
int const an = 7;///7条边
int const a[][2] = {
{1,2},{2,3},{3,1},{4,5},{5,6},{6,4},{1,4},{6,3}
};
for(int i=0;i<an;++i){
LxNode *src = g->getGNode(a[i][0]);
LxNode *dst = g->getGNode(a[i][1]);
LxEdge *p = new LxEdge(src, dst);
g->addEdge(src,dst,p);
}
return g;
}
/**
* 将图写成dot文件,可视化
* 需要实现DOTGraphTraits,否则就是默认值
* string: name 文件本名,最后的文件为"name.dot"
*/
void dump(std::string name){
SVF::GraphPrinter::WriteGraphToFile(SVFUtil::outs(), name, this);
}
/// 写dot文件所需
inline std::string getGraphName() const{
return "LxGraph";
}
};
图类型的traits
为自建的子图提供三个用于类型萃取的traits,非常简单
namespace llvm{
/// 以下三个结构体为类型萃取所需
template<> struct GraphTraits<LxNode*>:public GraphTraits<SVF::GenericNode<LxNode,LxEdge>* >{};
template<> struct GraphTraits<Inverse<LxNode*> >:public GraphTraits<Inverse<SVF::GenericNode<LxNode,LxEdge>* > >{};
template<> struct GraphTraits<LxGraph*>:public GraphTraits<SVF::GenericGraph<LxNode,LxEdge>* >{
typedef LxNode *NodeRef; // 只需写这一句话即可
};
}
写dot文件的traits
llvm
为输出dot文件提供了默认的traits,可以根据需要自行定制,做偏特化处理即可。此处将节点形状设置成圆形并显示节点编号,边上不显示任何内容。
namespace llvm{
template<>struct DOTGraphTraits<LxGraph*> : public DefaultDOTGraphTraits{
typedef LxNode NodeType;
typedef NodeType::iterator ChildIteratorTy;
/// 必须的,不可省略
DOTGraphTraits(bool isSimple = false):DefaultDOTGraphTraits(isSimple){}
/// Return name of the graph
static std::string getGraphName(LxGraph *graph){
return graph->getGraphName();
}
/**
* 指定节点的标签,显示在哪???
* LxNode* node: 指定节点
* LxGraph*:
* return: string
*/
static std::string getNodeLable(LxNode *node, LxGraph*){
std::string str;
raw_string_ostream rawstr(str);
/// 只写节点编号
rawstr<<"NodeID: "<<node->getId();
return rawstr.str();
}
/**
* 另一个标签,会显示在竖线之后
*/
static std::string getNodeIdentifierLabel(LxNode *node, LxGraph*){
std::string str;
raw_string_ostream rawstr(str);
/// 只写节点编号
rawstr<<" ID: "<<node->getId();
return rawstr.str();
}
/**
* 指定节点的属性字符串,例如"shape=circle",则节点呈圆形
*/
static std::string getNodeAttributes(LxNode *node, LxGraph*){
return "shape=circle";
}
};
}
强连通分量SCC
SVFTools
提供了强连通分量的方法,使用
N
u
u
t
i
l
a
Nuutila
Nuutila算法,
T
a
r
j
a
n
Tarjan
Tarjan算法的一种改进。源代码中给出了参考文献。
如果traits正确,非常简单:
SCCDetection<LxGraph*> scc(g); // 创建图g的SCC检测器
scc.find(); // 运行强连通分量的算法
f i n d find find完毕后,每个强连通分量用一个节点来代表,可以通过相应的成员函数获取,并能得到拓扑排序。当然,任给一个节点也可得知其属于哪一个强连通分量。
写一个简单的测试函数,如下
void testSCC(){
LxGraph *g = LxGraph::getDefaultTestGraph();
g->dump("scc");
/// 输出搜索顺序,因为节点编号是用哈希保存的,因此不按大小顺序
cout<<"搜索顺序"<<endl;
typedef llvm::GraphTraits<LxGraph*> GTraits;
GTraits::nodes_iterator I = GTraits::nodes_begin(g);
GTraits::nodes_iterator E = GTraits::nodes_end(g);
for(;I!=E;++I){
cout<<GTraits::getNodeID(*I)<<" ";
}
cout<<endl;
SCCDetection<LxGraph*> scc(g);
scc.find();
/// SCC具体内容
cout<<"SCC detail: "<<endl;
typedef vector<NodeID> vid;
map<NodeID, vid> sccInfo;
for(auto it=g->begin();it!=g->end();++it){
insert(scc.repNode(it->fi), it->fi, sccInfo);
}
for(auto p: sccInfo){
cout<<p.fi<<": "<<p.se<<endl;
}
/// 输出拓扑排序
auto stack = scc.topoNodeStack();
vid vec;
while(!stack.empty()){
vec.pb(stack.top());
stack.pop();
}
cout<<"拓扑排序"<<endl;
cout<<vec<<endl;
delete g;
}
输出:
搜索顺序
6 5 4 3 1 2
SCC detail:
3: 3 1 2
6: 6 5 4
拓扑排序
3 6
运行结果说明该图一共有2个SCC,分别是3、1、2和6、5、4。拓扑排序的结果是3、6。
该程序同时输出dot文件,将dot文件转为png文件,如下: