php静态代码分析工具设计与实现

背景:

  • 1.阅读调用栈复杂的业务代码

    很多业务代码由于时间的积累调用链较深,不能随便执行(xhprof等工具只能执行时采集调用栈,并且覆盖不到分支维度),通过IDE查看麻烦(要记住之前看的每一层,很容易迷路)

  • 2.找到历史遗留的无效代码

    经常有些代码被添加到代码库,但是一直没有调用,或者调用方是一个临时活动,入口已经删除了但没有删除干净,后面的model层还有遗留的代码,而IDE对应类中的共有方法是不会标明没有调用的,要一个一个手动翻看,工作量巨大。

  • 3.下线数据源

    下线数据源要分析调用入口,一般必须找到对应的接口才能找到属于的业务模块,相当于从最下游找到最上游,如果能通过自动化把上层的接口扫描出来,就可以快速分析数据源的依赖方

调研

静态分析代码工具有 Understand(侧重于源码阅读),sourcetrail(可以交互的方式图形化的分析代码关系,精确到结构体的每一个字段,可以拓展支持的语言,完善的有C、C++、Java的,php的没看到),phpstorm(比较传统的caller,callee分析,可以写拓展,但没有仔细研究过拓展能不能把静态代码分析那套东西利用起来)。考虑到实际项目中的情况比较复杂(毕竟面对的是各种历史原因积累起来的shit mountain),也没有找到比较解决痛点的现成的软件,考虑用nikic/PHP-Parser结合一些其他的php项目自己实现一套静态代码分析工具。

项目地址:

github

开发中, 扫描两次 第一次扫描所有项目总的类 和 注释,第二步从每个入口(enters)递归分析函数调用情况,后面可以根据调用情况绘制调用链 和 分析调用链。

  1. composer update
  2. 修改yaml 文件
  3. 运行:
    • 生成调用图(生成一个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
      

在这里插入图片描述

配置文件
  • 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();
      }
      

难点

  • 1.php代码是静态类型的,无法根据一行代码分析调用的函数,要结合上下文(变量类型)和 函数注释或类注释

    解决:提前分析所有注释(函数,类),制作符号表,对于局部变量的赋值也要分析变量的最终类型(参考上一个小节第二条)。

  • 2.php的语法复杂,比如调用方法有静态调用,成员函数调用。php中还有魔术方法(__get)可以使用没有在类中显式声明的变量

    解决:枚举所有情况

  • 3.对于接口调用(多态),无法找到具体调用的子类的方法(比如静态工厂,具体调用的方法只能运行时决定)

    没有什么好的办法,只能进一步分析代码

参考

项目
博客
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值