// 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 Blacklists for fuzzer.
 */

'use strict';

const fs = require('fs');
const path = require('path');

const random = require('./random.js');

const {generatedSloppy, generatedSoftSkipped, generatedSkipped} = require(
    './generated/exceptions.js');

const SKIPPED_FILES = [
    // Disabled for unexpected test behavior, specific to d8 shell.
    'd8-os.js',
    'd8-readbuffer.js',

    // Passes JS flags.
    'd8-arguments.js',

    // Slow tests or tests that are too large to be used as input.
    /numops-fuzz-part.*.js/,
    'regexp-pcre.js',
    'unicode-test.js',
    'unicodelctest.js',
    'unicodelctest-no-optimization.js',

    // Unsupported modules.
    /^modules.*\.js/,

    // Unsupported property escapes.
    /^regexp-property-.*\.js/,

    // Bad testcases that just loads a script that always throws errors.
    'regress-444805.js',
    'regress-crbug-489597.js',
    'regress-crbug-620253.js',

    // Just recursively loads itself.
    'regress-8510.js',
];

const SKIPPED_DIRECTORIES = [
    // Slow tests or tests that are too large to be used as input.
    'embenchen',
    'poppler',
    'sqlite',

    // Causes lots of failures.
    'test262',

    // Unavailable debug.Debug.
    'v8/test/debugger',
    'v8/test/inspector',

    // Unsupported modules.
    'v8/test/js-perf-test/Modules',

    // Contains tests expected to error out on parsing.
    'v8/test/message',

    // Needs specific dependencies for load of various tests.
    'v8/test/mjsunit/tools',

    // Unsupported e4x standard.
    'mozilla/data/e4x',

    // Bails out fast without ReadableStream support.
    'spidermonkey/non262/ReadableStream',
];

// Files used with a lower probability.
const SOFT_SKIPPED_FILES = [
    // Tests with large binary content.
    /^binaryen.*\.js/,

    // Tests slow to parse.
    // CrashTests:
    /^jquery.*\.js/,
    // Spidermonkey:
    'regress-308085.js',
    'regress-74474-002.js',
    'regress-74474-003.js',
    // V8:
    'object-literal.js',
];

// Flags that lead to false positives or that are already passed by default.
const DISALLOWED_FLAGS = [
    // Disallowed because features prefixed with "experimental" are not
    // stabilized yet and would cause too much noise when enabled.
    /^--experimental-.*/,

    // Disallowed due to noise. We explicitly add --es-staging to job
    // definitions, and all of these features are staged before launch.
    /^--harmony-.*/,

    // Disallowed because they are passed explicitly on the command line.
    '--allow-natives-syntax',
    '--debug-code',
    '--es-staging',
    '--wasm-staging',
    '--expose-gc',
    '--expose_gc',
    '--icu-data-file',
    '--random-seed',

    // Disallowed due to false positives.
    '--check-handle-count',
    '--correctness-fuzzer-suppressions',
    '--expose-debug-as',
    '--expose-natives-as',
    '--expose-trigger-failure',
    '--mock-arraybuffer-allocator',
    'natives',  // Used in conjuction with --expose-natives-as.
    /^--trace-path.*/,
];

// Flags only used with 25% probability.
const LOW_PROB_FLAGS_PROB = 0.25;
const LOW_PROB_FLAGS = [
    // Flags that lead to slow test performance.
    /^--gc-interval.*/,
    /^--deopt-every-n-times.*/,
];


// Flags printing data, leading to false positives in differential fuzzing.
const DISALLOWED_DIFFERENTIAL_FUZZ_FLAGS = [
    /^--gc-interval.*/,
    /^--perf.*/,
    /^--print.*/,
    /^--stress-runs.*/,
    /^--trace.*/,
    '--expose-externalize-string',
    '--interpreted-frames-native-stack',
    '--stress-opt',
    '--validate-asm',
];

const ALLOWED_RUNTIME_FUNCTIONS = new Set([
    // List of allowed runtime functions. Others will be replaced with no-ops.
    'ArrayBufferDetach',
    'DeoptimizeFunction',
    'DeoptimizeNow',
    'EnableCodeLoggingForTesting',
    'GetUndetectable',
    'HeapObjectVerify',
    'IsBeingInterpreted',
    'NeverOptimizeFunction',
    'OptimizeFunctionOnNextCall',
    'OptimizeOsr',
    'PrepareFunctionForOptimization',
    'SetAllocationTimeout',
    'SimulateNewspaceFull',
]);

const MAX_FILE_SIZE_BYTES = 128 * 1024;  // 128KB
const MEDIUM_FILE_SIZE_BYTES = 32 * 1024;  // 32KB

function _findMatch(iterable, candidate) {
  for (const entry of iterable) {
    if (typeof entry === 'string') {
      if (entry === candidate) {
        return true;
      }
    } else {
      if (entry.test(candidate)) {
        return true;
      }
    }
  }

  return false;
}

function _doesntMatch(iterable, candidate) {
  return !_findMatch(iterable, candidate);
}

// Convert Windows path separators.
function normalize(testPath) {
  return path.normalize(testPath).replace(/\\/g, '/');
}

function isTestSkippedAbs(absPath) {
  const basename = path.basename(absPath);
  if (_findMatch(SKIPPED_FILES, basename)) {
    return true;
  }

  const normalizedTestPath = normalize(absPath);
  for (const entry of SKIPPED_DIRECTORIES) {
    if (normalizedTestPath.includes(entry))  {
      return true;
    }
  }

  // Avoid OOM/hangs through huge inputs.
  const stat = fs.statSync(absPath);
  return (stat && stat.size >= MAX_FILE_SIZE_BYTES);
}

function isTestSkippedRel(relPath) {
  return generatedSkipped.has(normalize(relPath));
}

// For testing.
function getSoftSkipped() {
  return SOFT_SKIPPED_FILES;
}

// For testing.
function getGeneratedSoftSkipped() {
  return generatedSoftSkipped;
}

// For testing.
function getGeneratedSloppy() {
  return generatedSloppy;
}

function isTestSoftSkippedAbs(absPath) {
  const basename = path.basename(absPath);
  if (_findMatch(this.getSoftSkipped(), basename)) {
    return true;
  }

  // Graylist medium size files.
  const stat = fs.statSync(absPath);
  return (stat && stat.size >= MEDIUM_FILE_SIZE_BYTES);
}

function isTestSoftSkippedRel(relPath) {
  return this.getGeneratedSoftSkipped().has(normalize(relPath));
}

function isTestSloppyRel(relPath) {
  return this.getGeneratedSloppy().has(normalize(relPath));
}

function filterFlags(flags) {
  return flags.filter(flag => {
    return (
        _doesntMatch(DISALLOWED_FLAGS, flag) &&
        (_doesntMatch(LOW_PROB_FLAGS, flag) ||
         random.choose(LOW_PROB_FLAGS_PROB)));
  });
}

function filterDifferentialFuzzFlags(flags) {
  return flags.filter(
      flag => _doesntMatch(DISALLOWED_DIFFERENTIAL_FUZZ_FLAGS, flag));
}

function isAllowedRuntimeFunction(name) {
  if (process.env.APP_NAME != 'd8') {
    return false;
  }

  return ALLOWED_RUNTIME_FUNCTIONS.has(name);
}

module.exports = {
  filterDifferentialFuzzFlags: filterDifferentialFuzzFlags,
  filterFlags: filterFlags,
  getGeneratedSoftSkipped: getGeneratedSoftSkipped,
  getGeneratedSloppy: getGeneratedSloppy,
  getSoftSkipped: getSoftSkipped,
  isAllowedRuntimeFunction: isAllowedRuntimeFunction,
  isTestSkippedAbs: isTestSkippedAbs,
  isTestSkippedRel: isTestSkippedRel,
  isTestSoftSkippedAbs: isTestSoftSkippedAbs,
  isTestSoftSkippedRel: isTestSoftSkippedRel,
  isTestSloppyRel: isTestSloppyRel,
}
