Skip to content

Support more Ruby Marshal types#67

Open
waj wants to merge 1 commit into
philipcunningham:masterfrom
NoRedInk:support-more-ruby-marshal-types
Open

Support more Ruby Marshal types#67
waj wants to merge 1 commit into
philipcunningham:masterfrom
NoRedInk:support-more-ruby-marshal-types

Conversation

@waj
Copy link
Copy Markdown

@waj waj commented May 27, 2026

I was trying to use this library to parse some Rails sessions stored as Marshal values, but I found that some custom objects not only were not deserialized but they would break the parsing of the containing hash. For example, if the session dictionary contained some unsupported value, the subsequent entries in the hash were returned as unsupported as well, regardless of their type.

This PR fixes that by doing basic parsing of all the Marshal types and provide basic support for most of them.

Adds first-class RubyObject constructors for tags that were previously returned as Unsupported: RBignum, RRegexp, RHashWithDefault, RObject, RStruct, RClass, RModule, RUserDef, RUserMarshal, RData. The wrapper tags 'e' (extended) and 'C' (subclassed) pass the inner object through, matching Ruby's behaviour where the wrapper does not occupy its own slot in the object table.

As a consequence the parser is also stream-safe for tags it does not recognise: every known tag now consumes exactly its on-the-wire bytes, so an unmodelled type can no longer corrupt the rest of the stream. getIVar is likewise more lenient — when the wrapped object is not a string or carries extra instance variables, every byte is consumed and the inner object is surfaced unchanged instead of failing the parse.

Adds 14 specs covering each new constructor, the lenient IVar path, both wrapper tags, and an object-link that resolves to a cached RObject. Fixtures come from an extended test/dump.

Adds first-class RubyObject constructors for tags that were previously
returned as Unsupported: RBignum, RRegexp, RHashWithDefault, RObject,
RStruct, RClass, RModule, RUserDef, RUserMarshal, RData. The wrapper
tags 'e' (extended) and 'C' (subclassed) pass the inner object
through, matching Ruby's behaviour where the wrapper does not occupy
its own slot in the object table.

As a consequence the parser is also stream-safe for tags it does not
recognise: every known tag now consumes exactly its on-the-wire bytes,
so an unmodelled type can no longer corrupt the rest of the stream.
getIVar is likewise more lenient — when the wrapped object is not a
string or carries extra instance variables, every byte is consumed
and the inner object is surfaced unchanged instead of failing the
parse.

Adds 14 specs covering each new constructor, the lenient IVar path,
both wrapper tags, and an object-link that resolves to a cached
RObject. Fixtures come from an extended test/dump.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@philipcunningham
Copy link
Copy Markdown
Owner

@waj, thanks for the contribution. Before I dig in, could you add some motivation and framing to the PR description? For example, is this a use case gap you're filling, or something you've been working on for your own enjoyment? Either is welcome, just helps me know how best to engage.

@waj
Copy link
Copy Markdown
Author

waj commented May 27, 2026

Hi @philipcunningham! I just added the motivation to the description. I built this mostly with Claude but I think the result is pretty concise and correct. I've been testing this with many real world values before sending the PR and so far seems to be working well.

Let me know what do you think, and thanks a lot for this great library!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants