为了账号安全,请及时绑定邮箱和手机立即绑定

Hades:移动端静态分析框架

标签:
Premiere

webp

Hades Logo

只有通过别人的眼睛,才能真正地了解自己 ——《云图》

背景

作为全球最大的互联网 + 生活服务平台,美团点评近年来在业务上取得了飞速的发展。为支持业务的快速发展,移动研发团队规模也逐渐从零星的小作坊式运营,演变为千人级研发军团协同作战。

在公司蓬勃发展的大背景下,移动项目架构也有了全新的演进方向:需要支持高效的集成策略,支持研发流程自动化等等,最终提升研发效能,加速产品迭代和交付能力。

虽然高效的研发交付体系帮助 App 项目缩短了迭代周期,但井喷式的模块发版和频繁的项目集成,使得纯人工的项目维护和质量保证变得“独木难支”。

webp

静态分析需求

上图漫画中,列举了大型项目在持续优化和维护过程中较为常见的几类需求。这些需求主要包括以下几个方面:

  1. 在 CI 流程中加入静态准入检查,避免繁琐的人工 Review 以及减少人工 Review 可能带来的失误。

  2. 为了推进项目的优化过程,需要方法数监控、宏定义分析等代码分析报表和监控。

  3. 零 PV 报表、依赖分析和头文件引用规范、无用代码分析等项目优化方案。

不难发现,这些需求的本质是:借助代码静态分析能力,提升项目可持续发展所需要的自动化水平。针对 C/Objective-C 主流的静态分析开源项目包括:Static Analyzer、Infer、OCLint 等。但是,这些分析工具对我们而言存在一些问题:

  • 开发成本高,收益有限,研发参与积极性不够。

  • 针对局部代码分析,跨编译单元以及全局性分析较难。

  • 增量分析困难,CI 静态检查效率低下。

  • 工具性较强,大部分只作代码规范检查,应用范畴局限。

  • 接入和维护成本高,难以平台化。

针对以上背景和现有方案的不足,我们决定自研基于语义的静态分析框架。

Hades 项目简介

大众点评静态分析框架 Hades,取名源于古希腊神话中的冥王。冥王 Hades 公正无私,能够审视灵魂的是非善恶。

Hades 框架支持语义分析能力,我们希望这种能力不仅仅能够去实现一个传统的 Lint 工具,而且能成为创造更多能力的基础,可以帮助我们更轻松地审视代码,理解把控大型项目。

Hades 方案选型

文本处理方式

首先,最简单的静态分析是字符匹配和文本处理。这种方式虽然实现简单,但是存在能力上限,也不可能在语义理解上有足够的把控力。另外,以正则匹配为核心建立的工具栈难以得到持续优化。为了分析项目的依赖关系,我们需要判断代码中的符号含义以及符号间关系(如包含哪些类,类中有哪些方法等),分析过程的正则表达式如下图所示。

webp

正则匹配模式

由此可见,繁琐的文本匹配不仅可读性差,也存在容易分析出错的问题。

基于编译器的静态分析方案

我们需求的本质是对代码进行分析,而在源代码编译过程中,语法分析器会创建出抽象语法树(Abstract Syntax Tree 缩写为 AST)。AST 是源代码的抽象语法结构的树状表现形式,树上的每个节点都表示源码的一种结构。

webp

AST 描述

以上图为例,代码块区域是用 Objective-C 和 TypeScript 编写的一个简单条件语句源码,下面是其对应的抽象语法结构表达。这种树状的结构表达,省略了一些细节(比如:没有生成括号节点),从图中的这种映射关系中我们也可以发现:

  • 源码的语法结构是可以通过明确的数据结构表示的。

  • 大多数编程语言都可以用相似的 AST 表达的。

对于 C/Objective-C 而言,主流编译器是 Clang/LLVM(Low Level Virtual Machine)的,它是一个开源的编译器架构,并被成功应用到多个应用领域。Clang(发音为/klæŋ/,不是C浪)是 LLVM的一个编译器前端,它目前支持 C, C++, Objective-C 等编程语言。Clang 会对源程序进行词法分析和语义分析,将分析结果转换为 AST。现有方案中不少 Lint 工具便是基于 Clang 的,Clang 包含了以下特点:

  • 编译速度快:Clang 的编译速度远快于 GCC。

  • 占用内存小:Clang 生成的 AST 所占用的内存是 GCC 的五分之一左右。

  • 模块化设计:Clang 采用基于库的模块化设计,易于 IDE 集成及其他用途的重用。

因此,借助 Clang 的模块化设计和高效编译等诸多优点,Hades 也将更容易开发和升级维护。Clang 对源码强有力的分析能力也是主流静态分析工具的不二之选。

Clang AST 初识

Clang 项目非常庞大。仅仅是 Clang AST 相关代码就超过 10W+ 行代码。如何利用 Clang 实现 AST 分析工作,这里可以参考官网提供的文档 Choosing the Right Interface for Your Application ,以下是三种方式:

  • LibClang

    提供 C 语言的稳定接口,支持Python Binding。AST 并不完整,不能完全掌控 Clang AST。

  • Clang Plugins

    提供 C++ 接口,更新快,不能保留上下文信息。插件的存在形式是一个动态链接库,不能在构建环境外独立存在。

  • LibTooling

    提供 C++ 接口,更新快,可以通过标准的 main() 函数作为入口,可独立运行,能够完全掌控 AST,相比 Plugin 更容易设置。

这里我们选择可独立运行并且能完全掌控 AST 的 LibTooling 作为 Hades 的基础。

在使用 Clang 的学习过程中,基本的概念便是表示 AST 的节点类型,这里重要的几点是:

  • ASTContext。

    ASTContext 是编译实例用来保存 AST 相关信息的一种结构,也包含了编译期间的符号表。我们可以通过 TranslationUnitDecl * getTranslationUnitDecl(): 方法得到整个翻译单元的 AST 的入口节点。

  • 节点类型。

    AST 通过三组核心类构建:Decl (declarations)、Stmt (statements)、Type (types)。其它节点类型并不会从公共基类继承,因此,没有用于访问树中所有节点的通用接口。

  • 遍历方式。

    为了分析 AST,我们需要遍历语法树。Clang 提供了两种方式:RecursiveASTVisitor 和 ASTMatcher。RecursiveASTVisitor 能够让我们以深度优先的方式遍历 Clang AST 节点。我们可以通过扩展类并实现所需的 VisitXXX 方法来访问特定节点。

    ASTMatcher API 提供了一种域特定语言(DSL)来构建基于 Clang AST 的谓词,它能高效地匹配到我们感兴趣的节点。

    除了这两种方式外,LibClang 也提供了 Cursors 来遍历 AST。更多细节内容可以前往 :clang.llvm.org

常用开源工具的不足

通过上一章节的介绍,我们大致了解了 Clang 的基本特点。 但是在实践开发过程中发现:通过 Clang API 去遍历和分析 AST 的源码树形结构较为复杂。现有静态分析方案(如:OCLint),大多是直接给出封装好的 Lint 工具,扩展方面也是提供脚手架生成 Rule 文件,然后在 Rule 中编写访问特定 AST 节点的方法(例如:VisitObjCMethodDecl 方法用来访问 Objective-C 的方法定义)。

因此,现有方案大多数只提供了直接访问 AST 的方式,而且这种方式较为“局部”。每实现一个实际需求需要耗费大量精力去理解如何从 AST 分析映射到源码的语义逻辑。

但是,Code Review 时我们并不会将目标代码转换为 AST 然后再去分析代码的语义如何,更多的是直接理解代码的具体逻辑和调用关系。AST 树状结构分析的复杂性容易带来理解上的差异鸿沟。因此,这也不利于调动业务研发团队的积极性,很多基于源码分析工作也难以落地。

Hades 核心实现

为了让分析过程更清晰,我们需要在 AST 的基础之上再进行一次抽象。本章节主要内容包含:Hades 的整体架构、为什么要定义语义模型、定义什么样的语义模型、如何输出语义模型以及模型的序列化和持久化。

Hades 总体架构

按照 Hades 的架构目标进行基础方案选型以后,我们来看下 Hades 的整体技术框架,可以用下图所示的四层架构表示:

webp



作者:美团技术团队
链接:https://www.jianshu.com/p/ba0e22d15554


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消