From: dan@... Date: 2015-05-04T16:21:10+00:00 Subject: [ruby-core:69072] [Ruby trunk - Bug #11091] Symbolized Strings May Break Keyword Arguments Issue #11091 has been updated by Daniel Finnie. File test_case.rb added We just ran into this bug as well. Our script to reproduce is attached. We also noticed the same requirements as the original poster: multiple required keyword arguments, a **splat, and a dynamically created symbol. We ran the script below in 2.3.0-dev, where it seems to be fixed. We're still seeing the behavior in 2.2.2 (ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-darwin13]). Will this fix be backported? ---------------------------------------- Bug #11091: Symbolized Strings May Break Keyword Arguments https://bugs.ruby-lang.org/issues/11091#change-52318 * Author: Dominik Sch��ler * Status: Closed * Priority: Normal * Assignee: * ruby -v: ruby 2.2.2p95 (2015-04-13 revision 50295) [x86_64-linux] * Backport: 2.0.0: DONTNEED, 2.1: DONTNEED, 2.2: REQUIRED ---------------------------------------- See https://makandracards.com/makandra/32333-bugreport-symbolized-strings-break-keyword-arguments-in-ruby-2-2. --- **TL;DR** Under certain circumstances, dynamically defined symbols may break keyword arguments in Ruby 2.2. Specifically, **when** ��� - there is a method with several keyword arguments **and** a double-splat argument (e.g. `def m(foo: 'bar, option: 'will be lost', **further_options)`) - there is a dynamically created `Symbol` (e.g. `'culprit'.to_sym`) that is _created_ **before** the method is _parsed_ - the method gets called with both the `option` and a `culprit` keyword argument ��� **then** the `option` keyword argument will be `nil` inside of `#m`. ## Affected Ruby Versions - Affected version: 2.2.1, 2.2.2 - Unaffected versions: 1.x, 2.0, 2.1 ## How to Expose it The following code exposes the bug. Save it, make sure you have Ruby 2.2 and run it. ``` test_symbol = '__test' # The bug only occurs when a symbol used in a keyword argument is dynamically # added to the Ruby symbols table *before* Ruby first sees the keyword argument. existing = Symbol.all_symbols.map(&:to_s).grep('__test') raise "Symbol #{test_symbol} already exists in symbol table!" if existing.any? '__test'.to_sym # breaks it # :__test # does not break it # GC.start # fixes it # Why #eval? # Without, Ruby would parse the symbols in this code into its symbol table # before running the file, which prevents the bug. eval <<-RUBY $hash = { __test: '__test', lost: 'lost', q: 'q' } def _report(name, value) puts name.to_s << ': ' << (value ? 'ok' : 'broken') end # Confirmed broken when: # - `lost` is the second keyword argument Oo # - there is a double-splat argument def vulnerable_method_1(p: 'p', lost: 'lost', **options) _report(__method__, lost) end def vulnerable_method_2(p: 'p', lost: 'lost', q: 'q', **options) _report(__method__, lost) end def immune_method_1(lost: 'lost', p: 'p', **options) _report(__method__, lost) end def immune_method_2(q: 'q', lost: 'lost', __test: '__test') _report(__method__, lost) end def immune_method_3(lost: 'lost', **options) _report(__method__, lost) end RUBY # Exposure ##################################################################### puts '', 'Broken when calling with a hash' vulnerable_method_1($hash) vulnerable_method_2($hash) immune_method_1($hash) immune_method_2($hash) immune_method_3($hash) puts '', 'Double splat (**) has no influence:' vulnerable_method_1(**$hash) vulnerable_method_2(**$hash) immune_method_1(**$hash) immune_method_2(**$hash) immune_method_3(**$hash) puts '', 'Hash order does not matter:' inversed_hash = Hash[$hash.to_a.reverse] vulnerable_method_1(inversed_hash) vulnerable_method_2(inversed_hash) immune_method_1(inversed_hash) immune_method_2(inversed_hash) immune_method_3(inversed_hash) ``` ## References - Related (but does not fix it): [https://bugs.ruby-lang.org/issues/11027](https://bugs.ruby-lang.org/issues/11027) ---Files-------------------------------- symbol_bug.rb (1.92 KB) test_case.rb (482 Bytes) -- https://bugs.ruby-lang.org/