Fix memory leak in mjCModel::Compile.

This is a very subtle leak caused interaction between longjmp and compiler optimisation. Specifically, at the point where the setjmp takes places, these pointers have never been reassigned from its nullptr initialization. Without the volatile keyword, the compiler is free to assume that these pointers remain nullptr when the setjmp returns, and therefore to pass nullptr directly to the mj_deleteModel and mj_deleteData calls in the subsequent catch block, without ever reading the actual pointer values.

PiperOrigin-RevId: 466649447
Change-Id: I51e82ec8b566deb144221e11a99d5124a8267de4
This commit is contained in:
Saran Tunyasuvunakool
2022-08-10 05:11:15 -07:00
committed by Copybara-Service
parent c2aff3a9f2
commit e3ded23c66
2 changed files with 316 additions and 305 deletions
+97 -88
View File
@@ -2271,9 +2271,16 @@ static void warninghandler(const char* msg) {
// compiler // compiler
mjModel* mjCModel::Compile(const mjVFS* vfs) { mjModel* mjCModel::Compile(const mjVFS* vfs) {
unsigned int i; // The volatile keyword is necessary to prevent a possible memory leak due to
mjModel* m = 0; // an interaction between longjmp and compiler optimization. Specifically, at
mjData* data = 0; // the point where the setjmp takes places, these pointers have never been
// reassigned from their nullptr initialization. Without the volatile keyword,
// the compiler is free to assume that these pointers remain nullptr when the
// setjmp returns, and therefore to pass nullptr directly to the
// mj_deleteModel and mj_deleteData calls in the subsequent catch block,
// without ever reading the actual pointer values.
mjModel* volatile m = nullptr;
mjData* volatile data = nullptr;
// save error and warning handlers // save error and warning handlers
void (*save_error)(const char*) = _mjPRIVATE__get_tls_error_fn(); void (*save_error)(const char*) = _mjPRIVATE__get_tls_error_fn();
@@ -2289,12 +2296,38 @@ mjModel* mjCModel::Compile(const mjVFS* vfs) {
// init random number generator, to make textures reproducible // init random number generator, to make textures reproducible
srand(123); srand(123);
// guard for exceptions
try { try {
if (setjmp(error_jmp_buf) != 0) { if (setjmp(error_jmp_buf) != 0) {
// TryCompile resulted in an mju_error which was converted to a longjmp.
throw mjCError(0, "engine error: %s", errortext); throw mjCError(0, "engine error: %s", errortext);
} }
TryCompile(const_cast<mjModel**>(&m), const_cast<mjData**>(&data), vfs);
} catch (mjCError err) {
// deallocate everything allocated in Compile
mj_deleteModel(m);
mj_deleteData(data);
mjCBody* world = bodies[0];
Clear();
bodies.push_back(world);
// save error info
errInfo = err;
// restore handler, return 0
_mjPRIVATE__set_tls_error_fn(save_error);
_mjPRIVATE__set_tls_warning_fn(save_warning);
return nullptr;
}
// restore error handler, mark as compiled, return mjModel
_mjPRIVATE__set_tls_error_fn(save_error);
_mjPRIVATE__set_tls_warning_fn(save_warning);
compiled = true;
return m;
}
void mjCModel::TryCompile(mjModel** m, mjData** data, const mjVFS* vfs) {
// check if nan test works // check if nan test works
double test = mjNAN; double test = mjNAN;
if (mjuu_defined(test)) { if (mjuu_defined(test)) {
@@ -2326,7 +2359,7 @@ mjModel* mjCModel::Compile(const mjVFS* vfs) {
} }
// add missing keyframes // add missing keyframes
for (i=keys.size(); i<nkey; i++) { for (int i=keys.size(); i<nkey; i++) {
AddKey(); AddKey();
} }
@@ -2361,7 +2394,7 @@ mjModel* mjCModel::Compile(const mjVFS* vfs) {
IndexAssets(); IndexAssets();
// mark meshes that need convex hull // mark meshes that need convex hull
for (i=0; i<geoms.size(); i++) { for (int i=0; i<geoms.size(); i++) {
if (geoms[i]->meshid>=0 && geoms[i]->type==mjGEOM_MESH && if (geoms[i]->meshid>=0 && geoms[i]->type==mjGEOM_MESH &&
(geoms[i]->contype || geoms[i]->conaffinity)) { (geoms[i]->contype || geoms[i]->conaffinity)) {
meshes[geoms[i]->meshid]->needhull = true; meshes[geoms[i]->meshid]->needhull = true;
@@ -2369,82 +2402,82 @@ mjModel* mjCModel::Compile(const mjVFS* vfs) {
} }
// compile meshes (needed for geom compilation) // compile meshes (needed for geom compilation)
for (i=0; i<meshes.size(); i++) { for (int i=0; i<meshes.size(); i++) {
meshes[i]->Compile(vfs); meshes[i]->Compile(vfs);
} }
// automatically set nuser fields // automatically set nuser fields
if (nuser_body == -1) { if (nuser_body == -1) {
nuser_body = 0; nuser_body = 0;
for (i=0; i<bodies.size(); i++) { for (int i=0; i<bodies.size(); i++) {
nuser_body = mjMAX(nuser_body, bodies[i]->userdata.size()); nuser_body = mjMAX(nuser_body, bodies[i]->userdata.size());
} }
} }
if (nuser_jnt == -1) { if (nuser_jnt == -1) {
nuser_jnt = 0; nuser_jnt = 0;
for (i=0; i<joints.size(); i++) { for (int i=0; i<joints.size(); i++) {
nuser_jnt = mjMAX(nuser_jnt, joints[i]->userdata.size()); nuser_jnt = mjMAX(nuser_jnt, joints[i]->userdata.size());
} }
} }
if (nuser_geom == -1) { if (nuser_geom == -1) {
nuser_geom = 0; nuser_geom = 0;
for (i=0; i<geoms.size(); i++) { for (int i=0; i<geoms.size(); i++) {
nuser_geom = mjMAX(nuser_geom, geoms[i]->userdata.size()); nuser_geom = mjMAX(nuser_geom, geoms[i]->userdata.size());
} }
} }
if (nuser_site == -1) { if (nuser_site == -1) {
nuser_site = 0; nuser_site = 0;
for (i=0; i<sites.size(); i++) { for (int i=0; i<sites.size(); i++) {
nuser_site = mjMAX(nuser_site, sites[i]->userdata.size()); nuser_site = mjMAX(nuser_site, sites[i]->userdata.size());
} }
} }
if (nuser_cam == -1) { if (nuser_cam == -1) {
nuser_cam = 0; nuser_cam = 0;
for (i=0; i<cameras.size(); i++) { for (int i=0; i<cameras.size(); i++) {
nuser_cam = mjMAX(nuser_cam, cameras[i]->userdata.size()); nuser_cam = mjMAX(nuser_cam, cameras[i]->userdata.size());
} }
} }
if (nuser_tendon == -1) { if (nuser_tendon == -1) {
nuser_tendon = 0; nuser_tendon = 0;
for (i=0; i<tendons.size(); i++) { for (int i=0; i<tendons.size(); i++) {
nuser_tendon = mjMAX(nuser_tendon, tendons[i]->userdata.size()); nuser_tendon = mjMAX(nuser_tendon, tendons[i]->userdata.size());
} }
} }
if (nuser_actuator == -1) { if (nuser_actuator == -1) {
nuser_actuator = 0; nuser_actuator = 0;
for (i=0; i<actuators.size(); i++) { for (int i=0; i<actuators.size(); i++) {
nuser_actuator = mjMAX(nuser_actuator, actuators[i]->userdata.size()); nuser_actuator = mjMAX(nuser_actuator, actuators[i]->userdata.size());
} }
} }
if (nuser_sensor == -1) { if (nuser_sensor == -1) {
nuser_sensor = 0; nuser_sensor = 0;
for (i=0; i<sensors.size(); i++) { for (int i=0; i<sensors.size(); i++) {
nuser_sensor = mjMAX(nuser_sensor, sensors[i]->userdata.size()); nuser_sensor = mjMAX(nuser_sensor, sensors[i]->userdata.size());
} }
} }
// compile objects in kinematic tree // compile objects in kinematic tree
for (i=0; i<bodies.size(); i++) { for (int i=0; i<bodies.size(); i++) {
bodies[i]->Compile(); // also compiles joints, geoms, sites, cameras, lights bodies[i]->Compile(); // also compiles joints, geoms, sites, cameras, lights
} }
// compile all other objects except for keyframes // compile all other objects except for keyframes
for (i=0; i<skins.size(); i++) skins[i]->Compile(vfs); for (int i=0; i<skins.size(); i++) skins[i]->Compile(vfs);
for (i=0; i<hfields.size(); i++) hfields[i]->Compile(vfs); for (int i=0; i<hfields.size(); i++) hfields[i]->Compile(vfs);
for (i=0; i<textures.size(); i++) textures[i]->Compile(vfs); for (int i=0; i<textures.size(); i++) textures[i]->Compile(vfs);
for (i=0; i<materials.size(); i++) materials[i]->Compile(); for (int i=0; i<materials.size(); i++) materials[i]->Compile();
for (i=0; i<pairs.size(); i++) pairs[i]->Compile(); for (int i=0; i<pairs.size(); i++) pairs[i]->Compile();
for (i=0; i<excludes.size(); i++) excludes[i]->Compile(); for (int i=0; i<excludes.size(); i++) excludes[i]->Compile();
for (i=0; i<equalities.size(); i++) equalities[i]->Compile(); for (int i=0; i<equalities.size(); i++) equalities[i]->Compile();
for (i=0; i<tendons.size(); i++) tendons[i]->Compile(); for (int i=0; i<tendons.size(); i++) tendons[i]->Compile();
for (i=0; i<actuators.size(); i++) actuators[i]->Compile(); for (int i=0; i<actuators.size(); i++) actuators[i]->Compile();
for (i=0; i<sensors.size(); i++) sensors[i]->Compile(); for (int i=0; i<sensors.size(); i++) sensors[i]->Compile();
for (i=0; i<numerics.size(); i++) numerics[i]->Compile(); for (int i=0; i<numerics.size(); i++) numerics[i]->Compile();
for (i=0; i<texts.size(); i++) texts[i]->Compile(); for (int i=0; i<texts.size(); i++) texts[i]->Compile();
for (i=0; i<tuples.size(); i++) tuples[i]->Compile(); for (int i=0; i<tuples.size(); i++) tuples[i]->Compile();
// compile defaults: to enforce userdata length for writer // compile defaults: to enforce userdata length for writer
for (i=0; i<defaults.size(); i++) { for (int i=0; i<defaults.size(); i++) {
defaults[i]->Compile(this); defaults[i]->Compile(this);
} }
@@ -2464,7 +2497,7 @@ mjModel* mjCModel::Compile(const mjVFS* vfs) {
} }
// set nmocap and body.mocapid // set nmocap and body.mocapid
for (i=0; i<bodies.size(); i++) { for (int i=0; i<bodies.size(); i++) {
if (bodies[i]->mocap) { if (bodies[i]->mocap) {
bodies[i]->mocapid = nmocap; bodies[i]->mocapid = nmocap;
nmocap++; nmocap++;
@@ -2474,7 +2507,7 @@ mjModel* mjCModel::Compile(const mjVFS* vfs) {
} }
// check body mass and inertia // check body mass and inertia
for (i=1; i<bodies.size(); i++) { for (int i=1; i<bodies.size(); i++) {
mjCBody* b = bodies[i]; mjCBody* b = bodies[i];
// find moving body with small mass or inertia // find moving body with small mass or inertia
@@ -2504,7 +2537,7 @@ mjModel* mjCModel::Compile(const mjVFS* vfs) {
} }
// create low-level model // create low-level model
m = mj_makeModel(nq, nv, nu, na, nbody, njnt, ngeom, nsite, ncam, nlight, *m = mj_makeModel(nq, nv, nu, na, nbody, njnt, ngeom, nsite, ncam, nlight,
nmesh, nmeshvert, nmeshtexvert, nmeshface, nmeshgraph, nmesh, nmeshvert, nmeshtexvert, nmeshface, nmeshgraph,
nskin, nskinvert, nskintexvert, nskinface, nskinbone, nskinbonevert, nskin, nskinvert, nskintexvert, nskinface, nskinbone, nskinbonevert,
nhfield, nhfielddata, ntex, ntexdata, nmat, npair, nexclude, nhfield, nhfielddata, ntex, ntexdata, nmat, npair, nexclude,
@@ -2513,109 +2546,85 @@ mjModel* mjCModel::Compile(const mjVFS* vfs) {
ntuple, ntupledata, nkey, nmocap, ntuple, ntupledata, nkey, nmocap,
nuser_body, nuser_jnt, nuser_geom, nuser_site, nuser_cam, nuser_body, nuser_jnt, nuser_geom, nuser_site, nuser_cam,
nuser_tendon, nuser_actuator, nuser_sensor, nnames); nuser_tendon, nuser_actuator, nuser_sensor, nnames);
if (!m) { if (!*m) {
throw mjCError(0, "could not create mjModel"); throw mjCError(0, "could not create mjModel");
} }
// copy everything into low-level model // copy everything into low-level model
m->opt = option; (*m)->opt = option;
m->vis = visual; (*m)->vis = visual;
CopyNames(m); CopyNames(*m);
CopyTree(m); CopyTree(*m);
// keyframe compilation needs access to nq, nv, na, nmocap, qpos0 // keyframe compilation needs access to nq, nv, na, nmocap, qpos0
for (i=0; i<keys.size(); i++) { for (int i=0; i<keys.size(); i++) {
keys[i]->Compile(m); keys[i]->Compile(*m);
} }
// copy objects outsite kinematic tree (including keyframes) // copy objects outsite kinematic tree (including keyframes)
CopyObjects(m); CopyObjects(*m);
// scale mass // scale mass
if (settotalmass>0) { if (settotalmass>0) {
mj_setTotalmass(m, settotalmass); mj_setTotalmass(*m, settotalmass);
} }
// set stack size: user-specified or conservative heuristic // set stack size: user-specified or conservative heuristic
if (nstack>0) { if (nstack>0) {
m->nstack = nstack; (*m)->nstack = nstack;
} else { } else {
m->nstack = mjMAX(1000, (*m)->nstack = mjMAX(
5*(m->njmax + m->neq + m->nv)*(m->njmax + m->neq + m->nv) + 1000,
20*(m->nq + m->nv + m->nu + m->na + m->nbody + m->njnt + 5*((*m)->njmax + (*m)->neq + (*m)->nv)*((*m)->njmax + (*m)->neq + (*m)->nv) +
m->ngeom + m->nsite + m->neq + m->ntendon + m->nwrap)); 20*((*m)->nq + (*m)->nv + (*m)->nu + (*m)->na + (*m)->nbody + (*m)->njnt +
(*m)->ngeom + (*m)->nsite + (*m)->neq + (*m)->ntendon + (*m)->nwrap));
} }
// create data // create data
data = mj_makeData(m); *data = mj_makeData(*m);
if (!data) { if (!data) {
throw mjCError(0, "could not create mjData"); throw mjCError(0, "could not create mjData");
} }
// normalize keyframe quaternions // normalize keyframe quaternions
for (i=0; i<m->nkey; i++) { for (int i=0; i<(*m)->nkey; i++) {
mj_normalizeQuat(m, m->key_qpos+i*m->nq); mj_normalizeQuat(*m, (*m)->key_qpos+i*(*m)->nq);
} }
// set constant fields // set constant fields
mj_setConst(m, data); mj_setConst(*m, *data);
// automatic spring-damper adjustment // automatic spring-damper adjustment
AutoSpringDamper(m); AutoSpringDamper(*m);
// actuator lengthrange computation // actuator lengthrange computation
LengthRange(m, data); LengthRange(*m, *data);
// override model statistics if defined by user // override model statistics if defined by user
if (mjuu_defined(extent)) m->stat.extent = (mjtNum)extent; if (mjuu_defined(extent)) (*m)->stat.extent = (mjtNum)extent;
if (mjuu_defined(meaninertia)) m->stat.meaninertia = (mjtNum)meaninertia; if (mjuu_defined(meaninertia)) (*m)->stat.meaninertia = (mjtNum)meaninertia;
if (mjuu_defined(meanmass)) m->stat.meanmass = (mjtNum)meanmass; if (mjuu_defined(meanmass)) (*m)->stat.meanmass = (mjtNum)meanmass;
if (mjuu_defined(meansize)) m->stat.meansize = (mjtNum)meansize; if (mjuu_defined(meansize)) (*m)->stat.meansize = (mjtNum)meansize;
if (mjuu_defined(center[0])) copyvec(m->stat.center, center, 3); if (mjuu_defined(center[0])) copyvec((*m)->stat.center, center, 3);
// assert that model has valid references // assert that model has valid references
const char* validationerr = mj_validateReferences(m); const char* validationerr = mj_validateReferences(*m);
if (validationerr) { // SHOULD NOT OCCUR if (validationerr) { // SHOULD NOT OCCUR
throw mjCError(0, validationerr); throw mjCError(0, validationerr);
} }
// test forward simulation // test forward simulation
mj_resetData(m, data); mj_resetData(*m, *data);
mj_step(m, data); mj_step(*m, *data);
// delete data // delete data
mj_deleteData(data); mj_deleteData(*data);
data = NULL; data = nullptr;
// pass warning back // pass warning back
if (warningtext[0]) { if (warningtext[0]) {
mju::strcpy_arr(errInfo.message, warningtext); mju::strcpy_arr(errInfo.message, warningtext);
errInfo.warning = true; errInfo.warning = true;
} }
}
// handle mjCError exceptions
catch (mjCError err) {
// deallocate everything allocated in Compile
mj_deleteModel(m);
mj_deleteData(data);
mjCBody* world = bodies[0];
Clear();
bodies.push_back(world);
// save error info
errInfo = err;
// restore handler, return 0
_mjPRIVATE__set_tls_error_fn(save_error);
_mjPRIVATE__set_tls_warning_fn(save_warning);
return nullptr;
}
// restore error handler, mark as compiled, return mjModel
_mjPRIVATE__set_tls_error_fn(save_error);
_mjPRIVATE__set_tls_warning_fn(save_warning);
compiled = true;
return m;
} }
+2
View File
@@ -153,6 +153,8 @@ class mjCModel {
int nuser_sensor; // number of mjtNums in sensor_user int nuser_sensor; // number of mjtNums in sensor_user
private: private:
void TryCompile(mjModel** m, mjData** data, const mjVFS* vfs);
void Clear(void); // clear objects allocated by Compile void Clear(void); // clear objects allocated by Compile
template <class T> // add object of any type template <class T> // add object of any type