PR-URL: https://github.com/nodejs/node/pull/58070 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Darshan Sen <raisinten@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com>
238 lines
6.7 KiB
JavaScript
238 lines
6.7 KiB
JavaScript
// Copyright 2024 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 Main execution flow.
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
const path = require('path');
|
|
|
|
const db = require('./db.js');
|
|
const corpus = require('./corpus.js');
|
|
const random = require('./random.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;
|
|
const MAX_WASM_TEST_INPUTS_PER_TEST = 5;
|
|
|
|
/**
|
|
* Returns an array of maxium `count` parsed input sources, randomly
|
|
* selected from a primary corpus and a list of secondary corpora.
|
|
*/
|
|
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);
|
|
}
|
|
|
|
class Runner {
|
|
*inputGen() {
|
|
throw new Error("Not implemented.");
|
|
}
|
|
|
|
*enumerateInputs() {
|
|
let i = 0;
|
|
for (const value of this.inputGen()) {
|
|
yield [i, value];
|
|
i++;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runner that randomly selects a number of tests from all corpora.
|
|
*/
|
|
class RandomCorpusRunner extends Runner {
|
|
constructor(inputDir, primary, numFiles,
|
|
maxTestInputs=MAX_TEST_INPUTS_PER_TEST) {
|
|
super();
|
|
inputDir = path.resolve(inputDir);
|
|
this.primary = primary;
|
|
this.numFiles = numFiles;
|
|
this.maxTestInputs = maxTestInputs;
|
|
this.corpora = {
|
|
'v8': corpus.create(inputDir, 'v8'),
|
|
'chakra': corpus.create(inputDir, 'chakra'),
|
|
'spidermonkey': corpus.create(inputDir, 'spidermonkey'),
|
|
'jsc': corpus.create(inputDir, 'WebKit/JSTests'),
|
|
'crash': corpus.create(inputDir, 'CrashTests'),
|
|
};
|
|
}
|
|
|
|
*inputGen() {
|
|
const primary = this.corpora[this.primary];
|
|
const secondary = Object.values(this.corpora);
|
|
|
|
for (let i = 0; i < this.numFiles; i++) {
|
|
const inputs = getRandomInputs(
|
|
primary,
|
|
random.shuffle(secondary),
|
|
this.maxTestInputs);
|
|
|
|
if (inputs.length > 0) {
|
|
yield inputs;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Like above, including the Fuzzilli corpus.
|
|
*/
|
|
class RandomCorpusRunnerWithFuzzilli extends RandomCorpusRunner {
|
|
constructor(inputDir, primary, numFiles,
|
|
maxTestInputs=MAX_TEST_INPUTS_PER_TEST) {
|
|
super(inputDir, primary, numFiles, maxTestInputs);
|
|
this.corpora['fuzzilli'] = corpus.create(
|
|
inputDir, 'fuzzilli', false, this.corpora['v8']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runner that randomly selects Wasm cases from V8 and Fuzzilli.
|
|
*/
|
|
class RandomWasmCorpusRunner extends Runner {
|
|
constructor(inputDir, engine, numFiles,
|
|
maxTestInputs=MAX_WASM_TEST_INPUTS_PER_TEST) {
|
|
super();
|
|
this.numFiles = numFiles;
|
|
this.maxTestInputs = maxTestInputs;
|
|
|
|
// Bias a bit towards the V8 corpus.
|
|
const v8Corpus = corpus.create(inputDir, 'v8_wasm');
|
|
const fuzzilliCorpus = corpus.create(
|
|
inputDir, 'fuzzilli_wasm', false, v8Corpus);
|
|
this.corpora = [v8Corpus, v8Corpus, fuzzilliCorpus];
|
|
}
|
|
|
|
*inputGen() {
|
|
for (let i = 0; i < this.numFiles; i++) {
|
|
const count = random.randInt(1, this.maxTestInputs);
|
|
const inputs = [];
|
|
for (let j= 0; j < count; j++) {
|
|
inputs.push(...random.single(this.corpora).getRandomTestcases(1));
|
|
}
|
|
|
|
if (inputs.length > 0) {
|
|
yield inputs;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runner that enumerates all tests from a particular corpus.
|
|
*/
|
|
class SingleCorpusRunner extends Runner {
|
|
constructor(inputDir, corpusName, extraStrict) {
|
|
super();
|
|
this.corpus = corpus.create(
|
|
path.resolve(inputDir), corpusName, extraStrict);
|
|
}
|
|
|
|
*inputGen() {
|
|
for (const input of this.corpus.getAllTestcases()) {
|
|
yield [input];
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runner that enumerates random cases from the Fuzzilli corpus without
|
|
* repeats and without cases from the crashes directory.
|
|
*/
|
|
class RandomFuzzilliNoCrashCorpusRunner extends Runner {
|
|
constructor(inputDir, engine, numFiles) {
|
|
super();
|
|
this.numFiles = numFiles;
|
|
|
|
// We need a V8 corpus placeholder only to cross-load dependencies
|
|
// from there, e.g. the wasm module builder.
|
|
const v8Corpus = corpus.create(inputDir, 'v8');
|
|
this.corpus = corpus.create(
|
|
inputDir, 'fuzzilli_no_crash', false, v8Corpus);
|
|
}
|
|
|
|
*inputGen() {
|
|
// The 'permittedFiles' are already shuffled. Just using the first x
|
|
// files is random enough.
|
|
for (const relPath of this.corpus.permittedFiles.slice(0, this.numFiles)) {
|
|
const source = this.corpus.loadTestcase(relPath, false, 'sloppy');
|
|
if (source) {
|
|
yield [source];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Runner that enumerates a fixture case combined with a snippet from the DB
|
|
* for validation.
|
|
*/
|
|
class FixtureRunner extends Runner {
|
|
constructor(inputDir, engine, numFiles) {
|
|
// This runner ignores the usual inputs and always iterates over all
|
|
// snippets from the DB.
|
|
super();
|
|
this.dbPath = path.join(process.cwd(), 'db');
|
|
}
|
|
|
|
*inputGen() {
|
|
const mutateDb = new db.MutateDb(this.dbPath);
|
|
for (const expression of mutateDb.iterateStatements()) {
|
|
let templates;
|
|
if (expression.needsSuper) {
|
|
// One template for an insertion in a constructor and one in a method.
|
|
templates = ['cross_over_template_2.js', 'cross_over_template_3.js'];
|
|
} else {
|
|
templates = ['cross_over_template_1.js'];
|
|
}
|
|
|
|
for (const tmplName of templates) {
|
|
const tmpl = sourceHelpers.loadSource(
|
|
sourceHelpers.BASE_CORPUS, path.join('resources', tmplName));
|
|
// We glue the expression to the source. It's otherwise rather hard to
|
|
// pass this through into the mutation phases.
|
|
tmpl.__expression = expression;
|
|
yield [tmpl];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
FixtureRunner: FixtureRunner,
|
|
RandomCorpusRunner: RandomCorpusRunner,
|
|
RandomCorpusRunnerWithFuzzilli: RandomCorpusRunnerWithFuzzilli,
|
|
RandomFuzzilliNoCrashCorpusRunner: RandomFuzzilliNoCrashCorpusRunner,
|
|
RandomWasmCorpusRunner: RandomWasmCorpusRunner,
|
|
SingleCorpusRunner: SingleCorpusRunner,
|
|
};
|