From 15d27b363f031fc7c8ce1b42eef737e51073ec8d Mon Sep 17 00:00:00 2001 From: Yuval Tassa Date: Thu, 21 May 2026 09:54:33 -0700 Subject: [PATCH] Compile-time size assertions for MuJoCo basic types PiperOrigin-RevId: 919107499 Change-Id: I07fcba4f33c77a2a1e68528dba6d399af94d0220 --- CMakeLists.txt | 1 + doc/changelog.rst | 6 ++ doc/programming/index.rst | 2 + include/mujoco/mjassert.h | 133 +++++++++++++++++++++++++++ include/mujoco/mujoco.h | 1 + test/header_test.cc | 55 +---------- unity/Runtime/Bindings/MjBindings.cs | 1 + 7 files changed, 145 insertions(+), 54 deletions(-) create mode 100644 include/mujoco/mjassert.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 86fc48a1..e01c6621 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,7 @@ include(MujocoMacOS) include(MujocoDependencies) set(MUJOCO_HEADERS + include/mujoco/mjassert.h include/mujoco/mjdata.h include/mujoco/mjexport.h include/mujoco/mjmacro.h diff --git a/doc/changelog.rst b/doc/changelog.rst index 5cd5f9c1..b339da1f 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -80,6 +80,12 @@ General - MJX: Removed the deprecated ``nconmax`` argument from ``mjx.make_data`` and ``mjx.put_data`` in favor of ``naconmax``. + - Maybe-breaking: Added `mjassert.h + `__, a new header containing + compile-time assertions that verify the sizes of MuJoCo's public types for ABI stability. This is a first step + towards replacing ``int`` with strongly-typed enums in the public API. If these assertions fail on your compiler or + platform, please report the issue on GitHub. + Version 3.8.1 (May 11, 2026) ---------------------------- diff --git a/doc/programming/index.rst b/doc/programming/index.rst index ba6e5ea6..852685cb 100644 --- a/doc/programming/index.rst +++ b/doc/programming/index.rst @@ -184,6 +184,8 @@ links below, to make this documentation self-contained. code. `mjsan.h `__ Definitions required when building with sanitizer instrumentation. +`mjassert.h `__ + Compile-time size assertions verifying MuJoCo ABI stability across C and C++ compilers. .. _inVersion: diff --git a/include/mujoco/mjassert.h b/include/mujoco/mjassert.h new file mode 100644 index 00000000..5c69f940 --- /dev/null +++ b/include/mujoco/mjassert.h @@ -0,0 +1,133 @@ +// Copyright 2026 DeepMind Technologies Limited +// +// Licensed 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. + +#ifndef MUJOCO_MJASSERT_H_ +#define MUJOCO_MJASSERT_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__cplusplus) + #define MJ_ASSERT_SIZE(type, size) \ + static_assert(sizeof(type) == (size), #type " must be " #size " bytes for MuJoCo ABI stability") +#elif defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L + #define MJ_ASSERT_SIZE(type, size) \ + _Static_assert(sizeof(type) == (size), #type " must be " #size " bytes for MuJoCo ABI stability") +#else + #define MJ_ASSERT_SIZE(type, size) \ + typedef char mj_assert_##type[sizeof(type) == (size) ? 1 : -1] +#endif + +// mjtnum.h +MJ_ASSERT_SIZE(mjtByte, 1); +MJ_ASSERT_SIZE(mjtSize, 8); +#if !defined(mjUSESINGLE) +MJ_ASSERT_SIZE(mjtNum, 8); +#else +MJ_ASSERT_SIZE(mjtNum, 4); +#endif + +// mjmodel.h +MJ_ASSERT_SIZE(mjtDisableBit, 4); +MJ_ASSERT_SIZE(mjtEnableBit, 4); +MJ_ASSERT_SIZE(mjtJoint, 4); +MJ_ASSERT_SIZE(mjtGeom, 4); +MJ_ASSERT_SIZE(mjtProjection, 4); +MJ_ASSERT_SIZE(mjtCamLight, 4); +MJ_ASSERT_SIZE(mjtLightType, 4); +MJ_ASSERT_SIZE(mjtTexture, 4); +MJ_ASSERT_SIZE(mjtTextureRole, 4); +MJ_ASSERT_SIZE(mjtColorSpace, 4); +MJ_ASSERT_SIZE(mjtIntegrator, 4); +MJ_ASSERT_SIZE(mjtCone, 4); +MJ_ASSERT_SIZE(mjtJacobian, 4); +MJ_ASSERT_SIZE(mjtSolver, 4); +MJ_ASSERT_SIZE(mjtEq, 4); +MJ_ASSERT_SIZE(mjtWrap, 4); +MJ_ASSERT_SIZE(mjtTrn, 4); +MJ_ASSERT_SIZE(mjtDyn, 4); +MJ_ASSERT_SIZE(mjtGain, 4); +MJ_ASSERT_SIZE(mjtBias, 4); +MJ_ASSERT_SIZE(mjtObj, 4); +MJ_ASSERT_SIZE(mjtSensor, 4); +MJ_ASSERT_SIZE(mjtStage, 4); +MJ_ASSERT_SIZE(mjtDataType, 4); +MJ_ASSERT_SIZE(mjtConDataField, 4); +MJ_ASSERT_SIZE(mjtRayDataField, 4); +MJ_ASSERT_SIZE(mjtCamOutBit, 4); +MJ_ASSERT_SIZE(mjtSameFrame, 4); +MJ_ASSERT_SIZE(mjtSleepPolicy, 4); +MJ_ASSERT_SIZE(mjtLRMode, 4); +MJ_ASSERT_SIZE(mjtFlexSelf, 4); +MJ_ASSERT_SIZE(mjtSDFType, 4); + +// mjdata.h +MJ_ASSERT_SIZE(mjtState, 4); +MJ_ASSERT_SIZE(mjtConstraint, 4); +MJ_ASSERT_SIZE(mjtConstraintState, 4); +MJ_ASSERT_SIZE(mjtWarning, 4); +MJ_ASSERT_SIZE(mjtTimer, 4); +MJ_ASSERT_SIZE(mjtSleepState, 4); + +// mjspec.h +MJ_ASSERT_SIZE(mjtGeomInertia, 4); +MJ_ASSERT_SIZE(mjtMeshInertia, 4); +MJ_ASSERT_SIZE(mjtMeshBuiltin, 4); +MJ_ASSERT_SIZE(mjtBuiltin, 4); +MJ_ASSERT_SIZE(mjtMark, 4); +MJ_ASSERT_SIZE(mjtLimited, 4); +MJ_ASSERT_SIZE(mjtAlignFree, 4); +MJ_ASSERT_SIZE(mjtInertiaFromGeom, 4); +MJ_ASSERT_SIZE(mjtOrientation, 4); + +// mjvisualize.h +MJ_ASSERT_SIZE(mjtCatBit, 4); +MJ_ASSERT_SIZE(mjtMouse, 4); +MJ_ASSERT_SIZE(mjtPertBit, 4); +MJ_ASSERT_SIZE(mjtCamera, 4); +MJ_ASSERT_SIZE(mjtLabel, 4); +MJ_ASSERT_SIZE(mjtFrame, 4); +MJ_ASSERT_SIZE(mjtVisFlag, 4); +MJ_ASSERT_SIZE(mjtRndFlag, 4); +MJ_ASSERT_SIZE(mjtStereo, 4); + +// mjrender.h +MJ_ASSERT_SIZE(mjtGridPos, 4); +MJ_ASSERT_SIZE(mjtFramebuffer, 4); +MJ_ASSERT_SIZE(mjtDepthMap, 4); +MJ_ASSERT_SIZE(mjtFontScale, 4); +MJ_ASSERT_SIZE(mjtFont, 4); + +// mjui.h +MJ_ASSERT_SIZE(mjtButton, 4); +MJ_ASSERT_SIZE(mjtEvent, 4); +MJ_ASSERT_SIZE(mjtItem, 4); +MJ_ASSERT_SIZE(mjtSection, 4); + +// mjthread.h +MJ_ASSERT_SIZE(mjtTaskStatus, 4); + +// mjplugin.h +MJ_ASSERT_SIZE(mjtPluginCapabilityBit, 4); + +#undef MJ_ASSERT_SIZE + +#endif // MUJOCO_MJASSERT_H_ diff --git a/include/mujoco/mujoco.h b/include/mujoco/mujoco.h index 213afeb3..ac76dc0f 100644 --- a/include/mujoco/mujoco.h +++ b/include/mujoco/mujoco.h @@ -35,6 +35,7 @@ #include #include #include +#include // this is a C-API #ifdef __cplusplus diff --git a/test/header_test.cc b/test/header_test.cc index 98014a5f..3de549d2 100644 --- a/test/header_test.cc +++ b/test/header_test.cc @@ -21,12 +21,7 @@ #include #include -#include -#include -#include -#include -#include -#include +#include #include #include "test/fixture.h" @@ -58,59 +53,11 @@ void CheckAddressOrdering( using HeaderTest = MujocoTest; -TEST_F(HeaderTest, IntsHave4Bytes) { - EXPECT_EQ(4, sizeof(int)); -} - TEST_F(HeaderTest, IntsHaveAtLeast31Bits) { int shift_left_30 = 1 << 30; EXPECT_GT(shift_left_30, 0); } -TEST_F(HeaderTest, EnumsAreInts) { - EXPECT_EQ(sizeof(mjtDisableBit), sizeof(int)); - EXPECT_EQ(sizeof(mjtEnableBit), sizeof(int)); - EXPECT_EQ(sizeof(mjtJoint), sizeof(int)); - EXPECT_EQ(sizeof(mjtGeom), sizeof(int)); - EXPECT_EQ(sizeof(mjtCamLight), sizeof(int)); - EXPECT_EQ(sizeof(mjtTexture), sizeof(int)); - EXPECT_EQ(sizeof(mjtIntegrator), sizeof(int)); - EXPECT_EQ(sizeof(mjtCone), sizeof(int)); - EXPECT_EQ(sizeof(mjtJacobian), sizeof(int)); - EXPECT_EQ(sizeof(mjtSolver), sizeof(int)); - EXPECT_EQ(sizeof(mjtEq), sizeof(int)); - EXPECT_EQ(sizeof(mjtWrap), sizeof(int)); - EXPECT_EQ(sizeof(mjtTrn), sizeof(int)); - EXPECT_EQ(sizeof(mjtDyn), sizeof(int)); - EXPECT_EQ(sizeof(mjtGain), sizeof(int)); - EXPECT_EQ(sizeof(mjtBias), sizeof(int)); - EXPECT_EQ(sizeof(mjtObj), sizeof(int)); - EXPECT_EQ(sizeof(mjtConstraint), sizeof(int)); - EXPECT_EQ(sizeof(mjtConstraintState), sizeof(int)); - EXPECT_EQ(sizeof(mjtSensor), sizeof(int)); - EXPECT_EQ(sizeof(mjtStage), sizeof(int)); - EXPECT_EQ(sizeof(mjtDataType), sizeof(int)); - EXPECT_EQ(sizeof(mjtLRMode), sizeof(int)); - EXPECT_EQ(sizeof(mjtWarning), sizeof(int)); - EXPECT_EQ(sizeof(mjtTimer), sizeof(int)); - EXPECT_EQ(sizeof(mjtGridPos), sizeof(int)); - EXPECT_EQ(sizeof(mjtFramebuffer), sizeof(int)); - EXPECT_EQ(sizeof(mjtFontScale), sizeof(int)); - EXPECT_EQ(sizeof(mjtFont), sizeof(int)); - EXPECT_EQ(sizeof(mjtButton), sizeof(int)); - EXPECT_EQ(sizeof(mjtEvent), sizeof(int)); - EXPECT_EQ(sizeof(mjtItem), sizeof(int)); - EXPECT_EQ(sizeof(mjtCatBit), sizeof(int)); - EXPECT_EQ(sizeof(mjtMouse), sizeof(int)); - EXPECT_EQ(sizeof(mjtPertBit), sizeof(int)); - EXPECT_EQ(sizeof(mjtCamera), sizeof(int)); - EXPECT_EQ(sizeof(mjtLabel), sizeof(int)); - EXPECT_EQ(sizeof(mjtFrame), sizeof(int)); - EXPECT_EQ(sizeof(mjtVisFlag), sizeof(int)); - EXPECT_EQ(sizeof(mjtRndFlag), sizeof(int)); - EXPECT_EQ(sizeof(mjtStereo), sizeof(int)); -} - TEST_F(HeaderTest, MjOptionFields) { mjOption o; std::vector> fields; diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index 999c50ef..a4d3aa22 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -24,6 +24,7 @@ public static class MujocoLib { // ----------------------------------Constants---------------------------------- +public const bool THIRD_PARTY_MUJOCO_MJASSERT_H_ = true; public const bool THIRD_PARTY_MUJOCO_MJDATA_H_ = true; public const bool THIRD_PARTY_MUJOCO_MJEXPORT_H_ = true; public const bool MUJOCO_HELPER_DLL_LOCAL = true;