JSON::XS 4.0の変更点と、それにともなうJSON、JSON::PPの変更について

これはPerl Advent Calendar 2018 7日目の記事です。

tl;dr

先日リリースされたJSON::XS 4.0が2013年に行われたJSONの仕様変更に追随したので、decode_json($json) の結果はかならずしもリファレンスではなくなりました。encode_json($value) にもリファレンス以外の値を受け入れるようになっています。結果、JSONモジュールの挙動がバックエンドによって異なる状態になり、各地でテストがこけるなどの影響が出ました。これを受けてJSONJSON::PP側も変更に追随しましたが、みなさまの方でも適宜ご対応をお願いします。

2018年11月16日付けでJSON::XSのバージョン4.0が出ました。3.0が出たのが2013年10月29日のことですから、5年振りの大型アップデートということになります。今回の目玉は二点。いずれもこの数年界隈で何度となく話題に上がっていたものです。

allow_nonrefがデフォルトで有効に

配列やハッシュにくるまれていない素のスカラーを受け付けるallow_nonrefというオプションは2007年にJSON::XSがリリースされた当初から存在していたのですが、これはあくまでもRFC4627 (2006年)に対する拡張の扱いで、デフォルトでは無効になっていました。

その後、2013年にRFC7158が出てJSONの仕様が変わり、素のスカラーJSONの値として有効なものとなりますが、JSON::XSは(そしてJSONJSON::PPも)セキュリティ上の懸念があるとしてこの仕様変更には追随しませんでした。JSONJSON::PP側にも仕様変更に追随するよう問題提起されていましたが、もちろんJSON::XS側にも同じような要請はあったのでしょう。2016年にリリースされたJSON::XS 3.02では新旧の仕様に関してわざわざドキュメントに一節を設け、

JSON::XS will not allow scalar data in JSON texts by default

と態度表明をしていました。だから、私自身この変更を見たときにはかなり驚いたのですが、よく見てみると、その節の最後にはこのような留保も書いてありました。

future versions might/will likely upgrade to the newer RFC as default format, so you are advised to check your implementation and/or override the default with ->allow_nonref (0) to ensure that future versions are safe.

そして今回、この助言に従っていなかったモジュール、アプリケーションが仕様変更の影響を受けている、ということになります。

もっとも、問題になっているといっても、今回の変更はこれまでエラーとなっていたJSONやデータを受け付けるようになった、というもの。互換性のためにあえてそのようなデータ/JSONがエラーになることを確認するテストを書いていた場合、あるいはソケットなどを経由して垂れ流したJSONデータをインクリメンタルパーサーでデコードしていたという場合はともかくとして、一般的な用法の範囲内では、既存のデータ、既存のJSONが受け付けられなくなる、ということはありませんので、まずはご安心をば。

ただ、デコード時のエラーチェックが不足していたり、デコードしたデータがかならずリファレンスであると仮定しているようなコードでは、想定外の入力が入ってきて予期せぬエラーが発生することがありえるのでご注意ください。

既存の挙動を変えたくない場合は、encode_json/decode_jsonのようなラッパーをそのまま使うのはやめて、JSON::XS->new->utf8->allow_nonref(0)->encode/decodeのようにするか(もちろん可能ならJSON::XS->new->utf8->allow_nonref(0)の部分は何かの変数にキャッシュしておくべきでしょう)、いっそのことJSON::XS/JSON::PP 4.0に明示的に依存するようにして、テストもallow_nonrefが有効になっていることを前提にするのもありかと思います(4.0より前のJSON::XSはすでにCPANから削除されているため、明示的に指定しない限り、JSON::XSに依存しているモジュールがあれば現状ではJSON::XS 4.0がインストールされます)。

この変更によって、かれこれ三週間ほどJSON 2.97001のテストがこけ続け、CI環境や各種スモーカーを運用しているみなさまにはご迷惑をおかけしていたのですが、これを機にJSONおよびJSON::PPも同様にallow_nonrefを有効にした4.0をリリースしました。一部の界隈からはJSON::PPまで上げる必要はなかったのではないかという声も聞こえてきますが、そもそもJSON::PP側が待っていたのはJSON::XSが仕様変更に追随していなかったからですし、JSON::XSとJSONJSON::PPのメンテナ間ではなるべくお互いの互換性を維持しようという合意もできているので、この後に及んでJSON::PPだけあえて十年以上昔の古い仕様にこだわり続ける理由もないと思っています(この年末の忙しいときにこんなことはしたくなかったという思いはありますが、このタイミングを逃すと今度はPerl 5.30のコードフリーズが来るんですよね…)。

なお、JSON 4.0は、テストに使うJSON::backendPPモジュールがJSON::PP 4.0相当になっているだけで、実際にはJSON::XS 2〜4、JSON::PP 2.90〜4のいずれがバックエンドになっても動作します(Cpanel::JSON::XSも一部の非互換部分を除いては動作しますが、推奨しません。Cpanel::JSON::XSを使うのであれば、ラッパを使わず直接使うことをおすすめします)。

また、本項執筆時点ではCpanel::JSON::XSは今回の変更に追随していません。そのため、JSON::MaybeXSを利用して互換性のテストを行っているモジュールの中にはこの影響を受けてテストがこけているものが出ているかもしれません(メンテナ陣には問題が伝わっているので、この週末には必要に応じてなんらかの対策が取られるかもしれませんが、現時点でははっきりしたことはお伝えできません)。また、Cpanel::JSON::XSは後述するまったく別の理由で独自にバージョン4にしてしまったため、現状Cpanel::JSON::XSとJSONを共用している方は、この変更の有無をバージョンからは簡単に判別できない状態になっています(JSON::XSとJSON::PPはメジャーバージョンと主要な機能の対応が取れているので、JSON->backend->VERSION >= 4で判別できるのですが、JSONのバックエンドがCpanel::JSON::XSになるとこの条件では正しく判定できません)。Cpanel::JSON::XSをご利用の方はご注意ください。

boolean_values

JSON::XS 4.0では新たにboolean_valuesというメソッドが導入されました。これはJSONをデコードしたときにtrue/falseの値を任意のオブジェクトに変更できる、というものです。

これは、JSON単体で使っている分には特に意味のない機能ですが、JSONでデコードしたデータをたとえば何か別のシリアライザに引き渡したいとき、このboolean_valuesであらかじめbooleanやData::MessagePack::Booleanのオブジェクトを渡しておくと、データ構造の中にtrue/falseがあったらJSON::PP::Booleanのオブジェクトではなく、該当のオブジェクトに置き換わるので、YAMLやMessagePackでそのままシリアライズしやすくなる、と。

もっとも、この機能が有効なのはデコードのときのみなので、booleanのようにJSON::PP::Booleanとの互換性がないモジュールの場合、該当のデータ構造をJSONに戻す場合はconvert_blessedなどのお世話になることになります。JSON::PP::Booleanと互換性のあるTypes::Serialiser(::BooleanBase)やData::Boolのオブジェクトの場合はデコードで取り出したデータをそのままエンコードに回せます。

共通のブーリアンオブジェクトは欲しいけど、JSONじゃないものにまでJSON::PP::Booleanを使うのはなあ、という方はぜひ一度お試しください。

PERL_JSON_PP_USE_B環境変数

以前、YAPC::Hokkaidoの前夜祭でJSON::PPの数値判定処理を変更する実験的なパッチを受け入れたという話をしました。このパッチはPerl 5.27.1で一時的に(Perl側の変更のせいで)動作しなくなるなど、いささか微妙な性質のものなのですが、この変更によってJSONの出力が完全に一致するか確認するテストが壊れてしまうという苦情が届いていました。だいぶ今更ではあるのですが、状況を改善するため、JSON::PP 4.0ではPERL_JSON_PP_USE_B環境変数が真の場合は旧来のBモジュールを使った数値判定を行うようにしてあります。

Cpanel::JSON::XS由来の警告を無効に

Cpanel::JSON::XSが読み込まれた状態でJSON::PP(::Boolean)を読み込むと長らく警告が出るようになっていた(Cpanel::JSON::XS側はその警告を殺してJSON::PP::Booleanの挙動を差し替えていた)のですが、いい加減うっとおしくなっていたのでJSON::PP側でも警告を殺すようにしました。ロード順でJSON::PP::Booleanの挙動が変わるのはあまりうれしいことではないのですが、ふつうに真偽値としてのみ使ってくださる分には影響ないはずです。

番外

同じバージョン4.0つながりということでもうひとつ。今回JSONのテストを修正していてハマったことのひとつに、Cpanel::JSON::XS 3.99_01で導入されたCpanel::JSON::XS::TypeによってCpanel::JSON::XSのencode_jsonのプロトタイプが変わり、以下のようなテストが死ぬようになったことがあげられます。

ok ('[5]' eq encode_json $j1, "cjson1");

encode_json($j1)のように明示的にかっこでくくればよいだけの話ではあるのですが、ふだんからプロトタイプを利用してencode_jsonのかっこを省略している方は頭の片隅に入れておいていただければ。


というわけで、いろいろ大変なJSONまわりですが、今回4.0がらみで変わったところをまとめておきました。APIサーバなどでいずれかのモジュールをご利用の方も多いと思いますので、みなさまの方でも必要に応じて適宜ご対応いただければさいわいです。



明日は yukikimoto さんです。

London Perl Workshop 2017に行ってきた

tl;dr: It was really awesome (again)! A big thank you to the organisers and the speakers and everyone.

遅ればせながら、2017年11月25日にロンドンで開催されたLondon Perl Workshopに行ってきました。他の方の資料やビデオもそのうち公開されていくとおもいますが、とりあえず以下にLTの発表資料を置いておきます。2016年のヤパチーで話したPerl::PrereqScanner::NotQuiteLiteが大分いい感じになったので、そのうちCPANTSのチート対策します、という話(なぜかNotQuiteLiteという名前が想定外にウケたらしく、話している途中で何かやらかしたかとずいぶん戸惑ったものでした)。

NQLについては、発表駆動開発で当時できていなかったことの大半はできるようにしましたが、まだだいぶ遅いですし(一部の例外的なモジュールの解析にはPerl::PrereqScannerの倍以上かかったりする)、もう少し自由度を上げられるようにしないと自分自身が困るので、ドキュメントの更新ともども、時間ができたらなんとかしたいとおもっています。

おまけとしてTest::CPANfileというのもでっち上げました。テストするより都度更新した方が便利な場合も多いとはおもいますが、いろいろな理由でMinillaなどに移行できていない方には役に立つかもしれません。

WEB+DB PRESS Vol.100

8月24日発売のWEB+DB PRESS Vol.100に、Perl Hackers Hub連載の第46回として「Perl 5.26で変わること」という記事を書きました。タイトルからもおわかりの通り私のはこれまでYAPC::Hokkaido、YAPC::Kansai、YAPC::Fukuokaで話してきたことの焼き直しですが、100号記念ということで力の入った記事が多いのでぜひお手にとってみてください。

WEB+DB PRESS Vol.100

WEB+DB PRESS Vol.100

YAPC::Fukuokaに行ってきた

遅ればせながら、YAPC::Fukuokaに参加してきました。発表資料は以下の通りです。今回はPerl 5.26がリリースされたこともあって、前回、前々回の分から5.24の内容を削り、その分5.26のやや細かい話や、開発が始まったばかりの5.27系の話を追加しました。調べ物の過程でふたつほど5.26.0のバグを踏んだりもしましたが、いずれも次のリリース時には直る見込みです。準備の時間が足りず掘り下げきれなかった部分もありますが、その辺はまたいずれ何かの機会に。

JPAはじめスタッフ、スポンサーのみなさま、ありがとうございました。Okinawaも楽しみにしています。

'.' in @INC問題とその対処法について(2017年3月版)

YAPC::HokkaidoやYAPC::Kansaiで話した通り、Perl 5.26ではセキュリティ上の問題で@INCにカレントディレクトリが含まれなくなります。p5pやツールチェーン側ではその影響を軽減すべくCPANクライアントやTest::Harnessに従来の挙動を残すような仕組みを用意中ですが、現時点ではまだすべての対策が出そろっているわけではありません。最終的にどうすべきかは5月に開催される予定のPerl Toolchain Summit (旧Perl QA Hackathon)後にあらためてまとめるつもりですが、直接的な影響を受けるCPAN Authorのところには順次バグレポートが届いているかと思いますので、可能であれば以下の対応をご検討ください。

incディレクトリなどにMakefile.PL/Build.PL用の特殊なモジュールなどを同梱している場合

カレントディレクトリが@INCから消えることで、開発時および手動インストール時にはinc::Module::Install、inc::MyBuilderなどの同梱モジュールが正しく読み込めなくなる可能性があります。この辺は別途パッチがあたる可能性もありますが、基本的にはMakefile.PL/Build.PLなどの先頭付近に適宜以下のような行を追加しておいていただけると面倒がなくてよいかと思います(通常のインストール時にはCPANクライアント側で対策が取られるので問題にはならないはずです。また、どうせ開発するのは自分だけだから、perl -I. Makefile.PL/Build.PLするよ、という方もひとまず気にしなくてかまわないかと思います。ただ、CIがこけたりCPAN Testersなどから指摘を受ける可能性はあります)。なお、Makefile.PL/Build.PL については use lib "."; でもよいはずですが、今回の「対策」と呼ばれるものは "." を狙い撃ちにしているので、場合によってはおかしな副作用に巻き込まれる可能性があります。

use FindBin;
use lib $FindBin::Bin;

また、Module::Installについては、Perl本体あるいはツールチェーン側で何らかの特別対応が行われる可能性もあるのですが、最新のModule::Installにも警告が出ている通り、これまでにもいろいろと問題が出ていますので、特に最新版をお使いでない方は、可能であればこの機会にMinilla、Dist::Zilla等のオーサリングツールに移行しておいていただけると助かります。

テストファイル中で t::Foo のようなモジュールを呼んでいる場合

これも、面倒でなければ、上と同じく各テストファイル中でFindBinを呼んで、実際の階層に応じたlib設定をしておいてください。

また、修正すべきファイルが多すぎて大変な場合は、依存モジュールにTest::Harnessの3.38以降を加えておけば、とりあえずmake test時には@INCにカレントディレクトリが戻るようになります。

これも同じくどうせ自分だけだから…という考え方もありですが、Makefile.PL/Build.PLの場合と同様におそらくCIでこけたり、CPAN Testersから指摘を受けることになるかと思います。

do "localfile.pl" のような呼び出しをしている場合

CPANモジュールには少ないと思いますが、お手元のアプリやスクリプトで環境設定などのために do を使ったファイル読み込みをしている場合は do "./localfile.pl"; のように明示的にディレクトリを追加しておいてください(もちろんFindBinを利用してもかまいません)。

また、require "jcode.pl"; のような行も、あれば同様の修正をお願いします(もっとも、jcode.plについてはそのままだとそもそも5.26では読み込み時にエラーになりますが)。

スクリプトをインストールするディストリビューションの場合

インストール時に bin/foo(.pl) のようなスクリプトをインストールするディストリビューションについては、追加の対策をしておいた方がよいかもしれません。コアに入っているものについてはいまのところスクリプトの先頭付近に以下のような行が追加されていますが、これについてはno lib "."; の方がよいという異論もあります(no lib "." なら明示的な perl -I. も封じることができますが、-I オプションを使える状況ならどちらにしても迂回できるので現状ではそれほど違いを気にする必要はないかと思います)。

BEGIN { pop @INC if $INC[-1] eq '.' }

なお、この行は基本的にはスクリプトやアプリの先頭付近に一度追加すれば十分です。

やってはいけないこと

use lib "."; は探索パスの「先頭」にカレントディレクトリおよび関連ディレクトリを追加してしまうので、うかつに使うとかえって発端となった脆弱性の影響を受けやすくなってしまいます。私的なスクリプトなどの応急処置には便利ですが、一般に公開するモジュール、アプリなどでは使用を避けてください。シェルスクリプト/バッチファイルなどから呼び出すperlコマンドラインオプションに「-I.」を追加するのも同様の理由で危険です。

なお、CPANモジュールを使うだけの方は、5月のPerl Toolchain Summit以降にCPANクライアント(とTest::Harnessあたり)を最新にしておいていただければ、ほぼ問題なく5.26に移行できる(ようになる)見込みです。

YAPC::Kansai 2017に行ってきた

だいぶ遅ればせながらYAPC::Hokkaidoに引き続きYAPC::Kansaiも前夜祭から参加してきました。前夜祭についてはあいにく資料はありません。msysもcygwinも(ものによってはgnuwinのツール群も)入っていることとか、変換キーが「\」(カナ入力時には「ー」)、無変換キーがカナのオンオフ、半角/全角キーがエスケープになっていることとか、VMだけでなくWindows環境にもいちおうrakudoもgolangもnodejsもrubypythonも(もちろん複数バージョンのVisual Studioも)入っていることとか、話すつもりでいたことをもろもろ話し忘れたような気がしますが、誰得情報ということでご勘弁ください。ダイヤモンドカーソル依存やタッチパッドを無効にしている件も含めて、まっさらなノートPCを渡されると場合によってはものすごく困るクラスタに属しています。

本編の資料は以下の通りです。Perl 5、Perl 6ともに新しい安定版のリリースがあったわけではないのでHokkaidoからの引き写しも多いのですが、連続参加の方にも多少なりと新しい情報をお届けできるよう、Hokkaido以降に目にしたコメントなどへのレスを追加したり、2月にブリュッセルで開催されたFOSDEM 2017で仕入れてきた情報を追加したりしておきました。@INC問題についてはいよいよみなさんのところにも行動を求めるバグレポートが届き始めたようなので、別途記事を書いておきます。

何はともあれ、JPAはじめスタッフ、スポンサーのみなさま、ありがとうございました。Fukuokaも楽しみにしています。

YAPC::Hokkaido 2016に行ってきた

London Perl Workshop 2016に続いてYAPC::Hokkaidoにも前夜祭から参加してきました。資料は以下の通りです。前夜祭では今年の夏にまかまかさんからメンテを引き継いだJSON(::PP)の現状、土曜日の本編では昨年までならPerl5やPerl6の重鎮が発表していたであろうPerl5/6の現状をウォッチャー目線でざっくりまとめておきました。夏のLigthweight Language of Thingsでは日本語でスライドを書きましたが、今回は海外からのスピーカーがいらしたということでスライドはいつも通り英語になっています。

あと、syohexさんの発表にあったメンテをやめたくなったときの話ですが、CPAN/PAUSEではLancaster Consensusで合意されたADOPTME、HANDOFF、NEEDHELPといったアカウントにパーミッションを与えておくと、作者/プライマリメンテナの意志を機械的に判別可能な形で明示できます。現状ではRTくらいでしか一覧できないのですが、Neil Bowers氏のサイトでは最終更新日やバグの多さなどの観点も含めて誰かが引き継ぎを行った方がよさそうなモジュールも一覧になっています。余力がある方は自分が利用しているモジュールや興味のあるモジュールが挙がっていないかご確認いただければありがたく(DBD::SQLiteがリストに挙がっている件はこちらで把握しています)。

なにはともあれ、29年ぶりという大雪のみならず、いろいろな点で感慨深いYAPCでした。JPAはじめスタッフ、スポンサーのみなさまありがとうございました。