Stricter parsing of XML array attributes.

PiperOrigin-RevId: 451456510
Change-Id: I2556d4365762378261c04d3c9f1942166fbd156e
This commit is contained in:
Alessio Quaglino
2022-05-27 12:50:09 -07:00
committed by Copybara-Service
parent 0741273c86
commit f3453cf87f
4 changed files with 120 additions and 172 deletions
+27 -170
View File
@@ -19,6 +19,7 @@
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <ostream>
#include <sstream>
#include <string>
#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
// the content is returned in "text", the numeric data in "data"
// return true if attribute found, false if not found and not required
int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
double* data, string& text, bool required, bool exact) {
template<typename T>
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);
// check if attribute exists
@@ -567,34 +569,27 @@ int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
// get input stream
istringstream strm(text);
std::string token;
// read numbers
int i;
for (i=0; i<len; i++) {
strm >> data[i];
if (strm.eof()) {
i++;
break;
} else if (strm.bad()) {
int i = 0;
while (!strm.eof() && i < len) {
strm >> token;
istringstream token_strm(token);
token_strm >> data[i++];
if (token_strm.fail() || !token_strm.eof()) {
throw mjXError(elem, "problem reading attribute '%s'", attr);
}
}
strm >> std::ws;
// determine available length
int available = i;
if (strm.good()) {
double dummy;
strm >> dummy;
if (!strm.bad() && !strm.fail()) {
available++;
}
}
// check
if (exact && available<len) {
// check if there is not enough data
if (exact && i < len) {
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);
}
@@ -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
int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
float* data, string& text, bool required, bool exact) {
const char* pstr = elem->Attribute(attr);
// 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;
return ReadAttrCpp(elem, attr, len, data, text, required, exact);
}
@@ -662,55 +616,7 @@ int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
// int version
int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
int* data, string& text, bool required, bool exact) {
const char* pstr = elem->Attribute(attr);
// 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;
return ReadAttrCpp(elem, attr, len, data, text, required, exact);
}
@@ -718,56 +624,7 @@ int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
// byte version
int mjXUtil::ReadAttr(XMLElement* elem, const char* attr, const int len,
mjtByte* data, string& text, bool required, bool exact) {
const char* pstr = elem->Attribute(attr);
// 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;
return ReadAttrCpp(elem, attr, len, data, text, required, exact);
}
+6
View File
@@ -98,6 +98,12 @@ class mjXUtil {
// find value in map, return key ("": not found)
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
static int ReadAttr(tinyxml2::XMLElement* elem, const char* attr, const int len,
double* data, std::string& text,
@@ -22,7 +22,7 @@
</default>
<worldbody>
<body name="body0" pos="-3 0 ">
<body name="body0" pos="-3 0 0">
<joint name="joint0" type="slide" axis="0 0 1"/>
<geom/>
</body>
+86 -1
View File
@@ -128,6 +128,70 @@ TEST_F(UserDataTest, InvalidNUserSensor) {
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 ----------------------------
using RelativeFrameSensorParsingTest = MujocoTest;
@@ -207,7 +271,28 @@ TEST_F(ActuatorTest, IncompleteActlimited) {
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml, error.data(), error.size());
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