HACK: Static detection of infinite sequences
authorMike Curtiss <mcurtiss@fb.com>
Wed, 6 Mar 2013 07:22:54 +0000 (23:22 -0800)
committerOwen Yamauchi <oyamauchi@fb.com>
Wed, 27 Mar 2013 21:39:31 +0000 (14:39 -0700)
Summary:
Certain operations should not be performed on infinite sequences
(e.g. sorting, left-folds, summation).  In some cases, we can
detect that a sequence is infinite at compile-time and provide
a static_assert to prevent such dangerous operations.

Test Plan:
Manually created cases where the operation should
be disallowed.  Compiler correctly raised an error.

Reviewed By: tjackson@fb.com

FB internal diff: D740011

folly/experimental/FileGen-inl.h
folly/experimental/Gen-inl.h
folly/experimental/StringGen-inl.h
folly/experimental/test/GenTest.cpp

index d45cc61882a07ffc8e9b033c338b9c605dace2d8..21b0589c5dc6562346550d9cc3c2c6de5d7f91a6 100644 (file)
@@ -52,6 +52,11 @@ class FileReader : public GenImpl<ByteRange, FileReader> {
       }
     }
   }
+
+  // Technically, there could be infinite files (e.g. /dev/random), but people
+  // who open those can do so at their own risk.
+  static constexpr bool infinite = false;
+
  private:
   File file_;
   std::unique_ptr<IOBuf> buffer_;
index 6f68da1a76d568df7f0b798d6bab3c9731cd5e77..b0e989c1fad5bc22c30fe00c7af1326c261a9f02 100644 (file)
@@ -168,10 +168,16 @@ class GenImpl : public FBounded<Self> {
   template<class Body>
   void foreach(Body&& body) const {
     this->self().apply([&](Value value) -> bool {
+        static_assert(!infinite, "Cannot call foreach on infinite GenImpl");
         body(std::forward<Value>(value));
         return true;
       });
   }
+
+  // Child classes should override if the sequence generated is *definitely*
+  // infinite. 'infinite' may be false_type for some infinite sequences
+  // (due the the Halting Problem).
+  static constexpr bool infinite = false;
 };
 
 template<class LeftValue,
@@ -236,6 +242,8 @@ template<class Value,
 typename std::enable_if<
   IsCompatibleSignature<Handler, void(Value)>::value>::type
 operator|(const GenImpl<Value, Gen>& gen, Handler&& handler) {
+  static_assert(!Gen::infinite,
+                "Cannot pull all values from an infinite sequence.");
   gen.self().foreach(std::forward<Handler>(handler));
 }
 
@@ -439,6 +447,8 @@ public:
       body(arg);
     }
   }
+
+  static constexpr bool infinite = endless;
 };
 
 /**
@@ -470,6 +480,8 @@ public:
     first_.foreach(std::forward<Body>(body));
     second_.foreach(std::forward<Body>(body));
   }
+
+  static constexpr bool infinite = First::infinite || Second::infinite;
 };
 
 /**
@@ -567,6 +579,8 @@ class Map : public Operator<Map<Predicate>> {
         return handler(pred_(std::forward<Value>(value)));
       });
     }
+
+    static constexpr bool infinite = Source::infinite;
   };
 
   template<class Source,
@@ -631,6 +645,8 @@ class Filter : public Operator<Filter<Predicate>> {
         return true;
       });
     }
+
+    static constexpr bool infinite = Source::infinite;
   };
 
   template<class Source,
@@ -699,6 +715,9 @@ class Until : public Operator<Until<Predicate>> {
   Gen compose(const GenImpl<Value, Source>& source) const {
     return Gen(source.self(), pred_);
   }
+
+  // Theoretically an 'until' might stop an infinite
+  static constexpr bool infinite = false;
 };
 
 /**
@@ -809,6 +828,8 @@ class Skip : public Operator<Skip> {
           return handler(std::forward<Value>(value));
         });
     }
+
+    static constexpr bool infinite = Source::infinite;
   };
 
   template<class Source,
@@ -863,6 +884,7 @@ class Order : public Operator<Order<Selector, Comparer>> {
   class Generator :
     public GenImpl<StorageType&&,
                    Generator<Value, Source, StorageType, Result>> {
+    static_assert(!Source::infinite, "Cannot sort infinite source!");
     Source source_;
     Selector selector_;
     Comparer comparer_;
@@ -1010,6 +1032,7 @@ class FoldLeft : public Operator<FoldLeft<Seed, Fold>> {
   template<class Source,
            class Value>
   Seed compose(const GenImpl<Value, Source>& source) const {
+    static_assert(!Source::infinite, "Cannot foldl infinite source");
     Seed accum = seed_;
     source | [&](Value v) {
       accum = fold_(std::move(accum), std::forward<Value>(v));
@@ -1107,6 +1130,7 @@ class All : public Operator<All<Predicate>> {
   template<class Source,
            class Value>
   bool compose(const GenImpl<Value, Source>& source) const {
+    static_assert(!Source::infinite, "Cannot call 'all' on infinite source");
     bool all = true;
     source | [&](Value v) -> bool {
       if (!pred_(std::forward<Value>(v))) {
@@ -1173,6 +1197,7 @@ class Count : public Operator<Count> {
   template<class Source,
            class Value>
   size_t compose(const GenImpl<Value, Source>& source) const {
+    static_assert(!Source::infinite, "Cannot count infinite source");
     return foldl(size_t(0),
                  [](size_t accum, Value v) {
                    return accum + 1;
@@ -1189,12 +1214,11 @@ class Count : public Operator<Count> {
  */
 class Sum : public Operator<Sum> {
  public:
-  Sum() { }
-
   template<class Source,
            class Value,
            class StorageType = typename std::decay<Value>::type>
   StorageType compose(const GenImpl<Value, Source>& source) const {
+    static_assert(!Source::infinite, "Cannot sum infinite source");
     return foldl(StorageType(0),
                  [](StorageType&& accum, Value v) {
                    return std::move(accum) + std::forward<Value>(v);
@@ -1222,6 +1246,9 @@ class Contains : public Operator<Contains<Needle>> {
            class Value,
            class StorageType = typename std::decay<Value>::type>
   bool compose(const GenImpl<Value, Source>& source) const {
+    static_assert(!Source::infinite,
+                  "Calling contains on an infinite source might cause "
+                  "an infinite loop.");
     return !(source | [this](Value value) {
         return !(needle_ == std::forward<Value>(value));
       });
@@ -1414,6 +1441,8 @@ class Concat : public Operator<Concat> {
           inner.foreach(std::forward<Body>(body));
         });
     }
+
+    static constexpr bool infinite = Source::infinite;
   };
 
   template<class Value,
index c3fc4d1214548091e444006f3cd65bc88c5da56e..8ddab93af6cd67fe8f04fcfe910afeda33fa593a 100644 (file)
@@ -109,6 +109,8 @@ class StringResplitter : public Operator<StringResplitter> {
       }
       return true;
     }
+
+    static constexpr bool infinite = Source::infinite;
   };
 
   template<class Source,
index 651dc3954194e7c288544f1031ef3d4f7f34f578..2bc28467d18d7dfd33135fc1794f8a72ba16733d 100644 (file)
@@ -159,7 +159,7 @@ TEST(Gen, Contains) {
       | eachTo<std::string>();
 
     // std::string gen, const char* needle
-    EXPECT_TRUE(gen | contains("49"));
+    EXPECT_TRUE(gen | take(9999) | contains("49"));
   }
 }
 
@@ -427,7 +427,7 @@ TEST(Gen, Any) {
 TEST(Gen, All) {
   EXPECT_TRUE(seq(0, 10) | all([](int i) { return i < 11; }));
   EXPECT_FALSE(seq(0, 10) | all([](int i) { return i < 5; }));
-  EXPECT_FALSE(seq(0) | all([](int i) { return i < 10; }));
+  EXPECT_FALSE(seq(0) | take(9999) | all([](int i) { return i < 10; }));
 
   // empty lists satisfies all
   EXPECT_TRUE(seq(0) | take(0) | all([](int i) { return i < 50; }));