/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #pragma once #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace apache { namespace thrift { namespace populator { struct populator_opts { template struct range { Int min; Int max; range(Int min, Int max) : min(min), max(max) { assert(min <= max); } }; range<> list_len = range<>(0, 0xFF); range<> set_len = range<>(0, 0xFF); range<> map_len = range<>(0, 0xFF); range<> bin_len = range<>(0, 0xFF); range<> str_len = range<>(0, 0xFF); // Probability to use for populating optional fields. float optional_field_prob = 0.0; }; namespace detail { // generate a value with a Bernoulli distribution: // p is the probability of true, (1-p) for false template bool get_bernoulli(Rng& rng, float p) { std::bernoulli_distribution gen(p); return gen(rng); } // generate a value of type Int within [range.min, range.max] template Int rand_in_range(Rng& rng, const populator_opts::range& range) { // uniform_int_distribution undefined for char, // use the next larger type if it's small using int_type = fatal::conditional< (sizeof(Int) > 1), Int, fatal::conditional< std::numeric_limits::is_signed, signed short, unsigned short>>; std::uniform_int_distribution gen(range.min, range.max); int_type tmp = gen(rng); return static_cast(tmp); } // pointer type for cpp2.ref fields, while discrimiminating against the // pointer corner case in Thrift (e.g., a unqiue_pointer) template struct is_smart_pointer : std::false_type {}; template struct is_smart_pointer> : std::false_type {}; // supported smart pointer types for cpp2.ref_type fields template struct is_smart_pointer> : std::true_type {}; template struct is_smart_pointer> : std::true_type {}; template using enable_if_smart_pointer = typename std::enable_if::value>::type; template using disable_if_smart_pointer = typename std::enable_if::value>::type; // helper predicate for determining if a struct's MemberInfo is required // to be read out of the protocol struct is_required_field { template using apply = std::integral_constant< bool, MemberInfo::optional::value == optionality::required>; }; struct extract_descriptor_fid { template using apply = typename T::metadata::id; }; template struct deref; // General case: methods on deref are no-op, returning their input template struct deref> { static T& clear_and_get(T& in) { return in; } }; // Special case: We specifically *do not* dereference a unique pointer to // an IOBuf, because this is a type that the protocol can (de)serialize // directly template <> struct deref> { using T = std::unique_ptr; static T& clear_and_get(T& in) { return in; } }; // General case: deref returns a reference to what the // unique pointer contains template struct deref> { using T = typename std::remove_const::type; static T& clear_and_get(std::shared_ptr& in) { auto t = std::make_shared(); auto ret = t.get(); in = std::move(t); return *ret; } static T& clear_and_get(std::shared_ptr& in) { in = std::make_shared(); return *in; } static T& clear_and_get(std::unique_ptr& in) { in = std::make_unique(); return *in; } }; } // namespace detail template struct populator_methods; template struct populator_methods { template static Int next_value(Rng& rng) { using limits = std::numeric_limits; Int out = detail::rand_in_range( rng, populator_opts::range(limits::min(), limits::max())); DVLOG(4) << "generated int: " << out; return out; } // Special overload to work with lists of booleans. template static void populate( Rng& rng, const populator_opts&, std::vector::reference out) { out = next_value(rng); } template static void populate(Rng& rng, const populator_opts&, Int& out) { out = next_value(rng); } }; template struct populator_methods { template static void populate(Rng& rng, const populator_opts&, Fp& out) { std::uniform_real_distribution gen; out = gen(rng); DVLOG(4) << "generated real: " << out; } }; template <> struct populator_methods { template static void populate(Rng& rng, const populator_opts& opts, std::string& str) { using larger_char = fatal::conditional::is_signed, int, unsigned>; // all printable chars (see `man ascii`) std::uniform_int_distribution char_gen(0x20, 0x7E); const std::size_t length = detail::rand_in_range(rng, opts.str_len); str = std::string(length, 0); std::generate_n(str.begin(), length, [&]() { return static_cast(char_gen(rng)); }); DVLOG(4) << "generated string of len" << length; } }; template void generate_bytes( Rng& rng, Binary&, const std::size_t length, const WriteFunc& write_func) { std::uniform_int_distribution byte_gen(0, 0xFF); for (std::size_t i = 0; i < length; i++) { write_func(static_cast(byte_gen(rng))); } DVLOG(4) << "generated binary of length " << length; } template <> struct populator_methods { template static void populate(Rng& rng, const populator_opts& opts, std::string& bin) { const auto length = detail::rand_in_range(rng, opts.bin_len); bin = std::string(length, 0); auto iter = bin.begin(); generate_bytes(rng, bin, length, [&](uint8_t c) { *iter++ = c; }); } }; template <> struct populator_methods { template static void populate( Rng& rng, const populator_opts& opts, folly::IOBuf& bin) { const auto length = detail::rand_in_range(rng, opts.bin_len); bin = folly::IOBuf(folly::IOBuf::CREATE, length); bin.append(length); folly::io::RWUnshareCursor range(&bin); generate_bytes( rng, range, length, [&](uint8_t c) { range.write(c); }); } }; template <> struct populator_methods> { template static void populate( Rng& rng, const populator_opts& opts, std::unique_ptr& bin) { bin = std::make_unique(); return populator_methods::populate( rng, opts, *bin); } }; // handle dereferencing smart pointers template struct populator_methods< TypeClass, PtrType, detail::enable_if_smart_pointer> { using element_type = typename PtrType::element_type; using type_methods = populator_methods; template static void populate(Rng& rng, const populator_opts& opts, PtrType& out) { return type_methods::populate(rng, opts, *out); } }; // Enumerations template struct populator_methods { using int_type = typename std::underlying_type::type; using int_methods = populator_methods; template static void populate(Rng& rng, const populator_opts& opts, Type& out) { int_type tmp; int_methods::populate(rng, opts, tmp); out = static_cast(tmp); } }; // Lists template struct populator_methods, Type> { using elem_type = typename Type::value_type; using elem_tclass = ElemClass; static_assert( !std::is_same(), "Unable to serialize unknown list element"); using elem_methods = populator_methods; template static void populate(Rng& rng, const populator_opts& opts, Type& out) { std::uint32_t list_size = detail::rand_in_range(rng, opts.list_len); out = Type(); DVLOG(3) << "populating list size " << list_size; out.resize(list_size); for (decltype(list_size) i = 0; i < list_size; i++) { elem_methods::populate(rng, opts, out[i]); } } }; // Sets template struct populator_methods, Type> { // TODO: fair amount of shared code bewteen this and specialization for // type_class::list using elem_type = typename Type::value_type; using elem_tclass = ElemClass; static_assert( !std::is_same(), "Unable to serialize unknown type"); using elem_methods = populator_methods; template static void populate(Rng& rng, const populator_opts& opts, Type& out) { std::uint32_t set_size = detail::rand_in_range(rng, opts.set_len); DVLOG(3) << "populating set size " << set_size; out = Type(); for (decltype(set_size) i = 0; i < set_size; i++) { elem_type tmp; elem_methods::populate(rng, opts, tmp); out.insert(std::move(tmp)); } } }; // Maps template struct populator_methods, Type> { using key_type = typename Type::key_type; using key_tclass = KeyClass; using mapped_type = typename Type::mapped_type; using mapped_tclass = MappedClass; static_assert( !std::is_same(), "Unable to serialize unknown key type in map"); static_assert( !std::is_same(), "Unable to serialize unknown mapped type in map"); using key_methods = populator_methods; using mapped_methods = populator_methods; template static void populate(Rng& rng, const populator_opts& opts, Type& out) { std::uint32_t map_size = detail::rand_in_range(rng, opts.map_len); DVLOG(3) << "populating map size " << map_size; out = Type(); for (decltype(map_size) i = 0; i < map_size; i++) { key_type key_tmp; key_methods::populate(rng, opts, key_tmp); mapped_methods::populate(rng, opts, out[std::move(key_tmp)]); } } }; // specialization for variants (Thrift unions) template struct populator_methods { using traits = fatal::variant_traits; private: struct write_member_by_fid { template void operator()( fatal::indexed, Rng& rng, const populator_opts& opts, Union& obj) { using descriptor = fatal::get< typename traits::descriptors, Fid, detail::extract_descriptor_fid>; using methods = populator_methods< typename descriptor::metadata::type_class, typename descriptor::type>; assert(Fid::value == descriptor::metadata::id::value); DVLOG(3) << "writing union field " << fatal::z_data() << ", fid: " << descriptor::metadata::id::value; typename descriptor::type tmp; typename descriptor::setter setter; methods::populate(rng, opts, tmp); setter(obj, std::move(tmp)); } }; public: template static void populate(Rng& rng, const populator_opts& opts, Union& out) { DVLOG(3) << "begin writing union: " << fatal::z_data() << ", type: " << folly::to_underlying(out.getType()); // array of all possible FIDs of this union using fids_seq = fatal::sort, fatal::sequence, field_id_t>>; // std::array of field_id_t const auto range = populator_opts::range( 0, fatal::size::value - !fatal::empty::value); const auto selected = detail::rand_in_range(rng, range); fatal::sorted_search( fatal::as_array::data[selected], write_member_by_fid(), rng, opts, out); DVLOG(3) << "end writing union"; } }; // specialization for structs template struct populator_methods { private: using traits = apache::thrift::reflect_struct; using all_fields = fatal::partition; using required_fields = fatal::first; using optional_fields = fatal::second; using isset_array = std::array::value>; template < typename Member, typename MemberType, typename Methods, typename Enable = void> struct field_populator; // generic field writer template struct field_populator< Member, MemberType, Methods, detail::disable_if_smart_pointer> { template static void populate( Rng& rng, const populator_opts& opts, MemberType& out) { Methods::populate(rng, opts, out); } }; // writer for default/required ref structs template struct field_populator< Member, PtrType, Methods, detail::enable_if_smart_pointer> { using element_type = typename PtrType::element_type; template static void populate(Rng& rng, const populator_opts& opts, PtrType& out) { field_populator::populate( rng, opts, detail::deref::clear_and_get(out)); } }; class member_populator { public: template void operator()( fatal::indexed, Rng& rng, const populator_opts& opts, Struct& out) { using methods = populator_methods; auto&& got = typename Member::field_ref_getter{}(out); using member_type = folly::remove_cvref_t; // Popualate optional fields with `optional_field_prob` probability. const auto skip = // Member::optional::value == optionality::optional && !detail::get_bernoulli(rng, opts.optional_field_prob); if (skip) { return; } DVLOG(3) << "populating member: " << fatal::z_data(); Member::mark_set(out, true); ensure_cpp_ref(got); field_populator::populate(rng, opts, *got); } private: template static void ensure_cpp_ref(T&&) {} template static void ensure_cpp_ref(std::unique_ptr& v) { v = std::make_unique(); } template static void ensure_cpp_ref(std::shared_ptr& v) { v = std::make_shared(); } }; public: template static void populate(Rng& rng, const populator_opts& opts, Struct& out) { fatal::foreach( member_populator(), rng, opts, out); } }; /** * Entrypoints for using populator * Populates Thrift datatype with random data * * // C++ * MyStruct a; * populator_opts opts; * * populate(a, opts); * * @author: Dylan Knutson */ template void populate(Type& out, const populator_opts& opts, Rng& rng) { using TypeClass = type_class_of_thrift_class_t; return populator_methods::populate(rng, opts, out); } } // namespace populator } // namespace thrift } // namespace apache