• 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 Mutator for differential fuzzing.
7 */
8
9'use strict';
10
11const babelTemplate = require('@babel/template').default;
12const babelTypes = require('@babel/types');
13
14const common = require('./common.js');
15const mutator = require('./mutator.js');
16const random = require('../random.js');
17
18// Templates for various statements.
19const incCaught = babelTemplate('__caught++;');
20const printValue = babelTemplate('print(VALUE);');
21const printCaught = babelTemplate('print("Caught: " + __caught);');
22const printHash = babelTemplate('print("Hash: " + __hash);');
23const prettyPrint = babelTemplate('__prettyPrint(ID);');
24const prettyPrintExtra = babelTemplate('__prettyPrintExtra(ID);');
25
26// This section prefix is expected by v8_foozzie.py. Existing prefixes
27// (e.g. from CrashTests) are cleaned up with CLEANED_PREFIX.
28const SECTION_PREFIX = 'v8-foozzie source: ';
29const CLEANED_PREFIX = 'v***************e: ';
30
31/**
32 * Babel statement for calling deep printing from the fuzz library.
33 */
34function prettyPrintStatement(variable) {
35  return prettyPrint({ ID: babelTypes.cloneDeep(variable) });
36}
37
38/**
39 * As above, but using the "extra" variant, which will reduce printing
40 * after too many calls to prevent I/O flooding.
41 */
42function prettyPrintExtraStatement(variable) {
43  return prettyPrintExtra({ ID: babelTypes.cloneDeep(variable) });
44}
45
46/**
47 * Mutator for suppressing known and/or unfixable issues.
48 */
49class DifferentialFuzzSuppressions extends mutator.Mutator {
50  get visitor() {
51    let thisMutator = this;
52
53    return {
54      // Clean up strings containing the magic section prefix. Those can come
55      // e.g. from CrashTests and would confuse the deduplication in
56      // v8_foozzie.py.
57      StringLiteral(path) {
58        if (path.node.value.startsWith(SECTION_PREFIX)) {
59          const postfix = path.node.value.substring(SECTION_PREFIX.length);
60          path.node.value = CLEANED_PREFIX + postfix;
61          thisMutator.annotate(path.node, 'Replaced magic string');
62        }
63      },
64      // Known precision differences: https://crbug.com/1063568
65      BinaryExpression(path) {
66        if (path.node.operator == '**') {
67          path.node.operator = '+';
68          thisMutator.annotate(path.node, 'Replaced **');
69        }
70      },
71      // Unsupported language feature: https://crbug.com/1020573
72      MemberExpression(path) {
73        if (path.node.property.name == "arguments") {
74          let replacement = common.randomVariable(path);
75          if (!replacement) {
76            replacement = babelTypes.thisExpression();
77          }
78          thisMutator.annotate(replacement, 'Replaced .arguments');
79          thisMutator.replaceWithSkip(path, replacement);
80        }
81      },
82    };
83  }
84}
85
86/**
87 * Mutator for tracking original input files and for extra printing.
88 */
89class DifferentialFuzzMutator extends mutator.Mutator {
90  constructor(settings) {
91    super();
92    this.settings = settings;
93  }
94
95  /**
96   * Looks for the dummy node that marks the beginning of an input file
97   * from the corpus.
98   */
99  isSectionStart(path) {
100    return !!common.getOriginalPath(path.node);
101  }
102
103  /**
104   * Create print statements for printing the magic section prefix that's
105   * expected by v8_foozzie.py to differentiate different source files.
106   */
107  getSectionHeader(path) {
108    const orig = common.getOriginalPath(path.node);
109    return printValue({
110      VALUE: babelTypes.stringLiteral(SECTION_PREFIX + orig),
111    });
112  }
113
114  /**
115   * Create statements for extra printing at the end of a section. We print
116   * the number of caught exceptions, a generic hash of all observed values
117   * and the contents of all variables in scope.
118   */
119  getSectionFooter(path) {
120    const variables = common.availableVariables(path);
121    const statements = variables.map(prettyPrintStatement);
122    statements.unshift(printCaught());
123    statements.unshift(printHash());
124    const statement = babelTypes.tryStatement(
125        babelTypes.blockStatement(statements),
126        babelTypes.catchClause(
127            babelTypes.identifier('e'),
128            babelTypes.blockStatement([])));
129    this.annotate(statement, 'Print variables and exceptions from section');
130    return statement;
131  }
132
133  /**
134   * Helper for printing the contents of several variables.
135   */
136  printVariables(path, nodes) {
137    const statements = [];
138    for (const node of nodes) {
139      if (!babelTypes.isIdentifier(node) ||
140          !common.isVariableIdentifier(node.name))
141        continue;
142      statements.push(prettyPrintExtraStatement(node));
143    }
144    if (statements.length) {
145      this.annotate(statements[0], 'Extra variable printing');
146      this.insertAfterSkip(path, statements);
147    }
148  }
149
150  get visitor() {
151    const thisMutator = this;
152    const settings = this.settings;
153
154    return {
155      // Replace existing normal print statements with deep printing.
156      CallExpression(path) {
157        if (babelTypes.isIdentifier(path.node.callee) &&
158            path.node.callee.name == 'print') {
159          path.node.callee = babelTypes.identifier('__prettyPrintExtra');
160          thisMutator.annotate(path.node, 'Pretty printing');
161        }
162      },
163      // Either print or track caught exceptions, guarded by a probability.
164      CatchClause(path) {
165        const probability = random.random();
166        if (probability < settings.DIFF_FUZZ_EXTRA_PRINT &&
167            path.node.param &&
168            babelTypes.isIdentifier(path.node.param)) {
169          const statement = prettyPrintExtraStatement(path.node.param);
170          path.node.body.body.unshift(statement);
171        } else if (probability < settings.DIFF_FUZZ_TRACK_CAUGHT) {
172          path.node.body.body.unshift(incCaught());
173        }
174      },
175      // Insert section headers and footers between the contents of two
176      // original source files. We detect the dummy no-op nodes that were
177      // previously tagged with the original path of the file.
178      Noop(path) {
179        if (!thisMutator.isSectionStart(path)) {
180          return;
181        }
182        const header = thisMutator.getSectionHeader(path);
183        const footer = thisMutator.getSectionFooter(path);
184        thisMutator.insertBeforeSkip(path, footer);
185        thisMutator.insertBeforeSkip(path, header);
186      },
187      // Additionally we print one footer in the end.
188      Program: {
189        exit(path) {
190          const footer = thisMutator.getSectionFooter(path);
191          path.node.body.push(footer);
192        },
193      },
194      // Print contents of variables after assignments, guarded by a
195      // probability.
196      ExpressionStatement(path) {
197        if (!babelTypes.isAssignmentExpression(path.node.expression) ||
198            !random.choose(settings.DIFF_FUZZ_EXTRA_PRINT)) {
199          return;
200        }
201        const left = path.node.expression.left;
202        if (babelTypes.isMemberExpression(left)) {
203          thisMutator.printVariables(path, [left.object]);
204        } else {
205          thisMutator.printVariables(path, [left]);
206        }
207      },
208      // Print contents of variables after declaration, guarded by a
209      // probability.
210      VariableDeclaration(path) {
211        if (babelTypes.isLoop(path.parent) ||
212            !random.choose(settings.DIFF_FUZZ_EXTRA_PRINT)) {
213          return;
214        }
215        const identifiers = path.node.declarations.map(decl => decl.id);
216        thisMutator.printVariables(path, identifiers);
217      },
218    };
219  }
220}
221
222module.exports = {
223  DifferentialFuzzMutator: DifferentialFuzzMutator,
224  DifferentialFuzzSuppressions: DifferentialFuzzSuppressions,
225};
226