blob: 7906b4f1a053403763ee2d477c872e80ebc77891 [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 Mutator for object expressions.
*/
'use strict';
const babelTypes = require('@babel/types');
const common = require('./common.js');
const mutator = require('./mutator.js');
const random = require('../random.js');
const MAX_PROPERTIES = 50;
/**
* Turn the key of an object property into a string literal.
*/
function keyToString(key) {
if (babelTypes.isNumericLiteral(key)) {
return babelTypes.stringLiteral(key.value.toString());
}
if (babelTypes.isIdentifier(key)) {
return babelTypes.stringLiteral(key.name);
}
// Already a string literal.
return key;
}
class ObjectMutator extends mutator.Mutator {
constructor(settings) {
super();
this.settings = settings;
}
get visitor() {
const thisMutator = this;
return {
ObjectExpression(path) {
const properties = path.node.properties;
if (!random.choose(thisMutator.settings.MUTATE_OBJECTS) ||
properties.length > MAX_PROPERTIES) {
return;
}
// Use the indices of object properties for mutations. We ignore
// getters and setters.
const propertyIndicies = [];
for (const [index, property] of properties.entries()) {
if (babelTypes.isObjectProperty(property)) {
propertyIndicies.push(index);
}
}
// The mutations below require at least one property.
if (!propertyIndicies.length) {
return;
}
// Annotate object expression with the action taken.
function annotate(message) {
thisMutator.annotate(path.node, message);
}
function getOneRandomProperty() {
return properties[random.single(propertyIndicies)];
}
function getTwoRandomProperties() {
const [a, b] = random.sample(propertyIndicies, 2);
return [properties[a], properties[b]];
}
function swapPropertyValues() {
if (propertyIndicies.length > 1) {
annotate('Swap properties');
const [a, b] = getTwoRandomProperties();
[a.value, b.value] = [b.value, a.value];
}
}
function duplicatePropertyValue() {
if (propertyIndicies.length > 1) {
const [a, b] = random.shuffle(getTwoRandomProperties());
if (common.isLargeNode(b.value)) {
return;
}
annotate('Duplicate a property value');
a.value = babelTypes.cloneDeep(b.value);
}
}
function insertRandomValue() {
annotate('Insert a random value');
const property = getOneRandomProperty();
property.value = common.randomValue(path);
}
function stringifyKey() {
annotate('Stringify a property key');
const property = getOneRandomProperty();
property.key = keyToString(property.key);
}
function removeProperty() {
annotate('Remove a property');
properties.splice(random.single(propertyIndicies), 1);
}
// Mutation options. Repeated mutations have a higher probability.
const mutations = [
swapPropertyValues,
swapPropertyValues,
duplicatePropertyValue,
duplicatePropertyValue,
insertRandomValue,
insertRandomValue,
removeProperty,
stringifyKey,
];
// Perform mutation.
random.single(mutations)();
},
}
}
}
module.exports = {
ObjectMutator: ObjectMutator,
};