| // 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, |
| } |