kenjuの日記

About Programming, Mathematics and Security

エンコードが必要な文字の時YAML.dumpはvalueをクオートでラップして出力する

tl;dr

  • Rubyの現在のYAMLモジュールの実装の詳細は、cのYAML ParserライブラリlibyamlのブリッジであるPsych
  • YAML.dumpしたときに、valueがシングルクオートでラップされることがあるのは、エンコードが必要な文字を出力するとき(例:0x80
  • 意図しない出力結果になったときは、仕様なのか、実装依存の問題なのか、ライブラリのバグなのか、を切り分けて考えるべき

背景

YAML.dumpしたとき、その値について

  • クオートなしの文字列
  • シングルクオートつきの文字列
  • ダブルクオートつきの文字列の3パターンが出力される。

このクオートのつくパターンが少し複雑で、↓を見てもらうとわかる通り、「エンコードが必要な文字」が含まれる場合、クオートでラップされることがわかる。

require 'yaml'

p YAML.dump({ value: 'foo' })
p YAML.dump({ value: "foo" })

p YAML.dump({ value: '"foo"' })
p YAML.dump({ value: "'foo'" })

出力結果:

"---\n:value: foo\n"
"---\n:value: foo\n"
"---\n:value: '\"foo\"'\n"
"---\n:value: \"'foo'\"\n"

これは例えば、文字列に0x80のような文字列が含まれる場合にも、シングルクオートにラップされたvalueが出力される。 (ちなみに、0x80は10進数では128)。

0xFF以降、例えば0xGG0xFOはinvalidな16進数なので、それらの場合にはクオートにラップされずに出力される。

require 'yaml'

p YAML.dump({ value: '0x80' })
p YAML.dump({ value: '0xFF' })

p YAML.dump({ value: '0xGG' })
p YAML.dump({ value: '0xFOO' })

出力結果

"---\n:value: '0x80'\n"
"---\n:value: '0xFF'\n"
"---\n:value: 0xGG\n"
"---\n:value: 0xFOO\n"

github.com

今回、この仕様を知らずに、シングルクオートのあり・なしに依存したテストを書いてしまっていた。 出力結果がランダムに0x80***など、クオートでラップされる可能性のある値だったが故に、非決定的にテストがこける、という問題にあたった。

原因の切り分け方について

RubyのYAMLモジュールは、現在Psychがその実装の詳細を担っている。

github.com

そして、Psychは、cのYAML Parserライブラリであるlibyamlのブリッジにすぎない。

github.com

今回のように、意図しないYAML.dumpの挙動を見た場合、それが

  • YAMLの仕様に準拠した正しい挙動なのか
  • libyamlの実装依存の問題なのか
  • Psychの実装依存の問題なのか

を切り分けて考える必要がある。

例:

Bug #11988: YAML.dump doesn't quote string starting with 0 which will be recognized as float in YAML 1.2 - Ruby trunk - Ruby Issue Tracking System

補足

最新のYAMLの仕様については、以下を参照してください:

YAML Ain’t Markup Language (YAML™) Version 1.2