RFC-0057:无默认句柄

RFC-0057:默认无句柄
状态已接受
领域
  • FIDL
说明

我们建议默认禁止在 FIDL 类型声明中使用句柄,并添加新的关键字资源来标记允许包含句柄或其他资源类型的类型。添加或移除资源修饰符可能是一项破坏源代码的更改。

作者
提交日期(年-月-日)2020-01-16
审核日期(年-月-日)2020-01-23

“快看,不用手!”

摘要

我们建议默认禁止在 FIDL 类型声明中使用句柄,并在 新关键字 resource1 来标记允许包含标识名或 其他资源类型。添加或移除 resource 修饰符可以是 源代码更改

设计初衷

FIDL 的一大特色是支持 Zircon 标识名。句柄是 但会对其进行特殊处理:必须移动(而不是复制)它们, 并且必须关闭它们以避免泄露资源。这种特殊处理可导致 在考虑仅对普通数据有意义的特征时, 没有标识名。尽管 FIDL 绑定可以有条件地启用代码,但前提是 标识名的存在,这种做法不可取,因为它会破坏进化性 保证。例如,向表中添加字段通常是安全的, 句柄字段会破坏源代码,不仅对于该表,还对所有 来传递包含该元素的类型。这会使绑定变得保守, 始终假定类型可能包含句柄,即使库作者也是如此 并不想添加这些链接

对句柄的适应性导致了折衷问题:

  • 在 Dart 中,实现 FIDL 到 JSON 编码的努力收到了回复 因为它只适用于没有句柄的类型,因而会损害可演性。它 最终是使用 MaxHandles 属性构建的,但这是一个 临时解决方案,因为该属性仅适用于最外层的类型, 而不是通过它传递的所有类型。
  • 在 Rust 中,首次向类型添加句柄会破坏源代码 因为类型将不再派生 Clone trait。(正确克隆一个 需要调用 zx_handle_duplicate 系统调用, 这可能会失败。)
  • 协议的 Rust 绑定通过可变引用获取 FIDL 对象, 无需明确占有所有权,这样对象 没有标识名的容器可在之后重复使用

如果需要,您可以通过更安全、更人性化的方式处理所有这些情况 库作者来指示某个类型是否包含标识名,以及是否将 因此预计会破坏源代码

设计

术语

FIDL 类型可以是值类型,也可以是资源类型。资源类型 包括:

  • handlehandle<H>,其中H 是句柄子类型
  • Prequest<P>,其中 P 是协议的名称
  • 使用 resource 修饰符声明的结构、表或联合
  • 引用资源类型的类型别名
  • 封装资源类型的新类型 RFC-0052
  • T?,其中 T 不可为 null 资源类型
  • array<T>vector<T>,其中 T 是资源类型

其他所有类型均为值类型。

如果正确使用 resource 修饰符,值类型绝不会包含 句柄,而资源类型(现在或将来)可能包含句柄。

语言

可对结构体、表和联合应用新的修饰符 resource 声明。

如果没有 resource,则声明不得包含资源类型。 FIDL 编译器必须对此进行验证。它只需要检查直接字段:如果 A 包含 B,两者都未标记为资源,B 包含一个句柄, 编译将由于 B 而失败,无需单独的错误 有关包含句柄的 A 的消息。

使用 resource 时,可以在声明中包含资源类型。新的 其声明的类型也会被视为资源类型,即使它不包含 资源。

原则上,该语言允许对 newtype 声明使用 resource RFC-0052。但是,资源新类型没有实际用途 封装值类型,因此 newtype 会隐式继承值/资源 状态。

语法

此提案修改了 FIDL 语法中的一条规则:

declaration-modifiers = "strict" | "resource" ;

JSON IR

此提案将具有布尔值的"resource"键添加到 "struct_declarations""table_declarations""union_declarations" 数组。

请注意,此键与 "max_handles" 没有冗余性。值类型必须具有 max_handles 设置为零,但资源类型可以有任意数量的 max_handles,因为它反映了声明的实际内容(而不是 与库作者的 intent 相关联,以允许句柄)。

绑定

此提案不包含对绑定的特定更改。不过, 使 FIDL 绑定作者(包括 FIDL 团队)能够解决相关问题 请参阅动机部分。这里是一些示例, 但无需接受此 FTP:

  • 对值类型(或更多类型)实现 JSON 序列化和序列化 可能是 FIDL Text 格式,而不是 JSON, RFC-0058
  • 对值/资源上的 C++ Clone() 方法使用不同的类型签名 以强调只有资源克隆可能会失败。
  • 使 Rust 协议将值类型参数接受为 &T 和资源类型 设置为 T,而不是同时对这两者都使用 &mut T,并且仅改变 资源类型。

API 评分准则

API 评分准则中应该会提供关于何时使用 resource 的指导。一些简单的 用例:

  • 不含资源类型的结构体不应标记为 resource,因为 结构体未设计为扩展(在大部分情况下,之后添加句柄是 会破坏 ABI)。
  • 不应标记没有资源类型的严格表或联合体 resource,因为严格程度已经表明需要修改其字段 是一项破坏源代码的更改

它还应该解决了灵活表和联合的情况, 都拥有标识名例如,我们建议你执行一个错误操作 而库的覆盖范围则取决于库的用途 使用的语言进行破坏性更改的预期成本 使用次数以及其他因素。

实施策略

总体实现步骤包括:

  • 以原始格式解析 resource 关键字。
  • 迁移现有的 FIDL 库以使用 resource(如需了解详情,请参阅 未知
  • 使用测试验证 fidlc 中的值/资源类型规则。
  • resource 标志存储在 JSON IR 中,并在 fidlgen 中公开它。

工效学设计

此提案引入了一个新概念,使得 FIDL 更加复杂。取消点赞 其他 FIDL 结构,如“struct”和“协议”新用户不太可能 猜测什么是“资源”因此,他们需要从文档中学习。

此提案对 FIDL 语言的影响是有争议的 人体工学。它有助于吸引用户注意包含标识名的声明, 特别是在实际句柄值隐藏在嵌套结构中时。任何人 浏览库会立即发现结构体带有手柄, 只是数据另一方面,操心设计可能不太符合人体工程学 是否使用 resource 以及输入关键字。将一个声明从 资源值可能会产生痛苦的级联效应,导致许多类型必须 成为资源(不过这也是一件好事, 会显示为源代码中断)。

复杂性增加的原因得益于 FIDL 绑定的改进。使用 您可为值类型和资源类型自由提供不同的 API、绑定 可以变得更加安全和符合人体工程学。有关这些改进的示例,请参阅 绑定

文档和示例

您需要完成以下任务:

  • 更新所有涉及标识名的文档,以酌情使用 resource
  • 更新 FIDL 语言规范以解释 resource 修饰符。
  • 在 FIDL 教程中提及 resource。其中应该有一条简短的提示 解释了所有修饰符(例如 strictresource)。
  • 提供相关指南,说明没有标识名的新类型是否应成为资源。
  • 绑定利用值/资源的区别后,更新其 用于说明值类型提供的 API 之间的差异的文档 和资源类型,并提供在它们之间进行转换的说明 (如果可能的话)。

向后兼容性

此方案对 ABI 兼容性没有任何影响。

修正条款(2021 年 7 月)。在实施过程中,我们发现了一个边缘用例: 此提案与 RFC-0033: Handling of known fields and 严格程度。某些绑定在解码时会存储未知成员 和灵活联合;如果 未知成员包含句柄,因此在这种情况下解码必须失败。请参阅 兼容性指南

修正条款(2021 年 10 月)。经过 RFC-0137: 缓存中的未知数据 FIDL,则绑定不再存储未知数据,因此没有 边缘情况。因此,值/资源区分对 ABI 没有影响 兼容性。

添加或移除 resource 修饰符既不与源代码兼容,也不与源代码兼容 具有过渡性2 RFC-0024。 明确允许绑定为两种类型生成不兼容的 API 区别只在于存在修饰符, 无法编写在添加/删除 修饰符。想要在resource之间过渡的图书馆作者 与源代码兼容的方式必须创建新的类型和方法, 现有资源

绑定作者开始利用值/资源的区别之后, 我们将重新考虑这一决定。或许有必要为组织 (可能使用具有 [Transitional] 属性)。最初,我们不清楚这一点: 限制性,削弱了此提案旨在实现的潜在 API 改进 启用。

性能

此方案对构建性能的影响微乎其微:FIDL 编译器 只需略微多完成一些工作,即可解析新关键字,验证其使用情况。它没有 会直接影响 IPC 性能在某些方面, 如果绑定使用值/资源区分来创建 API, 避免不必要的复制例如,应该不需要克隆 值类型的对象来多次发送它。

安全

此方案不会直接影响安全性,但可让 提供更安全的 API例如,C++ 可以对 Clone() 强制进行错误处理 具有 [[nodiscard]] 的资源类型,或者 Rust 可以采用资源类型方法 移动参数,以防止随后意外使用已改变的对象。 此类更改可以防范 bug,包括安全 bug。

测试

我们将通过以下方式测试此功能:

  • 在 fidlc 中为解析和验证代码路径添加了测试。这些测试 应在各种情况下应用标记为 resource(或 not)不符合资源类型(或值类型)的定义。
  • 除了修复了 需要 resource 的现有声明。
  • 将 fidl 更改测试套件更新为 演示从值类型转换到资源类型的步骤, 反之亦然。

缺点、替代方案和未知之处

此方案引入了一个新关键字,使语言更加复杂。 关键字过多可能会造成问题;“严格资源联合”有点像 一口气。

此方案从两个方面削弱了 FIDL 进化性保证:

  • 以前,为类型添加句柄预计不会破坏源代码 更改。现在,这是明确允许的并且符合预期(除非类型为 由于预期需要添加句柄而标记为 resource)。
  • 之前,可以声明一个类型,但未来预期会:(1) 添加 并且 (2) 能够将其作为字段添加到 类型。现在,库作者首先必须在 (1) 和 (2) 之间做出选择。

该方案有两个主要的替代方案:

  • 不执行任何操作。让标识名能够随处使用,并实时记录 在添加或移除时,绑定必须保持源代码兼容性 标识名。
  • 默认的允许句柄。与此提案类似,但假定声明 默认资源类型,并且需要禁止使用 value 关键字 指定资源类型。

动机工效学两个部分认为 什么都不做对于另一种选择,经验表明 消息不包含句柄,并且在协议中传递句柄需要 关注和提前规划换言之,值类型是常见情况, 事后想到添加标识名的功能可能不如往常 看起来这表明最好将不允许标识名设置为默认使用。

此方案主要对使用 FIDL 绑定的最终开发者有益, 但这会影响 API 设计的库作者。这是一种权衡 遵守 Fuchsia API 委员会章程,该章程 使最终开发者优先于 API 设计人员和实现人员。

还建议了一种替代方案:作为引用处理。而不是 禁止标识名使用值类型,可以通过以下方式解决值/资源问题: 表示句柄作为参考。克隆包含句柄的结构 而只是对同一句柄再进行一次引用这可以通过 在 C++ 中使用 shared_ptr,并且无需 添加 resource 关键字。但是,它有一定的挑战:

  • 所有绑定都需要一个簿记机制,以确保标识名 只有在其最后一次引用消失后才会关闭。在有些地区,这可能很难 语言。
  • 向另一个进程发送一个句柄之后,对该句柄的所有其他引用都会 就像悬挂的指针一样处理标识名的便利性 更像是普通值意味着这些代码在编译时安全性较低, 情况。
  • 因为这涉及更改所有标识名的类型,因此很可能是 所有语言的重大更改顺利完成过渡 工作。

此提案还有几个待解决的问题:

  • 我们应如何迁移现有的 FIDL 库?正在将所有现有联系人标记为 使用 resource 进行声明虽然是安全的,但并不能反映库作者的 意图。仅标记最基本的代码(即包含句柄的类型) 是可行的,但如果假设任何没有 绝不含有任何标识名。
  • 如果已采用通用数据类型,此功能将如何与这些类型进行交互? 例如,如果我们定义一个 Pair<A, B> 类型,它在逻辑上应是一个 资源类型(如果 AB 是资源类型),而不是必须 为 Pair 本身添加注解。还有哪些情况最好 推导某个类型是否为资源?

早期艺术作品和参考资料

此提案的目标是,在以下情况下,允许发生破坏源代码的更改: 更改类型的值/资源状态。 RFC-0024 与此相关 目标,因为它确定了 FIDL 的源代码兼容性标准。它还 提到了标识名导致Clone特征难以在 此方案可以解决这个问题。

我们不知道有其他 IPC 系统能够解决这个问题 (区分可能包含句柄或系统资源的类型)。不过, 以一种“感染”的方式注释类型的概念所有使用网站都是通用的 编程语言。例如,JavaScript、Python 和 Rust 具有此行为,Haskell 中的 IO monad 也是如此。


  1. 此提案的较早版本称为关键字“entity”。

  2. 此提案的较早版本要求必须 可转换