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();
}