implement TestingPortal

Some our tests are intended to test behavior of external APIs (like
operator new etc), but they do need occasional ability to peek into
the guts of tcmalloc. We want to make our .so libraries to be built
with -fvisibility=hidden, so we'll loose this ability.

In order to have this limited ability for tests to get into the guts
of tcmalloc, we introduce TestingPortal exactly for that. It uses
carefuly designed and thouroughly undocumented GetNumericProperty
extension to grab pointer to TestingPortal. Then through calling
member functions of this instance, we're able to do the right thing.
This commit is contained in:
Aliaksey Kandratsenka 2024-02-24 16:35:25 -05:00
parent 66bdf24061
commit ddc162f1d1
4 changed files with 205 additions and 0 deletions

View File

@ -1055,6 +1055,13 @@ static inline void DebugDeallocate(void* ptr, int type, size_t given_size) {
if (ptr) MallocBlock::FromRawPointer(ptr)->Deallocate(type, given_size);
}
class ATTRIBUTE_HIDDEN DebugTestingPortal : public TestingPortalImpl {
public:
~DebugTestingPortal() override = default;
bool IsDebuggingMalloc() override { return true; }
int32_t& GetMaxFreeQueueSize() override { return FLAGS_max_free_queue_size; }
};
// ========================================================================= //
// The following functions may be called via MallocExtension::instance()
@ -1062,6 +1069,18 @@ static inline void DebugDeallocate(void* ptr, int type, size_t given_size) {
class DebugMallocImplementation : public TCMallocImplementation {
public:
virtual bool GetNumericProperty(const char* name, size_t* value) {
if (TestingPortal** portal = TestingPortal::CheckGetPortal(name, value); portal) {
static DebugTestingPortal* ptr = ([] () {
static struct {
alignas(DebugTestingPortal) char memory[sizeof(DebugTestingPortal)];
} storage;
return new (storage.memory) DebugTestingPortal;
})();
*portal = ptr;
*value = 1;
return true;
}
bool result = TCMallocImplementation::GetNumericProperty(name, value);
if (result && (strcmp(name, "generic.current_allocated_bytes") == 0)) {
// Subtract bytes kept in the free queue

View File

@ -2383,3 +2383,18 @@ const void* HeapLeakChecker::GetAllocCaller(void* ptr) {
RAW_CHECK(info.stack_depth >= 1, "");
return info.call_stack[0];
}
namespace tcmalloc {
ATTRIBUTE_HIDDEN
void DoIterateMemoryRegionMap(tcmalloc::FunctionRef<void(const void*)> callback) {
{ MemoryRegionMap::LockHolder l;
for (MemoryRegionMap::RegionIterator
i = MemoryRegionMap::BeginRegionLocked();
i != MemoryRegionMap::EndRegionLocked(); ++i) {
callback(&*i);
}
}
}
} // namespace tcmalloc

View File

@ -129,6 +129,7 @@
#include "thread_cache_ptr.h"
#include "maybe_emergency_malloc.h"
#include "testing_portal.h"
#if (defined(_WIN32) && !defined(__CYGWIN__) && !defined(__CYGWIN32__)) && !defined(WIN32_OVERRIDE_ALLOCATORS)
# define WIN32_DO_PATCHING 1
@ -156,9 +157,15 @@ using tcmalloc::Static;
using tcmalloc::ThreadCache;
using tcmalloc::ThreadCachePtr;
using tcmalloc::TestingPortal;
DECLARE_double(tcmalloc_release_rate);
DECLARE_int64(tcmalloc_heap_limit_mb);
#ifndef NO_HEAP_CHECK
DECLARE_string(heap_check);
#endif
// Those common architectures are known to be safe w.r.t. aliasing function
// with "extra" unused args to function with fewer arguments (e.g.
// tc_delete_nothrow being aliased to tc_delete).
@ -543,6 +550,75 @@ static void IterateOverRanges(void* arg, MallocExtension::RangeFunction func) {
}
}
namespace tcmalloc {
TestingPortal::~TestingPortal() = default;
// implemented in heap-checker.cc
extern void DoIterateMemoryRegionMap(tcmalloc::FunctionRef<void(const void*)> callback);
class ATTRIBUTE_HIDDEN TestingPortalImpl : public TestingPortal {
public:
~TestingPortalImpl() override = default;
bool HaveSystemRelease() override {
static bool have = ([] () {
size_t actual;
auto ptr = TCMalloc_SystemAlloc(kPageSize, &actual, 0);
return TCMalloc_SystemRelease(ptr, actual);
})();
return have;
}
bool IsDebuggingMalloc() override {
return false;
}
size_t GetPageSize() override {
return kPageSize;
}
size_t GetMinAlign() override {
return kMinAlign;
}
size_t GetMaxSize() override {
return kMaxSize;
}
int64_t& GetSampleParameter() override {
return FLAGS_tcmalloc_sample_parameter;
}
double& GetReleaseRate() override {
return FLAGS_tcmalloc_release_rate;
}
int32_t& GetMaxFreeQueueSize() override {
abort();
}
std::string_view GetHeapCheckFlag() {
#ifndef NO_HEAP_CHECK
return FLAGS_heap_check;
#else
return "";
#endif
}
void IterateMemoryRegionMap(tcmalloc::FunctionRef<void(const void*)> callback) {
#ifndef NO_HEAP_CHECK
DoIterateMemoryRegionMap(callback);
#endif
}
static TestingPortalImpl* Get() {
static TestingPortalImpl* ptr = ([] () {
static struct {
alignas(TestingPortalImpl) char memory[sizeof(TestingPortalImpl)];
} storage;
return new (storage.memory) TestingPortalImpl;
}());
return ptr;
}
};
} // namespace tcmalloc
using tcmalloc::TestingPortalImpl;
// TCMalloc's support for extra malloc interfaces
class TCMallocImplementation : public MallocExtension {
private:
@ -775,6 +851,12 @@ class TCMallocImplementation : public MallocExtension {
return true;
}
if (TestingPortal** portal = TestingPortal::CheckGetPortal(name, value); portal) {
*portal = TestingPortalImpl::Get();
*value = 1;
return true;
}
return false;
}

89
src/testing_portal.h Normal file
View File

@ -0,0 +1,89 @@
/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil -*-
* Copyright (c) 2024, gperftools Contributors
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TESTING_PORTAL_H_
#define TESTING_PORTAL_H_
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <gperftools/malloc_extension.h>
#include "base/function_ref.h"
#include "base/basictypes.h"
namespace tcmalloc {
class ATTRIBUTE_HIDDEN TestingPortal {
public:
static inline constexpr char kMagic[] = "tcmalloc.impl.testing-portal";
static TestingPortal* Get() {
static TestingPortal* instance = ([] () {
struct {
TestingPortal* ptr = nullptr;
size_t v = 0;
} s;
bool ok = MallocExtension::instance()->GetNumericProperty(kMagic, &s.v);
if (!ok || s.ptr == nullptr) {
abort();
}
return s.ptr;
})();
return instance;
}
static TestingPortal** CheckGetPortal(const char* property_name, size_t* value) {
if (strcmp(property_name, kMagic) != 0) {
return nullptr;
}
return reinterpret_cast<TestingPortal**>(value) - 1;
}
virtual bool HaveSystemRelease() = 0;
virtual bool IsDebuggingMalloc() = 0;
virtual size_t GetPageSize() = 0;
virtual size_t GetMinAlign() = 0;
virtual size_t GetMaxSize() = 0;
virtual int64_t& GetSampleParameter() = 0;
virtual double& GetReleaseRate() = 0;
virtual int32_t& GetMaxFreeQueueSize() = 0;
// For heap checker unit test
virtual std::string_view GetHeapCheckFlag() = 0;
virtual void IterateMemoryRegionMap(tcmalloc::FunctionRef<void(const void*)> callback) = 0;
protected:
virtual ~TestingPortal();
};
} // namespace tcmalloc
#endif // TESTING_PORTAL_H_