南京大学 | Python项目中的依赖分析 (TSE 2023)
2023-5-10 23:43:5 Author: 安全学术圈(查看原文) 阅读量:13 收藏

原文标题:Towards Better Dependency Management: A First Look At Dependency Smells in Python Projects
原文作者:Cao Y, Chen L, Ma W, et al.
原文链接:http://dx.doi.org/10.1109/tse.2022.3191353
发表期刊:IEEE Transactions on Software Engineering 2022
笔记作者:[email protected]安全学术圈
笔记小编:黄诚@安全学术圈

在现代软件开发中管理跨项目依赖是一件非常棘手的事情。依赖管理的一种主要方式是使用依赖配置文件,其出现给整个软件生态中的各方用户带来了极大的便利。然而,当依赖配置文件没能很好地编写和维护时,开发者则可能在项目中引入dependency smell。dependency smell因编写者在依赖配置文件中反复违反依赖管理招致,并且可能会潜在地导致严重后果。在这篇论文中作者从Python项目入手深入研究了三种dependency smell:1)Missing Dependency(MD);2)Bloated Dependency(BD);3)Version Constraint Inconsistency(VCI)。作者团队实现了一个名为 Python Cross-project Dependency(PyCD)的工具来准确提取配置文件中的依赖信息。该工具在212个Python项目上表明其超过了目前SOTA的方法。随后作者团队在132个Python项目上调查了三种dependency smell的普遍性、成因以及演化过程。结果表明:1)dependency smell在Python项目中十分普遍,并且在不同项目中各不一致;2)dependency smell出于各种原因被引入到项目中,主要是由于同步更新以及协作开发导致;3)dependency smell可以按照不同的类型以不同的模式移除。此外,作者团队报告并获得了来自40个有害dependency smell实例的响应,其中的34个已响应的dependency smell确实存在于项目中,其中的10个实例已经被修复或正在处理。来自开发者的反馈表明这些dependency smell确实对项目维护存在消极影响。

Introduction

为了使用其他的软件包来加快软件开发的速度,在软件开发中编写配置文件对依赖进行管理极为普遍,例如Python中的requirements.txt,Maven中的pom.xml,npm中的package.json,以及bundler的Gemfile。然而在带来便利的同时问题也随之产生:1)当在源码中导入相关依赖时,开发者没有在配置文件中完全或精确声明依赖关系,带来了MD和BD两种问题;2)而当开发者在多个配置文件中声明依赖关系时,则可能导致依赖声明中的版本约束不一致,带来VCI的问题。

Background

在Python项目中,开发者可以利用setup.py以及requirements.txt声明依赖,这些文件通常位于软件包的根目录。然而由于这些配置文件的命名方式以及位置没有硬性规定,为了方便起见,将这部分不规范的配置文件另归一类。

在这篇文章中,作者主要关注了三类依赖配置文件:

  • setup.py:主要关注setup.py中的四个参数,install_requirestest_requiresextras_requiresetup_requires
  • requirements.txt
  • Pipenv 和 Pipfile:用于导入跨软件生态的依赖(例如bundler,composer,npm,cargo和yarn等),通常用于project.toml。开发者可以使用packagedev-package两个参数来声明依赖。

Problem Definition

首先作者对项目之间的依赖关系进行定义,依据其是否存在版本约束分为两类,分别代表上游项目和下游项目:

接着作者对三种dependency smell进行定义:

MD即为缺失依赖,在源码中使用了但在配置文件中未进行声明:

BD即为臃肿依赖,在配置文件中进行了声明但在源码中并未使用:

VCI为版本约束不一致的依赖,即在不同的配置文件中声明了不同版本约束的依赖:

Methodology

作者整体的研究框架如下所示,包含工具PyCD实现,工具对比以及对三种dependency smell的观察研究。

Extracting Dependency Relations From Configurations

作者依据依赖关系的声明位置将其分为了两类:1)Declarative Dependency Declaration(DDD),该部分依赖存在于文本类配置文件,如requirements.txt以及Pipfile等;2)Imperative Dependency Declaration(IDD),存在于可执行文件中,如setup.py。

从配置文件中识别依赖关系存在两个主要问题:1)在各种文本配置文件中的依赖关系声明模式多种多样;2)可执行文件中的依赖声明存在路径选择的问题。因此单单使用正则表达式不足以解决该问题。

Identifying DDD: 作者总结了五种依赖声明的方式并且使用正则匹配的方式对文本配置文件中的依赖声明进行性识别。

  • DDD1:声明了需要安装的依赖名称以及版本约束,如果未进行版本约束,则将版本置为*。
  • DDD2:声明了需要安装的依赖名称以及版本约束,但是还需要满足特定的条件,例如不同的解释器版本或系统环境。
  • DDD3:声明了需要从远程仓库安装的依赖以及链接,将版本置为*。
  • DDD4:声明了需要从本地仓库或web链接安装的依赖以及链接。
  • DDD5:声明了在特定模式下(例如编辑模式 -e)需要从远程仓库、本地仓库或web链接安装的依赖以及链接。

而对于这些配置文件中产生的噪声,例如一些不可用的依赖声明,作者在PyPI上请求这些对应的依赖名以保证其可用性,并舍弃了不匹配任何软件包的依赖声明。

Identifying IDD: 对于可执行文件中的依赖声明,作者采用了抽象语法树的方法进行提取。依据前文的表述,在setup.py中仅需要对四个以来参数进行提取:install_requirestest_requiresextras_requiresetup_requires,最终的提取结果可表示为:{依赖名,依赖可达条件}。

具体的提取算法如算法1所示。在给定一个setup.py文件后,带有四个依赖参数的数据流集合DF,条件集合C以及函数集合FS会被初始化。在解析过程中,算法主要对三类语句进行处理:1)赋值语句,判断目标值是否在DF中,然后通过表2中的规则进行处理;2)条件判断语句,记录上下文信息并更新条件集合C;3)函数定义语句,记录函数返回值并更新函数集合FS

Extracting Dependency Relations From Source Code

作者主要关注了其他源码文件中的 import <package>from <package> import <module> 语句,具体的包名可以通过AST解析相应导入节点获取。同时作者排除了内置的模块或库名以及项目内定义的一些模块名。

而对于软件包名与导入名称不一致的问题,例如 Gitpython 是一个软件包,而开发者在使用过程中则需要导入 import git 来使用相关模块。作者通过下载前1万个热门Python软件包并解析其包内的 top_level.txt 文件来解决这个问题。

Data Extraction

作者采集了Github上排名前2,000的Python项目,排除没有通用配置文件的项目后保留了467,为了进一步减少工作量,随机采样了212个项目用于实验评估。而在观察研究中,作者保留了维护时间超过四年且版本数多于10的项目以更好地进行研究,最终保留了132个项目。

作者从这132个Python项目中提取了依赖关系,并将其分为三类:fixed version,constrained以及unconstrained。

此外,作者还对配置文件以及源码中存在的依赖数量分布进行了分析。

Evaluation of PyCD

作者在212个项目上选取了两种常用的工具进行对比:1)Github's Dependency Graph(GDG);2)Libraries.io。并选取了精度与召回率作为评价指标,即正确识别的依赖占所有识别依赖的比例以及占所有实际依赖的比例,结果如下图所示。

同时作者采用韦恩图的方式对各工具的识别结果进行了展示。

Dependency Smell

接着作者从132个项目出发对三种Denpendency Smell进行了观察研究,并对每种Smell从三个角度出发设置了若干RQ,即普遍性,成因以及变化过程。

MD: 作者使用缺失的依赖数目与源码使用依赖数量之比来描述MD的比例,并统计了各项目中的依赖缺失情况。

而对于MD的成因分析,作者选择了从仓库的commit入手进行分析。简单而言,作者首先定位了那些存在依赖缺失问题的源码文件,并分析每个commit中对应文件的依赖变化情况。最后,作者将开发者引入MD的各种意图进行了总结。

从项目中移除MD有两种方式,一是从源码中移除对应的导入,二是在配置文件中添加相应的依赖声明。通过分析仓库的commit记录,可以知道不同项目是如何对这些缺失依赖进行移除的。

同时作者还统计了这些MD移除的版本间隔。

BD: 作者使用臃肿的依赖数目与配置文件声明依赖数量之比来描述BD的比例,并统计了各项目中的依赖臃肿情况。

对于BD的成因分析,作者采用了与分析BD依赖时同样的方法,只是把分析对象切换为了存在臃肿依赖声明的配置文件,同样对导入原因进行了分析。

在对BD的变化过程进行分析时,在作者采用了一个长度为10的向量对项目的10个版本进行表示,并采用4种标签对项目状态进行描述:1)first consistency(C1),即配置文件与源码均无对应依赖;2)second consistency(C2),即依赖存在于配置文件且在源码中使用;3)Bloated(BD),配置文件中存在源码中未使用;4)Missing(MD),配置文件中未声明但在源码中使用。作者通过该种方式分别对BD的引入与移除过程进行了分析。

以及各种类型的臃肿依赖的移除方式。

VCI: 作者首先从两个维度对VCI的特点进行了分析,首先是依据配置文件的重要性将VCI划为了三个等级:1)Level 1,VCI发生在两个Within configuration files中,这种情况的VCI可能不会对安装和运行过程产生影响,但是可能会影响测试过程;2)Level 2,VCI发生在Root configuration files和Within configuration files中,这种情况将Within configuration files中的依赖版本与Root configuration files保持一致即可;3)Level 3,VCI发生在两个Root configuration files中,这种情况可能会严重影响项目的安装过程。

然后按照其是否存在冲突又分为了两个类别:1)Non-Conflicting Inconsistency(NCI),即存在同时满足两种依赖声明的项目版本;2)Conflicting Inconsistency(CI),不存在同时满足两个声明的版本。

作者使用版本约束不一致的依赖数量与配置文件声明依赖数量之比来描述VCI的比例,并统计了各项目中的依赖版本约束不一致的情况。

对于VCI的成因分析,作者采用了一种严谨的分析方法:第一作者对数据集中的VCI按照此前划分的三种等级进行分层采样。随后第一作者随机采样了其中的50个VCI实例,并对其成因进行分析,基于观察生成了一系列类别。最后另外三位作者对划分的类别进行检查以生成最终类别。最后使用敲定的类别对剩余的VCI实例进行标记,并在标记过程中讨论出现的异议直至达成共识。最后一共划分了四类成因:

  1. Type 1:在项目的模块中实行异步开发。项目中的多模块中存在相同的依赖项,但是使用不同的版本约束。这可能导致Level 1的VCI。

  2. Type 2:多版本维护。在项目的依赖配置文件中保留了多种版本约束以兼容新老用户。这可能导致Level 1和Level 2的VCI。

  3. Type 3:过时的版本约束。当项目变得越庞大时其维护也将变得越复杂,当一个模块中的依赖版本约束发生变化时同步对整个项目中的版本约束更新较为困难。这可能导致Level 2的VCI。

  4. Type 4:同步失败。开发者没有同时修改多个配置文件中的依赖版本约束。这可能导致Level 3的VCI。

最终的统计结果如下所示:

在对VCI的变化过程进行分析时,作者首先定义了VCI的三种移除模式:1)更新版本约束(Consistent),将配置文件中的冲突依赖版本置为相同状态;2)删除配置文件(DeleteFile),删除任一存在冲突的配置文件;3)移除依赖声明(RemoveDecl),删除任一存在冲突的配置文件中的相应依赖声明。最终的统计结果如下。

Harmful Dependency Smells

对于有害Dependency Smell的判定主要分为两个过程,首先作者通过制定规则初步筛选一些较为严重的Dependency Smell,然后反馈至项目仓库依据开发者的反馈情况进行判定。针对三种Dependency Smell,作者制定了不同的规则。作者保留了所有的BD。而对于MD,作者剔除了以下三类缺失依赖:

  • 缺失的依赖项被用于测试、配置、构建、和开发环节,以及文档中。

  • 缺失的依赖项被用于异常处理语句中。

  • 缺失的依赖项被注释或位于文档中。

作者报告了满足以下条件的VCI:

  • VCI属于Type 3或Type 4。

  • VCI存在Conflicting Inconsistency(CI)。

最终作者得到了114个smell的实例,其中29个已经被修复。在将所有的dependency smell汇总至同一个项目后,作者团队一共报告了39个issue,其中29个issue包含29个MD实例,11个issue包含13个BD实例,2个issue包含2个VCI实例。

排除开被开发者确认存在问题的dependency smell,从开发者的反馈中作者也发现了一些新的问题。首先是在MD的反馈中,一些用户使用较少的功能开发者并未对依赖进行默认声明,而是将其作为了可选项。而在BD的反馈意见中,一些开发者表明这些臃肿依赖属于间接依赖,即一些依赖项会依赖于改依赖,同时因为作者的方法仅对导入语句中的依赖进行识别,所以对其他的依赖使用方式存在漏报的情况,比如一些动态导入方法。一位开发者在VCI的反馈中提及其并不会对运行时造成影响。

安全学术圈招募队友-ing 
有兴趣加入学术圈的请联系 secdr#qq.com

文章来源: http://mp.weixin.qq.com/s?__biz=MzU5MTM5MTQ2MA==&mid=2247489022&idx=1&sn=b4e30ac939402cf34f1cd9755381546d&chksm=fe2eea75c9596363e38c345b191285408783ef57fb5e1930f8b41758124ab7d02ee60ea692da#rd
如有侵权请联系:admin#unsafe.sh