From 87c40e046cf14c6e0bc4d3ef60eb6ec225283498 Mon Sep 17 00:00:00 2001 From: "David R. MacIver" Date: Tue, 2 Jul 2019 10:16:18 +0100 Subject: [PATCH] Bound the number of consecutive discards we allow, terminating all rejection sampling --- .../hypothesis/internal/conjecture/data.py | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/hypothesis-python/src/hypothesis/internal/conjecture/data.py b/hypothesis-python/src/hypothesis/internal/conjecture/data.py index 97f4a13e56..c2a1ce585b 100644 --- a/hypothesis-python/src/hypothesis/internal/conjecture/data.py +++ b/hypothesis-python/src/hypothesis/internal/conjecture/data.py @@ -752,6 +752,7 @@ def __init__(self, max_length, draw_bytes, observer=None): self.draw_times = [] self.max_depth = 0 self.has_discards = False + self.consecutive_discard_counts = [] self.__result = None @@ -862,15 +863,34 @@ def start_example(self, label): if self.depth > self.max_depth: self.max_depth = self.depth self.__example_record.start_example(label) + self.consecutive_discard_counts.append(0) def stop_example(self, discard=False): if self.frozen: return + self.consecutive_discard_counts.pop() if discard: self.has_discards = True self.depth -= 1 assert self.depth >= -1 self.__example_record.stop_example(discard) + if self.consecutive_discard_counts: + # We block long sequences of discards. This helps us avoid performance + # problems where there is rejection sampling. In particular tests which + # have a very small actual state space but use rejection sampling will + # play badly with generate_novel_prefix() in DataTree, and will end up + # generating very long tests with long runs of the rejection sample. + if discard: + self.consecutive_discard_counts[-1] += 1 + # 20 is a fairly arbitrary limit chosen mostly so that all of the + # existing tests passed under it. Essentially no reasonable + # generation should hit this limit when running in purely random + # mode, but unreasonable generation is fairly widespread, and our + # manipulation of the bitstream can make it more likely. + if self.consecutive_discard_counts[-1] > 20: + self.mark_invalid() + else: + self.consecutive_discard_counts[-1] = 0 def note_event(self, event): self.events.add(event)