一、前言
通过前面三篇文章已经初步实现了将Lua源代码文件读取解析成语法树,现在就可以通过得到的语法树进行指定规则的代码扫描检查。下图简单列举了一下单个Lua文件内部的语法关系情况(注意并非真正的类图,也没有列举完全部的节点类型)。
二、变量作用域
1 function main() 2 local value = g_total + 1 3 print("value:", value) 4 end
上面的简单代码里有一个g_total的全局变量,它可能来自前面代码块的定义,也有可能来自底层导出的符号,或者是在其他文件中定义的全局变量。因此在判断一个变量是否存在首先需要创建一个全局的变量列表或白名单列表。
1 function demo(params) 2 local value1 = 1 3 if value1 == params then 4 local var_b = 2 5 local var_c = var_b + value1 6 end 7 end
上面代码也是一个函数声明,它有自己的私有变量列表(包括参数params和内部的变量value1);而var_b、var_c则属于是if内部的私有变量列表,在if之外是无法访问的。因此在代码扫描之前需要先建立不同参数的作用域。
先对所有文件的语法树扫描一遍,把里面的所有变量都记录下来,存入全局变量列表、当前文件变量列表、或者某个内部代码块的变量列表。然后再才对单个文件进行规则扫描。特别需要注意module方法声明的模块,内部的不加local的变量属于module的公共变量。
1 def build_symbols(block): 2 # block参数就是当前文件的全部代码 3 for statement in block.statements: 4 # 变量声明,属于block的自私变量列表 5 if statement.nodeType == LNodeCode.IDENTIFIER: 6 # 赋值包括a=1 和 a,b =1,2 等复杂的赋值语句 7 # 没有加local的情况下,很可能是全局变量 8 elif statement.nodeType == LNodeCode.ASSIGNMENT: 9 # 函数定义,需要检查函数体内部的语句statement.block 10 # 递归调用build_symbols检查 11 elif statement.nodeType == LNodeCode.FUNC: 12 # 函数调用类似 module("test",seeall)模块声明, 全局的test 13 # 类似 CreateClass("A", superB) ,全局的A 14 elif statemen
三、规则扫描
根据需要进行的一系列的规则进行语法树扫描检查,以检查变量是否存在为例:
1. 检查变量时需要先在局部变量列表中查找(if、for等代码块;函数代码块;文件代码块等)
2. 没有找到再去模块的公共变量列表(module)
3. 最后去全局变量列表中查找
4. 如果都没有找到的话那么就很可能这个变量不存在。
1 if statement.nodeType == LNodeCode.IDENTIFIER: 2 # 单个变量的声明只有可能是local a 3 check_var(statement.name, statement.is_local) 4 elif statement.nodeType == LNodeCode.FUNC_CALL: 5 # 函数调用的参数检查 6 check_multi_vars(statement.args) 7 # 函数本身检查,看是否有这样的函数 8 check_func(statement.name) 9 elif statement.nodeType == LNodeCode.OP: 10 # 操作例如+-*/%等,需要检查左右的操作变量是不是存在 11 check_left_vars(statement.left) 12 check_right_vars(statement.right)
同样也是遍历文件的block、函数体block、各种语句的block进行变量的存在性检查,具体的内部检查由于代码篇幅太长,就不详细贴出来了。
Lua静态代码扫描的基本处理方法就算列举完了,实际项目的代码中会比较复杂,而且扫描的规则也会有很多不同的需求,就需要实际去做才理解了。
文章来自我的公众号,大家如果有兴趣可以关注,具体扫描关注下图。