blob: 57f81f2a0074a7b3cb89a88515813864ad2df527 [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 Try catch wrapper.
*/
const babelTypes = require('@babel/types');
const common = require('./common.js');
const mutator = require('./mutator.js');
const random = require('../random.js');
// Default target probability for skipping try-catch completely.
const DEFAULT_SKIP_PROB = 0.2;
// Default target probability to wrap only on toplevel, i.e. to not nest
// try-catch.
const DEFAULT_TOPLEVEL_PROB = 0.3;
// Probability to deviate from defaults and use extreme cases.
const IGNORE_DEFAULT_PROB = 0.05;
// Member expressions to be wrapped. List of (object, property) identifier
// tuples.
const WRAPPED_MEMBER_EXPRESSIONS = [
['WebAssembly', 'Module'],
['WebAssembly', 'Instantiate'],
];
function wrapTryCatch(node) {
return babelTypes.tryStatement(
babelTypes.blockStatement([node]),
babelTypes.catchClause(
babelTypes.identifier('e'),
babelTypes.blockStatement([])));
}
function wrapTryCatchInFunction(node) {
const ret = wrapTryCatch(babelTypes.returnStatement(node));
const anonymousFun = babelTypes.functionExpression(
null, [], babelTypes.blockStatement([ret]));
return babelTypes.callExpression(anonymousFun, []);
}
// Wrap particular member expressions after `new` that are known to appear
// in initializer lists of `let` and `const`.
function replaceNewExpression(path) {
const callee = path.node.callee;
if (!babelTypes.isMemberExpression(callee) ||
!babelTypes.isIdentifier(callee.object) ||
!babelTypes.isIdentifier(callee.property)) {
return;
}
if (WRAPPED_MEMBER_EXPRESSIONS.some(
([object, property]) => callee.object.name === object &&
callee.property.name === property)) {
path.replaceWith(wrapTryCatchInFunction(path.node));
path.skip();
}
}
function replaceAndSkip(path) {
if (!babelTypes.isLabeledStatement(path.parent) ||
!babelTypes.isLoop(path.node)) {
// Don't wrap loops with labels as it makes continue
// statements syntactically invalid. We wrap the label
// instead below.
path.replaceWith(wrapTryCatch(path.node));
}
// Prevent infinite looping.
path.skip();
}
class AddTryCatchMutator extends mutator.Mutator {
callWithProb(path, fun) {
const probability = random.random();
if (probability < this.skipProb * this.loc) {
// Entirely skip try-catch wrapper.
path.skip();
} else if (probability < (this.skipProb + this.toplevelProb) * this.loc) {
// Only wrap on top-level.
fun(path);
}
}
get visitor() {
const thisMutator = this;
const accessStatement = {
enter(path) {
thisMutator.callWithProb(path, replaceAndSkip);
},
exit(path) {
// Apply nested wrapping (is only executed if not skipped above).
replaceAndSkip(path);
}
};
return {
Program: {
enter(path) {
// Track original source location fraction in [0, 1).
thisMutator.loc = 0;
// Target probability for skipping try-catch.
thisMutator.skipProb = DEFAULT_SKIP_PROB;
// Target probability for not nesting try-catch.
thisMutator.toplevelProb = DEFAULT_TOPLEVEL_PROB;
// Maybe deviate from target probability for the entire test.
if (random.choose(IGNORE_DEFAULT_PROB)) {
thisMutator.skipProb = random.uniform(0, 1);
thisMutator.toplevelProb = random.uniform(0, 1);
thisMutator.annotate(
path.node,
'Target skip probability ' + thisMutator.skipProb +
' and toplevel probability ' + thisMutator.toplevelProb);
}
}
},
Noop: {
enter(path) {
if (common.getSourceLoc(path.node)) {
thisMutator.loc = common.getSourceLoc(path.node);
}
},
},
ExpressionStatement: accessStatement,
IfStatement: accessStatement,
LabeledStatement: {
enter(path) {
// Apply an extra try-catch around the label of a loop, since we
// ignore the loop itself if it has a label.
if (babelTypes.isLoop(path.node.body)) {
thisMutator.callWithProb(path, replaceAndSkip);
}
},
exit(path) {
// Apply nested wrapping (is only executed if not skipped above).
if (babelTypes.isLoop(path.node.body)) {
replaceAndSkip(path);
}
},
},
// This covers {While|DoWhile|ForIn|ForOf|For}Statement.
Loop: accessStatement,
NewExpression: {
enter(path) {
thisMutator.callWithProb(path, replaceNewExpression);
},
exit(path) {
// Apply nested wrapping (is only executed if not skipped above).
replaceNewExpression(path);
}
},
SwitchStatement: accessStatement,
VariableDeclaration: {
enter(path) {
if (path.node.kind !== 'var' || babelTypes.isLoop(path.parent))
return;
thisMutator.callWithProb(path, replaceAndSkip);
},
exit(path) {
if (path.node.kind !== 'var' || babelTypes.isLoop(path.parent))
return;
// Apply nested wrapping (is only executed if not skipped above).
replaceAndSkip(path);
}
},
WithStatement: accessStatement,
};
}
}
module.exports = {
AddTryCatchMutator: AddTryCatchMutator,
}