SafeDrop

摘要

Rust是一种新兴的编程语言,旨在防止内存安全漏洞。然而,Rust目前的设计也带来了副作用,可能会增加内存安全问题的风险。特别是,它使用OBRM(基于所有权的资源管理),并在不使用垃圾收集器的情况下强制自动回收未使用的资源。因此,它可能错误地释放回收的内存,并导致free -after-free或double-free问题。在本文中,我们研究了无效内存回收问题,并提出了一种静态路径敏感的数据流分析方法SafeDrop来检测此类错误。我们的方法通过遍历控制流图和提取每个数据流的所有别名来迭代分析Rust crate的每个API。为了保证精度和可伸缩性,我们利用改进的Tarjan算法来实现可伸缩的路径敏感分析,以及基于缓存的策略来实现高效的过程间分析。实验结果表明,我们的方法能够成功地检测出所有存在的此类问题的cve,且假阳性数量有限。与最初的编译时间相比,分析开销范围从1.0%到110.7%。我们进一步将我们的工具应用到现实世界的几个Rust板条箱,发现8个Rust板条箱涉及无效的内存回收问题。

1.介绍

Rust是一种新兴的编程语言,具有具有吸引力的内存安全保护特性,同时提供了与C/ c++相比的效率。它通过将Rust编程语言划分为安全Rust和不安全Rust来实现这一目标。边界是一个“不安全”标记,它将内存安全问题的风险(例如,解除对原始指针的引用)限制在不安全代码[6]中。从狭义上讲,Rust保证了安全Rust的可靠性,没有引入内存安全问题[5]的风险。由于这些优势,许多现实世界的项目开始采用Rust,如Servo[3]和TockOS[22]。尽管Rust在防止内存安全bug方面的有效性得到了积极的反馈,但在实际的项目中仍然存在大量这样的bug[10,28,33]。

这项工作研究了在现实世界中与RAII(资源获取即初始化)相关的Rust板条箱(项目)中发现的一种特定类型的内存安全漏洞。具体来说,Rust采用了一种新颖的OBRM(基于所有权的资源管理)模型,该模型假设在创建所有者时应该分配资源,而当其所有者超出有效范围时应该重新分配资源。理想情况下,这个模型应该能够防止悬空指针[7]和内存泄漏[21],即使在程序遇到异常时也是如此。然而,我们观察到许多现实世界的关键缺陷锈箱与这样自动回收方案相关联,例如,它可能错误地放弃一些仍在使用的缓冲区,并招致使用——无错误(例如,cve - 2019 - 16140),也可能错误地悬空指针下降并导致双自由(例如,cve - 2019 - 16144)。

通常,内存回收错误是由不安全的代码触发的。Rust中需要不安全api来提供对实现细节[10]的低级控制和抽象。然而,滥用不安全的api会使基于所有权的资源管理系统的可靠性失效,并可能导致未定义的行为。例如,一个不安全的API可能导致共享别名的内存回收,并且删除一个实例将导致其余别名[12]的悬空指针。此外,Rust中的内部不安全[28]只允许内部有不安全代码的函数,并且可以作为安全函数调用,这可能在内部存在潜在的内存安全问题。当前的Rust编译器在不安全代码的内存安全风险方面做得很少,只是简单地假设开发人员应该负责使用它们。由于内存安全性是Rust所推广的最重要的特性,因此在可能的情况下减少此类风险是非常重要的。

为了解决这一问题,本文提出了一种静态路径敏感的数据流分析方法SafeDrop,用于检测由于自动重分配机制而导致的内存安全漏洞。safeddrop反复分析Rust板条箱的每个API,并检查Rust MIR(中级中级表示)中的每个drop语句是否可以安全启动。由于别名分析的根本问题是NP-hard(如果它可以被判定为[19]),我们采用了几种设计来提高方法的可伸缩性,同时不牺牲太多的精度。我们的方法采用了meet- over-paths (MOP)[1]方法,并在改进的Tarjan算法[11]的基础上提取出每个函数的所有有价值路径,该算法能够有效地消除循环中具有相同alias关系的冗余路径。对于每个路径,我们以对流敏感的方式提取所有别名的集合,并相应地分析每个drop语句的安全性。当遇到函数调用时,我们递归地对被调用者执行SafeDrop,并分析参数和返回值之间的别名关系。为了避免重复分析,我们对每个函数的混叠结果进行了缓存和重用。

我们已经将我们的方法实现为Rust编译器v1.52的查询(传递),并在现有的此类cve上进行了实际的实验。实验结果表明,SafeDrop可以成功地召回所有这些错误,但错误阳性的数量有限。与最初的编译时间相比,分析开销范围从1.0%到110.7%。我们进一步将safeddrop应用于其他几个Rust板条箱,发现8个板条箱有以前未知的无效内存回收问题。

我们将本文的贡献总结如下:
我们的论文是研究Rust程序中与RAII副作用相关的内存回收错误的第一次尝试。我们系统地讨论了这一问题,并提取了这类错误的几种常见模式。虽然我们的工作重点是Rust,但这个问题也可能存在于其他带有RAII的编程语言中。
我们设计并实现了一种新颖的路径敏感数据流分析方法,用于检测内存释放错误。它采用了一些精心的设计,以便在不牺牲太多精度的情况下实现可伸缩性,包括用于路径敏感分析的改进Tarjan算法,以及用于过程间分析的基于缓存的策略
我们已经用现有的Rust cve进行了实际的实验,并验证了我们方法的有效性和效率。此外,我们还发现了8个Rust板条箱涉及到以前未知的无效内存回收问题。

2.前言

本节介绍了问题的背景,包括Rust的内存管理模型,Rust编译器的基础知识和Rust的borrow checker,以及它的局限性。

2.1 Rust内存管理

Rust引入了一种新颖的基于所有权的资源管理系统来管理内存,该模型假设每个变量对其分配的内存具有独占所有权。所有权可以以可变或不可变的方式借用为引用,有几个限制(生命周期规则)。它要求引用不能比它的引用活得更久,并且可变引用不能有别名。Rust还提供了传统的原始指针(类似于引用),没有以前的要求。然而,任何可能违反内存安全的原始指针操作都被认为是不安全的,需要用“不安全”标记进行包装,例如,解引用原始指针。

在类型系统中,Rust将类型划分为两个互斥类型类型:复制特性用于仅堆叠的数据,Drop特性用于其他数据。这些特性对于管理内存和决定Rust如何处理右值以在赋值中生成左值,以及参数传递和值返回都很重要。如果右值具有Copy特性,Rust将在堆栈中复制(复制)它,而旧的变量仍然可用。否则,它将把所有权从右值转移(移动)到左值,旧的变量不再可用。

Rust的内存管理思想与c++的智能指针有很多相似之处,它有利于Rust实现自动RAII,即资源被限制在有效的范围[29]。特别是,每个drop-trait变量或temporary都与固定drop scope1相关联。当初始化变量或临时变量超出删除作用域时,析构函数将按顺序递归地删除其字段。因为Rust有意在每个程序点管理每个drop- trait实例的所有权,它可以在控制流离开drop作用域时自动运行析构函数,从而释放未使用的资源,而不需要垃圾回收。

2.2 Rust编译器基础知识

Rust编译器定义了一个查询系统来执行需求驱动的编译,这与传统的基于pass的系统[31]不同。图1给出了编译Rust程序的几个主要步骤,检测内存安全违规的关键设计是基于MIR的。MIR的语法是SSA(单一静态赋值)形式,类似于传统的LLVM IR(中间表示)[20]。为简单起见,清单1展示了Rust MIR的独特语法,它为实现基于所有权的资源管理奠定了基础,rfc# 12112提供了完整的语法系统。

根据语法,一个基本块由几个语句和一个终止符组成。语句主要有三种形式:赋值语句、活存语句和死存语句。赋值函数用不同的方式将RValue的值赋给LValue。特别地,移动LValue意味着将LValue的所有权转移给RValue;& mut LValue意味着借用LValue作为一个可变引用;和* LValue意味着创建一个指向LValue的不可变原始指针。storage-live语句和storage-dead语句分别表示变量存储的活动范围的开始和结束。每个基本块的结束符描述了它如何连接到后续块。Drop(Value, BB0, BB1)意味着一旦Value超出Drop作用域就调用Value的析构函数。为了在遇到异常时实现资源的自动回收,Rust MIR中可能导致异常的终止符很常见,也就是说,增加了一个额外的异常处理块BB1来接管堆栈展开过程。特别是,SwitchInt()是一个特殊的终止符,我们将在第4节中讨论它。

2.3 Rust的借用检查器

Rust MIR引入了一个借用(参考)检查基于寿命推断。非词法生存期(NLL)可以为程序员节省很多精力,因为他们需要以细粒度的方式冗长地指定生存期,并且它可以确定引用是否可用。rfc# 20943正式记录了该机制,我们在下面强调了关键思想。NLL根据活动性、子类型和再借用需求的考虑提取一组约束。活动约束意味着引用从声明到最后使用都是有效的;子类型约束意味着引用的生命期不应该超过它的引用;重借约束意味着一个引用的生存期不应该超过它的引用,这个引用是通过解除对其他引用的引用从实例创建的。然后Rust编译器解决了由NLL建立的约束,并通过定点迭代计算每个引用的最小生命周期。通过这种方式,它可以生成一个优化的解决方案。因此,借用检查器可以检测违反内存安全的行为,这些行为违反了之前为引用制定的生存期规则,也就是说,引用的生存期超过了它的引用。基于这些信息,可以在Rust编译器中保证引用的安全性。

2.4 Rust编译器的局限性

现在,我们介绍Rust中的内存管理模型和借用检查器。如图1所示,Rust编译器在将代码降低到MIR之前剥离“不安全”标记,因此借用检查器对安全Rust和不安全Rust都有效。然而,该系统存在几个缺陷。健全的寿命推断和借用检查,确保仅参考的安全。任何与原始指针交互的不安全代码都可能违反安全承诺,并可能导致内存回收。此外,内存管理模型不能区分多个drop-trait实例之间的别名关系,因为它只将每个实例关联到一个固定的drop-trait作用域。由于没有这样的算法来保证原始指针和内存回收的安全性,这是导致本文所研究的内存回收漏洞的主要原因。

3.问题陈述

3.1 激励的例子

Rust执行RAII并自动释放未使用的资源。在实践中,这种机制可能会错误地丢弃一些缓冲区,并且容易出现内存安全问题。基于有bug的Drop()终止符的出现,我们将此类问题分为两种情况:正常执行路径中的无效Drop和异常处理路径中的无效Drop。

3.1.1 正常执行的Drop无效。如果罪魁祸首Drop()终止符位于正常的执行路径,并且Drop()的参数启动不安全,就会发生这样的错误。我们使用图2a作为一个概念证明(PoC)示例来演示这些情况。

genvec()函数是一个内部不安全的构造函数,它基于原始指针ptr创建向量v。在这个PoC中,字符串s和向量v共享相同的内存空间。在自动回收string对象后,vector对象将包含一个悬空指针,指向已释放的缓冲区,稍后在main函数中使用这个vector对象将导致“After -free”。只要不再使用vector并且控制流超出其删除作用域,它就会被自动删除并产生双自由。实践中发现的此类bug有很多,如CVE-2019-16140是免费使用的,CVE-2018-20996和CVE-2019-16144是双免费的。

图2b显示了MIR语法中的等价描述。bb0中的_1创建一个新字符串,bb5中返回的向量_0基于_1创建,带有别名传播链1->5->4->3->2->8->0。因此,_0包含一个别名指针_1。也就是说,删除_1会导致_0的悬空指针,而稍后在main函数中删除_0会导致double free指针。

为什么不能借检查器添加对原始指针的支持来检测此类问题?问题在于处理函数调用的权衡设计。例如,_2是通过调用deref_mut(move _5)创建的,它的参数_5是_1的可变别名。为了简单起见,Rust假设每个参数要么将其所有权转移给drop-trait变量的被调用者,要么为copy-trait变量复制一个深度副本,然后返回值将不再与仍然存在的活变量共享所有权。这样的假设使借用检查程序免于复杂的过程间分析。作为一种交换,它在不安全的Rust中为原始指针留下洞。注意,from_raw_parts()是一个不安全的函数,它是导致本例中内存回收的罪魁祸首。然而,在将代码降低到MIR之后,“不安全”标记已经被移除,Rust编译器再也不能区分安全边界了。此外,当前的Rust编译器没有为原始指针添加别名检查支持,更不用说更复杂的数据结构了。因此,在目前的框架下,这个问题无法轻易解决。

3.1.2 异常处理无效丢弃。在一些真实的bug中,内存回收问题只存在于异常处理路径中,因为程序会陷入恐慌并进入unwind过程。例如,CVE-2019-16880和CVE-2019-16881在程序恐慌时存在double-free问题;CVE-2019-15552和CVE- 2019-15553可能会在异常处理时丢失未初始化的内存。图2展示了这个PoC来演示这个问题。

假设开发人员通过在图2a中的代码中添加mem::forget()来修复这个bug,从而防止了图2b中bb6中的drop(_1)。它们还可以在创建v和调用mem::forget()之间添加更多语句(例如,检索vector的内容)。这样,这些额外的语句仍然是容易受到攻击的程序点。如果程序在这些点出现恐慌,根据RAII的原则,Rust应该在堆栈展开期间通过不断调用Drop()来释放资源。由于别名drop-trait实例的存在,删除这些变量将导致双重自由问题。此外,mem::forget(s)接受字符串s的全部所有权,它通常尽可能晚地使用,以便其他语句仍然可以使用该字符串。

同样,删除未初始化的内存也是异常处理期间的一个常见问题。图2c和2d演示了一个PoC,它首先应用未初始化的缓冲区,然后对其进行初始化。但是,如果程序在缓冲区完全初始化之前出现恐慌,那么堆栈展开过程将丢弃那些未初始化的内存,这类似于在缓冲区有指针的情况下使用after-free。

3.2问题定义

我们把这个问题形式化如下。假设一种支持RAII的编程语言会根据一些静态策略自动释放未使用的内存。由于静态分析的限制,这样的回收可能会导致内存安全错误。特别是,有两种类型的无效内存回收。

定义3.1(删除使用中的缓冲区)。如果算法错误地释放了一些稍后将被访问的缓冲区,它将导致易受内存安全问题影响的悬空指针,包括free后使用(use-after-free)和double-free。

Definition3.2(删除无效的指针)。如果无效指针悬空,删除指针将导致双空闲;如果无效指针指向一个包含指针类型的未初始化内存,删除指针可能会递归地删除其嵌套指针,并导致无效的内存访问。

无效的drop问题应该是执行RAII的编程语言的常见问题,比如c++和Rust。然而,在《Rust》中,由于两个原因,它更严重。首先,Rust更强调内存安全,而这种安全问题是难以容忍的。其次,由于Rust没有垃圾收集器,它在资源回收和防止内存泄漏方面非常积极。

3.3 典型的模式

我们将举例说明这类错误的几个典型模式。注意,由于用于正常执行路径和异常处理路径的数据流方法是相似的,所以我们在这些模式中不再进一步区分它们。清单2从语句序列的角度总结了无效内存回收的7种典型模式。模式1和模式2对应于图2b中的PoC,它导致了免费使用(UAF)或双免费使用(DF)。我们使用GetPtr()作为获取drop-trait实例的原始指针的通用表示,并使用UnsafeConstruct()来表示构造新别名实例(例如from_raw_part())的不安全函数调用。模式3和模式4类似,但切换了UnsafeConstruct()和Drop()的顺序。模式5没有UnsafeConstruct(),但直接使用悬空指针,并导致免费使用。模式6和模式7对应于图2d中与未初始化内存相关的PoC。我们使用Uninitialized()来表示drop-trait实例的构造函数,而不需要初始化。使用未初始化的内存或直接删除内存都很容易受到无效内存访问(IMA)的影响。

4.方法

在本节中,我们将描述我们在第3节中描述的检测无效内存回收问题的方法。

4.1 Overall Framework

我们使用一种对路径敏感的数据流分析方法来解决这个问题,并将其集成到名为safeddrop的编译器中。图3概述了我们的方法的框架。它输入每个函数的MIR,并输出潜在无效内存回收问题的警告以及有bug的代码片段。下面我们将讨论safeddrop的关键步骤。

•path Extraction: safeddrop采用了meet-over-paths的方法来实现路径敏感度。由于函数的路径可以是无限的,我们采用了一种基于Tarjan算法的方法来合并冗余路径并生成生成树。这个步骤遍历这个树,最后枚举所有有价值的路径。

别名分析:SafeDrop字段敏感。这个步骤分析每个数据流的变量和复合类型的字段之间的别名关系。SafeDrop也是过程间的,上下文不敏感。它在返回值和参数之间缓存和重用被调用者获得的别名关系。

•无效删除检测:基于之前建立的别名集,这一步为每个数据流搜索脆弱的删除模式,并记录可疑的代码片段。

在下面的小节中,我们将通过解释更多细节来展开这些步骤。

4.2道路提取

SafeDrop采用了一种相遇过路径的方法来提高精度,而不是采用传统的半格方案[15]。它遍历函数的控制流图并列举所有有价值的路径。SafeDrop,价值的路径的概念有两个标准:1)有价值的路径应该是一个独特的代码块的开始节点(函数入口)和退出节点,和2)的一个有价值的路径不应该子集的任何其他有价值的路径。如果第二个条件存在冲突路径,我们只选择具有更多唯一块的路径。通过这种方式,我们不需要重复遍历循环,而只需要考虑最大的循环块集(作为一个强连接组件)。有效性可以根据[2]形式的SSA代码的别名分析规则自解释。

我们的方法利用改进的Tarjan算法[11]来删除冗余路径。主要算法的细节可以在算法1中找到。该算法首先使用一个修改图拉真算法来生成一个图的生成树(1 - 6行),然后遍历这个访问所有有价值的路径生成树(第7行)。传统Tarjan算法分解图的强连通分量(SCC)和消除了周期简洁地(2行)。因此,safeddrop可以将强连接的组件收缩成点,并生成此图的生成树(第3行)。理想情况下,生成树枚举所有有价值的路径,而无需重复遍历循环。然而,这种粗粒度的方法可能会由于某些特定的Rust语句而导致不正确的分析结果。例如,Rust对枚举类型引入了一种关键的设计,这使得传统的Tarjan算法不那么精确,因此我们应该做一些修改。图4演示了这样一个例外情况和我们相应的解决方案。

为了详细说明,图4使用了一种特定类型的终止符SwitchInt(),它是罪魁祸首。SwitchInt()的参数是一个枚举,枚举的变量决定了控制流,例如图4a中的节点3。在将scc收缩成点后,图4a中与SwitchInt()连接的分支是互斥的,因为变量A和变量B是互斥的。因此,SafeDrop将连续的𝑘SwitchInt()指向相同的枚举参数,其中包含𝑛可能的变量(第5行),然后为所有终止符枚举每个变量一次,以解决互斥锁冲突(第4-6行)。它删除不可达的路径,并为不同的变体(图4b中节点1的上分支)构建独立的路径,而不影响其他终止器(图4b中节点1的下分支)。该方法最终将本期的时间复杂度从𝑂(𝑛𝑘)降低到𝑂(𝑛)。

对生成树的遍历将为safeddrop提取所有有价值的路径。由于SafeDrop实现了一种相遇路径方案,它为每个路径分配独立的遍历状态。当遇到多分支节点时,它会备份状态,当从递归返回时,状态将被恢复(第18行)。此操作保证了面对分支时遍历状态的一致性。虽然我们利用了一个改进的Tarjan算法来删除冗余路径,但路径爆炸并非完全不可避免。不稳定的路径爆炸主要是由嵌套的条件语句而不是循环产生的,所以我们为它设置了一个较大的阈值。如果计数器超过上限,SafeDrop将执行传统的半格方案,这是精度和速度之间的权衡。

4.3 Alias Analysis

SafeDrop对每个路径执行别名分析,并为每个程序点建立别名集(第11行)。在本小节中,我们首先讨论别名分析的基本规则,然后介绍如何执行过程间别名分析。

4.3.1别名分析基本原则不是所有的化名都是我们的兴趣所在。由于内存回收错误通常是由Drop trait触发的,SafeDrop引入了一个类型过滤器来跳过对Copy trait(仅堆栈)变量的分析,并省略了对带有过滤类型的语句的分析。清单3提取了这个过程中可以忽略的无关类型。具有复制特性的Rust基元将被简单地过滤掉,即i32。作为一个例外,它删除过滤器中的切片、引用和原始指针类型,因为它们的别名关系的精确传输,即使它们是Copy trait。safeddrop将过滤器扩展为复合类型,并递归地检查每个字段,即Box。对于每个复合类型,只要它的所有组件都被选中为过滤,它就会被过滤,并且它应该同时实现Copy trait。

清单4总结了产生别名关系的5种语句。这些语句是对RValue进行不同操作的赋值。在对变量应用类型筛选器后,匹配语句将为RValue和LValue建立别名关系。在safeddrop中,我们使用union-find分离集来存储别名关系。如果集合A中的一个元素与集合B中的元素有别名关系,那么集合A和集合B将被合并。我们提供了一个建立别名的例子。如果一个赋值中的LValue和RValue都没有被过滤,并且赋值匹配LValue:= Ref(RValue)的形式,SafeDrop将建立别名关系并合并集合。

SafeDrop也是一种字段敏感的方法,因为字段不敏感的策略会给SafeDrop带来一系列的误报。由于它应用了union-find disjoint集合来表示别名关系,如果我们将整个复合类型变量视为一个单元而不是分割其字段,那么它的精确度就会大大降低,而且容易合并不相关的集合。因此,SafeDrop确定一个赋值中的LValue和RValue是否为复合类型变量的字段,并独立地为该字段建立别名集。此方法也适用于过程间别名分析中的参数传递和值返回,如下所示。

4.3.2过程间别名分析。别名分析的基本规则是一种引入过程的方法。它敏感地分析每个语句流,并将别名关系存储为每个程序点的分离集。然而,由于函数调用,这种分析可能不可靠。如果路径包含函数调用,我们应该执行过程间分析,以获得参数和返回值之间的别名关系。

被分析。程序的调用链嵌入到每个基本块的结束符中,包含参数、返回值和内部被调用者ID。作为函数调用的到达,SafeDrop调用一个查询,通过内部ID向编译器询问被调用者的最终优化的MIR。然后,SafeDrop遍历这个MIR,并为其变量建立别名集。被调用者的别名分析最终返回一个结果,其中包含参数和返回值之间的别名关系。

递归细分。SafeDrop采用定点迭代的方法来解决递归调用问题。它维护一个调用堆栈,以确保每个函数ID只能在这个堆栈中出现一次。当函数第一次到达并将其ID推入调用堆栈时,SafeDrop将在返回值和参数之间设置默认别名关系(false),并中止以防止无限递归。考虑到递归调用通常会退出,而别名关系也可能同时改变,分析程序应该使用更新的别名结果对堆栈中的函数重新执行SafeDrop,直到最终的别名关系到达固定点为止。

缓存优化。由于safeddrop是一种交叉路径的方法,参数和返回值之间的别名结果是所有有价值路径的联合(第15行)。也就是说,一个参数与返回值具有别名关系,只要它至少有一个路径具有这种关系。safeddrop只需要对给定的函数进行一次分析。在第一次遍历这个函数之后,分析结果将缓存到哈希表中(第9行),分析器可以在再次遇到这个函数时直接得到结果。

4.4无效丢弃检测规则

一个重分配的安全性通常取决于别名集。对于每个路径,safeddrop检测每个内存回收流—敏感地确认它是否会引起内存安全问题(第12行)。如果检测到内存回收错误,safeddrop将记录此问题的类型以及相关的源代码,然后在路径的末尾合并结果(第15行)。

SafeDrop维护一个污点集来记录释放的缓冲区,以及返回的悬空指针。当在终止符中查找Drop()时,它将删除变量添加到删除集中,并将其标记为删除源。对于复合类型的变量,safeddrop会将每个drop-trait字段添加到污点集,而不是整个变量。污染源在别名集中传播,并污染其他别名。然后,在每个程序点上,SafeDrop迭代地检查使用变量与污染集中所有元素之间的别名关系。作为一个异常,我们将由uninitialized()构造的变量插入到污染集,作为声明的时间,如果该变量后来被初始化,则删除该变量。

根据第3节中的典型模式,我们总结了无效内存回收引起的内存安全问题的4条规则,所有这些规则将应用于正常执行和异常处理路径,如下所示。

•Free后使用(use After Free):污点集包含using变量在语句中的别名,或将该变量传递给函数。
•double Free:污染集包含Drop()终止符中删除变量的别名。
•无效内存访问:在使用和删除时,污染集包含一个未初始化的变量。
悬空指针:污染集包含返回指针的别名。虽然它丢失了使用上下文,但这个指针实际上是有bug的,使用起来非常不安全。

safeddrop为这些规则使用独立的标志。true标志表示在这个函数中存在特定的内存安全问题。这些旗帜被缓存到哈希表的一部分inter-procedural后分析结果分析(9)行。对于每个路径,这些旗帜将合并以及别名关系参数和返回值,当SafeDrop完成遍历所选路径(15行)。

4.5紧急情况处理

改进后的Tarjan算法生成一棵生成树来提取有价值的路径,从而消除循环中的冗余遍历。然而,存在一种引入了大量误报的极端情况,我们使用图5作为例子。在图5中,_1被初始化并在SCC(2-3-4-5-6-7-8-9)中被删除。路径1-2-3-4-5-6-10中的节点10的回收是安全的,被认为是路径1-(2-3-3 -5-6-7-8-9)-10中的无效drop。第二条路径确实是假阳性,因为_1在节点4中被更新了,但我们忽略了从节点2到节点6的遍历两次。因此,我们进行了改进,使safeddrop记录变量的定义块,并验证针对同一个变量的重复删除的分支是否在SCC中的定义块和回收块之间。这种改进的正确性是基于混叠分析中混叠关系合并的单调性。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值