239 lines
7.7 KiB
C++
Raw Permalink Normal View History

#pragma once
#include <string>
#include <cstdint>
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
#include <chrono>
#include <stdexcept>
#include <mutex>
#include <condition_variable>
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
#include <memory>
#include <functional>
#include <future>
#include <vector>
#include <optional>
#include <nlohmann/json.hpp>
namespace nlohmann {
template<typename T>
void to_json(json& j, const std::optional<T>& opt) {
if (opt) j = *opt;
else j = nullptr;
}
template<typename T>
void from_json(const json& j, std::optional<T>& opt) {
if (j.is_null()) opt = std::nullopt;
else opt = j.get<T>();
}
}
// ============================================================
// Types
// ============================================================
struct TimerConfig {
std::string name;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(TimerConfig, name)
struct EchoRequest {
std::string message;
int64_t delayMs;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(EchoRequest, message, delayMs)
struct EchoResponse {
std::string echoed;
std::string timerName;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(EchoResponse, echoed, timerName)
struct ComplexRequest {
std::vector<EchoRequest> messages;
std::vector<std::string> tags;
std::optional<std::string> note;
std::optional<int64_t> retries;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ComplexRequest, messages, tags, note, retries)
struct ComplexResponse {
std::string summary;
int64_t itemCount;
bool hasNote;
};
NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE(ComplexResponse, summary, itemCount, hasNote)
// ============================================================
// C FFI declarations
// ============================================================
extern "C" {
typedef void (*FfiCallback)(int ret, const char* msg, size_t len, void* user_data);
int nimtimer_create(const char* config_json, FfiCallback callback, void* user_data);
int nimtimer_echo(void* ctx, FfiCallback callback, void* user_data, const char* req_json);
int nimtimer_version(void* ctx, FfiCallback callback, void* user_data);
int nimtimer_complex(void* ctx, FfiCallback callback, void* user_data, const char* req_json);
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
void nimtimer_destroy(void* ctx);
} // extern "C"
template<typename T>
inline std::string serializeFfiArg(const T& value) {
return nlohmann::json(value).dump();
}
inline std::string serializeFfiArg(void* value) {
return std::to_string(reinterpret_cast<uintptr_t>(value));
}
template<typename T>
inline T deserializeFfiResult(const std::string& raw) {
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
try {
return nlohmann::json::parse(raw).get<T>();
} catch (const nlohmann::json::exception& e) {
throw std::runtime_error(std::string("FFI response deserialization failed: ") + e.what());
}
}
template<>
inline void* deserializeFfiResult<void*>(const std::string& raw) {
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
try {
return reinterpret_cast<void*>(static_cast<uintptr_t>(std::stoull(raw)));
} catch (const std::exception& e) {
throw std::runtime_error(std::string("FFI returned non-numeric address: ") + raw);
}
}
// ============================================================
// Synchronous call helper (anonymous namespace, header-only)
// ============================================================
namespace {
struct FfiCallState_ {
std::mutex mtx;
std::condition_variable cv;
bool done{false};
bool ok{false};
std::string msg;
};
inline void ffi_cb_(int ret, const char* msg, size_t /*len*/, void* ud) {
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
auto* sptr = static_cast<std::shared_ptr<FfiCallState_>*>(ud);
{
auto& s = **sptr;
std::lock_guard<std::mutex> lock(s.mtx);
s.ok = (ret == 0);
s.msg = msg ? std::string(msg) : std::string{};
s.done = true;
s.cv.notify_one();
}
delete sptr;
}
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
inline std::string ffi_call_(std::function<int(FfiCallback, void*)> f,
std::chrono::milliseconds timeout) {
auto state = std::make_shared<FfiCallState_>();
auto* cb_ref = new std::shared_ptr<FfiCallState_>(state);
const int ret = f(ffi_cb_, cb_ref);
if (ret == 2) {
delete cb_ref;
throw std::runtime_error("RET_MISSING_CALLBACK (internal error)");
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
}
std::unique_lock<std::mutex> lock(state->mtx);
const bool fired = state->cv.wait_for(lock, timeout, [&]{ return state->done; });
if (!fired)
throw std::runtime_error("FFI call timed out after " + std::to_string(timeout.count()) + "ms");
if (!state->ok)
throw std::runtime_error(state->msg);
return state->msg;
}
} // anonymous namespace
// ============================================================
// High-level C++ context class
// ============================================================
class NimTimerCtx {
public:
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
static NimTimerCtx create(const TimerConfig& config, std::chrono::milliseconds timeout = std::chrono::seconds{30}) {
const auto config_json = serializeFfiArg(config);
const auto raw = ffi_call_([&](FfiCallback cb, void* ud) {
return nimtimer_create(config_json.c_str(), cb, ud);
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
}, timeout);
try {
const auto addr = std::stoull(raw);
return NimTimerCtx(reinterpret_cast<void*>(static_cast<uintptr_t>(addr)), timeout);
} catch (const std::exception&) {
throw std::runtime_error("FFI create returned non-numeric address: " + raw);
}
}
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
static std::future<NimTimerCtx> createAsync(const TimerConfig& config, std::chrono::milliseconds timeout = std::chrono::seconds{30}) {
return std::async(std::launch::async, [config, timeout]() { return create(config, timeout); });
}
~NimTimerCtx() {
if (ptr_) {
nimtimer_destroy(ptr_);
ptr_ = nullptr;
}
}
NimTimerCtx(const NimTimerCtx&) = delete;
NimTimerCtx& operator=(const NimTimerCtx&) = delete;
NimTimerCtx(NimTimerCtx&& other) noexcept : ptr_(other.ptr_), timeout_(other.timeout_) {
other.ptr_ = nullptr;
}
NimTimerCtx& operator=(NimTimerCtx&& other) noexcept {
if (this != &other) {
if (ptr_) nimtimer_destroy(ptr_);
ptr_ = other.ptr_;
timeout_ = other.timeout_;
other.ptr_ = nullptr;
}
return *this;
}
EchoResponse echo(const EchoRequest& req) const {
const auto req_json = serializeFfiArg(req);
const auto raw = ffi_call_([&](FfiCallback cb, void* ud) {
return nimtimer_echo(ptr_, cb, ud, req_json.c_str());
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
}, timeout_);
return deserializeFfiResult<EchoResponse>(raw);
}
std::future<EchoResponse> echoAsync(const EchoRequest& req) const {
return std::async(std::launch::async, [this, req]() { return echo(req); });
}
std::string version() const {
const auto raw = ffi_call_([&](FfiCallback cb, void* ud) {
return nimtimer_version(ptr_, cb, ud);
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
}, timeout_);
return deserializeFfiResult<std::string>(raw);
}
std::future<std::string> versionAsync() const {
return std::async(std::launch::async, [this]() { return version(); });
}
ComplexResponse complex(const ComplexRequest& req) const {
const auto req_json = serializeFfiArg(req);
const auto raw = ffi_call_([&](FfiCallback cb, void* ud) {
return nimtimer_complex(ptr_, cb, ud, req_json.c_str());
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
}, timeout_);
return deserializeFfiResult<ComplexResponse>(raw);
}
std::future<ComplexResponse> complexAsync(const ComplexRequest& req) const {
return std::async(std::launch::async, [this, req]() { return complex(req); });
}
private:
void* ptr_;
Fix cpp vulnerabilities 1. No timeout → wait_for + 30 s default (ffi/codegen/cpp.nim) ffi_call_ now takes std::chrono::milliseconds timeout and uses cv.wait_for. All factory/method signatures carry a timeout parameter (default std::chrono::seconds{30}), mirroring the Rust blocking API. 2. Stack-allocated state → shared_ptr ownership (ffi/codegen/cpp.nim) ffi_cb_ now receives a heap-allocated std::shared_ptr<FfiCallState_>* as user_data. The refcount is 2 going in (one for ffi_call_, one for the callback). If ffi_call_ times out and returns, its copy drops — but the state stays alive (refcount 1) until Nim eventually calls back and delete sptr in ffi_cb_ drops the last reference. No more stack UAF. 3. Destructor + Rule of 5 (ffi/codegen/cpp.nim, examples/nim_timer/nim_timer.nim) Added nimtimer_destroy to nim_timer.nim with {.dynlib, exportc, cdecl, raises: [].} — joins the FFI and watchdog threads, frees the context Codegen now always emits void {libName}_destroy(void* ctx) in extern "C" and generates a destructor, deleted copy ctor/assignment, and move ctor/assignment for the context class timeout_ stored in the class; move transfers it, destructor uses it 4. Hardcoded TimerConfig in createAsync (ffi/codegen/cpp.nim) createAsync now uses the actual ctorParams list (same as create), so it's correct for any library, not just nim_timer. 5. Opaque exceptions → clear error messages (ffi/codegen/cpp.nim) deserializeFfiResult wraps nlohmann::json::parse + .get<T>() in a catch that rethrows as "FFI response deserialization failed: ...". The stoull in create() is also try-caught with "FFI create returned non-numeric address: " + raw.
2026-05-04 00:36:52 +02:00
std::chrono::milliseconds timeout_;
explicit NimTimerCtx(void* p, std::chrono::milliseconds t) : ptr_(p), timeout_(t) {}
};