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 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 " --output-file path Store result in a file. If not specified, standard output is used.\n" .
50 " --memgraph-file path Store memgraph in a file.\n" .
51 " --help Show this help message.\n";
53 my @callStacksToExclude = ();
54 my @typesToExclude = ();
59 my $getOptionsResult = GetOptions(
60 'exclude-callstack:s' => \@callStacksToExclude,
61 'exclude-type:s' => \@typesToExclude,
62 'output-file:s' => \$outputPath,
63 'memgraph-file:s' => \$memgraphPath,
66 my $pidOrExecutableName = $ARGV[0];
68 if (!$getOptionsResult || $help) {
73 if (!$pidOrExecutableName) {
74 reportError("Missing argument: pid | executable.");
80 my $leaksOutput = runLeaks($pidOrExecutableName, $memgraphPath);
85 my $parsedOutput = parseLeaksOutput(@$leaksOutput);
91 # FIXME: Adjust the overall leak count in the output accordingly. This will affect callers, notably leakdetector.py.
92 my $parsedLineCount = @$parsedOutput;
93 removeMatchingRecords(@$parsedOutput, "callStack", @callStacksToExclude);
94 removeMatchingRecords(@$parsedOutput, "type", @typesToExclude);
95 my $excludeCount = $parsedLineCount - @$parsedOutput;
98 if (defined $outputPath) {
99 open($outputFH , '>', $outputPath) or die "Could not open $outputPath for writing";
104 foreach my $leak (@$parsedOutput) {
105 print $outputFH $leak->{"leaksOutput"};
109 print $outputFH "$excludeCount leaks excluded (not printed)\n";
117 # Returns the output of the leaks tool in list form.
120 my ($target, $memgraphPath) = @_;
122 if (defined $memgraphPath) {
123 `/usr/bin/leaks \"$target\" --outputGraph=\"$memgraphPath\"`;
124 $target = $memgraphPath;
127 # To get a result we can parse, we need to pass --list to all versions of the leaks tool
128 # that recognize it, but there is no certain way to tell in advance (the version of the
129 # tool is not always tied to OS version, and --help doesn't document the option in some
130 # of the tool versions where it's supported and needed).
131 print STDERR "Starting leaks\n";
132 my @leaksOutput = `/usr/bin/leaks \"$target\" --list`;
134 # FIXME: Remove the fallback once macOS Mojave is the oldest supported version.
135 my $leaksExitCode = $? >> 8;
136 if ($leaksExitCode > 1) {
137 @leaksOutput = `/usr/bin/leaks \"$target\"`;
140 return \@leaksOutput;
143 # Returns a list of hash references with the keys { address, size, type, callStack, leaksOutput }
144 sub parseLeaksOutput(\@)
146 my ($leaksOutput) = @_;
149 # Process 00000: 1234 nodes malloced for 1234 KB
150 # Process 00000: XX leaks for XXX total leaked bytes.
151 # Leak: 0x00000000 size=1234 [instance of 'blah']
152 # 0x00000000 0x00000000 0x00000000 0x00000000 a..d.e.e
154 # Call stack: leak_caller() | leak() | malloc
156 # We treat every line except for Process 00000: and Leak: as optional
159 my @parsedOutput = ();
161 my $parsedLeakCount = 0;
162 for my $line (@$leaksOutput) {
163 if ($line =~ /^Process \d+: (\d+) leaks?/) {
171 if ($line =~ /^Leak: /) {
174 my ($address) = ($line =~ /Leak: ([[:xdigit:]x]+)/);
175 if (!defined($address)) {
176 reportError("Could not parse Leak address.");
180 my ($size) = ($line =~ /size=([[:digit:]]+)/);
181 if (!defined($size)) {
182 reportError("Could not parse Leak size.");
186 # FIXME: This code seems wrong, the leaks tool doesn't actually use single quotes.
187 # We should reconcile with other format changes that happened since, such as the
188 # addition of zone information.
189 my ($type) = ($line =~ /'([^']+)'/); #'
190 if (!defined($type)) {
191 $type = ""; # The leaks tool sometimes omits the type.
195 "address" => $address,
198 "callStack" => "", # The leaks tool sometimes omits the call stack.
199 "leaksOutput" => $line
201 push(@parsedOutput, \%leak);
202 } elsif ($parsingLeak) {
203 $parsedOutput[$#parsedOutput]->{"leaksOutput"} .= $line;
204 if ($line =~ /Call stack:/) {
205 $parsedOutput[$#parsedOutput]->{"callStack"} = $line;
209 "leaksOutput" => $line
211 push(@parsedOutput, \%nonLeakLine);
215 if ($parsedLeakCount != $leakCount) {
216 reportError("Parsed leak count ($parsedLeakCount) does not match leak count reported by leaks tool ($leakCount).");
220 return \@parsedOutput;
223 sub removeMatchingRecords(\@$\@)
225 my ($recordList, $key, $regexpList) = @_;
227 RECORD: for (my $i = 0; $i < @$recordList;) {
228 my $record = $recordList->[$i];
230 foreach my $regexp (@$regexpList) {
231 if (defined $record->{$key} and $record->{$key} =~ $regexp) {
232 splice(@$recordList, $i, 1);
243 my ($errorMessage) = @_;
245 print STDERR basename($0) . ": $errorMessage\n";