Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 42 additions & 4 deletions args.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -3323,19 +3323,57 @@ namespace args
*/
struct ValueReader
{
private:
template <typename T>
static typename std::enable_if<std::is_integral<T>::value && std::is_unsigned<T>::value, bool>::type
HasUnsignedNegativeSign(const std::string &value)
{
const auto firstNonSpace = std::find_if_not(value.begin(), value.end(), [](char c)
{
return std::isspace(static_cast<unsigned char>(c)) != 0;
});

return firstNonSpace != value.end() && *firstNonSpace == '-';
}

template <typename T>
static typename std::enable_if<!std::is_integral<T>::value || !std::is_unsigned<T>::value, bool>::type
HasUnsignedNegativeSign(const std::string &)
{
return false;
}

public:
template <typename T>
typename std::enable_if<!std::is_assignable<T, std::string>::value, bool>::type
operator ()(const std::string &name, const std::string &value, T &destination)
{
std::istringstream ss(value);
bool failed = !(ss >> destination);
bool failed = HasUnsignedNegativeSign<T>(value);

std::istringstream ss(value);
if (!failed)
{
ss >> std::ws;
ss >> destination;
if (ss.fail())
{
failed = true;
}
else
{
// If we can read a non-whitespace character after parsing, the input had junk.
char extra;
if (ss >> extra)
{
failed = true;
}
else if (!ss.eof())
{
failed = true;
}
}
}

if (ss.rdbuf()->in_avail() > 0 || failed)
if (failed)
{
#ifdef ARGS_NOEXCEPT
(void)name;
Expand Down
25 changes: 25 additions & 0 deletions test.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,16 @@ TEST_CASE("Invalid argument parsing throws parsing exceptions", "[args]")
REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"--foo", "7e4"}), args::ParseError);
}

TEST_CASE("Negative values are rejected for unsigned flags", "[args]")
{
args::ArgumentParser parser("This is a test program.", "This goes after the options.");
args::ValueFlag<unsigned int> uid(parser, "UID", "numeric id", {'u', "uid"});

REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"--uid", "-1"}), args::ParseError);
REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"--uid=-1"}), args::ParseError);
REQUIRE_THROWS_AS(parser.ParseArgs(std::vector<std::string>{"--uid", "123abc"}), args::ParseError);
}

TEST_CASE("Argument flag lists work as expected", "[args]")
{
args::ArgumentParser parser("This is a test program.", "This goes after the options.");
Expand Down Expand Up @@ -1424,6 +1434,21 @@ TEST_CASE("Noexcept mode works as expected", "[args]")
REQUIRE(parser.GetError() == argstest::Error::None);
}

TEST_CASE("Negative values are rejected for unsigned flags in noexcept mode", "[args]")
{
argstest::ArgumentParser parser("Test command");
argstest::ValueFlag<unsigned int> uid(parser, "UID", "numeric id", {'u', "uid"});

parser.ParseArgs(std::vector<std::string>{"--uid", "-1"});
REQUIRE(parser.GetError() == argstest::Error::Parse);

parser.ParseArgs(std::vector<std::string>{"--uid=-1"});
REQUIRE(parser.GetError() == argstest::Error::Parse);

parser.ParseArgs(std::vector<std::string>{"--uid", "123abc"});
REQUIRE(parser.GetError() == argstest::Error::Parse);
}

TEST_CASE("Required flags work as expected in noexcept mode", "[args]")
{
argstest::ArgumentParser parser1("Test command");
Expand Down