mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[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:
@@ -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,
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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) { }
|
||||
@@ -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);
|
||||
+21
@@ -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);
|
||||
+3
@@ -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
|
||||
+5
@@ -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
Reference in New Issue
Block a user