rubyでも偽装問題!?クラス偽装を見破る

こんにちは、寺岡です。

 

近頃、巷を騒がせている偽装問題。
それにちなんで、Rubyで見かける偽装問題を見破ってみたいと思います。

 

偽装、もといProxyパターン

Proxyパターンと呼ばれるデザインパターンがあります。
噛み砕いて説明すると「対象のオブジェクトっぽい動きをするけどちょっとだけアレンジされてるよ」
みたいなオブジェクトを作成するときに使われます。

Rubyでは動的言語としての特徴を活かして、method_missingを使った以下のようなコードで実装されます。

HexIntの親クラスになっているBasicObjectは、Ruby1.9から登場したObjectクラスの親のクラスです。
==やmethod_missinngなど、ObjectClassの基本的なメソッドのいくつかが定義されています。

BasicObjectを継承して、method_missingを継承することで、別のクラス(オブジェクト)になりすますためのクラスを作ることができるのです。

 

どんなときにハマるの?

Railsで特定の日時の1年後を表すDateTimeオブジェクトが欲しい場合、以下のコードで実現できます。

この1.yearという数は、後で変更されるかもしれないので、設定ファイルに切り出すことにしました。
こんな時によく使うのがSettingsLogicというGemで、設定ファイルをYAML形式で扱えます。

では切り出した設定値を使って、日付を計算してみましょう。

あれ?おかしな日時になってしまいました。うーむ…。

値も同じ、クラスも同じ、どうして結果が違うのでしょうか。
もう少し詳しく調べてみます。

1.yearだけ普通のFloatと結果が違うことが分かりました。

 

実体は

実はSettings.expired_afterと1.yearのクラスは同じではありません。
Settings.expired_afterはFloatですが、1.yearの実体はActiveSupport::Durationクラスのインスタンスなのです。
設定ファイルのYAMLにERBで記述した 「expired_after: <%= 1.year %>」 は「expired_after: 31557600.0」のように文字列として展開された後、
YAMLのパーサによりFloatに変換されるため、ActiveSupport::Durationクラスのインスタンスであったという情報は失われてしまいます。

ではなぜ、1.year.classがFloatになるのでしょうか。

これは、最初に紹介した BasicObject による Proxyパターンの実装に起因します。
先ほどのHexIntのインスタンスに.classメソッドを呼び出した場合も、結果はFixnumになります。
BasicObjectクラスにはclassメソッドが定義されていないため、method_missingへと処理が移譲されてしまうのです。

 

クラス偽装を見破る

BasicObjectを用いたProxyクラスによる偽装を見破るために、以下のようなメソッドを定義してみます。
Ruby1.8と1.9以降でコードが違うのでご注意ください。

 

○○クラスの筈なのにおかしな動きをするなぁ、などという時は、このメソッドを利用して本当にそのクラスかどうか調べてみると良いかもしれません。

Comments are closed, but you can leave a trackback: Trackback URL.