3 # Copyright (C) 2007 Apple Inc. All rights reserved.
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions
9 # 1. Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # 2. Redistributions in binary form must reproduce the above copyright
12 # notice, this list of conditions and the following disclaimer in the
13 # documentation and/or other materials provided with the distribution.
14 # 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 # its contributors may be used to endorse or promote products derived
16 # from this software without specific prior written permission.
18 # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 # DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 # THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 # Script to run the Mac OS X leaks tool with more expressive '-exclude' lists.
38 sub parseLeaksOutput(\@);
39 sub removeMatchingRecords(\@$\@);
46 "Usage: " . basename($0) . " [options] pid | executable name\n" .
47 " --exclude-callstack regexp Exclude leaks whose call stacks match the regular expression 'regexp'.\n" .
48 " --exclude-type regexp Exclude leaks whose data types match the regular expression 'regexp'.\n" .
49 " --help Show this help message.\n";
51 my @callStacksToExclude = ();
52 my @typesToExclude = ();
55 my $getOptionsResult = GetOptions(
56 'exclude-callstack:s' => \@callStacksToExclude,
57 'exclude-type:s' => \@typesToExclude,
60 my $pidOrExecutableName = $ARGV[0];
62 if (!$getOptionsResult || $help) {
67 if (!$pidOrExecutableName) {
68 reportError("Missing argument: pid | executable.");
74 my $leaksOutput = runLeaks($pidOrExecutableName);
79 my $leakList = parseLeaksOutput(@$leaksOutput);
85 my $leakCount = @$leakList;
86 removeMatchingRecords(@$leakList, "callStack", @callStacksToExclude);
87 removeMatchingRecords(@$leakList, "type", @typesToExclude);
88 my $excludeCount = $leakCount - @$leakList;
91 print $leaksOutput->[0];
92 print $leaksOutput->[1];
93 foreach my $leak (@$leakList) {
94 print $leak->{"leaksOutput"};
98 print "$excludeCount leaks excluded (not printed)\n";
106 # Returns the output of the leaks tool in list form.
109 my ($pidOrExecutableName) = @_;
111 my @leaksOutput = `leaks $pidOrExecutableName`;
113 reportError("Error running leaks tool.");
117 return \@leaksOutput;
120 # Returns a list of hash references with the keys { address, size, type, callStack, leaksOutput }
121 sub parseLeaksOutput(\@)
123 my ($leaksOutput) = @_;
126 # Process 00000: 1234 nodes malloced for 1234 KB
127 # Process 00000: XX leaks for XXX total leaked bytes.
128 # Leak: 0x00000000 size=1234 [instance of 'blah']
129 # 0x00000000 0x00000000 0x00000000 0x00000000 a..d.e.e
131 # Call stack: leak_caller() | leak() | malloc
133 # We treat every line except for Process 00000: and Leak: as optional
135 # Skip header section until the first two "Process " lines.
136 # FIXME: In the future we may wish to propagate the header section through to our output.
137 until ($leaksOutput->[0] =~ /^Process /) {
141 my ($leakCount) = ($leaksOutput->[1] =~ /[[:blank:]]+([0-9]+)[[:blank:]]+leaks?/);
142 if (!defined($leakCount)) {
143 reportError("Could not parse leak count reported by leaks tool.");
148 for my $line (@$leaksOutput) {
149 next if $line =~ /^Process/;
150 next if $line =~ /^node buffer added/;
152 if ($line =~ /^Leak: /) {
153 my ($address) = ($line =~ /Leak: ([[:xdigit:]x]+)/);
154 if (!defined($address)) {
155 reportError("Could not parse Leak address.");
159 my ($size) = ($line =~ /size=([[:digit:]]+)/);
160 if (!defined($size)) {
161 reportError("Could not parse Leak size.");
165 my ($type) = ($line =~ /'([^']+)'/); #'
166 if (!defined($type)) {
167 $type = ""; # The leaks tool sometimes omits the type.
171 "address" => $address,
174 "callStack" => "", # The leaks tool sometimes omits the call stack.
175 "leaksOutput" => $line
177 push(@leakList, \%leak);
179 $leakList[$#leakList]->{"leaksOutput"} .= $line;
180 if ($line =~ /Call stack:/) {
181 $leakList[$#leakList]->{"callStack"} = $line;
186 if (@leakList != $leakCount) {
187 my $parsedLeakCount = @leakList;
188 reportError("Parsed leak count($parsedLeakCount) does not match leak count reported by leaks tool($leakCount).");
195 sub removeMatchingRecords(\@$\@)
197 my ($recordList, $key, $regexpList) = @_;
199 RECORD: for (my $i = 0; $i < @$recordList;) {
200 my $record = $recordList->[$i];
202 foreach my $regexp (@$regexpList) {
203 if ($record->{$key} =~ $regexp) {
204 splice(@$recordList, $i, 1);
215 my ($errorMessage) = @_;
217 print STDERR basename($0) . ": $errorMessage\n";