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