Refactor compiler warning handling.

Compiler warnings are now accumulated in a vector of strings within the mjSpec object. New API functions `mjs_numWarnings` and `mjs_getWarning` are added to access these warnings. The compiler's log handler now chains warnings to the global log handler, ensuring they are still displayed immediately. Call sites in `mj_loadXML`, `mj_compile`, and the Python and WASM bindings have been updated to use the new warning API.

PiperOrigin-RevId: 933361650
Change-Id: I47cab98a460c57b0898c0a1a43fce2a5b9648eb1
This commit is contained in:
Yuval Tassa
2026-06-16 16:27:52 -07:00
committed by Copybara-Service
parent 55c6332f20
commit 6f8bb5ef55
25 changed files with 640 additions and 89 deletions
+48 -9
View File
@@ -115,15 +115,20 @@ EMSCRIPTEN_DECLARE_VAL_TYPE(StringOrNull);
mju_error("Invalid argument: %s is undefined", #val); \
}
void ThrowMujocoErrorToJS(const char* msg) {
// Get a handle to the JS global Error constructor function, create a new
// object instance and then throw the object as an exception using the
// val::throw_() helper function.
val(val::global("Error").new_(val("MuJoCo Error: " + std::string(msg))))
.throw_();
void ThrowMujocoErrorToJS(const mjLogMessage* msg) {
if (msg->level == mjLOG_ERROR) {
std::string message = msg->subject;
if (msg->func) {
message = std::string(msg->func) + ": " + msg->subject;
}
// Get a handle to the JS global Error constructor function, create a new
// object instance and then throw the object as an exception using the
// val::throw_() helper function.
val(val::global("Error").new_(val("MuJoCo Error: " + message))).throw_();
}
}
__attribute__((constructor)) void InitMuJoCoErrorHandler() {
mju_user_error = ThrowMujocoErrorToJS;
mju_setLogHandler(ThrowMujocoErrorToJS);
}
// Generates a descriptive error message for when a key lookup fails.
@@ -8730,20 +8735,44 @@ std::unique_ptr<MjSpec> parseXMLString_wrapper(const std::string &xml) {
std::unique_ptr<MjModel> mj_compile_wrapper_1(const MjSpec& spec) {
mjSpec* spec_ptr = spec.get();
// suppress stderr playback: warnings are raised via console.warn() below
mjfLogHandler prev = _mjPRIVATE_setTlsLogHandler([](const mjLogMessage*) {});
mjModel* model = mj_compile(spec_ptr, nullptr);
if (!model || mjs_isWarning(spec_ptr)) {
_mjPRIVATE_setTlsLogHandler(prev);
if (!model) {
mju_error("%s", mjs_getError(spec_ptr));
}
int num_warnings = mjs_numWarnings(spec_ptr);
if (num_warnings > 0) {
for (int i = 0; i < num_warnings; ++i) {
val::global("console").call<void>(
"warn",
val("MuJoCo Warning: " + std::string(mjs_getWarning(spec_ptr, i))));
}
}
return std::unique_ptr<MjModel>(new MjModel(model));
}
std::unique_ptr<MjModel> mj_compile_wrapper_2(const MjSpec& spec, const MjVFS& vfs) {
mjSpec* spec_ptr = spec.get();
mjVFS* vfs_ptr = vfs.get();
// suppress stderr playback: warnings are raised via console.warn() below
mjfLogHandler prev = _mjPRIVATE_setTlsLogHandler([](const mjLogMessage*) {});
mjModel* model = mj_compile(spec_ptr, vfs_ptr);
if (!model || mjs_isWarning(spec_ptr)) {
_mjPRIVATE_setTlsLogHandler(prev);
if (!model) {
mju_error("%s", mjs_getError(spec_ptr));
}
int num_warnings = mjs_numWarnings(spec_ptr);
if (num_warnings > 0) {
for (int i = 0; i < num_warnings; ++i) {
val::global("console").call<void>(
"warn",
val("MuJoCo Warning: " + std::string(mjs_getWarning(spec_ptr, i))));
}
}
return std::unique_ptr<MjModel>(new MjModel(model));
}
@@ -10101,6 +10130,10 @@ std::optional<MjsDefault> mjs_getSpecDefault_wrapper(const MjSpec& s) {
return MjsDefault(result);
}
std::string mjs_getWarning_wrapper(const MjSpec& spec, int index) {
return std::string(mjs_getWarning(spec.get(), index));
}
std::optional<MjsWrap> mjs_getWrap_wrapper(const MjsTendon& tendonspec, int i) {
mjsWrap* result = mjs_getWrap(tendonspec.get(), i);
if (result == nullptr) {
@@ -10178,6 +10211,10 @@ std::optional<MjsElement> mjs_nextElement_wrapper(const MjSpec& s, const MjsElem
return MjsElement(result);
}
int mjs_numWarnings_wrapper(const MjSpec& spec) {
return mjs_numWarnings(spec.get());
}
std::string mjs_resolveOrientation_wrapper(const val& quat, mjtByte degree, const String& sequence, const MjsOrientation& orientation) {
CHECK_VAL(sequence);
UNPACK_VALUE(double, quat);
@@ -13742,6 +13779,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
function("mjs_getParent", &mjs_getParent_wrapper);
function("mjs_getSpec", &mjs_getSpec_wrapper);
function("mjs_getSpecDefault", &mjs_getSpecDefault_wrapper);
function("mjs_getWarning", &mjs_getWarning_wrapper);
function("mjs_getWrap", &mjs_getWrap_wrapper);
function("mjs_getWrapCoef", &mjs_getWrapCoef_wrapper);
function("mjs_getWrapDivisor", &mjs_getWrapDivisor_wrapper);
@@ -13753,6 +13791,7 @@ EMSCRIPTEN_BINDINGS(mujoco_bindings) {
function("mjs_makeMesh", &mjs_makeMesh_wrapper);
function("mjs_nextChild", &mjs_nextChild_wrapper);
function("mjs_nextElement", &mjs_nextElement_wrapper);
function("mjs_numWarnings", &mjs_numWarnings_wrapper);
function("mjs_resolveOrientation", &mjs_resolveOrientation_wrapper);
function("mjs_sensorDim", &mjs_sensorDim_wrapper);
function("mjs_setDeepCopy", &mjs_setDeepCopy_wrapper);