• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2020 the V8 project authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5/**
6 * @fileoverview Description of this file.
7 */
8
9'use strict';
10
11const assert = require('assert');
12const fs = require('fs');
13const path = require('path');
14
15const program = require('commander');
16
17const corpus = require('./corpus.js');
18const differentialScriptMutator = require('./differential_script_mutator.js');
19const random = require('./random.js');
20const scriptMutator = require('./script_mutator.js');
21const sourceHelpers = require('./source_helpers.js');
22
23// Maximum number of test inputs to use for one fuzz test.
24const MAX_TEST_INPUTS_PER_TEST = 10;
25
26// Base implementations for default or differential fuzzing.
27const SCRIPT_MUTATORS = {
28  default: scriptMutator.ScriptMutator,
29  foozzie: differentialScriptMutator.DifferentialScriptMutator,
30};
31
32function getRandomInputs(primaryCorpus, secondaryCorpora, count) {
33  count = random.randInt(2, count);
34
35  // Choose 40%-80% of inputs from primary corpus.
36  const primaryCount = Math.floor(random.uniform(0.4, 0.8) * count);
37  count -= primaryCount;
38
39  let inputs = primaryCorpus.getRandomTestcases(primaryCount);
40
41  // Split remainder equally between the secondary corpora.
42  const secondaryCount = Math.floor(count / secondaryCorpora.length);
43
44  for (let i = 0; i < secondaryCorpora.length; i++) {
45    let currentCount = secondaryCount;
46    if (i == secondaryCorpora.length - 1) {
47      // Last one takes the remainder.
48      currentCount = count;
49    }
50
51    count -= currentCount;
52    if (currentCount) {
53      inputs = inputs.concat(
54          secondaryCorpora[i].getRandomTestcases(currentCount));
55    }
56  }
57
58  return random.shuffle(inputs);
59}
60
61function collect(value, total) {
62  total.push(value);
63  return total;
64}
65
66function overrideSettings(settings, settingOverrides) {
67  for (const setting of settingOverrides) {
68    const parts = setting.split('=');
69    settings[parts[0]] = parseFloat(parts[1]);
70  }
71}
72
73function* randomInputGen(engine) {
74  const inputDir = path.resolve(program.input_dir);
75
76  const v8Corpus = new corpus.Corpus(inputDir, 'v8');
77  const chakraCorpus = new corpus.Corpus(inputDir, 'chakra');
78  const spiderMonkeyCorpus = new corpus.Corpus(inputDir, 'spidermonkey');
79  const jscCorpus = new corpus.Corpus(inputDir, 'WebKit/JSTests');
80  const crashTestsCorpus = new corpus.Corpus(inputDir, 'CrashTests');
81
82  for (let i = 0; i < program.no_of_files; i++) {
83    let inputs;
84    if (engine === 'V8') {
85      inputs = getRandomInputs(
86          v8Corpus,
87          random.shuffle([chakraCorpus, spiderMonkeyCorpus, jscCorpus,
88                          crashTestsCorpus, v8Corpus]),
89          MAX_TEST_INPUTS_PER_TEST);
90    } else if (engine == 'chakra') {
91      inputs = getRandomInputs(
92          chakraCorpus,
93          random.shuffle([v8Corpus, spiderMonkeyCorpus, jscCorpus,
94                          crashTestsCorpus]),
95          MAX_TEST_INPUTS_PER_TEST);
96    } else if (engine == 'spidermonkey') {
97      inputs = getRandomInputs(
98          spiderMonkeyCorpus,
99          random.shuffle([v8Corpus, chakraCorpus, jscCorpus,
100                          crashTestsCorpus]),
101          MAX_TEST_INPUTS_PER_TEST);
102    } else {
103      inputs = getRandomInputs(
104          jscCorpus,
105          random.shuffle([chakraCorpus, spiderMonkeyCorpus, v8Corpus,
106                          crashTestsCorpus]),
107          MAX_TEST_INPUTS_PER_TEST);
108    }
109
110    if (inputs.length > 0) {
111      yield inputs;
112    }
113  }
114}
115
116function* corpusInputGen() {
117  const inputCorpus = new corpus.Corpus(
118      path.resolve(program.input_dir),
119      program.mutate_corpus,
120      program.extra_strict);
121  for (const input of inputCorpus.getAllTestcases()) {
122    yield [input];
123  }
124}
125
126function* enumerate(iterable) {
127  let i = 0;
128  for (const value of iterable) {
129    yield [i, value];
130    i++;
131  }
132}
133
134function main() {
135  Error.stackTraceLimit = Infinity;
136
137  program
138    .version('0.0.1')
139    .option('-i, --input_dir <path>', 'Input directory.')
140    .option('-o, --output_dir <path>', 'Output directory.')
141    .option('-n, --no_of_files <n>', 'Output directory.', parseInt)
142    .option('-c, --mutate_corpus <name>', 'Mutate single files in a corpus.')
143    .option('-e, --extra_strict', 'Additionally parse files in strict mode.')
144    .option('-m, --mutate <path>', 'Mutate a file and output results.')
145    .option('-s, --setting [setting]', 'Settings overrides.', collect, [])
146    .option('-v, --verbose', 'More verbose printing.')
147    .option('-z, --zero_settings', 'Zero all settings.')
148    .parse(process.argv);
149
150  const settings = scriptMutator.defaultSettings();
151  if (program.zero_settings) {
152    for (const key of Object.keys(settings)) {
153      settings[key] = 0.0;
154    }
155  }
156
157  if (program.setting.length > 0) {
158    overrideSettings(settings, program.setting);
159  }
160
161  let app_name = process.env.APP_NAME;
162  if (app_name && app_name.endsWith('.exe')) {
163    app_name = app_name.substr(0, app_name.length - 4);
164  }
165
166  if (app_name === 'd8' ||
167      app_name === 'v8_simple_inspector_fuzzer' ||
168      app_name === 'v8_foozzie.py') {
169    // V8 supports running the raw d8 executable, the inspector fuzzer or
170    // the differential fuzzing harness 'foozzie'.
171    settings.engine = 'V8';
172  } else if (app_name === 'ch') {
173    settings.engine = 'chakra';
174  } else if (app_name === 'js') {
175    settings.engine = 'spidermonkey';
176  } else if (app_name === 'jsc') {
177    settings.engine = 'jsc';
178  } else {
179    console.log('ERROR: Invalid APP_NAME');
180    process.exit(1);
181  }
182
183  const mode = process.env.FUZZ_MODE || 'default';
184  assert(mode in SCRIPT_MUTATORS, `Unknown mode ${mode}`);
185  const mutator = new SCRIPT_MUTATORS[mode](settings);
186
187  if (program.mutate) {
188    const absPath = path.resolve(program.mutate);
189    const baseDir = path.dirname(absPath);
190    const fileName = path.basename(absPath);
191    const input = sourceHelpers.loadSource(
192        baseDir, fileName, program.extra_strict);
193    const mutated = mutator.mutateMultiple([input]);
194    console.log(mutated.code);
195    return;
196  }
197
198  let inputGen;
199
200  if (program.mutate_corpus) {
201    inputGen = corpusInputGen();
202  } else {
203    inputGen = randomInputGen(settings.engine);
204  }
205
206  for (const [i, inputs] of enumerate(inputGen)) {
207    const outputPath = path.join(program.output_dir, 'fuzz-' + i + '.js');
208
209    const start = Date.now();
210    const paths = inputs.map(input => input.relPath);
211
212    try {
213      const mutated = mutator.mutateMultiple(inputs);
214      fs.writeFileSync(outputPath, mutated.code);
215
216      if (settings.engine === 'V8' && mutated.flags && mutated.flags.length > 0) {
217        const flagsPath = path.join(program.output_dir, 'flags-' + i + '.js');
218        fs.writeFileSync(flagsPath, mutated.flags.join(' '));
219      }
220    } catch (e) {
221      if (e.message.startsWith('ENOSPC')) {
222        console.log('ERROR: No space left. Bailing out...');
223        console.log(e);
224        return;
225      }
226      console.log(`ERROR: Exception during mutate: ${paths}`);
227      console.log(e);
228      continue;
229    } finally {
230      if (program.verbose) {
231        const duration = Date.now() - start;
232        console.log(`Mutating ${paths} took ${duration} ms.`);
233      }
234    }
235    if ((i + 1)  % 10 == 0) {
236      console.log('Up to ', i + 1);
237    }
238  }
239}
240
241main();
242