This is the mail archive of the libstdc++@gcc.gnu.org mailing list for the libstdc++ project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Memory leaks in standard containers


I discovered through inspection that all of the standard containers 
leak memory under certain circumstances.  I don't know if this is 
considered a QoI issue, but I would consider it a serious bug.

The problem is that memory allocated through the container's allocator 
is never deallocated in the event that a container object's copy 
constructor throws an exception.  The leak might occur in copy 
constructors and assignment operators.

As far as I can see, the correct solution is to wrap pointers to 
allocated memory in standard containers with some sort of auto_ptr-like 
smart pointer.  I will work up a treatment, but in the mean time I've 
got a test case to illustrate the problem.  Note that this is a 
quick-and-dirty testcase implementation, I can get something better in 
a few days.

Diffs follow and two source files should be attached.

Index: Makefile.am
===================================================================
RCS file: /cvs/gcc/gcc/libstdc++-v3/testsuite/Makefile.am,v
retrieving revision 1.11
diff -c -3 -p -r1.11 Makefile.am
*** Makefile.am	1 Sep 2002 18:09:18 -0000	1.11
--- Makefile.am	18 Nov 2002 15:59:50 -0000
*************** INCLUDES = \
*** 51,57 ****
  
  ## Build support library.
  noinst_LIBRARIES = libv3test.a
! libv3test_a_SOURCES = testsuite_hooks.cc
  
  ## Build support utilities.
  ## Only build this as native, as need to find startup files and libc 
to link.
--- 51,57 ----
  
  ## Build support library.
  noinst_LIBRARIES = libv3test.a
! libv3test_a_SOURCES = testsuite_hooks.cc testsuite_allocator.cc
  
  ## Build support utilities.
  ## Only build this as native, as need to find startup files and libc 
to link.
Index: testsuite_hooks.cc
===================================================================
RCS file: /cvs/gcc/gcc/libstdc++-v3/testsuite/testsuite_hooks.cc,v
retrieving revision 1.4
diff -c -3 -p -r1.4 testsuite_hooks.cc
*** testsuite_hooks.cc	24 Oct 2002 23:27:27 -0000	1.4
--- testsuite_hooks.cc	18 Nov 2002 15:59:51 -0000
*************** gnu_counting_struct::size_type  gnu_coun
*** 80,83 ****
  
  int gnu_copy_tracker::itsCopyCount = 0;
  int gnu_copy_tracker::itsDtorCount = 0;
! 
--- 80,84 ----
  
  int gnu_copy_tracker::itsCopyCount = 0;
  int gnu_copy_tracker::itsDtorCount = 0;
! unsigned int gnu_copy_constructor::count_ = 0;
! unsigned int gnu_copy_constructor::throw_on_ = 0;
Index: testsuite_hooks.h
===================================================================
RCS file: /cvs/gcc/gcc/libstdc++-v3/testsuite/testsuite_hooks.h,v
retrieving revision 1.10
diff -c -3 -p -r1.10 testsuite_hooks.h
*** testsuite_hooks.h	2 Aug 2002 16:04:16 -0000	1.10
--- testsuite_hooks.h	18 Nov 2002 15:59:52 -0000
*************** class gnu_copy_tracker
*** 152,157 ****
--- 152,192 ----
      static int itsDtorCount;
  };
  
+ // A class for counting copy constructors and possible throwing an
+ // exception on a desired count.
+ class gnu_copy_constructor
+ {
+ public:
+   static unsigned int
+   count()
+   { return count_; }
+ 
+   static void
+   mark_call()
+   {
+     count_++;
+     if (count_ == throw_on_)
+     {
+       __throw_exception_again "copy constructor exception";
+     }
+   }
+ 
+   static void
+   reset()
+   {
+     count_ = 0;
+     throw_on_ = 0;
+   }
+ 
+   static void
+   throw_on(unsigned int count)
+   { throw_on_ = count; }
+ 
+ private:
+   static unsigned int count_;
+   static unsigned int throw_on_;
+ };
+ 
  struct gnu_char
  {
    unsigned long c;
Index: 23_containers/vector_ctor.cc
===================================================================
RCS file: 
/cvs/gcc/gcc/libstdc++-v3/testsuite/23_containers/vector_ctor.cc,v
retrieving revision 1.8
diff -c -3 -p -r1.8 vector_ctor.cc
*** 23_containers/vector_ctor.cc	1 May 2002 02:17:35 -0000	1.8
--- 23_containers/vector_ctor.cc	18 Nov 2002 15:59:53 -0000
***************
*** 22,27 ****
--- 22,28 ----
  
  #include <vector>
  #include <string>
+ #include <testsuite_allocator.h>
  #include <testsuite_hooks.h>
  
  template<typename T>
*************** void test04()
*** 94,105 ****
--- 95,159 ----
  #endif
  }
  
+ class SmwTest
+ {
+ public:
+   SmwTest() { }
+   SmwTest(const SmwTest& rhs)
+   { gnu_copy_constructor::mark_call(); }
+   ~SmwTest() { }
+ };
+ 
+ void
+ test_default_ctor_exception_safety()
+ {
+   typedef std::vector<SmwTest, gnu_new_allocator<SmwTest> > SmwVector;
+   gnu_copy_constructor::reset();
+   gnu_copy_constructor::throw_on(3);
+ 
+   try
+   {
+     SmwVector v01(7);
+     VERIFY(("no exception thrown", false));
+   }
+   catch (...)
+   {
+   }
+ 
+   VERIFY(("memory leak detected:",
+           gnu_allocator_tracker::allocationTotal() == 
gnu_allocator_tracker::deallocationTotal()));
+ }
+ 
+ void
+ test_copy_ctor_exception_safety()
+ {
+   typedef std::vector<SmwTest, gnu_new_allocator<SmwTest> > SmwVector;
+   SmwVector v01(7);
+   gnu_copy_constructor::reset();
+   gnu_copy_constructor::throw_on(3);
+ 
+   try
+   {
+     SmwVector v02(v01);
+     VERIFY(("no exception thrown", false));
+   }
+   catch (...)
+   {
+   }
+ 
+   VERIFY(("memory leak detected:",
+           gnu_allocator_tracker::allocationTotal() == 
gnu_allocator_tracker::deallocationTotal()));
+ }
+ 
  int main()
  {
    test01();
    test02(); 
    test03();
    test04();
+   test_default_ctor_exception_safety();
+   test_copy_ctor_exception_safety();
  
    return 0;
  }

-- 
Stephen M. Webb
#ifndef SMW_ALLOCATOR_H_
#define SMW_ALLOCATOR_H_

#include <cstddef>
#include <limits>

  class gnu_allocator_tracker {
  public:
    typedef std::size_t    size_type; 

    static void*
    allocate(size_type blocksize)
    {
      allocationTotal_ += blocksize;
      return ::operator new(blocksize);
    }

    static void
    construct()
    { constructCount_++; }

    static void
    destroy()
    { destructCount_++; }

    static void
    deallocate(void* p, size_type blocksize)
    {
      ::operator delete(p);
      deallocationTotal_ += blocksize;
    }

    static size_type
    allocationTotal() 
    { return allocationTotal_; }

    static size_type
    deallocationTotal()
    { return deallocationTotal_; }

    static int
    constructCount() 
    { return constructCount_; }

    static int
    destructCount() 
    { return destructCount_; }
    
    static void
    resetCounts()
    {
      allocationTotal_ = 0;
      deallocationTotal_ = 0;
      constructCount_ = 0;
      destructCount_ = 0;
    }

  private:
    static size_type  allocationTotal_;
    static size_type  deallocationTotal_;
    static int        constructCount_;
    static int        destructCount_;
  };

  // A simple basic allocator that just forwards to the gnu_allocator_tracker
  // to fulfill memory requests.  This class is templated on the target object
  // type, but gnu_allocator_tracker isn't.
  template <class T>
    class gnu_new_allocator
    {
    public:
      typedef T              value_type;
      typedef T*             pointer;
      typedef const T*       const_pointer;
      typedef T&             reference;
      typedef const T&       const_reference;
      typedef std::size_t    size_type; 
      typedef std::ptrdiff_t difference_type; 

      template <class U> struct rebind { typedef gnu_new_allocator<U> other; };

      pointer
      address(reference value) const
      { return &value; }

      const_pointer
      address(const_reference value) const
      { return &value; }

      gnu_new_allocator() throw()
      { }

      gnu_new_allocator(const gnu_new_allocator&) throw()
      { }

      template <class U>
        gnu_new_allocator(const gnu_new_allocator<U>&) throw()
        { }

      ~gnu_new_allocator() throw()
      { }

      size_type
      max_size() const throw()
      { return std::numeric_limits<std::size_t>::max() / sizeof(T); }

      pointer
      allocate(size_type num, const void* = 0)
      { return static_cast<pointer>(gnu_allocator_tracker::allocate(num * sizeof(T))); }

      void
      construct(pointer p, const T& value)
      {
        new (p) T(value);
        gnu_allocator_tracker::construct();
      }

      void
      destroy(pointer p)
      {
        p->~T();
        gnu_allocator_tracker::destroy();
      }

      void
      deallocate(pointer p, size_type num)
      { gnu_allocator_tracker::deallocate(p, num * sizeof(T)); }
    };

  template <class T1, class T2>
    bool
    operator==(const gnu_new_allocator<T1>&, const gnu_new_allocator<T2>&) throw()
    { return true; }

  template <class T1, class T2>
    bool
    operator!=(const gnu_new_allocator<T1>&, const gnu_new_allocator<T2>&) throw()
    { return false; }

#endif // SMW_ALLOCATOR_H_
// vi:set ts=2 sw=2 ai et:
// Utility subroutines for the C++ library testsuite.
//
// Copyright (C) 2002 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library.  This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 2, or (at your option)
// any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING.  If not, write to the Free
// Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307,
// USA.
//
// As a special exception, you may use this file as part of a free software
// library without restriction.  Specifically, if other files instantiate
// templates or use macros or inline functions from this file, or you compile
// this file and link it with other files to produce an executable, this
// file does not by itself cause the resulting executable to be covered by
// the GNU General Public License.  This exception does not however
// invalidate any other reasons why the executable file might be covered by
// the GNU General Public License.

#include <testsuite_allocator.h>

gnu_allocator_tracker::size_type gnu_allocator_tracker::allocationTotal_   = 0;
gnu_allocator_tracker::size_type gnu_allocator_tracker::deallocationTotal_ = 0;
int                              gnu_allocator_tracker::constructCount_    = 0;
int                              gnu_allocator_tracker::destructCount_     = 0;


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]