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; } @@ -220,6 +244,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 @@ -108,6 +108,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); @@ -124,6 +132,208 @@ 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; + + /// @class BoundIterator is a helper class for iterating through the bounds + /// of every Range from the RangeSet. It points to one of the range + /// values (`From`, `To`). + /// Incrementation uses RangeSet::const_iterator to pass from one bound to + /// another. RangeSet iterator increments if reaches the upper bound. + /// Example: + /// RangeSet RS{Range1, Range2, ... RangeN} + /// RangeBoundIterator it{RS}; + /// ++it; // Range1->From() + /// ++it; // Range1->To() + /// ++it; // Range2->From() + /// ++it; // Range2->To() + /// ... + /// + class BoundIterator { + using iterator = ContainerType::const_iterator; + iterator I; + iterator E; + const APSInt *Int; + bool IsFrom = true; + + public: + BoundIterator(const ContainerType &R) + : I(R.begin()), E(R.end()), Int(&I->From()) {} + + /// @returns - a pointer to value of the current bound; + /// - `nullptr` if iterator reached the end of the range. + const APSInt *operator*() { return Int; } + + /// Move the pointer to the next bound. + /// + /// @returns `true` if previous value is `From` and `false` otherwise. + bool operator++(int) { + if (IsFrom) + Int = &I->To(); + else { + ++I; + Int = (I != E) ? &I->From() : nullptr; + } + IsFrom = !IsFrom; + return !IsFrom; + } + }; + + /// @class DoubleIterator is a helper class for simultaneously iterating + /// through the two given range sets, depending on what bound is less. + /// + /// NOTE: Given sets shall not be empty. + class DoubleIterator { + BoundIterator LI; + BoundIterator RI; + BoundIterator *I = nullptr; + + public: + DoubleIterator(const ContainerType &LHS, const ContainerType &RHS) + : LI(LHS), RI(RHS), I((**LI < **RI) ? &LI : &RI) {} + + /// @returns - a pointer to value of the current bound; + /// - `nullptr` if iterator reached the end of the range. + const APSInt *operator*() { return **I; } + + /// Move the pointer to the next bound of one of the two sets. + /// + /// @returns `true` if previous value is `From` and `false` otherwise. + bool operator++(int) { + const bool IsFrom = (*I)++; + I = &((!*LI) ? RI : (!*RI) ? LI : (**LI < **RI) ? LI : RI); + return IsFrom; + } + }; + + DoubleIterator DI(LHS, RHS); + APSIntType Ty = APSIntType(LHS.begin()->From()); + const APSInt One = Ty.getValue(1); + const APSInt Min = Ty.getMinValue(); + const APSInt *F = nullptr; + const APSInt *T = nullptr; + const APSInt *Int = nullptr; + int BoundCounter = 0; + const size_t BoundSize = (LHS.size() + RHS.size()) * 2; + ContainerType Result; + + // Let's consider bounds as a brackets problem. In practice it can't be more + // than one nested level. BoundCounter counts a current state of a bound + // sequence. BoundCounter can't be more than 2. + // For example: + // LHS: ( ) ( ) ( ) + // RHS: ( ) ( ) ( ) ( ) + // DI: ( ) ( ( ) ) ( ( ) ( ) ) + // BC: 1 0 1 2 1 0 1 2 1 2 1 0 + // BoundCounter is 0 for outer `)`. + // BoundCounter is 1 for outer '(' or inner `)`. + // BoundCounter is 2 for inner `(`. + + // This loop serves to initialize `T` and reduce checks in the `for` loop + // below. + // We can be sure `DI` will not go out of bounds because every bound ends + // with `To` value. + size_t I = 0; + while (!T) { + Int = *DI; + BoundCounter += (DI++ ? 1 : -1); + if (!F && BoundCounter == 1) + F = Int; + else if (BoundCounter == 0) + T = Int; + ++I; + } + + // Carry this particular iteration out, because it is the only iteration + // which needs additional check for `*From != Min`. + // Rely on the fact that BoundCounter is 0. Thus, we can simplify the + // iteration. + if (I < BoundSize) { + Int = *DI; + DI++; + BoundCounter = 1; + // Check for adjacency. + // We don't need to check `!T` here because of the `while` loop above. + if (*Int != Min && *Int - One > *T) { + Result.emplace_back(*F, *T); + F = Int; + } + ++I; + } + + // We handle all corner cases above and can run a light version loop for the + // rest. + for (bool IsFrom; I < BoundSize; ++I) { + Int = *DI; + IsFrom = DI++; + BoundCounter += (IsFrom ? 1 : -1); + if (BoundCounter == 1) { + // Check for adjacency. + if (IsFrom && *Int - One > *T) { + Result.emplace_back(*F, *T); + F = Int; + } + } else if (BoundCounter == 0) + T = Int; + } + + /* + // NOTE: This is à generalized version of the algorithm above. + // It's created only for review purpose to raise the discussion about which + // of two approaches shall be accepted here. + // This comments and one of the approaches shall be deleted before commit. + + for (size_t I = 0; I < Sz; ++I) { + IsFrom = DI++; + BoundCounter += (IsFrom ? 1 : -1); + Int = *DI; + if (BoundCounter == 1) { + if (IsFrom) { + if (!T) + F = Int; + else if (*Int != Min && *Int - One > *T) { + Result.emplace_back(*F, *T); + F = Int; + } + } + } else if (BoundCounter == 0) + T = Int; + } + */ + + Result.emplace_back(*F, *T); + + return Result; +} + RangeSet RangeSet::Factory::getRangeSet(Range From) { ContainerType Result; Result.push_back(From); @@ -153,13 +363,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 @@ -41,6 +41,23 @@ 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,18 +72,14 @@ 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); } + using TV = TestValues; + static constexpr auto MIN = TV::MIN; + static constexpr auto MAX = TV::MAX; + static constexpr auto MID = TV::MID; + static constexpr auto A = TV::A; + static constexpr auto B = TV::B; + static constexpr auto C = TV::C; + static constexpr auto D = TV::D; static llvm::APSInt Base; const llvm::APSInt &from(BaseType X) { @@ -160,7 +173,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 +181,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); @@ -184,7 +220,8 @@ } // namespace template -llvm::APSInt RangeSetTest::Base{sizeof(BaseType) * 8, !isSigned()}; +llvm::APSInt RangeSetTest::Base{sizeof(BaseType) * 8, + std::is_unsigned::value}; using IntTypes = ::testing::Types; @@ -192,17 +229,13 @@ 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; + constexpr TypeParam MIN = TestFixture::MIN; + constexpr TypeParam MAX = TestFixture::MAX; + constexpr TypeParam MID = TestFixture::MID; + constexpr TypeParam A = TestFixture::A; + constexpr TypeParam B = TestFixture::B; + constexpr TypeParam C = TestFixture::C; + constexpr TypeParam D = TestFixture::D; static_assert(MIN < A && A < B && B < MID && MID < C && C < D && D < MAX, "Values shall be in an ascending order"); @@ -234,8 +267,8 @@ } TYPED_TEST(RangeSetTest, RangeSetRangeIntersectTest) { - constexpr TypeParam MIN = TestFixture::getMin(); - constexpr TypeParam MAX = TestFixture::getMax(); + constexpr auto MIN = TestFixture::MIN; + constexpr auto MAX = TestFixture::MAX; // Check that we can correctly intersect empty sets. this->checkIntersect({}, 10, 20, {}); @@ -300,9 +333,9 @@ 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(); + constexpr auto MIN = TestFixture::MIN; + constexpr auto MAX = TestFixture::MAX; + constexpr auto MID = TestFixture::MID; this->checkContains({{MIN, MAX}}, 0, true); this->checkContains({{MIN, MAX}}, MID, true); this->checkContains({{MIN, MAX}}, -10, true); @@ -331,9 +364,9 @@ } TYPED_TEST(RangeSetTest, RangeSetDeletePointTest) { - constexpr TypeParam MIN = TestFixture::getMin(); - constexpr TypeParam MAX = TestFixture::getMax(); - constexpr TypeParam MID = TestFixture::getMid(); + constexpr auto MIN = TestFixture::MIN; + constexpr auto MAX = TestFixture::MAX; + constexpr auto MID = TestFixture::MID; this->checkDelete(MID, {{MIN, MAX}}, {{MIN, MID - 1}, {MID + 1, MAX}}); // Check that delete works with an empty set. @@ -347,3 +380,210 @@ // 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) { + + // Use next values of the range {MIN, A, B, MID, C, D, MAX}. + constexpr auto MIN = TestFixture::MIN; + constexpr auto MAX = TestFixture::MAX; + constexpr auto MID = TestFixture::MID; + constexpr auto A = TestFixture::A; + constexpr auto B = TestFixture::B; + constexpr auto C = TestFixture::C; + constexpr auto D = TestFixture::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 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}}); +}