void t_hs_generator::generate_service_fuzzer()

in thrift/compiler/generate/t_hs_generator.cc [1472:1705]


void t_hs_generator::generate_service_fuzzer(const t_service* tservice) {
  string f_service_name =
      get_package_dir() + capitalize(service_name_) + "_Fuzzer.hs";
  f_service_fuzzer_.open(f_service_name.c_str());
  record_genfile(f_service_name);

  string module_prefix = get_module_prefix(program_);
  // Generate module declaration
  f_service_fuzzer_ <<
      //"{-# LANGUAGE ScopedTypeVariables, DeriveDataTypeable #-}" << nl <<
      hs_language_pragma() << nl << hs_autogen_comment() << nl << "module "
                    << module_prefix << capitalize(service_name_)
                    << "_Fuzzer (main) where" << nl;

  // Generate imports specific to the .thrift file.
  f_service_fuzzer_ << "import qualified " << module_prefix
                    << capitalize(program_name_) << "_Types" << nl
                    << "import qualified " << module_prefix
                    << capitalize(service_name_) << "_Client as Client" << nl;

  const vector<t_program*>& includes = program_->get_included_programs();
  for (const auto& program_include : includes) {
    f_service_fuzzer_ << "import qualified "
                      << get_module_prefix(program_include)
                      << capitalize(program_include->name()) << "_Types" << nl;
  }

  // Generate non-specific body code
  f_service_fuzzer_
      << nl << hs_imports() << "import Prelude ((>>), print)" << nl
      << "import qualified Prelude as P" << nl << "import Control.Monad (forM)"
      << nl << "import qualified Data.List as L" << nl
      << "import Data.Maybe (fromJust)" << nl
      << "import qualified Data.Map as Map" << nl
      << "import GHC.Int (Int64, Int32)" << nl
      << "import Data.ByteString.Lazy (ByteString)" << nl
      << "import System.Environment (getArgs)" << nl
      << "import Test.QuickCheck (arbitrary)" << nl
      << "import Test.QuickCheck.Gen (Gen(..))" << nl
      << "import Thrift.FuzzerSupport" << nl << "" << nl << "" << nl
      << "handleOptions :: ([Options -> Options], [String], [String]) -> Options"
      << nl
      << "handleOptions (transformers, (serviceName:[]), []) | serviceName `P.elem` serviceNames"
      << nl
      << "    = (P.foldl (P.flip ($)) defaultOptions transformers) { opt_service = serviceName } "
      << nl << "handleOptions (_, (serviceName:[]), []) | P.otherwise" << nl
      << "    = P.error $ usage ++ \"\\nUnknown serviceName \" ++ serviceName ++ \", should be one of \" ++ (P.show serviceNames)"
      << nl
      << "handleOptions (_, [], _) = P.error $ usage ++ \"\\nMissing mandatory serviceName to fuzz.\""
      << nl
      << "handleOptions (_, _a, []) = P.error $ usage ++ \"\\nToo many serviceNames, pick one.\""
      << nl << "handleOptions (_, _, e) = P.error $ usage ++ (P.show e)" << nl
      << "" << nl << "main :: IO ()" << nl << "main = do" << nl
      << "    args <- getArgs" << nl
      << "    let config = handleOptions (getOptions args)" << nl
      << "    fuzz config" << nl << "" << nl
      << "selectFuzzer :: Options -> (Options -> IO ())" << nl
      << "selectFuzzer (Options _host _port service _timeout _framed _verbose) "
      << nl << "    = fromJust $ P.lookup service fuzzerFunctions" << nl << ""
      << nl << "fuzz :: Options -> IO ()" << nl
      << "fuzz config = (selectFuzzer config) config" << nl << "" << nl
      << "-- Dynamic content" << nl << "" << nl
      << "-- Configuration via command-line parsing";

  // Generate service methods list and method->fuzzer mappings

  // We'll only generate fuzzers for functions that take arguments.
  vector<t_function*> functions = tservice->get_functions();
  vector<t_function*>::const_iterator functions_end;
  functions_end = remove_if(functions.begin(), functions.end(), hasNoArguments);
  vector<t_function*>::const_iterator f_iter;

  // service methods list
  f_service_fuzzer_ << nl << nl << "serviceNames :: [String]" << nl
                    << "serviceNames = [";

  bool first = true;
  for (f_iter = functions.begin(); f_iter != functions_end; ++f_iter) {
    auto funname = decapitalize((*f_iter)->get_name());
    if (first) {
      first = false;
    } else {
      f_service_fuzzer_ << ", ";
    }
    f_service_fuzzer_ << "\"" << funname << "\"";
  }
  f_service_fuzzer_ << "]" << nl;

  // map from method names to fuzzer functions
  f_service_fuzzer_ << nl << "fuzzerFunctions :: [(String, (Options -> IO ()))]"
                    << nl << "fuzzerFunctions = [";
  first = true;
  for (f_iter = functions.begin(); f_iter != functions_end; ++f_iter) {
    auto funname = decapitalize((*f_iter)->get_name());
    if (first) {
      first = false;
    } else {
      f_service_fuzzer_ << ", ";
    }
    f_service_fuzzer_ << "("
                      << "\"" << funname << "\""
                      << ", " << funname << "_fuzzer"
                      << ")";
  }

  f_service_fuzzer_ << "]" << nl;

  // Generate data generators for each data type used in any service method
  f_service_fuzzer_ << nl << "-- Random data generation" << nl;

  map<string, const t_type*> used_types;
  for (f_iter = functions.begin(); f_iter != functions_end; ++f_iter) {
    const vector<t_field*>& fields = (*f_iter)->get_paramlist()->get_members();
    vector<t_field*>::const_iterator fld_iter;
    for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) {
      auto type = (*fld_iter)->get_type();
      used_types.emplace(render_hs_type(type, false), type);
    }
  }

  // all the data generators we need
  for (const auto& used_type : used_types) {
    auto type_iter = used_type.second;
    const string& inf_type =
        "inf_" + render_hs_type_for_function_name(type_iter);
    const string& hs_type = render_hs_type(type_iter, true);

    f_service_fuzzer_ << inf_type << " :: IO [" << hs_type << "]" << nl
                      << inf_type
                      << " = infexamples (Arbitrary.arbitrary :: Gen "
                      << hs_type << ")" << nl << nl;
  }

  // For each service method that has arguments, generate the
  //   exception handler and fuzzer functions

  f_service_fuzzer_ << "-- Fuzzers and exception handlers" << nl;

  for (f_iter = functions.begin(); f_iter != functions_end; ++f_iter) {
    auto funname = decapitalize((*f_iter)->get_name());
    // fuzzer signature
    f_service_fuzzer_ << funname << "_fuzzer :: Options -> IO ()" << nl;
    // function
    f_service_fuzzer_ << funname << "_fuzzer opts = do" << nl;
    indent_up();
    const vector<t_field*>& fields = (*f_iter)->get_paramlist()->get_members();
    vector<t_field*>::const_iterator fld_iter;
    int varNum = 1;
    // random data sources
    for (fld_iter = fields.begin(); fld_iter != fields.end(); ++fld_iter) {
      indent(f_service_fuzzer_)
          << "a" << varNum << " <- "
          << "Applicative.ZipList <$> "
          << "inf_" << render_hs_type_for_function_name((*fld_iter)->get_type())
          << nl;
      varNum++;
    }
    // fuzzer invocation
    indent(f_service_fuzzer_) << "_ <- ";
    int argCount = fields.size();

    std::vector<std::string> argList;
    for (int i = 1; i < argCount + 1; ++i) {
      argList.push_back("a" + std::to_string(i));
    }

    std::string spaceSeparatedArgList;
    std::string showArgList;
    std::string showElemList;
    std::string fuzzString;
    std::string paramString("(");

    // iterate through every arg and format it
    for (const auto& arg : argList) {
      spaceSeparatedArgList += arg + " ";
      showArgList += "Show " + arg + ", ";
      showElemList += "show " + arg + " ++ ";
      paramString += arg + ", ";
      fuzzString += arg + " <*> ";
    }

    // erase extra elements at the end of the string and finish formatting
    spaceSeparatedArgList.erase(spaceSeparatedArgList.length() - 1);
    showArgList.erase(showArgList.length() - 2);
    showElemList.erase(showElemList.length() - 4);
    fuzzString.erase(fuzzString.length() - 5);
    paramString.erase(paramString.length() - 2);
    paramString += ")";

    if (argCount == 1) {
      f_service_fuzzer_ << "forM (Applicative.getZipList a1) " << funname
                        << "_fuzzFunc";
    } else {
      f_service_fuzzer_ << "P.sequence . Applicative.getZipList $ " << funname
                        << "_fuzzFunc <$> " << fuzzString;
    }
    f_service_fuzzer_ << nl << indent() << "return ()" << nl << indent();

    f_service_fuzzer_ << "where" << nl << indent() << funname << "_fuzzFunc ";
    f_service_fuzzer_ << spaceSeparatedArgList;
    f_service_fuzzer_ << " = let param = " + paramString + " in";
    f_service_fuzzer_ << nl << indent() << indent() << "if opt_framed opts"
                      << nl << indent() << indent()
                      << "then withThriftDo opts (withFramedTransport opts) ("
                      << funname << "_fuzzOnce param) (" << funname
                      << "_exceptionHandler param)" << nl << indent()
                      << indent()
                      << "else withThriftDo opts (withHandle opts) (" << funname
                      << "_fuzzOnce param) (" << funname
                      << "_exceptionHandler param)" << nl;

    indent_down();

    // exception handler
    f_service_fuzzer_ << nl << funname << "_exceptionHandler :: ("
                      << showArgList << ") => " << paramString << " -> IO ()";
    f_service_fuzzer_ << nl << funname << "_exceptionHandler " << paramString
                      << " = do" << nl;
    indent_up();
    indent(f_service_fuzzer_)
        << "P.putStrLn $ \"Got exception on data:\"" << nl << indent()
        << "P.putStrLn $ \"(\" ++ " << showElemList << " ++ \")\"";
    indent_down();

    // Thrift invoker
    f_service_fuzzer_ << nl << funname << "_fuzzOnce " << paramString
                      << " client = Client.";
    f_service_fuzzer_ << funname << " client " << spaceSeparatedArgList
                      << " >> return ()";
    f_service_fuzzer_ << nl << nl;
  }

  f_service_fuzzer_.close();
}