前言

翻了几篇 Python 静态分析相关的论文,看看有什么比较好的分析工具。

静态分析工具

1. Survey on Static Analysis Tools of Python Programs

SQAMIA 2019

概述了 Python 代码库静态分析的现有方法和工具,并介绍了一些新的研究方向。

总结了常见的 Python 静态分析工具之间的关系,简单介绍了(几行概述) PylintPyflakesflake8FrostedPycodestyleMypyPySymPyExZ3

设计并测试了 6 种典型的逻辑漏洞(logical errors),用 PyLint、Pyflakes、Flake8、Mypy、Frosted 检测

  • 使用默认配置
  • 逻辑错误会产生意外的输出或结果,但不一定会导致崩溃
  1. 引用未定义变量

    1
    2
    3
    4
    5
    import sys
    message = "Hello there!"
    if "greetMe" in sys.argv:
    print(mesage) # 变量名打错
    print("This code is fine, no problems.")
  2. 太多位置参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import sys # 未使用的导入

    class Person:

    def __init__(self, first_name, last_name, age):
    self.first_name = first_name
    self.last_name = last_name
    self.age = age

    if "Windows" in platform.platform(): # 未定义的 platform 变量
    print("You’ re using Windows !")

    self.age = self.getAge(1, 2, 3) # 太多位置参数

    def getAge(this): # 没有 self 参数
    return "18"
  3. 传递类型错误的参数

    1
    2
    3
    4
    def sum(x:int, y:int): # 类型注释
    return x + y
    print(sum(3, 4))
    print(sum(3, "4")) # 传递错误类型的参数
  4. 引用不存在的类属性

    1
    2
    3
    4
    5
    6
    7
    class Person():
    def __init__(self, name, age):
    self.name = name
    self.age = age
    PERSON1 = Person("Hristina", 23)
    print(PERSON1.age)
    print(PERSON1.height) # 引用不存在的属性
  5. 调用嵌套函数

    1
    2
    3
    4
    5
    6
    7
    def outer():
    x=3
    def inner():
    print(x)
    inner()
    outer()
    inner() # 调用 outer 内部定义的函数
  6. 闭包错误

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    # 闭包
    def outer():
    x = 3
    def inner():
    y = 3
    result = x + y
    return result
    return inner

    a = outer()
    print(a()) # 调用 inner()
    print(a.__name__) # 打印 inner

    # 闭包错误,非预期结果
    def greet(greet_word, name) :
    print(greet_word, name)
    greeters = list()
    names = ["Kiki", "Riki", "Joe"]
    for name in names:
    greeters.append(lambda x : greet(x, name))

    for greeter in greeters:
    greeter("Hi")
    # 打印
    # Hi Joe
    # Hi Joe
    # Hi Joe

2. Towards More Sophisticated Static Analysis Methods of Python Programs

Informatics 2019 • IEEE 15th International Scientific Conference on Informatics

和上一篇同样的作者,探讨了为 Python 开发更强大的静态分析工具的可能研究方向。

总结现有的静态分析方法:模式匹配、AST 匹配、符号执行、混合执行。对比基于 AST 的 Pylint 和实验性的符号执行工具 mini-mc(使用 Z3 约束求解器的 Python 接口)

  • 4 个代码片段测试 mini-mc 的检测能力,其中 2 个片段用于比较
Pylint mini-mc
引用未定义变量 x
可能的除零异常(误报) x
  1. 引用未定义变量

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    def func(arg):
    if(1== arg):
    print("branch11 ", os.getpid())
    z = 1
    if (1!=arg):
    print("branch21 ", os.getpid())
    x = z

    arg = BitVec(arg, 32)
    func(arg)
  2. 可能的除零异常

    1
    2
    3
    4
    5
    6
    7
    def func(arg):
    if arg == 41 :
    print("branch21 ", os.getpid())
    else: # 输入为 42 时确实会引起异常,但其他情况下没有问题
    print("branch22 ", os.getpid())
    z = arg - 42
    z = 99 / z

3. Static Value Analysis of Python Programs by Abstract Interpretation

NASA Formal Methods Symposium

通过 抽象解释 推断变量类型、运行时错误和未捕获异常,只支持一小部分内置对象和标准库的分析。

调用图工具

1. Empirical Study of Python Call Graph

2019 34th IEEE/ACM International Conference on Automated Software Engineering (ASE)

对现有的 Python 程序调用图生成工具进行比对(PyanCode2flowPycallgraph、Understand),以 Pycallgraph 作为基准,用常见的模块源代码进行测试(scikit-learn、theano、networkx、numba、joblib、pandas)

  • Pyan、Code2flow、Pycallgraph(6 年前停更):Github 开源工具
  • Understand:商业软件

针对 pandas 模块,各个工具生成的隐式节点数目有所不同,这造成了结果的巨大差异。

结论:现有的 Python 静态调用图工具在构建效果上存在较大差异,仍有改进的空间。

2. PyCG: Practical Call Graph Generation in Python

2021 IEEE/ACM 43rd International Conference on Software Engineering (ICSE)

提出了一种实用的、静态的 Python 调用图生成方法。涉及上下文敏感的过程间分析,不动点迭代算法等。没有分析循环和条件语句,也不使用变量类型信息,只能分析有源码的模块。

编写如下 crypto 模块进行测试:

1
2
3
4
5
6
7
8
9
10
11
12
import cryptops

class Crypto:
def __init__(self, key):
self.key = key

def apply(self, msg, func):
return func(self.key, msg)

crp = Crypto("secretkey")
encrypted = crp.apply("hello world", cryptops.encrypt)
decrypted = crp.apply(encrypted, cryptops.decrypt)

(a) 是实际的调用图(人工绘制),(b) Pyan 没有进行过程间分析,(c) Depends 的策略非常保守,只有预期信息足够才生成调用边

使用 PyCG 分析 crypto 模块,可以看到完整且正确的名称解析和调用

  • 橙色:模块
  • 红色:类
  • 黑色:函数
  • 蓝色:变量

微观基准测试套件(Micro-benchmark Suite)包含 112 个独特的小型代码,涵盖 Python 语言的各种特性,分为 16 个类别。

宏观基准测试(Macro-benchmarks)使用 5 个流行的开源 Python 软件,平均用了 10h 为每个项目生成调用图

PyCG 和 Pyan 对比,PyCG 基本为所有代码生成了完整的调用图(111/112),Sound 只覆盖了 103 是因为没有覆盖 Python 的星号赋值;Pyan 整体比较残念,在赋值相关的测试中表现良好。

这里的 complete 和 sound 是静态分析中的概念:

PyCG 和 Pyan、Depends 对比,在真实的 Python 项目上,PyCG 能够生成高精度的调用图,Recall 值较低是因为方法的局限和缺乏对 Python 某些功能特性的支持。

另外还比较了一下时间和内存的消耗(取 20 次的平均值)

3. Qualitative and Quantitative Analysis of Callgraph Algorithms for Python

2021 International Conference on Code Quality (ICCQ)

提出了一个可扩展的 Python 调用图比较分析框架 eval_CG,包含微观测试和宏观测试

  • 微观测试:49 个小型代码,分为 13类
  • 宏观测试:5 个开源 Python 项目,Python robotics、mitmproxy、cookiecutter、YouCompleteMe、The Fuck

对不同的调用图构造工具进行系统的比较

  • 静态调用图(Code2flow、Pyan、WALA)
  • 动态调用图(PyCallGraph)通过动态分析执行路径生成 Python 调用图,这种分析应该用另一种方法(例如模糊测试)来获得有意义的结果,否则会产生许多误报

结论:这些工具生成的静态调用图都包含虚假边,而且都没有生成 sound 的调用图(没有漏报)

结语

整理了几个可以用于生成调用图的工具,之后试试看: