Project

General

Profile

Bug #13232

Updated by nobu (Nobuyoshi Nakada) almost 8 years ago

Under very special cases trying to compare a BigDecimal to a float or Rational will give an unexpected result: 

     irb> BigDecimal('1') < 1e-10 
     => true 

 I couldn't find a sequence of steps which reproduces the problem with 100% guarantee, but the following seem to get close: 

 -- install a fresh copy of Ruby 2.3.3 from the stable sources 

     $ ./configure --prefix=$HOME/ruby-2.3.3-test 
     $ make main && make install-nodoc 
     $ export PATH=$HOME/ruby-2.3.3-test/bin:$PATH 

 -- install active support in $HOME/.gem 

     $ mkdir $HOME/bug 
     $ cd $HOME/bug 
     $ gem install bundler 
     $ echo -e "source 'https://rubygems.org'\ngem 'activesupport'" > Gemfile 
     $ bundle install --path=$HOME/.gem 

 -- run the script: 

     $ while ruby -e "require 'tempfile'; require 'bigdecimal'; f = Tempfile.new('bug'); srand(1); (1..2000).each { |i| f.rewind && f.puts('test') if i % 1000 == 1; x = BigDecimal('1'); puts([i, x]) || exit(1) if x < 1e-10.to_r }" ; do true ; done 

 The last script should not return. If it does then the bug is present: 

     $ while ruby -e ... 
     962 
     0.1E1 

 I can reproduce the bug on Ubuntu 16.04 running with Intel Core i3-4130 (desktop) and on Ubuntu 12.04, 14.04 and 16.04 running with Intel Xeon E5-2651 v2 (AWS). 

 I could track the problem down to the method VpNewRbClass in bigdecimal.c. It looks like the garbage collector is being called at the wrong time and corrupting memory. Applying the following patch gives me the output below: 

 ```diff 
 

     --- ext/bigdecimal/bigdecimal.c.orig 	 2017-02-19 00:30:06.885174089 -0300 
 
     +++ ext/bigdecimal/bigdecimal.c 	 2017-02-19 00:33:53.379501013 -0300 
 
     @@ -604,8 +604,14 @@ 
  
      VP_EXPORT Real * 
  
      VpNewRbClass(size_t mx, const char *str, VALUE klass) 
  
      { 
 
     -      VALUE obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); 
 
     -      Real *pv = VpAlloc(mx,str); 
 
     +      VALUE obj; 
 
     +      Real *pv; 
 
     +      if (mx == 36) 
 
     +          printf("--\nVpNewRbClass: %s\n", str); 
 
     +      obj = TypedData_Wrap_Struct(klass, &BigDecimal_data_type, 0); 
 
     +      if (mx == 36) 
 
     +          printf("VpNewRbClass: %s\n", str); 
 
     +      pv = VpAlloc(mx,str); 
      
          RTYPEDDATA_DATA(obj) = pv; 
      
          pv->obj = obj; 
      
          return pv; 
 ``` 

 Output: 

     $ while ruby -e ... 
     .... 
     -- 
     VpNewRbClass: 77371252455336267181195264 
     VpNewRbClass: 77371252455336267181195264 
     -- 
     VpNewRbClass: 77371252455336267181195264 
     VpNewRbClass: 77371252455336267181195264 
     -- 
     VpNewRbClass: 77371252455336267181195264 
     VpNewRbClass: ��V 
     962 
     0.1E1 

 Here's a possible fix. Someone please review it, because I have no experience with CRuby and don't feel confident about its correctness: 

 ```diff 
 

     --- ext/bigdecimal/bigdecimal.c.orig 	 2017-02-19 00:30:06.885174089 -0300 
 
     +++ ext/bigdecimal/bigdecimal.c 	 2017-02-19 00:39:32.884680313 -0300 
 
     @@ -227,6 +227,7 @@ 
  
      static Real* 
  
      GetVpValueWithPrec(VALUE v, long prec, int must) 
  
      { 
 
     +      ENTER(1); 
      
          Real *pv; 
      
          VALUE num, bg; 
      
          char szD[128]; 
 
     @@ -292,6 +293,7 @@ 
 
        
     
            case T_BIGNUM: 
          
              bg = rb_big2str(v, 10); 
 
     +          PUSH(bg); 
          
              return VpCreateRbObject(strlen(RSTRING_PTR(bg)) + VpBaseFig() + 1, 
                                  
                                      RSTRING_PTR(bg)); 
        
            default: 
 ``` 

Back