[fuzzer] Initial import of v8's fuzzer

Copied from  
https://chromium.googlesource.com/v8/v8/+/master/tools/clusterfuzz/js_fuzzer/
This commit is contained in:
Sathya Gunasekaran
2023-07-06 11:44:14 +05:30
parent 1bf00e6b6e
commit 31ec959e9b
148 changed files with 8946 additions and 0 deletions
@@ -0,0 +1,22 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
module.exports = {
"env": {
"node": true,
"commonjs": true,
"es6": true,
"mocha": true
},
"extends": "eslint:recommended",
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parserOptions": {
"ecmaVersion": 2018
},
"rules": {
}
};
@@ -0,0 +1,6 @@
/node_modules
/ochang_js_fuzzer*
/db/
/output.zip
/output/
/workdir/
@@ -0,0 +1,11 @@
# Metadata information for this directory.
#
# For more information on DIR_METADATA files, see:
# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/README.md
#
# For the schema of this file, see Metadata message:
# https://source.chromium.org/chromium/infra/infra/+/master:go/src/infra/tools/dirmd/proto/dir_metadata.proto
monorail {
component: "Infra>Client>V8"
}
@@ -0,0 +1,7 @@
set noparent
file:../../../INFRA_OWNERS
msarms@chromium.org
mslekova@chromium.org
ochang@chromium.org
@@ -0,0 +1,122 @@
# JS-Fuzzer
Javascript fuzzer for stand-alone shells like D8, Chakra, JSC or Spidermonkey.
Original author: Oliver Chang
# Building
This fuzzer may require versions of node that are newer than available on
ClusterFuzz, so we use [pkg](https://github.com/zeit/pkg) to create a self
contained binary) out of this.
## Prereqs
You need to intall nodejs and npm. Run `npm install` in this directory.
## Fuzzing DB
This fuzzer requires a fuzzing DB. To build one, get the latest `web_tests.zip`
from [gs://clusterfuzz-data/web_tests.zip](
https://storage.cloud.google.com/clusterfuzz-data/web_tests.zip) and unzip it
(note https://crbug.com/v8/10891 for making this data publicly available).
Then run:
```bash
$ mkdir db
$ node build_db.js -i /path/to/web_tests -o db chakra v8 spidermonkey WebKit/JSTests
```
This may take a while. Optionally test the fuzzing DB with:
```bash
$ node test_db.js -i db
```
## Building fuzzer
Then, to build the fuzzer,
```bash
$ ./node_modules/.bin/pkg -t node10-linux-x64 .
```
Replace "linux" with either "win" or "macos" for those platforms.
This builds a binary named `ochang_js_fuzzer` for Linux / macOS OR
`ochang_js_fuzzer.exe` for Windows.
## Packaging
Use `./package.sh`, `./package.sh win` or `./package.sh macos` to build and
create the `output.zip` archive or use these raw commands:
```bash
$ mkdir output
$ cd output
$ ln -s ../db db
$ ln -s ../ochang_js_fuzzer run
$ zip -r /path/output.zip *
```
**NOTE**: Add `.exe` to `ochang_js_fuzzer` and `run` filename above if archiving
for Windows platform.
# Development
Run the tests with:
```bash
$ npm test
```
When test expectations change, generate them with:
```bash
$ GENERATE=1 npm test
```
# Generating exceptional configurations
Tests that fail to parse or show very bad performance can be automatically
skipped or soft-skipped with the following script (takes >1h):
```bash
$ WEB_TESTS=/path/to/web_tests OUTPUT=/path/to/output/folder ./gen_exceptions.sh
```
# Experimenting (limited to differential fuzzing)
To locally evaluate the fuzzer, setup a work directory as follows:
```bash
$ workdir/
$ workdir/app_dir
$ workdir/fuzzer
$ workdir/input
$ workdir/output
```
The `app_dir` folder can be a symlink or should contain the bundled
version of `d8` with all files required for execution.
Copy the packaged `ochang_js_fuzzer` executable and the `db` folder
to the `fuzzer` directory or use a symlink.
The `input` directory is the root folder of the corpus, i.e. pointing
to the unzipped data of `gs://clusterfuzz-data/web_tests.zip`.
The `output` directory is expected to be empty. It'll contain all
output of the fuzzing session. Start the experiments with:
```bash
$ # Around ~40000 corresponds to 24h of fuzzing on a workstation.
$ NUM_RUNS = 40000
$ python tools/workbench.py $NUM_RUNS
```
You can check current stats with:
```bash
$ cat workdir/output/stats.json | python -m json.tool
```
When failures are found, you can forge minimization command lines with:
```bash
$ MINIMIZER_PATH = path/to/minimizer
$ python tools/minimize.py $MINIMIZER_PATH
```
The path should point to a local checkout of the [minimizer](https://chrome-internal.googlesource.com/chrome/tools/clusterfuzz/+/refs/heads/master/src/python/bot/minimizer/).
@@ -0,0 +1,65 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Collect JS nodes.
*/
const program = require('commander');
const corpus = require('./corpus.js');
const db = require('./db.js');
const path = require('path');
const sourceHelpers = require('./source_helpers.js');
function main() {
Error.stackTraceLimit = Infinity;
program
.version('0.0.1')
.option('-i, --input_dir <path>', 'Input directory.')
.option('-o, --output_dir <path>', 'Output directory.')
.parse(process.argv);
if (!program.args.length) {
console.log('Need to specify corpora.');
return;
}
if (!program.output_dir) {
console.log('Need to specify output dir.');
return;
}
const mutateDb = new db.MutateDbWriter(program.output_dir);
const inputDir = path.resolve(program.input_dir);
for (const corpusName of program.args) {
const curCorpus = new corpus.Corpus(inputDir, corpusName);
for (const relPath of curCorpus.relFiles()) {
let source;
try {
source = sourceHelpers.loadSource(inputDir, relPath);
} catch (e) {
console.log(e);
continue;
}
if (!source) {
continue;
}
try{
mutateDb.process(source);
} catch (e) {
console.log(e);
}
}
}
mutateDb.writeIndex();
}
main();
@@ -0,0 +1,141 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Corpus
*/
const program = require('commander');
const fs = require('fs');
const path = require('path');
const exceptions = require('./exceptions.js');
const random = require('./random.js');
const sourceHelpers = require('./source_helpers.js');
function* walkDirectory(directory, filter) {
// Generator for recursively walk a directory.
for (const filePath of fs.readdirSync(directory)) {
const currentPath = path.join(directory, filePath);
const stat = fs.lstatSync(currentPath);
if (stat.isFile()) {
if (!filter || filter(currentPath)) {
yield currentPath;
}
continue;
}
if (stat.isDirectory()) {
for (let childFilePath of walkDirectory(currentPath, filter)) {
yield childFilePath;
}
}
}
}
class Corpus {
// Input corpus.
constructor(inputDir, corpusName, extraStrict=false) {
this.inputDir = inputDir;
this.extraStrict = extraStrict;
// Filter for permitted JS files.
function isPermittedJSFile(absPath) {
return (absPath.endsWith('.js') &&
!exceptions.isTestSkippedAbs(absPath));
}
// Cache relative paths of all files in corpus.
this.skippedFiles = [];
this.softSkippedFiles = [];
this.permittedFiles = [];
const directory = path.join(inputDir, corpusName);
for (const absPath of walkDirectory(directory, isPermittedJSFile)) {
const relPath = path.relative(this.inputDir, absPath);
if (exceptions.isTestSkippedRel(relPath)) {
this.skippedFiles.push(relPath);
} else if (exceptions.isTestSoftSkippedAbs(absPath) ||
exceptions.isTestSoftSkippedRel(relPath)) {
this.softSkippedFiles.push(relPath);
} else {
this.permittedFiles.push(relPath);
}
}
random.shuffle(this.softSkippedFiles);
random.shuffle(this.permittedFiles);
}
// Relative paths of all files in corpus.
*relFiles() {
for (const relPath of this.permittedFiles) {
yield relPath;
}
for (const relPath of this.softSkippedFiles) {
yield relPath;
}
}
// Relative paths of all files in corpus including generated skipped.
*relFilesForGenSkipped() {
for (const relPath of this.relFiles()) {
yield relPath;
}
for (const relPath of this.skippedFiles) {
yield relPath;
}
}
/**
* Returns "count" relative test paths, randomly selected from soft-skipped
* and permitted files. Permitted files have a 4 times higher chance to
* be chosen.
*/
getRandomTestcasePaths(count) {
return random.twoBucketSample(
this.softSkippedFiles, this.permittedFiles, 4, count);
}
loadTestcase(relPath, strict, label) {
const start = Date.now();
try {
const source = sourceHelpers.loadSource(this.inputDir, relPath, strict);
if (program.verbose) {
const duration = Date.now() - start;
console.log(`Parsing ${relPath} ${label} took ${duration} ms.`);
}
return source;
} catch (e) {
console.log(`WARNING: failed to ${label} parse ${relPath}`);
console.log(e);
}
return undefined;
}
*loadTestcases(relPaths) {
for (const relPath of relPaths) {
if (this.extraStrict) {
// When re-generating the files marked sloppy, we additionally test if
// the file parses in strict mode.
this.loadTestcase(relPath, true, 'strict');
}
const source = this.loadTestcase(relPath, false, 'sloppy');
if (source) {
yield source;
}
}
}
getRandomTestcases(count) {
return Array.from(this.loadTestcases(this.getRandomTestcasePaths(count)));
}
getAllTestcases() {
return this.loadTestcases(this.relFilesForGenSkipped());
}
}
module.exports = {
Corpus: Corpus,
walkDirectory: walkDirectory,
}
+485
View File
@@ -0,0 +1,485 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Mutation Db.
*/
const crypto = require('crypto');
const fs = require('fs');
const fsPath = require('path');
const babelGenerator = require('@babel/generator').default;
const babelTemplate = require('@babel/template').default;
const babelTraverse = require('@babel/traverse').default;
const babelTypes = require('@babel/types');
const globals = require('globals');
const random = require('./random.js');
const sourceHelpers = require('./source_helpers.js');
const globalIdentifiers = new Set(Object.keys(globals.builtin));
const propertyNames = new Set([
// Parsed from https://github.com/tc39/ecma262/blob/master/spec.html
'add',
'anchor',
'apply',
'big',
'bind',
'blink',
'bold',
'buffer',
'byteLength',
'byteOffset',
'BYTES_PER_ELEMENT',
'call',
'catch',
'charAt',
'charCodeAt',
'clear',
'codePointAt',
'compile',
'concat',
'constructor',
'copyWithin',
'__defineGetter__',
'__defineSetter__',
'delete',
'endsWith',
'entries',
'every',
'exec',
'fill',
'filter',
'find',
'findIndex',
'fixed',
'flags',
'fontcolor',
'fontsize',
'forEach',
'get',
'getDate',
'getDay',
'getFloat32',
'getFloat64',
'getFullYear',
'getHours',
'getInt16',
'getInt32',
'getInt8',
'getMilliseconds',
'getMinutes',
'getMonth',
'getSeconds',
'getTime',
'getTimezoneOffset',
'getUint16',
'getUint32',
'getUint8',
'getUTCDate',
'getUTCDay',
'getUTCFullYear',
'getUTCHours',
'getUTCMilliseconds',
'getUTCMinutes',
'getUTCMonth',
'getUTCSeconds',
'getYear',
'global',
'has',
'hasInstance',
'hasOwnProperty',
'ignoreCase',
'includes',
'indexOf',
'isConcatSpreadable',
'isPrototypeOf',
'italics',
'iterator',
'join',
'keys',
'lastIndexOf',
'length',
'link',
'localeCompare',
'__lookupGetter__',
'__lookupSetter__',
'map',
'match',
'match',
'message',
'multiline',
'name',
'next',
'normalize',
'padEnd',
'padStart',
'pop',
'propertyIsEnumerable',
'__proto__',
'prototype',
'push',
'reduce',
'reduceRight',
'repeat',
'replace',
'replace',
'return',
'reverse',
'search',
'search',
'set',
'set',
'setDate',
'setFloat32',
'setFloat64',
'setFullYear',
'setHours',
'setInt16',
'setInt32',
'setInt8',
'setMilliseconds',
'setMinutes',
'setMonth',
'setSeconds',
'setTime',
'setUint16',
'setUint32',
'setUint8',
'setUTCDate',
'setUTCFullYear',
'setUTCHours',
'setUTCMilliseconds',
'setUTCMinutes',
'setUTCMonth',
'setUTCSeconds',
'setYear',
'shift',
'size',
'slice',
'slice',
'small',
'some',
'sort',
'source',
'species',
'splice',
'split',
'split',
'startsWith',
'sticky',
'strike',
'sub',
'subarray',
'substr',
'substring',
'sup',
'test',
'then',
'throw',
'toDateString',
'toExponential',
'toFixed',
'toGMTString',
'toISOString',
'toJSON',
'toLocaleDateString',
'toLocaleLowerCase',
'toLocaleString',
'toLocaleTimeString',
'toLocaleUpperCase',
'toLowerCase',
'toPrecision',
'toPrimitive',
'toString',
'toStringTag',
'toTimeString',
'toUpperCase',
'toUTCString',
'trim',
'unicode',
'unscopables',
'unshift',
'valueOf',
'values',
]);
const MAX_DEPENDENCIES = 2;
class Expression {
constructor(type, source, isStatement, originalPath,
dependencies, needsSuper) {
this.type = type;
this.source = source;
this.isStatement = isStatement;
this.originalPath = originalPath;
this.dependencies = dependencies;
this.needsSuper = needsSuper;
}
}
function dedupKey(expression) {
if (!expression.dependencies) {
return expression.source;
}
let result = expression.source;
for (let dependency of expression.dependencies) {
result = result.replace(new RegExp(dependency, 'g'), 'ID');
}
return result;
}
function _markSkipped(path) {
while (path) {
path.node.__skipped = true;
path = path.parentPath;
}
}
/**
* Returns true if an expression can be applied or false otherwise.
*/
function isValid(expression) {
const expressionTemplate = babelTemplate(
expression.source,
sourceHelpers.BABYLON_REPLACE_VAR_OPTIONS);
const dependencies = {};
if (expression.dependencies) {
for (const dependency of expression.dependencies) {
dependencies[dependency] = babelTypes.identifier('__v_0');
}
}
try {
expressionTemplate(dependencies);
} catch (e) {
return false;
}
return true;
}
class MutateDbWriter {
constructor(outputDir) {
this.seen = new Set();
this.outputDir = fsPath.resolve(outputDir);
this.index = {
statements: [],
superStatements: [],
all: [],
};
}
process(source) {
let self = this;
let varIndex = 0;
// First pass to collect dependency information.
babelTraverse(source.ast, {
Super(path) {
while (path) {
path.node.__needsSuper = true;
path = path.parentPath;
}
},
YieldExpression(path) {
// Don't include yield expressions in DB.
_markSkipped(path);
},
Identifier(path) {
if (globalIdentifiers.has(path.node.name) &&
path.node.name != 'eval') {
// Global name.
return;
}
if (propertyNames.has(path.node.name) &&
path.parentPath.isMemberExpression() &&
path.parentKey !== 'object') {
// Builtin property name.
return;
}
let binding = path.scope.getBinding(path.node.name);
if (!binding) {
// Unknown dependency. Don't handle this.
_markSkipped(path);
return;
}
let newName;
if (path.node.name.startsWith('VAR_')) {
newName = path.node.name;
} else if (babelTypes.isFunctionDeclaration(binding.path.node) ||
babelTypes.isFunctionExpression(binding.path.node) ||
babelTypes.isDeclaration(binding.path.node) ||
babelTypes.isFunctionExpression(binding.path.node)) {
// Unknown dependency. Don't handle this.
_markSkipped(path);
return;
} else {
newName = 'VAR_' + varIndex++;
path.scope.rename(path.node.name, newName);
}
// Mark all parents as having a dependency.
while (path) {
path.node.__idDependencies = path.node.__idDependencies || [];
if (path.node.__idDependencies.length <= MAX_DEPENDENCIES) {
path.node.__idDependencies.push(newName);
}
path = path.parentPath;
}
}
});
babelTraverse(source.ast, {
Expression(path) {
if (!path.parentPath.isExpressionStatement()) {
return;
}
if (path.node.__skipped ||
(path.node.__idDependencies &&
path.node.__idDependencies.length > MAX_DEPENDENCIES)) {
return;
}
if (path.isIdentifier() || path.isMemberExpression() ||
path.isConditionalExpression() ||
path.isBinaryExpression() || path.isDoExpression() ||
path.isLiteral() ||
path.isObjectExpression() || path.isArrayExpression()) {
// Skip:
// - Identifiers.
// - Member expressions (too many and too context dependent).
// - Conditional expressions (too many and too context dependent).
// - Binary expressions (too many).
// - Literals (too many).
// - Object/array expressions (too many).
return;
}
if (path.isAssignmentExpression()) {
if (!babelTypes.isMemberExpression(path.node.left)) {
// Skip assignments that aren't to properties.
return;
}
if (babelTypes.isIdentifier(path.node.left.object)) {
if (babelTypes.isNumericLiteral(path.node.left.property)) {
// Skip VAR[\d+] = ...;
// There are too many and they generally aren't very useful.
return;
}
if (babelTypes.isStringLiteral(path.node.left.property) &&
!propertyNames.has(path.node.left.property.value)) {
// Skip custom properties. e.g.
// VAR["abc"] = ...;
// There are too many and they generally aren't very useful.
return;
}
}
}
if (path.isCallExpression() &&
babelTypes.isIdentifier(path.node.callee) &&
!globalIdentifiers.has(path.node.callee.name)) {
// Skip VAR(...) calls since there's too much context we're missing.
return;
}
if (path.isUnaryExpression() && path.node.operator == '-') {
// Skip -... since there are too many.
return;
}
// Make the template.
let generated = babelGenerator(path.node, { concise: true }).code;
let expression = new Expression(
path.node.type,
generated,
path.parentPath.isExpressionStatement(),
source.relPath,
path.node.__idDependencies,
Boolean(path.node.__needsSuper));
// Try to de-dupe similar expressions.
let key = dedupKey(expression);
if (self.seen.has(key)) {
return;
}
// Test results.
if (!isValid(expression)) {
return;
}
// Write results.
let dirPath = fsPath.join(self.outputDir, expression.type);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath);
}
let sha1sum = crypto.createHash('sha1');
sha1sum.update(key);
let filePath = fsPath.join(dirPath, sha1sum.digest('hex') + '.json');
fs.writeFileSync(filePath, JSON.stringify(expression));
let relPath = fsPath.relative(self.outputDir, filePath);
// Update index.
self.seen.add(key);
self.index.all.push(relPath);
if (expression.needsSuper) {
self.index.superStatements.push(relPath);
} else {
self.index.statements.push(relPath);
}
}
});
}
writeIndex() {
fs.writeFileSync(
fsPath.join(this.outputDir, 'index.json'),
JSON.stringify(this.index));
}
}
class MutateDb {
constructor(outputDir) {
this.outputDir = fsPath.resolve(outputDir);
this.index = JSON.parse(
fs.readFileSync(fsPath.join(outputDir, 'index.json'), 'utf-8'));
}
getRandomStatement({canHaveSuper=false} = {}) {
let choices;
if (canHaveSuper) {
choices = random.randInt(0, 1) ?
this.index.all : this.index.superStatements;
} else {
choices = this.index.statements;
}
let path = fsPath.join(
this.outputDir, choices[random.randInt(0, choices.length - 1)]);
return JSON.parse(fs.readFileSync(path), 'utf-8');
}
}
module.exports = {
MutateDb: MutateDb,
MutateDbWriter: MutateDbWriter,
}
@@ -0,0 +1,168 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Script mutator for differential fuzzing.
*/
'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const common = require('./mutators/common.js');
const random = require('./random.js');
const sourceHelpers = require('./source_helpers.js');
const { filterDifferentialFuzzFlags } = require('./exceptions.js');
const { DifferentialFuzzMutator, DifferentialFuzzSuppressions } = require(
'./mutators/differential_fuzz_mutator.js');
const { ScriptMutator } = require('./script_mutator.js');
const USE_ORIGINAL_FLAGS_PROB = 0.2;
/**
* Randomly chooses a configuration from experiments. The configuration
* parameters are expected to be passed from a bundled V8 build. Constraints
* mentioned below are enforced by PRESUBMIT checks on the V8 side.
*
* @param {Object[]} experiments List of tuples (probability, first config name,
* second config name, second d8 name). The probabilities are integers in
* [0,100]. We assume the sum of all probabilities is 100.
* @param {Object[]} additionalFlags List of tuples (probability, flag strings).
* Probability is in [0,1).
* @return {string[]} List of flags for v8_foozzie.py.
*/
function chooseRandomFlags(experiments, additionalFlags) {
// Add additional flags to second config based on experiment percentages.
const extra_flags = [];
for (const [p, flags] of additionalFlags) {
if (random.choose(p)) {
for (const flag of flags.split(' ')) {
extra_flags.push('--second-config-extra-flags=' + flag);
}
}
}
// Calculate flags determining the experiment.
let acc = 0;
const threshold = random.random() * 100;
for (let [prob, first_config, second_config, second_d8] of experiments) {
acc += prob;
if (acc > threshold) {
return [
'--first-config=' + first_config,
'--second-config=' + second_config,
'--second-d8=' + second_d8,
].concat(extra_flags);
}
}
// Unreachable.
assert(false);
}
function loadJSONFromBuild(name) {
assert(process.env.APP_DIR);
const fullPath = path.join(path.resolve(process.env.APP_DIR), name);
return JSON.parse(fs.readFileSync(fullPath, 'utf-8'));
}
function hasMjsunit(dependencies) {
return dependencies.some(dep => dep.relPath.endsWith('mjsunit.js'));
}
function hasJSTests(dependencies) {
return dependencies.some(dep => dep.relPath.endsWith('jstest_stubs.js'));
}
class DifferentialScriptMutator extends ScriptMutator {
constructor(settings, db_path) {
super(settings, db_path);
// Mutators for differential fuzzing.
this.differential = [
new DifferentialFuzzSuppressions(settings),
new DifferentialFuzzMutator(settings),
];
// Flag configurations from the V8 build directory.
this.experiments = loadJSONFromBuild('v8_fuzz_experiments.json');
this.additionalFlags = loadJSONFromBuild('v8_fuzz_flags.json');
}
/**
* Performes the high-level mutation and afterwards adds flags for the
* v8_foozzie.py harness.
*/
mutateMultiple(inputs) {
const result = super.mutateMultiple(inputs);
const originalFlags = [];
// Keep original JS flags in some cases. Let the harness pass them to
// baseline _and_ comparison run.
if (random.choose(USE_ORIGINAL_FLAGS_PROB)) {
for (const flag of filterDifferentialFuzzFlags(result.flags)) {
originalFlags.push('--first-config-extra-flags=' + flag);
originalFlags.push('--second-config-extra-flags=' + flag);
}
}
// Add flags for the differnetial-fuzzing settings.
const fuzzFlags = chooseRandomFlags(this.experiments, this.additionalFlags);
result.flags = fuzzFlags.concat(originalFlags);
return result;
}
/**
* Mutatates a set of inputs.
*
* Additionally we prepare inputs by tagging each with the original source
* path for later printing. The mutated sources are post-processed by the
* differential-fuzz mutators, adding extra printing and other substitutions.
*/
mutateInputs(inputs) {
inputs.forEach(input => common.setOriginalPath(input, input.relPath));
const result = super.mutateInputs(inputs);
this.differential.forEach(mutator => mutator.mutate(result));
return result;
}
/**
* Adds extra dependencies for differential fuzzing.
*/
resolveDependencies(inputs) {
const dependencies = super.resolveDependencies(inputs);
// The suppression file neuters functions not working with differential
// fuzzing. It can also be used to temporarily silence some functionality
// leading to dupes of an active bug.
dependencies.push(
sourceHelpers.loadResource('differential_fuzz_suppressions.js'));
// Extra printing and tracking functionality.
dependencies.push(
sourceHelpers.loadResource('differential_fuzz_library.js'));
// Make Chakra tests print more.
dependencies.push(
sourceHelpers.loadResource('differential_fuzz_chakra.js'));
if (hasMjsunit(dependencies)) {
// Make V8 tests print more. We guard this as the functionality
// relies on mjsunit.js.
dependencies.push(sourceHelpers.loadResource('differential_fuzz_v8.js'));
}
if (hasJSTests(dependencies)) {
dependencies.push(
sourceHelpers.loadResource('differential_fuzz_jstest.js'));
}
return dependencies;
}
}
module.exports = {
DifferentialScriptMutator: DifferentialScriptMutator,
};
@@ -0,0 +1,256 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Blacklists for fuzzer.
*/
'use strict';
const fs = require('fs');
const path = require('path');
const random = require('./random.js');
const {generatedSloppy, generatedSoftSkipped, generatedSkipped} = require(
'./generated/exceptions.js');
const SKIPPED_FILES = [
// Disabled for unexpected test behavior, specific to d8 shell.
'd8-os.js',
'd8-readbuffer.js',
// Passes JS flags.
'd8-arguments.js',
// Slow tests or tests that are too large to be used as input.
/numops-fuzz-part.*.js/,
'regexp-pcre.js',
'unicode-test.js',
'unicodelctest.js',
'unicodelctest-no-optimization.js',
// Unsupported modules.
/^modules.*\.js/,
// Unsupported property escapes.
/^regexp-property-.*\.js/,
// Bad testcases that just loads a script that always throws errors.
'regress-444805.js',
'regress-crbug-489597.js',
'regress-crbug-620253.js',
// Just recursively loads itself.
'regress-8510.js',
];
const SKIPPED_DIRECTORIES = [
// Slow tests or tests that are too large to be used as input.
'embenchen',
'poppler',
'sqlite',
// Causes lots of failures.
'test262',
// Unavailable debug.Debug.
'v8/test/debugger',
'v8/test/inspector',
// Unsupported modules.
'v8/test/js-perf-test/Modules',
// Contains tests expected to error out on parsing.
'v8/test/message',
// Needs specific dependencies for load of various tests.
'v8/test/mjsunit/tools',
// Unsupported e4x standard.
'mozilla/data/e4x',
// Bails out fast without ReadableStream support.
'spidermonkey/non262/ReadableStream',
];
// Files used with a lower probability.
const SOFT_SKIPPED_FILES = [
// Tests with large binary content.
/^binaryen.*\.js/,
// Tests slow to parse.
// CrashTests:
/^jquery.*\.js/,
// Spidermonkey:
'regress-308085.js',
'regress-74474-002.js',
'regress-74474-003.js',
// V8:
'object-literal.js',
];
// Flags that lead to false positives or that are already passed by default.
const DISALLOWED_FLAGS = [
// Disallowed because features prefixed with "experimental" are not
// stabilized yet and would cause too much noise when enabled.
/^--experimental-.*/,
// Disallowed due to noise. We explicitly add --harmony to job
// definitions, and all of these features are staged before launch.
/^--harmony-.*/,
// Disallowed because they are passed explicitly on the command line.
'--allow-natives-syntax',
'--debug-code',
'--harmony',
'--wasm-staging',
'--expose-gc',
'--expose_gc',
'--icu-data-file',
'--random-seed',
// Disallowed due to false positives.
'--check-handle-count',
'--correctness-fuzzer-suppressions',
'--expose-debug-as',
'--expose-natives-as',
'--expose-trigger-failure',
'--mock-arraybuffer-allocator',
'natives', // Used in conjuction with --expose-natives-as.
/^--trace-path.*/,
];
// Flags only used with 25% probability.
const LOW_PROB_FLAGS_PROB = 0.25;
const LOW_PROB_FLAGS = [
// Flags that lead to slow test performance.
/^--gc-interval.*/,
/^--deopt-every-n-times.*/,
];
// Flags printing data, leading to false positives in differential fuzzing.
const DISALLOWED_DIFFERENTIAL_FUZZ_FLAGS = [
/^--gc-interval.*/,
/^--perf.*/,
/^--print.*/,
/^--stress-runs.*/,
/^--trace.*/,
'--expose-externalize-string',
'--interpreted-frames-native-stack',
'--validate-asm',
];
const MAX_FILE_SIZE_BYTES = 128 * 1024; // 128KB
const MEDIUM_FILE_SIZE_BYTES = 32 * 1024; // 32KB
function _findMatch(iterable, candidate) {
for (const entry of iterable) {
if (typeof entry === 'string') {
if (entry === candidate) {
return true;
}
} else {
if (entry.test(candidate)) {
return true;
}
}
}
return false;
}
function _doesntMatch(iterable, candidate) {
return !_findMatch(iterable, candidate);
}
// Convert Windows path separators.
function normalize(testPath) {
return path.normalize(testPath).replace(/\\/g, '/');
}
function isTestSkippedAbs(absPath) {
const basename = path.basename(absPath);
if (_findMatch(SKIPPED_FILES, basename)) {
return true;
}
const normalizedTestPath = normalize(absPath);
for (const entry of SKIPPED_DIRECTORIES) {
if (normalizedTestPath.includes(entry)) {
return true;
}
}
// Avoid OOM/hangs through huge inputs.
const stat = fs.statSync(absPath);
return (stat && stat.size >= MAX_FILE_SIZE_BYTES);
}
function isTestSkippedRel(relPath) {
return generatedSkipped.has(normalize(relPath));
}
// For testing.
function getSoftSkipped() {
return SOFT_SKIPPED_FILES;
}
// For testing.
function getGeneratedSoftSkipped() {
return generatedSoftSkipped;
}
// For testing.
function getGeneratedSloppy() {
return generatedSloppy;
}
function isTestSoftSkippedAbs(absPath) {
const basename = path.basename(absPath);
if (_findMatch(this.getSoftSkipped(), basename)) {
return true;
}
// Graylist medium size files.
const stat = fs.statSync(absPath);
return (stat && stat.size >= MEDIUM_FILE_SIZE_BYTES);
}
function isTestSoftSkippedRel(relPath) {
return this.getGeneratedSoftSkipped().has(normalize(relPath));
}
function isTestSloppyRel(relPath) {
return this.getGeneratedSloppy().has(normalize(relPath));
}
function filterFlags(flags) {
return flags.filter(flag => {
return (
_doesntMatch(DISALLOWED_FLAGS, flag) &&
(_doesntMatch(LOW_PROB_FLAGS, flag) ||
random.choose(LOW_PROB_FLAGS_PROB)));
});
}
function filterDifferentialFuzzFlags(flags) {
return flags.filter(
flag => _doesntMatch(DISALLOWED_DIFFERENTIAL_FUZZ_FLAGS, flag));
}
module.exports = {
filterDifferentialFuzzFlags: filterDifferentialFuzzFlags,
filterFlags: filterFlags,
getGeneratedSoftSkipped: getGeneratedSoftSkipped,
getGeneratedSloppy: getGeneratedSloppy,
getSoftSkipped: getSoftSkipped,
isTestSkippedAbs: isTestSkippedAbs,
isTestSkippedRel: isTestSkippedRel,
isTestSoftSkippedAbs: isTestSoftSkippedAbs,
isTestSoftSkippedRel: isTestSoftSkippedRel,
isTestSloppyRel: isTestSloppyRel,
}
@@ -0,0 +1,49 @@
#!/usr/bin/env python3
# Copyright 2020 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Launcher for the foozzie differential-fuzzing harness. Wraps foozzie
with Python2 for backwards-compatibility when bisecting.
Obsolete now after switching to Python3 entirely. We keep the launcher
for a transition period.
"""
import os
import re
import shutil
import subprocess
import sys
def find_harness_code(args):
for arg in args:
if arg.endswith('v8_foozzie.py'):
with open(arg) as f:
return f.read()
assert False, 'Foozzie harness not found'
if __name__ == '__main__':
# In some cases or older versions, the python executable is passed as
# first argument. Let's be robust either way, with or without full
# path or version.
if re.match(r'.*python.*', sys.argv[1]):
args = sys.argv[2:]
else:
args = sys.argv[1:]
python_exe = 'python3'
# To ease bisection of really old bugs, attempt to use Python2 as long
# as it is supported. This enables bisection before the point where the
# harness switched to Python3.
script = find_harness_code(args)
use_python3 = script.startswith('#!/usr/bin/env python3')
if not use_python3 and shutil.which('python2'):
python_exe = 'python2'
process = subprocess.Popen([python_exe] + args)
process.communicate()
sys.exit(process.returncode)
@@ -0,0 +1,196 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Generate exceptions from full corpus test report.
*/
const program = require('commander');
const assert = require('assert');
const babelGenerator = require('@babel/generator').default;
const babelTemplate = require('@babel/template').default;
const babelTypes = require('@babel/types');
const fs = require('fs');
const p = require('path');
const prettier = require("prettier");
const SPLIT_LINES_RE = /^.*([\n\r]+|$)/gm;
const PARSE_RE = /^Parsing (.*) sloppy took (\d+) ms\.\n$/;
const MUTATE_RE = /^Mutating (.*) took (\d+) ms\.\n$/;
const PARSE_FAILED_RE = /^WARNING: failed to sloppy parse (.*)\n$/;
const PARSE_STRICT_FAILED_RE = /^WARNING: failed to strict parse (.*)\n$/;
const MUTATE_FAILED_RE = /^ERROR: Exception during mutate: (.*)\n$/;
// Add tests matching error regexp to result array.
function matchError(regexp, line, resultArray){
const match = line.match(regexp);
if (!match) return false;
const relPath = match[1];
assert(relPath);
resultArray.push(relPath);
return true;
}
// Sum up total duration of tests matching the duration regexp and
// map test -> duration in result map.
function matchDuration(regexp, line, resultMap){
const match = line.match(regexp);
if (!match) return false;
const relPath = match[1];
assert(relPath);
resultMap[relPath] = (resultMap[relPath] || 0) + parseInt(match[2]);
return true;
}
// Create lists of failed and slow tests from stdout of a fuzzer run.
function processFuzzOutput(outputFile){
const text = fs.readFileSync(outputFile, 'utf-8');
const lines = text.match(SPLIT_LINES_RE);
const failedParse = [];
const failedParseStrict = [];
const failedMutate = [];
const durationsMap = {};
for (const line of lines) {
if (matchError(PARSE_FAILED_RE, line, failedParse))
continue;
if (matchError(PARSE_STRICT_FAILED_RE, line, failedParseStrict))
continue;
if (matchError(MUTATE_FAILED_RE, line, failedMutate))
continue;
if (matchDuration(PARSE_RE, line, durationsMap))
continue;
if (matchDuration(MUTATE_RE, line, durationsMap))
continue;
}
// Tuples (absPath, duration).
const total = Object.entries(durationsMap);
// Tuples (absPath, duration) with 2s < duration <= 10s.
const slow = total.filter(t => t[1] > 2000 && t[1] <= 10000);
// Tuples (absPath, duration) with 10s < duration.
const verySlow = total.filter(t => t[1] > 10000);
// Assert there's nothing horribly wrong with the results.
// We have at least 2500 tests in the output.
assert(total.length > 2500);
// No more than 5% parse/mutation errors.
assert(failedParse.length + failedMutate.length < total.length / 20);
// No more than 10% slow tests
assert(slow.length < total.length / 10);
// No more than 2% very slow tests.
assert(verySlow.length < total.length / 50);
// Sort everything.
failedParse.sort();
failedParseStrict.sort();
failedMutate.sort();
function slowestFirst(a, b) {
return b[1] - a[1];
}
slow.sort(slowestFirst);
verySlow.sort(slowestFirst);
return [failedParse, failedParseStrict, failedMutate, slow, verySlow];
}
// List of string literals of failed tests.
function getLiteralsForFailed(leadingComment, failedList) {
const result = failedList.map(path => babelTypes.stringLiteral(path));
if (result.length) {
babelTypes.addComment(result[0], 'leading', leadingComment);
}
return result;
}
// List of string literals of slow tests with duration comments.
function getLiteralsForSlow(leadingComment, slowList) {
const result = slowList.map(([path, duration]) => {
const literal = babelTypes.stringLiteral(path);
babelTypes.addComment(
literal, 'trailing', ` ${duration / 1000}s`, true);
return literal;
});
if (result.length) {
babelTypes.addComment(result[0], 'leading', leadingComment);
}
return result;
}
function main() {
program
.version('0.0.1')
.parse(process.argv);
if (!program.args.length) {
console.log('Need to specify stdout reports of fuzz runs.');
return;
}
let skipped = [];
let softSkipped = [];
let sloppy = [];
for (const outputFile of program.args) {
const [failedParse, failedParseStrict, failedMutate, slow, verySlow] = (
processFuzzOutput(outputFile));
const name = p.basename(outputFile, p.extname(outputFile));
// Skip tests that fail to parse/mutate or are very slow.
skipped = skipped.concat(getLiteralsForFailed(
` Tests with parse errors from ${name} `, failedParse));
skipped = skipped.concat(getLiteralsForFailed(
` Tests with mutation errors from ${name} `, failedMutate));
skipped = skipped.concat(getLiteralsForSlow(
` Very slow tests from ${name} `, verySlow));
// Soft-skip slow but not very slow tests.
softSkipped = softSkipped.concat(getLiteralsForSlow(
` Slow tests from ${name} `, slow));
// Mark sloppy tests.
sloppy = sloppy.concat(getLiteralsForFailed(
` Tests requiring sloppy mode from ${name} `, failedParseStrict));
}
const fileTemplate = babelTemplate(`
/**
* @fileoverview Autogenerated exceptions. Created with gen_exceptions.js.
*/
'use strict';
const skipped = SKIPPED;
const softSkipped = SOFTSKIPPED;
const sloppy = SLOPPY;
module.exports = {
generatedSkipped: new Set(skipped),
generatedSoftSkipped: new Set(softSkipped),
generatedSloppy: new Set(sloppy),
}
`, {preserveComments: true});
const skippedArray = babelTypes.arrayExpression(skipped);
const softSkippedArray = babelTypes.arrayExpression(softSkipped);
const sloppyArray = babelTypes.arrayExpression(sloppy);
const statements = fileTemplate({
SKIPPED: skippedArray,
SOFTSKIPPED: softSkippedArray,
SLOPPY: sloppyArray,
});
const resultProgram = babelTypes.program(statements);
const code = babelGenerator(resultProgram, { comments: true }).code;
const prettyCode = prettier.format(code, { parser: "babel" });
fs.writeFileSync('generated/exceptions.js', prettyCode);
}
main();
+12
View File
@@ -0,0 +1,12 @@
#!/bin/bash
# Copyright 2020 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
APP_NAME=d8 node run.js -i $WEB_TESTS -o $OUTPUT -z -v -e -c chakra > chakra.log
APP_NAME=d8 node run.js -i $WEB_TESTS -o $OUTPUT -z -v -e -c v8 > v8.log
APP_NAME=d8 node run.js -i $WEB_TESTS -o $OUTPUT -z -v -e -c spidermonkey > spidermonkey.log
APP_NAME=d8 node run.js -i $WEB_TESTS -o $OUTPUT -z -v -e -c WebKit/JSTests > jstests.log
APP_NAME=d8 node run.js -i $WEB_TESTS -o $OUTPUT -z -v -e -c CrashTests > crashtests.log
node gen_exceptions.js v8.log spidermonkey.log chakra.log jstests.log crashtests.log
@@ -0,0 +1,115 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Mutator for array expressions.
*/
'use strict';
const babelTypes = require('@babel/types');
const common = require('./common.js');
const mutator = require('./mutator.js');
const random = require('../random.js');
// Blueprint for choosing the maximum number of mutations. Bias towards
// performing only one mutation.
const MUTATION_CHOICES = [1, 1, 1, 1, 1, 2, 2, 2, 3];
const MAX_ARRAY_LENGTH = 50;
class ArrayMutator extends mutator.Mutator {
constructor(settings) {
super();
this.settings = settings;
}
get visitor() {
const thisMutator = this;
return {
ArrayExpression(path) {
const elements = path.node.elements;
if (!random.choose(thisMutator.settings.MUTATE_ARRAYS) ||
elements.length > MAX_ARRAY_LENGTH) {
return;
}
// Annotate array expression with the action taken, indicating
// if we also replaced elements.
function annotate(message, replace) {
if (replace) message += ' (replaced)';
thisMutator.annotate(path.node, message);
}
// Add or replace elements at a random index.
function randomSplice(replace, ...args) {
// Choose an index that's small enough to replace all desired items.
const index = random.randInt(0, elements.length - replace);
elements.splice(index, replace, ...args);
}
function duplicateElement(replace) {
const element = random.single(elements);
if (!element || common.isLargeNode(element)) {
return;
}
annotate('Duplicate an element', replace);
randomSplice(replace, babelTypes.cloneDeep(element));
}
function insertRandomValue(replace) {
annotate('Insert a random value', replace);
randomSplice(replace, common.randomValue(path));
}
function insertHole(replace) {
annotate('Insert a hole', replace);
randomSplice(replace, null);
}
function removeElements(count) {
annotate('Remove elements');
randomSplice(random.randInt(1, count));
}
function shuffle() {
annotate('Shuffle array');
random.shuffle(elements);
}
// Mutation options. Repeated mutations have a higher probability.
const mutations = [
() => duplicateElement(1),
() => duplicateElement(1),
() => duplicateElement(1),
() => duplicateElement(0),
() => duplicateElement(0),
() => insertRandomValue(1),
() => insertRandomValue(1),
() => insertRandomValue(0),
() => insertHole(1),
() => insertHole(0),
() => removeElements(1),
() => removeElements(elements.length),
shuffle,
];
// Perform several mutations.
const count = random.single(MUTATION_CHOICES);
for (let i = 0; i < count; i++) {
random.single(mutations)();
}
// Don't recurse on nested arrays.
path.skip();
},
}
}
}
module.exports = {
ArrayMutator: ArrayMutator,
};
@@ -0,0 +1,376 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Common mutator utilities.
*/
const babelTemplate = require('@babel/template').default;
const babelTypes = require('@babel/types');
const babylon = require('@babel/parser');
const sourceHelpers = require('../source_helpers.js');
const random = require('../random.js');
const INTERESTING_NUMBER_VALUES = [
-1, -0.0, 0, 1,
// Float values.
-0.000000000000001, 0.000000000000001,
// Special values.
NaN, +Infinity, -Infinity,
// Boundaries of int, signed, unsigned, SMI (near +/- 2^(30, 31, 32).
0x03fffffff, 0x040000000, 0x040000001,
-0x03fffffff, -0x040000000, -0x040000001,
0x07fffffff, 0x080000000, 0x080000001,
-0x07fffffff, -0x080000000, -0x080000001,
0x0ffffffff, 0x100000000, 0x100000001,
-0x0ffffffff, -0x100000000, -0x100000001,
// Boundaries of maximum safe integer (near +/- 2^53).
9007199254740990, 9007199254740991, 9007199254740992,
-9007199254740990, -9007199254740991, -9007199254740992,
// Boundaries of double.
5e-324, 1.7976931348623157e+308,
-5e-324,-1.7976931348623157e+308,
]
const INTERESTING_NON_NUMBER_VALUES = [
// Simple arrays.
'[]',
'Array(0x8000).fill("a")',
// Simple object.
'{}',
'{a: "foo", b: 10, c: {}}',
// Simple strings.
'"foo"',
'""',
// Simple regex.
'/0/',
'"/0/"',
// Simple symbol.
'Symbol("foo")',
// Long string.
'Array(0x8000).join("a")',
// Math.PI
'Math.PI',
// Others.
'false',
'true',
'undefined',
'null',
'this',
'this[0]',
'this[1]',
// Empty function.
'(function() {return 0;})',
// Objects with functions.
'({toString:function(){return "0";}})',
'({valueOf:function(){return 0;}})',
'({valueOf:function(){return "0";}})',
// Objects for primitive types created using new.
'(new Boolean(false))',
'(new Boolean(true))',
'(new String(""))',
'(new Number(0))',
'(new Number(-0))',
]
const LARGE_NODE_SIZE = 100;
const MAX_ARGUMENT_COUNT = 10;
function _identifier(identifier) {
return babelTypes.identifier(identifier);
}
function _numericLiteral(number) {
return babelTypes.numericLiteral(number);
}
function _unwrapExpressionStatement(value) {
if (babelTypes.isExpressionStatement(value)) {
return value.expression;
}
return value;
}
function isVariableIdentifier(name) {
return /__v_[0-9]+/.test(name);
}
function isFunctionIdentifier(name) {
return /__f_[0-9]+/.test(name);
}
function isInForLoopCondition(path) {
// Return whether if we're in the init/test/update parts of a for loop (but
// not the body). Mutating variables in the init/test/update will likely
// modify loop variables and cause infinite loops.
const forStatementChild = path.find(
p => p.parent && babelTypes.isForStatement(p.parent));
return (forStatementChild && forStatementChild.parentKey !== 'body');
}
function isInWhileLoop(path) {
// Return whether if we're in a while loop.
const whileStatement = path.find(p => babelTypes.isWhileStatement(p));
return Boolean(whileStatement);
}
function _availableIdentifiers(path, filter) {
// TODO(ochang): Consider globals that aren't declared with let/var etc.
const available = new Array();
const allBindings = path.scope.getAllBindings();
for (const key of Object.keys(allBindings)) {
if (!filter(key)) {
continue;
}
if (filter === isVariableIdentifier &&
path.willIMaybeExecuteBefore(allBindings[key].path)) {
continue;
}
available.push(_identifier(key));
}
return available;
}
function availableVariables(path) {
return _availableIdentifiers(path, isVariableIdentifier);
}
function availableFunctions(path) {
return _availableIdentifiers(path, isFunctionIdentifier);
}
function randomVariable(path) {
return random.single(availableVariables(path));
}
function randomFunction(path) {
return random.single(availableFunctions(path));
}
function randomSeed() {
return random.randInt(0, 2**20);
}
function randomObject(seed) {
if (seed === undefined) {
seed = randomSeed();
}
const template = babelTemplate('__getRandomObject(SEED)');
return template({
SEED: _numericLiteral(seed),
}).expression;
}
function randomProperty(identifier, seed) {
if (seed === undefined) {
seed = randomSeed();
}
const template = babelTemplate('__getRandomProperty(IDENTIFIER, SEED)');
return template({
IDENTIFIER: identifier,
SEED: _numericLiteral(seed),
}).expression;
}
function randomArguments(path) {
const numArgs = random.randInt(0, MAX_ARGUMENT_COUNT);
const args = [];
for (let i = 0; i < numArgs; i++) {
args.push(randomValue(path));
}
return args.map(_unwrapExpressionStatement);
}
function randomValue(path) {
const probability = random.random();
if (probability < 0.01) {
const randomFunc = randomFunction(path);
if (randomFunc) {
return randomFunc;
}
}
if (probability < 0.25) {
const randomVar = randomVariable(path);
if (randomVar) {
return randomVar;
}
}
if (probability < 0.5) {
return randomInterestingNumber();
}
if (probability < 0.75) {
return randomInterestingNonNumber();
}
return randomObject();
}
function callRandomFunction(path, identifier, seed) {
if (seed === undefined) {
seed = randomSeed();
}
let args = [
identifier,
_numericLiteral(seed)
];
args = args.map(_unwrapExpressionStatement);
args = args.concat(randomArguments(path));
return babelTypes.callExpression(
babelTypes.identifier('__callRandomFunction'),
args);
}
function nearbyRandomNumber(value) {
const probability = random.random();
if (probability < 0.9) {
return _numericLiteral(value + random.randInt(-0x10, 0x10));
} else if (probability < 0.95) {
return _numericLiteral(value + random.randInt(-0x100, 0x100));
} else if (probability < 0.99) {
return _numericLiteral(value + random.randInt(-0x1000, 0x1000));
}
return _numericLiteral(value + random.randInt(-0x10000, 0x10000));
}
function randomInterestingNumber() {
const value = random.single(INTERESTING_NUMBER_VALUES);
if (random.choose(0.05)) {
return nearbyRandomNumber(value);
}
return _numericLiteral(value);
}
function randomInterestingNonNumber() {
return babylon.parseExpression(random.single(INTERESTING_NON_NUMBER_VALUES));
}
function concatFlags(inputs) {
const flags = new Set();
for (const input of inputs) {
for (const flag of input.flags || []) {
flags.add(flag);
}
}
return Array.from(flags.values());
}
function concatPrograms(inputs) {
// Concatentate programs.
const resultProgram = babelTypes.program([]);
const result = babelTypes.file(resultProgram, [], null);
for (const input of inputs) {
const ast = input.ast.program;
resultProgram.body = resultProgram.body.concat(ast.body);
resultProgram.directives = resultProgram.directives.concat(ast.directives);
}
// TODO(machenbach): Concat dependencies here as soon as they are cached.
const combined = new sourceHelpers.ParsedSource(
result, '', '', concatFlags(inputs));
// If any input file is sloppy, the combined result is sloppy.
combined.sloppy = inputs.some(input => input.isSloppy());
return combined;
}
function setSourceLoc(source, index, total) {
const noop = babelTypes.noop();
noop.__loc = index / total;
noop.__self = noop;
source.ast.program.body.unshift(noop);
}
function getSourceLoc(node) {
// Source location is invalid in cloned nodes.
if (node !== node.__self) {
return undefined;
}
return node.__loc;
}
function setOriginalPath(source, originalPath) {
const noop = babelTypes.noop();
noop.__path = originalPath;
noop.__self = noop;
source.ast.program.body.unshift(noop);
}
function getOriginalPath(node) {
// Original path is invalid in cloned nodes.
if (node !== node.__self) {
return undefined;
}
return node.__path;
}
// Estimate the size of a node in raw source characters.
function isLargeNode(node) {
// Ignore array holes inserted by us (null) or previously cloned nodes
// (they have no start/end).
if (!node || node.start === undefined || node.end === undefined ) {
return false;
}
return node.end - node.start > LARGE_NODE_SIZE;
}
module.exports = {
callRandomFunction: callRandomFunction,
concatFlags: concatFlags,
concatPrograms: concatPrograms,
availableVariables: availableVariables,
availableFunctions: availableFunctions,
randomFunction: randomFunction,
randomVariable: randomVariable,
isInForLoopCondition: isInForLoopCondition,
isInWhileLoop: isInWhileLoop,
isLargeNode: isLargeNode,
isVariableIdentifier: isVariableIdentifier,
isFunctionIdentifier: isFunctionIdentifier,
nearbyRandomNumber: nearbyRandomNumber,
randomArguments: randomArguments,
randomInterestingNonNumber: randomInterestingNonNumber,
randomInterestingNumber: randomInterestingNumber,
randomObject: randomObject,
randomProperty: randomProperty,
randomSeed: randomSeed,
randomValue: randomValue,
getOriginalPath: getOriginalPath,
setOriginalPath: setOriginalPath,
getSourceLoc: getSourceLoc,
setSourceLoc: setSourceLoc,
}
@@ -0,0 +1,85 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Expression mutator.
*/
'use strict';
const babelTemplate = require('@babel/template').default;
const common = require('./common.js');
const random = require('../random.js');
const mutator = require('./mutator.js');
const sourceHelpers = require('../source_helpers.js');
class CrossOverMutator extends mutator.Mutator {
constructor(settings, db) {
super();
this.settings = settings;
this.db = db;
}
get visitor() {
const thisMutator = this;
return [{
ExpressionStatement(path) {
if (!random.choose(thisMutator.settings.MUTATE_CROSSOVER_INSERT)) {
return;
}
const canHaveSuper = Boolean(path.findParent(x => x.isClassMethod()));
const randomExpression = thisMutator.db.getRandomStatement(
{canHaveSuper: canHaveSuper});
// Insert the statement.
let toInsert = babelTemplate(
randomExpression.source,
sourceHelpers.BABYLON_REPLACE_VAR_OPTIONS);
const dependencies = {};
if (randomExpression.dependencies) {
const variables = common.availableVariables(path);
if (!variables.length) {
return;
}
for (const dependency of randomExpression.dependencies) {
dependencies[dependency] = random.single(variables);
}
}
try {
toInsert = toInsert(dependencies);
} catch (e) {
if (thisMutator.settings.testing) {
// Fail early in tests.
throw e;
}
console.log('ERROR: Failed to parse:', randomExpression.source);
console.log(e);
return;
}
thisMutator.annotate(
toInsert,
'Crossover from ' + randomExpression.originalPath);
if (random.choose(0.5)) {
thisMutator.insertBeforeSkip(path, toInsert);
} else {
thisMutator.insertAfterSkip(path, toInsert);
}
path.skip();
},
}, {
}];
}
}
module.exports = {
CrossOverMutator: CrossOverMutator,
};
@@ -0,0 +1,225 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Mutator for differential fuzzing.
*/
'use strict';
const babelTemplate = require('@babel/template').default;
const babelTypes = require('@babel/types');
const common = require('./common.js');
const mutator = require('./mutator.js');
const random = require('../random.js');
// Templates for various statements.
const incCaught = babelTemplate('__caught++;');
const printValue = babelTemplate('print(VALUE);');
const printCaught = babelTemplate('print("Caught: " + __caught);');
const printHash = babelTemplate('print("Hash: " + __hash);');
const prettyPrint = babelTemplate('__prettyPrint(ID);');
const prettyPrintExtra = babelTemplate('__prettyPrintExtra(ID);');
// This section prefix is expected by v8_foozzie.py. Existing prefixes
// (e.g. from CrashTests) are cleaned up with CLEANED_PREFIX.
const SECTION_PREFIX = 'v8-foozzie source: ';
const CLEANED_PREFIX = 'v***************e: ';
/**
* Babel statement for calling deep printing from the fuzz library.
*/
function prettyPrintStatement(variable) {
return prettyPrint({ ID: babelTypes.cloneDeep(variable) });
}
/**
* As above, but using the "extra" variant, which will reduce printing
* after too many calls to prevent I/O flooding.
*/
function prettyPrintExtraStatement(variable) {
return prettyPrintExtra({ ID: babelTypes.cloneDeep(variable) });
}
/**
* Mutator for suppressing known and/or unfixable issues.
*/
class DifferentialFuzzSuppressions extends mutator.Mutator {
get visitor() {
let thisMutator = this;
return {
// Clean up strings containing the magic section prefix. Those can come
// e.g. from CrashTests and would confuse the deduplication in
// v8_foozzie.py.
StringLiteral(path) {
if (path.node.value.startsWith(SECTION_PREFIX)) {
const postfix = path.node.value.substring(SECTION_PREFIX.length);
path.node.value = CLEANED_PREFIX + postfix;
thisMutator.annotate(path.node, 'Replaced magic string');
}
},
// Known precision differences: https://crbug.com/1063568
BinaryExpression(path) {
if (path.node.operator == '**') {
path.node.operator = '+';
thisMutator.annotate(path.node, 'Replaced **');
}
},
// Unsupported language feature: https://crbug.com/1020573
MemberExpression(path) {
if (path.node.property.name == "arguments") {
let replacement = common.randomVariable(path);
if (!replacement) {
replacement = babelTypes.thisExpression();
}
thisMutator.annotate(replacement, 'Replaced .arguments');
thisMutator.replaceWithSkip(path, replacement);
}
},
};
}
}
/**
* Mutator for tracking original input files and for extra printing.
*/
class DifferentialFuzzMutator extends mutator.Mutator {
constructor(settings) {
super();
this.settings = settings;
}
/**
* Looks for the dummy node that marks the beginning of an input file
* from the corpus.
*/
isSectionStart(path) {
return !!common.getOriginalPath(path.node);
}
/**
* Create print statements for printing the magic section prefix that's
* expected by v8_foozzie.py to differentiate different source files.
*/
getSectionHeader(path) {
const orig = common.getOriginalPath(path.node);
return printValue({
VALUE: babelTypes.stringLiteral(SECTION_PREFIX + orig),
});
}
/**
* Create statements for extra printing at the end of a section. We print
* the number of caught exceptions, a generic hash of all observed values
* and the contents of all variables in scope.
*/
getSectionFooter(path) {
const variables = common.availableVariables(path);
const statements = variables.map(prettyPrintStatement);
statements.unshift(printCaught());
statements.unshift(printHash());
const statement = babelTypes.tryStatement(
babelTypes.blockStatement(statements),
babelTypes.catchClause(
babelTypes.identifier('e'),
babelTypes.blockStatement([])));
this.annotate(statement, 'Print variables and exceptions from section');
return statement;
}
/**
* Helper for printing the contents of several variables.
*/
printVariables(path, nodes) {
const statements = [];
for (const node of nodes) {
if (!babelTypes.isIdentifier(node) ||
!common.isVariableIdentifier(node.name))
continue;
statements.push(prettyPrintExtraStatement(node));
}
if (statements.length) {
this.annotate(statements[0], 'Extra variable printing');
this.insertAfterSkip(path, statements);
}
}
get visitor() {
const thisMutator = this;
const settings = this.settings;
return {
// Replace existing normal print statements with deep printing.
CallExpression(path) {
if (babelTypes.isIdentifier(path.node.callee) &&
path.node.callee.name == 'print') {
path.node.callee = babelTypes.identifier('__prettyPrintExtra');
thisMutator.annotate(path.node, 'Pretty printing');
}
},
// Either print or track caught exceptions, guarded by a probability.
CatchClause(path) {
const probability = random.random();
if (probability < settings.DIFF_FUZZ_EXTRA_PRINT &&
path.node.param &&
babelTypes.isIdentifier(path.node.param)) {
const statement = prettyPrintExtraStatement(path.node.param);
path.node.body.body.unshift(statement);
} else if (probability < settings.DIFF_FUZZ_TRACK_CAUGHT) {
path.node.body.body.unshift(incCaught());
}
},
// Insert section headers and footers between the contents of two
// original source files. We detect the dummy no-op nodes that were
// previously tagged with the original path of the file.
Noop(path) {
if (!thisMutator.isSectionStart(path)) {
return;
}
const header = thisMutator.getSectionHeader(path);
const footer = thisMutator.getSectionFooter(path);
thisMutator.insertBeforeSkip(path, footer);
thisMutator.insertBeforeSkip(path, header);
},
// Additionally we print one footer in the end.
Program: {
exit(path) {
const footer = thisMutator.getSectionFooter(path);
path.node.body.push(footer);
},
},
// Print contents of variables after assignments, guarded by a
// probability.
ExpressionStatement(path) {
if (!babelTypes.isAssignmentExpression(path.node.expression) ||
!random.choose(settings.DIFF_FUZZ_EXTRA_PRINT)) {
return;
}
const left = path.node.expression.left;
if (babelTypes.isMemberExpression(left)) {
thisMutator.printVariables(path, [left.object]);
} else {
thisMutator.printVariables(path, [left]);
}
},
// Print contents of variables after declaration, guarded by a
// probability.
VariableDeclaration(path) {
if (babelTypes.isLoop(path.parent) ||
!random.choose(settings.DIFF_FUZZ_EXTRA_PRINT)) {
return;
}
const identifiers = path.node.declarations.map(decl => decl.id);
thisMutator.printVariables(path, identifiers);
},
};
}
}
module.exports = {
DifferentialFuzzMutator: DifferentialFuzzMutator,
DifferentialFuzzSuppressions: DifferentialFuzzSuppressions,
};
@@ -0,0 +1,63 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Expression mutator.
*/
'use strict';
const babelTypes = require('@babel/types');
const random = require('../random.js');
const mutator = require('./mutator.js');
class ExpressionMutator extends mutator.Mutator {
constructor(settings) {
super();
this.settings = settings;
}
get visitor() {
const thisMutator = this;
return {
ExpressionStatement(path) {
if (!random.choose(thisMutator.settings.MUTATE_EXPRESSIONS)) {
return;
}
const probability = random.random();
if (probability < 0.7) {
const repeated = babelTypes.cloneDeep(path.node);
thisMutator.annotate(repeated, 'Repeated');
thisMutator.insertBeforeSkip(path, repeated);
} else if (path.key > 0) {
// Get a random previous sibling.
const prev = path.getSibling(random.randInt(0, path.key - 1));
if (!prev || !prev.node) {
return;
}
// Either select a previous or the current node to clone.
const [selected, destination] = random.shuffle([prev, path]);
if (selected.isDeclaration()) {
return;
}
const cloned = babelTypes.cloneDeep(selected.node);
thisMutator.annotate(cloned, 'Cloned sibling');
if (random.choose(0.5)) {
thisMutator.insertBeforeSkip(destination, cloned);
} else {
thisMutator.insertAfterSkip(destination, cloned);
}
}
},
};
}
}
module.exports = {
ExpressionMutator: ExpressionMutator,
};
@@ -0,0 +1,149 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Function calls mutator.
*/
'use strict';
const babelTemplate = require('@babel/template').default;
const babelTypes = require('@babel/types');
const common = require('./common.js');
const random = require('../random.js');
const mutator = require('./mutator.js');
function _liftExpressionsToStatements(path, nodes) {
// If the node we're replacing is an expression in an expression statement,
// lift the replacement nodes into statements too.
if (!babelTypes.isExpressionStatement(path.parent)) {
return nodes;
}
return nodes.map(n => babelTypes.expressionStatement(n));
}
class FunctionCallMutator extends mutator.Mutator {
constructor(settings) {
super();
this.settings = settings;
}
get visitor() {
const thisMutator = this;
return {
CallExpression(path) {
if (!babelTypes.isIdentifier(path.node.callee)) {
return;
}
if (!common.isFunctionIdentifier(path.node.callee.name)) {
return;
}
if (!random.choose(thisMutator.settings.MUTATE_FUNCTION_CALLS)) {
return;
}
const probability = random.random();
if (probability < 0.3) {
const randFunc = common.randomFunction(path);
if (randFunc) {
thisMutator.annotate(
path.node,
`Replaced ${path.node.callee.name} with ${randFunc.name}`);
path.node.callee = randFunc;
}
} else if (probability < 0.7 && thisMutator.settings.engine == 'V8') {
const prepareTemplate = babelTemplate(
'__V8BuiltinPrepareFunctionForOptimization(ID)');
const optimizationMode = random.choose(0.7) ? 'Function' : 'Maglev';
const optimizeTemplate = babelTemplate(
`__V8BuiltinOptimize${optimizationMode}OnNextCall(ID)`);
const nodes = [
prepareTemplate({
ID: babelTypes.cloneDeep(path.node.callee),
}).expression,
babelTypes.cloneDeep(path.node),
babelTypes.cloneDeep(path.node),
optimizeTemplate({
ID: babelTypes.cloneDeep(path.node.callee),
}).expression,
];
thisMutator.annotate(
path.node,
`Optimizing ${path.node.callee.name}`);
if (!babelTypes.isExpressionStatement(path.parent)) {
nodes.push(path.node);
thisMutator.replaceWithSkip(
path, babelTypes.sequenceExpression(nodes));
} else {
thisMutator.insertBeforeSkip(
path, _liftExpressionsToStatements(path, nodes));
}
} else if (probability < 0.8 && thisMutator.settings.engine == 'V8') {
const template = babelTemplate(
'__V8BuiltinCompileBaseline(ID)');
const nodes = [
template({
ID: babelTypes.cloneDeep(path.node.callee),
}).expression,
];
thisMutator.annotate(
nodes[0],
`Compiling baseline ${path.node.callee.name}`);
if (!babelTypes.isExpressionStatement(path.parent)) {
nodes.push(path.node);
thisMutator.replaceWithSkip(
path, babelTypes.sequenceExpression(nodes));
} else {
thisMutator.insertBeforeSkip(
path, _liftExpressionsToStatements(path, nodes));
}
} else if (probability < 0.9 &&
thisMutator.settings.engine == 'V8') {
const template = babelTemplate(
'__V8BuiltinDeoptimizeFunction(ID)');
const insert = _liftExpressionsToStatements(path, [
template({
ID: babelTypes.cloneDeep(path.node.callee),
}).expression,
]);
thisMutator.annotate(
path.node,
`Deoptimizing ${path.node.callee.name}`);
thisMutator.insertAfterSkip(path, insert);
} else {
const template = babelTemplate(
'runNearStackLimit(() => { return CALL });');
thisMutator.annotate(
path.node,
`Run to stack limit ${path.node.callee.name}`);
thisMutator.replaceWithSkip(
path,
template({
CALL: path.node,
}).expression);
}
path.skip();
},
}
}
}
module.exports = {
FunctionCallMutator: FunctionCallMutator,
};
@@ -0,0 +1,98 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Mutator
*/
'use strict';
const babelTraverse = require('@babel/traverse').default;
const babelTypes = require('@babel/types');
class Mutator {
get visitor() {
return null;
}
_traverse(ast, visitor) {
let oldEnter = null;
if (Object.prototype.hasOwnProperty.call(visitor, 'enter')) {
oldEnter = visitor['enter'];
}
// Transparently skip nodes that are marked.
visitor['enter'] = (path) => {
if (this.shouldSkip(path.node)) {
path.skip();
return;
}
if (oldEnter) {
oldEnter(path);
}
}
babelTraverse(ast, visitor);
}
mutate(source) {
if (Array.isArray(this.visitor)) {
for (const visitor of this.visitor) {
this._traverse(source.ast, visitor);
}
} else {
this._traverse(source.ast, this.visitor);
}
}
get _skipPropertyName() {
return '__skip' + this.constructor.name;
}
shouldSkip(node) {
return Boolean(node[this._skipPropertyName]);
}
skipMutations(node) {
// Mark a node to skip further mutations of the same kind.
if (Array.isArray(node)) {
for (const item of node) {
item[this._skipPropertyName] = true;
}
} else {
node[this._skipPropertyName] = true;
}
return node;
}
insertBeforeSkip(path, node) {
this.skipMutations(node);
path.insertBefore(node);
}
insertAfterSkip(path, node) {
this.skipMutations(node);
path.insertAfter(node);
}
replaceWithSkip(path, node) {
this.skipMutations(node);
path.replaceWith(node);
}
replaceWithMultipleSkip(path, node) {
this.skipMutations(node);
path.replaceWithMultiple(node);
}
annotate(node, message) {
babelTypes.addComment(
node, 'leading', ` ${this.constructor.name}: ${message} `);
}
}
module.exports = {
Mutator: Mutator,
}
@@ -0,0 +1,89 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Normalizer.
* This renames variables so that we don't have collisions when combining
* different files. It also simplifies other logic when e.g. determining the
* type of an identifier.
*/
'use strict';
const babelTypes = require('@babel/types');
const mutator = require('./mutator.js');
class NormalizerContext {
constructor() {
this.funcIndex = 0;
this.varIndex = 0;
this.classIndex = 0;
}
}
class IdentifierNormalizer extends mutator.Mutator {
constructor() {
super();
this.context = new NormalizerContext();
}
get visitor() {
const context = this.context;
const renamed = new WeakSet();
const globalMappings = new Map();
return [{
Scope(path) {
for (const [name, binding] of Object.entries(path.scope.bindings)) {
if (renamed.has(binding.identifier)) {
continue;
}
renamed.add(binding.identifier);
if (babelTypes.isClassDeclaration(binding.path.node) ||
babelTypes.isClassExpression(binding.path.node)) {
path.scope.rename(name, '__c_' + context.classIndex++);
} else if (babelTypes.isFunctionDeclaration(binding.path.node) ||
babelTypes.isFunctionExpression(binding.path.node)) {
path.scope.rename(name, '__f_' + context.funcIndex++);
} else {
path.scope.rename(name, '__v_' + context.varIndex++);
}
}
},
AssignmentExpression(path) {
// Find assignments for which we have no binding in the scope. We assume
// that these are globals which are local to our script (which weren't
// declared with var/let/const etc).
const ids = path.getBindingIdentifiers();
for (const name in ids) {
if (!path.scope.getBinding(name)) {
globalMappings.set(name, '__v_' + context.varIndex++);
}
}
}
}, {
// Second pass to rename globals that weren't declared with
// var/let/const etc.
Identifier(path) {
if (!globalMappings.has(path.node.name)) {
return;
}
if (path.scope.getBinding(path.node.name)) {
// Don't rename if there is a binding that hides the global.
return;
}
path.node.name = globalMappings.get(path.node.name);
}
}];
}
}
module.exports = {
IdentifierNormalizer: IdentifierNormalizer,
};
@@ -0,0 +1,105 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Numbers mutator.
*/
'use strict';
const babelTypes = require('@babel/types');
const common = require('./common.js');
const random = require('../random.js');
const mutator = require('./mutator.js');
const MIN_SAFE_INTEGER = -9007199254740991;
const MAX_SAFE_INTEGER = 9007199254740991;
function isObjectKey(path) {
return (path.parent &&
babelTypes.isObjectMember(path.parent) &&
path.parent.key === path.node);
}
function createRandomNumber(value) {
// TODO(ochang): Maybe replace with variable.
const probability = random.random();
if (probability < 0.01) {
return babelTypes.numericLiteral(
random.randInt(MIN_SAFE_INTEGER, MAX_SAFE_INTEGER));
} else if (probability < 0.06) {
return common.randomInterestingNumber();
} else {
return common.nearbyRandomNumber(value);
}
}
class NumberMutator extends mutator.Mutator {
constructor(settings) {
super();
this.settings = settings;
}
ignore(path) {
return !random.choose(this.settings.MUTATE_NUMBERS) ||
common.isInForLoopCondition(path) ||
common.isInWhileLoop(path);
}
randomReplace(path, value, forcePositive=false) {
const randomNumber = createRandomNumber(value);
if (forcePositive) {
randomNumber.value = Math.abs(randomNumber.value);
}
this.annotate(
path.node,
`Replaced ${value} with ${randomNumber.value}`);
this.replaceWithSkip(path, randomNumber);
}
get visitor() {
const thisMutator = this;
return {
NumericLiteral(path) {
if (thisMutator.ignore(path)) {
return;
}
// We handle negative unary expressions separately to replace the whole
// expression below. E.g. -5 is UnaryExpression(-, NumericLiteral(5)).
if (path.parent && babelTypes.isUnaryExpression(path.parent) &&
path.parent.operator === '-') {
return;
}
// Enfore positive numbers if the literal is the key of an object
// property or method. Negative keys cause syntax errors.
const forcePositive = isObjectKey(path);
thisMutator.randomReplace(path, path.node.value, forcePositive);
},
UnaryExpression(path) {
if (thisMutator.ignore(path)) {
return;
}
// Handle the case we ignore above.
if (path.node.operator === '-' &&
babelTypes.isNumericLiteral(path.node.argument)) {
thisMutator.randomReplace(path, -path.node.argument.value);
}
}
};
}
}
module.exports = {
NumberMutator: NumberMutator,
};
@@ -0,0 +1,135 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Mutator for object expressions.
*/
'use strict';
const babelTypes = require('@babel/types');
const common = require('./common.js');
const mutator = require('./mutator.js');
const random = require('../random.js');
const MAX_PROPERTIES = 50;
/**
* Turn the key of an object property into a string literal.
*/
function keyToString(key) {
if (babelTypes.isNumericLiteral(key)) {
return babelTypes.stringLiteral(key.value.toString());
}
if (babelTypes.isIdentifier(key)) {
return babelTypes.stringLiteral(key.name);
}
// Already a string literal.
return key;
}
class ObjectMutator extends mutator.Mutator {
constructor(settings) {
super();
this.settings = settings;
}
get visitor() {
const thisMutator = this;
return {
ObjectExpression(path) {
const properties = path.node.properties;
if (!random.choose(thisMutator.settings.MUTATE_OBJECTS) ||
properties.length > MAX_PROPERTIES) {
return;
}
// Use the indices of object properties for mutations. We ignore
// getters and setters.
const propertyIndicies = [];
for (const [index, property] of properties.entries()) {
if (babelTypes.isObjectProperty(property)) {
propertyIndicies.push(index);
}
}
// The mutations below require at least one property.
if (!propertyIndicies.length) {
return;
}
// Annotate object expression with the action taken.
function annotate(message) {
thisMutator.annotate(path.node, message);
}
function getOneRandomProperty() {
return properties[random.single(propertyIndicies)];
}
function getTwoRandomProperties() {
const [a, b] = random.sample(propertyIndicies, 2);
return [properties[a], properties[b]];
}
function swapPropertyValues() {
if (propertyIndicies.length > 1) {
annotate('Swap properties');
const [a, b] = getTwoRandomProperties();
[a.value, b.value] = [b.value, a.value];
}
}
function duplicatePropertyValue() {
if (propertyIndicies.length > 1) {
const [a, b] = random.shuffle(getTwoRandomProperties());
if (common.isLargeNode(b.value)) {
return;
}
annotate('Duplicate a property value');
a.value = babelTypes.cloneDeep(b.value);
}
}
function insertRandomValue() {
annotate('Insert a random value');
const property = getOneRandomProperty();
property.value = common.randomValue(path);
}
function stringifyKey() {
annotate('Stringify a property key');
const property = getOneRandomProperty();
property.key = keyToString(property.key);
}
function removeProperty() {
annotate('Remove a property');
properties.splice(random.single(propertyIndicies), 1);
}
// Mutation options. Repeated mutations have a higher probability.
const mutations = [
swapPropertyValues,
swapPropertyValues,
duplicatePropertyValue,
duplicatePropertyValue,
insertRandomValue,
insertRandomValue,
removeProperty,
stringifyKey,
];
// Perform mutation.
random.single(mutations)();
},
}
}
}
module.exports = {
ObjectMutator: ObjectMutator,
};
@@ -0,0 +1,175 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Try catch wrapper.
*/
const babelTypes = require('@babel/types');
const common = require('./common.js');
const mutator = require('./mutator.js');
const random = require('../random.js');
// Default target probability for skipping try-catch completely.
const DEFAULT_SKIP_PROB = 0.2;
// Default target probability to wrap only on toplevel, i.e. to not nest
// try-catch.
const DEFAULT_TOPLEVEL_PROB = 0.3;
// Probability to deviate from defaults and use extreme cases.
const IGNORE_DEFAULT_PROB = 0.05;
// Member expressions to be wrapped. List of (object, property) identifier
// tuples.
const WRAPPED_MEMBER_EXPRESSIONS = [
['WebAssembly', 'Module'],
['WebAssembly', 'Instantiate'],
];
function wrapTryCatch(node) {
return babelTypes.tryStatement(
babelTypes.blockStatement([node]),
babelTypes.catchClause(
babelTypes.identifier('e'),
babelTypes.blockStatement([])));
}
function wrapTryCatchInFunction(node) {
const ret = wrapTryCatch(babelTypes.returnStatement(node));
const anonymousFun = babelTypes.functionExpression(
null, [], babelTypes.blockStatement([ret]));
return babelTypes.callExpression(anonymousFun, []);
}
// Wrap particular member expressions after `new` that are known to appear
// in initializer lists of `let` and `const`.
function replaceNewExpression(path) {
const callee = path.node.callee;
if (!babelTypes.isMemberExpression(callee) ||
!babelTypes.isIdentifier(callee.object) ||
!babelTypes.isIdentifier(callee.property)) {
return;
}
if (WRAPPED_MEMBER_EXPRESSIONS.some(
([object, property]) => callee.object.name === object &&
callee.property.name === property)) {
path.replaceWith(wrapTryCatchInFunction(path.node));
path.skip();
}
}
function replaceAndSkip(path) {
if (!babelTypes.isLabeledStatement(path.parent) ||
!babelTypes.isLoop(path.node)) {
// Don't wrap loops with labels as it makes continue
// statements syntactically invalid. We wrap the label
// instead below.
path.replaceWith(wrapTryCatch(path.node));
}
// Prevent infinite looping.
path.skip();
}
class AddTryCatchMutator extends mutator.Mutator {
callWithProb(path, fun) {
const probability = random.random();
if (probability < this.skipProb * this.loc) {
// Entirely skip try-catch wrapper.
path.skip();
} else if (probability < (this.skipProb + this.toplevelProb) * this.loc) {
// Only wrap on top-level.
fun(path);
}
}
get visitor() {
const thisMutator = this;
const accessStatement = {
enter(path) {
thisMutator.callWithProb(path, replaceAndSkip);
},
exit(path) {
// Apply nested wrapping (is only executed if not skipped above).
replaceAndSkip(path);
}
};
return {
Program: {
enter(path) {
// Track original source location fraction in [0, 1).
thisMutator.loc = 0;
// Target probability for skipping try-catch.
thisMutator.skipProb = DEFAULT_SKIP_PROB;
// Target probability for not nesting try-catch.
thisMutator.toplevelProb = DEFAULT_TOPLEVEL_PROB;
// Maybe deviate from target probability for the entire test.
if (random.choose(IGNORE_DEFAULT_PROB)) {
thisMutator.skipProb = random.uniform(0, 1);
thisMutator.toplevelProb = random.uniform(0, 1);
thisMutator.annotate(
path.node,
'Target skip probability ' + thisMutator.skipProb +
' and toplevel probability ' + thisMutator.toplevelProb);
}
}
},
Noop: {
enter(path) {
if (common.getSourceLoc(path.node)) {
thisMutator.loc = common.getSourceLoc(path.node);
}
},
},
ExpressionStatement: accessStatement,
IfStatement: accessStatement,
LabeledStatement: {
enter(path) {
// Apply an extra try-catch around the label of a loop, since we
// ignore the loop itself if it has a label.
if (babelTypes.isLoop(path.node.body)) {
thisMutator.callWithProb(path, replaceAndSkip);
}
},
exit(path) {
// Apply nested wrapping (is only executed if not skipped above).
if (babelTypes.isLoop(path.node.body)) {
replaceAndSkip(path);
}
},
},
// This covers {While|DoWhile|ForIn|ForOf|For}Statement.
Loop: accessStatement,
NewExpression: {
enter(path) {
thisMutator.callWithProb(path, replaceNewExpression);
},
exit(path) {
// Apply nested wrapping (is only executed if not skipped above).
replaceNewExpression(path);
}
},
SwitchStatement: accessStatement,
VariableDeclaration: {
enter(path) {
if (path.node.kind !== 'var' || babelTypes.isLoop(path.parent))
return;
thisMutator.callWithProb(path, replaceAndSkip);
},
exit(path) {
if (path.node.kind !== 'var' || babelTypes.isLoop(path.parent))
return;
// Apply nested wrapping (is only executed if not skipped above).
replaceAndSkip(path);
}
},
WithStatement: accessStatement,
};
}
}
module.exports = {
AddTryCatchMutator: AddTryCatchMutator,
}
@@ -0,0 +1,73 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Variables mutator.
*/
'use strict';
const babelTypes = require('@babel/types');
const common = require('./common.js');
const random = require('../random.js');
const mutator = require('./mutator.js');
function _isInFunctionParam(path) {
const child = path.find(p => p.parent && babelTypes.isFunction(p.parent));
return child && child.parentKey === 'params';
}
class VariableMutator extends mutator.Mutator {
constructor(settings) {
super();
this.settings = settings;
}
get visitor() {
const thisMutator = this;
return {
Identifier(path) {
if (!random.choose(thisMutator.settings.MUTATE_VARIABLES)) {
return;
}
if (!common.isVariableIdentifier(path.node.name)) {
return;
}
// Don't mutate variables that are being declared.
if (babelTypes.isVariableDeclarator(path.parent)) {
return;
}
// Don't mutate function params.
if (_isInFunctionParam(path)) {
return;
}
if (common.isInForLoopCondition(path) ||
common.isInWhileLoop(path)) {
return;
}
const randVar = common.randomVariable(path);
if (!randVar) {
return;
}
const newName = randVar.name;
thisMutator.annotate(
path.node,
`Replaced ${path.node.name} with ${newName}`);
path.node.name = newName;
}
};
}
}
module.exports = {
VariableMutator: VariableMutator,
};
@@ -0,0 +1,154 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Variables mutator.
*/
'use strict';
const babelTemplate = require('@babel/template').default;
const babelTypes = require('@babel/types');
const common = require('./common.js');
const random = require('../random.js');
const mutator = require('./mutator.js');
const MAX_MUTATION_RECURSION_DEPTH = 5;
class VariableOrObjectMutator extends mutator.Mutator {
constructor(settings) {
super();
this.settings = settings;
}
_randomVariableOrObject(path) {
const randomVar = common.randomVariable(path);
if (random.choose(0.05) || !randomVar) {
return common.randomObject();
}
return randomVar;
}
_randomVariableOrObjectMutations(path, recurseDepth=0) {
if (recurseDepth >= MAX_MUTATION_RECURSION_DEPTH) {
return new Array();
}
const probability = random.random();
if (probability < 0.3) {
const first = this._randomVariableOrObjectMutations(path, recurseDepth + 1);
const second = this._randomVariableOrObjectMutations(
path, recurseDepth + 1);
return first.concat(second);
}
const randVarOrObject = this._randomVariableOrObject(path);
const randProperty = common.randomProperty(randVarOrObject);
let newRandVarOrObject = randVarOrObject;
if (random.choose(0.2)) {
newRandVarOrObject = this._randomVariableOrObject(path);
}
const mutations = new Array();
if (probability < 0.4) {
const template = babelTemplate(
'delete IDENTIFIER[PROPERTY], __callGC()')
mutations.push(template({
IDENTIFIER: randVarOrObject,
PROPERTY: randProperty
}));
} else if (probability < 0.5) {
const template = babelTemplate(
'IDENTIFIER[PROPERTY], __callGC()')
mutations.push(template({
IDENTIFIER: randVarOrObject,
PROPERTY: randProperty
}));
} else if (probability < 0.6) {
const template = babelTemplate(
'IDENTIFIER[PROPERTY] = RANDOM, __callGC()')
mutations.push(template({
IDENTIFIER: randVarOrObject,
PROPERTY: randProperty,
RANDOM: common.randomValue(path),
}));
} else if (probability < 0.7) {
mutations.push(
babelTypes.expressionStatement(
common.callRandomFunction(path, randVarOrObject)));
} else if (probability < 0.8) {
const template = babelTemplate(
'VAR = IDENTIFIER, __callGC()')
var randomVar = common.randomVariable(path);
if (!randomVar) {
return mutations;
}
mutations.push(template({
VAR: randomVar,
IDENTIFIER: randVarOrObject,
}));
} else if (probability < 0.9) {
const template = babelTemplate(
'if (IDENTIFIER != null && typeof(IDENTIFIER) == "object") ' +
'Object.defineProperty(IDENTIFIER, PROPERTY, {value: VALUE})')
mutations.push(template({
IDENTIFIER: newRandVarOrObject,
PROPERTY: randProperty,
VALUE: common.randomValue(path),
}));
} else {
const template = babelTemplate(
'if (IDENTIFIER != null && typeof(IDENTIFIER) == "object") ' +
'Object.defineProperty(IDENTIFIER, PROPERTY, {' +
'get: function() { GETTER_MUTATION ; return VALUE; },' +
'set: function(value) { SETTER_MUTATION; }' +
'})');
mutations.push(template({
IDENTIFIER: newRandVarOrObject,
PROPERTY: randProperty,
GETTER_MUTATION: this._randomVariableOrObjectMutations(
path, recurseDepth + 1),
SETTER_MUTATION: this._randomVariableOrObjectMutations(
path, recurseDepth + 1),
VALUE: common.randomValue(path),
}));
}
return mutations;
}
get visitor() {
const settings = this.settings;
const thisMutator = this;
return {
ExpressionStatement(path) {
if (!random.choose(settings.ADD_VAR_OR_OBJ_MUTATIONS)) {
return;
}
const mutations = thisMutator._randomVariableOrObjectMutations(path);
thisMutator.annotate(mutations[0], 'Random mutation');
if (random.choose(0.5)) {
thisMutator.insertBeforeSkip(path, mutations);
} else {
thisMutator.insertAfterSkip(path, mutations);
}
path.skip();
}
};
}
}
module.exports = {
VariableOrObjectMutator: VariableOrObjectMutator,
};
@@ -0,0 +1,34 @@
{
"name": "ochang_js_fuzzer",
"version": "1.0.0",
"description": "",
"main": "run.js",
"scripts": {
"test": "echo 'no test'",
"build": "echo 'no build'"
},
"bin": "run.js",
"author": "ochang@google.com",
"license": "ISC",
"dependencies": {
"@babel/generator": "^7.1.3",
"@babel/template": "^7.1.2",
"@babel/traverse": "^7.1.4",
"@babel/types": "^7.1.3",
"@babel/parser": "^7.1.3",
"commander": "^2.11.0",
"globals": "^10.1.0",
"tempfile": "^3.0.0",
"tempy": "^0.5.0"
},
"devDependencies": {
"eslint": "^6.8.0",
"mocha": "^3.5.3",
"pkg": "^4.3.4",
"prettier": "2.0.5",
"sinon": "^4.0.0"
},
"pkg": {
"assets": "resources/**/*"
}
}
+32
View File
@@ -0,0 +1,32 @@
#!/bin/bash
# Copyright 2020 the V8 project authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
OS="linux"
OS_LABEL="Linux"
SUFFIX=""
if [[ -n "$1" && $1 == "win" ]]; then
OS="win"
OS_LABEL="Windows"
SUFFIX=".exe"
elif [[ -n "$1" && $1 == "macos" ]]; then
OS="macos"
OS_LABEL="MacOS"
fi
echo "Building and packaging for $OS_LABEL..."
(set -x; $DIR/node_modules/.bin/pkg -t node10-$OS-x64 $DIR)
rm -rf $DIR/output > /dev/null 2>&1 || true
rm $DIR/output.zip > /dev/null 2>&1 || true
mkdir $DIR/output
cd $DIR/output
ln -s ../db db
ln -s ../ochang_js_fuzzer$SUFFIX run$SUFFIX
ln -s ../foozzie_launcher.py foozzie_launcher.py
echo "Creating $DIR/output.zip"
(set -x; zip -r $DIR/output.zip * > /dev/null)
@@ -0,0 +1,113 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Random helpers.
*/
'use strict';
const assert = require('assert');
function randInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function choose(probability) {
return Math.random() < probability;
}
function random() {
return Math.random();
}
function uniform(min, max) {
return Math.random() * (max - min) + min;
}
function sample(iterable, count) {
const result = new Array(count);
let index = 0;
for (const item of iterable) {
if (index < count) {
result[index] = item;
} else {
const randIndex = randInt(0, index);
if (randIndex < count) {
result[randIndex] = item;
}
}
index++;
}
if (index < count) {
// Not enough items.
result.length = index;
}
return result;
}
function swap(array, p1, p2) {
[array[p1], array[p2]] = [array[p2], array[p1]];
}
/**
* Returns "count" elements, randomly selected from "highProbArray" and
* "lowProbArray". Elements from highProbArray have a "factor" times
* higher chance to be chosen. As a side effect, this swaps the chosen
* elements to the end of the respective input arrays. The complexity is
* O(count).
*/
function twoBucketSample(lowProbArray, highProbArray, factor, count) {
// Track number of available elements for choosing.
let low = lowProbArray.length;
let high = highProbArray.length;
assert(low + high >= count);
const result = [];
for (let i = 0; i < count; i++) {
// Map a random number to the summarized indices of both arrays. Give
// highProbArray elements a "factor" times higher probability.
const p = random();
const index = Math.floor(p * (high * factor + low));
if (index < low) {
// If the index is in the low part, draw the element and discard it.
result.push(lowProbArray[index]);
swap(lowProbArray, index, --low);
} else {
// Same as above but for a highProbArray element. The index is first
// mapped back to the array's range.
const highIndex = Math.floor((index - low) / factor);
result.push(highProbArray[highIndex]);
swap(highProbArray, highIndex, --high);
}
}
return result;
}
function single(array) {
return array[randInt(0, array.length - 1)];
}
function shuffle(array) {
for (let i = 0; i < array.length - 1; i++) {
const j = randInt(i, array.length - 1);
swap(array, i, j);
}
return array;
}
module.exports = {
choose: choose,
randInt: randInt,
random: random,
sample: sample,
shuffle: shuffle,
single: single,
twoBucketSample: twoBucketSample,
uniform: uniform,
}
@@ -0,0 +1,17 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Adjust chakra behavior for differential fuzzing.
this.WScript = new Proxy({}, {
get(target, name) {
switch (name) {
case 'Echo':
return __prettyPrintExtra;
default:
return {};
}
}
});
@@ -0,0 +1,11 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
function debug(msg) {
__prettyPrintExtra(msg);
}
function shouldBe(_a) {
__prettyPrintExtra((typeof _a == "function" ? _a() : eval(_a)));
}
@@ -0,0 +1,122 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Helpers for printing in correctness fuzzing.
// Global helper functions for printing.
var __prettyPrint;
var __prettyPrintExtra;
// Track caught exceptions.
var __caught = 0;
// Track a hash of all printed values - printing is cut off after a
// certain size.
var __hash = 0;
(function() {
const charCodeAt = String.prototype.charCodeAt;
const join = Array.prototype.join;
const map = Array.prototype.map;
const substring = String.prototype.substring;
const toString = Object.prototype.toString;
// Same as in mjsunit.js.
const classOf = function(object) {
// Argument must not be null or undefined.
const string = toString.call(object);
// String has format [object <ClassName>].
return substring.call(string, 8, string.length - 1);
};
// For standard cases use original prettyPrinted from mjsunit.
const origPrettyPrinted = prettyPrinted;
// Override prettyPrinted with a version that also recusively prints objects
// and arrays with a depth of 4. We don't track circles, but we'd cut off
// after a depth of 4 if there are any.
prettyPrinted = function prettyPrinted(value, depth=4) {
if (depth <= 0) {
return "...";
}
switch (typeof value) {
case "object":
if (value === null) return "null";
switch (classOf(value)) {
case "Array":
return prettyPrintedArray(value, depth);
case "Object":
return prettyPrintedObject(value, depth);
}
}
// Fall through to original version for all other types.
return origPrettyPrinted(value);
}
// Helper for pretty array with depth.
function prettyPrintedArray(array, depth) {
const result = map.call(array, (value, index, array) => {
if (value === undefined && !(index in array)) return "";
return prettyPrinted(value, depth - 1);
});
return `[${join.call(result, ", ")}]`;
}
// Helper for pretty objects with depth.
function prettyPrintedObject(object, depth) {
const keys = Object.keys(object);
const prettyValues = map.call(keys, (key) => {
return `${key}: ${prettyPrinted(object[key], depth - 1)}`;
});
const content = join.call(prettyValues, ", ");
return `${object.constructor.name || "Object"}{${content}}`;
}
// Helper for calculating a hash code of a string.
function hashCode(str) {
let hash = 0;
if (str.length == 0) {
return hash;
}
for (let i = 0; i < str.length; i++) {
const char = charCodeAt.call(str, i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash;
}
return hash;
}
// Upper limit for calling extra printing. When reached, hashes of
// strings are tracked and printed instead.
let maxExtraPrinting = 100;
// Helper for pretty printing.
__prettyPrint = function(value, extra=false) {
let str = prettyPrinted(value);
// Change __hash with the contents of the full string to
// keep track of differences also when we don't print.
const hash = hashCode(str);
__hash = hashCode(hash + __hash.toString());
if (extra && maxExtraPrinting-- <= 0) {
return;
}
// Cut off long strings to prevent overloading I/O. We still track
// the hash of the full string.
if (str.length > 64) {
const head = substring.call(str, 0, 54);
const tail = substring.call(str, str.length - 10, str.length - 1);
str = `${head}[...]${tail}`;
}
print(str);
};
__prettyPrintExtra = function (value) {
__prettyPrint(value, true);
}
})();
@@ -0,0 +1,8 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Substitute for mjsunit. We reuse prettyPrinted from mjsunit, but only if
// it is loaded. If not, we use this substitute instead.
let prettyPrinted = value => value;
@@ -0,0 +1,12 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Don't breach stack limit in differential fuzzing as it leads to
// early bailout.
runNearStackLimit = function(f) {
try {
f();
} catch (e) {}
};
@@ -0,0 +1,29 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Adjust mjsunit behavior for differential fuzzing.
// We're not interested in stack traces.
MjsUnitAssertionError = () => {};
// Do more printing in assertions for more correctness coverage.
failWithMessage = message => { __prettyPrint(message); };
assertSame = (expected, found, name_opt) => { __prettyPrint(found); };
assertNotSame = (expected, found, name_opt) => { __prettyPrint(found); };
assertEquals = (expected, found, name_opt) => { __prettyPrint(found); };
assertNotEquals = (expected, found, name_opt) => { __prettyPrint(found); };
assertNull = (value, name_opt) => { __prettyPrint(value); };
assertNotNull = (value, name_opt) => { __prettyPrint(value); };
// Suppress optimization status as it leads to false positives.
assertUnoptimized = () => {};
assertOptimized = () => {};
isNeverOptimize = () => {};
isAlwaysOptimize = () => {};
isInterpreted = () => {};
isBaseline = () => {};
isUnoptimized = () => {};
isOptimized = () => {};
isTurboFanned = () => {};
@@ -0,0 +1,116 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Slightly modified variants from http://code.fitness/post/2016/01/javascript-enumerate-methods.html.
function __isPropertyOfType(obj, name, type) {
let desc;
try {
desc = Object.getOwnPropertyDescriptor(obj, name);
} catch(e) {
return false;
}
if (!desc)
return false;
return typeof type === 'undefined' || typeof desc.value === type;
}
function __getProperties(obj, type) {
if (typeof obj === "undefined" || obj === null)
return [];
let properties = [];
for (let name of Object.getOwnPropertyNames(obj)) {
if (__isPropertyOfType(obj, name, type))
properties.push(name);
}
let proto = Object.getPrototypeOf(obj);
while (proto && proto != Object.prototype) {
Object.getOwnPropertyNames(proto)
.forEach (name => {
if (name !== 'constructor') {
if (__isPropertyOfType(proto, name, type))
properties.push(name);
}
});
proto = Object.getPrototypeOf(proto);
}
return properties;
}
function* __getObjects(root = this, level = 0) {
if (level > 4)
return;
let obj_names = __getProperties(root, 'object');
for (let obj_name of obj_names) {
let obj = root[obj_name];
if (obj === root)
continue;
yield obj;
yield* __getObjects(obj, level + 1);
}
}
function __getRandomObject(seed) {
let objects = [];
for (let obj of __getObjects()) {
objects.push(obj);
}
return objects[seed % objects.length];
}
function __getRandomProperty(obj, seed) {
let properties = __getProperties(obj);
if (!properties.length)
return undefined;
return properties[seed % properties.length];
}
function __callRandomFunction(obj, seed, ...args)
{
let functions = __getProperties(obj, 'function');
if (!functions.length)
return;
let random_function = functions[seed % functions.length];
try {
obj[random_function](...args);
} catch(e) { }
}
function runNearStackLimit(f) {
function t() {
try {
return t();
} catch (e) {
return f();
}
};
try {
return t();
} catch (e) {}
}
// Limit number of times we cause major GCs in tests to reduce hangs
// when called within larger loops.
let __callGC;
(function() {
let countGC = 0;
__callGC = function() {
if (countGC++ < 50) {
gc();
}
};
})();
// Neuter common test functions.
try { this.failWithMessage = nop; } catch(e) { }
try { this.triggerAssertFalse = nop; } catch(e) { }
try { this.quit = nop; } catch(e) { }
@@ -0,0 +1,41 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Minimally stub out methods from JSTest's standalone-pre.js.
function description(msg) {}
function debug(msg) {}
function shouldBe(_a) {
print((typeof _a == "function" ? _a() : eval(_a)));
}
function shouldBeTrue(_a) { shouldBe(_a); }
function shouldBeFalse(_a) { shouldBe(_a); }
function shouldBeNaN(_a) { shouldBe(_a); }
function shouldBeNull(_a) { shouldBe(_a); }
function shouldNotThrow(_a) { shouldBe(_a); }
function shouldThrow(_a) { shouldBe(_a); }
function noInline() {}
function finishJSTest() {}
// Stub out $vm.
try {
$vm;
} catch(e) {
const handler = {
get: function(x, prop) {
if (prop == Symbol.toPrimitive) {
return function() { return undefined; };
}
return dummy;
},
};
const dummy = new Proxy(function() { return dummy; }, handler);
this.$vm = dummy;
}
// Other functions.
function ensureArrayStorage() {}
function transferArrayBuffer() {}
@@ -0,0 +1,36 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Helper neuter function.
function nop() { return false; }
// Stubs for non-standard functions.
try { gc; } catch(e) {
this.gc = function () {
for (let i = 0; i < 10000; i++) {
let s = new String("AAAA" + Math.random());
}
}
}
try { uneval; } catch(e) { this.uneval = this.nop; }
try {
// For Chakra tests.
WScript;
} catch(e) {
this.WScript = new Proxy({}, {
get(target, name) {
switch (name) {
case 'Echo':
return print;
default:
return {};
}
}
});
}
try { this.alert = console.log; } catch(e) { }
try { this.print = console.log; } catch(e) { }
+241
View File
@@ -0,0 +1,241 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Description of this file.
*/
'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const program = require('commander');
const corpus = require('./corpus.js');
const differentialScriptMutator = require('./differential_script_mutator.js');
const random = require('./random.js');
const scriptMutator = require('./script_mutator.js');
const sourceHelpers = require('./source_helpers.js');
// Maximum number of test inputs to use for one fuzz test.
const MAX_TEST_INPUTS_PER_TEST = 10;
// Base implementations for default or differential fuzzing.
const SCRIPT_MUTATORS = {
default: scriptMutator.ScriptMutator,
foozzie: differentialScriptMutator.DifferentialScriptMutator,
};
function getRandomInputs(primaryCorpus, secondaryCorpora, count) {
count = random.randInt(2, count);
// Choose 40%-80% of inputs from primary corpus.
const primaryCount = Math.floor(random.uniform(0.4, 0.8) * count);
count -= primaryCount;
let inputs = primaryCorpus.getRandomTestcases(primaryCount);
// Split remainder equally between the secondary corpora.
const secondaryCount = Math.floor(count / secondaryCorpora.length);
for (let i = 0; i < secondaryCorpora.length; i++) {
let currentCount = secondaryCount;
if (i == secondaryCorpora.length - 1) {
// Last one takes the remainder.
currentCount = count;
}
count -= currentCount;
if (currentCount) {
inputs = inputs.concat(
secondaryCorpora[i].getRandomTestcases(currentCount));
}
}
return random.shuffle(inputs);
}
function collect(value, total) {
total.push(value);
return total;
}
function overrideSettings(settings, settingOverrides) {
for (const setting of settingOverrides) {
const parts = setting.split('=');
settings[parts[0]] = parseFloat(parts[1]);
}
}
function* randomInputGen(engine) {
const inputDir = path.resolve(program.input_dir);
const v8Corpus = new corpus.Corpus(inputDir, 'v8');
const chakraCorpus = new corpus.Corpus(inputDir, 'chakra');
const spiderMonkeyCorpus = new corpus.Corpus(inputDir, 'spidermonkey');
const jscCorpus = new corpus.Corpus(inputDir, 'WebKit/JSTests');
const crashTestsCorpus = new corpus.Corpus(inputDir, 'CrashTests');
for (let i = 0; i < program.no_of_files; i++) {
let inputs;
if (engine === 'V8') {
inputs = getRandomInputs(
v8Corpus,
random.shuffle([chakraCorpus, spiderMonkeyCorpus, jscCorpus,
crashTestsCorpus, v8Corpus]),
MAX_TEST_INPUTS_PER_TEST);
} else if (engine == 'chakra') {
inputs = getRandomInputs(
chakraCorpus,
random.shuffle([v8Corpus, spiderMonkeyCorpus, jscCorpus,
crashTestsCorpus]),
MAX_TEST_INPUTS_PER_TEST);
} else if (engine == 'spidermonkey') {
inputs = getRandomInputs(
spiderMonkeyCorpus,
random.shuffle([v8Corpus, chakraCorpus, jscCorpus,
crashTestsCorpus]),
MAX_TEST_INPUTS_PER_TEST);
} else {
inputs = getRandomInputs(
jscCorpus,
random.shuffle([chakraCorpus, spiderMonkeyCorpus, v8Corpus,
crashTestsCorpus]),
MAX_TEST_INPUTS_PER_TEST);
}
if (inputs.length > 0) {
yield inputs;
}
}
}
function* corpusInputGen() {
const inputCorpus = new corpus.Corpus(
path.resolve(program.input_dir),
program.mutate_corpus,
program.extra_strict);
for (const input of inputCorpus.getAllTestcases()) {
yield [input];
}
}
function* enumerate(iterable) {
let i = 0;
for (const value of iterable) {
yield [i, value];
i++;
}
}
function main() {
Error.stackTraceLimit = Infinity;
program
.version('0.0.1')
.option('-i, --input_dir <path>', 'Input directory.')
.option('-o, --output_dir <path>', 'Output directory.')
.option('-n, --no_of_files <n>', 'Output directory.', parseInt)
.option('-c, --mutate_corpus <name>', 'Mutate single files in a corpus.')
.option('-e, --extra_strict', 'Additionally parse files in strict mode.')
.option('-m, --mutate <path>', 'Mutate a file and output results.')
.option('-s, --setting [setting]', 'Settings overrides.', collect, [])
.option('-v, --verbose', 'More verbose printing.')
.option('-z, --zero_settings', 'Zero all settings.')
.parse(process.argv);
const settings = scriptMutator.defaultSettings();
if (program.zero_settings) {
for (const key of Object.keys(settings)) {
settings[key] = 0.0;
}
}
if (program.setting.length > 0) {
overrideSettings(settings, program.setting);
}
let app_name = process.env.APP_NAME;
if (app_name && app_name.endsWith('.exe')) {
app_name = app_name.substr(0, app_name.length - 4);
}
if (app_name === 'd8' ||
app_name === 'v8_simple_inspector_fuzzer' ||
app_name === 'v8_foozzie.py') {
// V8 supports running the raw d8 executable, the inspector fuzzer or
// the differential fuzzing harness 'foozzie'.
settings.engine = 'V8';
} else if (app_name === 'ch') {
settings.engine = 'chakra';
} else if (app_name === 'js') {
settings.engine = 'spidermonkey';
} else if (app_name === 'jsc') {
settings.engine = 'jsc';
} else {
console.log('ERROR: Invalid APP_NAME');
process.exit(1);
}
const mode = process.env.FUZZ_MODE || 'default';
assert(mode in SCRIPT_MUTATORS, `Unknown mode ${mode}`);
const mutator = new SCRIPT_MUTATORS[mode](settings);
if (program.mutate) {
const absPath = path.resolve(program.mutate);
const baseDir = path.dirname(absPath);
const fileName = path.basename(absPath);
const input = sourceHelpers.loadSource(
baseDir, fileName, program.extra_strict);
const mutated = mutator.mutateMultiple([input]);
console.log(mutated.code);
return;
}
let inputGen;
if (program.mutate_corpus) {
inputGen = corpusInputGen();
} else {
inputGen = randomInputGen(settings.engine);
}
for (const [i, inputs] of enumerate(inputGen)) {
const outputPath = path.join(program.output_dir, 'fuzz-' + i + '.js');
const start = Date.now();
const paths = inputs.map(input => input.relPath);
try {
const mutated = mutator.mutateMultiple(inputs);
fs.writeFileSync(outputPath, mutated.code);
if (settings.engine === 'V8' && mutated.flags && mutated.flags.length > 0) {
const flagsPath = path.join(program.output_dir, 'flags-' + i + '.js');
fs.writeFileSync(flagsPath, mutated.flags.join(' '));
}
} catch (e) {
if (e.message.startsWith('ENOSPC')) {
console.log('ERROR: No space left. Bailing out...');
console.log(e);
return;
}
console.log(`ERROR: Exception during mutate: ${paths}`);
console.log(e);
continue;
} finally {
if (program.verbose) {
const duration = Date.now() - start;
console.log(`Mutating ${paths} took ${duration} ms.`);
}
}
if ((i + 1) % 10 == 0) {
console.log('Up to ', i + 1);
}
}
}
main();
@@ -0,0 +1,253 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Script mutator.
*/
'use strict';
const fs = require('fs');
const path = require('path');
const common = require('./mutators/common.js');
const db = require('./db.js');
const random = require('./random.js');
const sourceHelpers = require('./source_helpers.js');
const { AddTryCatchMutator } = require('./mutators/try_catch.js');
const { ArrayMutator } = require('./mutators/array_mutator.js');
const { CrossOverMutator } = require('./mutators/crossover_mutator.js');
const { ExpressionMutator } = require('./mutators/expression_mutator.js');
const { FunctionCallMutator } = require('./mutators/function_call_mutator.js');
const { IdentifierNormalizer } = require('./mutators/normalizer.js');
const { NumberMutator } = require('./mutators/number_mutator.js');
const { ObjectMutator } = require('./mutators/object_mutator.js');
const { VariableMutator } = require('./mutators/variable_mutator.js');
const { VariableOrObjectMutator } = require('./mutators/variable_or_object_mutation.js');
const MAX_EXTRA_MUTATIONS = 5;
function defaultSettings() {
return {
ADD_VAR_OR_OBJ_MUTATIONS: 0.1,
DIFF_FUZZ_EXTRA_PRINT: 0.1,
DIFF_FUZZ_TRACK_CAUGHT: 0.4,
MUTATE_ARRAYS: 0.1,
MUTATE_CROSSOVER_INSERT: 0.05,
MUTATE_EXPRESSIONS: 0.1,
MUTATE_FUNCTION_CALLS: 0.1,
MUTATE_NUMBERS: 0.05,
MUTATE_OBJECTS: 0.1,
MUTATE_VARIABLES: 0.075,
SCRIPT_MUTATOR_EXTRA_MUTATIONS: 0.2,
SCRIPT_MUTATOR_SHUFFLE: 0.2,
};
}
class Result {
constructor(code, flags) {
this.code = code;
this.flags = flags;
}
}
class ScriptMutator {
constructor(settings, db_path=undefined) {
// Use process.cwd() to bypass pkg's snapshot filesystem.
this.mutateDb = new db.MutateDb(db_path || path.join(process.cwd(), 'db'));
this.mutators = [
new ArrayMutator(settings),
new ObjectMutator(settings),
new VariableMutator(settings),
new NumberMutator(settings),
new CrossOverMutator(settings, this.mutateDb),
new ExpressionMutator(settings),
new FunctionCallMutator(settings),
new VariableOrObjectMutator(settings),
];
this.trycatch = new AddTryCatchMutator(settings);
this.settings = settings;
}
_addMjsunitIfNeeded(dependencies, input) {
if (dependencies.has('mjsunit')) {
return;
}
if (!input.absPath.includes('mjsunit')) {
return;
}
// Find mjsunit.js
let mjsunitPath = input.absPath;
while (path.dirname(mjsunitPath) != mjsunitPath &&
path.basename(mjsunitPath) != 'mjsunit') {
mjsunitPath = path.dirname(mjsunitPath);
}
if (path.basename(mjsunitPath) == 'mjsunit') {
mjsunitPath = path.join(mjsunitPath, 'mjsunit.js');
dependencies.set('mjsunit', sourceHelpers.loadDependencyAbs(
input.baseDir, mjsunitPath));
return;
}
console.log('ERROR: Failed to find mjsunit.js');
}
_addSpiderMonkeyShellIfNeeded(dependencies, input) {
// Find shell.js files
const shellJsPaths = new Array();
let currentDir = path.dirname(input.absPath);
while (path.dirname(currentDir) != currentDir) {
const shellJsPath = path.join(currentDir, 'shell.js');
if (fs.existsSync(shellJsPath)) {
shellJsPaths.push(shellJsPath);
}
if (currentDir == 'spidermonkey') {
break;
}
currentDir = path.dirname(currentDir);
}
// Add shell.js dependencies in reverse to add ones that are higher up in
// the directory tree first.
for (let i = shellJsPaths.length - 1; i >= 0; i--) {
if (!dependencies.has(shellJsPaths[i])) {
const dependency = sourceHelpers.loadDependencyAbs(
input.baseDir, shellJsPaths[i]);
dependencies.set(shellJsPaths[i], dependency);
}
}
}
_addJSTestStubsIfNeeded(dependencies, input) {
if (dependencies.has('jstest_stubs') ||
!input.absPath.includes('JSTests')) {
return;
}
dependencies.set(
'jstest_stubs', sourceHelpers.loadResource('jstest_stubs.js'));
}
mutate(source) {
let mutators = this.mutators.slice();
let annotations = [];
if (random.choose(this.settings.SCRIPT_MUTATOR_SHUFFLE)){
annotations.push(' Script mutator: using shuffled mutators');
random.shuffle(mutators);
}
if (random.choose(this.settings.SCRIPT_MUTATOR_EXTRA_MUTATIONS)){
for (let i = random.randInt(1, MAX_EXTRA_MUTATIONS); i > 0; i--) {
let mutator = random.single(this.mutators);
mutators.push(mutator);
annotations.push(` Script mutator: extra ${mutator.constructor.name}`);
}
}
// Try-catch wrapping should always be the last mutation.
mutators.push(this.trycatch);
for (const mutator of mutators) {
mutator.mutate(source);
}
for (const annotation of annotations.reverse()) {
sourceHelpers.annotateWithComment(source.ast, annotation);
}
}
// Returns parsed dependencies for inputs.
resolveInputDependencies(inputs) {
const dependencies = new Map();
// Resolve test harness files.
inputs.forEach(input => {
try {
// TODO(machenbach): Some harness files contain load expressions
// that are not recursively resolved. We already remove them, but we
// also need to load the dependencies they point to.
this._addJSTestStubsIfNeeded(dependencies, input);
this._addMjsunitIfNeeded(dependencies, input)
this._addSpiderMonkeyShellIfNeeded(dependencies, input);
} catch (e) {
console.log(
'ERROR: Failed to resolve test harness for', input.relPath);
throw e;
}
});
// Resolve dependencies loaded within the input files.
inputs.forEach(input => {
try {
input.loadDependencies(dependencies);
} catch (e) {
console.log(
'ERROR: Failed to resolve dependencies for', input.relPath);
throw e;
}
});
// Map.values() returns values in insertion order.
return Array.from(dependencies.values());
}
// Combines input dependencies with fuzzer resources.
resolveDependencies(inputs) {
const dependencies = this.resolveInputDependencies(inputs);
// Add stubs for non-standard functions in the beginning.
dependencies.unshift(sourceHelpers.loadResource('stubs.js'));
// Add our fuzzing support helpers. This also overrides some common test
// functions from earlier dependencies that cause early bailouts.
dependencies.push(sourceHelpers.loadResource('fuzz_library.js'));
return dependencies;
}
// Normalizes, combines and mutates multiple inputs.
mutateInputs(inputs) {
const normalizerMutator = new IdentifierNormalizer();
for (const [index, input] of inputs.entries()) {
try {
normalizerMutator.mutate(input);
} catch (e) {
console.log('ERROR: Failed to normalize ', input.relPath);
throw e;
}
common.setSourceLoc(input, index, inputs.length);
}
// Combine ASTs into one. This is so that mutations have more context to
// cross over content between ASTs (e.g. variables).
const combinedSource = common.concatPrograms(inputs);
this.mutate(combinedSource);
return combinedSource;
}
mutateMultiple(inputs) {
// High level operation:
// 1) Compute dependencies from inputs.
// 2) Normalize, combine and mutate inputs.
// 3) Generate code with dependency code prepended.
const dependencies = this.resolveDependencies(inputs);
const combinedSource = this.mutateInputs(inputs);
const code = sourceHelpers.generateCode(combinedSource, dependencies);
const flags = common.concatFlags(dependencies.concat([combinedSource]));
return new Result(code, flags);
}
}
module.exports = {
defaultSettings: defaultSettings,
ScriptMutator: ScriptMutator,
};
@@ -0,0 +1,466 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Source loader.
*/
const fs = require('fs');
const fsPath = require('path');
const { EOL } = require('os');
const babelGenerator = require('@babel/generator').default;
const babelTraverse = require('@babel/traverse').default;
const babelTypes = require('@babel/types');
const babylon = require('@babel/parser');
const exceptions = require('./exceptions.js');
const SCRIPT = Symbol('SCRIPT');
const MODULE = Symbol('MODULE');
const V8_BUILTIN_PREFIX = '__V8Builtin';
const V8_REPLACE_BUILTIN_REGEXP = new RegExp(
V8_BUILTIN_PREFIX + '(\\w+)\\(', 'g');
const BABYLON_OPTIONS = {
sourceType: 'script',
allowReturnOutsideFunction: true,
tokens: false,
ranges: false,
plugins: [
'asyncGenerators',
'bigInt',
'classPrivateMethods',
'classPrivateProperties',
'classProperties',
'doExpressions',
'exportDefaultFrom',
'nullishCoalescingOperator',
'numericSeparator',
'objectRestSpread',
'optionalCatchBinding',
'optionalChaining',
],
}
const BABYLON_REPLACE_VAR_OPTIONS = Object.assign({}, BABYLON_OPTIONS);
BABYLON_REPLACE_VAR_OPTIONS['placeholderPattern'] = /^VAR_[0-9]+$/;
function _isV8OrSpiderMonkeyLoad(path) {
// 'load' and 'loadRelativeToScript' used by V8 and SpiderMonkey.
return (babelTypes.isIdentifier(path.node.callee) &&
(path.node.callee.name == 'load' ||
path.node.callee.name == 'loadRelativeToScript') &&
path.node.arguments.length == 1 &&
babelTypes.isStringLiteral(path.node.arguments[0]));
}
function _isChakraLoad(path) {
// 'WScript.LoadScriptFile' used by Chakra.
// TODO(ochang): The optional second argument can change semantics ("self",
// "samethread", "crossthread" etc).
// Investigate whether if it still makes sense to include them.
return (babelTypes.isMemberExpression(path.node.callee) &&
babelTypes.isIdentifier(path.node.callee.property) &&
path.node.callee.property.name == 'LoadScriptFile' &&
path.node.arguments.length >= 1 &&
babelTypes.isStringLiteral(path.node.arguments[0]));
}
function _findPath(path, caseSensitive=true) {
// If the path exists, return the path. Otherwise return null. Used to handle
// case insensitive matches for Chakra tests.
if (caseSensitive) {
return fs.existsSync(path) ? path : null;
}
path = fsPath.normalize(fsPath.resolve(path));
const pathComponents = path.split(fsPath.sep);
let realPath = fsPath.resolve(fsPath.sep);
for (let i = 1; i < pathComponents.length; i++) {
// For each path component, do a directory listing to see if there is a case
// insensitive match.
const curListing = fs.readdirSync(realPath);
let realComponent = null;
for (const component of curListing) {
if (i < pathComponents.length - 1 &&
!fs.statSync(fsPath.join(realPath, component)).isDirectory()) {
continue;
}
if (component.toLowerCase() == pathComponents[i].toLowerCase()) {
realComponent = component;
break;
}
}
if (!realComponent) {
return null;
}
realPath = fsPath.join(realPath, realComponent);
}
return realPath;
}
function _findDependentCodePath(filePath, baseDirectory, caseSensitive=true) {
const fullPath = fsPath.join(baseDirectory, filePath);
const realPath = _findPath(fullPath, caseSensitive)
if (realPath) {
// Check base directory of current file.
return realPath;
}
while (fsPath.dirname(baseDirectory) != baseDirectory) {
// Walk up the directory tree.
const testPath = fsPath.join(baseDirectory, filePath);
const realPath = _findPath(testPath, caseSensitive)
if (realPath) {
return realPath;
}
baseDirectory = fsPath.dirname(baseDirectory);
}
return null;
}
/**
* Removes V8/Spidermonkey/Chakra load expressions in a source AST and returns
* their string values in an array.
*
* @param {string} originalFilePath Absolute path to file.
* @param {AST} ast Babel AST of the sources.
*/
function resolveLoads(originalFilePath, ast) {
const dependencies = [];
babelTraverse(ast, {
CallExpression(path) {
const isV8OrSpiderMonkeyLoad = _isV8OrSpiderMonkeyLoad(path);
const isChakraLoad = _isChakraLoad(path);
if (!isV8OrSpiderMonkeyLoad && !isChakraLoad) {
return;
}
let loadValue = path.node.arguments[0].extra.rawValue;
// Normalize Windows path separators.
loadValue = loadValue.replace(/\\/g, fsPath.sep);
// Remove load call.
path.remove();
const resolvedPath = _findDependentCodePath(
loadValue, fsPath.dirname(originalFilePath), !isChakraLoad);
if (!resolvedPath) {
console.log('ERROR: Could not find dependent path for', loadValue);
return;
}
if (exceptions.isTestSkippedAbs(resolvedPath)) {
// Dependency is skipped.
return;
}
// Add the dependency path.
dependencies.push(resolvedPath);
}
});
return dependencies;
}
function isStrictDirective(directive) {
return (directive.value &&
babelTypes.isDirectiveLiteral(directive.value) &&
directive.value.value === 'use strict');
}
function replaceV8Builtins(code) {
return code.replace(/%(\w+)\(/g, V8_BUILTIN_PREFIX + '$1(');
}
function restoreV8Builtins(code) {
return code.replace(V8_REPLACE_BUILTIN_REGEXP, '%$1(');
}
function maybeUseStict(code, useStrict) {
if (useStrict) {
return `'use strict';${EOL}${EOL}${code}`;
}
return code;
}
class Source {
constructor(baseDir, relPath, flags, dependentPaths) {
this.baseDir = baseDir;
this.relPath = relPath;
this.flags = flags;
this.dependentPaths = dependentPaths;
this.sloppy = exceptions.isTestSloppyRel(relPath);
}
get absPath() {
return fsPath.join(this.baseDir, this.relPath);
}
/**
* Specifies if the source isn't compatible with strict mode.
*/
isSloppy() {
return this.sloppy;
}
/**
* Specifies if the source has a top-level 'use strict' directive.
*/
isStrict() {
throw Error('Not implemented');
}
/**
* Generates the code as a string without any top-level 'use strict'
* directives. V8 natives that were replaced before parsing are restored.
*/
generateNoStrict() {
throw Error('Not implemented');
}
/**
* Recursively adds dependencies of a this source file.
*
* @param {Map} dependencies Dependency map to which to add new, parsed
* dependencies unless they are already in the map.
* @param {Map} visitedDependencies A set for avoiding loops.
*/
loadDependencies(dependencies, visitedDependencies) {
visitedDependencies = visitedDependencies || new Set();
for (const absPath of this.dependentPaths) {
if (dependencies.has(absPath) ||
visitedDependencies.has(absPath)) {
// Already added.
continue;
}
// Prevent infinite loops.
visitedDependencies.add(absPath);
// Recursively load dependencies.
const dependency = loadDependencyAbs(this.baseDir, absPath);
dependency.loadDependencies(dependencies, visitedDependencies);
// Add the dependency.
dependencies.set(absPath, dependency);
}
}
}
/**
* Represents sources whose AST can be manipulated.
*/
class ParsedSource extends Source {
constructor(ast, baseDir, relPath, flags, dependentPaths) {
super(baseDir, relPath, flags, dependentPaths);
this.ast = ast;
}
isStrict() {
return !!this.ast.program.directives.filter(isStrictDirective).length;
}
generateNoStrict() {
const allDirectives = this.ast.program.directives;
this.ast.program.directives = this.ast.program.directives.filter(
directive => !isStrictDirective(directive));
try {
const code = babelGenerator(this.ast.program, {comments: true}).code;
return restoreV8Builtins(code);
} finally {
this.ast.program.directives = allDirectives;
}
}
}
/**
* Represents sources with cached code.
*/
class CachedSource extends Source {
constructor(source) {
super(source.baseDir, source.relPath, source.flags, source.dependentPaths);
this.use_strict = source.isStrict();
this.code = source.generateNoStrict();
}
isStrict() {
return this.use_strict;
}
generateNoStrict() {
return this.code;
}
}
/**
* Read file path into an AST.
*
* Post-processes the AST by replacing V8 natives and removing disallowed
* natives, as well as removing load expressions and adding the paths-to-load
* as meta data.
*/
function loadSource(baseDir, relPath, parseStrict=false) {
const absPath = fsPath.resolve(fsPath.join(baseDir, relPath));
const data = fs.readFileSync(absPath, 'utf-8');
if (guessType(data) !== SCRIPT) {
return null;
}
const preprocessed = maybeUseStict(replaceV8Builtins(data), parseStrict);
const ast = babylon.parse(preprocessed, BABYLON_OPTIONS);
removeComments(ast);
cleanAsserts(ast);
annotateWithOriginalPath(ast, relPath);
const flags = loadFlags(data);
const dependentPaths = resolveLoads(absPath, ast);
return new ParsedSource(ast, baseDir, relPath, flags, dependentPaths);
}
function guessType(data) {
if (data.includes('// MODULE')) {
return MODULE;
}
return SCRIPT;
}
/**
* Remove existing comments.
*/
function removeComments(ast) {
babelTraverse(ast, {
enter(path) {
babelTypes.removeComments(path.node);
}
});
}
/**
* Removes "Assert" from strings in spidermonkey shells or from older
* crash tests: https://crbug.com/1068268
*/
function cleanAsserts(ast) {
function replace(string) {
return string == null ? null : string.replace(/[Aa]ssert/g, '*****t');
}
babelTraverse(ast, {
StringLiteral(path) {
path.node.value = replace(path.node.value);
path.node.extra.raw = replace(path.node.extra.raw);
path.node.extra.rawValue = replace(path.node.extra.rawValue);
},
TemplateElement(path) {
path.node.value.cooked = replace(path.node.value.cooked);
path.node.value.raw = replace(path.node.value.raw);
},
});
}
/**
* Annotate code with top-level comment.
*/
function annotateWithComment(ast, comment) {
if (ast.program && ast.program.body && ast.program.body.length > 0) {
babelTypes.addComment(
ast.program.body[0], 'leading', comment, true);
}
}
/**
* Annotate code with original file path.
*/
function annotateWithOriginalPath(ast, relPath) {
annotateWithComment(ast, ' Original: ' + relPath);
}
// TODO(machenbach): Move this into the V8 corpus. Other test suites don't
// use this flag logic.
function loadFlags(data) {
const result = [];
let count = 0;
for (const line of data.split('\n')) {
if (count++ > 40) {
// No need to process the whole file. Flags are always added after the
// copyright header.
break;
}
const match = line.match(/\/\/ Flags:\s*(.*)\s*/);
if (!match) {
continue;
}
for (const flag of exceptions.filterFlags(match[1].split(/\s+/))) {
result.push(flag);
}
}
return result;
}
// Convenience helper to load sources with absolute paths.
function loadSourceAbs(baseDir, absPath) {
return loadSource(baseDir, fsPath.relative(baseDir, absPath));
}
const dependencyCache = new Map();
function loadDependency(baseDir, relPath) {
const absPath = fsPath.join(baseDir, relPath);
let dependency = dependencyCache.get(absPath);
if (!dependency) {
const source = loadSource(baseDir, relPath);
dependency = new CachedSource(source);
dependencyCache.set(absPath, dependency);
}
return dependency;
}
function loadDependencyAbs(baseDir, absPath) {
return loadDependency(baseDir, fsPath.relative(baseDir, absPath));
}
// Convenience helper to load a file from the resources directory.
function loadResource(fileName) {
return loadDependency(__dirname, fsPath.join('resources', fileName));
}
function generateCode(source, dependencies=[]) {
const allSources = dependencies.concat([source]);
const codePieces = allSources.map(
source => source.generateNoStrict());
if (allSources.some(source => source.isStrict()) &&
!allSources.some(source => source.isSloppy())) {
codePieces.unshift('\'use strict\';');
}
return codePieces.join(EOL + EOL);
}
module.exports = {
BABYLON_OPTIONS: BABYLON_OPTIONS,
BABYLON_REPLACE_VAR_OPTIONS: BABYLON_REPLACE_VAR_OPTIONS,
annotateWithComment: annotateWithComment,
generateCode: generateCode,
loadDependencyAbs: loadDependencyAbs,
loadResource: loadResource,
loadSource: loadSource,
loadSourceAbs: loadSourceAbs,
ParsedSource: ParsedSource,
}
@@ -0,0 +1,75 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Test helpers.
*/
'use strict';
const assert = require('assert');
const path = require('path');
const fs = require('fs');
const sourceHelpers = require('../source_helpers.js');
const BASE_DIR = path.join(path.dirname(__dirname), 'test_data');
const DB_DIR = path.join(BASE_DIR, 'fake_db');
const HEADER = `// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
`;
/**
* Create a function that returns one of `probs` when called. It rotates
* through the values. Useful to replace `random.random()` in tests using
* the probabilities that trigger different interesting cases.
*/
function cycleProbabilitiesFun(probs) {
let index = 0;
return () => {
index = index % probs.length;
return probs[index++];
};
}
/**
* Replace Math.random with a deterministic pseudo-random function.
*/
function deterministicRandom(sandbox) {
let seed = 1;
function random() {
const x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
}
sandbox.stub(Math, 'random').callsFake(() => { return random(); });
}
function loadTestData(relPath) {
return sourceHelpers.loadSource(BASE_DIR, relPath);
}
function assertExpectedResult(expectedPath, result) {
const absPath = path.join(BASE_DIR, expectedPath);
if (process.env.GENERATE) {
fs.writeFileSync(absPath, HEADER + result.trim() + '\n');
return;
}
// Omit copyright header when comparing files.
const expected = fs.readFileSync(absPath, 'utf-8').trim().split('\n');
expected.splice(0, 4);
assert.strictEqual(expected.join('\n'), result.trim());
}
module.exports = {
BASE_DIR: BASE_DIR,
DB_DIR: DB_DIR,
assertExpectedResult: assertExpectedResult,
cycleProbabilitiesFun: cycleProbabilitiesFun,
deterministicRandom: deterministicRandom,
loadTestData: loadTestData,
}
@@ -0,0 +1,34 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Tests for mutating variables
*/
'use strict';
const babelTraverse = require('@babel/traverse').default;
const common = require('../mutators/common.js');
const helpers = require('./helpers.js');
describe('Available variables and functions', () => {
it('test', () => {
const source = helpers.loadTestData('available_variables.js');
const result = new Array();
babelTraverse(source.ast, {
CallExpression(path) {
result.push({
variables: common.availableVariables(path),
functions: common.availableFunctions(path),
});
}
});
helpers.assertExpectedResult(
'available_variables_expected.js',
JSON.stringify(result, null, 2));
});
});
@@ -0,0 +1,113 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Corpus loading.
*/
'use strict';
const assert = require('assert');
const sinon = require('sinon');
const exceptions = require('../exceptions.js');
const corpus = require('../corpus.js');
const sandbox = sinon.createSandbox();
function testSoftSkipped(count, softSkipped, paths) {
sandbox.stub(exceptions, 'getSoftSkipped').callsFake(() => {
return softSkipped;
});
const mjsunit = new corpus.Corpus('test_data', 'mjsunit_softskipped');
const cases = mjsunit.getRandomTestcasePaths(count);
assert.deepEqual(paths, cases);
}
describe('Loading corpus', () => {
afterEach(() => {
sandbox.restore();
});
it('keeps all tests with no soft-skipped tests', () => {
sandbox.stub(Math, 'random').callsFake(() => 0.9);
testSoftSkipped(
3,
[],
['mjsunit_softskipped/permitted.js',
'mjsunit_softskipped/object-literal.js',
'mjsunit_softskipped/regress/binaryen-123.js']);
});
it('choose one test with no soft-skipped tests', () => {
sandbox.stub(Math, 'random').callsFake(() => 0.9);
testSoftSkipped(
1,
[],
['mjsunit_softskipped/permitted.js']);
});
it('keeps soft-skipped tests', () => {
sandbox.stub(Math, 'random').callsFake(() => 0.9);
testSoftSkipped(
1,
[/^binaryen.*\.js/, 'object-literal.js'],
['mjsunit_softskipped/permitted.js']);
});
it('keeps no generated soft-skipped tests', () => {
sandbox.stub(Math, 'random').callsFake(() => 0.9);
const softSkipped = [
// Correctly listed full relative path of test case.
'mjsunit_softskipped/regress/binaryen-123.js',
// Only basename doesn't match.
'object-literal.js',
// Only pieces of the path don't match.
'mjsunit_softskipped',
];
sandbox.stub(exceptions, 'getGeneratedSoftSkipped').callsFake(
() => { return new Set(softSkipped); });
testSoftSkipped(
2,
// None soft-skipped for basenames and regexps.
[],
// Only binaryen-123.js gets filtered out.
['mjsunit_softskipped/object-literal.js',
'mjsunit_softskipped/permitted.js']);
});
it('keeps soft-skipped tests by chance', () => {
sandbox.stub(Math, 'random').callsFake(() => 0);
testSoftSkipped(
3,
[/^binaryen.*\.js/, 'object-literal.js'],
['mjsunit_softskipped/object-literal.js',
'mjsunit_softskipped/regress/binaryen-123.js',
'mjsunit_softskipped/permitted.js']);
});
it('caches relative paths', () => {
sandbox.stub(Math, 'random').callsFake(() => 0);
sandbox.stub(exceptions, 'getSoftSkipped').callsFake(
() => { return ['object-literal.js']; });
const generatedSoftSkipped = [
'mjsunit_softskipped/regress/binaryen-123.js',
];
sandbox.stub(exceptions, 'getGeneratedSoftSkipped').callsFake(
() => { return new Set(generatedSoftSkipped); });
const mjsunit = new corpus.Corpus('test_data' , 'mjsunit_softskipped');
assert.deepEqual(
['mjsunit_softskipped/object-literal.js',
'mjsunit_softskipped/regress/binaryen-123.js'],
mjsunit.softSkippedFiles);
assert.deepEqual(
['mjsunit_softskipped/permitted.js'],
mjsunit.permittedFiles);
assert.deepEqual(
['mjsunit_softskipped/permitted.js',
'mjsunit_softskipped/object-literal.js',
'mjsunit_softskipped/regress/binaryen-123.js'],
Array.from(mjsunit.relFiles()));
});
});
@@ -0,0 +1,33 @@
// Copyright 2021 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Test the script building the DB.
*/
'use strict';
const assert = require('assert');
const { execSync } = require("child_process");
const fs = require('fs');
const path = require('path');
const tempy = require('tempy');
function buildDb(inputDir, corpusName, outputDir) {
execSync(
`node build_db.js -i ${inputDir} -o ${outputDir} ${corpusName}`,
{stdio: ['pipe']});
}
describe('DB tests', () => {
// Test feeds an expression that does not apply.
it('omits erroneous expressions', () => {
const outPath = tempy.directory();
buildDb('test_data/db', 'this', outPath);
const indexFile = path.join(outPath, 'index.json');
const indexJSON = JSON.parse(fs.readFileSync(indexFile), 'utf-8');
assert.deepEqual(
indexJSON, {"statements": [], "superStatements": [], "all": []});
});
});
@@ -0,0 +1,141 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Tests for differential fuzzing.
*/
'use strict';
const assert = require('assert');
const program = require('commander');
const sinon = require('sinon');
const helpers = require('./helpers.js');
const scriptMutator = require('../script_mutator.js');
const sourceHelpers = require('../source_helpers.js');
const random = require('../random.js');
const { DifferentialFuzzMutator, DifferentialFuzzSuppressions } = require(
'../mutators/differential_fuzz_mutator.js');
const { DifferentialScriptMutator } = require(
'../differential_script_mutator.js');
const sandbox = sinon.createSandbox();
function testMutators(settings, mutatorClass, inputFile, expectedFile) {
const source = helpers.loadTestData('differential_fuzz/' + inputFile);
const mutator = new mutatorClass(settings);
mutator.mutate(source);
const mutated = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(
'differential_fuzz/' + expectedFile, mutated);
}
describe('Differential fuzzing', () => {
beforeEach(() => {
// Zero settings for all mutators.
this.settings = scriptMutator.defaultSettings();
for (const key of Object.keys(this.settings)) {
this.settings[key] = 0.0;
}
// By default, deterministically use all mutations of differential
// fuzzing.
this.settings['DIFF_FUZZ_EXTRA_PRINT'] = 1.0;
this.settings['DIFF_FUZZ_TRACK_CAUGHT'] = 1.0;
// Fake fuzzer being called with --input_dir flag.
this.oldInputDir = program.input_dir;
program.input_dir = helpers.BASE_DIR;
});
afterEach(() => {
sandbox.restore();
program.input_dir = this.oldInputDir;
});
it('applies suppressions', () => {
// This selects the first random variable when replacing .arguments.
sandbox.stub(random, 'single').callsFake(a => a[0]);
testMutators(
this.settings,
DifferentialFuzzSuppressions,
'suppressions.js',
'suppressions_expected.js');
});
it('adds extra printing', () => {
testMutators(
this.settings,
DifferentialFuzzMutator,
'mutations.js',
'mutations_expected.js');
});
it('does no extra printing', () => {
this.settings['DIFF_FUZZ_EXTRA_PRINT'] = 0.0;
testMutators(
this.settings,
DifferentialFuzzMutator,
'exceptions.js',
'exceptions_expected.js');
});
it('runs end to end', () => {
// Don't choose any zeroed settings or IGNORE_DEFAULT_PROB in try-catch
// mutator. Choose using original flags with >= 2%.
const chooseOrigFlagsProb = 0.2;
sandbox.stub(random, 'choose').callsFake((p) => p >= chooseOrigFlagsProb);
// Fake build directory from which two json configurations for flags are
// loaded.
const env = {
APP_DIR: 'test_data/differential_fuzz',
GENERATE: process.env.GENERATE,
};
sandbox.stub(process, 'env').value(env);
// Fake loading resources and instead load one fixed fake file for each.
sandbox.stub(sourceHelpers, 'loadResource').callsFake(() => {
return helpers.loadTestData('differential_fuzz/fake_resource.js');
});
// Load input files.
const files = [
'differential_fuzz/input1.js',
'differential_fuzz/input2.js',
];
const sources = files.map(helpers.loadTestData);
// Apply top-level fuzzing, with all probabilistic configs switched off.
this.settings['DIFF_FUZZ_EXTRA_PRINT'] = 0.0;
this.settings['DIFF_FUZZ_TRACK_CAUGHT'] = 0.0;
const mutator = new DifferentialScriptMutator(
this.settings, helpers.DB_DIR);
const mutated = mutator.mutateMultiple(sources);
helpers.assertExpectedResult(
'differential_fuzz/combined_expected.js', mutated.code);
// Flags for v8_foozzie.py are calculated from v8_fuzz_experiments.json and
// v8_fuzz_flags.json in test_data/differential_fuzz.
const expectedFlags = [
'--first-config=ignition',
'--second-config=ignition_turbo',
'--second-d8=d8',
'--second-config-extra-flags=--foo1',
'--second-config-extra-flags=--foo2',
'--first-config-extra-flags=--flag1',
'--second-config-extra-flags=--flag1',
'--first-config-extra-flags=--flag2',
'--second-config-extra-flags=--flag2',
'--first-config-extra-flags=--flag3',
'--second-config-extra-flags=--flag3',
'--first-config-extra-flags=--flag4',
'--second-config-extra-flags=--flag4'
];
assert.deepEqual(expectedFlags, mutated.flags);
});
});
@@ -0,0 +1,113 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Tests for the differential-fuzzing library files.
*/
'use strict';
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const libPath = path.resolve(
path.join(__dirname, '..', 'resources', 'differential_fuzz_library.js'));
const code = fs.readFileSync(libPath, 'utf-8');
// We wire the print function to write to this result variable.
const resultDummy = 'let result; const print = text => { result = text; };';
// The prettyPrinted function from mjsunit is reused in the library.
const prettyPrint = 'let prettyPrinted = value => value;';
const hookedUpCode = resultDummy + prettyPrint + code;
// Runs the library, adds test code and verifies the result.
function testLibrary(testCode, expected) {
// The code isn't structured as a module. The test code is expected to
// evaluate to a result which we store in actual.
const actual = eval(hookedUpCode + testCode);
assert.deepEqual(expected, actual);
}
describe('Differential fuzzing library', () => {
it('prints objects', () => {
testLibrary(
'__prettyPrint([0, 1, 2, 3]); result;',
'[0, 1, 2, 3]');
testLibrary(
'__prettyPrint({0: 1, 2: 3}); result;',
'Object{0: 1, 2: 3}');
testLibrary(
'const o = {}; o.k = 42;__prettyPrint(o); result;',
'Object{k: 42}');
});
it('cuts off deep nesting', () => {
// We print only until a nesting depth of 4.
testLibrary(
'__prettyPrint({0: [1, 2, [3, {4: []}]]}); result;',
'Object{0: [1, 2, [3, Object{4: ...}]]}');
});
it('cuts off long strings', () => {
const long = new Array(66).join('a');
const head = new Array(55).join('a');
const tail = new Array(10).join('a');
testLibrary(
`__prettyPrint("${long}"); result;`,
`${head}[...]${tail}`);
// If the string gets longer, the cut-off version is still the same.
const veryLong = new Array(100).join('a');
testLibrary(
`__prettyPrint("${veryLong}"); result;`,
`${head}[...]${tail}`);
});
it('tracks hash difference', () => {
// Test that we track a hash value for each string we print.
const long = new Array(66).join('a');
testLibrary(
`__prettyPrint("${long}"); __hash;`,
2097980794);
// Test that the hash value differs, also when the cut-off result doesn't.
const veryLong = new Array(100).join('a');
testLibrary(
`__prettyPrint("${veryLong}"); __hash;`,
-428472866);
// Test that repeated calls update the hash.
testLibrary(
`__prettyPrint("${long}");__prettyPrint("${long}"); __hash;`,
-909224493);
});
it('limits extra printing', () => {
// Test that after exceeding the limit for calling extra printing, there
// is no new string printed (in the test case no new result added).
testLibrary(
'for (let i = 0; i < 20; i++) __prettyPrintExtra(i); result;',
'19');
testLibrary(
'for (let i = 0; i < 101; i++) __prettyPrintExtra(i); result;',
'99');
testLibrary(
'for (let i = 0; i < 102; i++) __prettyPrintExtra(i); result;',
'99');
});
it('tracks hash after limit', () => {
// Test that after exceeding the limit for calling extra printing, the
// hash is still updated.
testLibrary(
'for (let i = 0; i < 20; i++) __prettyPrintExtra(i); __hash;',
-945753644);
testLibrary(
'for (let i = 0; i < 101; i++) __prettyPrintExtra(i); __hash;',
1907055979);
testLibrary(
'for (let i = 0; i < 102; i++) __prettyPrintExtra(i); __hash;',
-590842070);
});
});
@@ -0,0 +1,69 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Test normalization.
*/
'use strict';
const sinon = require('sinon');
const helpers = require('./helpers.js');
const sourceHelpers = require('../source_helpers.js');
const { ScriptMutator } = require('../script_mutator.js');
const sandbox = sinon.createSandbox();
function testLoad(testPath, expectedPath) {
const mutator = new ScriptMutator({}, helpers.DB_DIR);
const source = helpers.loadTestData(testPath);
const dependencies = mutator.resolveInputDependencies([source]);
const code = sourceHelpers.generateCode(source, dependencies);
helpers.assertExpectedResult(expectedPath, code);
}
describe('V8 dependencies', () => {
it('test', () => {
testLoad(
'mjsunit/test_load.js',
'mjsunit/test_load_expected.js');
});
it('does not loop indefinitely', () => {
testLoad(
'mjsunit/test_load_self.js',
'mjsunit/test_load_self_expected.js');
});
});
describe('Chakra dependencies', () => {
it('test', () => {
testLoad(
'chakra/load.js',
'chakra/load_expected.js');
});
});
describe('JSTest dependencies', () => {
afterEach(() => {
sandbox.restore();
});
it('test', () => {
const fakeStubs = sourceHelpers.loadSource(
helpers.BASE_DIR, 'JSTests/fake_stub.js');
sandbox.stub(sourceHelpers, 'loadResource').callsFake(() => fakeStubs);
testLoad('JSTests/load.js', 'JSTests/load_expected.js');
});
});
describe('SpiderMonkey dependencies', () => {
it('test', () => {
testLoad(
'spidermonkey/test/load.js',
'spidermonkey/test/load_expected.js');
});
});
@@ -0,0 +1,47 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Tests for mutating arrays
*/
'use strict';
const sinon = require('sinon');
const babylon = require('@babel/parser');
const common = require('../mutators/common.js');
const helpers = require('./helpers.js');
const scriptMutator = require('../script_mutator.js');
const sourceHelpers = require('../source_helpers.js');
const {ArrayMutator} = require('../mutators/array_mutator.js');
const sandbox = sinon.createSandbox();
describe('Mutate arrays', () => {
afterEach(() => {
sandbox.restore();
});
it('performs all mutations', () => {
// Make random operations deterministic.
sandbox.stub(common, 'randomValue').callsFake(
() => babylon.parseExpression('""'));
helpers.deterministicRandom(sandbox);
const source = helpers.loadTestData('mutate_arrays.js');
const settings = scriptMutator.defaultSettings();
settings['MUTATE_ARRAYS'] = 1.0;
const mutator = new ArrayMutator(settings);
mutator.mutate(source);
const mutated = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(
'mutate_arrays_expected.js', mutated);
});
});
@@ -0,0 +1,79 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Tests for mutating expressions
*/
'use strict';
const assert = require('assert');
const babelTypes = require('@babel/types');
const sinon = require('sinon');
const common = require('../mutators/common.js');
const expressionMutator = require('../mutators/expression_mutator.js');
const helpers = require('./helpers.js');
const scriptMutator = require('../script_mutator.js');
const sourceHelpers = require('../source_helpers.js');
const random = require('../random.js');
const sandbox = sinon.createSandbox();
function testCloneSiblings(expected_file) {
const source = helpers.loadTestData('mutate_expressions.js');
const settings = scriptMutator.defaultSettings();
settings['MUTATE_EXPRESSIONS'] = 1.0;
const mutator = new expressionMutator.ExpressionMutator(settings);
mutator.mutate(source);
const mutated = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(expected_file, mutated);
}
describe('Mutate expressions', () => {
beforeEach(() => {
// Select the previous sibling.
sandbox.stub(random, 'randInt').callsFake((a, b) => b);
// This chooses cloning siblings.
sandbox.stub(random, 'random').callsFake(() => 0.8);
});
afterEach(() => {
sandbox.restore();
});
it('clones previous to current', () => {
// Keep the order of [previous, current], select previous.
sandbox.stub(random, 'shuffle').callsFake(a => a);
// Insert after. Keep returning true for the MUTATE_EXPRESSIONS check.
sandbox.stub(random, 'choose').callsFake(a => a === 1);
testCloneSiblings('mutate_expressions_previous_expected.js');
});
it('clones current to previous', () => {
// Switch the order of [previous, current], select current.
sandbox.stub(random, 'shuffle').callsFake(a => [a[1], a[0]]);
// Insert before.
sandbox.stub(random, 'choose').callsFake(() => true);
testCloneSiblings('mutate_expressions_current_expected.js');
});
});
describe('Cloning', () => {
// Ensure that the source location we add are not cloned.
it('is not copying added state', () => {
const source = helpers.loadTestData('mutate_expressions.js');
common.setSourceLoc(source, 5, 10);
const noopNode = source.ast.program.body[0];
assert.equal(0.5, common.getSourceLoc(noopNode));
const cloned = babelTypes.cloneDeep(noopNode);
assert.equal(undefined, common.getSourceLoc(cloned));
});
});
@@ -0,0 +1,84 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Tests for mutating funciton calls.
*/
'use strict';
const sinon = require('sinon');
const helpers = require('./helpers.js');
const random = require('../random.js');
const scriptMutator = require('../script_mutator.js');
const sourceHelpers = require('../source_helpers.js');
const functionCallMutator = require('../mutators/function_call_mutator.js');
const sandbox = sinon.createSandbox();
function loadAndMutate(input_file) {
const source = helpers.loadTestData(input_file);
const settings = scriptMutator.defaultSettings();
settings['engine'] = 'V8';
settings['MUTATE_FUNCTION_CALLS'] = 1.0;
const mutator = new functionCallMutator.FunctionCallMutator(settings);
mutator.mutate(source);
return source;
}
describe('Mutate functions', () => {
afterEach(() => {
sandbox.restore();
});
it('is robust without available functions', () => {
sandbox.stub(random, 'random').callsFake(() => { return 0.2; });
// We just ensure here that mutating this file doesn't throw.
loadAndMutate('mutate_function_call.js');
});
it('optimizes functions with turbofan in V8', () => {
sandbox.stub(random, 'random').callsFake(() => { return 0.5; });
sandbox.stub(random, 'choose').callsFake(p => true);
const source = loadAndMutate('mutate_function_call.js');
const mutated = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(
'mutate_function_call_expected.js', mutated);
});
it('optimizes functions with maglev in V8', () => {
sandbox.stub(random, 'random').callsFake(() => { return 0.5; });
// False-path takes 'Maglev'. Other calls to choose should return
// true. It's also used to determine if a mutator should be chosen.
sandbox.stub(random, 'choose').callsFake(p => p == 0.7 ? false : true);
const source = loadAndMutate('mutate_function_call.js');
const mutated = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(
'mutate_function_call_maglev_expected.js', mutated);
});
it('compiles functions in V8 to baseline', () => {
sandbox.stub(random, 'random').callsFake(() => { return 0.7; });
const source = loadAndMutate('mutate_function_call.js');
const mutated = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(
'mutate_function_call_baseline_expected.js', mutated);
});
it('deoptimizes functions in V8', () => {
sandbox.stub(random, 'random').callsFake(() => { return 0.8; });
const source = loadAndMutate('mutate_function_call.js');
const mutated = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(
'mutate_function_call_deopt_expected.js', mutated);
});
});
@@ -0,0 +1,54 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Tests for mutating variables
*/
'use strict';
const babelTypes = require('@babel/types');
const sinon = require('sinon');
const common = require('../mutators/common.js');
const helpers = require('./helpers.js');
const scriptMutator = require('../script_mutator.js');
const sourceHelpers = require('../source_helpers.js');
const numberMutator = require('../mutators/number_mutator.js');
const random = require('../random.js');
const sandbox = sinon.createSandbox();
describe('Mutate numbers', () => {
beforeEach(() => {
sandbox.stub(common, 'nearbyRandomNumber').callsFake(
() => { return babelTypes.numericLiteral(-3) });
sandbox.stub(common, 'randomInterestingNumber').callsFake(
() => { return babelTypes.numericLiteral(-4) });
sandbox.stub(random, 'randInt').callsFake(() => { return -5 });
// Interesting cases from number mutator.
const interestingProbs = [0.009, 0.05, 0.5];
sandbox.stub(random, 'random').callsFake(
helpers.cycleProbabilitiesFun(interestingProbs));
});
afterEach(() => {
sandbox.restore();
});
it('test', () => {
const source = helpers.loadTestData('mutate_numbers.js');
const settings = scriptMutator.defaultSettings();
settings['MUTATE_NUMBERS'] = 1.0;
const mutator = new numberMutator.NumberMutator(settings);
mutator.mutate(source);
const mutated = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(
'mutate_numbers_expected.js', mutated);
});
});
@@ -0,0 +1,47 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Tests for mutating object expressions
*/
'use strict';
const sinon = require('sinon');
const babylon = require('@babel/parser');
const common = require('../mutators/common.js');
const helpers = require('./helpers.js');
const scriptMutator = require('../script_mutator.js');
const sourceHelpers = require('../source_helpers.js');
const {ObjectMutator} = require('../mutators/object_mutator.js');
const sandbox = sinon.createSandbox();
describe('Mutate objects', () => {
afterEach(() => {
sandbox.restore();
});
it('performs all mutations', () => {
// Make random operations deterministic.
sandbox.stub(common, 'randomValue').callsFake(
() => babylon.parseExpression('""'));
helpers.deterministicRandom(sandbox);
const source = helpers.loadTestData('mutate_objects.js');
const settings = scriptMutator.defaultSettings();
settings['MUTATE_OBJECTS'] = 1.0;
const mutator = new ObjectMutator(settings);
mutator.mutate(source);
const mutated = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(
'mutate_objects_expected.js', mutated);
});
});
@@ -0,0 +1,72 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Test variable-or-object mutator.
*/
'use strict';
const babylon = require('@babel/parser');
const sinon = require('sinon');
const common = require('../mutators/common.js');
const helpers = require('./helpers.js');
const variableOrObject = require('../mutators/variable_or_object_mutation.js');
const random = require('../random.js');
const sourceHelpers = require('../source_helpers.js');
const sandbox = sinon.createSandbox();
function testMutations(testPath, expectedPath) {
const source = helpers.loadTestData(testPath);
const mutator = new variableOrObject.VariableOrObjectMutator(
{ ADD_VAR_OR_OBJ_MUTATIONS: 1.0 });
mutator.mutate(source);
const mutated = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(expectedPath, mutated);
}
describe('Variable or object mutator', () => {
beforeEach(() => {
// Make before/after insertion deterministic. This also chooses
// random objects.
sandbox.stub(random, 'choose').callsFake(() => { return true; });
// This stubs out the random seed.
sandbox.stub(random, 'randInt').callsFake(() => { return 123; });
// Random value is itself dependent on too much randomization.
sandbox.stub(common, 'randomValue').callsFake(
() => { return babylon.parseExpression('0'); });
});
afterEach(() => {
sandbox.restore();
});
it('test', () => {
let index = 0;
// Test different cases of _randomVariableOrObjectMutations in
// variable_or_object_mutation.js.
const choices = [
0.2, // Trigger recursive case.
0.3, // Recursion 1: Delete.
0.4, // Recursion 2: Property access.
0.5, // Random assignment.
// 0.6 case for randomFunction omitted as it has too much randomization.
0.7, // Variable assignment.
0.8, // Object.defineProperty.
0.9, // Object.defineProperty recursive.
0.3, // Recursion 1: Delete.
0.4, // Recursion 2: Property access.
];
sandbox.stub(random, 'random').callsFake(
() => { return choices[index++]; });
testMutations(
'mutate_var_or_obj.js',
'mutate_var_or_obj_expected.js');
});
});
@@ -0,0 +1,47 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Tests for mutating variables
*/
'use strict';
const babelTypes = require('@babel/types');
const sinon = require('sinon');
const common = require('../mutators/common.js');
const helpers = require('./helpers.js');
const scriptMutator = require('../script_mutator.js');
const sourceHelpers = require('../source_helpers.js');
const variableMutator = require('../mutators/variable_mutator.js');
const sandbox = sinon.createSandbox();
describe('Mutate variables', () => {
beforeEach(() => {
sandbox.stub(
common, 'randomVariable').callsFake(
() => { return babelTypes.identifier('REPLACED') });
});
afterEach(() => {
sandbox.restore();
});
it('test', () => {
const source = helpers.loadTestData('mutate_variables.js');
const settings = scriptMutator.defaultSettings();
settings['MUTATE_VARIABLES'] = 1.0;
const mutator = new variableMutator.VariableMutator(settings);
mutator.mutate(source);
const mutated = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(
'mutate_variables_expected.js', mutated);
});
});
@@ -0,0 +1,56 @@
// Copyright 2022 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Test shuffling mutators and extra mutations.
*
* Use minimal probability settings to demonstrate order changes of top-level
* mutators. Which mutations are used exactly is not relevant to the test and
* handled pseudo-randomly.
*/
'use strict';
const sinon = require('sinon');
const helpers = require('./helpers.js');
const scriptMutator = require('../script_mutator.js');
const sourceHelpers = require('../source_helpers.js');
const random = require('../random.js');
const sandbox = sinon.createSandbox();
describe('Toplevel mutations', () => {
afterEach(() => {
sandbox.restore();
});
it('shuffle their order', () => {
// Make random operations deterministic.
helpers.deterministicRandom(sandbox);
this.settings = {
ADD_VAR_OR_OBJ_MUTATIONS: 0.0,
MUTATE_CROSSOVER_INSERT: 0.0,
MUTATE_EXPRESSIONS: 0.0,
MUTATE_FUNCTION_CALLS: 1.0,
MUTATE_NUMBERS: 1.0,
MUTATE_VARIABLES: 0.0,
SCRIPT_MUTATOR_SHUFFLE: 1.0,
SCRIPT_MUTATOR_EXTRA_MUTATIONS: 1.0,
engine: 'V8',
testing: true,
};
const source = helpers.loadTestData('mutation_order/input.js');
const mutator = new scriptMutator.ScriptMutator(this.settings, helpers.DB_DIR);
const mutated = mutator.mutateInputs([source]);
const code = sourceHelpers.generateCode(mutated);
// The test data should be rich enough to produce a pattern from the
// FunctionCallMutator that afterwards gets mutated by the NumberMutator.
helpers.assertExpectedResult(
'mutation_order/output_expected.js', code);
});
});
@@ -0,0 +1,42 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Test normalization.
*/
'use strict';
const helpers = require('./helpers.js');
const normalizer = require('../mutators/normalizer.js');
const sourceHelpers = require('../source_helpers.js');
describe('Normalize', () => {
it('test basic', () => {
const source = helpers.loadTestData('normalize.js');
const mutator = new normalizer.IdentifierNormalizer();
mutator.mutate(source);
const normalized_0 = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(
'normalize_expected_0.js', normalized_0);
mutator.mutate(source);
const normalized_1 = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(
'normalize_expected_1.js', normalized_1);
});
it('test simple_test.js', () => {
const source = helpers.loadTestData('simple_test.js');
const mutator = new normalizer.IdentifierNormalizer();
mutator.mutate(source);
const normalized = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(
'simple_test_expected.js', normalized);
});
});
@@ -0,0 +1,51 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Test random utilities.
*/
'use strict';
const assert = require('assert');
const sinon = require('sinon');
const { twoBucketSample } = require('../random.js');
const sandbox = sinon.createSandbox();
describe('Two-bucket choosing', () => {
afterEach(() => {
sandbox.restore();
});
it('with one empty', () => {
sandbox.stub(Math, 'random').callsFake(() => 0.5);
assert.deepEqual([1, 2], twoBucketSample([0, 1, 2], [], 1, 2));
assert.deepEqual([1, 2], twoBucketSample([], [0, 1, 2], 1, 2));
assert.deepEqual([0], twoBucketSample([0], [], 1, 1));
assert.deepEqual([0], twoBucketSample([], [0], 1, 1));
});
it('chooses with 0.3', () => {
sandbox.stub(Math, 'random').callsFake(() => 0.3);
assert.deepEqual([1, 2], twoBucketSample([0, 1, 2], [3, 4, 5], 1, 2));
// Higher factor.
assert.deepEqual([3, 5], twoBucketSample([0, 1, 2], [3, 4, 5], 4, 2));
});
it('chooses with 0.7', () => {
sandbox.stub(Math, 'random').callsFake(() => 0.7);
assert.deepEqual([4, 3], twoBucketSample([0, 1, 2], [3, 4, 5], 1, 2));
});
it('chooses with 0.5', () => {
sandbox.stub(Math, 'random').callsFake(() => 0.5);
assert.deepEqual([3], twoBucketSample([0, 1], [2, 3, 4, 5], 1, 1));
assert.deepEqual([3], twoBucketSample([0, 1, 2, 3], [4, 5], 1, 1));
// Higher factor.
assert.deepEqual([4], twoBucketSample([0, 1, 2, 3], [4, 5], 2, 1));
});
});
@@ -0,0 +1,113 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Regression tests.
*/
'use strict';
const assert = require('assert');
const { execSync } = require("child_process");
const fs = require('fs');
const sinon = require('sinon');
const tempfile = require('tempfile');
const tempy = require('tempy');
const exceptions = require('../exceptions.js');
const helpers = require('./helpers.js');
const scriptMutator = require('../script_mutator.js');
const sandbox = sinon.createSandbox();
const SYNTAX_ERROR_RE = /.*SyntaxError.*/
function createFuzzTest(fake_db, settings, inputFiles) {
const sources = inputFiles.map(input => helpers.loadTestData(input));
const mutator = new scriptMutator.ScriptMutator(settings, fake_db);
const result = mutator.mutateMultiple(sources);
const output_file = tempfile('.js');
fs.writeFileSync(output_file, result.code);
return output_file;
}
function execFile(jsFile) {
execSync("node " + jsFile, {stdio: ['pipe']});
}
describe('Regression tests', () => {
beforeEach(() => {
helpers.deterministicRandom(sandbox);
this.settings = {
ADD_VAR_OR_OBJ_MUTATIONS: 0.0,
MUTATE_CROSSOVER_INSERT: 0.0,
MUTATE_EXPRESSIONS: 0.0,
MUTATE_FUNCTION_CALLS: 0.0,
MUTATE_NUMBERS: 0.0,
MUTATE_VARIABLES: 0.0,
engine: 'V8',
testing: true,
}
});
afterEach(() => {
sandbox.restore();
});
it('combine strict and with', () => {
// Test that when a file with "use strict" is used in the inputs,
// the result is only strict if no other file contains anything
// prohibited in strict mode (here a with statement).
// It is assumed that such input files are marked as sloppy in the
// auto generated exceptions.
sandbox.stub(exceptions, 'getGeneratedSloppy').callsFake(
() => { return new Set(['regress/strict/input_with.js']); });
const file = createFuzzTest(
'test_data/regress/strict/db',
this.settings,
['regress/strict/input_strict.js', 'regress/strict/input_with.js']);
execFile(file);
});
it('combine strict and delete', () => {
// As above with unqualified delete.
sandbox.stub(exceptions, 'getGeneratedSloppy').callsFake(
() => { return new Set(['regress/strict/input_delete.js']); });
const file = createFuzzTest(
'test_data/regress/strict/db',
this.settings,
['regress/strict/input_strict.js', 'regress/strict/input_delete.js']);
execFile(file);
});
it('mutates negative value', () => {
// This tests that the combination of number, function call and expression
// mutator does't produce an update expression.
// Previously the 1 in -1 was replaced with another negative number leading
// to e.g. -/*comment/*-2. Then cloning the expression removed the
// comment and produced --2 in the end.
this.settings['MUTATE_NUMBERS'] = 1.0;
this.settings['MUTATE_FUNCTION_CALLS'] = 1.0;
this.settings['MUTATE_EXPRESSIONS'] = 1.0;
const file = createFuzzTest(
'test_data/regress/numbers/db',
this.settings,
['regress/numbers/input_negative.js']);
execFile(file);
});
it('mutates indices', () => {
// Test that indices are not replaced with a negative number causing a
// syntax error (e.g. {-1: ""}).
this.settings['MUTATE_NUMBERS'] = 1.0;
const file = createFuzzTest(
'test_data/regress/numbers/db',
this.settings,
['regress/numbers/input_indices.js']);
execFile(file);
});
});
@@ -0,0 +1,85 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Test normalization.
*/
'use strict';
const sinon = require('sinon');
const common = require('../mutators/common.js');
const helpers = require('./helpers.js');
const random = require('../random.js');
const sourceHelpers = require('../source_helpers.js');
const tryCatch = require('../mutators/try_catch.js');
const sandbox = sinon.createSandbox();
function loadSource() {
return helpers.loadTestData('try_catch.js');
}
function testTryCatch(source, expected) {
const mutator = new tryCatch.AddTryCatchMutator();
mutator.mutate(source);
const mutated = sourceHelpers.generateCode(source);
helpers.assertExpectedResult(expected, mutated);
}
describe('Try catch', () => {
afterEach(() => {
sandbox.restore();
});
// Wrap on exit, hence wrap everything nested.
it('wraps all', () => {
sandbox.stub(random, 'choose').callsFake(() => { return false; });
sandbox.stub(random, 'random').callsFake(() => { return 0.7; });
testTryCatch(loadSource(), 'try_catch_expected.js');
});
// Wrap on enter and skip.
it('wraps toplevel', () => {
sandbox.stub(random, 'choose').callsFake(() => { return false; });
sandbox.stub(random, 'random').callsFake(() => { return 0.04; });
const source = loadSource();
// Fake source fraction 0.1 (i.e. the second of 10 files).
// Probability for toplevel try-catch is 0.05.
common.setSourceLoc(source, 1, 10);
testTryCatch(source, 'try_catch_toplevel_expected.js');
});
// Choose the rare case of skipping try-catch.
it('wraps nothing', () => {
sandbox.stub(random, 'choose').callsFake(() => { return false; });
sandbox.stub(random, 'random').callsFake(() => { return 0.01; });
const source = loadSource();
// Fake source fraction 0.1 (i.e. the second of 10 files).
// Probability for skipping is 0.02.
common.setSourceLoc(source, 1, 10);
testTryCatch(source, 'try_catch_nothing_expected.js');
});
// Choose to alter the target probability to 0.9 resulting in skipping
// all try-catch.
it('wraps nothing with high target probability', () => {
sandbox.stub(random, 'choose').callsFake(() => { return true; });
sandbox.stub(random, 'uniform').callsFake(() => { return 0.9; });
sandbox.stub(random, 'random').callsFake(() => { return 0.8; });
const source = loadSource();
// Fake source fraction 0.9 (i.e. the last of 10 files).
// Probability for skipping is 0.81 (0.9 * 0.9).
common.setSourceLoc(source, 9, 10);
testTryCatch(source, 'try_catch_alternate_expected.js');
});
});
@@ -0,0 +1,5 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
print("Fake stub");
@@ -0,0 +1,5 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
print("JSTest");
@@ -0,0 +1,9 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original: JSTests/fake_stub.js
print("Fake stub");
// Original: JSTests/load.js
print("JSTest");
@@ -0,0 +1,33 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
let __v_0 = 0;
let __v_1 = 0;
console.log(__v_0, __v_1, __f_0, __f_1);
function __f_0() {
let __v_2 = 0;
console.log(__v_0, __v_1, __v_2, __f_0, __f_1);
}
let __v_3 = 0;
console.log(__v_0, __v_1, __v_3, __f_0, __f_1);
function __f_1(__v_7) {
let __v_4 = 0;
console.log(__v_0, __v_1, __v_3, __v_4, __v_7, __f_0, __f_1);
{
let __v_5 = 0;
var __v_6 = 0;
console.log(__v_0, __v_1, __v_3, __v_4, __v_5, __v_6, __v_7, __f_0, __f_1, __f_2);
function __f_2 () {};
console.log(__v_0, __v_1, __v_3, __v_4, __v_5, __v_6, __v_7, __f_0, __f_1, __f_2);
}
// TODO(machenbach): __f_2 is missing as available identifier.
console.log(__v_0, __v_1, __v_3, __v_4, __v_6, __v_7, __f_0, __f_1, __f_2);
}
console.log(__v_0, __v_1, __v_3, __f_0, __f_1);
@@ -0,0 +1,270 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
[
{
"variables": [
{
"type": "Identifier",
"name": "__v_0"
},
{
"type": "Identifier",
"name": "__v_1"
}
],
"functions": [
{
"type": "Identifier",
"name": "__f_0"
},
{
"type": "Identifier",
"name": "__f_1"
}
]
},
{
"variables": [
{
"type": "Identifier",
"name": "__v_2"
},
{
"type": "Identifier",
"name": "__v_0"
},
{
"type": "Identifier",
"name": "__v_1"
}
],
"functions": [
{
"type": "Identifier",
"name": "__f_0"
},
{
"type": "Identifier",
"name": "__f_1"
}
]
},
{
"variables": [
{
"type": "Identifier",
"name": "__v_0"
},
{
"type": "Identifier",
"name": "__v_1"
},
{
"type": "Identifier",
"name": "__v_3"
}
],
"functions": [
{
"type": "Identifier",
"name": "__f_0"
},
{
"type": "Identifier",
"name": "__f_1"
}
]
},
{
"variables": [
{
"type": "Identifier",
"name": "__v_7"
},
{
"type": "Identifier",
"name": "__v_4"
},
{
"type": "Identifier",
"name": "__v_0"
},
{
"type": "Identifier",
"name": "__v_1"
},
{
"type": "Identifier",
"name": "__v_3"
}
],
"functions": [
{
"type": "Identifier",
"name": "__f_0"
},
{
"type": "Identifier",
"name": "__f_1"
}
]
},
{
"variables": [
{
"type": "Identifier",
"name": "__v_5"
},
{
"type": "Identifier",
"name": "__v_7"
},
{
"type": "Identifier",
"name": "__v_4"
},
{
"type": "Identifier",
"name": "__v_6"
},
{
"type": "Identifier",
"name": "__v_0"
},
{
"type": "Identifier",
"name": "__v_1"
},
{
"type": "Identifier",
"name": "__v_3"
}
],
"functions": [
{
"type": "Identifier",
"name": "__f_2"
},
{
"type": "Identifier",
"name": "__f_0"
},
{
"type": "Identifier",
"name": "__f_1"
}
]
},
{
"variables": [
{
"type": "Identifier",
"name": "__v_5"
},
{
"type": "Identifier",
"name": "__v_7"
},
{
"type": "Identifier",
"name": "__v_4"
},
{
"type": "Identifier",
"name": "__v_6"
},
{
"type": "Identifier",
"name": "__v_0"
},
{
"type": "Identifier",
"name": "__v_1"
},
{
"type": "Identifier",
"name": "__v_3"
}
],
"functions": [
{
"type": "Identifier",
"name": "__f_2"
},
{
"type": "Identifier",
"name": "__f_0"
},
{
"type": "Identifier",
"name": "__f_1"
}
]
},
{
"variables": [
{
"type": "Identifier",
"name": "__v_7"
},
{
"type": "Identifier",
"name": "__v_4"
},
{
"type": "Identifier",
"name": "__v_6"
},
{
"type": "Identifier",
"name": "__v_0"
},
{
"type": "Identifier",
"name": "__v_1"
},
{
"type": "Identifier",
"name": "__v_3"
}
],
"functions": [
{
"type": "Identifier",
"name": "__f_0"
},
{
"type": "Identifier",
"name": "__f_1"
}
]
},
{
"variables": [
{
"type": "Identifier",
"name": "__v_0"
},
{
"type": "Identifier",
"name": "__v_1"
},
{
"type": "Identifier",
"name": "__v_3"
}
],
"functions": [
{
"type": "Identifier",
"name": "__f_0"
},
{
"type": "Identifier",
"name": "__f_1"
}
]
}
]
@@ -0,0 +1,6 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
WScript.LoadScriptFile("..\\load2.js", "self");
console.log('load3');
@@ -0,0 +1,9 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
if (this.WScript && this.WScript.LoadScriptFile) {
WScript.LoadScriptFile("load1.js");
}
console.log('load.js');
@@ -0,0 +1,8 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Test case insensitivity.
WScript.LoadScriptFile("DIR\\LoAd3.js");
console.log('load1.js');
@@ -0,0 +1,5 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
console.log('load2.js');
@@ -0,0 +1,17 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original: chakra/load2.js
console.log('load2.js');
// Original: chakra/dir/load3.js
console.log('load3');
// Original: chakra/load1.js
console.log('load1.js');
// Original: chakra/load.js
if (this.WScript && this.WScript.LoadScriptFile) {}
console.log('load.js');
@@ -0,0 +1,11 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
class __C {
foo() {
let __v_0 = 2;
let __v_1 = 2;
Math.pow(__v_0, __v_1);
}
}
@@ -0,0 +1,9 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
function C() {
this.c = "c";
}
var c = new C();
@@ -0,0 +1,59 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original: differential_fuzz/fake_resource.js
print("I'm a resource.");
// Original: differential_fuzz/fake_resource.js
print("I'm a resource.");
// Original: differential_fuzz/fake_resource.js
print("I'm a resource.");
// Original: differential_fuzz/fake_resource.js
print("I'm a resource.");
// Original: differential_fuzz/fake_resource.js
print("I'm a resource.");
/* DifferentialFuzzMutator: Print variables and exceptions from section */
try {
print("Hash: " + __hash);
print("Caught: " + __caught);
} catch (e) {}
print("v8-foozzie source: differential_fuzz/input1.js");
// Original: differential_fuzz/input1.js
try {
var __v_0 = 0;
} catch (e) {}
try {
/* DifferentialFuzzMutator: Pretty printing */
__prettyPrintExtra(__v_0);
} catch (e) {}
/* DifferentialFuzzMutator: Print variables and exceptions from section */
try {
print("Hash: " + __hash);
print("Caught: " + __caught);
__prettyPrint(__v_0);
} catch (e) {}
print("v8-foozzie source: differential_fuzz/input2.js");
// Original: differential_fuzz/input2.js
let __v_1 = 1;
/* DifferentialFuzzMutator: Print variables and exceptions from section */
try {
print("Hash: " + __hash);
print("Caught: " + __caught);
__prettyPrint(__v_0);
__prettyPrint(__v_1);
} catch (e) {}
@@ -0,0 +1,8 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
try {
let __v_0 = boom;
} catch (e) {}
@@ -0,0 +1,16 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original: differential_fuzz/exceptions.js
try {
let __v_0 = boom;
} catch (e) {
__caught++;
}
/* DifferentialFuzzMutator: Print variables and exceptions from section */
try {
print("Hash: " + __hash);
print("Caught: " + __caught);
} catch (e) {}
@@ -0,0 +1,7 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file represents anything loaded from the resources directory.
print("I'm a resource.");
@@ -0,0 +1,9 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --flag1 --flag2
// Flags: --flag3
var a = 0;
print(a);
@@ -0,0 +1,7 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --flag4
let b = 1;
@@ -0,0 +1,26 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Print after declaration.
var __v_0 = [1, 2, 3];
// Don't print after declarations or assigments in loops.
for (let __v_1 = 0; __v_1 < 3; __v_1 += 1) {
// Print after multiple declarations.
let __v_2, __v_3 = 0;
// Print after assigning to member.
__v_0.foo = undefined;
// Replace with deep printing.
print(0);
// Print exception.
try {
// Print after assignment.
__v_1 += 1;
} catch(e) {}
}
@@ -0,0 +1,44 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original: differential_fuzz/mutations.js
var __v_0 = [1, 2, 3];
/* DifferentialFuzzMutator: Extra variable printing */
__prettyPrintExtra(__v_0);
for (let __v_1 = 0; __v_1 < 3; __v_1 += 1) {
let __v_2,
__v_3 = 0;
/* DifferentialFuzzMutator: Extra variable printing */
__prettyPrintExtra(__v_2);
__prettyPrintExtra(__v_3);
__v_0.foo = undefined;
/* DifferentialFuzzMutator: Extra variable printing */
__prettyPrintExtra(__v_0);
/* DifferentialFuzzMutator: Pretty printing */
__prettyPrintExtra(0);
try {
__v_1 += 1;
/* DifferentialFuzzMutator: Extra variable printing */
__prettyPrintExtra(__v_1);
} catch (e) {
__prettyPrintExtra(e);
}
}
/* DifferentialFuzzMutator: Print variables and exceptions from section */
try {
print("Hash: " + __hash);
print("Caught: " + __caught);
__prettyPrint(__v_0);
} catch (e) {}
@@ -0,0 +1,15 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// These statements might come from a CrashTest.
print("v8-foozzie source: some/file/name");
print('v8-foozzie source: some/file/name');
function foo(__v_0) {
// This is an unsupported language feature.
return 1 in foo.arguments;
}
// This leads to precision differences in optimized code.
print(192 ** -0.5);
@@ -0,0 +1,21 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original: differential_fuzz/suppressions.js
print(
/* DifferentialFuzzSuppressions: Replaced magic string */
"v***************e: some/file/name");
print(
/* DifferentialFuzzSuppressions: Replaced magic string */
"v***************e: some/file/name");
function foo(__v_0) {
return 1 in
/* DifferentialFuzzSuppressions: Replaced .arguments */
__v_0;
}
print(
/* DifferentialFuzzSuppressions: Replaced ** */
192 + -0.5);
@@ -0,0 +1,3 @@
[
[100, "ignition", "ignition_turbo", "d8"]
]
@@ -0,0 +1,3 @@
[
[1.0, "--foo1 --foo2"]
]
@@ -0,0 +1 @@
{}
@@ -0,0 +1,5 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var fakeMjsunit = 'fake';
@@ -0,0 +1,7 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var testLoad = 'test_load';
load('test_data/mjsunit/test_load_1.js');
load('test_load_0.js');
@@ -0,0 +1,8 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
load('test_data/mjsunit/test_load_1.js');
load('test_load_2.js');
load('test_load_3.js');
var testLoad0 = 'test_load_0';
@@ -0,0 +1,6 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
load('test_load_2.js');
var testLoad1 = 'test_load_1';
@@ -0,0 +1,5 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var testLoad2 = 'test_load_2';
@@ -0,0 +1,5 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
var testLoad3 = 'test_load_3';
@@ -0,0 +1,21 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original: mjsunit/mjsunit.js
var fakeMjsunit = 'fake';
// Original: mjsunit/test_load_2.js
var testLoad2 = 'test_load_2';
// Original: mjsunit/test_load_1.js
var testLoad1 = 'test_load_1';
// Original: mjsunit/test_load_3.js
var testLoad3 = 'test_load_3';
// Original: mjsunit/test_load_0.js
var testLoad0 = 'test_load_0';
// Original: mjsunit/test_load.js
var testLoad = 'test_load';
@@ -0,0 +1,5 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
load("test_load_self.js");
@@ -0,0 +1,6 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original: mjsunit/mjsunit.js
var fakeMjsunit = 'fake';
@@ -0,0 +1,5 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Fake file
@@ -0,0 +1,5 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Fake file
@@ -0,0 +1,5 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Fake file
@@ -0,0 +1,34 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
[];
[];
[];
[];
[];
[];
[];
[];
[];
[];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
[1, 2, 3];
@@ -0,0 +1,109 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Original: mutate_arrays.js
/* ArrayMutator: Remove elements */
/* ArrayMutator: Insert a hole */
[];
[];
/* ArrayMutator: Shuffle array */
[];
/* ArrayMutator: Insert a random value */
[""];
/* ArrayMutator: Insert a random value (replaced) */
[""];
/* ArrayMutator: Insert a hole (replaced) */
[,];
[];
/* ArrayMutator: Insert a hole (replaced) */
[,];
/* ArrayMutator: Remove elements */
[];
/* ArrayMutator: Remove elements */
[];
/* ArrayMutator: Duplicate an element */
[1, 1, 2, 3];
/* ArrayMutator: Insert a random value (replaced) */
[1, "", 3];
/* ArrayMutator: Remove elements */
[];
/* ArrayMutator: Duplicate an element */
[1, 2, 3, 2];
/* ArrayMutator: Remove elements */
[3];
/* ArrayMutator: Duplicate an element (replaced) */
[1, 2, 3];
/* ArrayMutator: Insert a hole (replaced) */
/* ArrayMutator: Duplicate an element (replaced) */
[1, 2,,];
/* ArrayMutator: Remove elements */
[1, 2];
/* ArrayMutator: Insert a hole (replaced) */
/* ArrayMutator: Duplicate an element */
[1, 1, 2,,];
/* ArrayMutator: Shuffle array */
[2, 1, 3];
/* ArrayMutator: Remove elements */
/* ArrayMutator: Remove elements */
[3];
/* ArrayMutator: Duplicate an element (replaced) */
[1, 2, 1];
/* ArrayMutator: Duplicate an element (replaced) */
/* ArrayMutator: Duplicate an element (replaced) */
[1, 2, 2];
/* ArrayMutator: Insert a random value */
[1, 2, 3, ""];
/* ArrayMutator: Duplicate an element */
[1, 2, 3, 3];
/* ArrayMutator: Remove elements */
/* ArrayMutator: Duplicate an element */
[1, 2];
/* ArrayMutator: Insert a random value (replaced) */
/* ArrayMutator: Duplicate an element (replaced) */
[1, 2, ""];
/* ArrayMutator: Insert a random value (replaced) */
/* ArrayMutator: Insert a random value (replaced) */
["", 2, 3];
/* ArrayMutator: Duplicate an element */
/* ArrayMutator: Remove elements */
[1, 1, 3];
/* ArrayMutator: Remove elements */
[1, 2];
@@ -0,0 +1,8 @@
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
1;
let foo = undefined;
2;
3;

Some files were not shown because too many files have changed in this diff Show More