mirror of
https://github.com/RPCS3/llvm-mirror.git
synced 2024-11-25 12:12:47 +01:00
f9bddc40cd
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
292 lines
8.4 KiB
C++
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
|