1563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#!/usr/bin/perl
2563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
3563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# Copyright (C) 2007 Apple Inc. All rights reserved.
4563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#
5563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# Redistribution and use in source and binary forms, with or without
6563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# modification, are permitted provided that the following conditions
7563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# are met:
8563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#
9563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# 1.  Redistributions of source code must retain the above copyright
10563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#     notice, this list of conditions and the following disclaimer. 
11563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# 2.  Redistributions in binary form must reproduce the above copyright
12563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#     notice, this list of conditions and the following disclaimer in the
13563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#     documentation and/or other materials provided with the distribution. 
14563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#     its contributors may be used to endorse or promote products derived
16563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#     from this software without specific prior written permission. 
17563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark#
18563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
29563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# Script to run the Mac OS X leaks tool with more expressive '-exclude' lists.
30563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
31563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkuse strict;
32563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkuse warnings;
33563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
34563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkuse File::Basename;
35563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkuse Getopt::Long;
36563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
37563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub runLeaks($);
38563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub parseLeaksOutput(\@);
39563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub removeMatchingRecords(\@$\@);
40563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub reportError($);
41563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
42563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub main()
43563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{
44563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    # Read options.
45563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $usage =
46563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        "Usage: " . basename($0) . " [options] pid | executable name\n" .
47563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        "  --exclude-callstack regexp   Exclude leaks whose call stacks match the regular expression 'regexp'.\n" .
48563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        "  --exclude-type regexp        Exclude leaks whose data types match the regular expression 'regexp'.\n" .
49563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        "  --help                       Show this help message.\n";
50563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
51563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my @callStacksToExclude = ();
52563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my @typesToExclude = ();
53563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $help = 0;
54563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
55563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $getOptionsResult = GetOptions(
56563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        'exclude-callstack:s' => \@callStacksToExclude,
57563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        'exclude-type:s' => \@typesToExclude,
58563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        'help' => \$help
59563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    );
60563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $pidOrExecutableName = $ARGV[0];
61563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
62563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    if (!$getOptionsResult || $help) {
63563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        print STDERR $usage;
64563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        return 1;
65563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    }
66563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
67563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    if (!$pidOrExecutableName) {
68563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        reportError("Missing argument: pid | executable.");
69563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        print STDERR $usage;
70563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        return 1;
71563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    }
72563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
73563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    # Run leaks tool.
74563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $leaksOutput = runLeaks($pidOrExecutableName);
75563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    if (!$leaksOutput) {
76563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        return 1;
77563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    }
78563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
79563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $leakList = parseLeaksOutput(@$leaksOutput);
80563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    if (!$leakList) {
81563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        return 1;
82563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    }
83563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
84563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    # Filter output.
85563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $leakCount = @$leakList;
86563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    removeMatchingRecords(@$leakList, "callStack", @callStacksToExclude);
87563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    removeMatchingRecords(@$leakList, "type", @typesToExclude);
88563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my $excludeCount = $leakCount - @$leakList;
89563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
90563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    # Dump results.
91563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    print $leaksOutput->[0];
92563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    print $leaksOutput->[1];
93563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    foreach my $leak (@$leakList) {
94563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        print $leak->{"leaksOutput"};
95563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    }
96563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
97563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    if ($excludeCount) {
98563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        print "$excludeCount leaks excluded (not printed)\n";
99563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    }
100563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
101563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    return 0;
102563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark}
103563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
104563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarkexit(main());
105563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
106563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# Returns the output of the leaks tool in list form.
107563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub runLeaks($)
108563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{
109563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my ($pidOrExecutableName) = @_;
110563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    
111563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my @leaksOutput = `leaks $pidOrExecutableName`;
112563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    if (!@leaksOutput) {
113563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        reportError("Error running leaks tool.");
114563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        return;
115563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    }
116563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    
117563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    return \@leaksOutput;
118563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark}
119563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
120563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark# Returns a list of hash references with the keys { address, size, type, callStack, leaksOutput }
121563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub parseLeaksOutput(\@)
122563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{
123563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my ($leaksOutput) = @_;
124563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
125563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    # Format:
126563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    #   Process 00000: 1234 nodes malloced for 1234 KB
127563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    #   Process 00000: XX leaks for XXX total leaked bytes.    
128563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    #   Leak: 0x00000000 size=1234 [instance of 'blah']
129563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    #       0x00000000 0x00000000 0x00000000 0x00000000 a..d.e.e
130563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    #       ...
131563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    #       Call stack: leak_caller() | leak() | malloc
132563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    #
133563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    #   We treat every line except for  Process 00000: and Leak: as optional
134563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
135d0825bca7fe65beaee391d30da42e937db621564Steve Block    # Newer versions of the leaks output have a header section at the top, with the first line describing the version of the output format.
136d0825bca7fe65beaee391d30da42e937db621564Steve Block    # If we detect the new format is being used then we eat all of the header section so the output matches the format of older versions.
137d0825bca7fe65beaee391d30da42e937db621564Steve Block    # FIXME: In the future we may wish to propagate this section through to our output.
138d0825bca7fe65beaee391d30da42e937db621564Steve Block    if ($leaksOutput->[0] =~ /^leaks Report Version:/) {
139d0825bca7fe65beaee391d30da42e937db621564Steve Block        while ($leaksOutput->[0] !~ /^Process /) {
140d0825bca7fe65beaee391d30da42e937db621564Steve Block            shift @$leaksOutput;
141d0825bca7fe65beaee391d30da42e937db621564Steve Block        }
142d0825bca7fe65beaee391d30da42e937db621564Steve Block    }
143d0825bca7fe65beaee391d30da42e937db621564Steve Block
144563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my ($leakCount) = ($leaksOutput->[1] =~ /[[:blank:]]+([0-9]+)[[:blank:]]+leaks?/);
145563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    if (!defined($leakCount)) {
146563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        reportError("Could not parse leak count reported by leaks tool.");
147563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        return;
148563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    }
149563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
150563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my @leakList = ();
151563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    for my $line (@$leaksOutput) {
152563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        next if $line =~ /^Process/;
153563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        next if $line =~ /^node buffer added/;
154563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        
155563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        if ($line =~ /^Leak: /) {
156563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            my ($address) = ($line =~ /Leak: ([[:xdigit:]x]+)/);
157563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            if (!defined($address)) {
158563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                reportError("Could not parse Leak address.");
159563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                return;
160563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            }
161563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
162563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            my ($size) = ($line =~ /size=([[:digit:]]+)/);
163563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            if (!defined($size)) {
164563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                reportError("Could not parse Leak size.");
165563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                return;
166563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            }
167563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
168563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            my ($type) = ($line =~ /'([^']+)'/); #'
169563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            if (!defined($type)) {
170563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                $type = ""; # The leaks tool sometimes omits the type.
171563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            }
172563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
173563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            my %leak = (
174563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                "address" => $address,
175563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                "size" => $size,
176563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                "type" => $type,
177563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                "callStack" => "", # The leaks tool sometimes omits the call stack.
178563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                "leaksOutput" => $line
179563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            );
180563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            push(@leakList, \%leak);
181563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        } else {
182563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            $leakList[$#leakList]->{"leaksOutput"} .= $line;
183563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            if ($line =~ /Call stack:/) {
184563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                $leakList[$#leakList]->{"callStack"} = $line;
185563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            }
186563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        }
187563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    }
188563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    
189563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    if (@leakList != $leakCount) {
190563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        my $parsedLeakCount = @leakList;
191563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        reportError("Parsed leak count($parsedLeakCount) does not match leak count reported by leaks tool($leakCount).");
192563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        return;
193563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    }
194563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
195563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    return \@leakList;
196563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark}
197563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
198563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub removeMatchingRecords(\@$\@)
199563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{
200563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my ($recordList, $key, $regexpList) = @_;
201563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    
202563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    RECORD: for (my $i = 0; $i < @$recordList;) {
203563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        my $record = $recordList->[$i];
204563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
205563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        foreach my $regexp (@$regexpList) {
206563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            if ($record->{$key} =~ $regexp) {
207563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                splice(@$recordList, $i, 1);
208563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark                next RECORD;
209563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark            }
210563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        }
211563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        
212563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark        $i++;
213563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    }
214563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark}
215563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark
216563af33bc48281d19dce701398dbb88cb54fd7ecCary Clarksub reportError($)
217563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark{
218563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    my ($errorMessage) = @_;
219563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    
220563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark    print STDERR basename($0) . ": $errorMessage\n";
221563af33bc48281d19dce701398dbb88cb54fd7ecCary Clark}
222