Bug 1596691 - Add a JSParser target to fuzz-tests. r=jandem

Differential Revision: https://phabricator.services.mozilla.com/D77006
This commit is contained in:
Christian Holler 2020-06-02 13:50:23 +00:00
Родитель 97d7ed04a7
Коммит f3e4280df1
3 изменённых файлов: 190 добавлений и 0 удалений

Просмотреть файл

@ -143,6 +143,9 @@ js/src/jsapi-tests/binast/
js/src/tests/
js/src/Y.js
# Fuzzing code for testing only, targeting the JS shell
js/src/fuzz-tests/
# Uses `#filter substitution`
mobile/android/app/mobile.js
mobile/android/app/geckoview-prefs.js

Просмотреть файл

@ -0,0 +1,83 @@
/* -*- Mode: javascript; tab-width: 8; 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/. */
// This fuzzing target aims to stress the SpiderMonkey parser. However, for
// this purpose, it does *not* use `parse()` because some past bugs in the
// parser could only be triggered in the runtime later. Instead, we use
// the `evaluate` function which parses and runs the code. This brings in
// other problems like timeouts and permanent side-effects. We try to minimize
// the amount of permanent side-effects from running the code by running it
// in a fresh global for each iteration. We also use a special function
// called `sanitizeGlobal` to remove any harmful shell functions from the
// global prior to running. Many of these shell functions would otherwise
// have permanent side-effects of some sort or be disruptive to testing like
// increasing the amount of timeouts or leak memory. Finally, the target also
// tries to catch timeouts locally and signal back any timeouts by returning 1
// from the iteration function.
// This global will hold the current fuzzing buffer for each iteration.
var fuzzBuf;
loadRelativeToScript("util/sanitize.js");
deterministicgc(true);
// Set a default value for timeouts to 1 second, but allow this to
// be set on the command line as well using -e fuzzTimeout=VAL.
if (typeof fuzzTimeout === "undefined") {
fuzzTimeout = 1;
}
function JSFuzzIterate() {
try {
let code = String.fromCharCode(...fuzzBuf);
let result = null;
// Create a new global and sanitize it such that its potentially permanent
// side-effects are reduced to a minimum.
let global = newGlobal();
sanitizeGlobal(global);
// Work around memory leaks when the hook is not set
evaluate(`
setModuleResolveHook(function(module, specifier) {
throw "Module '" + specifier + "' not found";
});
setModuleResolveHook = function() {};
`, { global: global, catchTermination: true });
// Start a timer and set a timeout in addition
let lfStart = monotonicNow();
timeout(fuzzTimeout, function() { return false; });
try {
result = evaluate(code, { global: global, catchTermination: true });
} catch(exc) {
print(exc);
}
timeout(-1);
let lfStop = monotonicNow();
// Reset some things that could have been altered by the code we ran
gczeal(0);
schedulegc(0);
setGCCallback({ action: "majorGC" });
clearSavedFrames();
// If we either ended terminating the script, or we took longer than
// the timeout set (but timeout didn't kick in), then we return 1 to
// signal libFuzzer that the sample just be abandoned.
if (result === "terminated" || (lfStop - lfStart > (fuzzTimeout * 1000 + 200))) {
return 1;
}
return 0;
} catch(exc) {
print("Caught toplevel exception: " + exc);
}
return 1;
}

Просмотреть файл

@ -0,0 +1,104 @@
/* -*- Mode: javascript; tab-width: 8; 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/. */
// This function can be used to "sanitize" a new global for fuzzing in such
// a way that permanent side-effects, hangs and behavior that could be harmful
// to libFuzzer targets is reduced to a minimum.
function sanitizeGlobal(g) {
let lfFuncs = {
// Noisy functions (output)
backtrace: function() {},
getBacktrace: function() {},
help: function() {},
print: function(s) { return s.toString(); },
printErr: function(s) { return s.toString(); },
putstr: function(s) { return s.toString(); },
stackDump: function() {},
dumpHeap: function() {},
dumpScopeChain: function() {},
dumpObjectWrappers: function() {},
dumpGCArenaInfo: function() {},
printProfilerEvents: function() {},
// Harmful functions (hangs, timeouts, leaks)
getLcovInfo: function() {},
readline: function() {},
readlineBuf: function() {},
timeout: function() {},
quit: function() {},
interruptIf: function() {},
terminate: function() {},
invokeInterruptCallback: function() {},
setInterruptCallback: function() {},
intern: function() {},
evalInWorker: function() {},
sleep: function() {},
cacheEntry: function() {},
streamCacheEntry: function() {},
createMappedArrayBuffer: function() {},
wasmCompileInSeparateProcess: function() {},
gcparam: function() {},
newGlobal: function() { return g; },
// Harmful functions (throw)
assertEq: function(a,b) { return a.toString() == b.toString(); },
throwError: function() {},
reportOutOfMemory: function() {},
throwOutOfMemory: function() {},
reportLargeAllocationFailure: function() {},
// Functions that need limiting
gczeal: function(m, f) { return gczeal(m, 100); },
startgc: function(n, o) { startgc(n > 20 ? 20 : n, o); },
gcslice: function(n) { gcslice(n > 20 ? 20 : n); },
// Global side-effects
deterministicgc: function() {},
fullcompartmentchecks: function() {},
setIonCheckGraphCoherency: function() {},
enableShellAllocationMetadataBuilder: function() {},
setTimeResolution: function() {},
options: function() { return "tracejit,methodjit,typeinfer"; },
setJitCompilerOption: function() {},
clearLastWarning: function() {},
enableSingleStepProfiling: function() {},
disableSingleStepProfiling: function() {},
enableGeckoProfiling: function() {},
enableGeckoProfilingWithSlowAssertions: function() {},
disableGeckoProfiling: function() {},
enqueueJob: function() {},
globalOfFirstJobInQueue: function() {},
drainJobQueue: function() {},
setPromiseRejectionTrackerCallback: function() {},
startTimingMutator: function() {},
stopTimingMutator: function() {},
setModuleLoadHook: function() {},
// Left enabled, as it is required for now to avoid leaks
//setModuleResolveHook: function() {},
setModuleMetadataHook: function() {},
setModuleDynamicImportHook: function() {},
finishDynamicModuleImport: function() {},
abortDynamicModuleImport: function() {},
offThreadCompileScript: function() {},
runOffThreadScript: function() {},
offThreadCompileModule: function() {},
finishOffThreadModule: function() {},
offThreadDecodeScript: function() {},
runOffThreadDecodedScript: function() {},
addPromiseReactions: function() {},
ignoreUnhandledRejections: function() {},
enableTrackAllocations: function() {},
disableTrackAllocations: function() {},
startTraceLogger: function() {},
stopTraceLogger: function() {},
setTestFilenameValidationCallback: function() {},
};
for (let lfFunc in lfFuncs) {
g[lfFunc] = lfFuncs[lfFunc];
}
return g;
}