【南大软件分析-学习笔记】Lecture 1-introduction
0、前言
程序分析技术是通过系统化方法理解、评估和优化程序行为、结构、性能及安全性的核心技术,广泛应用于软件开发、优化、缺陷检测和安全审计。接下来的日子鼠鼠将以南大程序分析课程为基础,分享本人学习中的一些感悟(其实就是自己太健忘了)。
原课程所用到的一些相关链接如下:
- 课程网站(含课件、实验作业、实验文档): https://tai-e.pascal-lab.net/lectures.html
- 实验作业在线评测oj平台:https://oj.pascal-lab.net/
- B站教学视频:https://www.bilibili.com/video/BV1b7411K7P4/
1、什么是Static Analysis
1.1 PL & Static Analysis
在开始之前,首先介绍一下 PL 与 Static Analysis 的关系。
Programming Languages,中文叫做程序语言设计,其主要主题有理论、环境以及应用三部分,其中理论部分主要包括某种语言的设计,包括其规则等基础部分,环境则主要关注编译器、运行系统等,而应用部分就主要关注的就是程序的分析、验证、合成部分了,课程重点关注的也就是 Program analysis 这一部分的内容。
1.2 Background & Challenge
近十年来,虽然各种语言不断涌现,但是语言的内核改变并没有很多,但是出于业务需求的不断增大,程序的规模正在不断的变大,因此,如果确保大型复杂项目的可靠性、安全性已经成为研究的重点。
静态分析(Staic analysis) 要求在运行一个程序 P 之前就对其进行分析,并推理它的行为,判定它是否满足某些性质,包括但不限于以下:
- 程序 P 是否存在隐私信息泄露?(private information leaks)
- 程序 P 是否会引用空指针?(null pointers)
- 程序 P 中的强制类型转换操作是否安全?(cast operations)
- 程序 P 里的变量 v1 和 v2 是否可能指向同一个内存地址?
- 程序 P 中的某些断言语句是否会触发失败?(assert statements)
- 是否存在死代码?(dead code)
静态分析也有助于我们写出更高质量的代码,例如 IDE 中的提示也是静态分析的一个具体应用。
2. Rice Theorem
然而,事与愿违,根据 Rice Theorem(莱斯定理):
“Any non-trivial property of the behavior of programs in a r.e. language is undecidable.”
定理表明,对于所有递归可枚举语言的所有非平凡性质都是不可判定的。
用更加简单通俗便于理解的话来说就是,不存在完美的静态分析方法能使得我们完美的判断出以上的需求(Yes or No)。
2.1 Sound & Complete
莱斯定理表明了一个完美的静态分析方法需要同时满足以下两个性质:
- Sound
- Complete
二者的关系可以用以下的集合图表示:
结合一个具体的实例来解释以上几个关键词,对于一个软件来说,在实际过程中假设有一万个用户进行了某个操作,其中有十个人触发了空指针错误,那么如果程序在静态分析阶段就精确判断出这十个人的操作,那么就将其定义为 Truth;如果程序在静态分析阶段判断出有五百个人触发了空指针错误,并且真正触发空指针错误的那十个人也确实在那五百个人当中,那么就可以将其定义为 Sound,显而易见,这个过程是 Overapproximate 的;相反的,如果程序在静态分析阶段判断出只有五个人触发了空指针错误,并且这五个人也确实触发了空指针错误,那么就将其定义为 Complete的,这个过程显而易见就是 Underaooroximate的。
简单来说,Sound是一种过多的输出,输出的是全部的真实报错和部分的虚假报错;而 Complete 与之相反,输出的是全是真是报错,但是比 Truth 少了一部分的报错。
2.2 Soundness & Completeness
一个可用的静态分析方法往往满足以下两个条件之一即可:
- Compromise soundness (false negatives)
- Compromise completeness (false positives)
再次引入了两个概念,False Negatives(假阴性) 和 False Positives(假阳性)。如图所示,红色部分是假阳性,是对于 Truth 来说误报的的部分,而绿色部分则是假阴性,是对于 Truth 来说漏报的部分。
由于漏报了错误的后果很明显要大于误报的后果,因此实际过程中往往要求分析结果可以是 Sound 的,但不能是 Complete 的。
2.3 Necessity of Soundness
以下两个的小例子简单展示了 Soundness 在静态分析中的必要性。
2.3.1 eg.1
如下图所展示的,B、C 是实现接口 A 的类,我们需要判断操作 3 是否是安全的。
如果只从蓝色部分来看,也就是 Unsound 的眼光,转换显而易见是合法的,即认为它是 Safe 的。
而在流图中,对于一个节点来说需要归并的判断所有指向它的节点,因此当采用 Sound 的眼光来看时,当程序实际运行到 B b' = (B) a.fld; 时,a.fld 实际上可能指向 B 对象,也可能指向 C 对象;如果它指向 B,转换成功,但如果它指向 C,由于 C 和 B 本质上是两个并列的子类,二者不能进行转换,会抛出 ClassCastException,所以这个强制转换不能保证安全。
2.3.2 eg.2
if(input)
x = 1;
else
x = 0;
-> x = ?
对于上面这个简单的程序,可以有以下四个分析结果:
- when input is true, x = 1; when input is false, x = 0
- x = 1 or x = 0
- x = 1 or x = 0 or x = 3 or x = 4 or x = 5 or x = 6
- x = 1 or x = 2 or x = 3 or x = 4
显而易见,分析结果1、2是正确的,并且它们都是 sound 的,因为在没有运行程序,即没有输入之前,x 的值可以是 0 或 1;同样的,分析结果 3 也是 sound 的,因为它也包含了正确的结果;而分析结果 4 就是错误的了,因为它漏报了,sound 的理念就是宁愿多报,也不能漏报,杜绝出错的可能。
进一步的,我们来横向对比一下分析结果 1 和 分析结果 2 ,二者的区别在于,前者 precise,但是 expensive;后者虽然 cheap,但是却imprecise。静态分析重点关注的就是如何在确保 sound 的情况下,在 presion 和 speed 之间做出雀舌。
3. Abstraction & Over-approximation
对于一个给定的程序,大多数的静态分析可以用到以下两个词来概括:Abstraction(抽象)和 Over-approximation(过度近似,其实就是 soundness),后者又包括 Transfer Functions(转换函数)和 Control Flows(控制流),这些都会放到后面的章节进行具体介绍。
下面结合了一个具体的例子进行讲解,给定一个程序,要求我们确定其中所有变量的符号是什么。
3.1 Abstraction
首先需要将给定的变量从 concrete domain 映射到 absract domain,映射关系如图所示:
五类符号分别为:+、-、0、top (表示unknown) 和 bottom (表示无意义、错误、undefined)
3.2 Transfer Functions
在静态分析中,Transfer Functions 定义了如何在抽象值上评估不同的程序语句,它通过分析问题以及不同程序语句的语义来定义,具体到这个例子是这样的:
其中需要关注的是图上画圈的两个,首先先看上方的那一个,由于除数不能为 0 ,那么其抽象后的结果自然为 bottom;再看下方的那一个,由于一个正数与负数相加的和既有可能是正数,也有可能是负数,因此结果为 top,即 unknown。
规定了 Transfer Functions 后,就可以对具体的输入进行抽象,抽象结果如下图所示:
其实 1、2的抽象结果与实际结果一致,这体现了静态分析 useful 的特点,而 3 虽然根据抽象结果表明确实是有错误的风险,但如果结合真实的输入发现实际中是不会报错的,这就体现了之前所提到的假阳性,也就是 sound 的体现。
3.3 Control Flows
最后是 Control Flows,控制流是指在实际场景中分析时,由于无法枚举所有路径,往往采用flow merging(即前文提到的归并思想)来处理,如下图所展示。
4 后记
第一课到这里结束啦,有说错的地方也欢迎指正哦。

浙公网安备 33010602011771号