| // 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, |
| }; |