我什么时候应该在 Racket 中使用 `protect-out`?

When should I use `protect-out` in Racket?

Racket 提供 protect-out to prevent module exports from being used with eval (or a deconstructed syntax object), unless the module has enough privileges (aka, has a strong enough code inspector)。该文档还为 它的作用提供了一个很好的示例:

> (module nest racket
    (provide num-eggs (protect-out num-chicks))
    (define num-eggs 2)
    (define num-chicks 3))
> (define weak-inspector (make-inspector (current-code-inspector)))
> (define (weak-eval x)
    (parameterize ([current-code-inspector weak-inspector])
      (define weak-ns (make-base-namespace))
      (namespace-attach-module (current-namespace)
                               ''nest
                               weak-ns)
      (parameterize ([current-namespace weak-ns])
        (namespace-require ''nest)
        (eval x))))
> (require 'nest)
> (list num-eggs num-chicks)
'(2 3)
> (weak-eval 'num-eggs)
2
> (weak-eval 'num-chicks)
?: access disallowed by code inspector to protected variable
  from module: 'nest
  at: num-chicks

也就是说,eval 有一个足够强大的代码检查器(因为它是在最初需要该模块的同一范围内调用的),因此能够获得导出。然而,weak-eval 没有,因为它得到了相同的模块实例,但是使用了一个较弱的检查器来进行 evaling.

我的问题是,什么时候应该使用protect-out?是否应该始终使用(或至少在可能的情况下)?或者是否有 protect-out 专门针对的特定工作流程?

不安全导出使用protect-out,其中不安全意味着有能力违反Racket语言或虚拟机的规则。特别是,滥用不安全的功能通常可能导致 Racket VM 崩溃。

不安全功能示例:

  • unsafe-vector-set! 或多或少允许您写入任意内存位置,可能会违反 GC 的不变量、Racket 在内存中的值表示的不变量等
  • unsafe-vector-ref 看起来不那么危险,但是你可以用它来从闭包中提取一个自由变量的值,即使 Racket 语言不允许检查过程的实现
  • 大部分 ffi/unsafe 显然是不安全的

这是一个不太明显的 不安全 过程的例子:

#lang racket
(require ffi/unsafe ffi/unsafe/define)
(define-ffi-definer define-c #f)  ;; searches libc, etc
(define-c fopen (_fun _path (_bytes = #"a") -> _pointer))
(define-c fclose (_fun _pointer -> _void))
(define-c fwrite (_fun _bytes _size _size _pointer -> _size))
(define (append-to-file path buf)
  (define fp (fopen path))
  (unless fp (error "couldn't open file"))
  (fwrite buf (bytes-length buf) 1 fp)
  (fclose fp))
(provide append-to-file)

考虑 append-to-file 过程。它是使用不安全特性(FFI)定义的,但是 FFI 类型 _path_bytes 会拒绝错误的 Racket 值,因此使用 append-to-file 应该不可能使 Racket 崩溃。 append-to-file 过程仍然是不安全的(虽然可能不像 unsafe-vector-set! 那样灾难性地不安全)因为它绕过了 Racket 的安全保护机制。该过程在另一方面是不安全的,因为 fopenfwrite 可以阻塞,而 Racket 代码不应该阻塞。

protect-out提供了Racket核心库中的不安全操作。如果您使用它们来定义您自己的不安全操作,您也应该使用 protect-out 提供派生的不安全操作。如果你使用不安全的特性定义了一个安全的特性,那么你可以正常提供(即不带protect-out),但是先慎重考虑!

如果您捕获了特权代码检查器(直接或间接通过 #%variable-reference),您还应该保护它,因为它可用于动态获取对不安全功能的访问。

目标是这样的:如果命名空间仅包含 属性 保护其不安全导出的模块,那么使用较弱的代码检查器评估的任何其他代码都不应违反 Racket VM 的不变量(例如使其崩溃、规避安全检查等)。