[prev in list] [next in list] [prev in thread] [next in thread]
List: fuzzing
Subject: Re: [fuzzing] fuzzing Digest, Vol 17, Issue 4
From: "Kevin Finisterre (lists)" <kf_lists () digitalmunition ! com>
Date: 2007-08-23 16:38:14
Message-ID: B6E2B093-B865-486B-8C1D-ACF744761A98 () digitalmunition ! com
[Download RAW message or body]
[Attachment #2 (multipart/alternative)]

On Aug 23, 2007, at 12:25 PM, 王王 wrote:
> excuse me ,i find i could not download the jsfunfuzz fuzzer from
> https://bugzilla.mozilla.org/show_bug.cgi?id=jsfunfuzz, even i
> registered a account.can any1 help me?or share me one copy?thank you.
>
> 2007/8/11, fuzzing-request@whitestar.linuxbox.org <fuzzing-
> request@whitestar.linuxbox.org >:
> Send fuzzing mailing list submissions to
> fuzzing@whitestar.linuxbox.org
>
> To subscribe or unsubscribe via the World Wide Web, visit
> http://www.whitestar.linuxbox.org/mailman/listinfo/fuzzing
> or, via email, send a message with subject or body 'help' to
> fuzzing-request@whitestar.linuxbox.org
>
> You can reach the person managing the list at
> fuzzing-owner@whitestar.linuxbox.org
>
> When replying, please edit your Subject line so it is more specific
> than "Re: Contents of fuzzing digest..."
>
>
> Today's Topics:
>
> 1. jsfunfuzz + slides ? (Mark Sec)
> 2. Re: jsfunfuzz + slides ? (Jerome Athias)
>
>
> ----------------------------------------------------------------------
>
> Message: 1
> Date: Fri, 10 Aug 2007 08:10:33 -0500
> From: "Mark Sec" < mark.sec@gmail.com>
> Subject: [fuzzing] jsfunfuzz + slides ?
> To: fuzzing@whitestar.linuxbox.org
> Message-ID:
> < 5598cfa10708100610s8b3ebd1hf4bb640f17f8bab0@mail.gmail.com>
> Content-Type: text/plain; charset="iso-8859-1"
>
> does any1 where i can download the jsfunfuzz fuzzer slides BH, and
> how to
> use that?
>
> -mark.
> -------------- next part --------------
> An HTML attachment was scrubbed...
> URL: http://www.whitestar.linuxbox.org/pipermail/fuzzing/
> attachments/20070810/ea2c1b18/attachment.html
>
> ------------------------------
>
> Message: 2
> Date: Fri, 10 Aug 2007 17:02:31 +0200
> From: Jerome Athias < jerome.athias@free.fr>
> Subject: Re: [fuzzing] jsfunfuzz + slides ?
> To: Mark Sec <mark.sec@gmail.com>
> Cc: fuzzing@whitestar.linuxbox.org
> Message-ID: <46BC7E07.3090102@free.fr>
> Content-Type: text/plain; charset="iso-8859-1"
>
> https://bugzilla.mozilla.org/show_bug.cgi?id=jsfunfuzz
>
> Regards
> /JA
> http://www.JA-PSI.fr
>
> Mark Sec a ?crit :
> >
> > does any1 where i can download the jsfunfuzz fuzzer slides BH,
> and how
> > to use that?
> >
> > -mark.
> -------------- next part --------------
> A non-text attachment was scrubbed...
> Name: smime.p7s
> Type: application/x-pkcs7-signature
> Size: 3253 bytes
> Desc: S/MIME Cryptographic Signature
> Url : http://www.whitestar.linuxbox.org/pipermail/fuzzing/
> attachments/20070810/bb4173b0/attachment-0001.bin
>
> ------------------------------
>
> _______________________________________________
> fuzzing mailing list
> fuzzing@whitestar.linuxbox.org
> http://www.whitestar.linuxbox.org/mailman/listinfo/fuzzing
>
>
> End of fuzzing Digest, Vol 17, Issue 4
> **************************************
>
> _______________________________________________
> fuzzing mailing list
> fuzzing@whitestar.linuxbox.org
> http://www.whitestar.linuxbox.org/mailman/listinfo/fuzzing
[Attachment #5 (multipart/mixed)]
[Attachment #7 (unknown)]
<HTML><BODY style="word-wrap: break-word; -khtml-nbsp-mode: space; -khtml-line-break: \
after-white-space; "><SPAN></SPAN></BODY></HTML>
["jsparsefuzz.html" (jsparsefuzz.html)]
<html>
<head>
<style>
pre { border: 1px solid #bbb; background: #eee; padding: 1em; }
</style>
<body>
<script type="application/javascript;version=1.7" src="jsparsefuzz.js"></script>
<!-- for browsers other than firefox, you'll need this instead: -->
<!-- <script src="jsparsefuzz.js"></script> -->
</body>
</html>
[Attachment #9 (unknown)]
<HTML><BODY style="word-wrap: break-word; -khtml-nbsp-mode: space; -khtml-line-break: \
after-white-space; "><SPAN></SPAN><SPAN></SPAN></BODY></HTML>
["jsparsefuzz.js" (jsparsefuzz.js)]
/*
This fuzzer creates random, possibly invalid, JavaScript function bodies.
It does this using recursion, thinking loosely in terms of "statements", \
"expressions", "lvalues", "literals", etc.
Once it creates a function body, it does the following things with it:
* Splits it in half and tries to compile each half, mostly to find bugs in the \
compiler's error-handling.
* Compiles it
* Decompiles it (using both toString and uneval)
* Checks two things about each decompilation:
* Does it compile?
* The "round-trip" test: when passed through the compiler and decomiler a second \
time, is the decompilation exactly the same? see below**
* Executes it
* If executing returned a generator, loops through the generator.
**This "round-trip change test" is intended to find bugs where the first
decompilation was incorrect. It is effective at finding bugs, but it also hits
false positives -- situations where a round-trip changes the decompilation, even
though the first decompilation was not really incorrect, just not fully
optimized or not canonical. Brendan has been nice about fixing these
"round-trip change" bugs even when they're not really bugs, since he sees value
in this type of fuzzing.
Bugs in SpiderMonkey found with this fuzzer are tracked by \
https://bugzilla.mozilla.org/show_bug.cgi?id=349611.
Some things to test this with:
JS Shell make -f Makefile.ref
JS Shell with extra GCing make XCFLAGS=-DWAY_TOO_MUCH_GC -f Makefile.ref
JS Shell without assertions make BUILD_OPT=1 OPTIMIZER=-Os -f Makefile.ref
Firefox
Valgrind?
Purify?
Todo:
Keep stats on exclusions (80% executed, 50% decompiled) and lengths?
Useful references:
http://www.codehouse.com/javascript/precedence/
http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference
http://developer.mozilla.org/en/docs/New_in_JavaScript_1.6
http://developer.mozilla.org/en/docs/New_in_JavaScript_1.7
*/
// Basic compatibility with Safari/WebKit and Mozilla's command-line jsshell.
var haveUneval = (typeof uneval == "function") ? true : false;
var jsshell = (typeof window == "undefined");
if (jsshell) {
dump = print;
dumpln = print;
alert = function(s) { dumpln(""); dumpln("Alert!? Alert is mapped to quit! Alerted \
text follows."); dumpln(s); quit(); } dumplnAndAlert = function(s) { dumpln(s); \
quit(); }
version(170); // make "yield" and "let" work.
}
if (!jsshell) {
if (typeof dump != "function") dump = function() { };
dumpln = function(s) { dump(s + "\n"); }
gc = function(){}; // :(
dumplnAndAlert = function(s) {
dumpln(s);
var p = document.createElement("pre");
p.appendChild(document.createTextNode(s));
document.body.appendChild(p);
}
}
// Be able to restore sanity...
var realEval = eval;
var realFunction = Function;
var realGC = gc;
// Given a seed, returns a function that generates random integers.
// Based on the Central Randomizer 1.3 (C) 1997 by Paul Houle (paul@honeylocust.com)
// See: http://www.honeylocust.com/javascript/randomizer.html
function randomizer(seed)
{
// Returns a somewhat random real number between 0 (inclusive) and 1 (not \
inclusive), like Math.random() but with a seed I control. // Modulus is in parens to \
work around a Crunchinator bug.
/*
function rndReal() {
seed = (seed*9301 + 49297) % (233280);
return seed/(233280.0);
};
*/
function rndReal() { return Math.random(); }
// Returns a somewhat random integer in the range {0, 1, 2, ..., number-1}
function rndInt(number) {
return Math.floor(rndReal() * number);
};
return rndInt;
}
var rnd = randomizer(6);
function rndElt(a) { return a[rnd(a.length)] }
// Each input to T should be a token or so, OR a bigger logical piece (such as a call \
to makeExpr). Smaller than a token is ok too ;)
// When "torture" is true, T may do any of the following:
// * skip a token
// * skip all the tokens to the left
// * skip all the tokens to the right
// * insert unterminated comments
// * insert line breaks
// * insert entire expressions
// * insert any token
// Even when not in "torture" mode, it may sneak in extra line breaks.
// Why did I decide to toString at every step, instead of making larger and larger \
arrays (or more and more deeply nested arrays?). no particular reason...
function Tmean(toks)
{
var torture = (rnd(170) == 57);
if (torture)
dumpln("Torture!!!");
var s = maybeLineBreak();
var i;
for (i=0; i<toks.length; ++i) {
// Catch bugs in the fuzzer. An easy mistake is
// return /*foo*/ + ...
// instead of
// return "/*foo*/" + ...
// Unary plus in the first one coerces the string that follows to number!
if(typeof(toks[i]) != "string")
alert("Strange toks[i]: " + toks[i]);
if (!(torture && rnd(12) == 0))
s += toks[i];
s += maybeLineBreak();
if (torture) switch(rnd(120)) {
case 0:
case 1:
s += maybeSpace() + rndElt(crazyTokens) + maybeSpace();
break;
case 2:
s += maybeSpace() + makeTerm(2) + maybeSpace();
break;
case 3:
s += maybeSpace() + makeExpr(2) + maybeSpace();
break;
case 4:
s += maybeSpace() + makeStatement(2) + maybeSpace();
break;
case 5:
s = "(" + s + ")"; // randomly parenthesize some prefix of it.
break;
case 6:
s = ""; // throw away everything before this point
break;
case 7:
return s; // throw away everything after this point
case 8:
s += UNTERMINATED_COMMENT;
break;
case 9:
s += UNTERMINATED_STRING_LITERAL;
break;
case 10:
if (rnd(2))
s += "(";
s += UNTERMINATED_REGEXP_LITERAL;
break;
default:
}
}
return s;
}
// For reference and debugging, here's what a T without the torture stuff and extra \
line breaks would look like: function Tnice(toks)
{
var s = ""
var i;
for (i=0; i<toks.length; ++i) {
if(typeof(toks[i]) != "string")
alert("Strange toks[i]: " + toks[i]);
s += toks[i];
}
return s;
}
T = Tmean;
var UNTERMINATED_COMMENT = "/*"; /* this comment is here so my text editor won't get \
confused */ var UNTERMINATED_STRING_LITERAL = "'";
var UNTERMINATED_REGEXP_LITERAL = "/";
function maybeLineBreak()
{
if (rnd(300) == 3)
return "\n"; // line break to trigger semicolon insertion and stuff
else
return "";
}
function maybeSpace()
{
if (rnd(2) == 0)
return " ";
else
return "";
}
function stripSemicolon(c)
{
var len = c.length;
if (c.charAt(len - 1) == ";")
return c.substr(0, len - 1);
else
return c;
}
function maybeLabel()
{
if (rnd(4) == 1)
return T([rndElt(["L", "M"]), ":"]);
else
return "";
}
function makeStatement(depth)
{
var dr = rnd(depth); // instead of depth - 1;
if (depth < rnd(8)) // frequently for small depth, infrequently for large depth
return makeLittleStatement(dr);
return (rndElt(statementMakers))(dr)
}
var varBinder = ["var ", "let ", "const ", ""];
var statementMakers = [
// Late-defined consts can cause problems, so let's late-define them!
function(dr) { return T([makeStatement(dr), " const ", makeId(dr), ";"]); },
function(dr) { return T([makeStatement(dr), makeStatement(dr)]); },
function(dr) { return T([makeStatement(dr-1), "\n", makeStatement(dr-1), "\n"]); },
// Stripping semilcolons. What happens if semicolons are missing? Especially with \
line breaks used in place of semicolons (semicolon insertion). function(dr) { return \
T([stripSemicolon(makeStatement(dr)), "\n", makeStatement(dr)]); }, function(dr) { \
return T([stripSemicolon(makeStatement(dr)), "\n" ]); }, \
function(dr) { return stripSemicolon(makeStatement(dr)); }, // usually invalid, but \
can be ok e.g. at the end of a block with curly braces
// Blocks and loops
function(dr) { return T(["{", makeStatement(dr), " }"]); },
function(dr) { return T(["{", makeStatement(dr-1), makeStatement(dr-1), " }"]); },
function(dr) { return T([maybeLabel(), "with", "(", makeExpr(dr), ")", \
makeStatementOrBlock(dr)]); }, function(dr) { return T([maybeLabel(), "with", "(", \
"{", makeId(dr), ": ", makeExpr(dr), "}", ")", makeStatementOrBlock(dr)]); },
// C-style "for" loops
// Two kinds of "for" loops: one with an expression as the first part, one with a \
var or let binding 'statement' as the first part. // I'm not sure if arbitrary \
statements are allowed there; I think not. function(dr) { return "/*noex infloop*/" \
+ T([maybeLabel(), "for", "(", makeExpr(dr), "; ", makeExpr(dr), "; ", makeExpr(dr), \
") ", makeStatementOrBlock(dr)]); }, function(dr) { return "/*noex infloop*/" + \
T([maybeLabel(), "for", "(", rndElt(varBinder), makeId(dr), "; ", makeExpr(dr), "; ", \
makeExpr(dr), ") ", makeStatementOrBlock(dr)]); },
// "for..in" loops
// -- for (key in obj)
function(dr) { return T([maybeLabel(), "for", "(", rndElt(varBinder), makeId(dr), " \
in ", makeExpr(dr-2), ") ", makeStatementOrBlock(dr)]); }, // -- for (key in \
generator()) function(dr) { return T([maybeLabel(), "for", "(", rndElt(varBinder), \
makeId(dr), " in ", "(", "(", makeFunction(dr), ")", "(", makeExpr(dr), ")", ")", \
")", makeStatementOrBlock(dr)]); }, // -- for each (value in obj)
function(dr) { return "/* nobrowserex bug 349964 */" + T([maybeLabel(), " for ", " \
each", "(", rndElt(varBinder), makeId(dr), " in ", makeExpr(dr-2), ") ", \
makeStatementOrBlock(dr)]); }, // -- for ([key, value] in obj), a special type of \
destructuring function(dr) { return T([maybeLabel(), " for ", "(", \
rndElt(varBinder), "[", makeId(dr), ", ", makeId(dr), "]", " in ", makeExpr(dr-2), ") \
", makeStatementOrBlock(dr)]); },
// Hoisty "for..in" loops. I don't know why this construct exists, but it does, \
and it hoists the initial-value expression above the loop. // With "var" or "const", \
the entire thing is hoisted. // With "let", only the value is hoisted, and it can be \
elim'ed as a useless statement. function(dr) { return "/* HOISTY1 */" + \
T([maybeLabel(), "for", "(", rndElt(varBinder), makeId(dr), " = ", makeExpr(dr), " in \
", makeExpr(dr-2), ") ", makeStatementOrBlock(dr)]); },
// Commented out until Brendan responds...
function(dr) { return "/* HOISTY2 */" + T([maybeLabel(), "for", "(", \
rndElt(varBinder), "[", makeId(dr), ", ", makeId(dr), "]", " = ", makeExpr(dr), " in \
", makeExpr(dr-2), ") ", makeStatementOrBlock(dr)]); },
function(dr) { return T([maybeLabel(), "while(0)" /*don't split this, it's needed \
to avoid noex*/, makeStatementOrBlock(dr)]); }, function(dr) { return "/*noex*/" + \
T([maybeLabel(), "while", "(", makeExpr(dr), ")", makeStatementOrBlock(dr)]); }, \
function(dr) { return T([maybeLabel(), "do ", makeStatementOrBlock(dr), " while(0)" \
/*don't split this, it's needed to avoid noex*/, ";"]); }, function(dr) { return \
"/*noex*/" + T([maybeLabel(), "do ", makeStatementOrBlock(dr), " while", "(", \
makeExpr(dr), ");"]); },
// Switch with some fallthroughs, default, etc.
function(dr) { return T([maybeLabel(), "switch", "(", makeExpr(dr), ")", " { ", \
"case", " 0", ":", " case", " 1: ", makeStatement(dr), ";", "break", ";", " case", " \
2", ": ", makeStatement(dr), ";", " case ", "3", ": ", makeStatement(dr), " ;", " \
default", ": ", makeStatement(dr), " }"]); },
// Switch with expressions as cases, instead of simple constants.
function(dr) { return T([maybeLabel(), "switch", "(", makeExpr(dr), ")", " { ", \
"case ", makeExpr(dr), ": ", makeStatement(dr), " case ", makeExpr(dr), ": ", \
makeStatement(dr), " }"]); },
// Let blocks, with and without multiple bindings, with and without initial values
function(dr) { return T(["let ", "(", makeId(dr), ")", \
" { ", makeStatement(dr), " }"]); }, function(dr) { return T(["let ", "(", \
makeId(dr), "=", "3", ", ", makeId(dr), "=", "4", ")", " { ", makeStatement(dr), " \
}"]); }, function(dr) { return T(["let ", "(", makeId(dr), "=", makeExpr(dr) + ")", \
" { ", makeStatement(dr), " }"]); },
// Conditionals, perhaps with 'else if' / 'else'
function(dr) { return T([maybeLabel(), "if(", makeExpr(dr), ") ", \
makeStatementOrBlock(dr)]); }, function(dr) { return T([maybeLabel(), "if(", \
makeExpr(dr), ") ", makeStatementOrBlock(dr-1), " else ", \
makeStatementOrBlock(dr-1)]); }, function(dr) { return T([maybeLabel(), "if(", \
makeExpr(dr), ") ", makeStatementOrBlock(dr-1), " else ", " if ", "(", makeExpr(dr), \
") ", makeStatementOrBlock(dr-1)]); }, function(dr) { return T([maybeLabel(), "if(", \
makeExpr(dr), ") ", makeStatementOrBlock(dr-1), " else ", " if ", "(", makeExpr(dr), \
") ", makeStatementOrBlock(dr-1), " else ", makeStatementOrBlock(dr-1)]); },
// A tricky pair of if/else cases.
// In the SECOND case, braces must be preserved to keep the final "else" associated \
with the first "if". function(dr) { return "/*TODE1*/" + T([maybeLabel(), "if(", \
makeExpr(dr), ") ", "{", " if ", "(", makeExpr(dr), ") ", makeStatementOrBlock(dr-1), \
" else ", makeStatementOrBlock(dr-1), "}"]); }, function(dr) { return "/*TODE2*/" + \
T([maybeLabel(), "if(", makeExpr(dr), ") ", "{", " if ", "(", makeExpr(dr), ") ", \
makeStatementOrBlock(dr-1), "}", " else ", makeStatementOrBlock(dr-1)]); },
// Exception-related statements :)
function(dr) { return makeExceptionyStatement(dr-1); makeExceptionyStatement(dr-1); \
}, function(dr) { return makeExceptionyStatement(dr-1); \
makeExceptionyStatement(dr-1); }, function(dr) { return makeExceptionyStatement(dr); \
}, function(dr) { return makeExceptionyStatement(dr); },
function(dr) { return makeExceptionyStatement(dr); },
function(dr) { return makeExceptionyStatement(dr); },
function(dr) { return makeExceptionyStatement(dr); },
// Labels. (JavaScript does not have goto, but it does have break-to-label and \
continue-to-label). function(dr) { return T(["L", ": ", makeStatementOrBlock(dr)]); \
}, ];
function makeLittleStatement(depth)
{
var dr = depth - 1;
if (rnd(4) == 1)
return makeStatement(dr);
return (rndElt(littleStatementMakers))(dr);
}
var littleStatementMakers =
[
// Tiny
function(dr) { return T([";"]); }, // e.g. empty "if" block
function(dr) { return T(["{", "}"]); ; }, // e.g. empty "if" block
function(dr) { return T([""]); },
// Force garbage collection
function(dr) { return "gc()"; },
// Throw stuff.
function(dr) { return T(["throw ", makeExpr(dr), ";"]); },
// Break/continue [to label].
function(dr) { return T([rndElt(["continue", "break"]), " ", rndElt(["L", "M", "", \
""]), ";"]); },
// Import and export. (I have not idea what these actually do.)
function(dr) { return T(["export ", makeId(dr), ";"]); },
function(dr) { return T(["export ", "*", ";"]); },
function(dr) { return T(["import ", makeId(dr), ".", makeId(dr), ";"]); },
function(dr) { return T(["import ", makeId(dr), ".", "*", ";"]); },
// Named and unnamed functions (which have different behaviors in different places: \
both can be expressions, // but unnamed functions "want" to be expressions and named \
functions "want" to be special statements) function(dr) { return makeFunction(dr); \
},
// Return, yield
function(dr) { return T(["return ", makeExpr(dr), ";"]); },
function(dr) { return "return;"; }, // return without a value is allowed in \
generators; return with a value is not. function(dr) { return T(["yield ", \
makeExpr(dr), ";"]); }, // note: yield can also be a left-unary operator, or \
something like that function(dr) { return "yield;"; },
// Expression statements
function(dr) { return T([makeExpr(dr), ";"]); },
function(dr) { return T(["(", makeExpr(dr), ")", ";"]); },
// Various kinds of variable declarations, with and without initial values \
(assignment). function(dr) { return T([rndElt(varBinder), makeId(dr), ";"]); },
function(dr) { return T([rndElt(varBinder), makeId(dr), " = ", makeExpr(dr), ";"]); \
}, function(dr) { return T([rndElt(varBinder), makeLValue(dr), " = ", makeExpr(dr), \
";"]); }, // e.g. "const [a,b] = [3,4];" ]
// makeStatementOrBlock exists because often, things have different behaviors \
depending on where there are braces. // for example, if braces are added or removed, \
the meaning of "let" can change. function makeStatementOrBlock(depth)
{
var dr = depth - 1;
return (rndElt(statementBlockMakers))(dr)
}
var statementBlockMakers = [
function(dr) { return makeStatement(dr); },
function(dr) { return makeStatement(dr); },
function(dr) { return T(["{", makeStatement(dr), " }"]); },
function(dr) { return T(["{", makeStatement(dr-1), makeStatement(dr-1), " }"]); },
]
// Extra-hard testing for try/catch/finally and related things.
function makeExceptionyStatement(depth)
{
var dr = depth - 1;
if (dr < 1)
return makeLittleStatement(dr);
return (rndElt(exceptionyStatementMakers))(dr);
}
var exceptionyStatementMakers = [
function(dr) { return makeTryBlock(dr); },
function(dr) { return makeStatement(dr); },
function(dr) { return makeLittleStatement(dr); },
function(dr) { return "return;" }, // return without a value can be mixed with \
yield function(dr) { return T(["return ", makeExpr(dr), ";"]); },
function(dr) { return T(["yield ", makeExpr(dr), ";"]); },
function(dr) { return T(["throw ", makeId(dr), ";"]); },
function(dr) { return "throw StopIteration;"; },
function(dr) { return "this.zzz.zzz;"; }, // throws; also tests \
js_DecompileValueGenerator in various locations function(dr) { return T([makeId(dr), \
" = ", makeId(dr), ";"]); }, function(dr) { return T([makeLValue(dr), " = ", \
makeId(dr), ";"]); },
// Iteration uses StopIteration internally.
// Iteration is also useful to test because it asserts that there is no pending \
exception. function(dr) { return "for(let y in []);"; },
function(dr) { return "for(let y in [5,6,7,8]) " + makeExceptionyStatement(dr); },
// Brendan says these are scary places to throw: with, let block, lambda called \
immediately in let expr. // And I think he was right.
function(dr) { return "with({}) " + makeExceptionyStatement(dr); },
function(dr) { return "with({}) { " + makeExceptionyStatement(dr) + " } "; },
function(dr) { return "let(y) { " + makeExceptionyStatement(dr); + "}"},
function(dr) { return "let(y) ((function(){" + makeExceptionyStatement(dr) + \
"})());" }
];
function makeTryBlock(depth)
{
// Catches: 1/6 chance of having none
// Catches: maybe 2 + 1/2
// So approximately 4 recursions into makeExceptionyStatement on average!
// Therefore we want to keep the chance of recursing too much down...
var dr = depth - rnd(3);
var s = T(["try", " { ", makeExceptionyStatement(dr), " } "]);
var numCatches = 0;
var ct;
while((ct = rnd(6)) < 2) {
// Add a guarded catch, using an expression or a function call.
++numCatches;
if (ct == 0)
s += T(["catch", "(", makeId(dr), " if ", makeExpr(dr), \
")", " { ", makeExceptionyStatement(dr), " } "]); else // ct == 1
s += T(["catch", "(", makeId(dr), " if ", "(function(){", \
makeExceptionyStatement(dr), "})())", " { ", makeExceptionyStatement(dr), " } "]); }
if (rnd(2)) {
// Add an unguarded catch.
++numCatches;
s += T(["catch", "(", makeId(dr), \
")", " { ", makeExceptionyStatement(dr), " } "]); }
if (numCatches == 0 || rnd(2) == 1) {
// Add a finally.
s += T(["finally", " { ", makeExceptionyStatement(dr), " } "]);
}
return s;
}
// Creates a string that sorta makes sense as an expression
function makeExpr(depth)
{
if (depth <= 0 || (rnd(7) == 1))
return makeTerm(depth - 1);
var dr = rnd(depth); // depth - 1;
var expr = (rndElt(exprMakers))(dr);
if (rnd(4) == 1)
return "(" + expr + ")";
else
return expr;
}
var binaryOps = [
// Long-standing JavaScript operators, roughly in order from \
http://www.codehouse.com/javascript/precedence/ " * ", " / ", " % ", " + ", " - ", " \
<< ", " >> ", " >>> ", " < ", " > ", " <= ", " >= ", " instanceof ", " in ", " == ", \
" != ", " === ", " !== ", " & ", " | ", " ^ ", " && ", " || ", " = ", " *= ", " /= \
", " %= ", " += ", " -= ", " <<= ", " >>= ", " >>>=", " &= ", " ^= ", " |= ", " , ",
// . is special, so test it as a group of right-unary ops, a special exprMaker for \
property access, and a special exprMaker for the xml filtering predicate operator // \
" . ",
// Added by E4X
" :: ", " .. ", " @ ",
// Frequent combinations of E4X things (and "*" namespace, which isn't produced by \
this fuzzer otherwise) " .@ ", " .@*:: ", " .@x:: ",
];
var leftUnaryOps = [
"--", "++",
"!", "+", "-", "~",
"void ", "typeof ", "delete ",
"new ", // but note that "new" can also be a very strange left-binary operator
"yield " // see http://www.python.org/dev/peps/pep-0342/ . Often needs to be \
parenthesized, so there's also a special exprMaker for it. ];
var rightUnaryOps = [
"++", "--",
// E4X
".*", ".@foo", ".@*"
];
var specialProperties = [".prop", ".__iterator__", ".__parent__", ".__proto__"]
var exprMakers =
[
// Left-unary operators
function(dr) { return T([rndElt(leftUnaryOps), makeExpr(dr)]); },
// Right-unary operators
function(dr) { return T([makeExpr(dr), rndElt(rightUnaryOps)]); },
function(dr) { return T([makeExpr(dr), rndElt(specialProperties)]); },
// Binary operators
function(dr) { return T([makeExpr(dr), rndElt(binaryOps), makeExpr(dr)]); },
// Ternary operator
function(dr) { return T([makeExpr(dr), " ? ", makeExpr(dr), " : ", makeExpr(dr)]); \
},
// In most contexts, yield expressions must be parenthesized, so including \
explicitly parenthesized yields makes actually-compiling yields appear more often. \
function(dr) { return T(["(", "yield ", makeExpr(dr), ")"]); },
// Array extras. The most interesting are map and filter, I think.
// These are mostly interesting to fuzzers in the sense of "what happens if i do \
strange things from a filter function?" e.g. modify the array.. :) // This fuzzer \
isn't the best for attacking this kind of thing, since it's unlikely that the code in \
the function will attempt to modify the array or make it go away. // The second \
parameter to "map" is used as the "this" for the function. function(dr) { return \
T(["[11,12,13,14]", ".", rndElt(["map", "filter", "some"]) ]); }, \
function(dr) { return T(["[15,16,17,18]", ".", rndElt(["map", "filter", \
"some"]), "(", makeFunction(dr), ", ", makeExpr(dr), ")"]); }, function(dr) { return \
T(["[", makeExpr(dr), "]", ".", rndElt(["map", "filter", "some"]), "(", \
makeFunction(dr), ")"]); },
// RegExp replace. This is interesting for same same reason as array extras.
function(dr) { return T(["'fafafa'", ".", "replace", "(", "/", "a", "/", "g", ", ", \
makeFunction(dr), ")"]); },
// XML filtering predicate operator!
function(dr) { return T([makeId(dr), ".", "(", makeExpr(dr), ")"]); },
function(dr) { return T([makeE4X(dr), ".", "(", makeExpr(dr), ")"]); },
// Dot (property access)
function(dr) { return T([makeId(dr), ".", makeId(dr)]); },
function(dr) { return T([makeExpr(dr), ".", makeId(dr)]); },
// Index into array
function(dr) { return T([ makeExpr(dr), "[", makeExpr(dr), "]"]); },
function(dr) { return T(["(", makeExpr(dr), ")", "[", makeExpr(dr), "]"]); },
// Containment in an array or object (or, if this happens to end up on the LHS of \
an assignment, destructuring) function(dr) { return T(["[", makeExpr(dr), "]"]); },
function(dr) { return T(["(", "{", makeId(dr), ": ", makeExpr(dr), "}", ")"]); },
// Functions: called immediately/not
function(dr) { return makeFunction(dr); },
function(dr) { return T(["(", makeFunction(dr), ")", "(", makeActualArgList(dr), \
")"]); },
// Try to call things that may or may not be functions.
function(dr) { return T([ makeExpr(dr), "(", makeActualArgList(dr), ")"]); \
}, function(dr) { return T(["(", makeExpr(dr), ")", "(", makeActualArgList(dr), \
")"]); },
// Binary "new", with and without clarifying parentheses, with empty and non-empty \
parens (note that empty parens for new usually decompile to zilch) function(dr) { \
return T(["new ", makeExpr(dr), "(", makeExpr(dr-1), ")"]); }, \
function(dr) { return T(["new ", "(", makeExpr(dr), ")", "(", makeExpr(dr-1), ")"]); \
}, function(dr) { return T(["new ", makeExpr(dr), "(", \
")"]); }, function(dr) { return T(["new ", "(", makeExpr(dr), ")", "(", \
")"]); },
// Sometimes we do crazy stuff, like putting a statement where an expression should \
go. This frequently causes a syntax error. function(dr) { return \
stripSemicolon(makeLittleStatement(dr)); }, function(dr) { return ""; },
// Let expressions -- note the lack of curly braces.
function(dr) { return T(["let", " (", "x", "=", "3", ",", "y", "=", "4", ") ", \
makeExpr(dr)]); }, function(dr) { return T(["let ", "(", makeId(dr), " = ", "3", \
") ", makeExpr(dr)]); }, function(dr) { return T(["let ", "(", makeId(dr), " = ", \
makeExpr(dr), ") ", makeExpr(dr)]); },
// Array comprehensions
function(dr) { return T(["[", makeExpr(dr), " for ", "(", makeId(dr), " in \
", makeExpr(dr-2), ")", "]"]); }, function(dr) { return T(["[", makeExpr(dr), " for \
", "each ", "(", makeId(dr), " in ", makeExpr(dr-2), ")", "]"]); },
function(dr) { return T([" /* Comment */", makeExpr(dr)]); },
function(dr) { return T(["\n", makeExpr(dr)]); }, // perhaps trigger semicolon \
insertion and stuff function(dr) { return T([makeExpr(dr), "\n"]); },
function(dr) { return T([makeLValue(dr)]); },
// Assignment (can be destructuring)
function(dr) { return T([makeLValue(dr), " = ", makeExpr(dr)]); },
function(dr) { return T([makeLValue(dr), " = ", makeExpr(dr)]); },
function(dr) { return T([makeLValue(dr), " = ", makeExpr(dr)]); },
function(dr) { return T([makeLValue(dr), " = ", makeExpr(dr)]); },
// Destructuring assignment
function(dr) { return T([makeDestructuringLValue(dr), " = ", makeExpr(dr)]); },
function(dr) { return T([makeDestructuringLValue(dr), " = ", makeExpr(dr)]); },
function(dr) { return T([makeDestructuringLValue(dr), " = ", makeExpr(dr)]); },
function(dr) { return T([makeDestructuringLValue(dr), " = ", makeExpr(dr)]); },
// Modifying assignment, with operators that do various coercions
function(dr) { return T([makeLValue(dr), rndElt(["|=", "%=", "+=", "-="]), \
makeExpr(dr)]); },
// Object literal with (newish-style?) getter and setter
function(dr) { return T(["(", "{", " get ", makeId(dr), "(", \
makeFormalArgList(dr-1), ")", " { " , makeStatement(dr-1), " }", ",", " set ", \
makeId(dr), "(", makeFormalArgList(dr-1), ")", " { ", makeStatement(dr-1), "}", " }", \
")"]); },
// Object literal with obscure old-style getter and setter
// commented out due to comments late in bug 352455
//function(dr) { return T(["(", "{", makeId(dr), " getter: ", makeFunction(dr), " \
}", ")"]); }, //function(dr) { return T(["(", "{", makeId(dr), " setter: ", \
makeFunction(dr), " }", ")"]); },
// Old-style getter/setter
function(dr) { return T([makeId(dr), ".", makeId(dr), " ", rndElt(["getter", \
"setter"]), "= ", makeFunction(dr)]); },
// Try to cause trouble using toString and valueOf.
function(dr) { return T(["({y:5, ", "toString: ", makeFunction(dr), "}", ")"]); },
function(dr) { return T(["({y:5, ", "toString: function() { return this; } }", \
")"]); }, // bwahaha function(dr) { return T(["({y:5, ", "toString: function() { \
return " + makeExpr(dr) + "; } }", ")"]); }, // Was commented out to avoid bug \
352742. (This is an execution bug, but compilation and decompilation are tested just \
fine by the toString stuff above.) function(dr) { return T(["({y:5, ", "valueOf: ", \
makeFunction(dr), "}", ")"]); }, function(dr) { return T(["({y:5, ", "valueOf: \
function() { return " + makeExpr(dr) + "; } }", ")"]); },
// Try to test function.call heavily.
function(dr) { return T(["(", makeFunction(dr), ")", ".", "call", "(", \
makeExpr(dr), ", ", makeExpr(dr), ")"]); },
// Test js_ReportIsNotFunction heavily.
function(dr) { return "(p={}, (p.z = " + makeExpr(dr) + ")())"; },
// Test js_ReportIsNotFunction heavily.
// Test decompilation for ".keyword" a bit.
// Test throwing-into-generator sometimes.
function(dr) { return T([makeExpr(dr), ".", "throw", "(", makeExpr(dr), ")"]); },
function(dr) { return T([makeExpr(dr), ".", "pow", "(", makeExpr(dr), ")"]); },
// Throws, but more importantly, tests js_DecompileValueGenerator in various \
contexts. function(dr) { return "this.zzz.zzz"; },
// Test obj.eval stuff carefully (but avoid clobbering eval)
function(dr) { return makeExpr(dr) + ".eval(" + makeExpr(dr) + ")"; },
function(dr) { return "eval(" + uneval(makeExpr(dr)) + ", " + makeExpr(dr) + ")"; \
}, // eval an actual expression! how clever! function(dr) { return "eval(" + \
uneval(makeStatement(dr)) + ", " + makeExpr(dr) + ")"; }, // eval an actual \
statement! how clever!
// Uneval needs more testing than it will get accidentally. No T() because I don't \
want uneval clobbered (assigned to). function(dr) { return "(uneval(" + makeId(dr) + \
"))"; },
// Constructors. No "T" -- don't screw with the constructors themselves; just call \
them. // Was commented out for bug 352742 (?)
function(dr) { return "/* CONSTRUCT */ new " + rndElt(constructors) + "(" + \
makeActualArgList(dr) + ")"; }
];
var constructors = [
/* "Error", "RangeError", "String", "Function", "Array", "Object" \
https://bugzilla.mozilla.org/show_bug.cgi?id=352742#c16 */ "Iterator"
];
function makeFunction(depth)
{
var dr = depth - 1;
if(rnd(5) == 1)
return makeExpr(dr);
var y = [
// Note that a function with a name is sometimes considered a statement rather \
than an expression.
// Unnamed (lambda, generator)
function(dr) { return T(["function", " (", makeFormalArgList(dr), ")", " { ", \
makeStatement(dr), " }"]); }, function(dr) { return T(["function", " (", \
makeFormalArgList(dr), ")", " { ", "return ", makeExpr(dr), " }"]); }, function(dr) \
{ return T(["function", " (", makeFormalArgList(dr), ")", " { ", "yield ", \
makeExpr(dr), " }"]); },
// Named
function(dr) { return T(["function ", makeId(dr), "(", makeFormalArgList(dr), \
")", " { ", makeStatement(dr), " }"]); },
// The identity function
function(dr) { return "function(id2) { return id2; }" },
// A generator that does something
function(dr) { return "function(y) { yield y; " + makeStatement(dr) + "; yield y; \
}" },
// Special functions that might have interesting results, especially when called \
"directly" by things like string.replace or array.map. function(dr) { return "eval" \
}, // eval is interesting both for its "no indirect calls" feature and for the way \
it's implemented -- a special bytecode. function(dr) { return "new Function" }, // \
this won't be interpreted the same way for each caller of makeFunction, but that's ok \
function(dr) { return "Function" }, // without "new"! it does seem to work... \
function(dr) { return "gc" }, function(dr) { return "Math.sin" },
function(dr) { return "/a/gi" }, // in Firefox, at least, regular expressions can \
be used as functions: e.g. "hahaa".replace(/a+/g, /aa/g) is hnullhaa. function(dr) { \
return "[1,2,3,4].map" }, function(dr) { return "[1,2,3,4].slice" },
function(dr) { return "'haha'.split" },
function(dr) { return T(["(", makeFunction(dr), ")", ".", "call"]); },
function(dr) { return T(["(", makeFunction(dr), ")", ".", "apply"]); },
];
return (rndElt(y))(dr);
}
function makeActualArgList(depth)
{
var nArgs = rnd(3);
if (nArgs == 0)
return "";
var argList = makeExpr(depth);
for (var i = 1; i < nArgs; ++i)
argList += ", " + makeExpr(depth - i);
dumpln("Actual argList: " + argList);
return argList;
}
function makeFormalArgList(depth)
{
var nArgs = rnd(3);
if (nArgs == 0)
return "";
var argList = makeFormalArg(depth)
for (var i = 1; i < nArgs; ++i)
argList += ", " + makeFormalArg(depth - i);
dumpln("Formal argList: " + argList);
return argList;
}
function makeFormalArg(depth)
{
if (rnd(4) == 1)
return makeDestructuringLValue(depth);
return makeId(depth);
}
function makeId(dr)
{
switch(rnd(200))
{
case 0:
return makeTerm(dr);
case 1:
return makeExpr(dr);
case 2: case 3: case 4: case 5:
return makeLValue(dr);
case 6: case 7:
return makeDestructuringLValue(dr);
case 8: case 9: case 10: case 11: case 12:
// some keywords that can be used as identifiers in some contexts (e.g. \
variables, function names, argument names) // but that's annoying, and some of these \
cause lots of syntax errors. return rndElt(["get", "set", "getter", "setter", \
"delete", "let", "yield", "each"]); }
return rndElt(["x", "x", "x", "x", "x", "x", "x", "x", // repeat "x" so it's likely \
to be bound more than once, causing "already bound" errors, elimination of \
assign-to-const, or conflicts "y", "window", "this", "\u3056", "NaN"
]);
// window is a const, so some attempts to redeclare it will cause errors
// eval is interesting because it cannot be called indirectly. and maybe also \
because it has its own opcode in jsopcode.tbl. // but bad things happen if you have \
"eval setter"... so let's not put eval in this list. }
function makeLValue(depth)
{
if (depth <= 0 || (rnd(7) == 1))
return makeId(depth - 1);
var dr = rnd(depth);
return (rndElt(lvalueMakers))(dr);
}
var lvalueMakers = [
// Simple variable names :)
function(dr) { return T([makeId(dr)]); },
// Destructuring
function(dr) { return makeDestructuringLValue(dr); },
function(dr) { return makeDestructuringLValue(dr); },
// Properties
function(dr) { return T([makeId(dr), ".", makeId(dr)]); },
function(dr) { return T([makeExpr(dr), ".", makeId(dr)]); },
function(dr) { return T([makeExpr(dr), "[", "'", makeId(dr), "'", "]"]); },
// Special properties
function(dr) { return T([makeId(dr), rndElt(specialProperties)]); },
// Certain functions can act as lvalues! See JS_HAS_LVALUE_RETURN in js engine \
source. function(dr) { return T([makeId(dr), "(", makeExpr(dr), ")"]); },
function(dr) { return T(["(", makeExpr(dr), ")", "(", makeExpr(dr), ")"]); },
// Parenthesized lvalues can cause problems ;)
function(dr) { return T(["(", makeLValue(dr), ")"]); },
function(dr) { return makeExpr(dr); } // intentionally bogus, but not quite \
garbage. ];
function makeDestructuringLValue(depth)
{
var dr = depth - 1;
if (dr < 0 || rnd(4) == 1)
return makeId(dr);
if (rnd(6) == 1)
return makeLValue(dr);
return (rndElt(destructuringLValueMakers))(dr);
}
var destructuringLValueMakers = [
// destructuring assignment: arrays
function(dr) { return T(["[", makeDestructuringLValue(dr), "]"]); },
function(dr) { return T(["[", makeDestructuringLValue(dr), ", ", \
makeDestructuringLValue(dr), "]"]); },
// destructuring assignment: objects
function(dr) { return T(["(", "{ ", makeId(dr), ": ", makeDestructuringLValue(dr), \
" }", ")"]); }, function(dr) { return T(["(", "{ ", makeId(dr), ": ", \
makeDestructuringLValue(dr), ", ", makeId(dr), ": ", makeDestructuringLValue(dr), " \
}", ")"]); }, ];
function makeTerm(dr)
{
var y = [
// Variable names
function(dr) { return makeId(dr); },
// Simple literals (no recursion required to make them)
function(dr) { return rndElt([
"[]", "[1]", "[[]]", "[[1]]",
"{}", "({})", "({a1:1})",
"[z1]", // can be interpreted as destructuring
"({a2:z2})", // can be interpreted as destructuring
"function(id) { return id }",
"function ([y]) { }",
"(function ([y]) { })()"
]);
},
function(dr) { return rndElt([ "0.1", ".2", "3", "1.3", "4.", \
"5.0000000000000000000000", "1e81", "1e+81", "1e-81", "1e4", "0", "-0", "(-0)", \
"0x99", "033", (""+Math.PI), "3/0", "-3/0", "0/0"]); }, function(dr) { return \
rndElt([ "true", "false", "undefined", "null"]); }, function(dr) { return rndElt([ \
"this", "window" ]); }, function(dr) { return rndElt([" \"\" ", " '' ", " /x/ ", " \
/x/g "]) },
// E4X literals
function(dr) { return rndElt([ "<x/>", "<y><z/></y>"]); },
function(dr) { return rndElt([ "@foo" /* makes sense in filtering predicates, at \
least... */, "*", "*::*"]); }, function(dr) { return makeE4X(dr) }, // xml
function(dr) { return T(["<", ">", makeE4X(dr), "<", "/", ">"]); }, // xml list
];
return (rndElt(y))(dr);
}
function maybeMakeTerm(depth)
{
if (rnd(2))
return makeTerm(depth - 1);
else
return "";
}
var crazyTokens =
[
// Some of this is from reading jsscan.h.
// Comments; comments hiding line breaks.
"//", UNTERMINATED_COMMENT, (UNTERMINATED_COMMENT + "\n"), "/*\n*/",
// groupers (which will usually be unmatched if they come from here ;)
"[", "{", "]", "}", "(", ")",
// a few operators
"!", "@", "%", "^", "*", "|", ":", "?", "'", "\"", ",", ".", "/",
"~", "_", "+", "=", "-", "++", "--", "+=", "%=", "|=", "-=",
// most real keywords plus a few reserved keywords
" in ", " instanceof ", " let ", " new ", " get ", " for ", " if ", " else ", " \
else if ", " try ", " catch ", " finally ", " export ", " import ", " void ", " with \
", " default ", " goto ", " case ", " switch ", " do ", " /*noex*/while ", " return \
", " yield ", " break ", " continue ", " typeof ", " var ", " const ", " enum ", // \
JS_HAS_RESERVED_ECMA_KEYWORDS " debugger ", // JS_HAS_DEBUGGER_KEYWORD
" super ", // TOK_PRIMARY!
" this ", // TOK_PRIMARY!
" null ", // TOK_PRIMARY!
" undefined ", // not a keyword, but a default part of the global object
"\n", // trigger semicolon insertion, also acts as whitespace where it might not be \
expected "\r",
"\u2028", // LINE_SEPARATOR?
"\u2029", // PARA_SEPARATOR?
"<" + "!" + "--", // beginning of HTML-style to-end-of-line comment (!)
"--" + ">", // end of HTML-style comment
"",
"\0", // confuse anything that tries to guess where a string ends. but note: \
"illegal character"! ];
function makeE4X(depth)
{
if (depth <= 0)
return T(["<", "x", ">", "<", "y", "/", ">", "<", "/", "x", ">"]);
var dr = depth - 1;
var y = [
function(dr) { return '<employee id="1"><name>Joe</name><age>20</age></employee>' \
}, function(dr) { return T(["<", ">", makeSubE4X(dr), "<", "/", ">"]); }, // xml \
list
function(dr) { return T(["<", ">", makeExpr(dr), "<", "/", ">"]); }, // bogus or \
text function(dr) { return T(["<", "zzz", ">", makeExpr(dr), "<", "/", "zzz", ">"]); \
}, // bogus or text
// mimic parts of this example at a time, from the e4x spec: <x><{tagname} \
{attributename}={attributevalue+attributevalue}>{content}</{tagname}></x>;
function(dr) { var tagId = makeId(dr); return T(["<", "{", tagId, "}", ">", \
makeSubE4X(dr), "<", "/", "{", tagId, "}", ">"]); }, function(dr) { var attrId = \
makeId(dr); var attrValExpr = makeExpr(dr); return T(["<", "xxx", " ", "{", attrId, \
"}", "=", "{", attrValExpr, "}", " ", "/", ">"]); }, function(dr) { var contentId = \
makeId(dr); return T(["<", "xxx", ">", "{", contentId, "}", "<", "/", "xxx", ">"]); \
},
// namespace stuff
function(dr) { var contentId = makeId(dr); return T(['<', 'bbb', ' ', 'xmlns', \
'=', '"', makeExpr(dr), '"', '>', makeSubE4X(dr), '<', '/', 'bbb', '>']); }, \
function(dr) { var contentId = makeId(dr); return T(['<', 'bbb', ' ', 'xmlns', ':', \
'ccc', '=', '"', makeExpr(dr), '"', '>', '<', 'ccc', ':', 'eee', '>', '<', '/', \
'ccc', ':', 'eee', '>', '<', '/', 'bbb', '>']); },
function(dr) { return makeExpr(dr); },
function(dr) { return makeSubE4X(dr); }, // naked cdata things, etc.
]
return (rndElt(y))(dr);
}
function makeSubE4X(depth)
{
if (rnd(8) == 0)
return "<" + "!" + "[" + "CDATA[" + makeExpr(depth - 1) + "]" + "]" + ">"
if (depth < -2)
return "";
var y = [
function(depth) { return T(["<", "ccc", ":", "ddd", ">", makeSubE4X(depth - 1), \
"<", "/", "ccc", ":", "ddd", ">"]); }, function(depth) { return makeE4X(depth) + \
makeSubE4X(depth - 1); }, function(depth) { return "yyy"; },
function(depth) { return T(["<", "!", "--", "yy", "--", ">"]); }, // XML comment
function(depth) { return T(["<", "!", "[", "CDATA", "[", "zz", "]", "]", ">"]); \
}, // XML cdata section function(depth) { return " "; },
function(depth) { return ""; },
];
return (rndElt(y))(depth);
}
function start()
{
count = 0;
if (jsshell) {
// Number of iterations: 20000 is good for use with multi_timed_run.py. (~40 \
seconds, reduction isn't bad.) // Raise for use without multi_timed_run.py (perhaps \
to Infinity). // Lower for use with WAY_TOO_MUCH_GC.
for (var i = 0; i < 20000; ++i)
testOne();
dumpln("It's looking good!"); // Magic string that multi_timed_run.py looks for
}
else {
setTimeout(testStuffForAWhile, 200);
}
}
function testStuffForAWhile()
{
for (var j = 0; j < 200; ++j)
testOne();
dumpln("...");
dumpln("");
setTimeout(testStuffForAWhile, 200);
}
function testOne()
{
++count;
var code = makeStatement(8);
if (haveUneval)
dumpln("count=" + count + "; tryItOut(" + uneval(code) + ");");
tryItOut(code);
}
function tryItOut(code)
{
var codeWithoutLineBreaks = code.replace(/\n/g, " ").replace(/\r/g, " "); // \
regexps can't match across lines var allowDecompile, checkRecompiling, \
checkForMismatch, allowExec, allowIter, checkLetScope;
// This section is mostly exclusions for known Spidermonkey bugs.
// Things like "noex" can appear in literal strings or comments.
// Don't exec things that are likely infinite loops, or have known bugs when \
executing. // Don't decompile things that have known bugs when decompiling, etc.
// Exclude things here if decompiling crashes.
allowDecompile = (code.indexOf("nodecompile") == -1)
;
// Exclude things here if decompiling returns something bogus that won't compile.
checkRecompiling = (code.indexOf("ignoredecompilation") == -1)
&& !( codeWithoutLineBreaks.match( /for.*\(.*in.*const/ )) // avoid bug 352083, \
with for loops or array comprehensions && !( codeWithoutLineBreaks.match( \
/case.*yield.*\:/ )) // avoid the two forms of bug 352441 && !( \
codeWithoutLineBreaks.match( /L\:.*let/ )) // avoid bug 352732 (and comment 1 there) \
&& (code.indexOf("eval") == -1) // avoid bug 352453 && (code.indexOf("HOISTY") == \
-1 || code.indexOf(":") == -1) // avoid bug 352921 (i hope this is correct) ;
// Exclude things here if decompiling returns something incorrect or suboptimal or \
non-canonical, but that will compile.
checkForMismatch = (code.indexOf("mismatchok") == -1)
&& !( codeWithoutLineBreaks.match( /\<.*\{/ )) // avoid bug 351706, which won't \
be fixed for a while :( && !( codeWithoutLineBreaks.match( /new.*\.\@/ )) // avoid \
bug 352789 && !( codeWithoutLineBreaks.match( /const.*if/ )) // avoid bug 352985
&& !( codeWithoutLineBreaks.match( /if.*const/ )) // avoid bug 352985
&& (code.indexOf("delete") == -1) // avoid bug 352027, which won't be fixed for \
a while :( && (code.indexOf("CDATA") == -1) // avoid bug 352285
&& (code.indexOf("<!") == -1) // avoid bug 352285
&& (code.indexOf("<?") == -1) // avoid bug 352285
&& (code.indexOf("import") == -1) // avoid bug 350681
&& (code.indexOf("export") == -1) // avoid bug 350681
&& (code.indexOf("new") == -1) // avoid bug 353146
&& (code.indexOf("-3/0") == -1) // avoid bug 351219 ? negation creeps away
&& (code.indexOf("3/0") == -1) // avoid thing where function() { return \
<x/>.(0/0) } changes parens during round trip, probably just more of bug 351219 && \
(code.indexOf("0/0") == -1) // avoid thing where function() { return <x/>.(0/0) } \
changes parens during round trip, probably just more of bug 351219
&& (code.indexOf("%") == -1) // avoid bug 352085 -- common
&& (code.indexOf(">>") == -1) // avoid bug 352085 -- somewhat common
&& (code.indexOf("<<") == -1) // avoid bug 352085 -- somewhat common
&& (code.indexOf("/") == -1 || (code.indexOf("\"") == -1 && code.indexOf("\'") \
== -1) ) // avoid bug 352085 - annoying && (code.indexOf("|") == -1 || \
(code.indexOf("\"") == -1 && code.indexOf("\'") == -1) ) // avoid bug 352085 - \
annoying && (code.indexOf("*") == -1 || (code.indexOf("\"") == -1 && \
code.indexOf("\'") == -1) ) // avoid bug 352085 && (code.indexOf("-") == -1 || \
(code.indexOf("\"") == -1 && code.indexOf("\'") == -1) ) // avoid bug 352085 && \
(code.indexOf("~") == -1 || (code.indexOf("\"") == -1 && code.indexOf("\'") == -1) ) \
// avoid bug 352085
// Mostly the same tests as for checkLetScope, hmm.
&& !( codeWithoutLineBreaks.match( /do.*let.*while/ )) // avoid bug 352421
&& !( codeWithoutLineBreaks.match( /with.*let/ )) // avoid rebracing issues that \
fall out of bug 352422 && !( codeWithoutLineBreaks.match( /for.*let.*\).*function/ \
)) // avoid bug 352735 (more rebracing stuff) && !( codeWithoutLineBreaks.match( \
/if\s*\(.*\).*let/ )) // avoid bug 352786 ;
checkLetScope = false // this test is slow :(
&& (code.indexOf("ignoreletscope") == -1)
// Shared...
&& !( codeWithoutLineBreaks.match( /do.*let.*while/ )) // avoid bug 352421
&& !( codeWithoutLineBreaks.match( /with.*let/ )) // avoid rebracing issues that \
fall out of bug 352422 && !( codeWithoutLineBreaks.match( \
/(for|while).*(for|while).*let/ )) // avoid bug 352907 && !( \
codeWithoutLineBreaks.match( /if\s*\(.*\).*let/ )) // avoid bug 352786
// Only for checkLetScope...
&& !( codeWithoutLineBreaks.match( /for.*let.*\).*function/ )) // avoid bug \
352735 (more rebracing stuff) ;
allowExec = code.indexOf("noex") == -1
&& (jsshell || code.indexOf("nobrowserex") == -1)
&& !( codeWithoutLineBreaks.match( /const.*for/ )) // can be an infinite loop: \
function() { const x = 1; for each(x in ({a1:1})) print(3); } && !( \
codeWithoutLineBreaks.match( /for.*const/ )) // can be an infinite loop: for each(x \
in ...); const x; && !( codeWithoutLineBreaks.match( /for.*in.*uneval/ )) // can be \
slow to loop through the huge string uneval(this), for example && !( \
codeWithoutLineBreaks.match( /delete.*Function/ )) // avoid bug 352604 (exclusion \
needed despite the realFunction stuff?!) && !( codeWithoutLineBreaks.match( \
/for.*for.*for.*for.*for/ )) // nested for loops (array comprehensions, etc) can take \
a while && !( codeWithoutLineBreaks.match( /eval.*return/ )) // avoid bug 352986
&& !( codeWithoutLineBreaks.match( /eval.*yield/ )) // avoid bug 352986
&& !( codeWithoutLineBreaks.match( /eval.*break/ )) // avoid bug 352986
&& !( codeWithoutLineBreaks.match( /eval.*continue/ )) // avoid bug 352986
;
allowIter = code.indexOf("noiter") == -1
&& typeof Iterator == "function" // false in e.g. Safari and older versions of \
Firefox ;
if(verbose)
dumpln("Verbose, count: " + count);
if(verbose)
dumpln("allowExec=" + allowExec + ", allowDecompile=" + allowDecompile + ", \
checkRecompiling=" + checkRecompiling + ", checkForMismatch=" + checkForMismatch + ", \
allowIter=" + allowIter + ", checkLetScope=" + checkLetScope);
tryHalves(code);
var f = null;
try {
// Try two methods of creating functions, just in case there are differences.
if (count % 2 == 0 && allowExec) {
if (verbose)
dumpln("About to compile, using eval hack.")
f = eval("$=function(){" + code + "};"); // Disadvantage: "}" can "escape", \
allowing code to *execute* that we only intended to compile. Hence the allowExec \
check. }
else {
if (verbose)
dumpln("About to compile, using new Function.")
f = new Function(code);
}
} catch(compileError) {
dumpln("Compiling threw: " + errorToString(compileError));
}
if (f && allowDecompile) {
if (verbose)
dumpln("About to do the 'toString' round-trip test");
checkRoundTrip2(f, code, checkRecompiling, checkForMismatch); // i like line \
breaks, so this one comes first if (haveUneval) {
if (verbose)
dumpln("About to do the 'uneval' round-trip test");
checkRoundTrip(f, code, checkRecompiling, checkForMismatch);
}
}
if (f && allowDecompile && checkRecompiling && checkLetScope) {
if (verbose)
dumpln("About to do the 'let x;' test");
checkLetX(code);
checkLetX(extractCode(f));
}
var rv = null;
if (allowExec && f) {
try {
// var e = eval(code);
if (verbose)
dumpln("About to run it!");
rv = f();
if (verbose)
dumpln("It ran!"); // Keeping this because I'm confused!
} catch(runError) {
if(verbose)
dumpln("Running threw! About to toString to error.");
dumpln("Running threw: " + errorToString(runError));
}
}
allowIter = allowIter && rv && typeof rv == "object";
if (allowIter) {
try {
allowIter = (Iterator(rv) === rv)
}
catch(e) {
// Is it a bug that it's possible to end up here? Probably not!
allowIter = false;
dumpln("Error while trying to determine whether it's an iterator!");
dumpln("The error was: " + e);
}
}
if (allowIter) {
dumpln("It's an iterator!");
try {
var iterCount = 0;
var iterValue;
// To keep Safari-compatibility, don't use "let", "each", etc.
for /* each */ ( /* let */ iterValue in rv)
++iterCount;
dumpln("Iterating succeeded, iterCount == " + iterCount);
} catch (iterError) {
dumpln("Iterating threw!");
dumpln("Iterating threw: " + errorToString(iterError));
}
}
// Restore important stuff that might have been broken as soon as possible :)
eval = realEval;
Function = realFunction;
gc = realGC;
if (count % 1000 == 0) {
dumpln("Paranoid GC!")
dumpln(count);
realGC();
}
if (!eval)
dumplnAndAlert("OMGWTFBBQ");
if (eval != realEval)
dumplnAndAlert("WTFWTFWTF")
if(verbose)
dumpln("Done trying out that function!");
dumpln("");
}
function tryHalves(code)
{
// See if there are any especially horrible bugs that appear when the parser has to \
start/stop in the middle of something. this is kinda evil.
// Stray "}"s are likely in secondHalf, so use new Function rather than eval. "}" \
can't escape from new Function :)
try {
firstHalf = code.substr(0, code.length / 2);
if (verbose)
dumpln("First half: " + firstHalf);
f = new Function(firstHalf);
uneval(f);
}
catch(e) {
if (verbose)
dumpln("First half compilation error: " + e);
}
try {
secondHalf = code.substr(code.length / 2, code.length);
if (verbose)
dumpln("Second half: " + secondHalf);
f = new Function(secondHalf);
uneval(f);
}
catch(e) {
if (verbose)
dumpln("Second half compilation error: " + e);
}
}
function errorToString(e)
{
try {
return ("" + e);
} catch (e2) {
return "Can't toString the error!!";
}
}
// Round-trip with uneval
function checkRoundTrip(f, code, checkRecompiling, checkForMismatch)
{
var g, uf, ug;
try {
uf = uneval(f);
} catch(e) { reportRoundTripIssue("Round-trip with uneval: can't uneval", code, \
null, null, errorToString(e)); return; }
if (checkRecompiling) {
try {
g = eval("$=" + uf);
ug = uneval(g);
if (checkForMismatch && ug != uf) {
reportRoundTripIssue("Round-trip with uneval: mismatch", code, uf, ug, \
"mismatch"); }
} catch(e) { reportRoundTripIssue("Round-trip with uneval: error", code, uf, ug, \
errorToString(e)); } }
}
// Round-trip with implicit toString
function checkRoundTrip2(f, code, checkRecompiling, checkForMismatch)
{
var uf, g;
try {
uf = "" + f;
} catch(e) { reportRoundTripIssue("Round-trip with implicit toString: can't \
toString", code, null, null, errorToString(e)); return; }
if (checkRecompiling) {
try {
g = eval("$=" + uf);
if (checkForMismatch && (""+g) != (""+f) ) {
reportRoundTripIssue("Round-trip with implicit toString", code, f, g, \
"mismatch"); }
} catch(e) { reportRoundTripIssue("Round-trip with implicit toString: error", \
code, f, g, errorToString(e)); } }
}
// Round-trip mismatches or errors usually indicate that the first uneval was WRONG.
function reportRoundTripIssue(issue, code, fs, gs, e)
{
var message = issue + "\n\n" +
"Code: " + code + "\n\n" +
"fs: " + fs + "\n\n" +
"gs: " + gs + "\n\n" +
"error: " + e;
dumplnAndAlert(message);
}
function checkLetX(code)
{
var letX = "\n;\nlet x;";
var hasTopLevelLet = !compiles(code + letX);
var f = new Function(code);
var ef = extractCode(f);
var getsTopLevelLet = !compiles(ef + letX);
if (hasTopLevelLet != getsTopLevelLet) {
var desc = "hasTopLevelLet == " + hasTopLevelLet + " but getsTopLevelLet == " + \
getsTopLevelLet; reportRoundTripIssue("checkLetX found that " + desc, code, ef, \
null, null); }
}
function extractCode(f)
{
// throw away the first and last lines of the function's string representation
// (this happens to work on spidermonkey trunk, dunno about anywhere else)
var uf = "" + f;
var lines = uf.split("\n");
var innerLines = lines.slice(1, -1);
return innerLines.join("\n");
}
function compiles(code)
{
try {
new Function(code);
return true;
} catch(e) {
return false;
}
}
var count;
var verbose;
/**************************************
* To reproduce a crash / assertion: *
**************************************/
// 1. Comment "start();" out.
verbose = false;
start();
// 2. Uncomment this and paste a line from the run's output (count=..., \
tryItOut(...)) below this line. verbose = true;
// 3. Run it.
// 4. If that doesn't work, maybe you're unlucky enough to have triggered a \
GC-related bug, or a bug that requires executing more than one of the randomly \
generated functions. // In that case, grep console output for tryItOut and paste it \
all in, then reduce using your favorite reduction method/tool.
[Attachment #11 (unknown)]
<HTML><BODY style="word-wrap: break-word; -khtml-nbsp-mode: space; -khtml-line-break: \
after-white-space; "><SPAN></SPAN><SPAN></SPAN></BODY></HTML>
["multi_timed_run.py" (multi_timed_run.py)]
#!/usr/bin/env python
# multi_timed_run.py by jesse ruderman
# based on timed_run.py by coop and bob clary
# Usage: multi_timed_run timeout command args
# e.g. multi_timed_run 240 ./js ~/Desktop/fuzz/jsparsefuzz/jsparsefuzz.js
# will run "./js ~/Desktop/fuzz/jsparsefuzz/jsparsefuzz.js" over and over, each time \
limiting # it to executing for 240 seconds. This makes it possible for the fuzzer to \
run unattended # (even overnight) -- when you come back, you'll see all the bugs it \
found.
# If jsparsefuzz.js finishes all of its iterations, multi_timed_run.py will just \
print "NORMAL", not showing # any of the log. If it doesn't finish, \
multi_timed_run.py will show the last 20 lines of the log.
# How to interpret various outputs:
# NORMAL *with output* -- usually indicates that the fuzzer made the javascript \
engine halt execution (out of memory?) # CRASHED -- on Mac OS X, use \
Console.app to view ~/Library/Logs/CrashReporter/js.log to see the stack # TIMED \
OUT -- hang or very slow loop # ABNORMAL -- a \
fuzzer-generated function did something that let to a TypeError in the fuzzer itself
exitOSError = 66
exitInterrupt = 99
import os, signal, sys, time
pid = None
def alarm_handler(signum, frame):
global pid
print "TIMED OUT!"
try:
os.kill(pid, signal.SIGKILL)
except:
pass
def forkexec(command, args, stdoutfn):
try:
pid = os.fork()
if pid == 0: # Child
# redirect stdout (just for the child!).
# i hope i'm not supposed to close() this file somehow...
so = file(stdoutfn, 'w')
os.dup2(so.fileno(), sys.stdout.fileno())
# transfer control of the child to the target program
os.execvp(command, args)
else: # Parent
return pid
except OSError, e:
print "ERROR: %s %s failed: %d (%s)" % (command, args, e.errno, e.strerror)
sys.exit(exitOSError)
def one_timed_run(logfilename):
global pid
signal.signal(signal.SIGALRM, alarm_handler)
signal.alarm(int(sys.argv[1]))
starttime = time.time()
msg = ''
try:
pid = forkexec(sys.argv[2], sys.argv[2:], logfilename)
try:
status = os.waitpid(pid, 0)[1]
except OSError:
msg = 'TIMED OUT' # assume this means it timed out
signal.alarm(0) # Cancel the alarm
except KeyboardInterrupt:
print "Bye!"
try:
os.kill(pid, 9)
except OSError, e:
print "Unable to kill it: %s" % e
sys.exit(exitInterrupt)
stoptime = time.time()
elapsedtime = stoptime - starttime
if (msg == 'TIMED OUT'):
pass
elif os.WIFEXITED(status):
rc = os.WEXITSTATUS(status)
if rc == 0:
msg = 'NORMAL'
else:
# the original timed_run.py considered rc >= 3 to be a crash. why?
msg = 'ABNORMAL ' + str(rc)
elif os.WIFSIGNALED(status):
print time.asctime()
msg = 'CRASHED signal %d' % os.WTERMSIG(status)
else:
msg = 'NONE'
print "EXIT STATUS: %s (%f seconds) (log file %s)" % (msg, elapsedtime, \
logfilename)
def succeeded(logfilename):
logfile = open(logfilename, "r")
for line in logfile:
if (line == "It's looking good!\n"):
return True
return False
def showtail(logfilename):
cmd = "tail -n 20 %s" % logfilename
print cmd
print ""
os.system(cmd)
print ""
print ""
def many_timed_runs():
iteration = 0
while True:
iteration += 1
logfilename = "w%d" % iteration
one_timed_run(logfilename)
if not succeeded(logfilename):
showtail(logfilename)
many_timed_runs()
[Attachment #13 (unknown)]
<HTML><BODY style="word-wrap: break-word; -khtml-nbsp-mode: space; -khtml-line-break: \
after-white-space; "><SPAN></SPAN><DIV><BR><DIV><DIV>On Aug 23, 2007, at 12:25 PM, \
王王 wrote:</DIV><BR class="Apple-interchange-newline"><BLOCKQUOTE \
type="cite">excuse me ,i find i could not download the jsfunfuzz fuzzer from <A \
href="https://bugzilla.mozilla.org/show_bug.cgi?id=jsfunfuzz">https://bugzilla.mozilla.org/show_bug.cgi?id=jsfunfuzz</A>, \
even i registered a account.can any1 help me?or share me one copy?thank you.<BR><BR> \
<DIV><SPAN class="gmail_quote">2007/8/11, <A \
href="mailto:fuzzing-request@whitestar.linuxbox.org">fuzzing-request@whitestar.linuxbox.org</A> \
<<A href="mailto:fuzzing-request@whitestar.linuxbox.org">fuzzing-request@whitestar.linuxbox.org \
</A>>:</SPAN> <BLOCKQUOTE class="gmail_quote" style="PADDING-LEFT: 1ex; MARGIN: \
0px 0px 0px 0.8ex; BORDER-LEFT: #ccc 1px solid">Send fuzzing mailing list submissions \
to<BR> <A \
href="mailto:fuzzing@whitestar.linuxbox.org">fuzzing@whitestar.linuxbox.org \
</A><BR><BR>To subscribe or unsubscribe via the World Wide Web, visit<BR> \
<A href="http://www.whitestar.linuxbox.org/mailman/listinfo/fuzzing">http://www.whitestar.linuxbox.org/mailman/listinfo/fuzzing</A><BR>or, \
via email, send a message with subject or body 'help' to <BR> <A \
href="mailto:fuzzing-request@whitestar.linuxbox.org">fuzzing-request@whitestar.linuxbox.org</A><BR><BR>You \
can reach the person managing the list at<BR> <A \
href="mailto:fuzzing-owner@whitestar.linuxbox.org"> \
fuzzing-owner@whitestar.linuxbox.org</A><BR><BR>When replying, please edit your \
Subject line so it is more specific<BR>than "Re: Contents of fuzzing \
digest..."<BR><BR><BR>Today's Topics:<BR><BR> 1. jsfunfuzz + slides ? (Mark Sec) \
<BR> 2. Re: jsfunfuzz + slides ? (Jerome \
Athias)<BR><BR><BR>----------------------------------------------------------------------<BR><BR>Message: \
1<BR>Date: Fri, 10 Aug 2007 08:10:33 -0500<BR>From: "Mark Sec" < <A \
href="mailto:mark.sec@gmail.com">mark.sec@gmail.com</A>><BR>Subject: [fuzzing] \
jsfunfuzz + slides ?<BR>To: <A \
href="mailto:fuzzing@whitestar.linuxbox.org">fuzzing@whitestar.linuxbox.org</A><BR>Message-ID:<BR> \
< <A href="mailto:5598cfa10708100610s8b3ebd1hf4bb640f17f8bab0@mail.gmail.com">5598cfa10708100610s8b3ebd1hf4bb640f17f8bab0@mail.gmail.com</A>><BR>Content-Type: \
text/plain; charset="iso-8859-1"<BR><BR>does any1 where i can download the jsfunfuzz \
fuzzer slides BH, and how to <BR>use that?<BR><BR>-mark.<BR>-------------- next part \
--------------<BR>An HTML attachment was scrubbed...<BR>URL: <A \
href="http://www.whitestar.linuxbox.org/pipermail/fuzzing/attachments/20070810/ea2c1b18/attachment.html"> \
http://www.whitestar.linuxbox.org/pipermail/fuzzing/attachments/20070810/ea2c1b18/attachment.html</A><BR><BR>------------------------------<BR><BR>Message: \
2<BR>Date: Fri, 10 Aug 2007 17:02:31 +0200<BR>From: Jerome Athias < <A \
href="mailto:jerome.athias@free.fr">jerome.athias@free.fr</A>><BR>Subject: Re: \
[fuzzing] jsfunfuzz + slides ?<BR>To: Mark Sec <<A \
href="mailto:mark.sec@gmail.com">mark.sec@gmail.com</A>><BR>Cc: <A \
href="mailto:fuzzing@whitestar.linuxbox.org"> \
fuzzing@whitestar.linuxbox.org</A><BR>Message-ID: <<A \
href="mailto:46BC7E07.3090102@free.fr">46BC7E07.3090102@free.fr</A>><BR>Content-Type: \
text/plain; charset="iso-8859-1"<BR><BR><A \
href="https://bugzilla.mozilla.org/show_bug.cgi?id=jsfunfuzz"> \
https://bugzilla.mozilla.org/show_bug.cgi?id=jsfunfuzz</A><BR><BR>Regards<BR>/JA<BR><A \
href="http://www.JA-PSI.fr">http://www.JA-PSI.fr</A><BR><BR>Mark Sec a ?crit \
:<BR>><BR>> does any1 where i can download the jsfunfuzz fuzzer slides BH, and \
how <BR>> to use that?<BR>><BR>> -mark.<BR>-------------- next part \
--------------<BR>A non-text attachment was scrubbed...<BR>Name: smime.p7s<BR>Type: \
application/x-pkcs7-signature<BR>Size: 3253 bytes<BR>Desc: S/MIME Cryptographic \
Signature <BR>Url : <A \
href="http://www.whitestar.linuxbox.org/pipermail/fuzzing/attachments/20070810/bb4173b \
0/attachment-0001.bin">http://www.whitestar.linuxbox.org/pipermail/fuzzing/attachments/20070810/bb4173b0/attachment-0001.bin \
</A><BR><BR>------------------------------<BR><BR>_______________________________________________<BR>fuzzing \
mailing list<BR><A href="mailto:fuzzing@whitestar.linuxbox.org">fuzzing@whitestar.linuxbox.org</A><BR><A \
href="http://www.whitestar.linuxbox.org/mailman/listinfo/fuzzing"> \
http://www.whitestar.linuxbox.org/mailman/listinfo/fuzzing</A><BR><BR><BR>End of \
fuzzing Digest, Vol 17, Issue \
4<BR>**************************************<BR></BLOCKQUOTE></DIV><BR><DIV \
style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; \
">_______________________________________________</DIV><DIV style="margin-top: 0px; \
margin-right: 0px; margin-bottom: 0px; margin-left: 0px; ">fuzzing mailing \
list</DIV><DIV style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; \
margin-left: 0px; "><A \
href="mailto:fuzzing@whitestar.linuxbox.org">fuzzing@whitestar.linuxbox.org</A></DIV><DIV \
style="margin-top: 0px; margin-right: 0px; margin-bottom: 0px; margin-left: 0px; "><A \
href="http://www.whitestar.linuxbox.org/mailman/listinfo/fuzzing">http://www.whitestar.linuxbox.org/mailman/listinfo/fuzzing</A></DIV> \
</BLOCKQUOTE></DIV><BR></DIV></BODY></HTML>
_______________________________________________
fuzzing mailing list
fuzzing@whitestar.linuxbox.org
http://www.whitestar.linuxbox.org/mailman/listinfo/fuzzing
[prev in list] [next in list] [prev in thread] [next in thread]
Configure |
About |
News |
Add a list |
Sponsored by KoreLogic