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