一种确定工程代码中的无用函数的方法和装置
技术领域
本发明涉及程序代码优化领域,更具体而言,涉及确定工程代码中的无用函数的方法和装置。
背景技术
应用的安装包大小,是一款应用的质量的核心指标之一,它会影响到包括下载转化率、应用启动时间、代码工程质量等等方面。
工程代码量是安装包大小的最主要组成部分,随着项目的不断迭代,工程代码中不可避免的出现了越来越多的无用函数。这些无用函数,会在至少两个方面对应用产生负面影响。首先是增加了安装包大小。多数移动应用中,由代码组成的部分占据整个安装包大小的80%左右,无用的函数没有任何功能作用,但仍占据的相当的安装包大小,并且增加的安装包大小,也会增加应用的启动时间,并降低运行效率。其次还增加客户端工程的维护成本。客户端代码中残留的无用函数,大多数是在项目长时间开发过程中产生的,这些“僵尸函数”大多都具有相当的历史因素,客户端成员需要额外理解这些无用函数的逻辑,极大的增加了客户端开发人员日常开发过程中的成本,降低了客户端开发工作的效率。
因此,如何识别工程中的无用函数,是减少移动应用安装包大小、增加代码质量的关键问题之一。
为了识别这些无用函数,有些代码的开发者和提供者会进行手动自我检查,也就是项目的开发者基于自己的经验来识别函数的引用情况,根据代码搜索以及经验判断一个函数是否是无用函数。在有些方案中,还会基于文本搜索进行未引用函数的自动化扫描,也就是根据函数名作为关键字,通过代码库中的搜索,判断函数的引用情况。然而,以上的方法效率不高,并且识别无用函数的准确度非常有限,常常出现误报、漏报的情况。
因此,需要更有效的方案,准确识别出工程代码中的无用函数,从而为程序代码的优化提供基础。
发明内容
本说明书中提供的实施例旨在提供更有效的无用函数扫描方法,解决现有技术中的不足。
为实现上述目的,在一个方面,说明书的实施例提供了一种提供工程代码的调用关系图的方法,包括:遍历所述工程代码所对应的抽象语法树,从中提取与直接调用事件相关的直接调用信息;从所述抽象语法树中推导获得与动态调用事件相关的动态调用信息;提供调用关系图,该调用关系图包括所述直接调用信息和所述动态调用信息。
在一个实施例中,所述从抽象语法树中推导获得与动态调用事件相关的动态调用信息包括:从所述抽象语法树中声明节点的表达式推导动态调用的调用类的类型,以及从所述声明节点的表达式推导动态调用的函数名。
在一个实施例中,所述从抽象语法树中声明节点的表达式推导动态调用的调用类的类型包括:响应于所述声明节点的表达式为类(class)动态构造类型的表达式,根据表达式中的字符串参数推导调用类的类型;响应于所述声明节点的表达式为Self.Super指针,根据表达式函数声明所在的类,推导出调用类;
从所述声明节点的表达式推导动态调用的函数名包括:响应于所述声明节点的表达式带有字符串类型的参数,推导出该字符串类型的参数的值,作为函数名;响应于所述声明节点的表达式为Objc地址类型,找到该地址中的函数声明节点,从该节点的声明中获取函数名。
在一个实施例中,所述提供调用关系图包括:采用数据库表格式提供所述调用关系图,所述数据库表格式包括:通过(函数的调用类,函数的方法名,函数的类型)定义的函数,通过(调用者,被调用者)定义的调用事件,以及用于定义类和接口信息的容器结构。
在第二方面,说明书实施例提供了一种确定工程代码中的无用函数的方法,包括:获取所述工程代码的调用关系图;基于调用关系图,确定至少一个根节点,所述根节点对应于确定被使用的函数;从所述至少一个根节点开始,采用染色逻辑对调用关系图中的函数进行染色;将未被染色的函数确定为无用函数。
在一个实施例中,所述调用关系图包括与直接调用事件相关的直接调用信息,以及与动态调用事件相关的动态调用信息。
在一个实施例中,所述调用关系图采用数据库表格式记录,所述数据库表格式包括:通过(函数的调用类,函数的方法名,函数的类型)定义的函数,通过(调用者,被调用者)定义的调用事件,以及用于定义类和接口信息的容器结构。
在一个实施例中,所述基于调用关系图,确定至少一个根节点包括:从所述调用关系图中确定出被使用的类;从所述被使用的类中确定出被使用的函数作为所述至少一个根节点。
在一个实施例中,所述染色逻辑包括:在染色某个函数之后,将该函数所调用的其他函数进行染色。
在一个实施例中,所述染色逻辑包括:在染色某个函数之后,找到该函数对应方法的子类,将子类中覆盖该方法的函数进行染色。
在一个实施例中,所述染色逻辑包括:在染色某个函数之后,判断该函数的调用者是否为接口;如果是接口,确定实现该接口的容器,对该容器中实现该函数对应方法的函数进行染色。
在一个实施例中,所述染色逻辑包括:根据系统回调函数表,对第一次染色的类容器中的系统回调函数进行染色。
在一个实施例中,所述染色逻辑包括:对预先定义的白名单中的函数进行染色。
在第三方面,说明书实施例公开了一种提供工程代码的调用关系图的装置,包括:直接提取单元,配置为,遍历所述工程代码所对应的抽象语法树,从中提取与直接调用事件相关的直接调用信息;动态推导单元,配置为,从所述抽象语法树中推导获得与动态调用事件相关的动态调用信息;提供单元,配置为,提供调用关系图,该调用关系图包括所述直接调用信息和所述动态调用信息。
在第四方面,说明书实施例提供一种确定工程代码中的无用函数的装置,包括:获取单元,配置为获取所述工程代码的调用关系图;根节点确定单元,配置为基于调用关系图,确定至少一个根节点,所述根节点对应于确定被使用的函数;染色单元,配置为从所述至少一个根节点开始,采用染色逻辑对调用关系图中的函数进行染色;无用函数确定单元,将未被染色的函数确定为无用函数。
在第五方面,说明书实施例提供一种计算机可读的存储介质,其上存储有指令代码,所述指令代码在计算机中执行时,令计算机执行以上第一方面中的方法。
在第六方面,说明书实施例提供一种计算机可读的存储介质,其上存储有指令代码,所述指令代码在计算机中执行时,令计算机执行以上第二方面中的方法。
利用以上各个方面中的方法、装置、存储介质中的一个或多个,可以更为有效地确定工程代码中的无用函数。
附图说明
图1示出一个实施例中提供调用关系图的方法的流程图;
图2示出一个实施例中确定无用函数的方法的流程图;
图3示出一个实施例中调用关系图的数据结构的关系示意图;
图4示出根据一个实施例的染色过程;
图5示出根据一个实施例的提供调用关系图的装置的示意图;
图6示出根据一个实施例的确定工程代码中的无用函数的装置的示意图。
具体实施方式
为了使本技术领域的人员更好地理解说明书中的技术方案,下面将结合实施例和附图,对所提供的技术方案进行详细描述。
在说明书提供的实施例中,为了确定工程代码中的无用函数,首先获取到该工程代码的调用关系图(CallGraph)。该调用关系图用于描述和反映工程代码中各个函数之间的调用关系。基于这样的调用关系图,模拟程序运行时的函数调用,利用染色逻辑,对被运行到的函数进行染色,在染色过程结束之后,将未被染色的函数作为无用函数进行报告。如此,更加准确地找到工程代码中的无用函数。
下面,具体描述实现上述构思的实施例。
图1示出一个实施例中提供调用关系图的方法的流程图。如图1所示,在该实施例中,方法包括:步骤11,遍历工程代码所对应的抽象语法树AST,从中提取与直接调用事件相关的直接调用信息;步骤12,从所述抽象语法树中推导获得与动态调用事件相关的动态调用信息;以及步骤13,提供调用关系图,该调用关系图包括所述直接调用信息和所述动态调用信息。下面具体描述以上步骤的执行过程。
首先,在步骤11,通过遍历抽象语法树AST获得直接调用信息。如本领域人员所知,抽象语法树AST是工程源代码的抽象语法结构的树状表现形式,通常可以由程序编译器在编译程序过程中产生和提供。由于抽象语法树AST记录了程序代码的算法与逻辑,通过遍历和分析这样的抽象语法树,可以直接提取出其中的函数声明、调用事件,将这样的信息作为直接调用信息。
本领域技术人员知道,在面向过程的工程代码中直接采用函数的概念,而在面向对象的工程代码中,使用“方法”(method)来描述类似概念。因此,在面向对象语言的情况下,当提及“方法”的时候,其作用类似于上述函数。
对于函数的调用,在许多工程代码中存在着动态调用的情况。这些动态调用往往无法从抽象语法树中直接提取。例如,在用于iOS平台的Objective-C(简称为Objc)中,参看以下两个简单的例子:
1.[self performSelector:@selector(methodA)];
在这个语句中,根据Objc的调用规则,self指针会调用一个methodA方法;
2.[buttonA addTarget:self action:@selector(methodA)forControlEvents:UIControlEventsTouchUpInside]
在这个语句中,代码为buttonA绑定了一个事件方法,当控件收到此事件后,会调用self指针的methodA方法。
从以上两个例子可以看出,Objc动态性是复杂的:方法的调用类(Receiver)和方法本身(Selector)都有可能是动态的。这些动态调用信息无法从AST中直接提取,而要通过分析和推导才能获得。
为此,在步骤12,从抽象语法树中推导获得与动态调用事件相关的动态调用信息。
在一个实施例中,步骤12进一步包括,从AST中声明节点的表达式推导动态调用的Receiver(调用类)的类型。
在一个例子中,推导调用类的类型可以包括以下过程。对于某个表达式Expr,判断该表达式是否为引用节点,如果是引用节点,则找到其引用阶段对应的声明节点。接着,根据声明节点的表达式的类型进一步判定。对于确定类型的表达式,该确定类型即是调用类的类型。对于非确定类型的表达式,如果是类class动态构造类型的表达式,则根据class动态构造表达式中必须的字符串String参数推导调用类的类型。如果该表达式为Self.Super指针,则根据表达式函数声明所在的类class,推导出调用类的类型,并将对应方法确定为类方法。
通过可以过程,可以推导出动态调用时调用类的类型信息。以上推导过程可以支持Objc下self指针、动态声明的Class、对象指针的类型的推导,有效获取动态调用信息。
在一个实施例中,步骤12进一步包括,从AST中声明节点的表达式推导函数名或方法名。
在一个例子中,推导函数名或方法名可以包括以下过程。对于某个表达式Expr,判断该表达式是否为引用节点,如果是引用节点,则找到其引用阶段对应的声明节点。接着,判断声明节点的表达式的类型。如果表达式为方法声明的类型,则根据声明结构,直接获取方法名(函数名)。如果该表达式为带有string类型的参数的selector节点,那么推导出该string类型的参数的值,作为函数名。如果该表达式为Objc地址类型(IMP),则找到该IMP中的函数声明节点,从该节点的声明中获取函数名。如果该表达式为旧版C语言中的函数指针,那么根据函数指针的结构确定函数名。如此,可以在各种动态调用的情况下,推导获得方法名或函数名。
基于步骤11获得的直接调用信息,和步骤12推导出的动态调用信息,在步骤13,提供调用关系图,其包括所述直接调用信息和所述动态调用信息。
调用关系图可以根据需要采用多种格式来记录。在一个例子中,调用关系图采用图表的形式,以直观示出函数之间的调用关系。在另一例子中,采用统一的数据库表格式来存储和记录调用关系图,以便于后续的存储和查询。在上述数据库表格式中,采用几种基本的数据结构来定义函数、调用事件,以及调用关系图中的其他要素。例如,可以通过(函数的调用类,函数的方法名,函数的类型)记录一个函数,通过(调用者,被调用者)记录一个调用事件,以及通过容器结构来记录类和接口信息。这样的数据库表格式在后文会有进一步详细描述。
可以理解,由于上述实施例中提供的调用关系图不仅包括直接调用信息,还包括推导获得的动态调用信息,因此,该调用关系图更加全面、完备地反映出工程代码中的函数调用关系。这也为后续的无用函数扫描提供了更好的基础。
在以上获得的调用关系图的基础上,就可以通过染色的方式,对未调用的无用函数进行扫描。
图2示出一个实施例中确定无用函数的方法的流程图。如图2所示,在该实施例中,首先在步骤21,获取工程代码的调用关系图;在步骤22,基于调用关系图,确定至少一个根节点,所述根节点对应于确定被使用的函数;接着,在步骤23,从至少一个根节点开始,采用染色逻辑对调用关系图中的函数进行染色;在步骤24,将未被染色的函数确定为无用函数。
下面描述以上各个步骤的执行过程。
在步骤21,获取工程代码的调用关系图。在一个例子中,通过图1所示的流程来获得工程代码的调用关系图。可以理解,也可以通过其他方式获得调用关系图,只要获得的调用关系图能够全面、准确地反映出工程代码中函数之间的调用关系。
在一个例子中,所获得的调用关系图包括与直接调用事件相关的直接调用信息,以及与动态调用事件相关的动态调用信息。
由于工程代码中函数之间的调用关系往往比较复杂,相应地,调用关系图的数据量也会比较庞大。后续对函数进行染色的过程中,往往需要反复查询调用关系图中的数据。为了便于调用关系图数据的存储和查询,在一个实施例中,采用统一的数据库表格式来存储和记录调用关系图。在上述数据库表格式中,采用几种基本的数据结构来定义函数、调用事件,以及调用关系图中的其他要素。
在一个实施例中,通过(函数的调用类,函数的方法名,函数的类型)定义一个函数,该数据结构为调用关系图中最基本的数据结构。例如,对于面向对象的语言,将函数定义为一个基本的MethodDecl结构,包含三个组成部分(Receiver,Selector,Type),其中Receiver定义该函数的调用类,Selector定义该函数的方法名,Type定义该函数的类型。
以上的函数定义结构,不仅从函数名这个维度来标识一个函数,还引入了Receiver,Type信息,如此可以更加准确地标识一个函数。事实上,一个项目代码中,不可避免地会有大量重名函数,这给无用函数扫描带来了很大困难。而通过引入函数的调用类、函数类型的附加信息,可以唯一地标识一个函数,避免因为函数重名而造成的问题,使得后续的无用函数的确定更加准确。
在此基础上,在一个实施例中,通过(调用者,被调用者)来定义一个调用事件。例如,将一个调用事件CallEvent定义为(Caller,Callee),其中Caller定义函数的调用者,Callee定义该调用事件所调用的函数。
此外,在一个实施例中,通过容器结构(container)定义类和接口信息。
图3示出一个实施例中上述数据结构的关系图。如图3所示,MethodDecl为其中最基本的数据结构。一个CallEvent中涉及的Caller调用者和Callee被调用者均对应一个函数,因此,一个CallEvent结构对应两个函数,也即两条MethodDecl结构。容器结构Container用于定义类和接口信息,因此,类容器ClassContainer和接口容器InterfaceContainer是基类Container的两个实现。基类Container可以包含若干条(n条)MethodDecl。
在一个实施例中,获取的调用关系图已经采用上述数据结构的格式。此时,可以直接存储和查询这样的调用关系图。在另一实施例中,获取的调用关系图采用其他形式来记录。在这样的情况下,方法还可以包括转换步骤,即,将其他格式的调用关系图转换为上述数据结构,从而便于后续的查询和检索。
此外,可以理解,以上的数据结构是一个实施例中为加速查询而采用的一种数据结构。本领域技术人员在阅读本说明书的情况下,也可以针对不同的程序语言的特点,设计采用其他数据结构来存储和记录调用关系图。
基于以上的调用关系图,可以开始染色过程。首先确定染色开始的起点。在一个实施例中,在步骤22,基于调用关系图,确定至少一个根节点,所述根节点对应于确定被使用的函数。换而言之,基于调用关系图生成一个根节点列表,该列表中的每个根节点都是确定被使用的函数。这些根节点可以作为染色开始的起点。
在一个实施例中,随机地选择若干个确定被使用的函数作为根节点。在另一实施例中,选择位于调用链上游的函数作为根节点以优化染色效果。
在面向对象的工程代码的例子中,上述确定根节点的步骤可以包括,首先确定出被使用的类,然后从被使用的类中确定出被使用的函数。可以理解,程序代码中往往会定义许多不同的类,类中可以包含不同的对象及其方法(函数)。这些类中,有一部分类并不使用,其对应的方法必然不会被调用;在使用的类中,也有一部分方法未被使用。因此,可以首先确定出被使用的类,从这些被使用的类中,确定出被使用的函数作为染色开始的根节点。在一个实施例中,从工程代码的根class开始确定被使用的类,然后从中确定被使用的函数。在一个Objc的具体例子中,可以从ViewController的类库中确定出被使用的类,然后从中确定被使用的函数作为根节点。
在确定出根节点的基础上,在步骤23,从根节点开始,采用染色逻辑对调用关系图中的函数进行染色。具体而言,在该步骤中,遍历根节点列表中的各个根节点,对于每个根节点,首先对该根节点对应的函数进行染色,然后采用染色逻辑,将该根节点的染色进行“扩散”,对调用关系图中的其他函数进行染色。在这其中,染色逻辑用于根据工程代码的实际执行过程,确定调用到的函数并进行染色。
具体地,在一个实施例中,上述染色逻辑包括:在染色某个函数之后,将该函数所调用的其他函数进行染色。这样的染色过程可以称为根据调用事件的直接染色。
在采用上述数据库表格式的情况下,根据调用事件的直接染色逻辑可以描述为,当一个MethodDecl被染色时,获取与该MethodDecl相关的所有调用事件CallEvent,将每个CallEvent中的被调用者Callee对应的函数进行染色。
在一个实施例中,上述染色逻辑还可以包括:在染色某个函数之后,找到该函数对应方法的子类,将子类中覆盖该方法的函数进行染色。这样的染色过程是考虑到面向对象的多态特性而进行的染色,可以称为根据多态性进行的间接染色。具体地,基于面向对象的多态特性,当使用一个基类指针调用一个函数时,这次调用可能会受到多态性的影响,被转发到继承于这个基类的子类上。因此,需要在染色一个函数之后,确定出该函数对应方法的子类,将子类中覆盖该方法的函数也进行染色。
在采用上述数据库表格式的情况下,根据多态性的间接染色逻辑可以描述为,当一个MethodDecl被染色时,找到所有覆盖它的MethodDecl进行染色。
在一个实施例中,上述染色逻辑还可以包括:在染色某个函数之后,判断该函数的调用者是否为接口;如果是接口,确定实现该接口的容器,对该容器中实现该函数对应方法的函数进行染色。这样的染色过程是考虑到面向对象中的接口与实现的关系而进行的染色,可以称为根据接口-实现关系进行的间接染色。这是因为,当使用一个接口指针调用一个方法时,这次调用可能会被转发到实现这个接口的所有对象上。在面向对象的语言中,接口可能有不同表达,例如在Java中是Interface,在Objc中是Protocol。
在采用上述数据库表格式的情况下,根据接口-实现关系进行的间接染色逻辑可以描述为,当一个MethodDecl被染色时,判断其Receiver是否为接口,如果是,找到所有实现这个接口的Container定义,并对这些Container中实现对应方法的MethodDecl进行染色。
在一个实施例中,上述染色逻辑还可以包括,当一个类容器第一次被染色时,根据系统回调函数表,对该类容器中的系统回调函数进行染色。这样的染色过程是考虑到系统回调而进行的染色,可以称为根据系统回调的染色。这是因为,在面向对象的环境下,若一个类继承自系统的类库,这个类中的某些方法可能永远也不会有直接调用,而是由系统库进行隐式调用。例如,在iOS平台中,继承自UIViewController的类库中的viewDidLoad等系统回调,以及在android平台中,继承自Activity的类库中的onCreate等系统回调。这些系统函数,均无法通过调用事件CallEvent进行直接染色,因此需要附加的染色逻辑对其进行染色。具体来说,可以根据平台设计一个系统回调函数表,根据该函数表进行染色。
在采用上述数据库表格式的情况下,根据系统回调的染色逻辑可以描述为,当一个类容器Container第一次被染色时,根据系统回调函数表,对这个Container中的系统回调函数进行染色。
在一个实施例中,上述染色逻辑还可以包括,对预先定义的白名单中的函数进行染色。这样的染色过程可以称为根据白名单的补充染色。这是考虑到,不同的工程代码,受其平台、开发框架的影响,可能会有一些定制的规则,如:某个函数是肯定会被调用的、继承自某个类的子类肯定会被实例化的、实现某个接口的类肯定会被实例化。因此,可以预先定义一个白名单UserWhiteList,在其中定义一定会调用到的函数。在染色过程之后,利用该白名单进行补充染色。
图4示出根据一个实施例的染色过程,也就是图2中步骤23的具体执行流程。如图4所示,首先在步骤41,对当前根节点进行染色;接着在步骤42,利用染色逻辑,对与当前被染色函数相关联的函数进行染色。在一个实施例中,步骤42进一步包括,步骤421,根据调用事件进行染色;步骤422,根据多态性进行染色;步骤423,根据接口-实现关系进行染色;步骤424,根据系统回调进行染色。以上几种染色逻辑的具体含义和染色过程如前所述,不再重复说明。
以上的染色过程是不断递归迭代的过程。在当前根节点被染色之后,通过步骤421-424,该根节点所调用的函数、该根节点对应的子类中覆盖该根节点方法的函数、调用该根节点的接口容器中的函数等相关函数均会被染色,染色迅速扩散到n个与该根节点的调用相关的函数上。接着,对于这n个新染色的函数的每一个,将其作为新的染色起点,继续执行步骤421-424,再次扩散染色。如此反复执行步骤421-424,直到无法进行新的染色。
此时,在步骤43,判断是否还存在未染色的根节点,若有,则将下一未染色的根节点作为当前根节点,回到步骤41再次执行染色过程。如此,遍历所有根节点进行染色。
在步骤43的判断为否,也就是根节点遍历结束之后,在步骤44,进行补充染色。补充染色过程可以包括,根据白名单进行染色。如此,完成对调用关系图中函数的染色过程。
需要说明的是,尽管以上出于示意需要示出了步骤421-424的执行顺序,但是可以理解,染色逻辑并不受其执行顺序的限制。本领域技术人员可以根据需要更改其执行顺序。
此外,以上列出了几种具体的染色逻辑作为示例,但是染色逻辑并不限于以上举例的几种。本领域技术人员在阅读本说明书的情况下,可以在此基础上,根据目标程序语言的特点,修改这些逻辑,或增加其他逻辑,以更好地针对目标程序语言进行染色。这样的修改和增加均应涵盖在发明构思之中。
在染色结束之后,如图2中步骤24所示,可以将未染色的函数确定为无用函数。进一步地,在一个实施例中,可以报告扫描出来的无用函数,从而为后续的代码优化提供基础。
通过图2实施例所示的方法可以看到,在该实施例的方法中,经过一次染色过程就可以完成无用函数的扫描,避免了基于代码文本搜索的多次全文搜索。此外,染色逻辑描述了代码运行中的实际调用情况,相比于没有任何语义分析的文本搜索,通过染色过程确定无用函数的方式结果更加准确。更具体来说,在真实编码中,无用函数分为:显式的无用函数,即此方法从未被其他方法直接调用过;以及隐式的无用函数,即此方法被其他方法直接调用过,但其整个调用链的根节点方法是无用函数。传统的无用函数扫描工具通常是根据方法名进行匹配,限于其各自的特性,仅能够识别出显式的无用函数,且因受重名方法的影响,结果也不尽然准确。而基于染色逻辑进行的无用函数扫描,由于模拟了代码运行中的实际调用,因此不仅能够扫描出显式的无用函数,还能够识别出隐式的无用函数,结果显然更加准确。
基于同样的构思,本说明书的实施例还提供一种提供调用关系图的装置,以及一种确定无用函数的装置。
图5示出根据一个实施例的提供调用关系图的装置的示意图。如图5所示,在该实施例中,提供调用关系图的装置50包括:直接提取单元51,其配置为,遍历工程代码所对应的抽象语法树AST,从中提取与直接调用事件相关的直接调用信息;动态推导单元52,其配置为,从所述抽象语法树中推导获得与动态调用事件相关的动态调用信息;以及提供单元53,其配置为提供调用关系图,该调用关系图包括所述直接调用信息和所述动态调用信息。
如本领域人员所知,抽象语法树AST是工程源代码的抽象语法结构的树状表现形式,可以由程序编译器在编译程序过程中产生和提供。由于抽象语法树AST记录了程序代码的算法与逻辑,在一个实施例中,直接提取单元51通过遍历和分析这样的抽象语法树,可以直接提取出其中的函数声明、调用事件,将这样的信息作为直接调用信息。
除直接调用信息之外,一些工程代码中还存在动态调用的情况。这些动态调用往往无法从抽象语法树中直接提取。为此,在一个实施例中,动态推导单元52包括:类型推导器521,配置为从所述抽象语法树中声明节点的表达式推导动态调用的调用类(Receiver)的类型,以及函数名推导器522,配置为从所述声明节点的表达式推导动态调用的函数名。
在一个实施例中,类型推导器521进一步配置为:响应于声明节点的表达式为类(class)动态构造类型的表达式,根据表达式中的字符串参数推导调用类的类型;响应于所述声明节点的表达式为Self.Super指针,根据表达式函数声明所在的类,推导出调用类。
在一个实施例中,函数名推导器522进一步配置为:响应于声明节点的表达式带有字符串类型的参数,推导出该字符串类型的参数的值,作为函数名;响应于所述声明节点的表达式为Objc地址类型,找到该地址中的函数声明节点,从该节点的声明中获取函数名。
在一个实施例中,提供单元53配置为,采用数据库表格式提供所述调用关系图,所述数据库表格式包括:通过(函数的调用类,函数的方法名,函数的类型)定义的函数,通过(调用者,被调用者)定义的调用事件,以及用于定义类和接口信息的容器结构。
在一个实施例中,上述装置50集成在现有编译器中,体现为现有编译器的一种扩展的前端工具或编译器插件。例如,Clang编译器提供有工具FrontAction,用于编写独立的编译器前端工具。利用这样的工具,可以在编译过程中执行额外的用户自定义的操作。因此,在一个例子中,利用这样的工具,可以使得编译器在编译过程中实现装置50中的操作过程,从而提供以上所述的调用关系图。
而在另一实施例中,上述装置50也可以体现为一个独立的装置。该装置可以通过多种方式与编译器相连接,从编译器获取工程代码的抽象语法树AST,从而基于抽象语法树来提供调用关系图。
图6示出根据一个实施例的确定工程代码中的无用函数的装置的示意图。如图6所示,用于确定无用函数的装置60包括:获取单元61,配置为获取所述工程代码的调用关系图;根节点确定单元62,配置为基于调用关系图,确定至少一个根节点,所述根节点对应于确定被使用的函数;染色单元63,配置为从所述至少一个根节点开始,采用染色逻辑对调用关系图中的函数进行染色;以及无用函数确定单元64,将未被染色的函数确定为无用函数。
在一个实施例中,获取单元61所获取的调用关系图包括与直接调用事件相关的直接调用信息,以及与动态调用事件相关的动态调用信息。
在一个实施例中,获取单元61所获取的调用关系图采用数据库表格式记录,所述数据库表格式包括:通过(函数的调用类,函数的方法名,函数的类型)定义的函数,通过(调用者,被调用者)定义的调用事件,以及用于定义类和接口信息的容器结构。
例如,在一个实施例中,调用关系图针对面向对象的语言,将函数定义为一个基本的MethodDecl结构,包含三个组成部分(Receiver,Selector,Type),其中Receiver定义该函数的类,Selector定义该函数的方法名,Type定义该函数的类型。在此基础上,在一个实施例中,将一个调用事件CallEvent定义为(Caller,Callee),其中Caller定义函数的调用者,Callee定义该调用事件所调用的函数。此外,在一个实施例中,通过容器结构(container)定义类和接口信息。
这样的数据库表结构可以使得后续染色单元63反复查询调用关系图更加快捷。
在另一实施例中,获取单元61所获取的调用关系图采用其他格式进行记录。在这样的情况下,可选的,装置60可以包括转换单元(未示出),用于将其他格式的调用关系图转换为上述的数据库表结构。
所获取的调用关系图可以存储在数据存储器65中。在一个实施例中,数据存储器65包含在装置60内部。在另一实施例中,数据存储器65也可以位于装置60外部,与装置60相连接,使得装置60能够读取其中的数据。图6示意性示出了装置60包含数据存储器65的情况。
在获取到适当的调用关系图的基础上,根节点确定单元62基于调用关系图,确定至少一个根节点,所述根节点对应于确定被使用的函数。在一个实施例中,根节点确定单元62进一步配置为:从所述调用关系图中确定出被使用的类;从所述被使用的类中确定出被使用的函数作为所述至少一个根节点。
在确定出根节点的基础上,染色单元63从上述根节点开始,采用染色逻辑对调用关系图中的函数进行染色。
在一个实施例中,染色单元63配置为:在染色某个函数之后,将该函数所调用的其他函数进行染色。
在一个实施例中,染色单元63还配置为:在染色某个函数之后,找到该函数对应方法的子类,将子类中覆盖该方法的函数进行染色。
在一个实施例之后,染色单元63配置为:在染色某个函数之后,判断该函数的调用者是否为接口;如果是接口,确定实现该接口的容器,对该容器中实现该函数对应方法的函数进行染色。
在一个实施例中,染色单元63配置为:根据系统回调函数表,对第一次染色的类容器中的系统回调函数进行染色。
在一个实施例中,染色单元63配置为:对预先定义的白名单中的函数进行染色。
可以理解,根据不同的程序语言的特点,适用的染色逻辑并不限于以上举例的几种。本领域技术人员在阅读本说明书的情况下,可以在此基础上,根据目标程序语言的特点,修改这些逻辑,或增加其他逻辑。相应地,染色单元可以根据这些修改的或增加的染色逻辑进行染色,以更好地适应目标程序语言的特点。
可以看到,在染色过程中,需要反复对调用关系图进行查询。为此,在一个实施例中,装置60还包括数据访存器66,该数据访存器66作为染色单元63与存储调用关系图的数据存储器65之间的接口,响应于染色单元63的查询命令,按照调用关系图的存储格式,对数据存储器65中存储的调用关系图进行查询,并将查询结果返回给染色单元63。例如,在染色单元63执行根据调用事件的染色逻辑时,需要查询A函数调用的所有其他函数。此时,就可以通过数据访存器66,在数据存储器65中查询调用关系图中所有caller为A的CallEvent,以及这些CallEvent中的Callee,并将查询到的Callee作为结果返回给染色单元63。这使得染色单元63的查询和执行更加高效。
在染色单元63对调用关系图中的函数进行染色之后,无用函数确定单元54就可以将未被染色的函数确定为无用函数。在一个实施例中,无用函数确定单元54还配置为,输出或者报告所确定的无用函数,作为后续代码优化的基础。
在一个实施例中,装置60表现为独立的装置;在另一实施例中,装置60也可以集成到已有的代码优化系统中,实现优化的功能。根据使用需要,装置60可以是在通用平台上执行的软件装置,或者专用的硬件装置,或者包括特定硬件平台以及其上运行的软件的组合装置。
在另一方面,本说明书的实施例还提供一种计算机可读的存储介质,其上存储有计算机指令代码,所述指令代码在计算机中执行时,令计算机执行以上描述的提供调用关系图的方法。
又一方面,本说明书的实施例还提供一种计算机可读的存储介质,其上存储有计算机指令代码,所述指令代码在计算机中执行时,令计算机执行以上描述的确定工程代码中的无用函数的方法。
本领域普通技术人员应该还可以进一步意识到,结合本文中所公开的实施例描述的各示例的单元及算法步骤,能够以电子硬件、计算机软件或者二者的结合来实现,为了清楚地说明硬件和软件的可互换性,在上述说明中已经按照功能一般性地描述了各示例的组成及步骤。这些功能究竟以硬件还是软件方式来执轨道,取决于技术方案的特定应用和设计约束条件。本领域普通技术人员可以对每个特定的应用来使用不同方法来实现所描述的功能,但是这种实现不应认为超出本申请的范围。
结合本文中所公开的实施例描述的方法或算法的步骤可以用硬件、处理器执轨道的软件模块,或者二者的结合来实施。软件模块可以置于随机存储器(RAM)、内存、只读存储器(ROM)、电可编程ROM、电可擦除可编程ROM、寄存器、硬盘、可移动磁盘、CD-ROM、或技术领域内所公知的任意其它形式的存储介质中。
以上所述的具体实施方式,对本发明的目的、技术方案和有益效果进行了进一步详细说明,所应理解的是,以上所述仅为本发明的具体实施方式而已,并不用于限定本发明的保护范围,凡在本发明的精神和原则之内,所做的任何修改、等同替换、改进等,均应包含在本发明的保护范围之内。