[pypy-svn] r77979 - in pypy/branch/leak-finder/pypy: . annotation jit/backend/llsupport jit/backend/test jit/backend/x86 jit/backend/x86/test jit/metainterp jit/metainterp/test module/cpyext/test rlib/test rpython rpython/lltypesystem rpython/lltypesystem/test rpython/memory rpython/memory/gctransform rpython/test tool tool/test translator/c/test

arigo at codespeak.net arigo at codespeak.net
Fri Oct 15 14:37:19 CEST 2010


Author: arigo
Date: Fri Oct 15 14:37:16 2010
New Revision: 77979

Added:
   pypy/branch/leak-finder/pypy/tool/leakfinder.py
   pypy/branch/leak-finder/pypy/tool/test/test_leakfinder.py
Modified:
   pypy/branch/leak-finder/pypy/annotation/builtin.py
   pypy/branch/leak-finder/pypy/conftest.py
   pypy/branch/leak-finder/pypy/jit/backend/llsupport/gc.py
   pypy/branch/leak-finder/pypy/jit/backend/llsupport/llmodel.py
   pypy/branch/leak-finder/pypy/jit/backend/test/runner_test.py
   pypy/branch/leak-finder/pypy/jit/backend/x86/assembler.py
   pypy/branch/leak-finder/pypy/jit/backend/x86/regalloc.py
   pypy/branch/leak-finder/pypy/jit/backend/x86/runner.py
   pypy/branch/leak-finder/pypy/jit/backend/x86/test/test_assembler.py
   pypy/branch/leak-finder/pypy/jit/metainterp/test/test_basic.py
   pypy/branch/leak-finder/pypy/jit/metainterp/virtualref.py
   pypy/branch/leak-finder/pypy/module/cpyext/test/test_cpyext.py
   pypy/branch/leak-finder/pypy/rlib/test/test_rdynload.py
   pypy/branch/leak-finder/pypy/rlib/test/test_rzlib.py
   pypy/branch/leak-finder/pypy/rpython/llinterp.py
   pypy/branch/leak-finder/pypy/rpython/lltypesystem/llmemory.py
   pypy/branch/leak-finder/pypy/rpython/lltypesystem/lltype.py
   pypy/branch/leak-finder/pypy/rpython/lltypesystem/rclass.py
   pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_ll2ctypes.py
   pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_llmemory.py
   pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_lltype.py
   pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_rffi.py
   pypy/branch/leak-finder/pypy/rpython/memory/gctransform/transform.py
   pypy/branch/leak-finder/pypy/rpython/memory/gcwrapper.py
   pypy/branch/leak-finder/pypy/rpython/memory/support.py
   pypy/branch/leak-finder/pypy/rpython/rbuiltin.py
   pypy/branch/leak-finder/pypy/rpython/test/test_llinterp.py
   pypy/branch/leak-finder/pypy/rpython/test/test_nongc.py
   pypy/branch/leak-finder/pypy/rpython/test/test_rptr.py
   pypy/branch/leak-finder/pypy/translator/c/test/test_lltyped.py
Log:
In-progress: remove the 'self.mallocs' from llinterp, and instead
use systematically the one from lltype.py introduced around r73335.


Modified: pypy/branch/leak-finder/pypy/annotation/builtin.py
==============================================================================
--- pypy/branch/leak-finder/pypy/annotation/builtin.py	(original)
+++ pypy/branch/leak-finder/pypy/annotation/builtin.py	Fri Oct 15 14:37:16 2010
@@ -423,7 +423,7 @@
 from pypy.annotation.model import SomePtr
 from pypy.rpython.lltypesystem import lltype
 
-def malloc(s_T, s_n=None, s_flavor=None, s_zero=None):
+def malloc(s_T, s_n=None, s_flavor=None, s_zero=None, s_track_allocation=None):
     assert (s_n is None or s_n.knowntype == int
             or issubclass(s_n.knowntype, pypy.rlib.rarithmetic.base_int))
     assert s_T.is_constant()
@@ -438,13 +438,15 @@
         r = SomePtr(lltype.typeOf(p))
     else:
         assert s_flavor.is_constant()
+        assert s_track_allocation is None or s_track_allocation.is_constant()
         # not sure how to call malloc() for the example 'p' in the
         # presence of s_extraargs
         r = SomePtr(lltype.Ptr(s_T.const))
     return r
 
-def free(s_p, s_flavor):
+def free(s_p, s_flavor, s_track_allocation=None):
     assert s_flavor.is_constant()
+    assert s_track_allocation is None or s_track_allocation.is_constant()
     # same problem as in malloc(): some flavors are not easy to
     # malloc-by-example
     #T = s_p.ll_ptrtype.TO

Modified: pypy/branch/leak-finder/pypy/conftest.py
==============================================================================
--- pypy/branch/leak-finder/pypy/conftest.py	(original)
+++ pypy/branch/leak-finder/pypy/conftest.py	Fri Oct 15 14:37:16 2010
@@ -7,6 +7,7 @@
 from inspect import isclass, getmro
 from pypy.tool.udir import udir
 from pypy.tool.autopath import pypydir
+from pypy.tool import leakfinder
 
 # pytest settings
 pytest_plugins = "resultlog",
@@ -354,7 +355,14 @@
 
     def runtest(self):
         try:
-            super(IntTestFunction, self).runtest()
+            leakfinder.start_tracking_allocations()
+            try:
+                super(IntTestFunction, self).runtest()
+            finally:
+                if leakfinder.TRACK_ALLOCATIONS:
+                    leaks = leakfinder.stop_tracking_allocations(False)
+                else:
+                    leaks = None   # stop_tracking_allocations() already called
         except OperationError, e:
             check_keyboard_interrupt(e)
             raise
@@ -373,6 +381,8 @@
                 _pygame_imported = True
                 assert option.view, ("should not invoke Pygame "
                                      "if conftest.option.view is False")
+        if leaks:        # check for leaks, but only if the test passed so far
+            raise leakfinder.MallocMismatch(leaks)
 
 class AppTestFunction(PyPyTestFunction):
     def _prunetraceback(self, traceback):

Modified: pypy/branch/leak-finder/pypy/jit/backend/llsupport/gc.py
==============================================================================
--- pypy/branch/leak-finder/pypy/jit/backend/llsupport/gc.py	(original)
+++ pypy/branch/leak-finder/pypy/jit/backend/llsupport/gc.py	Fri Oct 15 14:37:16 2010
@@ -158,7 +158,7 @@
         # used to avoid too many duplications in the GCREF_LISTs.
         self.hashtable = lltype.malloc(self.HASHTABLE,
                                        self.HASHTABLE_SIZE+1,
-                                       flavor='raw')
+                                       flavor='raw', track_allocation=False)
         dummy = lltype.direct_ptradd(lltype.direct_arrayitems(self.hashtable),
                                      self.HASHTABLE_SIZE)
         dummy = llmemory.cast_ptr_to_adr(dummy)
@@ -252,14 +252,15 @@
 
     def _enlarge_gcmap(self):
         newlength = 250 + self._gcmap_maxlength * 2
-        newgcmap = lltype.malloc(self.GCMAP_ARRAY, newlength, flavor='raw')
+        newgcmap = lltype.malloc(self.GCMAP_ARRAY, newlength, flavor='raw',
+                                 track_allocation=False)
         oldgcmap = self._gcmap
         for i in range(self._gcmap_curlength):
             newgcmap[i] = oldgcmap[i]
         self._gcmap = newgcmap
         self._gcmap_maxlength = newlength
         if oldgcmap:
-            lltype.free(oldgcmap, flavor='raw')
+            lltype.free(oldgcmap, flavor='raw', track_allocation=False)
 
     def get_basic_shape(self, is_64_bit=False):
         # XXX: Should this code even really know about stack frame layout of
@@ -308,7 +309,8 @@
         # them inside bigger arrays) and we never try to share them.
         length = len(shape)
         compressed = lltype.malloc(self.CALLSHAPE_ARRAY, length,
-                                   flavor='raw')
+                                   flavor='raw',
+                                   track_allocation=False)   # memory leak
         for i in range(length):
             compressed[length-1-i] = rffi.cast(rffi.UCHAR, shape[i])
         return llmemory.cast_ptr_to_adr(compressed)

Modified: pypy/branch/leak-finder/pypy/jit/backend/llsupport/llmodel.py
==============================================================================
--- pypy/branch/leak-finder/pypy/jit/backend/llsupport/llmodel.py	(original)
+++ pypy/branch/leak-finder/pypy/jit/backend/llsupport/llmodel.py	Fri Oct 15 14:37:16 2010
@@ -82,7 +82,8 @@
         # read back by the machine code reading at the address given by
         # pos_exception() and pos_exc_value().
         _exception_emulator = lltype.malloc(rffi.CArray(lltype.Signed), 2,
-                                            zero=True, flavor='raw')
+                                            zero=True, flavor='raw',
+                                            immortal=True)
         self._exception_emulator = _exception_emulator
 
         def _store_exception(lle):

Modified: pypy/branch/leak-finder/pypy/jit/backend/test/runner_test.py
==============================================================================
--- pypy/branch/leak-finder/pypy/jit/backend/test/runner_test.py	(original)
+++ pypy/branch/leak-finder/pypy/jit/backend/test/runner_test.py	Fri Oct 15 14:37:16 2010
@@ -1304,6 +1304,7 @@
                                    descr=fd)
             res = self.execute_operation(get_op, [s_box], 'int', descr=fd)
             assert res.getint()  == 32
+        lltype.free(s, flavor='raw')
 
     def test_new_with_vtable(self):
         cpu = self.cpu

Modified: pypy/branch/leak-finder/pypy/jit/backend/x86/assembler.py
==============================================================================
--- pypy/branch/leak-finder/pypy/jit/backend/x86/assembler.py	(original)
+++ pypy/branch/leak-finder/pypy/jit/backend/x86/assembler.py	Fri Oct 15 14:37:16 2010
@@ -249,7 +249,8 @@
 
     def _build_float_constants(self):
         # 44 bytes: 32 bytes for the data, and up to 12 bytes for alignment
-        addr = lltype.malloc(rffi.CArray(lltype.Char), 44, flavor='raw')
+        addr = lltype.malloc(rffi.CArray(lltype.Char), 44, flavor='raw',
+                             track_allocation=False)
         if not we_are_translated():
             self._keepalive_malloced_float_consts = addr
         float_constants = rffi.cast(lltype.Signed, addr)
@@ -399,7 +400,8 @@
             funcname = "<loop %d>" % len(self.loop_run_counters)
         # invent the counter, so we don't get too confused
         if self._debug:
-            struct = lltype.malloc(DEBUG_COUNTER, flavor='raw')
+            struct = lltype.malloc(DEBUG_COUNTER, flavor='raw',
+                                   track_allocation=False)   # known to leak
             struct.i = 0
             self.loop_run_counters.append((len(self.loop_run_counters), struct))
         return funcname

Modified: pypy/branch/leak-finder/pypy/jit/backend/x86/regalloc.py
==============================================================================
--- pypy/branch/leak-finder/pypy/jit/backend/x86/regalloc.py	(original)
+++ pypy/branch/leak-finder/pypy/jit/backend/x86/regalloc.py	Fri Oct 15 14:37:16 2010
@@ -70,8 +70,9 @@
 
     def _get_new_array(self):
         n = self.BASE_CONSTANT_SIZE
+        # known to leak
         self.cur_array = lltype.malloc(rffi.CArray(lltype.Float), n,
-                                       flavor='raw')
+                                       flavor='raw', track_allocation=False)
         self.cur_array_free = n
     _get_new_array._dont_inline_ = True
 

Modified: pypy/branch/leak-finder/pypy/jit/backend/x86/runner.py
==============================================================================
--- pypy/branch/leak-finder/pypy/jit/backend/x86/runner.py	(original)
+++ pypy/branch/leak-finder/pypy/jit/backend/x86/runner.py	Fri Oct 15 14:37:16 2010
@@ -113,7 +113,8 @@
         return CPU386.cast_adr_to_int(adr)
 
     all_null_registers = lltype.malloc(rffi.LONGP.TO, 24,
-                                       flavor='raw', zero=True)
+                                       flavor='raw', zero=True,
+                                       immortal=True)
 
     def force(self, addr_of_force_index):
         TP = rffi.CArrayPtr(lltype.Signed)

Modified: pypy/branch/leak-finder/pypy/jit/backend/x86/test/test_assembler.py
==============================================================================
--- pypy/branch/leak-finder/pypy/jit/backend/x86/test/test_assembler.py	(original)
+++ pypy/branch/leak-finder/pypy/jit/backend/x86/test/test_assembler.py	Fri Oct 15 14:37:16 2010
@@ -81,7 +81,8 @@
 
     # also test rebuild_faillocs_from_descr(), which should not
     # reproduce the holes at all
-    bytecode = lltype.malloc(rffi.UCHARP.TO, len(mc.content), flavor='raw')
+    bytecode = lltype.malloc(rffi.UCHARP.TO, len(mc.content), flavor='raw',
+                             immortal=True)
     for i in range(len(mc.content)):
         assert 0 <= mc.content[i] <= 255
         bytecode[i] = rffi.cast(rffi.UCHAR, mc.content[i])
@@ -115,7 +116,8 @@
         assert withfloats
         value = random.random() - 0.5
         # make sure it fits into 64 bits
-        tmp = lltype.malloc(rffi.LONGP.TO, 2, flavor='raw')
+        tmp = lltype.malloc(rffi.LONGP.TO, 2, flavor='raw',
+                            track_allocation=False)
         rffi.cast(rffi.DOUBLEP, tmp)[0] = value
         return rffi.cast(rffi.DOUBLEP, tmp)[0], tmp[0], tmp[1]
 
@@ -152,10 +154,12 @@
 
     # prepare the expected target arrays, the descr_bytecode,
     # the 'registers' and the 'stack' arrays according to 'content'
-    xmmregisters = lltype.malloc(rffi.LONGP.TO, 16+ACTUAL_CPU.NUM_REGS+1, flavor='raw')
+    xmmregisters = lltype.malloc(rffi.LONGP.TO, 16+ACTUAL_CPU.NUM_REGS+1,
+                                 flavor='raw', immortal=True)
     registers = rffi.ptradd(xmmregisters, 16)
     stacklen = baseloc + 10
-    stack = lltype.malloc(rffi.LONGP.TO, stacklen, flavor='raw')
+    stack = lltype.malloc(rffi.LONGP.TO, stacklen, flavor='raw',
+                          immortal=True)
     expected_ints = [0] * len(content)
     expected_ptrs = [lltype.nullptr(llmemory.GCREF.TO)] * len(content)
     expected_floats = [0.0] * len(content)
@@ -221,7 +225,7 @@
     descr_bytecode.append(0x00)
     descr_bytecode.append(0xCC)   # end marker
     descr_bytes = lltype.malloc(rffi.UCHARP.TO, len(descr_bytecode),
-                                flavor='raw')
+                                flavor='raw', immortal=True)
     for i in range(len(descr_bytecode)):
         assert 0 <= descr_bytecode[i] <= 255
         descr_bytes[i] = rffi.cast(rffi.UCHAR, descr_bytecode[i])

Modified: pypy/branch/leak-finder/pypy/jit/metainterp/test/test_basic.py
==============================================================================
--- pypy/branch/leak-finder/pypy/jit/metainterp/test/test_basic.py	(original)
+++ pypy/branch/leak-finder/pypy/jit/metainterp/test/test_basic.py	Fri Oct 15 14:37:16 2010
@@ -1751,7 +1751,7 @@
             c = bool(p1)
             d = not bool(p2)
             return 1000*a + 100*b + 10*c + d
-        prebuilt = [lltype.malloc(TP, flavor='raw')] * 2
+        prebuilt = [lltype.malloc(TP, flavor='raw', immortal=True)] * 2
         expected = f(0, 1)
         assert self.interp_operations(f, [0, 1]) == expected
 

Modified: pypy/branch/leak-finder/pypy/jit/metainterp/virtualref.py
==============================================================================
--- pypy/branch/leak-finder/pypy/jit/metainterp/virtualref.py	(original)
+++ pypy/branch/leak-finder/pypy/jit/metainterp/virtualref.py	Fri Oct 15 14:37:16 2010
@@ -16,7 +16,8 @@
             ('virtualref_index', lltype.Signed),
             ('forced', rclass.OBJECTPTR))
         self.jit_virtual_ref_vtable = lltype.malloc(rclass.OBJECT_VTABLE,
-                                                    zero=True, flavor='raw')
+                                                    zero=True, flavor='raw',
+                                                    immortal=True)
         self.jit_virtual_ref_vtable.name = rclass.alloc_array_name(
             'jit_virtual_ref')
         # build some constants

Modified: pypy/branch/leak-finder/pypy/module/cpyext/test/test_cpyext.py
==============================================================================
--- pypy/branch/leak-finder/pypy/module/cpyext/test/test_cpyext.py	(original)
+++ pypy/branch/leak-finder/pypy/module/cpyext/test/test_cpyext.py	Fri Oct 15 14:37:16 2010
@@ -126,17 +126,17 @@
         for w_obj in lost_objects_w:
             print >>sys.stderr, "Lost object %r" % (w_obj, )
             leaking = True
-        for llvalue in set(ll2ctypes.ALLOCATED.values()) - self.frozen_ll2callocations:
-            if getattr(llvalue, "_traceback", None): # this means that the allocation should be tracked
-                leaking = True
-                print >>sys.stderr, "Did not deallocate %r (ll2ctypes)" % (llvalue, )
-                print >>sys.stderr, "\t" + "\n\t".join(llvalue._traceback.splitlines())
-        for llvalue in lltype.ALLOCATED.keys():
+##        for llvalue in set(ll2ctypes.ALLOCATED.values()) - self.frozen_ll2callocations:
+##            if llvalue in lltype.ALLOCATED:
+##                leaking = True
+##                print >>sys.stderr, "Did not deallocate %r (ll2ctypes)" % (llvalue, )
+##                print >>sys.stderr, "\t" + "\n\t".join(llvalue._traceback.splitlines())
+        for llvalue, traceback in lltype.ALLOCATED.items():
             leaking = True
             print >>sys.stderr, "Did not deallocate %r (llvalue)" % (llvalue, )
-            print >>sys.stderr, "\t" + "\n\t".join(llvalue._traceback.splitlines())
+            print >>sys.stderr, "\t" + "\n\t".join(traceback.splitlines())
 
-        lltype.stop_tracking_allocations()
+        lltype.stop_tracking_allocations(check=False)
         return leaking
 
 class AppTestCpythonExtensionBase(LeakCheckingTest):

Modified: pypy/branch/leak-finder/pypy/rlib/test/test_rdynload.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rlib/test/test_rdynload.py	(original)
+++ pypy/branch/leak-finder/pypy/rlib/test/test_rdynload.py	Fri Oct 15 14:37:16 2010
@@ -5,11 +5,18 @@
 
 class TestDLOperations:
     def test_dlopen(self):
-        py.test.raises(DLOpenError, "dlopen(rffi.str2charp('xxxxxxxxxxxx'))")
-        assert dlopen(rffi.str2charp(get_libc_name()))
+        s = rffi.str2charp('xxxxxxxxxxxx')
+        py.test.raises(DLOpenError, "dlopen(s)")
+        rffi.free_charp(s)
+        #
+        s = rffi.str2charp(get_libc_name())
+        assert dlopen(s)
+        rffi.free_charp(s)
 
     def test_dlsym(self):
-        lib = dlopen(rffi.str2charp(get_libc_name()))
+        s = rffi.str2charp(get_libc_name())
+        lib = dlopen(s)
+        rffi.free_charp(s)
         handle = rffi.cast(lltype.Ptr(lltype.FuncType([lltype.Signed],
                            lltype.Signed)), dlsym(lib, 'abs'))
         assert 1 == handle(1)

Modified: pypy/branch/leak-finder/pypy/rlib/test/test_rzlib.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rlib/test/test_rzlib.py	(original)
+++ pypy/branch/leak-finder/pypy/rlib/test/test_rzlib.py	Fri Oct 15 14:37:16 2010
@@ -189,6 +189,8 @@
     assert unused3 == len('more_garbage')
     assert data3 == ''
 
+    rzlib.deflateEnd(stream)
+
 
 def test_decompress_max_length():
     """
@@ -205,6 +207,8 @@
     assert finished2 is True
     assert unused2 == 0
 
+    rzlib.deflateEnd(stream)
+
 
 def test_cornercases():
     """
@@ -215,6 +219,7 @@
     bytes += rzlib.compress(stream, "")
     bytes += rzlib.compress(stream, "", rzlib.Z_FINISH)
     assert zlib.decompress(bytes) == ""
+    rzlib.deflateEnd(stream)
 
     stream = rzlib.inflateInit()
     data, finished, unused = rzlib.decompress(stream, "")
@@ -228,3 +233,4 @@
         assert finished is False
         assert unused > 0
         buf = buf[-unused:]
+    rzlib.deflateEnd(stream)

Modified: pypy/branch/leak-finder/pypy/rpython/llinterp.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/llinterp.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/llinterp.py	Fri Oct 15 14:37:16 2010
@@ -48,8 +48,7 @@
 
     current_interpreter = None
 
-    def __init__(self, typer, tracing=True, exc_data_ptr=None,
-                 malloc_check=True):
+    def __init__(self, typer, tracing=True, exc_data_ptr=None):
         self.bindings = {}
         self.typer = typer
         # 'heap' is module or object that provides malloc, etc for lltype ops
@@ -57,9 +56,7 @@
         self.exc_data_ptr = exc_data_ptr
         self.frame_stack = []
         self.tracer = None
-        self.malloc_check = malloc_check
         self.frame_class = LLFrame
-        self.mallocs = {}
         if tracing:
             self.tracer = Tracer()
 
@@ -163,24 +160,6 @@
             return self.exc_data_ptr
         return None
 
-    def remember_malloc(self, ptr, llframe):
-        # err....
-        self.mallocs[ptr._obj] = llframe
-
-    def remember_free(self, ptr):
-        try:
-            del self.mallocs[ptr._obj]
-        except KeyError:
-            self._rehash_mallocs()
-            del self.mallocs[ptr._obj]
-
-    def _rehash_mallocs(self):
-        # rehashing is needed because some objects' hash may change
-        # when being turned to <C object>
-        items = self.mallocs.items()
-        self.mallocs = {}
-        self.mallocs.update(items)
-
     def _store_exception(self, exc):
         raise PleaseOverwriteStoreException("You just invoked ll2ctypes callback without overwriting _store_exception on llinterpreter")
 
@@ -726,13 +705,13 @@
     def op_malloc(self, obj, flags):
         flavor = flags['flavor']
         zero = flags.get('zero', False)
+        track_allocation = flags.get('track_allocation', True)
         if flavor == "stack":
             result = self.heap.malloc(obj, zero=zero, flavor='raw')
             self.alloca_objects.append(result)
             return result
-        ptr = self.heap.malloc(obj, zero=zero, flavor=flavor)
-        if flavor == 'raw' and self.llinterpreter.malloc_check:
-            self.llinterpreter.remember_malloc(ptr, self)
+        ptr = self.heap.malloc(obj, zero=zero, flavor=flavor,
+                               track_allocation=track_allocation)
         return ptr
 
     def op_malloc_varsize(self, obj, flags, size):
@@ -741,8 +720,6 @@
         assert flavor in ('gc', 'raw')
         try:
             ptr = self.heap.malloc(obj, size, zero=zero, flavor=flavor)
-            if flavor == 'raw' and self.llinterpreter.malloc_check:
-                self.llinterpreter.remember_malloc(ptr, self)
             return ptr
         except MemoryError:
             self.make_llexception()
@@ -759,11 +736,10 @@
         zero = flags.get('zero', False)
         return self.heap.malloc_nonmovable(TYPE, size, zero=zero)
 
-    def op_free(self, obj, flavor):
-        assert isinstance(flavor, str)
-        if flavor == 'raw' and self.llinterpreter.malloc_check:
-            self.llinterpreter.remember_free(obj)
-        self.heap.free(obj, flavor=flavor)
+    def op_free(self, obj, flags):
+        assert flags['flavor'] == 'raw'
+        track_allocation = flags.get('track_allocation', True)
+        self.heap.free(obj, flavor='raw', track_allocation=track_allocation)
 
     def op_shrink_array(self, obj, smallersize):
         return self.heap.shrink_array(obj, smallersize)

Modified: pypy/branch/leak-finder/pypy/rpython/lltypesystem/llmemory.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/lltypesystem/llmemory.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/lltypesystem/llmemory.py	Fri Oct 15 14:37:16 2010
@@ -105,11 +105,13 @@
         if (isinstance(self.TYPE, lltype.ContainerType)
             and self.TYPE._gckind == 'gc'):
             assert self.repeat == 1
-            p = lltype.malloc(self.TYPE, flavor='raw', zero=zero)
+            p = lltype.malloc(self.TYPE, flavor='raw', zero=zero,
+                              track_allocation=False)
             return cast_ptr_to_adr(p)
         else:
             T = lltype.FixedSizeArray(self.TYPE, self.repeat)
-            p = lltype.malloc(T, flavor='raw', zero=zero)
+            p = lltype.malloc(T, flavor='raw', zero=zero,
+                              track_allocation=False)
             array_adr = cast_ptr_to_adr(p)
             return array_adr + ArrayItemsOffset(T)
 
@@ -288,7 +290,8 @@
             count = 0
         p = lltype.malloc(parenttype or self.TYPE, count,
                           immortal = self.TYPE._gckind == 'raw',
-                          zero = zero)
+                          zero = zero,
+                          track_allocation = False)
         return cast_ptr_to_adr(p)
 
     def raw_memcopy(self, srcadr, dstadr):

Modified: pypy/branch/leak-finder/pypy/rpython/lltypesystem/lltype.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/lltypesystem/lltype.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/lltypesystem/lltype.py	Fri Oct 15 14:37:16 2010
@@ -1,7 +1,3 @@
-import StringIO
-import traceback
-import sys
-
 import py
 from pypy.rlib.rarithmetic import (r_int, r_uint, intmask, r_singlefloat,
                                    r_ulonglong, r_longlong, base_int,
@@ -10,25 +6,13 @@
 from pypy.tool.uid import Hashable
 from pypy.tool.tls import tlsobject
 from pypy.tool.identity_dict import identity_dict
+from pypy.tool import leakfinder
 from types import NoneType
 from sys import maxint
 import weakref
 
 TLS = tlsobject()
 
-# Track allocations to detect memory leaks
-# Don't track 'gc' and immortal mallocs
-TRACK_ALLOCATIONS = False
-ALLOCATED = identity_dict()
-
-def start_tracking_allocations():
-    global TRACK_ALLOCATIONS
-    TRACK_ALLOCATIONS = True
-    ALLOCATED.clear()
-
-def stop_tracking_allocations():
-    global TRACK_ALLOCATIONS
-    TRACK_ALLOCATIONS = False
 
 class _uninitialized(object):
     def __init__(self, TYPE):
@@ -1380,41 +1364,21 @@
     __slots__ = ('_TYPE',
                  '_parent_type', '_parent_index', '_keepparent',
                  '_wrparent',
-                 '__weakref__', '_traceback',
-                 '__storage')
+                 '__weakref__',
+                 '_storage')
 
-    def __init__(self, TYPE, track_allocation=None):
+    def __init__(self, TYPE):
         self._wrparent = None
         self._TYPE = TYPE
         self._storage = True    # means "use default storage", as opposed to:
                                 #    None            - container was freed
                                 #    <ctypes object> - using ctypes
                                 #                      (see ll2ctypes.py)
-        if track_allocation is not False and TRACK_ALLOCATIONS:
-            self._traceback = self._get_traceback()
-            ALLOCATED[self] = None
-        else:
-            self._traceback = None
-
-    def _get_traceback(self):
-        frame = sys._getframe().f_back.f_back.f_back.f_back
-        sio = StringIO.StringIO()
-        traceback.print_stack(frame, file=sio)
-        return sio.getvalue()
 
     def _free(self):
         self._check()   # no double-frees
         self._storage = None
 
-    def _storage_get(self):
-        return self.__storage
-
-    def _storage_set(self, value):
-        self.__storage = value
-        if value is not True and self in ALLOCATED:
-            del ALLOCATED[self]
-    _storage = property(_storage_get, _storage_set)
-
     def _was_freed(self):
         if self._storage is None:
             return True
@@ -1493,12 +1457,12 @@
 
     __slots__ = ('_hash_cache_', '_compilation_info')
 
-    def __new__(self, TYPE, n=None, initialization=None, parent=None, parentindex=None, track_allocation=None):
+    def __new__(self, TYPE, n=None, initialization=None, parent=None, parentindex=None):
         my_variety = _struct_variety(TYPE._names)
         return object.__new__(my_variety)
 
-    def __init__(self, TYPE, n=None, initialization=None, parent=None, parentindex=None, track_allocation=None):
-        _parentable.__init__(self, TYPE, track_allocation)
+    def __init__(self, TYPE, n=None, initialization=None, parent=None, parentindex=None):
+        _parentable.__init__(self, TYPE)
         if n is not None and TYPE._arrayfld is None:
             raise TypeError("%r is not variable-sized" % (TYPE,))
         if n is None and TYPE._arrayfld is not None:
@@ -1506,8 +1470,7 @@
         first, FIRSTTYPE = TYPE._first_struct()
         for fld, typ in TYPE._flds.items():
             if fld == TYPE._arrayfld:
-                value = _array(typ, n, initialization=initialization, parent=self, parentindex=fld,
-                               track_allocation=track_allocation)
+                value = _array(typ, n, initialization=initialization, parent=self, parentindex=fld)
             else:
                 value = typ._allocate(initialization=initialization, parent=self, parentindex=fld)
             setattr(self, fld, value)
@@ -1568,12 +1531,12 @@
 
     __slots__ = ('items',)
 
-    def __init__(self, TYPE, n, initialization=None, parent=None, parentindex=None, track_allocation=None):
+    def __init__(self, TYPE, n, initialization=None, parent=None, parentindex=None):
         if not isinstance(n, int):
             raise TypeError, "array length must be an int"
         if n < 0:
             raise ValueError, "negative array length"
-        _parentable.__init__(self, TYPE, track_allocation)
+        _parentable.__init__(self, TYPE)
         try:
             myrange = range(n)
         except OverflowError:
@@ -1640,7 +1603,7 @@
     _cache = weakref.WeakKeyDictionary()  # parentarray -> {subarrays}
 
     def __init__(self, TYPE, parent, baseoffset_or_fieldname):
-        _parentable.__init__(self, TYPE, track_allocation=False)
+        _parentable.__init__(self, TYPE)
         self._setparentstructure(parent, baseoffset_or_fieldname)
         # Keep the parent array alive, we share the same allocation.
         # Don't do it if we are inside a GC object, though -- it's someone
@@ -1648,6 +1611,13 @@
         if typeOf(top_container(parent))._gckind == 'raw':
             self._keepparent = parent
 
+    def __str__(self):
+        parent = self._wrparent()
+        if parent is None:
+            return '_subarray at %s in already freed' % (self._parent_index,)
+        return '_subarray at %r in %s' % (self._parent_index,
+                                          parent._TYPE)
+
     def __repr__(self):
         parent = self._wrparent()
         if parent is None:
@@ -1861,8 +1831,9 @@
         return id(self.value)
 
 
-def malloc(T, n=None, flavor='gc', immortal=False, zero=False):
-    assert flavor != 'cpy'
+def malloc(T, n=None, flavor='gc', immortal=False, zero=False,
+           track_allocation=True):
+    assert flavor in ('gc', 'raw')
     if zero or immortal:
         initialization = 'example'
     elif flavor == 'raw':
@@ -1870,9 +1841,9 @@
     else:
         initialization = 'malloc'
     if isinstance(T, Struct):
-        o = _struct(T, n, initialization=initialization, track_allocation=flavor == "raw" and not immortal)
+        o = _struct(T, n, initialization=initialization)
     elif isinstance(T, Array):
-        o = _array(T, n, initialization=initialization, track_allocation=flavor == "raw" and not immortal)
+        o = _array(T, n, initialization=initialization)
     elif isinstance(T, OpaqueType):
         assert n is None
         o = _opaque(T, initialization=initialization)
@@ -1880,15 +1851,19 @@
         raise TypeError, "malloc for Structs and Arrays only"
     if T._gckind != 'gc' and not immortal and flavor.startswith('gc'):
         raise TypeError, "gc flavor malloc of a non-GC non-immortal structure"
+    if flavor == "raw" and not immortal and track_allocation:
+        leakfinder.remember_malloc(o, framedepth=2)
     solid = immortal or not flavor.startswith('gc') # immortal or non-gc case
     return _ptr(Ptr(T), o, solid)
 
-def free(p, flavor):
+def free(p, flavor, track_allocation=True):
     if flavor.startswith('gc'):
         raise TypeError, "gc flavor free"
     T = typeOf(p)
     if not isinstance(T, Ptr) or p._togckind() != 'raw':
         raise TypeError, "free(): only for pointers to non-gc containers"
+    if track_allocation:
+        leakfinder.remember_free(p._obj0)
     p._obj0._free()
 
 def functionptr(TYPE, name, **attrs):

Modified: pypy/branch/leak-finder/pypy/rpython/lltypesystem/rclass.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/lltypesystem/rclass.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/lltypesystem/rclass.py	Fri Oct 15 14:37:16 2010
@@ -420,7 +420,7 @@
         return cast_pointer(self.lowleveltype, result)
 
     def create_instance(self):
-        return malloc(self.object_type, flavor=self.gcflavor)
+        return malloc(self.object_type, flavor=self.gcflavor, immortal=True)
 
     def initialize_prebuilt_data(self, value, classdef, result):
         if self.classdef is not None:

Modified: pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_ll2ctypes.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_ll2ctypes.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_ll2ctypes.py	Fri Oct 15 14:37:16 2010
@@ -765,6 +765,7 @@
         assert abs(float(b[1]) - 1.1) < 1E-6
         assert isinstance(b[2], rffi.r_singlefloat)
         assert abs(float(b[2]) - 2.2) < 1E-6
+        lltype.free(a, flavor='raw')
 
     def test_different_signatures(self):
         if sys.platform=='win32':
@@ -879,6 +880,7 @@
         qsort(rffi.cast(rffi.VOIDP, a), 5, rffi.sizeof(rffi.INT), compare)
         for i in range(5):
             assert a[i] == i + 1
+        lltype.free(a, flavor='raw')
 
     def test_array_type_bug(self):
         A = lltype.Array(lltype.Signed)

Modified: pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_llmemory.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_llmemory.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_llmemory.py	Fri Oct 15 14:37:16 2010
@@ -324,12 +324,14 @@
     p_t = lltype.malloc(T)
     assert p_t.s == lltype.nullptr(S)
     # raw malloc does not
-    p_raw_t = lltype.malloc(T, flavor="raw")
-    py.test.raises(lltype.UninitializedMemoryAccess, "p_raw_t.s")
+    U = lltype.Struct("U", ('x', lltype.Signed))
+    p_raw_t = lltype.malloc(U, flavor="raw")
+    py.test.raises(lltype.UninitializedMemoryAccess, "p_raw_t.x")
+    lltype.free(p_raw_t, flavor="raw")
     # this sort of raw_malloc too
-    p_raw_t = cast_adr_to_ptr(raw_malloc(sizeof(T)), lltype.Ptr(T))
-    py.test.raises(lltype.UninitializedMemoryAccess, "p_raw_t.s")
-    
+    p_raw_t = cast_adr_to_ptr(raw_malloc(sizeof(U)), lltype.Ptr(U))
+    py.test.raises(lltype.UninitializedMemoryAccess, "p_raw_t.x")
+
 
 def test_raw_malloc_signed_bunch():
     adr = raw_malloc(sizeof(lltype.Signed) * 50)
@@ -601,7 +603,8 @@
     a = lltype.malloc(A, flavor='raw')
     src = cast_ptr_to_adr(a) + itemoffsetof(A, 0)
     raw_memclear(src, sizeof(lltype.Signed) * 0)
-    
+    lltype.free(a, flavor="raw")
+
 def test_nonneg():
     S1 = lltype.GcStruct('S1', ('x', lltype.Float))
     A1 = lltype.GcArray(lltype.Float)

Modified: pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_lltype.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_lltype.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_lltype.py	Fri Oct 15 14:37:16 2010
@@ -2,6 +2,7 @@
 from pypy.rpython.lltypesystem.lltype import *
 from pypy.rpython.lltypesystem import lltype, rffi
 from pypy.tool.identity_dict import identity_dict
+from pypy.tool import leakfinder
 
 def isweak(p, T):
     try:
@@ -804,22 +805,20 @@
 
 
 class TestTrackAllocation:
-    def setup_method(self, func):
-        start_tracking_allocations()
-
-    def teardown_method(self, func):
-        assert not lltype.ALLOCATED, "Memory was not correctly freed"
-        stop_tracking_allocations()
+    def test_automatic_tracking(self):
+        # calls to start_tracking_allocations/stop_tracking_allocations
+        # should occur automatically from pypy/conftest.py.  Check that.
+        assert leakfinder.TRACK_ALLOCATIONS
 
     def test_track_allocation(self):
         """A malloc'd buffer fills the ALLOCATED dictionary"""
-        assert lltype.TRACK_ALLOCATIONS
-        assert not lltype.ALLOCATED
+        assert leakfinder.TRACK_ALLOCATIONS
+        assert not leakfinder.ALLOCATED
         buf = malloc(Array(Signed), 1, flavor="raw")
-        assert len(lltype.ALLOCATED) == 1
-        assert lltype.ALLOCATED.keys() == [buf._obj]
+        assert len(leakfinder.ALLOCATED) == 1
+        assert leakfinder.ALLOCATED.keys() == [buf._obj]
         free(buf, flavor="raw")
-        assert not lltype.ALLOCATED
+        assert not leakfinder.ALLOCATED
 
     def test_str_from_buffer(self):
         """gc-managed memory does not need to be freed"""
@@ -828,16 +827,22 @@
         for i in range(size): raw_buf[i] = 'a'
         rstr = rffi.str_from_buffer(raw_buf, gc_buf, size, size)
         rffi.keep_buffer_alive_until_here(raw_buf, gc_buf)
-        assert not lltype.ALLOCATED
+        assert not leakfinder.ALLOCATED
 
     def test_leak_traceback(self):
         """Test info stored for allocated items"""
         buf = malloc(Array(Signed), 1, flavor="raw")
-        traceback = lltype.ALLOCATED.keys()[0]._traceback
+        traceback = leakfinder.ALLOCATED.values()[0]
         lines = traceback.splitlines()
         assert 'malloc(' in lines[-1] and 'flavor="raw")' in lines[-1]
 
-        # XXX The traceback should not be too long
+        # The traceback should not be too long
         print traceback
 
         free(buf, flavor="raw")
+
+    def test_no_tracking(self):
+        p1 = malloc(Array(Signed), 1, flavor='raw', track_allocation=False)
+        p2 = malloc(Array(Signed), 1, flavor='raw', track_allocation=False)
+        free(p2, flavor='raw', track_allocation=False)
+        # p1 is not freed

Modified: pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_rffi.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_rffi.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/lltypesystem/test/test_rffi.py	Fri Oct 15 14:37:16 2010
@@ -9,7 +9,7 @@
 from pypy.rpython.lltypesystem.rstr import STR
 from pypy.rpython.lltypesystem import lltype
 from pypy.tool.udir import udir
-from pypy.rpython.test.test_llinterp import interpret, MallocMismatch
+from pypy.rpython.test.test_llinterp import interpret
 from pypy.rpython.test.tool import BaseRtypingTest, LLRtypeMixin, OORtypeMixin
 from pypy.annotation.annrpython import RPythonAnnotator
 from pypy.rpython.rtyper import RPythonTyper

Modified: pypy/branch/leak-finder/pypy/rpython/memory/gctransform/transform.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/memory/gctransform/transform.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/memory/gctransform/transform.py	Fri Oct 15 14:37:16 2010
@@ -430,7 +430,8 @@
         return self.parenttransformer.gct_malloc_varsize(hop)
     
     def gct_free(self, hop):
-        flavor = hop.spaceop.args[1].value
+        flags = hop.spaceop.args[1].value
+        flavor = flags['flavor']
         assert flavor == 'raw'
         return self.parenttransformer.gct_free(hop)
 
@@ -606,7 +607,8 @@
 
     def gct_free(self, hop):
         op = hop.spaceop
-        flavor = op.args[1].value
+        flags = op.args[1].value
+        flavor = flags['flavor']
         v = op.args[0]
         assert flavor != 'cpy', "cannot free CPython objects directly"
         if flavor == 'raw':

Modified: pypy/branch/leak-finder/pypy/rpython/memory/gcwrapper.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/memory/gcwrapper.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/memory/gcwrapper.py	Fri Oct 15 14:37:16 2010
@@ -42,7 +42,8 @@
     #
     # Interface for the llinterp
     #
-    def malloc(self, TYPE, n=None, flavor='gc', zero=False):
+    def malloc(self, TYPE, n=None, flavor='gc', zero=False,
+               track_allocation=True):
         if flavor == 'gc':
             typeid = self.get_type_id(TYPE)
             addr = self.gc.malloc(typeid, n, zero=zero)
@@ -51,7 +52,8 @@
                 gctypelayout.zero_gc_pointers(result)
             return result
         else:
-            return lltype.malloc(TYPE, n, flavor=flavor, zero=zero)
+            return lltype.malloc(TYPE, n, flavor=flavor, zero=zero,
+                                 track_allocation=track_allocation)
 
     def malloc_nonmovable(self, TYPE, n=None, zero=False):
         typeid = self.get_type_id(TYPE)
@@ -69,9 +71,10 @@
             return self.gc.shrink_array(addr, smallersize)
         return False
 
-    def free(self, TYPE, flavor='gc'):
+    def free(self, TYPE, flavor='gc', track_allocation=True):
         assert flavor != 'gc'
-        return lltype.free(TYPE, flavor=flavor)
+        return lltype.free(TYPE, flavor=flavor,
+                           track_allocation=track_allocation)
 
     def setfield(self, obj, fieldname, fieldvalue):
         STRUCT = lltype.typeOf(obj).TO

Modified: pypy/branch/leak-finder/pypy/rpython/memory/support.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/memory/support.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/memory/support.py	Fri Oct 15 14:37:16 2010
@@ -30,7 +30,8 @@
                 # we zero-initialize the chunks to make the translation
                 # backends happy, but we don't need to do it at run-time.
                 zero = not we_are_translated()
-                return lltype.malloc(CHUNK, flavor="raw", zero=zero)
+                return lltype.malloc(CHUNK, flavor="raw", zero=zero,
+                                     track_allocation=False)
                 
             result = self.free_list
             self.free_list = result.next
@@ -44,7 +45,7 @@
                 # Don't cache the old chunks but free them immediately.
                 # Helps debugging, and avoids that old chunks full of
                 # addresses left behind by a test end up in genc...
-                lltype.free(chunk, flavor="raw")
+                lltype.free(chunk, flavor="raw", track_allocation=False)
 
     unused_chunks = FreeList()
     cache[chunk_size] = unused_chunks, null_chunk

Modified: pypy/branch/leak-finder/pypy/rpython/rbuiltin.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/rbuiltin.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/rbuiltin.py	Fri Oct 15 14:37:16 2010
@@ -345,17 +345,22 @@
 BUILTIN_TYPER[object.__init__] = rtype_object__init__
 # annotation of low-level types
 
-def rtype_malloc(hop, i_flavor=None, i_zero=None):
+def rtype_malloc(hop, i_flavor=None, i_zero=None, i_track_allocation=None):
     assert hop.args_s[0].is_constant()
     vlist = [hop.inputarg(lltype.Void, arg=0)]
     opname = 'malloc'
-    v_flavor, v_zero = parse_kwds(hop, (i_flavor, lltype.Void), (i_zero, None))
+    v_flavor, v_zero, v_track_allocation = parse_kwds(hop,
+        (i_flavor, lltype.Void),
+        (i_zero, None),
+        (i_track_allocation, None))
 
     flags = {'flavor': 'gc'}
     if v_flavor is not None:
         flags['flavor'] = v_flavor.value
     if i_zero is not None:
         flags['zero'] = v_zero.value
+    if i_track_allocation is not None:
+        flags['track_allocation'] = v_track_allocation.value
     vlist.append(hop.inputconst(lltype.Void, flags))
         
     if hop.nb_args == 2:
@@ -366,10 +371,19 @@
     hop.exception_is_here()
     return hop.genop(opname, vlist, resulttype = hop.r_result.lowleveltype)
 
-def rtype_free(hop, i_flavor):
-    assert i_flavor == 1
+def rtype_free(hop, i_flavor, i_track_allocation=None):
+    vlist = [hop.inputarg(hop.args_r[0], arg=0)]
+    v_flavor, v_track_allocation = parse_kwds(hop,
+        (i_flavor, lltype.Void),
+        (i_track_allocation, None))
+    #
+    assert v_flavor is not None and v_flavor.value == 'raw'
+    flags = {'flavor': 'raw'}
+    if i_track_allocation is not None:
+        flags['track_allocation'] = v_track_allocation.value
+    vlist.append(hop.inputconst(lltype.Void, flags))
+    #
     hop.exception_cannot_occur()
-    vlist = hop.inputargs(hop.args_r[0], lltype.Void)
     hop.genop('free', vlist)
 
 def rtype_const_result(hop):
@@ -584,8 +598,9 @@
     vinst, = hop.inputargs(hop.args_r[0])
     flavor = hop.args_r[0].gcflavor
     assert flavor != 'gc'
-    cflavor = hop.inputconst(lltype.Void, flavor)
-    return hop.genop('free', [vinst, cflavor])
+    flags = {'flavor': flavor}
+    cflags = hop.inputconst(lltype.Void, flags)
+    return hop.genop('free', [vinst, cflags])
     
 BUILTIN_TYPER[objectmodel.free_non_gc_object] = rtype_free_non_gc_object
 

Modified: pypy/branch/leak-finder/pypy/rpython/test/test_llinterp.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/test/test_llinterp.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/test/test_llinterp.py	Fri Oct 15 14:37:16 2010
@@ -12,13 +12,11 @@
 from pypy.annotation.model import lltype_to_annotation
 from pypy.rlib.rarithmetic import r_uint, ovfcheck
 from pypy.rpython.ootypesystem import ootype
+from pypy.tool import leakfinder
 from pypy import conftest
 
 # switch on logging of interp to show more info on failing tests
 
-class MallocMismatch(Exception):
-    pass
-
 def setup_module(mod):
     mod.logstate = py.log._getstate()
     py.log.setconsumer("llinterp", py.log.STDOUT)
@@ -72,7 +70,7 @@
 
 def get_interpreter(func, values, view='auto', viewbefore='auto', policy=None,
                     someobjects=False, type_system="lltype", backendopt=False,
-                    config=None, malloc_check=True, **extraconfigopts):
+                    config=None, **extraconfigopts):
     extra_key = [(key, value) for key, value in extraconfigopts.iteritems()]
     extra_key.sort()
     extra_key = tuple(extra_key)
@@ -97,7 +95,7 @@
                                    viewbefore, policy, type_system=type_system,
                                    backendopt=backendopt, config=config,
                                    **extraconfigopts)
-        interp = LLInterpreter(typer, malloc_check=malloc_check)
+        interp = LLInterpreter(typer)
         _tcache[key] = (t, interp, graph)
         # keep the cache small 
         _lastinterpreted.append(key) 
@@ -115,10 +113,17 @@
     interp, graph = get_interpreter(func, values, view, viewbefore, policy,
                                     someobjects, type_system=type_system,
                                     backendopt=backendopt, config=config,
-                                    malloc_check=malloc_check, **kwargs)
-    result = interp.eval_graph(graph, values)
-    if malloc_check and interp.mallocs:
-        raise MallocMismatch(interp.mallocs)
+                                    **kwargs)
+    if not malloc_check:
+        result = interp.eval_graph(graph, values)
+    else:
+        prev = leakfinder.start_tracking_allocations()
+        try:
+            result = interp.eval_graph(graph, values)
+        finally:
+            leaks = leakfinder.stop_tracking_allocations(False, prev)
+        if leaks:
+            raise leakfinder.MallocMismatch(leaks)
     return result
 
 def interpret_raises(exc, func, values, view='auto', viewbefore='auto',
@@ -418,6 +423,7 @@
             assert result
 
 def test_stack_malloc():
+    py.test.skip("stack-flavored mallocs no longer supported")
     class A(object):
         pass
     def f():
@@ -430,6 +436,7 @@
     assert result == 1
 
 def test_invalid_stack_access():
+    py.test.skip("stack-flavored mallocs no longer supported")
     class A(object):
         pass
     globala = A()
@@ -605,7 +612,7 @@
         if x:
             free(t, flavor='raw')
     interpret(f, [1])
-    py.test.raises(MallocMismatch, "interpret(f, [0])")
+    py.test.raises(leakfinder.MallocMismatch, "interpret(f, [0])")
     
     def f():
         t1 = malloc(T, flavor='raw')

Modified: pypy/branch/leak-finder/pypy/rpython/test/test_nongc.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/test/test_nongc.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/test/test_nongc.py	Fri Oct 15 14:37:16 2010
@@ -79,7 +79,7 @@
     py.test.raises(TypeError,rtyper.specialize) # results in an invalid cast
 
 def test_isinstance():
-    class A:
+    class A(object):
         _alloc_flavor_ = "raw"
     class B(A):
         pass
@@ -95,7 +95,24 @@
             o = B()
         else:
             o = C()
-        return 100*isinstance(o, A)+10*isinstance(o, B)+1*isinstance(o ,C)
+        res = 100*isinstance(o, A) + 10*isinstance(o, B) + 1*isinstance(o, C)
+        if i == 0:
+            pass
+        elif i == 1:
+            assert isinstance(o, A)
+            free_non_gc_object(o)
+        elif i == 2:
+            assert isinstance(o, B)
+            free_non_gc_object(o)
+        else:
+            assert isinstance(o, C)
+            free_non_gc_object(o)
+        return res
+
+    assert f(1) == 100
+    assert f(2) == 110
+    assert f(3) == 111
+    assert f(0) == 0
 
     a = RPythonAnnotator()
     #does not raise:
@@ -131,10 +148,14 @@
             d = b
         elif i == 2:
             e = c
-        return (0x0001*(a is b) | 0x0002*(a is c) | 0x0004*(a is d) |
+        res =  (0x0001*(a is b) | 0x0002*(a is c) | 0x0004*(a is d) |
                 0x0008*(a is e) | 0x0010*(b is c) | 0x0020*(b is d) |
                 0x0040*(b is e) | 0x0080*(c is d) | 0x0100*(c is e) |
                 0x0200*(d is e))
+        free_non_gc_object(a)
+        free_non_gc_object(b)
+        free_non_gc_object(c)
+        return res
     a = RPythonAnnotator()
     #does not raise:
     s = a.build_types(f, [int])
@@ -169,10 +190,13 @@
             d = b
         elif i == 2:
             e = c
-        return (0x0001*(a is b) | 0x0002*(a is c) | 0x0004*(a is d) |
+        res =  (0x0001*(a is b) | 0x0002*(a is c) | 0x0004*(a is d) |
                 0x0008*(a is e) | 0x0010*(b is c) | 0x0020*(b is d) |
                 0x0040*(b is e) | 0x0080*(c is d) | 0x0100*(c is e) |
                 0x0200*(d is e))
+        free_non_gc_object(a)
+        free_non_gc_object(b)
+        return res
     a = RPythonAnnotator()
     #does not raise:
     s = a.build_types(f, [int])

Modified: pypy/branch/leak-finder/pypy/rpython/test/test_rptr.py
==============================================================================
--- pypy/branch/leak-finder/pypy/rpython/test/test_rptr.py	(original)
+++ pypy/branch/leak-finder/pypy/rpython/test/test_rptr.py	Fri Oct 15 14:37:16 2010
@@ -212,10 +212,31 @@
 
     S = Struct('S', ('x', Signed))
     def fn(n):
-        p = malloc(S, flavor='whatever')
+        p = malloc(S, flavor='raw')
         p.x = n
         result = p.x
-        free(p, flavor='whatever')
+        free(p, flavor='raw')
+        return n
+
+    res = interpret(fn, [23])
+    assert res == 23
+
+    S = Struct('S', ('x', Signed))
+    def fn(n):
+        p = malloc(S, flavor='raw', track_allocation=False)
+        p.x = n
+        result = p.x
+        return n
+
+    res = interpret(fn, [23])
+    assert res == 23
+
+    S = Struct('S', ('x', Signed))
+    def fn(n):
+        p = malloc(S, flavor='raw', track_allocation=False)
+        p.x = n
+        result = p.x
+        free(p, flavor='raw', track_allocation=False)
         return n
 
     res = interpret(fn, [23])

Added: pypy/branch/leak-finder/pypy/tool/leakfinder.py
==============================================================================
--- (empty file)
+++ pypy/branch/leak-finder/pypy/tool/leakfinder.py	Fri Oct 15 14:37:16 2010
@@ -0,0 +1,73 @@
+import sys, gc
+import cStringIO
+import traceback
+
+# Track allocations to detect memory leaks.
+# So far, this is used for lltype.malloc(flavor='raw').
+TRACK_ALLOCATIONS = False
+ALLOCATED = {}
+
+class MallocMismatch(Exception):
+    def __str__(self):
+        dict = self.args[0]
+        dict2 = {}
+        for obj, traceback in dict.items():
+            traceback = traceback.splitlines()
+            if len(traceback) > 8:
+                traceback = ['    ...'] + traceback[-6:]
+            traceback = '\n'.join(traceback)
+            dict2.setdefault(traceback, [])
+            dict2[traceback].append(obj)
+        lines = ['{']
+        for traceback, objs in dict2.items():
+            lines.append('')
+            for obj in objs:
+                lines.append('%s:' % (obj,))
+            lines.append(traceback)
+        lines.append('}')
+        return '\n'.join(lines)
+
+def start_tracking_allocations():
+    global TRACK_ALLOCATIONS
+    if TRACK_ALLOCATIONS:
+        result = ALLOCATED.copy()   # nested start
+    else:
+        result = None
+    TRACK_ALLOCATIONS = True
+    ALLOCATED.clear()
+    return result
+
+def stop_tracking_allocations(check, prev=None):
+    global TRACK_ALLOCATIONS
+    assert TRACK_ALLOCATIONS
+    for i in range(5):
+        if not ALLOCATED:
+            break
+        gc.collect()
+    result = ALLOCATED.copy()
+    ALLOCATED.clear()
+    if prev is None:
+        TRACK_ALLOCATIONS = False
+    else:
+        ALLOCATED.update(prev)
+    if check and result:
+        raise MallocMismatch(result)
+    return result
+
+def remember_malloc(obj, framedepth=1):
+    if TRACK_ALLOCATIONS:
+        frame = sys._getframe(framedepth)
+        sio = cStringIO.StringIO()
+        traceback.print_stack(frame, limit=10, file=sio)
+        tb = sio.getvalue()
+        ALLOCATED[obj] = tb
+
+def remember_free(obj):
+    if TRACK_ALLOCATIONS:
+        if obj not in ALLOCATED:
+            # rehashing is needed because some objects' hash may change
+            # e.g. when lltype objects are turned into <C object>
+            items = ALLOCATED.items()
+            ALLOCATED.clear()
+            ALLOCATED.update(items)
+        del ALLOCATED[obj]

Added: pypy/branch/leak-finder/pypy/tool/test/test_leakfinder.py
==============================================================================
--- (empty file)
+++ pypy/branch/leak-finder/pypy/tool/test/test_leakfinder.py	Fri Oct 15 14:37:16 2010
@@ -0,0 +1,70 @@
+import py
+from pypy.tool import leakfinder
+
+def test_start_stop():
+    leakfinder.start_tracking_allocations()
+    assert leakfinder.TRACK_ALLOCATIONS
+    leakfinder.stop_tracking_allocations(True)
+    assert not leakfinder.TRACK_ALLOCATIONS
+
+def test_start_stop_nested():
+    leakfinder.start_tracking_allocations()
+    p2 = leakfinder.start_tracking_allocations()
+    assert leakfinder.TRACK_ALLOCATIONS
+    leakfinder.stop_tracking_allocations(True, prev=p2)
+    assert leakfinder.TRACK_ALLOCATIONS
+    leakfinder.stop_tracking_allocations(True)
+    assert not leakfinder.TRACK_ALLOCATIONS
+
+def test_remember_free():
+    leakfinder.start_tracking_allocations()
+    x = 1234
+    leakfinder.remember_malloc(x)
+    leakfinder.remember_free(x)
+    leakfinder.stop_tracking_allocations(True)
+
+def test_remember_forget():
+    leakfinder.start_tracking_allocations()
+    x = 1234
+    leakfinder.remember_malloc(x)
+    py.test.raises(leakfinder.MallocMismatch,
+                   leakfinder.stop_tracking_allocations, True)
+
+def test_nested_remember_forget_1():
+    leakfinder.start_tracking_allocations()
+    x = 1234
+    leakfinder.remember_malloc(x)
+    p2 = leakfinder.start_tracking_allocations()
+    leakfinder.stop_tracking_allocations(True, prev=p2)
+    py.test.raises(leakfinder.MallocMismatch,
+                   leakfinder.stop_tracking_allocations, True)
+
+def test_nested_remember_forget_2():
+    p2 = leakfinder.start_tracking_allocations()
+    x = 1234
+    leakfinder.remember_malloc(x)
+    py.test.raises(leakfinder.MallocMismatch,
+                   leakfinder.stop_tracking_allocations, True, prev=p2)
+    leakfinder.stop_tracking_allocations(True)
+
+def test_traceback():
+    leakfinder.start_tracking_allocations()
+    x = 1234
+    leakfinder.remember_malloc(x)
+    res = leakfinder.stop_tracking_allocations(check=False)
+    assert res.keys() == [x]
+    print res[x]
+    assert isinstance(res[x], str)
+    assert 'test_traceback' in res[x]
+    assert 'leakfinder.remember_malloc(x)' in res[x]
+
+def test_malloc_mismatch():
+    import sys, traceback, cStringIO
+    sio = cStringIO.StringIO()
+    traceback.print_stack(sys._getframe(), limit=10, file=sio)
+    tb = sio.getvalue()
+    e = leakfinder.MallocMismatch({1234: tb, 2345: tb})
+    print str(e)
+    # grouped entries for 1234 and 2345
+    assert '1234:\n2345:\n' in str(e) or '2345:\n1234:\n' in str(e)
+    assert tb[-80:] in str(e)

Modified: pypy/branch/leak-finder/pypy/translator/c/test/test_lltyped.py
==============================================================================
--- pypy/branch/leak-finder/pypy/translator/c/test/test_lltyped.py	(original)
+++ pypy/branch/leak-finder/pypy/translator/c/test/test_lltyped.py	Fri Oct 15 14:37:16 2010
@@ -401,6 +401,7 @@
             for i in range(n):
                 p = malloc(S, flavor='raw', zero=True)
                 if p.x != 0 or p.y != 0:
+                    free(p, flavor='raw')
                     return -1
                 p.x = i
                 p.y = i
@@ -418,14 +419,16 @@
         def f(n):
             for length in range(n-1, -1, -1):
                 p = malloc(S, length, flavor='raw', zero=True)
-                if p.x != 0:
-                    return -1
-                p.x = n
-                for j in range(length):
-                    if p.y[j] != 0:
-                        return -3
-                    p.y[j] = n^j
-                free(p, flavor='raw')
+                try:
+                    if p.x != 0:
+                        return -1
+                    p.x = n
+                    for j in range(length):
+                        if p.y[j] != 0:
+                            return -3
+                        p.y[j] = n^j
+                finally:
+                    free(p, flavor='raw')
             return 42
 
         fn = self.getcompiled(f, [int])
@@ -655,7 +658,7 @@
     def test_prebuilt_ll2ctypes_array(self):
         from pypy.rpython.lltypesystem import rffi, ll2ctypes
         A = rffi.CArray(Char)
-        a = malloc(A, 6, flavor='raw')
+        a = malloc(A, 6, flavor='raw', immortal=True)
         a[0] = 'a'
         a[1] = 'b'
         a[2] = 'c'
@@ -676,7 +679,7 @@
     def test_ll2ctypes_array_from_c(self):
         from pypy.rpython.lltypesystem import rffi, ll2ctypes
         A = rffi.CArray(Char)
-        a = malloc(A, 6, flavor='raw')
+        a = malloc(A, 6, flavor='raw', immortal=True)
         a[0] = 'a'
         a[1] = 'b'
         a[2] = 'c'



More information about the Pypy-commit mailing list