From 7cb2e31f4b5d4bdd4b5174efb2793e10b6bb3d00 Mon Sep 17 00:00:00 2001 From: Yuval Tassa Date: Thu, 20 Nov 2025 09:02:58 -0800 Subject: [PATCH] Annotate GlobalModel for LSAN PiperOrigin-RevId: 834784936 Change-Id: I9381a062956df6cd7724402b72c2d2a2d8c49998 --- src/xml/xml_api.cc | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/xml/xml_api.cc b/src/xml/xml_api.cc index b9678eae..43ccf803 100644 --- a/src/xml/xml_api.cc +++ b/src/xml/xml_api.cc @@ -27,10 +27,9 @@ #include #include -#include "engine/engine_io.h" #include +#include "engine/engine_io.h" #include "user/user_resource.h" -#include "user/user_vfs.h" #include "xml/xml.h" #include "xml/xml_native_reader.h" #include "xml/xml_util.h" @@ -39,6 +38,31 @@ #include #endif + +// leak sanitizer support: default to no-op +#define MJ_LSAN_IGNORE(ptr) ((void)(ptr)) + +// on Linux with GCC/Clang and LSAN available +#if (defined(__GNUC__) || defined(__clang__)) && !defined(__APPLE__) && !defined(_WIN32) +#ifdef __has_include +#if __has_include() + +// define weak lsan_ignore_object symbol +#include +extern "C" void __lsan_ignore_object(const void*) __attribute__((weak)); +namespace { +inline void lsan_ignore(const void* ptr) { if (__lsan_ignore_object) __lsan_ignore_object(ptr); } +} // namespace + +// redefine MJ_LSAN_IGNORE to use lsan_ignore +#undef MJ_LSAN_IGNORE +#define MJ_LSAN_IGNORE(ptr) lsan_ignore(ptr) + +#endif // LSAN API available +#endif // __has_include +#endif // Linux with GCC/Clang + + //---------------------------------- Globals ------------------------------------------------------- namespace { @@ -53,6 +77,12 @@ class GlobalModel { std::optional ToXML(const mjModel* m, char* error, int error_sz); + // mark this GlobalModel and its allocations as intentional (not leaks) + void AnnotateLSan() { + MJ_LSAN_IGNORE(this); + MJ_LSAN_IGNORE(mutex_); + } + private: // using raw pointers as GlobalModel needs to be trivially destructible std::mutex* mutex_ = new std::mutex(); @@ -79,6 +109,11 @@ void GlobalModel::Set(mjSpec* spec) { mj_deleteSpec(spec_); } spec_ = spec; + + // mark the spec as an intentional long-lived allocation + if (spec_) { + MJ_LSAN_IGNORE(spec_); + } } @@ -86,6 +121,13 @@ void GlobalModel::Set(mjSpec* spec) { GlobalModel& GetGlobalModel() { static GlobalModel global_model; + // mark the GlobalModel singleton and its mutex as intentional allocations + static bool lsan_annotated = false; + if (!lsan_annotated) { + global_model.AnnotateLSan(); + lsan_annotated = true; + } + // global variables must be trivially destructible static_assert(std::is_trivially_destructible_v); return global_model;