| 
    
       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)
 
     |