4

Psych でデシリアライズして、クラス オブジェクトなどの既存のオブジェクトを返すにはどうすればよいですか?

クラスのシリアル化を行うには、次のことができます

require "psych"

class Class
  yaml_tag 'class'
  def encode_with coder
    coder.represent_scalar 'class', name
  end
end

yaml_string = Psych.dump(String) # => "--- !<class> String\n...\n" 

しかし、それを実行しようとするとPsych.load、String クラスではなく匿名クラスが取得されます。

通常の逆シリアル化方法は ですがObject#init_with(coder)、それは既存の匿名クラスの状態を変更するだけですが、私は String クラスが必要です。

Psych::Visitors::ToRuby#visit_Psych_Nodes_Scalar(o)で既存のオブジェクトを変更するのではなくinit_with、正しいオブジェクトが最初に作成されるようにする場合があります (たとえば、Complex(o.value)複素数を逆シリアル化するための呼び出し)。

低レベルまたは中レベルの放射で作業する運命にあるのでしょうか、それとも何か不足していますか?

バックグラウンド

プロジェクトについて、なぜクラスが必要なのか、なぜ (デ) シリアル化が必要なのかを説明します。

計画

Small Eigen Collider は、Ruby が実行するランダムなタスクを作成することを目的としています。最初の目的は、Ruby のさまざまな実装 (たとえば、Rubinius と JRuby) が同じランダムなタスクを与えられたときに同じ結果を返すかどうかを確認することでしたが、Rubinius と YARV をセグメンテーション違反する方法を検出するのにも適していることがわかりました。

各タスクは、次の要素で構成されています。

receiver.send(method_name, *parameters, &block)

receiverランダムに選択されたオブジェクト、method_nameはランダムに選択されたメソッドの名前、 は*parametersランダムに選択されたオブジェクトの配列です。&blockはあまりランダムではありません。基本的には と同等{|o| o.inspect}です。

たとえば、レシーバーが "a"、method_name が :casecmp、パラメーターが ["b"] の場合、呼び出します。

"a".send(:casecmp, "b") {|x| x.inspect}

これは(ブロックは無関係であるため)と同等です

"a".casecmp("b")

Small Eigen Collider はこのコードを実行し、これらの入力と戻り値をログに記録します。この例では、Ruby のほとんどの実装は -1 を返しますが、ある段階で Rubinius は +1 を返しました。(私はこれをバグhttps://github.com/evanphx/rubinius/issues/518として提出し、Rubinius のメンテナーがバグを修正しました)

クラスが必要な理由

Small Eigen Collider でクラス オブジェクトを使用できるようにしたいと考えています。通常、これらはレシーバーですが、パラメーターの 1 つになることもあります。

たとえば、YARV をセグメンテーション違反にする方法の 1 つは、次のようにすることです。

Thread.kill(nil)

この場合、レシーバーはクラス オブジェクト Thread であり、パラメーターは [nil] です。(バグレポート: http://redmine.ruby-lang.org/issues/show/4367 )

(デ)シリアライズが必要な理由

Small Eigen Collider は、いくつかの理由でシリアル化が必要です。

1 つは、乱数ジェネレーターを使用して一連のランダムなタスクを毎回生成するのは実用的ではないということです。JRuby には異なる乱数ジェネレーターが組み込まれているため、同じ PRNG シードが与えられたとしても、YARV に異なるタスクを与えます。代わりに、ランダムなタスクのリストを 1 回作成し (ruby bin/small_eigen_collider の最初の実行)、最初の実行でタスクのリストを tasks.yml にシリアル化し、その後のプログラムの実行 (別の方法を使用) を行います。 Ruby 実装) は、その tasks.yml ファイルを読み込んで、タスクのリストを取得します。

シリアル化が必要なもう 1 つの理由は、タスクのリストを編集できるようにしたいからです。セグメンテーション違反につながるタスクの長いリストがある場合、セグメンテーション違反を引き起こすのに必要な最小限のリストに減らしたいと考えています。たとえば、次のバグ でhttps://github.com/evanphx/rubinius/issues/643

ObjectSpace.undefine_finalizer(:symbol)

それ自体ではセグメンテーション違反は発生しません。

Symbol.all_symbols.inspect

しかし、2つを組み合わせると、そうなりました。しかし、私は何千ものタスクから始めたので、それらの 2 つのタスクだけに戻す必要がありました。

このコンテキストでは、既存のクラス オブジェクトを返す逆シリアル化は理にかなっていますか、それとももっと良い方法があると思いますか?

4

2 に答える 2

1

私の現在の研究の現状:

目的の動作を機能させるには、上記の回避策を使用できます。

ここに適切にフォーマットされたコード例があります:

string_yaml  = Psych.dump(Marshal.dump(String))
  # => "--- ! \"\\x04\\bc\\vString\"\n"
string_class = Marshal.load(Psych.load(string_yaml))
  # => String

実際のクラス処理はpsych/yamlに実装されていないため、クラスを変更するハックはおそらく機能しません。

スタンドアロンの lib であるこのレポテンダーラブ/サイクを使用できます。

(Gem: psych - ロードするには:gem 'psych'; require 'psych'を使用し、 でチェックしますPsych::VERSION)

行 249 ~ 251でわかるように、匿名クラス Class を持つオブジェクトの処理は処理されません。

クラス Class にモンキーパッチを適用する代わりに、このクラス処理を拡張して Psych lib に貢献することをお勧めします。

したがって、私の考えでは、yaml の最終的な結果は次のようになります。"--- !ruby/class String"

それについて一晩考えた後、私が言えることは、この機能は本当に素晴らしいことです!


アップデート

意図した方法で動作するように見える小さな解決策を見つけました:

コードの要点: gist.github.com/1012130 (説明コメント付き)

于 2011-06-07T09:10:53.273 に答える
1

Psych のメンテナは、クラスモジュールのシリアライズとデシリアライズを実装しました。今はRubyです!

于 2011-06-09T13:38:16.717 に答える