folly/folly/memory/test/UninitializedMemoryHacksTest.cpp

/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <folly/memory/UninitializedMemoryHacks.h>

#include <algorithm>
#include <memory>
#include <string>
#include <vector>

#include <folly/Random.h>
#include <folly/portability/GTest.h>

#include <glog/logging.h>

void describePlatform() {
  LOG(INFO) << "sizeof(void*) = " << sizeof(void*);

  LOG(INFO) << "sizeof(std::string) = " << sizeof(std::string);
#if defined(_LIBCPP_STRING)
  LOG(INFO) << "std::string from libc++";
#elif defined(_STLP_STRING)
  LOG(INFO) << "std::string from STLport";
#elif defined(_GLIBCXX_STRING) && _GLIBCXX_USE_CXX11_ABI
  LOG(INFO) << "std::string from libstdc++ with SSO";
#elif defined(_GLIBCXX_STRING)
  LOG(INFO) << "std::string from old libstdc++";
#elif defined(_MSC_VER)
  LOG(INFO) << "std::string from MSVC";
#else
  LOG(INFO) << "UNKNOWN std::string implementation";
#endif

  LOG(INFO) << "sizeof(std::vector<char>) = " << sizeof(std::vector<char>);
#if defined(_LIBCPP_VECTOR)
  LOG(INFO) << "std::vector from libc++";
#elif defined(_STLP_VECTOR)
  LOG(INFO) << "std::vector from STLport";
#elif defined(_GLIBCXX_VECTOR)
  LOG(INFO) << "std::vector from libstdc++";
#elif defined(_MSC_VER)
  LOG(INFO) << "std::vector from MSVC";
#else
  LOG(INFO) << "UNKNOWN std::vector implementation";
#endif
}

// Returns a concatenation of target[i] for those i where valid[i]
template <typename T>
T validData(T const& target, std::vector<bool> const& valid) {
  EXPECT_EQ(target.size(), valid.size());
  T rv;
  for (std::size_t i = 0; i < valid.size(); ++i) {
    if (valid[i]) {
      rv.push_back(target[i]);
    }
  }
  return rv;
}

template <typename T>
void doResizeWithoutInit(
    T& target, std::vector<bool>& valid, std::size_t newSize) {
  auto oldSize = target.size();
  auto before = validData(target, valid);
  folly::resizeWithoutInitialization(target, newSize);
  valid.resize(newSize);
  auto after = validData(target, valid);
  if (oldSize <= newSize) {
    EXPECT_EQ(before, after);
  } else {
    EXPECT_GE(before.size(), after.size());
    EXPECT_TRUE(std::equal(after.begin(), after.end(), before.begin()));
  }
}

template <typename T>
void doOverwrite(
    T& target, std::vector<bool>& valid, std::size_t b, std::size_t e) {
  for (auto i = b; i < e && i < target.size(); ++i) {
    target[i] = '0' + (i % 10);
    valid[i] = true;
  }
}

template <typename T>
void doResize(T& target, std::vector<bool>& valid, std::size_t newSize) {
  auto oldSize = target.size();
  auto before = validData(target, valid);
  target.resize(newSize);
  valid.resize(newSize);
  for (auto i = oldSize; i < newSize; ++i) {
    valid[i] = true;
  }
  auto after = validData(target, valid);
  if (oldSize == newSize) {
    EXPECT_EQ(before, after);
  } else if (oldSize < newSize) {
    EXPECT_LT(before.size(), after.size());
    EXPECT_TRUE(std::equal(before.begin(), before.end(), after.begin()));
  } else {
    EXPECT_GE(before.size(), after.size());
    EXPECT_TRUE(std::equal(after.begin(), after.end(), before.begin()));
  }
}

template <typename T>
void doClear(T& target, std::vector<bool>& valid) {
  target.clear();
  valid.clear();
}

template <typename T>
void doInsert(T& target, std::vector<bool>& valid, std::size_t i) {
  target.insert(target.begin() + i, 'I');
  valid.insert(valid.begin() + i, true);
}

template <typename T>
void doErase(T& target, std::vector<bool>& valid, std::size_t i) {
  target.erase(target.begin() + i);
  valid.erase(valid.begin() + i);
}

template <typename T>
void doPushBack(T& target, std::vector<bool>& valid) {
  target.push_back('P');
  valid.push_back(true);
}

template <typename T>
void genericCheck(T& target) {
  EXPECT_LE(target.size(), target.capacity());
  EXPECT_EQ(target.size() == 0, target.empty());
  EXPECT_EQ(target.size(), target.end() - target.begin());
  EXPECT_EQ(target.size(), target.cend() - target.cbegin());
  if (!target.empty()) {
    EXPECT_EQ(target.data(), &target[0]);
    EXPECT_EQ(target.data(), &target.front());
    EXPECT_EQ(target.data() + target.size() - 1, &target.back());
  }
}

template <typename T>
void check(T& target) {
  genericCheck(target);
}

template <>
void check<std::string>(std::string& target) {
  genericCheck(target);
  EXPECT_EQ(target.c_str(), target.data());
  EXPECT_EQ(target.c_str()[target.size()], '\0');
}

template <typename T>
void testSimple() {
  describePlatform();

  auto sizes = {0, 1, 10, 14, 15, 16, 17, 22, 23, 24, 32, 95, 100, 10000};
  for (auto i : sizes) {
    for (auto j : sizes) {
      {
        T target;
        std::vector<bool> valid;
        doResize(target, valid, i);
        doResizeWithoutInit(target, valid, j);
        check(target);
      }

      {
        T target;
        std::vector<bool> valid;
        doResize(target, valid, i);
        doResizeWithoutInit(target, valid, j);
        doOverwrite(target, valid, i, j);
        check(target);
      }

      {
        T target;
        std::vector<bool> valid;
        doResizeWithoutInit(target, valid, i);
        doResize(target, valid, j);
        doOverwrite(target, valid, i / 2, i / 2);
        check(target);
      }

      {
        T target;
        std::vector<bool> valid;
        doResizeWithoutInit(target, valid, i);
        doResize(target, valid, j);
        doOverwrite(target, valid, i, j);
        check(target);
      }
    }
  }
}

template <typename T>
void testRandom(size_t numSteps = 10000) {
  describePlatform();

  auto target = std::make_unique<T>();
  std::vector<bool> valid;

  for (size_t step = 0; step < numSteps; ++step) {
    auto pct = folly::Random::rand32(100);
    auto v = folly::Random::rand32(uint32_t{3} << folly::Random::rand32(14));

    if (pct < 5) {
      doClear(*target, valid);
    } else if (pct < 30) {
      T copy;
      folly::resizeWithoutInitialization(copy, target->size());
      for (size_t i = 0; i < copy.size(); ++i) {
        if (valid[i]) {
          copy[i] = target->at(i);
        }
      }
      if (pct < 10) {
        std::swap(copy, *target);
      } else if (pct < 15) {
        *target = std::move(copy);
      } else if (pct < 20) {
        *target = copy;
      } else if (pct < 25) {
        target = std::make_unique<T>(std::move(copy));
      } else {
        target = std::make_unique<T>(copy);
      }
    } else if (pct < 35) {
      target->reserve(v);
    } else if (pct < 40) {
      target->shrink_to_fit();
    } else if (pct < 45) {
      doResize(*target, valid, v);
    } else if (pct < 50) {
      doInsert(*target, valid, v % (target->size() + 1));
    } else if (pct < 55) {
      if (!target->empty()) {
        doErase(*target, valid, v % target->size());
      }
    } else if (pct < 60) {
      doPushBack(*target, valid);
    } else if (pct < 65) {
      target = std::make_unique<T>();
      valid.clear();
    } else if (pct < 80) {
      auto v2 = folly::Random::rand32(uint32_t{3} << folly::Random::rand32(14));
      doOverwrite(*target, valid, std::min(v, v2), std::max(v, v2));
    } else {
      doResizeWithoutInit(*target, valid, v);
    }

    // don't check every time in implementation does lazy work
    if (folly::Random::rand32(100) < 50) {
      check(*target);
    }
  }
}

TEST(UninitializedMemoryHacks, simpleString) {
  testSimple<std::string>();
}

TEST(UninitializedMemoryHacks, simpleStringWChar) {
  testSimple<std::wstring>();
}

TEST(UninitializedMemoryHacks, simpleStringSChar) {
  testSimple<std::basic_string<signed char>>();
}

TEST(UninitializedMemoryHacks, simpleVectorChar) {
  testSimple<std::vector<char>>();
}

TEST(UninitializedMemoryHacks, simpleVectorByte) {
  testSimple<std::vector<uint8_t>>();
}

TEST(UninitializedMemoryHacks, simpleVectorInt) {
  testSimple<std::vector<int>>();
}

TEST(UninitializedMemoryHacks, randomString) {
  testRandom<std::string>();
}

TEST(UninitializedMemoryHacks, randomStringWChar) {
  testRandom<std::wstring>();
}

TEST(UninitializedMemoryHacks, randomStringSChar) {
  testRandom<std::basic_string<signed char>>();
}

TEST(UninitializedMemoryHacks, randomVectorChar) {
  testRandom<std::vector<char>>();
}

TEST(UninitializedMemoryHacks, randomVectorByte) {
  testRandom<std::vector<uint8_t>>();
}

TEST(UninitializedMemoryHacks, randomVectorInt) {
  testRandom<std::vector<int>>();
}

// We are deliberately putting this at the bottom to make sure it can follow use
FOLLY_DECLARE_STRING_RESIZE_WITHOUT_INIT(signed char)
FOLLY_DECLARE_VECTOR_RESIZE_WITHOUT_INIT(int)