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 Common mutator utilities. 7 */ 8 9const babelTemplate = require('@babel/template').default; 10const babelTypes = require('@babel/types'); 11const babylon = require('@babel/parser'); 12 13const sourceHelpers = require('../source_helpers.js'); 14const random = require('../random.js'); 15 16const INTERESTING_NUMBER_VALUES = [ 17 -1, -0.0, 0, 1, 18 19 // Float values. 20 -0.000000000000001, 0.000000000000001, 21 22 // Special values. 23 NaN, +Infinity, -Infinity, 24 25 // Boundaries of int, signed, unsigned, SMI (near +/- 2^(30, 31, 32). 26 0x03fffffff, 0x040000000, 0x040000001, 27 -0x03fffffff, -0x040000000, -0x040000001, 28 0x07fffffff, 0x080000000, 0x080000001, 29 -0x07fffffff, -0x080000000, -0x080000001, 30 0x0ffffffff, 0x100000000, 0x100000001, 31 -0x0ffffffff, -0x100000000, -0x100000001, 32 33 // Boundaries of maximum safe integer (near +/- 2^53). 34 9007199254740990, 9007199254740991, 9007199254740992, 35 -9007199254740990, -9007199254740991, -9007199254740992, 36 37 // Boundaries of double. 38 5e-324, 1.7976931348623157e+308, 39 -5e-324,-1.7976931348623157e+308, 40] 41 42const INTERESTING_NON_NUMBER_VALUES = [ 43 // Simple arrays. 44 '[]', 45 'Array(0x8000).fill("a")', 46 47 // Simple object. 48 '{}', 49 '{a: "foo", b: 10, c: {}}', 50 51 // Simple strings. 52 '"foo"', 53 '""', 54 55 // Simple regex. 56 '/0/', 57 '"/0/"', 58 59 // Simple symbol. 60 'Symbol("foo")', 61 62 // Long string. 63 'Array(0x8000).join("a")', 64 65 // Math.PI 66 'Math.PI', 67 68 // Others. 69 'false', 70 'true', 71 'undefined', 72 'null', 73 'this', 74 'this[0]', 75 'this[1]', 76 77 // Empty function. 78 '(function() {return 0;})', 79 80 // Objects with functions. 81 '({toString:function(){return "0";}})', 82 '({valueOf:function(){return 0;}})', 83 '({valueOf:function(){return "0";}})', 84 85 // Objects for primitive types created using new. 86 '(new Boolean(false))', 87 '(new Boolean(true))', 88 '(new String(""))', 89 '(new Number(0))', 90 '(new Number(-0))', 91] 92 93const LARGE_NODE_SIZE = 100; 94const MAX_ARGUMENT_COUNT = 10; 95 96function _identifier(identifier) { 97 return babelTypes.identifier(identifier); 98} 99 100function _numericLiteral(number) { 101 return babelTypes.numericLiteral(number); 102} 103 104function _unwrapExpressionStatement(value) { 105 if (babelTypes.isExpressionStatement(value)) { 106 return value.expression; 107 } 108 109 return value; 110} 111 112function isVariableIdentifier(name) { 113 return /__v_[0-9]+/.test(name); 114} 115 116function isFunctionIdentifier(name) { 117 return /__f_[0-9]+/.test(name); 118} 119 120function isInForLoopCondition(path) { 121 // Return whether if we're in the init/test/update parts of a for loop (but 122 // not the body). Mutating variables in the init/test/update will likely 123 // modify loop variables and cause infinite loops. 124 const forStatementChild = path.find( 125 p => p.parent && babelTypes.isForStatement(p.parent)); 126 127 return (forStatementChild && forStatementChild.parentKey !== 'body'); 128} 129 130function isInWhileLoop(path) { 131 // Return whether if we're in a while loop. 132 const whileStatement = path.find(p => babelTypes.isWhileStatement(p)); 133 return Boolean(whileStatement); 134} 135 136function _availableIdentifiers(path, filter) { 137 // TODO(ochang): Consider globals that aren't declared with let/var etc. 138 const available = new Array(); 139 const allBindings = path.scope.getAllBindings(); 140 for (const key of Object.keys(allBindings)) { 141 if (!filter(key)) { 142 continue; 143 } 144 145 if (filter === isVariableIdentifier && 146 path.willIMaybeExecuteBefore(allBindings[key].path)) { 147 continue; 148 } 149 150 available.push(_identifier(key)); 151 } 152 153 return available; 154} 155 156function availableVariables(path) { 157 return _availableIdentifiers(path, isVariableIdentifier); 158} 159 160function availableFunctions(path) { 161 return _availableIdentifiers(path, isFunctionIdentifier); 162} 163 164function randomVariable(path) { 165 return random.single(availableVariables(path)); 166} 167 168function randomFunction(path) { 169 return random.single(availableFunctions(path)); 170} 171 172function randomSeed() { 173 return random.randInt(0, 2**20); 174} 175 176function randomObject(seed) { 177 if (seed === undefined) { 178 seed = randomSeed(); 179 } 180 181 const template = babelTemplate('__getRandomObject(SEED)'); 182 return template({ 183 SEED: _numericLiteral(seed), 184 }).expression; 185} 186 187function randomProperty(identifier, seed) { 188 if (seed === undefined) { 189 seed = randomSeed(); 190 } 191 192 const template = babelTemplate('__getRandomProperty(IDENTIFIER, SEED)'); 193 return template({ 194 IDENTIFIER: identifier, 195 SEED: _numericLiteral(seed), 196 }).expression; 197} 198 199function randomArguments(path) { 200 const numArgs = random.randInt(0, MAX_ARGUMENT_COUNT); 201 const args = []; 202 203 for (let i = 0; i < numArgs; i++) { 204 args.push(randomValue(path)); 205 } 206 207 return args.map(_unwrapExpressionStatement); 208} 209 210function randomValue(path) { 211 const probability = random.random(); 212 213 if (probability < 0.01) { 214 const randomFunc = randomFunction(path); 215 if (randomFunc) { 216 return randomFunc; 217 } 218 } 219 220 if (probability < 0.25) { 221 const randomVar = randomVariable(path); 222 if (randomVar) { 223 return randomVar; 224 } 225 } 226 227 if (probability < 0.5) { 228 return randomInterestingNumber(); 229 } 230 231 if (probability < 0.75) { 232 return randomInterestingNonNumber(); 233 } 234 235 return randomObject(); 236} 237 238function callRandomFunction(path, identifier, seed) { 239 if (seed === undefined) { 240 seed = randomSeed(); 241 } 242 243 let args = [ 244 identifier, 245 _numericLiteral(seed) 246 ]; 247 248 args = args.map(_unwrapExpressionStatement); 249 args = args.concat(randomArguments(path)); 250 251 return babelTypes.callExpression( 252 babelTypes.identifier('__callRandomFunction'), 253 args); 254} 255 256function nearbyRandomNumber(value) { 257 const probability = random.random(); 258 259 if (probability < 0.9) { 260 return _numericLiteral(value + random.randInt(-0x10, 0x10)); 261 } else if (probability < 0.95) { 262 return _numericLiteral(value + random.randInt(-0x100, 0x100)); 263 } else if (probability < 0.99) { 264 return _numericLiteral(value + random.randInt(-0x1000, 0x1000)); 265 } 266 267 return _numericLiteral(value + random.randInt(-0x10000, 0x10000)); 268} 269 270function randomInterestingNumber() { 271 const value = random.single(INTERESTING_NUMBER_VALUES); 272 if (random.choose(0.05)) { 273 return nearbyRandomNumber(value); 274 } 275 return _numericLiteral(value); 276} 277 278function randomInterestingNonNumber() { 279 return babylon.parseExpression(random.single(INTERESTING_NON_NUMBER_VALUES)); 280} 281 282function concatFlags(inputs) { 283 const flags = new Set(); 284 for (const input of inputs) { 285 for (const flag of input.flags || []) { 286 flags.add(flag); 287 } 288 } 289 return Array.from(flags.values()); 290} 291 292function concatPrograms(inputs) { 293 // Concatentate programs. 294 const resultProgram = babelTypes.program([]); 295 const result = babelTypes.file(resultProgram, [], null); 296 297 for (const input of inputs) { 298 const ast = input.ast.program; 299 resultProgram.body = resultProgram.body.concat(ast.body); 300 resultProgram.directives = resultProgram.directives.concat(ast.directives); 301 } 302 303 // TODO(machenbach): Concat dependencies here as soon as they are cached. 304 const combined = new sourceHelpers.ParsedSource( 305 result, '', '', concatFlags(inputs)); 306 // If any input file is sloppy, the combined result is sloppy. 307 combined.sloppy = inputs.some(input => input.isSloppy()); 308 return combined; 309} 310 311function setSourceLoc(source, index, total) { 312 const noop = babelTypes.noop(); 313 noop.__loc = index / total; 314 noop.__self = noop; 315 source.ast.program.body.unshift(noop); 316} 317 318function getSourceLoc(node) { 319 // Source location is invalid in cloned nodes. 320 if (node !== node.__self) { 321 return undefined; 322 } 323 return node.__loc; 324} 325 326function setOriginalPath(source, originalPath) { 327 const noop = babelTypes.noop(); 328 noop.__path = originalPath; 329 noop.__self = noop; 330 source.ast.program.body.unshift(noop); 331} 332 333function getOriginalPath(node) { 334 // Original path is invalid in cloned nodes. 335 if (node !== node.__self) { 336 return undefined; 337 } 338 return node.__path; 339} 340 341// Estimate the size of a node in raw source characters. 342function isLargeNode(node) { 343 // Ignore array holes inserted by us (null) or previously cloned nodes 344 // (they have no start/end). 345 if (!node || node.start === undefined || node.end === undefined ) { 346 return false; 347 } 348 return node.end - node.start > LARGE_NODE_SIZE; 349} 350 351module.exports = { 352 callRandomFunction: callRandomFunction, 353 concatFlags: concatFlags, 354 concatPrograms: concatPrograms, 355 availableVariables: availableVariables, 356 availableFunctions: availableFunctions, 357 randomFunction: randomFunction, 358 randomVariable: randomVariable, 359 isInForLoopCondition: isInForLoopCondition, 360 isInWhileLoop: isInWhileLoop, 361 isLargeNode: isLargeNode, 362 isVariableIdentifier: isVariableIdentifier, 363 isFunctionIdentifier: isFunctionIdentifier, 364 nearbyRandomNumber: nearbyRandomNumber, 365 randomArguments: randomArguments, 366 randomInterestingNonNumber: randomInterestingNonNumber, 367 randomInterestingNumber: randomInterestingNumber, 368 randomObject: randomObject, 369 randomProperty: randomProperty, 370 randomSeed: randomSeed, 371 randomValue: randomValue, 372 getOriginalPath: getOriginalPath, 373 setOriginalPath: setOriginalPath, 374 getSourceLoc: getSourceLoc, 375 setSourceLoc: setSourceLoc, 376} 377