psych icon indicating copy to clipboard operation
psych copied to clipboard

.to_yaml fails when tempfile is an instance variable

Open sdjespersen opened this issue 11 years ago • 7 comments

Here's a quick script to reproduce the issue:

require 'tempfile'
require 'psych'

class Foo 
  def initialize
    @tf = Tempfile.new('foo')
  end 
end 

f = Foo.new
puts f.to_yaml

Then ruby foo.rb produces the following stacktrace:

/Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:26:in `block in initialize': undefined method `name' for nil:NilClass (NoMethodError)
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in `yield'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in `default'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in `block in initialize'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in `yield'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in `default'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in `block in initialize'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in `yield'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in `default'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in `block in initialize'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in `yield'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in `default'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in `block in initialize'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:103:in `yield'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:103:in `default'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:103:in `accept'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:449:in `block in dump_ivars'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:447:in `each'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:447:in `dump_ivars'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:125:in `visit_Object'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:103:in `accept'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:67:in `push'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych.rb:242:in `dump'
  from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/core_ext.rb:14:in `psych_to_yaml'
  from foo.rb:11:in `<main>'

Is there a reason I shouldn't be using .to_yaml with such an object? Or is this a legitimate bug?

sdjespersen avatar Dec 19 '13 19:12 sdjespersen

Well, you can't really serialize an IO object. It should raise an exception. Maybe not this exception, but serializing an io object doesn't make sense.

Aaron Patterson http://tenderlovemaking.com/ I'm on an iPhone so I apologize for top posting.

On Dec 19, 2013, at 11:41 AM, sdjespersen [email protected] wrote:

Here's a quick script to reproduce the issue:

require 'tempfile' require 'psych'

class Foo def initialize @tf = Tempfile.new('foo') end end

f = Foo.new puts f.to_yaml Then ruby foo.rb produces the following stacktrace:

/Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:26:in block in initialize': undefined methodname' for nil:NilClass (NoMethodError) from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in yield' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:indefault' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in block in initialize' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:inyield' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in default' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:inblock in initialize' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in yield' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:indefault' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in block in initialize' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:inyield' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:in default' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:28:inblock in initialize' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:103:in yield' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:103:indefault' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:103:in accept' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:449:inblock in dump_ivars' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:447:in each' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:447:indump_ivars' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:125:in visit_Object' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:103:inaccept' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/visitors/yaml_tree.rb:67:in push' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych.rb:242:indump' from /Users/me/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/1.9.1/psych/core_ext.rb:14:in psych_to_yaml' from foo.rb:11:in

' Is there a reason I shouldn't be using .to_yaml with such an object? Or is this a legitimate bug?


Reply to this email directly or view it on GitHub.

tenderlove avatar Dec 19 '13 19:12 tenderlove

I see. However, the following doesn't complain:

tf = Tempfile.new('foo')
tf.to_yaml # => "--- !ruby/object:File {}\n"

sdjespersen avatar Dec 19 '13 19:12 sdjespersen

Ya, it probably shouldn't. Psych should behave like marshal:

irb(main):001:0> Marshal.dump File.open('/dev/null')
TypeError: can't dump File
    from (irb):1:in `dump'
    from (irb):1
    from /Users/aaron/.rbenv/versions/2.1.0-dev/bin/irb:11:in `<main>'
irb(main):002:0>

tenderlove avatar Dec 19 '13 21:12 tenderlove

A similar exception would be a nice feature, but as you pointed out, my application of yaml here is somewhat misguided.

sdjespersen avatar Dec 19 '13 23:12 sdjespersen

@tenderlove, had a similar issue,

my use case was to log as much data as possible from a failed request, for example, in a rails controller method:

    Event.create( :title => "Invalid request from client",
                  :params => request.filtered_parameters.to_yaml,
                  :session => session.to_yaml,
                  :kind => 'invalid request',
                  :message => options[:message],
                  :severity => 'low',
                  :request => request.env.reject{|k,v| k.to_s =~ /^action_controller..*/ or k.to_s =~ /^action_dispatch..*/}.to_yaml,
                  :response => {:body => response.body, :status => response.status, :location => response.location, :content_type => response.content_type, :charset => response.charset, }.to_yaml)

and the app is failing at:

request.env.reject{|k,v| k.to_s =~ /^action_controller..*/ or k.to_s =~ /^action_dispatch..*/}.to_yaml

The Marshall approach would force explicit filtering of a myriad of keys in a deep hash, and I guess the Syck behaviour under Rails 3.0 was to simply dump whatever was there and dumpable and give no useful information where no useful information can be given.

Deep hashes can be a bitch.

Both approaches have merit, maybe make the behaviour a configuration option in #to_yaml ?

A funny addition to the problem was that the exception happened only under Passenger in production, not on the development machine.

bbozo avatar Jan 23 '14 11:01 bbozo

Sorry I haven't made an progress on this in a while. How about this:

  1. Files (including tempfiles) will raise an exception by default
  2. You'll be able to configure an error handler to return a different value rather than raising an exception

For example:

Psych.dump(deeply_nested_hash, :on_error => lambda { |obj,ex|
  if obj == blah
    nil # nil is dumped rather than `obj`
  else
    raise ex
  end
})

tenderlove avatar Aug 29 '14 16:08 tenderlove

Don't know if his is related, but I tried to serialize a hash created from and Icalandar event and got the error: NoMethodError: undefined methodname' for nil:NilClass`

Debugging the problem I found this post. After a little digging I found my problem was that what I though was a text field in the hash had a class of `Icalendar::Values::Text'.

I can't remember the exact circumstance, but several publicly available icalanders (Apple's holidays) had some character sequences that was throwing an encoding error. I ended up having to force encoding on those text fields:

e.summary = e.summary.force_encoding("UTF-8")

That fix to a bug in (Icalendar,?) gave me the undefined method error. There is also a new issue (#208) that reference forced encoding. I fixed my problem by converting the fields to_s before serializing since I assume psych can't account for every gem specific class. The hash I created worked fine in displaying the calendar, but I decided to serialize and save it and ran into this problem.

salex avatar Jan 28 '15 13:01 salex