• 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 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