Как десериализовать занятия в 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 ответа
Статус-кво моих текущих исследований:
Чтобы получить желаемое поведение, вы можете использовать мой обходной путь, упомянутый выше.
Вот хорошо отформатированный пример кода:
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 (с описательными комментариями)