この記事は Ruby Advent Calendar 2021 の6日目の記事です。
Tebiki 社の渋谷です。今回は「Ruby は各オブジェクトに対して一意な整数(object identifier)が割り当てられているけど、JavaScript にはそういう値がないのはなんでなんだろう?」というのに思いを巡らせてみた話です。
Ruby の object identifier は以前アドレス値だった
Ruby ではオブジェクトを一意で特定できる object identifier を object_id というメソッドで取得できます。言語を利用する側としてはそんなに使う機会はないのですが、Rails 本体でもテストのときにオブジェクトの同一性を検証するために使われていたりします。 ※1
で、object_id を生成している具体的な実装を見てみると、Ruby 3.0.3の場合はこんな感じ、Ruby 2.6.9 の場合はこんな感じになっていました。
2.6.9 のコードだと object_id はアドレスを整数値化したものとなっていましたが、3.0.3 のコードでは独自で数字を振るようになっています。
これは 2.7 で GC.compact の対応(メモリのデフラグ的なやつ)を入れるために、デフラグしてアドレスが変わっても object_id が変わらないようにしたからです。偉業。
というわけで object_id の挙動をちょっと試してみました。
Ruby 2.6.9 の場合はこんな感じ。
次に、Ruby 3.0.3 で試すとこうなります。
というわけで現在の object_id はアドレスを利用したものではない生成方法になっていることが確認できます。 ※2
ちなみに Python でも id() というメソッドで object identifier が取得できて、そこで取得できる値はどうやらアドレスから生成しているようです。
JavaScript では object identifier はとれない
一方 JavaScript では object identifier はとれません。もし必要であれば Map を駆使したりして自分で採番する必要があります。
JavaScript でも変数に対する参照の概念は当然あります。ですがポインタのアドレスは見れませんし、オブジェクトの同一性を判断できるような値も取得できません。
WebKit での実装を調べてみたところ、==を使った同一性の検証の場合はアドレスを用いているようです。 ※3
ここで気になるのは同一性の検証はアドレスで行っているというところまでは同じなのに、なぜ JavaScript は object identifier が取れないのでしょう?
Ruby では object identifier があってうれしい場面もけっこうあるので、取得できてもいいように思いますよね。
気になったのでググってみると、メモリアドレスが見えてしまうことは脆弱性につながるからという言説を見つけました。
確かにJavaScript はブラウザ、つまりクライアント側で実行されるコードなので悪意のあるコードが実行されやすい環境にあります。
そういったコードに、アクセスしてはならないメモリへの手がかりを与えたくない。つまりセキュリティが要因で見せないようにしている、ということのようです。
メモリ関連の脆弱性でいうと Spectre に代表される投機的実行のサイドチャネル攻撃 ※4 が有名ですし、Chrome も対応を入れていたりします。
ただ、Spectre はつい最近の話だし、そこまで考えて実装したのだろうかという疑問が残ります。
DOM に id があるから不要だった説
ここで重要なのが、JavaScript という言語が Netscape Navigator というブラウザを起源としているということです。
どういうことかというと、初期のブラウザにとって同一性が重要だったのは DOM こそで、その DOM は id で特定できたし object identifier の必要がそもそもなかった。
つまり JavaScript は添え物で主体は DOM だった、という説をブチ上げたいと思います。
ECMAScript の初版では === がなかったというのはこの説にとって一つの有力な証拠ではないでしょうか。言語レベルで同一性そのものを重視していなかった可能性は結構あるんじゃないのかなと。全然違ってたらすみません…。
まとめ?
一通り調べてみた感想ですが、Ruby の object identifier をユーザーに提供するためにアドレスを利用するアイデアはとても自然な考え方だなぁというのと、言語というのは使うユースケースで仕様が変わっていくんだろうなぁと思いました(なので全ての言語は適材適所だよね的な)。
なお、すべて個人の推測ですので、答えが分かる方はぜひお教えください!
※1)object_id 以外でも同一性の判定は可能です
https://docs.ruby-lang.org/en/3.0.0/BasicObject.html#method-i-3D-3D
https://docs.ruby-lang.org/en/3.0.0/BasicObject.html#method-i-__id__
※2)1999年の時点ですでに object_id はあったっぽくて、それ以上は分かりませんでした
https://github.com/ruby/ruby/blob/ruby_1_3/ChangeLog#L1906
※3)JavaScript の規格である ECMAScript の仕様は ECMA-262 という規格番号で標準化されており、何を同一とみなすかも定義されています
https://tc39.github.io/ecma262/#sec-samevalue
※4)JavaScript を利用したサイドチャネル攻撃の論文の例です
http://www.misc0110.net/files/jszero.pdf
https://arxiv.org/pdf/2103.04952v1.pdf