226 lines
6.9 KiB
JavaScript
226 lines
6.9 KiB
JavaScript
|
// 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 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');
|
||
|
|
||
|
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,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
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),
|
||
|
new AddTryCatchMutator(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) {
|
||
|
for (const mutator of this.mutators) {
|
||
|
mutator.mutate(source);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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,
|
||
|
};
|