GEODE-8756: Fix CacheableString::objectSize (#703)

  - objectSize was not taking into account null-terminator character,
    which as of C++11, as stated in ยง 21.4.7.1, should be included at
    the end of the same string character sequence.
  - Also, in those cases in which SSO applied, the returned size was
    higher than the actual one.
  - A parametrized UT was added to check objectSize is returning the
    right size. Note that no fixed sizes are part of such test, given
    that each std::string implementation could have a different SSO
    size.

* Revision 2

 - Added a new class called size_tracking_allocator, which is a custom
   allocator used to track the size STL objects.
 - Changed testing approach so it does not replicate internal logic, and
   instead it instantiates a basic_string<char> with the new custom
   allocator so heap size is tracked this way.
diff --git a/cppcache/src/CacheableString.cpp b/cppcache/src/CacheableString.cpp
index 466566e..d51f3f7 100644
--- a/cppcache/src/CacheableString.cpp
+++ b/cppcache/src/CacheableString.cpp
@@ -119,8 +119,18 @@
 }
 
 size_t CacheableString::objectSize() const {
-  auto size = sizeof(CacheableString) +
-              sizeof(std::string::value_type) * m_str.capacity();
+  auto size = sizeof(CacheableString);
+
+  // This is calculated in order not to count more bytes than necessary
+  // whenever SSO applies.
+  auto delta = reinterpret_cast<const uint8_t*>(m_str.data()) -
+               reinterpret_cast<const uint8_t*>(&m_str);
+  if (delta >= static_cast<decltype(delta)>(sizeof(decltype(m_str))) ||
+      delta < 0) {
+    // Add an extra character for the null-terminator
+    size += sizeof(decltype(m_str)::value_type) * (m_str.capacity() + 1UL);
+  }
+
   return size;
 }
 
diff --git a/cppcache/src/util/size_tracking_allocator.hpp b/cppcache/src/util/size_tracking_allocator.hpp
new file mode 100644
index 0000000..e64b8e3
--- /dev/null
+++ b/cppcache/src/util/size_tracking_allocator.hpp
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+#pragma once
+
+#ifndef GEODE_UTIL_SIZE_TRACKING_ALLOCATOR_H_
+#define GEODE_UTIL_SIZE_TRACKING_ALLOCATOR_H_
+
+#include <functional>
+#include <mutex>
+
+namespace apache {
+namespace geode {
+namespace client {
+
+template <typename _Tp>
+class size_tracking_allocator {
+ public:
+  //    typedefs
+  using value_type = _Tp;
+  using pointer = value_type *;
+  using size_type = std::size_t;
+  using reference = value_type &;
+  using difference_type = std::ptrdiff_t;
+  using const_pointer = const value_type *;
+  using const_reference = const value_type &;
+  using allocate_callback = std::function<void(difference_type)>;
+
+ public:
+  template <typename U>
+  class rebind {
+   public:
+    using other = size_tracking_allocator<U>;
+  };
+
+ public:
+  explicit size_tracking_allocator() = default;
+  explicit size_tracking_allocator(const allocate_callback &cb)
+      : allocate_cb_{cb} {}
+
+  size_tracking_allocator(const size_tracking_allocator &other)
+      : allocate_cb_{other.allocate_cb_} {}
+
+  template <class U>
+  explicit size_tracking_allocator(const size_tracking_allocator<U> &other)
+      : allocate_cb_{other.allocate_cb_} {}
+
+  //    memory allocation
+  pointer allocate(size_type n, const void * = nullptr) {
+    auto size = n * sizeof(value_type);
+    pointer p = reinterpret_cast<pointer>(::operator new(size));
+    if (allocate_cb_) {
+      allocate_cb_(size);
+    }
+
+    return p;
+  }
+
+  void deallocate(pointer pAddress, size_type n) {
+    ::operator delete(pAddress);
+    if (allocate_cb_) {
+      allocate_cb_(-sizeof(value_type) * n);
+    }
+  }
+
+  //    size
+  size_type max_size() const {
+    return std::numeric_limits<size_type>::max() / sizeof(_Tp);
+  }
+
+  //    construction/destruction
+  void construct(pointer address, const value_type &object) {
+    new (address) value_type(object);
+  }
+
+  void destroy(pointer object) { object->~value_type(); }
+
+  bool operator==(size_tracking_allocator const &) { return true; }
+
+  bool operator!=(size_tracking_allocator const &other) {
+    return !operator==(other);
+  }
+
+ protected:
+  allocate_callback allocate_cb_;
+
+ private:
+  template <class U>
+  friend class size_tracking_allocator;
+};
+
+}  // namespace client
+}  // namespace geode
+}  // namespace apache
+
+#endif  // GEODE_UTIL_SIZE_TRACKING_ALLOCATOR_H_
diff --git a/cppcache/test/CacheableStringTests.cpp b/cppcache/test/CacheableStringTests.cpp
index c4a15fe..6d2979f 100644
--- a/cppcache/test/CacheableStringTests.cpp
+++ b/cppcache/test/CacheableStringTests.cpp
@@ -30,6 +30,7 @@
 #include "DataOutputInternal.hpp"
 #include "SerializationRegistry.hpp"
 #include "gtest_extensions.h"
+#include "util/size_tracking_allocator.hpp"
 
 namespace {
 
@@ -40,6 +41,8 @@
 using apache::geode::client::DataOutputInternal;
 using apache::geode::client::SerializationRegistry;
 
+using apache::geode::client::size_tracking_allocator;
+
 class TestDataOutput : public DataOutputInternal {
  public:
   TestDataOutput()
@@ -224,4 +227,25 @@
   EXPECT_EQ(utf8, str->value());
 }
 
+class CacheableStringSizeTests : public ::testing::TestWithParam<std::size_t> {
+};
+
+INSTANTIATE_TEST_SUITE_P(CacheableStringTests, CacheableStringSizeTests,
+                         ::testing::Values(0UL, 1UL, 3UL, 7UL, 15UL, 23UL, 31UL,
+                                           47UL, 63UL, 1025UL, 4097UL));
+
+TEST_P(CacheableStringSizeTests, TestObjectSize) {
+  auto str_size = GetParam();
+  auto expected_size = sizeof(CacheableString);
+  size_tracking_allocator<char> allocator{
+      [&expected_size](std::ptrdiff_t size) { expected_size += size; }};
+  std::basic_string<char, std::char_traits<char>, size_tracking_allocator<char>>
+      str(str_size, '_', allocator);
+
+  auto cacheable = CacheableString::create(std::string(str_size, '_'));
+  EXPECT_TRUE(cacheable);
+
+  EXPECT_EQ(expected_size, cacheable->objectSize());
+}
+
 }  // namespace