SVFTools图基类的简单使用

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文件,如下:
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值