differential_script_mutator.js 5.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 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,
};