Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/RangedConstraintManager.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/RangedConstraintManager.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/RangedConstraintManager.h @@ -139,6 +139,30 @@ /// Complexity: O(N) /// where N = size(Original) RangeSet add(RangeSet Original, const llvm::APSInt &Point); + /// Create a new set which is a union of two given ranges. + /// Possible intersections are not checked here. + /// + /// Complexity: O(N + M) + /// where N = size(LHS), M = size(RHS) + RangeSet unite(RangeSet LHS, RangeSet RHS); + /// Create a new set by uniting given range set with the given range. + /// All intersections and adjacent ranges are handled here. + /// + /// Complexity: O(N) + /// where N = size(Original) + RangeSet unite(RangeSet Original, Range Element); + /// Create a new set by uniting given range set with the given point. + /// All intersections and adjacent ranges are handled here. + /// + /// Complexity: O(N) + /// where N = size(Original) + RangeSet unite(RangeSet Original, llvm::APSInt Point); + /// Create a new set by uniting given range set with the given range + /// between points. All intersections and adjacent ranges are handled here. + /// + /// Complexity: O(N) + /// where N = size(Original) + RangeSet unite(RangeSet Original, llvm::APSInt From, llvm::APSInt To); RangeSet getEmptySet() { return &EmptySet; } @@ -223,6 +247,9 @@ ContainerType *construct(ContainerType &&From); RangeSet intersect(const ContainerType &LHS, const ContainerType &RHS); + /// NOTE: This function relies on the fact that all values in the + /// containers are persistent (created via BasicValueFactory::getValue). + ContainerType unite(const ContainerType &LHS, const ContainerType &RHS); // Many operations include producing new APSInt values and that's why // we need this factory. Index: clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp =================================================================== --- clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp +++ clang/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp @@ -110,6 +110,14 @@ RangeSet::ContainerType RangeSet::Factory::EmptySet{}; +RangeSet RangeSet::Factory::add(RangeSet LHS, RangeSet RHS) { + ContainerType Result; + Result.reserve(LHS.size() + RHS.size()); + std::merge(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(), + std::back_inserter(Result)); + return makePersistent(std::move(Result)); +} + RangeSet RangeSet::Factory::add(RangeSet Original, Range Element) { ContainerType Result; Result.reserve(Original.size() + 1); @@ -126,6 +134,129 @@ return add(Original, Range(Point)); } +RangeSet RangeSet::Factory::unite(RangeSet LHS, RangeSet RHS) { + ContainerType Result = unite(*LHS.Impl, *RHS.Impl); + return makePersistent(std::move(Result)); +} + +RangeSet RangeSet::Factory::unite(RangeSet Original, Range R) { + ContainerType Result; + Result.push_back(R); + Result = unite(*Original.Impl, Result); + return makePersistent(std::move(Result)); +} + +RangeSet RangeSet::Factory::unite(RangeSet Original, llvm::APSInt Point) { + return unite(Original, Range(ValueFactory.getValue(Point))); +} + +RangeSet RangeSet::Factory::unite(RangeSet Original, llvm::APSInt From, + llvm::APSInt To) { + return unite(Original, + Range(ValueFactory.getValue(From), ValueFactory.getValue(To))); +} + +RangeSet::ContainerType RangeSet::Factory::unite(const ContainerType &LHS, + const ContainerType &RHS) { + if (LHS.empty()) + return RHS; + if (RHS.empty()) + return LHS; + + using llvm::APSInt; + using iterator = ContainerType::const_iterator; + + iterator I1 = LHS.begin(); + iterator E1 = LHS.end(); + iterator I2 = RHS.begin(); + iterator E2 = RHS.end(); + APSIntType Ty = APSIntType(I1->From()); + const APSInt One = Ty.getValue(1); + const APSInt Min = Ty.getMinValue(); + const APSInt *F = nullptr; + ContainerType Result; + + // This calls when there are no ranges left in one of the ranges. + // Append the rest of the ranges from another range set to the Result + // and return the later. + auto AppendRest = [&Result](iterator I, iterator E) { + Result.append(I, E); + return Result; + }; + + // Handle a corner case first when both range sets start from MIN. + // This helps to avoid complicated conditions below. + if (Min == I1->From() && Min == I2->From()) { + if (I1->To() > I2->To()) { + // The second range is entirely inside the first one. Skip it. + // Check for the end of the range for every incrementation. + if (++I2 == E2) + return AppendRest(I1, E1); + } else { + // The first range is entirely inside the second one. Skip it. + // Check for the end of the range for every incrementation. + if (++I1 == E1) + return AppendRest(I2, E2); + } + } + + while (true) { + // I1->From() shall be lower than I2->From(). + // Otherwise, swap the iterators. + if (I1->From() > I2->From()) { + std::swap(I1, I2); + std::swap(E1, E2); + } + + // At this point, the next range surely starts with I1->From(). + F = &I1->From(); + + // Build a new range. + while (true) { + // Skip all enclosed ranges. + while (I1->To() >= I2->To()) { + // Check for the end of the range for every incrementation. + if (++I2 == E2) { + Result.emplace_back(*F, I1->To()); + return AppendRest(++I1, E1); + } + } + + // Check if we find the end of the new range. + // Add the range below out of this loop. + if (I1->To() < I2->From() - One) + break; + + // The first range is entirely inside the new range. Go next. + // Check for the end of the range for every incrementation. + if (++I1 == E1) { + Result.emplace_back(*F, I2->To()); + return AppendRest(++I2, E2); + } + + // We know that we are at one of the two cases: + // case 1: ###1###.###1###... + // case 2: #####1###.###1###. + // .......###2####### + // Every next range of the first set always go after the second range. + // So swap the iterators without any check. + std::swap(I1, I2); + std::swap(E1, E2); + } + + // Here the first and second ranges are disjoint. So we can add a new + // range. + Result.emplace_back(*F, I1->To()); + + // The first range is entirely inside the added range. Go next. + // Check for the end of the range for every incrementation. + if (++I1 == E1) + return AppendRest(I2, E2); + } + + llvm_unreachable("Normally, we should not reach here"); +} + RangeSet RangeSet::Factory::getRangeSet(Range From) { ContainerType Result; Result.push_back(From); @@ -155,13 +286,6 @@ return new (Buffer) ContainerType(std::move(From)); } -RangeSet RangeSet::Factory::add(RangeSet LHS, RangeSet RHS) { - ContainerType Result; - std::merge(LHS.begin(), LHS.end(), RHS.begin(), RHS.end(), - std::back_inserter(Result)); - return makePersistent(std::move(Result)); -} - const llvm::APSInt &RangeSet::getMinValue() const { assert(!isEmpty()); return begin()->From(); Index: clang/unittests/StaticAnalyzer/RangeSetTest.cpp =================================================================== --- clang/unittests/StaticAnalyzer/RangeSetTest.cpp +++ clang/unittests/StaticAnalyzer/RangeSetTest.cpp @@ -35,12 +35,34 @@ const RangeSet &Set) { return OS << toString(Set); } +// We need it here for better fail diagnostics from gtest. +LLVM_ATTRIBUTE_UNUSED static std::ostream &operator<<(std::ostream &OS, + const Range &R) { + return OS << toString(R); +} } // namespace ento } // namespace clang namespace { +template struct TestValues { + static constexpr T MIN = std::numeric_limits::min(); + static constexpr T MAX = std::numeric_limits::max(); + // MID is a value in the middle of the range + // which unary minus does not affect on, + // e.g. int8/int32(0), uint8(128), uint32(2147483648). + static constexpr T MID = + std::is_signed::value ? 0 : ~(static_cast(-1) / static_cast(2)); + static constexpr T A = MID - (MAX - MID) / 3 * 2; + static constexpr T B = MID - (MAX - MID) / 3; + static constexpr T C = -B; + static constexpr T D = -A; + + static_assert(MIN < A && A < B && B < MID && MID < C && C < D && D < MAX, + "Values shall be in an ascending order"); +}; + template class RangeSetTest : public testing::Test { public: // Init block @@ -55,24 +77,11 @@ using RawRange = std::pair; using RawRangeSet = std::initializer_list; - static constexpr BaseType getMin() { - return std::numeric_limits::min(); - } - static constexpr BaseType getMax() { - return std::numeric_limits::max(); - } - static constexpr BaseType getMid() { - return isSigned() ? 0 : ~(fromInt(-1) / fromInt(2)); - } - - static constexpr bool isSigned() { return std::is_signed::value; } - static constexpr BaseType fromInt(int X) { return static_cast(X); } - - static llvm::APSInt Base; const llvm::APSInt &from(BaseType X) { - llvm::APSInt Dummy = Base; - Dummy = X; - return BVF.getValue(Dummy); + static llvm::APSInt Base{sizeof(BaseType) * 8, + std::is_unsigned::value}; + Base = X; + return BVF.getValue(Base); } Range from(const RawRange &Init) { @@ -160,7 +169,7 @@ void checkAdd(RawRangeSet RawLHS, RawRangeSet RawRHS, RawRangeSet RawExpected) { - wrap(&Self::checkAddImpl, RawRHS, RawLHS, RawExpected); + wrap(&Self::checkAddImpl, RawLHS, RawRHS, RawExpected); } void checkAdd(RawRangeSet RawLHS, BaseType RawRHS, RawRangeSet RawExpected) { @@ -168,6 +177,29 @@ RawExpected); } + template + void checkUniteImpl(RangeSet LHS, RHSType RHS, RangeSet Expected) { + RangeSet Result = F.unite(LHS, RHS); + EXPECT_EQ(Result, Expected) + << "while uniting " << toString(LHS) << " and " << toString(RHS); + } + + void checkUnite(RawRangeSet RawLHS, RawRange RawRHS, + RawRangeSet RawExpected) { + wrap(&Self::checkUniteImpl, RawLHS, RawRHS, RawExpected); + } + + void checkUnite(RawRangeSet RawLHS, RawRangeSet RawRHS, + RawRangeSet RawExpected) { + wrap(&Self::checkUniteImpl, RawLHS, RawRHS, RawExpected); + } + + void checkUnite(RawRangeSet RawLHS, BaseType RawRHS, + RawRangeSet RawExpected) { + wrap(&Self::checkUniteImpl, RawLHS, RawRHS, + RawExpected); + } + void checkDeleteImpl(const llvm::APSInt &Point, RangeSet From, RangeSet Expected) { RangeSet Result = F.deletePoint(From, Point); @@ -183,29 +215,19 @@ } // namespace -template -llvm::APSInt RangeSetTest::Base{sizeof(BaseType) * 8, !isSigned()}; - using IntTypes = ::testing::Types; TYPED_TEST_SUITE(RangeSetTest, IntTypes, ); TYPED_TEST(RangeSetTest, RangeSetNegateTest) { - // Use next values of the range {MIN, A, B, MID, C, D, MAX}. - - constexpr TypeParam MIN = TestFixture::getMin(); - constexpr TypeParam MAX = TestFixture::getMax(); - // MID is a value in the middle of the range - // which unary minus does not affect on, - // e.g. int8/int32(0), uint8(128), uint32(2147483648). - constexpr TypeParam MID = TestFixture::getMid(); - constexpr TypeParam A = MID - TestFixture::fromInt(42 + 42); - constexpr TypeParam B = MID - TestFixture::fromInt(42); - constexpr TypeParam C = -B; - constexpr TypeParam D = -A; - - static_assert(MIN < A && A < B && B < MID && MID < C && C < D && D < MAX, - "Values shall be in an ascending order"); + using TV = TestValues; + constexpr auto MIN = TV::MIN; + constexpr auto MAX = TV::MAX; + constexpr auto MID = TV::MID; + constexpr auto A = TV::A; + constexpr auto B = TV::B; + constexpr auto C = TV::C; + constexpr auto D = TV::D; this->checkNegate({{MIN, A}}, {{MIN, MIN}, {D, MAX}}); this->checkNegate({{MIN, C}}, {{MIN, MIN}, {B, MAX}}); @@ -234,8 +256,9 @@ } TYPED_TEST(RangeSetTest, RangeSetRangeIntersectTest) { - constexpr TypeParam MIN = TestFixture::getMin(); - constexpr TypeParam MAX = TestFixture::getMax(); + using TV = TestValues; + constexpr auto MIN = TV::MIN; + constexpr auto MAX = TV::MAX; // Check that we can correctly intersect empty sets. this->checkIntersect({}, 10, 20, {}); @@ -300,9 +323,11 @@ this->checkContains({{0, 5}, {10, 12}, {15, 20}}, 10, true); this->checkContains({{0, 5}, {5, 7}, {8, 10}, {12, 41}}, 10, true); - constexpr TypeParam MIN = TestFixture::getMin(); - constexpr TypeParam MAX = TestFixture::getMax(); - constexpr TypeParam MID = TestFixture::getMid(); + using TV = TestValues; + constexpr auto MIN = TV::MIN; + constexpr auto MAX = TV::MAX; + constexpr auto MID = TV::MID; + this->checkContains({{MIN, MAX}}, 0, true); this->checkContains({{MIN, MAX}}, MID, true); this->checkContains({{MIN, MAX}}, -10, true); @@ -331,9 +356,10 @@ } TYPED_TEST(RangeSetTest, RangeSetDeletePointTest) { - constexpr TypeParam MIN = TestFixture::getMin(); - constexpr TypeParam MAX = TestFixture::getMax(); - constexpr TypeParam MID = TestFixture::getMid(); + using TV = TestValues; + constexpr auto MIN = TV::MIN; + constexpr auto MAX = TV::MAX; + constexpr auto MID = TV::MID; this->checkDelete(MID, {{MIN, MAX}}, {{MIN, MID - 1}, {MID + 1, MAX}}); // Check that delete works with an empty set. @@ -347,3 +373,221 @@ // Check that delete of the point not from the range set works as expected. this->checkDelete(10, {{0, 5}, {20, 30}}, {{0, 5}, {20, 30}}); } + +TYPED_TEST(RangeSetTest, RangeSetUniteTest) { + using TV = TestValues; + constexpr auto MIN = TV::MIN; + constexpr auto MAX = TV::MAX; + constexpr auto MID = TV::MID; + constexpr auto A = TV::A; + constexpr auto B = TV::B; + constexpr auto C = TV::C; + constexpr auto D = TV::D; + + // LHS and RHS is empty. + // RHS => + // LHS => = + // ___________________ ___________________ + this->checkUnite({}, {}, {}); + + // RHS is empty. + // RHS => + // LHS => _____ = _____ + // ______/_____\______ ______/_____\______ + this->checkUnite({{A, B}}, {}, {{A, B}}); + this->checkUnite({{A, B}, {C, D}}, {}, {{A, B}, {C, D}}); + this->checkUnite({{MIN, MIN}}, {}, {{MIN, MIN}}); + this->checkUnite({{MAX, MAX}}, {}, {{MAX, MAX}}); + this->checkUnite({{MIN, MIN}, {MAX, MAX}}, {}, {{MIN, MIN}, {MAX, MAX}}); + + // LHS is empty. + // RHS => ___ + // LHS => / \ = _____ + // ______/_____\______ ______/_____\______ + this->checkUnite({}, B, {{B, B}}); + this->checkUnite({}, {B, C}, {{B, C}}); + this->checkUnite({}, {{MIN, B}, {C, MAX}}, {{MIN, B}, {C, MAX}}); + this->checkUnite({}, {{MIN, MIN}}, {{MIN, MIN}}); + this->checkUnite({}, {{MAX, MAX}}, {{MAX, MAX}}); + this->checkUnite({}, {{MIN, MIN}, {MAX, MAX}}, {{MIN, MIN}, {MAX, MAX}}); + + // RHS is detached from LHS. + // RHS => ___ + // LHS => ___ / \ = ___ _____ + // __/___\___/_____\__ __/___\___/_____\__ + this->checkUnite({{A, C}}, D, {{A, C}, {D, D}}); + this->checkUnite({{MID, C}, {D, MAX}}, A, {{A, A}, {MID, C}, {D, MAX}}); + this->checkUnite({{A, B}}, {MID, D}, {{A, B}, {MID, D}}); + this->checkUnite({{MIN, A}, {D, MAX}}, {B, C}, {{MIN, A}, {B, C}, {D, MAX}}); + this->checkUnite({{B, MID}, {D, MAX}}, {{MIN, A}, {C, C}}, + {{MIN, A}, {B, MID}, {C, C}, {D, MAX}}); + this->checkUnite({{MIN, A}, {C, C}}, {{B, MID}, {D, MAX}}, + {{MIN, A}, {B, MID}, {C, C}, {D, MAX}}); + this->checkUnite({{MAX, MAX}}, {A, B}, {{A, B}, {MAX, MAX}}); + this->checkUnite({{MIN, MIN}}, {A, B}, {{MIN, MIN}, {A, B}}); + this->checkUnite({{MIN, MIN}}, {MAX, MAX}, {{MIN, MIN}, {MAX, MAX}}); + + // RHS is inside LHS. + // RHS => ___ + // LHS => ___/___\___ = ___________ + // ___/__/_____\__\___ ___/___________\___ + this->checkUnite({{A, C}}, MID, {{A, C}}); + this->checkUnite({{A, D}}, {B, C}, {{A, D}}); + + // RHS wraps LHS. + // RHS => _________ + // LHS => / _____ \ = ___________ + // ___/__/_____\__\___ ___/___________\___ + this->checkUnite({{MID, MID}}, {A, D}, {{A, D}}); + this->checkUnite({{B, C}}, {A, D}, {{A, D}}); + this->checkUnite({{A, B}}, {MIN, MAX}, {{MIN, MAX}}); + + // RHS equals to LHS. + // RHS => _________ + // LHS => /_________\ = ___________ + // ___/___________\___ ___/___________\___ + this->checkUnite({{MIN, MIN}}, MIN, {{MIN, MIN}}); + this->checkUnite({{A, B}}, {A, B}, {{A, B}}); + this->checkUnite({{MAX, MAX}}, {{MAX, MAX}}, {{MAX, MAX}}); + this->checkUnite({{MIN, MIN}}, {{MIN, MIN}}, {{MIN, MIN}}); + this->checkUnite({{MIN, MIN}, {MAX, MAX}}, {{MIN, MIN}, {MAX, MAX}}, + {{MIN, MIN}, {MAX, MAX}}); + + // RHS equals to LHS. + // RHS => _____ + // LHS => /_____\_____ = ___________ + // /_______\____\___ /___________\___ + this->checkUnite({{MIN, A}}, {MIN, B}, {{MIN, B}}); + + // RHS equals to LHS. + // RHS => __________ + // LHS => /______ \ = ___________ + // /_______\____\___ /___________\___ + this->checkUnite({{MIN, B}}, {MIN, A}, {{MIN, B}}); + + // RHS intersects right of LHS. + // RHS => ______ + // LHS => ___/____ \ = ___________ + // ___/__/_____\__\___ ___/___________\___ + this->checkUnite({{A, C}}, C, {{A, C}}); + this->checkUnite({{A, C}}, {B, D}, {{A, D}}); + + // RHS intersects left of LHS. + // RHS => ______ + // LHS => / ____\___ = ___________ + // ___/__/_____\__\___ ___/___________\___ + this->checkUnite({{B, D}}, B, {{B, D}}); + this->checkUnite({{B, D}}, {A, C}, {{A, D}}); + + // RHS adjacent to LHS on right. + // RHS => _____ + // LHS => ______ / \ = _______________ + // _/______\/_______\_ _/_______________\_ + this->checkUnite({{A, B - 1}}, B, {{A, B}}); + this->checkUnite({{A, C}}, {C + 1, D}, {{A, D}}); + + // RHS adjacent to LHS on left. + // RHS => _____ + // LHS => / \ ______ = _______________ + // _/_______\/______\_ _/_______________\_ + this->checkUnite({{B + 1, C}}, B, {{B, C}}); + this->checkUnite({{B, D}}, {A, B - 1}, {{A, D}}); + + // RHS adjacent to LHS in between. + // RHS => ___ + // LHS => ___ / \ ___ = _______________ + // _/___\/_____\/___\_ _/_______________\_ + this->checkUnite({{A, MID - 1}, {MID + 1, D}}, MID, {{A, D}}); + this->checkUnite({{MIN, A}, {D, MAX}}, {A + 1, D - 1}, {{MIN, MAX}}); + + // RHS adjacent to LHS on the outside. + // RHS => __ __ + // LHS => / \ ___ / \ = _______________ + // _/____\/___\/____\_ _/_______________\_ + this->checkUnite({{C, C}}, {{A, C - 1}, {C + 1, D}}, {{A, D}}); + this->checkUnite({{B, MID}}, {{A, B - 1}, {MID + 1, D}}, {{A, D}}); + + // RHS wraps two subranges of LHS. + // RHS => ___________ + // LHS => / ___ ___ \ = _____________ + // __/_/___\_/___\_\__ __/_____________\__ + this->checkUnite({{B, B}, {MID, MID}, {C, C}}, {{A, D}}, {{A, D}}); + this->checkUnite({{A, B}, {MID, C}}, {{MIN, D}}, {{MIN, D}}); + + // RHS intersects two subranges of LHS. + // RHS => _________ + // LHS => __/__ _\__ = _______________ + // _/_/___\____/__\_\_ _/_______________\_ + this->checkUnite({{MIN, B}, {C, MAX}}, {{A, D}}, {{MIN, MAX}}); + + // Multiple intersections. + + // RHS => + // LHS => /\ /\ = __ __ + // _/__\_/__\_/\_/\_/\_ _/__\_/__\_/\_/\_/\_ + this->checkUnite({{MIN, A}, {A + 2, B}}, {{MID, C}, {C + 2, D - 2}, {D, MAX}}, + {{MIN, A}, {A + 2, B}, {MID, C}, {C + 2, D - 2}, {D, MAX}}); + this->checkUnite({{MIN, MIN}, {A, A}}, {{B, B}, {C, C}, {MAX, MAX}}, + {{MIN, MIN}, {A, A}, {B, B}, {C, C}, {MAX, MAX}}); + + // RHS => + // LHS => /\ /\ = __ __ + // _/\_/\_/\__/__\_/__\_ _/\_/\_/\_/__\_/__\_ + this->checkUnite({{C + 2, D - 2}, {D, MAX}}, {{MIN, A}, {A + 2, B}, {MID, C}}, + {{MIN, A}, {A + 2, B}, {MID, C}, {C + 2, D - 2}, {D, MAX}}); + this->checkUnite({{C, C}, {MAX, MAX}}, {{MIN, MIN}, {A, A}, {B, B}}, + {{MIN, MIN}, {A, A}, {B, B}, {C, C}, {MAX, MAX}}); + + // RHS => + // LHS => _ /\ _ /\ _ /\ = + // _/_\_/__\_/_\_/__\_/_\_/__\_ + // + // RSLT => _ __ _ __ _ __ + // _/_\_/__\_/_\_/__\_/_\_/__\_ + this->checkUnite({{A + 2, B}, {MID + 2, C}, {D + 2, MAX}}, + {{MIN, A}, {B + 2, MID}, {C + 2, D}}, + {{MIN, A}, + {A + 2, B}, + {B + 2, MID}, + {MID + 2, C}, + {C + 2, D}, + {D + 2, MAX}}); + this->checkUnite({{A, A}, {C, C}, {MAX, MAX}}, {{MIN, MIN}, {B, B}, {D, D}}, + {{MIN, MIN}, {A, A}, {B, B}, {C, C}, {D, D}, {MAX, MAX}}); + + // RHS => + // LHS => /\ _ /\ _ /\ _ = + // _/__\_/_\_/__\_/_\_/__\_/_\_ + // + // RSLT => __ _ __ _ __ _ + // _/__\_/_\_/__\_/_\_/__\_/_\_ + this->checkUnite({{MIN, A}, {B + 2, MID}, {C + 2, D}}, + {{A + 2, B}, {MID + 2, C}, {D + 2, MAX}}, + {{MIN, A}, + {A + 2, B}, + {B + 2, MID}, + {MID + 2, C}, + {C + 2, D}, + {D + 2, MAX}}); + this->checkUnite({{MIN, MIN}, {B, B}, {D, D}}, {{A, A}, {C, C}, {MAX, MAX}}, + {{MIN, MIN}, {A, A}, {B, B}, {C, C}, {D, D}, {MAX, MAX}}); + + // RHS => _ __ _ + // LHS => /_\ /_ \ _ / \ = ___ ____________ + // _/___\_/__\_\/_\/___\_ _/___\_/____________\_ + this->checkUnite({{MIN, A}, {B, C}, {D, MAX}}, + {{MIN, A}, {B, C - 2}, {C + 1, D - 1}}, + {{MIN, A}, {B, MAX}}); + this->checkUnite({{A, A}, {B, MID}, {D, D}}, + {{A, A}, {B, B}, {MID + 1, D - 1}}, {{A, A}, {B, D}}); + + // RHS => ___ ___ + // LHS => /\ _/_ \_ / _ \ /\ = + // _/\_/__\//__\ /\\_/_/_\_\_/__\_ + // + // RSLT => ___________ _____ __ + // _/\_/___________\_/_____\_/__\_ + this->checkUnite({{A, B - 1}, {B + 1, C - 1}, {C + 2, D}, {MAX - 1, MAX}}, + {{MIN, MIN}, {B, MID}, {MID + 1, C}, {C + 4, D - 1}}, + {{MIN, MIN}, {A, C}, {C + 2, D}, {MAX - 1, MAX}}); +}