| // 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 Generate exceptions from full corpus test report. |
| */ |
| |
| const program = require('commander'); |
| |
| const assert = require('assert'); |
| const babelGenerator = require('@babel/generator').default; |
| const babelTemplate = require('@babel/template').default; |
| const babelTypes = require('@babel/types'); |
| const fs = require('fs'); |
| const p = require('path'); |
| const prettier = require("prettier"); |
| |
| const SPLIT_LINES_RE = /^.*([\n\r]+|$)/gm; |
| const PARSE_RE = /^Parsing (.*) sloppy took (\d+) ms\.\n$/; |
| const MUTATE_RE = /^Mutating (.*) took (\d+) ms\.\n$/; |
| const PARSE_FAILED_RE = /^WARNING: failed to sloppy parse (.*)\n$/; |
| const PARSE_STRICT_FAILED_RE = /^WARNING: failed to strict parse (.*)\n$/; |
| const MUTATE_FAILED_RE = /^ERROR: Exception during mutate: (.*)\n$/; |
| |
| // Add tests matching error regexp to result array. |
| function matchError(regexp, line, resultArray){ |
| const match = line.match(regexp); |
| if (!match) return false; |
| const relPath = match[1]; |
| assert(relPath); |
| resultArray.push(relPath); |
| return true; |
| } |
| |
| // Sum up total duration of tests matching the duration regexp and |
| // map test -> duration in result map. |
| function matchDuration(regexp, line, resultMap){ |
| const match = line.match(regexp); |
| if (!match) return false; |
| const relPath = match[1]; |
| assert(relPath); |
| resultMap[relPath] = (resultMap[relPath] || 0) + parseInt(match[2]); |
| return true; |
| } |
| |
| // Create lists of failed and slow tests from stdout of a fuzzer run. |
| function processFuzzOutput(outputFile){ |
| const text = fs.readFileSync(outputFile, 'utf-8'); |
| const lines = text.match(SPLIT_LINES_RE); |
| |
| const failedParse = []; |
| const failedParseStrict = []; |
| const failedMutate = []; |
| const durationsMap = {}; |
| |
| for (const line of lines) { |
| if (matchError(PARSE_FAILED_RE, line, failedParse)) |
| continue; |
| if (matchError(PARSE_STRICT_FAILED_RE, line, failedParseStrict)) |
| continue; |
| if (matchError(MUTATE_FAILED_RE, line, failedMutate)) |
| continue; |
| if (matchDuration(PARSE_RE, line, durationsMap)) |
| continue; |
| if (matchDuration(MUTATE_RE, line, durationsMap)) |
| continue; |
| } |
| |
| // Tuples (absPath, duration). |
| const total = Object.entries(durationsMap); |
| // Tuples (absPath, duration) with 2s < duration <= 10s. |
| const slow = total.filter(t => t[1] > 2000 && t[1] <= 10000); |
| // Tuples (absPath, duration) with 10s < duration. |
| const verySlow = total.filter(t => t[1] > 10000); |
| |
| // Assert there's nothing horribly wrong with the results. |
| // We have at least 2500 tests in the output. |
| assert(total.length > 2500); |
| // No more than 5% parse/mutation errors. |
| assert(failedParse.length + failedMutate.length < total.length / 20); |
| // No more than 10% slow tests |
| assert(slow.length < total.length / 10); |
| // No more than 2% very slow tests. |
| assert(verySlow.length < total.length / 50); |
| |
| // Sort everything. |
| failedParse.sort(); |
| failedParseStrict.sort(); |
| failedMutate.sort(); |
| |
| function slowestFirst(a, b) { |
| return b[1] - a[1]; |
| } |
| |
| slow.sort(slowestFirst); |
| verySlow.sort(slowestFirst); |
| |
| return [failedParse, failedParseStrict, failedMutate, slow, verySlow]; |
| } |
| |
| // List of string literals of failed tests. |
| function getLiteralsForFailed(leadingComment, failedList) { |
| const result = failedList.map(path => babelTypes.stringLiteral(path)); |
| if (result.length) { |
| babelTypes.addComment(result[0], 'leading', leadingComment); |
| } |
| return result; |
| } |
| |
| // List of string literals of slow tests with duration comments. |
| function getLiteralsForSlow(leadingComment, slowList) { |
| const result = slowList.map(([path, duration]) => { |
| const literal = babelTypes.stringLiteral(path); |
| babelTypes.addComment( |
| literal, 'trailing', ` ${duration / 1000}s`, true); |
| return literal; |
| }); |
| if (result.length) { |
| babelTypes.addComment(result[0], 'leading', leadingComment); |
| } |
| return result; |
| } |
| |
| function main() { |
| program |
| .version('0.0.1') |
| .parse(process.argv); |
| |
| if (!program.args.length) { |
| console.log('Need to specify stdout reports of fuzz runs.'); |
| return; |
| } |
| |
| let skipped = []; |
| let softSkipped = []; |
| let sloppy = []; |
| for (const outputFile of program.args) { |
| const [failedParse, failedParseStrict, failedMutate, slow, verySlow] = ( |
| processFuzzOutput(outputFile)); |
| const name = p.basename(outputFile, p.extname(outputFile)); |
| |
| // Skip tests that fail to parse/mutate or are very slow. |
| skipped = skipped.concat(getLiteralsForFailed( |
| ` Tests with parse errors from ${name} `, failedParse)); |
| skipped = skipped.concat(getLiteralsForFailed( |
| ` Tests with mutation errors from ${name} `, failedMutate)); |
| skipped = skipped.concat(getLiteralsForSlow( |
| ` Very slow tests from ${name} `, verySlow)); |
| |
| // Soft-skip slow but not very slow tests. |
| softSkipped = softSkipped.concat(getLiteralsForSlow( |
| ` Slow tests from ${name} `, slow)); |
| |
| // Mark sloppy tests. |
| sloppy = sloppy.concat(getLiteralsForFailed( |
| ` Tests requiring sloppy mode from ${name} `, failedParseStrict)); |
| } |
| |
| const fileTemplate = babelTemplate(` |
| /** |
| * @fileoverview Autogenerated exceptions. Created with gen_exceptions.js. |
| */ |
| |
| 'use strict'; |
| |
| const skipped = SKIPPED; |
| |
| const softSkipped = SOFTSKIPPED; |
| |
| const sloppy = SLOPPY; |
| |
| module.exports = { |
| generatedSkipped: new Set(skipped), |
| generatedSoftSkipped: new Set(softSkipped), |
| generatedSloppy: new Set(sloppy), |
| } |
| `, {preserveComments: true}); |
| |
| const skippedArray = babelTypes.arrayExpression(skipped); |
| const softSkippedArray = babelTypes.arrayExpression(softSkipped); |
| const sloppyArray = babelTypes.arrayExpression(sloppy); |
| |
| const statements = fileTemplate({ |
| SKIPPED: skippedArray, |
| SOFTSKIPPED: softSkippedArray, |
| SLOPPY: sloppyArray, |
| }); |
| |
| const resultProgram = babelTypes.program(statements); |
| const code = babelGenerator(resultProgram, { comments: true }).code; |
| const prettyCode = prettier.format(code, { parser: "babel" }); |
| fs.writeFileSync('generated/exceptions.js', prettyCode); |
| } |
| |
| main(); |