• 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 Script mutator.
7 */
8
9'use strict';
10
11const fs = require('fs');
12const path = require('path');
13
14const common = require('./mutators/common.js');
15const db = require('./db.js');
16const sourceHelpers = require('./source_helpers.js');
17
18const { AddTryCatchMutator } = require('./mutators/try_catch.js');
19const { ArrayMutator } = require('./mutators/array_mutator.js');
20const { CrossOverMutator } = require('./mutators/crossover_mutator.js');
21const { ExpressionMutator } = require('./mutators/expression_mutator.js');
22const { FunctionCallMutator } = require('./mutators/function_call_mutator.js');
23const { IdentifierNormalizer } = require('./mutators/normalizer.js');
24const { NumberMutator } = require('./mutators/number_mutator.js');
25const { ObjectMutator } = require('./mutators/object_mutator.js');
26const { VariableMutator } = require('./mutators/variable_mutator.js');
27const { VariableOrObjectMutator } = require('./mutators/variable_or_object_mutation.js');
28
29function defaultSettings() {
30  return {
31    ADD_VAR_OR_OBJ_MUTATIONS: 0.1,
32    DIFF_FUZZ_EXTRA_PRINT: 0.1,
33    DIFF_FUZZ_TRACK_CAUGHT: 0.4,
34    MUTATE_ARRAYS: 0.1,
35    MUTATE_CROSSOVER_INSERT: 0.05,
36    MUTATE_EXPRESSIONS: 0.1,
37    MUTATE_FUNCTION_CALLS: 0.1,
38    MUTATE_NUMBERS: 0.05,
39    MUTATE_OBJECTS: 0.1,
40    MUTATE_VARIABLES: 0.075,
41  };
42}
43
44class Result {
45  constructor(code, flags) {
46    this.code = code;
47    this.flags = flags;
48  }
49}
50
51class ScriptMutator {
52  constructor(settings, db_path=undefined) {
53    // Use process.cwd() to bypass pkg's snapshot filesystem.
54    this.mutateDb = new db.MutateDb(db_path || path.join(process.cwd(), 'db'));
55    this.mutators = [
56      new ArrayMutator(settings),
57      new ObjectMutator(settings),
58      new VariableMutator(settings),
59      new NumberMutator(settings),
60      new CrossOverMutator(settings, this.mutateDb),
61      new ExpressionMutator(settings),
62      new FunctionCallMutator(settings),
63      new VariableOrObjectMutator(settings),
64      new AddTryCatchMutator(settings),
65    ];
66  }
67
68  _addMjsunitIfNeeded(dependencies, input) {
69    if (dependencies.has('mjsunit')) {
70      return;
71    }
72
73    if (!input.absPath.includes('mjsunit')) {
74      return;
75    }
76
77    // Find mjsunit.js
78    let mjsunitPath = input.absPath;
79    while (path.dirname(mjsunitPath) != mjsunitPath &&
80           path.basename(mjsunitPath) != 'mjsunit') {
81      mjsunitPath = path.dirname(mjsunitPath);
82    }
83
84    if (path.basename(mjsunitPath) == 'mjsunit') {
85      mjsunitPath = path.join(mjsunitPath, 'mjsunit.js');
86      dependencies.set('mjsunit', sourceHelpers.loadDependencyAbs(
87          input.baseDir, mjsunitPath));
88      return;
89    }
90
91    console.log('ERROR: Failed to find mjsunit.js');
92  }
93
94  _addSpiderMonkeyShellIfNeeded(dependencies, input) {
95    // Find shell.js files
96    const shellJsPaths = new Array();
97    let currentDir = path.dirname(input.absPath);
98
99    while (path.dirname(currentDir) != currentDir) {
100      const shellJsPath = path.join(currentDir, 'shell.js');
101      if (fs.existsSync(shellJsPath)) {
102         shellJsPaths.push(shellJsPath);
103      }
104
105      if (currentDir == 'spidermonkey') {
106        break;
107      }
108      currentDir = path.dirname(currentDir);
109    }
110
111    // Add shell.js dependencies in reverse to add ones that are higher up in
112    // the directory tree first.
113    for (let i = shellJsPaths.length - 1; i >= 0; i--) {
114      if (!dependencies.has(shellJsPaths[i])) {
115        const dependency = sourceHelpers.loadDependencyAbs(
116            input.baseDir, shellJsPaths[i]);
117        dependencies.set(shellJsPaths[i], dependency);
118      }
119    }
120  }
121
122  _addJSTestStubsIfNeeded(dependencies, input) {
123    if (dependencies.has('jstest_stubs') ||
124        !input.absPath.includes('JSTests')) {
125      return;
126    }
127    dependencies.set(
128        'jstest_stubs', sourceHelpers.loadResource('jstest_stubs.js'));
129  }
130
131  mutate(source) {
132    for (const mutator of this.mutators) {
133      mutator.mutate(source);
134    }
135  }
136
137  // Returns parsed dependencies for inputs.
138  resolveInputDependencies(inputs) {
139    const dependencies = new Map();
140
141    // Resolve test harness files.
142    inputs.forEach(input => {
143      try {
144        // TODO(machenbach): Some harness files contain load expressions
145        // that are not recursively resolved. We already remove them, but we
146        // also need to load the dependencies they point to.
147        this._addJSTestStubsIfNeeded(dependencies, input);
148        this._addMjsunitIfNeeded(dependencies, input)
149        this._addSpiderMonkeyShellIfNeeded(dependencies, input);
150      } catch (e) {
151        console.log(
152            'ERROR: Failed to resolve test harness for', input.relPath);
153        throw e;
154      }
155    });
156
157    // Resolve dependencies loaded within the input files.
158    inputs.forEach(input => {
159      try {
160        input.loadDependencies(dependencies);
161      } catch (e) {
162        console.log(
163            'ERROR: Failed to resolve dependencies for', input.relPath);
164        throw e;
165      }
166    });
167
168    // Map.values() returns values in insertion order.
169    return Array.from(dependencies.values());
170  }
171
172  // Combines input dependencies with fuzzer resources.
173  resolveDependencies(inputs) {
174    const dependencies = this.resolveInputDependencies(inputs);
175
176    // Add stubs for non-standard functions in the beginning.
177    dependencies.unshift(sourceHelpers.loadResource('stubs.js'));
178
179    // Add our fuzzing support helpers. This also overrides some common test
180    // functions from earlier dependencies that cause early bailouts.
181    dependencies.push(sourceHelpers.loadResource('fuzz_library.js'));
182
183    return dependencies;
184  }
185
186  // Normalizes, combines and mutates multiple inputs.
187  mutateInputs(inputs) {
188    const normalizerMutator = new IdentifierNormalizer();
189
190    for (const [index, input] of inputs.entries()) {
191      try {
192        normalizerMutator.mutate(input);
193      } catch (e) {
194        console.log('ERROR: Failed to normalize ', input.relPath);
195        throw e;
196      }
197
198      common.setSourceLoc(input, index, inputs.length);
199    }
200
201    // Combine ASTs into one. This is so that mutations have more context to
202    // cross over content between ASTs (e.g. variables).
203    const combinedSource = common.concatPrograms(inputs);
204    this.mutate(combinedSource);
205
206    return combinedSource;
207  }
208
209  mutateMultiple(inputs) {
210    // High level operation:
211    // 1) Compute dependencies from inputs.
212    // 2) Normalize, combine and mutate inputs.
213    // 3) Generate code with dependency code prepended.
214    const dependencies = this.resolveDependencies(inputs);
215    const combinedSource = this.mutateInputs(inputs);
216    const code = sourceHelpers.generateCode(combinedSource, dependencies);
217    const flags = common.concatFlags(dependencies.concat([combinedSource]));
218    return new Result(code, flags);
219  }
220}
221
222module.exports = {
223  defaultSettings: defaultSettings,
224  ScriptMutator: ScriptMutator,
225};
226