Issue #12353 has been updated by Dan Barry.


Using Ruby 2.3, Marshal.dump(1.day) works with ActiveSupport 4.2.6 but not 4.1.15 because ActiveSupport::Duration inherits from BasicObject in 4.1.15 and Object in 4.2.6.

The issue is that as of Ruby 2.3, Marshal.dump no longer works with objects that inherit from BasicObject and do not directly define a respond_to? method. This is because vm_respond_to() in vm_method.c returns true if it can not find a method table entry for the respond_to? method, so w_object() in marshal.c tries to call marshal_dump. In Ruby 2.2 and prior, rb_obj_respond_to() would just call respond_to? instead of checking for a method table entry, so method_missing could receive the respond_to?.

Here's a simple example:

~~~
$ irb
2.3.1 :001 > class Wrapper < BasicObject
2.3.1 :002?>   def initialize wrapped_object
2.3.1 :003?>     @wrapped_object = wrapped_object
2.3.1 :004?>   end
2.3.1 :005?>   def method_missing *args, &block
2.3.1 :006?>     @wrapped_object.public_send *args, &block
2.3.1 :007?>   end
2.3.1 :008?> end
 => :method_missing 
2.3.1 :009 > wrapped_string = Wrapper.new "foo"
 => "foo" 
2.3.1 :010 > wrapped_string.respond_to? :marshal_dump
 => false 
2.3.1 :011 > Marshal.dump wrapped_string
NoMethodError: undefined method `marshal_dump' for "foo":String
	from (irb):6:in `public_send'
	from (irb):6:in `method_missing'
	from (irb):11:in `dump'
	from (irb):11
	from /Users/dan/.rvm/rubies/ruby-2.3.1/bin/irb:11:in `<main>'
2.3.1 :012 > class Wrapper
2.3.1 :013?>   def respond_to? *args, &block
2.3.1 :014?>     super
2.3.1 :015?>   end
2.3.1 :016?> end
 => :respond_to? 
2.3.1 :017 > wrapped_string.respond_to? :marshal_dump
 => false 
2.3.1 :018 > Marshal.dump wrapped_string
 => "\x04\bo:\fWrapper\x06:\x14@wrapped_objectI\"\bfoo\x06:\x06ET"
~~~

In Ruby 2.2 and earlier, Marshal.dump(wrapped_string) would work without needing to define a respond_to? method on Wrapper.

It's bad practice to define a method_missing without a corresponding respond_to_missing?, but even with a respond_to_missing? method defined, we still need a respond_to? in order for Marshal.dump to work:

~~~
2.3.1 :001 > class Wrapper < BasicObject
2.3.1 :002?>   def initialize wrapped_object
2.3.1 :003?>     @wrapped_object = wrapped_object
2.3.1 :004?>   end
2.3.1 :005?>   def method_missing *args, &block
2.3.1 :006?>     @wrapped_object.public_send *args, &block
2.3.1 :007?>   end
2.3.1 :008?>   def respond_to_missing? method_name, include_private = false
2.3.1 :009?>     @wrapped_object.respond_to? method_name, include_private
2.3.1 :010?>   end
2.3.1 :011?> end
 => :respond_to_missing? 
2.3.1 :012 > wrapped_string = Wrapper.new "foo"
 => "foo" 
2.3.1 :013 > wrapped_string.respond_to? :marshal_dump
 => false 
2.3.1 :014 > Marshal.dump wrapped_string
NoMethodError: undefined method `marshal_dump' for "foo":String
	from (irb):6:in `public_send'
	from (irb):6:in `method_missing'
	from (irb):14:in `dump'
	from (irb):14
	from /Users/dan/.rvm/rubies/ruby-2.3.1/bin/irb:11:in `<main>'
~~~

One way to achieve the expected behavior would be to change vm_respond_to() so that it calls method_missing(:respond_to?, priv) when it can't find a method table entry for respond_to? instead of just returning true.

I'd be happy to make this change if someone on the core team can let me know if my suggestion is the right way to handle this or if there's a different way things should be changed.

----------------------------------------
Bug #12353: Regression with Marshal.dump on ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]
https://bugs.ruby-lang.org/issues/12353#change-59344

* Author: Jeff C
* Status: Feedback
* Priority: Normal
* Assignee: 
* ruby -v: ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]
* Backport: 2.1: UNKNOWN, 2.2: UNKNOWN, 2.3: UNKNOWN
----------------------------------------
Attempting to call `Marshal.dump` on an `ActiveSupport::CoreExtensions::Numeric::Time` (e.g., `1.day`) triggers a ```NoMethodError: undefined method `marshal_dump' for 86400:Fixnum``` exception in both Ruby 2.3.0 and 2.3.1 on OS X (10.11.4) and in Travis CI with ActiveSupport 4.1.15. This works correctly in previous versions, including 2.2.5.

To reproduce:

```
% rbenv install 2.3.1
...
% rbenv global 2.3.1
% gem install activesupport -v 4.1.15
...
% ruby --version    
ruby 2.3.1p112 (2016-04-26 revision 54768) [x86_64-darwin15]
% irb
irb(main):001:0> require 'active_support/core_ext/numeric/time'
=> true
irb(main):002:0> Marshal.dump(1.day)
NoMethodError: undefined method `marshal_dump' for 86400:Fixnum
	from ~/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activesupport-4.1.15/lib/active_support/duration.rb:115:in `method_missing'
	from (irb):2:in `dump'
	from (irb):2
	from ~/.rbenv/versions/2.3.1/bin/irb:11:in `<main>'
```

In 2.2.5:

```
% rbenv install 2.2.5
...
% rbenv global 2.2.5
% gem install activesupport -v 4.1.15
...
% ruby --version
ruby 2.2.5p319 (2016-04-26 revision 54774) [x86_64-darwin15]
% irb                                
irb(main):001:0> require 'active_support/core_ext/numeric/time'
=> true
irb(main):002:0> Marshal.dump(1.day)
=> "\x04\bo:\x1CActiveSupport::Duration\a:\v@valuei\x03\x80Q\x01:\v@parts[\x06[\a:\tdaysi\x06"
```




-- 
https://bugs.ruby-lang.org/

Unsubscribe: <mailto:ruby-core-request / ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>