一种基于模式的Python代码内存泄漏检测方法
技术领域
本发明涉及软件
技术领域
,尤其涉及一种基于模式的Python代码内存泄漏检测方法。背景技术
内存泄漏是软件工程中常见的错误,即程序动态申请内存后,没有在其结束使用前释放内存,造成的内存资源长期被占用的情况。在项目早期,内存泄漏通常没有任何明显症状。内存泄漏的发生是一个连续不断的过程,随着其在系统中累积,泄漏对象的数量会不断增加,导致系统中的内存资源不断减少。当系统中的内存资源耗尽时,系统中的应用程序会暂时挂起,等待内存的重新分配。如果内存重新分配过程需要很长时间,它将导致系统崩溃。
近年来,编程语言采用垃圾回收机制等动态内存管理机制来帮助程序员防止泄漏缺陷的发生。但是,垃圾回收机制是通过对运行时的分析来处理内存泄漏的,它需要对程序进行检测、监视或测试。同时,需要有足够多的测试用例以保证测试过程中能够触发引起内存泄漏的代码。另一方面,研究人员也采用一些静态分析的方法进行内存泄漏的检测。静态分析方法静态分析将内存泄漏检测看成为一个可达性分析问题,可以在不实际运行程序的情况检测内存泄漏。但是,对于Python来说,代码不存在显式的内存分配和释放语句,因此静态地确定对象的可访问性的成本会非常昂贵。
发明内容
为了克服上述现有技术的不足,提出了一种基于模式的Python代码内存泄漏检测方法。本发明使用代码模式来解决内存泄漏的检测问题,可有效解决上述问题。本发明具体采用的技术方案如下:
一种基于模式的Python代码内存泄漏检测方法,其包括以下步骤:
S1、输入项目的源代码,遍历项目中的所有代码文件,加载每一个Python代码文件利用Python标准库中的ast模块得到每一个Python代码文件对应的抽象语法树
S2、利用抽象解释器技术基于步骤S1获得的抽象语法树进行类型推断,得到类型树其中类型树中的节点表示抽象类型,节点之间的关系表示从属关系;
S3、遍历步骤S2获得的类型树中的每个实例类型获取每个实例类型在类型树中的所有子节点,然后使用预定义的内存泄漏模式检查每个子节点b是否存在内存泄漏,如果满足其中一个内存泄漏模式,则记录引起内存泄漏的循环引用,每一个循环引用记录为一个节点序列[v0,v1,...vb],其中节点序列中的每个节点vi为类型树中的节点,相邻的节点之间存在引用关系,并且满足v0=vn。
作为优选,步骤S2中通过类型推断得到类型树的具体步骤如下:
S21、根据Python定义的类型,封装若干个抽象类型;
S22、对于每个Python代码文件对应的抽象语法树使用抽象解释器进行类型推断得到类型树类型树中的每个节点表示一种抽象类型且抽象语法树对应的模块类型加入到类型树T中作为根节点,节点之间的关系表示从属关系即子节点的定义在父节点内;
S23、依次遍历类型树中的每个函数类型的节点,得到所有函数类型;针对每一个函数类型,判断该函数类型是否在类型推断中存在调用,即有没有至少一个调用类型所调用的函数类型是该函数类型;如果有,则跳过继续对下一个函数类型进行判断,如果没有,则将其视为新产生的函数类型并创建一个调用类型,以未知类型作为传入参数,使用抽象解释器调用一次新创建的调用类型。
作为优选,步骤S21中封装的抽象类型包括以下11种:
1)模块类型:mod<id>,其中id表示模块的唯一标识符;
2)函数类型:fun<id>,其中id表示函数的唯一标识符;
3)调用类型:invoke<fun,[τ],τ>,其中fun表示该调用的函数类型,[τ]表示该调用需要的参数的类型,τ表示该调用的返回值的类型;
4)类类型:cls<id,[cls]>,其中id表示类的唯一标识符,[cls]表示该类的父类的类型;
5)实例类型:ins<cls>,其中cls表示该实例所属的类类型;
6)方法类型:meth<fun,ins>,其中fun表示函数类型,ins表示该方法所属的实例类型;
7)组合类型:任意类型的集合;
8)字典类型:dict<τ,τ>,其中两个τ分别表示字典中的健和值的类型;
9)列表类型:list<τ>,其中τ表示列表中的元素的类型;
10)元组类型:tuple<τ>,其中τ表示元组中的元素的类型;
11)集合类型:set<τ>,其中τ表示集合中的元素的类型。
作为优选,步骤S3中使用预定义的内存泄漏模式检查每个子节点b是否存在内存泄漏的具体步骤如下:
S31、预定义模式1为自引用造成的内存泄漏,判断子节点b是否满足模式1,如果b与相同,则认为满足模式1,此时记录引起内存泄漏的循环引用为
S32、预定义模式2为一个实例与容器之间的循环引用造成的内存泄漏,判断子节点b是否满足模式2,如果b严格包含则认为满足模式2,此时记录引起内存泄漏的循环引用为
S33、预定义模式3为一个实例与方法之间的循环引用造成的内存泄漏,判断是否满足模式3,判断步骤如S331和S332:
S331、如果子节点b属于方法类型,则检查b所属的实例类型b.ins,如果b.ins与相同,则认为满足模式3,此时记录引起内存泄漏的循环引用为
S332、如果子节点b属于组合类型,则检查b中包含的每个类型t,如果存在一个方法类型的t,其所属的实例类型t.ins与相同,则认为满足模式3,此时记录引起内存泄漏的循环引用为
S34、预定义模式4为两个实例之间的循环引用造成的内存泄漏,判断子节点b是否满足模式4,判断步骤如S341和S342:
S341、如果子节点b属于实例类型,并且b与不相同,则检查b的每个子节点,如果存在一个子节点c是实例类型并且与等价,则认为满足模式4,此时记录引起内存泄漏的循环引用为
S342、如果子节点b属于组合类型,则检查b中包含的每个类型t,如果存在一个t属于实例类型,则进一步检查t内的每个子节点c,如果存在一个子节点c是实例类型并且与等价,则认为满足模式4,此时记录引起内存泄漏的循环引用为
S35、预定义模式5为两个实例与容器之间的循环引用造成的内存泄漏,判断子节点b是否满足模式5,判断步骤如S351和S352:
S351、如果子节点b属于实例类型,并且b与不相同,则进一步检查b内的每个子节点,如果存在一个子节点c是实例类型并且包含则认为满足模式5,此时记录引起内存泄漏的循环引用为
S352、如果子节点b属于组合类型,则检查b中包含的每个类型t,如果存在一个t属于实例类型,则进一步检查t内的每个子节点c,如果存在一个子节点c是实例类型并且包含则认为满足模式5,此时记录引起内存泄漏的循环引用为
S36.预定义模式6为两个实例与方法之间的循环引用造成的内存泄漏,判断子节点b是否满足模式6,判断步骤如下:
如果子节点b属于实例类型,则进一步检查b的每个子节点c,如果存在一个子节点c是方法类型并且其所属的实例类型c.ins与相同,则认为满足模式6,此时记录引起内存泄漏的循环引用为
作为优选,步骤S32中对于任意两个类型x和y,两者之间的严格包含关系判断步骤如下:
1)如果x属于字典类型,则检查x的健或值是否与y等价,如果是则认为类型x包含类型y;
2)如果x属于集合类型、列表类型或元祖类型,则检查y是否属于x中的一项,如果是则认为类型x包含类型y;
3)如果x属于组合类型,则检查x中是否存在一个类型t包含类型y,如果是则认为类型x包含类型y。
作为优选,步骤S34中,对于任意两个类型x和y,两者之间的等价关系判断步骤如下:
1)如果x不属于组合类型,则判断x和y是否相同,如果相同则认为x和y等价;
2)如果x属于组合类型,则检查x中是否存在一个类型t与y等价,如果存在则认为x和y等价。
7.根据权利要求1所述的一种基于模式的Python代码内存泄漏检测方法,其特征在于,步骤S35中,对于任意两个类型x和y,两者之间的包含关系判断步骤如下:
1)如果x属于字典类型,则检查x的健或值是否与y相同,如果相同则认为类型x包含类型y。
2)如果x属于集合类型、列表类型或元祖类型,则检查y是否属于x,如果是则认为类型x包含类型y。
3)如果x属于组合类型,则检查x中是否存在一个类型t包含类型y,如果存在则认为类型x包含类型y。
本发明使用抽象解释器来进行类型推断,并在类型树的基础上采用基于模式的检测方法来检测代码中的内存泄漏,本法明具有如下收益:1、利用类型推断来获取代码的类型信息,使得内存泄漏检测适用于动态语言如Python;2、使用基于模式的内存泄漏检测方法,使得内存泄漏具有准确率高、速度快的特点。
附图说明
图1为本发明方法的流程图。
具体实施方式
为了使本发明的目的、技术方案及优点更加清楚明白,以下结合附图对本发明进行进一步详细说明。
相反,本发明涵盖任何由权利要求定义的在本发明的精髓和范围上做的替代、修改、等效方法以及方案。进一步,为了使公众对本发明有更好的了解,在下文对本发明的细节描述中,详尽描述了一些特定的细节部分。对本领域技术人员来说没有这些细节部分的描述也可以完全理解本发明。
如图1所示,本发明的一种基于模式的Python代码内存泄漏检测方法,包括以下步骤:
S1、代码提取:输入项目的源代码,遍历项目中的所有代码文件,加载每一个Python代码文件利用Python标准库中的ast模块相应函数得到每一个Python代码文件对应的抽象语法树
S2、类型解析:利用抽象解释器技术基于步骤S1获得的抽象语法树进行类型推断,得到类型树其中类型树中的节点表示抽象类型,节点之间的关系表示从属关系。
在本实施例中,利用抽象解释器技术基于步骤S1获得的抽象语法树进行类型推断得到类型树的具体步骤如下:
S21、根据Python定义的类型,封装若干个抽象类型;
本实施例中,封装的抽象类型包括以下11种:
1)模块类型:mod<id>,其中id表示模块的唯一标识符;
2)函数类型:fun<id>,其中id表示函数的唯一标识符;
3)调用类型:invoke<fun,[τ],τ>,其中fun表示该调用的函数类型,[τ]表示该调用需要的参数的类型,τ表示该调用的返回值的类型;
4)类类型:cls<id,[cls]>,其中id表示类的唯一标识符,[cls]表示该类的父类的类型;
5)实例类型:ins<cls>,其中cls表示该实例所属的类类型;
6)方法类型:meth<fun,ins>,其中fun表示函数类型,ins表示该方法所属的实例类型;
7)组合类型:任意类型的集合,即多个抽象类型的集合;
8)字典类型:dict<τ,τ>,其中两个τ分别表示字典中的健和值的类型;
9)列表类型:list<τ>,其中τ表示列表中的元素的类型;
10)元组类型:tuple<τ>,其中τ表示元组中的元素的类型;
11)集合类型:set<τ>,其中τ表示集合中的元素的类型。
S22、对于每个Python代码文件对应的抽象语法树使用抽象解释器进行类型推断得到类型树类型树中的每个节点表示一种抽象类型,节点之间的关系表示从属关系即子节点的定义在父节点内,而且抽象语法树对应的模块类型加入到类型树T中作为根节点
S23、依次遍历类型树中的每个函数类型的节点,得到所有函数类型;针对所有函数类型中的每一个函数类型,判断该函数类型是否在类型推断中存在调用,即有没有至少一个调用类型所调用的函数类型是该函数类型;如果有,则跳过继续对下一个函数类型进行判断,如果没有,则将其视为新产生的函数类型并创建一个调用类型,以未知类型作为传入参数,使用抽象解释器调用一次新创建的调用类型。
S3、内存泄漏检测:遍历步骤S2获得的类型树中的每个实例类型获取每个实例类型在类型树中的所有子节点,然后使用预定义的内存泄漏模式检查每个子节点b是否存在内存泄漏,如果满足其中一个内存泄漏模式,则记录引起内存泄漏的循环引用,每一个循环引用记录为一个节点序列[v0,v1,...vn],其中节点序列中的每个节点vi为类型树中的节点,相邻的节点之间存在引用关系,并且满足v0=vn。
本实施例中,使用预定义的内存泄漏模式检查每个子节点b是否存在内存泄漏的具体步骤如下:
S31、预定义模式1为自引用造成的内存泄漏,判断子节点b是否满足模式1,如果b与相同,则认为满足模式1,此时记录引起内存泄漏的循环引用为
S32、预定义模式2为一个实例与容器之间的循环引用造成的内存泄漏,判断子节点b是否满足模式2,如果b严格包含则认为满足模式2,此时记录引起内存泄漏的循环引用为
其中,判断b是否严格包含时,需要设定两者之间严格包含关系的判断规则。由于b和均代表了一种抽象类型,因此本实施例中对于任意两个抽象类型x和y,两者之间的严格包含关系判断步骤如下:
1)如果x属于字典类型,则检查x的健或值是否与y等价,如果是则认为类型x包含类型y;
2)如果x属于集合类型、列表类型或元祖类型,则检查y是否属于x中的一项,如果是则认为类型x包含类型y;
3)如果x属于组合类型,则检查x中是否存在一个类型t包含类型y,如果是则认为类型x包含类型y。
S33、预定义模式3为一个实例与方法之间的循环引用造成的内存泄漏,判断是否满足模式3,判断步骤如S331和S332:
S331、如果子节点b属于方法类型,则检查b所属的实例类型b.ins,如果b.ins与相同,则认为满足模式3,此时记录引起内存泄漏的循环引用为
S332、如果子节点b属于组合类型,则检查b中包含的每个类型t,如果存在一个方法类型的t,其所属的实例类型t.ins与相同,则认为满足模式3,此时记录引起内存泄漏的循环引用为
S34、预定义模式4为两个实例之间的循环引用造成的内存泄漏,判断子节点b是否满足模式4,判断步骤如S341和S342:
S341、如果子节点b属于实例类型,并且b与不相同,则检查b的每个子节点,如果存在一个子节点c是实例类型并且与等价,则认为满足模式4,此时记录引起内存泄漏的循环引用为
S342、如果子节点b属于组合类型,则检查b中包含的每个类型t,如果存在一个t属于实例类型,则进一步检查t内的每个子节点c,如果存在一个子节点c是实例类型并且与等价,则认为满足模式4,此时记录引起内存泄漏的循环引用为
其中,判断c与是否等价时,需要设定两者之间等价关系的判断规则。由于c和均代表了一种抽象类型,因此本实施例中对于任意两个抽象类型x和y,两者之间的等价关系判断步骤如下:
1)如果x不属于组合类型,则判断x和y是否相同,如果相同则认为x和y等价;
2)如果x属于组合类型,则检查x中是否存在一个类型t与y等价,如果存在则认为x和y等价。
S35、预定义模式5为两个实例与容器之间的循环引用造成的内存泄漏,判断子节点b是否满足模式5,判断步骤如S351和S352:
S351、如果子节点b属于实例类型,并且b与不相同,则进一步检查b内的每个子节点,如果存在一个子节点c是实例类型并且包含则认为满足模式5,此时记录引起内存泄漏的循环引用为
S352、如果子节点b属于组合类型,则检查b中包含的每个类型t,如果存在一个t属于实例类型,则进一步检查t内的每个子节点c,如果存在一个子节点c是实例类型并且包含则认为满足模式5,此时记录引起内存泄漏的循环引用为
其中,判断c是否包含时,需要设定两者之间包含关系的判断规则。由于c和均代表了一种抽象类型,因此本实施例中对于任意两个类型x和y,两者之间的包含关系判断步骤如下:
1)如果x属于字典类型,则检查x的健或值是否与y相同,如果相同则认为类型x包含类型y。
2)如果x属于集合类型、列表类型或元祖类型,则检查y是否属于x,如果是则认为类型x包含类型y。
S36.预定义模式6为两个实例与方法之间的循环引用造成的内存泄漏,判断子节点b是否满足模式6,判断步骤如下:
如果子节点b属于实例类型,则进一步检查b的每个子节点c,如果存在一个子节点c是方法类型并且其所属的实例类型c.ins与相同,则认为满足模式6,此时记录引起内存泄漏的循环引用为[c.ins,i]。
3)如果x属于组合类型,则检查x中是否存在一个类型t包含类型y,如果存在则认为类型x包含类型y。
上述S31~S36定义了6中不同的内存泄漏模式,每个子节点b只要符合任意一种模式就视为存在内存泄漏,若不符合任意一种模式则视为不存在内存泄漏。通过上述S1~S3的做法,就可以实现Python代码的内存泄漏检测,并定位得到造成内存泄漏的循环引用,最终可以得到所有导致内存泄漏的循环引用集合。该方法适用于软件开发过程中的编码阶段的检测,能有效地在代码运行之前检测出代码中存在的内存泄漏,及时通报相关开发人员采取相应的解决方案。
下面将上述S1~S3应用于一个具体实施例中,以展示其技术效果。
实施例
本实施例步骤与具体实施方式相同,在此不再进行赘述。下面就部分实施过程和实施结果进行展示:
数据源获取:本实施例所用的代码是从GitHub开源社区获得的4个真实开源项目的源代码。由于项目未保存有关真实内存泄漏的信息,因此有必要运行每个项目的测试用例以收集运行过程中产生的内存泄漏信息,其中每个项目的相关统计信息见表1。然后,将收集到的引起内存泄漏进行人工分类,分为符合本发明技术方案中的6种模式。
结果验证:本实施例将实际代码运行过程中收集到的引起内存泄漏的循环引用集合与将本发明技术方案应用于项目源代码所检测得到的可能引起内存泄漏的循环引用集合做比较,以评价其方案的有效性。为了验证本发明技术方案的技术效果,选取四个指标来衡量检测的性能:
SP:用本发明技术方案找到的并且在实际运行中存在的循环引用的个数。
SN:用本发明技术方案找到的并且在实际运行中不存在的循环引用的个数。
DP:实际运行中存在的并且用本发明技术方案找到的循环引用的个数。
DN:实际运行中存在的并且用本发明技术方案不能找到的循环引用的个数。
为了计算上述四个指标,需要判断用本发明技术方案找到的一个可能引起内存泄漏的循环引用Si是否与实际运行中收集到的一个引起内存泄漏的循环引用Di相同。假设本实施例将实际代码运行过程中收集到的引起实际内存泄漏的循环引用集合为D={D1,D2,...,Dn}。将本发明技术方案应用于项目源代码所检测得到的可能引起内存泄漏的循环引用集合为S={S1,S2,...,Sm}。假设Si=[s1,s2,...,sk]与Di=[d1,d2,...,dk]分别为包含k个类型且属于同一模式的循环引用,如果存在一个常量l(0≤l<k),si=di+l,则判断Si与Di相同。其中si=dj的判断方法如下:
(1)si与dj相同;
(2)如果dj与si相同,那么dj与si的子类型也相同
表2展示了在4个数据集上使用本发明技术方案进行内存泄漏检测的到的结果与实际代码运行所收集的内存泄漏进行的比较。由表中可以看到,4个项目的DN都为0,这证明了本发明提出的基于模式的内存泄漏检测方法的有效性,即在这4个项目中本发明技术方案可以找到所有实际运行出现的内存泄漏。另外,对于boto、djblets和libNeuroML三个项目,由表可见,本发明方法分别找出了3、3和6个实际代码运行中不存在的循环引用,通过人工校验,以上12个为实际运行中没有收集到的但是真实存在的内存泄漏。这是因为代码运行所收集到的内存泄漏依赖于测试用例,在测试用例不足时,很难收集到所有的内存泄漏。而本发明技术方案属于静态分析,不依赖于测试用例。从总体上看,SP比DP大,这说明本发明方法的准确率高,除了实际运行收集到的内存泄漏外,还能够找到因为测试用例无法覆盖到而在实际运行中没能收集到的内存泄漏。
表1 4个真实项目的实际运行得到内存泄漏数据集的统计信息表
表2本发明方法检测结果和实际代码运行收集的结果的对比
项目名称
SP
SN
DP
DN
boto
315
3
274
0
djblets
26
3
25
0
libNeuroML
256
6
63
0
pymtl
56
0
37
0
以上所述的实施例只是本发明的一种较佳的方案,然其并非用以限制本发明。有关技术领域的普通技术人员,在不脱离本发明的精神和范围的情况下,还可以做出各种变化和变型。因此凡采取等同替换或等效变换的方式所获得的技术方案,均落在本发明的保护范围内。