mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-29 20:07:30 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
372
tools/bloatview/bloatdiff.pl
Normal file
372
tools/bloatview/bloatdiff.pl
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
#!/usr/bin/perl -w
|
||||
# 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/.
|
||||
|
||||
|
||||
################################################################################
|
||||
|
||||
sub usage() {
|
||||
print <<EOUSAGE;
|
||||
# bloatdiff.pl - munges the output from
|
||||
# XPCOM_MEM_BLOAT_LOG=1
|
||||
# firefox-bin -P default resource:///res/bloatcycle.html
|
||||
# so that it does some summary and stats stuff.
|
||||
#
|
||||
# To show leak test results for a set of changes, do something like this:
|
||||
#
|
||||
# XPCOM_MEM_BLOAT_LOG=1
|
||||
# firefox-bin -P default resource:///res/bloatcycle.html > a.out
|
||||
# **make change**
|
||||
# firefox-bin -P default resource:///res/bloatcycle.html > b.out
|
||||
# bloatdiff.pl a.out b.out
|
||||
|
||||
EOUSAGE
|
||||
}
|
||||
|
||||
$OLDFILE = $ARGV[0];
|
||||
$NEWFILE = $ARGV[1];
|
||||
#$LABEL = $ARGV[2];
|
||||
|
||||
if (!$OLDFILE or
|
||||
! -e $OLDFILE or
|
||||
-z $OLDFILE) {
|
||||
print "\nError: Previous log file not specified, does not exist, or is empty.\n\n";
|
||||
&usage();
|
||||
exit 1;
|
||||
}
|
||||
|
||||
if (!$NEWFILE or
|
||||
! -e $NEWFILE or
|
||||
-z $NEWFILE) {
|
||||
print "\nError: Current log file not specified, does not exist, or is empty.\n\n";
|
||||
&usage();
|
||||
exit 1;
|
||||
}
|
||||
|
||||
sub processFile {
|
||||
my ($filename, $map, $prevMap) = @_;
|
||||
open(FH, $filename);
|
||||
while (<FH>) {
|
||||
if (m{
|
||||
^\s*(\d+)\s # Line number
|
||||
([\w:]+)\s+ # Name
|
||||
(-?\d+)\s+ # Size
|
||||
(-?\d+)\s+ # Leaked
|
||||
(-?\d+)\s+ # Objects Total
|
||||
(-?\d+)\s+ # Objects Rem
|
||||
\(\s*(-?[\d.]+)\s+ # Objects Mean
|
||||
\+/-\s+
|
||||
([\w.]+)\)\s+ # Objects StdDev
|
||||
(-?\d+)\s+ # Reference Total
|
||||
(-?\d+)\s+ # Reference Rem
|
||||
\(\s*(-?[\d.]+)\s+ # Reference Mean
|
||||
\+/-\s+
|
||||
([\w\.]+)\) # Reference StdDev
|
||||
}x) {
|
||||
$$map{$2} = { name => $2,
|
||||
size => $3,
|
||||
leaked => $4,
|
||||
objTotal => $5,
|
||||
objRem => $6,
|
||||
objMean => $7,
|
||||
objStdDev => $8,
|
||||
refTotal => $9,
|
||||
refRem => $10,
|
||||
refMean => $11,
|
||||
refStdDev => $12,
|
||||
bloat => $3 * $5 # size * objTotal
|
||||
};
|
||||
} else {
|
||||
# print "failed to parse: $_\n";
|
||||
}
|
||||
}
|
||||
close(FH);
|
||||
}
|
||||
|
||||
%oldMap = ();
|
||||
processFile($OLDFILE, \%oldMap);
|
||||
|
||||
%newMap = ();
|
||||
processFile($NEWFILE, \%newMap);
|
||||
|
||||
################################################################################
|
||||
|
||||
$inf = 9999999.99;
|
||||
|
||||
sub getLeaksDelta {
|
||||
my ($key) = @_;
|
||||
my $oldLeaks = $oldMap{$key}{leaked} || 0;
|
||||
my $newLeaks = $newMap{$key}{leaked};
|
||||
my $percentLeaks = 0;
|
||||
if ($oldLeaks == 0) {
|
||||
if ($newLeaks != 0) {
|
||||
# there weren't any leaks before, but now there are!
|
||||
$percentLeaks = $inf;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$percentLeaks = ($newLeaks - $oldLeaks) / $oldLeaks * 100;
|
||||
}
|
||||
# else we had no record of this class before
|
||||
return ($newLeaks - $oldLeaks, $percentLeaks);
|
||||
}
|
||||
|
||||
################################################################################
|
||||
|
||||
sub getBloatDelta {
|
||||
my ($key) = @_;
|
||||
my $newBloat = $newMap{$key}{bloat};
|
||||
my $percentBloat = 0;
|
||||
my $oldSize = $oldMap{$key}{size} || 0;
|
||||
my $oldTotal = $oldMap{$key}{objTotal} || 0;
|
||||
my $oldBloat = $oldTotal * $oldSize;
|
||||
if ($oldBloat == 0) {
|
||||
if ($newBloat != 0) {
|
||||
# this class wasn't used before, but now it is
|
||||
$percentBloat = $inf;
|
||||
}
|
||||
}
|
||||
else {
|
||||
$percentBloat = ($newBloat - $oldBloat) / $oldBloat * 100;
|
||||
}
|
||||
# else we had no record of this class before
|
||||
return ($newBloat - $oldBloat, $percentBloat);
|
||||
}
|
||||
|
||||
################################################################################
|
||||
|
||||
foreach $key (keys %newMap) {
|
||||
my ($newLeaks, $percentLeaks) = getLeaksDelta($key);
|
||||
my ($newBloat, $percentBloat) = getBloatDelta($key);
|
||||
$newMap{$key}{leakDelta} = $newLeaks;
|
||||
$newMap{$key}{leakPercent} = $percentLeaks;
|
||||
$newMap{$key}{bloatDelta} = $newBloat;
|
||||
$newMap{$key}{bloatPercent} = $percentBloat;
|
||||
}
|
||||
|
||||
################################################################################
|
||||
|
||||
# Print a value of bytes out in a reasonable
|
||||
# KB, MB, or GB form. Copied from build-seamonkey-util.pl, sorry. -mcafee
|
||||
sub PrintSize($) {
|
||||
|
||||
# print a number with 3 significant figures
|
||||
sub PrintNum($) {
|
||||
my ($num) = @_;
|
||||
my $rv;
|
||||
if ($num < 1) {
|
||||
$rv = sprintf "%.3f", ($num);
|
||||
} elsif ($num < 10) {
|
||||
$rv = sprintf "%.2f", ($num);
|
||||
} elsif ($num < 100) {
|
||||
$rv = sprintf "%.1f", ($num);
|
||||
} else {
|
||||
$rv = sprintf "%d", ($num);
|
||||
}
|
||||
}
|
||||
|
||||
my ($size) = @_;
|
||||
my $rv;
|
||||
if ($size > 1000000000) {
|
||||
$rv = PrintNum($size / 1000000000.0) . "G";
|
||||
} elsif ($size > 1000000) {
|
||||
$rv = PrintNum($size / 1000000.0) . "M";
|
||||
} elsif ($size > 1000) {
|
||||
$rv = PrintNum($size / 1000.0) . "K";
|
||||
} else {
|
||||
$rv = PrintNum($size);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
print "Bloat/Leak Delta Report\n";
|
||||
print "--------------------------------------------------------------------------------------\n";
|
||||
print "Current file: $NEWFILE\n";
|
||||
print "Previous file: $OLDFILE\n";
|
||||
print "----------------------------------------------leaks------leaks%------bloat------bloat%\n";
|
||||
|
||||
if (! $newMap{"TOTAL"} or
|
||||
! $newMap{"TOTAL"}{bloat}) {
|
||||
# It's OK if leaked or leakPercent are 0 (in fact, that would be good).
|
||||
# If bloatPercent is zero, it is also OK, because we may have just had
|
||||
# two runs exactly the same or with no new bloat.
|
||||
print "\nError: unable to calculate bloat/leak data.\n";
|
||||
print "There is no data present.\n\n";
|
||||
print "HINT - Did your test run complete successfully?\n";
|
||||
print "HINT - Are you pointing at the right log files?\n\n";
|
||||
&usage();
|
||||
exit 1;
|
||||
}
|
||||
|
||||
printf "%-40s %10s %10.2f%% %10s %10.2f%%\n",
|
||||
("TOTAL",
|
||||
$newMap{"TOTAL"}{leaked}, $newMap{"TOTAL"}{leakPercent},
|
||||
$newMap{"TOTAL"}{bloat}, $newMap{"TOTAL"}{bloatPercent});
|
||||
|
||||
################################################################################
|
||||
|
||||
sub percentStr {
|
||||
my ($p) = @_;
|
||||
if ($p == $inf) {
|
||||
return "-";
|
||||
}
|
||||
else {
|
||||
return sprintf "%10.2f%%", $p;
|
||||
}
|
||||
}
|
||||
|
||||
# NEW LEAKS
|
||||
@keys = sort { $newMap{$b}{leakPercent} <=> $newMap{$a}{leakPercent} } keys %newMap;
|
||||
my $needsHeading = 1;
|
||||
my $total = 0;
|
||||
foreach $key (@keys) {
|
||||
my $percentLeaks = $newMap{$key}{leakPercent};
|
||||
my $leaks = $newMap{$key}{leaked};
|
||||
if ($percentLeaks > 0 && $key !~ /TOTAL/) {
|
||||
if ($needsHeading) {
|
||||
printf "--NEW-LEAKS-----------------------------------leaks------leaks%%-----------------------\n";
|
||||
$needsHeading = 0;
|
||||
}
|
||||
printf "%-40s %10s %10s\n", ($key, $leaks, percentStr($percentLeaks));
|
||||
$total += $leaks;
|
||||
}
|
||||
}
|
||||
if (!$needsHeading) {
|
||||
printf "%-40s %10s\n", ("TOTAL", $total);
|
||||
}
|
||||
|
||||
# FIXED LEAKS
|
||||
@keys = sort { $newMap{$b}{leakPercent} <=> $newMap{$a}{leakPercent} } keys %newMap;
|
||||
$needsHeading = 1;
|
||||
$total = 0;
|
||||
foreach $key (@keys) {
|
||||
my $percentLeaks = $newMap{$key}{leakPercent};
|
||||
my $leaks = $newMap{$key}{leaked};
|
||||
if ($percentLeaks < 0 && $key !~ /TOTAL/) {
|
||||
if ($needsHeading) {
|
||||
printf "--FIXED-LEAKS---------------------------------leaks------leaks%%-----------------------\n";
|
||||
$needsHeading = 0;
|
||||
}
|
||||
printf "%-40s %10s %10s\n", ($key, $leaks, percentStr($percentLeaks));
|
||||
$total += $leaks;
|
||||
}
|
||||
}
|
||||
if (!$needsHeading) {
|
||||
printf "%-40s %10s\n", ("TOTAL", $total);
|
||||
}
|
||||
|
||||
# NEW BLOAT
|
||||
@keys = sort { $newMap{$b}{bloatPercent} <=> $newMap{$a}{bloatPercent} } keys %newMap;
|
||||
$needsHeading = 1;
|
||||
$total = 0;
|
||||
foreach $key (@keys) {
|
||||
my $percentBloat = $newMap{$key}{bloatPercent};
|
||||
my $bloat = $newMap{$key}{bloat};
|
||||
if ($percentBloat > 0 && $key !~ /TOTAL/) {
|
||||
if ($needsHeading) {
|
||||
printf "--NEW-BLOAT-----------------------------------bloat------bloat%%-----------------------\n";
|
||||
$needsHeading = 0;
|
||||
}
|
||||
printf "%-40s %10s %10s\n", ($key, $bloat, percentStr($percentBloat));
|
||||
$total += $bloat;
|
||||
}
|
||||
}
|
||||
if (!$needsHeading) {
|
||||
printf "%-40s %10s\n", ("TOTAL", $total);
|
||||
}
|
||||
|
||||
# ALL LEAKS
|
||||
@keys = sort { $newMap{$b}{leaked} <=> $newMap{$a}{leaked} } keys %newMap;
|
||||
$needsHeading = 1;
|
||||
$total = 0;
|
||||
foreach $key (@keys) {
|
||||
my $leaks = $newMap{$key}{leaked};
|
||||
my $percentLeaks = $newMap{$key}{leakPercent};
|
||||
if ($leaks > 0) {
|
||||
if ($needsHeading) {
|
||||
printf "--ALL-LEAKS-----------------------------------leaks------leaks%%-----------------------\n";
|
||||
$needsHeading = 0;
|
||||
}
|
||||
printf "%-40s %10s %10s\n", ($key, $leaks, percentStr($percentLeaks));
|
||||
if ($key !~ /TOTAL/) {
|
||||
$total += $leaks;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$needsHeading) {
|
||||
# printf "%-40s %10s\n", ("TOTAL", $total);
|
||||
}
|
||||
|
||||
# ALL BLOAT
|
||||
@keys = sort { $newMap{$b}{bloat} <=> $newMap{$a}{bloat} } keys %newMap;
|
||||
$needsHeading = 1;
|
||||
$total = 0;
|
||||
foreach $key (@keys) {
|
||||
my $bloat = $newMap{$key}{bloat};
|
||||
my $percentBloat = $newMap{$key}{bloatPercent};
|
||||
if ($bloat > 0) {
|
||||
if ($needsHeading) {
|
||||
printf "--ALL-BLOAT-----------------------------------bloat------bloat%%-----------------------\n";
|
||||
$needsHeading = 0;
|
||||
}
|
||||
printf "%-40s %10s %10s\n", ($key, $bloat, percentStr($percentBloat));
|
||||
if ($key !~ /TOTAL/) {
|
||||
$total += $bloat;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$needsHeading) {
|
||||
# printf "%-40s %10s\n", ("TOTAL", $total);
|
||||
}
|
||||
|
||||
# NEW CLASSES
|
||||
@keys = sort { $newMap{$b}{bloatDelta} <=> $newMap{$a}{bloatDelta} } keys %newMap;
|
||||
$needsHeading = 1;
|
||||
my $ltotal = 0;
|
||||
my $btotal = 0;
|
||||
foreach $key (@keys) {
|
||||
my $leaks = $newMap{$key}{leaked};
|
||||
my $bloat = $newMap{$key}{bloat};
|
||||
my $percentBloat = $newMap{$key}{bloatPercent};
|
||||
if ($percentBloat == $inf && $key !~ /TOTAL/) {
|
||||
if ($needsHeading) {
|
||||
printf "--CLASSES-NOT-REPORTED-LAST-TIME--------------leaks------bloat------------------------\n";
|
||||
$needsHeading = 0;
|
||||
}
|
||||
printf "%-40s %10s %10s\n", ($key, $leaks, $bloat);
|
||||
if ($key !~ /TOTAL/) {
|
||||
$ltotal += $leaks;
|
||||
$btotal += $bloat;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$needsHeading) {
|
||||
printf "%-40s %10s %10s\n", ("TOTAL", $ltotal, $btotal);
|
||||
}
|
||||
|
||||
# OLD CLASSES
|
||||
@keys = sort { ($oldMap{$b}{bloat} || 0) <=> ($oldMap{$a}{bloat} || 0) } keys %oldMap;
|
||||
$needsHeading = 1;
|
||||
$ltotal = 0;
|
||||
$btotal = 0;
|
||||
foreach $key (@keys) {
|
||||
if (!defined($newMap{$key})) {
|
||||
my $leaks = $oldMap{$key}{leaked};
|
||||
my $bloat = $oldMap{$key}{bloat};
|
||||
if ($needsHeading) {
|
||||
printf "--CLASSES-THAT-WENT-AWAY----------------------leaks------bloat------------------------\n";
|
||||
$needsHeading = 0;
|
||||
}
|
||||
printf "%-40s %10s %10s\n", ($key, $leaks, $bloat);
|
||||
if ($key !~ /TOTAL/) {
|
||||
$ltotal += $leaks;
|
||||
$btotal += $bloat;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!$needsHeading) {
|
||||
printf "%-40s %10s %10s\n", ("TOTAL", $ltotal, $btotal);
|
||||
}
|
||||
|
||||
print "--------------------------------------------------------------------------------------\n";
|
||||
590
tools/bloatview/bloattable.pl
Normal file
590
tools/bloatview/bloattable.pl
Normal file
|
|
@ -0,0 +1,590 @@
|
|||
#!/usr/bin/perl -w
|
||||
#
|
||||
# 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/.
|
||||
|
||||
# bloattable [-debug] [-source] [-byte n|-obj n|-ref n] <file1> <file2> ... <filen> > <html-file>
|
||||
#
|
||||
# file1, file2, ... filen should be successive BloatView files generated from the same run.
|
||||
# Summarize them in an HTML table. Output the HTML to the standard output.
|
||||
#
|
||||
# If -debug is set, create a slightly larger html file which is more suitable for debugging this script.
|
||||
# If -source is set, create an html file that prints the html source as the output
|
||||
# If -byte n, -obj n, or -ref n is given, make the page default to showing byte, object, or reference statistics,
|
||||
# respectively, and sort by the nth column (n is zero-based, so the first column has n==0).
|
||||
#
|
||||
# See http://lxr.mozilla.org/mozilla/source/xpcom/doc/MemoryTools.html
|
||||
|
||||
use 5.004;
|
||||
use strict;
|
||||
use diagnostics;
|
||||
use File::Basename;
|
||||
use Getopt::Long;
|
||||
|
||||
# The generated HTML is almost entirely generated by a script. Only the <HTML>, <HEAD>, and <BODY> elements are explicit
|
||||
# because a <SCRIPT> element cannot officially be a direct descendant of an <HTML> element.
|
||||
# The script itself is almost all generated by an eval of a large string. This allows the script to reproduce itself
|
||||
# when making a new page using document.write's. Re-sorting the page causes it to regenerate itself in this way.
|
||||
|
||||
|
||||
|
||||
# Return the file's modification date.
|
||||
sub fileModDate($) {
|
||||
my ($pathName) = @_;
|
||||
my ($dev, $ino, $mode, $nlink, $uid, $gid, $rdev, $size, $atime, $mtime, $ctime, $blksize, $blocks) =
|
||||
stat $pathName or die "Can't stat '$pathName'";
|
||||
return $mtime;
|
||||
}
|
||||
|
||||
|
||||
sub fileCoreName($) {
|
||||
my ($pathName) = @_;
|
||||
my $fileName = basename($pathName, "");
|
||||
$fileName =~ s/\..*//;
|
||||
return $fileName;
|
||||
}
|
||||
|
||||
|
||||
# Convert a raw string into a single-quoted JavaScript string.
|
||||
sub singleQuoteString($) {
|
||||
local ($_) = @_;
|
||||
s/\\/\\\\/g;
|
||||
s/'/\\'/g;
|
||||
s/\n/\\n/g;
|
||||
s/<\//<\\\//g;
|
||||
return "'$_'";
|
||||
}
|
||||
|
||||
|
||||
# Convert a raw string into a double-quoted JavaScript string.
|
||||
sub doubleQuoteString($) {
|
||||
local ($_) = @_;
|
||||
s/\\/\\\\/g;
|
||||
s/"/\\"/g;
|
||||
s/\n/\\n/g;
|
||||
s/<\//<\\\//g;
|
||||
return "\"$_\"";
|
||||
}
|
||||
|
||||
|
||||
# Quote special HTML characters in the string.
|
||||
sub quoteHTML($) {
|
||||
local ($_) = @_;
|
||||
s/\&/&/g;
|
||||
s/</</g;
|
||||
s/>/>/g;
|
||||
s/ / /g;
|
||||
s/\n/<BR>\n/g;
|
||||
return $_;
|
||||
}
|
||||
|
||||
|
||||
# Write the generated page to the standard output.
|
||||
# The script source code is read from this file past the __END__ marker
|
||||
# @$scriptData is the JavaScript source for the tables passed to JavaScript. Each entry is one line of JavaScript.
|
||||
# @$persistentScriptData is the same as @scriptData, but persists when the page reloads itself.
|
||||
# If $debug is true, generate the script directly instead of having it eval itself.
|
||||
# If $source is true, generate a script that displays the page's source instead of the page itself.
|
||||
sub generate(\@\@$$$$) {
|
||||
my ($scriptData, $persistentScriptData, $debug, $source, $showMode, $sortColumn) = @_;
|
||||
|
||||
my @scriptSource = <DATA>;
|
||||
chomp @scriptSource;
|
||||
print <<'EOS';
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<SCRIPT type="text/javascript">
|
||||
EOS
|
||||
|
||||
foreach (@$scriptData) {print "$_\n";}
|
||||
print "\n";
|
||||
|
||||
print "var srcArray = [\n";
|
||||
my @quotedScriptSource = map {
|
||||
my $line = $_;
|
||||
$line =~ s/^\s+//g;
|
||||
# $line =~ s/^\/\/SOURCE\s+//g if $source;
|
||||
$line =~ s/^\/\/.*//g;
|
||||
$line =~ s/\s+$//g;
|
||||
$line eq "" ? () : $line
|
||||
} @$persistentScriptData, @scriptSource;
|
||||
my $lastQuotedLine = pop @quotedScriptSource;
|
||||
foreach (@quotedScriptSource) {print doubleQuoteString($_), ",\n";}
|
||||
print doubleQuoteString($lastQuotedLine), "];\n\n";
|
||||
|
||||
if ($debug) {
|
||||
push @quotedScriptSource, $lastQuotedLine;
|
||||
foreach (@quotedScriptSource) {
|
||||
s/<\//<\\\//g; # This fails if a regexp ends with a '<'. Oh well....
|
||||
print "$_\n";
|
||||
}
|
||||
print "\n";
|
||||
} else {
|
||||
print "eval(srcArray.join(\"\\n\"));\n\n";
|
||||
}
|
||||
print "showMode = $showMode;\n";
|
||||
print "sortColumn = $sortColumn;\n";
|
||||
if ($source) {
|
||||
print <<'EOS';
|
||||
function writeQuotedHTML(s) {
|
||||
document.write(quoteHTML(s.toString()).replace(/\n/g, '<BR>\n'));
|
||||
}
|
||||
|
||||
var quotingDocument = {
|
||||
write: function () {
|
||||
for (var i = 0; i < arguments.length; i++)
|
||||
writeQuotedHTML(arguments[i]);
|
||||
},
|
||||
writeln: function () {
|
||||
for (var i = 0; i < arguments.length; i++)
|
||||
writeQuotedHTML(arguments[i]);
|
||||
document.writeln('<BR>');
|
||||
}
|
||||
};
|
||||
EOS
|
||||
} else {
|
||||
print "showHead(document);\n";
|
||||
}
|
||||
print "</SCRIPT>\n";
|
||||
print "</HEAD>\n\n";
|
||||
print "<BODY>\n";
|
||||
if ($source) {
|
||||
print "<P><TT>";
|
||||
print quoteHTML "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" \"http://www.w3.org/TR/REC-html40/loose.dtd\">\n";
|
||||
print quoteHTML "<HTML>\n";
|
||||
print quoteHTML "<HEAD>\n";
|
||||
print "<SCRIPT type=\"text/javascript\">showHead(quotingDocument);</SCRIPT>\n";
|
||||
print quoteHTML "</HEAD>\n\n";
|
||||
print quoteHTML "<BODY>\n";
|
||||
print "<SCRIPT type=\"text/javascript\">showBody(quotingDocument);</SCRIPT>\n";
|
||||
print quoteHTML "</BODY>\n";
|
||||
print quoteHTML "</HTML>\n";
|
||||
print "</TT></P>\n";
|
||||
} else {
|
||||
print "<SCRIPT type=\"text/javascript\">showBody(document);</SCRIPT>\n";
|
||||
}
|
||||
print "</BODY>\n";
|
||||
print "</HTML>\n";
|
||||
}
|
||||
|
||||
|
||||
|
||||
# Read the bloat file into hash table $h. The hash table is indexed by class names;
|
||||
# each entry is a list with the following elements:
|
||||
# bytesAlloc Total number of bytes allocated
|
||||
# bytesNet Total number of bytes allocated but not deallocated
|
||||
# objectsAlloc Total number of objects allocated
|
||||
# objectsNet Total number of objects allocated but not deallocated
|
||||
# refsAlloc Total number of references AddRef'd
|
||||
# refsNet Total number of references AddRef'd but not Released
|
||||
# Except for TOTAL, all hash table entries refer to mutually exclusive data.
|
||||
# $sizes is a hash table indexed by class names. Each entry of that table contains the class's instance size.
|
||||
sub readBloatFile($\%\%) {
|
||||
my ($file, $h, $sizes) = @_;
|
||||
local $_; # Needed for 'while (<FILE>)' below.
|
||||
|
||||
my $readSomething = 0;
|
||||
open FILE, $file;
|
||||
while (<FILE>) {
|
||||
if (my ($name, $size, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet) =
|
||||
/^\s*(?:\d+)\s+([\w:]+)\s+(\d+)\s+(-?\d+)\s+(\d+)\s+(-?\d+)\s*\([^()]*\)\s*(\d+)\s+(-?\d+)\s*\([^()]*\)\s*$/) {
|
||||
my $bytesAlloc;
|
||||
if ($name eq "TOTAL") {
|
||||
$size = "undefined";
|
||||
$bytesAlloc = "undefined";
|
||||
} else {
|
||||
$bytesAlloc = $objectsAlloc * $size;
|
||||
if ($bytesNet != $objectsNet * $size) {
|
||||
print STDERR "In '$file', class $name bytesNet != objectsNet * size: $bytesNet != $objectsNet * $size\n";
|
||||
}
|
||||
}
|
||||
print STDERR "Duplicate entry $name in '$file'\n" if $$h{$name};
|
||||
$$h{$name} = [$bytesAlloc, $bytesNet, $objectsAlloc, $objectsNet, $refsAlloc, $refsNet];
|
||||
|
||||
my $oldSize = $$sizes{$name};
|
||||
print STDERR "Mismatch of sizes of class $name: $oldSize and $size\n" if defined($oldSize) && $size ne $oldSize;
|
||||
$$sizes{$name} = $size;
|
||||
$readSomething = 1;
|
||||
} elsif (/^\s*(?:\d+)\s+([\w:]+)\s/) {
|
||||
print STDERR "Unable to parse '$file' line: $_";
|
||||
}
|
||||
}
|
||||
close FILE;
|
||||
print STDERR "No data in '$file'\n" unless $readSomething;
|
||||
return $h;
|
||||
}
|
||||
|
||||
|
||||
my %sizes; # <class-name> => <instance-size>
|
||||
my %tables; # <file-name> => <bloat-table>; see readBloatFile for format of <bloat-table>
|
||||
|
||||
# Generate the JavaScript source code for the row named $c. $l can contain the initial entries of the row.
|
||||
sub genTableRowSource($$) {
|
||||
my ($l, $c) = @_;
|
||||
my $lastE;
|
||||
foreach (@ARGV) {
|
||||
my $e = $tables{$_}{$c};
|
||||
if (defined($lastE) && !defined($e)) {
|
||||
$e = [0,0,0,0,0,0];
|
||||
print STDERR "Class $c is defined in an earlier file but not in '$_'\n";
|
||||
}
|
||||
if (defined $e) {
|
||||
if (defined $lastE) {
|
||||
for (my $i = 0; $i <= $#$e; $i++) {
|
||||
my $n = $$e[$i];
|
||||
$l .= ($n eq "undefined" ? "undefined" : $n - $$lastE[$i]) . ",";
|
||||
}
|
||||
$l .= " ";
|
||||
} else {
|
||||
$l .= join(",", @$e) . ", ";
|
||||
}
|
||||
$lastE = $e;
|
||||
} else {
|
||||
$l .= "0,0,0,0,0,0, ";
|
||||
}
|
||||
}
|
||||
$l .= join(",", @$lastE);
|
||||
return "[$l]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
my $debug;
|
||||
my $source;
|
||||
my $showMode;
|
||||
my $sortColumn;
|
||||
my @modeOptions;
|
||||
|
||||
GetOptions("debug" => \$debug, "source" => \$source, "byte=i" => \$modeOptions[0], "obj=i" => \$modeOptions[1], "ref=i" => \$modeOptions[2]);
|
||||
for (my $i = 0; $i != 3; $i++) {
|
||||
my $modeOption = $modeOptions[$i];
|
||||
if ($modeOption) {
|
||||
die "Only one of -byte, -obj, or -ref may be given" if defined $showMode;
|
||||
my $nFileColumns = scalar(@ARGV) + 1;
|
||||
die "-byte, -obj, or -ref column number out of range" if $modeOption < 0 || $modeOption >= 2 + 2*$nFileColumns;
|
||||
$showMode = $i;
|
||||
if ($modeOption >= 2) {
|
||||
$modeOption -= 2;
|
||||
$sortColumn = 2 + $showMode*2;
|
||||
if ($modeOption >= $nFileColumns) {
|
||||
$sortColumn++;
|
||||
$modeOption -= $nFileColumns;
|
||||
}
|
||||
$sortColumn += $modeOption*6;
|
||||
} else {
|
||||
$sortColumn = $modeOption;
|
||||
}
|
||||
}
|
||||
}
|
||||
unless (defined $showMode) {
|
||||
$showMode = 0;
|
||||
$sortColumn = 0;
|
||||
}
|
||||
|
||||
# Read all of the bloat files.
|
||||
foreach (@ARGV) {
|
||||
unless ($tables{$_}) {
|
||||
my $f = $_;
|
||||
my %table;
|
||||
|
||||
readBloatFile $_, %table, %sizes;
|
||||
$tables{$_} = \%table;
|
||||
}
|
||||
}
|
||||
die "No input" unless %sizes;
|
||||
|
||||
my @scriptData; # JavaScript source for the tables passed to JavaScript. Each entry is one line of JavaScript.
|
||||
my @persistentScriptData; # Same as @scriptData, but persists the page reloads itself.
|
||||
|
||||
# Print a list of bloat file names.
|
||||
push @persistentScriptData, "var nFiles = " . scalar(@ARGV) . ";";
|
||||
push @persistentScriptData, "var fileTags = [" . join(", ", map {singleQuoteString substr(fileCoreName($_), -10)} @ARGV) . "];";
|
||||
push @persistentScriptData, "var fileNames = [" . join(", ", map {singleQuoteString $_} @ARGV) . "];";
|
||||
push @persistentScriptData, "var fileDates = [" . join(", ", map {singleQuoteString localtime fileModDate $_} @ARGV) . "];";
|
||||
|
||||
# Print the bloat tables.
|
||||
push @persistentScriptData, "var totals = " . genTableRowSource('"TOTAL", undefined, ', "TOTAL") . ";";
|
||||
push @scriptData, "var classTables = [";
|
||||
delete $sizes{"TOTAL"};
|
||||
my @classes = sort(keys %sizes);
|
||||
for (my $i = 0; $i <= $#classes; $i++) {
|
||||
my $c = $classes[$i];
|
||||
push @scriptData, genTableRowSource(doubleQuoteString($c).", ".$sizes{$c}.", ", $c) . ($i == $#classes ? "];" : ",");
|
||||
}
|
||||
|
||||
generate(@scriptData, @persistentScriptData, $debug, $source, $showMode, $sortColumn);
|
||||
1;
|
||||
|
||||
|
||||
# The source of the eval'd JavaScript follows.
|
||||
# Comments starting with // that are alone on a line are stripped by the Perl script.
|
||||
__END__
|
||||
|
||||
// showMode: 0=bytes, 1=objects, 2=references
|
||||
var showMode;
|
||||
var modeName;
|
||||
var modeNameUpper;
|
||||
|
||||
var sortColumn;
|
||||
|
||||
// Sort according to the sortColumn. Column 0 is sorted alphabetically in ascending order.
|
||||
// All other columns are sorted numerically in descending order, with column 0 used for a secondary sort.
|
||||
// Undefined is always listed last.
|
||||
function sortCompare(x, y) {
|
||||
if (sortColumn) {
|
||||
var xc = x[sortColumn];
|
||||
var yc = y[sortColumn];
|
||||
if (xc < yc || xc === undefined && yc !== undefined) return 1;
|
||||
if (yc < xc || yc === undefined && xc !== undefined) return -1;
|
||||
}
|
||||
|
||||
var x0 = x[0];
|
||||
var y0 = y[0];
|
||||
if (x0 > y0 || x0 === undefined && y0 !== undefined) return 1;
|
||||
if (y0 > x0 || y0 === undefined && x0 !== undefined) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Quote special HTML characters in the string.
|
||||
function quoteHTML(s) {
|
||||
s = s.replace(/&/g, '&');
|
||||
// Can't use /</g because HTML interprets '</g' as ending the script!
|
||||
s = s.replace(/\x3C/g, '<');
|
||||
s = s.replace(/>/g, '>');
|
||||
s = s.replace(/ /g, ' ');
|
||||
return s;
|
||||
}
|
||||
|
||||
|
||||
function writeFileTable(d) {
|
||||
d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
|
||||
d.writeln('<TR>\n<TH>Name</TH>\n<TH>File</TH>\n<TH>Date</TH>\n</TR>');
|
||||
for (var i = 0; i < nFiles; i++)
|
||||
d.writeln('<TR>\n<TD>'+quoteHTML(fileTags[i])+'</TD>\n<TD><TT>'+quoteHTML(fileNames[i])+'</TT></TD>\n<TD>'+quoteHTML(fileDates[i])+'</TD>\n</TR>');
|
||||
d.writeln('</TABLE>');
|
||||
}
|
||||
|
||||
|
||||
function writeReloadLink(d, column, s, rowspan) {
|
||||
d.write(rowspan == 1 ? '<TH>' : '<TH rowspan='+rowspan+'>');
|
||||
if (column != sortColumn)
|
||||
d.write('<A href="javascript:reloadSelf('+column+','+showMode+')">');
|
||||
d.write(s);
|
||||
if (column != sortColumn)
|
||||
d.write('</A>');
|
||||
d.writeln('</TH>');
|
||||
}
|
||||
|
||||
function writeClassTableRow(d, row, base, modeName) {
|
||||
if (modeName) {
|
||||
d.writeln('<TR>\n<TH>'+modeName+'</TH>');
|
||||
} else {
|
||||
d.writeln('<TR>\n<TD><A href="javascript:showRowDetail(\''+row[0]+'\')">'+quoteHTML(row[0])+'</A></TD>');
|
||||
var v = row[1];
|
||||
d.writeln('<TD class=num>'+(v === undefined ? '' : v)+'</TD>');
|
||||
}
|
||||
for (var i = 0; i != 2; i++) {
|
||||
var c = base + i;
|
||||
for (var j = 0; j <= nFiles; j++) {
|
||||
v = row[c];
|
||||
var style = 'num';
|
||||
if (j != nFiles)
|
||||
if (v > 0) {
|
||||
style = 'pos';
|
||||
v = '+'+v;
|
||||
} else
|
||||
style = 'neg';
|
||||
d.writeln('<TD class='+style+'>'+(v === undefined ? '' : v)+'</TD>');
|
||||
c += 6;
|
||||
}
|
||||
}
|
||||
d.writeln('</TR>');
|
||||
}
|
||||
|
||||
function writeClassTable(d) {
|
||||
var base = 2 + showMode*2;
|
||||
|
||||
// Make a copy because a sort is destructive.
|
||||
var table = classTables.concat();
|
||||
table.sort(sortCompare);
|
||||
|
||||
d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
|
||||
|
||||
d.writeln('<TR>');
|
||||
writeReloadLink(d, 0, 'Class Name', 2);
|
||||
writeReloadLink(d, 1, 'Instance<BR>Size', 2);
|
||||
d.writeln('<TH colspan='+(nFiles+1)+'>'+modeNameUpper+'s allocated</TH>');
|
||||
d.writeln('<TH colspan='+(nFiles+1)+'>'+modeNameUpper+'s allocated but not freed</TH>\n</TR>');
|
||||
d.writeln('<TR>');
|
||||
for (var i = 0; i != 2; i++) {
|
||||
var c = base + i;
|
||||
for (var j = 0; j <= nFiles; j++) {
|
||||
writeReloadLink(d, c, j == nFiles ? 'Total' : quoteHTML(fileTags[j]), 1);
|
||||
c += 6;
|
||||
}
|
||||
}
|
||||
d.writeln('</TR>');
|
||||
|
||||
writeClassTableRow(d, totals, base, 0);
|
||||
for (var r = 0; r < table.length; r++)
|
||||
writeClassTableRow(d, table[r], base, 0);
|
||||
|
||||
d.writeln('</TABLE>');
|
||||
}
|
||||
|
||||
|
||||
var modeNames = ["byte", "object", "reference"];
|
||||
var modeNamesUpper = ["Byte", "Object", "Reference"];
|
||||
var styleSheet = '<STYLE type="TEXT/CSS">\n'+
|
||||
'BODY {background-color: #FFFFFF; color: #000000}\n'+
|
||||
'.num {text-align: right}\n'+
|
||||
'.pos {text-align: right; color: #CC0000}\n'+
|
||||
'.neg {text-align: right; color: #009900}\n'+
|
||||
'</STYLE>';
|
||||
|
||||
|
||||
function showHead(d) {
|
||||
modeName = modeNames[showMode];
|
||||
modeNameUpper = modeNamesUpper[showMode];
|
||||
d.writeln('<TITLE>'+modeNameUpper+' Bloats</TITLE>');
|
||||
d.writeln(styleSheet);
|
||||
}
|
||||
|
||||
function showBody(d) {
|
||||
d.writeln('<H1>'+modeNameUpper+' Bloats</H1>');
|
||||
writeFileTable(d);
|
||||
d.write('<FORM>');
|
||||
for (var i = 0; i != 3; i++)
|
||||
if (i != showMode) {
|
||||
var newSortColumn = sortColumn;
|
||||
if (sortColumn >= 2)
|
||||
newSortColumn = sortColumn + (i-showMode)*2;
|
||||
d.write('<INPUT type="button" value="Show '+modeNamesUpper[i]+'s" onClick="reloadSelf('+newSortColumn+','+i+')">');
|
||||
}
|
||||
d.writeln('</FORM>');
|
||||
d.writeln('<P>The numbers do not include <CODE>malloc</CODE>\'d data such as string contents.</P>');
|
||||
d.writeln('<P>Click on a column heading to sort by that column. Click on a class name to see details for that class.</P>');
|
||||
writeClassTable(d);
|
||||
}
|
||||
|
||||
|
||||
function showRowDetail(rowName) {
|
||||
var row;
|
||||
var i;
|
||||
|
||||
if (rowName == "TOTAL")
|
||||
row = totals;
|
||||
else {
|
||||
for (i = 0; i < classTables.length; i++)
|
||||
if (rowName == classTables[i][0]) {
|
||||
row = classTables[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (row) {
|
||||
var w = window.open("", "ClassTableRowDetails");
|
||||
var d = w.document;
|
||||
d.open();
|
||||
d.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">');
|
||||
d.writeln('<HTML>\n<HEAD>\n<TITLE>'+quoteHTML(rowName)+' bloat details</TITLE>');
|
||||
d.writeln(styleSheet);
|
||||
d.writeln('</HEAD>\n\n<BODY>');
|
||||
d.writeln('<H2>'+quoteHTML(rowName)+'</H2>');
|
||||
if (row[1] !== undefined)
|
||||
d.writeln('<P>Each instance has '+row[1]+' bytes.</P>');
|
||||
|
||||
d.writeln('<TABLE border=1 cellspacing=1 cellpadding=0>');
|
||||
d.writeln('<TR>\n<TH></TH>\n<TH colspan='+(nFiles+1)+'>Allocated</TH>');
|
||||
d.writeln('<TH colspan='+(nFiles+1)+'>Allocated but not freed</TH>\n</TR>');
|
||||
d.writeln('<TR>\n<TH></TH>');
|
||||
for (i = 0; i != 2; i++)
|
||||
for (var j = 0; j <= nFiles; j++)
|
||||
d.writeln('<TH>'+(j == nFiles ? 'Total' : quoteHTML(fileTags[j]))+'</TH>');
|
||||
d.writeln('</TR>');
|
||||
|
||||
for (i = 0; i != 3; i++)
|
||||
writeClassTableRow(d, row, 2+i*2, modeNamesUpper[i]+'s');
|
||||
|
||||
d.writeln('</TABLE>\n</BODY>\n</HTML>');
|
||||
d.close();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
function stringSource(s) {
|
||||
s = s.replace(/\\/g, '\\\\');
|
||||
s = s.replace(/"/g, '\\"');
|
||||
s = s.replace(/<\//g, '<\\/');
|
||||
return '"'+s+'"';
|
||||
}
|
||||
|
||||
function reloadSelf(n,m) {
|
||||
// Need to cache these because globals go away on document.open().
|
||||
var sa = srcArray;
|
||||
var ss = stringSource;
|
||||
var ct = classTables;
|
||||
var i;
|
||||
|
||||
document.open();
|
||||
// Uncomment this and comment the document.open() line above to see the reloaded page's source.
|
||||
//var w = window.open("", "NewDoc");
|
||||
//var d = w.document;
|
||||
//var document = new Object;
|
||||
//document.write = function () {
|
||||
// for (var i = 0; i < arguments.length; i++) {
|
||||
// var s = arguments[i].toString();
|
||||
// s = s.replace(/&/g, '&');
|
||||
// s = s.replace(/\x3C/g, '<');
|
||||
// s = s.replace(/>/g, '>');
|
||||
// s = s.replace(/ /g, ' ');
|
||||
// d.write(s);
|
||||
// }
|
||||
//};
|
||||
//document.writeln = function () {
|
||||
// for (var i = 0; i < arguments.length; i++) {
|
||||
// var s = arguments[i].toString();
|
||||
// s = s.replace(/&/g, '&');
|
||||
// s = s.replace(/\x3C/g, '<');
|
||||
// s = s.replace(/>/g, '>');
|
||||
// s = s.replace(/ /g, ' ');
|
||||
// d.write(s);
|
||||
// }
|
||||
// d.writeln('<BR>');
|
||||
//};
|
||||
|
||||
document.writeln('<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">');
|
||||
document.writeln('<HTML>\n<HEAD>\n<SCRIPT type="text/javascript">');
|
||||
|
||||
// Manually copy non-persistent script data
|
||||
if (!ct.length)
|
||||
document.writeln('var classTables = [];');
|
||||
else {
|
||||
document.writeln('var classTables = [');
|
||||
for (i = 0; i < ct.length; i++) {
|
||||
var row = ct[i];
|
||||
document.write('[' + ss(row[0]));
|
||||
for (var j = 1; j < row.length; j++)
|
||||
document.write(',' + row[j]);
|
||||
document.writeln(']' + (i == ct.length-1 ? '];' : ','));
|
||||
}
|
||||
}
|
||||
|
||||
document.writeln('var srcArray = [');
|
||||
for (i = 0; i < sa.length; i++) {
|
||||
document.write(ss(sa[i]));
|
||||
if (i != sa.length-1)
|
||||
document.writeln(',');
|
||||
}
|
||||
document.writeln('];');
|
||||
document.writeln('eval(srcArray.join("\\n"));');
|
||||
document.writeln('showMode = '+m+';');
|
||||
document.writeln('sortColumn = '+n+';');
|
||||
document.writeln('showHead(document);');
|
||||
document.writeln('</SCRIPT>\n</HEAD>\n\n<BODY>\n<SCRIPT type="text/javascript">showBody(document);</SCRIPT>\n</BODY>\n</HTML>');
|
||||
document.close();
|
||||
return undefined;
|
||||
}
|
||||
172
tools/check-moz-style/checkmozstyle.py
Normal file
172
tools/check-moz-style/checkmozstyle.py
Normal file
|
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# Copyright (C) 2009 Google Inc. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""Script to run the linter for source code of WebKit."""
|
||||
|
||||
import os
|
||||
import os.path
|
||||
import re
|
||||
import sys
|
||||
|
||||
import modules.cpplint as cpplint
|
||||
from modules.diff_parser import DiffParser
|
||||
from modules.scm import detect_scm_system
|
||||
|
||||
|
||||
# Override the usage of the lint tool.
|
||||
cpplint._USAGE = """
|
||||
Syntax: %(program_name)s [--verbose=#] [--git-commit=<COMMITISH>] [--output=vs7] [--filter=-x,+y,...]
|
||||
|
||||
The style guidelines this tries to follow are those in
|
||||
http://webkit.org/coding/coding-style.html
|
||||
|
||||
Every problem is given a confidence score from 1-5, with 5 meaning we are
|
||||
certain of the problem, and 1 meaning it could be a legitimate construct.
|
||||
This will miss some errors, and is not a substitute for a code review.
|
||||
|
||||
To prevent specific lines from being linted, add a '// NOLINT' comment to the
|
||||
end of the line.
|
||||
|
||||
Linted extensions are .cpp, .c and .h. Other file types will be ignored.
|
||||
|
||||
Flags:
|
||||
|
||||
verbose=#
|
||||
Specify a number 0-5 to restrict errors to certain verbosity levels.
|
||||
|
||||
git-commit=<COMMITISH>
|
||||
Check style for a specified git commit.
|
||||
Note that the program checks style based on current local file
|
||||
instead of actual diff of the git commit. So, if the files are
|
||||
updated after the specified git commit, the information of line
|
||||
number may be wrong.
|
||||
|
||||
output=vs7
|
||||
By default, the output is formatted to ease emacs parsing. Visual Studio
|
||||
compatible output (vs7) may also be used. Other formats are unsupported.
|
||||
|
||||
filter=-x,+y,...
|
||||
Specify a comma-separated list of category-filters to apply: only
|
||||
error messages whose category names pass the filters will be printed.
|
||||
(Category names are printed with the message and look like
|
||||
"[whitespace/indent]".) Filters are evaluated left to right.
|
||||
"-FOO" and "FOO" means "do not print categories that start with FOO".
|
||||
"+FOO" means "do print categories that start with FOO".
|
||||
|
||||
Examples: --filter=-whitespace,+whitespace/braces
|
||||
--filter=whitespace,runtime/printf,+runtime/printf_format
|
||||
--filter=-,+build/include_what_you_use
|
||||
|
||||
To see a list of all the categories used in %(program_name)s, pass no arg:
|
||||
--filter=
|
||||
""" % {'program_name': sys.argv[0]}
|
||||
|
||||
def process_patch(patch_string, root, cwd, scm):
|
||||
"""Does lint on a single patch.
|
||||
|
||||
Args:
|
||||
patch_string: A string of a patch.
|
||||
"""
|
||||
patch = DiffParser(patch_string.splitlines())
|
||||
|
||||
if not len(patch.files):
|
||||
cpplint.error("patch", 0, "patch/notempty", 3,
|
||||
"Patch does not appear to diff against any file.")
|
||||
return
|
||||
|
||||
if not patch.status_line:
|
||||
cpplint.error("patch", 0, "patch/nosummary", 3,
|
||||
"Patch does not have a summary.")
|
||||
else:
|
||||
proper_format = re.match(r"^Bug [0-9]+ - ", patch.status_line)
|
||||
if not proper_format:
|
||||
proper_format = re.match(r"^No bug - ", patch.status_line)
|
||||
cpplint.error("patch", 0, "patch/bugnumber", 3,
|
||||
"Patch summary should begin with 'Bug XXXXX - ' " +
|
||||
"or 'No bug -'.")
|
||||
|
||||
if not patch.patch_description:
|
||||
cpplint.error("patch", 0, "patch/nodescription", 3,
|
||||
"Patch does not have a description.")
|
||||
|
||||
for filename, diff in patch.files.iteritems():
|
||||
file_extension = os.path.splitext(filename)[1]
|
||||
|
||||
if file_extension in ['.cpp', '.c', '.h']:
|
||||
line_numbers = set()
|
||||
orig_filename = filename
|
||||
|
||||
def error_for_patch(filename, line_number, category, confidence,
|
||||
message):
|
||||
"""Wrapper function of cpplint.error for patches.
|
||||
|
||||
This function outputs errors only if the line number
|
||||
corresponds to lines which are modified or added.
|
||||
"""
|
||||
if not line_numbers:
|
||||
for line in diff.lines:
|
||||
# When deleted line is not set, it means that
|
||||
# the line is newly added.
|
||||
if not line[0]:
|
||||
line_numbers.add(line[1])
|
||||
|
||||
if line_number in line_numbers:
|
||||
cpplint.error(orig_filename, line_number,
|
||||
category, confidence, message)
|
||||
|
||||
cpplint.process_file(os.path.join(root, filename),
|
||||
relative_name=orig_filename,
|
||||
error=error_for_patch)
|
||||
|
||||
|
||||
def main():
|
||||
cpplint.use_mozilla_styles()
|
||||
|
||||
(args, flags) = cpplint.parse_arguments(sys.argv[1:], ["git-commit="])
|
||||
if args:
|
||||
sys.stderr.write("ERROR: We don't support files as arguments for " +
|
||||
"now.\n" + cpplint._USAGE)
|
||||
sys.exit(1)
|
||||
|
||||
cwd = os.path.abspath('.')
|
||||
scm = detect_scm_system(cwd)
|
||||
root = scm.find_checkout_root(cwd)
|
||||
|
||||
if "--git-commit" in flags:
|
||||
process_patch(scm.create_patch_from_local_commit(flags["--git-commit"]), root, cwd, scm)
|
||||
else:
|
||||
process_patch(scm.create_patch(), root, cwd, scm)
|
||||
|
||||
sys.stderr.write('Total errors found: %d\n' % cpplint.error_count())
|
||||
sys.exit(cpplint.error_count() > 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
162
tools/check-moz-style/diff_parser.py
Normal file
162
tools/check-moz-style/diff_parser.py
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
# Copyright (C) 2009 Google Inc. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""WebKit's Python module for interacting with patches."""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
|
||||
_regexp_compile_cache = {}
|
||||
|
||||
|
||||
def match(pattern, string):
|
||||
"""Matches the string with the pattern, caching the compiled regexp."""
|
||||
if not pattern in _regexp_compile_cache:
|
||||
_regexp_compile_cache[pattern] = re.compile(pattern)
|
||||
return _regexp_compile_cache[pattern].match(string)
|
||||
|
||||
|
||||
def git_diff_to_svn_diff(line):
|
||||
"""Converts a git formatted diff line to a svn formatted line.
|
||||
|
||||
Args:
|
||||
line: A string representing a line of the diff.
|
||||
"""
|
||||
conversion_patterns = (("^diff --git a/(.+) b/(?P<FilePath>.+)", lambda matched: "Index: " + matched.group('FilePath') + "\n"),
|
||||
("^new file.*", lambda matched: "\n"),
|
||||
("^index [0-9a-f]{7}\.\.[0-9a-f]{7} [0-9]{6}", lambda matched: "===================================================================\n"),
|
||||
("^--- a/(?P<FilePath>.+)", lambda matched: "--- " + matched.group('FilePath') + "\n"),
|
||||
("^\+\+\+ b/(?P<FilePath>.+)", lambda matched: "+++ " + matched.group('FilePath') + "\n"))
|
||||
|
||||
for pattern, conversion in conversion_patterns:
|
||||
matched = match(pattern, line)
|
||||
if matched:
|
||||
return conversion(matched)
|
||||
return line
|
||||
|
||||
|
||||
def get_diff_converter(first_diff_line):
|
||||
"""Gets a converter function of diff lines.
|
||||
|
||||
Args:
|
||||
first_diff_line: The first filename line of a diff file.
|
||||
If this line is git formatted, we'll return a
|
||||
converter from git to SVN.
|
||||
"""
|
||||
if match(r"^diff --git a/", first_diff_line):
|
||||
return git_diff_to_svn_diff
|
||||
return lambda input: input
|
||||
|
||||
|
||||
_INITIAL_STATE = 1
|
||||
_DECLARED_FILE_PATH = 2
|
||||
_PROCESSING_CHUNK = 3
|
||||
|
||||
|
||||
class DiffFile:
|
||||
"""Contains the information for one file in a patch.
|
||||
|
||||
The field "lines" is a list which contains tuples in this format:
|
||||
(deleted_line_number, new_line_number, line_string)
|
||||
If deleted_line_number is zero, it means this line is newly added.
|
||||
If new_line_number is zero, it means this line is deleted.
|
||||
"""
|
||||
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.lines = []
|
||||
|
||||
def add_new_line(self, line_number, line):
|
||||
self.lines.append((0, line_number, line))
|
||||
|
||||
def add_deleted_line(self, line_number, line):
|
||||
self.lines.append((line_number, 0, line))
|
||||
|
||||
def add_unchanged_line(self, deleted_line_number, new_line_number, line):
|
||||
self.lines.append((deleted_line_number, new_line_number, line))
|
||||
|
||||
|
||||
class DiffParser:
|
||||
"""A parser for a patch file.
|
||||
|
||||
The field "files" is a dict whose key is the filename and value is
|
||||
a DiffFile object.
|
||||
"""
|
||||
|
||||
def __init__(self, diff_input):
|
||||
"""Parses a diff.
|
||||
|
||||
Args:
|
||||
diff_input: An iterable object.
|
||||
"""
|
||||
state = _INITIAL_STATE
|
||||
|
||||
self.files = {}
|
||||
current_file = None
|
||||
old_diff_line = None
|
||||
new_diff_line = None
|
||||
for line in diff_input:
|
||||
line = line.rstrip("\n")
|
||||
if state == _INITIAL_STATE:
|
||||
transform_line = get_diff_converter(line)
|
||||
line = transform_line(line)
|
||||
|
||||
file_declaration = match(r"^Index: (?P<FilePath>.+)", line)
|
||||
if file_declaration:
|
||||
filename = file_declaration.group('FilePath')
|
||||
current_file = DiffFile(filename)
|
||||
self.files[filename] = current_file
|
||||
state = _DECLARED_FILE_PATH
|
||||
continue
|
||||
|
||||
lines_changed = match(r"^@@ -(?P<OldStartLine>\d+)(,\d+)? \+(?P<NewStartLine>\d+)(,\d+)? @@", line)
|
||||
if lines_changed:
|
||||
if state != _DECLARED_FILE_PATH and state != _PROCESSING_CHUNK:
|
||||
logging.error('Unexpected line change without file path declaration: %r' % line)
|
||||
old_diff_line = int(lines_changed.group('OldStartLine'))
|
||||
new_diff_line = int(lines_changed.group('NewStartLine'))
|
||||
state = _PROCESSING_CHUNK
|
||||
continue
|
||||
|
||||
if state == _PROCESSING_CHUNK:
|
||||
if line.startswith('+'):
|
||||
current_file.add_new_line(new_diff_line, line[1:])
|
||||
new_diff_line += 1
|
||||
elif line.startswith('-'):
|
||||
current_file.add_deleted_line(old_diff_line, line[1:])
|
||||
old_diff_line += 1
|
||||
elif line.startswith(' '):
|
||||
current_file.add_unchanged_line(old_diff_line, new_diff_line, line[1:])
|
||||
old_diff_line += 1
|
||||
new_diff_line += 1
|
||||
elif line == '\\ No newline at end of file':
|
||||
# Nothing to do. We may still have some added lines.
|
||||
pass
|
||||
else:
|
||||
logging.error('Unexpected diff format when parsing a chunk: %r' % line)
|
||||
1
tools/check-moz-style/modules/__init__.py
Normal file
1
tools/check-moz-style/modules/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Required for Python to search this directory for module files
|
||||
3150
tools/check-moz-style/modules/cpplint.py
vendored
Normal file
3150
tools/check-moz-style/modules/cpplint.py
vendored
Normal file
File diff suppressed because it is too large
Load diff
180
tools/check-moz-style/modules/diff_parser.py
Normal file
180
tools/check-moz-style/modules/diff_parser.py
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
# Copyright (C) 2009 Google Inc. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
"""WebKit's Python module for interacting with patches."""
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
|
||||
_regexp_compile_cache = {}
|
||||
|
||||
|
||||
def match(pattern, string):
|
||||
"""Matches the string with the pattern, caching the compiled regexp."""
|
||||
if not pattern in _regexp_compile_cache:
|
||||
_regexp_compile_cache[pattern] = re.compile(pattern)
|
||||
return _regexp_compile_cache[pattern].match(string)
|
||||
|
||||
|
||||
def git_diff_to_svn_diff(line):
|
||||
"""Converts a git formatted diff line to a svn formatted line.
|
||||
|
||||
Args:
|
||||
line: A string representing a line of the diff.
|
||||
"""
|
||||
conversion_patterns = (("^diff --git a/(.+) b/(?P<FilePath>.+)", lambda matched: "Index: " + matched.group('FilePath') + "\n"),
|
||||
("^new file.*", lambda matched: "\n"),
|
||||
("^index [0-9a-f]{7}\.\.[0-9a-f]{7} [0-9]{6}", lambda matched: "===================================================================\n"),
|
||||
("^--- a/(?P<FilePath>.+)", lambda matched: "--- " + matched.group('FilePath') + "\n"),
|
||||
("^\+\+\+ b/(?P<FilePath>.+)", lambda matched: "+++ " + matched.group('FilePath') + "\n"))
|
||||
|
||||
for pattern, conversion in conversion_patterns:
|
||||
matched = match(pattern, line)
|
||||
if matched:
|
||||
return conversion(matched)
|
||||
return line
|
||||
|
||||
|
||||
def get_diff_converter(first_diff_line):
|
||||
"""Gets a converter function of diff lines.
|
||||
|
||||
Args:
|
||||
first_diff_line: The first filename line of a diff file.
|
||||
If this line is git formatted, we'll return a
|
||||
converter from git to SVN.
|
||||
"""
|
||||
if match(r"^diff --git a/", first_diff_line):
|
||||
return git_diff_to_svn_diff
|
||||
return lambda input: input
|
||||
|
||||
|
||||
_INITIAL_STATE = 1
|
||||
_DECLARED_FILE_PATH = 2
|
||||
_PROCESSING_CHUNK = 3
|
||||
|
||||
|
||||
class DiffFile:
|
||||
"""Contains the information for one file in a patch.
|
||||
|
||||
The field "lines" is a list which contains tuples in this format:
|
||||
(deleted_line_number, new_line_number, line_string)
|
||||
If deleted_line_number is zero, it means this line is newly added.
|
||||
If new_line_number is zero, it means this line is deleted.
|
||||
"""
|
||||
|
||||
def __init__(self, filename):
|
||||
self.filename = filename
|
||||
self.lines = []
|
||||
|
||||
def add_new_line(self, line_number, line):
|
||||
self.lines.append((0, line_number, line))
|
||||
|
||||
def add_deleted_line(self, line_number, line):
|
||||
self.lines.append((line_number, 0, line))
|
||||
|
||||
def add_unchanged_line(self, deleted_line_number, new_line_number, line):
|
||||
self.lines.append((deleted_line_number, new_line_number, line))
|
||||
|
||||
|
||||
class DiffParser:
|
||||
"""A parser for a patch file.
|
||||
|
||||
The field "files" is a dict whose key is the filename and value is
|
||||
a DiffFile object.
|
||||
"""
|
||||
|
||||
def __init__(self, diff_input):
|
||||
"""Parses a diff.
|
||||
|
||||
Args:
|
||||
diff_input: An iterable object.
|
||||
"""
|
||||
state = _INITIAL_STATE
|
||||
|
||||
self.files = {}
|
||||
self.status_line = None
|
||||
self.patch_description = None
|
||||
current_file = None
|
||||
old_diff_line = None
|
||||
new_diff_line = None
|
||||
for line in diff_input:
|
||||
line = line.rstrip("\n")
|
||||
if state == _INITIAL_STATE:
|
||||
transform_line = get_diff_converter(line)
|
||||
line = transform_line(line)
|
||||
|
||||
comment_line = match(r"^\#", line)
|
||||
if comment_line:
|
||||
continue
|
||||
|
||||
file_declaration = match(r"^Index: (?P<FilePath>.+)", line)
|
||||
if file_declaration:
|
||||
filename = file_declaration.group('FilePath')
|
||||
current_file = DiffFile(filename)
|
||||
self.files[filename] = current_file
|
||||
state = _DECLARED_FILE_PATH
|
||||
continue
|
||||
|
||||
lines_changed = match(r"^@@ -(?P<OldStartLine>\d+)(,\d+)? \+(?P<NewStartLine>\d+)(,\d+)? @@", line)
|
||||
if lines_changed:
|
||||
if state != _DECLARED_FILE_PATH and state != _PROCESSING_CHUNK:
|
||||
logging.error('Unexpected line change without file path declaration: %r' % line)
|
||||
old_diff_line = int(lines_changed.group('OldStartLine'))
|
||||
new_diff_line = int(lines_changed.group('NewStartLine'))
|
||||
state = _PROCESSING_CHUNK
|
||||
continue
|
||||
|
||||
if state == _PROCESSING_CHUNK:
|
||||
if line.startswith('+'):
|
||||
current_file.add_new_line(new_diff_line, line[1:])
|
||||
new_diff_line += 1
|
||||
elif line.startswith('-'):
|
||||
current_file.add_deleted_line(old_diff_line, line[1:])
|
||||
old_diff_line += 1
|
||||
elif line.startswith(' '):
|
||||
current_file.add_unchanged_line(old_diff_line, new_diff_line, line[1:])
|
||||
old_diff_line += 1
|
||||
new_diff_line += 1
|
||||
elif line == '\\ No newline at end of file':
|
||||
# Nothing to do. We may still have some added lines.
|
||||
pass
|
||||
else:
|
||||
logging.error('Unexpected diff format when parsing a chunk: %r' % line)
|
||||
|
||||
# Patch description
|
||||
if state == _INITIAL_STATE:
|
||||
if not self.status_line:
|
||||
self.status_line = line
|
||||
else:
|
||||
if not self.patch_description:
|
||||
# Skip the first blank line after the patch description
|
||||
if line != "":
|
||||
self.patch_description = line
|
||||
else:
|
||||
self.patch_description = self.patch_description + "\n" + line
|
||||
39
tools/check-moz-style/modules/logging.py
Normal file
39
tools/check-moz-style/modules/logging.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# Copyright (c) 2009, Google Inc. All rights reserved.
|
||||
# Copyright (c) 2009 Apple Inc. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
# WebKit's Python module for logging
|
||||
|
||||
import sys
|
||||
|
||||
def log(string):
|
||||
print >> sys.stderr, string
|
||||
|
||||
def error(string):
|
||||
log("ERROR: " + string)
|
||||
exit(1)
|
||||
420
tools/check-moz-style/modules/scm.py
Normal file
420
tools/check-moz-style/modules/scm.py
Normal file
|
|
@ -0,0 +1,420 @@
|
|||
# Copyright (c) 2009, Google Inc. All rights reserved.
|
||||
# Copyright (c) 2009 Apple Inc. All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without
|
||||
# modification, are permitted provided that the following conditions are
|
||||
# met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright
|
||||
# notice, this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above
|
||||
# copyright notice, this list of conditions and the following disclaimer
|
||||
# in the documentation and/or other materials provided with the
|
||||
# distribution.
|
||||
# * Neither the name of Google Inc. nor the names of its
|
||||
# contributors may be used to endorse or promote products derived from
|
||||
# this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
#
|
||||
# Python module for interacting with an SCM system (like SVN or Git)
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# Import WebKit-specific modules.
|
||||
from modules.logging import error, log
|
||||
|
||||
def detect_scm_system(path):
|
||||
if HG.in_working_directory(path):
|
||||
return HG(cwd=path)
|
||||
|
||||
if SVN.in_working_directory(path):
|
||||
return SVN(cwd=path)
|
||||
|
||||
if Git.in_working_directory(path):
|
||||
return Git(cwd=path)
|
||||
|
||||
raise ScriptError("working directory is not a HG/SVN/Git repo")
|
||||
|
||||
def first_non_empty_line_after_index(lines, index=0):
|
||||
first_non_empty_line = index
|
||||
for line in lines[index:]:
|
||||
if re.match("^\s*$", line):
|
||||
first_non_empty_line += 1
|
||||
else:
|
||||
break
|
||||
return first_non_empty_line
|
||||
|
||||
|
||||
class CommitMessage:
|
||||
def __init__(self, message):
|
||||
self.message_lines = message[first_non_empty_line_after_index(message, 0):]
|
||||
|
||||
def body(self, lstrip=False):
|
||||
lines = self.message_lines[first_non_empty_line_after_index(self.message_lines, 1):]
|
||||
if lstrip:
|
||||
lines = [line.lstrip() for line in lines]
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
def description(self, lstrip=False, strip_url=False):
|
||||
line = self.message_lines[0]
|
||||
if lstrip:
|
||||
line = line.lstrip()
|
||||
if strip_url:
|
||||
line = re.sub("^(\s*)<.+> ", "\1", line)
|
||||
return line
|
||||
|
||||
def message(self):
|
||||
return "\n".join(self.message_lines) + "\n"
|
||||
|
||||
def parse_bug_id(self):
|
||||
for line in self.message_lines:
|
||||
match = re.search("http\://webkit\.org/b/(?P<bug_id>\d+)", line)
|
||||
if match:
|
||||
return match.group('bug_id')
|
||||
match = re.search(Bugzilla.bug_server_regex + "show_bug\.cgi\?id=(?P<bug_id>\d+)", line)
|
||||
if match:
|
||||
return match.group('bug_id')
|
||||
return None
|
||||
|
||||
|
||||
class ScriptError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class SCM:
|
||||
def __init__(self, cwd, dryrun=False):
|
||||
self.cwd = cwd
|
||||
self.checkout_root = self.find_checkout_root(self.cwd)
|
||||
self.dryrun = dryrun
|
||||
|
||||
@staticmethod
|
||||
def run_command(args, cwd=None, input=None, raise_on_failure=True, return_exit_code=False):
|
||||
stdin = subprocess.PIPE if input else None
|
||||
process = subprocess.Popen(args, stdout=subprocess.PIPE, stdin=stdin, cwd=cwd)
|
||||
output = process.communicate(input)[0].rstrip()
|
||||
exit_code = process.wait()
|
||||
if raise_on_failure and exit_code:
|
||||
raise ScriptError('Failed to run "%s" exit_code: %d cwd: %s' % (args, exit_code, cwd))
|
||||
if return_exit_code:
|
||||
return exit_code
|
||||
return output
|
||||
|
||||
def script_path(self, script_name):
|
||||
return os.path.join(self.checkout_root, "WebKitTools", "Scripts", script_name)
|
||||
|
||||
def ensure_clean_working_directory(self, force):
|
||||
if not force and not self.working_directory_is_clean():
|
||||
print self.run_command(self.status_command(), raise_on_failure=False)
|
||||
error("Working directory has modifications, pass --force-clean or --no-clean to continue.")
|
||||
|
||||
log("Cleaning working directory")
|
||||
self.clean_working_directory()
|
||||
|
||||
def ensure_no_local_commits(self, force):
|
||||
if not self.supports_local_commits():
|
||||
return
|
||||
commits = self.local_commits()
|
||||
if not len(commits):
|
||||
return
|
||||
if not force:
|
||||
error("Working directory has local commits, pass --force-clean to continue.")
|
||||
self.discard_local_commits()
|
||||
|
||||
def apply_patch(self, patch):
|
||||
# It's possible that the patch was not made from the root directory.
|
||||
# We should detect and handle that case.
|
||||
curl_process = subprocess.Popen(['curl', patch['url']], stdout=subprocess.PIPE)
|
||||
patch_apply_process = subprocess.Popen([self.script_path('svn-apply'), '--reviewer', patch['reviewer']], stdin=curl_process.stdout)
|
||||
|
||||
return_code = patch_apply_process.wait()
|
||||
if return_code:
|
||||
raise ScriptError("Patch %s from bug %s failed to download and apply." % (patch['url'], patch['bug_id']))
|
||||
|
||||
def run_status_and_extract_filenames(self, status_command, status_regexp):
|
||||
filenames = []
|
||||
for line in self.run_command(status_command).splitlines():
|
||||
match = re.search(status_regexp, line)
|
||||
if not match:
|
||||
continue
|
||||
# status = match.group('status')
|
||||
filename = match.group('filename')
|
||||
filenames.append(filename)
|
||||
return filenames
|
||||
|
||||
@staticmethod
|
||||
def in_working_directory(path):
|
||||
raise NotImplementedError, "subclasses must implement"
|
||||
|
||||
@staticmethod
|
||||
def find_checkout_root(path):
|
||||
raise NotImplementedError, "subclasses must implement"
|
||||
|
||||
@staticmethod
|
||||
def commit_success_regexp():
|
||||
raise NotImplementedError, "subclasses must implement"
|
||||
|
||||
def working_directory_is_clean(self):
|
||||
raise NotImplementedError, "subclasses must implement"
|
||||
|
||||
def clean_working_directory(self):
|
||||
raise NotImplementedError, "subclasses must implement"
|
||||
|
||||
def update_webkit(self):
|
||||
raise NotImplementedError, "subclasses must implement"
|
||||
|
||||
def status_command(self):
|
||||
raise NotImplementedError, "subclasses must implement"
|
||||
|
||||
def changed_files(self):
|
||||
raise NotImplementedError, "subclasses must implement"
|
||||
|
||||
def display_name(self):
|
||||
raise NotImplementedError, "subclasses must implement"
|
||||
|
||||
def create_patch(self):
|
||||
raise NotImplementedError, "subclasses must implement"
|
||||
|
||||
def commit_with_message(self, message):
|
||||
raise NotImplementedError, "subclasses must implement"
|
||||
|
||||
# Subclasses must indicate if they support local commits,
|
||||
# but the SCM baseclass will only call local_commits methods when this is true.
|
||||
@staticmethod
|
||||
def supports_local_commits():
|
||||
raise NotImplementedError, "subclasses must implement"
|
||||
|
||||
def create_patch_from_local_commit(self, commit_id):
|
||||
pass
|
||||
|
||||
def commit_locally_with_message(self, message):
|
||||
pass
|
||||
|
||||
def discard_local_commits(self):
|
||||
pass
|
||||
|
||||
def local_commits(self):
|
||||
return []
|
||||
|
||||
|
||||
class SVN(SCM):
|
||||
def __init__(self, cwd, dryrun=False):
|
||||
SCM.__init__(self, cwd, dryrun)
|
||||
self.cached_version = None
|
||||
|
||||
@staticmethod
|
||||
def in_working_directory(path):
|
||||
return os.path.isdir(os.path.join(path, '.svn'))
|
||||
|
||||
@staticmethod
|
||||
def find_uuid(path):
|
||||
if not SVN.in_working_directory(path):
|
||||
return None
|
||||
info = SVN.run_command(['svn', 'info', path])
|
||||
match = re.search("^Repository UUID: (?P<uuid>.+)$", info, re.MULTILINE)
|
||||
if not match:
|
||||
raise ScriptError('svn info did not contain a UUID.')
|
||||
return match.group('uuid')
|
||||
|
||||
@staticmethod
|
||||
def find_checkout_root(path):
|
||||
uuid = SVN.find_uuid(path)
|
||||
# If |path| is not in a working directory, we're supposed to return |path|.
|
||||
if not uuid:
|
||||
return path
|
||||
# Search up the directory hierarchy until we find a different UUID.
|
||||
last_path = None
|
||||
while True:
|
||||
if uuid != SVN.find_uuid(path):
|
||||
return last_path
|
||||
last_path = path
|
||||
(path, last_component) = os.path.split(path)
|
||||
if last_path == path:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def commit_success_regexp():
|
||||
return "^Committed revision (?P<svn_revision>\d+)\.$"
|
||||
|
||||
def svn_version(self):
|
||||
if not self.cached_version:
|
||||
self.cached_version = self.run_command(['svn', '--version', '--quiet'])
|
||||
|
||||
return self.cached_version
|
||||
|
||||
def working_directory_is_clean(self):
|
||||
return self.run_command(['svn', 'diff']) == ""
|
||||
|
||||
def clean_working_directory(self):
|
||||
self.run_command(['svn', 'revert', '-R', '.'])
|
||||
|
||||
def update_webkit(self):
|
||||
self.run_command(self.script_path("update-webkit"))
|
||||
|
||||
def status_command(self):
|
||||
return ['svn', 'status']
|
||||
|
||||
def changed_files(self):
|
||||
if self.svn_version() > "1.6":
|
||||
status_regexp = "^(?P<status>[ACDMR]).{6} (?P<filename>.+)$"
|
||||
else:
|
||||
status_regexp = "^(?P<status>[ACDMR]).{5} (?P<filename>.+)$"
|
||||
return self.run_status_and_extract_filenames(self.status_command(), status_regexp)
|
||||
|
||||
@staticmethod
|
||||
def supports_local_commits():
|
||||
return False
|
||||
|
||||
def display_name(self):
|
||||
return "svn"
|
||||
|
||||
def create_patch(self):
|
||||
return self.run_command(self.script_path("svn-create-patch"))
|
||||
|
||||
def commit_with_message(self, message):
|
||||
if self.dryrun:
|
||||
return "Dry run, no remote commit."
|
||||
return self.run_command(['svn', 'commit', '-m', message])
|
||||
|
||||
|
||||
# All git-specific logic should go here.
|
||||
class Git(SCM):
|
||||
def __init__(self, cwd, dryrun=False):
|
||||
SCM.__init__(self, cwd, dryrun)
|
||||
|
||||
@classmethod
|
||||
def in_working_directory(cls, path):
|
||||
return cls.run_command(['git', 'rev-parse', '--is-inside-work-tree'], raise_on_failure=False, cwd=path) == "true"
|
||||
|
||||
@classmethod
|
||||
def find_checkout_root(cls, path):
|
||||
# "git rev-parse --show-cdup" would be another way to get to the root
|
||||
(checkout_root, dot_git) = os.path.split(cls.run_command(['git', 'rev-parse', '--git-dir'], cwd=path))
|
||||
# If we were using 2.6 # checkout_root = os.path.relpath(checkout_root, path)
|
||||
if not os.path.isabs(checkout_root): # Sometimes git returns relative paths
|
||||
checkout_root = os.path.join(path, checkout_root)
|
||||
return checkout_root
|
||||
|
||||
@staticmethod
|
||||
def commit_success_regexp():
|
||||
return "^Committed r(?P<svn_revision>\d+)$"
|
||||
|
||||
def discard_local_commits(self):
|
||||
self.run_command(['git', 'reset', '--hard', 'trunk'])
|
||||
|
||||
def local_commits(self):
|
||||
return self.run_command(['git', 'log', '--pretty=oneline', 'HEAD...trunk']).splitlines()
|
||||
|
||||
def working_directory_is_clean(self):
|
||||
return self.run_command(['git', 'diff-index', 'HEAD']) == ""
|
||||
|
||||
def clean_working_directory(self):
|
||||
# Could run git clean here too, but that wouldn't match working_directory_is_clean
|
||||
self.run_command(['git', 'reset', '--hard', 'HEAD'])
|
||||
|
||||
def update_webkit(self):
|
||||
# FIXME: Should probably call update-webkit, no?
|
||||
log("Updating working directory")
|
||||
self.run_command(['git', 'svn', 'rebase'])
|
||||
|
||||
def status_command(self):
|
||||
return ['git', 'status']
|
||||
|
||||
def changed_files(self):
|
||||
status_command = ['git', 'diff', '-r', '--name-status', '-C', '-M', 'HEAD']
|
||||
status_regexp = '^(?P<status>[ADM])\t(?P<filename>.+)$'
|
||||
return self.run_status_and_extract_filenames(status_command, status_regexp)
|
||||
|
||||
@staticmethod
|
||||
def supports_local_commits():
|
||||
return True
|
||||
|
||||
def display_name(self):
|
||||
return "git"
|
||||
|
||||
def create_patch(self):
|
||||
return self.run_command(['git', 'diff', 'HEAD'])
|
||||
|
||||
def commit_with_message(self, message):
|
||||
self.commit_locally_with_message(message)
|
||||
return self.push_local_commits_to_server()
|
||||
|
||||
# Git-specific methods:
|
||||
|
||||
def create_patch_from_local_commit(self, commit_id):
|
||||
return self.run_command(['git', 'diff', commit_id + "^.." + commit_id])
|
||||
|
||||
def commit_locally_with_message(self, message):
|
||||
self.run_command(['git', 'commit', '--all', '-F', '-'], input=message)
|
||||
|
||||
def push_local_commits_to_server(self):
|
||||
if self.dryrun:
|
||||
return "Dry run, no remote commit."
|
||||
return self.run_command(['git', 'svn', 'dcommit'])
|
||||
|
||||
def commit_ids_from_range_arguments(self, args, cherry_pick=False):
|
||||
# First get the commit-ids for the passed in revisions.
|
||||
revisions = self.run_command(['git', 'rev-parse', '--revs-only'] + args).splitlines()
|
||||
|
||||
if cherry_pick:
|
||||
return revisions
|
||||
|
||||
# If we're not cherry picking and were only passed one revision, assume "^revision head" aka "revision..head".
|
||||
if len(revisions) < 2:
|
||||
revisions[0] = "^" + revisions[0]
|
||||
revisions.append("HEAD")
|
||||
|
||||
return self.run_command(['git', 'rev-list'] + revisions).splitlines()
|
||||
|
||||
def commit_message_for_local_commit(self, commit_id):
|
||||
commit_lines = self.run_command(['git', 'cat-file', 'commit', commit_id]).splitlines()
|
||||
|
||||
# Skip the git headers.
|
||||
first_line_after_headers = 0
|
||||
for line in commit_lines:
|
||||
first_line_after_headers += 1
|
||||
if line == "":
|
||||
break
|
||||
return CommitMessage(commit_lines[first_line_after_headers:])
|
||||
|
||||
def files_changed_summary_for_commit(self, commit_id):
|
||||
return self.run_command(['git', 'diff-tree', '--shortstat', '--no-commit-id', commit_id])
|
||||
|
||||
|
||||
# All hg-specific logic should go here.
|
||||
class HG(SCM):
|
||||
def __init__(self, cwd, dryrun=False):
|
||||
SCM.__init__(self, cwd, dryrun)
|
||||
|
||||
@classmethod
|
||||
def in_working_directory(cls, path):
|
||||
return cls.run_command(['hg', 'status'], cwd=path, return_exit_code=True) == 0
|
||||
|
||||
@classmethod
|
||||
def find_checkout_root(cls, path):
|
||||
checkout_root = cls.run_command(['hg', 'root'], cwd=path)
|
||||
return checkout_root
|
||||
|
||||
def status_command(self):
|
||||
return ['hg', 'status']
|
||||
|
||||
def display_name(self):
|
||||
return "hg"
|
||||
|
||||
def create_patch(self):
|
||||
if self.run_command(['hg', 'diff']) != "":
|
||||
sys.stderr.write("Warning: outstanding changes not include in style check.\n")
|
||||
return self.run_command(['hg', 'export', 'tip'])
|
||||
78
tools/check-moz-style/run_tests.py
Normal file
78
tools/check-moz-style/run_tests.py
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
#!/usr/bin/python
|
||||
#
|
||||
# Any copyright is dedicated to the Public Domain.
|
||||
# http://creativecommons.org/publicdomain/zero/1.0/
|
||||
#
|
||||
|
||||
from __future__ import print_function
|
||||
from modules.scm import detect_scm_system
|
||||
from contextlib import closing
|
||||
import checkmozstyle
|
||||
import os
|
||||
import modules.cpplint as cpplint
|
||||
import StringIO
|
||||
|
||||
TESTS = [
|
||||
# Empty patch
|
||||
{
|
||||
"patch": "tests/test1.patch",
|
||||
"cpp": "tests/test1.cpp",
|
||||
"out": "tests/test1.out"
|
||||
},
|
||||
# Bad header
|
||||
{
|
||||
"patch": "tests/test2.patch",
|
||||
"cpp": "tests/test2.cpp",
|
||||
"out": "tests/test2.out"
|
||||
},
|
||||
# Bad Description
|
||||
{
|
||||
"patch": "tests/test3.patch",
|
||||
"cpp": "tests/test3.cpp",
|
||||
"out": "tests/test3.out"
|
||||
},
|
||||
# readability tests
|
||||
{
|
||||
"patch": "tests/test4.patch",
|
||||
"cpp": "tests/test4.cpp",
|
||||
"out": "tests/test4.out"
|
||||
},
|
||||
# runtime tests
|
||||
{
|
||||
"patch": "tests/test5.patch",
|
||||
"cpp": "tests/test5.cpp",
|
||||
"out": "tests/test5.out"
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
cwd = os.path.abspath('.')
|
||||
scm = detect_scm_system(cwd)
|
||||
cpplint.use_mozilla_styles()
|
||||
(args, flags) = cpplint.parse_arguments([])
|
||||
|
||||
for test in TESTS:
|
||||
with open(test["patch"]) as fh:
|
||||
patch = fh.read()
|
||||
|
||||
with closing(StringIO.StringIO()) as output:
|
||||
cpplint.set_stream(output)
|
||||
checkmozstyle.process_patch(patch, cwd, cwd, scm)
|
||||
result = output.getvalue()
|
||||
|
||||
with open(test["out"]) as fh:
|
||||
expected_output = fh.read()
|
||||
|
||||
test_status = "PASSED"
|
||||
if result != expected_output:
|
||||
test_status = "FAILED"
|
||||
print("TEST " + test["patch"] + " " + test_status)
|
||||
print("Got result:\n" + result + "Expected:\n" + expected_output)
|
||||
else:
|
||||
print("TEST " + test["patch"] + " " + test_status)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
0
tools/check-moz-style/tests/test1.cpp
Normal file
0
tools/check-moz-style/tests/test1.cpp
Normal file
1
tools/check-moz-style/tests/test1.out
Normal file
1
tools/check-moz-style/tests/test1.out
Normal file
|
|
@ -0,0 +1 @@
|
|||
patch:0: Patch does not appear to diff against any file. [patch/notempty] [3]
|
||||
1
tools/check-moz-style/tests/test1.patch
Normal file
1
tools/check-moz-style/tests/test1.patch
Normal file
|
|
@ -0,0 +1 @@
|
|||
Bad patch that doesn't diff any files
|
||||
3
tools/check-moz-style/tests/test2.cpp
Normal file
3
tools/check-moz-style/tests/test2.cpp
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
int main() {
|
||||
return 0;
|
||||
}
|
||||
4
tools/check-moz-style/tests/test2.out
Normal file
4
tools/check-moz-style/tests/test2.out
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
patch:0: Patch does not have a summary. [patch/nosummary] [3]
|
||||
patch:0: Patch does not have a description. [patch/nodescription] [3]
|
||||
tests/test2.cpp:1: No copyright message found. [legal/copyright] [3]
|
||||
Done processing tests/test2.cpp
|
||||
9
tools/check-moz-style/tests/test2.patch
Normal file
9
tools/check-moz-style/tests/test2.patch
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Test
|
||||
diff --git a/tests/test2.cpp b/tests/test2.cpp
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/tests/test2.cpp
|
||||
@@ -0,0 +1,3 @@
|
||||
+int main() {
|
||||
+ return 0;
|
||||
+}
|
||||
3
tools/check-moz-style/tests/test3.out
Normal file
3
tools/check-moz-style/tests/test3.out
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
patch:0: Patch summary should begin with 'Bug XXXXX - ' or 'No bug -'. [patch/bugnumber] [3]
|
||||
tests/test2.cpp:1: No copyright message found. [legal/copyright] [3]
|
||||
Done processing tests/test2.cpp
|
||||
12
tools/check-moz-style/tests/test3.patch
Normal file
12
tools/check-moz-style/tests/test3.patch
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Test
|
||||
patch summary with no bug number
|
||||
|
||||
Some bogus patch description
|
||||
diff --git a/tests/test2.cpp b/tests/test2.cpp
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/tests/test2.cpp
|
||||
@@ -0,0 +1,3 @@
|
||||
+int main() {
|
||||
+ return 0;
|
||||
+}
|
||||
40
tools/check-moz-style/tests/test4.cpp
Normal file
40
tools/check-moz-style/tests/test4.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
class ShouldUseExplicit {
|
||||
// runtime/explicit
|
||||
ShouldUseExplicit(int i);
|
||||
};
|
||||
|
||||
// readability/function
|
||||
int foo(int) {
|
||||
}
|
||||
|
||||
int main() {
|
||||
int i = 0;
|
||||
|
||||
// readability/control_flow
|
||||
// XXX This doesn't trigger it. It needs to be fixed.
|
||||
if (i) {
|
||||
return;
|
||||
} else {
|
||||
i++;
|
||||
}
|
||||
|
||||
// whitespace/parens
|
||||
if(i){}
|
||||
|
||||
// readability/casting
|
||||
void* bad = (void*)i;
|
||||
|
||||
// readability/comparison_to_zero
|
||||
if (i == true) {}
|
||||
if (i == false) {}
|
||||
if (i != true) {}
|
||||
if (i != false) {}
|
||||
if (i == NULL) {}
|
||||
if (i != NULL) {}
|
||||
if (i == nullptr) {}
|
||||
if (i != nullptr) {}
|
||||
if (i) {}
|
||||
if (!i) {}
|
||||
|
||||
return 0;
|
||||
}
|
||||
13
tools/check-moz-style/tests/test4.out
Normal file
13
tools/check-moz-style/tests/test4.out
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
tests/test4.cpp:1: No copyright message found. [legal/copyright] [3]
|
||||
tests/test4.cpp:3: Single-argument constructors should be marked explicit. [runtime/explicit] [5]
|
||||
tests/test4.cpp:7: All parameters should be named in a function [readability/function] [3]
|
||||
tests/test4.cpp:22: Missing space before ( in if( [whitespace/parens] [5]
|
||||
tests/test4.cpp:22: Missing space before { [whitespace/braces] [5]
|
||||
tests/test4.cpp:25: Using C-style cast. Use reinterpret_cast<void*>(...) instead [readability/casting] [4]
|
||||
tests/test4.cpp:28: Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons. [readability/comparison_to_zero] [5]
|
||||
tests/test4.cpp:29: Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons. [readability/comparison_to_zero] [5]
|
||||
tests/test4.cpp:30: Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons. [readability/comparison_to_zero] [5]
|
||||
tests/test4.cpp:31: Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons. [readability/comparison_to_zero] [5]
|
||||
tests/test4.cpp:32: Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons. [readability/comparison_to_zero] [5]
|
||||
tests/test4.cpp:33: Tests for true/false, null/non-null, and zero/non-zero should all be done without equality comparisons. [readability/comparison_to_zero] [5]
|
||||
Done processing tests/test4.cpp
|
||||
49
tools/check-moz-style/tests/test4.patch
Normal file
49
tools/check-moz-style/tests/test4.patch
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
# Test
|
||||
Bug 12 - patch summary with no bug number
|
||||
|
||||
Some bogus patch description
|
||||
diff --git a/tests/test4.cpp b/tests/test4.cpp
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/tests/test4.cpp
|
||||
@@ -0,0 +1,49 @@
|
||||
+class ShouldUseExplicit {
|
||||
+ // runtime/explicit
|
||||
+ ShouldUseExplicit(int i);
|
||||
+};
|
||||
+
|
||||
+// readability/function
|
||||
+int foo(int) {
|
||||
+}
|
||||
+
|
||||
+int main() {
|
||||
+ int i = 0;
|
||||
+
|
||||
+ // readability/control_flow
|
||||
+ // XXX This doesn't trigger it. It needs to be fixed.
|
||||
+ if (i) {
|
||||
+ return;
|
||||
+ } else {
|
||||
+ i++;
|
||||
+ }
|
||||
+
|
||||
+ // whitespace/parens
|
||||
+ if(i){}
|
||||
+
|
||||
+ // readability/casting
|
||||
+ void* bad = (void*)i;
|
||||
+
|
||||
+ // readability/comparison_to_zero
|
||||
+ if (i == true) {}
|
||||
+ if (i == false) {}
|
||||
+ if (i != true) {}
|
||||
+ if (i != false) {}
|
||||
+ if (i == NULL) {}
|
||||
+ if (i != NULL) {}
|
||||
+ if (i == nullptr) {}
|
||||
+ if (i != nullptr) {}
|
||||
+ if (i) {}
|
||||
+ if (!i) {}
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
24
tools/check-moz-style/tests/test5.cpp
Normal file
24
tools/check-moz-style/tests/test5.cpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// License bogus
|
||||
|
||||
// runtime/virtual
|
||||
class ShouldHaveVirtualDes {
|
||||
virtual foo();
|
||||
};
|
||||
|
||||
int main() {
|
||||
// runtime/memset
|
||||
memset(blah, sizeof(blah), 0);
|
||||
|
||||
// runtime/rtti
|
||||
dynamic_cast<Derived*>(obj);
|
||||
|
||||
// runtime/sizeof
|
||||
int varname = 0;
|
||||
int mySize = sizeof(int);
|
||||
|
||||
// runtime/threadsafe_fn
|
||||
getpwuid();
|
||||
strtok();
|
||||
|
||||
return 0;
|
||||
}
|
||||
7
tools/check-moz-style/tests/test5.out
Normal file
7
tools/check-moz-style/tests/test5.out
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
tests/test5.cpp:4: The class ShouldHaveVirtualDes probably needs a virtual destructor due to having virtual method(s), one declared at line 5. [runtime/virtual] [4]
|
||||
tests/test5.cpp:10: Did you mean "memset(blah, 0, sizeof(blah))"? [runtime/memset] [4]
|
||||
tests/test5.cpp:13: Do not use dynamic_cast<>. If you need to cast within a class hierarchy, use static_cast<> to upcast. Mozilla doesn't support RTTI. [runtime/rtti] [5]
|
||||
tests/test5.cpp:17: Using sizeof(type). Use sizeof(varname) instead if possible [runtime/sizeof] [1]
|
||||
tests/test5.cpp:20: Consider using getpwuid_r(...) instead of getpwuid(...) for improved thread safety. [runtime/threadsafe_fn] [2]
|
||||
tests/test5.cpp:21: Consider using strtok_r(...) instead of strtok(...) for improved thread safety. [runtime/threadsafe_fn] [2]
|
||||
Done processing tests/test5.cpp
|
||||
33
tools/check-moz-style/tests/test5.patch
Normal file
33
tools/check-moz-style/tests/test5.patch
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Test
|
||||
Bug 12 - patch summary with no bug number
|
||||
|
||||
Some bogus patch description
|
||||
diff --git a/tests/test5.cpp b/tests/test5.cpp
|
||||
new file mode 100644
|
||||
--- /dev/null
|
||||
+++ b/tests/test5.cpp
|
||||
@@ -0,0 +1,24 @@
|
||||
+// License bogus
|
||||
+
|
||||
+// runtime/virtual
|
||||
+class ShouldHaveVirtualDes {
|
||||
+ virtual foo();
|
||||
+};
|
||||
+
|
||||
+int main() {
|
||||
+ // runtime/memset
|
||||
+ memset(blah, sizeof(blah), 0);
|
||||
+
|
||||
+ // runtime/rtti
|
||||
+ dynamic_cast<Derived*>(obj);
|
||||
+
|
||||
+ // runtime/sizeof
|
||||
+ int varname = 0;
|
||||
+ int mySize = sizeof(int);
|
||||
+
|
||||
+ // runtime/threadsafe_fn
|
||||
+ getpwuid();
|
||||
+ strtok();
|
||||
+
|
||||
+ return 0;
|
||||
+}
|
||||
128
tools/coverity/model.cpp
Normal file
128
tools/coverity/model.cpp
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
Coverity model file in order to avoid false-positive
|
||||
*/
|
||||
|
||||
#define NULL (void *)0
|
||||
|
||||
typedef unsigned char jsbytecode;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef unsigned int int32_t;
|
||||
typedef unsigned char uint8_t;
|
||||
|
||||
static const uint16_t CHUNK_HEAD_SIZE = 8;
|
||||
|
||||
void assert(bool expr) {
|
||||
if (!expr) {
|
||||
__coverity_panic__();
|
||||
}
|
||||
}
|
||||
|
||||
#define ERREXIT(cinfo, err) __coverity_panic__();
|
||||
|
||||
void MOZ_ASSUME_UNREACHABLE(char * str) {
|
||||
__coverity_panic__();
|
||||
}
|
||||
|
||||
static void MOZ_ReportAssertionFailure(const char* aStr, const char* aFilename,
|
||||
int aLine) {
|
||||
__coverity_panic__();
|
||||
}
|
||||
|
||||
static void MOZ_ReportCrash(const char* aStr, const char* aFilename,
|
||||
int aLine) {
|
||||
__coverity_panic__();
|
||||
}
|
||||
|
||||
#define MOZ_ASSERT(expr, msg) assert(!!(expr))
|
||||
|
||||
#define MOZ_ASSERT(expr) assert(!!(expr))
|
||||
|
||||
#define NS_ASSERTION(expr, msg) assert(!!(expr))
|
||||
|
||||
#define PORT_Assert(expr) assert(!!(expr))
|
||||
|
||||
#define PR_ASSERT(expr) assert(!!(expr))
|
||||
|
||||
int GET_JUMP_OFFSET(jsbytecode* pc) {
|
||||
__coverity_tainted_data_sanitize__(&pc[1]);
|
||||
__coverity_tainted_data_sanitize__(&pc[2]);
|
||||
__coverity_tainted_data_sanitize__(&pc[3]);
|
||||
__coverity_tainted_data_sanitize__(&pc[4]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
// Data sanity checkers
|
||||
#define XPT_SWAB16(data) __coverity_tainted_data_sanitize__(&data)
|
||||
|
||||
#define XPT_SWAB32(data) __coverity_tainted_data_sanitize__(&data)
|
||||
|
||||
|
||||
static unsigned GET_UINT24(const jsbytecode* pc) {
|
||||
__coverity_tainted_data_sanitize__(static_cast<void*>(pc));
|
||||
//return unsigned((pc[1] << 16) | (pc[2] << 8) | pc[3]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
class HeaderParser {
|
||||
|
||||
private:
|
||||
class ChunkHeader {
|
||||
|
||||
uint8_t mRaw[CHUNK_HEAD_SIZE];
|
||||
|
||||
HeaderParser::ChunkHeader::ChunkSize() const {
|
||||
__coverity_tainted_data_sanitize__(static_cast<void*>(&mRaw[4]));
|
||||
__coverity_tainted_data_sanitize__(static_cast<void*>(&mRaw[5]));
|
||||
__coverity_tainted_data_sanitize__(static_cast<void*>(&mRaw[6]));
|
||||
__coverity_tainted_data_sanitize__(static_cast<void*>(&mRaw[7]));
|
||||
|
||||
return ((mRaw[7] << 24) | (mRaw[6] << 16) | (mRaw[5] << 8) | (mRaw[4]));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
void NS_DebugBreak(uint32_t aSeverity, const char* aStr, const char* aExpr,
|
||||
const char* aFile, int32_t aLine) {
|
||||
__coverity_panic__();
|
||||
}
|
||||
|
||||
static inline void Swap(uint32_t* value) {
|
||||
__coverity_tainted_data_sanitize__(static_cast<void*>(&value));
|
||||
*value = (*value >> 24) |
|
||||
((*value >> 8) & 0x0000ff00) |
|
||||
((*value << 8) & 0x00ff0000) |
|
||||
(*value << 24);
|
||||
}
|
||||
|
||||
static uint32_t xtolong (const uint8_t *ll) {
|
||||
__coverity_tainted_data_sanitize__(static_cast<void*>(&ll[0]));
|
||||
__coverity_tainted_data_sanitize__(static_cast<void*>(&ll[1]));
|
||||
__coverity_tainted_data_sanitize__(static_cast<void*>(&ll[2]));
|
||||
__coverity_tainted_data_sanitize__(static_cast<void*>(&ll[3]));
|
||||
|
||||
return (uint32_t)( (ll [0] << 0) |
|
||||
(ll [1] << 8) |
|
||||
(ll [2] << 16) |
|
||||
(ll [3] << 24) );
|
||||
}
|
||||
|
||||
class ByteReader {
|
||||
public:
|
||||
const uint8_t* Read(size_t aCount);
|
||||
uint32_t ReadU24() {
|
||||
const uint8_t *ptr = Read(3);
|
||||
if (!ptr) {
|
||||
MOZ_ASSERT(false);
|
||||
return 0;
|
||||
}
|
||||
__coverity_tainted_data_sanitize__(static_cast<void*>(&ptr[0]));
|
||||
__coverity_tainted_data_sanitize__(static_cast<void*>(&ptr[1]));
|
||||
__coverity_tainted_data_sanitize__(static_cast<void*>(&ptr[2]));
|
||||
return ptr[0] << 16 | ptr[1] << 8 | ptr[2];
|
||||
}
|
||||
};
|
||||
|
||||
13
tools/docs/Vagrantfile
vendored
Normal file
13
tools/docs/Vagrantfile
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
# We intentionally use the old config format because Mozilla's Jenkins
|
||||
# server doesn't run a modern Vagrant.
|
||||
Vagrant::Config.run do |config|
|
||||
config.vm.box = "precise64"
|
||||
config.vm.box_url = "http://files.vagrantup.com/precise64.box"
|
||||
config.vm.share_folder("gecko", "/gecko", "../..")
|
||||
# Doxygen needs more than the default memory or it will swap and be
|
||||
# extremely slow.
|
||||
config.vm.customize ["modifyvm", :id, "--memory", 2048]
|
||||
end
|
||||
83
tools/docs/conf.py
Normal file
83
tools/docs/conf.py
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# 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/.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
# Set up Python environment to load build system packages.
|
||||
OUR_DIR = os.path.dirname(__file__)
|
||||
topsrcdir = os.path.normpath(os.path.join(OUR_DIR, '..', '..'))
|
||||
|
||||
EXTRA_PATHS = (
|
||||
'layout/tools/reftest',
|
||||
'python/futures',
|
||||
'python/jsmin',
|
||||
'python/mach',
|
||||
'python/mozbuild',
|
||||
'python/mozversioncontrol',
|
||||
'python/which',
|
||||
'testing/mozbase/manifestparser',
|
||||
'testing/mozbase/mozfile',
|
||||
'testing/mozbase/mozprocess',
|
||||
)
|
||||
|
||||
sys.path[:0] = [os.path.join(topsrcdir, p) for p in EXTRA_PATHS]
|
||||
|
||||
sys.path.insert(0, OUR_DIR)
|
||||
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.graphviz',
|
||||
'sphinx.ext.todo',
|
||||
'mozbuild.sphinx',
|
||||
]
|
||||
|
||||
templates_path = ['_templates']
|
||||
source_suffix = '.rst'
|
||||
master_doc = 'index'
|
||||
project = u'Mozilla Source Tree Docs'
|
||||
year = datetime.now().year
|
||||
|
||||
# Grab the version from the source tree's milestone.
|
||||
# FUTURE Use Python API from bug 941299.
|
||||
with open(os.path.join(topsrcdir, 'config', 'milestone.txt'), 'rt') as fh:
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
|
||||
if not line or line.startswith('#'):
|
||||
continue
|
||||
|
||||
release = line
|
||||
break
|
||||
|
||||
version = re.sub(r'[ab]\d+$', '', release)
|
||||
|
||||
exclude_patterns = ['_build', '_staging', '_venv']
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# We need to perform some adjustment of the settings and environment
|
||||
# when running on Read The Docs.
|
||||
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
|
||||
|
||||
if on_rtd:
|
||||
# SHELL isn't set on RTD and mach.mixin.process's import raises if a
|
||||
# shell-related environment variable can't be found. Set the variable here
|
||||
# to hack us into working on RTD.
|
||||
assert 'SHELL' not in os.environ
|
||||
os.environ['SHELL'] = '/bin/bash'
|
||||
else:
|
||||
# We only need to set the RTD theme when not on RTD because the RTD
|
||||
# environment handles this otherwise.
|
||||
import sphinx_rtd_theme
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
|
||||
|
||||
html_static_path = ['_static']
|
||||
htmlhelp_basename = 'MozillaTreeDocs'
|
||||
58
tools/docs/index.rst
Normal file
58
tools/docs/index.rst
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
=================================
|
||||
Mozilla Source Tree Documentation
|
||||
=================================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
{indexes}
|
||||
|
||||
Python Packages
|
||||
===============
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
{python_packages}
|
||||
|
||||
Managing Documentation
|
||||
======================
|
||||
|
||||
This documentation is generated via the
|
||||
`Sphinx <http://sphinx-doc.org/>`_ tool from sources in the tree.
|
||||
|
||||
To build the documentation, run ``mach doc``. Run
|
||||
``mach help doc`` to see configurable options.
|
||||
|
||||
Adding Documentation
|
||||
--------------------
|
||||
|
||||
To add new documentation, define the ``SPHINX_TREES`` and
|
||||
``SPHINX_PYTHON_PACKAGE_DIRS`` variables in ``moz.build`` files in
|
||||
the tree and documentation will automatically get picked up.
|
||||
|
||||
Say you have a directory ``featureX`` you would like to write some
|
||||
documentation for. Here are the steps to create Sphinx documentation
|
||||
for it:
|
||||
|
||||
1. Create a directory for the docs. This is typically ``docs``. e.g.
|
||||
``featureX/docs``.
|
||||
2. Create an ``index.rst`` file in this directory. The ``index.rst`` file
|
||||
is the root documentation for that section. See ``build/docs/index.rst``
|
||||
for an example file.
|
||||
3. In a ``moz.build`` file (typically the one in the parent directory of
|
||||
the ``docs`` directory), define ``SPHINX_TREES`` to hook up the plumbing.
|
||||
e.g. ``SPHINX_TREES['featureX'] = 'docs'``. This says *the ``docs``
|
||||
directory under the current directory should be installed into the
|
||||
Sphinx documentation tree under ``/featureX``*.
|
||||
4. If you have Python packages you would like to generate Python API
|
||||
documentation for, you can use ``SPHINX_PYTHON_PACKAGE_DIRS`` to
|
||||
declare directories containing Python packages. e.g.
|
||||
``SPHINX_PYTHON_PACKAGE_DIRS += ['mozpackage']``.
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
117
tools/docs/mach_commands.py
Normal file
117
tools/docs/mach_commands.py
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
# 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/.
|
||||
|
||||
from __future__ import absolute_import, print_function, unicode_literals
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from mach.decorators import (
|
||||
Command,
|
||||
CommandArgument,
|
||||
CommandProvider,
|
||||
)
|
||||
|
||||
import mozhttpd
|
||||
|
||||
from mozbuild.base import MachCommandBase
|
||||
|
||||
|
||||
@CommandProvider
|
||||
class Documentation(MachCommandBase):
|
||||
"""Helps manage in-tree documentation."""
|
||||
|
||||
@Command('doc', category='devenv',
|
||||
description='Generate and display documentation from the tree.')
|
||||
@CommandArgument('what', nargs='*', metavar='DIRECTORY [, DIRECTORY]',
|
||||
help='Path(s) to documentation to build and display.')
|
||||
@CommandArgument('--format', default='html',
|
||||
help='Documentation format to write.')
|
||||
@CommandArgument('--outdir', default=None, metavar='DESTINATION',
|
||||
help='Where to write output.')
|
||||
@CommandArgument('--no-open', dest='auto_open', default=True, action='store_false',
|
||||
help="Don't automatically open HTML docs in a browser.")
|
||||
@CommandArgument('--http', const=':6666', metavar='ADDRESS', nargs='?',
|
||||
help='Serve documentation on an HTTP server, e.g. ":6666".')
|
||||
def build_docs(self, what=None, format=None, outdir=None, auto_open=True, http=None):
|
||||
self._activate_virtualenv()
|
||||
self.virtualenv_manager.install_pip_package('sphinx_rtd_theme==0.1.6')
|
||||
|
||||
import sphinx
|
||||
import webbrowser
|
||||
|
||||
if not outdir:
|
||||
outdir = os.path.join(self.topobjdir, 'docs')
|
||||
if not what:
|
||||
what = [os.path.join(self.topsrcdir, 'tools')]
|
||||
outdir = os.path.join(outdir, format)
|
||||
|
||||
generated = []
|
||||
failed = []
|
||||
for path in what:
|
||||
path = os.path.normpath(os.path.abspath(path))
|
||||
docdir = self._find_doc_dir(path)
|
||||
|
||||
if not docdir:
|
||||
failed.append((path, 'could not find docs at this location'))
|
||||
continue
|
||||
|
||||
# find project name to use as a namespace within `outdir`
|
||||
project = self._find_project_name(docdir)
|
||||
savedir = os.path.join(outdir, project)
|
||||
|
||||
args = [
|
||||
'sphinx',
|
||||
'-b', format,
|
||||
docdir,
|
||||
savedir,
|
||||
]
|
||||
result = sphinx.build_main(args)
|
||||
if result != 0:
|
||||
failed.append((path, 'sphinx return code %d' % result))
|
||||
else:
|
||||
generated.append(savedir)
|
||||
|
||||
index_path = os.path.join(savedir, 'index.html')
|
||||
if not http and auto_open and os.path.isfile(index_path):
|
||||
webbrowser.open(index_path)
|
||||
|
||||
if generated:
|
||||
print('\nGenerated documentation:\n%s\n' % '\n'.join(generated))
|
||||
|
||||
if failed:
|
||||
failed = ['%s: %s' % (f[0], f[1]) for f in failed]
|
||||
return die('failed to generate documentation:\n%s' % '\n'.join(failed))
|
||||
|
||||
if http is not None:
|
||||
host, port = http.split(':', 1)
|
||||
addr = (host, int(port))
|
||||
if len(addr) != 2:
|
||||
return die('invalid address: %s' % http)
|
||||
|
||||
httpd = mozhttpd.MozHttpd(host=addr[0], port=addr[1], docroot=outdir)
|
||||
print('listening on %s:%d' % addr)
|
||||
httpd.start(block=True)
|
||||
|
||||
def _find_project_name(self, path):
|
||||
import imp
|
||||
path = os.path.join(path, 'conf.py')
|
||||
with open(path, 'r') as fh:
|
||||
conf = imp.load_module('doc_conf', fh, path,
|
||||
('.py', 'r', imp.PY_SOURCE))
|
||||
|
||||
return conf.project.replace(' ', '_')
|
||||
|
||||
def _find_doc_dir(self, path):
|
||||
search_dirs = ('doc', 'docs')
|
||||
for d in search_dirs:
|
||||
p = os.path.join(path, d)
|
||||
if os.path.isfile(os.path.join(p, 'conf.py')):
|
||||
return p
|
||||
|
||||
|
||||
def die(msg, exit_code=1):
|
||||
msg = '%s: %s' % (sys.argv[0], msg)
|
||||
print(msg, file=sys.stderr)
|
||||
return exit_code
|
||||
126
tools/docs/moztreedocs/__init__.py
Normal file
126
tools/docs/moztreedocs/__init__.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
# 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/.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import os
|
||||
|
||||
from mozbuild.frontend.reader import BuildReader
|
||||
from mozpack.copier import FileCopier
|
||||
from mozpack.files import FileFinder
|
||||
from mozpack.manifests import InstallManifest
|
||||
|
||||
import sphinx
|
||||
import sphinx.apidoc
|
||||
|
||||
|
||||
class SphinxManager(object):
|
||||
"""Manages the generation of Sphinx documentation for the tree."""
|
||||
|
||||
def __init__(self, topsrcdir, main_path, output_dir):
|
||||
self._topsrcdir = topsrcdir
|
||||
self._output_dir = output_dir
|
||||
self._docs_dir = os.path.join(output_dir, '_staging')
|
||||
self._conf_py_path = os.path.join(main_path, 'conf.py')
|
||||
self._index_path = os.path.join(main_path, 'index.rst')
|
||||
self._trees = {}
|
||||
self._python_package_dirs = set()
|
||||
|
||||
def read_build_config(self):
|
||||
"""Read the active build config and add docs to this instance."""
|
||||
|
||||
# Reading the Sphinx variables doesn't require a full build context.
|
||||
# Only define the parts we need.
|
||||
class fakeconfig(object):
|
||||
def __init__(self, topsrcdir):
|
||||
self.topsrcdir = topsrcdir
|
||||
|
||||
config = fakeconfig(self._topsrcdir)
|
||||
reader = BuildReader(config)
|
||||
|
||||
for path, name, key, value in reader.find_sphinx_variables():
|
||||
reldir = os.path.dirname(path)
|
||||
|
||||
if name == 'SPHINX_TREES':
|
||||
assert key
|
||||
self.add_tree(os.path.join(reldir, value),
|
||||
os.path.join(reldir, key))
|
||||
|
||||
if name == 'SPHINX_PYTHON_PACKAGE_DIRS':
|
||||
self.add_python_package_dir(os.path.join(reldir, value))
|
||||
|
||||
def add_tree(self, source_dir, dest_dir):
|
||||
"""Add a directory from where docs should be sourced."""
|
||||
if dest_dir in self._trees:
|
||||
raise Exception('%s has already been registered as a destination.'
|
||||
% dest_dir)
|
||||
|
||||
self._trees[dest_dir] = source_dir
|
||||
|
||||
def add_python_package_dir(self, source_dir):
|
||||
"""Add a directory containing Python packages.
|
||||
|
||||
Added directories will have Python API docs generated automatically.
|
||||
"""
|
||||
self._python_package_dirs.add(source_dir)
|
||||
|
||||
def generate_docs(self, app):
|
||||
"""Generate/stage documentation."""
|
||||
app.info('Reading Sphinx metadata from build configuration')
|
||||
self.read_build_config()
|
||||
app.info('Staging static documentation')
|
||||
self._synchronize_docs()
|
||||
app.info('Generating Python API documentation')
|
||||
self._generate_python_api_docs()
|
||||
|
||||
def _generate_python_api_docs(self):
|
||||
"""Generate Python API doc files."""
|
||||
out_dir = os.path.join(self._docs_dir, 'python')
|
||||
base_args = ['sphinx', '--no-toc', '-o', out_dir]
|
||||
|
||||
for p in sorted(self._python_package_dirs):
|
||||
full = os.path.join(self._topsrcdir, p)
|
||||
|
||||
finder = FileFinder(full, find_executables=False)
|
||||
dirs = {os.path.dirname(f[0]) for f in finder.find('**')}
|
||||
|
||||
excludes = {d for d in dirs if d.endswith('test')}
|
||||
|
||||
args = list(base_args)
|
||||
args.append(full)
|
||||
args.extend(excludes)
|
||||
|
||||
sphinx.apidoc.main(args)
|
||||
|
||||
def _synchronize_docs(self):
|
||||
m = InstallManifest()
|
||||
|
||||
m.add_symlink(self._conf_py_path, 'conf.py')
|
||||
|
||||
for dest, source in sorted(self._trees.items()):
|
||||
source_dir = os.path.join(self._topsrcdir, source)
|
||||
for root, dirs, files in os.walk(source_dir):
|
||||
for f in files:
|
||||
source_path = os.path.join(root, f)
|
||||
rel_source = source_path[len(source_dir) + 1:]
|
||||
|
||||
m.add_symlink(source_path, os.path.join(dest, rel_source))
|
||||
|
||||
copier = FileCopier()
|
||||
m.populate_registry(copier)
|
||||
copier.copy(self._docs_dir)
|
||||
|
||||
with open(self._index_path, 'rb') as fh:
|
||||
data = fh.read()
|
||||
|
||||
indexes = ['%s/index' % p for p in sorted(self._trees.keys())]
|
||||
indexes = '\n '.join(indexes)
|
||||
|
||||
packages = [os.path.basename(p) for p in self._python_package_dirs]
|
||||
packages = ['python/%s' % p for p in packages]
|
||||
packages = '\n '.join(sorted(packages))
|
||||
data = data.format(indexes=indexes, python_packages=packages)
|
||||
|
||||
with open(os.path.join(self._docs_dir, 'index.rst'), 'wb') as fh:
|
||||
fh.write(data)
|
||||
67
tools/fuzzing/interface/FuzzingInterface.cpp
Normal file
67
tools/fuzzing/interface/FuzzingInterface.cpp
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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/. */
|
||||
|
||||
/*
|
||||
* Interface implementation for the unified fuzzing interface
|
||||
*/
|
||||
|
||||
#include "FuzzingInterface.h"
|
||||
|
||||
#include "nsNetUtil.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
#ifdef __AFL_COMPILER
|
||||
void afl_interface_stream(const char* testFile, FuzzingTestFuncStream testFunc) {
|
||||
nsresult rv;
|
||||
nsCOMPtr<nsIProperties> dirService =
|
||||
do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
|
||||
MOZ_RELEASE_ASSERT(dirService != nullptr);
|
||||
nsCOMPtr<nsIFile> file;
|
||||
rv = dirService->Get(NS_OS_CURRENT_WORKING_DIR,
|
||||
NS_GET_IID(nsIFile), getter_AddRefs(file));
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||
file->AppendNative(nsDependentCString(testFile));
|
||||
while(__AFL_LOOP(1000)) {
|
||||
nsCOMPtr<nsIInputStream> inputStream;
|
||||
rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), file);
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||
if (!NS_InputStreamIsBuffered(inputStream)) {
|
||||
nsCOMPtr<nsIInputStream> bufStream;
|
||||
rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream),
|
||||
inputStream, 1024);
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv));
|
||||
inputStream = bufStream;
|
||||
}
|
||||
testFunc(inputStream.forget());
|
||||
}
|
||||
}
|
||||
|
||||
void afl_interface_raw(const char* testFile, FuzzingTestFuncRaw testFunc) {
|
||||
char* buf = NULL;
|
||||
|
||||
while(__AFL_LOOP(1000)) {
|
||||
std::ifstream is;
|
||||
is.open (testFile, std::ios::binary);
|
||||
is.seekg (0, std::ios::end);
|
||||
int len = is.tellg();
|
||||
is.seekg (0, std::ios::beg);
|
||||
MOZ_RELEASE_ASSERT(len >= 0);
|
||||
if (!len) {
|
||||
is.close();
|
||||
continue;
|
||||
}
|
||||
buf = (char*)realloc(buf, len);
|
||||
MOZ_RELEASE_ASSERT(buf);
|
||||
is.read(buf,len);
|
||||
is.close();
|
||||
testFunc((uint8_t*)buf, (size_t)len);
|
||||
}
|
||||
|
||||
free(buf);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace mozilla
|
||||
100
tools/fuzzing/interface/FuzzingInterface.h
Normal file
100
tools/fuzzing/interface/FuzzingInterface.h
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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/. */
|
||||
|
||||
/*
|
||||
* Interface definitions for the unified fuzzing interface
|
||||
*/
|
||||
|
||||
#ifndef FuzzingInterface_h__
|
||||
#define FuzzingInterface_h__
|
||||
|
||||
#include "gtest/gtest.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsIInputStream.h"
|
||||
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "nsIDirectoryService.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsStreamUtils.h"
|
||||
#include "nsStringStream.h"
|
||||
|
||||
#include <fstream>
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
typedef int(*FuzzingTestFuncRaw)(const uint8_t*, size_t);
|
||||
typedef int(*FuzzingTestFuncStream)(nsCOMPtr<nsIInputStream>);
|
||||
|
||||
#ifdef __AFL_COMPILER
|
||||
void afl_interface_stream(const char* testFile, FuzzingTestFuncStream testFunc);
|
||||
void afl_interface_raw(const char* testFile, FuzzingTestFuncRaw testFunc);
|
||||
|
||||
#define MOZ_AFL_INTERFACE_COMMON(initFunc) \
|
||||
initFunc(NULL, NULL); \
|
||||
char* testFilePtr = getenv("MOZ_FUZZ_TESTFILE"); \
|
||||
if (!testFilePtr) { \
|
||||
EXPECT_TRUE(false) << "Must specify testfile in MOZ_FUZZ_TESTFILE environment variable."; \
|
||||
return; \
|
||||
} \
|
||||
/* Make a copy of testFilePtr so the testing function can safely call getenv */ \
|
||||
std::string testFile(testFilePtr);
|
||||
|
||||
#define MOZ_AFL_INTERFACE_STREAM(initFunc, testFunc, moduleName) \
|
||||
TEST(AFL, moduleName) { \
|
||||
MOZ_AFL_INTERFACE_COMMON(initFunc); \
|
||||
::mozilla::afl_interface_stream(testFile.c_str(), testFunc); \
|
||||
}
|
||||
|
||||
#define MOZ_AFL_INTERFACE_RAW(initFunc, testFunc, moduleName) \
|
||||
TEST(AFL, moduleName) { \
|
||||
MOZ_AFL_INTERFACE_COMMON(initFunc); \
|
||||
::mozilla::afl_interface_raw(testFile.c_str(), testFunc); \
|
||||
}
|
||||
#else
|
||||
#define MOZ_AFL_INTERFACE_STREAM(initFunc, testFunc, moduleName) /* Nothing */
|
||||
#define MOZ_AFL_INTERFACE_RAW(initFunc, testFunc, moduleName) /* Nothing */
|
||||
#endif
|
||||
|
||||
#ifdef LIBFUZZER
|
||||
#define MOZ_LIBFUZZER_INTERFACE_STREAM(initFunc, testFunc, moduleName) \
|
||||
static int LibFuzzerTest##moduleName (const uint8_t *data, size_t size) { \
|
||||
if (size > INT32_MAX) \
|
||||
return 0; \
|
||||
nsCOMPtr<nsIInputStream> stream; \
|
||||
nsresult rv = NS_NewByteInputStream(getter_AddRefs(stream), \
|
||||
(const char*)data, size, NS_ASSIGNMENT_DEPEND); \
|
||||
MOZ_RELEASE_ASSERT(NS_SUCCEEDED(rv)); \
|
||||
testFunc(stream.forget()); \
|
||||
return 0; \
|
||||
} \
|
||||
static void __attribute__ ((constructor)) LibFuzzerRegister() { \
|
||||
::mozilla::LibFuzzerRegistry::getInstance().registerModule( \
|
||||
#moduleName, initFunc, LibFuzzerTest##moduleName \
|
||||
); \
|
||||
}
|
||||
|
||||
#define MOZ_LIBFUZZER_INTERFACE_RAW(initFunc, testFunc, moduleName) \
|
||||
static void __attribute__ ((constructor)) LibFuzzerRegister() { \
|
||||
::mozilla::LibFuzzerRegistry::getInstance().registerModule( \
|
||||
#moduleName, initFunc, testFunc \
|
||||
); \
|
||||
}
|
||||
#else
|
||||
#define MOZ_LIBFUZZER_INTERFACE_STREAM(initFunc, testFunc, moduleName) /* Nothing */
|
||||
#define MOZ_LIBFUZZER_INTERFACE_RAW(initFunc, testFunc, moduleName) /* Nothing */
|
||||
#endif
|
||||
|
||||
#define MOZ_FUZZING_INTERFACE_STREAM(initFunc, testFunc, moduleName) \
|
||||
MOZ_LIBFUZZER_INTERFACE_STREAM(initFunc, testFunc, moduleName); \
|
||||
MOZ_AFL_INTERFACE_STREAM(initFunc, testFunc, moduleName);
|
||||
|
||||
#define MOZ_FUZZING_INTERFACE_RAW(initFunc, testFunc, moduleName) \
|
||||
MOZ_LIBFUZZER_INTERFACE_RAW(initFunc, testFunc, moduleName); \
|
||||
MOZ_AFL_INTERFACE_RAW(initFunc, testFunc, moduleName);
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // FuzzingInterface_h__
|
||||
15
tools/fuzzing/interface/moz.build
Normal file
15
tools/fuzzing/interface/moz.build
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
EXPORTS += [
|
||||
'FuzzingInterface.h',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'FuzzingInterface.cpp',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = 'xul-gtest'
|
||||
37
tools/fuzzing/libfuzzer/FuzzerCustomMain.cpp
Normal file
37
tools/fuzzing/libfuzzer/FuzzerCustomMain.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* * 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/. */
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
#include "FuzzerInterface.h"
|
||||
#include "FuzzerInternal.h"
|
||||
#include "harness/LibFuzzerRegistry.h"
|
||||
|
||||
/* This is a wrapper defined in browser/app/nsBrowserApp.cpp,
|
||||
* encapsulating the XRE_ equivalent defined in libxul */
|
||||
extern void libFuzzerGetFuncs(const char*, LibFuzzerInitFunc*,
|
||||
LibFuzzerTestingFunc*);
|
||||
|
||||
int libfuzzer_main(int argc, char **argv) {
|
||||
LibFuzzerInitFunc initFunc = nullptr;
|
||||
LibFuzzerTestingFunc testingFunc = nullptr;
|
||||
|
||||
libFuzzerGetFuncs(getenv("LIBFUZZER"), &initFunc, &testingFunc);
|
||||
|
||||
if (initFunc) {
|
||||
int ret = initFunc(&argc, &argv);
|
||||
if (ret) {
|
||||
fprintf(stderr, "LibFuzzer: Error: Initialize callback failed\n");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (!testingFunc) {
|
||||
fprintf(stderr, "LibFuzzer: Error: No testing callback found\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return fuzzer::FuzzerDriver(&argc, &argv, testingFunc);
|
||||
}
|
||||
12
tools/fuzzing/libfuzzer/Makefile.in
Normal file
12
tools/fuzzing/libfuzzer/Makefile.in
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# 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/.
|
||||
|
||||
include $(topsrcdir)/config/rules.mk
|
||||
|
||||
# According to the LLVM docs, LibFuzzer isn't supposed to be built with any
|
||||
# sanitizer flags and in fact, building it with ASan coverage currently causes
|
||||
# Clang 3.9+ to crash, so we filter out all sanitizer-related flags here.
|
||||
CXXFLAGS := $(filter-out -fsanitize%,$(CXXFLAGS))
|
||||
CFLAGS := $(filter-out -fsanitize%,$(CFLAGS))
|
||||
LDFLAGS := $(filter-out -fsanitize%,$(LDFLAGS))
|
||||
7
tools/fuzzing/libfuzzer/clone_libfuzzer.sh
Normal file
7
tools/fuzzing/libfuzzer/clone_libfuzzer.sh
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
#!/bin/sh
|
||||
|
||||
mkdir tmp/
|
||||
git clone --no-checkout --depth 1 https://chromium.googlesource.com/chromium/llvm-project/llvm/lib/Fuzzer tmp/
|
||||
mv tmp/.git .
|
||||
rm -Rf tmp
|
||||
git reset --hard HEAD
|
||||
32
tools/fuzzing/libfuzzer/harness/LibFuzzerRegistry.cpp
Normal file
32
tools/fuzzing/libfuzzer/harness/LibFuzzerRegistry.cpp
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* * 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/. */
|
||||
|
||||
#include "LibFuzzerRegistry.h"
|
||||
|
||||
extern "C" {
|
||||
void MOZ_EXPORT XRE_LibFuzzerGetFuncs(const char* moduleName, LibFuzzerInitFunc* initFunc, LibFuzzerTestingFunc* testingFunc) {
|
||||
std::string moduleNameStr(moduleName);
|
||||
mozilla::LibFuzzerFunctions funcs = mozilla::LibFuzzerRegistry::getInstance().getModuleFunctions(moduleNameStr);
|
||||
*initFunc = funcs.first;
|
||||
*testingFunc = funcs.second;
|
||||
}
|
||||
}
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
LibFuzzerRegistry& LibFuzzerRegistry::getInstance() {
|
||||
static LibFuzzerRegistry instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void LibFuzzerRegistry::registerModule(std::string moduleName, LibFuzzerInitFunc initFunc, LibFuzzerTestingFunc testingFunc) {
|
||||
moduleMap.insert(std::pair<std::string, LibFuzzerFunctions>(moduleName,LibFuzzerFunctions(initFunc, testingFunc)));
|
||||
}
|
||||
|
||||
LibFuzzerFunctions LibFuzzerRegistry::getModuleFunctions(std::string& moduleName) {
|
||||
return moduleMap[moduleName];
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
41
tools/fuzzing/libfuzzer/harness/LibFuzzerRegistry.h
Normal file
41
tools/fuzzing/libfuzzer/harness/LibFuzzerRegistry.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* * 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/. */
|
||||
|
||||
#ifndef _LibFuzzerRegistry_h__
|
||||
#define _LibFuzzerRegistry_h__
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "mozilla/Attributes.h"
|
||||
|
||||
typedef int(*LibFuzzerMain)(int, char**);
|
||||
typedef int(*LibFuzzerInitFunc)(int*, char***);
|
||||
typedef int(*LibFuzzerTestingFunc)(const uint8_t*, size_t);
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
typedef std::pair<LibFuzzerInitFunc, LibFuzzerTestingFunc> LibFuzzerFunctions;
|
||||
|
||||
class LibFuzzerRegistry {
|
||||
public:
|
||||
MOZ_EXPORT static LibFuzzerRegistry& getInstance();
|
||||
MOZ_EXPORT void registerModule(std::string moduleName, LibFuzzerInitFunc initFunc, LibFuzzerTestingFunc testingFunc);
|
||||
MOZ_EXPORT LibFuzzerFunctions getModuleFunctions(std::string& moduleName);
|
||||
|
||||
LibFuzzerRegistry(LibFuzzerRegistry const&) = delete;
|
||||
void operator=(LibFuzzerRegistry const&) = delete;
|
||||
|
||||
private:
|
||||
LibFuzzerRegistry() {};
|
||||
std::map<std::string, LibFuzzerFunctions> moduleMap;
|
||||
};
|
||||
|
||||
} // namespace mozilla
|
||||
|
||||
|
||||
#endif // _LibFuzzerRegistry_h__
|
||||
38
tools/fuzzing/libfuzzer/harness/LibFuzzerRunner.cpp
Normal file
38
tools/fuzzing/libfuzzer/harness/LibFuzzerRunner.cpp
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* * 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/. */
|
||||
|
||||
#include "LibFuzzerRunner.h"
|
||||
#include "mozilla/Attributes.h"
|
||||
#include "prenv.h"
|
||||
|
||||
#include "LibFuzzerTestHarness.h"
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
// We use a static var 'libFuzzerRunner' defined in nsAppRunner.cpp.
|
||||
// libFuzzerRunner is initialized to nullptr but if LibFuzzer (this file)
|
||||
// is linked in then libFuzzerRunner will be set here indicating that
|
||||
// we want to call into LibFuzzer's main.
|
||||
class _InitLibFuzzer {
|
||||
public:
|
||||
_InitLibFuzzer() {
|
||||
libFuzzerRunner = new LibFuzzerRunner();
|
||||
}
|
||||
} InitLibFuzzer;
|
||||
|
||||
int LibFuzzerRunner::Run() {
|
||||
ScopedXPCOM xpcom("LibFuzzer");
|
||||
return mFuzzerMain(mArgc, mArgv);
|
||||
}
|
||||
|
||||
typedef int(*LibFuzzerMain)(int, char**);
|
||||
|
||||
void LibFuzzerRunner::setParams(int argc, char** argv, LibFuzzerMain main) {
|
||||
mArgc = argc;
|
||||
mArgv = argv;
|
||||
mFuzzerMain = main;
|
||||
}
|
||||
|
||||
} // namespace mozilla
|
||||
23
tools/fuzzing/libfuzzer/harness/LibFuzzerRunner.h
Normal file
23
tools/fuzzing/libfuzzer/harness/LibFuzzerRunner.h
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* * 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/. */
|
||||
|
||||
namespace mozilla {
|
||||
|
||||
typedef int(*LibFuzzerMain)(int, char**);
|
||||
|
||||
class LibFuzzerRunner {
|
||||
public:
|
||||
int Run();
|
||||
void setParams(int argc, char** argv, LibFuzzerMain main);
|
||||
|
||||
private:
|
||||
int mArgc;
|
||||
char** mArgv;
|
||||
LibFuzzerMain mFuzzerMain;
|
||||
};
|
||||
|
||||
extern LibFuzzerRunner* libFuzzerRunner;
|
||||
|
||||
} // namespace mozilla
|
||||
298
tools/fuzzing/libfuzzer/harness/LibFuzzerTestHarness.h
Normal file
298
tools/fuzzing/libfuzzer/harness/LibFuzzerTestHarness.h
Normal file
|
|
@ -0,0 +1,298 @@
|
|||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* 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/. */
|
||||
|
||||
/*
|
||||
* Test harness for XPCOM objects, providing a scoped XPCOM initializer,
|
||||
* nsCOMPtr, nsRefPtr, do_CreateInstance, do_GetService, ns(Auto|C|)String,
|
||||
* and stdio.h/stdlib.h.
|
||||
*/
|
||||
|
||||
#ifndef LibFuzzerTestHarness_h__
|
||||
#define LibFuzzerTestHarness_h__
|
||||
|
||||
#include "mozilla/ArrayUtils.h"
|
||||
|
||||
#include "prenv.h"
|
||||
#include "nsComponentManagerUtils.h"
|
||||
#include "nsServiceManagerUtils.h"
|
||||
#include "nsCOMPtr.h"
|
||||
#include "nsAutoPtr.h"
|
||||
#include "nsStringGlue.h"
|
||||
#include "nsAppDirectoryServiceDefs.h"
|
||||
#include "nsDirectoryServiceDefs.h"
|
||||
#include "nsDirectoryServiceUtils.h"
|
||||
#include "nsIDirectoryService.h"
|
||||
#include "nsIFile.h"
|
||||
#include "nsIProperties.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "nsXULAppAPI.h"
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
namespace {
|
||||
|
||||
static uint32_t gFailCount = 0;
|
||||
|
||||
/**
|
||||
* Prints the given failure message and arguments using printf, prepending
|
||||
* "TEST-UNEXPECTED-FAIL " for the benefit of the test harness and
|
||||
* appending "\n" to eliminate having to type it at each call site.
|
||||
*/
|
||||
void fail(const char* msg, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
printf("TEST-UNEXPECTED-FAIL | ");
|
||||
|
||||
va_start(ap, msg);
|
||||
vprintf(msg, ap);
|
||||
va_end(ap);
|
||||
|
||||
putchar('\n');
|
||||
++gFailCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prints the given success message and arguments using printf, prepending
|
||||
* "TEST-PASS " for the benefit of the test harness and
|
||||
* appending "\n" to eliminate having to type it at each call site.
|
||||
*/
|
||||
void passed(const char* msg, ...)
|
||||
{
|
||||
va_list ap;
|
||||
|
||||
printf("TEST-PASS | ");
|
||||
|
||||
va_start(ap, msg);
|
||||
vprintf(msg, ap);
|
||||
va_end(ap);
|
||||
|
||||
putchar('\n');
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
static class ScopedXPCOM : public nsIDirectoryServiceProvider2
|
||||
{
|
||||
public:
|
||||
NS_DECL_ISUPPORTS
|
||||
|
||||
explicit ScopedXPCOM(const char* testName,
|
||||
nsIDirectoryServiceProvider *dirSvcProvider = nullptr)
|
||||
: mDirSvcProvider(dirSvcProvider)
|
||||
{
|
||||
mTestName = testName;
|
||||
printf("Running %s tests...\n", mTestName);
|
||||
|
||||
nsresult rv = NS_InitXPCOM2(&mServMgr, nullptr, this);
|
||||
if (NS_FAILED(rv))
|
||||
{
|
||||
fail("NS_InitXPCOM2 returned failure code 0x%x", rv);
|
||||
mServMgr = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
~ScopedXPCOM()
|
||||
{
|
||||
// If we created a profile directory, we need to remove it.
|
||||
if (mProfD) {
|
||||
nsCOMPtr<nsIObserverService> os =
|
||||
do_GetService(NS_OBSERVERSERVICE_CONTRACTID);
|
||||
MOZ_ASSERT(os);
|
||||
if (os) {
|
||||
MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-change-net-teardown", nullptr));
|
||||
MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-change-teardown", nullptr));
|
||||
MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-before-change", nullptr));
|
||||
MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-before-change-qm", nullptr));
|
||||
MOZ_ALWAYS_SUCCEEDS(os->NotifyObservers(nullptr, "profile-before-change-telemetry", nullptr));
|
||||
}
|
||||
|
||||
if (NS_FAILED(mProfD->Remove(true))) {
|
||||
NS_WARNING("Problem removing profile directory");
|
||||
}
|
||||
|
||||
mProfD = nullptr;
|
||||
}
|
||||
|
||||
if (mServMgr)
|
||||
{
|
||||
NS_RELEASE(mServMgr);
|
||||
nsresult rv = NS_ShutdownXPCOM(nullptr);
|
||||
if (NS_FAILED(rv))
|
||||
{
|
||||
fail("XPCOM shutdown failed with code 0x%x", rv);
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
printf("Finished running %s tests.\n", mTestName);
|
||||
}
|
||||
|
||||
bool failed()
|
||||
{
|
||||
return mServMgr == nullptr;
|
||||
}
|
||||
|
||||
already_AddRefed<nsIFile> GetProfileDirectory()
|
||||
{
|
||||
if (mProfD) {
|
||||
nsCOMPtr<nsIFile> copy = mProfD;
|
||||
return copy.forget();
|
||||
}
|
||||
|
||||
// Create a unique temporary folder to use for this test.
|
||||
// Note that runcppunittests.py will run tests with a temp
|
||||
// directory as the cwd, so just put something under that.
|
||||
nsCOMPtr<nsIFile> profD;
|
||||
nsresult rv = NS_GetSpecialDirectory(NS_OS_CURRENT_PROCESS_DIR,
|
||||
getter_AddRefs(profD));
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
rv = profD->Append(NS_LITERAL_STRING("cpp-unit-profd"));
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
rv = profD->CreateUnique(nsIFile::DIRECTORY_TYPE, 0755);
|
||||
NS_ENSURE_SUCCESS(rv, nullptr);
|
||||
|
||||
mProfD = profD;
|
||||
return profD.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<nsIFile> GetGREDirectory()
|
||||
{
|
||||
if (mGRED) {
|
||||
nsCOMPtr<nsIFile> copy = mGRED;
|
||||
return copy.forget();
|
||||
}
|
||||
|
||||
char* env = PR_GetEnv("MOZ_XRE_DIR");
|
||||
nsCOMPtr<nsIFile> greD;
|
||||
if (env) {
|
||||
NS_NewLocalFile(NS_ConvertUTF8toUTF16(env), false,
|
||||
getter_AddRefs(greD));
|
||||
}
|
||||
|
||||
mGRED = greD;
|
||||
return greD.forget();
|
||||
}
|
||||
|
||||
already_AddRefed<nsIFile> GetGREBinDirectory()
|
||||
{
|
||||
if (mGREBinD) {
|
||||
nsCOMPtr<nsIFile> copy = mGREBinD;
|
||||
return copy.forget();
|
||||
}
|
||||
|
||||
nsCOMPtr<nsIFile> greD = GetGREDirectory();
|
||||
if (!greD) {
|
||||
return greD.forget();
|
||||
}
|
||||
greD->Clone(getter_AddRefs(mGREBinD));
|
||||
|
||||
#ifdef XP_MACOSX
|
||||
nsAutoCString leafName;
|
||||
mGREBinD->GetNativeLeafName(leafName);
|
||||
if (leafName.Equals("Resources")) {
|
||||
mGREBinD->SetNativeLeafName(NS_LITERAL_CSTRING("MacOS"));
|
||||
}
|
||||
#endif
|
||||
|
||||
nsCOMPtr<nsIFile> copy = mGREBinD;
|
||||
return copy.forget();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//// nsIDirectoryServiceProvider
|
||||
|
||||
NS_IMETHODIMP GetFile(const char *aProperty, bool *_persistent,
|
||||
nsIFile **_result) override
|
||||
{
|
||||
// If we were supplied a directory service provider, ask it first.
|
||||
if (mDirSvcProvider &&
|
||||
NS_SUCCEEDED(mDirSvcProvider->GetFile(aProperty, _persistent,
|
||||
_result))) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// Otherwise, the test harness provides some directories automatically.
|
||||
if (0 == strcmp(aProperty, NS_APP_USER_PROFILE_50_DIR) ||
|
||||
0 == strcmp(aProperty, NS_APP_USER_PROFILE_LOCAL_50_DIR) ||
|
||||
0 == strcmp(aProperty, NS_APP_PROFILE_LOCAL_DIR_STARTUP)) {
|
||||
nsCOMPtr<nsIFile> profD = GetProfileDirectory();
|
||||
NS_ENSURE_TRUE(profD, NS_ERROR_FAILURE);
|
||||
|
||||
nsCOMPtr<nsIFile> clone;
|
||||
nsresult rv = profD->Clone(getter_AddRefs(clone));
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
*_persistent = true;
|
||||
clone.forget(_result);
|
||||
return NS_OK;
|
||||
} else if (0 == strcmp(aProperty, NS_GRE_DIR)) {
|
||||
nsCOMPtr<nsIFile> greD = GetGREDirectory();
|
||||
NS_ENSURE_TRUE(greD, NS_ERROR_FAILURE);
|
||||
|
||||
*_persistent = true;
|
||||
greD.forget(_result);
|
||||
return NS_OK;
|
||||
} else if (0 == strcmp(aProperty, NS_GRE_BIN_DIR)) {
|
||||
nsCOMPtr<nsIFile> greBinD = GetGREBinDirectory();
|
||||
NS_ENSURE_TRUE(greBinD, NS_ERROR_FAILURE);
|
||||
|
||||
*_persistent = true;
|
||||
greBinD.forget(_result);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
//// nsIDirectoryServiceProvider2
|
||||
|
||||
NS_IMETHODIMP GetFiles(const char *aProperty, nsISimpleEnumerator **_enum) override
|
||||
{
|
||||
// If we were supplied a directory service provider, ask it first.
|
||||
nsCOMPtr<nsIDirectoryServiceProvider2> provider =
|
||||
do_QueryInterface(mDirSvcProvider);
|
||||
if (provider && NS_SUCCEEDED(provider->GetFiles(aProperty, _enum))) {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* mTestName;
|
||||
nsIServiceManager* mServMgr;
|
||||
nsCOMPtr<nsIDirectoryServiceProvider> mDirSvcProvider;
|
||||
nsCOMPtr<nsIFile> mProfD;
|
||||
nsCOMPtr<nsIFile> mGRED;
|
||||
nsCOMPtr<nsIFile> mGREBinD;
|
||||
};
|
||||
|
||||
NS_IMPL_QUERY_INTERFACE(
|
||||
ScopedXPCOM,
|
||||
nsIDirectoryServiceProvider,
|
||||
nsIDirectoryServiceProvider2
|
||||
)
|
||||
|
||||
NS_IMETHODIMP_(MozExternalRefCountType)
|
||||
ScopedXPCOM::AddRef()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(MozExternalRefCountType)
|
||||
ScopedXPCOM::Release()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // LibFuzzerTestHarness_h__
|
||||
19
tools/fuzzing/libfuzzer/harness/moz.build
Normal file
19
tools/fuzzing/libfuzzer/harness/moz.build
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
Library('fuzzer-runner')
|
||||
|
||||
SOURCES += [
|
||||
'LibFuzzerRegistry.cpp',
|
||||
'LibFuzzerRunner.cpp',
|
||||
]
|
||||
|
||||
EXPORTS += [
|
||||
'LibFuzzerRegistry.h',
|
||||
'LibFuzzerRunner.h',
|
||||
]
|
||||
|
||||
FINAL_LIBRARY = "xul"
|
||||
26
tools/fuzzing/libfuzzer/moz.build
Normal file
26
tools/fuzzing/libfuzzer/moz.build
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
Library('fuzzer')
|
||||
|
||||
DIRS += [
|
||||
'harness',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'FuzzerCrossOver.cpp',
|
||||
'FuzzerCustomMain.cpp',
|
||||
'FuzzerDriver.cpp',
|
||||
'FuzzerExtFunctionsDlsym.cpp',
|
||||
'FuzzerExtFunctionsWeak.cpp',
|
||||
'FuzzerIO.cpp',
|
||||
'FuzzerLoop.cpp',
|
||||
'FuzzerMutate.cpp',
|
||||
'FuzzerSHA1.cpp',
|
||||
'FuzzerTracePC.cpp',
|
||||
'FuzzerTraceState.cpp',
|
||||
'FuzzerUtil.cpp',
|
||||
]
|
||||
14
tools/fuzzing/moz.build
Normal file
14
tools/fuzzing/moz.build
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DIRS += [
|
||||
'interface',
|
||||
]
|
||||
|
||||
if CONFIG['LIBFUZZER']:
|
||||
DIRS += [
|
||||
'libfuzzer',
|
||||
]
|
||||
330
tools/jprof/README.html
Normal file
330
tools/jprof/README.html
Normal file
|
|
@ -0,0 +1,330 @@
|
|||
<!-- 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/. -->
|
||||
|
||||
<html>
|
||||
<head><title>The Jprof Profiler</title></head>
|
||||
|
||||
<body bgcolor="#FFFFFF" text="#000000"
|
||||
link="#0000EE" vlink="#551A8B" alink="#FF0000">
|
||||
<center>
|
||||
<h1>The Jprof Profiler</h1>
|
||||
<font size="-1">
|
||||
<a href="mailto:jim_nance%yahoo.com">jim_nance@yahoo.com</a><p>
|
||||
Recent (4/2011) updates Randell Jesup (see bugzilla for contact info)
|
||||
</font>
|
||||
<hr>
|
||||
|
||||
<a href="#introduction">Introduction</a> | <a href="#operation">Operation</a> |
|
||||
<a href="#setup">Setup</a> | <a href="#usage">Usage</a> |
|
||||
<a href="#interpretation">Interpretation</a>
|
||||
|
||||
</center>
|
||||
<hr>
|
||||
|
||||
<h3><a name="introduction">Introduction</a></h3>
|
||||
|
||||
Jprof is a profiling tool. I am writing it because I need to find out
|
||||
where mozilla is spending its time, and there do not seem to be any
|
||||
profilers for Linux that can handle threads and/or shared libraries.
|
||||
This code is based heavily on Kipp Hickman's leaky.
|
||||
|
||||
<h3><a name="operation">Operation</a></h3>
|
||||
|
||||
Jprof operates by installing a timer which periodically interrupts mozilla.
|
||||
When this timer goes off, the jprof code inside mozilla walks the function call
|
||||
stack to determine which code was executing and saves the results into the
|
||||
<code>jprof-log</code> and <code>jprof-map</code> files. By collecting a large
|
||||
number of these call stacks, it is possible to deduce where mozilla is spending
|
||||
its time.
|
||||
|
||||
<h3><a name="setup">Setup</a></h3>
|
||||
|
||||
<p>Configure your mozilla with jprof support by adding
|
||||
<code>--enable-jprof</code> to your configure options (eg adding
|
||||
<code>ac_add_options --enable-jprof</code> to your <code>.mozconfig</code>) and
|
||||
making sure that you do <strong>not</strong> have the
|
||||
<code>--enable-strip</code> configure option set -- jprof needs symbols to
|
||||
operate. On many architectures with GCC, you'll need to add
|
||||
<code>--enable-optimize="-O3 -fno-omit-frame-pointer"</code> or the
|
||||
equivalent to ensure frame pointer generation in the compiler you're using.</p>
|
||||
|
||||
<p>Finally, build mozilla with your new configuration. Now you can run jprof.</p>
|
||||
|
||||
<h3><a name="usage">Usage</a></h3>
|
||||
<pre> jprof [-v] [-t] [-e exclude] [-i include] [-s stackdepth] [--last] [--all] [--start n [--end m]] [--output-dir dir] prog log [log2 ...]</pre>
|
||||
Options:
|
||||
<ul>
|
||||
<li><b>-s depth</b> : Limit depth looked at from captured stack
|
||||
frames</li>
|
||||
<li><b>-v</b> : Output some information about the symbols, memory map, etc.</li>
|
||||
<li><b>-t or --threads</b> : Group output according to thread. May require external
|
||||
LD_PRELOAD library to help force sampling of spawned threads; jprof
|
||||
may capture the main thread only. See <a
|
||||
href="http://sam.zoy.org/writings/programming/gprof.html">gprof-helper</a>;
|
||||
it may need adaption for jprof.</li>
|
||||
<li><b>--only-thread id</b> : Only output data for thread 'id'</li>
|
||||
<li><b>-e exclusion</b> : Allows excluding specific stack frames</li>
|
||||
<li><b>-i inclusion</b> : Allows including specific stack frames</li>
|
||||
<li><b>--last</b> : Only process data from the last 'section' of sampling
|
||||
(starting at the last PROF)</li>
|
||||
<li><b>--start N</b> : Start processing data at 'section' N </li>
|
||||
<li><b>--end N</b> : Stop processing data at 'section' N </li>
|
||||
<li><b>--output-dir dir</b> : Store generated .html files in the given directory </li>
|
||||
</ul>
|
||||
The behavior of jprof is determined by the value of the JPROF_FLAGS environment
|
||||
variable. This environment variable can be composed of several substrings
|
||||
which have the following meanings:
|
||||
<ul>
|
||||
<li> <b>JP_START</b> : Install the signal handler, and start sending the
|
||||
timer signals.
|
||||
|
||||
<li> <b>JP_DEFER</b> : Install the signal handler, but don't start sending
|
||||
the timer signals. The user must start the signals by sending the first
|
||||
one (with <code>kill -PROF</code>, or with <code>kill -ALRM</code> if
|
||||
JP_REALTIME is used, or with <code>kill -POLL</code> (also known as <code>kill -IO</code>) if JP_RTC_HZ is used).
|
||||
|
||||
<li> <b>JP_FIRST=x</b> : Wait x seconds before starting the timer
|
||||
|
||||
<li> <b>JP_PERIOD=y</b> : Set timer to interrupt every y seconds. Only
|
||||
values of y greater than or equal to 0.001 are supported. Default is
|
||||
0.050 (50ms).
|
||||
|
||||
<li> <b>JP_REALTIME</b> : Do the profiling in intervals of real time rather
|
||||
than intervals of time used by the mozilla process (and the kernel
|
||||
when doing work for mozilla). This could probably lead to weird
|
||||
results (you'll see whatever runs when mozilla is waiting for events),
|
||||
but is needed to see time spent in the X server.
|
||||
|
||||
<li> <b>JP_RTC_HZ=freq</b> : This option, only available on Linux if the
|
||||
kernel is built with RTC support, makes jprof use the RTC timer instead of
|
||||
using its own timer. This option, like JP_REALTIME, uses intervals of real
|
||||
time. This option overrides JP_PERIOD. <code>freq</code> is the frequency
|
||||
at which the timer should fire, measured in Hz. It must be a power of 2.
|
||||
The maximal frequency allowed by the kernel can be changed by writing to
|
||||
<code>/proc/sys/dev/rtc/max-user-freq</code>; the maximum value it can be
|
||||
set to is 8192. Note that <code>/dev/rtc</code> will need to be readable
|
||||
by the Firefox process; making that file world-readable is a simple way to
|
||||
accomplish that.
|
||||
|
||||
<li> <b>JP_CIRCULAR=size</b> : This tells jprof to store samples in a
|
||||
circular buffer of the given size, which then will be saved (appended)
|
||||
to disk when SIGUSR1 is received or JProfStopProfiling is done. If the
|
||||
buffer overflows, the oldest entries will be evicted until there's
|
||||
space for the new entry.<p>
|
||||
|
||||
SIGUSR2 will cause the circular buffer to be cleared.
|
||||
|
||||
<li> <b>JP_FILENAME=basefilename</b> : This is the filename used for
|
||||
saving the log files to; the default is "jprof-log". If Electrolysis
|
||||
is used, each process after the first will have the process ID
|
||||
added ("jprof-log-3212");
|
||||
|
||||
</ul>
|
||||
|
||||
<h4>Starting and stopping jprof from JavaScript</h4>
|
||||
<p>
|
||||
A build with jprof enabled adds four functions to the Window object:<p>
|
||||
<code>JProfStartProfiling()</code> and <code>JProfStopProfiling()</code>: When used with JP_DEFER, these
|
||||
allow one to start and stop the timer just around whatever critical section is
|
||||
being profiled.</p><p>
|
||||
<code>JProfClearCircular()</code> and <code>JProfSaveCircular()</code>:
|
||||
These clear the circular buffer and save the buffer (without stopping), respectively.</p>
|
||||
|
||||
<h4>Examples of JPROF_FLAGS usage</h4>
|
||||
<ul>
|
||||
|
||||
<li>To make the timer start firing 3 seconds after the program is started and
|
||||
fire every 25 milliseconds of program time use:
|
||||
<pre>
|
||||
setenv JPROF_FLAGS "JP_START JP_FIRST=3 JP_PERIOD=0.025" </pre>
|
||||
|
||||
<li>To make the timer start on your signal and fire every 1 millisecond of
|
||||
program time use:
|
||||
<pre>
|
||||
setenv JPROF_FLAGS "JP_DEFER JP_PERIOD=0.001" </pre>
|
||||
|
||||
<li>To make the timer start on your signal and fire every 10 milliseconds of
|
||||
wall-clock time use:
|
||||
<pre>
|
||||
setenv JPROF_FLAGS "JP_DEFER JP_PERIOD=0.010 JP_REALTIME" </pre>
|
||||
|
||||
<li>To make the timer start on your signal and fire at 8192 Hz in wall-clock
|
||||
time use:
|
||||
<pre>
|
||||
setenv JPROF_FLAGS "JP_DEFER JP_RTC_HZ=8192" </pre>
|
||||
|
||||
<li>To make the timer start on JProfStartProfiling() and run continously
|
||||
with a 1ms sample rate until told to stop, then save the last 1MB of
|
||||
data:
|
||||
<pre>
|
||||
setenv JPROF_FLAGS "JP_DEFER JP_CIRCULAR=1048576 JP_PERIOD=0.001" </pre>
|
||||
|
||||
</ul>
|
||||
|
||||
<h4>Pausing profiles</h4>
|
||||
|
||||
<P>jprof can be paused at any time by sending a SIGUSR1 to mozilla (<code>kill
|
||||
-USR1</code>). This will cause the timer signals to stop and jprof-map to be
|
||||
written, but it will not close jprof-log. Combining SIGUSR1 with the JP_DEFER
|
||||
option allows profiling of one sequence of actions by starting the timer right
|
||||
before starting the actions and stopping the timer right afterward.
|
||||
|
||||
<P>After a SIGUSR1, sending another timer signal (SIGPROF, SIGALRM, or SIGPOLL (aka SIGIO),
|
||||
depending on the mode) can be used to continue writing data to the same
|
||||
output.
|
||||
|
||||
<P>SIGUSR2 will cause the circular buffer to be cleared, if it's in use.
|
||||
This is useful right before running a test when you're using a large,
|
||||
continuous circular buffer, or programmatically at the start of an action
|
||||
which might take too long (JProfClearCircular()).
|
||||
|
||||
<h4>Looking at the results</h4>
|
||||
|
||||
Now that we have <code>jprof-log</code> and <code>jprof-map</code> files, we
|
||||
can use the jprof executable is used to turn them into readable output. To do
|
||||
this jprof needs the name of the mozilla binary and the log file. It deduces
|
||||
the name of the map file:
|
||||
|
||||
<pre>
|
||||
./jprof /home/user/mozilla/objdir/dist/bin/firefox ./jprof-log > tmp.html
|
||||
</pre>
|
||||
|
||||
This will generate the file <code>tmp.html</code> which you should view in a
|
||||
web browser.
|
||||
|
||||
<pre>
|
||||
./jprof --output-dir=/tmp /home/user/mozilla/objdir/dist/bin/firefox ./jprof-log*
|
||||
</pre>
|
||||
|
||||
This will generate a set of files in /tmp for each process.
|
||||
|
||||
|
||||
<h3><a name="interpretation">Interpretation</a></h3>
|
||||
|
||||
|
||||
The Jprof output is split into a flat portion and a hierarchical portion.
|
||||
There are links to each section at the top of the page. It is typically
|
||||
easier to analyze the profile by starting with the flat output and following
|
||||
the links contained in the flat output up to the hierarchical output.
|
||||
|
||||
<h4><a name="flat">Flat output</a></h3>
|
||||
|
||||
The flat portion of the profile indicates which functions were executing
|
||||
when the timer was going off. It is displayed as a list of functions names
|
||||
on the right and the number of times that function was interrupted on the
|
||||
left. The list is sorted by decreasing interrupt count. For example:
|
||||
|
||||
<blockquote> <pre>
|
||||
Total hit count: 151603
|
||||
Count %Total Function Name
|
||||
|
||||
<a href="#23081">8806 5.8 __libc_poll</a>
|
||||
<a href="#40008">2254 1.5 __i686.get_pc_thunk.bx</a>
|
||||
<a href="#21390">2053 1.4 _int_malloc</a>
|
||||
<a href="#49013">1777 1.2 nsStyleContext::GetStyleData(nsStyleStructID)</a>
|
||||
<a href="#21380">1600 1.1 __libc_malloc</a>
|
||||
<a href="#603">1552 1.0 nsCOMPtr_base::~nsCOMPtr_base()</a>
|
||||
</pre> </blockquote>
|
||||
|
||||
This shows that of the 151603 times the timer fired, 1777 (1.2% of the total) were inside nsStyleContext::GetStyleData() and 1552 (1.0% of the total) were in the nsCOMPtr_base destructor.
|
||||
|
||||
<p>
|
||||
In general, the functions with the highest count are the functions which
|
||||
are taking the most time.
|
||||
|
||||
<P>
|
||||
The function names are linked to the entry for that function in the
|
||||
hierarchical profile, which is described in the next section.
|
||||
|
||||
<h4><a name="hier">Hierarchical output</a></h4>
|
||||
|
||||
The hierarchical output is divided up into sections, with each section
|
||||
corresponding to one function. A typical section looks something like
|
||||
this:
|
||||
|
||||
<blockquote><pre>
|
||||
index Count Hits Function Name
|
||||
<A href="#72871"> 545 (46.4%) nsBlockFrame::ReflowInlineFrames(nsBlockReflowState&, nsLineList_iterator, int*)</A>
|
||||
<A href="#72873"> 100 (8.5%) nsBlockFrame::ReflowDirtyLines(nsBlockReflowState&)</A>
|
||||
72870 4 (0.3%) <a name=72870> 645 (54.9%)</a> <b>nsBlockFrame::DoReflowInlineFrames(nsBlockReflowState&, nsLineLayout&, nsLineList_iterator, nsFlowAreaRect&, int&, nsFloatManager::SavedState*, int*, LineReflowStatus*, int)</b>
|
||||
<A href="#72821"> 545 (46.4%) nsBlockFrame::ReflowInlineFrame(nsBlockReflowState&, nsLineLayout&, nsLineList_iterator, nsIFrame*, LineReflowStatus*)</A>
|
||||
<A href="#72853"> 83 (7.1%) nsBlockFrame::PlaceLine(nsBlockReflowState&, nsLineLayout&, nsLineList_iterator, nsFloatManager::SavedState*, nsRect&, int&, int*)</A>
|
||||
<A href="#74150"> 9 (0.8%) nsLineLayout::BeginLineReflow(int, int, int, int, int, int)</A>
|
||||
<A href="#74897"> 1 (0.1%) nsTextFrame::GetType() const</A>
|
||||
<A href="#74131"> 1 (0.1%) nsLineLayout::RelativePositionFrames(nsOverflowAreas&)</A>
|
||||
<A href="#58320"> 1 (0.1%) __i686.get_pc_thunk.bx</A>
|
||||
<A href="#53077"> 1 (0.1%) PL_ArenaAllocate</A>
|
||||
</pre></blockquote>
|
||||
|
||||
The information this block tells us is:
|
||||
|
||||
<ul>
|
||||
<li>There were 4 profiler hits <em>in</em> <code>nsBlockFrame::DoReflowInlineFrames</code>
|
||||
<li>There were 645 profiler hits <em>in or under</em> <code>nsBlockFrame::DoReflowInlineFrames</code>. Of these:
|
||||
<ul>
|
||||
<li>545 were in or under <code>nsBlockFrame::ReflowInlineFrame</code>
|
||||
<li>83 were in or under <code>nsBlockFrame::PlaceLine</code>
|
||||
<li>9 were in or under <code>nsLineLayout::BeginLineReflow</code>
|
||||
<li>1 was in or under <code>nsTextFrame::GetType</code>
|
||||
<li>1 was in or under <code>nsLineLayout::RelativePositionFrames</code>
|
||||
<li>1 was in or under <code>__i686.get_pc_thunk.bx</code>
|
||||
<li>1 was in or under <code>PL_ArenaAllocate</code>
|
||||
</ul>
|
||||
<li>Of these 645 calls into <code>nsBlockFrame::DoReflowInlineFrames</code>:
|
||||
<ul>
|
||||
<li>545 came from <code>nsBlockFrame::ReflowInlineFrames</code>
|
||||
<li>100 came from <code>nsBlockFrame::ReflowDirtyLines</code>
|
||||
</ul>
|
||||
</ul>
|
||||
|
||||
|
||||
The rest of this section explains how to read this information off from the jprof output.
|
||||
|
||||
<p>This block corresponds to the function <code>nsBlockFrame::DoReflowInlineFrames</code>, which is
|
||||
therefore bolded and not a link. The name of this function is preceded by
|
||||
five numbers which have the following meaning. The number on the left (72870)
|
||||
is the index number, and is not important. The next number (4) and the
|
||||
percentage following (0.3%) are the number
|
||||
of times this function was interrupted by the timer and the percentage of
|
||||
the total hits that is. The last number pair ("645 (54.9%)")
|
||||
are the number of times this function was in the call stack when the timer went
|
||||
off. That is, the timer went off while we were in code that was ultimately
|
||||
called from <code>nsBlockFrame::DoReflowInlineFrames</code>.
|
||||
<p>For our example we can see that our function was in the call stack for
|
||||
645 interrupt ticks, but we were only the function that was running when
|
||||
the interrupt arrived 4 times.
|
||||
<P>
|
||||
The functions listed above the line for <code>nsBlockFrame::DoReflowInlineFrames</code> are its
|
||||
callers. The numbers to the left of these function names are the numbers of
|
||||
times these functions were in the call stack as callers of
|
||||
<code>nsBlockFrame::DoReflowInlineFrames</code>. In our example, we were called 545 times by
|
||||
<code>nsBlockFrame::ReflowInlineFrames</code> and 100 times by
|
||||
<code>nsBlockFrame::ReflowDirtyLines</code>.
|
||||
<P>
|
||||
The functions listed below the line for <code>nsBlockFrame::DoReflowInlineFrames</code> are its
|
||||
callees. The numbers to the left of the function names are the numbers of
|
||||
times these functions were in the callstack as callees of
|
||||
<code>nsBlockFrame::DoReflowInlineFrames</code> and the corresponding percentages. In our example, of the 645 profiler hits under <code>nsBlockFrame::DoReflowInlineFrames</code> 545 were under <code>nsBlockFrame::ReflowInlineFrame</code>, 83 were under <code>nsBlockFrame::PlaceLine</code>, and so forth.<p>
|
||||
|
||||
<b>NOTE:</b> If there are loops of execution or recursion, the numbers will
|
||||
not add up and percentages can exceed 100%. If a function directly calls
|
||||
itself "(self)" will be appended to the line, but indirect recursion will
|
||||
not be marked.
|
||||
|
||||
<h3>Bugs</h3>
|
||||
The current build of Jprof has only been tested under Ubuntu 8.04 LTS, but
|
||||
should work under any fairly modern linux distribution using GCC/GLIBC.
|
||||
Please update this document with any known compatibilities/incompatibilities.
|
||||
<p>
|
||||
If you get an error:<p><code>Inconsistency detected by ld.so: dl-open.c: 260: dl_open_worker: Assertion `_dl_debug_initialize (0, args->nsid)->r_state == RT_CONSISTENT' failed!
|
||||
</code><p>that means you've hit a timing hole in the version of glibc you're
|
||||
running. See <a
|
||||
href="http://sources.redhat.com/bugzilla/show_bug.cgi?id=4578">Redhat bug 4578</a>.
|
||||
<!-- <h3>Update</h3>
|
||||
<ul>
|
||||
</ul>
|
||||
-->
|
||||
|
||||
</body>
|
||||
</html>
|
||||
231
tools/jprof/bfd.cpp
Normal file
231
tools/jprof/bfd.cpp
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
// vim:ts=8:sw=2:et:
|
||||
/* 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/. */
|
||||
|
||||
#include "leaky.h"
|
||||
|
||||
#ifdef USE_BFD
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <libgen.h>
|
||||
#include <bfd.h>
|
||||
#include <cxxabi.h>
|
||||
|
||||
static bfd *try_debug_file(const char *filename, unsigned long crc32)
|
||||
{
|
||||
int fd = open(filename, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return nullptr;
|
||||
|
||||
unsigned char buf[4*1024];
|
||||
unsigned long crc = 0;
|
||||
|
||||
while (1) {
|
||||
ssize_t count = read(fd, buf, sizeof(buf));
|
||||
if (count <= 0)
|
||||
break;
|
||||
|
||||
crc = bfd_calc_gnu_debuglink_crc32(crc, buf, count);
|
||||
}
|
||||
|
||||
close(fd);
|
||||
|
||||
if (crc != crc32)
|
||||
return nullptr;
|
||||
|
||||
bfd *object = bfd_openr(filename, nullptr);
|
||||
if (!bfd_check_format(object, bfd_object)) {
|
||||
bfd_close(object);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
static bfd *find_debug_file(bfd *lib, const char *aFileName)
|
||||
{
|
||||
// check for a separate debug file with symbols
|
||||
asection *sect = bfd_get_section_by_name(lib, ".gnu_debuglink");
|
||||
|
||||
if (!sect)
|
||||
return nullptr;
|
||||
|
||||
bfd_size_type debuglinkSize = bfd_section_size (objfile->obfd, sect);
|
||||
|
||||
char *debuglink = new char[debuglinkSize];
|
||||
bfd_get_section_contents(lib, sect, debuglink, 0, debuglinkSize);
|
||||
|
||||
// crc checksum is aligned to 4 bytes, and after the NUL.
|
||||
int crc_offset = (int(strlen(debuglink)) & ~3) + 4;
|
||||
unsigned long crc32 = bfd_get_32(lib, debuglink + crc_offset);
|
||||
|
||||
// directory component
|
||||
char *dirbuf = strdup(aFileName);
|
||||
const char *dir = dirname(dirbuf);
|
||||
|
||||
static const char debug_subdir[] = ".debug";
|
||||
// This is gdb's default global debugging info directory, but gdb can
|
||||
// be instructed to use a different directory.
|
||||
static const char global_debug_dir[] = "/usr/lib/debug";
|
||||
|
||||
char *filename =
|
||||
new char[strlen(global_debug_dir) + strlen(dir) + crc_offset + 3];
|
||||
|
||||
// /path/debuglink
|
||||
sprintf(filename, "%s/%s", dir, debuglink);
|
||||
bfd *debugFile = try_debug_file(filename, crc32);
|
||||
if (!debugFile) {
|
||||
|
||||
// /path/.debug/debuglink
|
||||
sprintf(filename, "%s/%s/%s", dir, debug_subdir, debuglink);
|
||||
debugFile = try_debug_file(filename, crc32);
|
||||
if (!debugFile) {
|
||||
|
||||
// /usr/lib/debug/path/debuglink
|
||||
sprintf(filename, "%s/%s/%s", global_debug_dir, dir, debuglink);
|
||||
debugFile = try_debug_file(filename, crc32);
|
||||
}
|
||||
}
|
||||
|
||||
delete[] filename;
|
||||
free(dirbuf);
|
||||
delete[] debuglink;
|
||||
|
||||
return debugFile;
|
||||
}
|
||||
|
||||
|
||||
// Use an indirect array to avoid copying tons of objects
|
||||
Symbol ** leaky::ExtendSymbols(int num)
|
||||
{
|
||||
long n = numExternalSymbols + num;
|
||||
|
||||
externalSymbols = (Symbol**)
|
||||
realloc(externalSymbols,
|
||||
(size_t) (sizeof(externalSymbols[0]) * n));
|
||||
Symbol *new_array = new Symbol[n];
|
||||
for (int i = 0; i < num; i++) {
|
||||
externalSymbols[i + numExternalSymbols] = &new_array[i];
|
||||
}
|
||||
lastSymbol = externalSymbols + n;
|
||||
Symbol **sp = externalSymbols + numExternalSymbols;
|
||||
numExternalSymbols = n;
|
||||
return sp;
|
||||
}
|
||||
|
||||
#define NEXT_SYMBOL do { sp++; \
|
||||
if (sp >= lastSymbol) { \
|
||||
sp = ExtendSymbols(16384); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
void leaky::ReadSymbols(const char *aFileName, u_long aBaseAddress)
|
||||
{
|
||||
int initialSymbols = usefulSymbols;
|
||||
if (nullptr == externalSymbols) {
|
||||
externalSymbols = (Symbol**) calloc(sizeof(Symbol*),10000);
|
||||
Symbol *new_array = new Symbol[10000];
|
||||
for (int i = 0; i < 10000; i++) {
|
||||
externalSymbols[i] = &new_array[i];
|
||||
}
|
||||
numExternalSymbols = 10000;
|
||||
}
|
||||
Symbol** sp = externalSymbols + usefulSymbols;
|
||||
lastSymbol = externalSymbols + numExternalSymbols;
|
||||
|
||||
// Create a dummy symbol for the library so, if it doesn't have any
|
||||
// symbols, we show it by library.
|
||||
(*sp)->Init(aFileName, aBaseAddress);
|
||||
NEXT_SYMBOL;
|
||||
|
||||
bfd_boolean kDynamic = (bfd_boolean) false;
|
||||
|
||||
static int firstTime = 1;
|
||||
if (firstTime) {
|
||||
firstTime = 0;
|
||||
bfd_init ();
|
||||
}
|
||||
|
||||
bfd* lib = bfd_openr(aFileName, nullptr);
|
||||
if (nullptr == lib) {
|
||||
return;
|
||||
}
|
||||
if (!bfd_check_format(lib, bfd_object)) {
|
||||
bfd_close(lib);
|
||||
return;
|
||||
}
|
||||
|
||||
bfd *symbolFile = find_debug_file(lib, aFileName);
|
||||
|
||||
// read mini symbols
|
||||
PTR minisyms;
|
||||
unsigned int size;
|
||||
long symcount = 0;
|
||||
|
||||
if (symbolFile) {
|
||||
symcount = bfd_read_minisymbols(symbolFile, kDynamic, &minisyms, &size);
|
||||
if (symcount == 0) {
|
||||
bfd_close(symbolFile);
|
||||
} else {
|
||||
bfd_close(lib);
|
||||
}
|
||||
}
|
||||
if (symcount == 0) {
|
||||
symcount = bfd_read_minisymbols(lib, kDynamic, &minisyms, &size);
|
||||
if (symcount == 0) {
|
||||
// symtab is empty; try dynamic symbols
|
||||
kDynamic = (bfd_boolean) true;
|
||||
symcount = bfd_read_minisymbols(lib, kDynamic, &minisyms, &size);
|
||||
}
|
||||
symbolFile = lib;
|
||||
}
|
||||
|
||||
asymbol* store;
|
||||
store = bfd_make_empty_symbol(symbolFile);
|
||||
|
||||
// Scan symbols
|
||||
size_t demangle_buffer_size = 128;
|
||||
char *demangle_buffer = (char*) malloc(demangle_buffer_size);
|
||||
bfd_byte* from = (bfd_byte *) minisyms;
|
||||
bfd_byte* fromend = from + symcount * size;
|
||||
for (; from < fromend; from += size) {
|
||||
asymbol *sym;
|
||||
sym = bfd_minisymbol_to_symbol(symbolFile, kDynamic, (const PTR) from, store);
|
||||
|
||||
symbol_info syminfo;
|
||||
bfd_get_symbol_info (symbolFile, sym, &syminfo);
|
||||
|
||||
// if ((syminfo.type == 'T') || (syminfo.type == 't')) {
|
||||
const char* nm = bfd_asymbol_name(sym);
|
||||
if (nm && nm[0]) {
|
||||
char* dnm = nullptr;
|
||||
if (strncmp("__thunk", nm, 7)) {
|
||||
dnm =
|
||||
abi::__cxa_demangle(nm, demangle_buffer, &demangle_buffer_size, 0);
|
||||
if (dnm) {
|
||||
demangle_buffer = dnm;
|
||||
}
|
||||
}
|
||||
(*sp)->Init(dnm ? dnm : nm, syminfo.value + aBaseAddress);
|
||||
NEXT_SYMBOL;
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
free(demangle_buffer);
|
||||
demangle_buffer = nullptr;
|
||||
|
||||
bfd_close(symbolFile);
|
||||
|
||||
int interesting = sp - externalSymbols;
|
||||
if (!quiet) {
|
||||
printf("%s provided %d symbols\n", aFileName,
|
||||
interesting - initialSymbols);
|
||||
}
|
||||
usefulSymbols = interesting;
|
||||
}
|
||||
|
||||
#endif /* USE_BFD */
|
||||
99
tools/jprof/coff.cpp
Normal file
99
tools/jprof/coff.cpp
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/* 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/. */
|
||||
|
||||
#include "leaky.h"
|
||||
|
||||
#ifdef USE_COFF
|
||||
|
||||
#define LANGUAGE_C
|
||||
#include <sym.h>
|
||||
#include <cmplrs/stsupport.h>
|
||||
#include <symconst.h>
|
||||
#include <filehdr.h>
|
||||
#include <ldfcn.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef IRIX4
|
||||
extern "C" {
|
||||
extern char *demangle(char const* in);
|
||||
};
|
||||
#else
|
||||
#include <dem.h>
|
||||
#endif
|
||||
|
||||
static char *Demangle(char *rawName)
|
||||
{
|
||||
#ifdef IRIX4
|
||||
return strdup(demangle(rawName));
|
||||
#else
|
||||
char namebuf[4000];
|
||||
demangle(rawName, namebuf);
|
||||
return strdup(namebuf);
|
||||
#endif
|
||||
}
|
||||
|
||||
void leaky::readSymbols(const char *fileName)
|
||||
{
|
||||
LDFILE *ldptr;
|
||||
|
||||
ldptr = ldopen(fileName, nullptr);
|
||||
if (!ldptr) {
|
||||
fprintf(stderr, "%s: unable to open \"%s\"\n", applicationName,
|
||||
fileName);
|
||||
exit(-1);
|
||||
}
|
||||
if (PSYMTAB(ldptr) == 0) {
|
||||
fprintf(stderr, "%s: \"%s\": has no symbol table\n", applicationName,
|
||||
fileName);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
long isymMax = SYMHEADER(ldptr).isymMax;
|
||||
long iextMax = SYMHEADER(ldptr).iextMax;
|
||||
long iMax = isymMax + iextMax;
|
||||
|
||||
long alloced = 10000;
|
||||
Symbol* syms = (Symbol*) malloc(sizeof(Symbol) * 10000);
|
||||
Symbol* sp = syms;
|
||||
Symbol* last = syms + alloced;
|
||||
SYMR symr;
|
||||
|
||||
for (long isym = 0; isym < iMax; isym++) {
|
||||
if (ldtbread(ldptr, isym, &symr) != SUCCESS) {
|
||||
fprintf(stderr, "%s: can't read symbol #%d\n", applicationName,
|
||||
isym);
|
||||
exit(-1);
|
||||
}
|
||||
if (isym < isymMax) {
|
||||
if ((symr.st == stStaticProc)
|
||||
|| ((symr.st == stProc) &&
|
||||
((symr.sc == scText) || (symr.sc == scAbs)))
|
||||
|| ((symr.st == stBlock) &&
|
||||
(symr.sc == scText))) {
|
||||
// Text symbol. Set name field to point to the symbol name
|
||||
sp->name = Demangle(ldgetname(ldptr, &symr));
|
||||
sp->address = symr.value;
|
||||
sp++;
|
||||
if (sp >= last) {
|
||||
long n = alloced + 10000;
|
||||
syms = (Symbol*)
|
||||
realloc(syms, (size_t) (sizeof(Symbol) * n));
|
||||
last = syms + n;
|
||||
sp = syms + alloced;
|
||||
alloced = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int interesting = sp - syms;
|
||||
if (!quiet) {
|
||||
printf("Total of %d symbols\n", interesting);
|
||||
}
|
||||
usefulSymbols = interesting;
|
||||
externalSymbols = syms;
|
||||
}
|
||||
|
||||
#endif /* USE_COFF */
|
||||
133
tools/jprof/elf.cpp
Normal file
133
tools/jprof/elf.cpp
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/* 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/. */
|
||||
|
||||
#include "leaky.h"
|
||||
|
||||
#ifdef USE_ELF
|
||||
|
||||
#include "leaky.h"
|
||||
#include <stdio.h>
|
||||
#include <malloc.h>
|
||||
#include <libelf/libelf.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
|
||||
void leaky::readSymbols(const char *fileName)
|
||||
{
|
||||
int fd = ::open(fileName, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
fprintf(stderr, "%s: unable to open \"%s\"\n", applicationName,
|
||||
fileName);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
elf_version(EV_CURRENT);
|
||||
Elf *elf = elf_begin(fd, ELF_C_READ, 0);
|
||||
if (!elf) {
|
||||
fprintf(stderr, "%s: \"%s\": has no symbol table\n", applicationName,
|
||||
fileName);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
long alloced = 10000;
|
||||
Symbol* syms = (Symbol*) malloc(sizeof(Symbol) * 10000);
|
||||
Symbol* sp = syms;
|
||||
Symbol* last = syms + alloced;
|
||||
|
||||
// Get each of the relevant sections and add them to the list of
|
||||
// symbols.
|
||||
Elf32_Ehdr *ehdr = elf32_getehdr(elf);
|
||||
if (!ehdr) {
|
||||
fprintf(stderr, "%s: elf library lossage\n", applicationName);
|
||||
exit(-1);
|
||||
}
|
||||
#if 0
|
||||
Elf32_Half ndx = ehdr->e_shstrndx;
|
||||
#endif
|
||||
|
||||
Elf_Scn *scn = 0;
|
||||
int strtabndx = -1;
|
||||
for (int i = 1; (scn = elf_nextscn(elf, scn)) != 0; i++) {
|
||||
Elf32_Shdr *shdr = elf32_getshdr(scn);
|
||||
#if 0
|
||||
char *name = elf_strptr(elf, ndx, (size_t) shdr->sh_name);
|
||||
printf("Section %s (%d 0x%x)\n", name ? name : "(null)",
|
||||
shdr->sh_type, shdr->sh_type);
|
||||
#endif
|
||||
if (shdr->sh_type == SHT_STRTAB) {
|
||||
/* We assume here that string tables preceed symbol tables... */
|
||||
strtabndx = i;
|
||||
continue;
|
||||
}
|
||||
#if 0
|
||||
if (shdr->sh_type == SHT_DYNAMIC) {
|
||||
/* Dynamic */
|
||||
Elf_Data *data = elf_getdata(scn, 0);
|
||||
if (!data || !data->d_size) {
|
||||
printf("No data...");
|
||||
continue;
|
||||
}
|
||||
|
||||
Elf32_Dyn *dyn = (Elf32_Dyn*) data->d_buf;
|
||||
Elf32_Dyn *lastdyn =
|
||||
(Elf32_Dyn*) ((char*) data->d_buf + data->d_size);
|
||||
for (; dyn < lastdyn; dyn++) {
|
||||
printf("tag=%d value=0x%x\n", dyn->d_tag, dyn->d_un.d_val);
|
||||
}
|
||||
} else
|
||||
#endif
|
||||
if ((shdr->sh_type == SHT_SYMTAB) ||
|
||||
(shdr->sh_type == SHT_DYNSYM)) {
|
||||
/* Symbol table */
|
||||
Elf_Data *data = elf_getdata(scn, 0);
|
||||
if (!data || !data->d_size) {
|
||||
printf("No data...");
|
||||
continue;
|
||||
}
|
||||
|
||||
/* In theory we now have the symbols... */
|
||||
Elf32_Sym *esym = (Elf32_Sym*) data->d_buf;
|
||||
Elf32_Sym *lastsym =
|
||||
(Elf32_Sym*) ((char*) data->d_buf + data->d_size);
|
||||
for (; esym < lastsym; esym++) {
|
||||
#if 0
|
||||
char *nm = elf_strptr(elf, strtabndx, (size_t)esym->st_name);
|
||||
printf("%20s 0x%08x %02x %02x\n",
|
||||
nm, esym->st_value, ELF32_ST_BIND(esym->st_info),
|
||||
ELF32_ST_TYPE(esym->st_info));
|
||||
#endif
|
||||
if ((esym->st_value == 0) ||
|
||||
(ELF32_ST_BIND(esym->st_info) == STB_WEAK) ||
|
||||
(ELF32_ST_BIND(esym->st_info) == STB_NUM) ||
|
||||
(ELF32_ST_TYPE(esym->st_info) != STT_FUNC)) {
|
||||
continue;
|
||||
}
|
||||
#if 1
|
||||
char *nm = elf_strptr(elf, strtabndx, (size_t)esym->st_name);
|
||||
#endif
|
||||
sp->name = nm ? strdup(nm) : "(no name)";
|
||||
sp->address = esym->st_value;
|
||||
sp++;
|
||||
if (sp >= last) {
|
||||
long n = alloced + 10000;
|
||||
syms = (Symbol*)
|
||||
realloc(syms, (size_t) (sizeof(Symbol) * n));
|
||||
last = syms + n;
|
||||
sp = syms + alloced;
|
||||
alloced = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int interesting = sp - syms;
|
||||
if (!quiet) {
|
||||
printf("Total of %d symbols\n", interesting);
|
||||
}
|
||||
usefulSymbols = interesting;
|
||||
externalSymbols = syms;
|
||||
}
|
||||
|
||||
#endif /* USE_ELF */
|
||||
71
tools/jprof/intcnt.cpp
Normal file
71
tools/jprof/intcnt.cpp
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/* 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/. */
|
||||
|
||||
#include "intcnt.h"
|
||||
|
||||
IntCount::IntCount() : numInts(0), iPair(nullptr) { }
|
||||
IntCount::~IntCount() { delete [] iPair;}
|
||||
int IntCount::getSize() {return numInts;}
|
||||
int IntCount::getCount(int pos) {return iPair[pos].cnt;}
|
||||
int IntCount::getIndex(int pos) {return iPair[pos].idx;}
|
||||
|
||||
void IntCount::clear()
|
||||
{
|
||||
delete[] iPair;
|
||||
iPair = new IntPair[0];
|
||||
numInts = 0;
|
||||
}
|
||||
|
||||
int IntCount::countAdd(int index, int increment)
|
||||
{
|
||||
if(numInts) {
|
||||
// Do a binary search to find the element
|
||||
int divPoint = 0;
|
||||
|
||||
if(index>iPair[numInts-1].idx) {
|
||||
divPoint = numInts;
|
||||
} else if(index<iPair[0].idx) {
|
||||
divPoint = 0;
|
||||
} else {
|
||||
int low=0, high=numInts-1;
|
||||
int mid = (low+high)/2;
|
||||
while(1) {
|
||||
mid = (low+high)/2;
|
||||
|
||||
if(index<iPair[mid].idx) {
|
||||
high = mid;
|
||||
} else if(index>iPair[mid].idx) {
|
||||
if(mid<numInts-1 && index<iPair[mid+1].idx) {
|
||||
divPoint = mid+1;
|
||||
break;
|
||||
} else {
|
||||
low = mid+1;
|
||||
}
|
||||
} else if(index==iPair[mid].idx) {
|
||||
return iPair[mid].cnt += increment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int i;
|
||||
IntPair *tpair = new IntPair[numInts+1];
|
||||
for(i=0; i<divPoint; i++) {
|
||||
tpair[i] = iPair[i];
|
||||
}
|
||||
for(i=divPoint; i<numInts; i++) {
|
||||
tpair[i+1] = iPair[i];
|
||||
}
|
||||
++numInts;
|
||||
delete [] iPair;
|
||||
iPair = tpair;
|
||||
iPair[divPoint].idx = index;
|
||||
iPair[divPoint].cnt = increment;
|
||||
return increment;
|
||||
} else {
|
||||
iPair = new IntPair[1];
|
||||
numInts = 1;
|
||||
iPair[0].idx = index;
|
||||
return iPair[0].cnt = increment;
|
||||
}
|
||||
}
|
||||
38
tools/jprof/intcnt.h
Normal file
38
tools/jprof/intcnt.h
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
/* 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/. */
|
||||
|
||||
#ifndef INTCNT_H
|
||||
#define INTCNT_H
|
||||
|
||||
class IntCount
|
||||
{
|
||||
public:
|
||||
IntCount();
|
||||
~IntCount();
|
||||
void clear();
|
||||
int countAdd(int index, int increment=1);
|
||||
int countGet(int index);
|
||||
int getSize();
|
||||
int getCount(int pos);
|
||||
int getIndex(int pos);
|
||||
|
||||
IntCount(const IntCount&old)
|
||||
{
|
||||
numInts = old.numInts;
|
||||
if (numInts > 0) {
|
||||
iPair = new IntPair[numInts];
|
||||
for (int i = 0; i < numInts; i++) {
|
||||
iPair[i] = old.iPair[i];
|
||||
}
|
||||
} else {
|
||||
iPair = nullptr;
|
||||
}
|
||||
}
|
||||
private:
|
||||
|
||||
int numInts;
|
||||
struct IntPair{int idx; int cnt;} *iPair;
|
||||
};
|
||||
|
||||
#endif
|
||||
46
tools/jprof/jprofsig
Normal file
46
tools/jprof/jprofsig
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
#!/bin/sh
|
||||
#
|
||||
# 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/.
|
||||
|
||||
#
|
||||
# Find Mozilla PID and send it a signal, to be used
|
||||
# with the jprof tool.
|
||||
#
|
||||
|
||||
jpsignal_usage() {
|
||||
echo "Usage: jprofsig [start|stop]"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ $# != 1 ]; then
|
||||
echo "Wrong number of arguments."
|
||||
jpsignal_usage
|
||||
fi
|
||||
|
||||
jpsignal_arg="$1"
|
||||
|
||||
# Find & print mozilla PID
|
||||
tmpmoz=`ps aux | grep mozilla-bin | head -1 | awk '{ print $2 }'`
|
||||
echo "Mozilla PID = $tmpmoz"
|
||||
|
||||
# See how we were called.
|
||||
case "$jpsignal_arg" in
|
||||
start)
|
||||
if [ "$JP_REALTIME" = 1 ]; then
|
||||
kill -ALRM $tmpmoz
|
||||
else
|
||||
# Normal, non-realtime mode.
|
||||
kill -PROF $tmpmoz
|
||||
fi
|
||||
;;
|
||||
stop)
|
||||
kill -USR1 $tmpmoz
|
||||
;;
|
||||
*)
|
||||
jpsignal_usage
|
||||
exit 1
|
||||
esac
|
||||
|
||||
exit 0
|
||||
863
tools/jprof/leaky.cpp
Normal file
863
tools/jprof/leaky.cpp
Normal file
|
|
@ -0,0 +1,863 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* 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/. */
|
||||
|
||||
#include "leaky.h"
|
||||
#include "intcnt.h"
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#ifndef NTO
|
||||
#include <getopt.h>
|
||||
#endif
|
||||
#include <assert.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef NTO
|
||||
#include <mem.h>
|
||||
#endif
|
||||
|
||||
#ifndef FALSE
|
||||
#define FALSE 0
|
||||
#endif
|
||||
#ifndef TRUE
|
||||
#define TRUE 1
|
||||
#endif
|
||||
|
||||
static const u_int DefaultBuckets = 10007; // arbitrary, but prime
|
||||
static const u_int MaxBuckets = 1000003; // arbitrary, but prime
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
leaky* l = new leaky;
|
||||
|
||||
l->initialize(argc, argv);
|
||||
l->outputfd = stdout;
|
||||
|
||||
for (int i = 0; i < l->numLogFiles; i++) {
|
||||
if (l->output_dir || l->numLogFiles > 1) {
|
||||
char name[2048]; // XXX fix
|
||||
if (l->output_dir)
|
||||
snprintf(name,sizeof(name),"%s/%s.html",l->output_dir,argv[l->logFileIndex + i]);
|
||||
else
|
||||
snprintf(name,sizeof(name),"%s.html",argv[l->logFileIndex + i]);
|
||||
|
||||
fprintf(stderr,"opening %s\n",name);
|
||||
l->outputfd = fopen(name,"w");
|
||||
// if an error we won't process the file
|
||||
}
|
||||
if (l->outputfd) { // paranoia
|
||||
l->open(argv[l->logFileIndex + i]);
|
||||
|
||||
if (l->outputfd != stderr) {
|
||||
fclose(l->outputfd);
|
||||
l->outputfd = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *
|
||||
htmlify(const char *in)
|
||||
{
|
||||
const char *p = in;
|
||||
char *out, *q;
|
||||
int n = 0;
|
||||
size_t newlen;
|
||||
|
||||
// Count the number of '<' and '>' in the input.
|
||||
while ((p = strpbrk(p, "<>")))
|
||||
{
|
||||
++n;
|
||||
++p;
|
||||
}
|
||||
|
||||
// Knowing the number of '<' and '>', we can calculate the space
|
||||
// needed for the output string.
|
||||
newlen = strlen(in) + n * 3 + 1;
|
||||
out = new char[newlen];
|
||||
|
||||
// Copy the input to the output, with substitutions.
|
||||
p = in;
|
||||
q = out;
|
||||
do
|
||||
{
|
||||
if (*p == '<')
|
||||
{
|
||||
strcpy(q, "<");
|
||||
q += 4;
|
||||
}
|
||||
else if (*p == '>')
|
||||
{
|
||||
strcpy(q, ">");
|
||||
q += 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
*q++ = *p;
|
||||
}
|
||||
p++;
|
||||
} while (*p);
|
||||
*q = '\0';
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
leaky::leaky()
|
||||
{
|
||||
applicationName = nullptr;
|
||||
progFile = nullptr;
|
||||
|
||||
quiet = true;
|
||||
showAddress = false;
|
||||
showThreads = false;
|
||||
stackDepth = 100000;
|
||||
onlyThread = 0;
|
||||
cleo = false;
|
||||
|
||||
mappedLogFile = -1;
|
||||
firstLogEntry = lastLogEntry = 0;
|
||||
|
||||
sfd = -1;
|
||||
externalSymbols = 0;
|
||||
usefulSymbols = 0;
|
||||
numExternalSymbols = 0;
|
||||
lowestSymbolAddr = 0;
|
||||
highestSymbolAddr = 0;
|
||||
|
||||
loadMap = nullptr;
|
||||
|
||||
collect_last = false;
|
||||
collect_start = -1;
|
||||
collect_end = -1;
|
||||
}
|
||||
|
||||
leaky::~leaky()
|
||||
{
|
||||
}
|
||||
|
||||
void leaky::usageError()
|
||||
{
|
||||
fprintf(stderr, "Usage: %s [-v] [-t] [-e exclude] [-i include] [-s stackdepth] [--last] [--all] [--start n [--end m]] [--cleo] [--output-dir dir] prog log [log2 ...]\n", (char*) applicationName);
|
||||
fprintf(stderr,
|
||||
"\t-v: verbose\n"
|
||||
"\t-t | --threads: split threads\n"
|
||||
"\t--only-thread n: only profile thread N\n"
|
||||
"\t-i include-id: stack must include specified id\n"
|
||||
"\t-e exclude-id: stack must NOT include specified id\n"
|
||||
"\t-s stackdepth: Limit depth looked at from captured stack frames\n"
|
||||
"\t--last: only profile the last capture section\n"
|
||||
"\t--start n [--end m]: profile n to m (or end) capture sections\n"
|
||||
"\t--cleo: format output for 'cleopatra' display\n"
|
||||
"\t--output-dir dir: write output files to dir\n"
|
||||
"\tIf there's one log, output goes to stdout unless --output-dir is set\n"
|
||||
"\tIf there are more than one log, output files will be named with .html added\n"
|
||||
);
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
static struct option longopts[] = {
|
||||
{ "threads", 0, nullptr, 't' },
|
||||
{ "only-thread", 1, nullptr, 'T' },
|
||||
{ "last", 0, nullptr, 'l' },
|
||||
{ "start", 1, nullptr, 'x' },
|
||||
{ "end", 1, nullptr, 'n' },
|
||||
{ "cleo",0, nullptr, 'c' },
|
||||
{ "output-dir", 1, nullptr, 'd' },
|
||||
{ nullptr, 0, nullptr, 0 },
|
||||
};
|
||||
|
||||
void leaky::initialize(int argc, char** argv)
|
||||
{
|
||||
applicationName = argv[0];
|
||||
applicationName = strrchr(applicationName, '/');
|
||||
if (!applicationName) {
|
||||
applicationName = argv[0];
|
||||
} else {
|
||||
applicationName++;
|
||||
}
|
||||
|
||||
int arg;
|
||||
int errflg = 0;
|
||||
int longindex = 0;
|
||||
|
||||
onlyThread = 0;
|
||||
output_dir = nullptr;
|
||||
cleo = false;
|
||||
|
||||
// XXX tons of cruft here left over from tracemalloc
|
||||
// XXX The -- options shouldn't need short versions, or they should be documented
|
||||
while (((arg = getopt_long(argc, argv, "adEe:gh:i:r:Rs:tT:qvx:ln:",longopts,&longindex)) != -1)) {
|
||||
switch (arg) {
|
||||
case '?':
|
||||
default:
|
||||
fprintf(stderr,"error: unknown option %c\n",optopt);
|
||||
errflg++;
|
||||
break;
|
||||
case 'a':
|
||||
break;
|
||||
case 'A': // not implemented
|
||||
showAddress = true;
|
||||
break;
|
||||
case 'c':
|
||||
cleo = true;
|
||||
break;
|
||||
case 'd':
|
||||
output_dir = optarg; // reference to an argv pointer
|
||||
break;
|
||||
case 'R':
|
||||
break;
|
||||
case 'e':
|
||||
exclusions.add(optarg);
|
||||
break;
|
||||
case 'g':
|
||||
break;
|
||||
case 'r': // not implemented
|
||||
roots.add(optarg);
|
||||
if (!includes.IsEmpty()) {
|
||||
errflg++;
|
||||
}
|
||||
break;
|
||||
case 'i':
|
||||
includes.add(optarg);
|
||||
if (!roots.IsEmpty()) {
|
||||
errflg++;
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
break;
|
||||
case 's':
|
||||
stackDepth = atoi(optarg);
|
||||
if (stackDepth < 2) {
|
||||
stackDepth = 2;
|
||||
}
|
||||
break;
|
||||
case 'x':
|
||||
// --start
|
||||
collect_start = atoi(optarg);
|
||||
break;
|
||||
case 'n':
|
||||
// --end
|
||||
collect_end = atoi(optarg);
|
||||
break;
|
||||
case 'l':
|
||||
// --last
|
||||
collect_last = true;
|
||||
break;
|
||||
case 'q':
|
||||
break;
|
||||
case 'v':
|
||||
quiet = !quiet;
|
||||
break;
|
||||
case 't':
|
||||
showThreads = true;
|
||||
break;
|
||||
case 'T':
|
||||
showThreads = true;
|
||||
onlyThread = atoi(optarg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (errflg || ((argc - optind) < 2)) {
|
||||
usageError();
|
||||
}
|
||||
progFile = argv[optind++];
|
||||
logFileIndex = optind;
|
||||
numLogFiles = argc - optind;
|
||||
if (!quiet)
|
||||
fprintf(stderr,"numlogfiles = %d\n",numLogFiles);
|
||||
}
|
||||
|
||||
static void* mapFile(int fd, u_int flags, off_t* sz)
|
||||
{
|
||||
struct stat sb;
|
||||
if (fstat(fd, &sb) < 0) {
|
||||
perror("fstat");
|
||||
exit(-1);
|
||||
}
|
||||
void* base = mmap(0, (int)sb.st_size, flags, MAP_PRIVATE, fd, 0);
|
||||
if (!base) {
|
||||
perror("mmap");
|
||||
exit(-1);
|
||||
}
|
||||
*sz = sb.st_size;
|
||||
return base;
|
||||
}
|
||||
|
||||
void leaky::LoadMap()
|
||||
{
|
||||
malloc_map_entry mme;
|
||||
char name[1000];
|
||||
|
||||
if (!loadMap) {
|
||||
// all files use the same map
|
||||
int fd = ::open(M_MAPFILE, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
perror("open: " M_MAPFILE);
|
||||
exit(-1);
|
||||
}
|
||||
for (;;) {
|
||||
int nb = read(fd, &mme, sizeof(mme));
|
||||
if (nb != sizeof(mme)) break;
|
||||
nb = read(fd, name, mme.nameLen);
|
||||
if (nb != (int)mme.nameLen) break;
|
||||
name[mme.nameLen] = 0;
|
||||
if (!quiet) {
|
||||
fprintf(stderr,"%s @ %lx\n", name, mme.address);
|
||||
}
|
||||
|
||||
LoadMapEntry* lme = new LoadMapEntry;
|
||||
lme->address = mme.address;
|
||||
lme->name = strdup(name);
|
||||
lme->next = loadMap;
|
||||
loadMap = lme;
|
||||
}
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
void leaky::open(char *logFile)
|
||||
{
|
||||
int threadArray[100]; // should auto-expand
|
||||
int last_thread = -1;
|
||||
int numThreads = 0;
|
||||
int section = -1;
|
||||
bool collecting = false;
|
||||
|
||||
LoadMap();
|
||||
|
||||
setupSymbols(progFile);
|
||||
|
||||
// open up the log file
|
||||
if (mappedLogFile)
|
||||
::close(mappedLogFile);
|
||||
|
||||
mappedLogFile = ::open(logFile, O_RDONLY);
|
||||
if (mappedLogFile < 0) {
|
||||
perror("open");
|
||||
exit(-1);
|
||||
}
|
||||
off_t size;
|
||||
firstLogEntry = (malloc_log_entry*) mapFile(mappedLogFile, PROT_READ, &size);
|
||||
lastLogEntry = (malloc_log_entry*)((char*)firstLogEntry + size);
|
||||
|
||||
if (!collect_last || collect_start < 0) {
|
||||
collecting = true;
|
||||
}
|
||||
|
||||
// First, restrict it to the capture sections specified (all, last, start/end)
|
||||
// This loop walks through all the call stacks we recorded
|
||||
for (malloc_log_entry* lep=firstLogEntry;
|
||||
lep < lastLogEntry;
|
||||
lep = reinterpret_cast<malloc_log_entry*>(&lep->pcs[lep->numpcs])) {
|
||||
|
||||
if (lep->flags & JP_FIRST_AFTER_PAUSE) {
|
||||
section++;
|
||||
if (collect_last) {
|
||||
firstLogEntry = lep;
|
||||
numThreads = 0;
|
||||
collecting = true;
|
||||
}
|
||||
if (collect_start == section) {
|
||||
collecting = true;
|
||||
firstLogEntry = lep;
|
||||
}
|
||||
if (collect_end == section) {
|
||||
collecting = false;
|
||||
lastLogEntry = lep;
|
||||
}
|
||||
if (!quiet)
|
||||
fprintf(stderr,"New section %d: first=%p, last=%p, collecting=%d\n",
|
||||
section,(void*)firstLogEntry,(void*)lastLogEntry,collecting);
|
||||
}
|
||||
|
||||
// Capture thread info at the same time
|
||||
|
||||
// Find all the threads captured
|
||||
|
||||
// pthread/linux docs say the signal can be delivered to any thread in
|
||||
// the process. In practice, it appears in Linux that it's always
|
||||
// delivered to the thread that called setitimer(), and each thread can
|
||||
// have a separate itimer. There's a support library for gprof that
|
||||
// overlays pthread_create() to set timers in any threads you spawn.
|
||||
if (showThreads && collecting) {
|
||||
if (lep->thread != last_thread)
|
||||
{
|
||||
int i;
|
||||
for (i=0; i<numThreads; i++)
|
||||
{
|
||||
if (lep->thread == threadArray[i])
|
||||
break;
|
||||
}
|
||||
if (i == numThreads &&
|
||||
i < (int) (sizeof(threadArray)/sizeof(threadArray[0])))
|
||||
{
|
||||
threadArray[i] = lep->thread;
|
||||
numThreads++;
|
||||
if (!quiet)
|
||||
fprintf(stderr,"new thread %d\n",lep->thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!quiet)
|
||||
fprintf(stderr,"Done collecting: sections %d: first=%p, last=%p, numThreads=%d\n",
|
||||
section,(void*)firstLogEntry,(void*)lastLogEntry,numThreads);
|
||||
|
||||
if (!cleo) {
|
||||
fprintf(outputfd,"<html><head><title>Jprof Profile Report</title></head><body>\n");
|
||||
fprintf(outputfd,"<h1><center>Jprof Profile Report</center></h1>\n");
|
||||
}
|
||||
|
||||
if (showThreads)
|
||||
{
|
||||
fprintf(stderr,"Num threads %d\n",numThreads);
|
||||
|
||||
if (!cleo) {
|
||||
fprintf(outputfd,"<hr>Threads:<p><pre>\n");
|
||||
for (int i=0; i<numThreads; i++)
|
||||
{
|
||||
fprintf(outputfd," <a href=\"#thread_%d\">%d</a> ",
|
||||
threadArray[i],threadArray[i]);
|
||||
if ((i+1)%10 == 0)
|
||||
fprintf(outputfd,"<br>\n");
|
||||
}
|
||||
fprintf(outputfd,"</pre>");
|
||||
}
|
||||
|
||||
for (int i=0; i<numThreads; i++)
|
||||
{
|
||||
if (!onlyThread || onlyThread == threadArray[i])
|
||||
analyze(threadArray[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
analyze(0);
|
||||
}
|
||||
|
||||
if (!cleo)
|
||||
fprintf(outputfd,"</pre></body></html>\n");
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
|
||||
static int symbolOrder(void const* a, void const* b)
|
||||
{
|
||||
Symbol const** ap = (Symbol const **)a;
|
||||
Symbol const** bp = (Symbol const **)b;
|
||||
return (*ap)->address == (*bp)->address ? 0 :
|
||||
((*ap)->address > (*bp)->address ? 1 : -1);
|
||||
}
|
||||
|
||||
void leaky::ReadSharedLibrarySymbols()
|
||||
{
|
||||
LoadMapEntry* lme = loadMap;
|
||||
while (nullptr != lme) {
|
||||
ReadSymbols(lme->name, lme->address);
|
||||
lme = lme->next;
|
||||
}
|
||||
}
|
||||
|
||||
void leaky::setupSymbols(const char *fileName)
|
||||
{
|
||||
if (usefulSymbols == 0) {
|
||||
// only read once!
|
||||
|
||||
// Read in symbols from the program
|
||||
ReadSymbols(fileName, 0);
|
||||
|
||||
// Read in symbols from the .so's
|
||||
ReadSharedLibrarySymbols();
|
||||
|
||||
if (!quiet) {
|
||||
fprintf(stderr,"A total of %d symbols were loaded\n", usefulSymbols);
|
||||
}
|
||||
|
||||
// Now sort them
|
||||
qsort(externalSymbols, usefulSymbols, sizeof(Symbol *), symbolOrder);
|
||||
lowestSymbolAddr = externalSymbols[0]->address;
|
||||
highestSymbolAddr = externalSymbols[usefulSymbols-1]->address;
|
||||
}
|
||||
}
|
||||
|
||||
// Binary search the table, looking for a symbol that covers this
|
||||
// address.
|
||||
int leaky::findSymbolIndex(u_long addr)
|
||||
{
|
||||
u_int base = 0;
|
||||
u_int limit = usefulSymbols - 1;
|
||||
Symbol** end = &externalSymbols[limit];
|
||||
while (base <= limit) {
|
||||
u_int midPoint = (base + limit)>>1;
|
||||
Symbol** sp = &externalSymbols[midPoint];
|
||||
if (addr < (*sp)->address) {
|
||||
if (midPoint == 0) {
|
||||
return -1;
|
||||
}
|
||||
limit = midPoint - 1;
|
||||
} else {
|
||||
if (sp+1 < end) {
|
||||
if (addr < (*(sp+1))->address) {
|
||||
return midPoint;
|
||||
}
|
||||
} else {
|
||||
return midPoint;
|
||||
}
|
||||
base = midPoint + 1;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Symbol* leaky::findSymbol(u_long addr)
|
||||
{
|
||||
int idx = findSymbolIndex(addr);
|
||||
|
||||
if(idx<0) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return externalSymbols[idx];
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
bool leaky::excluded(malloc_log_entry* lep)
|
||||
{
|
||||
if (exclusions.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
char** pcp = &lep->pcs[0];
|
||||
u_int n = lep->numpcs;
|
||||
for (u_int i = 0; i < n; i++, pcp++) {
|
||||
Symbol* sp = findSymbol((u_long) *pcp);
|
||||
if (sp && exclusions.contains(sp->name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool leaky::included(malloc_log_entry* lep)
|
||||
{
|
||||
if (includes.IsEmpty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
char** pcp = &lep->pcs[0];
|
||||
u_int n = lep->numpcs;
|
||||
for (u_int i = 0; i < n; i++, pcp++) {
|
||||
Symbol* sp = findSymbol((u_long) *pcp);
|
||||
if (sp && includes.contains(sp->name)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
void leaky::displayStackTrace(FILE* out, malloc_log_entry* lep)
|
||||
{
|
||||
char** pcp = &lep->pcs[0];
|
||||
u_int n = (lep->numpcs < stackDepth) ? lep->numpcs : stackDepth;
|
||||
for (u_int i = 0; i < n; i++, pcp++) {
|
||||
u_long addr = (u_long) *pcp;
|
||||
Symbol* sp = findSymbol(addr);
|
||||
if (sp) {
|
||||
fputs(sp->name, out);
|
||||
if (showAddress) {
|
||||
fprintf(out, "[%p]", (char*)addr);
|
||||
}
|
||||
}
|
||||
else {
|
||||
fprintf(out, "<%p>", (char*)addr);
|
||||
}
|
||||
fputc(' ', out);
|
||||
}
|
||||
fputc('\n', out);
|
||||
}
|
||||
|
||||
void leaky::dumpEntryToLog(malloc_log_entry* lep)
|
||||
{
|
||||
printf("%ld\t", lep->delTime);
|
||||
printf(" --> ");
|
||||
displayStackTrace(outputfd, lep);
|
||||
}
|
||||
|
||||
void leaky::generateReportHTML(FILE *fp, int *countArray, int count, int thread)
|
||||
{
|
||||
fprintf(fp,"<center>");
|
||||
if (showThreads)
|
||||
{
|
||||
fprintf(fp,"<hr><A NAME=thread_%d><b>Thread: %d</b></A><p>",
|
||||
thread,thread);
|
||||
}
|
||||
fprintf(fp,"<A href=#flat_%d>flat</A><b> | </b><A href=#hier_%d>hierarchical</A>",
|
||||
thread,thread);
|
||||
fprintf(fp,"</center><P><P><P>\n");
|
||||
|
||||
int totalTimerHits = count;
|
||||
int *rankingTable = new int[usefulSymbols];
|
||||
|
||||
for(int cnt=usefulSymbols; --cnt>=0; rankingTable[cnt]=cnt);
|
||||
|
||||
// Drat. I would use ::qsort() but I would need a global variable and my
|
||||
// intro-pascal professor threatened to flunk anyone who used globals.
|
||||
// She damaged me for life :-) (That was 1986. See how much influence
|
||||
// she had. I don't remember her name but I always feel guilty about globals)
|
||||
|
||||
// Shell Sort. 581130733 is the max 31 bit value of h = 3h+1
|
||||
int mx, i, h;
|
||||
for(mx=usefulSymbols/9, h=581130733; h>0; h/=3) {
|
||||
if(h<mx) {
|
||||
for(i = h-1; i<usefulSymbols; i++) {
|
||||
int j, tmp=rankingTable[i], val = countArray[tmp];
|
||||
for(j = i; (j>=h) && (countArray[rankingTable[j-h]]<val); j-=h) {
|
||||
rankingTable[j] = rankingTable[j-h];
|
||||
}
|
||||
rankingTable[j] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ok, We are sorted now. Let's go through the table until we get to
|
||||
// functions that were never called. Right now we don't do much inside
|
||||
// this loop. Later we can get callers and callees into it like gprof
|
||||
// does
|
||||
fprintf(fp,
|
||||
"<h2><A NAME=hier_%d></A><center><a href=\"http://dxr.mozilla.org/mozilla-central/source/tools/jprof/README.html#hier\">Hierarchical Profile</a></center></h2><hr>\n",
|
||||
thread);
|
||||
fprintf(fp, "<pre>\n");
|
||||
fprintf(fp, "%6s %6s %4s %s\n",
|
||||
"index", "Count", "Hits", "Function Name");
|
||||
|
||||
for(i=0; i<usefulSymbols && countArray[rankingTable[i]]>0; i++) {
|
||||
Symbol **sp=&externalSymbols[rankingTable[i]];
|
||||
|
||||
(*sp)->cntP.printReport(fp, this, rankingTable[i], totalTimerHits);
|
||||
|
||||
char *symname = htmlify((*sp)->name);
|
||||
fprintf(fp, "%6d %6d (%3.1f%%)%s <a name=%d>%8d (%3.1f%%)</a>%s <b>%s</b>\n",
|
||||
rankingTable[i],
|
||||
(*sp)->timerHit, ((*sp)->timerHit*1000/totalTimerHits)/10.0,
|
||||
((*sp)->timerHit*1000/totalTimerHits)/10.0 >= 10.0 ? "" : " ",
|
||||
rankingTable[i], countArray[rankingTable[i]],
|
||||
(countArray[rankingTable[i]]*1000/totalTimerHits)/10.0,
|
||||
(countArray[rankingTable[i]]*1000/totalTimerHits)/10.0 >= 10.0 ? "" : " ",
|
||||
symname);
|
||||
delete [] symname;
|
||||
|
||||
(*sp)->cntC.printReport(fp, this, rankingTable[i], totalTimerHits);
|
||||
|
||||
fprintf(fp, "<hr>\n");
|
||||
}
|
||||
fprintf(fp,"</pre>\n");
|
||||
|
||||
// OK, Now we want to print the flat profile. To do this we resort on
|
||||
// the hit count.
|
||||
|
||||
// Cut-N-Paste Shell sort from above. The Ranking Table has already been
|
||||
// populated, so we do not have to reinitialize it.
|
||||
for(mx=usefulSymbols/9, h=581130733; h>0; h/=3) {
|
||||
if(h<mx) {
|
||||
for(i = h-1; i<usefulSymbols; i++) {
|
||||
int j, tmp=rankingTable[i], val = externalSymbols[tmp]->timerHit;
|
||||
for(j = i;
|
||||
(j>=h) && (externalSymbols[rankingTable[j-h]]->timerHit<val); j-=h) {
|
||||
rankingTable[j] = rankingTable[j-h];
|
||||
}
|
||||
rankingTable[j] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-count up total counter hits, to get a percentage.
|
||||
// I wanted the total before walking the list, if this
|
||||
// double-pass over externalSymbols gets slow we can
|
||||
// do single-pass and print this out after the loop finishes.
|
||||
totalTimerHits = 0;
|
||||
for(i=0;
|
||||
i<usefulSymbols && externalSymbols[rankingTable[i]]->timerHit>0; i++) {
|
||||
Symbol **sp=&externalSymbols[rankingTable[i]];
|
||||
totalTimerHits += (*sp)->timerHit;
|
||||
}
|
||||
if (totalTimerHits == 0)
|
||||
totalTimerHits = 1;
|
||||
|
||||
if (totalTimerHits != count)
|
||||
fprintf(stderr,"Hit count mismatch: count=%d; totalTimerHits=%d",
|
||||
count,totalTimerHits);
|
||||
|
||||
fprintf(fp,"<h2><A NAME=flat_%d></A><center><a href=\"http://dxr.mozilla.org/mozilla-central/source/tools/jprof/README.html#flat\">Flat Profile</a></center></h2><br>\n",
|
||||
thread);
|
||||
fprintf(fp, "<pre>\n");
|
||||
|
||||
fprintf(fp, "Total hit count: %d\n", totalTimerHits);
|
||||
fprintf(fp, "Count %%Total Function Name\n");
|
||||
// Now loop for as long as we have timer hits
|
||||
for(i=0;
|
||||
i<usefulSymbols && externalSymbols[rankingTable[i]]->timerHit>0; i++) {
|
||||
|
||||
Symbol **sp=&externalSymbols[rankingTable[i]];
|
||||
|
||||
char *symname = htmlify((*sp)->name);
|
||||
fprintf(fp, "<a href=\"#%d\">%3d %-2.1f %s</a>\n",
|
||||
rankingTable[i], (*sp)->timerHit,
|
||||
((float)(*sp)->timerHit/(float)totalTimerHits)*100.0, symname);
|
||||
delete [] symname;
|
||||
}
|
||||
}
|
||||
|
||||
void leaky::analyze(int thread)
|
||||
{
|
||||
int *countArray = new int[usefulSymbols];
|
||||
int *flagArray = new int[usefulSymbols];
|
||||
|
||||
//Zero our function call counter
|
||||
memset(countArray, 0, sizeof(countArray[0])*usefulSymbols);
|
||||
|
||||
// reset hit counts
|
||||
for(int i=0; i<usefulSymbols; i++) {
|
||||
externalSymbols[i]->timerHit = 0;
|
||||
externalSymbols[i]->regClear();
|
||||
}
|
||||
|
||||
// The flag array is used to prevent counting symbols multiple times
|
||||
// if functions are called recursively. In order to keep from having
|
||||
// to zero it on each pass through the loop, we mark it with the value
|
||||
// of stacks on each trip through the loop. This means we can determine
|
||||
// if we have seen this symbol for this stack trace w/o having to reset
|
||||
// from the prior stacktrace.
|
||||
memset(flagArray, -1, sizeof(flagArray[0])*usefulSymbols);
|
||||
|
||||
if (cleo)
|
||||
fprintf(outputfd,"m-Start\n");
|
||||
|
||||
// This loop walks through all the call stacks we recorded
|
||||
// --last, --start and --end can restrict it, as can excludes/includes
|
||||
stacks = 0;
|
||||
for(malloc_log_entry* lep=firstLogEntry;
|
||||
lep < lastLogEntry;
|
||||
lep = reinterpret_cast<malloc_log_entry*>(&lep->pcs[lep->numpcs])) {
|
||||
|
||||
if ((thread != 0 && lep->thread != thread) ||
|
||||
excluded(lep) || !included(lep))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
++stacks; // How many stack frames did we collect
|
||||
|
||||
u_int n = (lep->numpcs < stackDepth) ? lep->numpcs : stackDepth;
|
||||
char** pcp = &lep->pcs[n-1];
|
||||
int idx=-1, parrentIdx=-1; // Init idx incase n==0
|
||||
if (cleo) {
|
||||
// This loop walks through every symbol in the call stack. By walking it
|
||||
// backwards we know who called the function when we get there.
|
||||
char type = 's';
|
||||
for (int i=n-1; i>=0; --i, --pcp) {
|
||||
idx = findSymbolIndex(reinterpret_cast<u_long>(*pcp));
|
||||
|
||||
if(idx>=0) {
|
||||
// Skip over bogus __restore_rt frames that realtime profiling
|
||||
// can introduce.
|
||||
if (i > 0 && !strcmp(externalSymbols[idx]->name, "__restore_rt")) {
|
||||
--pcp;
|
||||
--i;
|
||||
idx = findSymbolIndex(reinterpret_cast<u_long>(*pcp));
|
||||
if (idx < 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Symbol **sp=&externalSymbols[idx];
|
||||
char *symname = htmlify((*sp)->name);
|
||||
fprintf(outputfd,"%c-%s\n",type,symname);
|
||||
delete [] symname;
|
||||
}
|
||||
// else can't find symbol - ignore
|
||||
type = 'c';
|
||||
}
|
||||
} else {
|
||||
// This loop walks through every symbol in the call stack. By walking it
|
||||
// backwards we know who called the function when we get there.
|
||||
for (int i=n-1; i>=0; --i, --pcp) {
|
||||
idx = findSymbolIndex(reinterpret_cast<u_long>(*pcp));
|
||||
|
||||
if(idx>=0) {
|
||||
// Skip over bogus __restore_rt frames that realtime profiling
|
||||
// can introduce.
|
||||
if (i > 0 && !strcmp(externalSymbols[idx]->name, "__restore_rt")) {
|
||||
--pcp;
|
||||
--i;
|
||||
idx = findSymbolIndex(reinterpret_cast<u_long>(*pcp));
|
||||
if (idx < 0) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If we have not seen this symbol before count it and mark it as seen
|
||||
if(flagArray[idx]!=stacks && ((flagArray[idx]=stacks) || true)) {
|
||||
++countArray[idx];
|
||||
}
|
||||
|
||||
// We know who we are and we know who our parrent is. Count this
|
||||
if(parrentIdx>=0) {
|
||||
externalSymbols[parrentIdx]->regChild(idx);
|
||||
externalSymbols[idx]->regParrent(parrentIdx);
|
||||
}
|
||||
// inside if() so an unknown in the middle of a stack won't break
|
||||
// the link!
|
||||
parrentIdx=idx;
|
||||
}
|
||||
}
|
||||
|
||||
// idx should be the function that we were in when we received the signal.
|
||||
if(idx>=0) {
|
||||
++externalSymbols[idx]->timerHit;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (!cleo)
|
||||
generateReportHTML(outputfd, countArray, stacks, thread);
|
||||
}
|
||||
|
||||
void FunctionCount::printReport(FILE *fp, leaky *lk, int parent, int total)
|
||||
{
|
||||
const char *fmt = " <A href=\"#%d\">%8d (%3.1f%%)%s %s</A>%s\n";
|
||||
|
||||
int nmax, tmax=((~0U)>>1);
|
||||
|
||||
do {
|
||||
nmax=0;
|
||||
for(int j=getSize(); --j>=0;) {
|
||||
int cnt = getCount(j);
|
||||
if(cnt==tmax) {
|
||||
int idx = getIndex(j);
|
||||
char *symname = htmlify(lk->indexToName(idx));
|
||||
fprintf(fp, fmt, idx, getCount(j),
|
||||
getCount(j)*100.0/total,
|
||||
getCount(j)*100.0/total >= 10.0 ? "" : " ",
|
||||
symname,
|
||||
parent == idx ? " (self)" : "");
|
||||
delete [] symname;
|
||||
} else if(cnt<tmax && cnt>nmax) {
|
||||
nmax=cnt;
|
||||
}
|
||||
}
|
||||
} while((tmax=nmax)>0);
|
||||
}
|
||||
122
tools/jprof/leaky.h
Normal file
122
tools/jprof/leaky.h
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/* 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/. */
|
||||
|
||||
#ifndef __leaky_h_
|
||||
#define __leaky_h_
|
||||
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include "libmalloc.h"
|
||||
#include "strset.h"
|
||||
#include "intcnt.h"
|
||||
|
||||
typedef unsigned int u_int;
|
||||
|
||||
struct Symbol;
|
||||
struct leaky;
|
||||
|
||||
class FunctionCount : public IntCount
|
||||
{
|
||||
public:
|
||||
void printReport(FILE *fp, leaky *lk, int parent, int total);
|
||||
};
|
||||
|
||||
struct Symbol {
|
||||
char* name;
|
||||
u_long address;
|
||||
int timerHit;
|
||||
FunctionCount cntP, cntC;
|
||||
|
||||
int regChild(int id) {return cntC.countAdd(id, 1);}
|
||||
int regParrent(int id) {return cntP.countAdd(id, 1);}
|
||||
void regClear() {cntC.clear(); cntP.clear();}
|
||||
|
||||
Symbol() : timerHit(0) {}
|
||||
void Init(const char* aName, u_long aAddress) {
|
||||
name = aName ? strdup(aName) : (char *)"";
|
||||
address = aAddress;
|
||||
}
|
||||
};
|
||||
|
||||
struct LoadMapEntry {
|
||||
char* name; // name of .so
|
||||
u_long address; // base address where it was mapped in
|
||||
LoadMapEntry* next;
|
||||
};
|
||||
|
||||
struct leaky {
|
||||
leaky();
|
||||
~leaky();
|
||||
|
||||
void initialize(int argc, char** argv);
|
||||
void open(char *arg);
|
||||
|
||||
char* applicationName;
|
||||
int logFileIndex;
|
||||
int numLogFiles;
|
||||
char* progFile;
|
||||
FILE* outputfd;
|
||||
|
||||
bool quiet;
|
||||
bool showAddress;
|
||||
bool showThreads;
|
||||
bool cleo;
|
||||
u_int stackDepth;
|
||||
int onlyThread;
|
||||
char* output_dir;
|
||||
|
||||
int mappedLogFile;
|
||||
malloc_log_entry* firstLogEntry;
|
||||
malloc_log_entry* lastLogEntry;
|
||||
|
||||
int stacks;
|
||||
|
||||
int sfd;
|
||||
Symbol** externalSymbols;
|
||||
Symbol** lastSymbol;
|
||||
int usefulSymbols;
|
||||
int numExternalSymbols;
|
||||
StrSet exclusions;
|
||||
u_long lowestSymbolAddr;
|
||||
u_long highestSymbolAddr;
|
||||
|
||||
LoadMapEntry* loadMap;
|
||||
|
||||
bool collect_last;
|
||||
int collect_start;
|
||||
int collect_end;
|
||||
|
||||
StrSet roots;
|
||||
StrSet includes;
|
||||
|
||||
void usageError();
|
||||
|
||||
void LoadMap();
|
||||
|
||||
void analyze(int thread);
|
||||
|
||||
void dumpEntryToLog(malloc_log_entry* lep);
|
||||
|
||||
void insertAddress(u_long address, malloc_log_entry* lep);
|
||||
void removeAddress(u_long address, malloc_log_entry* lep);
|
||||
|
||||
void displayStackTrace(FILE* out, malloc_log_entry* lep);
|
||||
|
||||
Symbol ** ExtendSymbols(int num);
|
||||
void ReadSymbols(const char* fileName, u_long aBaseAddress);
|
||||
void ReadSharedLibrarySymbols();
|
||||
void setupSymbols(const char* fileName);
|
||||
Symbol* findSymbol(u_long address);
|
||||
bool excluded(malloc_log_entry* lep);
|
||||
bool included(malloc_log_entry* lep);
|
||||
const char* indexToName(int idx) {return externalSymbols[idx]->name;}
|
||||
|
||||
private:
|
||||
void generateReportHTML(FILE *fp, int *countArray, int count, int thread);
|
||||
int findSymbolIndex(u_long address);
|
||||
};
|
||||
|
||||
#endif /* __leaky_h_ */
|
||||
28
tools/jprof/moz.build
Normal file
28
tools/jprof/moz.build
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
DIRS += ['stub']
|
||||
|
||||
Program('jprof')
|
||||
|
||||
SOURCES += [
|
||||
'bfd.cpp',
|
||||
'coff.cpp',
|
||||
'elf.cpp',
|
||||
'intcnt.cpp',
|
||||
'leaky.cpp',
|
||||
'strset.cpp',
|
||||
]
|
||||
|
||||
LOCAL_INCLUDES += [
|
||||
'stub',
|
||||
]
|
||||
|
||||
OS_LIBS += [
|
||||
'dl',
|
||||
'bfd',
|
||||
'iberty',
|
||||
]
|
||||
143
tools/jprof/split-profile.py
Normal file
143
tools/jprof/split-profile.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
#!/usr/bin/python
|
||||
# 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/.
|
||||
|
||||
# This program splits up a jprof profile into multiple files based on a
|
||||
# list of functions in a text file. First, a complete profile is
|
||||
# generated. Then, for each line in the text file, a profile is
|
||||
# generated containing only stacks that go through that line, and also
|
||||
# excluding all stacks in earlier lines in the text file. This means
|
||||
# that the text file, from start to end, is splitting out pieces of the
|
||||
# profile in their own file. Finally, a final profile containing the
|
||||
# remainder is produced.
|
||||
|
||||
# The program takes four arguments:
|
||||
# (1) The path to jprof.
|
||||
# (2) The path to the text file describing the splits. The output
|
||||
# will be placed in the same directory as this file.
|
||||
# (3) The program that was profiled.
|
||||
# (4) The jprof-log file generated by the profile, to be split up.
|
||||
# (Really, all arguments from (3) and later are passed through to
|
||||
# jprof, so additional arguments could be provided if you want to pass
|
||||
# additional arguments to jprof.)
|
||||
|
||||
# In slightly more detail:
|
||||
#
|
||||
# This script uses jprof's includes (-i) and excludes (-e) options to
|
||||
# split profiles into segments. It takes as input a single text file,
|
||||
# and from that text file creates a series of jprof profiles in the
|
||||
# directory the text file is in.
|
||||
#
|
||||
# The input file format looks like the following:
|
||||
#
|
||||
# poll g_main_poll
|
||||
# GetRuleCascade CSSRuleProcessor::GetRuleCascade(nsPresContext *, nsIAtom *)
|
||||
# RuleProcessorData RuleProcessorData::RuleProcessorData(nsPresContext *, nsIContent *, nsRuleWalker *, nsCompatibility *)
|
||||
#
|
||||
# From this input file, the script will construct a profile called
|
||||
# jprof-0.html that contains the whole profile, a profile called
|
||||
# jprof-1-poll.html that includes only stacks with g_main_poll, a
|
||||
# profile called jprof-2-GetRuleCascade.html that includes only stacks
|
||||
# that have GetRuleCascade and do not have g_main_poll, a profile called
|
||||
# jprof-3-RuleProcessorData.html that includes only stacks that have the
|
||||
# RuleProcessorData constructor and do not have GetRuleCascade or
|
||||
# g_main_poll, and a profile called jprof-4.html that includes only
|
||||
# stacks that do not have any of the three functions in them.
|
||||
#
|
||||
# This means that all of the segments of the profile, except
|
||||
# jprof-0.html, are mutually exclusive. Thus clever ordering of the
|
||||
# functions in the input file can lead to a logical splitting of the
|
||||
# profile into segments.
|
||||
|
||||
import sys
|
||||
import subprocess
|
||||
import os.path
|
||||
|
||||
if len(sys.argv) < 5:
|
||||
sys.stderr.write("Expected arguments: <jprof> <split-file> <program> <jprof-log>\n")
|
||||
sys.exit(1)
|
||||
|
||||
jprof = sys.argv[1]
|
||||
splitfile = sys.argv[2]
|
||||
passthrough = sys.argv[3:]
|
||||
|
||||
for f in [jprof, splitfile]:
|
||||
if not os.path.isfile(f):
|
||||
sys.stderr.write("could not find file: {0}\n".format(f))
|
||||
sys.exit(1)
|
||||
|
||||
def read_splits(splitfile):
|
||||
"""
|
||||
Read splitfile (each line of which contains a name, a space, and
|
||||
then a function name to split on), and return a list of pairs
|
||||
representing exactly that. (Note that the name cannot contain
|
||||
spaces, but the function name can, and often does.)
|
||||
"""
|
||||
def line_to_split(line):
|
||||
line = line.strip("\r\n")
|
||||
idx = line.index(" ")
|
||||
return (line[0:idx], line[idx+1:])
|
||||
|
||||
io = open(splitfile, "r")
|
||||
result = [line_to_split(line) for line in io]
|
||||
io.close()
|
||||
return result
|
||||
|
||||
splits = read_splits(splitfile)
|
||||
|
||||
def generate_profile(options, destfile):
|
||||
"""
|
||||
Run jprof to generate one split of the profile.
|
||||
"""
|
||||
args = [jprof] + options + passthrough
|
||||
print "Generating {0}".format(destfile)
|
||||
destio = open(destfile, "w")
|
||||
# jprof expects the "jprof-map" file to be in its current working directory
|
||||
cwd = None
|
||||
for option in passthrough:
|
||||
if option.find("jprof-log"):
|
||||
cwd = os.path.dirname(option)
|
||||
if cwd is None:
|
||||
raise StandardError("no jprof-log option given")
|
||||
process = subprocess.Popen(args, stdout=destio, cwd=cwd)
|
||||
process.wait()
|
||||
destio.close()
|
||||
if process.returncode != 0:
|
||||
os.remove(destfile)
|
||||
sys.stderr.write("Error {0} from command:\n {1}\n".format(process.returncode, " ".join(args)))
|
||||
sys.exit(process.returncode)
|
||||
|
||||
def output_filename(number, splitname):
|
||||
"""
|
||||
Return the filename (absolute path) we should use to output the
|
||||
profile segment with the given number and splitname. Splitname
|
||||
should be None for the complete profile and the remainder.
|
||||
"""
|
||||
def pad_count(i):
|
||||
result = str(i)
|
||||
# 0-pad to the same length
|
||||
result = "0" * (len(str(len(splits) + 1)) - len(result)) + result
|
||||
return result
|
||||
|
||||
name = pad_count(number)
|
||||
if splitname is not None:
|
||||
name += "-" + splitname
|
||||
|
||||
return os.path.join(os.path.dirname(splitfile),
|
||||
"jprof-{0}.html".format(name))
|
||||
|
||||
# generate the complete profile
|
||||
generate_profile([], output_filename(0, None))
|
||||
|
||||
# generate the listed splits
|
||||
count = 1
|
||||
excludes = []
|
||||
for (splitname, splitfunction) in splits:
|
||||
generate_profile(excludes + ["-i" + splitfunction],
|
||||
output_filename(count, splitname))
|
||||
excludes += ["-e" + splitfunction]
|
||||
count = count + 1
|
||||
|
||||
# generate the remainder after the splits
|
||||
generate_profile(excludes, output_filename(count, None))
|
||||
40
tools/jprof/strset.cpp
Normal file
40
tools/jprof/strset.cpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/* 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/. */
|
||||
|
||||
#include "strset.h"
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
|
||||
StrSet::StrSet()
|
||||
{
|
||||
strings = 0;
|
||||
numstrings = 0;
|
||||
}
|
||||
|
||||
void StrSet::add(const char* s)
|
||||
{
|
||||
if (strings) {
|
||||
strings = (char**) realloc(strings, (numstrings + 1) * sizeof(char*));
|
||||
} else {
|
||||
strings = (char**) malloc(sizeof(char*));
|
||||
}
|
||||
strings[numstrings] = strdup(s);
|
||||
numstrings++;
|
||||
}
|
||||
|
||||
int StrSet::contains(const char* s)
|
||||
{
|
||||
char** sp = strings;
|
||||
int i = numstrings;
|
||||
|
||||
while (--i >= 0) {
|
||||
char *ss = *sp++;
|
||||
if (ss[0] == s[0]) {
|
||||
if (strcmp(ss, s) == 0) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
19
tools/jprof/strset.h
Normal file
19
tools/jprof/strset.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/* 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/. */
|
||||
|
||||
#ifndef __strset_h_
|
||||
#define __strset_h_
|
||||
|
||||
struct StrSet {
|
||||
StrSet();
|
||||
|
||||
void add(const char* string);
|
||||
int contains(const char* string);
|
||||
bool IsEmpty() const { return 0 == numstrings; }
|
||||
|
||||
char** strings;
|
||||
int numstrings;
|
||||
};
|
||||
|
||||
#endif /* __strset_h_ */
|
||||
8
tools/jprof/stub/Makefile.in
Normal file
8
tools/jprof/stub/Makefile.in
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#! gmake
|
||||
#
|
||||
# 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/.
|
||||
|
||||
# override optimization
|
||||
MOZ_OPTIMIZE_FLAGS = -fno-omit-frame-pointer
|
||||
18
tools/jprof/stub/config.h
Normal file
18
tools/jprof/stub/config.h
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
/* 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/. */
|
||||
|
||||
#ifndef config_h___
|
||||
#define config_h___
|
||||
|
||||
#define MAX_STACK_CRAWL 500
|
||||
#define M_LOGFILE "jprof-log"
|
||||
#define M_MAPFILE "jprof-map"
|
||||
|
||||
#if defined(linux) || defined(NTO)
|
||||
#define USE_BFD
|
||||
#undef NEED_WRAPPERS
|
||||
|
||||
#endif /* linux */
|
||||
|
||||
#endif /* config_h___ */
|
||||
17
tools/jprof/stub/jprof.h
Normal file
17
tools/jprof/stub/jprof.h
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
/* 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/. */
|
||||
|
||||
#ifndef jprof_h___
|
||||
#define jprof_h___
|
||||
#include "nscore.h"
|
||||
|
||||
#ifdef _IMPL_JPPROF_API
|
||||
#define JPROF_API(type) NS_EXPORT_(type)
|
||||
#else
|
||||
#define JPROF_API(type) NS_IMPORT_(type)
|
||||
#endif
|
||||
|
||||
JPROF_API(void) setupProfilingStuff(void);
|
||||
|
||||
#endif /* jprof_h___ */
|
||||
790
tools/jprof/stub/libmalloc.cpp
Normal file
790
tools/jprof/stub/libmalloc.cpp
Normal file
|
|
@ -0,0 +1,790 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
// vim:cindent:sw=4:et:ts=8:
|
||||
/* 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/. */
|
||||
|
||||
// The linux glibc hides part of sigaction if _POSIX_SOURCE is defined
|
||||
#if defined(linux)
|
||||
#undef _POSIX_SOURCE
|
||||
#undef _SVID_SOURCE
|
||||
#ifndef _GNU_SOURCE
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <errno.h>
|
||||
#if defined(linux)
|
||||
#include <linux/rtc.h>
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <signal.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <ucontext.h>
|
||||
#include <execinfo.h>
|
||||
|
||||
#include "libmalloc.h"
|
||||
#include "jprof.h"
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <dlfcn.h>
|
||||
|
||||
// Must define before including jprof.h
|
||||
void *moz_xmalloc(size_t size)
|
||||
{
|
||||
return malloc(size);
|
||||
}
|
||||
|
||||
void moz_xfree(void *mem)
|
||||
{
|
||||
free(mem);
|
||||
}
|
||||
|
||||
#ifdef NTO
|
||||
#include <sys/link.h>
|
||||
extern r_debug _r_debug;
|
||||
#else
|
||||
#include <link.h>
|
||||
#endif
|
||||
|
||||
#define USE_GLIBC_BACKTRACE 1
|
||||
// To debug, use #define JPROF_STATIC
|
||||
#define JPROF_STATIC static
|
||||
|
||||
static int gLogFD = -1;
|
||||
static pthread_t main_thread;
|
||||
|
||||
static bool gIsSlave = false;
|
||||
static int gFilenamePID;
|
||||
|
||||
static void startSignalCounter(unsigned long millisec);
|
||||
static int enableRTCSignals(bool enable);
|
||||
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// replace use of atexit()
|
||||
|
||||
static void DumpAddressMap();
|
||||
|
||||
struct JprofShutdown {
|
||||
JprofShutdown() {}
|
||||
~JprofShutdown() {
|
||||
DumpAddressMap();
|
||||
}
|
||||
};
|
||||
|
||||
static void RegisterJprofShutdown() {
|
||||
// This instanciates the dummy class above, and will trigger the class
|
||||
// destructor when libxul is unloaded. This is equivalent to atexit(),
|
||||
// but gracefully handles dlclose().
|
||||
static JprofShutdown t;
|
||||
}
|
||||
|
||||
#if defined(i386) || defined(_i386) || defined(__x86_64__)
|
||||
JPROF_STATIC void CrawlStack(malloc_log_entry* me,
|
||||
void* stack_top, void* top_instr_ptr)
|
||||
{
|
||||
#if USE_GLIBC_BACKTRACE
|
||||
// This probably works on more than x86! But we need a way to get the
|
||||
// top instruction pointer, which is kindof arch-specific
|
||||
void *array[500];
|
||||
int cnt, i;
|
||||
u_long numpcs = 0;
|
||||
|
||||
// This is from glibc. A more generic version might use
|
||||
// libunwind and/or CaptureStackBackTrace() on Windows
|
||||
cnt = backtrace(&array[0],sizeof(array)/sizeof(array[0]));
|
||||
|
||||
// StackHook->JprofLog->CrawlStack
|
||||
// Then we have sigaction, which replaced top_instr_ptr
|
||||
array[3] = top_instr_ptr;
|
||||
for (i = 3; i < cnt; i++)
|
||||
{
|
||||
me->pcs[numpcs++] = (char *) array[i];
|
||||
}
|
||||
me->numpcs = numpcs;
|
||||
|
||||
#else
|
||||
// original code - this breaks on many platforms
|
||||
void **bp;
|
||||
#if defined(__i386)
|
||||
__asm__( "movl %%ebp, %0" : "=g"(bp));
|
||||
#elif defined(__x86_64__)
|
||||
__asm__( "movq %%rbp, %0" : "=g"(bp));
|
||||
#else
|
||||
// It would be nice if this worked uniformly, but at least on i386 and
|
||||
// x86_64, it stopped working with gcc 4.1, because it points to the
|
||||
// end of the saved registers instead of the start.
|
||||
bp = __builtin_frame_address(0);
|
||||
#endif
|
||||
u_long numpcs = 0;
|
||||
bool tracing = false;
|
||||
|
||||
me->pcs[numpcs++] = (char*) top_instr_ptr;
|
||||
|
||||
while (numpcs < MAX_STACK_CRAWL) {
|
||||
void** nextbp = (void**) *bp++;
|
||||
void* pc = *bp;
|
||||
if (nextbp < bp) {
|
||||
break;
|
||||
}
|
||||
if (tracing) {
|
||||
// Skip the signal handling.
|
||||
me->pcs[numpcs++] = (char*) pc;
|
||||
}
|
||||
else if (pc == top_instr_ptr) {
|
||||
tracing = true;
|
||||
}
|
||||
bp = nextbp;
|
||||
}
|
||||
me->numpcs = numpcs;
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
static int rtcHz;
|
||||
static int rtcFD = -1;
|
||||
static bool circular = false;
|
||||
|
||||
#if defined(linux) || defined(NTO)
|
||||
static void DumpAddressMap()
|
||||
{
|
||||
// Turn off the timer so we don't get interrupts during shutdown
|
||||
#if defined(linux)
|
||||
if (rtcHz) {
|
||||
enableRTCSignals(false);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
startSignalCounter(0);
|
||||
}
|
||||
|
||||
char filename[2048];
|
||||
if (gIsSlave)
|
||||
snprintf(filename, sizeof(filename), "%s-%d", M_MAPFILE, gFilenamePID);
|
||||
else
|
||||
snprintf(filename, sizeof(filename), "%s", M_MAPFILE);
|
||||
|
||||
int mfd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
|
||||
if (mfd >= 0) {
|
||||
malloc_map_entry mme;
|
||||
link_map* map = _r_debug.r_map;
|
||||
while (nullptr != map) {
|
||||
if (map->l_name && *map->l_name) {
|
||||
mme.nameLen = strlen(map->l_name);
|
||||
mme.address = map->l_addr;
|
||||
write(mfd, &mme, sizeof(mme));
|
||||
write(mfd, map->l_name, mme.nameLen);
|
||||
#if 0
|
||||
write(1, map->l_name, mme.nameLen);
|
||||
write(1, "\n", 1);
|
||||
#endif
|
||||
}
|
||||
map = map->l_next;
|
||||
}
|
||||
close(mfd);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static bool was_paused = true;
|
||||
|
||||
JPROF_STATIC void JprofBufferDump();
|
||||
JPROF_STATIC void JprofBufferClear();
|
||||
|
||||
static void ClearProfilingHook(int signum)
|
||||
{
|
||||
if (circular) {
|
||||
JprofBufferClear();
|
||||
puts("Jprof: cleared circular buffer.");
|
||||
}
|
||||
}
|
||||
|
||||
static void EndProfilingHook(int signum)
|
||||
{
|
||||
if (circular)
|
||||
JprofBufferDump();
|
||||
|
||||
DumpAddressMap();
|
||||
was_paused = true;
|
||||
puts("Jprof: profiling paused.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// proper usage would be a template, including the function to find the
|
||||
// size of an entry, or include a size header explicitly to each entry.
|
||||
#if defined(linux)
|
||||
#define DUMB_LOCK() pthread_mutex_lock(&mutex);
|
||||
#define DUMB_UNLOCK() pthread_mutex_unlock(&mutex);
|
||||
#else
|
||||
#define DUMB_LOCK() FIXME()
|
||||
#define DUMB_UNLOCK() FIXME()
|
||||
#endif
|
||||
|
||||
|
||||
class DumbCircularBuffer
|
||||
{
|
||||
public:
|
||||
DumbCircularBuffer(size_t init_buffer_size) {
|
||||
used = 0;
|
||||
buffer_size = init_buffer_size;
|
||||
buffer = (unsigned char *) malloc(buffer_size);
|
||||
head = tail = buffer;
|
||||
|
||||
#if defined(linux)
|
||||
pthread_mutexattr_t mAttr;
|
||||
pthread_mutexattr_settype(&mAttr, PTHREAD_MUTEX_RECURSIVE_NP);
|
||||
pthread_mutex_init(&mutex, &mAttr);
|
||||
pthread_mutexattr_destroy(&mAttr);
|
||||
#endif
|
||||
}
|
||||
~DumbCircularBuffer() {
|
||||
free(buffer);
|
||||
#if defined(linux)
|
||||
pthread_mutex_destroy (&mutex);
|
||||
#endif
|
||||
}
|
||||
|
||||
void clear() {
|
||||
DUMB_LOCK();
|
||||
head = tail;
|
||||
used = 0;
|
||||
DUMB_UNLOCK();
|
||||
}
|
||||
|
||||
bool empty() {
|
||||
return head == tail;
|
||||
}
|
||||
|
||||
size_t space_available() {
|
||||
size_t result;
|
||||
DUMB_LOCK();
|
||||
if (tail > head)
|
||||
result = buffer_size - (tail-head) - 1;
|
||||
else
|
||||
result = head-tail - 1;
|
||||
DUMB_UNLOCK();
|
||||
return result;
|
||||
}
|
||||
|
||||
void drop(size_t size) {
|
||||
// assumes correctness!
|
||||
DUMB_LOCK();
|
||||
head += size;
|
||||
if (head >= &buffer[buffer_size])
|
||||
head -= buffer_size;
|
||||
used--;
|
||||
DUMB_UNLOCK();
|
||||
}
|
||||
|
||||
bool insert(void *data, size_t size) {
|
||||
// can fail if not enough space in the entire buffer
|
||||
DUMB_LOCK();
|
||||
if (space_available() < size)
|
||||
return false;
|
||||
|
||||
size_t max_without_wrap = &buffer[buffer_size] - tail;
|
||||
size_t initial = size > max_without_wrap ? max_without_wrap : size;
|
||||
#if DEBUG_CIRCULAR
|
||||
fprintf(stderr,"insert(%d): max_without_wrap %d, size %d, initial %d\n",used,max_without_wrap,size,initial);
|
||||
#endif
|
||||
memcpy(tail,data,initial);
|
||||
tail += initial;
|
||||
data = ((char *)data)+initial;
|
||||
size -= initial;
|
||||
if (size != 0) {
|
||||
#if DEBUG_CIRCULAR
|
||||
fprintf(stderr,"wrapping by %d bytes\n",size);
|
||||
#endif
|
||||
memcpy(buffer,data,size);
|
||||
tail = &(((unsigned char *)buffer)[size]);
|
||||
}
|
||||
|
||||
used++;
|
||||
DUMB_UNLOCK();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// for external access to the buffer (saving)
|
||||
void lock() {
|
||||
DUMB_LOCK();
|
||||
}
|
||||
|
||||
void unlock() {
|
||||
DUMB_UNLOCK();
|
||||
}
|
||||
|
||||
// XXX These really shouldn't be public...
|
||||
unsigned char *head;
|
||||
unsigned char *tail;
|
||||
unsigned int used;
|
||||
unsigned char *buffer;
|
||||
size_t buffer_size;
|
||||
|
||||
private:
|
||||
pthread_mutex_t mutex;
|
||||
};
|
||||
|
||||
class DumbCircularBuffer *JprofBuffer;
|
||||
|
||||
JPROF_STATIC void
|
||||
JprofBufferInit(size_t size)
|
||||
{
|
||||
JprofBuffer = new DumbCircularBuffer(size);
|
||||
}
|
||||
|
||||
JPROF_STATIC void
|
||||
JprofBufferClear()
|
||||
{
|
||||
fprintf(stderr,"Told to clear JPROF circular buffer\n");
|
||||
JprofBuffer->clear();
|
||||
}
|
||||
|
||||
JPROF_STATIC size_t
|
||||
JprofEntrySizeof(malloc_log_entry *me)
|
||||
{
|
||||
return offsetof(malloc_log_entry, pcs) + me->numpcs*sizeof(char*);
|
||||
}
|
||||
|
||||
JPROF_STATIC void
|
||||
JprofBufferAppend(malloc_log_entry *me)
|
||||
{
|
||||
size_t size = JprofEntrySizeof(me);
|
||||
|
||||
do {
|
||||
while (JprofBuffer->space_available() < size &&
|
||||
JprofBuffer->used > 0) {
|
||||
#if DEBUG_CIRCULAR
|
||||
fprintf(stderr,"dropping entry: %d in use, %d free, need %d, size_to_free = %d\n",
|
||||
JprofBuffer->used,JprofBuffer->space_available(),size,JprofEntrySizeof((malloc_log_entry *) JprofBuffer->head));
|
||||
#endif
|
||||
JprofBuffer->drop(JprofEntrySizeof((malloc_log_entry *) JprofBuffer->head));
|
||||
}
|
||||
if (JprofBuffer->space_available() < size)
|
||||
return;
|
||||
|
||||
} while (!JprofBuffer->insert(me,size));
|
||||
}
|
||||
|
||||
JPROF_STATIC void
|
||||
JprofBufferDump()
|
||||
{
|
||||
JprofBuffer->lock();
|
||||
#if DEBUG_CIRCULAR
|
||||
fprintf(stderr,"dumping JP_CIRCULAR buffer, %d of %d bytes\n",
|
||||
JprofBuffer->tail > JprofBuffer->head ?
|
||||
JprofBuffer->tail - JprofBuffer->head :
|
||||
JprofBuffer->buffer_size + JprofBuffer->tail - JprofBuffer->head,
|
||||
JprofBuffer->buffer_size);
|
||||
#endif
|
||||
if (JprofBuffer->tail >= JprofBuffer->head) {
|
||||
write(gLogFD, JprofBuffer->head, JprofBuffer->tail - JprofBuffer->head);
|
||||
} else {
|
||||
write(gLogFD, JprofBuffer->head, &(JprofBuffer->buffer[JprofBuffer->buffer_size]) - JprofBuffer->head);
|
||||
write(gLogFD, JprofBuffer->buffer, JprofBuffer->tail - JprofBuffer->buffer);
|
||||
}
|
||||
JprofBuffer->clear();
|
||||
JprofBuffer->unlock();
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
JPROF_STATIC void
|
||||
JprofLog(u_long aTime, void* stack_top, void* top_instr_ptr)
|
||||
{
|
||||
// Static is simply to make debugging tolerable
|
||||
static malloc_log_entry me;
|
||||
|
||||
me.delTime = aTime;
|
||||
me.thread = syscall(SYS_gettid); //gettid();
|
||||
if (was_paused) {
|
||||
me.flags = JP_FIRST_AFTER_PAUSE;
|
||||
was_paused = 0;
|
||||
} else {
|
||||
me.flags = 0;
|
||||
}
|
||||
|
||||
CrawlStack(&me, stack_top, top_instr_ptr);
|
||||
|
||||
#ifndef NTO
|
||||
if (circular) {
|
||||
JprofBufferAppend(&me);
|
||||
} else {
|
||||
write(gLogFD, &me, JprofEntrySizeof(&me));
|
||||
}
|
||||
#else
|
||||
printf("Neutrino is missing the pcs member of malloc_log_entry!! \n");
|
||||
#endif
|
||||
}
|
||||
|
||||
static int realTime;
|
||||
|
||||
/* Lets interrupt at 10 Hz. This is so my log files don't get too large.
|
||||
* This can be changed to a faster value latter. This timer is not
|
||||
* programmed to reset, even though it is capable of doing so. This is
|
||||
* to keep from getting interrupts from inside of the handler.
|
||||
*/
|
||||
static void startSignalCounter(unsigned long millisec)
|
||||
{
|
||||
struct itimerval tvalue;
|
||||
|
||||
tvalue.it_interval.tv_sec = 0;
|
||||
tvalue.it_interval.tv_usec = 0;
|
||||
tvalue.it_value.tv_sec = millisec/1000;
|
||||
tvalue.it_value.tv_usec = (millisec%1000)*1000;
|
||||
|
||||
if (realTime) {
|
||||
setitimer(ITIMER_REAL, &tvalue, nullptr);
|
||||
} else {
|
||||
setitimer(ITIMER_PROF, &tvalue, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
static long timerMilliSec = 50;
|
||||
|
||||
#if defined(linux)
|
||||
static int setupRTCSignals(int hz, struct sigaction *sap)
|
||||
{
|
||||
/* global */ rtcFD = open("/dev/rtc", O_RDONLY);
|
||||
if (rtcFD < 0) {
|
||||
perror("JPROF_RTC setup: open(\"/dev/rtc\", O_RDONLY)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (sigaction(SIGIO, sap, nullptr) == -1) {
|
||||
perror("JPROF_RTC setup: sigaction(SIGIO)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ioctl(rtcFD, RTC_IRQP_SET, hz) == -1) {
|
||||
perror("JPROF_RTC setup: ioctl(/dev/rtc, RTC_IRQP_SET, $JPROF_RTC_HZ)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ioctl(rtcFD, RTC_PIE_ON, 0) == -1) {
|
||||
perror("JPROF_RTC setup: ioctl(/dev/rtc, RTC_PIE_ON)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fcntl(rtcFD, F_SETSIG, 0) == -1) {
|
||||
perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETSIG, 0)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (fcntl(rtcFD, F_SETOWN, getpid()) == -1) {
|
||||
perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETOWN, getpid())");
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int enableRTCSignals(bool enable)
|
||||
{
|
||||
static bool enabled = false;
|
||||
if (enabled == enable) {
|
||||
return 0;
|
||||
}
|
||||
enabled = enable;
|
||||
|
||||
int flags = fcntl(rtcFD, F_GETFL);
|
||||
if (flags < 0) {
|
||||
perror("JPROF_RTC setup: fcntl(/dev/rtc, F_GETFL)");
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (enable) {
|
||||
flags |= FASYNC;
|
||||
} else {
|
||||
flags &= ~FASYNC;
|
||||
}
|
||||
|
||||
if (fcntl(rtcFD, F_SETFL, flags) == -1) {
|
||||
if (enable) {
|
||||
perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETFL, flags | FASYNC)");
|
||||
} else {
|
||||
perror("JPROF_RTC setup: fcntl(/dev/rtc, F_SETFL, flags & ~FASYNC)");
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
#endif
|
||||
|
||||
JPROF_STATIC void StackHook(
|
||||
int signum,
|
||||
siginfo_t *info,
|
||||
void *ucontext)
|
||||
{
|
||||
static struct timeval tFirst;
|
||||
static int first=1;
|
||||
size_t millisec = 0;
|
||||
|
||||
#if defined(linux)
|
||||
if (rtcHz && pthread_self() != main_thread) {
|
||||
// Only collect stack data on the main thread, for now.
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if(first && !(first=0)) {
|
||||
puts("Jprof: received first signal");
|
||||
#if defined(linux)
|
||||
if (rtcHz) {
|
||||
enableRTCSignals(true);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
gettimeofday(&tFirst, 0);
|
||||
millisec = 0;
|
||||
}
|
||||
} else {
|
||||
#if defined(linux)
|
||||
if (rtcHz) {
|
||||
enableRTCSignals(true);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
struct timeval tNow;
|
||||
gettimeofday(&tNow, 0);
|
||||
double usec = 1e6*(tNow.tv_sec - tFirst.tv_sec);
|
||||
usec += (tNow.tv_usec - tFirst.tv_usec);
|
||||
millisec = static_cast<size_t>(usec*1e-3);
|
||||
}
|
||||
}
|
||||
|
||||
gregset_t &gregs = ((ucontext_t*)ucontext)->uc_mcontext.gregs;
|
||||
#ifdef __x86_64__
|
||||
JprofLog(millisec, (void*)gregs[REG_RSP], (void*)gregs[REG_RIP]);
|
||||
#else
|
||||
JprofLog(millisec, (void*)gregs[REG_ESP], (void*)gregs[REG_EIP]);
|
||||
#endif
|
||||
|
||||
if (!rtcHz)
|
||||
startSignalCounter(timerMilliSec);
|
||||
}
|
||||
|
||||
NS_EXPORT_(void) setupProfilingStuff(void)
|
||||
{
|
||||
static int gFirstTime = 1;
|
||||
char filename[2048]; // XXX fix
|
||||
|
||||
if(gFirstTime && !(gFirstTime=0)) {
|
||||
int startTimer = 1;
|
||||
int doNotStart = 1;
|
||||
int firstDelay = 0;
|
||||
int append = O_TRUNC;
|
||||
char *tst = getenv("JPROF_FLAGS");
|
||||
|
||||
/* Options from JPROF_FLAGS environment variable:
|
||||
* JP_DEFER -> Wait for a SIGPROF (or SIGALRM, if JP_REALTIME
|
||||
* is set) from userland before starting
|
||||
* to generate them internally
|
||||
* JP_START -> Install the signal handler
|
||||
* JP_PERIOD -> Time between profiler ticks
|
||||
* JP_FIRST -> Extra delay before starting
|
||||
* JP_REALTIME -> Take stack traces in intervals of real time
|
||||
* rather than time used by the process (and the
|
||||
* system for the process). This is useful for
|
||||
* finding time spent by the X server.
|
||||
* JP_APPEND -> Append to jprof-log rather than overwriting it.
|
||||
* This is somewhat risky since it depends on the
|
||||
* address map staying constant across multiple runs.
|
||||
* JP_FILENAME -> base filename to use when saving logs. Note that
|
||||
* this does not affect the mapfile.
|
||||
* JP_CIRCULAR -> use a circular buffer of size N, write/clear on SIGUSR1
|
||||
*
|
||||
* JPROF_SLAVE is set if this is not the first process.
|
||||
*/
|
||||
|
||||
circular = false;
|
||||
|
||||
if(tst) {
|
||||
if(strstr(tst, "JP_DEFER"))
|
||||
{
|
||||
doNotStart = 0;
|
||||
startTimer = 0;
|
||||
}
|
||||
if(strstr(tst, "JP_START")) doNotStart = 0;
|
||||
if(strstr(tst, "JP_REALTIME")) realTime = 1;
|
||||
if(strstr(tst, "JP_APPEND")) append = O_APPEND;
|
||||
|
||||
char *delay = strstr(tst,"JP_PERIOD=");
|
||||
if(delay) {
|
||||
double tmp = strtod(delay+strlen("JP_PERIOD="), nullptr);
|
||||
if (tmp>=1e-3) {
|
||||
timerMilliSec = static_cast<unsigned long>(1000 * tmp);
|
||||
} else {
|
||||
fprintf(stderr,
|
||||
"JP_PERIOD of %g less than 0.001 (1ms), using 1ms\n",
|
||||
tmp);
|
||||
timerMilliSec = 1;
|
||||
}
|
||||
}
|
||||
|
||||
char *circular_op = strstr(tst,"JP_CIRCULAR=");
|
||||
if(circular_op) {
|
||||
size_t size = atol(circular_op+strlen("JP_CIRCULAR="));
|
||||
if (size < 1000) {
|
||||
fprintf(stderr,
|
||||
"JP_CIRCULAR of %lu less than 1000, using 10000\n",
|
||||
(unsigned long) size);
|
||||
size = 10000;
|
||||
}
|
||||
JprofBufferInit(size);
|
||||
fprintf(stderr,"JP_CIRCULAR buffer of %lu bytes\n", (unsigned long) size);
|
||||
circular = true;
|
||||
}
|
||||
|
||||
char *first = strstr(tst, "JP_FIRST=");
|
||||
if(first) {
|
||||
firstDelay = atol(first+strlen("JP_FIRST="));
|
||||
}
|
||||
|
||||
char *rtc = strstr(tst, "JP_RTC_HZ=");
|
||||
if (rtc) {
|
||||
#if defined(linux)
|
||||
rtcHz = atol(rtc+strlen("JP_RTC_HZ="));
|
||||
timerMilliSec = 0; /* This makes JP_FIRST work right. */
|
||||
realTime = 1; /* It's the _R_TC and all. ;) */
|
||||
|
||||
#define IS_POWER_OF_TWO(x) (((x) & ((x) - 1)) == 0)
|
||||
|
||||
if (!IS_POWER_OF_TWO(rtcHz) || rtcHz < 2) {
|
||||
fprintf(stderr, "JP_RTC_HZ must be power of two and >= 2, "
|
||||
"but %d was provided; using default of 2048\n",
|
||||
rtcHz);
|
||||
rtcHz = 2048;
|
||||
}
|
||||
#else
|
||||
fputs("JP_RTC_HZ found, but RTC profiling only supported on "
|
||||
"Linux!\n", stderr);
|
||||
|
||||
#endif
|
||||
}
|
||||
const char *f = strstr(tst,"JP_FILENAME=");
|
||||
if (f)
|
||||
f = f + strlen("JP_FILENAME=");
|
||||
else
|
||||
f = M_LOGFILE;
|
||||
|
||||
char *is_slave = getenv("JPROF_SLAVE");
|
||||
if (!is_slave)
|
||||
setenv("JPROF_SLAVE","", 0);
|
||||
gIsSlave = !!is_slave;
|
||||
|
||||
gFilenamePID = syscall(SYS_gettid); //gettid();
|
||||
if (is_slave)
|
||||
snprintf(filename,sizeof(filename),"%s-%d",f,gFilenamePID);
|
||||
else
|
||||
snprintf(filename,sizeof(filename),"%s",f);
|
||||
|
||||
// XXX FIX! inherit current capture state!
|
||||
}
|
||||
|
||||
if(!doNotStart) {
|
||||
|
||||
if(gLogFD<0) {
|
||||
gLogFD = open(filename, O_CREAT | O_WRONLY | append, 0666);
|
||||
if(gLogFD<0) {
|
||||
fprintf(stderr, "Unable to create " M_LOGFILE);
|
||||
perror(":");
|
||||
} else {
|
||||
struct sigaction action;
|
||||
sigset_t mset;
|
||||
|
||||
// Dump out the address map when we terminate
|
||||
RegisterJprofShutdown();
|
||||
|
||||
main_thread = pthread_self();
|
||||
//fprintf(stderr,"jprof: main_thread = %u\n",
|
||||
// (unsigned int)main_thread);
|
||||
|
||||
// FIX! probably should block these against each other
|
||||
// Very unlikely.
|
||||
sigemptyset(&mset);
|
||||
action.sa_handler = nullptr;
|
||||
action.sa_sigaction = StackHook;
|
||||
action.sa_mask = mset;
|
||||
action.sa_flags = SA_RESTART | SA_SIGINFO;
|
||||
#if defined(linux)
|
||||
if (rtcHz) {
|
||||
if (!setupRTCSignals(rtcHz, &action)) {
|
||||
fputs("jprof: Error initializing RTC, NOT "
|
||||
"profiling\n", stderr);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!rtcHz || firstDelay != 0)
|
||||
#endif
|
||||
{
|
||||
if (realTime) {
|
||||
sigaction(SIGALRM, &action, nullptr);
|
||||
}
|
||||
}
|
||||
// enable PROF in all cases to simplify JP_DEFER/pause/restart
|
||||
sigaction(SIGPROF, &action, nullptr);
|
||||
|
||||
// make it so a SIGUSR1 will stop the profiling
|
||||
// Note: It currently does not close the logfile.
|
||||
// This could be configurable (so that it could
|
||||
// later be reopened).
|
||||
|
||||
struct sigaction stop_action;
|
||||
stop_action.sa_handler = EndProfilingHook;
|
||||
stop_action.sa_mask = mset;
|
||||
stop_action.sa_flags = SA_RESTART;
|
||||
sigaction(SIGUSR1, &stop_action, nullptr);
|
||||
|
||||
// make it so a SIGUSR2 will clear the circular buffer
|
||||
|
||||
stop_action.sa_handler = ClearProfilingHook;
|
||||
stop_action.sa_mask = mset;
|
||||
stop_action.sa_flags = SA_RESTART;
|
||||
sigaction(SIGUSR2, &stop_action, nullptr);
|
||||
|
||||
printf("Jprof: Initialized signal handler and set "
|
||||
"timer for %lu %s, %d s "
|
||||
"initial delay\n",
|
||||
rtcHz ? rtcHz : timerMilliSec,
|
||||
rtcHz ? "Hz" : "ms",
|
||||
firstDelay);
|
||||
|
||||
if(startTimer) {
|
||||
#if defined(linux)
|
||||
/* If we have an initial delay we can just use
|
||||
startSignalCounter to set up a timer to fire the
|
||||
first stackHook after that delay. When that happens
|
||||
we'll go and switch to RTC profiling. */
|
||||
if (rtcHz && firstDelay == 0) {
|
||||
puts("Jprof: enabled RTC signals");
|
||||
enableRTCSignals(true);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
puts("Jprof: started timer");
|
||||
startSignalCounter(firstDelay*1000 + timerMilliSec);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
printf("setupProfilingStuff() called multiple times\n");
|
||||
}
|
||||
}
|
||||
45
tools/jprof/stub/libmalloc.h
Normal file
45
tools/jprof/stub/libmalloc.h
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/* 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/. */
|
||||
|
||||
#ifndef libmalloc_h___
|
||||
#define libmalloc_h___
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "config.h"
|
||||
|
||||
typedef unsigned long u_long;
|
||||
|
||||
// For me->flags
|
||||
#define JP_FIRST_AFTER_PAUSE 1
|
||||
|
||||
// Format of a jprof log entry. This is what's written out to the
|
||||
// "jprof-log" file.
|
||||
// It's called malloc_log_entry because the history of jprof is that
|
||||
// it's a modified version of tracemalloc.
|
||||
struct malloc_log_entry {
|
||||
u_long delTime;
|
||||
u_long numpcs;
|
||||
unsigned int flags;
|
||||
int thread;
|
||||
char* pcs[MAX_STACK_CRAWL];
|
||||
};
|
||||
|
||||
// Format of a malloc map entry; after this struct is nameLen+1 bytes of
|
||||
// name data.
|
||||
struct malloc_map_entry {
|
||||
u_long nameLen;
|
||||
u_long address; // base address
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
} /* end of extern "C" */
|
||||
#endif
|
||||
|
||||
#endif /* libmalloc_h___ */
|
||||
17
tools/jprof/stub/moz.build
Normal file
17
tools/jprof/stub/moz.build
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
EXPORTS += [
|
||||
'jprof.h',
|
||||
]
|
||||
|
||||
SOURCES += [
|
||||
'libmalloc.cpp',
|
||||
]
|
||||
|
||||
SharedLibrary('jprof')
|
||||
|
||||
DEFINES['_IMPL_JPROF_API'] = True
|
||||
302
tools/leak-gauge/leak-gauge.html
Normal file
302
tools/leak-gauge/leak-gauge.html
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
|
||||
"http://www.w3.org/TR/html4/strict.dtd">
|
||||
<!--
|
||||
vim:sw=4:ts=4:et:
|
||||
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/.
|
||||
-->
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Leak Gauge</title>
|
||||
|
||||
<style type="text/css">
|
||||
pre { margin: 0; }
|
||||
pre.output { border: medium solid; padding: 1em; margin: 1em; }
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
|
||||
function runfile(file) {
|
||||
var result = "Results of processing log " + file.fileName + " :\n";
|
||||
|
||||
var fileReader = new FileReader();
|
||||
fileReader.onload = function(e)
|
||||
{
|
||||
runContents(result, e.target.result);
|
||||
}
|
||||
fileReader.readAsText(file, "iso-8859-1");
|
||||
}
|
||||
|
||||
function runContents(result, contents) {
|
||||
// A hash of objects (keyed by the first word of the line in the log)
|
||||
// that have two public methods, handle_line and dump (to be called using
|
||||
// call, above), along with any private data they need.
|
||||
var handlers = {
|
||||
"DOMWINDOW": {
|
||||
count: 0,
|
||||
windows: {},
|
||||
handle_line: function(line) {
|
||||
var match = line.match(/^([0-9a-f]*) (\S*)(.*)/);
|
||||
if (match) {
|
||||
var addr = match[1];
|
||||
var verb = match[2];
|
||||
var rest = match[3];
|
||||
if (verb == "created") {
|
||||
var m = rest.match(/ outer=([0-9a-f]*)$/);
|
||||
if (!m)
|
||||
throw "outer expected";
|
||||
this.windows[addr] = { outer: m[1] };
|
||||
++this.count;
|
||||
} else if (verb == "destroyed") {
|
||||
delete this.windows[addr];
|
||||
} else if (verb == "SetNewDocument") {
|
||||
var m = rest.match(/^ (.*)$/);
|
||||
if (!m)
|
||||
throw "URI expected";
|
||||
this.windows[addr][m[1]] = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
dump: function() {
|
||||
for (var addr in this.windows) {
|
||||
var winobj = this.windows[addr];
|
||||
var outer = winobj.outer;
|
||||
delete winobj.outer;
|
||||
result += "Leaked " + (outer == "0" ? "outer" : "inner") +
|
||||
" window " + addr + " " +
|
||||
(outer == "0" ? "" : "(outer " + outer + ") ") +
|
||||
"at address " + addr + ".\n";
|
||||
for (var uri in winobj) {
|
||||
result += " ... with URI \"" + uri + "\".\n";
|
||||
}
|
||||
}
|
||||
},
|
||||
summary: function() {
|
||||
var len = 0;
|
||||
for (var w in this.windows)
|
||||
++len;
|
||||
result += 'Leaked ' + len + ' out of ' +
|
||||
this.count + " DOM Windows\n";
|
||||
}
|
||||
},
|
||||
"DOCUMENT": {
|
||||
count: 0,
|
||||
docs: {},
|
||||
handle_line: function(line) {
|
||||
var match = line.match(/^([0-9a-f]*) (\S*)(.*)/);
|
||||
if (match) {
|
||||
var addr = match[1];
|
||||
var verb = match[2];
|
||||
var rest = match[3];
|
||||
if (verb == "created") {
|
||||
this.docs[addr] = {};
|
||||
++this.count;
|
||||
} else if (verb == "destroyed") {
|
||||
delete this.docs[addr];
|
||||
} else if (verb == "ResetToURI" ||
|
||||
verb == "StartDocumentLoad") {
|
||||
var m = rest.match(/^ (.*)$/);
|
||||
if (!m)
|
||||
throw "URI expected";
|
||||
var uri = m[1];
|
||||
var doc_info = this.docs[addr];
|
||||
doc_info[uri] = true;
|
||||
if ("nim" in doc_info) {
|
||||
doc_info["nim"][uri] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
dump: function() {
|
||||
for (var addr in this.docs) {
|
||||
var doc = this.docs[addr];
|
||||
result += "Leaked document at address " + addr + ".\n";
|
||||
for (var uri in doc) {
|
||||
if (uri != "nim") {
|
||||
result += " ... with URI \"" + uri + "\".\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
summary: function() {
|
||||
var len = 0;
|
||||
for (var w in this.docs)
|
||||
++len;
|
||||
result += 'Leaked ' + len + ' out of ' +
|
||||
this.count + " documents\n";
|
||||
}
|
||||
},
|
||||
"DOCSHELL": {
|
||||
count: 0,
|
||||
shells: {},
|
||||
handle_line: function(line) {
|
||||
var match = line.match(/^([0-9a-f]*) (\S*)(.*)/);
|
||||
if (match) {
|
||||
var addr = match[1];
|
||||
var verb = match[2];
|
||||
var rest = match[3];
|
||||
if (verb == "created") {
|
||||
this.shells[addr] = {};
|
||||
++this.count;
|
||||
} else if (verb == "destroyed") {
|
||||
delete this.shells[addr];
|
||||
} else if (verb == "InternalLoad" ||
|
||||
verb == "SetCurrentURI") {
|
||||
var m = rest.match(/^ (.*)$/);
|
||||
if (!m)
|
||||
throw "URI expected";
|
||||
this.shells[addr][m[1]] = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
dump: function() {
|
||||
for (var addr in this.shells) {
|
||||
var doc = this.shells[addr];
|
||||
result += "Leaked docshell at address " + addr + ".\n";
|
||||
for (var uri in doc) {
|
||||
result += " ... which loaded URI \"" + uri + "\".\n";
|
||||
}
|
||||
}
|
||||
},
|
||||
summary: function() {
|
||||
var len = 0;
|
||||
for (var w in this.shells)
|
||||
++len;
|
||||
result += 'Leaked ' + len + ' out of ' +
|
||||
this.count + " docshells\n";
|
||||
}
|
||||
},
|
||||
"NODEINFOMANAGER": {
|
||||
count: 0,
|
||||
nims: {},
|
||||
handle_line: function(line) {
|
||||
var match = line.match(/^([0-9a-f]*) (\S*)(.*)/);
|
||||
if (match) {
|
||||
var addr = match[1];
|
||||
var verb = match[2];
|
||||
var rest = match[3];
|
||||
if (verb == "created") {
|
||||
this.nims[addr] = {};
|
||||
++this.count;
|
||||
} else if (verb == "destroyed") {
|
||||
delete this.nims[addr];
|
||||
} else if (verb == "Init") {
|
||||
var m = rest.match(/^ document=(.*)$/);
|
||||
if (!m)
|
||||
throw "document pointer expected";
|
||||
var nim_info = this.nims[addr];
|
||||
var doc = m[1];
|
||||
if (doc != "0") {
|
||||
var doc_info = handlers["DOCUMENT"].docs[doc];
|
||||
for (var uri in doc_info) {
|
||||
nim_info[uri] = true;
|
||||
}
|
||||
doc_info["nim"] = nim_info;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
dump: function() {
|
||||
for (var addr in this.nims) {
|
||||
var nim = this.nims[addr];
|
||||
result += "Leaked content nodes associated with node info manager at address " + addr + ".\n";
|
||||
for (var uri in nim) {
|
||||
result += " ... with document URI \"" + uri + "\".\n";
|
||||
}
|
||||
}
|
||||
},
|
||||
summary: function() {
|
||||
var len = 0;
|
||||
for (var w in this.nims)
|
||||
++len;
|
||||
result += 'Leaked content nodes in ' + len + ' out of ' +
|
||||
this.count + " documents\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var lines = contents.split(/[\r\n]+/);
|
||||
for (var j in lines) {
|
||||
var line = lines[j];
|
||||
// strip off initial "-", thread id, and thread pointer; separate
|
||||
// first word and rest
|
||||
var matches = line.match(/^\-?[0-9]*\[[0-9a-f]*\]: (\S*) (.*)$/);
|
||||
if (matches) {
|
||||
var handler = matches[1];
|
||||
var data = matches[2];
|
||||
if (typeof(handlers[handler]) != "undefined") {
|
||||
handlers[handler].handle_line(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (var handler in handlers)
|
||||
handlers[handler].dump();
|
||||
if (result.length)
|
||||
result += "\n";
|
||||
result += "Summary:\n";
|
||||
for (var handler in handlers)
|
||||
handlers[handler].summary();
|
||||
result += "\n";
|
||||
|
||||
var out = document.createElement("pre");
|
||||
out.className = "output";
|
||||
out.appendChild(document.createTextNode(result));
|
||||
document.body.appendChild(out);
|
||||
}
|
||||
|
||||
function run() {
|
||||
var input = document.getElementById("fileinput");
|
||||
var files = input.files;
|
||||
for (var i = 0; i < files.length; ++i)
|
||||
runfile(files[i]);
|
||||
// So the user can process the same filename again (after
|
||||
// overwriting the log), clear the value on the form input so we
|
||||
// will always get an onchange event.
|
||||
input.value = "";
|
||||
}
|
||||
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<h1>Leak Gauge</h1>
|
||||
|
||||
<pre>$Id: leak-gauge.html,v 1.8 2008/02/08 19:55:34 dbaron%dbaron.org Exp $</pre>
|
||||
|
||||
<p>This script is designed to help testers isolate and simplify testcases
|
||||
for many classes of leaks (those that involve large graphs of core
|
||||
data structures) in Mozilla-based browsers. It is designed to print
|
||||
information about what has leaked by processing a log taken while
|
||||
running the browser. Such a log can be taken over a long session of
|
||||
normal browsing and then the log can be processed to find sites that
|
||||
leak. Once a site is known to leak, the logging can then be repeated
|
||||
to figure out under what conditions the leak occurs.</p>
|
||||
|
||||
<p>The way to create this log is to set the environment variables:</p>
|
||||
<pre> MOZ_LOG=DOMLeak:5,DocumentLeak:5,nsDocShellLeak:5,NodeInfoManagerLeak:5
|
||||
MOZ_LOG_FILE=nspr.log <i>(or any other filename of your choice)</i></pre>
|
||||
<p>in your shell and then run the program.</p>
|
||||
<ul>
|
||||
<li>In a Windows command prompt, set environment variables with
|
||||
<pre> set VAR=value</pre></li>
|
||||
<li> In an sh-based shell such as bash, set environment variables with
|
||||
<pre> export VAR=value</pre></li>
|
||||
<li>In a csh-based shell such as tcsh, set environment variables with
|
||||
<pre> setenv VAR value</pre></li>
|
||||
</ul>
|
||||
|
||||
<p>Once you have this log from a complete run of the browser (you have
|
||||
to exit; otherwise it will look like everything leaked), you can load
|
||||
this page (be careful not to overwrite the log when starting the browser
|
||||
to load this page) and enter the filename of the log:</p>
|
||||
|
||||
<p><input type="file" id="fileinput" onchange="run()"></p>
|
||||
|
||||
<p>Then you'll see the output below, which will tell you which of
|
||||
certain core objects leaked and the URLs associated with those
|
||||
objects.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
239
tools/leak-gauge/leak-gauge.pl
Normal file
239
tools/leak-gauge/leak-gauge.pl
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
#!/usr/bin/perl -w
|
||||
# vim:sw=4:ts=4:et:
|
||||
# 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/.
|
||||
|
||||
# $Id: leak-gauge.pl,v 1.8 2008/02/08 19:55:03 dbaron%dbaron.org Exp $
|
||||
# This script is designed to help testers isolate and simplify testcases
|
||||
# for many classes of leaks (those that involve large graphs of core
|
||||
# data structures) in Mozilla-based browsers. It is designed to print
|
||||
# information about what has leaked by processing a log taken while
|
||||
# running the browser. Such a log can be taken over a long session of
|
||||
# normal browsing and then the log can be processed to find sites that
|
||||
# leak. Once a site is known to leak, the logging can then be repeated
|
||||
# to figure out under what conditions the leak occurs.
|
||||
#
|
||||
# The way to create this log is to set the environment variables:
|
||||
# MOZ_LOG=DOMLeak:5,DocumentLeak:5,nsDocShellLeak:5,NodeInfoManagerLeak:5
|
||||
# MOZ_LOG_FILE=nspr.log (or any other filename of your choice)
|
||||
# in your shell and then run the program.
|
||||
# * In a Windows command prompt, set environment variables with
|
||||
# set VAR=value
|
||||
# * In an sh-based shell such as bash, set environment variables with
|
||||
# export VAR=value
|
||||
# * In a csh-based shell such as tcsh, set environment variables with
|
||||
# setenv VAR value
|
||||
#
|
||||
# Then, after you have exited the browser, run this perl script over the
|
||||
# log. Either of the following commands should work:
|
||||
# perl ./path/to/leak-gauge.pl nspr.log
|
||||
# cat nspr.log | perl ./path/to/leak-gauge.pl
|
||||
# and it will tell you which of certain core objects leaked and the URLs
|
||||
# associated with those objects.
|
||||
|
||||
|
||||
# Nobody said I'm not allowed to write my own object system in perl. No
|
||||
# classes here. Just objects and methods.
|
||||
sub call {
|
||||
my $func = shift;
|
||||
my $obj = shift;
|
||||
my $funcref = ${$obj}{$func};
|
||||
&$funcref($obj, @_);
|
||||
}
|
||||
|
||||
# A hash of objects (keyed by the first word of the line in the log)
|
||||
# that have two public methods, handle_line and dump (to be called using
|
||||
# call, above), along with any private data they need.
|
||||
my $handlers = {
|
||||
"DOMWINDOW" => {
|
||||
count => 0,
|
||||
windows => {},
|
||||
handle_line => sub($$) {
|
||||
my ($self, $line) = @_;
|
||||
my $windows = ${$self}{windows};
|
||||
if ($line =~ /^([0-9a-f]*) (\S*)/) {
|
||||
my ($addr, $verb, $rest) = ($1, $2, $');
|
||||
if ($verb eq "created") {
|
||||
$rest =~ / outer=([0-9a-f]*)$/ || die "outer expected";
|
||||
my $outer = $1;
|
||||
${$windows}{$addr} = { outer => $1 };
|
||||
++${$self}{count};
|
||||
} elsif ($verb eq "destroyed") {
|
||||
delete ${$windows}{$addr};
|
||||
} elsif ($verb eq "SetNewDocument") {
|
||||
$rest =~ /^ (.*)$/ || die "URI expected";
|
||||
my $uri = ($1);
|
||||
${${$windows}{$addr}}{$uri} = 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
dump => sub ($) {
|
||||
my ($self) = @_;
|
||||
my $windows = ${$self}{windows};
|
||||
foreach my $addr (keys(%{$windows})) {
|
||||
my $winobj = ${$windows}{$addr};
|
||||
my $outer = delete ${$winobj}{outer};
|
||||
print "Leaked " . ($outer eq "0" ? "outer" : "inner") .
|
||||
" window $addr " .
|
||||
($outer eq "0" ? "" : "(outer $outer) ") .
|
||||
"at address $addr.\n";
|
||||
foreach my $uri (keys(%{$winobj})) {
|
||||
print " ... with URI \"$uri\".\n";
|
||||
}
|
||||
}
|
||||
},
|
||||
summary => sub($) {
|
||||
my ($self) = @_;
|
||||
my $windows = ${$self}{windows};
|
||||
print 'Leaked ' . keys(%{$windows}) . ' out of ' .
|
||||
${$self}{count} . " DOM Windows\n";
|
||||
}
|
||||
},
|
||||
"DOCUMENT" => {
|
||||
count => 0,
|
||||
docs => {},
|
||||
handle_line => sub($$) {
|
||||
my ($self, $line) = @_;
|
||||
# This doesn't work; I don't have time to figure out why not.
|
||||
# my $docs = ${$self}{docs};
|
||||
my $docs = ${$handlers}{"DOCUMENT"}{docs};
|
||||
if ($line =~ /^([0-9a-f]*) (\S*)/) {
|
||||
my ($addr, $verb, $rest) = ($1, $2, $');
|
||||
if ($verb eq "created") {
|
||||
${$docs}{$addr} = {};
|
||||
++${$self}{count};
|
||||
} elsif ($verb eq "destroyed") {
|
||||
delete ${$docs}{$addr};
|
||||
} elsif ($verb eq "ResetToURI" ||
|
||||
$verb eq "StartDocumentLoad") {
|
||||
$rest =~ /^ (.*)$/ || die "URI expected";
|
||||
my $uri = $1;
|
||||
my $doc_info = ${$docs}{$addr};
|
||||
${$doc_info}{$uri} = 1;
|
||||
if (exists(${$doc_info}{"nim"})) {
|
||||
${$doc_info}{"nim"}{$uri} = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
dump => sub ($) {
|
||||
my ($self) = @_;
|
||||
my $docs = ${$self}{docs};
|
||||
foreach my $addr (keys(%{$docs})) {
|
||||
print "Leaked document at address $addr.\n";
|
||||
foreach my $uri (keys(%{${$docs}{$addr}})) {
|
||||
print " ... with URI \"$uri\".\n" unless $uri eq "nim";
|
||||
}
|
||||
}
|
||||
},
|
||||
summary => sub($) {
|
||||
my ($self) = @_;
|
||||
my $docs = ${$self}{docs};
|
||||
print 'Leaked ' . keys(%{$docs}) . ' out of ' .
|
||||
${$self}{count} . " documents\n";
|
||||
}
|
||||
},
|
||||
"DOCSHELL" => {
|
||||
count => 0,
|
||||
shells => {},
|
||||
handle_line => sub($$) {
|
||||
my ($self, $line) = @_;
|
||||
my $shells = ${$self}{shells};
|
||||
if ($line =~ /^([0-9a-f]*) (\S*)/) {
|
||||
my ($addr, $verb, $rest) = ($1, $2, $');
|
||||
if ($verb eq "created") {
|
||||
${$shells}{$addr} = {};
|
||||
++${$self}{count};
|
||||
} elsif ($verb eq "destroyed") {
|
||||
delete ${$shells}{$addr};
|
||||
} elsif ($verb eq "InternalLoad" ||
|
||||
$verb eq "SetCurrentURI") {
|
||||
$rest =~ /^ (.*)$/ || die "URI expected";
|
||||
my $uri = $1;
|
||||
${${$shells}{$addr}}{$uri} = 1;
|
||||
}
|
||||
}
|
||||
},
|
||||
dump => sub ($) {
|
||||
my ($self) = @_;
|
||||
my $shells = ${$self}{shells};
|
||||
foreach my $addr (keys(%{$shells})) {
|
||||
print "Leaked docshell at address $addr.\n";
|
||||
foreach my $uri (keys(%{${$shells}{$addr}})) {
|
||||
print " ... which loaded URI \"$uri\".\n";
|
||||
}
|
||||
}
|
||||
},
|
||||
summary => sub($) {
|
||||
my ($self) = @_;
|
||||
my $shells = ${$self}{shells};
|
||||
print 'Leaked ' . keys(%{$shells}) . ' out of ' .
|
||||
${$self}{count} . " docshells\n";
|
||||
}
|
||||
},
|
||||
"NODEINFOMANAGER" => {
|
||||
count => 0,
|
||||
nims => {},
|
||||
handle_line => sub($$) {
|
||||
my ($self, $line) = @_;
|
||||
my $nims = ${$self}{nims};
|
||||
if ($line =~ /^([0-9a-f]*) (\S*)/) {
|
||||
my ($addr, $verb, $rest) = ($1, $2, $');
|
||||
if ($verb eq "created") {
|
||||
${$nims}{$addr} = {};
|
||||
++${$self}{count};
|
||||
} elsif ($verb eq "destroyed") {
|
||||
delete ${$nims}{$addr};
|
||||
} elsif ($verb eq "Init") {
|
||||
$rest =~ /^ document=(.*)$/ ||
|
||||
die "document pointer expected";
|
||||
my $doc = $1;
|
||||
if ($doc ne "0") {
|
||||
my $nim_info = ${$nims}{$addr};
|
||||
my $doc_info = ${$handlers}{"DOCUMENT"}{docs}{$doc};
|
||||
foreach my $uri (keys(%{$doc_info})) {
|
||||
${$nim_info}{$uri} = 1;
|
||||
}
|
||||
${$doc_info}{"nim"} = $nim_info;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
dump => sub ($) {
|
||||
my ($self) = @_;
|
||||
my $nims = ${$self}{nims};
|
||||
foreach my $addr (keys(%{$nims})) {
|
||||
print "Leaked content nodes associated with node info manager at address $addr.\n";
|
||||
foreach my $uri (keys(%{${$nims}{$addr}})) {
|
||||
print " ... with document URI \"$uri\".\n";
|
||||
}
|
||||
}
|
||||
},
|
||||
summary => sub($) {
|
||||
my ($self) = @_;
|
||||
my $nims = ${$self}{nims};
|
||||
print 'Leaked content nodes within ' . keys(%{$nims}) . ' out of ' .
|
||||
${$self}{count} . " documents\n";
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
while (<>) {
|
||||
# strip off initial "-", thread id, and thread pointer; separate
|
||||
# first word and rest
|
||||
if (/^\-?[0-9]*\[[0-9a-f]*\]: (\S*) ([^\n\r]*)[\n\r]*$/) {
|
||||
my ($handler, $data) = ($1, $2);
|
||||
if (defined(${$handlers}{$handler})) {
|
||||
call("handle_line", ${$handlers}{$handler}, $data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach my $key (keys(%{$handlers})) {
|
||||
call("dump", ${$handlers}{$key});
|
||||
}
|
||||
print "Summary:\n";
|
||||
foreach my $key (keys(%{$handlers})) {
|
||||
call("summary", ${$handlers}{$key});
|
||||
}
|
||||
192
tools/lint/docs/Makefile
Normal file
192
tools/lint/docs/Makefile
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# User-friendly check for sphinx-build
|
||||
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
|
||||
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
|
||||
endif
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " applehelp to make an Apple Help Book"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/mozlint.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/mozlint.qhc"
|
||||
|
||||
applehelp:
|
||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||
@echo
|
||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
||||
"~/Library/Documentation/Help or install it in your application" \
|
||||
"bundle."
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/mozlint"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/mozlint"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
coverage:
|
||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||
@echo "Testing of coverage in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/coverage/python.txt."
|
||||
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
112
tools/lint/docs/conf.py
Normal file
112
tools/lint/docs/conf.py
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# mozlint documentation build configuration file, created by
|
||||
# sphinx-quickstart on Fri Nov 27 17:38:49 2015.
|
||||
#
|
||||
# This file is execfile()d with the current directory set to its
|
||||
# containing dir.
|
||||
|
||||
import os
|
||||
|
||||
# -- General configuration ------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'sphinx.ext.intersphinx',
|
||||
]
|
||||
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
# source_suffix = ['.rst', '.md']
|
||||
source_suffix = '.rst'
|
||||
|
||||
# The master toctree document.
|
||||
master_doc = 'index'
|
||||
|
||||
# General information about the project.
|
||||
project = u'mozlint'
|
||||
copyright = u'2015, Andrew Halberstadt'
|
||||
author = u'Andrew Halberstadt'
|
||||
|
||||
# The short X.Y version.
|
||||
version = '0.1.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = '0.1.0'
|
||||
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
language = None
|
||||
|
||||
# List of patterns, relative to source directory, that match files and
|
||||
# directories to ignore when looking for source files.
|
||||
exclude_patterns = ['_build']
|
||||
|
||||
# The name of the Pygments (syntax highlighting) style to use.
|
||||
pygments_style = 'sphinx'
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = False
|
||||
|
||||
|
||||
# -- Options for HTML output ----------------------------------------------
|
||||
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# a list of builtin themes.
|
||||
html_theme = 'default'
|
||||
|
||||
if os.environ.get('READTHEDOCS', None) != 'True':
|
||||
try:
|
||||
import sphinx_rtd_theme
|
||||
html_theme = 'sphinx_rtd_theme'
|
||||
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
# Add any paths that contain custom static files (such as style sheets) here,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||
html_static_path = ['_static']
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'mozlintdoc'
|
||||
|
||||
# -- Options for LaTeX output ---------------------------------------------
|
||||
|
||||
latex_elements = {}
|
||||
|
||||
# Grouping the document tree into LaTeX files. List of tuples
|
||||
# (source start file, target name, title,
|
||||
# author, documentclass [howto, manual, or own class]).
|
||||
latex_documents = [
|
||||
(master_doc, 'mozlint.tex', u'mozlint Documentation',
|
||||
u'Andrew Halberstadt', 'manual'),
|
||||
]
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'mozlint', u'mozlint Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'mozlint', u'mozlint Documentation',
|
||||
author, 'mozlint', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Example configuration for intersphinx: refer to the Python standard library.
|
||||
intersphinx_mapping = {'https://docs.python.org/': None}
|
||||
153
tools/lint/docs/create.rst
Normal file
153
tools/lint/docs/create.rst
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
Adding a New Linter to the Tree
|
||||
===============================
|
||||
|
||||
A linter is a python file with a ``.lint`` extension and a global dict called LINTER. Depending on how
|
||||
complex it is, there may or may not be any actual python code alongside the LINTER definition.
|
||||
|
||||
Here's a trivial example:
|
||||
|
||||
no-eval.lint
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
LINTER = {
|
||||
'name': 'EvalLinter',
|
||||
'description': "Ensures the string 'eval' doesn't show up."
|
||||
'include': "**/*.js",
|
||||
'type': 'string',
|
||||
'payload': 'eval',
|
||||
}
|
||||
|
||||
Now ``no-eval.lint`` gets passed into :func:`LintRoller.read`.
|
||||
|
||||
|
||||
Linter Types
|
||||
------------
|
||||
|
||||
There are three types of linters, though more may be added in the future.
|
||||
|
||||
1. string - fails if substring is found
|
||||
2. regex - fails if regex matches
|
||||
3. external - fails if a python function returns a non-empty result list
|
||||
4. structured_log - fails if a mozlog logger emits any lint_error or lint_warning log messages
|
||||
|
||||
As seen from the example above, string and regex linters are very easy to create, but they
|
||||
should be avoided if possible. It is much better to use a context aware linter for the language you
|
||||
are trying to lint. For example, use eslint to lint JavaScript files, use flake8 to lint python
|
||||
files, etc.
|
||||
|
||||
Which brings us to the third and most interesting type of linter,
|
||||
external. External linters call an arbitrary python function which is
|
||||
responsible for not only running the linter, but ensuring the results
|
||||
are structured properly. For example, an external type could shell out
|
||||
to a 3rd party linter, collect the output and format it into a list of
|
||||
:class:`ResultContainer` objects. The signature for this python
|
||||
function is ``lint(files, **kwargs)``, where ``files`` is a list of
|
||||
files to lint.
|
||||
|
||||
Structured log linters are much like external linters, but suitable
|
||||
for cases where the linter code is using mozlog and emits
|
||||
``lint_error`` or ``lint_warning`` logging messages when the lint
|
||||
fails. This is recommended for writing novel gecko-specific lints. In
|
||||
this case the signature for lint functions is ``lint(files, logger,
|
||||
**kwargs)``.
|
||||
|
||||
LINTER Definition
|
||||
-----------------
|
||||
|
||||
Each ``.lint`` file must have a variable called LINTER which is a dict containing metadata about the
|
||||
linter. Here are the supported keys:
|
||||
|
||||
* name - The name of the linter (required)
|
||||
* description - A brief description of the linter's purpose (required)
|
||||
* type - One of 'string', 'regex' or 'external' (required)
|
||||
* payload - The actual linting logic, depends on the type (required)
|
||||
* include - A list of glob patterns that must be matched (optional)
|
||||
* exclude - A list of glob patterns that must not be matched (optional)
|
||||
* extensions - A list of file extensions to be considered (optional)
|
||||
* setup - A function that sets up external dependencies (optional)
|
||||
|
||||
In addition to the above, some ``.lint`` files correspond to a single lint rule. For these, the
|
||||
following additional keys may be specified:
|
||||
|
||||
* message - A string to print on infraction (optional)
|
||||
* hint - A string with a clue on how to fix the infraction (optional)
|
||||
* rule - An id string for the lint rule (optional)
|
||||
* level - The severity of the infraction, either 'error' or 'warning' (optional)
|
||||
|
||||
For structured_log lints the following additional keys apply:
|
||||
|
||||
* logger - A StructuredLog object to use for logging. If not supplied
|
||||
one will be created (optional)
|
||||
|
||||
Example
|
||||
-------
|
||||
|
||||
Here is an example of an external linter that shells out to the python flake8 linter:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from collections import defaultdict
|
||||
|
||||
from mozlint import result
|
||||
|
||||
|
||||
FLAKE8_NOT_FOUND = """
|
||||
Could not find flake8! Install flake8 and try again.
|
||||
""".strip()
|
||||
|
||||
|
||||
def lint(files, **lintargs):
|
||||
import which
|
||||
|
||||
binary = os.environ.get('FLAKE8')
|
||||
if not binary:
|
||||
try:
|
||||
binary = which.which('flake8')
|
||||
except which.WhichError:
|
||||
print(FLAKE8_NOT_FOUND)
|
||||
return 1
|
||||
|
||||
# Flake8 allows passing in a custom format string. We use
|
||||
# this to help mold the default flake8 format into what
|
||||
# mozlint's ResultContainer object expects.
|
||||
cmdargs = [
|
||||
binary,
|
||||
'--format',
|
||||
'{"path":"%(path)s","lineno":%(row)s,"column":%(col)s,"rule":"%(code)s","message":"%(text)s"}',
|
||||
] + files
|
||||
|
||||
proc = subprocess.Popen(cmdargs, stdout=subprocess.PIPE, env=os.environ)
|
||||
output = proc.communicate()[0]
|
||||
|
||||
# all passed
|
||||
if not output:
|
||||
return []
|
||||
|
||||
results = []
|
||||
for line in output.splitlines():
|
||||
# res is a dict of the form specified by --format above
|
||||
res = json.loads(line)
|
||||
|
||||
# parse level out of the id string
|
||||
if 'code' in res and res['code'].startswith('W'):
|
||||
res['level'] = 'warning'
|
||||
|
||||
# result.from_linter is a convenience method that
|
||||
# creates a ResultContainer using a LINTER definition
|
||||
# to populate some defaults.
|
||||
results.append(result.from_linter(LINTER, **res))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
LINTER = {
|
||||
'name': "flake8",
|
||||
'description': "Python linter",
|
||||
'include': ['**/*.py'],
|
||||
'type': 'external',
|
||||
'payload': lint,
|
||||
}
|
||||
37
tools/lint/docs/index.rst
Normal file
37
tools/lint/docs/index.rst
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
Linting
|
||||
=======
|
||||
|
||||
Linters are used in mozilla-central to help enforce coding style and avoid bad practices. Due to the
|
||||
wide variety of languages in use and the varying style preferences per team, this is not an easy
|
||||
task. In addition, linters should be runnable from editors, from the command line, from review tools
|
||||
and from continuous integration. It's easy to see how the complexity of running all of these
|
||||
different kinds of linters in all of these different places could quickly balloon out of control.
|
||||
|
||||
``Mozlint`` is a library that accomplishes two goals:
|
||||
|
||||
1. It provides a standard method for adding new linters to the tree, which can be as easy as
|
||||
defining a json object in a ``.lint`` file. This helps keep lint related code localized, and
|
||||
prevents different teams from coming up with their own unique lint implementations.
|
||||
2. It provides a streamlined interface for running all linters at once. Instead of running N
|
||||
different lint commands to test your patch, a single ``mach lint`` command will automatically run
|
||||
all applicable linters. This means there is a single API surface that other tools can use to
|
||||
invoke linters.
|
||||
|
||||
``Mozlint`` isn't designed to be used directly by end users. Instead, it can be consumed by things
|
||||
like mach, mozreview and taskcluster.
|
||||
|
||||
.. toctree::
|
||||
:caption: Linting User Guide
|
||||
:maxdepth: 2
|
||||
|
||||
usage
|
||||
create
|
||||
linters/eslint
|
||||
linters/flake8
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
174
tools/lint/docs/linters/eslint-plugin-mozilla.rst
Normal file
174
tools/lint/docs/linters/eslint-plugin-mozilla.rst
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
=====================
|
||||
Mozilla ESLint Plugin
|
||||
=====================
|
||||
|
||||
|
||||
balanced-listeners
|
||||
------------------
|
||||
|
||||
Checks that for every occurence of 'addEventListener' or 'on' there is an
|
||||
occurence of 'removeEventListener' or 'off' with the same event name.
|
||||
|
||||
|
||||
components-imports
|
||||
------------------
|
||||
|
||||
Checks the filename of imported files e.g. ``Cu.import("some/path/Blah.jsm")``
|
||||
adds Blah to the global scope.
|
||||
|
||||
|
||||
import-browserjs-globals
|
||||
------------------------
|
||||
|
||||
When included files from the main browser UI scripts will be loaded and any
|
||||
declared globals will be defined for the current file. This is mostly useful for
|
||||
browser-chrome mochitests that call browser functions.
|
||||
|
||||
|
||||
import-globals-from
|
||||
-------------------
|
||||
|
||||
Parses a file for globals defined in various unique Mozilla ways.
|
||||
|
||||
When a "import-globals-from <path>" comment is found in a file, then all globals
|
||||
from the file at <path> will be imported in the current scope. This will also
|
||||
operate recursively.
|
||||
|
||||
This is useful for scripts that are loaded as <script> tag in a window and rely
|
||||
on each other's globals.
|
||||
|
||||
If <path> is a relative path, then it must be relative to the file being
|
||||
checked by the rule.
|
||||
|
||||
|
||||
import-headjs-globals
|
||||
---------------------
|
||||
|
||||
Import globals from head.js and from any files that were imported by
|
||||
head.js (as far as we can correctly resolve the path).
|
||||
|
||||
The following file import patterns are supported:
|
||||
|
||||
- ``Services.scriptloader.loadSubScript(path)``
|
||||
- ``loader.loadSubScript(path)``
|
||||
- ``loadSubScript(path)``
|
||||
- ``loadHelperScript(path)``
|
||||
- ``import-globals-from path``
|
||||
|
||||
If path does not exist because it is generated e.g.
|
||||
``testdir + "/somefile.js"`` we do our best to resolve it.
|
||||
|
||||
The following patterns are supported:
|
||||
|
||||
- ``Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");``
|
||||
- ``loader.lazyImporter(this, "name1");``
|
||||
- ``loader.lazyRequireGetter(this, "name2"``
|
||||
- ``loader.lazyServiceGetter(this, "name3"``
|
||||
- ``XPCOMUtils.defineLazyModuleGetter(this, "setNamedTimeout", ...)``
|
||||
- ``loader.lazyGetter(this, "toolboxStrings"``
|
||||
- ``XPCOMUtils.defineLazyGetter(this, "clipboardHelper"``
|
||||
|
||||
|
||||
mark-test-function-used
|
||||
-----------------------
|
||||
|
||||
Simply marks `test` (the test method) or `run_test` as used when in mochitests
|
||||
or xpcshell tests respectively. This avoids ESLint telling us that the function
|
||||
is never called.
|
||||
|
||||
|
||||
no-aArgs
|
||||
--------
|
||||
|
||||
Checks that function argument names don't start with lowercase 'a' followed by
|
||||
a capital letter. This is to prevent the use of Hungarian notation whereby the
|
||||
first letter is a prefix that indicates the type or intended use of a variable.
|
||||
|
||||
|
||||
no-cpows-in-tests
|
||||
-----------------
|
||||
|
||||
This rule checks if the file is a browser mochitest and, if so, checks for
|
||||
possible CPOW usage by checking for the following strings:
|
||||
|
||||
- "gBrowser.contentWindow"
|
||||
- "gBrowser.contentDocument"
|
||||
- "gBrowser.selectedBrowser.contentWindow"
|
||||
- "browser.contentDocument"
|
||||
- "window.content"
|
||||
- "content"
|
||||
- "content."
|
||||
|
||||
Note: These are string matches so we will miss situations where the parent
|
||||
object is assigned to another variable e.g.::
|
||||
|
||||
var b = gBrowser;
|
||||
b.content // Would not be detected as a CPOW.
|
||||
|
||||
|
||||
no-single-arg-cu-import
|
||||
-----------------------
|
||||
|
||||
Rejects calls to "Cu.import" that do not supply a second argument (meaning they
|
||||
add the exported properties into global scope).
|
||||
|
||||
|
||||
reject-importGlobalProperties
|
||||
-----------------------------
|
||||
|
||||
Rejects calls to ``Cu.importGlobalProperties``. Use of this function is
|
||||
undesirable in some parts of the tree.
|
||||
|
||||
|
||||
reject-some-requires
|
||||
--------------------
|
||||
|
||||
This takes an option, a regular expression. Invocations of
|
||||
``require`` with a string literal argument are matched against this
|
||||
regexp; and if it matches, the ``require`` use is flagged.
|
||||
|
||||
|
||||
this-top-level-scope
|
||||
--------------------
|
||||
|
||||
Treats top-level assignments like ``this.mumble = value`` as declaring a global.
|
||||
|
||||
Note: These are string matches so we will miss situations where the parent
|
||||
object is assigned to another variable e.g.::
|
||||
|
||||
var b = gBrowser;
|
||||
b.content // Would not be detected as a CPOW.
|
||||
|
||||
|
||||
var-only-at-top-level
|
||||
---------------------
|
||||
|
||||
Marks all var declarations that are not at the top level invalid.
|
||||
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
+-------+-----------------------+
|
||||
| Possible values for all rules |
|
||||
+-------+-----------------------+
|
||||
| Value | Meaning |
|
||||
+-------+-----------------------+
|
||||
| 0 | Deactivated |
|
||||
+-------+-----------------------+
|
||||
| 1 | Warning |
|
||||
+-------+-----------------------+
|
||||
| 2 | Error |
|
||||
+-------+-----------------------+
|
||||
|
||||
Example configuration::
|
||||
|
||||
"rules": {
|
||||
"mozilla/balanced-listeners": 2,
|
||||
"mozilla/components-imports": 1,
|
||||
"mozilla/import-globals-from": 1,
|
||||
"mozilla/import-headjs-globals": 1,
|
||||
"mozilla/mark-test-function-used": 1,
|
||||
"mozilla/var-only-at-top-level": 1,
|
||||
"mozilla/no-cpows-in-tests": 1,
|
||||
}
|
||||
45
tools/lint/docs/linters/eslint.rst
Normal file
45
tools/lint/docs/linters/eslint.rst
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
ESLint
|
||||
======
|
||||
|
||||
`ESLint`_ is a popular linter for JavaScript.
|
||||
|
||||
Run Locally
|
||||
-----------
|
||||
|
||||
The mozlint integration of `ESLint`_ can be run using mach:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
$ mach lint --linter eslint <file paths>
|
||||
|
||||
Alternatively, omit the ``--linter eslint`` and run all configured linters, which will include
|
||||
ESLint.
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
The `ESLint`_ mozilla-central integration uses a blacklist to exclude certain directories from being
|
||||
linted. This lives in ``topsrcdir/.eslintignore``. If you don't wish your directory to be linted, it
|
||||
must be added here.
|
||||
|
||||
The global configuration file lives in ``topsrcdir/.eslintrc``. This global configuration can be
|
||||
overridden by including an ``.eslintrc`` in the appropriate subdirectory. For an overview of the
|
||||
supported configuration, see `ESLint's documentation`_.
|
||||
|
||||
|
||||
ESLint Plugin Mozilla
|
||||
---------------------
|
||||
|
||||
In addition to default ESLint rules, there are several Mozilla-specific rules that are defined in
|
||||
the :doc:`Mozilla ESLint Plugin <eslint-plugin-mozilla>`.
|
||||
|
||||
|
||||
.. _ESLint: http://eslint.org/
|
||||
.. _ESLint's documentation: http://eslint.org/docs/user-guide/configuring
|
||||
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
eslint-plugin-mozilla
|
||||
50
tools/lint/docs/linters/flake8.rst
Normal file
50
tools/lint/docs/linters/flake8.rst
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
Flake8
|
||||
======
|
||||
|
||||
`Flake8`_ is a popular lint wrapper for python. Under the hood, it runs three other tools and
|
||||
combines their results:
|
||||
|
||||
* `pep8`_ for checking style
|
||||
* `pyflakes`_ for checking syntax
|
||||
* `mccabe`_ for checking complexity
|
||||
|
||||
|
||||
Run Locally
|
||||
-----------
|
||||
|
||||
The mozlint integration of flake8 can be run using mach:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
$ mach lint --linter flake8 <file paths>
|
||||
|
||||
Alternatively, omit the ``--linter flake8`` and run all configured linters, which will include
|
||||
flake8.
|
||||
|
||||
|
||||
Configuration
|
||||
-------------
|
||||
|
||||
Only directories explicitly whitelisted will have flake8 run against them. To enable flake8 linting
|
||||
in a source directory, it must be added to the ``include`` directive in ```tools/lint/flake8.lint``.
|
||||
If you wish to exclude a subdirectory of an included one, you can add it to the ``exclude``
|
||||
directive.
|
||||
|
||||
The default configuration file lives in ``topsrcdir/.flake8``. The default configuration can be
|
||||
overriden for a given subdirectory by creating a new ``.flake8`` file in the subdirectory. Be warned
|
||||
that ``.flake8`` files cannot inherit from one another, so all configuration you wish to keep must
|
||||
be re-defined.
|
||||
|
||||
.. warning::
|
||||
|
||||
Only ``.flake8`` files that live in a directory that is explicitly included in the ``include``
|
||||
directive will be considered. See `bug 1277851`_ for more details.
|
||||
|
||||
For an overview of the supported configuration, see `flake8's documentation`_.
|
||||
|
||||
.. _Flake8: https://flake8.readthedocs.io/en/latest/
|
||||
.. _pep8: http://pep8.readthedocs.io/en/latest/
|
||||
.. _pyflakes: https://github.com/pyflakes/pyflakes
|
||||
.. _mccabe: https://github.com/pycqa/mccabe
|
||||
.. _bug 1277851: https://bugzilla.mozilla.org/show_bug.cgi?id=1277851
|
||||
.. _flake8's documentation: https://flake8.readthedocs.io/en/latest/config.html
|
||||
263
tools/lint/docs/make.bat
Normal file
263
tools/lint/docs/make.bat
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. xml to make Docutils-native XML files
|
||||
echo. pseudoxml to make pseudoxml-XML files for display purposes
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
echo. coverage to run coverage check of the documentation if enabled
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
|
||||
REM Check if sphinx-build is available and fallback to Python version if any
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 goto sphinx_python
|
||||
goto sphinx_ok
|
||||
|
||||
:sphinx_python
|
||||
|
||||
set SPHINXBUILD=python -m sphinx.__init__
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:sphinx_ok
|
||||
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\mozlint.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\mozlint.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdf" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf
|
||||
cd %~dp0
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdfja" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf-ja
|
||||
cd %~dp0
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "coverage" (
|
||||
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of coverage in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/coverage/python.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "xml" (
|
||||
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The XML files are in %BUILDDIR%/xml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pseudoxml" (
|
||||
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
||||
41
tools/lint/docs/usage.rst
Normal file
41
tools/lint/docs/usage.rst
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
Running Linters Locally
|
||||
=======================
|
||||
|
||||
You can run all the various linters in the tree using the ``mach lint`` command. Simply pass in the
|
||||
directory or file you wish to lint (defaults to current working directory):
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
./mach lint path/to/files
|
||||
|
||||
Multiple paths are allowed:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
./mach lint path/to/foo.js path/to/bar.py path/to/dir
|
||||
|
||||
``Mozlint`` will automatically determine which types of files exist, and which linters need to be run
|
||||
against them. For example, if the directory contains both JavaScript and Python files then mozlint
|
||||
will automatically run both ESLint and Flake8 against those files respectively.
|
||||
|
||||
To restrict which linters are invoked manually, pass in ``-l/--linter``:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
./mach lint -l eslint path/to/files
|
||||
|
||||
Finally, ``mozlint`` can lint the files touched by a set of revisions or the working directory using
|
||||
the ``-r/--rev`` and ``-w/--workdir`` arguments respectively. These work both with mercurial and
|
||||
git. In the case of ``--rev`` the value is passed directly to the underlying vcs, so normal revision
|
||||
specifiers will work. For example, say we want to lint all files touched by the last three commits.
|
||||
In mercurial, this would be:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
./mach lint -r ".~2::."
|
||||
|
||||
In git, this would be:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
./mach lint -r "HEAD~2 HEAD"
|
||||
368
tools/lint/eslint.lint
Normal file
368
tools/lint/eslint.lint
Normal file
|
|
@ -0,0 +1,368 @@
|
|||
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
import json
|
||||
import os
|
||||
import platform
|
||||
import re
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
from distutils.version import LooseVersion
|
||||
|
||||
import which
|
||||
from mozprocess import ProcessHandler
|
||||
|
||||
from mozlint import result
|
||||
|
||||
ESLINT_ERROR_MESSAGE = """
|
||||
An error occurred running eslint. Please check the following error messages:
|
||||
|
||||
{}
|
||||
""".strip()
|
||||
|
||||
ESLINT_NOT_FOUND_MESSAGE = """
|
||||
Could not find eslint! We looked at the --binary option, at the ESLINT
|
||||
environment variable, and then at your local node_modules path. Please Install
|
||||
eslint and needed plugins with:
|
||||
|
||||
mach eslint --setup
|
||||
|
||||
and try again.
|
||||
""".strip()
|
||||
|
||||
NODE_NOT_FOUND_MESSAGE = """
|
||||
nodejs v4.2.3 is either not installed or is installed to a non-standard path.
|
||||
Please install nodejs from https://nodejs.org and try again.
|
||||
|
||||
Valid installation paths:
|
||||
""".strip()
|
||||
|
||||
NPM_NOT_FOUND_MESSAGE = """
|
||||
Node Package Manager (npm) is either not installed or installed to a
|
||||
non-standard path. Please install npm from https://nodejs.org (it comes as an
|
||||
option in the node installation) and try again.
|
||||
|
||||
Valid installation paths:
|
||||
""".strip()
|
||||
|
||||
|
||||
VERSION_RE = re.compile(r"^\d+\.\d+\.\d+$")
|
||||
CARET_VERSION_RANGE_RE = re.compile(r"^\^((\d+)\.\d+\.\d+)$")
|
||||
|
||||
EXTENSIONS = ['.js', '.jsm', '.jsx', '.xml', '.html', '.xhtml']
|
||||
|
||||
project_root = None
|
||||
|
||||
|
||||
def eslint_setup():
|
||||
"""Ensure eslint is optimally configured.
|
||||
|
||||
This command will inspect your eslint configuration and
|
||||
guide you through an interactive wizard helping you configure
|
||||
eslint for optimal use on Mozilla projects.
|
||||
"""
|
||||
orig_cwd = os.getcwd()
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
|
||||
module_path = get_eslint_module_path()
|
||||
|
||||
# npm sometimes fails to respect cwd when it is run using check_call so
|
||||
# we manually switch folders here instead.
|
||||
os.chdir(module_path)
|
||||
|
||||
npm_path = get_node_or_npm_path("npm")
|
||||
if not npm_path:
|
||||
return 1
|
||||
|
||||
# Install ESLint and external plugins
|
||||
cmd = [npm_path, "install"]
|
||||
print("Installing eslint for mach using \"%s\"..." % (" ".join(cmd)))
|
||||
if not call_process("eslint", cmd):
|
||||
return 1
|
||||
|
||||
# Install in-tree ESLint plugin
|
||||
cmd = [npm_path, "install",
|
||||
os.path.join(module_path, "eslint-plugin-mozilla")]
|
||||
print("Installing eslint-plugin-mozilla using \"%s\"..." % (" ".join(cmd)))
|
||||
if not call_process("eslint-plugin-mozilla", cmd):
|
||||
return 1
|
||||
|
||||
eslint_path = os.path.join(module_path, "node_modules", ".bin", "eslint")
|
||||
|
||||
print("\nESLint and approved plugins installed successfully!")
|
||||
print("\nNOTE: Your local eslint binary is at %s\n" % eslint_path)
|
||||
|
||||
os.chdir(orig_cwd)
|
||||
|
||||
|
||||
def call_process(name, cmd, cwd=None):
|
||||
try:
|
||||
with open(os.devnull, "w") as fnull:
|
||||
subprocess.check_call(cmd, cwd=cwd, stdout=fnull)
|
||||
except subprocess.CalledProcessError:
|
||||
if cwd:
|
||||
print("\nError installing %s in the %s folder, aborting." % (name, cwd))
|
||||
else:
|
||||
print("\nError installing %s, aborting." % name)
|
||||
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def expected_eslint_modules():
|
||||
# Read the expected version of ESLint and external modules
|
||||
expected_modules_path = os.path.join(get_eslint_module_path(), "package.json")
|
||||
with open(expected_modules_path, "r") as f:
|
||||
expected_modules = json.load(f)["dependencies"]
|
||||
|
||||
# Also read the in-tree ESLint plugin version
|
||||
mozilla_json_path = os.path.join(get_eslint_module_path(),
|
||||
"eslint-plugin-mozilla", "package.json")
|
||||
with open(mozilla_json_path, "r") as f:
|
||||
expected_modules["eslint-plugin-mozilla"] = json.load(f)["version"]
|
||||
|
||||
return expected_modules
|
||||
|
||||
|
||||
def eslint_module_has_issues():
|
||||
has_issues = False
|
||||
node_modules_path = os.path.join(get_eslint_module_path(), "node_modules")
|
||||
|
||||
for name, version_range in expected_eslint_modules().iteritems():
|
||||
path = os.path.join(node_modules_path, name, "package.json")
|
||||
|
||||
if not os.path.exists(path):
|
||||
print("%s v%s needs to be installed locally." % (name, version_range))
|
||||
has_issues = True
|
||||
continue
|
||||
|
||||
data = json.load(open(path))
|
||||
|
||||
if not version_in_range(data["version"], version_range):
|
||||
print("%s v%s should be v%s." % (name, data["version"], version_range))
|
||||
has_issues = True
|
||||
|
||||
return has_issues
|
||||
|
||||
|
||||
def version_in_range(version, version_range):
|
||||
"""
|
||||
Check if a module version is inside a version range. Only supports explicit versions and
|
||||
caret ranges for the moment, since that's all we've used so far.
|
||||
"""
|
||||
if version == version_range:
|
||||
return True
|
||||
|
||||
version_match = VERSION_RE.match(version)
|
||||
if not version_match:
|
||||
raise RuntimeError("mach eslint doesn't understand module version %s" % version)
|
||||
version = LooseVersion(version)
|
||||
|
||||
# Caret ranges as specified by npm allow changes that do not modify the left-most non-zero
|
||||
# digit in the [major, minor, patch] tuple. The code below assumes the major digit is
|
||||
# non-zero.
|
||||
range_match = CARET_VERSION_RANGE_RE.match(version_range)
|
||||
if range_match:
|
||||
range_version = range_match.group(1)
|
||||
range_major = int(range_match.group(2))
|
||||
|
||||
range_min = LooseVersion(range_version)
|
||||
range_max = LooseVersion("%d.0.0" % (range_major + 1))
|
||||
|
||||
return range_min <= version < range_max
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_possible_node_paths_win():
|
||||
"""
|
||||
Return possible nodejs paths on Windows.
|
||||
"""
|
||||
if platform.system() != "Windows":
|
||||
return []
|
||||
|
||||
return list({
|
||||
"%s\\nodejs" % os.environ.get("SystemDrive"),
|
||||
os.path.join(os.environ.get("ProgramFiles"), "nodejs"),
|
||||
os.path.join(os.environ.get("PROGRAMW6432"), "nodejs"),
|
||||
os.path.join(os.environ.get("PROGRAMFILES"), "nodejs")
|
||||
})
|
||||
|
||||
|
||||
def get_node_or_npm_path(filename, minversion=None):
|
||||
"""
|
||||
Return the nodejs or npm path.
|
||||
"""
|
||||
if platform.system() == "Windows":
|
||||
for ext in [".cmd", ".exe", ""]:
|
||||
try:
|
||||
node_or_npm_path = which.which(filename + ext,
|
||||
path=get_possible_node_paths_win())
|
||||
if is_valid(node_or_npm_path, minversion):
|
||||
return node_or_npm_path
|
||||
except which.WhichError:
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
node_or_npm_path = which.which(filename)
|
||||
if is_valid(node_or_npm_path, minversion):
|
||||
return node_or_npm_path
|
||||
except which.WhichError:
|
||||
pass
|
||||
|
||||
if filename == "node":
|
||||
print(NODE_NOT_FOUND_MESSAGE)
|
||||
elif filename == "npm":
|
||||
print(NPM_NOT_FOUND_MESSAGE)
|
||||
|
||||
if platform.system() == "Windows":
|
||||
app_paths = get_possible_node_paths_win()
|
||||
|
||||
for p in app_paths:
|
||||
print(" - %s" % p)
|
||||
elif platform.system() == "Darwin":
|
||||
print(" - /usr/local/bin/node")
|
||||
elif platform.system() == "Linux":
|
||||
print(" - /usr/bin/nodejs")
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def is_valid(path, minversion=None):
|
||||
try:
|
||||
version_str = subprocess.check_output([path, "--version"],
|
||||
stderr=subprocess.STDOUT)
|
||||
if minversion:
|
||||
# nodejs prefixes its version strings with "v"
|
||||
version = LooseVersion(version_str.lstrip('v'))
|
||||
return version >= minversion
|
||||
return True
|
||||
except (subprocess.CalledProcessError, OSError):
|
||||
return False
|
||||
|
||||
|
||||
def get_project_root():
|
||||
global project_root
|
||||
return project_root
|
||||
|
||||
|
||||
def get_eslint_module_path():
|
||||
return os.path.join(get_project_root(), "tools", "lint", "eslint")
|
||||
|
||||
|
||||
def lint(paths, binary=None, fix=None, setup=None, **lintargs):
|
||||
"""Run eslint."""
|
||||
global project_root
|
||||
project_root = lintargs['root']
|
||||
|
||||
module_path = get_eslint_module_path()
|
||||
|
||||
# eslint requires at least node 4.2.3
|
||||
node_path = get_node_or_npm_path("node", LooseVersion("4.2.3"))
|
||||
if not node_path:
|
||||
return 1
|
||||
|
||||
if setup:
|
||||
return eslint_setup()
|
||||
|
||||
npm_path = get_node_or_npm_path("npm")
|
||||
if not npm_path:
|
||||
return 1
|
||||
|
||||
if eslint_module_has_issues():
|
||||
eslint_setup()
|
||||
|
||||
# Valid binaries are:
|
||||
# - Any provided by the binary argument.
|
||||
# - Any pointed at by the ESLINT environmental variable.
|
||||
# - Those provided by mach eslint --setup.
|
||||
#
|
||||
# eslint --setup installs some mozilla specific plugins and installs
|
||||
# all node modules locally. This is the preferred method of
|
||||
# installation.
|
||||
|
||||
if not binary:
|
||||
binary = os.environ.get('ESLINT', None)
|
||||
|
||||
if not binary:
|
||||
binary = os.path.join(module_path, "node_modules", ".bin", "eslint")
|
||||
if not os.path.isfile(binary):
|
||||
binary = None
|
||||
|
||||
if not binary:
|
||||
print(ESLINT_NOT_FOUND_MESSAGE)
|
||||
return 1
|
||||
|
||||
extra_args = lintargs.get('extra_args') or []
|
||||
cmd_args = [binary,
|
||||
# Enable the HTML plugin.
|
||||
# We can't currently enable this in the global config file
|
||||
# because it has bad interactions with the SublimeText
|
||||
# ESLint plugin (bug 1229874).
|
||||
'--plugin', 'html',
|
||||
# This keeps ext as a single argument.
|
||||
'--ext', '[{}]'.format(','.join(EXTENSIONS)),
|
||||
'--format', 'json',
|
||||
] + extra_args + paths
|
||||
|
||||
# eslint requires that --fix be set before the --ext argument.
|
||||
if fix:
|
||||
cmd_args.insert(1, '--fix')
|
||||
|
||||
shell = False
|
||||
if os.environ.get('MSYSTEM') in ('MINGW32', 'MINGW64'):
|
||||
# The eslint binary needs to be run from a shell with msys
|
||||
shell = True
|
||||
|
||||
orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
proc = ProcessHandler(cmd_args, env=os.environ, stream=None, shell=shell)
|
||||
proc.run()
|
||||
signal.signal(signal.SIGINT, orig)
|
||||
|
||||
try:
|
||||
proc.wait()
|
||||
except KeyboardInterrupt:
|
||||
proc.kill()
|
||||
return []
|
||||
|
||||
if not proc.output:
|
||||
return [] # no output means success
|
||||
|
||||
try:
|
||||
jsonresult = json.loads(proc.output[0])
|
||||
except ValueError:
|
||||
print(ESLINT_ERROR_MESSAGE.format("\n".join(proc.output)))
|
||||
return 1
|
||||
|
||||
results = []
|
||||
for obj in jsonresult:
|
||||
errors = obj['messages']
|
||||
|
||||
for err in errors:
|
||||
err.update({
|
||||
'hint': err.get('fix'),
|
||||
'level': 'error' if err['severity'] == 2 else 'warning',
|
||||
'lineno': err.get('line'),
|
||||
'path': obj['filePath'],
|
||||
'rule': err.get('ruleId'),
|
||||
})
|
||||
results.append(result.from_linter(LINTER, **err))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
LINTER = {
|
||||
'name': "eslint",
|
||||
'description': "JavaScript linter",
|
||||
# ESLint infra handles its own path filtering, so just include cwd
|
||||
'include': ['.'],
|
||||
'exclude': [],
|
||||
'extensions': EXTENSIONS,
|
||||
'type': 'external',
|
||||
'payload': lint,
|
||||
}
|
||||
363
tools/lint/eslint/eslint-plugin-mozilla/LICENSE
Normal file
363
tools/lint/eslint/eslint-plugin-mozilla/LICENSE
Normal file
|
|
@ -0,0 +1,363 @@
|
|||
Mozilla Public License, version 2.0
|
||||
|
||||
1. Definitions
|
||||
|
||||
1.1. "Contributor"
|
||||
|
||||
means each individual or legal entity that creates, contributes to the
|
||||
creation of, or owns Covered Software.
|
||||
|
||||
1.2. "Contributor Version"
|
||||
|
||||
means the combination of the Contributions of others (if any) used by a
|
||||
Contributor and that particular Contributor's Contribution.
|
||||
|
||||
1.3. "Contribution"
|
||||
|
||||
means Covered Software of a particular Contributor.
|
||||
|
||||
1.4. "Covered Software"
|
||||
|
||||
means Source Code Form to which the initial Contributor has attached the
|
||||
notice in Exhibit A, the Executable Form of such Source Code Form, and
|
||||
Modifications of such Source Code Form, in each case including portions
|
||||
thereof.
|
||||
|
||||
1.5. "Incompatible With Secondary Licenses"
|
||||
means
|
||||
|
||||
a. that the initial Contributor has attached the notice described in
|
||||
Exhibit B to the Covered Software; or
|
||||
|
||||
b. that the Covered Software was made available under the terms of
|
||||
version 1.1 or earlier of the License, but not also under the terms of
|
||||
a Secondary License.
|
||||
|
||||
1.6. "Executable Form"
|
||||
|
||||
means any form of the work other than Source Code Form.
|
||||
|
||||
1.7. "Larger Work"
|
||||
|
||||
means a work that combines Covered Software with other material, in a
|
||||
separate file or files, that is not Covered Software.
|
||||
|
||||
1.8. "License"
|
||||
|
||||
means this document.
|
||||
|
||||
1.9. "Licensable"
|
||||
|
||||
means having the right to grant, to the maximum extent possible, whether
|
||||
at the time of the initial grant or subsequently, any and all of the
|
||||
rights conveyed by this License.
|
||||
|
||||
1.10. "Modifications"
|
||||
|
||||
means any of the following:
|
||||
|
||||
a. any file in Source Code Form that results from an addition to,
|
||||
deletion from, or modification of the contents of Covered Software; or
|
||||
|
||||
b. any new file in Source Code Form that contains any Covered Software.
|
||||
|
||||
1.11. "Patent Claims" of a Contributor
|
||||
|
||||
means any patent claim(s), including without limitation, method,
|
||||
process, and apparatus claims, in any patent Licensable by such
|
||||
Contributor that would be infringed, but for the grant of the License,
|
||||
by the making, using, selling, offering for sale, having made, import,
|
||||
or transfer of either its Contributions or its Contributor Version.
|
||||
|
||||
1.12. "Secondary License"
|
||||
|
||||
means either the GNU General Public License, Version 2.0, the GNU Lesser
|
||||
General Public License, Version 2.1, the GNU Affero General Public
|
||||
License, Version 3.0, or any later versions of those licenses.
|
||||
|
||||
1.13. "Source Code Form"
|
||||
|
||||
means the form of the work preferred for making modifications.
|
||||
|
||||
1.14. "You" (or "Your")
|
||||
|
||||
means an individual or a legal entity exercising rights under this
|
||||
License. For legal entities, "You" includes any entity that controls, is
|
||||
controlled by, or is under common control with You. For purposes of this
|
||||
definition, "control" means (a) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (b) ownership of more than fifty percent (50%) of the
|
||||
outstanding shares or beneficial ownership of such entity.
|
||||
|
||||
|
||||
2. License Grants and Conditions
|
||||
|
||||
2.1. Grants
|
||||
|
||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
||||
non-exclusive license:
|
||||
|
||||
a. under intellectual property rights (other than patent or trademark)
|
||||
Licensable by such Contributor to use, reproduce, make available,
|
||||
modify, display, perform, distribute, and otherwise exploit its
|
||||
Contributions, either on an unmodified basis, with Modifications, or
|
||||
as part of a Larger Work; and
|
||||
|
||||
b. under Patent Claims of such Contributor to make, use, sell, offer for
|
||||
sale, have made, import, and otherwise transfer either its
|
||||
Contributions or its Contributor Version.
|
||||
|
||||
2.2. Effective Date
|
||||
|
||||
The licenses granted in Section 2.1 with respect to any Contribution
|
||||
become effective for each Contribution on the date the Contributor first
|
||||
distributes such Contribution.
|
||||
|
||||
2.3. Limitations on Grant Scope
|
||||
|
||||
The licenses granted in this Section 2 are the only rights granted under
|
||||
this License. No additional rights or licenses will be implied from the
|
||||
distribution or licensing of Covered Software under this License.
|
||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
||||
Contributor:
|
||||
|
||||
a. for any code that a Contributor has removed from Covered Software; or
|
||||
|
||||
b. for infringements caused by: (i) Your and any other third party's
|
||||
modifications of Covered Software, or (ii) the combination of its
|
||||
Contributions with other software (except as part of its Contributor
|
||||
Version); or
|
||||
|
||||
c. under Patent Claims infringed by Covered Software in the absence of
|
||||
its Contributions.
|
||||
|
||||
This License does not grant any rights in the trademarks, service marks,
|
||||
or logos of any Contributor (except as may be necessary to comply with
|
||||
the notice requirements in Section 3.4).
|
||||
|
||||
2.4. Subsequent Licenses
|
||||
|
||||
No Contributor makes additional grants as a result of Your choice to
|
||||
distribute the Covered Software under a subsequent version of this
|
||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
||||
permitted under the terms of Section 3.3).
|
||||
|
||||
2.5. Representation
|
||||
|
||||
Each Contributor represents that the Contributor believes its
|
||||
Contributions are its original creation(s) or it has sufficient rights to
|
||||
grant the rights to its Contributions conveyed by this License.
|
||||
|
||||
2.6. Fair Use
|
||||
|
||||
This License is not intended to limit any rights You have under
|
||||
applicable copyright doctrines of fair use, fair dealing, or other
|
||||
equivalents.
|
||||
|
||||
2.7. Conditions
|
||||
|
||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
|
||||
Section 2.1.
|
||||
|
||||
|
||||
3. Responsibilities
|
||||
|
||||
3.1. Distribution of Source Form
|
||||
|
||||
All distribution of Covered Software in Source Code Form, including any
|
||||
Modifications that You create or to which You contribute, must be under
|
||||
the terms of this License. You must inform recipients that the Source
|
||||
Code Form of the Covered Software is governed by the terms of this
|
||||
License, and how they can obtain a copy of this License. You may not
|
||||
attempt to alter or restrict the recipients' rights in the Source Code
|
||||
Form.
|
||||
|
||||
3.2. Distribution of Executable Form
|
||||
|
||||
If You distribute Covered Software in Executable Form then:
|
||||
|
||||
a. such Covered Software must also be made available in Source Code Form,
|
||||
as described in Section 3.1, and You must inform recipients of the
|
||||
Executable Form how they can obtain a copy of such Source Code Form by
|
||||
reasonable means in a timely manner, at a charge no more than the cost
|
||||
of distribution to the recipient; and
|
||||
|
||||
b. You may distribute such Executable Form under the terms of this
|
||||
License, or sublicense it under different terms, provided that the
|
||||
license for the Executable Form does not attempt to limit or alter the
|
||||
recipients' rights in the Source Code Form under this License.
|
||||
|
||||
3.3. Distribution of a Larger Work
|
||||
|
||||
You may create and distribute a Larger Work under terms of Your choice,
|
||||
provided that You also comply with the requirements of this License for
|
||||
the Covered Software. If the Larger Work is a combination of Covered
|
||||
Software with a work governed by one or more Secondary Licenses, and the
|
||||
Covered Software is not Incompatible With Secondary Licenses, this
|
||||
License permits You to additionally distribute such Covered Software
|
||||
under the terms of such Secondary License(s), so that the recipient of
|
||||
the Larger Work may, at their option, further distribute the Covered
|
||||
Software under the terms of either this License or such Secondary
|
||||
License(s).
|
||||
|
||||
3.4. Notices
|
||||
|
||||
You may not remove or alter the substance of any license notices
|
||||
(including copyright notices, patent notices, disclaimers of warranty, or
|
||||
limitations of liability) contained within the Source Code Form of the
|
||||
Covered Software, except that You may alter any license notices to the
|
||||
extent required to remedy known factual inaccuracies.
|
||||
|
||||
3.5. Application of Additional Terms
|
||||
|
||||
You may choose to offer, and to charge a fee for, warranty, support,
|
||||
indemnity or liability obligations to one or more recipients of Covered
|
||||
Software. However, You may do so only on Your own behalf, and not on
|
||||
behalf of any Contributor. You must make it absolutely clear that any
|
||||
such warranty, support, indemnity, or liability obligation is offered by
|
||||
You alone, and You hereby agree to indemnify every Contributor for any
|
||||
liability incurred by such Contributor as a result of warranty, support,
|
||||
indemnity or liability terms You offer. You may include additional
|
||||
disclaimers of warranty and limitations of liability specific to any
|
||||
jurisdiction.
|
||||
|
||||
4. Inability to Comply Due to Statute or Regulation
|
||||
|
||||
If it is impossible for You to comply with any of the terms of this License
|
||||
with respect to some or all of the Covered Software due to statute,
|
||||
judicial order, or regulation then You must: (a) comply with the terms of
|
||||
this License to the maximum extent possible; and (b) describe the
|
||||
limitations and the code they affect. Such description must be placed in a
|
||||
text file included with all distributions of the Covered Software under
|
||||
this License. Except to the extent prohibited by statute or regulation,
|
||||
such description must be sufficiently detailed for a recipient of ordinary
|
||||
skill to be able to understand it.
|
||||
|
||||
5. Termination
|
||||
|
||||
5.1. The rights granted under this License will terminate automatically if You
|
||||
fail to comply with any of its terms. However, if You become compliant,
|
||||
then the rights granted under this License from a particular Contributor
|
||||
are reinstated (a) provisionally, unless and until such Contributor
|
||||
explicitly and finally terminates Your grants, and (b) on an ongoing
|
||||
basis, if such Contributor fails to notify You of the non-compliance by
|
||||
some reasonable means prior to 60 days after You have come back into
|
||||
compliance. Moreover, Your grants from a particular Contributor are
|
||||
reinstated on an ongoing basis if such Contributor notifies You of the
|
||||
non-compliance by some reasonable means, this is the first time You have
|
||||
received notice of non-compliance with this License from such
|
||||
Contributor, and You become compliant prior to 30 days after Your receipt
|
||||
of the notice.
|
||||
|
||||
5.2. If You initiate litigation against any entity by asserting a patent
|
||||
infringement claim (excluding declaratory judgment actions,
|
||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
||||
directly or indirectly infringes any patent, then the rights granted to
|
||||
You by any and all Contributors for the Covered Software under Section
|
||||
2.1 of this License shall terminate.
|
||||
|
||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
|
||||
license agreements (excluding distributors and resellers) which have been
|
||||
validly granted by You or Your distributors under this License prior to
|
||||
termination shall survive termination.
|
||||
|
||||
6. Disclaimer of Warranty
|
||||
|
||||
Covered Software is provided under this License on an "as is" basis,
|
||||
without warranty of any kind, either expressed, implied, or statutory,
|
||||
including, without limitation, warranties that the Covered Software is free
|
||||
of defects, merchantable, fit for a particular purpose or non-infringing.
|
||||
The entire risk as to the quality and performance of the Covered Software
|
||||
is with You. Should any Covered Software prove defective in any respect,
|
||||
You (not any Contributor) assume the cost of any necessary servicing,
|
||||
repair, or correction. This disclaimer of warranty constitutes an essential
|
||||
part of this License. No use of any Covered Software is authorized under
|
||||
this License except under this disclaimer.
|
||||
|
||||
7. Limitation of Liability
|
||||
|
||||
Under no circumstances and under no legal theory, whether tort (including
|
||||
negligence), contract, or otherwise, shall any Contributor, or anyone who
|
||||
distributes Covered Software as permitted above, be liable to You for any
|
||||
direct, indirect, special, incidental, or consequential damages of any
|
||||
character including, without limitation, damages for lost profits, loss of
|
||||
goodwill, work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses, even if such party shall have been
|
||||
informed of the possibility of such damages. This limitation of liability
|
||||
shall not apply to liability for death or personal injury resulting from
|
||||
such party's negligence to the extent applicable law prohibits such
|
||||
limitation. Some jurisdictions do not allow the exclusion or limitation of
|
||||
incidental or consequential damages, so this exclusion and limitation may
|
||||
not apply to You.
|
||||
|
||||
8. Litigation
|
||||
|
||||
Any litigation relating to this License may be brought only in the courts
|
||||
of a jurisdiction where the defendant maintains its principal place of
|
||||
business and such litigation shall be governed by laws of that
|
||||
jurisdiction, without reference to its conflict-of-law provisions. Nothing
|
||||
in this Section shall prevent a party's ability to bring cross-claims or
|
||||
counter-claims.
|
||||
|
||||
9. Miscellaneous
|
||||
|
||||
This License represents the complete agreement concerning the subject
|
||||
matter hereof. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable. Any law or regulation which provides that
|
||||
the language of a contract shall be construed against the drafter shall not
|
||||
be used to construe this License against a Contributor.
|
||||
|
||||
|
||||
10. Versions of the License
|
||||
|
||||
10.1. New Versions
|
||||
|
||||
Mozilla Foundation is the license steward. Except as provided in Section
|
||||
10.3, no one other than the license steward has the right to modify or
|
||||
publish new versions of this License. Each version will be given a
|
||||
distinguishing version number.
|
||||
|
||||
10.2. Effect of New Versions
|
||||
|
||||
You may distribute the Covered Software under the terms of the version
|
||||
of the License under which You originally received the Covered Software,
|
||||
or under the terms of any subsequent version published by the license
|
||||
steward.
|
||||
|
||||
10.3. Modified Versions
|
||||
|
||||
If you create software not governed by this License, and you want to
|
||||
create a new license for such software, you may create and use a
|
||||
modified version of this License if you rename the license and remove
|
||||
any references to the name of the license steward (except to note that
|
||||
such modified license differs from this License).
|
||||
|
||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
||||
Licenses If You choose to distribute Source Code Form that is
|
||||
Incompatible With Secondary Licenses under the terms of this version of
|
||||
the License, the notice described in Exhibit B of this License must be
|
||||
attached.
|
||||
|
||||
Exhibit A - Source Code Form License Notice
|
||||
|
||||
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/.
|
||||
|
||||
If it is not possible or desirable to put the notice in a particular file,
|
||||
then You may include the notice in a location (such as a LICENSE file in a
|
||||
relevant directory) where a recipient would be likely to look for such a
|
||||
notice.
|
||||
|
||||
You may add additional accurate notices of copyright ownership.
|
||||
|
||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
||||
|
||||
This Source Code Form is "Incompatible
|
||||
With Secondary Licenses", as defined by
|
||||
the Mozilla Public License, v. 2.0.
|
||||
|
||||
188
tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
Normal file
188
tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
/**
|
||||
* @fileoverview functions for scanning an AST for globals including
|
||||
* traversing referenced scripts.
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const helpers = require("./helpers");
|
||||
const escope = require("escope");
|
||||
const estraverse = require("estraverse");
|
||||
|
||||
/**
|
||||
* Parses a list of "name:boolean_value" or/and "name" options divided by comma or
|
||||
* whitespace.
|
||||
*
|
||||
* This function was copied from eslint.js
|
||||
*
|
||||
* @param {string} string The string to parse.
|
||||
* @param {Comment} comment The comment node which has the string.
|
||||
* @returns {Object} Result map object of names and boolean values
|
||||
*/
|
||||
function parseBooleanConfig(string, comment) {
|
||||
let items = {};
|
||||
|
||||
// Collapse whitespace around : to make parsing easier
|
||||
string = string.replace(/\s*:\s*/g, ":");
|
||||
// Collapse whitespace around ,
|
||||
string = string.replace(/\s*,\s*/g, ",");
|
||||
|
||||
string.split(/\s|,+/).forEach(function(name) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
let pos = name.indexOf(":");
|
||||
let value = undefined;
|
||||
if (pos !== -1) {
|
||||
value = name.substring(pos + 1, name.length);
|
||||
name = name.substring(0, pos);
|
||||
}
|
||||
|
||||
items[name] = {
|
||||
value: (value === "true"),
|
||||
comment: comment
|
||||
};
|
||||
});
|
||||
|
||||
return items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Global discovery can require parsing many files. This map of
|
||||
* {String} => {Object} caches what globals were discovered for a file path.
|
||||
*/
|
||||
const globalCache = new Map();
|
||||
|
||||
/**
|
||||
* An object that returns found globals for given AST node types. Each prototype
|
||||
* property should be named for a node type and accepts a node parameter and a
|
||||
* parents parameter which is a list of the parent nodes of the current node.
|
||||
* Each returns an array of globals found.
|
||||
*
|
||||
* @param {String} path
|
||||
* The absolute path of the file being parsed.
|
||||
*/
|
||||
function GlobalsForNode(path) {
|
||||
this.path = path;
|
||||
this.root = helpers.getRootDir(path);
|
||||
}
|
||||
|
||||
GlobalsForNode.prototype = {
|
||||
BlockComment(node, parents) {
|
||||
let value = node.value.trim();
|
||||
let match = /^import-globals-from\s+(.+)$/.exec(value);
|
||||
if (!match) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let filePath = match[1].trim();
|
||||
|
||||
if (!path.isAbsolute(filePath)) {
|
||||
let dirName = path.dirname(this.path);
|
||||
filePath = path.resolve(dirName, filePath);
|
||||
}
|
||||
|
||||
return module.exports.getGlobalsForFile(filePath);
|
||||
},
|
||||
|
||||
ExpressionStatement(node, parents) {
|
||||
let isGlobal = helpers.getIsGlobalScope(parents);
|
||||
let names = helpers.convertExpressionToGlobals(node, isGlobal, this.root);
|
||||
return names.map(name => { return { name, writable: true }});
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Returns all globals for a given file. Recursively searches through
|
||||
* import-globals-from directives and also includes globals defined by
|
||||
* standard eslint directives.
|
||||
*
|
||||
* @param {String} path
|
||||
* The absolute path of the file to be parsed.
|
||||
*/
|
||||
getGlobalsForFile(path) {
|
||||
if (globalCache.has(path)) {
|
||||
return globalCache.get(path);
|
||||
}
|
||||
|
||||
let content = fs.readFileSync(path, "utf8");
|
||||
|
||||
// Parse the content into an AST
|
||||
let ast = helpers.getAST(content);
|
||||
|
||||
// Discover global declarations
|
||||
let scopeManager = escope.analyze(ast);
|
||||
let globalScope = scopeManager.acquire(ast);
|
||||
|
||||
let globals = Object.keys(globalScope.variables).map(v => ({
|
||||
name: globalScope.variables[v].name,
|
||||
writable: true,
|
||||
}));
|
||||
|
||||
// Walk over the AST to find any of our custom globals
|
||||
let handler = new GlobalsForNode(path);
|
||||
|
||||
helpers.walkAST(ast, (type, node, parents) => {
|
||||
// We have to discover any globals that ESLint would have defined through
|
||||
// comment directives
|
||||
if (type == "BlockComment") {
|
||||
let value = node.value.trim();
|
||||
let match = /^globals?\s+(.+)$/.exec(value);
|
||||
if (match) {
|
||||
let values = parseBooleanConfig(match[1].trim(), node);
|
||||
for (let name of Object.keys(values)) {
|
||||
globals.push({
|
||||
name,
|
||||
writable: values[name].value
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (type in handler) {
|
||||
let newGlobals = handler[type](node, parents);
|
||||
globals.push.apply(globals, newGlobals);
|
||||
}
|
||||
});
|
||||
|
||||
globalCache.set(path, globals);
|
||||
|
||||
return globals;
|
||||
},
|
||||
|
||||
/**
|
||||
* Intended to be used as-is for an ESLint rule that parses for globals in
|
||||
* the current file and recurses through import-globals-from directives.
|
||||
*
|
||||
* @param {Object} context
|
||||
* The ESLint parsing context.
|
||||
*/
|
||||
getESLintGlobalParser(context) {
|
||||
let globalScope;
|
||||
|
||||
let parser = {
|
||||
Program(node) {
|
||||
globalScope = context.getScope();
|
||||
}
|
||||
};
|
||||
|
||||
// Install thin wrappers around GlobalsForNode
|
||||
let handler = new GlobalsForNode(helpers.getAbsoluteFilePath(context));
|
||||
|
||||
for (let type of Object.keys(GlobalsForNode.prototype)) {
|
||||
parser[type] = function(node) {
|
||||
let globals = handler[type](node, context.getAncestors());
|
||||
helpers.addGlobals(globals, globalScope);
|
||||
}
|
||||
}
|
||||
|
||||
return parser;
|
||||
}
|
||||
};
|
||||
524
tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
Normal file
524
tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
/**
|
||||
* @fileoverview A collection of helper functions.
|
||||
* 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/.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
var escope = require("escope");
|
||||
var espree = require("espree");
|
||||
var estraverse = require("estraverse");
|
||||
var path = require("path");
|
||||
var fs = require("fs");
|
||||
var ini = require("ini-parser");
|
||||
|
||||
var modules = null;
|
||||
var directoryManifests = new Map();
|
||||
|
||||
var definitions = [
|
||||
/^loader\.lazyGetter\(this, "(\w+)"/,
|
||||
/^loader\.lazyImporter\(this, "(\w+)"/,
|
||||
/^loader\.lazyServiceGetter\(this, "(\w+)"/,
|
||||
/^loader\.lazyRequireGetter\(this, "(\w+)"/,
|
||||
/^XPCOMUtils\.defineLazyGetter\(this, "(\w+)"/,
|
||||
/^XPCOMUtils\.defineLazyModuleGetter\(this, "(\w+)"/,
|
||||
/^XPCOMUtils\.defineLazyServiceGetter\(this, "(\w+)"/,
|
||||
/^XPCOMUtils\.defineConstant\(this, "(\w+)"/,
|
||||
/^DevToolsUtils\.defineLazyModuleGetter\(this, "(\w+)"/,
|
||||
/^DevToolsUtils\.defineLazyGetter\(this, "(\w+)"/,
|
||||
/^Object\.defineProperty\(this, "(\w+)"/,
|
||||
/^Reflect\.defineProperty\(this, "(\w+)"/,
|
||||
/^this\.__defineGetter__\("(\w+)"/,
|
||||
/^this\.(\w+) =/
|
||||
];
|
||||
|
||||
var imports = [
|
||||
/^(?:Cu|Components\.utils)\.import\(".*\/((.*?)\.jsm?)"(?:, this)?\)/,
|
||||
];
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* Gets the abstract syntax tree (AST) of the JavaScript source code contained
|
||||
* in sourceText.
|
||||
*
|
||||
* @param {String} sourceText
|
||||
* Text containing valid JavaScript.
|
||||
*
|
||||
* @return {Object}
|
||||
* The resulting AST.
|
||||
*/
|
||||
getAST: function(sourceText) {
|
||||
// Use a permissive config file to allow parsing of anything that Espree
|
||||
// can parse.
|
||||
var config = this.getPermissiveConfig();
|
||||
|
||||
return espree.parse(sourceText, config);
|
||||
},
|
||||
|
||||
/**
|
||||
* A simplistic conversion of some AST nodes to a standard string form.
|
||||
*
|
||||
* @param {Object} node
|
||||
* The AST node to convert.
|
||||
*
|
||||
* @return {String}
|
||||
* The JS source for the node.
|
||||
*/
|
||||
getASTSource: function(node) {
|
||||
switch (node.type) {
|
||||
case "MemberExpression":
|
||||
if (node.computed)
|
||||
throw new Error("getASTSource unsupported computed MemberExpression");
|
||||
return this.getASTSource(node.object) + "." + this.getASTSource(node.property);
|
||||
case "ThisExpression":
|
||||
return "this";
|
||||
case "Identifier":
|
||||
return node.name;
|
||||
case "Literal":
|
||||
return JSON.stringify(node.value);
|
||||
case "CallExpression":
|
||||
var args = node.arguments.map(a => this.getASTSource(a)).join(", ");
|
||||
return this.getASTSource(node.callee) + "(" + args + ")";
|
||||
case "ObjectExpression":
|
||||
return "{}";
|
||||
case "ExpressionStatement":
|
||||
return this.getASTSource(node.expression) + ";";
|
||||
case "FunctionExpression":
|
||||
return "function() {}";
|
||||
case "ArrowFunctionExpression":
|
||||
return "() => {}";
|
||||
case "AssignmentExpression":
|
||||
return this.getASTSource(node.left) + " = " + this.getASTSource(node.right);
|
||||
default:
|
||||
throw new Error("getASTSource unsupported node type: " + node.type);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* This walks an AST in a manner similar to ESLint passing node and comment
|
||||
* events to the listener. The listener is expected to be a simple function
|
||||
* which accepts node type, node and parents arguments.
|
||||
*
|
||||
* @param {Object} ast
|
||||
* The AST to walk.
|
||||
* @param {Function} listener
|
||||
* A callback function to call for the nodes. Passed three arguments,
|
||||
* event type, node and an array of parent nodes for the current node.
|
||||
*/
|
||||
walkAST(ast, listener) {
|
||||
let parents = [];
|
||||
|
||||
let seenComments = new Set();
|
||||
function sendCommentEvents(comments) {
|
||||
if (!comments) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let comment of comments) {
|
||||
if (seenComments.has(comment)) {
|
||||
return;
|
||||
}
|
||||
seenComments.add(comment);
|
||||
|
||||
listener(comment.type + "Comment", comment, parents);
|
||||
}
|
||||
}
|
||||
|
||||
estraverse.traverse(ast, {
|
||||
enter(node, parent) {
|
||||
// Comments are held in node.comments for empty programs
|
||||
let leadingComments = node.leadingComments;
|
||||
if (node.type === "Program" && node.body.length == 0) {
|
||||
leadingComments = node.comments;
|
||||
}
|
||||
|
||||
sendCommentEvents(leadingComments);
|
||||
listener(node.type, node, parents);
|
||||
sendCommentEvents(node.trailingComments);
|
||||
|
||||
parents.push(node);
|
||||
},
|
||||
|
||||
leave(node, parent) {
|
||||
// TODO send comment exit events
|
||||
listener(node.type + ":exit", node, parents);
|
||||
|
||||
if (parents.length == 0) {
|
||||
throw new Error("Left more nodes than entered.");
|
||||
}
|
||||
parents.pop();
|
||||
}
|
||||
});
|
||||
if (parents.length) {
|
||||
throw new Error("Entered more nodes than left.");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Attempts to convert an ExpressionStatement to likely global variable
|
||||
* definitions.
|
||||
*
|
||||
* @param {Object} node
|
||||
* The AST node to convert.
|
||||
* @param {boolean} isGlobal
|
||||
* True if the current node is in the global scope.
|
||||
* @param {String} repository
|
||||
* The root of the repository.
|
||||
*
|
||||
* @return {Array}
|
||||
* An array of variable names defined.
|
||||
*/
|
||||
convertExpressionToGlobals: function(node, isGlobal, repository) {
|
||||
if (!modules) {
|
||||
modules = require(path.join(repository, "tools", "lint", "eslint", "modules.json"));
|
||||
}
|
||||
|
||||
try {
|
||||
var source = this.getASTSource(node);
|
||||
}
|
||||
catch (e) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (var reg of definitions) {
|
||||
var match = source.match(reg);
|
||||
if (match) {
|
||||
// Must be in the global scope
|
||||
if (!isGlobal) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [match[1]];
|
||||
}
|
||||
}
|
||||
|
||||
for (reg of imports) {
|
||||
var match = source.match(reg);
|
||||
if (match) {
|
||||
// The two argument form is only acceptable in the global scope
|
||||
if (node.expression.arguments.length > 1 && !isGlobal) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (match[1] in modules) {
|
||||
return modules[match[1]];
|
||||
}
|
||||
|
||||
return [match[2]];
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a variable to the current scope.
|
||||
* HACK: This relies on eslint internals so it could break at any time.
|
||||
*
|
||||
* @param {String} name
|
||||
* The variable name to add to the scope.
|
||||
* @param {ASTScope} scope
|
||||
* The scope to add to.
|
||||
* @param {boolean} writable
|
||||
* Whether the global can be overwritten.
|
||||
*/
|
||||
addVarToScope: function(name, scope, writable) {
|
||||
scope.__defineGeneric(name, scope.set, scope.variables, null, null);
|
||||
|
||||
let variable = scope.set.get(name);
|
||||
variable.eslintExplicitGlobal = false;
|
||||
variable.writeable = writable;
|
||||
|
||||
// Walk to the global scope which holds all undeclared variables.
|
||||
while (scope.type != "global") {
|
||||
scope = scope.upper;
|
||||
}
|
||||
|
||||
// "through" contains all references with no found definition.
|
||||
scope.through = scope.through.filter(function(reference) {
|
||||
if (reference.identifier.name != name) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Links the variable and the reference.
|
||||
// And this reference is removed from `Scope#through`.
|
||||
reference.resolved = variable;
|
||||
variable.references.push(reference);
|
||||
return false;
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a set of globals to a scope.
|
||||
*
|
||||
* @param {Array} globalVars
|
||||
* An array of global variable names.
|
||||
* @param {ASTScope} scope
|
||||
* The scope.
|
||||
*/
|
||||
addGlobals: function(globalVars, scope) {
|
||||
globalVars.forEach(v => this.addVarToScope(v.name, scope, v.writable));
|
||||
},
|
||||
|
||||
/**
|
||||
* To allow espree to parse almost any JavaScript we need as many features as
|
||||
* possible turned on. This method returns that config.
|
||||
*
|
||||
* @return {Object}
|
||||
* Espree compatible permissive config.
|
||||
*/
|
||||
getPermissiveConfig: function() {
|
||||
return {
|
||||
range: true,
|
||||
loc: true,
|
||||
comment: true,
|
||||
attachComment: true,
|
||||
ecmaVersion: 8,
|
||||
sourceType: "script",
|
||||
ecmaFeatures: {
|
||||
experimentalObjectRestSpread: true,
|
||||
globalReturn: true,
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether the context is the global scope.
|
||||
*
|
||||
* @param {Array} ancestors
|
||||
* The parents of the current node.
|
||||
*
|
||||
* @return {Boolean}
|
||||
* True or false
|
||||
*/
|
||||
getIsGlobalScope: function(ancestors) {
|
||||
for (let parent of ancestors) {
|
||||
if (parent.type == "FunctionExpression" ||
|
||||
parent.type == "FunctionDeclaration") {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether we might be in a test head file.
|
||||
*
|
||||
* @param {RuleContext} scope
|
||||
* You should pass this from within a rule
|
||||
* e.g. helpers.getIsHeadFile(this)
|
||||
*
|
||||
* @return {Boolean}
|
||||
* True or false
|
||||
*/
|
||||
getIsHeadFile: function(scope) {
|
||||
var pathAndFilename = this.cleanUpPath(scope.getFilename());
|
||||
|
||||
return /.*[\\/]head(_.+)?\.js$/.test(pathAndFilename);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the head files for a potential test file
|
||||
*
|
||||
* @param {RuleContext} scope
|
||||
* You should pass this from within a rule
|
||||
* e.g. helpers.getIsHeadFile(this)
|
||||
*
|
||||
* @return {String[]}
|
||||
* Paths to head files to load for the test
|
||||
*/
|
||||
getTestHeadFiles: function(scope) {
|
||||
if (!this.getIsTest(scope)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let filepath = this.cleanUpPath(scope.getFilename());
|
||||
let dir = path.dirname(filepath);
|
||||
|
||||
let names = fs.readdirSync(dir)
|
||||
.filter(name => name.startsWith("head") && name.endsWith(".js"))
|
||||
.map(name => path.join(dir, name));
|
||||
return names;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets all the test manifest data for a directory
|
||||
*
|
||||
* @param {String} dir
|
||||
* The directory
|
||||
*
|
||||
* @return {Array}
|
||||
* An array of objects with file and manifest properties
|
||||
*/
|
||||
getManifestsForDirectory: function(dir) {
|
||||
if (directoryManifests.has(dir)) {
|
||||
return directoryManifests.get(dir);
|
||||
}
|
||||
|
||||
let manifests = [];
|
||||
|
||||
let names = fs.readdirSync(dir);
|
||||
for (let name of names) {
|
||||
if (!name.endsWith(".ini")) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
let manifest = ini.parse(fs.readFileSync(path.join(dir, name), 'utf8'));
|
||||
|
||||
manifests.push({
|
||||
file: path.join(dir, name),
|
||||
manifest
|
||||
})
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
|
||||
directoryManifests.set(dir, manifests);
|
||||
return manifests;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the manifest file a test is listed in
|
||||
*
|
||||
* @param {RuleContext} scope
|
||||
* You should pass this from within a rule
|
||||
* e.g. helpers.getIsHeadFile(this)
|
||||
*
|
||||
* @return {String}
|
||||
* The path to the test manifest file
|
||||
*/
|
||||
getTestManifest: function(scope) {
|
||||
let filepath = this.cleanUpPath(scope.getFilename());
|
||||
|
||||
let dir = path.dirname(filepath);
|
||||
let filename = path.basename(filepath);
|
||||
|
||||
for (let manifest of this.getManifestsForDirectory(dir)) {
|
||||
if (filename in manifest.manifest) {
|
||||
return manifest.file;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check whether we are in a test of some kind.
|
||||
*
|
||||
* @param {RuleContext} scope
|
||||
* You should pass this from within a rule
|
||||
* e.g. helpers.getIsTest(this)
|
||||
*
|
||||
* @return {Boolean}
|
||||
* True or false
|
||||
*/
|
||||
getIsTest: function(scope) {
|
||||
// Regardless of the manifest name being in a manifest means we're a test.
|
||||
let manifest = this.getTestManifest(scope);
|
||||
if (manifest) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !!this.getTestType(scope);
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the type of test or null if this isn't a test.
|
||||
*
|
||||
* @param {RuleContext} scope
|
||||
* You should pass this from within a rule
|
||||
* e.g. helpers.getIsHeadFile(this)
|
||||
*
|
||||
* @return {String or null}
|
||||
* Test type: xpcshell, browser, chrome, mochitest
|
||||
*/
|
||||
getTestType: function(scope) {
|
||||
let manifest = this.getTestManifest(scope);
|
||||
if (manifest) {
|
||||
let name = path.basename(manifest);
|
||||
for (let testType of ["browser", "xpcshell", "chrome", "mochitest"]) {
|
||||
if (name.startsWith(testType)) {
|
||||
return testType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let filepath = this.cleanUpPath(scope.getFilename());
|
||||
let filename = path.basename(filepath);
|
||||
|
||||
if (filename.startsWith("browser_")) {
|
||||
return "browser";
|
||||
}
|
||||
|
||||
if (filename.startsWith("test_")) {
|
||||
return "xpcshell";
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the root directory of the repository by walking up directories until
|
||||
* a .eslintignore file is found.
|
||||
* @param {String} fileName
|
||||
* The absolute path of a file in the repository
|
||||
*
|
||||
* @return {String} The absolute path of the repository directory
|
||||
*/
|
||||
getRootDir: function(fileName) {
|
||||
var dirName = path.dirname(fileName);
|
||||
|
||||
while (dirName && !fs.existsSync(path.join(dirName, ".eslintignore"))) {
|
||||
dirName = path.dirname(dirName);
|
||||
}
|
||||
|
||||
if (!dirName) {
|
||||
throw new Error("Unable to find root of repository");
|
||||
}
|
||||
|
||||
return dirName;
|
||||
},
|
||||
|
||||
/**
|
||||
* ESLint may be executed from various places: from mach, at the root of the
|
||||
* repository, or from a directory in the repository when, for instance,
|
||||
* executed by a text editor's plugin.
|
||||
* The value returned by context.getFileName() varies because of this.
|
||||
* This helper function makes sure to return an absolute file path for the
|
||||
* current context, by looking at process.cwd().
|
||||
* @param {Context} context
|
||||
* @return {String} The absolute path
|
||||
*/
|
||||
getAbsoluteFilePath: function(context) {
|
||||
var fileName = this.cleanUpPath(context.getFilename());
|
||||
var cwd = process.cwd();
|
||||
|
||||
if (path.isAbsolute(fileName)) {
|
||||
// Case 2: executed from the repo's root with mach:
|
||||
// fileName: /path/to/mozilla/repo/a/b/c/d.js
|
||||
// cwd: /path/to/mozilla/repo
|
||||
return fileName;
|
||||
} else if (path.basename(fileName) == fileName) {
|
||||
// Case 1b: executed from a nested directory, fileName is the base name
|
||||
// without any path info (happens in Atom with linter-eslint)
|
||||
return path.join(cwd, fileName);
|
||||
} else {
|
||||
// Case 1: executed form in a nested directory, e.g. from a text editor:
|
||||
// fileName: a/b/c/d.js
|
||||
// cwd: /path/to/mozilla/repo/a/b/c
|
||||
var dirName = path.dirname(fileName);
|
||||
return cwd.slice(0, cwd.length - dirName.length) + fileName;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* When ESLint is run from SublimeText, paths retrieved from
|
||||
* context.getFileName contain leading and trailing double-quote characters.
|
||||
* These characters need to be removed.
|
||||
*/
|
||||
cleanUpPath: function(path) {
|
||||
return path.replace(/^"/, "").replace(/"$/, "");
|
||||
}
|
||||
};
|
||||
45
tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
Normal file
45
tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
/**
|
||||
* @fileoverview A collection of rules that help enforce JavaScript coding
|
||||
* standard and avoid common errors in the Mozilla 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Plugin Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
processors: {
|
||||
".xml": require("../lib/processors/xbl-bindings"),
|
||||
},
|
||||
rules: {
|
||||
"balanced-listeners": require("../lib/rules/balanced-listeners"),
|
||||
"import-globals": require("../lib/rules/import-globals"),
|
||||
"import-headjs-globals": require("../lib/rules/import-headjs-globals"),
|
||||
"import-browserjs-globals": require("../lib/rules/import-browserjs-globals"),
|
||||
"mark-test-function-used": require("../lib/rules/mark-test-function-used"),
|
||||
"no-aArgs": require("../lib/rules/no-aArgs"),
|
||||
"no-cpows-in-tests": require("../lib/rules/no-cpows-in-tests"),
|
||||
"no-single-arg-cu-import": require("../lib/rules/no-single-arg-cu-import"),
|
||||
"reject-importGlobalProperties": require("../lib/rules/reject-importGlobalProperties"),
|
||||
"reject-some-requires": require("../lib/rules/reject-some-requires"),
|
||||
"var-only-at-top-level": require("../lib/rules/var-only-at-top-level")
|
||||
},
|
||||
rulesConfig: {
|
||||
"balanced-listeners": 0,
|
||||
"import-globals": 0,
|
||||
"import-headjs-globals": 0,
|
||||
"import-browserjs-globals": 0,
|
||||
"mark-test-function-used": 0,
|
||||
"no-aArgs": 0,
|
||||
"no-cpows-in-tests": 0,
|
||||
"no-single-arg-cu-import": 0,
|
||||
"reject-importGlobalProperties": 0,
|
||||
"reject-some-requires": 0,
|
||||
"var-only-at-top-level": 0
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,363 @@
|
|||
/**
|
||||
* @fileoverview Converts functions and handlers from XBL bindings into JS
|
||||
* functions
|
||||
*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const NS_XBL = "http://www.mozilla.org/xbl";
|
||||
|
||||
let sax = require("sax");
|
||||
|
||||
// Converts sax's error message to something that eslint will understand
|
||||
let errorRegex = /(.*)\nLine: (\d+)\nColumn: (\d+)\nChar: (.*)/
|
||||
function parseError(err) {
|
||||
let matches = err.message.match(errorRegex);
|
||||
if (!matches)
|
||||
return null;
|
||||
|
||||
return {
|
||||
fatal: true,
|
||||
message: matches[1],
|
||||
line: parseInt(matches[2]) + 1,
|
||||
column: parseInt(matches[3])
|
||||
}
|
||||
}
|
||||
|
||||
let entityRegex = /&[\w][\w-\.]*;/g;
|
||||
|
||||
// A simple sax listener that generates a tree of element information
|
||||
function XMLParser(parser) {
|
||||
this.parser = parser;
|
||||
parser.onopentag = this.onOpenTag.bind(this);
|
||||
parser.onclosetag = this.onCloseTag.bind(this);
|
||||
parser.ontext = this.onText.bind(this);
|
||||
parser.onopencdata = this.onOpenCDATA.bind(this);
|
||||
parser.oncdata = this.onCDATA.bind(this);
|
||||
parser.oncomment = this.onComment.bind(this);
|
||||
|
||||
this.document = {
|
||||
local: "#document",
|
||||
uri: null,
|
||||
children: [],
|
||||
comments: [],
|
||||
}
|
||||
this._currentNode = this.document;
|
||||
}
|
||||
|
||||
XMLParser.prototype = {
|
||||
parser: null,
|
||||
|
||||
onOpenTag: function(tag) {
|
||||
let node = {
|
||||
parentNode: this._currentNode,
|
||||
local: tag.local,
|
||||
namespace: tag.uri,
|
||||
attributes: {},
|
||||
children: [],
|
||||
comments: [],
|
||||
textContent: "",
|
||||
textLine: this.parser.line,
|
||||
textColumn: this.parser.column,
|
||||
textEndLine: this.parser.line
|
||||
}
|
||||
|
||||
for (let attr of Object.keys(tag.attributes)) {
|
||||
if (tag.attributes[attr].uri == "") {
|
||||
node.attributes[attr] = tag.attributes[attr].value;
|
||||
}
|
||||
}
|
||||
|
||||
this._currentNode.children.push(node);
|
||||
this._currentNode = node;
|
||||
},
|
||||
|
||||
onCloseTag: function(tagname) {
|
||||
this._currentNode.textEndLine = this.parser.line;
|
||||
this._currentNode = this._currentNode.parentNode;
|
||||
},
|
||||
|
||||
addText: function(text) {
|
||||
this._currentNode.textContent += text;
|
||||
},
|
||||
|
||||
onText: function(text) {
|
||||
// Replace entities with some valid JS token.
|
||||
this.addText(text.replace(entityRegex, "null"));
|
||||
},
|
||||
|
||||
onOpenCDATA: function() {
|
||||
// Turn the CDATA opening tag into whitespace for indent alignment
|
||||
this.addText(" ".repeat("<![CDATA[".length));
|
||||
},
|
||||
|
||||
onCDATA: function(text) {
|
||||
this.addText(text);
|
||||
},
|
||||
|
||||
onComment: function(text) {
|
||||
this._currentNode.comments.push(text);
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Processor Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
const INDENT_LEVEL = 2;
|
||||
|
||||
function indent(count) {
|
||||
return " ".repeat(count * INDENT_LEVEL);
|
||||
}
|
||||
|
||||
// Stores any XML parse error
|
||||
let xmlParseError = null;
|
||||
|
||||
// Stores the lines of JS code generated from the XBL
|
||||
let scriptLines = [];
|
||||
// Stores a map from the synthetic line number to the real line number
|
||||
// and column offset.
|
||||
let lineMap = [];
|
||||
|
||||
function addSyntheticLine(line, linePos, addDisableLine) {
|
||||
lineMap[scriptLines.length] = { line: linePos, offset: null };
|
||||
scriptLines.push(line + (addDisableLine ? "" : " // eslint-disable-line"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds generated lines from an XBL node to the script to be passed back to eslint.
|
||||
*/
|
||||
function addNodeLines(node, reindent) {
|
||||
let lines = node.textContent.split("\n");
|
||||
let startLine = node.textLine;
|
||||
let startColumn = node.textColumn;
|
||||
|
||||
// The case where there are no whitespace lines before the first text is
|
||||
// treated differently for indentation
|
||||
let indentFirst = false;
|
||||
|
||||
// Strip off any preceeding whitespace only lines. These are often used to
|
||||
// format the XML and CDATA blocks.
|
||||
while (lines.length && lines[0].trim() == "") {
|
||||
indentFirst = true;
|
||||
startLine++;
|
||||
lines.shift();
|
||||
}
|
||||
|
||||
// Strip off any whitespace lines at the end. These are often used to line
|
||||
// up the closing tags
|
||||
while (lines.length && lines[lines.length - 1].trim() == "") {
|
||||
lines.pop();
|
||||
}
|
||||
|
||||
if (!indentFirst) {
|
||||
let firstLine = lines.shift();
|
||||
firstLine = " ".repeat(reindent * INDENT_LEVEL) + firstLine;
|
||||
// ESLint counts columns starting at 1 rather than 0
|
||||
lineMap[scriptLines.length] = { line: startLine, offset: reindent * INDENT_LEVEL - (startColumn - 1) };
|
||||
scriptLines.push(firstLine);
|
||||
startLine++;
|
||||
}
|
||||
|
||||
// Find the preceeding whitespace for all lines that aren't entirely whitespace
|
||||
let indents = lines.filter(s => s.trim().length > 0)
|
||||
.map(s => s.length - s.trimLeft().length);
|
||||
// Find the smallest indent level in use
|
||||
let minIndent = Math.min.apply(null, indents);
|
||||
|
||||
for (let line of lines) {
|
||||
if (line.trim().length == 0) {
|
||||
// Don't offset lines that are only whitespace, the only possible JS error
|
||||
// is trailing whitespace and we want it to point at the right place
|
||||
lineMap[scriptLines.length] = { line: startLine, offset: 0 };
|
||||
} else {
|
||||
line = " ".repeat(reindent * INDENT_LEVEL) + line.substring(minIndent);
|
||||
lineMap[scriptLines.length] = { line: startLine, offset: reindent * INDENT_LEVEL - (minIndent - 1) };
|
||||
}
|
||||
|
||||
scriptLines.push(line);
|
||||
startLine++;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
preprocess: function(text, filename) {
|
||||
xmlParseError = null;
|
||||
scriptLines = [];
|
||||
lineMap = [];
|
||||
|
||||
// Non-strict allows us to ignore many errors from entities and
|
||||
// preprocessing at the expense of failing to report some XML errors.
|
||||
// Unfortunately it also throws away the case of tagnames and attributes
|
||||
let parser = sax.parser(false, {
|
||||
lowercase: true,
|
||||
xmlns: true,
|
||||
});
|
||||
|
||||
parser.onerror = function(err) {
|
||||
xmlParseError = parseError(err);
|
||||
}
|
||||
|
||||
let xp = new XMLParser(parser);
|
||||
parser.write(text);
|
||||
|
||||
// Sanity checks to make sure we're dealing with an XBL document
|
||||
let document = xp.document;
|
||||
if (document.children.length != 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let bindings = document.children[0];
|
||||
if (bindings.local != "bindings" || bindings.namespace != NS_XBL) {
|
||||
return [];
|
||||
}
|
||||
|
||||
for (let comment of document.comments) {
|
||||
addSyntheticLine(`/*`, 0, true);
|
||||
for (let line of comment.split("\n")) {
|
||||
addSyntheticLine(`${line.trim()}`, 0, true);
|
||||
}
|
||||
addSyntheticLine(`*/`, 0, true);
|
||||
}
|
||||
|
||||
addSyntheticLine(`this.bindings = {`, bindings.textLine);
|
||||
|
||||
for (let binding of bindings.children) {
|
||||
if (binding.local != "binding" || binding.namespace != NS_XBL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addSyntheticLine(indent(1) + `"${binding.attributes.id}": {`, binding.textLine);
|
||||
|
||||
for (let part of binding.children) {
|
||||
if (part.namespace != NS_XBL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part.local == "implementation") {
|
||||
addSyntheticLine(indent(2) + `implementation: {`, part.textLine);
|
||||
} else if (part.local == "handlers") {
|
||||
addSyntheticLine(indent(2) + `handlers: [`, part.textLine);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let item of part.children) {
|
||||
if (item.namespace != NS_XBL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (item.local) {
|
||||
case "field": {
|
||||
// Fields are something like lazy getter functions
|
||||
|
||||
// Ignore empty fields
|
||||
if (item.textContent.trim().length == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
addSyntheticLine(indent(3) + `get ${item.attributes.name}() {`, item.textLine);
|
||||
addSyntheticLine(indent(4) + `return (`, item.textLine);
|
||||
|
||||
// Remove trailing semicolons, as we are adding our own
|
||||
item.textContent = item.textContent.replace(/;(?=\s*$)/, "");
|
||||
addNodeLines(item, 5);
|
||||
|
||||
addSyntheticLine(indent(4) + `);`, item.textLine);
|
||||
addSyntheticLine(indent(3) + `},`, item.textEndLine);
|
||||
break;
|
||||
}
|
||||
case "constructor":
|
||||
case "destructor": {
|
||||
// Constructors and destructors become function declarations
|
||||
addSyntheticLine(indent(3) + `${item.local}() {`, item.textLine);
|
||||
addNodeLines(item, 4);
|
||||
addSyntheticLine(indent(3) + `},`, item.textEndLine);
|
||||
break;
|
||||
}
|
||||
case "method": {
|
||||
// Methods become function declarations with the appropriate params
|
||||
|
||||
let params = item.children.filter(n => n.local == "parameter" && n.namespace == NS_XBL)
|
||||
.map(n => n.attributes.name)
|
||||
.join(", ");
|
||||
let body = item.children.filter(n => n.local == "body" && n.namespace == NS_XBL)[0];
|
||||
|
||||
addSyntheticLine(indent(3) + `${item.attributes.name}(${params}) {`, item.textLine);
|
||||
addNodeLines(body, 4);
|
||||
addSyntheticLine(indent(3) + `},`, item.textEndLine);
|
||||
break;
|
||||
}
|
||||
case "property": {
|
||||
// Properties become one or two function declarations
|
||||
for (let propdef of item.children) {
|
||||
if (propdef.namespace != NS_XBL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (propdef.local == "setter") {
|
||||
addSyntheticLine(indent(3) + `set ${item.attributes.name}(val) {`, propdef.textLine);
|
||||
} else if (propdef.local == "getter") {
|
||||
addSyntheticLine(indent(3) + `get ${item.attributes.name}() {`, propdef.textLine);
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
addNodeLines(propdef, 4);
|
||||
addSyntheticLine(indent(3) + `},`, propdef.textEndLine);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "handler": {
|
||||
// Handlers become a function declaration with an `event` parameter
|
||||
addSyntheticLine(indent(3) + `function(event) {`, item.textLine);
|
||||
addNodeLines(item, 4);
|
||||
addSyntheticLine(indent(3) + `},`, item.textEndLine);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
addSyntheticLine(indent(2) + (part.local == "implementation" ? `},` : `],`), part.textEndLine);
|
||||
}
|
||||
addSyntheticLine(indent(1) + `},`, binding.textEndLine);
|
||||
}
|
||||
addSyntheticLine(`};`, bindings.textEndLine);
|
||||
|
||||
let script = scriptLines.join("\n") + "\n";
|
||||
return [script];
|
||||
},
|
||||
|
||||
postprocess: function(messages, filename) {
|
||||
// If there was an XML parse error then just return that
|
||||
if (xmlParseError) {
|
||||
return [xmlParseError];
|
||||
}
|
||||
|
||||
// For every message from every script block update the line to point to the
|
||||
// correct place.
|
||||
let errors = [];
|
||||
for (let i = 0; i < messages.length; i++) {
|
||||
for (let message of messages[i]) {
|
||||
// ESLint indexes lines starting at 1 but our arrays start at 0
|
||||
let mapped = lineMap[message.line - 1];
|
||||
|
||||
message.line = mapped.line + 1;
|
||||
if (mapped.offset) {
|
||||
message.column -= mapped.offset;
|
||||
} else {
|
||||
message.column = NaN;
|
||||
}
|
||||
|
||||
errors.push(message);
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
};
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
"use strict";
|
||||
|
||||
/**
|
||||
* Based on npm coding standards at https://docs.npmjs.com/misc/coding-style.
|
||||
*
|
||||
* The places we differ from the npm coding style:
|
||||
* - Commas should be at the end of a line.
|
||||
* - Always use semicolons.
|
||||
* - Functions should not have whitespace before params.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
"env": {
|
||||
"node": true
|
||||
},
|
||||
|
||||
"rules": {
|
||||
"brace-style": ["error", "1tbs"],
|
||||
"camelcase": "error",
|
||||
"comma-dangle": ["error", "never"],
|
||||
"comma-spacing": "error",
|
||||
"comma-style": ["error", "last"],
|
||||
"curly": ["error", "multi-line"],
|
||||
"handle-callback-err": ["error", "er"],
|
||||
"indent": ["error", 2, {"SwitchCase": 1}],
|
||||
"max-len": ["error", 80, "error"],
|
||||
"no-multiple-empty-lines": ["error", {"max": 1}],
|
||||
"no-undef": "error",
|
||||
"no-undef-init": "error",
|
||||
"no-unexpected-multiline": "error",
|
||||
"object-curly-spacing": "off",
|
||||
"one-var": ["error", "never"],
|
||||
"operator-linebreak": ["error", "after"],
|
||||
"semi": ["error", "always"],
|
||||
"space-before-blocks": "error",
|
||||
"space-before-function-paren": ["error", "never"],
|
||||
"keyword-spacing": "error",
|
||||
"strict": ["error", "global"],
|
||||
},
|
||||
|
||||
// Globals accessible within node modules.
|
||||
"globals": {
|
||||
"DTRACE_HTTP_CLIENT_REQUEST": true,
|
||||
"DTRACE_HTTP_CLIENT_RESPONSE": true,
|
||||
"DTRACE_HTTP_SERVER_REQUEST": true,
|
||||
"DTRACE_HTTP_SERVER_RESPONSE": true,
|
||||
"DTRACE_NET_SERVER_CONNECTION": true,
|
||||
"DTRACE_NET_STREAM_END": true,
|
||||
"Intl": true,
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1,113 @@
|
|||
/**
|
||||
* @fileoverview Check that there's a removeEventListener for each
|
||||
* addEventListener and an off for each on.
|
||||
* Note that for now, this rule is rather simple in that it only checks that
|
||||
* for each event name there is both an add and remove listener. It doesn't
|
||||
* check that these are called on the right objects or with the same callback.
|
||||
*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
var DICTIONARY = {
|
||||
"addEventListener": "removeEventListener",
|
||||
"on": "off"
|
||||
};
|
||||
// Invert this dictionary to make it easy later.
|
||||
var INVERTED_DICTIONARY = {};
|
||||
for (var i in DICTIONARY) {
|
||||
INVERTED_DICTIONARY[DICTIONARY[i]] = i;
|
||||
}
|
||||
|
||||
// Collect the add/remove listeners in these 2 arrays.
|
||||
var addedListeners = [];
|
||||
var removedListeners = [];
|
||||
|
||||
function addAddedListener(node) {
|
||||
addedListeners.push({
|
||||
functionName: node.callee.property.name,
|
||||
type: node.arguments[0].value,
|
||||
node: node.callee.property,
|
||||
useCapture: node.arguments[2] ? node.arguments[2].value : null
|
||||
});
|
||||
}
|
||||
|
||||
function addRemovedListener(node) {
|
||||
removedListeners.push({
|
||||
functionName: node.callee.property.name,
|
||||
type: node.arguments[0].value,
|
||||
useCapture: node.arguments[2] ? node.arguments[2].value : null
|
||||
});
|
||||
}
|
||||
|
||||
function getUnbalancedListeners() {
|
||||
var unbalanced = [];
|
||||
|
||||
for (var j = 0; j < addedListeners.length; j++) {
|
||||
if (!hasRemovedListener(addedListeners[j])) {
|
||||
unbalanced.push(addedListeners[j]);
|
||||
}
|
||||
}
|
||||
addedListeners = removedListeners = [];
|
||||
|
||||
return unbalanced;
|
||||
}
|
||||
|
||||
function hasRemovedListener(addedListener) {
|
||||
for (var k = 0; k < removedListeners.length; k++) {
|
||||
var listener = removedListeners[k];
|
||||
if (DICTIONARY[addedListener.functionName] === listener.functionName &&
|
||||
addedListener.type === listener.type &&
|
||||
addedListener.useCapture === listener.useCapture) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
CallExpression: function(node) {
|
||||
if (node.arguments.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.callee.type === "MemberExpression") {
|
||||
var listenerMethodName = node.callee.property.name;
|
||||
|
||||
if (DICTIONARY.hasOwnProperty(listenerMethodName)) {
|
||||
addAddedListener(node);
|
||||
} else if (INVERTED_DICTIONARY.hasOwnProperty(listenerMethodName)) {
|
||||
addRemovedListener(node);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
"Program:exit": function() {
|
||||
getUnbalancedListeners().forEach(function(listener) {
|
||||
context.report(listener.node,
|
||||
"No corresponding '{{functionName}}({{type}})' was found.",
|
||||
{
|
||||
functionName: DICTIONARY[listener.functionName],
|
||||
type: listener.type
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* @fileoverview Import globals from browser.js.
|
||||
*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var helpers = require("../helpers");
|
||||
var globals = require("../globals");
|
||||
|
||||
const SCRIPTS = [
|
||||
//"browser/base/content/nsContextMenu.js",
|
||||
"toolkit/content/contentAreaUtils.js",
|
||||
"browser/components/places/content/editBookmarkOverlay.js",
|
||||
"toolkit/components/printing/content/printUtils.js",
|
||||
"toolkit/content/viewZoomOverlay.js",
|
||||
"browser/components/places/content/browserPlacesViews.js",
|
||||
"browser/base/content/browser.js",
|
||||
"browser/components/downloads/content/downloads.js",
|
||||
"browser/components/downloads/content/indicator.js",
|
||||
"browser/components/customizableui/content/panelUI.js",
|
||||
"toolkit/components/viewsource/content/viewSourceUtils.js",
|
||||
"browser/base/content/browser-addons.js",
|
||||
"browser/base/content/browser-ctrlTab.js",
|
||||
"browser/base/content/browser-customization.js",
|
||||
"browser/base/content/browser-devedition.js",
|
||||
"browser/base/content/browser-feeds.js",
|
||||
"browser/base/content/browser-fullScreenAndPointerLock.js",
|
||||
"browser/base/content/browser-fullZoom.js",
|
||||
"browser/base/content/browser-gestureSupport.js",
|
||||
"browser/base/content/browser-media.js",
|
||||
"browser/base/content/browser-places.js",
|
||||
"browser/base/content/browser-plugins.js",
|
||||
"browser/base/content/browser-refreshblocker.js",
|
||||
"browser/base/content/browser-safebrowsing.js",
|
||||
"browser/base/content/browser-sidebar.js",
|
||||
"browser/base/content/browser-social.js",
|
||||
"browser/base/content/browser-syncui.js",
|
||||
"browser/base/content/browser-tabsintitlebar.js",
|
||||
"browser/base/content/browser-thumbnails.js",
|
||||
"browser/base/content/browser-trackingprotection.js",
|
||||
"browser/base/content/browser-data-submission-info-bar.js",
|
||||
"browser/base/content/browser-fxaccounts.js"
|
||||
];
|
||||
|
||||
module.exports = function(context) {
|
||||
return {
|
||||
Program: function(node) {
|
||||
if (helpers.getTestType(this) != "browser" &&
|
||||
!helpers.getIsHeadFile(this)) {
|
||||
return;
|
||||
}
|
||||
|
||||
let filepath = helpers.getAbsoluteFilePath(context);
|
||||
let root = helpers.getRootDir(filepath);
|
||||
for (let script of SCRIPTS) {
|
||||
let fileName = path.join(root, script);
|
||||
try {
|
||||
let newGlobals = globals.getGlobalsForFile(fileName);
|
||||
helpers.addGlobals(newGlobals, context.getScope());
|
||||
} catch (e) {
|
||||
context.report(
|
||||
node,
|
||||
"Could not load globals from file {{filePath}}: {{error}}",
|
||||
{
|
||||
filePath: path.relative(root, fileName),
|
||||
error: e
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* @fileoverview Discovers all globals for the current file.
|
||||
*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
module.exports = require("../globals").getESLintGlobalParser;
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @fileoverview Import globals from head.js and from any files that were
|
||||
* imported by head.js (as far as we can correctly resolve the path).
|
||||
*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
var fs = require("fs");
|
||||
var path = require("path");
|
||||
var helpers = require("../helpers");
|
||||
var globals = require("../globals");
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
function importHead(path, node) {
|
||||
try {
|
||||
let stats = fs.statSync(path);
|
||||
if (!stats.isFile()) {
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
return;
|
||||
}
|
||||
|
||||
let newGlobals = globals.getGlobalsForFile(path);
|
||||
helpers.addGlobals(newGlobals, context.getScope());
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
Program: function(node) {
|
||||
let heads = helpers.getTestHeadFiles(this);
|
||||
for (let head of heads) {
|
||||
importHead(head, node);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @fileoverview Simply marks `test` (the test method) or `run_test` as used when
|
||||
* in mochitests or xpcshell tests respectively. This avoids ESLint telling us
|
||||
* that the function is never called.
|
||||
*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
var helpers = require("../helpers");
|
||||
|
||||
module.exports = function(context) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
Program: function() {
|
||||
if (helpers.getTestType(this) == "browser") {
|
||||
context.markVariableAsUsed("test");
|
||||
return;
|
||||
}
|
||||
|
||||
if (helpers.getTestType(this) == "xpcshell") {
|
||||
context.markVariableAsUsed("run_test");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @fileoverview warns against using hungarian notation in function arguments
|
||||
* (i.e. aArg).
|
||||
*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function isPrefixed(name) {
|
||||
return name.length >= 2 && /^a[A-Z]/.test(name);
|
||||
}
|
||||
|
||||
function deHungarianize(name) {
|
||||
return name.substring(1, 2).toLowerCase() +
|
||||
name.substring(2, name.length);
|
||||
}
|
||||
|
||||
function checkFunction(node) {
|
||||
for (var i = 0; i < node.params.length; i++) {
|
||||
var param = node.params[i];
|
||||
if (param.name && isPrefixed(param.name)) {
|
||||
var errorObj = {
|
||||
name: param.name,
|
||||
suggestion: deHungarianize(param.name)
|
||||
};
|
||||
context.report(param,
|
||||
"Parameter '{{name}}' uses Hungarian Notation, " +
|
||||
"consider using '{{suggestion}}' instead.",
|
||||
errorObj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
"FunctionDeclaration": checkFunction,
|
||||
"ArrowFunctionExpression": checkFunction,
|
||||
"FunctionExpression": checkFunction
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
/**
|
||||
* @fileoverview Prevent access to CPOWs in browser mochitests.
|
||||
*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
var helpers = require("../helpers");
|
||||
|
||||
var cpows = [
|
||||
/^gBrowser\.contentWindow/,
|
||||
/^gBrowser\.contentDocument/,
|
||||
/^gBrowser\.selectedBrowser.contentWindow/,
|
||||
/^browser\.contentDocument/,
|
||||
/^window\.content/
|
||||
];
|
||||
|
||||
var isInContentTask = false;
|
||||
|
||||
module.exports = function(context) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function showError(node, identifier) {
|
||||
if (isInContentTask) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: node,
|
||||
message: identifier +
|
||||
" is a possible Cross Process Object Wrapper (CPOW)."
|
||||
});
|
||||
}
|
||||
|
||||
function isContentTask(node) {
|
||||
return node &&
|
||||
node.type === "MemberExpression" &&
|
||||
node.property.type === "Identifier" &&
|
||||
node.property.name === "spawn" &&
|
||||
node.object.type === "Identifier" &&
|
||||
node.object.name === "ContentTask";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
CallExpression: function(node) {
|
||||
if (isContentTask(node.callee)) {
|
||||
isInContentTask = true;
|
||||
}
|
||||
},
|
||||
|
||||
"CallExpression:exit": function(node) {
|
||||
if (isContentTask(node.callee)) {
|
||||
isInContentTask = false;
|
||||
}
|
||||
},
|
||||
|
||||
MemberExpression: function(node) {
|
||||
if (helpers.getTestType(this) != "browser") {
|
||||
return;
|
||||
}
|
||||
|
||||
var expression = context.getSource(node);
|
||||
|
||||
// Only report a single CPOW error per node -- so if checking
|
||||
// |cpows| reports one, don't report another below.
|
||||
var someCpowFound = cpows.some(function(cpow) {
|
||||
if (cpow.test(expression)) {
|
||||
showError(node, expression);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
if (!someCpowFound && helpers.getIsGlobalScope(context.getAncestors())) {
|
||||
if (/^content\./.test(expression)) {
|
||||
showError(node, expression);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Identifier: function(node) {
|
||||
if (helpers.getTestType(this) != "browser") {
|
||||
return;
|
||||
}
|
||||
|
||||
var expression = context.getSource(node);
|
||||
if (expression == "content" || /^content\./.test(expression)) {
|
||||
if (node.parent.type === "MemberExpression" &&
|
||||
node.parent.object &&
|
||||
node.parent.object.type === "Identifier" &&
|
||||
node.parent.object.name != "content") {
|
||||
return;
|
||||
}
|
||||
showError(node, expression);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* @fileoverview Reject use of single argument Cu.import
|
||||
*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
var helpers = require("../helpers");
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
"CallExpression": function(node) {
|
||||
if (node.callee.type === "MemberExpression") {
|
||||
let memexp = node.callee;
|
||||
if (memexp.object.type === "Identifier" &&
|
||||
// Only Cu, not Components.utils; see bug 1230369.
|
||||
memexp.object.name === "Cu" &&
|
||||
memexp.property.type === "Identifier" &&
|
||||
memexp.property.name === "import" &&
|
||||
node.arguments.length === 1) {
|
||||
context.report(node, "Single argument Cu.import exposes new " +
|
||||
"globals to all modules");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/**
|
||||
* @fileoverview Reject use of Cu.importGlobalProperties
|
||||
*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
var helpers = require("../helpers");
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
"CallExpression": function(node) {
|
||||
if (node.callee.type === "MemberExpression") {
|
||||
let memexp = node.callee;
|
||||
if (memexp.object.type === "Identifier" &&
|
||||
// Only Cu, not Components.utils; see bug 1230369.
|
||||
memexp.object.name === "Cu" &&
|
||||
memexp.property.type === "Identifier" &&
|
||||
memexp.property.name === "importGlobalProperties") {
|
||||
context.report(node, "Unexpected call to Cu.importGlobalProperties");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* @fileoverview Reject some uses of require.
|
||||
*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
if (typeof(context.options[0]) !== "string") {
|
||||
throw new Error("reject-some-requires expects a regexp");
|
||||
}
|
||||
const RX = new RegExp(context.options[0]);
|
||||
|
||||
const checkPath = function(node, path) {
|
||||
if (RX.test(path)) {
|
||||
context.report(node, `require(${path}) is not allowed`);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
"CallExpression": function(node) {
|
||||
if (node.callee.type == "Identifier" &&
|
||||
node.callee.name == "require" &&
|
||||
node.arguments.length == 1 &&
|
||||
node.arguments[0].type == "Literal") {
|
||||
checkPath(node, node.arguments[0].value);
|
||||
} else if (node.callee.type == "MemberExpression" &&
|
||||
node.callee.property.type == "Identifier" &&
|
||||
node.callee.property.name == "lazyRequireGetter" &&
|
||||
node.arguments.length >= 3 &&
|
||||
node.arguments[2].type == "Literal") {
|
||||
checkPath(node, node.arguments[2].value);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* @fileoverview Marks all var declarations that are not at the top level
|
||||
* invalid.
|
||||
*
|
||||
* 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/.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
// -----------------------------------------------------------------------------
|
||||
|
||||
var helpers = require("../helpers");
|
||||
|
||||
module.exports = function(context) {
|
||||
// ---------------------------------------------------------------------------
|
||||
// Public
|
||||
// --------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
"VariableDeclaration": function(node) {
|
||||
if (node.kind === "var") {
|
||||
if (helpers.getIsGlobalScope(context.getAncestors())) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report(node, "Unexpected var, use let or const instead.");
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
29
tools/lint/eslint/eslint-plugin-mozilla/package.json
Normal file
29
tools/lint/eslint/eslint-plugin-mozilla/package.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "eslint-plugin-mozilla",
|
||||
"version": "0.2.5",
|
||||
"description": "A collection of rules that help enforce JavaScript coding standard in the Mozilla project.",
|
||||
"keywords": [
|
||||
"eslint",
|
||||
"eslintplugin",
|
||||
"eslint-plugin",
|
||||
"mozilla",
|
||||
"firefox"
|
||||
],
|
||||
"bugs": {
|
||||
"url": "https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox&component=Developer%20Tools"
|
||||
},
|
||||
"homepage": "https://bugzilla.mozilla.org/show_bug.cgi?id=1203520",
|
||||
"author": "Mike Ratcliffe",
|
||||
"main": "lib/index.js",
|
||||
"dependencies": {
|
||||
"escope": "^3.6.0",
|
||||
"espree": "^3.2.0",
|
||||
"estraverse": "^4.2.0",
|
||||
"ini-parser": "^0.0.2",
|
||||
"sax": "^1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
},
|
||||
"license": "MPL-2.0"
|
||||
}
|
||||
9
tools/lint/eslint/manifest.tt
Normal file
9
tools/lint/eslint/manifest.tt
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
[
|
||||
{
|
||||
"size": 2768586,
|
||||
"visibility": "public",
|
||||
"digest": "1b9a7c5b1ca8d7d2aeb803055129d1cb0fa0d17b90dd3cf852ea77d86d1b1d9d09f34007a71908074afef9ce1ab87972a5cf16a36952e1a6159f7abfc6fa15b3",
|
||||
"algorithm": "sha512",
|
||||
"filename": "eslint.tar.gz"
|
||||
}
|
||||
]
|
||||
247
tools/lint/eslint/modules.json
Normal file
247
tools/lint/eslint/modules.json
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
{
|
||||
"AboutHome.jsm": ["AboutHomeUtils", "AboutHome"],
|
||||
"AddonLogging.jsm": ["LogManager"],
|
||||
"AddonManager.jsm": ["AddonManager", "AddonManagerPrivate"],
|
||||
"addons.js": ["AddonsEngine", "AddonValidator"],
|
||||
"addons.jsm": ["Addon", "STATE_ENABLED", "STATE_DISABLED"],
|
||||
"addonsreconciler.js": ["AddonsReconciler", "CHANGE_INSTALLED", "CHANGE_UNINSTALLED", "CHANGE_ENABLED", "CHANGE_DISABLED"],
|
||||
"AddonTestUtils.jsm": ["AddonTestUtils", "MockAsyncShutdown"],
|
||||
"addonutils.js": ["AddonUtils"],
|
||||
"ajv-4.1.1.js": ["Ajv"],
|
||||
"AlertsHelper.jsm": [],
|
||||
"AppData.jsm": ["makeFakeAppDir"],
|
||||
"AppInfo.jsm": ["newAppInfo", "getAppInfo", "updateAppInfo"],
|
||||
"AppsServiceChild.jsm": ["DOMApplicationRegistry", "WrappedManifestCache"],
|
||||
"arrays.js": ["inArray", "getSet", "indexOf", "remove", "rindexOf", "compare"],
|
||||
"assertions.js": ["Assert", "Expect"],
|
||||
"async.js": ["Async"],
|
||||
"AsyncSpellCheckTestHelper.jsm": ["onSpellCheck"],
|
||||
"Battery.jsm": ["GetBattery", "Battery"],
|
||||
"blocklist-clients.js": ["AddonBlocklistClient", "GfxBlocklistClient", "OneCRLBlocklistClient", "PluginBlocklistClient", "FILENAME_ADDONS_JSON", "FILENAME_GFX_JSON", "FILENAME_PLUGINS_JSON"],
|
||||
"blocklist-updater.js": ["checkVersions", "addTestBlocklistClient"],
|
||||
"bogus_element_type.jsm": [],
|
||||
"bookmark_validator.js": ["BookmarkValidator", "BookmarkProblemData"],
|
||||
"bookmarks.js": ["BookmarksEngine", "PlacesItem", "Bookmark", "BookmarkFolder", "BookmarkQuery", "Livemark", "BookmarkSeparator"],
|
||||
"bookmarks.jsm": ["PlacesItem", "Bookmark", "Separator", "Livemark", "BookmarkFolder", "DumpBookmarks"],
|
||||
"BootstrapMonitor.jsm": ["monitor"],
|
||||
"browser-loader.js": ["BrowserLoader"],
|
||||
"browserid_identity.js": ["BrowserIDManager", "AuthenticationError"],
|
||||
"CertUtils.jsm": ["BadCertHandler", "checkCert", "readCertPrefs", "validateCert"],
|
||||
"clients.js": ["ClientEngine", "ClientsRec"],
|
||||
"CloudSyncAdapters.jsm": ["Adapters"],
|
||||
"CloudSyncBookmarks.jsm": ["Bookmarks"],
|
||||
"CloudSyncBookmarksFolderCache.jsm": ["FolderCache"],
|
||||
"CloudSyncEventSource.jsm": ["EventSource"],
|
||||
"CloudSyncLocal.jsm": ["Local"],
|
||||
"CloudSyncPlacesWrapper.jsm": ["PlacesWrapper"],
|
||||
"CloudSyncTabs.jsm": ["Tabs"],
|
||||
"cluster.js": ["ClusterManager"],
|
||||
"collection_validator.js": ["CollectionValidator", "CollectionProblemData"],
|
||||
"Console.jsm": ["console", "ConsoleAPI"],
|
||||
"Constants.jsm": ["Roles", "Events", "Relations", "Filters", "States", "Prefilters"],
|
||||
"ContactDB.jsm": ["ContactDB", "DB_NAME", "STORE_NAME", "SAVED_GETALL_STORE_NAME", "REVISION_STORE", "DB_VERSION"],
|
||||
"content-server.jsm": ["init"],
|
||||
"content.jsm": ["registerContentFrame"],
|
||||
"ContentCrashHandlers.jsm": ["TabCrashHandler", "PluginCrashReporter", "UnsubmittedCrashHandler"],
|
||||
"ContentObservers.jsm": [],
|
||||
"ContentPrefUtils.jsm": ["ContentPref", "cbHandleResult", "cbHandleError", "cbHandleCompletion", "safeCallback"],
|
||||
"controller.js": ["MozMillController", "globalEventRegistry", "sleep", "windowMap"],
|
||||
"cookies.js": ["Cookies"],
|
||||
"CoverageUtils.jsm": ["CoverageCollector"],
|
||||
"CrashManagerTest.jsm": ["configureLogging", "getManager", "sleep", "TestingCrashManager"],
|
||||
"dbg-client.jsm": ["DebuggerTransport", "DebuggerClient", "RootClient", "LongStringClient", "EnvironmentClient", "ObjectClient"],
|
||||
"dbg-server.jsm": ["DebuggerServer", "ActorPool", "OriginalLocation"],
|
||||
"debug.js": ["NS_ASSERT"],
|
||||
"declined.js": ["DeclinedEngines"],
|
||||
"dispatcher.js": ["Dispatcher"],
|
||||
"distribution.js": ["DistributionCustomizer"],
|
||||
"DNSTypes.jsm": ["DNS_QUERY_RESPONSE_CODES", "DNS_AUTHORITATIVE_ANSWER_CODES", "DNS_CLASS_CODES", "DNS_RECORD_TYPES"],
|
||||
"dom.js": ["getAttributes"],
|
||||
"DOMRequestHelper.jsm": ["DOMRequestIpcHelper"],
|
||||
"DownloadCore.jsm": ["Download", "DownloadSource", "DownloadTarget", "DownloadError", "DownloadSaver", "DownloadCopySaver", "DownloadLegacySaver", "DownloadPDFSaver"],
|
||||
"DownloadList.jsm": ["DownloadList", "DownloadCombinedList", "DownloadSummary"],
|
||||
"E10SAddonsRollout.jsm": ["isAddonPartOfE10SRollout"],
|
||||
"elementslib.js": ["ID", "Link", "XPath", "Selector", "Name", "Anon", "AnonXPath", "Lookup", "_byID", "_byName", "_byAttrib", "_byAnonAttrib"],
|
||||
"engines.js": ["EngineManager", "Engine", "SyncEngine", "Tracker", "Store", "Changeset"],
|
||||
"enginesync.js": ["EngineSynchronizer"],
|
||||
"errors.js": ["BaseError", "ApplicationQuitError", "AssertionError", "TimeoutError"],
|
||||
"evaluate.js": ["evaluate", "sandbox", "Sandboxes"],
|
||||
"event-emitter.js": ["EventEmitter"],
|
||||
"EventUtils.js": ["disableNonTestMouseEvents", "sendMouseEvent", "sendChar", "sendString", "sendKey", "synthesizeMouse", "synthesizeTouch", "synthesizeMouseAtPoint", "synthesizeTouchAtPoint", "synthesizeMouseAtCenter", "synthesizeTouchAtCenter", "synthesizeWheel", "synthesizeKey", "synthesizeMouseExpectEvent", "synthesizeKeyExpectEvent", "synthesizeText", "synthesizeComposition", "synthesizeQuerySelectedText"],
|
||||
"Extension.jsm": ["Extension", "ExtensionData"],
|
||||
"ExtensionAPI.jsm": ["ExtensionAPI", "ExtensionAPIs"],
|
||||
"ExtensionXPCShellUtils.jsm": ["ExtensionTestUtils"],
|
||||
"fakeservices.js": ["FakeCryptoService", "FakeFilesystemService", "FakeGUIDService", "fakeSHA256HMAC"],
|
||||
"file_expandosharing.jsm": ["checkFromJSM"],
|
||||
"file_stringencoding.jsm": ["checkFromJSM"],
|
||||
"file_url.jsm": ["checkFromJSM"],
|
||||
"file_worker_url.jsm": ["checkFromJSM"],
|
||||
"Finder.jsm": ["Finder", "GetClipboardSearchString"],
|
||||
"FormAutofillContent.jsm": ["FormAutofillHandler"],
|
||||
"forms.js": ["FormEngine", "FormRec", "FormValidator"],
|
||||
"forms.jsm": ["FormData"],
|
||||
"frame.js": ["Collector", "Runner", "events", "runTestFile", "log", "timers", "persisted", "shutdownApplication"],
|
||||
"FrameScriptManager.jsm": ["getNewLoaderID"],
|
||||
"fxa_utils.js": ["initializeIdentityWithTokenServerResponse"],
|
||||
"fxaccounts.jsm": ["Authentication"],
|
||||
"FxAccounts.jsm": ["fxAccounts", "FxAccounts"],
|
||||
"FxAccountsOAuthGrantClient.jsm": ["FxAccountsOAuthGrantClient", "FxAccountsOAuthGrantClientError"],
|
||||
"FxAccountsProfileClient.jsm": ["FxAccountsProfileClient", "FxAccountsProfileClientError"],
|
||||
"FxAccountsPush.js": ["FxAccountsPushService"],
|
||||
"FxAccountsStorage.jsm": ["FxAccountsStorageManagerCanStoreField", "FxAccountsStorageManager"],
|
||||
"FxAccountsWebChannel.jsm": ["EnsureFxAccountsWebChannel"],
|
||||
"FxaMigrator.jsm": ["fxaMigrator"],
|
||||
"gDevTools.jsm": ["gDevTools", "gDevToolsBrowser"],
|
||||
"gDevTools.jsm": ["gDevTools", "gDevToolsBrowser"],
|
||||
"Geometry.jsm": ["Point", "Rect"],
|
||||
"Gestures.jsm": ["GestureSettings", "GestureTracker"],
|
||||
"GMPInstallManager.jsm": ["GMPInstallManager", "GMPExtractor", "GMPDownloader", "GMPAddon"],
|
||||
"GMPProvider.jsm": [],
|
||||
"GMPUtils.jsm": ["EME_ADOBE_ID", "GMP_PLUGIN_IDS", "GMPPrefs", "GMPUtils", "OPEN_H264_ID", "WIDEVINE_ID"],
|
||||
"hawkclient.js": ["HawkClient"],
|
||||
"hawkrequest.js": ["HAWKAuthenticatedRESTRequest", "deriveHawkCredentials"],
|
||||
"HelperApps.jsm": ["App", "HelperApps"],
|
||||
"history.js": ["HistoryEngine", "HistoryRec"],
|
||||
"history.jsm": ["HistoryEntry", "DumpHistory"],
|
||||
"Http.jsm": ["httpRequest", "percentEncode"],
|
||||
"httpd.js": ["HTTP_400", "HTTP_401", "HTTP_402", "HTTP_403", "HTTP_404", "HTTP_405", "HTTP_406", "HTTP_407", "HTTP_408", "HTTP_409", "HTTP_410", "HTTP_411", "HTTP_412", "HTTP_413", "HTTP_414", "HTTP_415", "HTTP_417", "HTTP_500", "HTTP_501", "HTTP_502", "HTTP_503", "HTTP_504", "HTTP_505", "HttpError", "HttpServer"],
|
||||
"identity.js": ["IdentityManager"],
|
||||
"Identity.jsm": ["IdentityService"],
|
||||
"IdentityUtils.jsm": ["checkDeprecated", "checkRenamed", "getRandomId", "objectCopy", "makeMessageObject"],
|
||||
"import_module.jsm": ["MODULE_IMPORTED", "MODULE_URI", "SUBMODULE_IMPORTED", "same_scope", "SUBMODULE_IMPORTED_TO_SCOPE"],
|
||||
"import_sub_module.jsm": ["SUBMODULE_IMPORTED", "test_obj"],
|
||||
"InlineSpellChecker.jsm": ["InlineSpellChecker", "SpellCheckHelper"],
|
||||
"JNI.jsm": ["JNI", "android_log"],
|
||||
"jpakeclient.js": ["JPAKEClient", "SendCredentialsController"],
|
||||
"Jsbeautify.jsm": ["jsBeautify"],
|
||||
"jsdebugger.jsm": ["addDebuggerToGlobal"],
|
||||
"json2.js": ["JSON"],
|
||||
"keys.js": ["BulkKeyBundle", "SyncKeyBundle"],
|
||||
"KeyValueParser.jsm": ["parseKeyValuePairsFromLines", "parseKeyValuePairs", "parseKeyValuePairsFromFile"],
|
||||
"kinto-http-client.js": ["KintoHttpClient"],
|
||||
"kinto-offline-client.js": ["loadKinto"],
|
||||
"loader-plugin-raw.jsm": ["requireRawId"],
|
||||
"loader.js": ["WorkerDebuggerLoader", "worker"],
|
||||
"Loader.jsm": ["DevToolsLoader", "devtools", "BuiltinProvider", "require", "loader"],
|
||||
"logger.jsm": ["Logger"],
|
||||
"logging.js": ["getTestLogger", "initTestLogging"],
|
||||
"LoginManagerContent.jsm": ["LoginManagerContent", "LoginFormFactory", "UserAutoCompleteResult"],
|
||||
"LoginRecipes.jsm": ["LoginRecipesContent", "LoginRecipesParent"],
|
||||
"logmanager.js": ["LogManager"],
|
||||
"LogUtils.jsm": ["Logger"],
|
||||
"lz4.js": ["Lz4"],
|
||||
"lz4_internal.js": ["Primitives"],
|
||||
"main.js": ["Weave"],
|
||||
"MatchPattern.jsm": ["MatchPattern", "MatchGlobs", "MatchURLFilters"],
|
||||
"mcc_iso3166_table.jsm": ["MCC_ISO3166_TABLE"],
|
||||
"message.js": ["Command", "Message", "MessageOrigin", "Response"],
|
||||
"Messaging.jsm": ["sendMessageToJava", "Messaging"],
|
||||
"microformat-shiv.js": ["Microformats"],
|
||||
"MigrationUtils.jsm": ["MigrationUtils", "MigratorPrototype"],
|
||||
"MinimalIdentity.jsm": ["IdentityService"],
|
||||
"mozelement.js": ["Elem", "Selector", "ID", "Link", "XPath", "Name", "Lookup", "MozMillElement", "MozMillCheckBox", "MozMillRadio", "MozMillDropList", "MozMillTextBox", "subclasses"],
|
||||
"mozmill.js": ["controller", "utils", "elementslib", "os", "getBrowserController", "newBrowserController", "getAddonsController", "getPreferencesController", "newMail3PaneController", "getMail3PaneController", "wm", "platform", "getAddrbkController", "getMsgComposeController", "getDownloadsController", "Application", "findElement", "getPlacesController", "isMac", "isLinux", "isWindows", "firePythonCallback", "getAddons"],
|
||||
"msgbroker.js": ["addListener", "addObject", "removeListener", "sendMessage", "log", "pass", "fail"],
|
||||
"MulticastDNSAndroid.jsm": ["MulticastDNS"],
|
||||
"NativeMessaging.jsm": ["HostManifestManager", "NativeApp"],
|
||||
"NetworkPrioritizer.jsm": ["trackBrowserWindow"],
|
||||
"NotificationDB.jsm": [],
|
||||
"nsFormAutoCompleteResult.jsm": ["FormAutoCompleteResult"],
|
||||
"objects.js": ["getLength"],
|
||||
"observers.js": ["Observers"],
|
||||
"offlineAppCache.jsm": ["OfflineAppCacheHelper"],
|
||||
"OrientationChangeHandler.jsm": [],
|
||||
"os.js": ["listDirectory", "getFileForPath", "abspath", "getPlatform"],
|
||||
"osfile.jsm": ["OS"],
|
||||
"osfile_async_front.jsm": ["OS"],
|
||||
"osfile_native.jsm": ["read"],
|
||||
"osfile_shared_allthreads.jsm": ["LOG", "clone", "Config", "Constants", "Type", "HollowStructure", "OSError", "Library", "declareFFI", "declareLazy", "declareLazyFFI", "normalizeBufferArgs", "projectValue", "isArrayBuffer", "isTypedArray", "defineLazyGetter", "OS"],
|
||||
"osfile_unix_allthreads.jsm": ["declareFFI", "libc", "Error", "AbstractInfo", "AbstractEntry", "Type", "POS_START", "POS_CURRENT", "POS_END"],
|
||||
"osfile_win_allthreads.jsm": ["declareFFI", "libc", "Error", "AbstractInfo", "AbstractEntry", "Type", "POS_START", "POS_CURRENT", "POS_END"],
|
||||
"ospath_unix.jsm": ["basename", "dirname", "join", "normalize", "split", "toFileURI", "fromFileURI"],
|
||||
"ospath_win.jsm": ["basename", "dirname", "join", "normalize", "split", "winGetDrive", "winIsAbsolute", "toFileURI", "fromFileURI"],
|
||||
"OutputGenerator.jsm": ["UtteranceGenerator", "BrailleGenerator"],
|
||||
"PageMenu.jsm": ["PageMenuParent", "PageMenuChild"],
|
||||
"PageThumbs.jsm": ["PageThumbs", "PageThumbsStorage"],
|
||||
"Parser.jsm": ["Parser", "ParserHelpers", "SyntaxTreeVisitor"],
|
||||
"parsingTestHelpers.jsm": ["generateURIsFromDirTree"],
|
||||
"passwords.js": ["PasswordEngine", "LoginRec", "PasswordValidator"],
|
||||
"passwords.jsm": ["Password", "DumpPasswords"],
|
||||
"PdfJsNetwork.jsm": ["NetworkManager"],
|
||||
"PermissionSettings.jsm": ["PermissionSettingsModule"],
|
||||
"PermissionsTable.jsm": ["PermissionsTable", "PermissionsReverseTable", "expandPermissions", "appendAccessToPermName", "isExplicitInPermissionsTable", "AllPossiblePermissions"],
|
||||
"PhoneNumberMetaData.jsm": ["PHONE_NUMBER_META_DATA"],
|
||||
"PlacesUtils.jsm": ["PlacesUtils", "PlacesAggregatedTransaction", "PlacesCreateFolderTransaction", "PlacesCreateBookmarkTransaction", "PlacesCreateSeparatorTransaction", "PlacesCreateLivemarkTransaction", "PlacesMoveItemTransaction", "PlacesRemoveItemTransaction", "PlacesEditItemTitleTransaction", "PlacesEditBookmarkURITransaction", "PlacesSetItemAnnotationTransaction", "PlacesSetPageAnnotationTransaction", "PlacesEditBookmarkKeywordTransaction", "PlacesEditBookmarkPostDataTransaction", "PlacesEditItemDateAddedTransaction", "PlacesEditItemLastModifiedTransaction", "PlacesSortFolderByNameTransaction", "PlacesTagURITransaction", "PlacesUntagURITransaction"],
|
||||
"PluginProvider.jsm": [],
|
||||
"PointerAdapter.jsm": ["PointerRelay", "PointerAdapter"],
|
||||
"policies.js": ["ErrorHandler", "SyncScheduler"],
|
||||
"prefs.js": ["PrefsEngine", "PrefRec"],
|
||||
"prefs.jsm": ["Preference"],
|
||||
"PresentationDeviceInfoManager.jsm": ["PresentationDeviceInfoService"],
|
||||
"PromiseWorker.jsm": ["BasePromiseWorker"],
|
||||
"PushCrypto.jsm": ["PushCrypto", "concatArray"],
|
||||
"quit.js": ["goQuitApplication"],
|
||||
"record.js": ["WBORecord", "RecordManager", "CryptoWrapper", "CollectionKeyManager", "Collection"],
|
||||
"recursive_importA.jsm": ["foo", "bar"],
|
||||
"recursive_importB.jsm": ["baz", "qux"],
|
||||
"reflect.jsm": ["Reflect"],
|
||||
"RemoteFinder.jsm": ["RemoteFinder", "RemoteFinderListener"],
|
||||
"RemotePageManager.jsm": ["RemotePages", "RemotePageManager", "PageListener"],
|
||||
"RemoteWebProgress.jsm": ["RemoteWebProgressManager"],
|
||||
"resource.js": ["AsyncResource", "Resource"],
|
||||
"responsivedesign.jsm": ["ResponsiveUIManager"],
|
||||
"rest.js": ["RESTRequest", "RESTResponse", "TokenAuthenticatedRESTRequest", "SyncStorageRequest"],
|
||||
"rotaryengine.js": ["RotaryEngine", "RotaryRecord", "RotaryStore", "RotaryTracker"],
|
||||
"RTCStatsReport.jsm": ["convertToRTCStatsReport"],
|
||||
"scratchpad-manager.jsm": ["ScratchpadManager"],
|
||||
"server.js": ["MarionetteServer"],
|
||||
"service.js": ["Service"],
|
||||
"SettingsDB.jsm": ["SettingsDB", "SETTINGSDB_NAME", "SETTINGSSTORE_NAME"],
|
||||
"SharedPromptUtils.jsm": ["PromptUtils", "EnableDelayHelper"],
|
||||
"ShutdownLeaksCollector.jsm": ["ContentCollector"],
|
||||
"SignInToWebsite.jsm": ["SignInToWebsiteController"],
|
||||
"Social.jsm": ["Social", "OpenGraphBuilder", "DynamicResizeWatcher", "sizeSocialPanelToContent"],
|
||||
"SpecialPowersObserver.jsm": ["SpecialPowersObserver", "SpecialPowersObserverFactory"],
|
||||
"stack.js": ["findCallerFrame"],
|
||||
"StateMachineHelper.jsm": ["State", "CommandType"],
|
||||
"status.js": ["Status"],
|
||||
"storageserver.js": ["ServerBSO", "StorageServerCallback", "StorageServerCollection", "StorageServer", "storageServerForUsers"],
|
||||
"stringbundle.js": ["StringBundle"],
|
||||
"strings.js": ["trim", "vslice"],
|
||||
"StructuredLog.jsm": ["StructuredLogger", "StructuredFormatter"],
|
||||
"StyleEditorUtil.jsm": ["getString", "assert", "log", "text", "wire", "showFilePicker"],
|
||||
"subprocess_common.jsm": ["BaseProcess", "PromiseWorker", "SubprocessConstants"],
|
||||
"subprocess_unix.jsm": ["SubprocessImpl"],
|
||||
"subprocess_win.jsm": ["SubprocessImpl"],
|
||||
"sync.jsm": ["Authentication"],
|
||||
"tabs.js": ["TabEngine", "TabSetRecord"],
|
||||
"tabs.jsm": ["BrowserTabs"],
|
||||
"tcpsocket_test.jsm": ["createSocket", "createServer", "enablePrefsAndPermissions", "socketCompartmentInstanceOfArrayBuffer"],
|
||||
"telemetry.js": ["SyncTelemetry"],
|
||||
"test.jsm": ["Foo"],
|
||||
"test2.jsm": ["Bar"],
|
||||
"test_bug883784.jsm": ["Test"],
|
||||
"Timer.jsm": ["setTimeout", "clearTimeout", "setInterval", "clearInterval"],
|
||||
"tokenserverclient.js": ["TokenServerClient", "TokenServerClientError", "TokenServerClientNetworkError", "TokenServerClientServerError"],
|
||||
"ToolboxProcess.jsm": ["BrowserToolboxProcess"],
|
||||
"tps.jsm": ["ACTIONS", "TPS"],
|
||||
"Translation.jsm": ["Translation", "TranslationTelemetry"],
|
||||
"Traversal.jsm": ["TraversalRules", "TraversalHelper"],
|
||||
"UpdateTelemetry.jsm": ["AUSTLMY"],
|
||||
"userapi.js": ["UserAPI10Client"],
|
||||
"util.js": ["getChromeWindow", "XPCOMUtils", "Services", "Utils", "Async", "Svc", "Str"],
|
||||
"utils.js": ["applicationName", "assert", "Copy", "getBrowserObject", "getChromeWindow", "getWindows", "getWindowByTitle", "getWindowByType", "getWindowId", "getMethodInWindows", "getPreference", "saveDataURL", "setPreference", "sleep", "startTimer", "stopTimer", "takeScreenshot", "unwrapNode", "waitFor", "btoa", "encryptPayload", "isConfiguredWithLegacyIdentity", "ensureLegacyIdentityManager", "setBasicCredentials", "makeIdentityConfig", "makeFxAccountsInternalMock", "configureFxAccountIdentity", "configureIdentity", "SyncTestingInfrastructure", "waitForZeroTimer", "Promise", "add_identity_test", "MockFxaStorageManager", "AccountState", "sumHistogram", "CommonUtils", "CryptoUtils", "TestingUtils"],
|
||||
"Utils.jsm": ["Utils", "Logger", "PivotContext", "PrefCache", "SettingCache"],
|
||||
"VariablesView.jsm": ["VariablesView", "escapeHTML"],
|
||||
"VariablesViewController.jsm": ["VariablesViewController", "StackFrameUtils"],
|
||||
"version.jsm": ["VERSION"],
|
||||
"vtt.jsm": ["WebVTT"],
|
||||
"WebChannel.jsm": ["WebChannel", "WebChannelBroker"],
|
||||
"WindowDraggingUtils.jsm": ["WindowDraggingElement"],
|
||||
"windows.js": ["init", "map"],
|
||||
"windows.jsm": ["BrowserWindows"],
|
||||
"WindowsJumpLists.jsm": ["WinTaskbarJumpList"],
|
||||
"WindowsPreviewPerTab.jsm": ["AeroPeek"],
|
||||
"withs.js": ["startsWith", "endsWith"],
|
||||
"xul-app.jsm": ["XulApp"]
|
||||
}
|
||||
718
tools/lint/eslint/npm-shrinkwrap.json
generated
Normal file
718
tools/lint/eslint/npm-shrinkwrap.json
generated
Normal file
|
|
@ -0,0 +1,718 @@
|
|||
{
|
||||
"name": "mach-eslint",
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "4.0.3",
|
||||
"from": "acorn@>=4.0.1 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.3.tgz"
|
||||
},
|
||||
"acorn-jsx": {
|
||||
"version": "3.0.1",
|
||||
"from": "acorn-jsx@>=3.0.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "3.3.0",
|
||||
"from": "acorn@>=3.0.4 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ajv": {
|
||||
"version": "4.8.2",
|
||||
"from": "ajv@>=4.7.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-4.8.2.tgz"
|
||||
},
|
||||
"ajv-keywords": {
|
||||
"version": "1.1.1",
|
||||
"from": "ajv-keywords@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.1.1.tgz"
|
||||
},
|
||||
"ansi-escapes": {
|
||||
"version": "1.4.0",
|
||||
"from": "ansi-escapes@>=1.1.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz"
|
||||
},
|
||||
"ansi-regex": {
|
||||
"version": "2.0.0",
|
||||
"from": "ansi-regex@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.0.0.tgz"
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "2.2.1",
|
||||
"from": "ansi-styles@>=2.2.1 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz"
|
||||
},
|
||||
"argparse": {
|
||||
"version": "1.0.9",
|
||||
"from": "argparse@>=1.0.7 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz"
|
||||
},
|
||||
"array-union": {
|
||||
"version": "1.0.2",
|
||||
"from": "array-union@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz"
|
||||
},
|
||||
"array-uniq": {
|
||||
"version": "1.0.3",
|
||||
"from": "array-uniq@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz"
|
||||
},
|
||||
"arrify": {
|
||||
"version": "1.0.1",
|
||||
"from": "arrify@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz"
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "0.4.2",
|
||||
"from": "balanced-match@>=0.4.1 <0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz"
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.6",
|
||||
"from": "brace-expansion@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz"
|
||||
},
|
||||
"caller-path": {
|
||||
"version": "0.1.0",
|
||||
"from": "caller-path@>=0.1.0 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz"
|
||||
},
|
||||
"callsites": {
|
||||
"version": "0.2.0",
|
||||
"from": "callsites@>=0.2.0 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz"
|
||||
},
|
||||
"chalk": {
|
||||
"version": "1.1.3",
|
||||
"from": "chalk@>=1.1.3 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz"
|
||||
},
|
||||
"circular-json": {
|
||||
"version": "0.3.1",
|
||||
"from": "circular-json@>=0.3.0 <0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.1.tgz"
|
||||
},
|
||||
"cli-cursor": {
|
||||
"version": "1.0.2",
|
||||
"from": "cli-cursor@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz"
|
||||
},
|
||||
"cli-width": {
|
||||
"version": "2.1.0",
|
||||
"from": "cli-width@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz"
|
||||
},
|
||||
"co": {
|
||||
"version": "4.6.0",
|
||||
"from": "co@>=4.6.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz"
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"from": "code-point-at@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz"
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"from": "concat-map@0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
||||
},
|
||||
"concat-stream": {
|
||||
"version": "1.5.2",
|
||||
"from": "concat-stream@>=1.4.6 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz"
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
"from": "core-util-is@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz"
|
||||
},
|
||||
"d": {
|
||||
"version": "0.1.1",
|
||||
"from": "d@>=0.1.1 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/d/-/d-0.1.1.tgz"
|
||||
},
|
||||
"debug": {
|
||||
"version": "2.3.0",
|
||||
"from": "debug@>=2.1.1 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.3.0.tgz"
|
||||
},
|
||||
"deep-is": {
|
||||
"version": "0.1.3",
|
||||
"from": "deep-is@>=0.1.3 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz"
|
||||
},
|
||||
"del": {
|
||||
"version": "2.2.2",
|
||||
"from": "del@>=2.0.2 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz"
|
||||
},
|
||||
"doctrine": {
|
||||
"version": "1.5.0",
|
||||
"from": "doctrine@>=1.2.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz"
|
||||
},
|
||||
"dom-serializer": {
|
||||
"version": "0.1.0",
|
||||
"from": "dom-serializer@>=0.0.0 <1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.0.tgz",
|
||||
"dependencies": {
|
||||
"domelementtype": {
|
||||
"version": "1.1.3",
|
||||
"from": "domelementtype@>=1.1.1 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"domelementtype": {
|
||||
"version": "1.3.0",
|
||||
"from": "domelementtype@>=1.3.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.0.tgz"
|
||||
},
|
||||
"domhandler": {
|
||||
"version": "2.3.0",
|
||||
"from": "domhandler@>=2.3.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.3.0.tgz"
|
||||
},
|
||||
"domutils": {
|
||||
"version": "1.5.1",
|
||||
"from": "domutils@>=1.5.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz"
|
||||
},
|
||||
"entities": {
|
||||
"version": "1.1.1",
|
||||
"from": "entities@>=1.1.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz"
|
||||
},
|
||||
"es5-ext": {
|
||||
"version": "0.10.12",
|
||||
"from": "es5-ext@>=0.10.11 <0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.12.tgz"
|
||||
},
|
||||
"es6-iterator": {
|
||||
"version": "2.0.0",
|
||||
"from": "es6-iterator@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.0.tgz"
|
||||
},
|
||||
"es6-map": {
|
||||
"version": "0.1.4",
|
||||
"from": "es6-map@>=0.1.3 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.4.tgz"
|
||||
},
|
||||
"es6-set": {
|
||||
"version": "0.1.4",
|
||||
"from": "es6-set@>=0.1.3 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.4.tgz"
|
||||
},
|
||||
"es6-symbol": {
|
||||
"version": "3.1.0",
|
||||
"from": "es6-symbol@>=3.1.0 <3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.0.tgz"
|
||||
},
|
||||
"es6-weak-map": {
|
||||
"version": "2.0.1",
|
||||
"from": "es6-weak-map@>=2.0.1 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.1.tgz"
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"from": "escape-string-regexp@>=1.0.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz"
|
||||
},
|
||||
"escope": {
|
||||
"version": "3.6.0",
|
||||
"from": "escope@>=3.6.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz"
|
||||
},
|
||||
"eslint": {
|
||||
"version": "3.8.1",
|
||||
"from": "eslint@3.8.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint/-/eslint-3.8.1.tgz"
|
||||
},
|
||||
"eslint-plugin-html": {
|
||||
"version": "1.5.2",
|
||||
"from": "eslint-plugin-html@1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-1.5.2.tgz"
|
||||
},
|
||||
"eslint-plugin-react": {
|
||||
"version": "4.2.3",
|
||||
"from": "eslint-plugin-react@4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-4.2.3.tgz"
|
||||
},
|
||||
"espree": {
|
||||
"version": "3.3.2",
|
||||
"from": "espree@>=3.2.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-3.3.2.tgz"
|
||||
},
|
||||
"esprima": {
|
||||
"version": "2.7.3",
|
||||
"from": "esprima@>=2.6.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz"
|
||||
},
|
||||
"esrecurse": {
|
||||
"version": "4.1.0",
|
||||
"from": "esrecurse@>=4.1.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.1.0.tgz",
|
||||
"dependencies": {
|
||||
"estraverse": {
|
||||
"version": "4.1.1",
|
||||
"from": "estraverse@>=4.1.0 <4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.1.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"estraverse": {
|
||||
"version": "4.2.0",
|
||||
"from": "estraverse@>=4.2.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz"
|
||||
},
|
||||
"esutils": {
|
||||
"version": "2.0.2",
|
||||
"from": "esutils@>=2.0.2 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz"
|
||||
},
|
||||
"event-emitter": {
|
||||
"version": "0.3.4",
|
||||
"from": "event-emitter@>=0.3.4 <0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.4.tgz"
|
||||
},
|
||||
"exit-hook": {
|
||||
"version": "1.1.1",
|
||||
"from": "exit-hook@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz"
|
||||
},
|
||||
"fast-levenshtein": {
|
||||
"version": "2.0.5",
|
||||
"from": "fast-levenshtein@>=2.0.4 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.5.tgz"
|
||||
},
|
||||
"figures": {
|
||||
"version": "1.7.0",
|
||||
"from": "figures@>=1.3.5 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz"
|
||||
},
|
||||
"file-entry-cache": {
|
||||
"version": "2.0.0",
|
||||
"from": "file-entry-cache@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz"
|
||||
},
|
||||
"flat-cache": {
|
||||
"version": "1.2.1",
|
||||
"from": "flat-cache@>=1.2.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.2.1.tgz"
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"from": "fs.realpath@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz"
|
||||
},
|
||||
"generate-function": {
|
||||
"version": "2.0.0",
|
||||
"from": "generate-function@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz"
|
||||
},
|
||||
"generate-object-property": {
|
||||
"version": "1.2.0",
|
||||
"from": "generate-object-property@>=1.1.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz"
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.1.1",
|
||||
"from": "glob@>=7.0.3 <8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.1.tgz"
|
||||
},
|
||||
"globals": {
|
||||
"version": "9.12.0",
|
||||
"from": "globals@>=9.2.0 <10.0.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-9.12.0.tgz"
|
||||
},
|
||||
"globby": {
|
||||
"version": "5.0.0",
|
||||
"from": "globby@>=5.0.0 <6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz"
|
||||
},
|
||||
"graceful-fs": {
|
||||
"version": "4.1.10",
|
||||
"from": "graceful-fs@>=4.1.2 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.10.tgz"
|
||||
},
|
||||
"has-ansi": {
|
||||
"version": "2.0.0",
|
||||
"from": "has-ansi@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz"
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "3.9.2",
|
||||
"from": "htmlparser2@>=3.8.2 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.9.2.tgz"
|
||||
},
|
||||
"ignore": {
|
||||
"version": "3.2.0",
|
||||
"from": "ignore@>=3.1.5 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ignore/-/ignore-3.2.0.tgz"
|
||||
},
|
||||
"imurmurhash": {
|
||||
"version": "0.1.4",
|
||||
"from": "imurmurhash@>=0.1.4 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz"
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"from": "inflight@>=1.0.4 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz"
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"from": "inherits@>=2.0.1 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz"
|
||||
},
|
||||
"ini-parser": {
|
||||
"version": "0.0.2",
|
||||
"from": "ini-parser@>=0.0.2 <0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/ini-parser/-/ini-parser-0.0.2.tgz"
|
||||
},
|
||||
"inquirer": {
|
||||
"version": "0.12.0",
|
||||
"from": "inquirer@>=0.12.0 <0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz"
|
||||
},
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "1.0.0",
|
||||
"from": "is-fullwidth-code-point@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz"
|
||||
},
|
||||
"is-my-json-valid": {
|
||||
"version": "2.15.0",
|
||||
"from": "is-my-json-valid@>=2.10.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz"
|
||||
},
|
||||
"is-path-cwd": {
|
||||
"version": "1.0.0",
|
||||
"from": "is-path-cwd@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz"
|
||||
},
|
||||
"is-path-in-cwd": {
|
||||
"version": "1.0.0",
|
||||
"from": "is-path-in-cwd@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz"
|
||||
},
|
||||
"is-path-inside": {
|
||||
"version": "1.0.0",
|
||||
"from": "is-path-inside@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.0.tgz"
|
||||
},
|
||||
"is-property": {
|
||||
"version": "1.0.2",
|
||||
"from": "is-property@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz"
|
||||
},
|
||||
"is-resolvable": {
|
||||
"version": "1.0.0",
|
||||
"from": "is-resolvable@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.0.0.tgz"
|
||||
},
|
||||
"isarray": {
|
||||
"version": "1.0.0",
|
||||
"from": "isarray@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz"
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.6.1",
|
||||
"from": "js-yaml@>=3.5.1 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.6.1.tgz"
|
||||
},
|
||||
"json-stable-stringify": {
|
||||
"version": "1.0.1",
|
||||
"from": "json-stable-stringify@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz"
|
||||
},
|
||||
"jsonify": {
|
||||
"version": "0.0.0",
|
||||
"from": "jsonify@>=0.0.0 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz"
|
||||
},
|
||||
"jsonpointer": {
|
||||
"version": "4.0.0",
|
||||
"from": "jsonpointer@>=4.0.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.0.tgz"
|
||||
},
|
||||
"levn": {
|
||||
"version": "0.3.0",
|
||||
"from": "levn@>=0.3.0 <0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz"
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.16.6",
|
||||
"from": "lodash@>=4.0.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.6.tgz"
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.3",
|
||||
"from": "minimatch@>=3.0.2 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz"
|
||||
},
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"from": "minimist@0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.1",
|
||||
"from": "mkdirp@>=0.5.0 <0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz"
|
||||
},
|
||||
"ms": {
|
||||
"version": "0.7.2",
|
||||
"from": "ms@0.7.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-0.7.2.tgz"
|
||||
},
|
||||
"mute-stream": {
|
||||
"version": "0.0.5",
|
||||
"from": "mute-stream@0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz"
|
||||
},
|
||||
"natural-compare": {
|
||||
"version": "1.4.0",
|
||||
"from": "natural-compare@>=1.4.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
|
||||
},
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"from": "number-is-nan@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz"
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.0",
|
||||
"from": "object-assign@>=4.0.1 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz"
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"from": "once@>=1.3.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz"
|
||||
},
|
||||
"onetime": {
|
||||
"version": "1.1.0",
|
||||
"from": "onetime@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz"
|
||||
},
|
||||
"optionator": {
|
||||
"version": "0.8.2",
|
||||
"from": "optionator@>=0.8.2 <0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz"
|
||||
},
|
||||
"os-homedir": {
|
||||
"version": "1.0.2",
|
||||
"from": "os-homedir@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz"
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"from": "path-is-absolute@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz"
|
||||
},
|
||||
"path-is-inside": {
|
||||
"version": "1.0.2",
|
||||
"from": "path-is-inside@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz"
|
||||
},
|
||||
"pify": {
|
||||
"version": "2.3.0",
|
||||
"from": "pify@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz"
|
||||
},
|
||||
"pinkie": {
|
||||
"version": "2.0.4",
|
||||
"from": "pinkie@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz"
|
||||
},
|
||||
"pinkie-promise": {
|
||||
"version": "2.0.1",
|
||||
"from": "pinkie-promise@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz"
|
||||
},
|
||||
"pluralize": {
|
||||
"version": "1.2.1",
|
||||
"from": "pluralize@>=1.2.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz"
|
||||
},
|
||||
"prelude-ls": {
|
||||
"version": "1.1.2",
|
||||
"from": "prelude-ls@>=1.1.2 <1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz"
|
||||
},
|
||||
"process-nextick-args": {
|
||||
"version": "1.0.7",
|
||||
"from": "process-nextick-args@>=1.0.6 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz"
|
||||
},
|
||||
"progress": {
|
||||
"version": "1.1.8",
|
||||
"from": "progress@>=1.1.8 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz"
|
||||
},
|
||||
"readable-stream": {
|
||||
"version": "2.0.6",
|
||||
"from": "readable-stream@>=2.0.0 <2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz"
|
||||
},
|
||||
"readline2": {
|
||||
"version": "1.0.1",
|
||||
"from": "readline2@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz"
|
||||
},
|
||||
"require-uncached": {
|
||||
"version": "1.0.3",
|
||||
"from": "require-uncached@>=1.0.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz"
|
||||
},
|
||||
"resolve-from": {
|
||||
"version": "1.0.1",
|
||||
"from": "resolve-from@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz"
|
||||
},
|
||||
"restore-cursor": {
|
||||
"version": "1.0.1",
|
||||
"from": "restore-cursor@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz"
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.5.4",
|
||||
"from": "rimraf@>=2.2.8 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz"
|
||||
},
|
||||
"run-async": {
|
||||
"version": "0.1.0",
|
||||
"from": "run-async@>=0.1.0 <0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz"
|
||||
},
|
||||
"rx-lite": {
|
||||
"version": "3.1.2",
|
||||
"from": "rx-lite@>=3.1.2 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz"
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.1",
|
||||
"from": "sax@>=1.1.4 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz"
|
||||
},
|
||||
"shelljs": {
|
||||
"version": "0.6.1",
|
||||
"from": "shelljs@>=0.6.0 <0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.6.1.tgz"
|
||||
},
|
||||
"slice-ansi": {
|
||||
"version": "0.0.4",
|
||||
"from": "slice-ansi@0.0.4",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz"
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3",
|
||||
"from": "sprintf-js@>=1.0.2 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz"
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "0.10.31",
|
||||
"from": "string_decoder@>=0.10.0 <0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz"
|
||||
},
|
||||
"string-width": {
|
||||
"version": "1.0.2",
|
||||
"from": "string-width@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz"
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"from": "strip-ansi@>=3.0.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz"
|
||||
},
|
||||
"strip-bom": {
|
||||
"version": "3.0.0",
|
||||
"from": "strip-bom@>=3.0.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz"
|
||||
},
|
||||
"strip-json-comments": {
|
||||
"version": "1.0.4",
|
||||
"from": "strip-json-comments@>=1.0.1 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-1.0.4.tgz"
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "2.0.0",
|
||||
"from": "supports-color@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz"
|
||||
},
|
||||
"table": {
|
||||
"version": "3.8.3",
|
||||
"from": "table@>=3.7.8 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz",
|
||||
"dependencies": {
|
||||
"is-fullwidth-code-point": {
|
||||
"version": "2.0.0",
|
||||
"from": "is-fullwidth-code-point@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz"
|
||||
},
|
||||
"string-width": {
|
||||
"version": "2.0.0",
|
||||
"from": "string-width@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.0.0.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"text-table": {
|
||||
"version": "0.2.0",
|
||||
"from": "text-table@>=0.2.0 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
|
||||
},
|
||||
"through": {
|
||||
"version": "2.3.8",
|
||||
"from": "through@>=2.3.6 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz"
|
||||
},
|
||||
"tryit": {
|
||||
"version": "1.0.3",
|
||||
"from": "tryit@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tryit/-/tryit-1.0.3.tgz"
|
||||
},
|
||||
"type-check": {
|
||||
"version": "0.3.2",
|
||||
"from": "type-check@>=0.3.2 <0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz"
|
||||
},
|
||||
"typedarray": {
|
||||
"version": "0.0.6",
|
||||
"from": "typedarray@>=0.0.5 <0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz"
|
||||
},
|
||||
"user-home": {
|
||||
"version": "2.0.0",
|
||||
"from": "user-home@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz"
|
||||
},
|
||||
"util-deprecate": {
|
||||
"version": "1.0.2",
|
||||
"from": "util-deprecate@>=1.0.1 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||
},
|
||||
"wordwrap": {
|
||||
"version": "1.0.0",
|
||||
"from": "wordwrap@>=1.0.0 <1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz"
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"from": "wrappy@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
|
||||
},
|
||||
"write": {
|
||||
"version": "0.2.1",
|
||||
"from": "write@>=0.2.1 <0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz"
|
||||
},
|
||||
"xtend": {
|
||||
"version": "4.0.1",
|
||||
"from": "xtend@>=4.0.0 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
|
||||
}
|
||||
}
|
||||
}
|
||||
16
tools/lint/eslint/package.json
Normal file
16
tools/lint/eslint/package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "mach-eslint",
|
||||
"description": "ESLint and external plugins for use with mach",
|
||||
"repository": {},
|
||||
"license": "MPL-2.0",
|
||||
"dependencies": {
|
||||
"eslint": "3.8.1",
|
||||
"eslint-plugin-html": "1.5.2",
|
||||
"eslint-plugin-react": "4.2.3",
|
||||
"escope": "^3.6.0",
|
||||
"espree": "^3.2.0",
|
||||
"estraverse": "^4.2.0",
|
||||
"ini-parser": "^0.0.2",
|
||||
"sax": "^1.1.4"
|
||||
}
|
||||
}
|
||||
70
tools/lint/eslint/update
Normal file
70
tools/lint/eslint/update
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
#!/bin/sh
|
||||
# Force the scripts working directory to be projdir/tools/lint/eslint.
|
||||
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
cd $DIR
|
||||
|
||||
echo "To complete this script you will need the following tokens from https://api.pub.build.mozilla.org/tokenauth/"
|
||||
echo " - tooltool.upload.public"
|
||||
echo " - tooltool.download.public"
|
||||
echo ""
|
||||
read -p "Are these tokens visible at the above URL (y/n)?" choice
|
||||
case "$choice" in
|
||||
y|Y )
|
||||
echo ""
|
||||
echo "1. Go to https://api.pub.build.mozilla.org/"
|
||||
echo "2. Log in using your Mozilla LDAP account."
|
||||
echo "3. Click on \"Tokens.\""
|
||||
echo "4. Issue a user token with the permissions tooltool.upload.public and tooltool.download.public."
|
||||
echo ""
|
||||
echo "When you click issue you will be presented with a long string. Paste the string into a temporary file called ~/.tooltool-token."
|
||||
echo ""
|
||||
read -rsp $'Press any key to continue...\n' -n 1
|
||||
;;
|
||||
n|N )
|
||||
echo ""
|
||||
echo "You will need to contact somebody that has these permissions... people most likely to have these permissions are members of the releng, ateam, a sheriff, mratcliffe, or jryans"
|
||||
exit 1
|
||||
;;
|
||||
* )
|
||||
echo ""
|
||||
echo "Invalid input."
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
|
||||
echo ""
|
||||
echo "Removing node_modules and npm_shrinkwrap.json..."
|
||||
rm -rf node_modules/
|
||||
rm npm-shrinkwrap.json
|
||||
|
||||
echo "Installing eslint and external plugins..."
|
||||
# ESLint and all _external_ plugins are listed in this directory's package.json,
|
||||
# so a regular `npm install` will install them at the specified versions.
|
||||
# The in-tree eslint-plugin-mozilla is kept out of this tooltool archive on
|
||||
# purpose so that it can be changed by any developer without requiring tooltool
|
||||
# access to make changes.
|
||||
npm install
|
||||
|
||||
echo "Creating npm shrinkwrap..."
|
||||
npm shrinkwrap
|
||||
|
||||
echo "Creating eslint.tar.gz..."
|
||||
tar cvfz eslint.tar.gz node_modules
|
||||
|
||||
echo "Downloading tooltool..."
|
||||
wget https://raw.githubusercontent.com/mozilla/build-tooltool/master/tooltool.py
|
||||
chmod +x tooltool.py
|
||||
|
||||
echo "Adding eslint.tar.gz to tooltool..."
|
||||
rm manifest.tt
|
||||
./tooltool.py add --visibility public eslint.tar.gz
|
||||
|
||||
echo "Uploading eslint.tar.gz to tooltool..."
|
||||
./tooltool.py upload --authentication-file=~/.tooltool-token --message "node_modules folder update for tools/lint/eslint"
|
||||
|
||||
echo "Cleaning up..."
|
||||
rm eslint.tar.gz
|
||||
rm tooltool.py
|
||||
|
||||
echo ""
|
||||
echo "Update complete, please commit and check in your changes."
|
||||
195
tools/lint/flake8.lint
Normal file
195
tools/lint/flake8.lint
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# 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/.
|
||||
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
import subprocess
|
||||
|
||||
import which
|
||||
from mozprocess import ProcessHandler
|
||||
|
||||
from mozlint import result
|
||||
|
||||
|
||||
here = os.path.abspath(os.path.dirname(__file__))
|
||||
FLAKE8_REQUIREMENTS_PATH = os.path.join(here, 'flake8', 'flake8_requirements.txt')
|
||||
|
||||
FLAKE8_NOT_FOUND = """
|
||||
Could not find flake8! Install flake8 and try again.
|
||||
|
||||
$ pip install -U --require-hashes -r {}
|
||||
""".strip().format(FLAKE8_REQUIREMENTS_PATH)
|
||||
|
||||
|
||||
FLAKE8_INSTALL_ERROR = """
|
||||
Unable to install correct version of flake8
|
||||
Try to install it manually with:
|
||||
$ pip install -U --require-hashes -r {}
|
||||
""".strip().format(FLAKE8_REQUIREMENTS_PATH)
|
||||
|
||||
LINE_OFFSETS = {
|
||||
# continuation line under-indented for hanging indent
|
||||
'E121': (-1, 2),
|
||||
# continuation line missing indentation or outdented
|
||||
'E122': (-1, 2),
|
||||
# continuation line over-indented for hanging indent
|
||||
'E126': (-1, 2),
|
||||
# continuation line over-indented for visual indent
|
||||
'E127': (-1, 2),
|
||||
# continuation line under-indented for visual indent
|
||||
'E128': (-1, 2),
|
||||
# continuation line unaligned for hanging indend
|
||||
'E131': (-1, 2),
|
||||
# expected 1 blank line, found 0
|
||||
'E301': (-1, 2),
|
||||
# expected 2 blank lines, found 1
|
||||
'E302': (-2, 3),
|
||||
}
|
||||
"""Maps a flake8 error to a lineoffset tuple.
|
||||
|
||||
The offset is of the form (lineno_offset, num_lines) and is passed
|
||||
to the lineoffset property of `ResultContainer`.
|
||||
"""
|
||||
|
||||
EXTENSIONS = ['.py', '.lint']
|
||||
results = []
|
||||
|
||||
|
||||
def process_line(line):
|
||||
# Escape slashes otherwise JSON conversion will not work
|
||||
line = line.replace('\\', '\\\\')
|
||||
try:
|
||||
res = json.loads(line)
|
||||
except ValueError:
|
||||
print('Non JSON output from linter, will not be processed: {}'.format(line))
|
||||
return
|
||||
|
||||
if 'code' in res:
|
||||
if res['code'].startswith('W'):
|
||||
res['level'] = 'warning'
|
||||
|
||||
if res['code'] in LINE_OFFSETS:
|
||||
res['lineoffset'] = LINE_OFFSETS[res['code']]
|
||||
|
||||
results.append(result.from_linter(LINTER, **res))
|
||||
|
||||
|
||||
def run_process(cmdargs):
|
||||
# flake8 seems to handle SIGINT poorly. Handle it here instead
|
||||
# so we can kill the process without a cryptic traceback.
|
||||
orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
|
||||
proc = ProcessHandler(cmdargs, env=os.environ,
|
||||
processOutputLine=process_line)
|
||||
proc.run()
|
||||
signal.signal(signal.SIGINT, orig)
|
||||
|
||||
try:
|
||||
proc.wait()
|
||||
except KeyboardInterrupt:
|
||||
proc.kill()
|
||||
|
||||
|
||||
def get_flake8_binary():
|
||||
"""
|
||||
Returns the path of the first flake8 binary available
|
||||
if not found returns None
|
||||
"""
|
||||
binary = os.environ.get('FLAKE8')
|
||||
if binary:
|
||||
return binary
|
||||
|
||||
try:
|
||||
return which.which('flake8')
|
||||
except which.WhichError:
|
||||
return None
|
||||
|
||||
|
||||
def _run_pip(*args):
|
||||
"""
|
||||
Helper function that runs pip with subprocess
|
||||
"""
|
||||
try:
|
||||
subprocess.check_output(['pip'] + list(args),
|
||||
stderr=subprocess.STDOUT)
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(e.output)
|
||||
return False
|
||||
|
||||
|
||||
def reinstall_flake8():
|
||||
"""
|
||||
Try to install flake8 at the target version, returns True on success
|
||||
otherwise prints the otuput of the pip command and returns False
|
||||
"""
|
||||
if _run_pip('install', '-U',
|
||||
'--require-hashes', '-r',
|
||||
FLAKE8_REQUIREMENTS_PATH):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def lint(files, **lintargs):
|
||||
|
||||
if not reinstall_flake8():
|
||||
print(FLAKE8_INSTALL_ERROR)
|
||||
return 1
|
||||
|
||||
binary = get_flake8_binary()
|
||||
|
||||
cmdargs = [
|
||||
binary,
|
||||
'--format', '{"path":"%(path)s","lineno":%(row)s,'
|
||||
'"column":%(col)s,"rule":"%(code)s","message":"%(text)s"}',
|
||||
]
|
||||
|
||||
# Run any paths with a .flake8 file in the directory separately so
|
||||
# it gets picked up. This means only .flake8 files that live in
|
||||
# directories that are explicitly included will be considered.
|
||||
# See bug 1277851
|
||||
no_config = []
|
||||
for f in files:
|
||||
if not os.path.isfile(os.path.join(f, '.flake8')):
|
||||
no_config.append(f)
|
||||
continue
|
||||
run_process(cmdargs+[f])
|
||||
|
||||
# XXX For some reason passing in --exclude results in flake8 not using
|
||||
# the local .flake8 file. So for now only pass in --exclude if there
|
||||
# is no local config.
|
||||
exclude = lintargs.get('exclude')
|
||||
if exclude:
|
||||
cmdargs += ['--exclude', ','.join(lintargs['exclude'])]
|
||||
|
||||
if no_config:
|
||||
run_process(cmdargs+no_config)
|
||||
|
||||
return results
|
||||
|
||||
|
||||
LINTER = {
|
||||
'name': "flake8",
|
||||
'description': "Python linter",
|
||||
'include': [
|
||||
'python/mozlint',
|
||||
'taskcluster',
|
||||
'testing/firefox-ui',
|
||||
'testing/marionette/client',
|
||||
'testing/marionette/harness',
|
||||
'testing/marionette/puppeteer',
|
||||
'testing/mozbase',
|
||||
'testing/mochitest',
|
||||
'testing/talos/',
|
||||
'tools/lint',
|
||||
],
|
||||
'exclude': ["testing/mozbase/mozdevice/mozdevice/Zeroconf.py",
|
||||
'testing/mochitest/pywebsocket'],
|
||||
'extensions': EXTENSIONS,
|
||||
'type': 'external',
|
||||
'payload': lint,
|
||||
}
|
||||
4
tools/lint/flake8/flake8_requirements.txt
Normal file
4
tools/lint/flake8/flake8_requirements.txt
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
flake8==2.5.4 --hash=sha256:fb5a67af4024622287a76abf6b7fe4fb3cfacf765a790976ce64f52c44c88e4a
|
||||
mccabe==0.4.0 --hash=sha256:cbc2938f6c01061bc6d21d0c838c2489664755cb18676f0734d7617f4577d09e
|
||||
pep8==1.7.0 --hash=sha256:4fc2e478addcf17016657dff30b2d8d611e8341fac19ccf2768802f6635d7b8a
|
||||
pyflakes==1.2.3 --hash=sha256:e87bac26c62ea5b45067cc89e4a12f56e1483f1f2cda17e7c9b375b9fd2f40da
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue