| // 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 Function calls mutator. |
| */ |
| |
| 'use strict'; |
| |
| const babelTemplate = require('@babel/template').default; |
| const babelTypes = require('@babel/types'); |
| |
| const common = require('./common.js'); |
| const random = require('../random.js'); |
| const mutator = require('./mutator.js'); |
| |
| function _liftExpressionsToStatements(path, nodes) { |
| // If the node we're replacing is an expression in an expression statement, |
| // lift the replacement nodes into statements too. |
| if (!babelTypes.isExpressionStatement(path.parent)) { |
| return nodes; |
| } |
| |
| return nodes.map(n => babelTypes.expressionStatement(n)); |
| } |
| |
| class FunctionCallMutator extends mutator.Mutator { |
| constructor(settings) { |
| super(); |
| this.settings = settings; |
| } |
| |
| get visitor() { |
| const thisMutator = this; |
| |
| return { |
| CallExpression(path) { |
| if (!babelTypes.isIdentifier(path.node.callee)) { |
| return; |
| } |
| |
| if (!common.isFunctionIdentifier(path.node.callee.name)) { |
| return; |
| } |
| |
| if (!random.choose(thisMutator.settings.MUTATE_FUNCTION_CALLS)) { |
| return; |
| } |
| |
| const probability = random.random(); |
| if (probability < 0.5) { |
| const randFunc = common.randomFunction(path); |
| if (randFunc) { |
| thisMutator.annotate( |
| path.node, |
| `Replaced ${path.node.callee.name} with ${randFunc.name}`); |
| |
| path.node.callee = randFunc; |
| } |
| } else if (probability < 0.7 && thisMutator.settings.engine == 'V8') { |
| const prepareTemplate = babelTemplate( |
| '__V8BuiltinPrepareFunctionForOptimization(ID)'); |
| const optimizeTemplate = babelTemplate( |
| '__V8BuiltinOptimizeFunctionOnNextCall(ID)'); |
| |
| const nodes = [ |
| prepareTemplate({ |
| ID: babelTypes.cloneDeep(path.node.callee), |
| }).expression, |
| babelTypes.cloneDeep(path.node), |
| babelTypes.cloneDeep(path.node), |
| optimizeTemplate({ |
| ID: babelTypes.cloneDeep(path.node.callee), |
| }).expression, |
| ]; |
| |
| thisMutator.annotate( |
| path.node, |
| `Optimizing ${path.node.callee.name}`); |
| if (!babelTypes.isExpressionStatement(path.parent)) { |
| nodes.push(path.node); |
| thisMutator.replaceWithSkip( |
| path, babelTypes.sequenceExpression(nodes)); |
| } else { |
| thisMutator.insertBeforeSkip( |
| path, _liftExpressionsToStatements(path, nodes)); |
| } |
| } else if (probability < 0.85 && |
| thisMutator.settings.engine == 'V8') { |
| const template = babelTemplate( |
| '__V8BuiltinDeoptimizeFunction(ID)'); |
| const insert = _liftExpressionsToStatements(path, [ |
| template({ |
| ID: babelTypes.cloneDeep(path.node.callee), |
| }).expression, |
| ]); |
| |
| thisMutator.annotate( |
| path.node, |
| `Deoptimizing ${path.node.callee.name}`); |
| |
| thisMutator.insertAfterSkip(path, insert); |
| } else { |
| const template = babelTemplate( |
| 'runNearStackLimit(() => { return CALL });'); |
| thisMutator.annotate( |
| path.node, |
| `Run to stack limit ${path.node.callee.name}`); |
| |
| thisMutator.replaceWithSkip( |
| path, |
| template({ |
| CALL: path.node, |
| }).expression); |
| } |
| |
| path.skip(); |
| }, |
| } |
| } |
| } |
| |
| module.exports = { |
| FunctionCallMutator: FunctionCallMutator, |
| }; |