diff options
author | Lang Hames <lhames@gmail.com> | 2019-02-05 23:17:11 +0000 |
---|---|---|
committer | Lang Hames <lhames@gmail.com> | 2019-02-05 23:17:11 +0000 |
commit | 3e040e05f89c058bb6e6a88c4e9ffccf1185b084 (patch) | |
tree | 499687725ced8471cb5893b8d57f80f937c60165 /llvm/unittests/ADT | |
parent | 7b7a4ef3d33d85efa6b27a51919fe7ef956be6ee (diff) | |
download | bcm5719-llvm-3e040e05f89c058bb6e6a88c4e9ffccf1185b084.tar.gz bcm5719-llvm-3e040e05f89c058bb6e6a88c4e9ffccf1185b084.zip |
[ADT] Add a fallible_iterator wrapper.
A fallible iterator is one whose increment or decrement operations may fail.
This would usually be supported by replacing the ++ and -- operators with
methods that return error:
class MyFallibleIterator {
public:
// ...
Error inc();
Errro dec();
// ...
};
The downside of this style is that it no longer conforms to the C++ iterator
concept, and can not make use of standard algorithms and features such as
range-based for loops.
The fallible_iterator wrapper takes an iterator written in the style above
and adapts it to (mostly) conform with the C++ iterator concept. It does this
by providing standard ++ and -- operator implementations, returning any errors
generated via a side channel (an Error reference passed into the wrapper at
construction time), and immediately jumping the iterator to a known 'end'
value upon error. It also marks the Error as checked any time an iterator is
compared with a known end value and found to be inequal, allowing early exit
from loops without redundant error checking*.
Usage looks like:
MyFallibleIterator I = ..., E = ...;
Error Err = Error::success();
for (auto &Elem : make_fallible_range(I, E, Err)) {
// Loop body is only entered when safe.
// Early exits from loop body permitted without checking Err.
if (SomeCondition)
return;
}
if (Err)
// Handle error.
* Since failure causes a fallible iterator to jump to end, testing that a
fallible iterator is not an end value implicitly verifies that the error is a
success value, and so is equivalent to an error check.
Reviewers: dblaikie, rupprecht
Subscribers: mgorny, dexonsmith, kristina, llvm-commits
Tags: #llvm
Differential Revision: https://reviews.llvm.org/D57618
llvm-svn: 353237
Diffstat (limited to 'llvm/unittests/ADT')
-rw-r--r-- | llvm/unittests/ADT/CMakeLists.txt | 3 | ||||
-rw-r--r-- | llvm/unittests/ADT/FallibleIteratorTest.cpp | 291 |
2 files changed, 294 insertions, 0 deletions
diff --git a/llvm/unittests/ADT/CMakeLists.txt b/llvm/unittests/ADT/CMakeLists.txt index 098b6b67416..d2a35273389 100644 --- a/llvm/unittests/ADT/CMakeLists.txt +++ b/llvm/unittests/ADT/CMakeLists.txt @@ -18,6 +18,7 @@ add_llvm_unittest(ADTTests DenseSetTest.cpp DepthFirstIteratorTest.cpp EquivalenceClassesTest.cpp + FallibleIteratorTest.cpp FoldingSet.cpp FunctionExtrasTest.cpp FunctionRefTest.cpp @@ -71,4 +72,6 @@ add_llvm_unittest(ADTTests VariadicFunctionTest.cpp ) +target_link_libraries(ADTTests PRIVATE LLVMTestingSupport) + add_dependencies(ADTTests intrinsics_gen) diff --git a/llvm/unittests/ADT/FallibleIteratorTest.cpp b/llvm/unittests/ADT/FallibleIteratorTest.cpp new file mode 100644 index 00000000000..d3389744ffb --- /dev/null +++ b/llvm/unittests/ADT/FallibleIteratorTest.cpp @@ -0,0 +1,291 @@ +//===- unittests/ADT/FallibleIteratorTest.cpp - fallible_iterator.h tests -===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/fallible_iterator.h" +#include "llvm/Testing/Support/Error.h" + +#include "gtest/gtest-spi.h" +#include "gtest/gtest.h" + +#include <utility> +#include <vector> + +using namespace llvm; + +namespace { + +using ItemValid = enum { ValidItem, InvalidItem }; +using LinkValid = enum { ValidLink, InvalidLink }; + +class Item { +public: + Item(ItemValid V) : V(V) {} + bool isValid() const { return V == ValidItem; } + +private: + ItemValid V; +}; + +// A utility to mock "bad collections". It supports both invalid items, +// where the dereference operator may return an Error, and bad links +// where the inc/dec operations may return an Error. +// Each element of the mock collection contains a pair of a (possibly broken) +// item and link. +using FallibleCollection = std::vector<std::pair<Item, LinkValid>>; + +class FallibleCollectionWalker { +public: + FallibleCollectionWalker(FallibleCollection &C, unsigned Idx) + : C(C), Idx(Idx) {} + + Item &operator*() { return C[Idx].first; } + + const Item &operator*() const { return C[Idx].first; } + + Error inc() { + assert(Idx != C.size() && "Walking off end of (mock) collection"); + if (C[Idx].second == ValidLink) { + ++Idx; + return Error::success(); + } + return make_error<StringError>("cant get next object in (mock) collection", + inconvertibleErrorCode()); + } + + Error dec() { + assert(Idx != 0 && "Walking off start of (mock) collection"); + --Idx; + if (C[Idx].second == ValidLink) + return Error::success(); + return make_error<StringError>("cant get prev object in (mock) collection", + inconvertibleErrorCode()); + } + + friend bool operator==(const FallibleCollectionWalker &LHS, + const FallibleCollectionWalker &RHS) { + assert(&LHS.C == &RHS.C && "Comparing iterators across collectionss."); + return LHS.Idx == RHS.Idx; + } + +private: + FallibleCollection &C; + unsigned Idx; +}; + +class FallibleCollectionWalkerWithStructDeref + : public FallibleCollectionWalker { +public: + using FallibleCollectionWalker::FallibleCollectionWalker; + + Item *operator->() { return &this->operator*(); } + + const Item *operator->() const { return &this->operator*(); } +}; + +class FallibleCollectionWalkerWithFallibleDeref + : public FallibleCollectionWalker { +public: + using FallibleCollectionWalker::FallibleCollectionWalker; + + Expected<Item &> operator*() { + auto &I = FallibleCollectionWalker::operator*(); + if (!I.isValid()) + return make_error<StringError>("bad item", inconvertibleErrorCode()); + return I; + } + + Expected<const Item &> operator*() const { + const auto &I = FallibleCollectionWalker::operator*(); + if (!I.isValid()) + return make_error<StringError>("bad item", inconvertibleErrorCode()); + return I; + } +}; + +TEST(FallibleIteratorTest, BasicSuccess) { + + // Check that a basic use-case involing successful iteration over a + // "FallibleCollection" works. + + FallibleCollection C({{ValidItem, ValidLink}, {ValidItem, ValidLink}}); + + FallibleCollectionWalker begin(C, 0); + FallibleCollectionWalker end(C, 2); + + Error Err = Error::success(); + for (auto &Elem : + make_fallible_range<FallibleCollectionWalker>(begin, end, Err)) + EXPECT_TRUE(Elem.isValid()); + cantFail(std::move(Err)); +} + +TEST(FallibleIteratorTest, BasicFailure) { + + // Check that a iteration failure (due to the InvalidLink state on element one + // of the fallible collection) breaks out of the loop and raises an Error. + + FallibleCollection C({{ValidItem, ValidLink}, {ValidItem, InvalidLink}}); + + FallibleCollectionWalker begin(C, 0); + FallibleCollectionWalker end(C, 2); + + Error Err = Error::success(); + for (auto &Elem : + make_fallible_range<FallibleCollectionWalker>(begin, end, Err)) + EXPECT_TRUE(Elem.isValid()); + + EXPECT_THAT_ERROR(std::move(Err), Failed()) << "Expected failure value"; +} + +TEST(FallibleIteratorTest, NoRedundantErrorCheckOnEarlyExit) { + + // Check that an early return from the loop body does not require a redundant + // check of Err. + + FallibleCollection C({{ValidItem, ValidLink}, {ValidItem, ValidLink}}); + + FallibleCollectionWalker begin(C, 0); + FallibleCollectionWalker end(C, 2); + + Error Err = Error::success(); + for (auto &Elem : + make_fallible_range<FallibleCollectionWalker>(begin, end, Err)) { + (void)Elem; + return; + } + // Err not checked, but should be ok because we exit from the loop + // body. +} + +#if LLVM_ENABLE_ABI_BREAKING_CHECKS +TEST(FallibleIteratorTest, RegularLoopExitRequiresErrorCheck) { + + // Check that Err must be checked after a normal (i.e. not early) loop exit + // by failing to check and expecting program death (due to the unchecked + // error). + + EXPECT_DEATH( + { + FallibleCollection C({{ValidItem, ValidLink}, {ValidItem, ValidLink}}); + + FallibleCollectionWalker begin(C, 0); + FallibleCollectionWalker end(C, 2); + + Error Err = Error::success(); + for (auto &Elem : + make_fallible_range<FallibleCollectionWalker>(begin, end, Err)) + (void)Elem; + }, + "Program aborted due to an unhandled Error:") + << "Normal (i.e. not early) loop exit should require an error check"; +} +#endif + +TEST(FallibleIteratorTest, RawIncrementAndDecrementBehavior) { + + // Check the exact behavior of increment / decrement. + + FallibleCollection C({{ValidItem, ValidLink}, + {ValidItem, InvalidLink}, + {ValidItem, ValidLink}, + {ValidItem, InvalidLink}}); + + { + // One increment from begin succeeds. + Error Err = Error::success(); + auto I = make_fallible_itr(FallibleCollectionWalker(C, 0), Err); + ++I; + EXPECT_THAT_ERROR(std::move(Err), Succeeded()); + } + + { + // Two increments from begin fail. + Error Err = Error::success(); + auto I = make_fallible_itr(FallibleCollectionWalker(C, 0), Err); + ++I; + EXPECT_THAT_ERROR(std::move(Err), Succeeded()); + ++I; + EXPECT_THAT_ERROR(std::move(Err), Failed()) << "Expected failure value"; + } + + { + // One decement from element three succeeds. + Error Err = Error::success(); + auto I = make_fallible_itr(FallibleCollectionWalker(C, 3), Err); + --I; + EXPECT_THAT_ERROR(std::move(Err), Succeeded()); + } + + { + // One decement from element three succeeds. + Error Err = Error::success(); + auto I = make_fallible_itr(FallibleCollectionWalker(C, 3), Err); + --I; + EXPECT_THAT_ERROR(std::move(Err), Succeeded()); + --I; + EXPECT_THAT_ERROR(std::move(Err), Failed()); + } +} + +TEST(FallibleIteratorTest, CheckStructDerefOperatorSupport) { + // Check that the fallible_iterator wrapper forwards through to the + // underlying iterator's structure dereference operator if present. + + FallibleCollection C({{ValidItem, ValidLink}, + {ValidItem, ValidLink}, + {InvalidItem, InvalidLink}}); + + FallibleCollectionWalkerWithStructDeref begin(C, 0); + + { + Error Err = Error::success(); + auto I = make_fallible_itr(begin, Err); + EXPECT_TRUE(I->isValid()); + cantFail(std::move(Err)); + } + + { + Error Err = Error::success(); + const auto I = make_fallible_itr(begin, Err); + EXPECT_TRUE(I->isValid()); + cantFail(std::move(Err)); + } +} + +TEST(FallibleIteratorTest, CheckDerefToExpectedSupport) { + + // Check that the fallible_iterator wrapper forwards value types, in + // particular llvm::Expected, correctly. + + FallibleCollection C({{ValidItem, ValidLink}, + {InvalidItem, ValidLink}, + {ValidItem, ValidLink}}); + + FallibleCollectionWalkerWithFallibleDeref begin(C, 0); + FallibleCollectionWalkerWithFallibleDeref end(C, 3); + + Error Err = Error::success(); + auto I = make_fallible_itr(begin, Err); + auto E = make_fallible_end(end); + + Expected<Item> V1 = *I; + EXPECT_THAT_ERROR(V1.takeError(), Succeeded()); + ++I; + EXPECT_NE(I, E); // Implicitly check error. + Expected<Item> V2 = *I; + EXPECT_THAT_ERROR(V2.takeError(), Failed()); + ++I; + EXPECT_NE(I, E); // Implicitly check error. + Expected<Item> V3 = *I; + EXPECT_THAT_ERROR(V3.takeError(), Succeeded()); + ++I; + EXPECT_EQ(I, E); + cantFail(std::move(Err)); +} + +} // namespace |