1// Copyright 2020 the V8 project authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5/** 6 * @fileoverview Try catch wrapper. 7 */ 8 9const babelTypes = require('@babel/types'); 10 11const common = require('./common.js'); 12const mutator = require('./mutator.js'); 13const random = require('../random.js'); 14 15// Default target probability for skipping try-catch completely. 16const DEFAULT_SKIP_PROB = 0.2; 17 18// Default target probability to wrap only on toplevel, i.e. to not nest 19// try-catch. 20const DEFAULT_TOPLEVEL_PROB = 0.3; 21 22// Probability to deviate from defaults and use extreme cases. 23const IGNORE_DEFAULT_PROB = 0.05; 24 25// Member expressions to be wrapped. List of (object, property) identifier 26// tuples. 27const WRAPPED_MEMBER_EXPRESSIONS = [ 28 ['WebAssembly', 'Module'], 29 ['WebAssembly', 'Instantiate'], 30]; 31 32function wrapTryCatch(node) { 33 return babelTypes.tryStatement( 34 babelTypes.blockStatement([node]), 35 babelTypes.catchClause( 36 babelTypes.identifier('e'), 37 babelTypes.blockStatement([]))); 38} 39 40function wrapTryCatchInFunction(node) { 41 const ret = wrapTryCatch(babelTypes.returnStatement(node)); 42 const anonymousFun = babelTypes.functionExpression( 43 null, [], babelTypes.blockStatement([ret])); 44 return babelTypes.callExpression(anonymousFun, []); 45} 46 47// Wrap particular member expressions after `new` that are known to appear 48// in initializer lists of `let` and `const`. 49function replaceNewExpression(path) { 50 const callee = path.node.callee; 51 if (!babelTypes.isMemberExpression(callee) || 52 !babelTypes.isIdentifier(callee.object) || 53 !babelTypes.isIdentifier(callee.property)) { 54 return; 55 } 56 if (WRAPPED_MEMBER_EXPRESSIONS.some( 57 ([object, property]) => callee.object.name === object && 58 callee.property.name === property)) { 59 path.replaceWith(wrapTryCatchInFunction(path.node)); 60 path.skip(); 61 } 62} 63 64function replaceAndSkip(path) { 65 if (!babelTypes.isLabeledStatement(path.parent) || 66 !babelTypes.isLoop(path.node)) { 67 // Don't wrap loops with labels as it makes continue 68 // statements syntactically invalid. We wrap the label 69 // instead below. 70 path.replaceWith(wrapTryCatch(path.node)); 71 } 72 // Prevent infinite looping. 73 path.skip(); 74} 75 76class AddTryCatchMutator extends mutator.Mutator { 77 callWithProb(path, fun) { 78 const probability = random.random(); 79 if (probability < this.skipProb * this.loc) { 80 // Entirely skip try-catch wrapper. 81 path.skip(); 82 } else if (probability < (this.skipProb + this.toplevelProb) * this.loc) { 83 // Only wrap on top-level. 84 fun(path); 85 } 86 } 87 88 get visitor() { 89 const thisMutator = this; 90 const accessStatement = { 91 enter(path) { 92 thisMutator.callWithProb(path, replaceAndSkip); 93 }, 94 exit(path) { 95 // Apply nested wrapping (is only executed if not skipped above). 96 replaceAndSkip(path); 97 } 98 }; 99 return { 100 Program: { 101 enter(path) { 102 // Track original source location fraction in [0, 1). 103 thisMutator.loc = 0; 104 // Target probability for skipping try-catch. 105 thisMutator.skipProb = DEFAULT_SKIP_PROB; 106 // Target probability for not nesting try-catch. 107 thisMutator.toplevelProb = DEFAULT_TOPLEVEL_PROB; 108 // Maybe deviate from target probability for the entire test. 109 if (random.choose(IGNORE_DEFAULT_PROB)) { 110 thisMutator.skipProb = random.uniform(0, 1); 111 thisMutator.toplevelProb = random.uniform(0, 1); 112 thisMutator.annotate( 113 path.node, 114 'Target skip probability ' + thisMutator.skipProb + 115 ' and toplevel probability ' + thisMutator.toplevelProb); 116 } 117 } 118 }, 119 Noop: { 120 enter(path) { 121 if (common.getSourceLoc(path.node)) { 122 thisMutator.loc = common.getSourceLoc(path.node); 123 } 124 }, 125 }, 126 ExpressionStatement: accessStatement, 127 IfStatement: accessStatement, 128 LabeledStatement: { 129 enter(path) { 130 // Apply an extra try-catch around the label of a loop, since we 131 // ignore the loop itself if it has a label. 132 if (babelTypes.isLoop(path.node.body)) { 133 thisMutator.callWithProb(path, replaceAndSkip); 134 } 135 }, 136 exit(path) { 137 // Apply nested wrapping (is only executed if not skipped above). 138 if (babelTypes.isLoop(path.node.body)) { 139 replaceAndSkip(path); 140 } 141 }, 142 }, 143 // This covers {While|DoWhile|ForIn|ForOf|For}Statement. 144 Loop: accessStatement, 145 NewExpression: { 146 enter(path) { 147 thisMutator.callWithProb(path, replaceNewExpression); 148 }, 149 exit(path) { 150 // Apply nested wrapping (is only executed if not skipped above). 151 replaceNewExpression(path); 152 } 153 }, 154 SwitchStatement: accessStatement, 155 VariableDeclaration: { 156 enter(path) { 157 if (path.node.kind !== 'var' || babelTypes.isLoop(path.parent)) 158 return; 159 thisMutator.callWithProb(path, replaceAndSkip); 160 }, 161 exit(path) { 162 if (path.node.kind !== 'var' || babelTypes.isLoop(path.parent)) 163 return; 164 // Apply nested wrapping (is only executed if not skipped above). 165 replaceAndSkip(path); 166 } 167 }, 168 WithStatement: accessStatement, 169 }; 170 } 171} 172 173module.exports = { 174 AddTryCatchMutator: AddTryCatchMutator, 175} 176