blob: c0f3ea8d93c131e238a710d6420eaa4c427772c2 [file] [log] [blame]
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Numbers mutator.
*/
'use strict';
const babelTypes = require('@babel/types');
const common = require('./common.js');
const random = require('../random.js');
const mutator = require('./mutator.js');
const MIN_SAFE_INTEGER = -9007199254740991;
const MAX_SAFE_INTEGER = 9007199254740991;
function isObjectKey(path) {
return (path.parent &&
babelTypes.isObjectMember(path.parent) &&
path.parent.key === path.node);
}
function createRandomNumber(value) {
// TODO(ochang): Maybe replace with variable.
const probability = random.random();
if (probability < 0.01) {
return babelTypes.numericLiteral(
random.randInt(MIN_SAFE_INTEGER, MAX_SAFE_INTEGER));
} else if (probability < 0.06) {
return common.randomInterestingNumber();
} else {
return common.nearbyRandomNumber(value);
}
}
class NumberMutator extends mutator.Mutator {
constructor(settings) {
super();
this.settings = settings;
}
ignore(path) {
return !random.choose(this.settings.MUTATE_NUMBERS) ||
common.isInForLoopCondition(path) ||
common.isInWhileLoop(path);
}
randomReplace(path, value, forcePositive=false) {
const randomNumber = createRandomNumber(value);
if (forcePositive) {
randomNumber.value = Math.abs(randomNumber.value);
}
this.annotate(
path.node,
`Replaced ${value} with ${randomNumber.value}`);
this.replaceWithSkip(path, randomNumber);
}
get visitor() {
const thisMutator = this;
return {
NumericLiteral(path) {
if (thisMutator.ignore(path)) {
return;
}
// We handle negative unary expressions separately to replace the whole
// expression below. E.g. -5 is UnaryExpression(-, NumericLiteral(5)).
if (path.parent && babelTypes.isUnaryExpression(path.parent) &&
path.parent.operator === '-') {
return;
}
// Enfore positive numbers if the literal is the key of an object
// property or method. Negative keys cause syntax errors.
const forcePositive = isObjectKey(path);
thisMutator.randomReplace(path, path.node.value, forcePositive);
},
UnaryExpression(path) {
if (thisMutator.ignore(path)) {
return;
}
// Handle the case we ignore above.
if (path.node.operator === '-' &&
babelTypes.isNumericLiteral(path.node.argument)) {
thisMutator.randomReplace(path, -path.node.argument.value);
}
}
};
}
}
module.exports = {
NumberMutator: NumberMutator,
};