Stricter parsing of XML array attributes.
PiperOrigin-RevId: 451456510 Change-Id: I2556d4365762378261c04d3c9f1942166fbd156e
This commit is contained in:
committed by
Copybara-Service
parent
0741273c86
commit
f3453cf87f
+27
-170
@@ -19,6 +19,7 @@
|
|||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
#include <ostream>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@@ -549,8 +550,9 @@ string mjXUtil::FindValue(const mjMap* map, int mapsz, int value) {
|
|||||||
// "len" is the number of floats or doubles to be read
|
// "len" is the number of floats or doubles to be read
|
||||||
// the content is returned in "text", the numeric data in "data"
|
// the content is returned in "text", the numeric data in "data"
|
||||||
// return true if attribute found, false if not found and not required
|
// return true if attribute found, false if not found and not required
|
||||||
int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
|
template<typename T>
|
||||||
double* data, string& text, bool required, bool exact) {
|
int mjXUtil::ReadAttrCpp(XMLElement* elem, const char* attr, const int len,
|
||||||
|
T* data, string& text, bool required, bool exact) {
|
||||||
const char* pstr = elem->Attribute(attr);
|
const char* pstr = elem->Attribute(attr);
|
||||||
|
|
||||||
// check if attribute exists
|
// check if attribute exists
|
||||||
@@ -567,34 +569,27 @@ int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
|
|||||||
|
|
||||||
// get input stream
|
// get input stream
|
||||||
istringstream strm(text);
|
istringstream strm(text);
|
||||||
|
std::string token;
|
||||||
|
|
||||||
// read numbers
|
// read numbers
|
||||||
int i;
|
int i = 0;
|
||||||
for (i=0; i<len; i++) {
|
while (!strm.eof() && i < len) {
|
||||||
strm >> data[i];
|
strm >> token;
|
||||||
if (strm.eof()) {
|
istringstream token_strm(token);
|
||||||
i++;
|
token_strm >> data[i++];
|
||||||
break;
|
if (token_strm.fail() || !token_strm.eof()) {
|
||||||
} else if (strm.bad()) {
|
|
||||||
throw mjXError(elem, "problem reading attribute '%s'", attr);
|
throw mjXError(elem, "problem reading attribute '%s'", attr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
strm >> std::ws;
|
||||||
|
|
||||||
// determine available length
|
// check if there is not enough data
|
||||||
int available = i;
|
if (exact && i < len) {
|
||||||
if (strm.good()) {
|
|
||||||
double dummy;
|
|
||||||
strm >> dummy;
|
|
||||||
if (!strm.bad() && !strm.fail()) {
|
|
||||||
available++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check
|
|
||||||
if (exact && available<len) {
|
|
||||||
throw mjXError(elem, "attribute '%s' does not have enough data", attr);
|
throw mjXError(elem, "attribute '%s' does not have enough data", attr);
|
||||||
}
|
}
|
||||||
if (available>len) {
|
|
||||||
|
// check if there is too much data
|
||||||
|
if (!strm.eof()) {
|
||||||
throw mjXError(elem, "attribute '%s' has too much data", attr);
|
throw mjXError(elem, "attribute '%s' has too much data", attr);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -603,58 +598,17 @@ int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
|
||||||
|
double* data, string& text, bool required, bool exact) {
|
||||||
|
return ReadAttrCpp(elem, attr, len, data, text, required, exact);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// float version
|
// float version
|
||||||
int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
|
int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
|
||||||
float* data, string& text, bool required, bool exact) {
|
float* data, string& text, bool required, bool exact) {
|
||||||
const char* pstr = elem->Attribute(attr);
|
return ReadAttrCpp(elem, attr, len, data, text, required, exact);
|
||||||
|
|
||||||
// check if attribute exists
|
|
||||||
if (!pstr) {
|
|
||||||
if (required) {
|
|
||||||
throw mjXError(elem, "required attribute missing: '%s'", attr);
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert to string, remove trailing white space
|
|
||||||
text = string(pstr);
|
|
||||||
text.erase(text.find_last_not_of(" \t\n\r\f\v") + 1);
|
|
||||||
|
|
||||||
// get input stream
|
|
||||||
istringstream strm(text);
|
|
||||||
|
|
||||||
// read numbers
|
|
||||||
int i;
|
|
||||||
for (i=0; i<len; i++) {
|
|
||||||
strm >> data[i];
|
|
||||||
if (strm.eof()) {
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
} else if (strm.bad()) {
|
|
||||||
throw mjXError(elem, "problem reading attribute '%s'", attr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine available length
|
|
||||||
int available = i;
|
|
||||||
if (strm.good()) {
|
|
||||||
float dummy;
|
|
||||||
strm >> dummy;
|
|
||||||
if (!strm.bad() && !strm.fail()) {
|
|
||||||
available++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check
|
|
||||||
if (exact && available<len) {
|
|
||||||
throw mjXError(elem, "attribute '%s' does not have enough data", attr);
|
|
||||||
}
|
|
||||||
if (available>len) {
|
|
||||||
throw mjXError(elem, "attribute '%s' has too much data", attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -662,55 +616,7 @@ int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
|
|||||||
// int version
|
// int version
|
||||||
int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
|
int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
|
||||||
int* data, string& text, bool required, bool exact) {
|
int* data, string& text, bool required, bool exact) {
|
||||||
const char* pstr = elem->Attribute(attr);
|
return ReadAttrCpp(elem, attr, len, data, text, required, exact);
|
||||||
|
|
||||||
// check if attribute exists
|
|
||||||
if (!pstr) {
|
|
||||||
if (required) {
|
|
||||||
throw mjXError(elem, "required attribute missing: '%s'", attr);
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert to string, remove trailing white space
|
|
||||||
text = string(pstr);
|
|
||||||
text.erase(text.find_last_not_of(" \t\n\r\f\v") + 1);
|
|
||||||
|
|
||||||
// get input stream
|
|
||||||
istringstream strm(text);
|
|
||||||
|
|
||||||
// read numbers
|
|
||||||
int i;
|
|
||||||
for (i=0; i<len; i++) {
|
|
||||||
strm >> data[i];
|
|
||||||
if (strm.eof()) {
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
} else if (strm.bad()) {
|
|
||||||
throw mjXError(elem, "problem reading attribute '%s'", attr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine available length
|
|
||||||
int available = i;
|
|
||||||
if (strm.good()) {
|
|
||||||
mjtByte dummy;
|
|
||||||
strm >> dummy;
|
|
||||||
if (!strm.bad() && !strm.fail()) {
|
|
||||||
available++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check
|
|
||||||
if (exact && available<len) {
|
|
||||||
throw mjXError(elem, "attribute '%s' does not have enough data", attr);
|
|
||||||
}
|
|
||||||
if (available>len) {
|
|
||||||
throw mjXError(elem, "attribute '%s' has too much data", attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -718,56 +624,7 @@ int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
|
|||||||
// byte version
|
// byte version
|
||||||
int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
|
int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
|
||||||
mjtByte* data, string& text, bool required, bool exact) {
|
mjtByte* data, string& text, bool required, bool exact) {
|
||||||
const char* pstr = elem->Attribute(attr);
|
return ReadAttrCpp(elem, attr, len, data, text, required, exact);
|
||||||
|
|
||||||
// check if attribute exists
|
|
||||||
if (!pstr) {
|
|
||||||
if (required) {
|
|
||||||
throw mjXError(elem, "required attribute missing: '%s'", attr);
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert to string, remove trailing white space
|
|
||||||
text = string(pstr);
|
|
||||||
text.erase(text.find_last_not_of(" \t\n\r\f\v") + 1);
|
|
||||||
|
|
||||||
// get input stream
|
|
||||||
istringstream strm(text);
|
|
||||||
|
|
||||||
// read numbers
|
|
||||||
int i, tmp;
|
|
||||||
for (i=0; i<len; i++) {
|
|
||||||
strm >> tmp;
|
|
||||||
data[i] = (mjtByte)(tmp & 0xFF);
|
|
||||||
if (strm.eof()) {
|
|
||||||
i++;
|
|
||||||
break;
|
|
||||||
} else if (strm.bad()) {
|
|
||||||
throw mjXError(elem, "problem reading attribute '%s'", attr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// determine available length
|
|
||||||
int available = i;
|
|
||||||
if (strm.good()) {
|
|
||||||
mjtByte dummy;
|
|
||||||
strm >> dummy;
|
|
||||||
if (!strm.bad() && !strm.fail()) {
|
|
||||||
available++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// check
|
|
||||||
if (exact && available<len) {
|
|
||||||
throw mjXError(elem, "attribute '%s' does not have enough data", attr);
|
|
||||||
}
|
|
||||||
if (available>len) {
|
|
||||||
throw mjXError(elem, "attribute '%s' has too much data", attr);
|
|
||||||
}
|
|
||||||
|
|
||||||
return i;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -98,6 +98,12 @@ class mjXUtil {
|
|||||||
// find value in map, return key ("": not found)
|
// find value in map, return key ("": not found)
|
||||||
static std::string FindValue(const mjMap* map, int mapsz, int value);
|
static std::string FindValue(const mjMap* map, int mapsz, int value);
|
||||||
|
|
||||||
|
// read any type from attribute, return number read
|
||||||
|
template<typename T>
|
||||||
|
static int ReadAttrCpp(tinyxml2::XMLElement* elem, const char* attr, const int len,
|
||||||
|
T* data, std::string& text,
|
||||||
|
bool required = false, bool exact = true);
|
||||||
|
|
||||||
// read DOUBLE array from attribute, return number read
|
// read DOUBLE array from attribute, return number read
|
||||||
static int ReadAttr(tinyxml2::XMLElement* elem, const char* attr, const int len,
|
static int ReadAttr(tinyxml2::XMLElement* elem, const char* attr, const int len,
|
||||||
double* data, std::string& text,
|
double* data, std::string& text,
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
</default>
|
</default>
|
||||||
|
|
||||||
<worldbody>
|
<worldbody>
|
||||||
<body name="body0" pos="-3 0 ">
|
<body name="body0" pos="-3 0 0">
|
||||||
<joint name="joint0" type="slide" axis="0 0 1"/>
|
<joint name="joint0" type="slide" axis="0 0 1"/>
|
||||||
<geom/>
|
<geom/>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -128,6 +128,70 @@ TEST_F(UserDataTest, InvalidNUserSensor) {
|
|||||||
EXPECT_THAT(error.data(), HasSubstr("nuser_sensor"));
|
EXPECT_THAT(error.data(), HasSubstr("nuser_sensor"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(UserDataTest, InvalidArrayElement) {
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body>
|
||||||
|
<geom size="1" axisangle="1.0 0.0 0.0 [[1]]"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
std::array<char, 1024> error;
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, IsNull());
|
||||||
|
EXPECT_THAT(error.data(), HasSubstr("problem reading attribute 'axisangle'"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UserDataTest, InvalidArrayLength) {
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body>
|
||||||
|
<geom size="1" axisangle="1 0 0 0 asd"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
std::array<char, 1024> error;
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, IsNull());
|
||||||
|
EXPECT_THAT(error.data(), HasSubstr("has too much data"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UserDataTest, InvalidNumber) {
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body>
|
||||||
|
<geom size="1" axisangle="1 0.1.2.3"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
std::array<char, 1024> error;
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, IsNull());
|
||||||
|
EXPECT_THAT(error.data(), HasSubstr("problem reading attribute"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(UserDataTest, AllowsSpaces) {
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body>
|
||||||
|
<geom size="1" axisangle="1 0 0 0 "/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
std::array<char, 1024> error;
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, Not(IsNull()));
|
||||||
|
mj_deleteModel(model);
|
||||||
|
}
|
||||||
|
|
||||||
// ------------- test relative frame sensor parsing ----------------------------
|
// ------------- test relative frame sensor parsing ----------------------------
|
||||||
|
|
||||||
using RelativeFrameSensorParsingTest = MujocoTest;
|
using RelativeFrameSensorParsingTest = MujocoTest;
|
||||||
@@ -207,7 +271,28 @@ TEST_F(ActuatorTest, IncompleteActlimited) {
|
|||||||
std::array<char, 1024> error;
|
std::array<char, 1024> error;
|
||||||
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
ASSERT_THAT(model, IsNull());
|
ASSERT_THAT(model, IsNull());
|
||||||
EXPECT_THAT(error.data(), HasSubstr("actrange"));
|
EXPECT_THAT(error.data(), HasSubstr("attribute 'actrange' does not have enough data"));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ActuatorTest, ReadsByte) {
|
||||||
|
static constexpr char xml[] = R"(
|
||||||
|
<mujoco>
|
||||||
|
<worldbody>
|
||||||
|
<body>
|
||||||
|
<joint name="hinge"/>
|
||||||
|
<geom size="1"/>
|
||||||
|
</body>
|
||||||
|
</worldbody>
|
||||||
|
<actuator>
|
||||||
|
<general joint="hinge" dyntype="filter" actlimited="true" actrange="-1 1"/>
|
||||||
|
</actuator>
|
||||||
|
</mujoco>
|
||||||
|
)";
|
||||||
|
std::array<char, 1024> error;
|
||||||
|
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
|
||||||
|
ASSERT_THAT(model, Not(IsNull()));
|
||||||
|
EXPECT_EQ(*(model->actuator_actlimited), (mjtByte)(1 & 0xFF));
|
||||||
|
mj_deleteModel(model);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|||||||
Reference in New Issue
Block a user