ScalaJSからjavascriptのmsgpack呼び出すことによりScalaコードのバグ見つけたりByte表現の違いについて学んだ話(msgpackのjavascriptのライブラリはバグってなかったごめんなさい)

昨日の続編


ScalaJSからpure javascriptなライブラリを呼び出す(あるいはmsgpackのjs実装のバグを見つけたかもしれない話)

togetterまとめ


とにかく「msgpackのjs実装のバグ」は勘違いでした、すみません。
見つかったscodec-msgpackのバグについて簡単に説明しておきます。


まず、msgpackのInteger型は何種類ありますか?10種類ありますね?
10種類全部解説するのは面倒なので、以下の仕様を見てもらうとして

https://github.com/msgpack/msgpack/blob/0b8f5ac67cd/spec.md#int-format-family

今回の関連も含めて、大雑把な解説をすれば、
たとえばその中には

  • 32bit符号付き
  • 64bit符号付き

の2種類があります。1や0、あるいはJVMのInt.MaxValue(2147483647)やInt.MinValue(-2147483648)は32bitの範囲に収まります。
しかし、それら32bitにおさまるものを、msgpack上の64bit符号付きのものに入れることも勿論可能です。

msgpackの仕様上

「32bitに収まるならそちらに入れなければならない(もしくは入れることを推奨する」なんていうものはありません。しかし、大抵の実装は32bitに入るならそちらに入れたほうがシリアライズ後のサイズが小さくなるので、そちらにシリアライズします。嘘つきました(コメントを参照)

問題が起きやすいのはデシリアライズ時です。
シリアライズ時に「32bitにおさまるならそちらに入れる」決まりにしておいて、デシリアライズのテストにそのシリアライズするコードを使うと
「64bit符号付きをJVMのIntとしてデコードする(32bitに収まる値が入っていたら成功、収まらない値なら失敗)」
みたいなテストがちゃんと書けません。
あえてテストのためだけに
「32bitにおさまるけど64bitにシリアライズ
という多少面倒な作業が必要になります。*1
msgpackある程度知っている人なら普通な罠だろうし、msgpackの数値表現理解していない人にとっては(自分の説明雑なので?)なに言ってるかわからないかもしれません。


で、結論としては、scodec-msgpackが見事にそのパターンのバグでした。なぜそれが今回発覚したのか?というと、
「msgpack-javascriptが何故か32bitに収まる範囲なのに64bitにシリアライズする場合がある」
からです。なぜそうしてるのかの理由は知りません。

このscodec-msgpackのバグは自分がもみあげさんが直します。


さて、あともう一つは、単にByte表現の違いの部分を(最初ある意味気がついていたにもかかわらず)見逃していただけでした。togetterにまとめた中のりりろじさんの発言で十分な気がするのでそちらを読んでください。


昨日は途中までやってすぐに解決しそうにないから中途半端な状態でblog書いたけど、結果から言うとべつにすごく難しいバグでもなかったので、もうすこし頑張って全部解決した状態でblog書いても良かったですね、今後頑張ります・・・

*1:scodec-msgpackではない自分のmsgpack4zのほうではそういうテスト書いてます https://github.com/msgpack4z/msgpack4z-core/blob/v0.3.1/src/test/scala/msgpack4z/IntSpec.scala