背景:
- 1.阅读调用栈复杂的业务代码
很多业务代码由于时间的积累调用链较深,不能随便执行(xhprof等工具只能执行时采集调用栈,并且覆盖不到分支维度),通过IDE查看麻烦(要记住之前看的每一层,很容易迷路)
- 2.找到历史遗留的无效代码
经常有些代码被添加到代码库,但是一直没有调用,或者调用方是一个临时活动,入口已经删除了但没有删除干净,后面的model层还有遗留的代码,而IDE对应类中的共有方法是不会标明没有调用的,要一个一个手动翻看,工作量巨大。
- 3.下线数据源
下线数据源要分析调用入口,一般必须找到对应的接口才能找到属于的业务模块,相当于从最下游找到最上游,如果能通过自动化把上层的接口扫描出来,就可以快速分析数据源的依赖方
调研
静态分析代码工具有 Understand(侧重于源码阅读),sourcetrail(可以交互的方式图形化的分析代码关系,精确到结构体的每一个字段,可以拓展支持的语言,完善的有C、C++、Java的,php的没看到),phpstorm(比较传统的caller,callee分析,可以写拓展,但没有仔细研究过拓展能不能把静态代码分析那套东西利用起来)。考虑到实际项目中的情况比较复杂(毕竟面对的是各种历史原因积累起来的shit mountain),也没有找到比较解决痛点的现成的软件,考虑用nikic/PHP-Parser结合一些其他的php项目自己实现一套静态代码分析工具。
项目地址:
开发中, 扫描两次 第一次扫描所有项目总的类 和 注释,第二步从每个入口(enters)递归分析函数调用情况,后面可以根据调用情况绘制调用链 和 分析调用链。
- composer update
- 修改yaml 文件
- 运行:
- 生成调用图(生成一个graphviz可以解析的 dot文件)
php test.php digraph ControllerAccountAccount index -c /Users/momo/codescanner/open-cart.yaml
- 找到一个方法有哪些入口函数在调用
php test.php caller Manager method -c /Users/momo/codescanner/open-cart.yaml
- 找到调用链无法覆盖到的类
php test.php unused /Users/momo/codescanner/open-cart.yaml
- 生成调用图(生成一个graphviz可以解析的 dot文件)
配置文件
- project-dir
- includes 需要分析的目录
- excludes 不需要分析的目录(相对目录)
- enters
- includes 项目的入口(控制器 mvc 中的c)
- cache 缓存目录
- dot dot文件最后生成放入的目录
不足:
- 1.只适合调用关系复杂的项目,单个复杂的函数无法分析
- 2.依赖项目的注释,没有注释就阻塞分析
- 3.对于现代的命名空间支持不好
- 4.对于同名的类支持不好
feature
-
根据入口方法,导出调用链(场景:用于阅读代码)
- 展示方法调用的普通函数
- 配置特殊类的颜色 或 形状(比如继承某个类的类)
- 支持配置哪些类不会继续递归分析(停止递归)比如基础的功能打日志 读取配置文件 上报监控报警 只会增加生成的调用层级
这里m0~4都是方法,m0调用 m1,m2,m3。m1和 m2调用m4。
-
指定一个类,扫描出所有涉及到这个类的入口(场景:下线代码或数据源)
-
指定入口,获取当前项目中,有哪些类,无法通过入口扫描调用链覆盖到(场景:找到废弃代码)
TODO列表
- 支持缓存(扫描文件,生成调用链在代码不改动的情况下 只生成一次)
- 找到一个方法 到 另一个方法的所有调用路径
- 动态解析调用的类
- parent::method()
- static::method()
- self::method()
- 动态解析变量类型,减少方法调用无法确定方法所属类的情况
- 成员函数调用(this 或 局部变量)
$a = $this->getMethod(); // 需要根据getMethod的注释 或 return语句来解析
- 静态方法调用
$a = Class::staticMethod(); // 需要根据staticMethod的注释 或 return语句来解析
- 三元表达式
$a = 1 + 1 > 2? $b : $c; // 要综合$b 或 $c的类型来解析
- new 运算符
$a = new Class();
- foreach的情况 根据数组的类型 推导变量的类型
$a = []; // SomeClass[] foreach($a as $b) { $b->method(); // $b 的类型为SomeClass }
- 变量(参数或返回值)采用了php的强类型
function some(SomeClass $b) { $b->method(); }
- 成员函数调用(this 或 局部变量)
难点
- 1.php代码是静态类型的,无法根据一行代码分析调用的函数,要结合上下文(变量类型)和 函数注释或类注释
解决:提前分析所有注释(函数,类),制作符号表,对于局部变量的赋值也要分析变量的最终类型(参考上一个小节第二条)。
- 2.php的语法复杂,比如调用方法有静态调用,成员函数调用。php中还有魔术方法(__get)可以使用没有在类中显式声明的变量
解决:枚举所有情况
- 3.对于接口调用(多态),无法找到具体调用的子类的方法(比如静态工厂,具体调用的方法只能运行时决定)
没有什么好的办法,只能进一步分析代码
参考
项目
- nikic/PHP-Parser
根据代码生成可遍历的抽象语法树,php核心开发者维护的,很好,很强大
- alom/graphviz
使用php生成graphviz 可执行的格式的文件
- Trismegiste/Mondrian
将代码的调用链或类关系(UML)生成图