Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@ if(NOT GA_SHARED_LIB)
########################################

file(GLOB_RECURSE TEST_SRC_FILES "${PROJECT_SOURCE_DIR}/test/*.cpp")
list(FILTER TEST_SRC_FILES EXCLUDE REGEX "/test/allocator_boundary/")

########################################
# Unit Tests
Expand All @@ -271,6 +272,18 @@ if(NOT GA_SHARED_LIB)

########################################
add_test(NAME ${UT_PROJECT_NAME} COMMAND GameAnalyticsUnitTests)

if(APPLE)
# Mimics monolithic Unreal: private operator new/delete.
set(AB_TEST_NAME "${PROJECT_NAME}AllocatorBoundaryTest")
add_executable(${AB_TEST_NAME} "${PROJECT_SOURCE_DIR}/test/allocator_boundary/main.cpp")
target_link_libraries(${AB_TEST_NAME} ${PROJECT_NAME})
file(WRITE "${CMAKE_BINARY_DIR}/no_exports.txt" "")
target_link_options(${AB_TEST_NAME} PRIVATE "LINKER:-exported_symbols_list,${CMAKE_BINARY_DIR}/no_exports.txt")
add_test(NAME ${AB_TEST_NAME} COMMAND ${AB_TEST_NAME})
add_test(NAME ${PROJECT_NAME}NoLibcxxStringImports
COMMAND sh -c "! nm -u $<TARGET_FILE:${AB_TEST_NAME}> | grep -E '^_+ZNSt3__1(12basic_string|15basic_stringbuf|1[89]basic_[io]?stringstream|plI)'")
endif()
else()
message(STATUS "Skipping unit tests (not available for shared library builds)")
endif()
Expand Down
81 changes: 38 additions & 43 deletions source/gameanalytics/GAHTTPApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -323,59 +323,54 @@ namespace gameanalytics

ErrorType errorType = std::make_tuple(category, area);

bool useGzip = this->useGzip;

auto task = std::async(std::launch::async, [=]() -> void
int64_t now = utilities::GAUtilities::timeIntervalSince1970();
if(timestampMap.count(errorType) == 0)
{
int64_t now = utilities::GAUtilities::timeIntervalSince1970();
if(timestampMap.count(errorType) == 0)
{
timestampMap[errorType] = now;
}
if(countMap.count(errorType) == 0)
{
countMap[errorType] = 0;
}

constexpr int64_t FREQUENCY = 3600; // 1h
timestampMap[errorType] = now;
}
if(countMap.count(errorType) == 0)
{
countMap[errorType] = 0;
}

constexpr int64_t FREQUENCY = 3600; // 1h

int64_t diff = now - timestampMap[errorType];
if(diff >= FREQUENCY)
{
countMap[errorType] = 0;
timestampMap[errorType] = now;
}
int64_t diff = now - timestampMap[errorType];
if(diff >= FREQUENCY)
{
countMap[errorType] = 0;
timestampMap[errorType] = now;
}

if(countMap[errorType] >= MaxCount)
{
return;
}
if(countMap[errorType] >= MaxCount)
{
return;
}

std::vector<uint8_t> payloadData = getInstance().createPayloadData(payloadJSONString, useGzip);
std::vector<uint8_t> payloadData = getInstance().createPayloadData(payloadJSONString, useGzip);

std::string auth = createAuth(payloadData);
GAHttpClient::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr);
std::string auth = createAuth(payloadData);
GAHttpClient::Response response = impl->sendRequest(url, auth, payloadData, useGzip, nullptr);

if(response.code < 0)
{
logging::GALogger::e("Request failed: %s", url.c_str());
return;
}
if(response.code < 0)
{
logging::GALogger::e("Request failed: %s", url.c_str());
return;
}

std::string_view content = response.toString();
std::string_view content = response.toString();

// process the response
logging::GALogger::d("sdk error content : %.*s", (int)content.size(), content.data());
// process the response
logging::GALogger::d("sdk error content : %.*s", (int)content.size(), content.data());

// if not 200 result
if (response.code != HTTP_RESPONSE_OK && response.code != HTTP_RESPONSE_NO_CONTENT)
{
logging::GALogger::d("sdk error failed. response code not 200 or 204. status code: %ld", response.code);
return;
}
// if not 200 result
if (response.code != HTTP_RESPONSE_OK && response.code != HTTP_RESPONSE_NO_CONTENT)
{
logging::GALogger::d("sdk error failed. response code not 200 or 204. status code: %ld", response.code);
return;
}

countMap[errorType] = countMap[errorType] + 1;
});
countMap[errorType] = countMap[errorType] + 1;
}

std::vector<uint8_t> GAHTTPApi::createPayloadData(std::string const& payload, bool gzip)
Expand Down
87 changes: 87 additions & 0 deletions source/gameanalytics/GAThread.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//
// GA-SDK-CPP
// Copyright 2018 GameAnalytics C++ SDK. All rights reserved.
//

#pragma once

#if !defined(__APPLE__)

#include <thread>

namespace gameanalytics::threading
{
using GAThread = std::thread;
}

#else

#include <functional>
#include <pthread.h>

namespace gameanalytics::threading
{
// std::thread frees its TLS block inside libc++.dylib.
class GAThread
{
public:
GAThread() = default;

explicit GAThread(std::function<void()> fn)
{
auto* heapFn = new std::function<void()>(std::move(fn));
_joinable = pthread_create(&_handle, nullptr, &GAThread::entry, heapFn) == 0;
if (!_joinable)
{
delete heapFn;
}
}

~GAThread()
{
join();
}

GAThread(GAThread&& other) noexcept
{
*this = std::move(other);
}

GAThread& operator=(GAThread&& other) noexcept
{
join();
_handle = other._handle;
_joinable = other._joinable;
other._joinable = false;
return *this;
}

GAThread(GAThread const&) = delete;
GAThread& operator=(GAThread const&) = delete;

bool joinable() const { return _joinable; }

void join()
{
if (_joinable)
{
_joinable = false;
pthread_join(_handle, nullptr);
}
}

private:
static void* entry(void* arg)
{
auto* fn = static_cast<std::function<void()>*>(arg);
(*fn)();
delete fn;
return nullptr;
}

pthread_t _handle = {};
bool _joinable = false;
};
}

#endif
2 changes: 1 addition & 1 deletion source/gameanalytics/GAThreading.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ namespace gameanalytics

GAThreading::GAThreading()
{
_thread = std::thread(
_thread = GAThread(
[this]()
{
work();
Expand Down
3 changes: 2 additions & 1 deletion source/gameanalytics/GAThreading.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <algorithm>

#include "GACommon.h"
#include "GAThread.h"

namespace gameanalytics
{
Expand Down Expand Up @@ -71,7 +72,7 @@ namespace gameanalytics

std::vector<ScheduledTask> _tasks;
std::queue<Block> _blocks;
std::thread _thread;
GAThread _thread;
std::mutex _blockMutex;
std::mutex _taskMutex;
std::atomic<bool> _endThread = false;
Expand Down
5 changes: 4 additions & 1 deletion source/gameanalytics/GAValidator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,10 @@ namespace gameanalytics
out.action = http::EGASdkErrorAction::InvalidAmount;
out.parameter = http::EGASdkErrorParameter::Amount;

out.reason = std::to_string(amount);
// std::to_string(double) allocates in libc++.dylib.
char amountStr[64] = {};
std::snprintf(amountStr, sizeof(amountStr), "%f", amount);
out.reason = amountStr;

return;
}
Expand Down
15 changes: 15 additions & 0 deletions source/gameanalytics/Platform/GALibcxxInstantiations.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Keeps libc++ string allocations inside this binary.
#include <string>
#include <sstream>

#if defined(__APPLE__) && defined(_LIBCPP_VERSION)

template class std::basic_string<char>;
template std::string std::operator+<char, std::char_traits<char>, std::allocator<char>>(const char*, const std::string&);

template class std::basic_stringbuf<char>;
template class std::basic_stringstream<char>;
template class std::basic_ostringstream<char>;
template class std::basic_istringstream<char>;

#endif
55 changes: 55 additions & 0 deletions test/allocator_boundary/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Simulates a host replacing operator new/delete without exporting them.
#include <cstdio>
#include <cstdlib>
#include <new>

#include "GameAnalytics/GameAnalytics.h"

namespace
{
constexpr unsigned long long kMagic = 0x4745554E47494E45ULL;
constexpr size_t kHeader = 16;
}

void* operator new(size_t n)
{
auto* p = static_cast<unsigned long long*>(std::malloc(n + kHeader));
if (!p) throw std::bad_alloc();
p[0] = kMagic;
return p + 2;
}
void* operator new[](size_t n) { return operator new(n); }

void operator delete(void* p) noexcept
{
if (!p) return;
auto* q = static_cast<unsigned long long*>(p) - 2;
if (q[0] != kMagic)
{
std::fprintf(stderr, "FATAL: freeing pointer %p allocated outside this binary\n", p);
std::abort();
}
q[0] = 0;
std::free(q);
}
void operator delete[](void* p) noexcept { operator delete(p); }
void operator delete(void* p, size_t) noexcept { operator delete(p); }
void operator delete[](void* p, size_t) noexcept { operator delete(p); }

int main()
{
using namespace gameanalytics;

GameAnalytics::setEnabledInfoLog(true);
GameAnalytics::setEnabledEventSubmission(false);
GameAnalytics::configureBuild("allocator-boundary-test 1.0");
GameAnalytics::initialize("00000000000000000000000000000000", "0000000000000000000000000000000000000000");

GameAnalytics::addDesignEvent("allocator:boundary:check");
GameAnalytics::addResourceEvent(EGAResourceFlowType::Sink, "gems", -1e20f, "boost", "speed");

GameAnalytics::onQuit();

std::printf("ALLOCATOR BOUNDARY TEST OK\n");
return 0;
}
Loading