Как десериализовать занятия в Psych?

Как десериализовать в 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) десериализовать комплексное число), но я не думаю, что мне следует использовать этот метод.

Обречен ли я работать с излучением низкого или среднего уровня, или я что-то упустил?

Фон

Я опишу проект, почему ему нужны классы, и почему он нуждается в (де) сериализации.

проект

Малый Собственный Коллайдер нацелен на создание случайных задач для Ruby. Первоначальная цель состояла в том, чтобы увидеть, возвращают ли разные реализации Ruby (например, Rubinius и JRuby) одинаковые результаты при выполнении одинаковых случайных задач, но я обнаружил, что это также хорошо для обнаружения способов обойти ошибки Rubinius и YARV.

Каждое задание состоит из следующего:

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

где receiver является случайно выбранным объектом, и method_name это имя случайно выбранного метода, и *parameters это массив случайно выбранных объектов. &block не очень случайно - это в основном эквивалентно {|o| o.inspect},

Например, если получатель был "a", имя метода было: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 исправили ошибку)

Зачем нужны занятия

Я хочу иметь возможность использовать объекты класса в моем Малом Собственном Коллайдере. Как правило, они будут приемником, но они также могут быть одним из параметров.

Например, я обнаружил, что один из способов segfault YARV это сделать

Thread.kill(nil)

В этом случае получателем является объект класса Thread, а параметрами - [nil]. (Отчет об ошибке: http://redmine.ruby-lang.org/issues/show/4367)

Зачем нужна (де) сериализация

Малый Собственный Коллайдер нуждается в сериализации по нескольким причинам.

Во-первых, использование генератора случайных чисел для генерации серии случайных задач каждый раз нецелесообразно. JRuby имеет другой встроенный генератор случайных чисел, так что даже при наличии одного и того же начального числа PRNG он будет выполнять различные задачи для YARV. Вместо этого я один раз создаю список случайных задач (первый запуск ruby ​​bin/small_eigen_collider), инициализирую сериализацию списка задач для tasks.yml, а затем выполняю последующие запуска программы (используя разные Реализации Ruby) прочитайте в этом файле tasks.yml, чтобы получить список задач.

Еще одна причина, по которой мне нужна сериализация, заключается в том, что я хочу иметь возможность редактировать список задач. Если у меня длинный список задач, приводящий к ошибке сегментации, я хочу сократить список до минимума, необходимого для возникновения ошибки сегментации. Например, со следующей ошибкой https://github.com/evanphx/rubinius/issues/643,

ObjectSpace.undefine_finalizer(:symbol)

само по себе не вызывает ошибки сегментации, и при этом

Symbol.all_symbols.inspect

но если вы соединили их вместе, это произошло. Но я начал с тысяч задач, и мне нужно было свести их к этим двум задачам.

Имеет ли смысл десериализация, возвращающая существующие объекты класса, в этом контексте, или вы думаете, что есть лучший способ?

2 ответа

Решение

Сопровождающий Psych реализовал сериализацию и десериализацию классов и модулей. Это сейчас в Ruby!

Статус-кво моих текущих исследований:

Чтобы получить желаемое поведение, вы можете использовать мой обходной путь, упомянутый выше.

Вот хорошо отформатированный пример кода:

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

Возможно, ваш хак с модификацией Class никогда не сработает, потому что реальная обработка классов не реализована в psych / yaml.

Вы можете взять этот репо нежный / псих, который является автономной библиотекой.

(Gem: psych - чтобы загрузить его, используйте: gem 'psych'; require 'psych' и проверить с Psych::VERSION)

Как вы можете видеть в строке 249-251, обработка объектов с помощью анонимного класса Class не обрабатывается.

Вместо того, чтобы устанавливать класс Class, я рекомендую вам внести свой вклад в библиотеку Psych, расширив обработку этого класса.

Так что, на мой взгляд, окончательный результат yaml должен выглядеть примерно так: "--- !ruby/class String"

После одной ночи, подумав об этом, я могу сказать, что эта функция была бы действительно хороша!


Обновить

Нашел крошечное решение, которое, кажется, работает намеченным образом:

Суть кода: https://gist.github.com/1012130 (с описательными комментариями)

Другие вопросы по тегам