Ruby是否保证eval(str.inspect)== str?

oknrviil  于 2023-04-05  发布在  Ruby
关注(0)|答案(1)|浏览(102)

我最近发现自己需要根据用户输入生成一个简单的Ruby脚本,其中一些需要作为字符串文字包含在脚本中。虽然在我的特定情况下,输入来自可信的来源,但我仍然希望以一种即使输入字符串碰巧包含引号,反斜杠,换行符,哈希标记或其他意外的元字符也不会中断的方式来完成这一工作。
显而易见的解决方案(如this earlier question的公认答案中所推荐的)是使用String#inspect方法,其文档称:
返回 str 的可打印版本,用引号括起来,特殊字符被转义。
然而,文档中并没有明确说明将String#inspect的输出作为Ruby代码进行求值将返回原始字符串。事实上,我确实在技术上设法使用非Unicode字符串提出了一个反例:

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

然而,我需要编码的所有字符串都是Unicode字符串,所以这个反例对我来说只是理论上的兴趣。但我仍然希望得到一些文档化的保证,因此有以下问题:
1.如果str是Unicode字符串,那么eval(str.inspect)是否保证等于str
1.如果没有,是否有其他方法可以在生成的Ruby代码中转义字符串文字,并保证始终有效?
还有一个问题:

  1. eval("'" + str.gsub(/[\\']/, { "\\" => "\\\\", "'" => "\\'" }) + "'")是否总是保证等于str
0sgqnhkj

0sgqnhkj1#

让我试着总结一下目前为止的调查结果(包括Max的现已删除的answer,它将我介绍给了String#dump):
String#inspect的文档并不保证eval的输出会产生原始字符串。但是,至少从Ruby 3.0.2开始,String#dump的文档 * 确实 * 保证了这一点:
此方法可用于往返:如果结果new_str被eval'艾德,它将产生原始字符串。
因此,我的问题#1和#2的答案似乎是:
1.不,Ruby文档并不保证eval(str.inspect)等于str(尽管在实践中它似乎确实有效;见下文)。

  1. OTOH,eval(str.dump) * 被记录为始终等于str
    当然,虽然有文档很好,但确保实际行为与文档中的行为相匹配也是一个好主意。
    根据我的测试,至少在相对现代的Ruby版本上,* 凭经验 *,String#inspectString#dumpseem 产生的输出在eval编辑时等于原始(Unicode)字符串。
    具体来说,使用下面的测试字符串(我相信它包含了所有当前分配的非代理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"

看起来eval(str.inspect) == streval(str.dump) == str在CRuby 2.6.10和3.3.0dev以及JRuby www.example.com上的计算结果都为true9.3.10.0(我碰巧安装了这些工具,并且可以方便地使用它们)。
然而,我的附加问题#3中的gsub方法并不完全有效;有问题的字符序列是"\r\n"(即ASCII CR+LF),即使在单引号字符串中,它也明显地被折叠成单个LF。具体地说,结果是eval("'\r\n'") == "\n"(!)。
(我发现这一点是基于一个警告:在使用包含所有Unicode字符的字符串进行测试时,我得到了一个警告:warning: encountered \r in middle of line, treated as a mere space。这让我怀疑可能有一些有趣的分析发生在换行符上,所以我将"\r\n"添加到我的测试字符串中,并得到了一个不匹配的结果。)
另外,在测试String#dump时,我碰巧注意到上面的测试字符串无法正确地与String#undump进行往返。一个更简单的测试用例演示了同样的问题,例如str = "\u0001\uABCD",其中str.dump.undump引发RuntimeError: hex escape and Unicode escape are mixed
显然,问题在于String#dump将ASCII C0 control codes中的字符编码为\x*NN*形式的十六进制转义码,但将U+007 F以上的非ASCII Unicode字符编码为\u*NNNN*形式(或\u{*NNNNN*}用于BMP以外的字符),由于某种原因String#undump不喜欢。虽然这不是eval()的问题,它似乎很高兴地接受了String#dump的输出,它可能仍然算作一个bug。我现在已经将其报告为https://bugs.ruby-lang.org/issues/19558

相关问题