Skip to content

Commit

Permalink
Make parsing of values syntactically more strict with bad values gene…
Browse files Browse the repository at this point in the history
…rating an error (#244)

Currently, libsdformat silently ignores syntax errors when parsing values. For example, <pose>bad 0 0 0 0 0</pose> or <pose>0.1 0.2 0.3 0.4 0.5</pose> are both bad values for the <pose> tag, but the parsed values are <pose>0 0 0 0 0 0</pose> and <pose>0.1 0.2 0.3 0.4 0.5 0</pose> respectively. With this PR, libsdformat will generate an error for such values.
  • Loading branch information
azeey authored and scpeters committed Jun 9, 2020
1 parent 8d644b9 commit 55029bb
Show file tree
Hide file tree
Showing 9 changed files with 168 additions and 47 deletions.
3 changes: 3 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

### libsdformat 10.0.0 (202X-XX-XX)

1. Make parsing of values syntactically more strict with bad values generating an error
* [Pull request 244](https://github.com/osrf/sdformat/pull/244)

1. Don't install deprecated parser_urdf.hh header file, fix cmake warning about newline file, fix cmake warning about newlines.
* [Pull request 276](https://github.com/osrf/sdformat/pull/276)

Expand Down
6 changes: 6 additions & 0 deletions include/sdf/Types.hh
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ namespace sdf
SDFORMAT_VISIBLE
std::string trim(const char *_in);

/// \brief Trim leading and trailing whitespace from a string.
/// \param[in] _in The string to trim.
/// \return A string containing the trimmed value.
SDFORMAT_VISIBLE
std::string trim(const std::string &_in);

/// \brief check if two values are equal, within a tolerance
/// \param[in] _a the first value
/// \param[in] _b the second value
Expand Down
101 changes: 59 additions & 42 deletions src/Param.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

#include <algorithm>
#include <cctype>
#include <cstdint>
#include <locale>
#include <sstream>
Expand Down Expand Up @@ -271,16 +272,39 @@ std::string Param::GetDefaultAsString() const
return ss.str();
}

//////////////////////////////////////////////////
/// \brief Helper function for Param::ValueFromString
/// \param[in] _input Input string.
/// \param[in] _key Key of the parameter, used for error message.
/// \param[out] _value This will be set with the parsed value.
/// \return True if parsing succeeded.
template <typename T>
bool ParseUsingStringStream(const std::string &_input, const std::string &_key,
ParamPrivate::ParamVariant &_value)
{
StringStreamClassicLocale ss(_input);
T _val;
ss >> _val;
if (ss.fail())
{
sdferr << "Unknown error. Unable to set value [" << _input << " ] for key["
<< _key << "]\n";
return false;
}
_value = _val;
return true;
}

//////////////////////////////////////////////////
bool Param::ValueFromString(const std::string &_value)
{
// Under some circumstances, latin locales (es_ES or pt_BR) will return a
// comma for decimal position instead of a dot, making the conversion
// to fail. See bug #60 for more information. Force to use always C
setlocale(LC_NUMERIC, "C");

std::string tmp(_value);
std::string lowerTmp = lowercase(_value);
std::string trimmed = sdf::trim(_value);
std::string tmp(trimmed);
std::string lowerTmp = lowercase(trimmed);

// "true" and "false" doesn't work properly
if (lowerTmp == "true")
Expand Down Expand Up @@ -335,11 +359,8 @@ bool Param::ValueFromString(const std::string &_value)
}
else if (this->dataPtr->typeName == "uint64_t")
{
StringStreamClassicLocale ss(tmp);
std::uint64_t u64tmp;

ss >> u64tmp;
this->dataPtr->value = u64tmp;
return ParseUsingStringStream<std::uint64_t>(tmp, this->dataPtr->key,
this->dataPtr->value);
}
else if (this->dataPtr->typeName == "unsigned int")
{
Expand All @@ -357,66 +378,62 @@ bool Param::ValueFromString(const std::string &_value)
else if (this->dataPtr->typeName == "sdf::Time" ||
this->dataPtr->typeName == "time")
{
StringStreamClassicLocale ss(tmp);
sdf::Time timetmp;

ss >> timetmp;
this->dataPtr->value = timetmp;
return ParseUsingStringStream<sdf::Time>(tmp, this->dataPtr->key,
this->dataPtr->value);
}
else if (this->dataPtr->typeName == "ignition::math::Color" ||
this->dataPtr->typeName == "color")
{
StringStreamClassicLocale ss(tmp);
ignition::math::Color colortmp;
// The insertion operator (>>) expects 4 values, but the last value (the
// alpha) is optional. We first try to parse assuming the alpha is
// specified. If that fails, we append the default value of alpha to the
// string and try to parse again.
bool result = ParseUsingStringStream<ignition::math::Color>(
tmp, this->dataPtr->key, this->dataPtr->value);

ss >> colortmp;
this->dataPtr->value = colortmp;
if (!result)
{
ignition::math::Color colortmp;
return ParseUsingStringStream<ignition::math::Color>(
tmp + " " + std::to_string(colortmp.A()), this->dataPtr->key,
this->dataPtr->value);
}
else
return true;
}
else if (this->dataPtr->typeName == "ignition::math::Vector2i" ||
this->dataPtr->typeName == "vector2i")
{
StringStreamClassicLocale ss(tmp);
ignition::math::Vector2i vectmp;

ss >> vectmp;
this->dataPtr->value = vectmp;
return ParseUsingStringStream<ignition::math::Vector2i>(
tmp, this->dataPtr->key, this->dataPtr->value);
}
else if (this->dataPtr->typeName == "ignition::math::Vector2d" ||
this->dataPtr->typeName == "vector2d")
{
StringStreamClassicLocale ss(tmp);
ignition::math::Vector2d vectmp;

ss >> vectmp;
this->dataPtr->value = vectmp;
return ParseUsingStringStream<ignition::math::Vector2d>(
tmp, this->dataPtr->key, this->dataPtr->value);
}
else if (this->dataPtr->typeName == "ignition::math::Vector3d" ||
this->dataPtr->typeName == "vector3")
{
StringStreamClassicLocale ss(tmp);
ignition::math::Vector3d vectmp;

ss >> vectmp;
this->dataPtr->value = vectmp;
return ParseUsingStringStream<ignition::math::Vector3d>(
tmp, this->dataPtr->key, this->dataPtr->value);
}
else if (this->dataPtr->typeName == "ignition::math::Pose3d" ||
this->dataPtr->typeName == "pose" ||
this->dataPtr->typeName == "Pose")
{
StringStreamClassicLocale ss(tmp);
ignition::math::Pose3d posetmp;

ss >> posetmp;
this->dataPtr->value = posetmp;
if (!tmp.empty())
{
return ParseUsingStringStream<ignition::math::Pose3d>(
tmp, this->dataPtr->key, this->dataPtr->value);
}
}
else if (this->dataPtr->typeName == "ignition::math::Quaterniond" ||
this->dataPtr->typeName == "quaternion")
{
StringStreamClassicLocale ss(tmp);
ignition::math::Quaterniond quattmp;

ss >> quattmp;
this->dataPtr->value = quattmp;
return ParseUsingStringStream<ignition::math::Quaterniond>(
tmp, this->dataPtr->key, this->dataPtr->value);
}
else
{
Expand Down
12 changes: 8 additions & 4 deletions src/Types.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,17 +53,21 @@ std::vector<std::string> split(const std::string &_str,
//////////////////////////////////////////////////
std::string trim(const char *_in)
{
std::string str(_in);
return sdf::trim(std::string(_in));
}

const size_t strBegin = str.find_first_not_of(" \t");
//////////////////////////////////////////////////
std::string trim(const std::string &_in)
{
const size_t strBegin = _in.find_first_not_of(" \t");
if (strBegin == std::string::npos)
{
return "";
}

const size_t strRange = str.find_last_not_of(" \t") - strBegin + 1;
const size_t strRange = _in.find_last_not_of(" \t") - strBegin + 1;

return str.substr(strBegin, strRange);
return _in.substr(strBegin, strRange);
}

/////////////////////////////////////////////////
Expand Down
61 changes: 61 additions & 0 deletions src/parser_TEST.cc
Original file line number Diff line number Diff line change
Expand Up @@ -472,6 +472,67 @@ TEST(Parser, NameUniqueness)
}
}

/////////////////////////////////////////////////
/// Check that _a contains _b
static bool contains(const std::string &_a, const std::string &_b)
{
return _a.find(_b) != std::string::npos;
}

/////////////////////////////////////////////////
TEST(Parser, SyntaxErrorInValues)
{
std::string pathBase = PROJECT_SOURCE_PATH;
pathBase += "/test/sdf";

// Capture sdferr output
std::stringstream buffer;
auto old = std::cerr.rdbuf(buffer.rdbuf());

#ifdef _WIN32
sdf::Console::Instance()->SetQuiet(false);
#endif

{
std::string path = pathBase +"/bad_syntax_pose.sdf";
sdf::SDFPtr sdf(new sdf::SDF());
sdf::init(sdf);

sdf::readFile(path, sdf);
EXPECT_PRED2(contains, buffer.str(),
"Unable to set value [bad 0 0 0 0 0 ] for key[pose]");
}
{
// clear the contents of the buffer
buffer.str("");
std::string path = pathBase +"/bad_syntax_double.sdf";
sdf::SDFPtr sdf(new sdf::SDF());
sdf::init(sdf);

sdf::readFile(path, sdf);
EXPECT_PRED2(contains, buffer.str(),
"Unable to set value [bad ] for key[linear]");
}
{
// clear the contents of the buffer
buffer.str("");
std::string path = pathBase +"/bad_syntax_vector.sdf";
sdf::SDFPtr sdf(new sdf::SDF());
sdf::init(sdf);

sdf::readFile(path, sdf);
EXPECT_PRED2(contains, buffer.str(),
"Unable to set value [0 1 bad ] for key[gravity]");
}

// Revert cerr rdbug so as to not interfere with other tests
std::cerr.rdbuf(old);
#ifdef _WIN32
sdf::Console::Instance()->SetQuiet(true);
#endif
}

/////////////////////////////////////////////////
/////////////////////////////////////////////////
/// Main
int main(int argc, char **argv)
Expand Down
13 changes: 13 additions & 0 deletions test/sdf/bad_syntax_double.sdf
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" ?>
<sdf version="1.6">
<world name="default">
<model name="robot1">
<link name="link">
<velocity_decay>
<linear>bad</linear>
</velocity_decay>
</link>
</model>
</world>
</sdf>

10 changes: 10 additions & 0 deletions test/sdf/bad_syntax_pose.sdf
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" ?>
<sdf version="1.6">
<world name="default">
<model name="robot1">
<pose>bad 0 0 0 0 0</pose>
<link name="link"/>
</model>
</world>
</sdf>

7 changes: 7 additions & 0 deletions test/sdf/bad_syntax_vector.sdf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?xml version="1.0" ?>
<sdf version="1.6">
<world name="default">
<gravity>0 1 bad</gravity>
</world>
</sdf>

2 changes: 1 addition & 1 deletion test/sdf/box_bad_test.world
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<sdf version="1.6">
<world name="default">
<model name="box">
<pose>0 0.5 0.0 0.0 0.0</pose>
<pose>0 0.5 0.0 0.0 0.0 0.0</pose>
<link>
<inertial>
<mass>0.05</mass>
Expand Down

0 comments on commit 55029bb

Please sign in to comment.