• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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