Boost logo

Boost :

From: Boris Gubenko (Boris.Gubenko_at_[hidden])
Date: 2007-12-03 21:55:21


  A failure of dynamic_bitset library test dyn_bitset_unit_tests3
  on Tru64/cxx and HP-UX/aCC revealed what appears to be a bug in
  <boost/dynamic_bitset/dynamic_bitset.hpp> introduced after
  Boost 1.34.

  The bug affects platforms where 'long' type is larger than 'int'.
  On both Tru64 and HP-UX, 'int' is a 32-bit type. On Tru64, 'long'
  is a 64-bit type. On HP-UX, 'long' is a 64-bit type in 64-bit data
  model (+DD64, which is how Boost testing is done on this platform)
  and 32-bit type otherwise.

  The test also fails on other platforms and while I cannot verify it,
  from the test output it apears that these platforms are affected by
  the same bug.

  What fails is the two testcases below in b.to_long() suite in
  boost/libs/dynamic_bitset/dyn_bitset_unit_tests3.cpp invoked with
  run_test_cases<unsigned int>() :

  {
    std::string str(ul_width - 1, '1');
    boost::dynamic_bitset<Block> b(str);
    Tests::to_ulong(b);
  }
  {
    std::string ul_str(ul_width, '1');
    boost::dynamic_bitset<Block> b(ul_str);
    Tests::to_ulong(b);
  }

  The code in <boost/libs/dynamic_bitset/bitset_test.hpp> converts
  dynamic_bitset<unsigned int> argument to unsigned long and checks
  that the appropriate bits in the result are set:

      result_type num = lhs.to_ulong();
      // Be sure the number is right
      if (sz == 0)
        BOOST_CHECK(num == 0);
      else {
        for (std::size_t i = 0; i < sz; ++i)
          BOOST_CHECK(lhs[i] == (i < n ? nth_bit(num, i) : 0));
      }

  This check fails for the bits in the upper half of the result.

  Here is an excerpt from dynamic_bitset<Block, Allocator>::to_ulong()
  function in <boost/dynamic_bitset/dynamic_bitset.hpp> in the trunk:

  result_type result = 0;
  for (size_type i = 0; i <= last_block; ++i) {
    assert((size_type)bits_per_block * i < (size_type)ulong_width);
    result |= (m_bits[i] << (bits_per_block * i));
  }

  In our case, last_block = 1 and bits_per_block = 32. m_bits[i] has
  'unsigned int' type.

  On the second loop iteration, a 32-bit integer is left-shifted by 32 bit
  positions. This triggers undefined behaviour. From 5.8 - Shift operators
  [expr.shift] : "The behavior is undefined if the right operand is [...]
  greater than or equal to the length in bits of the promoted left
  operand." With cxx and aCC, such shift results in zeroing of all bits
  of the left operand.

  Here is the code from <boost/dynamic_bitset/dynamic_bitset.hpp> in Boost
  1.34 :

  unsigned long result = 0;
  for (size_type i = 0; i <= last_block; ++i) {
    assert((size_type)bits_per_block * i < (size_type)ulong_width); // gps
    unsigned long piece = m_bits[i];
    result |= (piece << (bits_per_block * i));
  }

  Here on the second loop iteration, a 64-bit long is left-shifted by 32
  bit positions which yields the intended result.

  x.cpp below illustrates the difference between trunk and Boost 1.34:

x.cpp
-----
#include <stdio.h>
#include <limits.h>

int main() {
  unsigned int m_bits[] = { UINT_MAX, UINT_MAX };
  unsigned int bits_per_block = sizeof(int) * CHAR_BIT;
  unsigned long result = 0;
  for(int i = 0; i <= 1 ; ++i) {
#ifndef Boost_1_34
    result |= (m_bits[i] << (bits_per_block * i));
#else
    unsigned long piece = m_bits[i];
    result |= (piece << (bits_per_block * i));
#endif
    printf("result = %llx\n", result);
  }
}

cxxosf.zko.hp.com> cxx x.cpp && a.out
result = ffffffff
result = ffffffff
cxxosf.zko.hp.com> cxx x.cpp -DBoost_1_34 && a.out
result = ffffffff
result = ffffffffffffffff
cxxosf.zko.hp.com>

  Replacing

    result |= (m_bits[i] << (bits_per_block * i));

  with

    unsigned long piece = m_bits[i];
    result |= (piece << (bits_per_block * i));

  on the trunk makes the test succeed: verified on Tru64/cxx and on
  HP-UX/aCC.

  In <http://lists.boost.org/Archives/boost/2007/12/131126.php> I said:

> It looks like a regression in boost/dynamic_bitset/dynamic_bitset.hpp
> introduced in 1.35, but even in 1.34.1, it seems to me that the code in
> to_ulong() is relying on undefined behaviour.

  What I was referring to is the code in 1.34 applying bitwise shift
  operator to a long operand. It would trigger an undefined behavior
  in C where "The operands shall have integer type", but it is legal
  in C++ where "The operands shall be of integral or enumeration type".
  So, I no longer claim that the code in 1.34 is relying on undefined
  behaviour.

  And the last thing: why the bug affects only platforms where 'long'
  type is larger than 'int'? Because if they are the same, as is the
  case on VMS or HP-UX in 32-bit data model, last_block = 0 and the
  single iteration of the for loop just assigns m_bits[0] to the
  result: the right operand of the shift operator is zero.

  To fix the problem, I suggest to replace the buggy code in
  <boost/dynamic_bitset/dynamic_bitset.hpp> in the trunk with that
  in 1.34. If there are no objections, I can do it.

  Thanks,
    Boris


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk