llvm-mirror/unittests/ADT/FallibleIteratorTest.cpp
Lang Hames f9bddc40cd [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
2019-02-05 23:17:11 +00:00

292 lines
8.4 KiB
C++

//===- 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