Bug #16642

Splatted empty hash literal produces frozen hash object

Added by Dan0042 (Daniel DeLorme) about 1 month ago. Updated 16 days ago.

Target version:
ruby -v:
ruby 2.7.1p37 (2020-02-20) [x86_64-openbsd6.6]


When splatting an empty hash literal, internally it's optimized using a global frozen hash object, but this implementation detail can leak into the ruby code outside:

ruby2_keywords def foo(*a) a.last end
h = foo(**{})
h[1] = 2 
# can't modify frozen Hash: {} (FrozenError)

I think this can be considered a bug?


ruby2_keywords-empty-kw-splat-16642.patch (2.49 KB) ruby2_keywords-empty-kw-splat-16642.patch jeremyevans0 (Jeremy Evans), 02/24/2020 09:22 PM

Updated by jeremyevans0 (Jeremy Evans) about 1 month ago

I agree it is a bug. I'm not sure it is worth fixing. Basically, the reason behind it is the parser doesn't separate hash compilation from keyword argument compilation, and the optimization for **{} as the only keyword argument ends up applying to hashes as well. It is simple to remove the optimization, but it will result in worse performance for keyword arguments using **{} (which can be used to avoid keyword argument warnings in Ruby 2.7). Separate parsing and/or compilation of keyword arguments and hashes will fix this, and it is on my todo list.

Updated by jeremyevans0 (Jeremy Evans) about 1 month ago

Actually, looks like I didn't read the bug report closely enough. This is a different issue, and suggests that we should either recognize the frozen hash in the ruby2_keywords case and dup it, or remove the optimization.

The issue I was referring to in my last comment is the fact that {**{}}.frozen? is true, due to the same optimization.

Updated by jeremyevans0 (Jeremy Evans) about 1 month ago

This was already fixed in master at f8a8f055123bc81fc13fa295b936504196df0da4, which changed it so ruby2_keywords does not pass empty keyword splats as hashes. Passing empty keyword splats as hashes is not needed in Ruby 3, it is only needed in Ruby 2.7 to avoid a final positional hash from being treated as keywords in a later method call.

Attached is a patch that fixes this in Ruby 2.7.

Updated by naruse (Yui NARUSE) 16 days ago

  • Backport changed from 2.5: DONTNEED, 2.6: DONTNEED, 2.7: REQUIRED to 2.5: DONTNEED, 2.6: DONTNEED, 2.7: DONE

ruby_2_7 commit:7804720c631d309b2f19457e703ecfb47a59589f.

Also available in: Atom PDF