静态代码检查方法、装置、存储介质和计算机设备与流程

文档序号:15587947发布日期:2018-10-02 18:36阅读:139来源:国知局
本申请涉及软件测试
技术领域
:,特别是涉及一种静态代码检查方法、装置、计算机可读存储介质和计算机设备。
背景技术
::静态代码检查是指无需运行被测代码,仅通过分析或检查源程序的语法、结构等来对被测代码进行检查,找出被测代码中的错误。静态代码检查能够发现编译器不能发现的问题,也可以自定义一些检查规范,来检查代码编写中不规范、存在安全隐患的地方。以对lua语言编写的代码文件进行静态代码检查为例,由于lua语言具有简洁,体积小,执行效率高,可以方便地与c++、c#等语言进行交互,支持app进行热更新等特点,越来越多地在游戏领域使用。lua的变量默认为全局变量,可以分布在不同的文件中。而传统的静态代码检查工具,例如luacheck等,仅能够分开对每个lua代码文件进行检查,导致全局变量的检查误报率高,即检查的准确率低。技术实现要素:基于此,有必要针对静态代码检查准确率低的技术问题,提供一种静态代码检查方法、装置、存储介质和计算机设备。一种静态代码检查方法,包括:获取各代码文件;对各代码文件进行处理,得到各代码文件的标记序列;识别所述标记序列中的依赖函数;根据所述依赖函数分析各代码文件之间的依赖关系,将未被依赖过的代码文件作为检查入口文件;根据被依赖的代码文件,展开对应的存在依赖关系的所述检查入口文件以模拟所述依赖函数的执行逻辑;对各展开后代码文件的标记序列进行检查,得到检查结果。一种静态代码检查装置,包括:代码文件获取模块,用于获取各代码文件;文件处理模块,用于对各代码文件进行处理,得到各代码文件的标记序列;识别模块,用于识别所述标记序列中的依赖函数;依赖分析模块,用于根据所述依赖函数分析各代码文件之间的依赖关系,将未被依赖过的代码文件作为检查入口文件;展开模块,用于根据被依赖的代码文件,展开对应的存在依赖关系的所述检查入口文件以模拟所述依赖函数的执行逻辑;检查模块,用于对各展开后代码文件的标记序列进行检查,得到检查结果。一种计算机可读存储介质,存储有计算机程序,所述计算机程序被处理器执行时,使得所述处理器执行上述方法的步骤。一种计算机设备,包括存储器和处理器,所述存储器存储有计算机程序,所述计算机程序被所述处理器执行时,使得所述处理器执行上述方法的步骤。上述静态代码检查方法、装置、计算机可读存储介质和计算机设备,通过识别标记序列中的依赖函数,根据依赖函数分析各代码文件之间的依赖关系,将未被依赖过的代码文件作为检查入口文件,根据被依赖的代码文件,展开对应的存在依赖关系的检查入口文件以模拟依赖函数的执行逻辑。该方法能够将被依赖的代码的执行逻辑在检查入口文件中展开,即将存在依赖关系的文件结合起来,考虑了全局变量分布在不同文件中的特点,实现了代码跨文件的静态代码检查处理,从而减少了全局变量检查的误报率,进一步地提高了静态代码检查的准确率。附图说明图1为一个实施例中静态代码检查方法的流程示意图;图2为一个实施例中文件依赖关系的示意图;图3为一个实施例中对检查入口文件进行展示的步骤流程示意图;图4为一个实施例中将逻辑相关的标记关联的示意图;图5为一个实施例中创建的抽象语法树的示意图;图6为一个实施例中静态代码检查的命令行工具的界面示意图;图7为一个实施例中静态代码检查gui工具的界面示意图;图8为另一个实施例中静态代码检查方法的流程示意图;图9为一个实施例中静态代码检查装置的结构框图;图10为一个实施例中计算机设备的结构框图。具体实施方式为了使本申请的目的、技术方案及优点更加清楚明白,以下结合附图及实施例,对本申请进行进一步详细说明。应当理解,此处所描述的具体实施例仅仅用以解释本申请,并不用于限定本申请。本申请的静态代码检查方法应用于终端。其中终端为开发终端,例如游戏开发人员所使用的终端。终端具体可以是台式终端或移动终端,移动终端具体可以为笔记本电脑。开发人员在使用动态脚本编程语言(例如lua)编写完成游戏代码后,通过终端运行本申请的静态代码检查方法,对编写完成的代码进行检查。如图1所示,在一个实施例中,提供了一种静态代码检查方法。本实施例主要以该方法应用于开发终端为例进行说明。参照图1,该静态代码检查方法具体包括如下步骤:s102,获取各代码文件。代码文件是指使用编程语言编写的代码文件,是静态代码检查的检查对象。以编程语言为lua编码的代码文件为例,lua是一种动态类型脚本语言,具有无需编译即可执行、变量首次出现即定义,变量类型可以混用等特点。lua通常和其他编程语言一起使用,例如c++,c#等。以游戏应用开发为例,对于一个游戏应用的开发,核心功能通常使用c++,c#等编程语言编写,使用lua等脚本语言编写嵌入式脚本放到游戏中。本申请的静态代码检查方法用于对lua编写的代码进行检查,本实施例中的代码文件即lua文件。lua文件即后缀名称为.lua的代码文件。具体地,对于应用程序的代码,从中提取后缀名称为.lua的代码文件,得到各代码文件。在实际应用中,在终端运行的静态代码检查程序,输入代码目录,由静态代码检查程序在对代码目录下寻找所有的lua文件。其中,代码目录可以由测试人员手动输入。一些应用程序在开发时的某些代码使用的是第三方库,应用程序开发过程中并不会对第三方库的代码进行修改。为避免代码检查时的误报,通常会对第三方库的代码进行屏蔽,即设置屏蔽路径,对屏蔽路径下的诸如第三方库的代码的检查结果不显示。但在寻找lua文件的过程中,为了保证符号的完整性,即使配置的屏蔽路径,屏蔽路径下的代码文件仍会寻找出来。s104,对各代码文件进行处理,得到各代码文件的标记序列。得到标记序列的处理过程,具体为对代码文件进行词法分析的过程,具体地,将代码文件中的字符序列转换为标记(token)序列。标记(token)是编程语言中的最小元素,token包括单词符号(例如单词字符串)或符号。代码文件中通常包括程序相关的代码字符序列和对代码字符序列的注释。对各代码文件的处理,包括移除代码文件中的注释,仅保留代码字符序列。对于代码字符序列,根据构词规则识别标记,得到各代码文件的标记序列。s106,识别标记序列中的依赖函数。依赖函数是指具有引用功能,能够在当前的代码文件中引用另一个代码文件的函数。依赖函数的参数为被引用的代码文件的名称。在一个应用程序的代码中,可通过自定义函数或是程序语言所提供的依赖函数,在一个代码文件中引用另一个代码文件。以lua为例,在一个应用程序的代码中,通过自定义函数或程序语言所提供的加载函数,一个lua文件能够指定加载另一个lua文件。通过加载了另一个lua文件,就可以使用被加载的lua文件中的全局函数和全局数据,这样修改完lua代码后,可以不用重启程序立刻失效。基于lua语言的这一特点,一个应用程序的代码中,存在加载关系的各lua文件之间存在依赖关系。加载函数即依赖函数的一种。例如,lua提供三个加载函数,分别为require,dofile和loadfile。其中,loadfile加载文件,编译代码,不会运行文件里的代码。dofile加载文件,编译并执行文件里的代码。require加载文件,编译代码,并在第一次加载文件里,执行文件里的代码。以一个名称为a的lua文件为例,a=require(“b”)print(b)以上代码表示,名称为a的lua文件加载了名称为b的lua文件,名称为a的lua文件与名称为b的lua文件存在加载关系,则名称为a的lua文件依赖名称为b的lua文件。其中,require,dofile和loadfile为依赖函数。无论是自定义的依赖函数,还是程序语言所提供的依赖函数,均可预先配置依赖函数对应的字符作为查找标准,根据配置的依赖函数的字符,识别标记序列中的依赖函数。s108,根据依赖函数分析各代码文件之间的依赖关系,将未被依赖过的代码文件作为检查入口文件。具体地,对各代码之间的依赖关系分析,基于表示依赖关系的关键字进行。其中,表示依赖关系的关键字为自定义函数或程序语言所提供的依赖函数对应的标记序列。表示依赖的关键字可预先由测试人员进行配置。未被依赖过的代码文件表示该代码文件未被其它lua文件所依赖,以未被依赖过的代码文件作为检查入口文件,该未被依赖过的lua文件依赖(加载)了其它的lua文件,通过这种依赖关系,能够将加载了其它lua文件的lua文件展开。例如,如果文件a通过依赖函数等加载文件b,则认为文件a依赖文件b,文件b被依赖一次。从任意文件开始,依次分析每一个文件所依赖的文件,可以得到一个文件依赖关系形成的依赖关系图。一个实施例中的依赖关系如图2所示,具体为文件b被文件c依赖,文件a依赖文件b和c,文件d依赖文件c。在这个依赖关系图中,文件d未被任何一个文件所依赖,故将文件d作为检查入口文件。s110,根据被依赖的代码文件,展开对应的存在依赖关系的检查入口文件以模拟依赖函数的执行逻辑。依赖函数为表征依赖关系的函数,可以为加载函数,例如require,dofile和loadfile系数加载函数或自定义加载函数。展开是指对于在检查入口文件中,根据被依赖的代码文件,模拟依赖函数的执行逻辑。具体地,展开后的检查入口文件以自定义函数的形式模拟被依赖的代码文件的执行逻辑。通过根据依赖的代码文件,展开对应的存储依赖关系的检查入口文件,以模拟依赖函数的执行逻辑,能够将被依赖的代码的执行逻辑在检查入口文件中展开,即将存在依赖关系的文件结合起来,考虑了全局变量分布在不同文件中的特点,实现了代码跨文件的静态代码检查处理,从而减少了全局变量检查的误报率。s112,对各展开后代码文件的标记序列进行检查,得到检查结果。具体地,对各展开的代码文件对应的每个标记序列,依次执行配置的每个检查项。检查特定问题分类的程序称作检查项。静态代码检查程序通常包含多个检查项。检查项可包括未初始化变量检查、未使用变量检查,函数返回值检查、未使用函数参数检查等等。其中,对于各检查项的配置信息可通过静态代码检查工具所提供的配置界面进行配置,得到配置文件。工具的配置文件包括两部分,分别为系统配置文件和自定义配置文件。其中,系统配置文件配置了编程语言,例如lua提供的系统变量和函数的配置规则。自定义配置文件配置了不同问题功能检查、路径屏蔽、已定义变量和具体项目相关的配置信息。对各展开后代码文件的标记序列进行检查,根据配置信息,对每一标记序列执行各检查项对应的检查逻辑,得到检查结果。上述的静态代码检查方法,通过识别标记序列中的依赖函数,根据依赖函数分析各代码文件之间的依赖关系,将未被依赖过的代码文件作为检查入口文件,根据被依赖的代码文件,展开对应的存在依赖关系的检查入口文件以模拟依赖函数的执行逻辑。该方法能够将被依赖的代码的执行逻辑在检查入口文件中展开,即将存在依赖关系的文件结合起来,考虑了全局变量分布在不同文件中的特点,实现了代码跨文件的静态代码检查处理,从而减少了全局变量检查的误报率,进一步地提高了静态代码检查的准确率。在另一个实施例中,根据依赖函数分析各代码文件之间的依赖关系,将未被依赖过的代码文件作为检查入口文件,包括:根据依赖函数的参数,确定被依赖的代码文件;确定存在依赖函数的代码文件与被依赖的代码文件的依赖关系;将未被依赖过的代码文件作为检查入口文件。具体地,依赖函数的参数为被引用的代码文件的名称。例如一个代码文件中,使用了依赖函数引用另一个代码文件,以lua的加载函数require为例:--test.lua—a=require(“lib”)print(a)其中,“lib”为加载函数require的参数,表示被加载的lua文件的名称,即被依赖的代码文件的名称为“lib”,存在依赖函数require的代码文件为名称为test的lua文件,在这个lua文件中,使用了加载函数require加载名称为lib的lua文件。则可确定这两个代码文件之间的依赖关系。检查入口文件即静态代码检查的入口或起点。由于检查入口文件未被其它代码文件所依赖,可能是独立的,或是依赖了其它代码文件。若依赖了其它代码文件,能够根据这种依赖关系获取到其所依赖的代码文件。故而,将未被其它代码文件所依赖的代码文件作为检查入口文件,能够依次依照这种依赖关系,寻找到所依赖的代码文件,为跨文件处理提供基础。在另一个实施例中,根据被依赖的代码文件,展开对应的存在依赖关系的检查入口文件以模拟依赖函数的执行逻辑的步骤包括:在检查入口文件中,删除依赖函数;增加设定的全局函数,将被依赖的代码文件作为全局函数的函数体。由于存在依赖关系的两个代码文件之间为引用关系,即存依赖函数的代码文件引用了被依赖的代码文件,故,可以根据这种引用关系,将被依赖代码文件的相关代码展示在存在依赖函数的代码文件中。具体地,删除依赖函数,增加设定的全局函数,将被依赖的代码文件作为全局函数的函数体。其中,函数体是函数中需要执行的代码语句块。即,展开的实质是利用全局函数模拟依赖函数的执行逻辑。本实施例中,对于全局函数的函数名称,不应当与代码文件中的变量重复,在实际的应用中,可使用设定的一个特定字符串+流水码的形式,作为全局函数的函数名称。其中,“function(x)<body>end”视为函数的构造式,又被称其为匿名函数。将被依赖的代码文件作为构造的全局函数的函数体。以lua的两个代码文件进行说明,一个代码文件的名称为test,一个代码文件的名称为lib。其中,名称为lib的代码文件的具体代码如下:--lib.lua—locala=123returna名称为test的代码文件的具体代码为下:--test.lua—a=require(“lib”)print(a)根据识别的依赖函数require,以及依赖函数的参数所表示的加载的代码文件的名称,可以确定上述两个代码文件之间存在依赖关系,具体为名称为test的lua文件,加载了名称为lib的lua文件。即,名称为lib的lua文件为被依赖文件,名称为test的lua文件为存在依赖函数的lua文件。在这个依赖关系中,名称为test的lua文件未被依赖,则将其作为检查入口文件。展开检查入口文件,即在名称为test的lua文件中,删除依赖函数,增加设定的全局函数,将被依赖的名称为lib的lua文件的代码作为全局函数的函数体。展开后的检查入口文件为:这种展示的方式,可以完全的保留变量的作用域和被依赖的代码文件中return的逻辑语义,等价模拟依赖函数的执行逻辑,能够避免后续语法分析、检查程序的跨文件复杂性。在另一个实施例中,从依赖函数是否运行被依赖代码文件中的代码的角度,可以将依赖函数分为运行类型的依赖函数和非运行类型的依赖函数。具体地,非运行类型的依赖函数仅加载文件,编译代码,不会运行代码文件中的代码。运行类型的依赖函数加载文件,编译代码,并运行代码文件中的代码。以lua中的三个加载函数为例,分别为require,dofile和loadfile。这三个加载函数分别表示不同的类型,具有不同的功能。其中,loadfile加载文件,编译代码,不会运行文件里的代码,为非运行类型的依赖函数。dofile加载文件,编译并执行文件里的代码。require加载文件,编译代码,并在第一次加载文件时,执行文件里的代码。dofile和require由于会运行文件里的代码,为运行类型的依赖函数。对于运行类型的依赖函数,由于需要体现运行的逻辑,在增加设定的全局函数,将被依赖的代码文件的代码作为全局函数的函数体之后,还包括:调用全局函数。以上述的名称为test的lua文件中作为入口文件为例,展开后的代码为:可以理解的是,对于运行类型的依赖函数,可根据具体的运行逻辑设置展开规则。例如,require加载文件,编译代码,并在第一次加载文件时,执行文件里的代码。则对于存在require函数的检查入口文件的展开,其展开逻辑如图3所示,包括以下步骤:s302,确定检查入口文件。检查入口即根据依赖关系分析得到的,未被依赖过的代码文件。在一个应用程序的静态代码分析中,检查入口可以为多个,后续对每个检查入口的处理,可以并行处理。s304,在各检查入口文件中,查找依赖函数。s306,判断依赖函数的类型。若依赖函数为require,则执行s310.s310,在检查入口文件中,删除依赖函数。s312,增加设定的全局函数,将被依赖的代码文件的代码作为全局函数的函数体。具体地,添加自定义全局函数对应的字符串,例如,全局函数的字符号包括functionname()end,将被依赖的代码文件添加到右括号和end之间。s314,调用全局函数。具体地,在end后添加函数调用全局函数对应的代码。以上述名称为test的lua文件中作为入口文件为例,展开后的代码为:s316,判断检查入口文件是否还存在依赖函数。若否,则结束,若是,则返回步骤s304。以上述的名称为test的lua文件中作为入口文件为例,展开后的代码为:这种展示的方式,可以完全的保留变量的作用域和被依赖的代码文件中return的逻辑语义,等价模拟依赖函数的执行逻辑,能够避免后续语法分析、检查程序的跨文件复杂性。在另一个实施例中,对各代码文件进行处理,得到各代码文件的标记序列,包括:对各代码文件进行预处理;将预处理后的各代码文件进行分词处理,得到各代码文件的标记序列;将标记序列中逻辑上相关的标记关联。其中,预处理是指为使分词更加准确,对代码文件在分词前进行的预处理行为。分词前的预处理包括去除注释和转义处理。在实际的应用中,在进行分词预处理前,还可以使用与编程语言对应的词法分析器,从代码文件中的源程序提取标识符、注释,数字、变量和运算符等。以编程语言为lua为例,词法分析器可以借鉴lua的lexer词法解析库进行词法分析。对预处理后的各代码文件进行分词处理,分词处理可采用传统的分词器进行,本实施例中不再赘述。分词处理后,得到各代码文件的标记(token)序列,即各token组成的序列。其中,token是分词程序的输出。一个程序语句通常有若干个token组成。例如,print(a)这个语句包含“print”、“(”、“a”、“)”这四个token。分词后并采用双向链表的方式记录所有的token。双向链表是一种基础计算机程序数据结构,每个元素可以访问前一个以及后一个元素。一个代码文件中的标记序列,可能存在逻辑上相关的token。以代码print(a[1])为例,分词后得到的标记序列分别为:print、(、a、[、1、]和)。其中左中括号和右中括号为在逻辑上相关的token),左括号和右括号为在逻辑上相关的token。关联后的标记序列如图4所示。通过将逻辑上相关的token关联,便于根据关联关系进行检查。在另一个实施例中,对各代码文件进行处理,得到各代码文件的标记序列的步骤,还包括:基于简化逻辑,对分词得到的标记进行简化。不同项目的代码、不同程序员的代码风格是不同的,从而使代码文件具有多样性,这样不利于后续的语法分析。在不改变代码文件的逻辑的情况下,采用预设的简化逻辑对标记序列进行简化。token简化主要在于简化后续语法分析以及问题检查程序开发的复杂度,一些主要的简化项如表1所示:表1简化逻辑在另一个实施例中,对各展开后文件的标记序列进行检查,得到检查结果,包括:对展开后文件的标记序列进行语法分析,得到代码文件中的作用域、变量和函数;根据作用域、变量和函数进行对各token进行检查。语法分析是指对代码文件进行分析,以分析代码是否符合编程语言的语言规范。语法分析可利用预先根据编程语言的语言规范,编写符合开源语法分析器规定的文法,得到的语言描述文件,利用语言识别工具将语言描述文件转换成语法分析器。具体地,语法分析器根据标记序列和语法描述文件中的语法部分,将标记序列组成在一起,识别代码中的作用域、变量和函数等。其中,作用域是指任何包含多条语句的代码结构。例如doxxxend是一个无条件do作用域,ifxxxthenyyyend是一个条件作用域,每一个token都有一个字段记录所在的作用域。变量信息记录了变量的id,作用类型(全局,局部,table的成员等),首次定义token,首次定义值类型(nil,boolean,number,string,function等),以及变量定义方式(参数,for循环变量,系统变量等)。函数信息记录函数定义的token,参数个数,返回值个数等。根据语法分析后提取的作用域,变量和函数,对各token依次执行配置的检查项。本实施例中检查项的开发可以在更高的语法抽象层次上(函数,作用域,变量等)进行,以简化检查项的开发。具体地,在对展开后文件的标记序列进行语法分析,得到代码文件中的作用域、变量和函数,包括:根据展开后文件的标记序列构建抽象语法树;根据抽象语法树,对展开后文件的标记序列进行语法分析,得到代码文件中的作用域、变量和函数。其中,抽像语法树(ast).用数状图来表示程序的语法结构,是一个二叉树,每一个非叶子节点代表一个运算符,它的两个子节点分别代表该运算符的两个运算分量。树形语法结构包含了表达式的逻辑结构和运算符的优先级关系,可以方便的表达程序的执行方式,极大的简化程序分析逻辑。本方案中的ast仅限制在单行表达式。例如对于if表达式ifa==nilora==0thenreturnfalseend,可以创建两个ast。如图5所示。根据抽象语法树,对展开后文件的标记序列进行语法分析,得到代码文件中的作用域、变量和函数。以未使用的函数参数检查为例,一种实现方式如下:依次检查每一个token,如果token没有关联任何变量或者token是函数参数定义,则继续处理下一个token;否则标记该函数参数使用过。遍历所有的变量,如果变量是函数参数,且参数没有被标记过,则该函数参数没有被使用过。输出未使用函数参数报错信息。例如:functionfoo(a,b)print(a)end一次遍历function=>foo=>(=>a=>,=>b=>)=>print=>(遇到a作为函数参数被使用了,标记参数a被使用,而参数b没有被使用,最后报错参数b未使用再以未定义变量使用检查为例,说明跨文件分析的重要意义。对于如下两个代码文件:--filever.luagversion=”5.3.3”--filetest.luarequire(“ver”)print(gversion)如果没有分析require依赖的ver.lua文件,则文件test.lua中,gversion就是未定义的变量,单独扫描ver.lua,会发现gversion定义了确没有使用,造成两条误报。而实际上,做了入口分析以及展开后,得到的文件如下--filetest.luaexpandedfunctionanonymous()gversion=”5.3.3”endanonymous();print(gversion)这样函数anonymous定义了变量gversion,print使用的是定义过的变量。文件展开后的语法分析阶段可以分析出来第三行的gversion和最后一行的gversion属于同一个变量。未初始化变量检查程序只需要从第一行开始分析,发现定义了函数anonymous,然后分析函数体,发现函数初始化了全局变量gversion,则将变量gversion标记为已初始化,后续遇到所有的gversion变量都无需再报错(为了简化表述,忽略函数执行顺序)。因此,采用本申请的静态代码检查方法,确定检查入口文件,并根据依赖关系对检查入口文件进行展开对降低误报的重要作用,语法分析对于降低检查项开发复杂度也具有重要意义。在另一个实施例中,在对各展开后代码文件的标记序列进行检查,得到检查结果之后,还包括:根据预先配置的过滤项,对所述检查结果进行过滤后输出。具体地,lua语言通常和c++、c#等宿主语言配置使用,会依赖其他语言导出的符号、变量、函数等。单纯的扫描lua源代码会出现很多符号未定义的问题。静态代码检查工具会模拟luabind、tolua++等工具的行为,导出c++、c#中特定标记的枚举、变量、函数等过滤项,并将这一部分符号用于lua的错误过滤。符号的导出只需要对c++、c#语言做简单的分词、语法分析即可实现。其中,过滤项可预先配置。该方法可以识别c++,c#等语言的导出符号识别,过滤结果,提高代码检查的准确性。在另一种实施方式中,过滤的方式还可以为,在检查入口文件加载根据预先配置的过滤项编写的过滤代码文件。具体地,将c++、c#中特定标记的枚举、变量、函数等过滤项编写成一个过滤代码文件,可以为lua文件,和其他lua文件一起扫描,每个入口文件默认加载该导出文件。对各展开后代码文件的标记序列进行检查,得到检查结果包括:根据所述过滤代码文件,对各展开后代码文件的标记序列进行检查,得到检查结果。即在扫描时就根据过滤代码文件进行过滤,以达到降低误报的作用。在另一个实施例中,对各展开后代码文件的标记序列进行检查,得到检查结果之后,还包括输出检查结果。具体地,对检查结果进行格式化输出。其中,检查结果的输出目录可预先配置。具体地,在实际应用中,静态代码检查工具可以是命令行版本或gui版本。命令行工具可以单独使用,如图6所示。在windows命令行界面或者linux/mac终端,向扫描程序指定输入输出目录就可以进行扫描,扫描后的结果可以已xml文件的格式输出到屏幕或者文件。扫描结果提供错误所在的文件、行号、错误类型,具体的错误信息等。在windows平台,扫描工具提供gui工具,gui工具的工作界面如图7所示。gui程序和命令行程序为两个独立进程。指定扫描输入目录后,点击扫描,可以显示扫描结果,双击可以产看错误代码。下面,以静态代码编程语言为lua,对静态代码检查方法进行详细说明。图8为一个实施例中静态代码检查方法的流程示意图。应该理解的是,虽然图8的流程图中的各个步骤按照箭头的指示依次显示,但是这些步骤并不是必然按照箭头指示的顺序依次执行。除非本文中有明确的说明,这些步骤的执行并没有严格的顺序限制,这些步骤可以以其它的顺序执行。而且,图8中的至少一部分步骤可以包括多个子步骤或者多个阶段,这些子步骤或者阶段并不必然是在同一时刻执行完成,而是可以在不同的时刻执行,这些子步骤或者阶段的执行顺序也不必然是依次进行,而是可以与其它步骤或者其它步骤的子步骤或者阶段的至少一部分轮流或者交替地执行。如图8所示,包括:步骤1,获取各lua文件。具体地,对于应用程序的代码,从中提取后缀名称为.lua的代码文件,得到各代码文件。在实际应用中,在终端运行的静态代码检查程序,输入代码目录,由静态代码检查程序在对代码目录下寻找所有的lua文件。步骤2,对各lua文件进行处理。具体地,对各代码文件进行处理,得到各代码文件的标记序列。其中,处理包括:对各代码文件进行注释移除和转义等预处理;将预处理后的各代码文件进行分词处理,得到各代码文件的标记序列;将标记序列中逻辑上相关的token关联。步骤3,对各lua文件进行依赖关系分析。具体地,依赖关系根据各lua文件中的依赖函数确定。依赖函数在lua语言中具体为加载函数,例如require,dofile和loadfile等。依赖函数的参数为被引用的代码文件的名称。以lua的两个代码文件进行说明,一个代码文件的名称为test,一个代码文件的名称为lib。其中,名称为lib的代码文件的具体代码如下:--lib.lua—locala=123returna名称为test的代码文件的具体代码为下:--test.lua—a=require(“lib”)print(a)根据识别的依赖函数require,以及依赖函数的参数所表示的加载的代码文件的名称,可以确定上述两个代码文件之间存在依赖关系。步骤4,确定各lua文件中的检查入口文件。检查入口文件即静态代码检查的入口或起点。由于检查入口文件未被其它代码文件所依赖,可能是独立的,或是依赖了其它代码文件。若依赖了其它代码文件,能够根据这种依赖关系获取到其所依赖的代码文件。故而,将未被其它代码文件所依赖的代码文件作为检查入口文件,能够依次依照这种依赖关系,寻找到所依赖的代码文件,为跨文件处理提供基础。例如,上述的两个代码文件,名称为lib的lua文件为被依赖文件,名称为test的lua文件为存在依赖函数的lua文件。在这个依赖关系中,名称为test的lua文件未被依赖,则将其作为检查入口文件。步骤5,根据被依赖的代码文件,展开对应的存在依赖关系的检查入口文件以模拟依赖函数的执行逻辑。具体地,删除依赖函数,增加设定的全局函数,将被依赖的代码文件作为全局函数的函数体。其中,函数体是函数中需要执行的代码语句块。即,展开的实质是利用全局函数模拟依赖函数的执行逻辑。例如,名称为test的lua文件被展开后的代码为:步骤6,根据展开后文件的标记序列构建抽像语法树。抽像语法树(ast).用数状图来表示程序的语法结构,可以方便的表达程序的执行方式,极大的简化程序分析逻辑。步骤7,根据所述抽象语法树,对展开后文件的标记序列进行语法分析,得到代码文件中的作用域、变量和函数。语法分析是指对代码文件进行分析,以分析代码是否符合编程语言的语言规范。语法分析可利用预先根据编程语言的语言规范,编写符合开源语法分析器规定的文法,得到的语言描述文件,进行利用语言识别工具将语言描述文件转换成语法分析器。具体地,语法分析器根据标记序列和语法描述文件中的语法部分,将标记序列组成在一起,识别代码中的作用域、变量和函数等。步骤8,根据所述作用域、变量和函数,对各token进行检查,得到初步检查结果。对各展开后代码文件的标记序列进行检查,根据配置信息,对每一标记序列执行各检查项对应的检查逻辑,得到检查结果。检查项可包括未初始化变量检查、未使用变量检查,函数返回值检查、未使用函数参数检查等等。步骤9,根据预先配置的过滤项,对初步检查结果进行过滤。具体地,lua语言通常和c++、c#等宿主语言配置使用,会依赖其他语言导出的符号、变量、函数等。单纯的扫描lua源代码会出现很多符号未定义的问题。静态代码检查工具会模拟luabind、tolua++等工具的行为,导出c++、c#中特定标记的枚举、变量、函数等过滤项,并将这一部分符号用于lua的错误过滤。符号的导出只需要对c++、c#语言做简单的分词、语法分析即可实现。其中,过滤项可预先配置。该方法可以识别c++,c#等语言的导出符号识别,过滤结果,提高代码检查的准确性。步骤10,输出最终检查结果。本实施例的静态代码分析方法,除文件依赖分析、确定检查入口文件和过滤结果外,其它步骤中,对各lua文件的处理,对各检查入口文件的处理可以并行化处理,提高处理效率。另外,通过文件依赖关系问题,可以有效处理文件依赖问题,避免符号缺失导致的大量误报。以未定义全局变量检查为例,本实施例中提出的跨文件依赖分析方案以及对初步结果过滤,可以使扫描准确性达到80%以上,扫描结果准确率远高于现有的静态代码分析工具。同时,对于标记序列进行ast处理,并分析出作用域、变量、函数等语法结构,在此基础上进行问题检查程序的开发,可以有效降提升开发效率,降低开发难度。在一个实施例中,提供一种静态代码检查装置,如图9所示包括:代码文件获取模块901,用于获取各代码文件。文件处理模块902,用于对各代码文件进行处理,得到各代码文件的标记序列。识别模块903,用于识别标记序列中的依赖函数。依赖分析模块904,用于根据依赖函数分析各代码文件之间的依赖关系,将未被依赖过的代码文件作为检查入口文件。展开模块905,用于根据被依赖的代码文件,展开对应的存在依赖关系的检查入口文件以模拟依赖函数的执行逻辑。检查模块906,用于对各展开后代码文件的标记序列进行检查,得到检查结果。上述静态代码检查装置,通过根据依赖的代码文件,展开对应的存储依赖关系的检查入口文件,以模拟依赖函数的执行逻辑,能够将被依赖的代码的执行逻辑在检查入口文件中展开,即将存在依赖关系的文件结合起来,考虑了全局变量分布在不同文件中的特点,实现了代码跨文件的静态代码检查处理,从而减少了全局变量检查的误报率。在另一个实施例中,依赖分析模块包括:被依赖文件确定模块,用于根据依赖函数的参数,确定被依赖的代码文件。依赖关系确定模块,用于确定存在依赖函数的代码文件与被依赖的代码文件的依赖关系。检查入口确定模块,用于将未被依赖过的代码文件作为检查入口文件。在另一个实施例中,展开模块包括:删除模块,用于在检查入口文件中,删除依赖函数。增加模块,用于增加设定的全局函数,将被依赖的代码文件的代码作为全局函数的函数体。在另一个实施例中,静态代码检查装置还包括:识别模块,用于识别依赖函数的类型;调用模块,用于当依赖函数的类型为运行类型时,调用全局函数。在另一个实施例中,文件处理模块包括:预处理模块,用于对各代码文件进行预处理;分词模块,用于将预处理后的各代码文件进行分词处理,得到各代码文件的标记序列;简化模块,用于基于简化逻辑,对分词得到的token进行简化。关联模块,用于将标记序列中逻辑上相关的token关联。在另一个实施例中,检查模块,包括:语法分析模块,用于对展开后文件的标记序列进行语法分析,得到代码文件中的作用域、变量和函数;token检查模块,用于根据作用域、变量和函数,对各token进行检查。具体地,语法分析模块用于根据展开后文件的标记序列构建抽象语法树,根据抽象语法树,对展开后文件的标记序列进行语法分析,得到代码文件中的作用域、变量和函数。在另一个实施例中,静态代码检查装置,还包括过滤模块,用于根据预先配置的过滤项,对检查结果进行过滤后输出。在另一个实施例中,静态代码检查装置,还包括:加载模块,用于在检查入口文件加载根据预先配置的过滤项编写的过滤代码文件。检查模块,用于根据过滤代码文件,对各展开后代码文件的标记序列进行检查,得到检查结果。图10示出了一个实施例中计算机设备的内部结构图。该计算机设备具体可以是图1中的终端110。如图10所示,该计算机设备包括该计算机设备包括通过系统总线连接的处理器、存储器、网络接口、输入装置和显示屏。其中,存储器包括非易失性存储介质和内存储器。该计算机设备的非易失性存储介质存储有操作系统,还可存储有计算机程序,该计算机程序被处理器执行时,可使得处理器实现静态代码检查方法。该内存储器中也可储存有计算机程序,该计算机程序被处理器执行时,可使得处理器执行静态代码检查方法。计算机设备的显示屏可以是液晶显示屏或者电子墨水显示屏,计算机设备的输入装置可以是显示屏上覆盖的触摸层,也可以是计算机设备外壳上设置的按键、轨迹球或触控板,还可以是外接的键盘、触控板或鼠标等。本领域技术人员可以理解,图10中示出的结构,仅仅是与本申请方案相关的部分结构的框图,并不构成对本申请方案所应用于其上的计算机设备的限定,具体的计算机设备可以包括比图中所示更多或更少的部件,或者组合某些部件,或者具有不同的部件布置。在一个实施例中,本申请提供的静态代码检查装置可以实现为一种计算机程序的形式,计算机程序可在如图10所示的计算机设备上运行。计算机设备的存储器中可存储组成该静态代码检查装置的各个程序模块,比如,图9所示的代码文件获取模块、文件处理模块和识别模块。各个程序模块构成的计算机程序使得处理器执行本说明书中描述的本申请各个实施例的静态代码检查方法中的步骤。例如,图10所示的计算机设备可以通过如图9所示的静态代码检查装置中的代码文件获取模块执行获取各代码文件的步骤。计算机设备可通过文件处理模块执行对各代码文件进行处理,得到各代码文件的标记序列的步骤。计算机设备可通过识别模块执行识别标记序列中的依赖函数的步骤。一种计算机可读存储介质,存储有计算机程序,计算机程序被处理器执行时,使得处理器执行以下步骤:获取各代码文件;对各代码文件进行处理,得到各代码文件的标记序列;识别标记序列中的依赖函数;根据依赖函数分析各代码文件之间的依赖关系,将未被依赖过的代码文件作为检查入口文件;根据被依赖的代码文件,展开对应的存在依赖关系的检查入口文件以模拟依赖函数的执行逻辑;对各展开后代码文件的标记序列进行检查,得到检查结果。在另一个实施例中,根据依赖函数分析各代码文件之间的依赖关系,将未被依赖过的代码文件作为检查入口文件,包括:根据依赖函数的参数,确定被依赖的代码文件;确定存在依赖函数的代码文件与被依赖的代码文件的依赖关系;将未被依赖过的代码文件作为检查入口文件。在另一个实施例中,根据被依赖的代码文件,展开对应的存在依赖关系的检查入口文件以模拟依赖函数的执行逻辑,包括:在检查入口文件中,删除依赖函数;增加设定的全局函数,将被依赖的代码文件的代码作为全局函数的函数体。在另一个实施例中,计算机程序被处理器执行时,使得处理器还执行以下步骤:识别依赖函数的类型;当依赖函数的类型为运行类型时,在增加设定的全局函数,将被依赖的代码文件的代码作为全局函数的函数体之后,还包括:调用全局函数。在另一个实施例中,对各代码文件进行处理,得到各代码文件的标记序列,包括:对各代码文件进行预处理;将预处理后的各代码文件进行分词处理,得到各代码文件的标记序列;将标记序列中逻辑上相关的token关联。在另一个实施例中,计算机程序被处理器执行时,使得处理器还执行以下步骤:基于简化逻辑,对分词得到的token进行简化。在另一个实施例中,对各展开后文件的标记序列进行检查,得到检查结果,包括:对展开后文件的标记序列进行语法分析,得到代码文件中的作用域、变量和函数;根据作用域、变量和函数,对各token进行检查。在另一个实施例中,在对展开后文件的标记序列进行语法分析,得到代码文件中的作用域、变量和函数,包括:根据展开后文件的标记序列构建抽象语法树;根据抽象语法树,对展开后文件的标记序列进行语法分析,得到代码文件中的作用域、变量和函数。在另一个实施例中,计算机程序被处理器执行时,使得处理器还执行以下步骤:根据预先配置的过滤项,对检查结果进行过滤后输出。在另一个实施例中,计算机程序被处理器执行时,使得处理器还执行以下步骤:在检查入口文件加载根据预先配置的过滤项编写的过滤代码文件;对各展开后代码文件的标记序列进行检查,得到检查结果包括:根据过滤代码文件,对各展开后代码文件的标记序列进行检查,得到检查结果。一种计算机设备,包括存储器和处理器,存储器存储有计算机程序,计算机程序被处理器执行时,使得处理器执行以下步骤:获取各代码文件;对各代码文件进行处理,得到各代码文件的标记序列;识别标记序列中的依赖函数;根据依赖函数分析各代码文件之间的依赖关系,将未被依赖过的代码文件作为检查入口文件;根据被依赖的代码文件,展开对应的存在依赖关系的检查入口文件以模拟依赖函数的执行逻辑;对各展开后代码文件的标记序列进行检查,得到检查结果。在另一个实施例中,根据依赖函数分析各代码文件之间的依赖关系,将未被依赖过的代码文件作为检查入口文件,包括:根据依赖函数的参数,确定被依赖的代码文件;确定存在依赖函数的代码文件与被依赖的代码文件的依赖关系;将未被依赖过的代码文件作为检查入口文件。在另一个实施例中,根据被依赖的代码文件,展开对应的存在依赖关系的检查入口文件以模拟依赖函数的执行逻辑,包括:在检查入口文件中,删除依赖函数;增加设定的全局函数,将被依赖的代码文件的代码作为全局函数的函数体。在另一个实施例中,计算机程序被处理器执行时,使得处理器还执行以下步骤:识别依赖函数的类型;当依赖函数的类型为运行类型时,在增加设定的全局函数,将被依赖的代码文件的代码作为全局函数的函数体之后,还包括:调用全局函数。在另一个实施例中,对各代码文件进行处理,得到各代码文件的标记序列,包括:对各代码文件进行预处理;将预处理后的各代码文件进行分词处理,得到各代码文件的标记序列;将标记序列中逻辑上相关的token关联。在另一个实施例中,计算机程序被处理器执行时,使得处理器还执行以下步骤:基于简化逻辑,对分词得到的token进行简化。在另一个实施例中,对各展开后文件的标记序列进行检查,得到检查结果,包括:对展开后文件的标记序列进行语法分析,得到代码文件中的作用域、变量和函数;根据作用域、变量和函数,对各token进行检查。在另一个实施例中,在对展开后文件的标记序列进行语法分析,得到代码文件中的作用域、变量和函数,包括:根据展开后文件的标记序列构建抽象语法树;根据抽象语法树,对展开后文件的标记序列进行语法分析,得到代码文件中的作用域、变量和函数。在另一个实施例中,计算机程序被处理器执行时,使得处理器还执行以下步骤:根据预先配置的过滤项,对检查结果进行过滤后输出。在另一个实施例中,计算机程序被处理器执行时,使得处理器还执行以下步骤:在检查入口文件加载根据预先配置的过滤项编写的过滤代码文件;对各展开后代码文件的标记序列进行检查,得到检查结果包括:根据过滤代码文件,对各展开后代码文件的标记序列进行检查,得到检查结果。本领域普通技术人员可以理解实现上述实施例方法中的全部或部分流程,是可以通过计算机程序来指令相关的硬件来完成,所述的程序可存储于一非易失性计算机可读取存储介质中,该程序在执行时,可包括如上述各方法的实施例的流程。其中,本申请所提供的各实施例中所使用的对存储器、存储、数据库或其它介质的任何引用,均可包括非易失性和/或易失性存储器。非易失性存储器可包括只读存储器(rom)、可编程rom(prom)、电可编程rom(eprom)、电可擦除可编程rom(eeprom)或闪存。易失性存储器可包括随机存取存储器(ram)或者外部高速缓冲存储器。作为说明而非局限,ram以多种形式可得,诸如静态ram(sram)、动态ram(dram)、同步dram(sdram)、双数据率sdram(ddrsdram)、增强型sdram(esdram)、同步链路(synchlink)dram(sldram)、存储器总线(rambus)直接ram(rdram)、直接存储器总线动态ram(drdram)、以及存储器总线动态ram(rdram)等。以上实施例的各技术特征可以进行任意的组合,为使描述简洁,未对上述实施例中的各个技术特征所有可能的组合都进行描述,然而,只要这些技术特征的组合不存在矛盾,都应当认为是本说明书记载的范围。以上所述实施例仅表达了本申请的几种实施方式,其描述较为具体和详细,但并不能因此而理解为对本申请专利范围的限制。应当指出的是,对于本领域的普通技术人员来说,在不脱离本申请构思的前提下,还可以做出若干变形和改进,这些都属于本申请的保护范围。因此,本申请专利的保护范围应以所附权利要求为准。当前第1页12当前第1页12
当前第1页1 2 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1