Ruby 分享一个静态分析代码提取函数参数的实现思路

zhb85 · 2024年11月29日 · 161 次阅读

最近完成了一个需求,从工程脚本代码中提取出给设备下发的命令,例如:telnet.cmd "ls",需要将 ls 提取出来。 代码有几种情况 1,变量拼接,嵌入

cmd = 'ls'
telnet.cmd cmd + 'dir0'
telnet.cmd "ls #{xxx}"

2, 循环

['ls', 'dir'].each do |cmd|
  telnet.cmd cmd
end

3, 分支

if a
  str = 'ls'
else
  str = 'dir'
end
telnet.cmd str

4, 函数调用,以及跨文件调用

def send_cmd str
  telnet.cmd str
end
send_cmd 'ls'

需求是不能有遗漏,尽量将命令提取出来。

总体思路是,用 ruby_parser 将代码转换成 s 表达式,对 s 表达式求值 s 表达式长这个样子

s(:defn, :send_cmd, s(:args, :str), 
  s(:call, s(:call, nil, :telnet), :cmd, s(:lvar, :str)))

深度遍历这个语法树,对每个语法单位进行求值,将方法 cmd 的参数记录下来,即得到所有的命令。

具体解决方案: 1,变量拼接处理是最简单的,在代码块范围内附带一个 hash 对象,将赋值语法,例如 s(:lasgn, :a, s(:str, "123")) 记录在 hash 中, 相应的,在获取变量的时候从 hash 中提取出来

2,循环的语法单位是:iter,例如

#[].each do |x| end
s(:iter, s(:call, s(:array), :each), s(:args, :x))

这个可以用 send 方法将:each 发送给迭代对象

3,分支的处理我的做法是,多次遍历,每次遍历到 if 分支的时候,只求值后一个分支里的语句,求值后删除,下一次遍历另外的语句 这样每个分支都会走到,缺点是会有一些信息丢失

4,函数调用,分为两部分 1, 执行:defn 函数定义的时候,将函数定义保存在函数表中 2, 在执行到:call 的时候,会对:call 的参数求值,例如求值后的表达式变成 s(:call,...参数) 其中参数的值已经求出, 将这个放入函数调用队列中,将代码全部处理后,会得到一个函数调用队列,对其迭代求值,直到队列为空。

经过以上处理,最终效果比较令人满意,唯一遗憾的是分支结构有信息丢失,或许增加改变分支的处理顺序可以弥补。

1 楼 已删除
需要 登录 后方可回复, 如果你还没有账号请 注册新账号