Commit ae4fa388 authored by Mike Curtiss's avatar Mike Curtiss Committed by Owen Yamauchi

HACK: Static detection of infinite sequences

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
parent 5c77fedb
......@@ -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_;
......
......@@ -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,
......
......@@ -109,6 +109,8 @@ class StringResplitter : public Operator<StringResplitter> {
}
return true;
}
static constexpr bool infinite = Source::infinite;
};
template<class Source,
......
......@@ -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; }));
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment