Ruby 能保证 eval(str.inspect) == str 吗?

Does Ruby guarantee that eval(str.inspect) == str?

提问人:Ilmari Karonen 提问时间:3/28/2023 更新时间:3/31/2023 访问量:84

问:

我最近发现自己需要根据用户输入生成一个简单的 Ruby 脚本,其中一些脚本需要作为字符串文字包含在脚本中。虽然在我的特定情况下,此输入来自受信任的来源,但我仍然希望以一种即使输入字符串恰好包含引号、反斜杠、换行符、哈希标记或其他意外元字符也不会中断的方式执行此操作。

显而易见的解决方案(如前面问题的公认答案中所建议的那样)是使用 String#inspect 方法,其文档指出:

返回 str 的可打印版本,用引号括起来,特殊字符转义。

然而,该文档没有明确说明评估 Ruby 代码的输出将返回原始字符串。而且,事实上,我确实在技术上设法提出了一个使用非Unicode字符串的反例:String#inspect

pry(main)> str = 0x80.chr; eval(str.inspect) == str
=> false

但是,我需要编码的所有字符串都是 Unicode 字符串,因此这个反例对我来说只是理论上的兴趣。但我仍然想要一些记录在案的保证,因此存在以下问题:

  1. 如果是 Unicode 字符串,则保证等于 吗?eval(str.inspect)strstr
  2. 如果没有,是否还有其他方法可以在生成的 Ruby 代码中转义字符串文字,并保证始终有效?

另外,一个额外的问题:

  1. 总是保证相等吗?eval("'" + str.gsub(/[\\']/, { "\\" => "\\\\", "'" => "\\'" }) + "'")str
Ruby 字符串 转义 eval

评论


答:

0赞 Ilmari Karonen 3/28/2023 #1

让我试着总结一下我到目前为止的调查结果(包括马克斯现在删除的答案,它向我介绍了):String#dump


String#inspect 的文档不保证其输出会生成原始字符串。但是,至少从 Ruby 3.0.2 开始,String#dump 的文档确实做出了这样的保证:eval

此方法可用于往返:如果结果被求值,它将生成原始字符串。new_str

因此,我的问题 #1 和 #2 的答案似乎是:

  1. 不,不能保证 Ruby 文档是等同的(尽管在实践中它似乎确实有效;见下文)。eval(str.inspect)str

  2. OTOH,记录为始终相等。eval(str.dump)str


当然,虽然拥有文档是件好事,但确保实际行为与文档内容相匹配也是一个好主意。

根据我的测试,从经验上讲,至少在相对现代的 Ruby 版本上,两者似乎都产生了输出,当 ed 时,等于原始 (Unicode) 字符串。String#inspectString#dumpeval

具体来说,使用以下测试字符串(我相信它包含所有当前分配的非代理项 Unicode 字符,以及一些额外的潜在问题字符对和序列),

unicode_points = (0..0xD7FF).to_a + (0xE000..0xE007F).to_a
str = unicode_points.map { |i| i.chr(Encoding::UTF_8) }.join("")
str += "\#{foo} \\\\ \\\' \\\" \r\n\t"

似乎在 CRuby 2.6.10 和 3.3.0dev 以及 JRuby 9.3.10.0 上都评估为 true(这是我碰巧安装并方便使用的)。eval(str.inspect) == streval(str.dump) == str


然而,我的奖金问题 #3 中的方法并不完全有效;有问题的字符序列是(即 ASCII CR+LF),即使在单引号字符串中,它显然也会折叠成一个 LF。具体来说,事实证明(!gsub"\r\n"eval("'\r\n'") == "\n"

(我发现这一点是基于我在使用包含所有 Unicode 字符的字符串进行测试时收到的警告。这让我怀疑换行符可能存在一些有趣的解析,所以我添加到我的测试字符串中并得到了不匹配。warning: encountered \r in middle of line, treated as a mere space"\r\n"


另外,在测试时,我碰巧注意到上面的测试字符串无法正确往返。一个演示相同问题的更简单的测试用例是 ,为此会引发 。String#dumpString#undumpstr = "\u0001\uABCD"str.dump.undumpRuntimeError: hex escape and Unicode escape are mixed

显然,问题在于将 ASCII C0 控制代码中的字符编码为形式的十六进制转义码,但形式中高于 U+007F 的非 ASCII Unicode 字符(或用于 BMP 之外的字符),由于某种原因不喜欢。虽然这不是 的问题,但 似乎很乐意接受 的输出,它可能仍然算作一个错误。我现在已将其报告为 https://bugs.ruby-lang.org/issues/19558String#dump\xNN\uNNNN\u{NNNNN}String#undumpeval()String#dump