Skip to content

Commit 4fe72cd

Browse files
Frank Qinfacebook-github-bot
Frank Qin
authored andcommitted
Add recursion limit to the populator options
Summary: Add a field `recursion_limit` to prevent the populator from infinite recursion, when populating recursive data structures such as `apache::thrift::protocol::detail::Value`. The recursion limit checks the type tags along the `populate()` call stacks and ensures that no tag appears more than `recursion_limit` times. Reviewed By: Mizuchi Differential Revision: D70921830 fbshipit-source-id: d053a7fa9b528f50840fc2d732dfdd8c9a9b4de6
1 parent 27802f8 commit 4fe72cd

File tree

3 files changed

+133
-51
lines changed

3 files changed

+133
-51
lines changed

third-party/thrift/src/thrift/lib/cpp2/reflection/populator.h

+112-51
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <iostream>
2121
#include <random>
2222
#include <type_traits>
23+
#include <typeindex>
2324
#include <vector>
2425

2526
#include <folly/FBString.h>
@@ -48,6 +49,7 @@ struct populator_opts {
4849
range<> str_len = range<>(0, 0xFF);
4950
// Probability to use for populating optional fields.
5051
float optional_field_prob = 0.0;
52+
size_t recursion_limit = 0;
5153
};
5254

5355
namespace detail {
@@ -144,6 +146,44 @@ struct deref<PtrType, enable_if_smart_pointer<PtrType>> {
144146
template <typename T>
145147
using infer_tag = type::infer_tag<T, true /* GuessStringTag */>;
146148

149+
template <typename Rng>
150+
struct State {
151+
Rng& rng;
152+
const populator_opts& opts;
153+
std::map<std::type_index, size_t> tag_counts;
154+
};
155+
156+
// Using the type_info of the tags to check if we are populating recursively
157+
template <typename Tag>
158+
class RecursionGuard {
159+
private:
160+
size_t& cnt_;
161+
const bool can_recurse_;
162+
163+
public:
164+
template <typename Rng>
165+
explicit RecursionGuard(State<Rng>& state)
166+
: cnt_(state.tag_counts[typeid(Tag)]),
167+
can_recurse_(cnt_ <= state.opts.recursion_limit) {
168+
if (can_recurse_) {
169+
cnt_++;
170+
}
171+
}
172+
173+
~RecursionGuard() {
174+
if (can_recurse_) {
175+
cnt_--;
176+
}
177+
}
178+
179+
operator bool() const { return can_recurse_; }
180+
181+
RecursionGuard(const RecursionGuard&) = delete;
182+
RecursionGuard(RecursionGuard&&) = delete;
183+
RecursionGuard& operator=(const RecursionGuard&) = delete;
184+
RecursionGuard& operator=(RecursionGuard&&) = delete;
185+
};
186+
147187
} // namespace detail
148188

149189
template <typename Tag, typename Type, typename Enable = void>
@@ -166,13 +206,13 @@ struct populator_methods<
166206
// Special overload to work with lists of booleans.
167207
template <typename Rng>
168208
static void populate(
169-
Rng& rng, const populator_opts&, std::vector<bool>::reference out) {
170-
out = next_value(rng);
209+
detail::State<Rng>& state, std::vector<bool>::reference out) {
210+
out = next_value(state.rng);
171211
}
172212

173213
template <typename Rng>
174-
static void populate(Rng& rng, const populator_opts&, Int& out) {
175-
out = next_value(rng);
214+
static void populate(detail::State<Rng>& state, Int& out) {
215+
out = next_value(state.rng);
176216
}
177217
};
178218

@@ -183,28 +223,29 @@ struct populator_methods<
183223
std::enable_if_t<
184224
type::is_a_v<detail::infer_tag<Fp>, type::floating_point_c>>> {
185225
template <typename Rng>
186-
static void populate(Rng& rng, const populator_opts&, Fp& out) {
226+
static void populate(detail::State<Rng>& state, Fp& out) {
187227
std::uniform_real_distribution<Fp> gen;
188-
out = gen(rng);
228+
out = gen(state.rng);
189229
DVLOG(4) << "generated real: " << out;
190230
}
191231
};
192232

193233
template <>
194234
struct populator_methods<type::string_t, std::string> {
195235
template <typename Rng>
196-
static void populate(Rng& rng, const populator_opts& opts, std::string& str) {
236+
static void populate(detail::State<Rng>& state, std::string& str) {
197237
using larger_char =
198238
std::conditional_t<std::numeric_limits<char>::is_signed, int, unsigned>;
199239

200240
// all printable chars (see `man ascii`)
201241
std::uniform_int_distribution<larger_char> char_gen(0x20, 0x7E);
202242

203-
const std::size_t length = detail::rand_in_range(rng, opts.str_len);
243+
const std::size_t length =
244+
detail::rand_in_range(state.rng, state.opts.str_len);
204245

205246
str = std::string(length, 0);
206247
std::generate_n(str.begin(), length, [&]() {
207-
return static_cast<char>(char_gen(rng));
248+
return static_cast<char>(char_gen(state.rng));
208249
});
209250

210251
DVLOG(4) << "generated string of len" << length;
@@ -216,10 +257,9 @@ struct populator_methods<
216257
type::cpp_type<folly::fbstring, type::string_t>,
217258
folly::fbstring> {
218259
template <typename Rng>
219-
static void populate(
220-
Rng& rng, const populator_opts& opts, folly::fbstring& bin) {
260+
static void populate(detail::State<Rng>& state, folly::fbstring& bin) {
221261
std::string t;
222-
populator_methods<type::string_t, std::string>::populate(rng, opts, t);
262+
populator_methods<type::string_t, std::string>::populate(state, t);
223263
bin = folly::fbstring(std::move(t));
224264
}
225265
};
@@ -237,11 +277,11 @@ void generate_bytes(
237277
template <>
238278
struct populator_methods<type::binary_t, std::string> {
239279
template <typename Rng>
240-
static void populate(Rng& rng, const populator_opts& opts, std::string& bin) {
241-
const auto length = detail::rand_in_range(rng, opts.bin_len);
280+
static void populate(detail::State<Rng>& state, std::string& bin) {
281+
const auto length = detail::rand_in_range(state.rng, state.opts.bin_len);
242282
bin = std::string(length, 0);
243283
auto iter = bin.begin();
244-
generate_bytes(rng, bin, length, [&](uint8_t c) { *iter++ = c; });
284+
generate_bytes(state.rng, bin, length, [&](uint8_t c) { *iter++ = c; });
245285
}
246286
};
247287

@@ -250,10 +290,9 @@ struct populator_methods<
250290
type::cpp_type<folly::fbstring, type::binary_t>,
251291
folly::fbstring> {
252292
template <typename Rng>
253-
static void populate(
254-
Rng& rng, const populator_opts& opts, folly::fbstring& bin) {
293+
static void populate(detail::State<Rng>& state, folly::fbstring& bin) {
255294
std::string t;
256-
populator_methods<type::binary_t, std::string>::populate(rng, opts, t);
295+
populator_methods<type::binary_t, std::string>::populate(state, t);
257296
bin = folly::fbstring(std::move(t));
258297
}
259298
};
@@ -263,14 +302,13 @@ struct populator_methods<
263302
type::cpp_type<folly::IOBuf, type::binary_t>,
264303
folly::IOBuf> {
265304
template <typename Rng>
266-
static void populate(
267-
Rng& rng, const populator_opts& opts, folly::IOBuf& bin) {
268-
const auto length = detail::rand_in_range(rng, opts.bin_len);
305+
static void populate(detail::State<Rng>& state, folly::IOBuf& bin) {
306+
const auto length = detail::rand_in_range(state.rng, state.opts.bin_len);
269307
bin = folly::IOBuf(folly::IOBuf::CREATE, length);
270308
bin.append(length);
271309
folly::io::RWUnshareCursor range(&bin);
272310
generate_bytes(
273-
rng, range, length, [&](uint8_t c) { range.write<uint8_t>(c); });
311+
state.rng, range, length, [&](uint8_t c) { range.write<uint8_t>(c); });
274312
}
275313
};
276314

@@ -280,13 +318,11 @@ struct populator_methods<
280318
std::unique_ptr<folly::IOBuf>> {
281319
template <typename Rng>
282320
static void populate(
283-
Rng& rng,
284-
const populator_opts& opts,
285-
std::unique_ptr<folly::IOBuf>& bin) {
321+
detail::State<Rng>& state, std::unique_ptr<folly::IOBuf>& bin) {
286322
bin = std::make_unique<folly::IOBuf>();
287323
return populator_methods<
288324
type::cpp_type<folly::IOBuf, type::binary_t>,
289-
folly::IOBuf>::populate(rng, opts, *bin);
325+
folly::IOBuf>::populate(state, *bin);
290326
}
291327
};
292328

@@ -300,8 +336,8 @@ struct populator_methods<
300336
using type_methods = populator_methods<Tag, element_type>;
301337

302338
template <typename Rng>
303-
static void populate(Rng& rng, const populator_opts& opts, PtrType& out) {
304-
return type_methods::populate(rng, opts, *out);
339+
static void populate(detail::State<Rng>& state, PtrType& out) {
340+
return type_methods::populate(state, *out);
305341
}
306342
};
307343

@@ -315,9 +351,9 @@ struct populator_methods<
315351
using int_methods = populator_methods<detail::infer_tag<int_type>, int_type>;
316352

317353
template <typename Rng>
318-
static void populate(Rng& rng, const populator_opts& opts, Type& out) {
354+
static void populate(detail::State<Rng>& state, Type& out) {
319355
int_type tmp;
320-
int_methods::populate(rng, opts, tmp);
356+
int_methods::populate(state, tmp);
321357
out = static_cast<Type>(tmp);
322358
}
323359
};
@@ -329,15 +365,21 @@ struct populator_methods<type::list<ElemTag>, Type> {
329365
using elem_methods = populator_methods<ElemTag, elem_type>;
330366

331367
template <typename Rng>
332-
static void populate(Rng& rng, const populator_opts& opts, Type& out) {
333-
std::uint32_t list_size = detail::rand_in_range(rng, opts.list_len);
368+
static void populate(detail::State<Rng>& state, Type& out) {
369+
auto recursion_guard = detail::RecursionGuard<type::list<ElemTag>>(state);
370+
if (!recursion_guard) {
371+
return;
372+
}
373+
374+
std::uint32_t list_size =
375+
detail::rand_in_range(state.rng, state.opts.list_len);
334376
out = Type();
335377

336378
DVLOG(3) << "populating list size " << list_size;
337379

338380
out.resize(list_size);
339381
for (decltype(list_size) i = 0; i < list_size; i++) {
340-
elem_methods::populate(rng, opts, out[i]);
382+
elem_methods::populate(state, out[i]);
341383
}
342384
}
343385
};
@@ -351,15 +393,21 @@ struct populator_methods<type::set<ElemTag>, Type> {
351393
using elem_methods = populator_methods<ElemTag, elem_type>;
352394

353395
template <typename Rng>
354-
static void populate(Rng& rng, const populator_opts& opts, Type& out) {
355-
std::uint32_t set_size = detail::rand_in_range(rng, opts.set_len);
396+
static void populate(detail::State<Rng>& state, Type& out) {
397+
auto recursion_guard = detail::RecursionGuard<type::set<ElemTag>>(state);
398+
if (!recursion_guard) {
399+
return;
400+
}
401+
402+
std::uint32_t set_size =
403+
detail::rand_in_range(state.rng, state.opts.set_len);
356404

357405
DVLOG(3) << "populating set size " << set_size;
358406
out = Type();
359407

360408
for (decltype(set_size) i = 0; i < set_size; i++) {
361409
elem_type tmp;
362-
elem_methods::populate(rng, opts, tmp);
410+
elem_methods::populate(state, tmp);
363411
out.insert(std::move(tmp));
364412
}
365413
}
@@ -375,16 +423,23 @@ struct populator_methods<type::map<KeyTag, MappedTag>, Type> {
375423
using mapped_methods = populator_methods<MappedTag, mapped_type>;
376424

377425
template <typename Rng>
378-
static void populate(Rng& rng, const populator_opts& opts, Type& out) {
379-
std::uint32_t map_size = detail::rand_in_range(rng, opts.map_len);
426+
static void populate(detail::State<Rng>& state, Type& out) {
427+
auto recursion_guard =
428+
detail::RecursionGuard<type::map<KeyTag, MappedTag>>(state);
429+
if (!recursion_guard) {
430+
return;
431+
}
432+
433+
std::uint32_t map_size =
434+
detail::rand_in_range(state.rng, state.opts.map_len);
380435

381436
DVLOG(3) << "populating map size " << map_size;
382437
out = Type();
383438

384439
for (decltype(map_size) i = 0; i < map_size; i++) {
385440
key_type key_tmp;
386-
key_methods::populate(rng, opts, key_tmp);
387-
mapped_methods::populate(rng, opts, out[std::move(key_tmp)]);
441+
key_methods::populate(state, key_tmp);
442+
mapped_methods::populate(state, out[std::move(key_tmp)]);
388443
}
389444
}
390445
};
@@ -393,13 +448,13 @@ struct populator_methods<type::map<KeyTag, MappedTag>, Type> {
393448
template <typename Union>
394449
struct populator_methods<type::union_t<Union>, Union> {
395450
template <typename Rng>
396-
static void populate(Rng& rng, const populator_opts& opts, Union& out) {
451+
static void populate(detail::State<Rng>& state, Union& out) {
397452
DVLOG(3) << "begin writing union: "
398453
<< op::get_class_name_v<Union> << ", type: "
399454
<< folly::to_underlying(out.getType());
400455

401456
const auto selected = static_cast<type::Ordinal>(detail::rand_in_range(
402-
rng, populator_opts::range<size_t>{0, op::size_v<Union> - 1}));
457+
state.rng, populator_opts::range<size_t>{0, op::size_v<Union> - 1}));
403458

404459
op::for_each_ordinal<Union>([&](auto ord) {
405460
using Ord = decltype(ord);
@@ -414,7 +469,7 @@ struct populator_methods<type::union_t<Union>, Union> {
414469
<< op::get_name_v<Union, Ord> << ", fid: "
415470
<< folly::to_underlying(op::get_field_id_v<Union, Ord>);
416471

417-
methods::populate(rng, opts, op::get<Ord>(out).ensure());
472+
methods::populate(state, op::get<Ord>(out).ensure());
418473
});
419474

420475
DVLOG(3) << "end writing union";
@@ -428,7 +483,7 @@ struct populator_methods<type::struct_t<Struct>, Struct> {
428483
class member_populator {
429484
public:
430485
template <typename Ord, typename Rng>
431-
void operator()(Ord, Rng& rng, const populator_opts& opts, Struct& out) {
486+
void operator()(Ord, detail::State<Rng>& state, Struct& out) {
432487
using methods = populator_methods<
433488
op::get_type_tag<Struct, Ord>,
434489
op::get_native_type<Struct, Ord>>;
@@ -439,7 +494,7 @@ struct populator_methods<type::struct_t<Struct>, Struct> {
439494
// Popualate optional fields with `optional_field_prob` probability.
440495
const auto skip = //
441496
::apache::thrift::detail::is_optional_field_ref_v<field_ref_type> &&
442-
!detail::get_bernoulli(rng, opts.optional_field_prob);
497+
!detail::get_bernoulli(state.rng, state.opts.optional_field_prob);
443498
if (skip) {
444499
return;
445500
}
@@ -448,15 +503,20 @@ struct populator_methods<type::struct_t<Struct>, Struct> {
448503

449504
op::ensure<Ord>(out);
450505
methods::populate(
451-
rng, opts, detail::deref<field_ref_type>::clear_and_get(got));
506+
state, detail::deref<field_ref_type>::clear_and_get(got));
452507
}
453508
};
454509

455510
public:
456511
template <typename Rng>
457-
static void populate(Rng& rng, const populator_opts& opts, Struct& out) {
512+
static void populate(detail::State<Rng>& state, Struct& out) {
513+
auto recursion_guard =
514+
detail::RecursionGuard<type::struct_t<Struct>>(state);
515+
if (!recursion_guard) {
516+
return;
517+
}
458518
op::for_each_ordinal<Struct>(
459-
[&](auto ord) { member_populator()(ord, rng, opts, out); });
519+
[&](auto ord) { member_populator()(ord, state, out); });
460520
}
461521
};
462522
template <typename Exn>
@@ -470,9 +530,9 @@ struct populator_methods<type::adapted<Adapter, InnerTag>, T> {
470530
using inner_methods = populator_methods<InnerTag, inner_type>;
471531

472532
template <typename Rng>
473-
static void populate(Rng& rng, const populator_opts& opts, T& out) {
533+
static void populate(detail::State<Rng>& state, T& out) {
474534
inner_type tmp;
475-
inner_methods::populate(rng, opts, tmp);
535+
inner_methods::populate(state, tmp);
476536
out = Adapter::fromThrift(std::move(tmp));
477537
}
478538
};
@@ -500,7 +560,8 @@ struct populator_methods<
500560

501561
template <typename Type, typename Rng, typename Tag = detail::infer_tag<Type>>
502562
void populate(Type& out, const populator_opts& opts, Rng& rng) {
503-
return populator_methods<Tag, Type>::populate(rng, opts, out);
563+
detail::State<Rng> state{rng, opts, {}};
564+
return populator_methods<Tag, Type>::populate(state, out);
504565
}
505566

506567
} // namespace apache::thrift::populator

0 commit comments

Comments
 (0)