[Libreoffice-commits] core.git: bin/refcount_leak.py

Michael Stahl mstahl at redhat.com
Mon Oct 20 08:17:06 PDT 2014


 bin/refcount_leak.py |  179 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 179 insertions(+)

New commits:
commit a4e219eee6314f26b7d9058e714c62facc2bc08c
Author: Michael Stahl <mstahl at redhat.com>
Date:   Mon Oct 20 15:33:05 2014 +0200

    add bin/refcount_leak.py script to help detect leaks of UNO objects
    
    Initially this didn't help at all, but with some tweaks it now finds
    the leaked ChartModel as the #3 and #4 printed stacks, with a very bad
    score of 40.
    
    Change-Id: I918bcb64bad0b9432bf2b670b03915836fb34a11

diff --git a/bin/refcount_leak.py b/bin/refcount_leak.py
new file mode 100755
index 0000000..2a24cb5
--- /dev/null
+++ b/bin/refcount_leak.py
@@ -0,0 +1,179 @@
+#!/usr/bin/python3
+# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
+#
+# This file is part of the LibreOffice project.
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+#
+
+### script to help debug leaks of reference counted objects
+
+## I. to use it, first override acquire() and release()
+
+# Foo * g_pTrackedFoo = 0;
+
+# Foo::Foo()
+#   static int nFoos = 0;
+#     if (++nFoos == 42) // track instance #42
+#       g_pTrackedFoo = this;
+
+# void Foo::acquire()
+#   if (this == g_pTrackedFoo)
+#     ; // set gdb breakpoint here
+#   Foo_Base::acquire()
+
+# void Foo::release()
+#   if (this == g_pTrackedFoo)
+#      ; // set gdb breakpoint here
+#   Foo_Base::release()
+
+## II. run test/soffice in gdb and set breakpoints in acquire/release
+##     with a command to print the backtrace
+
+# set logging on
+# break foo.cxx:123
+# break foo.cxx:234
+
+# command 1 2
+# bt
+# c
+# end
+# run
+
+## III. now feed logfile gdb.txt into this script
+
+# bin/refcount_leak.py < gdb.txt
+
+###
+
+from operator import itemgetter
+import re
+import sys
+
+threshold = 2
+
+class Trace:
+    clock = 0 # global counter
+    # frames: list of stack frames, beginning with outermost
+    def __init__(self, lines):
+        lines.reverse()
+        self.frames = lines
+        Trace.clock += 1
+        self.clock = Trace.clock
+
+def addTrace(traces, lines):
+    if not(traces is None) and len(lines) > 0:
+        traces.append(Trace(lines))
+
+def readGdbLog(infile):
+    traces_acquire = []
+    traces_release = []
+    current = None
+    lines = []
+    apattern = re.compile("^Breakpoint.*::acquire")
+    rpattern = re.compile("^Breakpoint.*::release")
+    for line in infile:
+        if apattern.match(line):
+            addTrace(current, lines)
+            lines = []
+            current = traces_acquire
+        if rpattern.match(line):
+            addTrace(current, lines)
+            lines = []
+            current = traces_release
+        if line.startswith("#"):
+            # strip #123 stack frame number, and newline
+            lines.append(line[line.index("0x"):-1])
+    addTrace(current, lines)
+    print("# parsed traces acquire: ", len(traces_acquire))
+    print("# parsed traces release: ", len(traces_release))
+    return (traces_acquire, traces_release)
+
+def getFunction(frame):
+    start = frame.index(" in ") + len(" in ")
+    try:
+        end = frame.index(" at ", start)
+    except ValueError as e:
+        # argh... stack frames may be split across multiple lines if
+        # a parameter has a fancy pretty printer
+        return frame[start:]
+    return frame[start:end]
+
+
+def matchStack(trace_acquire, trace_release):
+    if trace_release.clock < trace_acquire.clock:
+        return None # acquire must precede release
+    common = 0
+    refpattern = re.compile(r"::Reference<.*>::Reference\(")
+    for (frame1, frame2) in zip(trace_release.frames, trace_acquire.frames):
+        if frame1 == frame2:
+            common += 1
+        else:
+            if getFunction(frame1) == getFunction(frame2):
+                common += 1
+            acquireframes = len(trace_acquire.frames)
+            # there is sometimes a dozen frames of UNO type related junk
+            # on the stack where the acquire() happens, which breaks the
+            # matching; try to avoid that
+            for i in range(common, acquireframes):
+                if refpattern.search(trace_acquire.frames[i]):
+                    acquireframes = i+1 # cut off junk above Reference ctor
+                    break
+            score = max(len(trace_release.frames), acquireframes) - common
+            # smaller score is better
+            return (score, trace_release.clock - trace_acquire.clock)
+
+# brute force greedy n^2 matching
+def matchStacks(traces_acquire, traces_release):
+    matches = []
+    for release in traces_release:
+        for acquire in traces_acquire:
+            score = matchStack(acquire, release)
+            if score is not None:
+                matches.append((score, acquire, release))
+    matches.sort(key=itemgetter(0))
+    return matches
+
+def bestMatches(traces_acquire, traces_release, matches):
+    traces_aunmatched = traces_acquire
+    traces_runmatched = traces_release
+    bestmatches = []
+    for (score,acquire,release) in matches:
+        if not(acquire in traces_aunmatched and release in traces_runmatched):
+            continue
+        traces_aunmatched.remove(acquire)
+        traces_runmatched.remove(release)
+        bestmatches.append((score,acquire,release))
+    print("# unmatched acquire: ", len(traces_aunmatched))
+    print("# unmatched release: ", len(traces_runmatched))
+    return (bestmatches,traces_aunmatched,traces_runmatched)
+
+def printTrace(trace):
+    for frame in reversed(trace.frames):
+        print("  ", frame)
+
+def printMatched(bestmatches):
+    for (score,acquire,release) in reversed(bestmatches):
+        print("\n*** Matched trace with score: ", score)
+        print(" acquire: ")
+        printTrace(acquire)
+        print(" release: ")
+        printTrace(release)
+
+def printUnmatched(traces, prefix):
+    for trace in traces:
+        print("\n*** Unmatched trace (", prefix, "):")
+        printTrace(trace)
+
+if __name__ == "__main__":
+    (traces_acquire, traces_release) = readGdbLog(sys.stdin)
+    matches = matchStacks(traces_acquire, traces_release)
+    (bestmatches,traces_au,traces_ru) = bestMatches(traces_acquire, traces_release, matches)
+    # print output, sorted with the most suspicious stuff first:
+    printUnmatched(traces_au, "acquire")
+    printUnmatched(traces_ru, "release")
+    printMatched(bestmatches)
+
+# vim:set shiftwidth=4 softtabstop=4 expandtab:


More information about the Libreoffice-commits mailing list